diff --git a/CHANGELOG.md b/CHANGELOG.md index 7402c15..4d9a55e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## 202409.04 - Fixed wastewater sample writing bug. -- Added regex exclusion for KitTypeReagentRole to trim down Bacteria Positive Control list. +- Added regex exclusion for KitTypeReagentRole.uses to trim down Bacteria Positive Control lot list. ## 202409.03 diff --git a/TODO.md b/TODO.md index 01b83bb..cd07e19 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ -- [ ] Upgrade to generators when returning lists. +- [x] Upgrade to generators when returning lists. - [x] Revamp frontend.widgets.controls_chart to include visualizations? - [x] Convert Parsers to using openpyxl. - The hardest part of this is going to be the sample parsing. I'm onto using the cell formulas in the plate map to suss out the location in the lookup table, but it could get a little recursive up in here. diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py index e71b7d7..370f424 100644 --- a/src/submissions/backend/db/models/kits.py +++ b/src/submissions/backend/db/models/kits.py @@ -14,7 +14,7 @@ from sqlalchemy.orm import relationship, validates, Query from sqlalchemy.ext.associationproxy import association_proxy from datetime import date import logging, re -from tools import check_authorization, setup_lookup, Report, Result, jinja_template_loading +from tools import check_authorization, setup_lookup, Report, Result, jinja_template_loading, check_regex_match from typing import List, Literal, Generator, Any from pandas import ExcelFile from pathlib import Path @@ -175,7 +175,7 @@ class KitType(BaseClass): return [item.reagent_role for item in relevant_associations] # TODO: Move to BasicSubmission? - def construct_xl_map_for_use(self, submission_type: str | SubmissionType) -> Generator[str, str]: + def construct_xl_map_for_use(self, submission_type: str | SubmissionType) -> Generator[(str, str)]: """ Creates map of locations in Excel workbook for a SubmissionType @@ -1139,6 +1139,23 @@ class KitTypeReagentRoleAssociation(BaseClass): base_dict[k] = v return base_dict + def get_all_relevant_reagents(self) -> Generator[Reagent, None, None]: + """ + Creates a generator that will resolve in to a list filling the role associated with this object. + + Returns: + Generator: Generates of reagents. + """ + # logger.debug(f"Attempting lookup of reagents by type: {reagent.type}") + reagents = self.reagent_role.instances + try: + regex = self.uses['exclude_regex'] + except KeyError: + regex = "^$" + relevant_reagents = [reagent for reagent in reagents if + not check_regex_match(pattern=regex, check=str(reagent.lot))] + for rel_reagent in relevant_reagents: + yield rel_reagent class SubmissionReagentAssociation(BaseClass): """ diff --git a/src/submissions/frontend/visualizations/control_charts.py b/src/submissions/frontend/visualizations/control_charts.py index 087c900..4cbe7ff 100644 --- a/src/submissions/frontend/visualizations/control_charts.py +++ b/src/submissions/frontend/visualizations/control_charts.py @@ -124,17 +124,17 @@ class CustomFigure(Figure): fig_len = len(self.data) if len(modes) > 1: for ii, mode in enumerate(modes): - # What I need to do is create a list of bools with the same length as the fig.data + # NOTE: What I need to do is create a list of bools with the same length as the fig.data mode_vis = [True] * fig_len - # And break it into {len(modes)} chunks + # NOTE: And break it into {len(modes)} chunks mode_vis = list(divide_chunks(mode_vis, len(modes))) - # Then, for each chunk, if the chunk index isn't equal to the index of the current mode, set to false + # NOTE: Then, for each chunk, if the chunk index isn't equal to the index of the current mode, set to false for jj, sublist in enumerate(mode_vis): if jj != ii: mode_vis[jj] = [not elem for elem in mode_vis[jj]] - # Finally, flatten list. + # NOTE: Finally, flatten list. mode_vis = [item for sublist in mode_vis for item in sublist] - # Now, yield button to add to list + # NOTE: Now, yield button to add to list yield dict(label=mode, method="update", args=[ {"visible": mode_vis}, {"yaxis.title.text": mode}, diff --git a/src/submissions/frontend/widgets/submission_details.py b/src/submissions/frontend/widgets/submission_details.py index 69f06f4..9c2612d 100644 --- a/src/submissions/frontend/widgets/submission_details.py +++ b/src/submissions/frontend/widgets/submission_details.py @@ -152,9 +152,10 @@ class SubmissionDetails(QDialog): with open(template_path.joinpath("css", "styles.css"), "r") as f: css = f.read() # logger.debug(f"Submission_details: {pformat(self.base_dict)}") + # logger.debug(f"User is power user: {is_power_user()}") self.html = self.template.render(sub=self.base_dict, signing_permission=is_power_user(), css=css) self.webview.setHtml(self.html) - # self.btn.setEnabled(True) + @pyqtSlot(str) def sign_off(self, submission: str | BasicSubmission): diff --git a/src/submissions/frontend/widgets/submission_widget.py b/src/submissions/frontend/widgets/submission_widget.py index 50f8235..a390e78 100644 --- a/src/submissions/frontend/widgets/submission_widget.py +++ b/src/submissions/frontend/widgets/submission_widget.py @@ -1,18 +1,15 @@ ''' Contains all submission related frontend functions ''' -import re -import sys from PyQt6.QtWidgets import ( QWidget, QPushButton, QVBoxLayout, QComboBox, QDateEdit, QLineEdit, QLabel ) from PyQt6.QtCore import pyqtSignal, Qt -from pathlib import Path from . import select_open_file, select_save_file import logging, difflib, inspect from pathlib import Path -from tools import Report, Result, check_not_nan, main_form_style, report_result +from tools import Report, Result, check_not_nan, main_form_style, report_result, check_regex_match from backend.excel.parser import SheetParser from backend.validators import PydSubmission, PydReagent from backend.db import ( @@ -27,10 +24,11 @@ from datetime import date logger = logging.getLogger(f"submissions.{__name__}") + class MyQComboBox(QComboBox): def __init__(self, scrollWidget=None, *args, **kwargs): super(MyQComboBox, self).__init__(*args, **kwargs) - self.scrollWidget=scrollWidget + self.scrollWidget = scrollWidget self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) logger.debug(f"Scrollwidget: {scrollWidget}") @@ -40,10 +38,11 @@ class MyQComboBox(QComboBox): else: return self.scrollWidget.wheelEvent(*args, **kwargs) + class MyQDateEdit(QDateEdit): def __init__(self, scrollWidget=None, *args, **kwargs): super(MyQDateEdit, self).__init__(*args, **kwargs) - self.scrollWidget=scrollWidget + self.scrollWidget = scrollWidget self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) def wheelEvent(self, *args, **kwargs): @@ -243,7 +242,8 @@ class SubmissionFormWidget(QWidget): else: widget = None case _: - widget = self.InfoItem(parent=self, key=key, value=value, submission_type=submission_type, sub_obj=sub_obj) + widget = self.InfoItem(parent=self, key=key, value=value, submission_type=submission_type, + sub_obj=sub_obj) # logger.debug(f"Setting widget enabled to: {not disable}") if disable: widget.input.setEnabled(False) @@ -266,9 +266,6 @@ class SubmissionFormWidget(QWidget): """ extraction_kit = args[0] # caller = inspect.stack()[1].function.__repr__().replace("'", "") - # logger.debug(f"Self.reagents: {self.reagents}") - # logger.debug(f"\n\n{pformat(caller)}\n\n") - # logger.debug(f"SubmissionType: {self.submission_type}") report = Report() # logger.debug(f"Extraction kit: {extraction_kit}") # NOTE: Remove previous reagent widgets @@ -723,26 +720,13 @@ class SubmissionFormWidget(QWidget): def __init__(self, scrollWidget, reagent, extraction_kit: str) -> None: super().__init__(scrollWidget=scrollWidget) self.setEditable(True) - # logger.debug(f"Attempting lookup of reagents by type: {reagent.type}") - lookup = Reagent.query(reagent_role=reagent.role) looked_up_rt = KitTypeReagentRoleAssociation.query(reagent_role=reagent.role, kit_type=extraction_kit) - try: - regex = re.compile(rf"{looked_up_rt.uses['exclude_regex']}") - except KeyError: - regex = re.compile(r"^$") - relevant_reagents = [str(item.lot) for item in lookup if not regex.match(str(item.lot))] - output_reg = [] - for rel_reagent in relevant_reagents: - # NOTE: extract strings from any sets. - if isinstance(rel_reagent, set): - for thing in rel_reagent: - output_reg.append(thing) - elif isinstance(rel_reagent, str): - output_reg.append(rel_reagent) - relevant_reagents = output_reg - # NOTE: if reagent in sheet is not found insert it into the front of relevant reagents so it shows + # relevant_reagents = [str(item.lot) for item in + # self.relevant_reagents(assoc=looked_up_rt)] + relevant_reagents = [str(item.lot) for item in looked_up_rt.get_all_relevant_reagents()] # logger.debug(f"Relevant reagents for {reagent.lot}: {relevant_reagents}") + # NOTE: if reagent in sheet is not found insert it into the front of relevant reagents so it shows if str(reagent.lot) not in relevant_reagents: if check_not_nan(reagent.lot): relevant_reagents.insert(0, str(reagent.lot)) @@ -778,5 +762,20 @@ class SubmissionFormWidget(QWidget): self.setToolTip(f"Enter lot number for the reagent used for {reagent.role}") # self.setStyleSheet(main_form_style) - - + def relevant_reagents(self, assoc: KitTypeReagentRoleAssociation): + # logger.debug(f"Attempting lookup of reagents by type: {reagent.type}") + lookup = Reagent.query(reagent_role=assoc.reagent_role) + try: + regex = assoc.uses['exclude_regex'] + except KeyError: + regex = "^$" + relevant_reagents = [item for item in lookup if + not check_regex_match(pattern=regex, check=str(item.lot))] + for rel_reagent in relevant_reagents: + # # NOTE: extract strings from any sets. + # if isinstance(rel_reagent, set): + # for thing in rel_reagent: + # yield thing + # elif isinstance(rel_reagent, str): + # yield rel_reagent + yield rel_reagent diff --git a/src/submissions/templates/basicsubmission_details.html b/src/submissions/templates/basicsubmission_details.html index bc6e782..96ad124 100644 --- a/src/submissions/templates/basicsubmission_details.html +++ b/src/submissions/templates/basicsubmission_details.html @@ -66,10 +66,11 @@

Plate map:

{% endif %} - {% endblock %} {% if signing_permission %} {% endif %} + {% endblock %} +


@@ -94,10 +95,8 @@ {% endfor %} document.getElementById("sign_btn").addEventListener("click", function(){ backend.sign_off("{{ sub['plate_number'] }}"); - }) - document.addEventListener('DOMContentLoaded', function() { - backend.activate_export(true); - }, false); + }); + {% endblock %}