diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py index e980c45..1a5304e 100644 --- a/src/submissions/backend/db/models/__init__.py +++ b/src/submissions/backend/db/models/__init__.py @@ -13,6 +13,7 @@ from sqlalchemy import Column, INTEGER, String, JSON from sqlalchemy.ext.associationproxy import AssociationProxy from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session, InstrumentedAttribute, ColumnProperty from sqlalchemy.ext.declarative import declared_attr +from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.exc import ArgumentError from typing import Any, List, ClassVar from pathlib import Path @@ -237,10 +238,10 @@ class BaseClass(Base): @classmethod def query_or_create(cls, **kwargs) -> Tuple[Any, bool]: new = False - allowed = [k for k, v in cls.__dict__.items() if isinstance(v, InstrumentedAttribute)] + allowed = [k for k, v in cls.__dict__.items() if isinstance(v, InstrumentedAttribute) or isinstance(v, hybrid_property)] # and not isinstance(v.property, _RelationshipDeclared)] sanitized_kwargs = {k: v for k, v in kwargs.items() if k in allowed} - # logger.debug(f"Sanitized kwargs: {sanitized_kwargs}") + logger.debug(f"Sanitized kwargs: {sanitized_kwargs}") instance = cls.query(**sanitized_kwargs) if not instance or isinstance(instance, list): instance = cls() @@ -273,7 +274,7 @@ class BaseClass(Base): return cls.execute_query(**kwargs) @classmethod - def execute_query(cls, query: Query = None, model=None, limit: int = 0, **kwargs) -> Any | List[Any]: + def execute_query(cls, query: Query = None, model=None, limit: int = 0, offset:int|None=None, **kwargs) -> Any | List[Any]: """ Execute sqlalchemy query with relevant defaults. @@ -291,22 +292,32 @@ class BaseClass(Base): # logger.debug(f"Model: {model}") if query is None: query: Query = cls.__database_session__.query(cls) + else: + logger.debug(f"Incoming query: {query}") singles = cls.get_default_info('singles') for k, v in kwargs.items(): - - logger.info(f"Using key: {k} with value: {v}") + logger.info(f"Using key: {k} with value: {v} against {cls}") try: attr = getattr(cls, k) - # NOTE: account for attrs that use list. - if attr.property.uselist: - query = query.filter(attr.contains(v)) - else: - query = query.filter(attr == v) except (ArgumentError, AttributeError) as e: - logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.") + logger.error(f"Attribute {k} unavailable due to:\n\t{e}\n.") + continue + # NOTE: account for attrs that use list. + try: + check = attr.property.uselist + except AttributeError: + check = False + if check: + logger.debug("Got uselist") + query = query.filter(attr.contains(v)) + else: + logger.debug("Single item.") + query = query.filter(attr == v) if k in singles: logger.warning(f"{k} is in singles. Returning only one value.") limit = 1 + if offset: + query.offset(offset) with query.session.no_autoflush: match limit: case 0: @@ -476,13 +487,13 @@ class BaseClass(Base): # logger.debug(f"Attempting to set: {key} to {value}") if key.startswith("_"): return super().__setattr__(key, value) - try: - check = not hasattr(self, key) - except: - return + # try: + check = not hasattr(self, key) + # except: + # return if check: try: - json.dumps(value) + value = json.dumps(value) except TypeError: value = str(value) self._misc_info.update({key: value}) @@ -612,6 +623,7 @@ class BaseClass(Base): if dlg.exec(): pass + class LogMixin(Base): tracking_exclusion: ClassVar = ['artic_technician', 'clientsubmissionsampleassociation', 'submission_reagent_associations', 'submission_equipment_associations', diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index 0e8e563..f93dc88 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -149,12 +149,14 @@ class ClientSubmission(BaseClass, LogMixin): pass # query = query.order_by(cls.submitted_date.desc()) # NOTE: Split query results into pages of size {page_size} - if page_size > 0: - query = query.limit(page_size) + if page_size > 0 and limit == 0: + limit = page_size page = page - 1 if page is not None: - query = query.offset(page * page_size) - return cls.execute_query(query=query, limit=limit, **kwargs) + offset = page * page_size + else: + offset = None + return cls.execute_query(query=query, limit=limit, offset=offset, **kwargs) @classmethod def submissions_to_df(cls, submissiontype: str | None = None, limit: int = 0, @@ -269,7 +271,9 @@ class ClientSubmission(BaseClass, LogMixin): try: assert isinstance(sample, Sample) except AssertionError: + logger.warning(f"Converting {sample} to sql.") sample = sample.to_sql() + logger.debug(sample.__dict__) try: row = sample._misc_info['row'] except (KeyError, AttributeError): @@ -278,10 +282,12 @@ class ClientSubmission(BaseClass, LogMixin): column = sample._misc_info['column'] except KeyError: column = 0 + logger.debug(f"Sample: {sample}") + submission_rank = sample._misc_info['submission_rank'] assoc = ClientSubmissionSampleAssociation( sample=sample, submission=self, - submission_rank=sample._misc_info['submission_rank'], + submission_rank=submission_rank, row=row, column=column ) @@ -310,8 +316,9 @@ class ClientSubmission(BaseClass, LogMixin): for sample in active_samples: sample = sample.to_sql() logger.debug(f"Sample: {sample.id}") - assoc = run.add_sample(sample) - assoc.save() + if sample not in run.sample: + assoc = run.add_sample(sample) + assoc.save() else: logger.warning("Run cancelled.") obj.set_data() diff --git a/src/submissions/backend/excel/parsers/__init__.py b/src/submissions/backend/excel/parsers/__init__.py index 0e0048a..7e5db59 100644 --- a/src/submissions/backend/excel/parsers/__init__.py +++ b/src/submissions/backend/excel/parsers/__init__.py @@ -43,9 +43,10 @@ class DefaultParser(object): """ self.proceduretype = proceduretype try: - self._pyd_object = getattr(pydant, f"Pyd{self.__class__.__name__.replace('Parser', '')}") - except AttributeError: - self._pyd_object = pydant.PydResults + self._pyd_object = getattr(pydant, f"Pyd{self.__class__.__name__.replace('Parser', '').replace('Info', '')}") + except AttributeError as e: + logger.error(f"Couldn't get pyd object: Pyd{self.__class__.__name__.replace('Parser', '').replace('Info', '')}") + self._pyd_object = getattr(pydant, self.__class__.pyd_name) self.workbook = load_workbook(self.filepath, data_only=True) if not range_dict: self.range_dict = self.__class__.default_range_dict @@ -118,7 +119,7 @@ class DefaultTABLEParser(DefaultParser): if isinstance(key, str): key = key.lower().replace(" ", "_") key = re.sub(r"_(\(.*\)|#)", "", key) - logger.debug(f"Row {ii} values: {key}: {value}") + # logger.debug(f"Row {ii} values: {key}: {value}") output[key] = value yield output @@ -126,4 +127,4 @@ class DefaultTABLEParser(DefaultParser): return [self._pyd_object(**output) for output in self.parsed_info] from .clientsubmission_parser import * -from backend.excel.parsers.results_parsers.pcr_results_parser import * +from backend.excel.parsers.results_parsers.pcr_results_parser import PCRInfoParser, PCRSampleParser diff --git a/src/submissions/backend/excel/parsers/clientsubmission_parser.py b/src/submissions/backend/excel/parsers/clientsubmission_parser.py index 7c82922..ac96e9e 100644 --- a/src/submissions/backend/excel/parsers/clientsubmission_parser.py +++ b/src/submissions/backend/excel/parsers/clientsubmission_parser.py @@ -74,6 +74,8 @@ class ClientSubmissionInfoParser(DefaultKEYVALUEParser, SubmissionTyperMixin): Object for retrieving submitter info from "sample list" sheet """ + pyd_name = "PydClientSubmission" + default_range_dict = [dict( start_row=2, end_row=18, @@ -110,6 +112,8 @@ class ClientSubmissionSampleParser(DefaultTABLEParser, SubmissionTyperMixin): Object for retrieving submitter samples from "sample list" sheet """ + pyd_name = "PydSample" + default_range_dict = [dict( header_row=19, end_row=115, @@ -126,7 +130,7 @@ class ClientSubmissionSampleParser(DefaultTABLEParser, SubmissionTyperMixin): def parsed_info(self) -> Generator[dict, None, None]: output = super().parsed_info for ii, sample in enumerate(output): - logger.debug(f"Parsed info sample: {sample}") + # logger.debug(f"Parsed info sample: {sample}") if isinstance(sample["row"], str) and sample["row"].lower() in ascii_lowercase[0:8]: try: sample["row"] = row_keys[sample["row"]] diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py index 35b09a8..36bbdcf 100644 --- a/src/submissions/backend/validators/pydant.py +++ b/src/submissions/backend/validators/pydant.py @@ -290,6 +290,16 @@ class PydSample(PydBaseClass): value = row_keys[value] return value + def improved_dict(self, dictionaries: bool = True) -> dict: + output = super().improved_dict(dictionaries=dictionaries) + output['name'] = self.sample_id + del output['sampletype'] + return output + + def to_sql(self): + sql = super().to_sql() + sql._misc_info["submission_rank"] = self.submission_rank + return sql class PydTips(BaseModel): name: str diff --git a/src/submissions/frontend/widgets/procedure_creation.py b/src/submissions/frontend/widgets/procedure_creation.py index 2172ffb..656004d 100644 --- a/src/submissions/frontend/widgets/procedure_creation.py +++ b/src/submissions/frontend/widgets/procedure_creation.py @@ -90,7 +90,7 @@ class ProcedureCreation(QDialog): plate_map=self.plate_map, edit=self.edit ) - with open("web.html", "w") as f: + with open("procedure_creation_rendered.html", "w") as f: f.write(html) self.webview.setHtml(html) diff --git a/src/submissions/frontend/widgets/sample_checker.py b/src/submissions/frontend/widgets/sample_checker.py index 39d5959..62e7669 100644 --- a/src/submissions/frontend/widgets/sample_checker.py +++ b/src/submissions/frontend/widgets/sample_checker.py @@ -5,11 +5,9 @@ from PyQt6.QtCore import Qt, pyqtSlot from PyQt6.QtWebChannel import QWebChannel from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QGridLayout - from backend.db.models import ClientSubmission from backend.validators import PydSample, RSLNamer -from tools import get_application_from_parent, jinja_template_loading - +from tools import get_application_from_parent, jinja_template_loading, render_details_template env = jinja_template_loading() @@ -24,6 +22,7 @@ class SampleChecker(QDialog): self.rsl_plate_number = RSLNamer.construct_new_plate_name(clientsubmission.to_dict()) else: self.rsl_plate_number = clientsubmission + logger.debug(f"RSL Plate number: {self.rsl_plate_number}") self.samples = samples self.setWindowTitle(title) self.app = get_application_from_parent(parent) @@ -38,22 +37,27 @@ class SampleChecker(QDialog): # NOTE: Used to maintain javascript functions. template = env.get_template("sample_checker.html") template_path = Path(template.environment.loader.__getattribute__("searchpath")[0]) - with open(template_path.joinpath("css", "styles.css"), "r") as f: - css = f.read() + # with open(template_path.joinpath("css", "styles.css"), "r") as f: + # css = [f.read()] try: samples = self.formatted_list except AttributeError as e: logger.error(f"Problem getting sample list: {e}") samples = [] - html = template.render(samples=samples, css=css, rsl_plate_number=self.rsl_plate_number) + # html = template.render(samples=samples, css=css, rsl_plate_number=self.rsl_plate_number) + html = render_details_template(template_name="sample_checker", samples=samples, rsl_plate_number=self.rsl_plate_number) self.webview.setHtml(html) + self.webview.page().setWebChannel(self.channel) QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel self.buttonBox = QDialogButtonBox(QBtn) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.layout.addWidget(self.buttonBox, 11, 9, 1, 1, alignment=Qt.AlignmentFlag.AlignRight) self.setLayout(self.layout) - self.webview.page().setWebChannel(self.channel) + + with open("sample_checker_rendered.html", "w") as f: + f.write(html) + logger.debug(f"HTML sample checker written!") @pyqtSlot(str, str, str) def text_changed(self, submission_rank: str, key: str, new_value: str): @@ -65,8 +69,8 @@ class SampleChecker(QDialog): return item.__setattr__(key, new_value) - @pyqtSlot(str, bool) - def enable_sample(self, submission_rank: str, enabled: bool): + @pyqtSlot(int, bool) + def enable_sample(self, submission_rank: int, enabled: bool): logger.debug(f"Name: {submission_rank}, Enabled: {enabled}") try: item = next((sample for sample in self.samples if int(submission_rank) == sample.submission_rank)) diff --git a/src/submissions/frontend/widgets/submission_table.py b/src/submissions/frontend/widgets/submission_table.py index f80b90c..2541cc6 100644 --- a/src/submissions/frontend/widgets/submission_table.py +++ b/src/submissions/frontend/widgets/submission_table.py @@ -325,8 +325,9 @@ class SubmissionsTree(QTreeView): """ indexes = self.selectedIndexes() dicto = next((item.data(1) for item in indexes if item.data(1))) + logger.debug(f"Dicto: {pformat(dicto)}") query_obj = dicto['item_type'].query(name=dicto['query_str'], limit=1) - logger.debug(query_obj) + logger.debug(f"Querying: {query_obj}") # NOTE: Convert to data in id column (i.e. column 0) # id = id.sibling(id.row(), 0).data() # logger.debug(id.model().query_group_object(id.row())) diff --git a/src/submissions/frontend/widgets/submission_widget.py b/src/submissions/frontend/widgets/submission_widget.py index dcd5286..2a4163f 100644 --- a/src/submissions/frontend/widgets/submission_widget.py +++ b/src/submissions/frontend/widgets/submission_widget.py @@ -141,6 +141,11 @@ class SubmissionFormContainer(QWidget): checker = SampleChecker(self, "Sample Checker", self.pydsamples) if checker.exec(): # logger.debug(pformat(self.pydclientsubmission.sample)) + try: + assert isinstance(self.pydclientsubmission, PydClientSubmission) + except AssertionError as e: + logger.error(f"Got wrong type for {self.pydclientsubmission}: {type(self.pydclientsubmission)}") + raise e self.form = self.pydclientsubmission.to_form(parent=self) self.form.samples = self.pydsamples self.layout().addWidget(self.form) diff --git a/src/submissions/templates/details.html b/src/submissions/templates/details.html index 7145c36..ef608a3 100644 --- a/src/submissions/templates/details.html +++ b/src/submissions/templates/details.html @@ -24,23 +24,23 @@ {% block script %} {% if not child %} - -{% endif %} + + + + + + + + + + + + + {% for j in js%} - {% endblock %} + + + + {{ super() }} + {% endblock %}