Mid documentation and code clean-up.

This commit is contained in:
lwark
2024-07-03 09:50:44 -05:00
parent 49c010ef49
commit 12ca3157e5
11 changed files with 140 additions and 99 deletions

View File

@@ -1,5 +1,6 @@
## 202406.04 ## 202406.04
- Exported submission details will now be in docx format.
- Adding in tips to Equipment usage. - Adding in tips to Equipment usage.
- New WastewaterArticAssociation will track previously missed sample info. - New WastewaterArticAssociation will track previously missed sample info.

View File

@@ -112,7 +112,7 @@ class BaseClass(Base):
@classmethod @classmethod
def execute_query(cls, query: Query = None, model=None, limit: int = 0, **kwargs) -> Any | List[Any]: def execute_query(cls, query: Query = None, model=None, limit: int = 0, **kwargs) -> Any | List[Any]:
""" """
Execute sqlalchemy query. Execute sqlalchemy query with relevant defaults.
Args: Args:
model (Any, optional): model to be queried. Defaults to None model (Any, optional): model to be queried. Defaults to None
@@ -137,6 +137,7 @@ class BaseClass(Base):
except (ArgumentError, AttributeError) as e: except (ArgumentError, AttributeError) as e:
logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.") logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.")
if k in singles: if k in singles:
logger.warning(f"{k} is in singles. Returning only one value.")
limit = 1 limit = 1
with query.session.no_autoflush: with query.session.no_autoflush:
match limit: match limit:
@@ -165,8 +166,8 @@ class ConfigItem(BaseClass):
Key:JSON objects to store config settings in database. Key:JSON objects to store config settings in database.
""" """
id = Column(INTEGER, primary_key=True) id = Column(INTEGER, primary_key=True)
key = Column(String(32)) key = Column(String(32)) #: Name of the configuration item.
value = Column(JSON) value = Column(JSON) #: Value associated with the config item.
def __repr__(self): def __repr__(self):
return f"ConfigItem({self.key} : {self.value})" return f"ConfigItem({self.key} : {self.value})"

View File

@@ -55,7 +55,7 @@ class ControlType(BaseClass):
def get_subtypes(self, mode: str) -> List[str]: def get_subtypes(self, mode: str) -> List[str]:
""" """
Get subtypes associated with this controltype Get subtypes associated with this controltype (currently used only for Kraken)
Args: Args:
mode (str): analysis mode name mode (str): analysis mode name
@@ -140,15 +140,11 @@ class Control(BaseClass):
kraken = {} kraken = {}
# logger.debug("calculating kraken count total to use in percentage") # logger.debug("calculating kraken count total to use in percentage")
kraken_cnt_total = sum([kraken[item]['kraken_count'] for item in kraken]) kraken_cnt_total = sum([kraken[item]['kraken_count'] for item in kraken])
new_kraken = [] # logger.debug("Creating new kraken.")
for item in kraken: new_kraken = [dict(name=item, kraken_count=kraken[item]['kraken_count'], kraken_percent="{0:.0%}".format(kraken[item]['kraken_count'] / kraken_cnt_total)) for item in kraken]
# logger.debug("calculating kraken percent (overwrites what's already been scraped)")
kraken_percent = kraken[item]['kraken_count'] / kraken_cnt_total
new_kraken.append({'name': item, 'kraken_count': kraken[item]['kraken_count'],
'kraken_percent': "{0:.0%}".format(kraken_percent)})
new_kraken = sorted(new_kraken, key=itemgetter('kraken_count'), reverse=True) new_kraken = sorted(new_kraken, key=itemgetter('kraken_count'), reverse=True)
# logger.debug("setting targets") # logger.debug("setting targets")
if self.controltype.targets == []: if not self.controltype.targets:
targets = ["None"] targets = ["None"]
else: else:
targets = self.controltype.targets targets = self.controltype.targets

View File

@@ -70,6 +70,7 @@ kittypes_processes = Table(
extend_existing=True extend_existing=True
) )
# logger.debug("Table for TipRole/Tips relations")
tiproles_tips = Table( tiproles_tips = Table(
"_tiproles_tips", "_tiproles_tips",
Base.metadata, Base.metadata,
@@ -78,6 +79,7 @@ tiproles_tips = Table(
extend_existing=True extend_existing=True
) )
# logger.debug("Table for Process/TipRole relations")
process_tiprole = Table( process_tiprole = Table(
"_process_tiprole", "_process_tiprole",
Base.metadata, Base.metadata,
@@ -86,6 +88,7 @@ process_tiprole = Table(
extend_existing=True extend_existing=True
) )
# logger.debug("Table for Equipment/Tips relations")
equipment_tips = Table( equipment_tips = Table(
"_equipment_tips", "_equipment_tips",
Base.metadata, Base.metadata,
@@ -410,6 +413,7 @@ class Reagent(BaseClass):
except (TypeError, AttributeError) as e: except (TypeError, AttributeError) as e:
place_holder = date.today() place_holder = date.today()
logger.error(f"We got a type error setting {self.lot} expiry: {e}. setting to today for testing") logger.error(f"We got a type error setting {self.lot} expiry: {e}. setting to today for testing")
# NOTE: The notation for not having an expiry is 1970.1.1
if self.expiry.year == 1970: if self.expiry.year == 1970:
place_holder = "NA" place_holder = "NA"
else: else:
@@ -700,7 +704,13 @@ class SubmissionType(BaseClass):
output[item.equipment_role.name] = emap output[item.equipment_role.name] = emap
return output return output
def construct_tips_map(self): def construct_tips_map(self) -> dict:
"""
Constructs map of tips to excel cells.
Returns:
dict: Tip locations in the excel sheet.
"""
output = {} output = {}
for item in self.submissiontype_tiprole_associations: for item in self.submissiontype_tiprole_associations:
tmap = item.uses tmap = item.uses
@@ -1142,6 +1152,7 @@ class Equipment(BaseClass):
processes = [process for process in processes if extraction_kit in process.kit_types] processes = [process for process in processes if extraction_kit in process.kit_types]
case _: case _:
pass pass
# NOTE: Convert to strings
processes = [process.name for process in processes] processes = [process.name for process in processes]
assert all([isinstance(process, str) for process in processes]) assert all([isinstance(process, str) for process in processes])
if len(processes) == 0: if len(processes) == 0:
@@ -1193,7 +1204,8 @@ class Equipment(BaseClass):
return cls.execute_query(query=query, limit=limit) return cls.execute_query(query=query, limit=limit)
def to_pydantic(self, submission_type: SubmissionType, def to_pydantic(self, submission_type: SubmissionType,
extraction_kit: str | KitType | None = None) -> "PydEquipment": extraction_kit: str | KitType | None = None,
role: str = None) -> "PydEquipment":
""" """
Creates PydEquipment of this Equipment Creates PydEquipment of this Equipment
@@ -1206,7 +1218,7 @@ class Equipment(BaseClass):
""" """
from backend.validators.pydant import PydEquipment from backend.validators.pydant import PydEquipment
return PydEquipment( return PydEquipment(
processes=self.get_processes(submission_type=submission_type, extraction_kit=extraction_kit), role=None, processes=self.get_processes(submission_type=submission_type, extraction_kit=extraction_kit), role=role,
**self.to_dict(processes=False)) **self.to_dict(processes=False))
@classmethod @classmethod

View File

@@ -17,7 +17,6 @@ orgs_contacts = Table(
Base.metadata, Base.metadata,
Column("org_id", INTEGER, ForeignKey("_organization.id")), Column("org_id", INTEGER, ForeignKey("_organization.id")),
Column("contact_id", INTEGER, ForeignKey("_contact.id")), Column("contact_id", INTEGER, ForeignKey("_contact.id")),
# __table_args__ = {'extend_existing': True}
extend_existing=True extend_existing=True
) )

View File

@@ -32,7 +32,7 @@ from pathlib import Path
from jinja2.exceptions import TemplateNotFound from jinja2.exceptions import TemplateNotFound
from jinja2 import Template from jinja2 import Template
from docxtpl import InlineImage from docxtpl import InlineImage
from io import BytesIO from docx.shared import Inches
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -229,6 +229,15 @@ class BasicSubmission(BaseClass):
@classmethod @classmethod
def finalize_details(cls, input_dict: dict) -> dict: def finalize_details(cls, input_dict: dict) -> dict:
"""
Make final adjustments to the details dictionary before display.
Args:
input_dict (dict): Incoming dictionary.
Returns:
dict: Final details dictionary.
"""
del input_dict['id'] del input_dict['id']
return input_dict return input_dict
@@ -610,6 +619,12 @@ class BasicSubmission(BaseClass):
@classmethod @classmethod
def get_regex(cls) -> str: def get_regex(cls) -> str:
"""
Dummy for inheritence.
Returns:
str: Regex for submission type.
"""
return cls.construct_regex() return cls.construct_regex()
# Polymorphic functions # Polymorphic functions
@@ -733,7 +748,16 @@ class BasicSubmission(BaseClass):
@classmethod @classmethod
def custom_docx_writer(cls, input_dict:dict, tpl_obj=None): def custom_docx_writer(cls, input_dict:dict, tpl_obj=None):
"""
Adds custom fields to docx template writer for exported details.
Args:
input_dict (dict): Incoming default dictionary.
tpl_obj (_type_, optional): Template object. Defaults to None.
Returns:
dict: Dictionary with information added.
"""
return input_dict return input_dict
@classmethod @classmethod
@@ -785,6 +809,7 @@ class BasicSubmission(BaseClass):
repeat = "" repeat = ""
outstr = re.sub(r"(-\dR)\d?", rf"\1 {repeat}", outstr).replace(" ", "") outstr = re.sub(r"(-\dR)\d?", rf"\1 {repeat}", outstr).replace(" ", "")
abb = cls.get_default_info('abbreviation') abb = cls.get_default_info('abbreviation')
outstr = re.sub(rf"RSL{abb}", rf"RSL-{abb}", outstr)
return re.sub(rf"{abb}(\d)", rf"{abb}-\1", outstr) return re.sub(rf"{abb}(\d)", rf"{abb}-\1", outstr)
@classmethod @classmethod
@@ -924,7 +949,7 @@ class BasicSubmission(BaseClass):
if submission_type is not None: if submission_type is not None:
model = cls.find_polymorphic_subclass(polymorphic_identity=submission_type) model = cls.find_polymorphic_subclass(polymorphic_identity=submission_type)
elif len(kwargs) > 0: elif len(kwargs) > 0:
# find the subclass containing the relevant attributes # NOTE: find the subclass containing the relevant attributes
# logger.debug(f"Attributes for search: {kwargs}") # logger.debug(f"Attributes for search: {kwargs}")
model = cls.find_polymorphic_subclass(attrs=kwargs) model = cls.find_polymorphic_subclass(attrs=kwargs)
else: else:
@@ -1241,7 +1266,7 @@ class BacterialCulture(BasicSubmission):
plate_map (dict | None, optional): _description_. Defaults to None. plate_map (dict | None, optional): _description_. Defaults to None.
Returns: Returns:
dict: _description_ dict: Updated dictionary.
""" """
from . import ControlType from . import ControlType
input_dict = super().finalize_parse(input_dict, xl, info_map) input_dict = super().finalize_parse(input_dict, xl, info_map)
@@ -1495,7 +1520,17 @@ class Wastewater(BasicSubmission):
# self.report.add_result(Result(msg=f"We added PCR info to {sub.rsl_plate_num}.", status='Information')) # self.report.add_result(Result(msg=f"We added PCR info to {sub.rsl_plate_num}.", status='Information'))
@classmethod @classmethod
def custom_docx_writer(cls, input_dict:dict, tpl_obj=None): def custom_docx_writer(cls, input_dict:dict, tpl_obj=None) -> dict:
"""
Adds custom fields to docx template writer for exported details. Extends parent.
Args:
input_dict (dict): Incoming default dictionary.
tpl_obj (_type_, optional): Template object. Defaults to None.
Returns:
dict: Dictionary with information added.
"""
from backend.excel.writer import DocxWriter from backend.excel.writer import DocxWriter
input_dict = super().custom_docx_writer(input_dict) input_dict = super().custom_docx_writer(input_dict)
well_24 = [] well_24 = []
@@ -1611,6 +1646,7 @@ class WastewaterArtic(BasicSubmission):
instr = re.sub(r"^(\d{6})", f"RSL-AR-\\1", instr) instr = re.sub(r"^(\d{6})", f"RSL-AR-\\1", instr)
# logger.debug(f"name coming out of Artic namer: {instr}") # logger.debug(f"name coming out of Artic namer: {instr}")
outstr = super().enforce_name(instr=instr, data=data) outstr = super().enforce_name(instr=instr, data=data)
outstr = outstr.replace("RSLAR", "RSL-AR")
return outstr return outstr
@classmethod @classmethod
@@ -1788,7 +1824,6 @@ class WastewaterArtic(BasicSubmission):
input_excel = super().custom_info_writer(input_excel, info, backup) input_excel = super().custom_info_writer(input_excel, info, backup)
# logger.debug(f"Info:\n{pformat(info)}") # logger.debug(f"Info:\n{pformat(info)}")
# NOTE: check for source plate information # NOTE: check for source plate information
# check = 'source_plates' in info.keys() and info['source_plates'] is not None
if check_key_or_attr(key='source_plates', interest=info, check_none=True): if check_key_or_attr(key='source_plates', interest=info, check_none=True):
worksheet = input_excel['First Strand List'] worksheet = input_excel['First Strand List']
start_row = 8 start_row = 8
@@ -1805,15 +1840,11 @@ class WastewaterArtic(BasicSubmission):
except TypeError: except TypeError:
pass pass
# NOTE: check for gel information # NOTE: check for gel information
# check = 'gel_info' in info.keys() and info['gel_info']['value'] is not None
if check_key_or_attr(key='gel_info', interest=info, check_none=True): if check_key_or_attr(key='gel_info', interest=info, check_none=True):
# logger.debug(f"Gel info check passed.") # logger.debug(f"Gel info check passed.")
# if info['gel_info'] is not None:
# logger.debug(f"Gel info not none.")
# NOTE: print json field gel results to Egel results # NOTE: print json field gel results to Egel results
worksheet = input_excel['Egel results'] worksheet = input_excel['Egel results']
# TODO: Move all this into a seperate function? # TODO: Move all this into a seperate function?
#
start_row = 21 start_row = 21
start_column = 15 start_column = 15
for row, ki in enumerate(info['gel_info']['value'], start=1): for row, ki in enumerate(info['gel_info']['value'], start=1):
@@ -1831,13 +1862,11 @@ class WastewaterArtic(BasicSubmission):
worksheet.cell(row=row, column=column, value=kj['value']) worksheet.cell(row=row, column=column, value=kj['value'])
except AttributeError: except AttributeError:
logger.error(f"Failed {kj['name']} with value {kj['value']} to row {row}, column {column}") logger.error(f"Failed {kj['name']} with value {kj['value']} to row {row}, column {column}")
# check = 'gel_image' in info.keys() and info['gel_image']['value'] is not None if check_key_or_attr(key='gel_image_path', interest=info, check_none=True):
if check_key_or_attr(key='gel_image', interest=info, check_none=True):
# if info['gel_image'] is not None:
worksheet = input_excel['Egel results'] worksheet = input_excel['Egel results']
# logger.debug(f"We got an image: {info['gel_image']}") # logger.debug(f"We got an image: {info['gel_image']}")
with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped: with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped:
z = zipped.extract(info['gel_image']['value'], Path(TemporaryDirectory().name)) z = zipped.extract(info['gel_image_path']['value'], Path(TemporaryDirectory().name))
img = OpenpyxlImage(z) img = OpenpyxlImage(z)
img.height = 400 # insert image height in pixels as float or int (e.g. 305.5) img.height = 400 # insert image height in pixels as float or int (e.g. 305.5)
img.width = 600 img.width = 600
@@ -1860,36 +1889,16 @@ class WastewaterArtic(BasicSubmission):
base_dict['excluded'] += ['gel_info', 'gel_image', 'headers', "dna_core_submission_number", "source_plates", base_dict['excluded'] += ['gel_info', 'gel_image', 'headers', "dna_core_submission_number", "source_plates",
"gel_controls, gel_image_path"] "gel_controls, gel_image_path"]
base_dict['DNA Core ID'] = base_dict['dna_core_submission_number'] base_dict['DNA Core ID'] = base_dict['dna_core_submission_number']
# check = 'gel_info' in base_dict.keys() and base_dict['gel_info'] is not None
if check_key_or_attr(key='gel_info', interest=base_dict, check_none=True): if check_key_or_attr(key='gel_info', interest=base_dict, check_none=True):
headers = [item['name'] for item in base_dict['gel_info'][0]['values']] headers = [item['name'] for item in base_dict['gel_info'][0]['values']]
base_dict['headers'] = [''] * (4 - len(headers)) base_dict['headers'] = [''] * (4 - len(headers))
base_dict['headers'] += headers base_dict['headers'] += headers
# logger.debug(f"Gel info: {pformat(base_dict['headers'])}") # logger.debug(f"Gel info: {pformat(base_dict['headers'])}")
# check = 'gel_image' in base_dict.keys() and base_dict['gel_image'] is not None
if check_key_or_attr(key='gel_image_path', interest=base_dict, check_none=True): if check_key_or_attr(key='gel_image_path', interest=base_dict, check_none=True):
with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped: with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped:
base_dict['gel_image'] = base64.b64encode(zipped.read(base_dict['gel_image_path'])).decode('utf-8') base_dict['gel_image'] = base64.b64encode(zipped.read(base_dict['gel_image_path'])).decode('utf-8')
return base_dict, template return base_dict, template
# 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.")
# output = []
#
# for assoc in self.submission_sample_associations:
# dicto = assoc.to_sub_dict()
# output.append(dicto)
# return output
def custom_context_events(self) -> dict: def custom_context_events(self) -> dict:
""" """
Creates dictionary of str:function to be passed to context menu. Extends parent Creates dictionary of str:function to be passed to context menu. Extends parent
@@ -1902,6 +1911,13 @@ class WastewaterArtic(BasicSubmission):
return events return events
def set_attribute(self, key: str, value): def set_attribute(self, key: str, value):
"""
Performs custom attribute setting based on values. Extends parent
Args:
key (str): name of attribute
value (_type_): value of attribute
"""
super().set_attribute(key=key, value=value) super().set_attribute(key=key, value=value)
if key == 'gel_info': if key == 'gel_info':
if len(self.gel_info) > 3: if len(self.gel_info) > 3:
@@ -1938,7 +1954,17 @@ class WastewaterArtic(BasicSubmission):
self.save() self.save()
@classmethod @classmethod
def custom_docx_writer(cls, input_dict:dict, tpl_obj=None): def custom_docx_writer(cls, input_dict:dict, tpl_obj=None) -> dict:
"""
Adds custom fields to docx template writer for exported details.
Args:
input_dict (dict): Incoming default dictionary/
tpl_obj (_type_, optional): Template object. Defaults to None.
Returns:
dict: Dictionary with information added.
"""
input_dict = super().custom_docx_writer(input_dict) input_dict = super().custom_docx_writer(input_dict)
if check_key_or_attr(key='gel_image_path', interest=input_dict, check_none=True): if check_key_or_attr(key='gel_image_path', interest=input_dict, check_none=True):
with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped: with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped:
@@ -1946,12 +1972,11 @@ class WastewaterArtic(BasicSubmission):
with tempfile.TemporaryFile(mode="wb", suffix=".jpg", delete=False) as tmp: with tempfile.TemporaryFile(mode="wb", suffix=".jpg", delete=False) as tmp:
tmp.write(img) tmp.write(img)
logger.debug(f"Tempfile: {tmp.name}") logger.debug(f"Tempfile: {tmp.name}")
img = InlineImage(tpl_obj, image_descriptor=tmp.name)#, width=5.5)#, height=400) img = InlineImage(tpl_obj, image_descriptor=tmp.name, width=Inches(5.5))#, width=5.5)#, height=400)
input_dict['gel_image'] = img input_dict['gel_image'] = img
return input_dict return input_dict
# Sample Classes # Sample Classes
class BasicSample(BaseClass): class BasicSample(BaseClass):
@@ -2009,6 +2034,12 @@ class BasicSample(BaseClass):
@classmethod @classmethod
def timestamps(cls) -> List[str]: def timestamps(cls) -> List[str]:
"""
Constructs a list of all attributes stored as SQL Timestamps
Returns:
List[str]: Attribute list
"""
output = [item.name for item in cls.__table__.columns if isinstance(item.type, TIMESTAMP)] output = [item.name for item in cls.__table__.columns if isinstance(item.type, TIMESTAMP)]
if issubclass(cls, BasicSample) and not cls.__name__ == "BasicSample": if issubclass(cls, BasicSample) and not cls.__name__ == "BasicSample":
output += BasicSample.timestamps() output += BasicSample.timestamps()
@@ -2082,10 +2113,6 @@ class BasicSample(BaseClass):
logger.info(f"Recruiting model: {model}") logger.info(f"Recruiting model: {model}")
return model return model
@classmethod
def sql_enforcer(cls, pyd_sample: "PydSample"):
return pyd_sample
@classmethod @classmethod
def parse_sample(cls, input_dict: dict) -> dict: def parse_sample(cls, input_dict: dict) -> dict:
f""" f"""
@@ -2194,6 +2221,15 @@ class BasicSample(BaseClass):
# limit: int = 0, # limit: int = 0,
**kwargs **kwargs
) -> List[BasicSample]: ) -> List[BasicSample]:
"""
Allows for fuzzy search of samples. (Experimental)
Args:
sample_type (str | BasicSample | None, optional): Type of sample. Defaults to None.
Returns:
List[BasicSample]: List of samples that match kwarg search parameters.
"""
match sample_type: match sample_type:
case str(): case str():
model = cls.find_polymorphic_subclass(polymorphic_identity=sample_type) model = cls.find_polymorphic_subclass(polymorphic_identity=sample_type)
@@ -2220,26 +2256,20 @@ class BasicSample(BaseClass):
return [dict(label="Submitter ID", field="submitter_id")] return [dict(label="Submitter ID", field="submitter_id")]
@classmethod @classmethod
def samples_to_df(cls, sample_type: str | None | BasicSample = None, **kwargs): def samples_to_df(cls, sample_list:List[BasicSample], **kwargs) -> pd.DataFrame:
# def samples_to_df(cls, sample_type:str|None|BasicSample=None, searchables:dict={}): """
logger.debug(f"Checking {sample_type} with type {type(sample_type)}") Runs a fuzzy search and converts into a dataframe.
match sample_type:
case str(): Args:
model = BasicSample.find_polymorphic_subclass(polymorphic_identity=sample_type) sample_list (List[BasicSample]): List of samples to be parsed. Defaults to None.
case _:
try: Returns:
check = issubclass(sample_type, BasicSample) pd.DataFrame: Dataframe all samples
except TypeError: """
check = False if not isinstance(sample_list, list):
if check: sample_list = [sample_list]
model = sample_type
else:
model = cls
q_out = model.fuzzy_search(sample_type=sample_type, **kwargs)
if not isinstance(q_out, list):
q_out = [q_out]
try: try:
samples = [sample.to_sub_dict() for sample in q_out] samples = [sample.to_sub_dict() for sample in sample_list]
except TypeError as e: except TypeError as e:
logger.error(f"Couldn't find any samples with data: {kwargs}\nDue to {e}") logger.error(f"Couldn't find any samples with data: {kwargs}\nDue to {e}")
return None return None
@@ -2287,6 +2317,12 @@ class WastewaterSample(BasicSample):
@classmethod @classmethod
def get_default_info(cls, *args): def get_default_info(cls, *args):
"""
Returns default info for a model. Extends BaseClass method.
Returns:
dict | list | str: Output of key:value dict or single (list, str) desired variable
"""
dicto = super().get_default_info(*args) dicto = super().get_default_info(*args)
match dicto: match dicto:
case dict(): case dict():

View File

@@ -740,7 +740,10 @@ class PydSubmission(BaseModel, extra='allow'):
if tips is None: if tips is None:
continue continue
logger.debug(f"Converting tips: {tips} to sql.") logger.debug(f"Converting tips: {tips} to sql.")
association = tips.to_sql(submission=instance) try:
association = tips.to_sql(submission=instance)
except AttributeError:
continue
if association is not None and association not in instance.submission_tips_associations: if association is not None and association not in instance.submission_tips_associations:
# association.save() # association.save()
instance.submission_tips_associations.append(association) instance.submission_tips_associations.append(association)

View File

@@ -99,6 +99,7 @@ class RoleComboBox(QWidget):
self.check.setChecked(False) self.check.setChecked(False)
else: else:
self.check.setChecked(True) self.check.setChecked(True)
self.check.stateChanged.connect(self.toggle_checked)
self.box = QComboBox() self.box = QComboBox()
self.box.setMaximumWidth(200) self.box.setMaximumWidth(200)
self.box.setMinimumWidth(200) self.box.setMinimumWidth(200)
@@ -118,6 +119,7 @@ class RoleComboBox(QWidget):
self.layout.addWidget(self.box, 0, 2) self.layout.addWidget(self.box, 0, 2)
self.layout.addWidget(self.process, 0, 3) self.layout.addWidget(self.process, 0, 3)
self.setLayout(self.layout) self.setLayout(self.layout)
self.toggle_checked()
def update_processes(self): def update_processes(self):
""" """
@@ -176,3 +178,14 @@ class RoleComboBox(QWidget):
) )
except Exception as e: except Exception as e:
logger.error(f"Could create PydEquipment due to: {e}") logger.error(f"Could create PydEquipment due to: {e}")
def toggle_checked(self):
for widget in self.findChildren(QWidget):
match widget:
case QCheckBox():
continue
case _:
if self.check.isChecked():
widget.setEnabled(True)
else:
widget.setEnabled(False)

View File

@@ -51,7 +51,9 @@ class SearchBox(QDialog):
def update_data(self): def update_data(self):
fields = self.parse_form() fields = self.parse_form()
data = self.type.samples_to_df(sample_type=self.type, **fields) # data = self.type.samples_to_df(sample_type=self.type, **fields)
data = self.type.fuzzy_search(sample_type=self.type, **fields)
data = self.type.samples_to_df(sample_list=data)
# logger.debug(f"Data: {data}") # logger.debug(f"Data: {data}")
self.results.setData(df=data) self.results.setData(df=data)

View File

@@ -20,8 +20,6 @@ from PIL import Image
from typing import List from typing import List
from backend.excel.writer import DocxWriter from backend.excel.writer import DocxWriter
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -97,7 +95,7 @@ class SubmissionDetails(QDialog):
template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0]) template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0])
with open(template_path.joinpath("css", "styles.css"), "r") as f: with open(template_path.joinpath("css", "styles.css"), "r") as f:
css = f.read() css = f.read()
logger.debug(f"Submission_details: {pformat(self.base_dict)}") # logger.debug(f"Submission_details: {pformat(self.base_dict)}")
self.html = self.template.render(sub=self.base_dict, signing_permission=is_power_user(), css=css) self.html = self.template.render(sub=self.base_dict, signing_permission=is_power_user(), css=css)
self.webview.setHtml(self.html) self.webview.setHtml(self.html)
self.setWindowTitle(f"Submission Details - {submission.rsl_plate_num}") self.setWindowTitle(f"Submission Details - {submission.rsl_plate_num}")
@@ -118,30 +116,10 @@ class SubmissionDetails(QDialog):
writer = DocxWriter(base_dict=self.base_dict) writer = DocxWriter(base_dict=self.base_dict)
fname = select_save_file(obj=self, default_name=self.base_dict['plate_number'], extension="docx") fname = select_save_file(obj=self, default_name=self.base_dict['plate_number'], extension="docx")
writer.save(fname) writer.save(fname)
# image_io = BytesIO()
# temp_dir = Path(TemporaryDirectory().name)
# hti = Html2Image(output_path=temp_dir, size=(2400, 1500))
# temp_file = Path(TemporaryFile(dir=temp_dir, suffix=".png").name)
# screenshot = hti.screenshot(self.base_dict['platemap'], save_as=temp_file.name)
# export_map = Image.open(screenshot[0])
# export_map = export_map.convert('RGB')
# try:
# export_map.save(image_io, 'JPEG')
# except AttributeError:
# logger.error(f"No plate map found")
# self.base_dict['export_map'] = base64.b64encode(image_io.getvalue()).decode('utf-8')
# del self.base_dict['platemap']
# self.html2 = self.template.render(sub=self.base_dict)
try: try:
html_to_pdf(html=self.html, output_file=fname) html_to_pdf(html=self.html, output_file=fname)
except PermissionError as e: except PermissionError as e:
logger.error(f"Error saving pdf: {e}") logger.error(f"Error saving pdf: {e}")
# msg = QMessageBox()
# msg.setText("Permission Error")
# msg.setInformativeText(f"Looks like {fname.__str__()} is open.\nPlease close it and try again.")
# msg.setWindowTitle("Permission Error")
# msg.exec()
class SubmissionComment(QDialog): class SubmissionComment(QDialog):
""" """