diff --git a/CHANGELOG.md b/CHANGELOG.md index a865960..2b2a5d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## 202404.04 - Storing of default values in db rather than hardcoded. +- Removed usage of reportlab. PDF creation handled by PyQt6 +- Reconfigured parsers, forms and reports around new default values. +- Fixed 'Missing' and 'Parsed' reagents disconnect in forms. ## 202404.03 diff --git a/TODO.md b/TODO.md index a18737c..b5510fd 100644 --- a/TODO.md +++ b/TODO.md @@ -1,17 +1,18 @@ -- [ ] Put "Not applicable" reagents in to_dict() method. +- [x] Put "Not applicable" reagents in to_dict() method. - Currently in to_pydantic(). - [x] Critical: Convert Json lits to dicts so I can have them update properly without using crashy Sqlalchemy-json - Was actually not necessary. -- [ ] Fix Parsed/Missing mix ups. +- [x] Fix Parsed/Missing mix ups. - [x] Have sample parser check for controls and add to reagents? - [x] Update controls to NestedMutableJson - [x] Appending of qPCR results to WW not saving. Find out why. - Possibly due to immutable JSON? But... it's worked before... Right? - Based on research, if a top-level JSON field is not changed, SQLalchemy will not detect changes. - Using sqlalchemy-json module seems to have helped. -- [ ] Add Bead basher and Assist to DB. + - found that by using 'flag_modified' this can be fixed +- [x] Add Bead basher and Assist to DB. - [x] Artic not creating right plate name. -- [ ] Merge BasicSubmission.find_subclasses and BasicSubmission.find_polymorphic_subclass +- [x] Merge BasicSubmission.find_subclasses and BasicSubmission.find_polymorphic_subclass - [x] Fix updating of Extraction Kit in submission form widget. - [x] Fix cropping of gel image. - [ ] Create Tips ... *sigh*. diff --git a/requirements.txt b/requirements.txt index 4458e27..b9a2fef 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py index 08ada5a..e479224 100644 --- a/src/submissions/backend/db/models/kits.py +++ b/src/submissions/backend/db/models/kits.py @@ -382,7 +382,8 @@ class Reagent(BaseClass): name=self.name, type=rtype, lot=self.lot, - expiry=place_holder + expiry=place_holder, + missing=False ) def update_last_used(self, kit:KitType) -> Report: @@ -902,7 +903,7 @@ class SubmissionReagentAssociation(BaseClass): submission = relationship("BasicSubmission", back_populates="submission_reagent_associations") #: associated submission - reagent = relationship(Reagent, back_populates="reagent_submission_associations") #: associatied reagent + reagent = relationship(Reagent, back_populates="reagent_submission_associations") #: associated reagent def __repr__(self) -> str: """ @@ -941,11 +942,6 @@ class SubmissionReagentAssociation(BaseClass): if isinstance(reagent, str): reagent = Reagent.query(lot_number=reagent) query = query.filter(cls.reagent==reagent) - # case str(): - # logger.debug(f"Lookup SubmissionReagentAssociation by reagent str {reagent}") - - # query = query.filter(cls.reagent==reagent) - # logger.debug(f"Result: {query.all()}") case _: pass match submission: @@ -954,11 +950,6 @@ class SubmissionReagentAssociation(BaseClass): submission = BasicSubmission.query(rsl_number=submission) # logger.debug(f"Lookup SubmissionReagentAssociation by submission BasicSubmission {submission}") query = query.filter(cls.submission==submission) - # case str(): - # logger.debug(f"Lookup SubmissionReagentAssociation by submission str {submission}") - # submission = BasicSubmission.query(rsl_number=submission) - # query = query.filter(cls.submission==submission) - # logger.debug(f"Result: {query.all()}") case int(): # logger.debug(f"Lookup SubmissionReagentAssociation by submission id {submission}") submission = BasicSubmission.query(id=submission) @@ -1141,7 +1132,7 @@ class EquipmentRole(BaseClass): cascade="all, delete-orphan", ) #: relation to SubmissionTypes - submission_types = association_proxy("equipmentrole_submission_associations", "submission_type") #: proxy to equipmentrole_submissiontype_associations.submission_type + submission_types = association_proxy("equipmentrole_submissiontype_associations", "submission_type") #: proxy to equipmentrole_submissiontype_associations.submission_type def __repr__(self) -> str: """ diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index 913e483..449ca97 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -3,17 +3,12 @@ Models for the main submission types. ''' from __future__ import annotations from getpass import getuser -import logging, uuid, tempfile, re, yaml, base64, sys +import logging, uuid, tempfile, re, yaml, base64 from zipfile import ZipFile from tempfile import TemporaryDirectory -# from reportlab.graphics.barcode import createBarcodeImageInMemory -# from reportlab.graphics.shapes import Drawing -# from reportlab.lib.units import mm from operator import attrgetter, itemgetter from pprint import pformat from . import BaseClass, Reagent, SubmissionType, KitType, Organization -# MutableDict and JSONEncodedDict are custom classes designed to get around JSON columns not being updated. -# See: https://docs.sqlalchemy.org/en/14/orm/extensions/mutable.html#establishing-mutability-on-scalar-column-values from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case from sqlalchemy.orm import relationship, validates, Query from sqlalchemy.orm.attributes import flag_modified @@ -61,6 +56,7 @@ class BasicSubmission(BaseClass): submission_category = Column(String(64)) #: ["Research", "Diagnostic", "Surveillance", "Validation"], else defaults to submission_type_name cost_centre = Column(String(64)) #: Permanent storage of used cost centre in case organization field changed in the future. + submission_sample_associations = relationship( "SubmissionSampleAssociation", back_populates="submission", @@ -182,30 +178,19 @@ class BasicSubmission(BaseClass): ext_kit = None # load scraped extraction info try: - # ext_info = json.loads(self.extraction_info) ext_info = self.extraction_info except TypeError: ext_info = None - # except JSONDecodeError as e: - # ext_info = None - # logger.error(f"Json error in {self.rsl_plate_num}: {e}") output = { "id": self.id, "Plate Number": self.rsl_plate_num, "Submission Type": self.submission_type_name, - # "Submission Category": self.submission_category, "Submitter Plate Number": self.submitter_plate_num, "Submitted Date": self.submitted_date.strftime("%Y-%m-%d"), "Submitting Lab": sub_lab, "Sample Count": self.sample_count, "Extraction Kit": ext_kit, - # "Technician": self.technician, "Cost": self.run_cost, - # "reagents": reagents, - # "samples": samples, - # "extraction_info": ext_info, - # "comment": comments, - # "equipment": equipment } if report: return output @@ -347,19 +332,6 @@ class BasicSubmission(BaseClass): """ return [item.role for item in self.submission_equipment_associations] - # def make_plate_barcode(self, width:int=100, height:int=25) -> Drawing: - # """ - # Creates a barcode image for this BasicSubmission. - - # Args: - # width (int, optional): Width (pixels) of image. Defaults to 100. - # height (int, optional): Height (pixels) of image. Defaults to 25. - - # Returns: - # Drawing: image object - # """ - # return createBarcodeImageInMemory('Code128', value=self.rsl_plate_num, width=width*mm, height=height*mm, humanReadable=True, format="png") - @classmethod def submissions_to_df(cls, submission_type:str|None=None, limit:int=0) -> pd.DataFrame: """ @@ -491,14 +463,17 @@ class BasicSubmission(BaseClass): """ from backend.validators import PydSubmission, PydSample, PydReagent, PydEquipment dicto = self.to_dict(full_data=True, backup=backup) + logger.debug("To dict complete") new_dict = {} for key, value in dicto.items(): + # start = time() + # logger.debug(f"Checking {key}") missing = value is None or value in ['', 'None'] match key: case "reagents": - new_dict[key] = [PydReagent(**reagent, missing=False) for reagent in value] + + new_dict[key] = [PydReagent(**reagent) for reagent in value] case "samples": - # samples = {k.lower().replace(" ", "_"):v for k,v in dicto['samples'].items()} new_dict[key] = [PydSample(**{k.lower().replace(" ", "_"):v for k,v in sample.items()}) for sample in dicto['samples']] case "equipment": try: @@ -512,7 +487,9 @@ class BasicSubmission(BaseClass): case _: logger.debug(f"Setting dict {key} to {value}") new_dict[key.lower().replace(" ", "_")] = dict(value=value, missing=missing) + # logger.debug(f"{key} complete after {time()-start}") new_dict['filepath'] = Path(tempfile.TemporaryFile().name) + logger.debug("Done converting fields.") return PydSubmission(**new_dict) def save(self, original:bool=True): @@ -540,63 +517,46 @@ class BasicSubmission(BaseClass): rstring = rf'{"|".join([item.get_regex() for item in cls.__subclasses__()])}' regex = re.compile(rstring, flags = re.IGNORECASE | re.VERBOSE) return regex - + @classmethod - def find_subclasses(cls, attrs:dict|None=None, submission_type:str|SubmissionType|None=None): + def find_polymorphic_subclass(cls, attrs: dict|None = None, polymorphic_identity:str|SubmissionType|None = None): """ - Retrieves subclasses of this class matching patterned + Find subclass based on polymorphic identity or relevant attributes. Args: - attrs (dict | None, optional): Attributes to look for. Defaults to None. - submission_type (str | SubmissionType | None, optional): Submission type. Defaults to None. - - Raises: - AttributeError: Raised if attr given, but not found. + polymorphic_identity (str | None, optional): String representing polymorphic identity. Defaults to None. + attrs (str | SubmissionType | None, optional): Attributes of the relevant class. Defaults to None. Returns: _type_: Subclass of interest. - """ - match submission_type: + """ + # logger.debug(f"Controlling for dict value") + if isinstance(polymorphic_identity, dict): + polymorphic_identity = polymorphic_identity['value'] + if isinstance(polymorphic_identity, SubmissionType): + polymorphic_identity = polymorphic_identity.name + # if polymorphic_identity != None: + model = cls + match polymorphic_identity: case str(): - return cls.find_polymorphic_subclass(submission_type) - case SubmissionType(): - return cls.find_polymorphic_subclass(submission_type.name) + try: + logger.info(f"Recruiting: {cls}") + model = [item for item in cls.__subclasses__() if + item.__mapper_args__['polymorphic_identity'] == polymorphic_identity][0] + except Exception as e: + logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}") case _: pass - if attrs == None or len(attrs) == 0: - return cls + if attrs is None or len(attrs) == 0: + return model if any([not hasattr(cls, attr) for attr in attrs]): # looks for first model that has all included kwargs try: model = [subclass for subclass in cls.__subclasses__() if all([hasattr(subclass, attr) for attr in attrs])][0] except IndexError as e: raise AttributeError(f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs)}") - else: - model = cls logger.info(f"Recruiting model: {model}") return model - - @classmethod - def find_polymorphic_subclass(cls, polymorphic_identity:str|None=None): - """ - Find subclass based on polymorphic identity. - - Args: - polymorphic_identity (str | None, optional): String representing polymorphic identity. Defaults to None. - - Returns: - _type_: Subclass of interest. - """ - # logger.debug(f"Controlling for dict value") - if isinstance(polymorphic_identity, dict): - polymorphic_identity = polymorphic_identity['value'] - if polymorphic_identity != None: - try: - cls = [item for item in cls.__subclasses__() if item.__mapper_args__['polymorphic_identity']==polymorphic_identity][0] - logger.info(f"Recruiting: {cls}") - except Exception as e: - logger.error(f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}") - return cls # Child class custom functions @@ -838,17 +798,22 @@ class BasicSubmission(BaseClass): Returns: models.BasicSubmission | List[models.BasicSubmission]: Submission(s) of interest - """ - + """ + logger.debug(f"Incoming kwargs: {kwargs}") # NOTE: if you go back to using 'model' change the appropriate cls to model in the query filters - if submission_type == None: + if submission_type is not None: + # if isinstance(submission_type, SubmissionType): + # model = cls.find_subclasses(submission_type=submission_type.name) + model = cls.find_polymorphic_subclass(polymorphic_identity=submission_type) + # else: + # model = cls.find_subclasses(submission_type=submission_type) + elif len(kwargs) > 0: # find the subclass containing the relevant attributes - model = cls.find_subclasses(attrs=kwargs) + logger.debug(f"Attributes for search: {kwargs}") + # model = cls.find_subclasses(attrs=kwargs) + model = cls.find_polymorphic_subclass(attrs=kwargs) else: - if isinstance(submission_type, SubmissionType): - model = cls.find_subclasses(submission_type=submission_type.name) - else: - model = cls.find_subclasses(submission_type=submission_type) + model = cls query: Query = cls.__database_session__.query(model) if start_date != None and end_date == None: logger.warning(f"Start date with no end date, using today.") @@ -883,23 +848,23 @@ class BasicSubmission(BaseClass): # logger.debug(f"Compensating for same date by using time") if start_date == end_date: start_date = datetime.strptime(start_date, "%Y-%m-%d").strftime("%Y-%m-%d %H:%M:%S.%f") - query = query.filter(cls.submitted_date==start_date) + query = query.filter(model.submitted_date==start_date) else: - query = query.filter(cls.submitted_date.between(start_date, end_date)) + query = query.filter(model.submitted_date.between(start_date, end_date)) # by reagent (for some reason) match reagent: case str(): # logger.debug(f"Looking up BasicSubmission with reagent: {reagent}") - query = query.join(cls.reagents).filter(Reagent.lot==reagent) + query = query.join(model.submission_reagent_associations).filter(SubmissionSampleAssociation.reagent.lot==reagent) case Reagent(): # logger.debug(f"Looking up BasicSubmission with reagent: {reagent}") - query = query.join(reagents_submissions).filter(reagents_submissions.c.reagent_id==reagent.id) + query = query.join(model.submission_reagent_associations).join(SubmissionSampleAssociation.reagent).filter(Reagent.lot==reagent) case _: pass # by rsl number (returns only a single value) match rsl_number: case str(): - query = query.filter(cls.rsl_plate_num==rsl_number) + query = query.filter(model.rsl_plate_num==rsl_number) # logger.debug(f"At this point the query gets: {query.all()}") limit = 1 case _: @@ -908,20 +873,21 @@ class BasicSubmission(BaseClass): match id: case int(): # logger.debug(f"Looking up BasicSubmission with id: {id}") - query = query.filter(cls.id==id) + query = query.filter(model.id==id) limit = 1 case str(): # logger.debug(f"Looking up BasicSubmission with id: {id}") - query = query.filter(cls.id==int(id)) + query = query.filter(model.id==int(id)) limit = 1 case _: pass for k, v in kwargs.items(): - attr = getattr(cls, k) + logger.debug(f"Looking up attribute: {k}") + attr = getattr(model, k) logger.debug(f"Got attr: {attr}") query = query.filter(attr==v) - if len(kwargs) > 0: - limit = 1 + # if len(kwargs) > 0: + # limit = 1 if chronologic: query.order_by(cls.submitted_date) return cls.query_return(query=query, limit=limit) @@ -952,7 +918,7 @@ class BasicSubmission(BaseClass): instance = cls.query(submission_type=submission_type, limit=1, **kwargs) # logger.debug(f"Retrieved instance: {instance}") if instance == None: - used_class = cls.find_subclasses(attrs=kwargs, submission_type=submission_type) + used_class = cls.find_polymorphic_subclass(attrs=kwargs, polymorphic_identity=submission_type) instance = used_class(**kwargs) match submission_type: case str(): @@ -1039,12 +1005,6 @@ class BasicSubmission(BaseClass): dlg = SubmissionComment(parent=obj, submission=self) if dlg.exec(): comment = dlg.parse_form() - # try: - # # For some reason .append results in new comment being ignored, so have to concatenate lists. - # self.comment = self.comment + comment - # except (AttributeError, TypeError) as e: - # logger.error(f"Hit error ({e}) creating comment") - # self.comment = comment self.set_attribute(key='comment', value=comment) logger.debug(self.comment) self.save(original=False) @@ -1250,7 +1210,6 @@ class Wastewater(BasicSubmission): id = Column(INTEGER, ForeignKey('_basicsubmission.id'), primary_key=True) ext_technician = Column(String(64)) #: Name of technician doing extraction pcr_technician = Column(String(64)) #: Name of technician doing pcr - # pcr_info = Column(JSON) pcr_info = Column(JSON)#: unstructured output from pcr table logger or user(Artic) __mapper_args__ = __mapper_args__ = dict(polymorphic_identity="Wastewater", @@ -1268,7 +1227,6 @@ class Wastewater(BasicSubmission): if report: return output try: - # output['pcr_info'] = json.loads(self.pcr_info) output['pcr_info'] = self.pcr_info except TypeError as e: pass @@ -1280,13 +1238,8 @@ class Wastewater(BasicSubmission): output["PCR Technician"] = self.technician else: output['PCR Technician'] = self.pcr_technician - # output['Technician'] = self.technician}, Ext: {ext_tech}, PCR: {pcr_tech}" return output - # @classmethod - # def get_default_info(cls) -> dict: - return dict(abbreviation="WW", submission_type="Wastewater") - @classmethod def parse_info(cls, input_dict:dict, xl:pd.ExcelFile|None=None) -> dict: """ @@ -1444,10 +1397,6 @@ class WastewaterArtic(BasicSubmission): output['source_plates'] = self.source_plates return output - # @classmethod - # def get_default_info(cls) -> str: - # return dict(abbreviation="AR", submission_type="Wastewater Artic") - @classmethod def parse_info(cls, input_dict:dict, xl:pd.ExcelFile|None=None) -> dict: """ @@ -1460,15 +1409,12 @@ class WastewaterArtic(BasicSubmission): Returns: dict: Updated sample dictionary """ - # from backend.validators import RSLNamer input_dict = super().parse_info(input_dict) workbook = load_workbook(xl.io, data_only=True) ws = workbook['Egel results'] data = [ws.cell(row=ii,column=jj) for jj in range(15,27) for ii in range(10,18)] data = [cell for cell in data if cell.value is not None and "NTC" in cell.value] input_dict['gel_controls'] = [dict(sample_id=cell.value, location=f"{row_map[cell.row-9]}{str(cell.column-14).zfill(2)}") for cell in data] - # df = xl.parse("Egel results").iloc[7:16, 13:26] - # df = df.set_index(df.columns[0]) ws = workbook['First Strand List'] data = [dict(plate=ws.cell(row=ii, column=3).value, starting_sample=ws.cell(row=ii, column=4).value) for ii in range(8,11)] input_dict['source_plates'] = data @@ -1615,30 +1561,6 @@ class WastewaterArtic(BasicSubmission): worksheet = input_excel["First Strand List"] samples = cls.query(rsl_number=info['rsl_plate_num']['value']).submission_sample_associations samples = sorted(samples, key=attrgetter('column', 'row')) - # try: - # source_plates = [item['plate'] for item in info['source_plates']] - # first_samples = [item['start_sample'] for item in info['source_plates']] - # except: - # source_plates = [] - # first_samples = [] - # for sample in samples: - # sample = sample.sample - # try: - # assoc = [item.submission.rsl_plate_num for item in sample.sample_submission_associations if item.submission.submission_type_name=="Wastewater"][-1] - # except IndexError: - # logger.error(f"Association not found for {sample}") - # continue - # if assoc not in source_plates: - # source_plates.append(assoc) - # first_samples.append(sample.ww_processing_num) - # # Pad list to length of 3 - # source_plates += ['None'] * (3 - len(source_plates)) - # first_samples += [''] * (3 - len(first_samples)) - # source_plates = zip(source_plates, first_samples, strict=False) - # for iii, plate in enumerate(source_plates, start=8): - # logger.debug(f"Plate: {plate}") - # for jjj, value in enumerate(plate, start=3): - # worksheet.cell(row=iii, column=jjj, value=value) logger.debug(f"Info:\n{pformat(info)}") check = 'source_plates' in info.keys() and info['source_plates'] is not None if check: @@ -1655,7 +1577,6 @@ class WastewaterArtic(BasicSubmission): worksheet.cell(row=row, column=4, value=plate['starting_sample']) except TypeError: pass - # sys.exit(f"Hardcoded stop: backend.models.submissions:1629") check = 'gel_info' in info.keys() and info['gel_info']['value'] is not None if check: # logger.debug(f"Gel info check passed.") @@ -1726,7 +1647,6 @@ class WastewaterArtic(BasicSubmission): List[dict]: Updated dictionaries """ logger.debug(f"Hello from {self.__class__.__name__} dictionary sample adjuster.") - # if backup: output = [] for assoc in self.submission_sample_associations: dicto = assoc.to_sub_dict() @@ -1738,9 +1658,6 @@ class WastewaterArtic(BasicSubmission): old_assoc = WastewaterAssociation.query(submission=old_sub, sample=assoc.sample, limit=1) dicto['well'] = f"{row_map[old_assoc.row]}{old_assoc.column}" output.append(dicto) - # else: - # output = super().adjust_to_dict_samples(backup=False) - return output def custom_context_events(self) -> dict: @@ -1846,12 +1763,14 @@ class BasicSample(BaseClass): Returns: dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above """ - # logger.debug(f"Converting {self} to dict.") + logger.debug(f"Converting {self} to dict.") + # start = time() sample = {} sample['Submitter ID'] = self.submitter_id sample['Sample Type'] = self.sample_type if full_data: sample['submissions'] = sorted([item.to_sub_dict() for item in self.sample_submission_associations], key=itemgetter('submitted_date')) + # logger.debug(f"Done converting {self} after {time()-start}") return sample def set_attribute(self, name:str, value): @@ -2093,7 +2012,7 @@ class WastewaterSample(BasicSample): output_dict['collection_date'] = parse(output_dict['collection_date']).date() except ParserError: logger.error(f"Problem parsing collection_date: {output_dict['collection_date']}") - output_dict['collection_date'] = date(1,1,1) + output_dict['collection_date'] = date(1970,1,1) case datetime(): output_dict['collection_date'] = output_dict['collection_date'].date() case date(): @@ -2136,6 +2055,7 @@ class BacterialCultureSample(BasicSample): Returns: dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above """ + # start = time() sample = super().to_sub_dict(full_data=full_data) sample['Name'] = self.submitter_id sample['Organism'] = self.organism @@ -2143,6 +2063,7 @@ class BacterialCultureSample(BasicSample): if self.control != None: sample['colour'] = [0,128,0] sample['tooltip'] = f"Control: {self.control.controltype.name} - {self.control.controltype.targets}" + # logger.debug(f"Done converting to {self} to dict after {time()-start}") return sample # Submission to Sample Associations @@ -2201,8 +2122,9 @@ class SubmissionSampleAssociation(BaseClass): dict: Updated dictionary with row, column and well updated """ # Get sample info - # logger.debug(f"Running {self.__repr__()}") + logger.debug(f"Running {self.__repr__()}") sample = self.sample.to_sub_dict() + logger.debug("Sample conversion complete.") sample['Name'] = self.sample.submitter_id sample['Row'] = self.row sample['Column'] = self.column @@ -2450,5 +2372,3 @@ class WastewaterAssociation(SubmissionSampleAssociation): except ValueError as e: logger.error(f"Problem incrementing id: {e}") return 1 - - \ No newline at end of file diff --git a/src/submissions/backend/excel/parser.py b/src/submissions/backend/excel/parser.py index 69ed3a6..b810c7b 100644 --- a/src/submissions/backend/excel/parser.py +++ b/src/submissions/backend/excel/parser.py @@ -103,7 +103,7 @@ class SheetParser(object): if not check_not_nan(self.sub['extraction_kit']['value']): dlg = ObjectSelector(title="Kit Needed", message="At minimum a kit is needed. Please select one.", obj_type=KitType) if dlg.exec(): - self.sub['extraction_kit'] = dict(value=dlg.getValues(), missing=True) + self.sub['extraction_kit'] = dict(value=dlg.parse_form(), missing=True) else: raise ValueError("Extraction kit needed.") else: diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py index 4d3c64c..4343ab4 100644 --- a/src/submissions/backend/validators/pydant.py +++ b/src/submissions/backend/validators/pydant.py @@ -363,7 +363,7 @@ class PydSubmission(BaseModel, extra='allow'): from frontend.widgets.pop_ups import ObjectSelector dlg = ObjectSelector(title="Missing Submitting Lab", message="We need a submitting lab. Please select from the list.", obj_type=Organization) if dlg.exec(): - value['value'] = dlg.getValues() + value['value'] = dlg.parse_form() else: value['value'] = None return value @@ -714,7 +714,7 @@ class PydSubmission(BaseModel, extra='allow'): # logger.debug(f"Attempting to write lot {reagent['lot']['value']} in: row {reagent['lot']['row']}, column {reagent['lot']['column']}") worksheet.cell(row=reagent['lot']['row'], column=reagent['lot']['column'], value=reagent['lot']['value']) # logger.debug(f"Attempting to write expiry {reagent['expiry']['value']} in: row {reagent['expiry']['row']}, column {reagent['expiry']['column']}") - if reagent['expiry']['value'].year == 1970: + if isinstance(reagent['expiry']['value'], date) and reagent['expiry']['value'].year == 1970: reagent['expiry']['value'] = "NA" worksheet.cell(row=reagent['expiry']['row'], column=reagent['expiry']['column'], value=reagent['expiry']['value']) try: diff --git a/src/submissions/frontend/widgets/misc.py b/src/submissions/frontend/widgets/misc.py index a802b3c..ddd4362 100644 --- a/src/submissions/frontend/widgets/misc.py +++ b/src/submissions/frontend/widgets/misc.py @@ -50,7 +50,10 @@ class AddReagentForm(QDialog): if expiry == None: self.exp_input.setDate(QDate.currentDate()) else: - self.exp_input.setDate(expiry) + try: + self.exp_input.setDate(expiry) + except TypeError: + self.exp_input.setDate(QDate.currentDate()) # widget to get reagent type info self.type_input = QComboBox() self.type_input.setObjectName('type') diff --git a/src/submissions/frontend/widgets/pop_ups.py b/src/submissions/frontend/widgets/pop_ups.py index 414e2aa..a901182 100644 --- a/src/submissions/frontend/widgets/pop_ups.py +++ b/src/submissions/frontend/widgets/pop_ups.py @@ -71,7 +71,7 @@ class ObjectSelector(QDialog): self.layout.addWidget(self.buttonBox) self.setLayout(self.layout) - def getValues(self) -> str: + def parse_form(self) -> str: """ Get KitType(str) from widget