diff --git a/src/submissions/__init__.py b/src/submissions/__init__.py
index 13844c3..190ce7a 100644
--- a/src/submissions/__init__.py
+++ b/src/submissions/__init__.py
@@ -1,4 +1,6 @@
# __init__.py
# Version of the realpython-reader package
-__version__ = "1.3.0"
+__version__ = "202302.3b"
+__author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"}
+__copyright__ = "2022-2023, Government of Canada"
diff --git a/src/submissions/backend/db/__init__.py b/src/submissions/backend/db/__init__.py
index bd6a8b6..198f85e 100644
--- a/src/submissions/backend/db/__init__.py
+++ b/src/submissions/backend/db/__init__.py
@@ -15,7 +15,9 @@ import json
# from dateutil.relativedelta import relativedelta
from getpass import getuser
import numpy as np
-from tools import check_not_nan
+from tools import check_not_nan, check_is_power_user
+import yaml
+from pathlib import Path
logger = logging.getLogger(f"submissions.{__name__}")
@@ -367,7 +369,8 @@ def lookup_org_by_name(ctx:dict, name:str|None) -> models.Organization:
models.Organization: retrieved organization
"""
logger.debug(f"Querying organization: {name}")
- return ctx['database_session'].query(models.Organization).filter(models.Organization.name==name).first()
+ # return ctx['database_session'].query(models.Organization).filter(models.Organization.name==name).first()
+ return ctx['database_session'].query(models.Organization).filter(models.Organization.name.startswith(name)).first()
def submissions_to_df(ctx:dict, sub_type:str|None=None) -> pd.DataFrame:
"""
@@ -457,13 +460,10 @@ def create_kit_from_yaml(ctx:dict, exp:dict) -> dict:
Returns:
dict: a dictionary containing results of db addition
"""
- try:
- power_users = ctx['power_users']
- except KeyError:
- logger.debug("This user does not have permission to add kits.")
- return {'code':1,'message':"This user does not have permission to add kits."}
- logger.debug(f"Adding kit for user: {getuser()}")
- if getuser() not in power_users:
+ # try:
+ # power_users = ctx['power_users']
+ # except KeyError:
+ if not check_is_power_user(ctx=ctx):
logger.debug(f"{getuser()} does not have permission to add kits.")
return {'code':1, 'message':"This user does not have permission to add kits."}
for type in exp:
@@ -499,13 +499,14 @@ def create_org_from_yaml(ctx:dict, org:dict) -> dict:
Returns:
dict: dictionary containing results of db addition
"""
- try:
- power_users = ctx['power_users']
- except KeyError:
- logger.debug("This user does not have permission to add kits.")
- return {'code':1,'message':"This user does not have permission to add organizations."}
- logger.debug(f"Adding organization for user: {getuser()}")
- if getuser() not in power_users:
+ # try:
+ # power_users = ctx['power_users']
+ # except KeyError:
+ # logger.debug("This user does not have permission to add kits.")
+ # return {'code':1,'message':"This user does not have permission to add organizations."}
+ # logger.debug(f"Adding organization for user: {getuser()}")
+ # if getuser() not in power_users:
+ if not check_is_power_user(ctx=ctx):
logger.debug(f"{getuser()} does not have permission to add kits.")
return {'code':1, 'message':"This user does not have permission to add organizations."}
for client in org:
@@ -623,4 +624,24 @@ def lookup_submission_by_rsl_num(ctx:dict, rsl_num:str):
def lookup_submissions_using_reagent(ctx:dict, reagent:models.Reagent) -> list[models.BasicSubmission]:
- return ctx['database_session'].query(models.BasicSubmission).join(reagents_submissions).filter(reagents_submissions.c.reagent_id==reagent.id).all()
\ No newline at end of file
+ return ctx['database_session'].query(models.BasicSubmission).join(reagents_submissions).filter(reagents_submissions.c.reagent_id==reagent.id).all()
+
+
+def delete_submission_by_id(ctx:dict, id:int) -> None:
+ """
+ Deletes a submission and its associated samples from the database.
+
+ Args:
+ ctx (dict): settings passed down from gui
+ id (int): id of submission to be deleted.
+ """
+ # In order to properly do this Im' going to have to delete all of the secondary table stuff as well.
+ sub = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.id==id).first()
+ backup = sub.to_dict()
+ with open(Path(ctx['backup_path']).joinpath(f"{sub.rsl_plate_num}-backup({date.today().strftime('%Y%m%d')}).yml"), "w") as f:
+ yaml.dump(backup, f)
+ sub.reagents = []
+ for sample in sub.samples:
+ ctx['database_session'].delete(sample)
+ ctx["database_session"].delete(sub)
+ ctx["database_session"].commit()
\ No newline at end of file
diff --git a/src/submissions/backend/db/models/controls.py b/src/submissions/backend/db/models/controls.py
index 5e73a54..0d9c8f9 100644
--- a/src/submissions/backend/db/models/controls.py
+++ b/src/submissions/backend/db/models/controls.py
@@ -56,7 +56,7 @@ class Control(Base):
output = {
"name" : self.name,
"type" : self.controltype.name,
- "targets" : " ,".join(targets),
+ "targets" : ", ".join(targets),
"kraken" : new_kraken[0:5]
}
return output
diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py
index 0daf05d..1ca5b0f 100644
--- a/src/submissions/backend/db/models/submissions.py
+++ b/src/submissions/backend/db/models/submissions.py
@@ -74,6 +74,14 @@ class BasicSubmission(Base):
ext_info = json.loads(self.extraction_info)
except TypeError:
ext_info = None
+ try:
+ reagents = [item.to_sub_dict() for item in self.reagents]
+ except:
+ reagents = None
+ try:
+ samples = [item.to_sub_dict() for item in self.samples]
+ except:
+ samples = None
output = {
"id": self.id,
"Plate Number": self.rsl_plate_num,
@@ -85,6 +93,8 @@ class BasicSubmission(Base):
"Extraction Kit": ext_kit,
"Technician": self.technician,
"Cost": self.run_cost,
+ "reagents": reagents,
+ "samples": samples,
"ext_info": ext_info
}
# logger.debug(f"{self.rsl_plate_num} extraction: {output['Extraction Status']}")
diff --git a/src/submissions/backend/excel/parser.py b/src/submissions/backend/excel/parser.py
index bc1bded..0d4c029 100644
--- a/src/submissions/backend/excel/parser.py
+++ b/src/submissions/backend/excel/parser.py
@@ -187,14 +187,18 @@ class SheetParser(object):
if not isinstance(row[5], float) and check_not_nan(row[5]):
# must be prefixed with 'lot_' to be recognized by gui
# regex below will remove 80% from 80% ethanol in the Wastewater kit.
- output_key = re.sub(r"\d{1,3}%", "", row[0].lower().strip().replace(' ', '_'))
+ output_key = re.sub(r"^\d{1,3}%\s?", "", row[0].lower().strip().replace(' ', '_'))
+ output_key = output_key.strip("_")
try:
output_var = row[5].upper()
except AttributeError:
logger.debug(f"Couldn't upperize {row[5]}, must be a number")
output_var = row[5]
if check_not_nan(row[7]):
- expiry = row[7].date()
+ try:
+ expiry = row[7].date()
+ except AttributeError:
+ expiry = date.today()
else:
expiry = date.today()
self.sub[f"lot_{output_key}"] = {'lot':output_var, 'exp':expiry}
diff --git a/src/submissions/backend/excel/reports.py b/src/submissions/backend/excel/reports.py
index 0ffd4a6..674e040 100644
--- a/src/submissions/backend/excel/reports.py
+++ b/src/submissions/backend/excel/reports.py
@@ -41,6 +41,9 @@ def make_report_xlsx(records:list[dict]) -> DataFrame:
# df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')] = df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')].applymap('${:,.2f}'.format)
return df2
+# def split_row_item(item:str) -> float:
+# return item.split(" ")[-1]
+
def make_report_html(df:DataFrame, start_date:date, end_date:date) -> str:
@@ -59,17 +62,23 @@ def make_report_html(df:DataFrame, start_date:date, end_date:date) -> str:
output = []
logger.debug(f"Report DataFrame: {df}")
for ii, row in enumerate(df.iterrows()):
- row = [item for item in row]
- logger.debug(f"Row: {row}")
-
+ # row = [item for item in row]
+ logger.debug(f"Row {ii}: {row}")
lab = row[0][0]
+ logger.debug(type(row))
logger.debug(f"Old lab: {old_lab}, Current lab: {lab}")
- kit = dict(name=row[0][1], cost=row[1]['Cost'], plate_count=int(row[1]['Kit Count']), sample_count=int(row[1]['Sample Count']))
+ logger.debug(f"Name: {row[0][1]}")
+ data = [item for item in row[1]]
+ # logger.debug(data)
+ # logger.debug(f"Cost: {split_row_item(data[1])}")
+ # logger.debug(f"Kit count: {split_row_item(data[0])}")
+ # logger.debug(f"Sample Count: {split_row_item(data[2])}")
+ kit = dict(name=row[0][1], cost=data[1], plate_count=int(data[0]), sample_count=int(data[2]))
if lab == old_lab:
- output[ii-1]['kits'].append(kit)
- output[ii-1]['total_cost'] += kit['cost']
- output[ii-1]['total_samples'] += kit['sample_count']
- output[ii-1]['total_plates'] += kit['plate_count']
+ output[-1]['kits'].append(kit)
+ output[-1]['total_cost'] += kit['cost']
+ output[-1]['total_samples'] += kit['sample_count']
+ output[-1]['total_plates'] += kit['plate_count']
else:
adder = dict(lab=lab, kits=[kit], total_cost=kit['cost'], total_samples=kit['sample_count'], total_plates=kit['plate_count'])
output.append(adder)
diff --git a/src/submissions/frontend/__init__.py b/src/submissions/frontend/__init__.py
index a565fe9..fc259a5 100644
--- a/src/submissions/frontend/__init__.py
+++ b/src/submissions/frontend/__init__.py
@@ -52,7 +52,7 @@ class App(QMainWindow):
super().__init__()
self.ctx = ctx
try:
- self.title = f"Submissions App (v{ctx['package'].__version__})"
+ self.title = f"Submissions App (v{ctx['package'].__version__}) - {ctx['database']}"
except AttributeError:
self.title = f"Submissions App"
# set initial app position and size
@@ -85,6 +85,7 @@ class App(QMainWindow):
reportMenu = menuBar.addMenu("&Reports")
maintenanceMenu = menuBar.addMenu("&Monthly")
helpMenu = menuBar.addMenu("&Help")
+ helpMenu.addAction(self.helpAction)
fileMenu.addAction(self.importAction)
reportMenu.addAction(self.generateReportAction)
maintenanceMenu.addAction(self.joinControlsAction)
@@ -111,6 +112,7 @@ class App(QMainWindow):
self.addOrgAction = QAction("Add Org", self)
self.joinControlsAction = QAction("Link Controls")
self.joinExtractionAction = QAction("Link Ext Logs")
+ self.helpAction = QAction("&About", self)
def _connectActions(self):
@@ -128,6 +130,12 @@ class App(QMainWindow):
self.table_widget.datepicker.end_date.dateChanged.connect(self._controls_getter)
self.joinControlsAction.triggered.connect(self.linkControls)
self.joinExtractionAction.triggered.connect(self.linkExtractions)
+ self.helpAction.triggered.connect(self.showAbout)
+
+ def showAbout(self):
+ output = f"Version: {self.ctx['package'].__version__}\n\nAuthor: {self.ctx['package'].__author__['name']} - {self.ctx['package'].__author__['email']}\n\nCopyright: {self.ctx['package'].__copyright__}"
+ about = AlertPop(message=output, status="information")
+ about.exec()
def importSubmission(self):
diff --git a/src/submissions/frontend/custom_widgets/sub_details.py b/src/submissions/frontend/custom_widgets/sub_details.py
index a0e5247..9eea304 100644
--- a/src/submissions/frontend/custom_widgets/sub_details.py
+++ b/src/submissions/frontend/custom_widgets/sub_details.py
@@ -2,17 +2,19 @@ from datetime import date
from PyQt6.QtWidgets import (
QVBoxLayout, QDialog, QTableView,
QTextEdit, QPushButton, QScrollArea,
- QMessageBox, QFileDialog
+ QMessageBox, QFileDialog, QMenu
)
from PyQt6.QtCore import Qt, QAbstractTableModel
-from PyQt6.QtGui import QFontMetrics
+from PyQt6.QtGui import QFontMetrics, QAction, QCursor
-from backend.db import submissions_to_df, lookup_submission_by_id, lookup_all_sample_types, create_kit_from_yaml
+from backend.db import submissions_to_df, lookup_submission_by_id, delete_submission_by_id
from jinja2 import Environment, FileSystemLoader
from xhtml2pdf import pisa
import sys
from pathlib import Path
import logging
+from .pop_ups import AlertPop, QuestionAsker
+from tools import check_is_power_user
logger = logging.getLogger(f"submissions.{__name__}")
@@ -91,6 +93,14 @@ class SubmissionsSheet(QTableView):
sets data in model
"""
self.data = submissions_to_df(ctx=self.ctx)
+ try:
+ del self.data['samples']
+ except KeyError:
+ pass
+ try:
+ del self.data['reagents']
+ except KeyError:
+ pass
self.model = pandasModel(self.data)
self.setModel(self.model)
# self.resize(800,600)
@@ -99,15 +109,43 @@ class SubmissionsSheet(QTableView):
"""
creates detailed data to show in seperate window
"""
- index=(self.selectionModel().currentIndex())
+ index = (self.selectionModel().currentIndex())
# logger.debug(index)
- value=index.sibling(index.row(),0).data()
+ value = index.sibling(index.row(),0).data()
dlg = SubmissionDetails(ctx=self.ctx, id=value)
# dlg.show()
if dlg.exec():
pass
+ def contextMenuEvent(self, event):
+ self.menu = QMenu(self)
+ renameAction = QAction('Delete', self)
+ detailsAction = QAction('Details', self)
+ # Originally I intended to limit deletions to power users.
+ # renameAction.setEnabled(False)
+ # if check_is_power_user(ctx=self.ctx):
+ # renameAction.setEnabled(True)
+ renameAction.triggered.connect(lambda: self.delete_item(event))
+ detailsAction.triggered.connect(lambda: self.show_details())
+ self.menu.addAction(detailsAction)
+ self.menu.addAction(renameAction)
+ # add other required actions
+ self.menu.popup(QCursor.pos())
+
+
+ def delete_item(self, event):
+ index = (self.selectionModel().currentIndex())
+ value = index.sibling(index.row(),0).data()
+ logger.debug(index)
+ msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {index.sibling(index.row(),1).data()}?\n")
+ if msg.exec():
+ delete_submission_by_id(ctx=self.ctx, id=value)
+ else:
+ return
+ self.setData()
+
+
class SubmissionDetails(QDialog):
@@ -130,8 +168,8 @@ class SubmissionDetails(QDialog):
# don't want id
del self.base_dict['id']
# convert sub objects to dicts
- self.base_dict['reagents'] = [item.to_sub_dict() for item in data.reagents]
- self.base_dict['samples'] = [item.to_sub_dict() for item in data.samples]
+ # self.base_dict['reagents'] = [item.to_sub_dict() for item in data.reagents]
+ # self.base_dict['samples'] = [item.to_sub_dict() for item in data.samples]
# retrieve jinja template
template = env.get_template("submission_details.txt")
# render using object dict
diff --git a/src/submissions/templates/submission_details.html b/src/submissions/templates/submission_details.html
index 185f925..13d0967 100644
--- a/src/submissions/templates/submission_details.html
+++ b/src/submissions/templates/submission_details.html
@@ -20,6 +20,7 @@
{{ item['type'] }}: {{ item['lot'] }} (EXP: {{ item['expiry'] }})
{% endif %}
{% endfor %}
{% for item in sub['samples'] %}
{% if loop.index == 1 %}
@@ -28,6 +29,7 @@
{{ item['well'] }}: {{ item['name'] }}
{% endif %}
{% endfor %}