diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py
index a848504..e980c45 100644
--- a/src/submissions/backend/db/models/__init__.py
+++ b/src/submissions/backend/db/models/__init__.py
@@ -603,7 +603,7 @@ class BaseClass(Base):
pyd = getattr(pydant, pyd_model_name)
except AttributeError:
raise AttributeError(f"Could not get pydantic class {pyd_model_name}")
- return pyd(**self.details_dict())
+ return pyd(**self.details_dict(**kwargs))
def show_details(self, obj):
logger.debug("Show Details")
diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py
index edeba6d..5f7cb54 100644
--- a/src/submissions/backend/db/models/kits.py
+++ b/src/submissions/backend/db/models/kits.py
@@ -5,13 +5,11 @@ from __future__ import annotations
import zipfile, logging, re
from operator import itemgetter
from pprint import pformat
-
import numpy as np
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey, Interval, Table, FLOAT, BLOB
from sqlalchemy.orm import relationship, validates, Query
from sqlalchemy.ext.associationproxy import association_proxy
from datetime import date, datetime, timedelta
-
from tools import check_authorization, setup_lookup, Report, Result, check_regex_match, timezone, \
jinja_template_loading, flatten_list
from typing import List, Literal, Generator, Any, Tuple, TYPE_CHECKING
@@ -562,7 +560,8 @@ class ReagentRole(BaseClass):
def get_reagents(self, kittype: str | KitType | None = None):
if not kittype:
- return [f"{reagent.name} - {reagent.lot}" for reagent in self.reagent]
+ # return [f"{reagent.name} - {reagent.lot} - {reagent.expiry}" for reagent in self.reagent]
+ return [reagent.to_pydantic() for reagent in self.reagent]
if isinstance(kittype, str):
kittype = KitType.query(name=kittype)
assoc = next((item for item in self.reagentrolekittypeassociation if item.kittype == kittype), None)
@@ -571,7 +570,8 @@ class ReagentRole(BaseClass):
last_used = Reagent.query(name=assoc.last_used)
if last_used:
reagents.insert(0, reagents.pop(reagents.index(last_used)))
- return [f"{reagent.name} - {reagent.lot}" for reagent in reagents]
+ # return [f"{reagent.name} - {reagent.lot} - {reagent.expiry}" for reagent in reagents]
+ return [reagent.to_pydantic(reagentrole=self.name) for reagent in reagents]
class Reagent(BaseClass, LogMixin):
@@ -680,24 +680,24 @@ class Reagent(BaseClass, LogMixin):
report.add_result(Result(msg=f"Updating last used {rt} was not performed.", status="Information"))
return report
- @classmethod
- def query_or_create(cls, **kwargs) -> Reagent:
- from backend.validators.pydant import PydReagent
- new = False
- disallowed = ['expiry']
- sanitized_kwargs = {k: v for k, v in kwargs.items() if k not in disallowed}
- instance = cls.query(**sanitized_kwargs)
- if not instance or isinstance(instance, list):
- if "reagentrole" not in kwargs:
- try:
- kwargs['reagentrole'] = kwargs['name']
- except KeyError:
- pass
- instance = PydReagent(**kwargs)
- new = True
- instance = instance.to_sql()
- logger.info(f"Instance from query or create: {instance}")
- return instance, new
+ # @classmethod
+ # def query_or_create(cls, **kwargs) -> Reagent:
+ # from backend.validators.pydant import PydReagent
+ # new = False
+ # disallowed = ['expiry']
+ # sanitized_kwargs = {k: v for k, v in kwargs.items() if k not in disallowed}
+ # instance = cls.query(**sanitized_kwargs)
+ # if not instance or isinstance(instance, list):
+ # if "reagentrole" not in kwargs:
+ # try:
+ # kwargs['reagentrole'] = kwargs['name']
+ # except KeyError:
+ # pass
+ # instance = PydReagent(**kwargs)
+ # new = True
+ # instance = instance.to_sql()
+ # logger.info(f"Instance from query or create: {instance}")
+ # return instance, new
@classmethod
@setup_lookup
@@ -800,6 +800,12 @@ class Reagent(BaseClass, LogMixin):
expiry="Use exact date on reagent.\nEOL will be calculated from kittype automatically"
)
+ def details_dict(self, reagentrole:str|None=None, **kwargs):
+ output = super().details_dict()
+ if reagentrole:
+ output['reagentrole'] = reagentrole
+ return output
+
class Discount(BaseClass):
"""
@@ -1278,7 +1284,7 @@ class ProcedureType(BaseClass):
if self.plate_rows == 0 or self.plate_columns == 0:
return "
"
sample_dicts = self.pad_sample_dicts(sample_dicts=sample_dicts)
- logger.debug(f"Sample dicts: {pformat(sample_dicts)}")
+ # logger.debug(f"Sample dicts: {pformat(sample_dicts)}")
vw = round((-0.07 * len(sample_dicts)) + 12.2, 1)
# NOTE: An overly complicated list comprehension create a list of sample locations
# NOTE: next will return a blank cell if no value found for row/column
@@ -1291,14 +1297,14 @@ class ProcedureType(BaseClass):
def pad_sample_dicts(self, sample_dicts: List["PydSample"]):
from backend.validators.pydant import PydSample
output = []
- logger.debug(f"Rows: {self.plate_rows}")
- logger.debug(f"Columns: {self.plate_columns}")
+ # logger.debug(f"Rows: {self.plate_rows}")
+ # logger.debug(f"Columns: {self.plate_columns}")
for row, column in self.ranked_plate.values():
sample = next((sample for sample in sample_dicts if sample.row == row and sample.column == column),
PydSample(**dict(sample_id="", row=row, column=column, enabled=False)))
sample.background_color = "#6ffe1d" if sample.enabled else "#ffffff"
output.append(sample)
- logger.debug(f"Appending {sample} at row {row}, column {column}")
+ # logger.debug(f"Appending {sample} at row {row}, column {column}")
return output
@@ -1453,6 +1459,8 @@ class Procedure(BaseClass):
dlg = ProcedureCreation(parent=obj, procedure=self.to_pydantic(), edit=True)
if dlg.exec():
logger.debug("Edited")
+ sql, _ = dlg.return_sql()
+ sql.save()
def add_comment(self, obj):
logger.debug("Add Comment!")
diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py
index 6ef9037..9cdaaa1 100644
--- a/src/submissions/backend/validators/pydant.py
+++ b/src/submissions/backend/validators/pydant.py
@@ -183,7 +183,7 @@ class PydReagent(PydBaseClass):
case _:
return convert_nans_to_nones(str(value))
if value is None:
- value = date.today()
+ value = datetime.combine(date.today(), datetime.max.time())
return value
@field_validator("expiry")
@@ -201,6 +201,11 @@ class PydReagent(PydBaseClass):
else:
return values.data['reagentrole'].strip()
+ # @field_validator("reagentrole", mode="before")
+ # @classmethod
+ # def rescue_reagentrole(cls, value):
+ # if
+
def improved_dict(self) -> dict:
"""
Constructs a dictionary consisting of model.fields and model.extras
@@ -226,23 +231,26 @@ class PydReagent(PydBaseClass):
report = Report()
if self.model_extra is not None:
self.__dict__.update(self.model_extra)
- reagent = Reagent.query(lot=self.lot, name=self.name)
+ reagent, new = Reagent.query_or_create(lot=self.lot, name=self.name)
# logger.debug(f"Reagent: {reagent}")
- if reagent is None:
- reagent = Reagent()
- for key, value in self.__dict__.items():
- if isinstance(value, dict):
- value = value['value']
- # NOTE: reagent method sets fields based on keys in dictionary
- reagent.set_attribute(key, value)
- if procedure is not None and reagent not in procedure.reagents:
- assoc = ProcedureReagentAssociation(reagent=reagent, procedure=procedure)
- assoc.comments = self.comment
- else:
- assoc = None
- else:
- if submission is not None and reagent not in submission.reagents:
- submission.update_reagentassoc(reagent=reagent, role=self.role)
+ # if reagent is None:
+ # reagent = Reagent()
+ # for key, value in self.__dict__.items():
+ # if isinstance(value, dict):
+ # if key == "misc_info":
+ # value = value
+ # else:
+ # value = value['value']
+ # # NOTE: reagent method sets fields based on keys in dictionary
+ # reagent.set_attribute(key, value)
+ # if procedure is not None and reagent not in procedure.reagents:
+ # assoc = ProcedureReagentAssociation(reagent=reagent, procedure=procedure)
+ # assoc.comments = self.comment
+ # else:
+ # assoc = None
+ # else:
+ # if submission is not None and reagent not in submission.reagents:
+ # submission.update_reagentassoc(reagent=reagent, role=self.role)
return reagent, report
@@ -1486,8 +1494,10 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
if isinstance(kittype, str):
kittype_obj = KitType.query(name=kittype)
try:
- self.reagentrole = {item.name: item.get_reagents(kittype=kittype_obj) + ["New"] for item in
- kittype_obj.get_reagents(proceduretype=self.proceduretype)}
+ self.reagentrole = {
+ item.name: item.get_reagents(kittype=kittype_obj) + [PydReagent(name="--New--", lot="", reagentrole="")]
+ for item in
+ kittype_obj.get_reagents(proceduretype=self.proceduretype)}
except AttributeError:
self.reagentrole = {}
@@ -1511,19 +1521,19 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
logger.debug(f"Incoming sample_list:\n{pformat(sample_list)}")
for sample_dict in sample_list:
if sample_dict['sample_id'].startswith("blank_"):
- continue
+ sample_dict['sample_id'] = ""
row, column = self.proceduretype.ranked_plate[sample_dict['index']]
logger.debug(f"Row: {row}, Column: {column}")
try:
sample = next(
- (item for item in self.samples if item.sample_id.upper() == sample_dict['sample_id'].upper()))
+ (item for item in self.sample if item.sample_id.upper() == sample_dict['sample_id'].upper()))
except StopIteration:
# NOTE: Code to check for added controls.
logger.debug(
f"Sample not found by name: {sample_dict['sample_id']}, checking row {row} column {column}")
try:
sample = next(
- (item for item in self.samples if item.row == row and item.column == column))
+ (item for item in self.sample if item.row == row and item.column == column))
except StopIteration:
logger.error(f"Couldn't find sample: {pformat(sample_dict)}")
continue
@@ -1532,7 +1542,23 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
sample.well_id = sample_dict['sample_id']
sample.row = row
sample.column = column
- logger.debug(f"Updated samples:\n{pformat(self.samples)}")
+ # logger.debug(f"Updated samples:\n{pformat(self.sample)}")
+
+ def update_reagents(self, reagentrole: str, name: str, lot: str, expiry: str):
+ removable = next((item for item in self.reagent if item.reagentrole == reagentrole), None)
+ if removable:
+ idx = self.reagent.index(removable)
+ self.reagent.remove(removable)
+ else:
+ idx = 0
+ insertable = PydReagent(reagentrole=reagentrole, name=name, lot=lot, expiry=expiry)
+ self.reagent.insert(idx, insertable)
+ logger.debug(self.reagent)
+
+ @classmethod
+ def update_new_reagents(cls, reagent: PydReagent):
+ reg = reagent.to_sql()
+ reg.save()
def to_sql(self):
from backend.db.models import RunSampleAssociation, ProcedureSampleAssociation
@@ -1540,12 +1566,24 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
# for result in self.results:
# result, _ = result.to_sql()
sql = super().to_sql()
- logger.debug(f"Initial PYD: {pformat(self.__dict__)}")
+ # logger.debug(f"Initial PYD: {pformat(self.__dict__)}")
# sql.results = [result.to_sql() for result in self.results]
if self.run:
sql.run = self.run
if self.proceduretype:
sql.proceduretype = self.proceduretype
+ # Note: convert any new reagents to sql and save
+ for reagentrole, reagents in self.reagentrole.items():
+ for reagent in reagents:
+ if not reagent.lot or reagent.name == "--New--":
+ continue
+ self.update_new_reagents(reagent)
+ # NOTE: reset reagent associations.
+ sql.procedurereagentassociation = []
+ for reagent in self.reagent:
+ reagent = reagent.to_sql()
+ if reagent not in sql.reagent:
+ reagent_assoc = ProcedureReagentAssociation(reagent=reagent, procedure=sql)
try:
start_index = max([item.id for item in ProcedureSampleAssociation.query()]) + 1
except ValueError:
@@ -1571,13 +1609,15 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
kittype = KitType.query(name=self.kittype['value'], limit=1)
if kittype:
sql.kittype = kittype
+ logger.debug(self.reagent)
for equipment in self.equipment:
equip = Equipment.query(name=equipment.name)
if equip not in sql.equipment:
- equip_assoc = ProcedureEquipmentAssociation(equipment=equip, procedure=sql, equipmentrole=equip.equipmentrole[0])
+ equip_assoc = ProcedureEquipmentAssociation(equipment=equip, procedure=sql,
+ equipmentrole=equip.equipmentrole[0])
process = equipment.process.to_sql()
equip_assoc.process = process
- logger.debug(f"Output sql: {[pformat(item.__dict__) for item in sql.procedureequipmentassociation]}")
+ # logger.debug(f"Output sql: {[pformat(item.__dict__) for item in sql.procedureequipmentassociation]}")
return sql, None
diff --git a/src/submissions/frontend/widgets/procedure_creation.py b/src/submissions/frontend/widgets/procedure_creation.py
index bc33805..37c06cf 100644
--- a/src/submissions/frontend/widgets/procedure_creation.py
+++ b/src/submissions/frontend/widgets/procedure_creation.py
@@ -3,6 +3,7 @@
"""
from __future__ import annotations
+import datetime
import os
import sys, logging
from pathlib import Path
@@ -122,6 +123,24 @@ class ProcedureCreation(QDialog):
def log(self, logtext: str):
logger.debug(logtext)
+ @pyqtSlot(str, str, str, str)
+ def add_new_reagent(self, reagentrole: str, name: str, lot: str, expiry: str):
+ from backend.validators.pydant import PydReagent
+ expiry = datetime.datetime.strptime(expiry, "%Y-%m-%d")
+ pyd = PydReagent(reagentrole=reagentrole, name=name, lot=lot, expiry=expiry)
+ logger.debug(pyd)
+ self.procedure.reagentrole[reagentrole].insert(0, pyd)
+ logger.debug(pformat(self.procedure.__dict__))
+ self.set_html()
+
+ @pyqtSlot(str, str)
+ def update_reagent(self, reagentrole:str, name_lot_expiry:str):
+ try:
+ name, lot, expiry = name_lot_expiry.split(" - ")
+ except ValueError:
+ return
+ self.procedure.update_reagents(reagentrole=reagentrole, name=name, lot=lot, expiry=expiry)
+
def return_sql(self):
return self.procedure.to_sql()
diff --git a/src/submissions/frontend/widgets/submission_table.py b/src/submissions/frontend/widgets/submission_table.py
index 3a3501e..f80b90c 100644
--- a/src/submissions/frontend/widgets/submission_table.py
+++ b/src/submissions/frontend/widgets/submission_table.py
@@ -296,7 +296,7 @@ class SubmissionsTree(QTreeView):
self.setAlternatingRowColors(True)
self.setIndentation(20)
self.setItemsExpandable(True)
- self.expanded.connect(self.expand_item)
+ # self.expanded.connect(self.expand_item)
for ii in range(2):
self.resizeColumnToContents(ii)
diff --git a/src/submissions/templates/js/grid_drag.js b/src/submissions/templates/js/grid_drag.js
index 49a4538..40c76e2 100644
--- a/src/submissions/templates/js/grid_drag.js
+++ b/src/submissions/templates/js/grid_drag.js
@@ -32,24 +32,24 @@ gridContainer.addEventListener("drop", (e) => {
targetItem !== draggedItem //&&
//targetItem.classList.contains("well")
) {
- backend.log(targetItem.id);
+// backend.log(targetItem.id);
const draggedIndex = [...gridContainer.children].indexOf(draggedItem);
const targetIndex = [...gridContainer.children].indexOf(targetItem);
if (draggedIndex < targetIndex) {
- backend.log(draggedIndex.toString() + " " + targetIndex.toString() + " Lesser");
+// backend.log(draggedIndex.toString() + " " + targetIndex.toString() + " Lesser");
gridContainer.insertBefore(draggedItem, targetItem.nextSibling);
} else {
- backend.log(draggedIndex.toString() + " " + targetIndex.toString() + " Greater");
+// backend.log(draggedIndex.toString() + " " + targetIndex.toString() + " Greater");
gridContainer.insertBefore(draggedItem, targetItem);
}
-// output = [];
-// fullGrid = [...gridContainer.children];
-// fullGrid.forEach(function(item, index) {
-// output.push({sample_id: item.id, index: index + 1})
-// });
-// backend.rearrange_plate(output);
- rearrange_plate();
+ output = [];
+ fullGrid = [...gridContainer.children];
+ fullGrid.forEach(function(item, index) {
+ output.push({sample_id: item.id, index: index + 1})
+ });
+ backend.rearrange_plate(output);
+// rearrange_plate();
}
});
\ No newline at end of file
diff --git a/src/submissions/templates/js/procedure_form.js b/src/submissions/templates/js/procedure_form.js
index feff608..15aa5e9 100644
--- a/src/submissions/templates/js/procedure_form.js
+++ b/src/submissions/templates/js/procedure_form.js
@@ -21,45 +21,39 @@ for(let i = 0; i < formtexts.length; i++) {
})
};
+var changed_it = new Event('change');
+
var reagentRoles = document.getElementsByClassName("reagentrole");
for(let i = 0; i < reagentRoles.length; i++) {
reagentRoles[i].addEventListener("change", function() {
- if (reagentRoles[i].value === "New") {
-
+ if (reagentRoles[i].value.includes("--New--")) {
var br = document.createElement("br");
var new_reg = document.getElementById("new_" + reagentRoles[i].id);
- console.log(new_reg.id);
var new_form = document.createElement("form");
-
+ new_form.setAttribute("class", "new_reagent_form")
+ new_form.setAttribute("id", reagentRoles[i].id + "_addition")
var rr_name = document.createElement("input");
rr_name.setAttribute("type", "text");
rr_name.setAttribute("id", "new_" + reagentRoles[i].id + "_name");
-
var rr_name_label = document.createElement("label");
rr_name_label.setAttribute("for", "new_" + reagentRoles[i].id + "_name");
rr_name_label.innerHTML = "Name:";
-
var rr_lot = document.createElement("input");
rr_lot.setAttribute("type", "text");
rr_lot.setAttribute("id", "new_" + reagentRoles[i].id + "_lot");
-
var rr_lot_label = document.createElement("label");
rr_lot_label.setAttribute("for", "new_" + reagentRoles[i].id + "_lot");
rr_lot_label.innerHTML = "Lot:";
-
var rr_expiry = document.createElement("input");
rr_expiry.setAttribute("type", "date");
rr_expiry.setAttribute("id", "new_" + reagentRoles[i].id + "_expiry");
-
var rr_expiry_label = document.createElement("label");
rr_expiry_label.setAttribute("for", "new_" + reagentRoles[i].id + "_expiry");
rr_expiry_label.innerHTML = "Expiry:";
-
var submit_btn = document.createElement("input");
submit_btn.setAttribute("type", "submit");
submit_btn.setAttribute("value", "Submit");
-
new_form.appendChild(br.cloneNode());
new_form.appendChild(rr_name_label);
new_form.appendChild(rr_name);
@@ -72,19 +66,27 @@ for(let i = 0; i < reagentRoles.length; i++) {
new_form.appendChild(br.cloneNode());
new_form.appendChild(submit_btn);
new_form.appendChild(br.cloneNode());
-
new_form.onsubmit = function(event) {
event.preventDefault();
- alert(reagentRoles[i].id);
name = document.getElementById("new_" + reagentRoles[i].id + "_name").value;
lot = document.getElementById("new_" + reagentRoles[i].id + "_lot").value;
expiry = document.getElementById("new_" + reagentRoles[i].id + "_expiry").value;
- alert("Submitting: " + name + ", " + lot);
- backend.log(name + " " + lot + " " + expiry);
+ backend.add_new_reagent(reagentRoles[i].id, name, lot, expiry);
+ new_form.remove();
+ reagentRoles[i].dispatchEvent(changed_it);
}
-
new_reg.appendChild(new_form);
+ } else {
+ newregform = document.getElementById(reagentRoles[i].id + "_addition");
+ newregform.remove();
+ backend.update_reagent(reagentRoles[i].id, reagentRoles[i].value)
}
- })
+ });
};
+window.onload = function() {
+ for(let i = 0; i < reagentRoles.length; i++) {
+ backend.update_reagent(reagentRoles[i].id, reagentRoles[i].value);
+ }
+}
+
diff --git a/src/submissions/templates/procedure_creation.html b/src/submissions/templates/procedure_creation.html
index ef4a65c..35c8b05 100644
--- a/src/submissions/templates/procedure_creation.html
+++ b/src/submissions/templates/procedure_creation.html
@@ -31,12 +31,14 @@
{% endfor %}
{% if procedure['reagentrole'] %}
-