Tips parser complete.

This commit is contained in:
lwark
2024-06-13 09:11:53 -05:00
parent 78c92cd31f
commit e0e3080af0
7 changed files with 311 additions and 66 deletions

View File

@@ -8,13 +8,12 @@ from sqlalchemy.ext.associationproxy import association_proxy
from datetime import date
import logging, re
from tools import check_authorization, setup_lookup, Report, Result
from typing import List, Literal
from typing import List, Literal, Any
from pandas import ExcelFile
from pathlib import Path
from . import Base, BaseClass, Organization
from io import BytesIO
logger = logging.getLogger(f'submissions.{__name__}')
# logger.debug("Table for ReagentType/Reagent relations")
@@ -79,14 +78,6 @@ tiproles_tips = Table(
extend_existing=True
)
submissions_tips = Table(
"_submissions_tips",
Base.metadata,
Column("submission_id", INTEGER, ForeignKey("_basicsubmissions.id")),
Column("tips_id", INTEGER, ForeignKey("_tips.id")),
extend_existing=True
)
process_tiprole = Table(
"_process_tiprole",
Base.metadata,
@@ -95,6 +86,15 @@ process_tiprole = Table(
extend_existing=True
)
equipment_tips = Table(
"_equipment_tips",
Base.metadata,
Column("equipment_id", INTEGER, ForeignKey("_equipment.id")),
Column("tips_id", INTEGER, ForeignKey("_tips.id")),
extend_existing=True
)
class KitType(BaseClass):
"""
Base of kits used in submission processing
@@ -133,7 +133,8 @@ class KitType(BaseClass):
"""
return f"<KitType({self.name})>"
def get_reagents(self, required: bool = False, submission_type: str | SubmissionType | None = None) -> List[ReagentRole]:
def get_reagents(self, required: bool = False, submission_type: str | SubmissionType | None = None) -> List[
ReagentRole]:
"""
Return ReagentTypes linked to kit through KitTypeReagentTypeAssociation.
@@ -143,7 +144,7 @@ class KitType(BaseClass):
Returns:
List[ReagentRole]: List of reagents linked to this kit.
"""
"""
match submission_type:
case SubmissionType():
# logger.debug(f"Getting reagents by SubmissionType {submission_type}")
@@ -609,6 +610,12 @@ class SubmissionType(BaseClass):
cascade="all, delete-orphan"
) #: triple association of KitTypes, ReagentTypes, SubmissionTypes
submissiontype_tiprole_associations = relationship(
"SubmissionTypeTipRoleAssociation",
back_populates="submission_type",
cascade="all, delete-orphan"
)
def __repr__(self) -> str:
"""
Returns:
@@ -656,7 +663,7 @@ class SubmissionType(BaseClass):
Returns:
dict: Map of locations
"""
"""
info = self.info_map
# logger.debug(f"Info map: {info}")
output = {}
@@ -674,7 +681,7 @@ class SubmissionType(BaseClass):
Returns:
dict: sample location map
"""
"""
return self.sample_map
def construct_equipment_map(self) -> dict:
@@ -693,6 +700,15 @@ class SubmissionType(BaseClass):
output[item.equipment_role.name] = emap
return output
def construct_tips_map(self):
output = {}
for item in self.submissiontype_tiprole_associations:
tmap = item.uses
if tmap is None:
tmap = {}
output[item.tip_role.name] = tmap
return output
def get_equipment(self, extraction_kit: str | KitType | None = None) -> List['PydEquipmentRole']:
"""
Returns PydEquipmentRole of all equipment associated with this SubmissionType
@@ -735,7 +751,7 @@ class SubmissionType(BaseClass):
Returns:
BasicSubmission: Submission class
"""
"""
from .submissions import BasicSubmission
return BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.name)
@@ -1071,6 +1087,8 @@ class Equipment(BaseClass):
secondary=equipmentroles_equipment) #: relation to EquipmentRoles
processes = relationship("Process", back_populates="equipment",
secondary=equipment_processes) #: relation to Processes
tips = relationship("Tips", back_populates="equipment",
secondary=equipment_tips) #: relation to Processes
equipment_submission_associations = relationship(
"SubmissionEquipmentAssociation",
back_populates="equipment",
@@ -1467,7 +1485,7 @@ class Process(BaseClass):
backref='process') #: relation to SubmissionEquipmentAssociation
kit_types = relationship("KitType", back_populates='processes',
secondary=kittypes_processes) #: relation to KitType
tip_roles = relationship("TipRoles", back_populates='processes',
tip_roles = relationship("TipRole", back_populates='processes',
secondary=process_tiprole) #: relation to KitType
def __repr__(self) -> str:
@@ -1502,19 +1520,25 @@ class Process(BaseClass):
class TipRole(BaseClass):
id = Column(INTEGER, primary_key=True) #: primary key
name = Column(String(64)) #: name of reagent type
instances = relationship("Tips", back_populates="role",
secondary=tiproles_tips) #: concrete instances of this reagent type
processes = relationship("Process", back_populates="tip_roles", secondary=process_tiprole)
tiprole_submissiontype_associations = relationship(
"SubmissionTypeTipRoleAssociation",
back_populates="tip_role",
cascade="all, delete-orphan"
) #: associated submission
submission_types = association_proxy("tiprole_submissiontype_associations", "submission_type")
def __repr__(self):
return f"<TipRole({self.name})>"
class Tips(BaseClass):
class Tips(BaseClass):
id = Column(INTEGER, primary_key=True) #: primary key
role = relationship("TipRole", back_populates="instances",
secondary=tiproles_tips) #: joined parent reagent type
@@ -1522,10 +1546,64 @@ class Tips(BaseClass):
name="fk_tip_role_id")) #: id of parent reagent type
name = Column(String(64)) #: tip common name
lot = Column(String(64)) #: lot number of tips
submissions = relationship("BasicSubmission", back_populates="tips",
secondary=submissions_tips) #: associated submission
equipment = relationship("Equipment", back_populates="tips",
secondary=equipment_tips) #: associated submission
tips_submission_associations = relationship(
"SubmissionTipsAssociation",
back_populates="tips",
cascade="all, delete-orphan"
) #: associated submission
submissions = association_proxy("tips_submission_associations", 'submission')
def __repr__(self):
return f"<Tips({self.name})>"
@classmethod
def query(cls, name: str | None = None, lot: str | None = None, limit: int = 0, **kwargs) -> Any | List[Any]:
query = cls.__database_session__.query(cls)
match name:
case str():
# logger.debug(f"Lookup Equipment by name str {name}")
query = query.filter(cls.name == name)
case _:
pass
match lot:
case str():
# logger.debug(f"Lookup Equipment by nickname str {nickname}")
query = query.filter(cls.lot == lot)
limit = 1
case _:
pass
return cls.execute_query(query=query, limit=limit)
class SubmissionTypeTipRoleAssociation(BaseClass):
"""
Abstract association between SubmissionType and TipRole
"""
tiprole_id = Column(INTEGER, ForeignKey("_tiprole.id"), primary_key=True) #: id of associated equipment
submissiontype_id = Column(INTEGER, ForeignKey("_submissiontype.id"),
primary_key=True) #: id of associated submission
uses = Column(JSON) #: locations of equipment on the submission type excel sheet.
static = Column(INTEGER,
default=1) #: if 1 this piece of equipment will always be used, otherwise it will need to be selected from list?
submission_type = relationship(SubmissionType,
back_populates="submissiontype_tiprole_associations") #: associated submission
tip_role = relationship(TipRole,
back_populates="tiprole_submissiontype_associations") #: associated equipment
class SubmissionTipsAssociation(BaseClass):
tip_id = Column(INTEGER, ForeignKey("_tips.id"), primary_key=True) #: id of associated equipment
submission_id = Column(INTEGER, ForeignKey("_basicsubmission.id"), primary_key=True) #: id of associated submission
submission = relationship("BasicSubmission",
back_populates="submission_tips_associations") #: associated submission
tips = relationship(Tips,
back_populates="tips_submission_associations") #: associated equipment
role_name = Column(String(32)) #, ForeignKey("_tiprole.name"))
# role = relationship(TipRole)
def to_sub_dict(self):
return dict(role=self.role_name, name=self.tips.name, lot=self.tips.lot)

View File

@@ -10,7 +10,7 @@ from zipfile import ZipFile
from tempfile import TemporaryDirectory
from operator import attrgetter, itemgetter
from pprint import pformat
from . import BaseClass, Reagent, SubmissionType, KitType, Organization, Contact
from . import BaseClass, Reagent, SubmissionType, KitType, Organization, Contact, Tips, TipRole, SubmissionTipsAssociation
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
@@ -96,6 +96,14 @@ class BasicSubmission(BaseClass):
equipment = association_proxy("submission_equipment_associations",
"equipment") #: Association proxy to SubmissionEquipmentAssociation.equipment
submission_tips_associations = relationship(
"SubmissionTipsAssociation",
back_populates="submission",
cascade="all, delete-orphan")
tips = association_proxy("submission_tips_associations",
"tips")
# NOTE: Allows for subclassing into ex. BacterialCulture, Wastewater, etc.
__mapper_args__ = {
"polymorphic_identity": "Basic Submission",
@@ -248,7 +256,6 @@ class BasicSubmission(BaseClass):
ext_info = self.extraction_info
except TypeError:
ext_info = None
output = {
"id": self.id,
"plate_number": self.rsl_plate_num,
@@ -282,16 +289,24 @@ class BasicSubmission(BaseClass):
# logger.debug("Running equipment")
try:
equipment = [item.to_sub_dict() for item in self.submission_equipment_associations]
if len(equipment) == 0:
if not equipment:
equipment = None
except Exception as e:
logger.error(f"Error setting equipment: {e}")
equipment = None
try:
tips = [item.to_sub_dict() for item in self.submission_tips_associations]
if not tips:
tips = None
except Exception as e:
logger.error(f"Error setting tips: {e}")
tips = None
cost_centre = self.cost_centre
else:
reagents = None
samples = None
equipment = None
tips = None
cost_centre = None
# logger.debug("Getting comments")
try:
@@ -315,6 +330,7 @@ class BasicSubmission(BaseClass):
output["extraction_info"] = ext_info
output["comment"] = comments
output["equipment"] = equipment
output["tips"] = tips
output["cost_centre"] = cost_centre
output["signed_by"] = self.signed_by
# logger.debug(f"Setting contact to: {contact} of type: {type(contact)}")
@@ -440,7 +456,7 @@ class BasicSubmission(BaseClass):
excluded = ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents',
'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number', 'gel_controls',
'source_plates', 'pcr_technician', 'ext_technician', 'artic_technician', 'cost_centre',
'signed_by', 'artic_date', 'gel_barcode', 'gel_date', 'ngs_date', 'contact_phone', 'contact']
'signed_by', 'artic_date', 'gel_barcode', 'gel_date', 'ngs_date', 'contact_phone', 'contact', 'tips']
for item in excluded:
try:
df = df.drop(item, axis=1)
@@ -1110,6 +1126,12 @@ class BasicSubmission(BaseClass):
_, assoc = equip.toSQL(submission=self)
# logger.debug(f"Appending SubmissionEquipmentAssociation: {assoc}")
assoc.save()
if equip.tips:
logger.debug("We have tips in this equipment")
for tips in equip.tips:
tassoc = tips.to_sql(submission=self)
tassoc.save()
else:
pass

View File

@@ -10,7 +10,7 @@ import pandas as pd
from openpyxl import load_workbook, Workbook
from pathlib import Path
from backend.db.models import *
from backend.validators import PydSubmission, PydReagent, RSLNamer, PydSample, PydEquipment
from backend.validators import PydSubmission, PydReagent, RSLNamer, PydSample, PydEquipment, PydTips
import logging, re
from collections import OrderedDict
from datetime import date
@@ -57,6 +57,7 @@ class SheetParser(object):
self.parse_reagents()
self.parse_samples()
self.parse_equipment()
self.parse_tips()
self.finalize_parse()
# logger.debug(f"Parser.sub after info scrape: {pformat(self.sub)}")
@@ -101,6 +102,10 @@ class SheetParser(object):
parser = EquipmentParser(xl=self.xl, submission_type=self.submission_type)
self.sub['equipment'] = parser.parse_equipment()
def parse_tips(self):
parser = TipParser(xl=self.xl, submission_type=self.submission_type)
self.sub['tips'] = parser.parse_tips()
def import_kit_validation_check(self):
"""
Enforce that the parser has an extraction kit
@@ -139,13 +144,21 @@ class SheetParser(object):
pyd_dict['reagents'] = [PydReagent(**reagent) for reagent in self.sub['reagents']]
# logger.debug(f"Equipment: {self.sub['equipment']}")
try:
check = len(self.sub['equipment']) == 0
check = bool(self.sub['equipment'])
except TypeError:
check = True
check = False
if check:
pyd_dict['equipment'] = None
pyd_dict['equipment'] = [PydEquipment(**equipment) for equipment in self.sub['equipment']]
else:
pyd_dict['equipment'] = self.sub['equipment']
pyd_dict['equipment'] = None
try:
check = bool(self.sub['tips'])
except TypeError:
check = False
if check:
pyd_dict['tips'] = [PydTips(**tips) for tips in self.sub['tips']]
else:
pyd_dict['tips'] = None
psm = PydSubmission(filepath=self.filepath, **pyd_dict)
return psm
@@ -535,6 +548,7 @@ class SampleParser(object):
samples = remove_key_from_list_of_dicts(samples, "id")
return sorted(samples, key=lambda k: (k['row'], k['column']))
class EquipmentParser(object):
def __init__(self, xl: Workbook, submission_type: str|SubmissionType) -> None:
@@ -567,15 +581,74 @@ class EquipmentParser(object):
# logger.debug(f"Using equipment regex: {regex} on {input}")
try:
return regex.search(input).group().strip("-")
except AttributeError:
except AttributeError as e:
logger.error(f"Error getting asset number for {input}: {e}")
return input
def parse_equipment(self) -> List[PydEquipment]:
def parse_equipment(self) -> List[dict]:
"""
Scrapes equipment from xl sheet
Returns:
List[PydEquipment]: list of equipment
List[dict]: list of equipment
"""
logger.debug(f"Equipment parser going into parsing: {pformat(self.__dict__)}")
output = []
# logger.debug(f"Sheets: {sheets}")
for sheet in self.xl.sheetnames:
ws = self.xl[sheet]
try:
relevant = {k:v for k,v in self.map.items() if v['sheet'] == sheet}
except (TypeError, KeyError) as e:
logger.error(f"Error creating relevant equipment list: {e}")
continue
logger.debug(f"Relevant equipment: {pformat(relevant)}")
previous_asset = ""
for k, v in relevant.items():
logger.debug(f"Checking: {v}")
asset = ws.cell(v['name']['row'], v['name']['column']).value
if not check_not_nan(asset):
asset = previous_asset
else:
previous_asset = asset
asset = self.get_asset_number(input=asset)
logger.debug(f"asset: {asset}")
eq = Equipment.query(name=asset)
process = ws.cell(row=v['process']['row'], column=v['process']['column']).value
try:
output.append(
dict(name=eq.name, processes=[process], role=k, asset_number=eq.asset_number,
nickname=eq.nickname))
except AttributeError:
logger.error(f"Unable to add {eq} to list.")
logger.debug(f"Here is the output so far: {pformat(output)}")
return output
class TipParser(object):
def __init__(self, xl: Workbook, submission_type: str|SubmissionType) -> None:
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
self.submission_type = submission_type
self.xl = xl
self.map = self.fetch_tip_map()
def fetch_tip_map(self) -> List[dict]:
"""
Gets the map of equipment locations in the submission type's spreadsheet
Returns:
List[dict]: List of locations
"""
return self.submission_type.construct_tips_map()
def parse_tips(self) -> List[dict]:
"""
Scrapes equipment from xl sheet
Returns:
List[dict]: list of equipment
"""
# logger.debug(f"Equipment parser going into parsing: {pformat(self.__dict__)}")
output = []
@@ -583,29 +656,34 @@ class EquipmentParser(object):
for sheet in self.xl.sheetnames:
ws = self.xl[sheet]
try:
relevant = [item for item in self.map if item['sheet'] == sheet]
except (TypeError, KeyError):
relevant = {k: v for k, v in self.map.items() if v['sheet'] == sheet}
except (TypeError, KeyError) as e:
logger.error(f"Error creating relevant equipment list: {e}")
continue
# logger.debug(f"Relevant equipment: {pformat(relevant)}")
logger.debug(f"Relevant equipment: {pformat(relevant)}")
previous_asset = ""
for equipment in relevant:
asset = ws.cell(equipment['name']['row'], equipment['name']['column'])
for k, v in relevant.items():
asset = ws.cell(v['name']['row'], v['name']['column']).value
if "lot" in v.keys():
lot = ws.cell(v['lot']['row'], v['lot']['column']).value
else:
lot = None
if not check_not_nan(asset):
asset = previous_asset
else:
previous_asset = asset
asset = self.get_asset_number(input=asset)
eq = Equipment.query(asset_number=asset)
process = ws.cell(row=equipment['process']['row'], column=equipment['process']['column'])
logger.debug(f"asset: {asset}")
eq = Tips.query(lot=lot, name=asset, limit=1)
# process = ws.cell(row=v['process']['row'], column=v['process']['column']).value
try:
output.append(
dict(name=eq.name, processes=[process], role=equipment['role'], asset_number=asset,
nickname=eq.nickname))
dict(name=eq.name, role=k, lot=lot))
except AttributeError:
logger.error(f"Unable to add {eq} to PydEquipment list.")
# logger.debug(f"Here is the output so far: {pformat(output)}")
logger.error(f"Unable to add {eq} to PydTips list.")
logger.debug(f"Here is the output so far: {pformat(output)}")
return output
class PCRParser(object):
"""Object to pull data from Design and Analysis PCR export file."""

View File

@@ -179,4 +179,4 @@ class RSLNamer(object):
from .pydant import PydSubmission, PydKit, PydContact, PydOrganization, PydSample, PydReagent, PydReagentRole, \
PydEquipment, PydEquipmentRole
PydEquipment, PydEquipmentRole, PydTips

View File

@@ -245,7 +245,10 @@ class PydSample(BaseModel, extra='allow'):
instance.__setattr__(key, value)
out_associations = []
if submission is not None:
assoc_type = self.sample_type.replace("Sample", "").strip()
if isinstance(submission, str):
submission = BasicSubmission.query(rsl_plate_num=submission)
assoc_type = submission.submission_type_name
# assoc_type = self.sample_type.replace("Sample", "").strip()
for row, column, aid, submission_rank in zip(self.row, self.column, self.assoc_id, self.submission_rank):
# logger.debug(f"Looking up association with identity: ({submission.submission_type_name} Association)")
# logger.debug(f"Looking up association with identity: ({assoc_type} Association)")
@@ -254,7 +257,7 @@ class PydSample(BaseModel, extra='allow'):
sample=instance,
row=row, column=column, id=aid,
submission_rank=submission_rank)
# logger.debug(f"Using submission_sample_association: {association}")
logger.debug(f"Using submission_sample_association: {association}")
try:
# instance.sample_submission_associations.append(association)
out_associations.append(association)
@@ -272,12 +275,24 @@ class PydSample(BaseModel, extra='allow'):
return {k: getattr(self, k) for k in fields}
class PydTips(BaseModel):
name: str
lot: str|None = Field(default=None)
role: str
def to_sql(self, submission:BasicSubmission):
tips = Tips.query(name=self.name, lot=self.lot, limit=1)
assoc = SubmissionTipsAssociation(submission=submission, tips=tips, role_name=self.role)
return assoc
class PydEquipment(BaseModel, extra='ignore'):
asset_number: str
name: str
nickname: str | None
processes: List[str] | None
role: str | None
tips: List[PydTips]|None = Field(default=None)
@field_validator('processes', mode='before')
@classmethod
@@ -703,18 +718,23 @@ class PydSubmission(BaseModel, extra='allow'):
instance.submission_sample_associations.append(assoc)
case "equipment":
# logger.debug(f"Equipment: {pformat(self.equipment)}")
try:
for equip in self.equipment:
if equip is None:
continue
except UnboundLocalError:
continue
for equip in self.equipment:
equip, association = equip.toSQL(submission=instance)
if association is not None:
association.save()
# association.save()
# logger.debug(
# f"Equipment association SQL object to be added to submission: {association.__dict__}")
instance.submission_equipment_associations.append(association)
case "tips":
for tips in self.tips:
if tips is None:
continue
association = tips.to_sql()
if association is not None:
# association.save()
instance.submission_tips_associations.append(association)
case item if item in instance.jsons():
# logger.debug(f"{item} is a json.")
try:
@@ -949,3 +969,4 @@ class PydEquipmentRole(BaseModel):
"""
from frontend.widgets.equipment_usage import RoleComboBox
return RoleComboBox(parent=parent, role=self, used=used)

View File

@@ -1,9 +1,11 @@
import sys
from pprint import pformat
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (QDialog, QComboBox, QCheckBox,
QLabel, QWidget, QHBoxLayout,
QVBoxLayout, QDialogButtonBox)
from backend.db.models import Equipment, BasicSubmission
from backend.validators.pydant import PydEquipment, PydEquipmentRole
from PyQt6.QtWidgets import (QDialog, QComboBox, QCheckBox,
QLabel, QWidget, QHBoxLayout,
QVBoxLayout, QDialogButtonBox, QGridLayout)
from backend.db.models import Equipment, BasicSubmission, Process
from backend.validators.pydant import PydEquipment, PydEquipmentRole, PydTips
import logging
from typing import List
@@ -56,7 +58,8 @@ class EquipmentUsage(QDialog):
output.append(widget.parse_form())
case _:
pass
return [item for item in output if item != None]
logger.debug(f"parsed output of Equsage form: {pformat(output)}")
return [item for item in output if item is not None]
class LabelRow(QWidget):
@@ -66,7 +69,7 @@ class EquipmentUsage(QDialog):
self.check = QCheckBox()
self.layout.addWidget(self.check)
self.check.stateChanged.connect(self.check_all)
for item in ["Role", "Equipment", "Process"]:
for item in ["Role", "Equipment", "Process", "Tips"]:
l = QLabel(item)
l.setMaximumWidth(200)
l.setMinimumWidth(200)
@@ -84,7 +87,8 @@ class RoleComboBox(QWidget):
def __init__(self, parent, role:PydEquipmentRole, used:list) -> None:
super().__init__(parent)
self.layout = QHBoxLayout()
# self.layout = QHBoxLayout()
self.layout = QGridLayout()
self.role = role
self.check = QCheckBox()
if role.name in used:
@@ -99,15 +103,20 @@ class RoleComboBox(QWidget):
self.process = QComboBox()
self.process.setMaximumWidth(200)
self.process.setMinimumWidth(200)
self.process.setEditable(True)
self.layout.addWidget(self.check)
self.process.setEditable(False)
self.process.currentTextChanged.connect(self.update_tips)
# self.tips = QComboBox()
# self.tips.setMaximumWidth(200)
# self.tips.setMinimumWidth(200)
# self.tips.setEditable(True)
self.layout.addWidget(self.check,0,0)
label = QLabel(f"{role.name}:")
label.setMinimumWidth(200)
label.setMaximumWidth(200)
label.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.layout.addWidget(label)
self.layout.addWidget(self.box)
self.layout.addWidget(self.process)
self.layout.addWidget(label,0,1)
self.layout.addWidget(self.box,0,2)
self.layout.addWidget(self.process,0,3)
self.setLayout(self.layout)
def update_processes(self):
@@ -121,6 +130,28 @@ class RoleComboBox(QWidget):
self.process.clear()
self.process.addItems([item for item in equip2.processes if item in self.role.processes])
def update_tips(self):
process = self.process.currentText()
logger.debug(f"Checking process: {process}")
process = Process.query(name=process)
if process.tip_roles:
for iii, tip_role in enumerate(process.tip_roles):
widget = QComboBox()
tip_choices = [item.name for item in tip_role.instances]
widget.setEditable(False)
widget.addItems(tip_choices)
# logger.debug(f"Tiprole: {tip_role.__dict__}")
widget.setObjectName(f"tips_{tip_role.name}")
widget.setMinimumWidth(100)
widget.setMaximumWidth(100)
self.layout.addWidget(widget, iii, 4)
else:
widget = QLabel("")
widget.setMinimumWidth(100)
widget.setMaximumWidth(100)
self.layout.addWidget(widget,0,4)
def parse_form(self) -> PydEquipment|None:
"""
Creates PydEquipment for values in form
@@ -129,8 +160,17 @@ class RoleComboBox(QWidget):
PydEquipment|None: PydEquipment matching form
"""
eq = Equipment.query(name=self.box.currentText())
tips = [PydTips(name=item.currentText(), role=item.objectName().lstrip("tips").lstrip("_")) for item in self.findChildren(QComboBox) if item.objectName().startswith("tips")]
logger.debug(tips)
try:
return PydEquipment(name=eq.name, processes=[self.process.currentText()], role=self.role.name, asset_number=eq.asset_number, nickname=eq.nickname)
return PydEquipment(
name=eq.name,
processes=[self.process.currentText()],
role=self.role.name,
asset_number=eq.asset_number,
nickname=eq.nickname,
tips=tips
)
except Exception as e:
logger.error(f"Could create PydEquipment due to: {e}")

View File

@@ -55,6 +55,12 @@
&nbsp;&nbsp;&nbsp;&nbsp;<b>{{ item['role'] }}:</b> {{ item['name'] }} ({{ item['asset_number'] }}): {{ item['processes'][0]|replace('\n\t', '<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;') }}<br>
{% endfor %}</p>
{% endif %}
{% if sub['tips'] %}
<h3><u>Tips:</u></h3>
<p>{% for item in sub['tips'] %}
&nbsp;&nbsp;&nbsp;&nbsp;<b>{{ item['role'] }}:</b> {{ item['name'] }} ({{ item['lot'] }})<br>
{% endfor %}</p>
{% endif %}
{% if sub['samples'] %}
<h3><u>Samples:</u></h3>
<p>{% for item in sub['samples'] %}