Mid clean-up.

This commit is contained in:
lwark
2025-01-10 13:49:24 -06:00
parent d93da3c90c
commit 5cded949ed
12 changed files with 178 additions and 90 deletions

View File

@@ -1,4 +1,6 @@
- [ ] Find a way to merge AddEdit with ReagentAdder - [ ] Stop displacing date on Irida controls and just do what Turnaround time does.
- [ ] Get Manager window working for KitType, maybe SubmissionType
- [x] Find a way to merge AddEdit with ReagentAdder
- [x] Find a way to merge omni_search and sample_search - [x] Find a way to merge omni_search and sample_search
- [x] Allow parsing of custom fields to a json 'custom' field in _basicsubmissions - [x] Allow parsing of custom fields to a json 'custom' field in _basicsubmissions
- [x] Upgrade to generators when returning lists. - [x] Upgrade to generators when returning lists.
@@ -13,7 +15,7 @@
- [x] Fix Artic RSLNamer - [x] Fix Artic RSLNamer
- [x] Put "Not applicable" reagents in to_dict() method. - [x] Put "Not applicable" reagents in to_dict() method.
- Currently in to_pydantic(). - Currently in to_pydantic().
- [x] Critical: Convert Json lits to dicts so I can have them update properly without using crashy Sqlalchemy-json - [x] Critical: Convert Json list to dicts so I can have them update properly without using crashy Sqlalchemy-json
- Was actually not necessary. - Was actually not necessary.
- [x] Fix Parsed/Missing mix ups. - [x] Fix Parsed/Missing mix ups.
- [x] Have sample parser check for controls and add to reagents? - [x] Have sample parser check for controls and add to reagents?

View File

@@ -4,6 +4,7 @@ Contains all models for sqlalchemy
from __future__ import annotations from __future__ import annotations
import sys, logging import sys, logging
from pandas import DataFrame from pandas import DataFrame
from pydantic import BaseModel
from sqlalchemy import Column, INTEGER, String, JSON from sqlalchemy import Column, INTEGER, String, JSON
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
@@ -17,6 +18,7 @@ from tools import report_result
if 'pytest' in sys.modules: if 'pytest' in sys.modules:
sys.path.append(Path(__file__).parents[4].absolute().joinpath("tests").__str__()) sys.path.append(Path(__file__).parents[4].absolute().joinpath("tests").__str__())
# NOTE: For inheriting in LogMixin
Base: DeclarativeMeta = declarative_base() Base: DeclarativeMeta = declarative_base()
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -31,6 +33,7 @@ class LogMixin(Base):
if len(name) > 64: if len(name) > 64:
name = name.replace("<", "").replace(">", "") name = name.replace("<", "").replace(">", "")
if len(name) > 64: if len(name) > 64:
# NOTE: As if re'agent'
name = name.replace("agent", "") name = name.replace("agent", "")
if len(name) > 64: if len(name) > 64:
name = f"...{name[-61:]}" name = f"...{name[-61:]}"
@@ -116,7 +119,7 @@ class BaseClass(Base):
return dict(singles=singles) return dict(singles=singles)
@classmethod @classmethod
def find_regular_subclass(cls, name: str | None = None) -> Any: def find_regular_subclass(cls, name: str = "") -> Any:
""" """
Args: Args:
@@ -126,8 +129,9 @@ class BaseClass(Base):
Any: Subclass of this object Any: Subclass of this object
""" """
if not name: # if not name:
return cls # logger.warning("You need to include a name of what you're looking for.")
# return cls
if " " in name: if " " in name:
search = name.title().replace(" ", "") search = name.title().replace(" ", "")
else: else:
@@ -171,7 +175,7 @@ class BaseClass(Base):
try: try:
records = [obj.to_sub_dict(**kwargs) for obj in objects] records = [obj.to_sub_dict(**kwargs) for obj in objects]
except AttributeError: except AttributeError:
records = [obj.to_dict() for obj in objects] records = [obj.to_omnigui_dict() for obj in objects]
return DataFrame.from_records(records) return DataFrame.from_records(records)
@classmethod @classmethod
@@ -190,7 +194,7 @@ class BaseClass(Base):
Execute sqlalchemy query with relevant defaults. 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, allows for plugging in. Defaults to None
query (Query, optional): input query object. Defaults to None query (Query, optional): input query object. Defaults to None
limit (int): Maximum number of results. (0 = all). Defaults to 0 limit (int): Maximum number of results. (0 = all). Defaults to 0
@@ -237,13 +241,28 @@ class BaseClass(Base):
report.add_result(Result(msg=e, status="Critical")) report.add_result(Result(msg=e, status="Critical"))
return report return report
def to_dict(self): def to_omnigui_dict(self) -> dict:
"""
For getting any object in an omni-thing friendly output.
Returns:
dict: Dictionary of object minus _sa_instance_state with id at the front.
"""
dicto = {k: v for k, v in self.__dict__.items() if k not in ["_sa_instance_state"]} dicto = {k: v for k, v in self.__dict__.items() if k not in ["_sa_instance_state"]}
try:
dicto = {'id': dicto.pop('id'), **dicto} dicto = {'id': dicto.pop('id'), **dicto}
except KeyError:
pass
return dicto return dicto
@classmethod @classmethod
def get_pydantic_model(cls): def get_pydantic_model(cls) -> BaseModel:
"""
Gets the pydantic model corresponding to this object.
Returns:
Pydantic model with name "Pyd{cls.__name__}"
"""
from backend.validators import pydant from backend.validators import pydant
try: try:
model = getattr(pydant, f"Pyd{cls.__name__}") model = getattr(pydant, f"Pyd{cls.__name__}")
@@ -252,7 +271,13 @@ class BaseClass(Base):
return model return model
@classproperty @classproperty
def add_edit_tooltips(self): def add_edit_tooltips(self) -> dict:
"""
Gets tooltips for Omni-add-edit
Returns:
dict: custom dictionary for this class.
"""
return dict() return dict()
@@ -270,7 +295,7 @@ class ConfigItem(BaseClass):
@classmethod @classmethod
def get_config_items(cls, *args) -> ConfigItem | List[ConfigItem]: def get_config_items(cls, *args) -> ConfigItem | List[ConfigItem]:
""" """
Get desired config items from database Get desired config items, or all from database
Returns: Returns:
ConfigItem|List[ConfigItem]: Config item(s) ConfigItem|List[ConfigItem]: Config item(s)
@@ -283,6 +308,7 @@ class ConfigItem(BaseClass):
case 1: case 1:
config_items = query.filter(cls.key == args[0]).first() config_items = query.filter(cls.key == args[0]).first()
case _: case _:
# NOTE: All items whose key field is in args.
config_items = query.filter(cls.key.in_(args)).all() config_items = query.filter(cls.key.in_(args)).all()
return config_items return config_items

View File

@@ -24,7 +24,7 @@ class AuditLog(Base):
changes = Column(JSON) changes = Column(JSON)
def __repr__(self): def __repr__(self):
return f"<{self.user} @ {self.time}>" return f"<{self.object}: {self.user} @ {self.time}>"
@classmethod @classmethod
def query(cls, start_date: date | str | int | None = None, end_date: date | str | int | None = None) -> List["AuditLog"]: def query(cls, start_date: date | str | int | None = None, end_date: date | str | int | None = None) -> List["AuditLog"]:

View File

@@ -255,6 +255,7 @@ class Control(BaseClass):
f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicSubmission") f"Could not get polymorph {polymorphic_identity} of {cls} due to {e}, falling back to BasicSubmission")
case _: case _:
pass pass
# NOTE: if attrs passed in and this cls doesn't have all attributes in attr
if attrs and any([not hasattr(cls, attr) for attr in attrs.keys()]): if attrs and any([not hasattr(cls, attr) for attr in attrs.keys()]):
# NOTE: looks for first model that has all included kwargs # NOTE: looks for first model that has all included kwargs
try: try:
@@ -272,6 +273,9 @@ class Control(BaseClass):
Args: Args:
parent (QWidget): chart holding widget to add buttons to. parent (QWidget): chart holding widget to add buttons to.
Returns:
None: Child methods will return things.
""" """
return None return None
@@ -284,7 +288,7 @@ class Control(BaseClass):
chart_settings (dict): settings passed down from chart widget chart_settings (dict): settings passed down from chart widget
ctx (Settings): settings passed down from gui ctx (Settings): settings passed down from gui
""" """
return None return Report(), None
def delete(self): def delete(self):
self.__database_session__.delete(self) self.__database_session__.delete(self)
@@ -315,8 +319,14 @@ class PCRControl(Control):
Returns: Returns:
dict: Output dict of name, ct, subtype, target, reagent_lot and submitted_date dict: Output dict of name, ct, subtype, target, reagent_lot and submitted_date
""" """
return dict(name=self.name, ct=self.ct, subtype=self.subtype, target=self.target, reagent_lot=self.reagent_lot, return dict(
submitted_date=self.submitted_date.date()) name=self.name,
ct=self.ct,
subtype=self.subtype,
target=self.target,
reagent_lot=self.reagent_lot,
submitted_date=self.submitted_date.date()
)
@classmethod @classmethod
@report_result @report_result
@@ -403,9 +413,9 @@ class IridaControl(Control):
kraken = self.kraken kraken = self.kraken
except TypeError: except TypeError:
kraken = {} kraken = {}
kraken_cnt_total = sum([kraken[item]['kraken_count'] for item in kraken]) kraken_cnt_total = sum([item['kraken_count'] for item in kraken.values()])
new_kraken = [dict(name=item, kraken_count=kraken[item]['kraken_count'], new_kraken = [dict(name=item, kraken_count=kraken[item]['kraken_count'],
kraken_percent="{0:.0%}".format(kraken[item]['kraken_count'] / kraken_cnt_total), kraken_percent=f"{kraken[item]['kraken_count'] / kraken_cnt_total:0.2%}",
target=item in self.controltype.targets) target=item in self.controltype.targets)
for item in kraken] for item in kraken]
new_kraken = sorted(new_kraken, key=itemgetter('kraken_count'), reverse=True) new_kraken = sorted(new_kraken, key=itemgetter('kraken_count'), reverse=True)
@@ -479,6 +489,7 @@ class IridaControl(Control):
@classmethod @classmethod
def make_parent_buttons(cls, parent: QWidget) -> None: def make_parent_buttons(cls, parent: QWidget) -> None:
""" """
Creates buttons for controlling
Args: Args:
parent (QWidget): chart holding widget to add buttons to. parent (QWidget): chart holding widget to add buttons to.
@@ -486,6 +497,7 @@ class IridaControl(Control):
""" """
super().make_parent_buttons(parent=parent) super().make_parent_buttons(parent=parent)
rows = parent.layout.rowCount() - 2 rows = parent.layout.rowCount() - 2
# NOTE: check box for consolidating off-target items
checker = QCheckBox(parent) checker = QCheckBox(parent)
checker.setChecked(True) checker.setChecked(True)
checker.setObjectName("irida_check") checker.setObjectName("irida_check")
@@ -703,6 +715,12 @@ class IridaControl(Control):
df = df[df.name not in exclude] df = df[df.name not in exclude]
return df return df
def to_pydantic(self): def to_pydantic(self) -> "PydIridaControl":
"""
Constructs a pydantic version of this object.
Returns:
PydIridaControl: This object as a pydantic model.
"""
from backend.validators import PydIridaControl from backend.validators import PydIridaControl
return PydIridaControl(**self.__dict__) return PydIridaControl(**self.__dict__)

View File

@@ -129,8 +129,10 @@ class KitType(BaseClass):
""" """
return f"<KitType({self.name})>" return f"<KitType({self.name})>"
def get_reagents(self, required: bool = False, submission_type: str | SubmissionType | None = None) -> Generator[ def get_reagents(self,
ReagentRole, None, None]: required: bool = False,
submission_type: str | SubmissionType | None = None
) -> Generator[ReagentRole, None, None]:
""" """
Return ReagentTypes linked to kit through KitTypeReagentTypeAssociation. Return ReagentTypes linked to kit through KitTypeReagentTypeAssociation.
@@ -240,23 +242,23 @@ class KitType(BaseClass):
dict: Dictionary containing relevant info for SubmissionType construction dict: Dictionary containing relevant info for SubmissionType construction
""" """
base_dict = dict(name=self.name, reagent_roles=[], equipment_roles=[]) base_dict = dict(name=self.name, reagent_roles=[], equipment_roles=[])
for k, v in self.construct_xl_map_for_use(submission_type=submission_type): for key, value in self.construct_xl_map_for_use(submission_type=submission_type):
try: try:
assoc = next(item for item in self.kit_reagentrole_associations if item.reagent_role.name == k) assoc = next(item for item in self.kit_reagentrole_associations if item.reagent_role.name == key)
except StopIteration as e: except StopIteration as e:
continue continue
for kk, vv in assoc.to_export_dict().items(): for kk, vv in assoc.to_export_dict().items():
v[kk] = vv value[kk] = vv
base_dict['reagent_roles'].append(v) base_dict['reagent_roles'].append(value)
for k, v in submission_type.construct_field_map("equipment"): for key, value in submission_type.construct_field_map("equipment"):
try: try:
assoc = next(item for item in submission_type.submissiontype_equipmentrole_associations if assoc = next(item for item in submission_type.submissiontype_equipmentrole_associations if
item.equipment_role.name == k) item.equipment_role.name == key)
except StopIteration: except StopIteration:
continue continue
for kk, vv in assoc.to_export_dict(extraction_kit=self).items(): for kk, vv in assoc.to_export_dict(extraction_kit=self).items():
v[kk] = vv value[kk] = vv
base_dict['equipment_roles'].append(v) base_dict['equipment_roles'].append(value)
return base_dict return base_dict
@classmethod @classmethod
@@ -402,6 +404,7 @@ class ReagentRole(BaseClass):
case _: case _:
pass pass
assert reagent.role assert reagent.role
# NOTE: Get all roles common to the reagent and the kit.
result = set(kit_type.reagent_roles).intersection(reagent.role) result = set(kit_type.reagent_roles).intersection(reagent.role)
return next((item for item in result), None) return next((item for item in result), None)
match name: match name:
@@ -500,7 +503,7 @@ class Reagent(BaseClass, LogMixin):
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 # NOTE: The notation for not having an expiry is 1970.01.01
if self.expiry.year == 1970: if self.expiry.year == 1970:
place_holder = "NA" place_holder = "NA"
else: else:
@@ -555,7 +558,7 @@ class Reagent(BaseClass, LogMixin):
instance = PydReagent(**kwargs) instance = PydReagent(**kwargs)
new = True new = True
instance, _ = instance.toSQL() instance, _ = instance.toSQL()
logger.debug(f"Instance: {instance}") logger.info(f"Instance from query or create: {instance}")
return instance, new return instance, new
@classmethod @classmethod
@@ -609,33 +612,70 @@ class Reagent(BaseClass, LogMixin):
pass pass
return cls.execute_query(query=query, limit=limit) return cls.execute_query(query=query, limit=limit)
def set_attribute(self, key, value):
match key:
case "lot":
value = value.upper()
case "role":
match value:
case ReagentRole():
role = value
case str():
role = ReagentRole.query(name=value, limit=1)
case _:
return
if role and role not in self.role:
self.role.append(role)
return
case "comment":
return
case "expiry":
if isinstance(value, str):
value = date(year=1970, month=1, day=1)
# NOTE: if min time is used, any reagent set to expire today (Bac postive control, eg) will have expired at midnight and therefore be flagged.
# NOTE: Make expiry at date given, plus maximum time = end of day
value = datetime.combine(value, datetime.max.time())
value = value.replace(tzinfo=timezone)
case _:
pass
logger.debug(f"Role to be set to: {value}")
try:
self.__setattr__(key, value)
except AttributeError as e:
logger.error(f"Could not set {key} due to {e}")
@check_authorization @check_authorization
def edit_from_search(self, obj, **kwargs): def edit_from_search(self, obj, **kwargs):
from frontend.widgets.misc import AddReagentForm from frontend.widgets.omni_add_edit import AddEdit
role = ReagentRole.query(kwargs['role']) role = ReagentRole.query(kwargs['role'])
if role: if role:
role_name = role.name role_name = role.name
else: else:
role_name = None role_name = None
dlg = AddReagentForm(reagent_lot=self.lot, reagent_role=role_name, expiry=self.expiry, reagent_name=self.name) # dlg = AddReagentForm(reagent_lot=self.lot, reagent_role=role_name, expiry=self.expiry, reagent_name=self.name)
dlg = AddEdit(parent=None, instance=self)
if dlg.exec(): if dlg.exec():
vars = dlg.parse_form() pyd = dlg.parse_form()
for key, value in vars.items(): for field in pyd.model_fields:
match key: self.set_attribute(field, pyd.__getattribute__(field))
case "expiry": # for key, value in vars.items():
if isinstance(value, str): # match key:
field_value = datetime.strptime(value, "%Y-%m-%d") # case "expiry":
elif isinstance(value, date): # if isinstance(value, str):
field_value = datetime.combine(value, datetime.max.time()) # field_value = datetime.strptime(value, "%Y-%m-%d")
else: # elif isinstance(value, date):
field_value = value # field_value = datetime.combine(value, datetime.max.time())
field_value.replace(tzinfo=timezone) # else:
case "role": # field_value = value
continue # field_value.replace(tzinfo=timezone)
case _: # case "role":
field_value = value # continue
self.__setattr__(key, field_value) # case _:
# field_value = value
# self.__setattr__(key, field_value)
self.save() self.save()
# print(self.__dict__)
@classproperty @classproperty
def add_edit_tooltips(self): def add_edit_tooltips(self):
@@ -767,7 +807,7 @@ class SubmissionType(BaseClass):
Grabs the default excel template file. Grabs the default excel template file.
Returns: Returns:
bytes: The excel sheet. bytes: The Excel sheet.
""" """
submission_type = cls.query(name="Bacterial Culture") submission_type = cls.query(name="Bacterial Culture")
return submission_type.template_file return submission_type.template_file
@@ -787,13 +827,13 @@ class SubmissionType(BaseClass):
def set_template_file(self, filepath: Path | str): def set_template_file(self, filepath: Path | str):
""" """
Sets the binary store to an excel file. Sets the binary store to an Excel file.
Args: Args:
filepath (Path | str): Path to the template file. filepath (Path | str): Path to the template file.
Raises: Raises:
ValueError: Raised if file is not excel file. ValueError: Raised if file is not Excel file.
""" """
if isinstance(filepath, str): if isinstance(filepath, str):
filepath = Path(filepath) filepath = Path(filepath)

View File

@@ -375,6 +375,9 @@ class BasicSubmission(BaseClass, LogMixin):
output["contact_phone"] = contact_phone output["contact_phone"] = contact_phone
output["custom"] = custom output["custom"] = custom
output["controls"] = controls output["controls"] = controls
try:
output["completed_date"] = self.completed_date.strftime("%Y-%m-%d")
except AttributeError:
output["completed_date"] = self.completed_date output["completed_date"] = self.completed_date
return output return output

View File

@@ -3,6 +3,7 @@ contains writer objects for pushing values to submission sheet templates.
""" """
import logging import logging
from copy import copy from copy import copy
from datetime import datetime
from operator import itemgetter from operator import itemgetter
from pprint import pformat from pprint import pformat
from typing import List, Generator, Tuple from typing import List, Generator, Tuple

View File

@@ -48,7 +48,7 @@ class PydReagent(BaseModel):
def rescue_type_with_lookup(cls, value, values): def rescue_type_with_lookup(cls, value, values):
if value is None and values.data['lot'] is not None: if value is None and values.data['lot'] is not None:
try: try:
return Reagent.query(lot=values.data['lot'].name) return Reagent.query(lot=values.data['lot']).name
except AttributeError: except AttributeError:
return value return value
return value return value
@@ -133,28 +133,8 @@ class PydReagent(BaseModel):
for key, value in self.__dict__.items(): for key, value in self.__dict__.items():
if isinstance(value, dict): if isinstance(value, dict):
value = value['value'] value = value['value']
# NOTE: set fields based on keys in dictionary # NOTE: reagent method sets fields based on keys in dictionary
match key: reagent.set_attribute(key, value)
case "lot":
reagent.lot = value.upper()
case "role":
reagent_role = ReagentRole.query(name=value)
if reagent_role is not None:
reagent.role.append(reagent_role)
case "comment":
continue
case "expiry":
if isinstance(value, str):
value = date(year=1970, month=1, day=1)
# NOTE: if min time is used, any reagent set to expire today (Bac postive control, eg) will have expired at midnight and therefore be flagged.
# NOTE: Make expiry at date given, plus now time + 1 hour
value = datetime.combine(value, datetime.max.time())
reagent.expiry = value.replace(tzinfo=timezone)
case _:
try:
reagent.__setattr__(key, value)
except AttributeError:
logger.error(f"Couldn't set {key} to {value}")
if submission is not None and reagent not in submission.reagents: if submission is not None and reagent not in submission.reagents:
assoc = SubmissionReagentAssociation(reagent=reagent, submission=submission) assoc = SubmissionReagentAssociation(reagent=reagent, submission=submission)
assoc.comments = self.comment assoc.comments = self.comment
@@ -830,7 +810,7 @@ class PydSubmission(BaseModel, extra='allow'):
case item if item in instance.timestamps(): case item if item in instance.timestamps():
logger.warning(f"Incoming timestamp key: {item}, with value: {value}") logger.warning(f"Incoming timestamp key: {item}, with value: {value}")
if isinstance(value, date): if isinstance(value, date):
value = datetime.combine(value, datetime.max.time()) value = datetime.combine(value, datetime.now().time())
value = value.replace(tzinfo=timezone) value = value.replace(tzinfo=timezone)
elif isinstance(value, str): elif isinstance(value, str):
value: datetime = datetime.strptime(value, "%Y-%m-%d") value: datetime = datetime.strptime(value, "%Y-%m-%d")

View File

@@ -12,7 +12,7 @@ from PyQt6.QtGui import QAction
from pathlib import Path from pathlib import Path
from markdown import markdown from markdown import markdown
from __init__ import project_path from __init__ import project_path
from backend import SubmissionType, Reagent, BasicSample, Organization from backend import SubmissionType, Reagent, BasicSample, Organization, KitType
from tools import check_if_app, Settings, Report, jinja_template_loading, check_authorization, page_size, is_power_user from tools import check_if_app, Settings, Report, jinja_template_loading, check_authorization, page_size, is_power_user
from .functions import select_save_file, select_open_file from .functions import select_save_file, select_open_file
# from datetime import date # from datetime import date
@@ -84,6 +84,7 @@ class App(QMainWindow):
maintenanceMenu.addAction(self.joinPCRAction) maintenanceMenu.addAction(self.joinPCRAction)
editMenu.addAction(self.editReagentAction) editMenu.addAction(self.editReagentAction)
editMenu.addAction(self.manageOrgsAction) editMenu.addAction(self.manageOrgsAction)
# editMenu.addAction(self.manageKitsAction)
if not is_power_user(): if not is_power_user():
editMenu.setEnabled(False) editMenu.setEnabled(False)
@@ -111,6 +112,7 @@ class App(QMainWindow):
self.yamlImportAction = QAction("Import Type Template", self) self.yamlImportAction = QAction("Import Type Template", self)
self.editReagentAction = QAction("Edit Reagent", self) self.editReagentAction = QAction("Edit Reagent", self)
self.manageOrgsAction = QAction("Manage Clients", self) self.manageOrgsAction = QAction("Manage Clients", self)
self.manageKitsAction = QAction("Manage Kits", self)
def _connectActions(self): def _connectActions(self):
""" """
@@ -129,6 +131,7 @@ class App(QMainWindow):
self.table_widget.pager.current_page.textChanged.connect(self.update_data) self.table_widget.pager.current_page.textChanged.connect(self.update_data)
self.editReagentAction.triggered.connect(self.edit_reagent) self.editReagentAction.triggered.connect(self.edit_reagent)
self.manageOrgsAction.triggered.connect(self.manage_orgs) self.manageOrgsAction.triggered.connect(self.manage_orgs)
self.manageKitsAction.triggered.connect(self.manage_kits)
def showAbout(self): def showAbout(self):
""" """
@@ -219,6 +222,11 @@ class App(QMainWindow):
new_org = dlg.parse_form() new_org = dlg.parse_form()
# logger.debug(new_org.__dict__) # logger.debug(new_org.__dict__)
def manage_kits(self):
dlg = ManagerWindow(parent=self, object_type=KitType, extras=[])
if dlg.exec():
print(dlg.parse_form())
class AddSubForm(QWidget): class AddSubForm(QWidget):
def __init__(self, parent: QWidget): def __init__(self, parent: QWidget):

View File

@@ -11,7 +11,7 @@ import logging
from sqlalchemy.orm.relationships import _RelationshipDeclared from sqlalchemy.orm.relationships import _RelationshipDeclared
from tools import Report, Result from tools import Report, Result, report_result
logger = logging.getLogger(f"submissions.{__name__}") logger = logging.getLogger(f"submissions.{__name__}")
@@ -23,7 +23,7 @@ class AddEdit(QDialog):
self.instance = instance self.instance = instance
self.object_type = instance.__class__ self.object_type = instance.__class__
self.layout = QGridLayout(self) self.layout = QGridLayout(self)
logger.debug(f"Manager: {manager}") # logger.debug(f"Manager: {manager}")
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn) self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept) self.buttonBox.accepted.connect(self.accept)
@@ -36,7 +36,7 @@ class AddEdit(QDialog):
fields = {'name': fields.pop('name'), **fields} fields = {'name': fields.pop('name'), **fields}
except KeyError: except KeyError:
pass pass
logger.debug(pformat(fields, indent=4)) # logger.debug(pformat(fields, indent=4))
height_counter = 0 height_counter = 0
for key, field in fields.items(): for key, field in fields.items():
try: try:
@@ -47,7 +47,7 @@ class AddEdit(QDialog):
logger.debug(f"{key} property: {type(field['class_attr'].property)}") logger.debug(f"{key} property: {type(field['class_attr'].property)}")
# widget = EditProperty(self, key=key, column_type=field.property.expression.type, # widget = EditProperty(self, key=key, column_type=field.property.expression.type,
# value=getattr(self.instance, key)) # value=getattr(self.instance, key))
logger.debug(f"Column type: {field}, Value: {value}") # logger.debug(f"Column type: {field}, Value: {value}")
widget = EditProperty(self, key=key, column_type=field, value=value) widget = EditProperty(self, key=key, column_type=field, value=value)
except AttributeError as e: except AttributeError as e:
logger.error(f"Problem setting widget {key}: {e}") logger.error(f"Problem setting widget {key}: {e}")
@@ -60,6 +60,7 @@ class AddEdit(QDialog):
self.setMinimumSize(600, 50 * height_counter) self.setMinimumSize(600, 50 * height_counter)
self.setLayout(self.layout) self.setLayout(self.layout)
@report_result
def parse_form(self) -> Tuple[BaseModel, Report]: def parse_form(self) -> Tuple[BaseModel, Report]:
report = Report() report = Report()
parsed = {result[0].strip(":"): result[1] for result in [item.parse_form() for item in self.findChildren(EditProperty)] if result[0]} parsed = {result[0].strip(":"): result[1] for result in [item.parse_form() for item in self.findChildren(EditProperty)] if result[0]}

View File

@@ -188,6 +188,8 @@ class EditRelationship(QWidget):
dlg = AddEdit(self, instance=instance, manager=self.parent().object_type.__name__.lower()) dlg = AddEdit(self, instance=instance, manager=self.parent().object_type.__name__.lower())
if dlg.exec(): if dlg.exec():
new_instance = dlg.parse_form() new_instance = dlg.parse_form()
new_instance, result = new_instance.toSQL()
logger.debug(f"New instance: {new_instance}")
addition = getattr(self.parent().instance, self.objectName()) addition = getattr(self.parent().instance, self.objectName())
if isinstance(addition, InstrumentedList): if isinstance(addition, InstrumentedList):
addition.append(new_instance) addition.append(new_instance)
@@ -211,7 +213,7 @@ class EditRelationship(QWidget):
sets data in model sets data in model
""" """
# logger.debug(self.data) # logger.debug(self.data)
self.data = DataFrame.from_records([item.to_dict() for item in self.data]) self.data = DataFrame.from_records([item.to_omnigui_dict() for item in self.data])
try: try:
self.columns_of_interest = [dict(name=item, column=self.data.columns.get_loc(item)) for item in self.extras] self.columns_of_interest = [dict(name=item, column=self.data.columns.get_loc(item)) for item in self.extras]
except (KeyError, AttributeError): except (KeyError, AttributeError):

View File

@@ -68,7 +68,11 @@ class SearchBox(QDialog):
self.object_type = self.original_type self.object_type = self.original_type
else: else:
self.object_type = self.original_type.find_regular_subclass(self.sub_class.currentText()) self.object_type = self.original_type.find_regular_subclass(self.sub_class.currentText())
for iii, searchable in enumerate(self.object_type.searchables): try:
search_fields = self.object_type.searchables
except AttributeError:
search_fields = []
for iii, searchable in enumerate(search_fields):
widget = FieldSearch(parent=self, label=searchable, field_name=searchable) widget = FieldSearch(parent=self, label=searchable, field_name=searchable)
widget.setObjectName(searchable) widget.setObjectName(searchable)
self.layout.addWidget(widget, 1 + iii, 0) self.layout.addWidget(widget, 1 + iii, 0)
@@ -142,7 +146,10 @@ class SearchResults(QTableView):
self.context = kwargs self.context = kwargs
self.parent = parent self.parent = parent
self.object_type = object_type self.object_type = object_type
try:
self.extras = extras + self.object_type.searchables self.extras = extras + self.object_type.searchables
except AttributeError:
self.extras = extras
def setData(self, df: DataFrame) -> None: def setData(self, df: DataFrame) -> None:
""" """