Renaming ReagentType to ReagentRole

This commit is contained in:
lwark
2024-06-07 14:15:19 -05:00
parent a355c5bb76
commit c7b6c90eac
8 changed files with 197 additions and 129 deletions

View File

@@ -13,24 +13,27 @@ logger = logging.getLogger(f"submissions.{__name__}")
# table containing organization/contact relationship # table containing organization/contact relationship
orgs_contacts = Table( orgs_contacts = Table(
"_orgs_contacts", "_orgs_contacts",
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} # __table_args__ = {'extend_existing': True}
extend_existing = True extend_existing=True
) )
class Organization(BaseClass): class Organization(BaseClass):
""" """
Base of organization Base of organization
""" """
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
name = Column(String(64)) #: organization name name = Column(String(64)) #: organization name
submissions = relationship("BasicSubmission", back_populates="submitting_lab") #: submissions this organization has submitted submissions = relationship("BasicSubmission",
cost_centre = Column(String()) #: cost centre used by org for payment back_populates="submitting_lab") #: submissions this organization has submitted
contacts = relationship("Contact", back_populates="organization", secondary=orgs_contacts) #: contacts involved with this org cost_centre = Column(String()) #: cost centre used by org for payment
contacts = relationship("Contact", back_populates="organization",
secondary=orgs_contacts) #: contacts involved with this org
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<Organization({self.name})>" return f"<Organization({self.name})>"
@@ -38,10 +41,10 @@ class Organization(BaseClass):
@classmethod @classmethod
@setup_lookup @setup_lookup
def query(cls, def query(cls,
id:int|None=None, id: int | None = None,
name:str|None=None, name: str | None = None,
limit:int=0, limit: int = 0,
) -> Organization|List[Organization]: ) -> Organization | List[Organization]:
""" """
Lookup organizations in the database by a number of parameters. Lookup organizations in the database by a number of parameters.
@@ -51,11 +54,11 @@ class Organization(BaseClass):
Returns: Returns:
Organization|List[Organization]: Organization|List[Organization]:
""" """
query: Query = cls.__database_session__.query(cls) query: Query = cls.__database_session__.query(cls)
match id: match id:
case int(): case int():
query = query.filter(cls.id==id) query = query.filter(cls.id == id)
limit = 1 limit = 1
case _: case _:
pass pass
@@ -73,33 +76,35 @@ class Organization(BaseClass):
def save(self): def save(self):
super().save() super().save()
class Contact(BaseClass): class Contact(BaseClass):
""" """
Base of Contact Base of Contact
""" """
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
name = Column(String(64)) #: contact name name = Column(String(64)) #: contact name
email = Column(String(64)) #: contact email email = Column(String(64)) #: contact email
phone = Column(String(32)) #: contact phone number phone = Column(String(32)) #: contact phone number
organization = relationship("Organization", back_populates="contacts", uselist=True, secondary=orgs_contacts) #: relationship to joined organization organization = relationship("Organization", back_populates="contacts", uselist=True,
submissions = relationship("BasicSubmission", back_populates="contact") #: submissions this contact has submitted secondary=orgs_contacts) #: relationship to joined organization
submissions = relationship("BasicSubmission", back_populates="contact") #: submissions this contact has submitted
def __repr__(self) -> str: def __repr__(self) -> str:
""" """
Returns: Returns:
str: Representation of this Contact str: Representation of this Contact
""" """
return f"<Contact({self.name})>" return f"<Contact({self.name})>"
@classmethod @classmethod
@setup_lookup @setup_lookup
def query(cls, def query(cls,
name:str|None=None, name: str | None = None,
email:str|None=None, email: str | None = None,
phone:str|None=None, phone: str | None = None,
limit:int=0, limit: int = 0,
) -> Contact|List[Contact]: ) -> Contact | List[Contact]:
""" """
Lookup contacts in the database by a number of parameters. Lookup contacts in the database by a number of parameters.
@@ -111,29 +116,28 @@ class Contact(BaseClass):
Returns: Returns:
Contact|List[Contact]: Contact(s) of interest. Contact|List[Contact]: Contact(s) of interest.
""" """
# super().query(session) # super().query(session)
query: Query = cls.__database_session__.query(cls) query: Query = cls.__database_session__.query(cls)
match name: match name:
case str(): case str():
# logger.debug(f"Looking up contact with name: {name}") # logger.debug(f"Looking up contact with name: {name}")
query = query.filter(cls.name==name) query = query.filter(cls.name == name)
limit = 1 limit = 1
case _: case _:
pass pass
match email: match email:
case str(): case str():
# logger.debug(f"Looking up contact with email: {name}") # logger.debug(f"Looking up contact with email: {name}")
query = query.filter(cls.email==email) query = query.filter(cls.email == email)
limit = 1 limit = 1
case _: case _:
pass pass
match phone: match phone:
case str(): case str():
# logger.debug(f"Looking up contact with phone: {name}") # logger.debug(f"Looking up contact with phone: {name}")
query = query.filter(cls.phone==phone) query = query.filter(cls.phone == phone)
limit = 1 limit = 1
case _: case _:
pass pass
return cls.execute_query(query=query, limit=limit) return cls.execute_query(query=query, limit=limit)

View File

@@ -248,6 +248,7 @@ class BasicSubmission(BaseClass):
ext_info = self.extraction_info ext_info = self.extraction_info
except TypeError: except TypeError:
ext_info = None ext_info = None
output = { output = {
"id": self.id, "id": self.id,
"plate_number": self.rsl_plate_num, "plate_number": self.rsl_plate_num,
@@ -257,8 +258,7 @@ class BasicSubmission(BaseClass):
"submitting_lab": sub_lab, "submitting_lab": sub_lab,
"sample_count": self.sample_count, "sample_count": self.sample_count,
"extraction_kit": ext_kit, "extraction_kit": ext_kit,
"cost": self.run_cost, "cost": self.run_cost
} }
if report: if report:
return output return output
@@ -299,6 +299,15 @@ class BasicSubmission(BaseClass):
except Exception as e: except Exception as e:
logger.error(f"Error setting comment: {self.comment}, {e}") logger.error(f"Error setting comment: {self.comment}, {e}")
comments = None comments = None
try:
contact = self.contact.name
except AttributeError as e:
logger.error(f"Problem setting contact: {e}")
contact = "NA"
try:
contact_phone = self.contact.phone
except AttributeError:
contact_phone = "NA"
output["submission_category"] = self.submission_category output["submission_category"] = self.submission_category
output["technician"] = self.technician output["technician"] = self.technician
output["reagents"] = reagents output["reagents"] = reagents
@@ -308,9 +317,9 @@ class BasicSubmission(BaseClass):
output["equipment"] = equipment output["equipment"] = equipment
output["cost_centre"] = cost_centre output["cost_centre"] = cost_centre
output["signed_by"] = self.signed_by output["signed_by"] = self.signed_by
output["contact"] = self.contact.name # logger.debug(f"Setting contact to: {contact} of type: {type(contact)}")
output["contact_phone"] = self.contact.phone output["contact"] = contact
output["contact_phone"] = contact_phone
return output return output
def calculate_column_count(self) -> int: def calculate_column_count(self) -> int:
@@ -431,7 +440,7 @@ class BasicSubmission(BaseClass):
excluded = ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents', excluded = ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents',
'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number', 'gel_controls', 'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number', 'gel_controls',
'source_plates', 'pcr_technician', 'ext_technician', 'artic_technician', 'cost_centre', 'source_plates', 'pcr_technician', 'ext_technician', 'artic_technician', 'cost_centre',
'signed_by'] 'signed_by', 'artic_date', 'gel_barcode', 'gel_date', 'ngs_date', 'contact_phone', 'contact']
for item in excluded: for item in excluded:
try: try:
df = df.drop(item, axis=1) df = df.drop(item, axis=1)
@@ -544,7 +553,6 @@ class BasicSubmission(BaseClass):
# logger.debug("To dict complete") # logger.debug("To dict complete")
new_dict = {} new_dict = {}
for key, value in dicto.items(): for key, value in dicto.items():
# start = time()
# logger.debug(f"Checking {key}") # logger.debug(f"Checking {key}")
missing = value is None or value in ['', 'None'] missing = value is None or value in ['', 'None']
match key: match key:
@@ -1403,6 +1411,10 @@ class WastewaterArtic(BasicSubmission):
gel_info = Column(JSON) #: unstructured data from gel. gel_info = Column(JSON) #: unstructured data from gel.
gel_controls = Column(JSON) #: locations of controls on the gel gel_controls = Column(JSON) #: locations of controls on the gel
source_plates = Column(JSON) #: wastewater plates that samples come from source_plates = Column(JSON) #: wastewater plates that samples come from
artic_date = Column(TIMESTAMP) #: Date Artic Performed
ngs_date = Column(TIMESTAMP) #: Date submission received
gel_date = Column(TIMESTAMP) #: Date submission received
gel_barcode = Column(String(16))
__mapper_args__ = dict(polymorphic_identity="Wastewater Artic", __mapper_args__ = dict(polymorphic_identity="Wastewater Artic",
polymorphic_load="inline", polymorphic_load="inline",
@@ -1418,12 +1430,18 @@ class WastewaterArtic(BasicSubmission):
output = super().to_dict(full_data=full_data, backup=backup, report=report) output = super().to_dict(full_data=full_data, backup=backup, report=report)
if self.artic_technician in [None, "None"]: if self.artic_technician in [None, "None"]:
output['artic_technician'] = self.technician output['artic_technician'] = self.technician
else:
output['artic_technician'] = self.artic_technician
if report: if report:
return output return output
output['gel_info'] = self.gel_info output['gel_info'] = self.gel_info
output['gel_image'] = self.gel_image output['gel_image'] = self.gel_image
output['dna_core_submission_number'] = self.dna_core_submission_number output['dna_core_submission_number'] = self.dna_core_submission_number
output['source_plates'] = self.source_plates output['source_plates'] = self.source_plates
output['artic_date'] = self.artic_date or self.submitted_date
output['ngs_date'] = self.ngs_date or self.submitted_date
output['gel_date'] = self.gel_date or self.submitted_date
output['gel_barcode'] = self.gel_barcode
return output return output
@classmethod @classmethod
@@ -1756,19 +1774,22 @@ class WastewaterArtic(BasicSubmission):
output.append(dicto) output.append(dicto)
continue continue
for item in self.source_plates: for item in self.source_plates:
old_plate = WastewaterAssociation.query(submission=item['plate'], sample=assoc.sample, limit=1) if assoc.sample.id is None:
old_plate = None
else:
old_plate = WastewaterAssociation.query(submission=item['plate'], sample=assoc.sample, limit=1)
if old_plate is not None: if old_plate is not None:
set_plate = old_plate.submission.rsl_plate_num set_plate = old_plate.submission.rsl_plate_num
logger.debug(dicto['WW Processing Num']) # logger.debug(f"Dictionary: {pformat(dicto)}")
if dicto['WW Processing Num'].startswith("NTC"): if dicto['ww_processing_num'].startswith("NTC"):
dicto['Well'] = dicto['WW Processing Num'] dicto['well'] = dicto['ww_processing_num']
else: else:
dicto['Well'] = f"{row_map[old_plate.row]}{old_plate.column}" dicto['well'] = f"{row_map[old_plate.row]}{old_plate.column}"
break break
elif dicto['WW Processing Num'].startswith("NTC"): elif dicto['ww_processing_num'].startswith("NTC"):
dicto['Well'] = dicto['WW Processing Num'] dicto['well'] = dicto['ww_processing_num']
dicto['plate_name'] = set_plate dicto['plate_name'] = set_plate
logger.debug(f"Here is our raw sample: {pformat(dicto)}") # logger.debug(f"Here is our raw sample: {pformat(dicto)}")
output.append(dicto) output.append(dicto)
return output return output
@@ -1801,7 +1822,7 @@ class WastewaterArtic(BasicSubmission):
fname = select_open_file(obj=obj, file_extension="jpg") fname = select_open_file(obj=obj, file_extension="jpg")
dlg = GelBox(parent=obj, img_path=fname, submission=self) dlg = GelBox(parent=obj, img_path=fname, submission=self)
if dlg.exec(): if dlg.exec():
self.dna_core_submission_number, img_path, output, comment = dlg.parse_form() self.dna_core_submission_number, self.gel_barcode, img_path, output, comment = dlg.parse_form()
self.gel_image = img_path.name self.gel_image = img_path.name
self.gel_info = output self.gel_info = output
dt = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S") dt = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")
@@ -2135,7 +2156,7 @@ class BasicSample(BaseClass):
if dlg.exec(): if dlg.exec():
pass pass
#Below are the custom sample types # Below are the custom sample types
class WastewaterSample(BasicSample): class WastewaterSample(BasicSample):
""" """
@@ -2235,6 +2256,7 @@ class WastewaterSample(BasicSample):
searchables.append(dict(label=label, field=item)) searchables.append(dict(label=label, field=item))
return searchables return searchables
class BacterialCultureSample(BasicSample): class BacterialCultureSample(BasicSample):
""" """
base of bacterial culture sample base of bacterial culture sample

View File

@@ -139,7 +139,7 @@ class SheetParser(object):
# logger.debug(f"Submission dictionary coming into 'to_pydantic':\n{pformat(self.sub)}") # logger.debug(f"Submission dictionary coming into 'to_pydantic':\n{pformat(self.sub)}")
pyd_dict = copy(self.sub) pyd_dict = copy(self.sub)
pyd_dict['samples'] = [PydSample(**sample) for sample in self.sub['samples']] pyd_dict['samples'] = [PydSample(**sample) for sample in self.sub['samples']]
logger.debug(f"Reagents: {pformat(self.sub['reagents'])}") # logger.debug(f"Reagents: {pformat(self.sub['reagents'])}")
pyd_dict['reagents'] = [PydReagent(**reagent) for reagent in self.sub['reagents']] pyd_dict['reagents'] = [PydReagent(**reagent) for reagent in self.sub['reagents']]
# logger.debug(f"Equipment: {self.sub['equipment']}") # logger.debug(f"Equipment: {self.sub['equipment']}")
try: try:
@@ -215,13 +215,13 @@ class InfoParser(object):
new = location new = location
new['name'] = k new['name'] = k
relevant.append(new) relevant.append(new)
logger.debug(f"relevant map for {sheet}: {pformat(relevant)}") # logger.debug(f"relevant map for {sheet}: {pformat(relevant)}")
if not relevant: if not relevant:
continue continue
for item in relevant: for item in relevant:
# NOTE: Get cell contents at this location # NOTE: Get cell contents at this location
value = ws.cell(row=item['row'], column=item['column']).value value = ws.cell(row=item['row'], column=item['column']).value
logger.debug(f"Value for {item['name']} = {value}") # logger.debug(f"Value for {item['name']} = {value}")
match item['name']: match item['name']:
case "submission_type": case "submission_type":
value, missing = is_missing(value) value, missing = is_missing(value)

View File

@@ -53,7 +53,8 @@ class SheetWriter(object):
template = self.submission_type.template_file template = self.submission_type.template_file
workbook = load_workbook(BytesIO(template)) workbook = load_workbook(BytesIO(template))
missing_only = False missing_only = False
self.workbook = workbook # self.workbook = workbook
self.xl = workbook
self.write_info() self.write_info()
self.write_reagents() self.write_reagents()
self.write_samples() self.write_samples()
@@ -62,23 +63,23 @@ class SheetWriter(object):
def write_info(self): def write_info(self):
disallowed = ['filepath', 'reagents', 'samples', 'equipment', 'controls'] disallowed = ['filepath', 'reagents', 'samples', 'equipment', 'controls']
info_dict = {k: v for k, v in self.sub.items() if k not in disallowed} info_dict = {k: v for k, v in self.sub.items() if k not in disallowed}
writer = InfoWriter(xl=self.workbook, submission_type=self.submission_type, info_dict=info_dict) writer = InfoWriter(xl=self.xl, submission_type=self.submission_type, info_dict=info_dict)
self.xl = writer.write_info() self.xl = writer.write_info()
def write_reagents(self): def write_reagents(self):
reagent_list = self.sub['reagents'] reagent_list = self.sub['reagents']
writer = ReagentWriter(xl=self.workbook, submission_type=self.submission_type, writer = ReagentWriter(xl=self.xl, submission_type=self.submission_type,
extraction_kit=self.sub['extraction_kit'], reagent_list=reagent_list) extraction_kit=self.sub['extraction_kit'], reagent_list=reagent_list)
self.xl = writer.write_reagents() self.xl = writer.write_reagents()
def write_samples(self): def write_samples(self):
sample_list = self.sub['samples'] sample_list = self.sub['samples']
writer = SampleWriter(xl=self.workbook, submission_type=self.submission_type, sample_list=sample_list) writer = SampleWriter(xl=self.xl, submission_type=self.submission_type, sample_list=sample_list)
self.xl = writer.write_samples() self.xl = writer.write_samples()
def write_equipment(self): def write_equipment(self):
equipment_list = self.sub['equipment'] equipment_list = self.sub['equipment']
writer = EquipmentWriter(xl=self.workbook, submission_type=self.submission_type, equipment_list=equipment_list) writer = EquipmentWriter(xl=self.xl, submission_type=self.submission_type, equipment_list=equipment_list)
self.xl = writer.write_equipment() self.xl = writer.write_equipment()
@@ -93,18 +94,18 @@ class InfoWriter(object):
self.submission_type = submission_type self.submission_type = submission_type
self.sub_object = sub_object self.sub_object = sub_object
self.xl = xl self.xl = xl
map = submission_type.construct_info_map(mode='write') info_map = submission_type.construct_info_map(mode='write')
self.info = self.reconcile_map(info_dict, map) self.info = self.reconcile_map(info_dict, info_map)
# logger.debug(pformat(self.info)) # logger.debug(pformat(self.info))
def reconcile_map(self, info_dict: dict, map: dict) -> dict: def reconcile_map(self, info_dict: dict, info_map: dict) -> dict:
output = {} output = {}
for k, v in info_dict.items(): for k, v in info_dict.items():
if v is None: if v is None:
continue continue
dicto = {} dicto = {}
try: try:
dicto['locations'] = map[k] dicto['locations'] = info_map[k]
except KeyError: except KeyError:
# continue # continue
pass pass
@@ -126,7 +127,7 @@ class InfoWriter(object):
logger.error(f"No locations for {k}, skipping") logger.error(f"No locations for {k}, skipping")
continue continue
for loc in locations: for loc in locations:
# logger.debug(f"Writing {k} to {loc['sheet']}, row: {loc['row']}, column: {loc['column']}") logger.debug(f"Writing {k} to {loc['sheet']}, row: {loc['row']}, column: {loc['column']}")
sheet = self.xl[loc['sheet']] sheet = self.xl[loc['sheet']]
sheet.cell(row=loc['row'], column=loc['column'], value=v['value']) sheet.cell(row=loc['row'], column=loc['column'], value=v['value'])
return self.sub_object.custom_info_writer(self.xl, info=self.info) return self.sub_object.custom_info_writer(self.xl, info=self.info)
@@ -141,14 +142,14 @@ class ReagentWriter(object):
submission_type = SubmissionType.query(name=submission_type) submission_type = SubmissionType.query(name=submission_type)
if isinstance(extraction_kit, str): if isinstance(extraction_kit, str):
kit_type = KitType.query(name=extraction_kit) kit_type = KitType.query(name=extraction_kit)
map = kit_type.construct_xl_map_for_use(submission_type) reagent_map = kit_type.construct_xl_map_for_use(submission_type)
self.reagents = self.reconcile_map(reagent_list=reagent_list, map=map) self.reagents = self.reconcile_map(reagent_list=reagent_list, reagent_map=reagent_map)
def reconcile_map(self, reagent_list, map) -> List[dict]: def reconcile_map(self, reagent_list, reagent_map) -> List[dict]:
output = [] output = []
for reagent in reagent_list: for reagent in reagent_list:
try: try:
mp_info = map[reagent['role']] mp_info = reagent_map[reagent['role']]
except KeyError: except KeyError:
continue continue
placeholder = copy(reagent) placeholder = copy(reagent)
@@ -182,7 +183,7 @@ class SampleWriter(object):
submission_type = SubmissionType.query(name=submission_type) submission_type = SubmissionType.query(name=submission_type)
self.submission_type = submission_type self.submission_type = submission_type
self.xl = xl self.xl = xl
self.map = submission_type.construct_sample_map()['lookup_table'] self.sample_map = submission_type.construct_sample_map()['lookup_table']
self.samples = self.reconcile_map(sample_list) self.samples = self.reconcile_map(sample_list)
def reconcile_map(self, sample_list: list): def reconcile_map(self, sample_list: list):
@@ -200,10 +201,10 @@ class SampleWriter(object):
return sorted(output, key=lambda k: k['submission_rank']) return sorted(output, key=lambda k: k['submission_rank'])
def write_samples(self): def write_samples(self):
sheet = self.xl[self.map['sheet']] sheet = self.xl[self.sample_map['sheet']]
columns = self.map['sample_columns'] columns = self.sample_map['sample_columns']
for ii, sample in enumerate(self.samples): for ii, sample in enumerate(self.samples):
row = self.map['start_row'] + (sample['submission_rank'] - 1) row = self.sample_map['start_row'] + (sample['submission_rank'] - 1)
for k, v in sample.items(): for k, v in sample.items():
try: try:
column = columns[k] column = columns[k]
@@ -220,13 +221,15 @@ class EquipmentWriter(object):
submission_type = SubmissionType.query(name=submission_type) submission_type = SubmissionType.query(name=submission_type)
self.submission_type = submission_type self.submission_type = submission_type
self.xl = xl self.xl = xl
map = self.submission_type.construct_equipment_map() equipment_map = self.submission_type.construct_equipment_map()
self.equipment = self.reconcile_map(equipment_list=equipment_list, map=map) self.equipment = self.reconcile_map(equipment_list=equipment_list, equipment_map=equipment_map)
def reconcile_map(self, equipment_list: list, map: list): def reconcile_map(self, equipment_list: list, equipment_map: list):
output = [] output = []
if equipment_list is None:
return output
for ii, equipment in enumerate(equipment_list, start=1): for ii, equipment in enumerate(equipment_list, start=1):
mp_info = map[equipment['role']] mp_info = equipment_map[equipment['role']]
# logger.debug(f"{equipment['role']} map: {mp_info}") # logger.debug(f"{equipment['role']} map: {mp_info}")
placeholder = copy(equipment) placeholder = copy(equipment)
if mp_info == {}: if mp_info == {}:

View File

@@ -573,10 +573,22 @@ class PydSubmission(BaseModel, extra='allow'):
@field_validator("contact") @field_validator("contact")
@classmethod @classmethod
def get_contact_from_org(cls, value, values): def get_contact_from_org(cls, value, values):
logger.debug(f"Checking on value: {value}")
match value:
case dict():
if isinstance(value['value'], tuple):
value['value'] = value['value'][0]
case tuple():
value = dict(value=value[0], missing=False)
case _:
value = dict(value=value, missing=False)
check = Contact.query(name=value['value']) check = Contact.query(name=value['value'])
if check is None: if check is None:
org = Organization.query(name=values.data['submitting_lab']['value']) org = Organization.query(name=values.data['submitting_lab']['value'])
contact = org.contacts[0].name contact = org.contacts[0].name
logger.debug(f"Pulled: {contact}")
if isinstance(contact, tuple):
contact = contact[0]
return dict(value=contact, missing=True) return dict(value=contact, missing=True)
else: else:
return value return value

View File

@@ -55,6 +55,9 @@ class GelBox(QDialog):
layout.addWidget(QLabel("DNA Core Submission Number"),0,1) layout.addWidget(QLabel("DNA Core Submission Number"),0,1)
self.core_number = QLineEdit() self.core_number = QLineEdit()
layout.addWidget(self.core_number, 0,2) layout.addWidget(self.core_number, 0,2)
layout.addWidget(QLabel("Gel Barcode"),0,3)
self.gel_barcode = QLineEdit()
layout.addWidget(self.gel_barcode, 0, 4)
# setting this layout to the widget # setting this layout to the widget
# plot window goes on right side, spanning 3 rows # plot window goes on right side, spanning 3 rows
layout.addWidget(self.imv, 1, 1,20,20) layout.addWidget(self.imv, 1, 1,20,20)
@@ -80,8 +83,9 @@ class GelBox(QDialog):
Tuple[str, str|Path, list]: output values Tuple[str, str|Path, list]: output values
""" """
dna_core_submission_number = self.core_number.text() dna_core_submission_number = self.core_number.text()
gel_barcode = self.gel_barcode.text()
values, comment = self.form.parse_form() values, comment = self.form.parse_form()
return dna_core_submission_number, self.img_path, values, comment return dna_core_submission_number, gel_barcode, self.img_path, values, comment
class ControlsForm(QWidget): class ControlsForm(QWidget):

View File

@@ -9,12 +9,12 @@ from pathlib import Path
from . import select_open_file, select_save_file from . import select_open_file, select_save_file
import logging, difflib, inspect import logging, difflib, inspect
from pathlib import Path from pathlib import Path
from tools import Report, Result, check_not_nan, workbook_2_csv from tools import Report, Result, check_not_nan, workbook_2_csv, main_form_style
from backend.excel.parser import SheetParser from backend.excel.parser import SheetParser
from backend.validators import PydSubmission, PydReagent from backend.validators import PydSubmission, PydReagent
from backend.db import ( from backend.db import (
KitType, Organization, SubmissionType, Reagent, KitType, Organization, SubmissionType, Reagent,
ReagentRole, KitTypeReagentRoleAssociation ReagentRole, KitTypeReagentRoleAssociation, BasicSubmission
) )
from pprint import pformat from pprint import pformat
from .pop_ups import QuestionAsker, AlertPop from .pop_ups import QuestionAsker, AlertPop
@@ -149,6 +149,8 @@ class SubmissionFormContainer(QWidget):
class SubmissionFormWidget(QWidget): class SubmissionFormWidget(QWidget):
def __init__(self, parent: QWidget, submission: PydSubmission) -> None: def __init__(self, parent: QWidget, submission: PydSubmission) -> None:
super().__init__(parent) super().__init__(parent)
# self.report = Report() # self.report = Report()
@@ -169,15 +171,16 @@ class SubmissionFormWidget(QWidget):
except AttributeError: except AttributeError:
logger.error(f"Couldn't get attribute from pyd: {k}") logger.error(f"Couldn't get attribute from pyd: {k}")
value = dict(value=None, missing=True) value = dict(value=None, missing=True)
add_widget = self.create_widget(key=k, value=value, submission_type=self.pyd.submission_type['value']) add_widget = self.create_widget(key=k, value=value, submission_type=self.pyd.submission_type['value'], sub_obj=st)
if add_widget != None: if add_widget != None:
self.layout.addWidget(add_widget) self.layout.addWidget(add_widget)
if k == "extraction_kit": if k == "extraction_kit":
add_widget.input.currentTextChanged.connect(self.scrape_reagents) add_widget.input.currentTextChanged.connect(self.scrape_reagents)
self.setStyleSheet(main_form_style)
self.scrape_reagents(self.pyd.extraction_kit) self.scrape_reagents(self.pyd.extraction_kit)
def create_widget(self, key: str, value: dict | PydReagent, submission_type: str | None = None, def create_widget(self, key: str, value: dict | PydReagent, submission_type: str | None = None,
extraction_kit: str | None = None) -> "self.InfoItem": extraction_kit: str | None = None, sub_obj:BasicSubmission|None=None) -> "self.InfoItem":
""" """
Make an InfoItem widget to hold a field Make an InfoItem widget to hold a field
@@ -197,13 +200,13 @@ class SubmissionFormWidget(QWidget):
else: else:
widget = None widget = None
case _: case _:
widget = self.InfoItem(self, key=key, value=value, submission_type=submission_type) widget = self.InfoItem(self, key=key, value=value, submission_type=submission_type, sub_obj=sub_obj)
return widget return widget
return None return None
def scrape_reagents(self, *args, **kwargs): #extraction_kit:str, caller:str|None=None): def scrape_reagents(self, *args, **kwargs): #extraction_kit:str, caller:str|None=None):
""" """
Extracted scrape reagents function that will run when Extracted scrape reagents function that will run when
form 'extraction_kit' widget is updated. form 'extraction_kit' widget is updated.
Args: Args:
@@ -395,11 +398,11 @@ class SubmissionFormWidget(QWidget):
class InfoItem(QWidget): class InfoItem(QWidget):
def __init__(self, parent: QWidget, key: str, value: dict, submission_type: str | None = None) -> None: def __init__(self, parent: QWidget, key: str, value: dict, submission_type: str | None = None, sub_obj:BasicSubmission|None=None) -> None:
super().__init__(parent) super().__init__(parent)
layout = QVBoxLayout() layout = QVBoxLayout()
self.label = self.ParsedQLabel(key=key, value=value) self.label = self.ParsedQLabel(key=key, value=value)
self.input: QWidget = self.set_widget(parent=self, key=key, value=value, submission_type=submission_type) self.input: QWidget = self.set_widget(parent=self, key=key, value=value, submission_type=submission_type, sub_obj=sub_obj)
self.setObjectName(key) self.setObjectName(key)
try: try:
self.missing: bool = value['missing'] self.missing: bool = value['missing']
@@ -436,7 +439,7 @@ class SubmissionFormWidget(QWidget):
return None, None return None, None
return self.input.objectName(), dict(value=value, missing=self.missing) return self.input.objectName(), dict(value=value, missing=self.missing)
def set_widget(self, parent: QWidget, key: str, value: dict, submission_type: str | None = None) -> QWidget: def set_widget(self, parent: QWidget, key: str, value: dict, submission_type: str | None = None, sub_obj:BasicSubmission|None=None) -> QWidget:
""" """
Creates form widget Creates form widget
@@ -449,6 +452,8 @@ class SubmissionFormWidget(QWidget):
Returns: Returns:
QWidget: Form object QWidget: Form object
""" """
if sub_obj is None:
sub_obj = SubmissionType.query(name=submission_type).get_submission_class()
try: try:
value = value['value'] value = value['value']
except (TypeError, KeyError): except (TypeError, KeyError):
@@ -488,15 +493,15 @@ class SubmissionFormWidget(QWidget):
logger.error(f"Couldn't find {obj.prsr.sub['extraction_kit']}") logger.error(f"Couldn't find {obj.prsr.sub['extraction_kit']}")
obj.ext_kit = uses[0] obj.ext_kit = uses[0]
add_widget.addItems(uses) add_widget.addItems(uses)
case 'submitted_date': # case 'submitted_date':
# NOTE: uses base calendar # # NOTE: uses base calendar
add_widget = QDateEdit(calendarPopup=True) # add_widget = QDateEdit(calendarPopup=True)
# NOTE: sets submitted date based on date found in excel sheet # # NOTE: sets submitted date based on date found in excel sheet
try: # try:
add_widget.setDate(value) # add_widget.setDate(value)
# NOTE: if not found, use today # # NOTE: if not found, use today
except: # except:
add_widget.setDate(date.today()) # add_widget.setDate(date.today())
case 'submission_category': case 'submission_category':
add_widget = QComboBox() add_widget = QComboBox()
cats = ['Diagnostic', "Surveillance", "Research"] cats = ['Diagnostic', "Surveillance", "Research"]
@@ -507,13 +512,23 @@ class SubmissionFormWidget(QWidget):
cats.insert(0, cats.pop(cats.index(submission_type))) cats.insert(0, cats.pop(cats.index(submission_type)))
add_widget.addItems(cats) add_widget.addItems(cats)
case _: case _:
# NOTE: anything else gets added in as a line edit if key in sub_obj.timestamps():
add_widget = QLineEdit() add_widget = QDateEdit(calendarPopup=True)
# logger.debug(f"Setting widget text to {str(value).replace('_', ' ')}") # NOTE: sets submitted date based on date found in excel sheet
add_widget.setText(str(value).replace("_", " ")) try:
add_widget.setDate(value)
# NOTE: if not found, use today
except:
add_widget.setDate(date.today())
else:
# NOTE: anything else gets added in as a line edit
add_widget = QLineEdit()
# logger.debug(f"Setting widget text to {str(value).replace('_', ' ')}")
add_widget.setText(str(value).replace("_", " "))
if add_widget is not None: if add_widget is not None:
add_widget.setObjectName(key) add_widget.setObjectName(key)
add_widget.setParent(parent) add_widget.setParent(parent)
# add_widget.setStyleSheet(main_form_style)
return add_widget return add_widget
def update_missing(self): def update_missing(self):
@@ -569,6 +584,7 @@ class SubmissionFormWidget(QWidget):
self.label = self.ReagentParsedLabel(reagent=reagent) self.label = self.ReagentParsedLabel(reagent=reagent)
layout.addWidget(self.label) layout.addWidget(self.label)
self.lot = self.ReagentLot(reagent=reagent, extraction_kit=extraction_kit) self.lot = self.ReagentLot(reagent=reagent, extraction_kit=extraction_kit)
# self.lot.setStyleSheet(main_form_style)
layout.addWidget(self.lot) layout.addWidget(self.lot)
# NOTE: Remove spacing between reagents # NOTE: Remove spacing between reagents
layout.setContentsMargins(0, 0, 0, 0) layout.setContentsMargins(0, 0, 0, 0)
@@ -615,7 +631,7 @@ class SubmissionFormWidget(QWidget):
Set widget status to updated Set widget status to updated
""" """
self.missing = True self.missing = True
self.label.updated(self.reagent.type) self.label.updated(self.reagent.role)
class ReagentParsedLabel(QLabel): class ReagentParsedLabel(QLabel):
@@ -692,4 +708,4 @@ class SubmissionFormWidget(QWidget):
# logger.debug(f"New relevant reagents: {relevant_reagents}") # logger.debug(f"New relevant reagents: {relevant_reagents}")
self.setObjectName(f"lot_{reagent.role}") self.setObjectName(f"lot_{reagent.role}")
self.addItems(relevant_reagents) self.addItems(relevant_reagents)
self.setStyleSheet("{ background-color: white }") # self.setStyleSheet(main_form_style)

View File

@@ -43,6 +43,13 @@ LOGDIR = main_aux_dir.joinpath("logs")
row_map = {1: "A", 2: "B", 3: "C", 4: "D", 5: "E", 6: "F", 7: "G", 8: "H"} row_map = {1: "A", 2: "B", 3: "C", 4: "D", 5: "E", 6: "F", 7: "G", 8: "H"}
row_keys = {v: k for k, v in row_map.items()} row_keys = {v: k for k, v in row_map.items()}
main_form_style = '''
QComboBox:!editable, QDateEdit {
background-color:light gray;
}
'''
def check_not_nan(cell_contents) -> bool: def check_not_nan(cell_contents) -> bool:
""" """
@@ -329,6 +336,19 @@ def get_config(settings_path: Path | str | None = None) -> Settings:
return Settings(**settings) return Settings(**settings)
def check_if_app() -> bool:
"""
Checks if the program is running from pyinstaller compiled
Returns:
bool: True if running from pyinstaller. Else False.
"""
if getattr(sys, 'frozen', False):
return True
else:
return False
# Logging formatters # Logging formatters
class GroupWriteRotatingFileHandler(handlers.RotatingFileHandler): class GroupWriteRotatingFileHandler(handlers.RotatingFileHandler):
@@ -362,23 +382,23 @@ class CustomFormatter(logging.Formatter):
BOLD = '\033[1m' BOLD = '\033[1m'
UNDERLINE = '\033[4m' UNDERLINE = '\033[4m'
format = "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s" log_format = "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s"
FORMATS = { FORMATS = {
logging.DEBUG: bcolors.ENDC + format + bcolors.ENDC, logging.DEBUG: bcolors.ENDC + log_format + bcolors.ENDC,
logging.INFO: bcolors.ENDC + format + bcolors.ENDC, logging.INFO: bcolors.ENDC + log_format + bcolors.ENDC,
logging.WARNING: bcolors.WARNING + format + bcolors.ENDC, logging.WARNING: bcolors.WARNING + log_format + bcolors.ENDC,
logging.ERROR: bcolors.FAIL + format + bcolors.ENDC, logging.ERROR: bcolors.FAIL + log_format + bcolors.ENDC,
logging.CRITICAL: bcolors.FAIL + format + bcolors.ENDC logging.CRITICAL: bcolors.FAIL + log_format + bcolors.ENDC
} }
def format(self, record): def format(self, record):
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
if check_if_app(): if check_if_app():
return record log_fmt = self.log_format
else: else:
return formatter.format(record) log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)
class StreamToLogger(object): class StreamToLogger(object):
@@ -490,19 +510,6 @@ def jinja_template_loading() -> Environment:
return env return env
def check_if_app() -> bool:
"""
Checks if the program is running from pyinstaller compiled
Returns:
bool: True if running from pyinstaller. Else False.
"""
if getattr(sys, 'frozen', False):
return True
else:
return False
def convert_well_to_row_column(input_str: str) -> Tuple[int, int]: def convert_well_to_row_column(input_str: str) -> Tuple[int, int]:
""" """
Converts typical alphanumeric (i.e. "A2") to row, column Converts typical alphanumeric (i.e. "A2") to row, column
@@ -591,7 +598,7 @@ class Report(BaseModel):
logger.info(f"Adding {res} from {result} to results.") logger.info(f"Adding {res} from {result} to results.")
self.results.append(res) self.results.append(res)
case _: case _:
logger.error(f"Unknown variable type: {type(result)}") logger.error(f"Unknown variable type: {type(result)} for <Result> entry into <Report>")
def is_empty(self): def is_empty(self):
return bool(self.results) return bool(self.results)