Replaced some list comprehension with generators, javascript in templates to create click event.
This commit is contained in:
@@ -283,6 +283,17 @@ class BasicSubmission(BaseClass):
|
||||
del input_dict['id']
|
||||
return input_dict
|
||||
|
||||
def generate_associations(self, name:str, extra:str|None=None):
|
||||
try:
|
||||
field = self.__getattribute__(name)
|
||||
except AttributeError:
|
||||
return None
|
||||
for item in field:
|
||||
if extra:
|
||||
yield item.to_sub_dict(extra)
|
||||
else:
|
||||
yield item.to_sub_dict()
|
||||
|
||||
def to_dict(self, full_data: bool = False, backup: bool = False, report: bool = False) -> dict:
|
||||
"""
|
||||
Constructs dictionary used in submissions summary
|
||||
@@ -332,6 +343,10 @@ class BasicSubmission(BaseClass):
|
||||
try:
|
||||
reagents = [item.to_sub_dict(extraction_kit=self.extraction_kit) for item in
|
||||
self.submission_reagent_associations]
|
||||
except Exception as e:
|
||||
logger.error(f"We got an error retrieving reagents: {e}")
|
||||
reagents = []
|
||||
finally:
|
||||
for k, v in self.extraction_kit.construct_xl_map_for_use(self.submission_type):
|
||||
if k == 'info':
|
||||
continue
|
||||
@@ -341,26 +356,26 @@ class BasicSubmission(BaseClass):
|
||||
reagents.append(
|
||||
dict(role=k, name="Not Applicable", lot="NA", expiry=expiry,
|
||||
missing=True))
|
||||
except Exception as e:
|
||||
logger.error(f"We got an error retrieving reagents: {e}")
|
||||
reagents = None
|
||||
# logger.debug(f"Running samples.")
|
||||
samples = self.adjust_to_dict_samples(backup=backup)
|
||||
# samples = self.adjust_to_dict_samples(backup=backup)
|
||||
samples = self.generate_associations(name="submission_sample_associations")
|
||||
# logger.debug("Running equipment")
|
||||
try:
|
||||
equipment = [item.to_sub_dict() for item in self.submission_equipment_associations]
|
||||
if not equipment:
|
||||
equipment = None
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting equipment: {e}")
|
||||
equipment = None
|
||||
try:
|
||||
tips = [item.to_sub_dict() for item in self.submission_tips_associations]
|
||||
if not tips:
|
||||
tips = None
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting tips: {e}")
|
||||
tips = None
|
||||
equipment = self.generate_associations(name="submission_equipment_associations")
|
||||
# try:
|
||||
# equipment = [item.to_sub_dict() for item in self.submission_equipment_associations]
|
||||
# if not equipment:
|
||||
# equipment = None
|
||||
# except Exception as e:
|
||||
# logger.error(f"Error setting equipment: {e}")
|
||||
# equipment = None
|
||||
tips = self.generate_associations(name="submission_tips_associations")
|
||||
# try:
|
||||
# tips = [item.to_sub_dict() for item in self.submission_tips_associations]
|
||||
# if not tips:
|
||||
# tips = None
|
||||
# except Exception as e:
|
||||
# logger.error(f"Error setting tips: {e}")
|
||||
# tips = None
|
||||
cost_centre = self.cost_centre
|
||||
custom = self.custom
|
||||
else:
|
||||
@@ -1013,18 +1028,18 @@ class BasicSubmission(BaseClass):
|
||||
logger.info(f"Hello from {cls.__mapper_args__['polymorphic_identity']} sampler")
|
||||
return samples
|
||||
|
||||
def adjust_to_dict_samples(self, backup: bool = False) -> List[dict]:
|
||||
"""
|
||||
Updates sample dictionaries with custom values
|
||||
|
||||
Args:
|
||||
backup (bool, optional): Whether to perform backup. Defaults to False.
|
||||
|
||||
Returns:
|
||||
List[dict]: Updated dictionaries
|
||||
"""
|
||||
# logger.debug(f"Hello from {self.__class__.__name__} dictionary sample adjuster.")
|
||||
return [item.to_sub_dict() for item in self.submission_sample_associations]
|
||||
# def adjust_to_dict_samples(self, backup: bool = False) -> List[dict]:
|
||||
# """
|
||||
# Updates sample dictionaries with custom values
|
||||
#
|
||||
# Args:
|
||||
# backup (bool, optional): Whether to perform backup. Defaults to False.
|
||||
#
|
||||
# Returns:
|
||||
# List[dict]: Updated dictionaries
|
||||
# """
|
||||
# # logger.debug(f"Hello from {self.__class__.__name__} dictionary sample adjuster.")
|
||||
# return [item.to_sub_dict() for item in self.submission_sample_associations]
|
||||
|
||||
@classmethod
|
||||
def get_details_template(cls, base_dict: dict) -> Tuple[dict, Template]:
|
||||
|
||||
@@ -53,7 +53,6 @@ class SheetParser(object):
|
||||
self.parse_samples()
|
||||
self.parse_equipment()
|
||||
self.parse_tips()
|
||||
# self.finalize_parse()
|
||||
# logger.debug(f"Parser.sub after info scrape: {pformat(self.sub)}")
|
||||
|
||||
def parse_info(self):
|
||||
@@ -63,6 +62,8 @@ class SheetParser(object):
|
||||
parser = InfoParser(xl=self.xl, submission_type=self.submission_type, sub_object=self.sub_object)
|
||||
info = parser.parse_info()
|
||||
self.info_map = parser.map
|
||||
# NOTE: in order to accommodate generic submission types we have to check for the type in the excel sheet and
|
||||
# rerun accordingly
|
||||
try:
|
||||
check = info['submission_type']['value'] not in [None, "None", "", " "]
|
||||
except KeyError:
|
||||
@@ -78,13 +79,15 @@ class SheetParser(object):
|
||||
else:
|
||||
self.submission_type = RSLNamer.retrieve_submission_type(filename=self.filepath)
|
||||
self.parse_info()
|
||||
for k, v in info.items():
|
||||
match k:
|
||||
# NOTE: exclude samples.
|
||||
case "sample":
|
||||
continue
|
||||
case _:
|
||||
self.sub[k] = v
|
||||
[self.sub.__setitem__(k, v) for k, v in info.items()]
|
||||
# for k, v in info.items():
|
||||
# match k:
|
||||
# # NOTE: exclude samples.
|
||||
# case "sample":
|
||||
# logger.debug(f"Sample found: {k}: {v}")
|
||||
# continue
|
||||
# case _:
|
||||
# self.sub[k] = v
|
||||
|
||||
def parse_reagents(self, extraction_kit: str | None = None):
|
||||
"""
|
||||
@@ -105,7 +108,7 @@ class SheetParser(object):
|
||||
Calls sample parser to pull info from the excel sheet
|
||||
"""
|
||||
parser = SampleParser(xl=self.xl, submission_type=self.submission_type)
|
||||
self.sub['samples'] = parser.reconcile_samples()
|
||||
self.sub['samples'] = parser.parse_samples()
|
||||
|
||||
def parse_equipment(self):
|
||||
"""
|
||||
@@ -145,29 +148,29 @@ class SheetParser(object):
|
||||
PydSubmission: output pydantic model
|
||||
"""
|
||||
# logger.debug(f"Submission dictionary coming into 'to_pydantic':\n{pformat(self.sub)}")
|
||||
pyd_dict = copy(self.sub)
|
||||
pyd_dict['samples'] = [PydSample(**sample) for sample in self.sub['samples']]
|
||||
# pyd_dict = copy(self.sub)
|
||||
# self.sub['samples'] = [PydSample(**sample) for sample in self.sub['samples']]
|
||||
# logger.debug(f"Reagents: {pformat(self.sub['reagents'])}")
|
||||
pyd_dict['reagents'] = [PydReagent(**reagent) for reagent in self.sub['reagents']]
|
||||
# self.sub['reagents'] = [PydReagent(**reagent) for reagent in self.sub['reagents']]
|
||||
# logger.debug(f"Equipment: {self.sub['equipment']}")
|
||||
try:
|
||||
check = bool(self.sub['equipment'])
|
||||
except TypeError:
|
||||
check = False
|
||||
if check:
|
||||
pyd_dict['equipment'] = [PydEquipment(**equipment) for equipment in self.sub['equipment']]
|
||||
else:
|
||||
pyd_dict['equipment'] = None
|
||||
try:
|
||||
check = bool(self.sub['tips'])
|
||||
except TypeError:
|
||||
check = False
|
||||
if check:
|
||||
pyd_dict['tips'] = [PydTips(**tips) for tips in self.sub['tips']]
|
||||
else:
|
||||
pyd_dict['tips'] = None
|
||||
psm = PydSubmission(filepath=self.filepath, run_custom=True, **pyd_dict)
|
||||
return psm
|
||||
# try:
|
||||
# check = bool(self.sub['equipment'])
|
||||
# except TypeError:
|
||||
# check = False
|
||||
# if check:
|
||||
# self.sub['equipment'] = [PydEquipment(**equipment) for equipment in self.sub['equipment']]
|
||||
# else:
|
||||
# self.sub['equipment'] = None
|
||||
# try:
|
||||
# check = bool(self.sub['tips'])
|
||||
# except TypeError:
|
||||
# check = False
|
||||
# if check:
|
||||
# self.sub['tips'] = [PydTips(**tips) for tips in self.sub['tips']]
|
||||
# else:
|
||||
# self.sub['tips'] = None
|
||||
return PydSubmission(filepath=self.filepath, run_custom=True, **self.sub)
|
||||
# return psm
|
||||
|
||||
|
||||
class InfoParser(object):
|
||||
@@ -287,7 +290,7 @@ class ReagentParser(object):
|
||||
extraction_kit (str): Extraction kit used.
|
||||
sub_object (BasicSubmission | None, optional): Submission object holding methods. Defaults to None.
|
||||
"""
|
||||
# logger.debug("\n\nHello from ReagentParser!\n\n")
|
||||
logger.debug("\n\nHello from ReagentParser!\n\n")
|
||||
if isinstance(submission_type, str):
|
||||
submission_type = SubmissionType.query(name=submission_type)
|
||||
self.submission_type_obj = submission_type
|
||||
@@ -382,7 +385,7 @@ class SampleParser(object):
|
||||
sample_map (dict | None, optional): Locations in database where samples are found. Defaults to None.
|
||||
sub_object (BasicSubmission | None, optional): Submission object holding methods. Defaults to None.
|
||||
"""
|
||||
# logger.debug("\n\nHello from SampleParser!\n\n")
|
||||
logger.debug("\n\nHello from SampleParser!\n\n")
|
||||
self.samples = []
|
||||
self.xl = xl
|
||||
if isinstance(submission_type, str):
|
||||
@@ -477,49 +480,50 @@ class SampleParser(object):
|
||||
lookup_samples.append(self.samp_object.parse_sample(row_dict))
|
||||
return lookup_samples
|
||||
|
||||
def parse_samples(self) -> Tuple[Report | None, List[dict] | List[PydSample]]:
|
||||
"""
|
||||
Parse merged platemap/lookup info into dicts/samples
|
||||
# def parse_samples(self) -> Tuple[Report | None, List[dict] | List[PydSample]]:
|
||||
# """
|
||||
# Parse merged platemap/lookup info into dicts/samples
|
||||
#
|
||||
# Returns:
|
||||
# List[dict]|List[models.BasicSample]: List of samples
|
||||
# """
|
||||
# result = None
|
||||
# new_samples = []
|
||||
# # logger.debug(f"Starting samples: {pformat(self.samples)}")
|
||||
# for sample in self.samples:
|
||||
# translated_dict = {}
|
||||
# for k, v in sample.items():
|
||||
# match v:
|
||||
# case dict():
|
||||
# v = None
|
||||
# case float():
|
||||
# v = convert_nans_to_nones(v)
|
||||
# case _:
|
||||
# v = v
|
||||
# translated_dict[k] = convert_nans_to_nones(v)
|
||||
# translated_dict['sample_type'] = f"{self.submission_type} Sample"
|
||||
# translated_dict = self.sub_object.parse_samples(translated_dict)
|
||||
# translated_dict = self.samp_object.parse_sample(translated_dict)
|
||||
# # logger.debug(f"Here is the output of the custom parser:\n{translated_dict}")
|
||||
# new_samples.append(PydSample(**translated_dict))
|
||||
# return result, new_samples
|
||||
|
||||
Returns:
|
||||
List[dict]|List[models.BasicSample]: List of samples
|
||||
"""
|
||||
result = None
|
||||
new_samples = []
|
||||
# logger.debug(f"Starting samples: {pformat(self.samples)}")
|
||||
for sample in self.samples:
|
||||
translated_dict = {}
|
||||
for k, v in sample.items():
|
||||
match v:
|
||||
case dict():
|
||||
v = None
|
||||
case float():
|
||||
v = convert_nans_to_nones(v)
|
||||
case _:
|
||||
v = v
|
||||
translated_dict[k] = convert_nans_to_nones(v)
|
||||
translated_dict['sample_type'] = f"{self.submission_type} Sample"
|
||||
translated_dict = self.sub_object.parse_samples(translated_dict)
|
||||
translated_dict = self.samp_object.parse_sample(translated_dict)
|
||||
# logger.debug(f"Here is the output of the custom parser:\n{translated_dict}")
|
||||
new_samples.append(PydSample(**translated_dict))
|
||||
return result, new_samples
|
||||
|
||||
def reconcile_samples(self) -> Generator[dict, None, None]:
|
||||
def parse_samples(self) -> Generator[dict, None, None]:
|
||||
"""
|
||||
Merges sample info from lookup table and plate map.
|
||||
|
||||
Returns:
|
||||
List[dict]: Reconciled samples
|
||||
"""
|
||||
if self.plate_map_samples is None or self.lookup_samples is None:
|
||||
if not self.plate_map_samples or not self.lookup_samples:
|
||||
logger.error(f"No separate samples, returning")
|
||||
self.samples = self.lookup_samples or self.plate_map_samples
|
||||
return
|
||||
merge_on_id = self.sample_info_map['lookup_table']['merge_on_id']
|
||||
plate_map_samples = sorted(copy(self.plate_map_samples), key=lambda d: d['id'])
|
||||
lookup_samples = sorted(copy(self.lookup_samples), key=lambda d: d[merge_on_id])
|
||||
print(pformat(plate_map_samples))
|
||||
print(pformat(lookup_samples))
|
||||
# print(pformat(plate_map_samples))
|
||||
# print(pformat(lookup_samples))
|
||||
for ii, psample in enumerate(plate_map_samples):
|
||||
try:
|
||||
check = psample['id'] == lookup_samples[ii][merge_on_id]
|
||||
@@ -563,6 +567,7 @@ class EquipmentParser(object):
|
||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||
submission_type (str | SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
||||
"""
|
||||
logger.debug("\n\nHello from EquipmentParser!\n\n")
|
||||
if isinstance(submission_type, str):
|
||||
submission_type = SubmissionType.query(name=submission_type)
|
||||
self.submission_type = submission_type
|
||||
@@ -649,6 +654,7 @@ class TipParser(object):
|
||||
xl (Workbook): Openpyxl workbook from submitted excel file.
|
||||
submission_type (str | SubmissionType): Type of submission expected (Wastewater, Bacterial Culture, etc.)
|
||||
"""
|
||||
logger.debug("\n\nHello from TipParser!\n\n")
|
||||
if isinstance(submission_type, str):
|
||||
submission_type = SubmissionType.query(name=submission_type)
|
||||
self.submission_type = submission_type
|
||||
|
||||
@@ -9,6 +9,7 @@ from datetime import date, datetime, timedelta
|
||||
from dateutil.parser import parse
|
||||
from dateutil.parser import ParserError
|
||||
from typing import List, Tuple, Literal
|
||||
from types import GeneratorType
|
||||
from . import RSLNamer
|
||||
from pathlib import Path
|
||||
from tools import check_not_nan, convert_nans_to_nones, Report, Result
|
||||
@@ -395,16 +396,32 @@ class PydSubmission(BaseModel, extra='allow'):
|
||||
submission_category: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
|
||||
comment: dict | None = Field(default=dict(value="", missing=True), validate_default=True)
|
||||
reagents: List[dict] | List[PydReagent] = []
|
||||
samples: List[PydSample]
|
||||
samples: List[PydSample] | Generator
|
||||
equipment: List[PydEquipment] | None = []
|
||||
cost_centre: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
|
||||
contact: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
|
||||
tips: List[PydTips] | None =[]
|
||||
|
||||
@field_validator("tips", mode="before")
|
||||
@classmethod
|
||||
def expand_tips(cls, value):
|
||||
# print(f"\n{type(value)}\n")
|
||||
if isinstance(value, dict):
|
||||
value = value['value']
|
||||
if isinstance(value, Generator):
|
||||
logger.debug("We have a generator")
|
||||
return [PydTips(**tips) for tips in value]
|
||||
if not value:
|
||||
return []
|
||||
return value
|
||||
|
||||
@field_validator('equipment', mode='before')
|
||||
@classmethod
|
||||
def convert_equipment_dict(cls, value):
|
||||
# logger.debug(f"Equipment: {value}")
|
||||
|
||||
if isinstance(value, Generator):
|
||||
logger.debug("We have a generator")
|
||||
return [PydEquipment(**equipment) for equipment in value]
|
||||
if isinstance(value, dict):
|
||||
return value['value']
|
||||
return value
|
||||
@@ -580,6 +597,24 @@ class PydSubmission(BaseModel, extra='allow'):
|
||||
value['value'] = values.data['submission_type']['value']
|
||||
return value
|
||||
|
||||
@field_validator("reagents", mode="before")
|
||||
@classmethod
|
||||
def expand_reagents(cls, value):
|
||||
# print(f"\n{type(value)}\n")
|
||||
if isinstance(value, Generator):
|
||||
logger.debug("We have a generator")
|
||||
return [PydReagent(**reagent) for reagent in value]
|
||||
return value
|
||||
|
||||
@field_validator("samples", mode="before")
|
||||
@classmethod
|
||||
def expand_samples(cls, value):
|
||||
# print(f"\n{type(value)}\n")
|
||||
if isinstance(value, Generator):
|
||||
logger.debug("We have a generator")
|
||||
return [PydSample(**sample) for sample in value]
|
||||
return value
|
||||
|
||||
@field_validator("samples")
|
||||
@classmethod
|
||||
def assign_ids(cls, value):
|
||||
|
||||
@@ -93,15 +93,16 @@ class SubmissionDetails(QDialog):
|
||||
Args:
|
||||
sample (str): Submitter Id of the sample.
|
||||
"""
|
||||
logger.debug(f"Details: {sample}")
|
||||
if isinstance(sample, str):
|
||||
sample = BasicSample.query(submitter_id=sample)
|
||||
base_dict = sample.to_sub_dict(full_data=True)
|
||||
exclude = ['submissions', 'excluded', 'colour', 'tooltip']
|
||||
try:
|
||||
base_dict['excluded'] += exclude
|
||||
except KeyError:
|
||||
base_dict['excluded'] = exclude
|
||||
template = sample.get_details_template(base_dict=base_dict)
|
||||
# try:
|
||||
# base_dict['excluded'] += exclude
|
||||
# except KeyError:
|
||||
base_dict['excluded'] = exclude
|
||||
template = sample.get_details_template()
|
||||
template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0])
|
||||
with open(template_path.joinpath("css", "styles.css"), "r") as f:
|
||||
css = f.read()
|
||||
@@ -158,6 +159,8 @@ class SubmissionDetails(QDialog):
|
||||
# logger.debug(f"Submission_details: {pformat(self.base_dict)}")
|
||||
# logger.debug(f"User is power user: {is_power_user()}")
|
||||
self.html = self.template.render(sub=self.base_dict, signing_permission=is_power_user(), css=css)
|
||||
with open("test.html", "w") as f:
|
||||
f.write(self.html)
|
||||
self.webview.setHtml(self.html)
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<h3><u>Reagents:</u></h3>
|
||||
<p>{% for item in sub['reagents'] %}
|
||||
<b>{{ item['role'] }}</b>: <a class="data-link" id="{{ item['lot'] }}">{{ item['lot'] }} (EXP: {{ item['expiry'] }})</a><br>
|
||||
<b>{{ item['role'] }}</b>: <a class="data-link reagent" id="{{ item['lot'] }}">{{ item['lot'] }} (EXP: {{ item['expiry'] }})</a><br>
|
||||
{% endfor %}</p>
|
||||
|
||||
{% if sub['equipment'] %}
|
||||
@@ -38,7 +38,7 @@
|
||||
{% if sub['samples'] %}
|
||||
<h3><u>Samples:</u></h3>
|
||||
<p>{% for item in sub['samples'] %}
|
||||
<b>{{ item['well'] }}:</b><a class="data-link" id="{{ item['submitter_id'] }}_alpha">{% if item['organism'] %} {{ item['name'] }} - ({{ item['organism']|replace('\n\t', '<br> ') }}){% else %} {{ item['name']|replace('\n\t', '<br> ') }}{% endif %}</a><br>
|
||||
<b>{{ item['well'] }}:</b><a class="data-link sample" id="{{ item['submitter_id'] }}">{% if item['organism'] %} {{ item['name'] }} - ({{ item['organism']|replace('\n\t', '<br> ') }}){% else %} {{ item['name']|replace('\n\t', '<br> ') }}{% endif %}</a><br>
|
||||
{% endfor %}</p>
|
||||
{% endif %}
|
||||
|
||||
@@ -83,22 +83,25 @@
|
||||
<script>
|
||||
{% block script %}
|
||||
{{ super() }}
|
||||
{% for sample in sub['samples'] %}
|
||||
document.getElementById("{{ sample['submitter_id'] }}").addEventListener("click", function(){
|
||||
backend.sample_details("{{ sample['submitter_id'] }}");
|
||||
});
|
||||
document.getElementById("{{ sample['submitter_id'] }}_alpha").addEventListener("click", function(){
|
||||
backend.sample_details("{{ sample['submitter_id'] }}");
|
||||
});
|
||||
{% endfor %}
|
||||
{% for reagent in sub['reagents'] %}
|
||||
document.getElementById("{{ reagent['lot'] }}").addEventListener("click", function(){
|
||||
backend.reagent_details("{{ reagent['lot'] }}", "{{ sub['extraction_kit'] }}");
|
||||
});
|
||||
{% endfor %}
|
||||
|
||||
document.getElementById("sign_btn").addEventListener("click", function(){
|
||||
backend.sign_off("{{ sub['plate_number'] }}");
|
||||
});
|
||||
var sampleSelection = document.getElementsByClassName('sample');
|
||||
|
||||
for(let i = 0; i < sampleSelection.length; i++) {
|
||||
sampleSelection[i].addEventListener("click", function() {
|
||||
backend.sample_details(sampleSelection[i].id);
|
||||
})
|
||||
}
|
||||
|
||||
var reagentSelection = document.getElementsByClassName('reagent');
|
||||
|
||||
for(let i = 0; i < reagentSelection.length; i++) {
|
||||
reagentSelection[i].addEventListener("click", function() {
|
||||
backend.reagent_details(reagentSelection[i].id, "{{ sub['extraction_kit'] }}");
|
||||
})
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="gallery" style="display: grid;grid-template-columns: repeat({{ PLATE_COLUMNS }}, 7.5vw);grid-template-rows: repeat({{ PLATE_ROWS }}, 7.5vw);grid-gap: 2px;">
|
||||
{% for sample in samples %}
|
||||
<div class="well data-link" id="{{sample['submitter_id']}}" style="background-color: {{sample['background_color']}};
|
||||
<div class="well data-link sample" id="{{sample['submitter_id']}}" style="background-color: {{sample['background_color']}};
|
||||
border: 1px solid #000;
|
||||
padding: 20px;
|
||||
grid-column-start: {{sample['column']}};
|
||||
|
||||
Reference in New Issue
Block a user