basic procedure creation working
This commit is contained in:
@@ -6,6 +6,7 @@ import json, zipfile, yaml, logging, re, sys
|
|||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
from jinja2 import Template, TemplateNotFound
|
from jinja2 import Template, TemplateNotFound
|
||||||
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey, Interval, Table, FLOAT, BLOB
|
from sqlalchemy import Column, String, TIMESTAMP, JSON, INTEGER, ForeignKey, Interval, Table, FLOAT, BLOB
|
||||||
from sqlalchemy.orm import relationship, validates, Query
|
from sqlalchemy.orm import relationship, validates, Query
|
||||||
@@ -539,9 +540,19 @@ class ReagentRole(BaseClass):
|
|||||||
logger.debug(f"Constructing OmniReagentRole with name {self.name}")
|
logger.debug(f"Constructing OmniReagentRole with name {self.name}")
|
||||||
return OmniReagentRole(instance_object=self, name=self.name, eol_ext=self.eol_ext)
|
return OmniReagentRole(instance_object=self, name=self.name, eol_ext=self.eol_ext)
|
||||||
|
|
||||||
@property
|
def get_reagents(self, kittype: str | KitType | None = None):
|
||||||
def reagents(self):
|
if not kittype:
|
||||||
return [f"{reagent.name} - {reagent.lot}" for reagent in self.reagent]
|
return [f"{reagent.name} - {reagent.lot}" 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)
|
||||||
|
reagents = [reagent for reagent in self.reagent]
|
||||||
|
if assoc:
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
class Reagent(BaseClass, LogMixin):
|
class Reagent(BaseClass, LogMixin):
|
||||||
"""
|
"""
|
||||||
@@ -1151,21 +1162,25 @@ class ProcedureType(BaseClass):
|
|||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
return dict(
|
return dict(
|
||||||
name=self.name,
|
name=self.name,
|
||||||
kittype=[item.name for item in self.kittype]
|
kittype=[item.name for item in self.kittype],
|
||||||
|
plate_rows=self.plate_rows,
|
||||||
|
plate_columns=self.plate_columns
|
||||||
)
|
)
|
||||||
|
|
||||||
def construct_dummy_procedure(self):
|
def construct_dummy_procedure(self, run: Run|None=None):
|
||||||
from backend.validators.pydant import PydProcedure
|
from backend.validators.pydant import PydProcedure
|
||||||
|
if run:
|
||||||
|
samples = run.constuct_sample_dicts_for_proceduretype(proceduretype=self)
|
||||||
output = dict(
|
output = dict(
|
||||||
proceduretype=self,
|
proceduretype=self,
|
||||||
#name=dict(value=self.name, missing=True),
|
#name=dict(value=self.name, missing=True),
|
||||||
#possible_kits=[kittype.name for kittype in self.kittype],
|
#possible_kits=[kittype.name for kittype in self.kittype],
|
||||||
repeat=False,
|
repeat=False
|
||||||
plate_map=self.construct_plate_map()
|
# plate_map=plate_map
|
||||||
)
|
)
|
||||||
return PydProcedure(**output)
|
return PydProcedure(**output)
|
||||||
|
|
||||||
def construct_plate_map(self) -> str:
|
def construct_plate_map(self, sample_dicts: List[dict]) -> str:
|
||||||
"""
|
"""
|
||||||
Constructs an html based plate map for procedure details.
|
Constructs an html based plate map for procedure details.
|
||||||
|
|
||||||
@@ -1179,22 +1194,31 @@ class ProcedureType(BaseClass):
|
|||||||
"""
|
"""
|
||||||
if self.plate_rows == 0 or self.plate_columns == 0:
|
if self.plate_rows == 0 or self.plate_columns == 0:
|
||||||
return "<br/>"
|
return "<br/>"
|
||||||
plate_rows = range(1, self.plate_rows + 1)
|
# plate_rows = range(1, self.plate_rows + 1)
|
||||||
plate_columns = range(1, self.plate_columns + 1)
|
# plate_columns = range(1, self.plate_columns + 1)
|
||||||
total_wells = self.plate_columns * self.plate_rows
|
# total_wells = self.plate_columns * self.plate_rows
|
||||||
vw = round((-0.07 * total_wells) + 12.2, 1)
|
vw = round((-0.07 * len(sample_dicts)) + 12.2, 1)
|
||||||
|
# sample_dicts = run.constuct_sample_dicts_for_proceduretype(proceduretype=self)
|
||||||
|
# output_samples = [next((item for item in sample_dicts if item['row'] == row and item['column'] == column),
|
||||||
wells = [dict(name="", row=row, column=column, background_color="#ffffff")
|
# dict(sample_id="", row=row, column=column, background_color="#ffffff"))
|
||||||
for row in plate_rows
|
# for row in plate_rows
|
||||||
for column in plate_columns]
|
# for column in plate_columns]
|
||||||
|
# logger.debug(f"Output samples:\n{pformat(output_samples)}")
|
||||||
# NOTE: An overly complicated list comprehension create a list of sample locations
|
# 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
|
# NOTE: next will return a blank cell if no value found for row/column
|
||||||
env = jinja_template_loading()
|
env = jinja_template_loading()
|
||||||
template = env.get_template("plate_map.html")
|
template = env.get_template("plate_map.html")
|
||||||
html = template.render(plate_rows=self.plate_rows, plate_columns=self.plate_columns, samples=wells, vw=vw)
|
html = template.render(plate_rows=self.plate_rows, plate_columns=self.plate_columns, samples=sample_dicts, vw=vw)
|
||||||
return html + "<br/>"
|
return html + "<br/>"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ranked_plate(self):
|
||||||
|
matrix = np.array([[0 for yyy in range(1, self.plate_rows + 1)] for xxx in range(1, self.plate_columns + 1)])
|
||||||
|
return {iii: (item[0][1] + 1, item[0][0] + 1) for iii, item in enumerate(np.ndenumerate(matrix), start=1)}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_wells(self):
|
||||||
|
return self.plate_rows * self.plate_columns
|
||||||
|
|
||||||
class Procedure(BaseClass):
|
class Procedure(BaseClass):
|
||||||
id = Column(INTEGER, primary_key=True)
|
id = Column(INTEGER, primary_key=True)
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ from openpyxl.drawing.image import Image as OpenpyxlImage
|
|||||||
from tools import row_map, setup_lookup, jinja_template_loading, rreplace, row_keys, check_key_or_attr, Result, Report, \
|
from tools import row_map, setup_lookup, jinja_template_loading, rreplace, row_keys, check_key_or_attr, Result, Report, \
|
||||||
report_result, create_holidays_for_year, check_dictionary_inclusion_equality
|
report_result, create_holidays_for_year, check_dictionary_inclusion_equality
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
from typing import List, Any, Tuple, Literal, Generator, Type
|
from typing import List, Any, Tuple, Literal, Generator, Type, TYPE_CHECKING
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from jinja2.exceptions import TemplateNotFound
|
from jinja2.exceptions import TemplateNotFound
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from backend.db.models.kits import ProcedureType
|
||||||
|
|
||||||
from . import kittype_procedure
|
from . import kittype_procedure
|
||||||
|
|
||||||
@@ -653,6 +655,10 @@ class Run(BaseClass, LogMixin):
|
|||||||
output_list = [assoc.hitpicked for assoc in self.runsampleassociation]
|
output_list = [assoc.hitpicked for assoc in self.runsampleassociation]
|
||||||
return output_list
|
return output_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sample_dicts(self) -> List[dict]:
|
||||||
|
return [dict(sample_id=assoc.sample.sample_id, row=assoc.row, column=assoc.column, background_color="#6ffe1d") for assoc in self.runsampleassociation]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_plate_map(cls, sample_list: list, plate_rows: int = 8, plate_columns=12) -> str:
|
def make_plate_map(cls, sample_list: list, plate_rows: int = 8, plate_columns=12) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -1273,6 +1279,51 @@ class Run(BaseClass, LogMixin):
|
|||||||
def allowed_procedures(self):
|
def allowed_procedures(self):
|
||||||
return self.clientsubmission.submissiontype.proceduretype
|
return self.clientsubmission.submissiontype.proceduretype
|
||||||
|
|
||||||
|
def get_submission_rank_of_sample(self, sample: Sample|str):
|
||||||
|
if isinstance(sample, str):
|
||||||
|
sample = Sample.query(sample_id=sample)
|
||||||
|
clientsubmissionsampleassoc = next((assoc for assoc in self.clientsubmission.clientsubmissionsampleassociation
|
||||||
|
if assoc.sample == sample), None)
|
||||||
|
if clientsubmissionsampleassoc:
|
||||||
|
return clientsubmissionsampleassoc.submission_rank
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def constuct_sample_dicts_for_proceduretype(self, proceduretype: ProcedureType):
|
||||||
|
plate_dict = proceduretype.ranked_plate
|
||||||
|
ranked_samples = []
|
||||||
|
unranked_samples = []
|
||||||
|
for sample in self.sample:
|
||||||
|
submission_rank = self.get_submission_rank_of_sample(sample=sample)
|
||||||
|
if submission_rank != 0:
|
||||||
|
row, column = plate_dict[submission_rank]
|
||||||
|
ranked_samples.append(dict(sample_id=sample.sample_id, row=row, column=column, submission_rank=submission_rank, background_color="#6ffe1d"))
|
||||||
|
else:
|
||||||
|
unranked_samples.append(sample)
|
||||||
|
possible_ranks = (item for item in list(plate_dict.keys()) if item not in [sample['submission_rank'] for sample in ranked_samples])
|
||||||
|
# logger.debug(possible_ranks)
|
||||||
|
# possible_ranks = (plate_dict[idx] for idx in possible_ranks)
|
||||||
|
for sample in unranked_samples:
|
||||||
|
try:
|
||||||
|
submission_rank = next(possible_ranks)
|
||||||
|
except StopIteration:
|
||||||
|
continue
|
||||||
|
row, column = plate_dict[submission_rank]
|
||||||
|
ranked_samples.append(
|
||||||
|
dict(sample_id=sample.sample_id, row=row, column=column, submission_rank=submission_rank,
|
||||||
|
background_color="#6ffe1d"))
|
||||||
|
padded_list = []
|
||||||
|
for iii in range(1, proceduretype.total_wells+1):
|
||||||
|
sample = next((item for item in ranked_samples if item['submission_rank']==iii),
|
||||||
|
dict(sample_id="", row=0, column=0, submission_rank=iii)
|
||||||
|
)
|
||||||
|
padded_list.append(sample)
|
||||||
|
logger.debug(f"Final padded list:\n{pformat(list(sorted(padded_list, key=itemgetter('submission_rank'))))}")
|
||||||
|
return list(sorted(padded_list, key=itemgetter('submission_rank')))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SampleType(BaseClass):
|
class SampleType(BaseClass):
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
|
|||||||
@@ -243,6 +243,8 @@ class PydSample(PydBaseClass):
|
|||||||
sampletype: str | None = Field(default=None)
|
sampletype: str | None = Field(default=None)
|
||||||
submission_rank: int | List[int] | None = Field(default=0, validate_default=True)
|
submission_rank: int | List[int] | None = Field(default=0, validate_default=True)
|
||||||
enabled: bool = Field(default=True)
|
enabled: bool = Field(default=True)
|
||||||
|
row: int = Field(default=0)
|
||||||
|
column: int = Field(default=0)
|
||||||
|
|
||||||
@field_validator("sample_id", mode="before")
|
@field_validator("sample_id", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1336,6 +1338,7 @@ class PydElastic(BaseModel, extra="allow", arbitrary_types_allowed=True):
|
|||||||
|
|
||||||
class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
||||||
proceduretype: ProcedureType | None = Field(default=None)
|
proceduretype: ProcedureType | None = Field(default=None)
|
||||||
|
run: Run | None = Field(default=None)
|
||||||
name: dict = Field(default=dict(value="NA", missing=True), validate_default=True)
|
name: dict = Field(default=dict(value="NA", missing=True), validate_default=True)
|
||||||
technician: dict = Field(default=dict(value="NA", missing=True))
|
technician: dict = Field(default=dict(value="NA", missing=True))
|
||||||
repeat: bool = Field(default=False)
|
repeat: bool = Field(default=False)
|
||||||
@@ -1344,6 +1347,7 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
plate_map: str | None = Field(default=None)
|
plate_map: str | None = Field(default=None)
|
||||||
reagent: list | None = Field(default=[])
|
reagent: list | None = Field(default=[])
|
||||||
reagentrole: dict | None = Field(default={}, validate_default=True)
|
reagentrole: dict | None = Field(default={}, validate_default=True)
|
||||||
|
samples: List[PydSample] = Field(default=[])
|
||||||
|
|
||||||
@field_validator("name")
|
@field_validator("name")
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1379,18 +1383,68 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
value = {item.name: item.reagents for item in kittype.reagentrole}
|
value = {item.name: item.reagents for item in kittype.reagentrole}
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def update_kittype_reagentroles(self, kittype: str | KitType):
|
def update_kittype_reagentroles(self, kittype: str | KitType):
|
||||||
if kittype == self.__class__.model_fields['kittype'].default['value']:
|
if kittype == self.__class__.model_fields['kittype'].default['value']:
|
||||||
return
|
return
|
||||||
if isinstance(kittype, str):
|
if isinstance(kittype, str):
|
||||||
kittype_obj = KitType.query(name=kittype)
|
kittype_obj = KitType.query(name=kittype)
|
||||||
try:
|
try:
|
||||||
self.reagentrole = {item.name: item.reagents for item in
|
self.reagentrole = {item.name: item.get_reagents(kittype=kittype_obj) for item in
|
||||||
kittype_obj.get_reagents(proceduretype=self.proceduretype)}
|
kittype_obj.get_reagents(proceduretype=self.proceduretype)}
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.reagentrole = {}
|
self.reagentrole = {}
|
||||||
self.possible_kits.insert(0, self.possible_kits.pop(self.possible_kits.index(kittype)))
|
self.possible_kits.insert(0, self.possible_kits.pop(self.possible_kits.index(kittype)))
|
||||||
|
|
||||||
|
def shuffle_samples(self, source_row: int, source_column: int, destination_row: int, destination_column=int):
|
||||||
|
logger.debug(f"Attempting sample shuffle.")
|
||||||
|
try:
|
||||||
|
source_sample = next(
|
||||||
|
(sample for sample in self.samples if sample.row == source_row and sample.column == source_column))
|
||||||
|
except StopIteration:
|
||||||
|
raise StopIteration("Couldn't find proper sample.")
|
||||||
|
logger.debug(f"Source Well: {source_row}, {source_column}")
|
||||||
|
logger.debug(f"Destination Well: {destination_row}, {destination_column}")
|
||||||
|
updateable_samples = []
|
||||||
|
if source_row > destination_row and source_column >= destination_column:
|
||||||
|
logger.debug(f"Sample was moved ahead.")
|
||||||
|
movement = "pos"
|
||||||
|
for sample in self.samples:
|
||||||
|
if sample.row >= destination_row and sample.column >= destination_column:
|
||||||
|
if sample.row <= source_row and sample.column <= source_column:
|
||||||
|
updateable_samples.append(sample)
|
||||||
|
|
||||||
|
elif source_row < destination_row and source_column <= destination_column:
|
||||||
|
logger.debug(f"Sample was moved back.")
|
||||||
|
movement = "neg"
|
||||||
|
for sample in self.samples:
|
||||||
|
if sample.row <= destination_row and sample.column <= destination_column:
|
||||||
|
if sample.row >= source_row and sample.column >= source_column:
|
||||||
|
updateable_samples.append(sample)
|
||||||
|
else:
|
||||||
|
logger.debug(f"Don't know what happened.")
|
||||||
|
logger.debug(f"Samples to be updated: {pformat(updateable_samples)}")
|
||||||
|
for sample in updateable_samples:
|
||||||
|
if sample.row == source_row and sample.column == source_column:
|
||||||
|
sample.row = destination_row
|
||||||
|
sample.column = destination_column
|
||||||
|
else:
|
||||||
|
match movement:
|
||||||
|
case "pos":
|
||||||
|
if sample.row + 1 > 8:
|
||||||
|
sample.column += 1
|
||||||
|
sample.row = 1
|
||||||
|
else:
|
||||||
|
sample.row += 1
|
||||||
|
case "neg":
|
||||||
|
if sample.row - 1 <= 0:
|
||||||
|
sample.column -= 1
|
||||||
|
sample.row = 8
|
||||||
|
else:
|
||||||
|
sample.row -= 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PydClientSubmission(PydBaseClass):
|
class PydClientSubmission(PydBaseClass):
|
||||||
# sql_object: ClassVar = ClientSubmission
|
# sql_object: ClassVar = ClientSubmission
|
||||||
|
|||||||
@@ -84,3 +84,29 @@ div.gallery {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plate {
|
||||||
|
display: inline-grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
place-content: center center;
|
||||||
|
grid-gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.well {
|
||||||
|
border: 1px solid #000;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.well:hover {
|
||||||
|
box-shadow: 0 4px 25px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-item p {
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
<div class="gallery" style="display: grid;grid-template-columns: repeat({{ plate_columns }}, {{ vw }}vw);grid-template-rows: repeat({{ plate_rows }}, {{ vw }}vw);grid-gap: 2px;">
|
<div class="plate" id="plate-container" style="grid-template-columns: repeat({{ plate_columns }}, {{ vw }}vw);grid-template-rows: repeat({{ plate_rows }}, {{ vw }}vw);">
|
||||||
{% for sample in samples %}
|
{% for sample in samples %}
|
||||||
<div class="well data-link sample" id="{{sample['submitter_id']}}" style="background-color: {{ sample['background_color'] }};
|
<div class="well" draggable="true" id="sample_{{ sample['submission_rank'] }}" style="background-color: {{ sample['background_color'] }};">
|
||||||
border: 1px solid #000;
|
<p style="font-size: 0.7em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}</p>
|
||||||
padding: 20px;
|
<!-- <div class="tooltip" style="font-size: 0.5em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}-->
|
||||||
grid-column-start: {{sample['column']}};
|
<!-- <span class="tooltiptext">{{ sample['tooltip'] }}</span>-->
|
||||||
|
<!-- </div>--
|
||||||
|
grid-column-start: {{sample['column']}};
|
||||||
grid-column-end: {{sample['column']}};
|
grid-column-end: {{sample['column']}};
|
||||||
grid-row-start: {{sample['row']}};
|
grid-row-start: {{sample['row']}};
|
||||||
grid-row-end: {{sample['row']}};
|
grid-row-end: {{sample['row']}};-->
|
||||||
display: flex;
|
|
||||||
">
|
|
||||||
<div class="tooltip" style="font-size: 0.5em; text-align: center; word-wrap: break-word;">{{ sample['name'] }}
|
|
||||||
<span class="tooltiptext">{{ sample['tooltip'] }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
124
src/submissions/templates/procedure_creation.html
Normal file
124
src/submissions/templates/procedure_creation.html
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
{% extends "details.html" %}
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
<title> New {{ proceduretype['name'] }} for {{ run['plate_number'] }}</title>
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<!-- Is this working? -->
|
||||||
|
<body>
|
||||||
|
{% block body %}
|
||||||
|
<h1>New {{ proceduretype['name'] }} for {{ run['plate_number'] }}</h1><br><br>
|
||||||
|
<div class="procedure_creation_leftright">
|
||||||
|
<div class="left">
|
||||||
|
<form>
|
||||||
|
<label for="technician">Technician:</label><br>
|
||||||
|
<input type="text" class="form_text" id="technician" name="technician" width="100%" value="{{ procedure['technician']['value'] }}" background-color="{{ procedure['technician']['colour'] }}"><br><br>
|
||||||
|
<label for="repeat">Repeat:</label>
|
||||||
|
<input type="checkbox" class="form_check" id="repeat" name="repeat" value="{{ procedure['repeat'] }}"><br><br>
|
||||||
|
<label>Kit Type:</label><br>
|
||||||
|
<select class="dropdown" id="kittype" background-colour="{{ procedure['kittype']['colour'] }}">
|
||||||
|
{% for kittype in procedure['possible_kits'] %}
|
||||||
|
<option value="{{ kittype }}">{{ kittype }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select><br>
|
||||||
|
{% if procedure['reagentrole'] %}
|
||||||
|
<br><hr><br>
|
||||||
|
{% for key, value in procedure['reagentrole'].items() %}
|
||||||
|
<label for="{{ key }}">{{ key }}:</label><br>
|
||||||
|
<select class="reagentrole dropdown" id="{{ key }}" name="{{ reagentrole }}"><br>
|
||||||
|
{% for reagent in value %}
|
||||||
|
<option value="{{ reagent }}">{{ reagent }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
{% if plate_map %}
|
||||||
|
<h1>Plate map:</h1>
|
||||||
|
{{ plate_map }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
{% block script %}
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
document.getElementById("kittype").addEventListener("change", function() {
|
||||||
|
backend.update_kit(this.value);
|
||||||
|
})
|
||||||
|
|
||||||
|
var formchecks = document.getElementsByClassName('form_check');
|
||||||
|
|
||||||
|
for(let i = 0; i < formchecks.length; i++) {
|
||||||
|
formchecks[i].addEventListener("change", function() {
|
||||||
|
backend.check_toggle(formchecks[i].id, formchecks[i].checked);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
var formtexts = document.getElementsByClassName('form_text');
|
||||||
|
|
||||||
|
for(let i = 0; i < formtexts.length; i++) {
|
||||||
|
formtexts[i].addEventListener("input", function() {
|
||||||
|
backend.text_changed(formtexts[i].id, formtexts[i].value);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const gridContainer = document.getElementById("plate-container");
|
||||||
|
let draggedItem = null;
|
||||||
|
|
||||||
|
//Handle Drag start
|
||||||
|
gridContainer.addEventListener("dragstart", (e) => {
|
||||||
|
draggedItem = e.target;
|
||||||
|
draggedItem.style.opacity = "0.5";
|
||||||
|
});
|
||||||
|
|
||||||
|
//Handle Drag End
|
||||||
|
gridContainer.addEventListener("dragend", (e) => {
|
||||||
|
e.target.style.opacity = "1";
|
||||||
|
draggedItem = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
//handle dragging ove grid items
|
||||||
|
gridContainer.addEventListener("dragover", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Handle Drop
|
||||||
|
gridContainer.addEventListener("drop", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const targetItem = e.target;
|
||||||
|
if (
|
||||||
|
targetItem &&
|
||||||
|
targetItem !== draggedItem &&
|
||||||
|
targetItem.classList.contains("well")
|
||||||
|
) {
|
||||||
|
const draggedIndex = [...gridContainer.children].indexOf(draggedItem);
|
||||||
|
const targetIndex = [...gridContainer.children].indexOf(targetItem);
|
||||||
|
if (draggedIndex < targetIndex) {
|
||||||
|
backend.log_drag(draggedIndex.toString(), targetIndex.toString() + " Lesser");
|
||||||
|
gridContainer.insertBefore(draggedItem, targetItem.nextSibling);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
backend.log_drag(draggedIndex.toString(), targetIndex.toString() + " Greater");
|
||||||
|
gridContainer.insertBefore(draggedItem, targetItem);
|
||||||
|
|
||||||
|
}
|
||||||
|
output = [];
|
||||||
|
fullGrid = [...gridContainer.children];
|
||||||
|
fullGrid.forEach(function(item, index) {
|
||||||
|
output.push({item: item, index: index})
|
||||||
|
});
|
||||||
|
backend.replow(output);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
{% endblock %}
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
@@ -27,7 +27,6 @@ from sqlalchemy.exc import IntegrityError as sqlalcIntegrityError
|
|||||||
from pytz import timezone as tz
|
from pytz import timezone as tz
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
timezone = tz("America/Winnipeg")
|
timezone = tz("America/Winnipeg")
|
||||||
|
|
||||||
logger = logging.getLogger(f"procedure.{__name__}")
|
logger = logging.getLogger(f"procedure.{__name__}")
|
||||||
@@ -249,6 +248,7 @@ def timer(func):
|
|||||||
func (__function__): incoming function
|
func (__function__): incoming function
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
@@ -257,6 +257,7 @@ def timer(func):
|
|||||||
run_time = end_time - start_time
|
run_time = end_time - start_time
|
||||||
print(f"Finished {func.__name__}() in {run_time:.4f} secs")
|
print(f"Finished {func.__name__}() in {run_time:.4f} secs")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@@ -295,7 +296,6 @@ class GroupWriteRotatingFileHandler(handlers.RotatingFileHandler):
|
|||||||
|
|
||||||
|
|
||||||
class CustomFormatter(logging.Formatter):
|
class CustomFormatter(logging.Formatter):
|
||||||
|
|
||||||
class bcolors:
|
class bcolors:
|
||||||
HEADER = '\033[95m'
|
HEADER = '\033[95m'
|
||||||
OKBLUE = '\033[94m'
|
OKBLUE = '\033[94m'
|
||||||
@@ -482,6 +482,7 @@ def setup_lookup(func):
|
|||||||
elif v is not None:
|
elif v is not None:
|
||||||
sanitized_kwargs[k] = v
|
sanitized_kwargs[k] = v
|
||||||
return func(*args, **sanitized_kwargs)
|
return func(*args, **sanitized_kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@@ -532,7 +533,6 @@ def get_application_from_parent(widget):
|
|||||||
|
|
||||||
|
|
||||||
class Result(BaseModel, arbitrary_types_allowed=True):
|
class Result(BaseModel, arbitrary_types_allowed=True):
|
||||||
|
|
||||||
owner: str = Field(default="", validate_default=True)
|
owner: str = Field(default="", validate_default=True)
|
||||||
code: int = Field(default=0)
|
code: int = Field(default=0)
|
||||||
msg: str | Exception
|
msg: str | Exception
|
||||||
@@ -768,6 +768,7 @@ def under_development(func):
|
|||||||
Result(owner=func.__str__(), code=1, msg=error_msg,
|
Result(owner=func.__str__(), code=1, msg=error_msg,
|
||||||
status="warning"))
|
status="warning"))
|
||||||
return report
|
return report
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@@ -856,6 +857,7 @@ def create_holidays_for_year(year: int | None = None) -> List[date]:
|
|||||||
offset = -d.weekday() # weekday == 0 means Monday
|
offset = -d.weekday() # weekday == 0 means Monday
|
||||||
output = d + timedelta(offset)
|
output = d + timedelta(offset)
|
||||||
return output.date()
|
return output.date()
|
||||||
|
|
||||||
if not year:
|
if not year:
|
||||||
year = date.today().year
|
year = date.today().year
|
||||||
# NOTE: Includes New Year's day for next year.
|
# NOTE: Includes New Year's day for next year.
|
||||||
@@ -900,6 +902,11 @@ def flatten_list(input_list: list):
|
|||||||
return list(itertools.chain.from_iterable(input_list))
|
return list(itertools.chain.from_iterable(input_list))
|
||||||
|
|
||||||
|
|
||||||
|
def create_plate_grid(rows: int, columns: int):
|
||||||
|
matrix = np.array([[0 for yyy in range(1, columns + 1)] for xxx in range(1, rows + 1)])
|
||||||
|
return {iii: (item[0][1]+1, item[0][0]+1) for iii, item in enumerate(np.ndenumerate(matrix), start=1)}
|
||||||
|
|
||||||
|
|
||||||
class classproperty(property):
|
class classproperty(property):
|
||||||
def __get__(self, owner_self, owner_cls):
|
def __get__(self, owner_self, owner_cls):
|
||||||
return self.fget(owner_cls)
|
return self.fget(owner_cls)
|
||||||
@@ -1293,4 +1300,3 @@ class Settings(BaseSettings, extra="allow"):
|
|||||||
|
|
||||||
|
|
||||||
ctx = Settings()
|
ctx = Settings()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user