Update of Reagent usage ui for addition of new reagents.
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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 "<br/>"
|
||||
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!")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,14 @@
|
||||
{% endfor %}
|
||||
</select><br>
|
||||
{% if procedure['reagentrole'] %}
|
||||
<br><hr><br>
|
||||
<br>
|
||||
<h1><u>Reagents</u></h1>
|
||||
<hr>
|
||||
{% for key, value in procedure['reagentrole'].items() %}
|
||||
<label for="{{ key }}">{{ key }}:</label><br>
|
||||
<select class="reagentrole dropdown" id="{{ key }}" name="{{ key }}"><br>
|
||||
{% for reagent in value %}
|
||||
<option value="{{ reagent }}">{{ reagent }}</option>
|
||||
<option value="{{ reagent.name }} {% if reagent.lot %}- {{ reagent.lot }} - {{ reagent.expiry.date() }}{% endif %}">{{ reagent.name }} {% if reagent.lot %}- {{ reagent.lot }} - {{ reagent.expiry.date() }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="new_reagent" id="new_{{ key }}"></div>
|
||||
|
||||
Reference in New Issue
Block a user