Adding in commenting submissions and barcode creation.
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
## 202305.02
|
## 202305.02
|
||||||
|
|
||||||
|
- Added rudimentary barcode printing.
|
||||||
|
- Added ability to comment on submissions.
|
||||||
- Updated kit creation methods to keep pace with new cost calculations.
|
- Updated kit creation methods to keep pace with new cost calculations.
|
||||||
|
|
||||||
## 202305.01
|
## 202305.01
|
||||||
|
|||||||
2
TODO.md
2
TODO.md
@@ -1 +1,3 @@
|
|||||||
|
- [ ] Create a method for commenting submissions.
|
||||||
|
- [ ] Create barcode generator, because of reasons that may or may not exist.
|
||||||
- [x] Move bulk of functions from frontend.__init__ to frontend.functions as __init__ is getting bloated.
|
- [x] Move bulk of functions from frontend.__init__ to frontend.functions as __init__ is getting bloated.
|
||||||
@@ -55,9 +55,9 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
|
|||||||
# are written from script.py.mako
|
# are written from script.py.mako
|
||||||
# output_encoding = utf-8
|
# output_encoding = utf-8
|
||||||
|
|
||||||
; sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db
|
sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db
|
||||||
sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\DB_backups\submissions-20230427.db
|
; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\DB_backups\submissions-20230427.db
|
||||||
; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\tests\test_assets\submissions_test.db
|
; msqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\tests\test_assets\submissions_test.db
|
||||||
|
|
||||||
|
|
||||||
[post_write_hooks]
|
[post_write_hooks]
|
||||||
|
|||||||
33
alembic/versions/a31943b2284c_added_commenting.py
Normal file
33
alembic/versions/a31943b2284c_added_commenting.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"""added commenting
|
||||||
|
|
||||||
|
Revision ID: a31943b2284c
|
||||||
|
Revises: 83b06f3f4869
|
||||||
|
Create Date: 2023-05-10 11:34:30.339915
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'a31943b2284c'
|
||||||
|
down_revision = '83b06f3f4869'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('_submissions', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('comment', sa.JSON(), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('_submissions', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('comment')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
"""split mutable costs into 96 and 24
|
|
||||||
|
|
||||||
Revision ID: dc780c868efd
|
|
||||||
Revises: 83b06f3f4869
|
|
||||||
Create Date: 2023-05-01 14:05:47.762441
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy.dialects import sqlite
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'dc780c868efd'
|
|
||||||
down_revision = '83b06f3f4869'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('_alembic_tmp__kits')
|
|
||||||
with op.batch_alter_table('_kits', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('mutable_cost_96', sa.FLOAT(precision=2), nullable=True))
|
|
||||||
batch_op.add_column(sa.Column('mutable_cost_24', sa.FLOAT(precision=2), nullable=True))
|
|
||||||
batch_op.drop_column('mutable_cost')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
|
|
||||||
with op.batch_alter_table('_kits', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('mutable_cost', sa.FLOAT(), nullable=True))
|
|
||||||
batch_op.drop_column('mutable_cost_24')
|
|
||||||
batch_op.drop_column('mutable_cost_96')
|
|
||||||
|
|
||||||
op.create_table('_alembic_tmp__kits',
|
|
||||||
sa.Column('id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('name', sa.VARCHAR(length=64), nullable=True),
|
|
||||||
sa.Column('used_for', sqlite.JSON(), nullable=True),
|
|
||||||
sa.Column('cost_per_run', sa.FLOAT(), nullable=True),
|
|
||||||
sa.Column('reagent_types_id', sa.INTEGER(), nullable=True),
|
|
||||||
sa.Column('constant_cost', sa.FLOAT(), nullable=True),
|
|
||||||
sa.Column('mutable_cost_96', sa.FLOAT(), nullable=True),
|
|
||||||
sa.Column('mutable_cost_24', sa.FLOAT(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['reagent_types_id'], ['_reagent_types.id'], ondelete='SET NULL'),
|
|
||||||
sa.PrimaryKeyConstraint('id'),
|
|
||||||
sa.UniqueConstraint('name')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -38,6 +38,7 @@ class BasicSubmission(Base):
|
|||||||
extraction_info = Column(JSON) #: unstructured output from the extraction table logger.
|
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.
|
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.
|
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.
|
# Allows for subclassing into ex. BacterialCulture, Wastewater, etc.
|
||||||
__mapper_args__ = {
|
__mapper_args__ = {
|
||||||
@@ -93,6 +94,11 @@ class BasicSubmission(Base):
|
|||||||
samples = [item.to_sub_dict() for item in self.samples]
|
samples = [item.to_sub_dict() for item in self.samples]
|
||||||
except:
|
except:
|
||||||
samples = None
|
samples = None
|
||||||
|
try:
|
||||||
|
comments = self.comment
|
||||||
|
except:
|
||||||
|
logger.error(self.comment)
|
||||||
|
comments = None
|
||||||
output = {
|
output = {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"Plate Number": self.rsl_plate_num,
|
"Plate Number": self.rsl_plate_num,
|
||||||
@@ -106,9 +112,11 @@ class BasicSubmission(Base):
|
|||||||
"Cost": self.run_cost,
|
"Cost": self.run_cost,
|
||||||
"reagents": reagents,
|
"reagents": reagents,
|
||||||
"samples": samples,
|
"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"{self.rsl_plate_num} extraction: {output['Extraction Status']}")
|
||||||
|
# logger.debug(f"Output dict: {output}")
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
'''
|
'''
|
||||||
Contains widgets specific to the submission summary and submission details.
|
Contains widgets specific to the submission summary and submission details.
|
||||||
'''
|
'''
|
||||||
|
from datetime import datetime
|
||||||
|
from PyQt6 import QtPrintSupport
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QVBoxLayout, QDialog, QTableView,
|
QVBoxLayout, QDialog, QTableView,
|
||||||
QTextEdit, QPushButton, QScrollArea,
|
QTextEdit, QPushButton, QScrollArea,
|
||||||
QMessageBox, QFileDialog, QMenu
|
QMessageBox, QFileDialog, QMenu, QLabel,
|
||||||
|
QDialogButtonBox, QToolBar, QMainWindow
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
|
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
|
||||||
from PyQt6.QtGui import QFontMetrics, QAction, QCursor
|
from PyQt6.QtGui import QFontMetrics, QAction, QCursor, QPixmap, QPainter
|
||||||
from backend.db import submissions_to_df, lookup_submission_by_id, delete_submission_by_id
|
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 jinja2 import Environment, FileSystemLoader
|
||||||
from xhtml2pdf import pisa
|
from xhtml2pdf import pisa
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
from .pop_ups import QuestionAsker
|
from .pop_ups import QuestionAsker
|
||||||
|
from ..visualizations import make_plate_barcode
|
||||||
|
from getpass import getuser
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
@@ -120,6 +125,22 @@ class SubmissionsSheet(QTableView):
|
|||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
pass
|
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):
|
def contextMenuEvent(self, event):
|
||||||
"""
|
"""
|
||||||
@@ -131,10 +152,16 @@ class SubmissionsSheet(QTableView):
|
|||||||
self.menu = QMenu(self)
|
self.menu = QMenu(self)
|
||||||
renameAction = QAction('Delete', self)
|
renameAction = QAction('Delete', self)
|
||||||
detailsAction = QAction('Details', self)
|
detailsAction = QAction('Details', self)
|
||||||
|
barcodeAction = QAction("Print Barcode", self)
|
||||||
|
commentAction = QAction("Add Comment", self)
|
||||||
renameAction.triggered.connect(lambda: self.delete_item(event))
|
renameAction.triggered.connect(lambda: self.delete_item(event))
|
||||||
detailsAction.triggered.connect(lambda: self.show_details())
|
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(detailsAction)
|
||||||
self.menu.addAction(renameAction)
|
self.menu.addAction(renameAction)
|
||||||
|
self.menu.addAction(barcodeAction)
|
||||||
|
self.menu.addAction(commentAction)
|
||||||
# add other required actions
|
# add other required actions
|
||||||
self.menu.popup(QCursor.pos())
|
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.setInformativeText(f"Looks like {fname.__str__()} is open.\nPlease close it and try again.")
|
||||||
msg.setWindowTitle("Permission Error")
|
msg.setWindowTitle("Permission Error")
|
||||||
msg.exec()
|
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()
|
||||||
|
|
||||||
|
|
||||||
@@ -2,3 +2,4 @@
|
|||||||
Contains all operations for creating charts, graphs and visual effects.
|
Contains all operations for creating charts, graphs and visual effects.
|
||||||
'''
|
'''
|
||||||
from .control_charts import *
|
from .control_charts import *
|
||||||
|
from .barcode import *
|
||||||
8
src/submissions/frontend/visualizations/barcode.py
Normal file
8
src/submissions/frontend/visualizations/barcode.py
Normal 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")
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Submission Details for {{ sub['Plate Number'] }}</title>
|
<title>Submission Details for {{ sub['Plate Number'] }}</title>
|
||||||
</head>
|
</head>
|
||||||
{% set excluded = ['reagents', 'samples', 'controls', 'ext_info', 'pcr_info'] %}
|
{% set excluded = ['reagents', 'samples', 'controls', 'ext_info', 'pcr_info', 'comments'] %}
|
||||||
<body>
|
<body>
|
||||||
<h2><u>Submission Details for {{ sub['Plate Number'] }}</u></h2>
|
<h2><u>Submission Details for {{ sub['Plate Number'] }}</u></h2>
|
||||||
<p>{% for key, value in sub.items() if key not in excluded %}
|
<p>{% for key, value in sub.items() if key not in excluded %}
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
{% for entry in sub['ext_info'] %}
|
{% for entry in sub['ext_info'] %}
|
||||||
<h3><u>Extraction Status:</u></h3>
|
<h3><u>Extraction Status:</u></h3>
|
||||||
<p>{% for key, value in entry.items() %}
|
<p>{% for key, value in entry.items() %}
|
||||||
{% if loop.index == 1%}
|
{% if loop.index == 1 %}
|
||||||
<b>{{ key|replace('_', ' ')|title() }}:</b> {{ value }}<br>
|
<b>{{ key|replace('_', ' ')|title() }}:</b> {{ value }}<br>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if "column" in key %}
|
{% if "column" in key %}
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<h3><u>qPCR Status:</u></h3>
|
<h3><u>qPCR Status:</u></h3>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>{% for key, value in entry.items() if key != 'imported_by'%}
|
<p>{% for key, value in entry.items() if key != 'imported_by'%}
|
||||||
{% if loop.index == 1%}
|
{% if loop.index == 1 %}
|
||||||
<b>{{ key|replace('_', ' ')|title() }}:</b> {{ value }}<br>
|
<b>{{ key|replace('_', ' ')|title() }}:</b> {{ value }}<br>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if "column" in key %}
|
{% if "column" in key %}
|
||||||
@@ -83,5 +83,15 @@
|
|||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if sub['comments'] %}
|
||||||
|
<h3><u>Comments:</u></h3>
|
||||||
|
<p>{% for entry in sub['comments'] %}
|
||||||
|
{% if loop.index == 1 %}
|
||||||
|
<b>{{ entry['name'] }}:</b><br> {{ entry['text'] }}<br>- {{ entry['time'] }}<br>
|
||||||
|
{% else %}
|
||||||
|
<b>{{ entry['name'] }}:</b><br> {{ entry['text'] }}<br>- {{ entry['time'] }}<br>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}</p>
|
||||||
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{# template for constructing submission details #}
|
{# 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 != 'reagents' and key != 'samples' and key != 'controls' and key != 'ext_info' #}
|
||||||
{% for key, value in sub.items() if key not in excluded %}
|
{% for key, value in sub.items() if key not in excluded %}
|
||||||
{% if key=='Cost' %} {{ key }}: {{ "${:,.2f}".format(value) }} {% else %} {{ key }}: {{ value }} {% endif %}
|
{% 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' %}
|
{% for key, value in entry.items() if key != 'imported_by' %}
|
||||||
{{ key|replace('_', ' ')|title() }}: {{ value }}{% endfor %}{% endfor %}
|
{{ key|replace('_', ' ')|title() }}: {{ value }}{% endfor %}{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if sub['comments'] %}
|
||||||
|
Comments:
|
||||||
|
{% for item in sub['comments'] %}
|
||||||
|
{{ item['name'] }}:
|
||||||
|
{{ item['text'] }}
|
||||||
|
- {{ item['time'] }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
@@ -195,7 +195,7 @@ class RSLNamer(object):
|
|||||||
return
|
return
|
||||||
logger.debug(f"Attempting match of {in_str}")
|
logger.debug(f"Attempting match of {in_str}")
|
||||||
regex = re.compile(r"""
|
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})
|
(?P<bacterial_culture>RSL-?\d{2}-?\d{4})
|
||||||
""", flags = re.IGNORECASE | re.VERBOSE)
|
""", flags = re.IGNORECASE | re.VERBOSE)
|
||||||
m = regex.search(in_str)
|
m = regex.search(in_str)
|
||||||
|
|||||||
Reference in New Issue
Block a user