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
- Auto-filling of some empty cells in excel file.

View File

@@ -6,15 +6,16 @@
## Logging in New Run:
*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)
a. All fields should be filled in to ensure proper lookups of reagents.
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.
1. Ensure a properly formatted Submission Excel form has been filled out.
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.
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.
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.
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.
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.
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] Code clean-up and refactor (2023-07).
- [ ] Migrate context settings to pydantic-settings model.

View File

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

View File

@@ -11,8 +11,9 @@ from PyQt6.QtWidgets import (
QMessageBox, QFileDialog, QMenu, QLabel,
QDialogButtonBox, QToolBar
)
from PyQt6.QtWebEngineWidgets import QWebEngineView
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.excel import make_hitpicks
from configure import jinja_template_loading
@@ -266,40 +267,25 @@ class SubmissionDetails(QDialog):
# don't want id
del self.base_dict['id']
# retrieve jinja template
template = env.get_template("submission_details.txt")
# template = env.get_template("submission_details.txt")
# render using object dict
text = template.render(sub=self.base_dict)
# text = template.render(sub=self.base_dict)
# create text field
txt_editor = QTextEdit(self)
txt_editor.setReadOnly(True)
txt_editor.document().setPlainText(text)
# txt_editor = QTextEdit(self)
# txt_editor.setReadOnly(True)
# txt_editor.document().setPlainText(text)
# resize
font = txt_editor.document().defaultFont()
fontMetrics = QFontMetrics(font)
textSize = fontMetrics.size(0, txt_editor.toPlainText())
w = textSize.width() + 10
h = textSize.height() + 10
txt_editor.setMinimumSize(w, h)
txt_editor.setMaximumSize(w, h)
txt_editor.resize(w, h)
interior.resize(w,900)
txt_editor.setText(text)
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
# font = txt_editor.document().defaultFont()
# fontMetrics = QFontMetrics(font)
# textSize = fontMetrics.size(0, txt_editor.toPlainText())
# w = textSize.width() + 10
# h = textSize.height() + 10
# txt_editor.setMinimumSize(w, h)
# txt_editor.setMaximumSize(w, h)
# txt_editor.resize(w, h)
# interior.resize(w,900)
# txt_editor.setText(text)
# interior.setWidget(txt_editor)
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)
@@ -312,10 +298,45 @@ class SubmissionDetails(QDialog):
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)
template = env.get_template("submission_details.html")
self.html = template.render(sub=self.base_dict)
webview = QWebEngineView()
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:
home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__()
except FileNotFoundError:
@@ -326,7 +347,7 @@ class SubmissionDetails(QDialog):
return
try:
with open(fname, "w+b") as f:
pisa.CreatePDF(html, dest=f)
pisa.CreatePDF(self.html, dest=f)
except PermissionError as e:
logger.error(f"Error saving pdf: {e}")
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):
"""
Automatically fills in excel cells with reagent info.
Automatically fills in excel cells with submission info.
Args:
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 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}
# 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]
# hacky manipulation of submission type so it looks better.
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}
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:
new_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']['value'] = reagent['expiry']
new_reagent['sheet'] = relevant_map[new_reagent['type']]['sheet']
# name is only present for Bacterial Culture
try:
new_reagent['name'] = relevant_map[new_reagent['type']]['name']
new_reagent['name']['value'] = reagent['type']
except:
pass
new_reagents.append(new_reagent)
# construct new info objects to put into excel sheets
new_info = []
for item in relevant_info:
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_info.append(new_item)
logger.debug(f"New reagents: {new_reagents}")
# open the workbook using openpyxl
workbook = load_workbook(obj.xl)
# get list of sheet names
sheets = workbook.sheetnames
logger.debug(workbook.sheetnames)
# logger.debug(workbook.sheetnames)
for sheet in sheets:
# open sheet
worksheet=workbook[sheet]
# Get relevant reagents for that sheet
sheet_reagents = [item for item in new_reagents if sheet in item['sheet']]
for reagent in sheet_reagents:
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())
except:
pass
# Get relevant info for that sheet
sheet_info = [item for item in new_info if sheet in item['location']['sheets']]
for item in sheet_info:
logger.debug(f"Attempting: {item['type']}")
worksheet.cell(row=item['location']['row'], column=item['location']['column'], value=item['value'])
# Hacky way to
if info['submission_type'] == "Bacterial Culture":
workbook["Sample List"].cell(row=14, column=2, value=getuser())
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 %}