Prior to moving settings to pydantic-settings

This commit is contained in:
Landon Wark
2023-07-24 09:17:09 -05:00
parent f6e270f52d
commit 63308674c6
7 changed files with 85 additions and 86 deletions

View File

@@ -1,3 +1,7 @@
## 202307.04
- Individual plate details now in html format.
## 202307.03 ## 202307.03
- Auto-filling of some empty cells in excel file. - Auto-filling of some empty cells in excel file.

View File

@@ -6,15 +6,16 @@
## Logging in New Run: ## Logging in New Run:
*should fit 90% of usage cases* *should fit 90% of usage cases*
1. Ensure a properly formatted Submission Excel form has been filled out. (It will save you a few headaches) 1. Ensure a properly formatted Submission Excel form has been filled out.
a. All fields should be filled in to ensure proper lookups of reagents. a. The program can fill in reagent fields and some other information automatically, but should be checked for accuracy afterwards.
2. Click on 'File' in the menu bar, followed by 'Import Submission' and use the file dialog to locate the form you definitely made sure was properly filled out in step 1. 2. Click on 'File' in the menu bar, followed by 'Import Submission' and use the file dialog to locate the form.
3. Click 'Ok'. 3. Click 'Ok'.
4. Most of the fields in the form should be automatically filled in from the form area to the left of the screen. 4. Most of the fields in the form should be automatically filled in from the form area to the left of the screen.
5. You may need to maximize the app to ensure you can see all the info. 5. You may need to maximize the app to ensure you can see all the info.
6. Any fields that are not automatically filled in can be filled in manually from the drop down menus. 6. Any fields that are not automatically filled in can be filled in manually from the drop down menus.
a. Any reagent lots not found in the drop downs can be typed in manually.
7. Once you are certain all the information is correct, click 'Submit' at the bottom of the form. 7. Once you are certain all the information is correct, click 'Submit' at the bottom of the form.
8. Add in any reagents the app doesn't recognize. 8. Add in any new reagents the app doesn't have in the database.
9. Once the new run shows up at the bottom of the Submissions, everything is fine. 9. Once the new run shows up at the bottom of the Submissions, everything is fine.
10. In case of any mistakes, the run can be overwritten by a reimport. 10. In case of any mistakes, the run can be overwritten by a reimport.

View File

@@ -1,3 +1,6 @@
- [ ] Check robotics' use of Artic submission forms (i.e. will we be able to make our own forms?)
- [ ] Streamline addition of new kits by moving as much into DB as possible.
- [x] Make plate details from html, same as export.
- [x] Put in SN controls I guess. - [x] Put in SN controls I guess.
- [x] Code clean-up and refactor (2023-07). - [x] Code clean-up and refactor (2023-07).
- [ ] Migrate context settings to pydantic-settings model. - [ ] Migrate context settings to pydantic-settings model.

View File

@@ -4,7 +4,7 @@ from pathlib import Path
# Version of the realpython-reader package # Version of the realpython-reader package
__project__ = "submissions" __project__ = "submissions"
__version__ = "202307.3b" __version__ = "202307.4b"
__author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"} __author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"}
__copyright__ = "2022-2023, Government of Canada" __copyright__ = "2022-2023, Government of Canada"

View File

@@ -11,8 +11,9 @@ from PyQt6.QtWidgets import (
QMessageBox, QFileDialog, QMenu, QLabel, QMessageBox, QFileDialog, QMenu, QLabel,
QDialogButtonBox, QToolBar QDialogButtonBox, QToolBar
) )
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
from PyQt6.QtGui import QFontMetrics, QAction, QCursor, QPixmap, QPainter from PyQt6.QtGui import QAction, QCursor, QPixmap, QPainter
from backend.db import submissions_to_df, lookup_submission_by_id, delete_submission_by_id, lookup_submission_by_rsl_num, hitpick_plate from backend.db import submissions_to_df, lookup_submission_by_id, delete_submission_by_id, lookup_submission_by_rsl_num, hitpick_plate
from backend.excel import make_hitpicks from backend.excel import make_hitpicks
from configure import jinja_template_loading from configure import jinja_template_loading
@@ -266,40 +267,25 @@ class SubmissionDetails(QDialog):
# don't want id # don't want id
del self.base_dict['id'] del self.base_dict['id']
# retrieve jinja template # retrieve jinja template
template = env.get_template("submission_details.txt") # template = env.get_template("submission_details.txt")
# render using object dict # render using object dict
text = template.render(sub=self.base_dict) # text = template.render(sub=self.base_dict)
# create text field # create text field
txt_editor = QTextEdit(self) # txt_editor = QTextEdit(self)
txt_editor.setReadOnly(True) # txt_editor.setReadOnly(True)
txt_editor.document().setPlainText(text) # txt_editor.document().setPlainText(text)
# resize # resize
font = txt_editor.document().defaultFont() # font = txt_editor.document().defaultFont()
fontMetrics = QFontMetrics(font) # fontMetrics = QFontMetrics(font)
textSize = fontMetrics.size(0, txt_editor.toPlainText()) # textSize = fontMetrics.size(0, txt_editor.toPlainText())
w = textSize.width() + 10 # w = textSize.width() + 10
h = textSize.height() + 10 # h = textSize.height() + 10
txt_editor.setMinimumSize(w, h) # txt_editor.setMinimumSize(w, h)
txt_editor.setMaximumSize(w, h) # txt_editor.setMaximumSize(w, h)
txt_editor.resize(w, h) # txt_editor.resize(w, h)
interior.resize(w,900) # interior.resize(w,900)
txt_editor.setText(text) # txt_editor.setText(text)
interior.setWidget(txt_editor) # interior.setWidget(txt_editor)
self.layout = QVBoxLayout()
self.setFixedSize(w, 900)
# button to export a pdf version
btn = QPushButton("Export PDF")
btn.setParent(self)
btn.setFixedWidth(w)
btn.clicked.connect(self.export)
def export(self):
"""
Renders submission to html, then creates and saves .pdf file to user selected file.
"""
template = env.get_template("submission_details.html")
# make barcode because, reasons
self.base_dict['barcode'] = base64.b64encode(make_plate_barcode(self.base_dict['Plate Number'], width=120, height=30)).decode('utf-8') self.base_dict['barcode'] = base64.b64encode(make_plate_barcode(self.base_dict['Plate Number'], width=120, height=30)).decode('utf-8')
sub = lookup_submission_by_rsl_num(ctx=self.ctx, rsl_num=self.base_dict['Plate Number']) sub = lookup_submission_by_rsl_num(ctx=self.ctx, rsl_num=self.base_dict['Plate Number'])
plate_dicto = hitpick_plate(sub) plate_dicto = hitpick_plate(sub)
@@ -312,10 +298,45 @@ class SubmissionDetails(QDialog):
logger.error(f"No plate map found for {sub.rsl_plate_num}") logger.error(f"No plate map found for {sub.rsl_plate_num}")
# platemap.save("test.jpg", 'JPEG') # platemap.save("test.jpg", 'JPEG')
self.base_dict['platemap'] = base64.b64encode(image_io.getvalue()).decode('utf-8') self.base_dict['platemap'] = base64.b64encode(image_io.getvalue()).decode('utf-8')
logger.debug(self.base_dict) template = env.get_template("submission_details.html")
html = template.render(sub=self.base_dict) self.html = template.render(sub=self.base_dict)
with open("test.html", "w") as f: webview = QWebEngineView()
f.write(html) webview.setMinimumSize(900, 500)
webview.setMaximumSize(900, 500)
webview.setHtml(self.html)
self.layout = QVBoxLayout()
interior.resize(900, 500)
interior.setWidget(webview)
self.setFixedSize(900, 500)
# button to export a pdf version
btn = QPushButton("Export PDF")
btn.setParent(self)
btn.setFixedWidth(900)
btn.clicked.connect(self.export)
def export(self):
"""
Renders submission to html, then creates and saves .pdf file to user selected file.
"""
# template = env.get_template("submission_details.html")
# # make barcode because, reasons
# self.base_dict['barcode'] = base64.b64encode(make_plate_barcode(self.base_dict['Plate Number'], width=120, height=30)).decode('utf-8')
# sub = lookup_submission_by_rsl_num(ctx=self.ctx, rsl_num=self.base_dict['Plate Number'])
# plate_dicto = hitpick_plate(sub)
# platemap = make_plate_map(plate_dicto)
# logger.debug(f"platemap: {platemap}")
# image_io = BytesIO()
# try:
# platemap.save(image_io, 'JPEG')
# except AttributeError:
# logger.error(f"No plate map found for {sub.rsl_plate_num}")
# # platemap.save("test.jpg", 'JPEG')
# self.base_dict['platemap'] = base64.b64encode(image_io.getvalue()).decode('utf-8')
# logger.debug(self.base_dict)
# html = template.render(sub=self.base_dict)
# with open("test.html", "w") as f:
# f.write(html)
try: try:
home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__() home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__()
except FileNotFoundError: except FileNotFoundError:
@@ -326,7 +347,7 @@ class SubmissionDetails(QDialog):
return return
try: try:
with open(fname, "w+b") as f: with open(fname, "w+b") as f:
pisa.CreatePDF(html, dest=f) pisa.CreatePDF(self.html, dest=f)
except PermissionError as e: except PermissionError as e:
logger.error(f"Error saving pdf: {e}") logger.error(f"Error saving pdf: {e}")
msg = QMessageBox() msg = QMessageBox()

View File

@@ -816,7 +816,7 @@ def import_pcr_results_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
def autofill_excel(obj:QMainWindow, xl_map:dict, reagents:List[dict], missing_reagents:List[str], info:dict): def autofill_excel(obj:QMainWindow, xl_map:dict, reagents:List[dict], missing_reagents:List[str], info:dict):
""" """
Automatically fills in excel cells with reagent info. Automatically fills in excel cells with submission info.
Args: Args:
obj (QMainWindow): Original main app window obj (QMainWindow): Original main app window
@@ -829,13 +829,15 @@ def autofill_excel(obj:QMainWindow, xl_map:dict, reagents:List[dict], missing_re
logger.debug(f"Here is the info dict coming in:\n{pprint.pformat(info)}") logger.debug(f"Here is the info dict coming in:\n{pprint.pformat(info)}")
logger.debug(f"Here are the missing reagents:\n{missing_reagents}") logger.debug(f"Here are the missing reagents:\n{missing_reagents}")
# pare down the xl map to only the missing data.
relevant_map = {k:v for k,v in xl_map.items() if k in missing_reagents} relevant_map = {k:v for k,v in xl_map.items() if k in missing_reagents}
# logger.debug(relevant_map) # pare down reagents to only what's missing
relevant_reagents = [item for item in reagents if item['type'] in missing_reagents] relevant_reagents = [item for item in reagents if item['type'] in missing_reagents]
# hacky manipulation of submission type so it looks better.
info['submission_type'] = info['submission_type'].replace("_", " ").title() info['submission_type'] = info['submission_type'].replace("_", " ").title()
# pare down info to just what's missing
relevant_info = {k:v for k,v in info.items() if k in missing_reagents} relevant_info = {k:v for k,v in info.items() if k in missing_reagents}
logger.debug(f"Here is the relevant info: {pprint.pformat(relevant_info)}") logger.debug(f"Here is the relevant info: {pprint.pformat(relevant_info)}")
# logger.debug(f"Relevant reagents:\n{relevant_reagents}")
# construct new objects to put into excel sheets: # construct new objects to put into excel sheets:
new_reagents = [] new_reagents = []
for reagent in relevant_reagents: for reagent in relevant_reagents:
@@ -846,12 +848,14 @@ def autofill_excel(obj:QMainWindow, xl_map:dict, reagents:List[dict], missing_re
new_reagent['expiry'] = relevant_map[new_reagent['type']]['expiry'] new_reagent['expiry'] = relevant_map[new_reagent['type']]['expiry']
new_reagent['expiry']['value'] = reagent['expiry'] new_reagent['expiry']['value'] = reagent['expiry']
new_reagent['sheet'] = relevant_map[new_reagent['type']]['sheet'] new_reagent['sheet'] = relevant_map[new_reagent['type']]['sheet']
# name is only present for Bacterial Culture
try: try:
new_reagent['name'] = relevant_map[new_reagent['type']]['name'] new_reagent['name'] = relevant_map[new_reagent['type']]['name']
new_reagent['name']['value'] = reagent['type'] new_reagent['name']['value'] = reagent['type']
except: except:
pass pass
new_reagents.append(new_reagent) new_reagents.append(new_reagent)
# construct new info objects to put into excel sheets
new_info = [] new_info = []
for item in relevant_info: for item in relevant_info:
new_item = {} new_item = {}
@@ -860,11 +864,15 @@ def autofill_excel(obj:QMainWindow, xl_map:dict, reagents:List[dict], missing_re
new_item['value'] = relevant_info[item] new_item['value'] = relevant_info[item]
new_info.append(new_item) new_info.append(new_item)
logger.debug(f"New reagents: {new_reagents}") logger.debug(f"New reagents: {new_reagents}")
# open the workbook using openpyxl
workbook = load_workbook(obj.xl) workbook = load_workbook(obj.xl)
# get list of sheet names
sheets = workbook.sheetnames sheets = workbook.sheetnames
logger.debug(workbook.sheetnames) # logger.debug(workbook.sheetnames)
for sheet in sheets: for sheet in sheets:
# open sheet
worksheet=workbook[sheet] worksheet=workbook[sheet]
# Get relevant reagents for that sheet
sheet_reagents = [item for item in new_reagents if sheet in item['sheet']] sheet_reagents = [item for item in new_reagents if sheet in item['sheet']]
for reagent in sheet_reagents: for reagent in sheet_reagents:
logger.debug(f"Attempting: {reagent['type']}:") logger.debug(f"Attempting: {reagent['type']}:")
@@ -874,10 +882,12 @@ def autofill_excel(obj:QMainWindow, xl_map:dict, reagents:List[dict], missing_re
worksheet.cell(row=reagent['name']['row'], column=reagent['name']['column'], value=reagent['name']['value'].replace("_", " ").upper()) worksheet.cell(row=reagent['name']['row'], column=reagent['name']['column'], value=reagent['name']['value'].replace("_", " ").upper())
except: except:
pass pass
# Get relevant info for that sheet
sheet_info = [item for item in new_info if sheet in item['location']['sheets']] sheet_info = [item for item in new_info if sheet in item['location']['sheets']]
for item in sheet_info: for item in sheet_info:
logger.debug(f"Attempting: {item['type']}") logger.debug(f"Attempting: {item['type']}")
worksheet.cell(row=item['location']['row'], column=item['location']['column'], value=item['value']) worksheet.cell(row=item['location']['row'], column=item['location']['column'], value=item['value'])
# Hacky way to
if info['submission_type'] == "Bacterial Culture": if info['submission_type'] == "Bacterial Culture":
workbook["Sample List"].cell(row=14, column=2, value=getuser()) workbook["Sample List"].cell(row=14, column=2, value=getuser())
fname = select_save_file(obj=obj, default_name=info['rsl_plate_num'], extension="xlsx") fname = select_save_file(obj=obj, default_name=info['rsl_plate_num'], extension="xlsx")

View File

@@ -1,40 +0,0 @@
{# template for constructing submission details #}
{% set excluded = ['reagents', 'samples', 'controls', 'ext_info', 'pcr_info', 'comments'] %}
{# for key, value in sub.items() if key != 'reagents' and key != 'samples' and key != 'controls' and key != 'ext_info' #}
{% for key, value in sub.items() if key not in excluded %}
{% if key=='Cost' %} {{ key }}: {{ "${:,.2f}".format(value) }} {% else %} {{ key }}: {{ value }} {% endif %}
{% endfor %}
Reagents:
{% for item in sub['reagents'] %}
{{ item['type'] }}: {{ item['lot'] }} (EXP: {{ item['expiry'] }}){% endfor %}
{% if sub['samples']%}
Samples:
{% for item in sub['samples'] %}
{{ item['well'] }}: {{ item['name'] }}{% endfor %}{% endif %}
{% if sub['controls'] %}
Attached Controls:
{% for item in sub['controls'] %}
{{ item['name'] }}: {{ item['type'] }} (Targets: {{ item['targets'] }})
{% if item['kraken'] %}
{{ item['name'] }} Top 5 Kraken Results
{% for genera in item['kraken'] %}
{{ genera['name'] }}: {{ genera['kraken_count'] }} ({{ genera['kraken_percent'] }}){% endfor %}{% endif %}
{% endfor %}{% endif %}
{% if sub['ext_info'] %}{% for entry in sub['ext_info'] %}
Extraction Status:
{% for key, value in entry.items() %}
{{ key|replace('_', ' ')|title() }}: {{ value }}{% endfor %}{% endfor %}{% endif %}
{% if sub['pcr_info'] %}{% for entry in sub['pcr_info'] %}
{% if 'comment' not in entry.keys() %}qPCR Momentum Status:{% else %}
qPCR Status{% endif %}
{% for key, value in entry.items() if key != 'imported_by' %}
{{ key|replace('_', ' ')|title() }}: {{ value }}{% endfor %}{% endfor %}
{% endif %}
{% if sub['comments'] %}
Comments:
{% for item in sub['comments'] %}
{{ item['name'] }}:
{{ item['text'] }}
- {{ item['time'] }}
{% endfor %}
{% endif %}