Pre-fixing JSON update bug.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
- Various bug fixes.
|
||||
- Move import PCR results to context menu.
|
||||
- Automated backup of database.
|
||||
- Added ability to sign off on submission in submission details.
|
||||
|
||||
## 202403.03
|
||||
|
||||
8
TODO.md
8
TODO.md
@@ -1,5 +1,11 @@
|
||||
- [ ] Appending of qPCR results to WW not saving. Find out why.
|
||||
- Possibly due to immutable JSON? But... it's worked before... Right?
|
||||
- Based on research, if a top-level JSON field is not changed, SQLalchemy will not detect changes.
|
||||
- May have to use a special class: [link](https://docs.sqlalchemy.org/en/14/orm/extensions/mutable.html#establishing-mutability-on-scalar-column-values)
|
||||
- [ ] Add Bead basher and Assit to DB.
|
||||
- [x] Artic not creating right plate name.
|
||||
- [ ] Merge BasicSubmission.find_subclasses and BasicSubmission.find_polymorphic_subclass
|
||||
- [ ] Fix updating of Extraction Kit in submission form widget.
|
||||
- [x] Fix updating of Extraction Kit in submission form widget.
|
||||
- [x] Fix cropping of gel image.
|
||||
- [ ] Create Tips ... *sigh*.
|
||||
- [x] Create platemap image from html for export to pdf.
|
||||
|
||||
@@ -86,6 +86,7 @@ class BaseClass(Base):
|
||||
"""
|
||||
Add the object to the database and commit
|
||||
"""
|
||||
logger.debug(f"Saving object: {pformat(self.__dict__)}")
|
||||
try:
|
||||
self.__database_session__.add(self)
|
||||
self.__database_session__.commit()
|
||||
|
||||
@@ -436,6 +436,7 @@ class BasicSubmission(BaseClass):
|
||||
Args:
|
||||
original (bool, optional): Is this the first save. Defaults to True.
|
||||
"""
|
||||
logger.debug("Saving submission.")
|
||||
if original:
|
||||
self.uploaded_by = getuser()
|
||||
super().save()
|
||||
@@ -605,12 +606,15 @@ class BasicSubmission(BaseClass):
|
||||
# logger.info(f"Hello from {cls.__mapper_args__['polymorphic_identity']} Enforcer!")
|
||||
# return instr
|
||||
from backend.validators import RSLNamer
|
||||
logger.debug(f"instr coming into {cls}: {instr}")
|
||||
logger.debug(f"data coming into {cls}: {data}")
|
||||
defaults = cls.get_default_info()
|
||||
data['abbreviation'] = defaults['abbreviation']
|
||||
if 'submission_type' not in data.keys() or data['submission_type'] in [None, ""]:
|
||||
data['submission_type'] = defaults['submission_type']
|
||||
# outstr = super().enforce_name(instr=instr, data=data)
|
||||
if instr in [None, ""]:
|
||||
logger.debug("Sending to RSLNamer to make new plate name.")
|
||||
outstr = RSLNamer.construct_new_plate_name(data=data)
|
||||
else:
|
||||
outstr = instr
|
||||
@@ -620,6 +624,7 @@ class BasicSubmission(BaseClass):
|
||||
outstr = re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\1\2\3", outstr)
|
||||
outstr = re.sub(rf"{data['abbreviation']}(\d{6})", rf"{data['abbreviation']}-\1", outstr, flags=re.IGNORECASE).upper()
|
||||
except (AttributeError, TypeError) as e:
|
||||
logger.error(f"Error making outstr: {e}, sending to RSLNamer to make new plate name.")
|
||||
outstr = RSLNamer.construct_new_plate_name(data=data)
|
||||
try:
|
||||
plate_number = re.search(r"(?:(-|_)\d)(?!\d)", outstr).group().strip("_").strip("-")
|
||||
@@ -1281,28 +1286,32 @@ class Wastewater(BasicSubmission):
|
||||
if hasattr(self, 'pcr_info') and self.pcr_info != None:
|
||||
# existing = json.loads(sub.pcr_info)
|
||||
existing = self.pcr_info
|
||||
logger.debug(f"Found existing pcr info: {pformat(self.pcr_info)}")
|
||||
else:
|
||||
existing = None
|
||||
if existing != None:
|
||||
# update pcr_info
|
||||
try:
|
||||
logger.debug(f"Updating {type(existing)}: {existing} with {type(parser.pcr)}: {parser.pcr}")
|
||||
logger.debug(f"Updating {type(existing)}:\n {pformat(existing)} with {type(parser.pcr)}:\n {pformat(parser.pcr)}")
|
||||
# if json.dumps(parser.pcr) not in sub.pcr_info:
|
||||
if parser.pcr not in self.pcr_info:
|
||||
logger.debug(f"This is new pcr info, appending to existing")
|
||||
existing.append(parser.pcr)
|
||||
logger.debug(f"Setting: {existing}")
|
||||
else:
|
||||
logger.debug("This info already exists, skipping.")
|
||||
# logger.debug(f"Setting {self.rsl_plate_num} PCR to:\n {pformat(existing)}")
|
||||
# sub.pcr_info = json.dumps(existing)
|
||||
self.pcr_info = existing
|
||||
except TypeError:
|
||||
logger.error(f"Error updating!")
|
||||
# sub.pcr_info = json.dumps([parser.pcr])
|
||||
self.pcr_info = [parser.pcr]
|
||||
logger.debug(f"Final pcr info for {self.rsl_plate_num}: {self.pcr_info}")
|
||||
logger.debug(f"Final pcr info for {self.rsl_plate_num}:\n {pformat(self.pcr_info)}")
|
||||
else:
|
||||
# sub.pcr_info = json.dumps([parser.pcr])
|
||||
self.pcr_info = [parser.pcr]
|
||||
logger.debug(f"Existing {type(self.pcr_info)}: {self.pcr_info}")
|
||||
logger.debug(f"Inserting {type(parser.pcr)}: {parser.pcr}")
|
||||
# logger.debug(f"Existing {type(self.pcr_info)}: {self.pcr_info}")
|
||||
# logger.debug(f"Inserting {type(parser.pcr)}: {parser.pcr}")
|
||||
self.save(original=False)
|
||||
logger.debug(f"Got {len(parser.samples)} samples to update!")
|
||||
logger.debug(f"Parser samples: {parser.samples}")
|
||||
@@ -1383,15 +1392,15 @@ class WastewaterArtic(BasicSubmission):
|
||||
instr = re.sub(r"Artic", "", instr, flags=re.IGNORECASE)
|
||||
except (AttributeError, TypeError) as e:
|
||||
logger.error(f"Problem using regex: {e}")
|
||||
try:
|
||||
check = instr.startswith("RSL")
|
||||
except AttributeError:
|
||||
check = False
|
||||
if not check:
|
||||
try:
|
||||
instr = "RSL" + instr
|
||||
except:
|
||||
instr = "RSL"
|
||||
# try:
|
||||
# check = instr.startswith("RSL")
|
||||
# except AttributeError:
|
||||
# check = False
|
||||
# if not check:
|
||||
# try:
|
||||
# instr = "RSL" + instr
|
||||
# except TypeError:
|
||||
# instr = "RSL"
|
||||
outstr = super().enforce_name(instr=instr, data=data)
|
||||
return outstr
|
||||
|
||||
@@ -1540,7 +1549,8 @@ class WastewaterArtic(BasicSubmission):
|
||||
df.sort_values(by=['destination_column', 'destination_row'], inplace=True)
|
||||
except AttributeError as e:
|
||||
logger.error(f"Couldn't construct df due to {e}")
|
||||
input_dict['csv'] = df
|
||||
# input_dict['csv'] = df
|
||||
input_dict['csv'] = xl.parse("hitpicks_csv_to_export")
|
||||
return input_dict
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -266,7 +266,11 @@ class ReagentParser(object):
|
||||
# logger.debug(f"Got lot for {item}-{name}: {lot} as {type(lot)}")
|
||||
lot = str(lot)
|
||||
logger.debug(f"Going into pydantic: name: {name}, lot: {lot}, expiry: {expiry}, type: {item.strip()}, comment: {comment}")
|
||||
if name.lower() != "not applicable":
|
||||
try:
|
||||
check = name.lower() != "not applicable"
|
||||
except AttributeError:
|
||||
check = True
|
||||
if check:
|
||||
listo.append(PydReagent(type=item.strip(), lot=lot, expiry=expiry, name=name, comment=comment, missing=missing))
|
||||
return listo
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ class PydSample(BaseModel, extra='allow'):
|
||||
row=row, column=column, id=id)
|
||||
# logger.debug(f"Using submission_sample_association: {association}")
|
||||
try:
|
||||
instance.sample_submission_associations.append(association)
|
||||
# instance.sample_submission_associations.append(association)
|
||||
out_associations.append(association)
|
||||
except IntegrityError as e:
|
||||
logger.error(f"Could not attach submission sample association due to: {e}")
|
||||
@@ -809,7 +809,8 @@ class PydSubmission(BaseModel, extra='allow'):
|
||||
# # what reagent types are in both lists?
|
||||
# missing = list(set(ext_kit_rtypes).difference(reagenttypes))
|
||||
missing = []
|
||||
output_reagents = self.reagents
|
||||
# output_reagents = self.reagents
|
||||
output_reagents = ext_kit_rtypes
|
||||
logger.debug(f"Already have these reagent types: {reagenttypes}")
|
||||
for rt in ext_kit_rtypes:
|
||||
if rt.type not in reagenttypes:
|
||||
|
||||
@@ -9,9 +9,10 @@ from PyQt6.QtWidgets import (
|
||||
from PyQt6.QtGui import QAction
|
||||
from pathlib import Path
|
||||
from tools import check_if_app, Settings, Report
|
||||
from datetime import date
|
||||
from .pop_ups import AlertPop
|
||||
from .misc import LogParser
|
||||
import logging, webbrowser, sys
|
||||
import logging, webbrowser, sys, shutil
|
||||
from .submission_table import SubmissionsSheet
|
||||
from .submission_widget import SubmissionFormContainer
|
||||
from .controls_chart import ControlsViewer
|
||||
@@ -51,6 +52,7 @@ class App(QMainWindow):
|
||||
self._connectActions()
|
||||
self.show()
|
||||
self.statusBar().showMessage('Ready', 5000)
|
||||
self.backup_database()
|
||||
|
||||
def _createMenuBar(self):
|
||||
"""
|
||||
@@ -159,6 +161,16 @@ class App(QMainWindow):
|
||||
dlg = LogParser(self)
|
||||
dlg.exec()
|
||||
|
||||
def backup_database(self):
|
||||
month = date.today().strftime("%Y-%m")
|
||||
# day = date.today().strftime("%Y-%m-%d")
|
||||
logger.debug(f"Here is the db directory: {self.ctx.database_path}")
|
||||
logger.debug(f"Here is the backup directory: {self.ctx.backup_path}")
|
||||
current_month_bak = Path(self.ctx.backup_path).joinpath(f"submissions_backup-{month}").resolve().with_suffix(".db")
|
||||
if not current_month_bak.exists() and "demo" not in self.ctx.database_path.__str__():
|
||||
logger.debug("No backup found for this month, backing up database.")
|
||||
shutil.copyfile(self.ctx.database_path, current_month_bak)
|
||||
|
||||
class AddSubForm(QWidget):
|
||||
|
||||
def __init__(self, parent:QWidget):
|
||||
|
||||
@@ -5,7 +5,7 @@ from PyQt6.QtWidgets import (
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from pathlib import Path
|
||||
from . import select_open_file, select_save_file
|
||||
import logging, difflib, inspect, json
|
||||
import logging, difflib, inspect, pickle
|
||||
from pathlib import Path
|
||||
from tools import Report, Result, check_not_nan
|
||||
from backend.excel.parser import SheetParser, PCRParser
|
||||
@@ -147,7 +147,7 @@ class SubmissionFormWidget(QWidget):
|
||||
|
||||
def __init__(self, parent: QWidget, submission:PydSubmission) -> None:
|
||||
super().__init__(parent)
|
||||
self.report = Report()
|
||||
# self.report = Report()
|
||||
self.app = parent.app
|
||||
self.pyd = submission
|
||||
# self.input = [{k:v} for k,v in kwargs.items()]
|
||||
@@ -177,18 +177,18 @@ class SubmissionFormWidget(QWidget):
|
||||
self.scrape_reagents(self.pyd.extraction_kit)
|
||||
# extraction kit must be added last so widget order makes sense.
|
||||
# self.layout.addWidget(self.create_widget(key="extraction_kit", value=self.extraction_kit, submission_type=self.submission_type))
|
||||
if hasattr(self.pyd, "csv"):
|
||||
export_csv_btn = QPushButton("Export CSV")
|
||||
export_csv_btn.setObjectName("export_csv_btn")
|
||||
self.layout.addWidget(export_csv_btn)
|
||||
export_csv_btn.clicked.connect(self.export_csv_function)
|
||||
submit_btn = QPushButton("Submit")
|
||||
submit_btn.setObjectName("submit_btn")
|
||||
self.layout.addWidget(submit_btn)
|
||||
submit_btn.clicked.connect(self.submit_new_sample_function)
|
||||
self.setLayout(self.layout)
|
||||
self.app.report.add_result(self.report)
|
||||
self.app.result_reporter()
|
||||
# if hasattr(self.pyd, "csv"):
|
||||
# export_csv_btn = QPushButton("Export CSV")
|
||||
# export_csv_btn.setObjectName("export_csv_btn")
|
||||
# self.layout.addWidget(export_csv_btn)
|
||||
# export_csv_btn.clicked.connect(self.export_csv_function)
|
||||
# submit_btn = QPushButton("Submit")
|
||||
# submit_btn.setObjectName("submit_btn")
|
||||
# self.layout.addWidget(submit_btn)
|
||||
# submit_btn.clicked.connect(self.submit_new_sample_function)
|
||||
# self.setLayout(self.layout)
|
||||
# self.app.report.add_result(self.report)
|
||||
# self.app.result_reporter()
|
||||
|
||||
def create_widget(self, key:str, value:dict|PydReagent, submission_type:str|None=None, extraction_kit:str|None=None) -> "self.InfoItem":
|
||||
"""
|
||||
@@ -230,7 +230,7 @@ class SubmissionFormWidget(QWidget):
|
||||
caller = inspect.stack()[1].function.__repr__().replace("'", "")
|
||||
# self.reagents = []
|
||||
# logger.debug(f"Self.reagents: {self.reagents}")
|
||||
# logger.debug(f"\n\n{caller}\n\n")
|
||||
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}")
|
||||
@@ -257,13 +257,25 @@ class SubmissionFormWidget(QWidget):
|
||||
# self.pyd.reagents = already_have + reagents
|
||||
# logger.debug(f"Reagents: {self.reagents}")
|
||||
# self.kit_integrity_completion_function(extraction_kit=extraction_kit)
|
||||
reagents, report = self.pyd.check_kit_integrity(extraction_kit=extraction_kit)
|
||||
reagents, integrity_report = self.pyd.check_kit_integrity(extraction_kit=extraction_kit)
|
||||
# logger.debug(f"Missing reagents: {obj.missing_reagents}")
|
||||
for reagent in reagents:
|
||||
add_widget = self.ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.pyd.extraction_kit)
|
||||
self.layout.addWidget(add_widget)
|
||||
self.report.add_result(report)
|
||||
logger.debug(f"Outgoing report: {self.report.results}")
|
||||
report.add_result(integrity_report)
|
||||
logger.debug(f"Outgoing report: {report.results}")
|
||||
if hasattr(self.pyd, "csv"):
|
||||
export_csv_btn = QPushButton("Export CSV")
|
||||
export_csv_btn.setObjectName("export_csv_btn")
|
||||
self.layout.addWidget(export_csv_btn)
|
||||
export_csv_btn.clicked.connect(self.export_csv_function)
|
||||
submit_btn = QPushButton("Submit")
|
||||
submit_btn.setObjectName("submit_btn")
|
||||
self.layout.addWidget(submit_btn)
|
||||
submit_btn.clicked.connect(self.submit_new_sample_function)
|
||||
self.setLayout(self.layout)
|
||||
self.app.report.add_result(report)
|
||||
self.app.result_reporter()
|
||||
|
||||
def kit_integrity_completion_function(self, extraction_kit:str|None=None):
|
||||
"""
|
||||
@@ -302,7 +314,6 @@ class SubmissionFormWidget(QWidget):
|
||||
Alternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.
|
||||
\n\nPlease make sure you check the lots carefully!""".replace(" ", ""), status="Warning")
|
||||
report.add_result(result)
|
||||
|
||||
self.report.add_result(report)
|
||||
logger.debug(f"Outgoing report: {self.report.results}")
|
||||
|
||||
|
||||
@@ -538,7 +538,11 @@ def rreplace(s, old, new):
|
||||
ctx = get_config(None)
|
||||
|
||||
def is_power_user() -> bool:
|
||||
return getpass.getuser() in ctx.power_users
|
||||
try:
|
||||
check = getpass.getuser() in ctx.power_users
|
||||
except:
|
||||
check = False
|
||||
return check
|
||||
|
||||
def check_authorization(func):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user