Adding in commenting submissions and barcode creation.

This commit is contained in:
Landon Wark
2023-05-12 14:26:18 -05:00
parent 8a95e3a4c5
commit 903f403672
12 changed files with 231 additions and 64 deletions

View File

@@ -38,6 +38,7 @@ class BasicSubmission(Base):
extraction_info = Column(JSON) #: unstructured output from the extraction table logger.
run_cost = Column(FLOAT(2)) #: total cost of running the plate. Set from constant and mutable kit costs at time of creation.
uploaded_by = Column(String(32)) #: user name of person who submitted the submission to the database.
comment = Column(JSON)
# Allows for subclassing into ex. BacterialCulture, Wastewater, etc.
__mapper_args__ = {
@@ -93,6 +94,11 @@ class BasicSubmission(Base):
samples = [item.to_sub_dict() for item in self.samples]
except:
samples = None
try:
comments = self.comment
except:
logger.error(self.comment)
comments = None
output = {
"id": self.id,
"Plate Number": self.rsl_plate_num,
@@ -106,9 +112,11 @@ class BasicSubmission(Base):
"Cost": self.run_cost,
"reagents": reagents,
"samples": samples,
"ext_info": ext_info
"ext_info": ext_info,
"comments": comments
}
# logger.debug(f"{self.rsl_plate_num} extraction: {output['Extraction Status']}")
# logger.debug(f"Output dict: {output}")
return output

View File

@@ -1,20 +1,25 @@
'''
Contains widgets specific to the submission summary and submission details.
'''
from datetime import datetime
from PyQt6 import QtPrintSupport
from PyQt6.QtWidgets import (
QVBoxLayout, QDialog, QTableView,
QTextEdit, QPushButton, QScrollArea,
QMessageBox, QFileDialog, QMenu
QMessageBox, QFileDialog, QMenu, QLabel,
QDialogButtonBox, QToolBar, QMainWindow
)
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
from PyQt6.QtGui import QFontMetrics, QAction, QCursor
from backend.db import submissions_to_df, lookup_submission_by_id, delete_submission_by_id
from PyQt6.QtGui import QFontMetrics, QAction, QCursor, QPixmap, QPainter
from backend.db import submissions_to_df, lookup_submission_by_id, delete_submission_by_id, lookup_submission_by_rsl_num
from jinja2 import Environment, FileSystemLoader
from xhtml2pdf import pisa
import sys
from pathlib import Path
import logging
from .pop_ups import QuestionAsker
from ..visualizations import make_plate_barcode
from getpass import getuser
logger = logging.getLogger(f"submissions.{__name__}")
@@ -120,6 +125,22 @@ class SubmissionsSheet(QTableView):
if dlg.exec():
pass
def create_barcode(self) -> None:
index = (self.selectionModel().currentIndex())
value = index.sibling(index.row(),1).data()
logger.debug(f"Selected value: {value}")
dlg = BarcodeWindow(value)
if dlg.exec():
dlg.print_barcode()
def add_comment(self) -> None:
index = (self.selectionModel().currentIndex())
value = index.sibling(index.row(),1).data()
logger.debug(f"Selected value: {value}")
dlg = SubmissionComment(ctx=self.ctx, rsl=value)
if dlg.exec():
dlg.add_comment()
def contextMenuEvent(self, event):
"""
@@ -131,10 +152,16 @@ class SubmissionsSheet(QTableView):
self.menu = QMenu(self)
renameAction = QAction('Delete', self)
detailsAction = QAction('Details', self)
barcodeAction = QAction("Print Barcode", self)
commentAction = QAction("Add Comment", self)
renameAction.triggered.connect(lambda: self.delete_item(event))
detailsAction.triggered.connect(lambda: self.show_details())
barcodeAction.triggered.connect(lambda: self.create_barcode())
commentAction.triggered.connect(lambda: self.add_comment())
self.menu.addAction(detailsAction)
self.menu.addAction(renameAction)
self.menu.addAction(barcodeAction)
self.menu.addAction(commentAction)
# add other required actions
self.menu.popup(QCursor.pos())
@@ -227,3 +254,122 @@ class SubmissionDetails(QDialog):
msg.setInformativeText(f"Looks like {fname.__str__()} is open.\nPlease close it and try again.")
msg.setWindowTitle("Permission Error")
msg.exec()
class BarcodeWindow(QDialog):
def __init__(self, rsl_num:str):
super().__init__()
# set the title
self.setWindowTitle("Image")
self.layout = QVBoxLayout()
# setting the geometry of window
self.setGeometry(0, 0, 400, 300)
# creating label
self.label = QLabel()
self.img = make_plate_barcode(rsl_num)
# logger.debug(dir(img), img.contents[0])
# fp = BytesIO().read()
# img.save(formats=['png'], fnRoot=fp)
# pixmap = QPixmap("C:\\Users\\lwark\\Documents\\python\\submissions\\src\\Drawing000.png")
self.pixmap = QPixmap()
# self.pixmap.loadFromData(self.img.asString("bmp"))
self.pixmap.loadFromData(self.img)
# adding image to label
self.label.setPixmap(self.pixmap)
# Optional, resize label to image size
# self.label.resize(self.pixmap.width(), self.pixmap.height())
# self.label.resize(200, 200)
# show all the widgets]
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout.addWidget(self.label)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
self._createActions()
self._createToolBar()
self._connectActions()
def _createToolBar(self):
"""
adds items to menu bar
"""
toolbar = QToolBar("My main toolbar")
# self.addToolBar(toolbar)
# self.layout.setToolBar(toolbar)
toolbar.addAction(self.printAction)
def _createActions(self):
"""
creates actions
"""
self.printAction = QAction("&Print", self)
def _connectActions(self):
"""
connect menu and tool bar item to functions
"""
self.printAction.triggered.connect(self.print_barcode)
def print_barcode(self):
printer = QtPrintSupport.QPrinter()
dialog = QtPrintSupport.QPrintDialog(printer)
if dialog.exec():
self.handle_paint_request(printer, self.pixmap.toImage())
def handle_paint_request(self, printer:QtPrintSupport.QPrinter, im):
logger.debug(f"Hello from print handler.")
painter = QPainter(printer)
image = QPixmap.fromImage(im)
painter.drawPixmap(120, -20, image)
painter.end()
class SubmissionComment(QDialog):
"""
a window showing text details of submission
"""
def __init__(self, ctx:dict, rsl:str) -> None:
super().__init__()
self.ctx = ctx
self.rsl = rsl
self.setWindowTitle(f"{self.rsl} Submission Comment")
# create text field
self.txt_editor = QTextEdit(self)
self.txt_editor.setReadOnly(False)
self.txt_editor.setText("Add Comment")
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
self.setFixedSize(400, 300)
self.layout.addWidget(self.txt_editor)
self.layout.addWidget(self.buttonBox, alignment=Qt.AlignmentFlag.AlignBottom)
self.setLayout(self.layout)
def add_comment(self):
commenter = getuser()
comment = self.txt_editor.toPlainText()
dt = datetime.strftime(datetime.now(), "%Y-%m-d %H:%M:%S")
full_comment = {"name":commenter, "time": dt, "text": comment}
logger.debug(f"Full comment: {full_comment}")
sub = lookup_submission_by_rsl_num(ctx = self.ctx, rsl_num=self.rsl)
try:
sub.comment.append(full_comment)
except AttributeError:
sub.comment = [full_comment]
logger.debug(sub.__dict__)
self.ctx['database_session'].add(sub)
self.ctx['database_session'].commit()

View File

@@ -1,4 +1,5 @@
'''
Contains all operations for creating charts, graphs and visual effects.
'''
from .control_charts import *
from .control_charts import *
from .barcode import *

View File

@@ -0,0 +1,8 @@
from reportlab.graphics.barcode import createBarcodeDrawing, createBarcodeImageInMemory
from reportlab.graphics.shapes import Drawing
from reportlab.lib.units import mm
def make_plate_barcode(text:str) -> Drawing:
# return createBarcodeDrawing('Code128', value=text, width=200, height=50, humanReadable=True)
return createBarcodeImageInMemory('Code128', value=text, width=100*mm, height=25*mm, humanReadable=True, format="png")

View File

@@ -3,7 +3,7 @@
<head>
<title>Submission Details for {{ sub['Plate Number'] }}</title>
</head>
{% set excluded = ['reagents', 'samples', 'controls', 'ext_info', 'pcr_info'] %}
{% set excluded = ['reagents', 'samples', 'controls', 'ext_info', 'pcr_info', 'comments'] %}
<body>
<h2><u>Submission Details for {{ sub['Plate Number'] }}</u></h2>
<p>{% for key, value in sub.items() if key not in excluded %}
@@ -51,7 +51,7 @@
{% for entry in sub['ext_info'] %}
<h3><u>Extraction Status:</u></h3>
<p>{% for key, value in entry.items() %}
{% if loop.index == 1%}
{% if loop.index == 1 %}
&nbsp;&nbsp;&nbsp;<b>{{ key|replace('_', ' ')|title() }}:</b> {{ value }}<br>
{% else %}
{% if "column" in key %}
@@ -71,7 +71,7 @@
<h3><u>qPCR Status:</u></h3>
{% endif %}
<p>{% for key, value in entry.items() if key != 'imported_by'%}
{% if loop.index == 1%}
{% if loop.index == 1 %}
&nbsp;&nbsp;&nbsp;<b>{{ key|replace('_', ' ')|title() }}:</b> {{ value }}<br>
{% else %}
{% if "column" in key %}
@@ -83,5 +83,15 @@
{% endfor %}</p>
{% endfor %}
{% endif %}
{% if sub['comments'] %}
<h3><u>Comments:</u></h3>
<p>{% for entry in sub['comments'] %}
{% if loop.index == 1 %}
&nbsp;&nbsp;&nbsp;<b>{{ entry['name'] }}:</b><br> {{ entry['text'] }}<br>- {{ entry['time'] }}<br>
{% else %}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>{{ entry['name'] }}:</b><br> {{ entry['text'] }}<br>- {{ entry['time'] }}<br>
{% endif %}
{% endfor %}</p>
{% endif %}
</body>
</html>

View File

@@ -1,5 +1,5 @@
{# template for constructing submission details #}
{% set excluded = ['reagents', 'samples', 'controls', 'ext_info', 'pcr_info'] %}
{% 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 %}
@@ -30,3 +30,11 @@ 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 %}

View File

@@ -195,7 +195,7 @@ class RSLNamer(object):
return
logger.debug(f"Attempting match of {in_str}")
regex = re.compile(r"""
(?P<wastewater>RSL(?:-|_)?WW(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(?:_|-)\d(?!\d))?)|
(?P<wastewater>RSL(?:-|_)?WW(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(?:_|-)R?\d(?!\d))?)|
(?P<bacterial_culture>RSL-?\d{2}-?\d{4})
""", flags = re.IGNORECASE | re.VERBOSE)
m = regex.search(in_str)