Writer beginnings

This commit is contained in:
lwark
2025-07-08 12:17:32 -05:00
parent 0472afd9a5
commit 432854e76f
12 changed files with 141 additions and 129 deletions

View File

@@ -18,7 +18,10 @@ from sqlalchemy.exc import ArgumentError
from typing import Any, List, ClassVar from typing import Any, List, ClassVar
from pathlib import Path from pathlib import Path
from sqlalchemy.orm.relationships import _RelationshipDeclared from sqlalchemy.orm.relationships import _RelationshipDeclared
from frontend import select_save_file
from tools import report_result, list_sort_dict from tools import report_result, list_sort_dict
from backend.excel import writers
# NOTE: Load testing environment # NOTE: Load testing environment
if 'pytest' in sys.modules: if 'pytest' in sys.modules:
@@ -241,6 +244,7 @@ class BaseClass(Base):
allowed = [k for k, v in cls.__dict__.items() if isinstance(v, InstrumentedAttribute) or isinstance(v, hybrid_property)] allowed = [k for k, v in cls.__dict__.items() if isinstance(v, InstrumentedAttribute) or isinstance(v, hybrid_property)]
# and not isinstance(v.property, _RelationshipDeclared)] # and not isinstance(v.property, _RelationshipDeclared)]
sanitized_kwargs = {k: v for k, v in kwargs.items() if k in allowed} sanitized_kwargs = {k: v for k, v in kwargs.items() if k in allowed}
outside_kwargs = {k: v for k, v in kwargs.items() if k not in allowed}
logger.debug(f"Sanitized kwargs: {sanitized_kwargs}") logger.debug(f"Sanitized kwargs: {sanitized_kwargs}")
instance = cls.query(**sanitized_kwargs) instance = cls.query(**sanitized_kwargs)
if not instance or isinstance(instance, list): if not instance or isinstance(instance, list):
@@ -258,6 +262,7 @@ class BaseClass(Base):
setattr(instance, k, v.to_sql()) setattr(instance, k, v.to_sql())
else: else:
logger.error(f"Could not set {k} due to {e}") logger.error(f"Could not set {k} due to {e}")
instance._misc_info.update(outside_kwargs)
logger.info(f"Instance from query or create: {instance}, new: {new}") logger.info(f"Instance from query or create: {instance}, new: {new}")
return instance, new return instance, new
@@ -309,10 +314,16 @@ class BaseClass(Base):
check = False check = False
if check: if check:
logger.debug("Got uselist") logger.debug("Got uselist")
query = query.filter(attr.contains(v)) try:
query = query.filter(attr.contains(v))
except ArgumentError:
continue
else: else:
logger.debug("Single item.") logger.debug("Single item.")
query = query.filter(attr == v) try:
query = query.filter(attr == v)
except ArgumentError:
continue
if k in singles: if k in singles:
logger.warning(f"{k} is in singles. Returning only one value.") logger.warning(f"{k} is in singles. Returning only one value.")
limit = 1 limit = 1
@@ -496,7 +507,10 @@ class BaseClass(Base):
value = json.dumps(value) value = json.dumps(value)
except TypeError: except TypeError:
value = str(value) value = str(value)
self._misc_info.update({key: value}) try:
self._misc_info.update({key: value})
except AttributeError:
self._misc_info = {key: value}
return return
try: try:
field_type = getattr(self.__class__, key) field_type = getattr(self.__class__, key)
@@ -623,6 +637,17 @@ class BaseClass(Base):
if dlg.exec(): if dlg.exec():
pass pass
def export(self, obj, output_filepath: str|Path|None=None):
if not hasattr(self, "template_file"):
logger.error(f"Export not implemented for {self.__class__.__name__}")
return
pyd = self.to_pydantic()
if not output_filepath:
output_filepath = select_save_file(obj=obj, default_name=pyd.construct_filename(), extension="xlsx")
Writer = getattr(writers, f"{self.__class__.__name__}Writer")
writer = Writer(output_filepath=output_filepath, pydant_obj=pyd, range_dict=self.range_dict)
workbook = writer
class LogMixin(Base): class LogMixin(Base):
tracking_exclusion: ClassVar = ['artic_technician', 'clientsubmissionsampleassociation', tracking_exclusion: ClassVar = ['artic_technician', 'clientsubmissionsampleassociation',

View File

@@ -15,6 +15,8 @@ from operator import itemgetter
from pprint import pformat from pprint import pformat
from pandas import DataFrame from pandas import DataFrame
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from frontend import select_save_file
from . import Base, BaseClass, Reagent, SubmissionType, KitType, ClientLab, Contact, LogMixin, Procedure, kittype_procedure from . import Base, BaseClass, Reagent, SubmissionType, KitType, ClientLab, Contact, LogMixin, Procedure, kittype_procedure
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case, func, Table, Sequence from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case, func, Table, Sequence
from sqlalchemy.orm import relationship, validates, Query from sqlalchemy.orm import relationship, validates, Query
@@ -158,6 +160,14 @@ class ClientSubmission(BaseClass, LogMixin):
offset = None offset = None
return cls.execute_query(query=query, limit=limit, offset=offset, **kwargs) return cls.execute_query(query=query, limit=limit, offset=offset, **kwargs)
@property
def template_file(self):
return self.submissiontype.template_file
@property
def range_dict(self):
return self.submissiontype.info_map
@classmethod @classmethod
def submissions_to_df(cls, submissiontype: str | None = None, limit: int = 0, def submissions_to_df(cls, submissiontype: str | None = None, limit: int = 0,
chronologic: bool = True, page: int = 1, page_size: int = 250) -> pd.DataFrame: chronologic: bool = True, page: int = 1, page_size: int = 250) -> pd.DataFrame:
@@ -318,12 +328,12 @@ class ClientSubmission(BaseClass, LogMixin):
logger.debug(f"Sample: {sample.id}") logger.debug(f"Sample: {sample.id}")
if sample not in run.sample: if sample not in run.sample:
assoc = run.add_sample(sample) assoc = run.add_sample(sample)
assoc.save() # assoc.save()
run.save()
else: else:
logger.warning("Run cancelled.") logger.warning("Run cancelled.")
obj.set_data() obj.set_data()
def edit(self, obj): def edit(self, obj):
logger.debug("Edit") logger.debug("Edit")
@@ -352,6 +362,9 @@ class ClientSubmission(BaseClass, LogMixin):
output['expanded'] = ["clientlab", "contact", "submissiontype"] output['expanded'] = ["clientlab", "contact", "submissiontype"]
return output return output
def to_pydantic(self, filepath: Path|str|None=None, **kwargs):
output = super().to_pydantic(filepath=filepath, **kwargs)
return output
class Run(BaseClass, LogMixin): class Run(BaseClass, LogMixin):
""" """
@@ -1266,6 +1279,15 @@ class Run(BaseClass, LogMixin):
self.set_attribute(key='comment', value=comment) self.set_attribute(key='comment', value=comment)
self.save(original=False) self.save(original=False)
def export(self, obj, output_filepath: str|Path|None=None):
clientsubmission_pyd = self.clientsubmission.to_pydantic()
if not output_filepath:
output_filepath = select_save_file(obj=obj, default_name=clientsubmission_pyd.construct_filename(), extension="xlsx")
Writer = getattr(writers, "ClientSubmissionWriter")
writer = Writer(output_filepath=output_filepath, pydant_obj=pyd, range_dict=self.range_dict)
workbook = writer.
def backup(self, obj=None, fname: Path | None = None, full_backup: bool = False): def backup(self, obj=None, fname: Path | None = None, full_backup: bool = False):
""" """
Exports xlsx info files for this instance. Exports xlsx info files for this instance.
@@ -1890,8 +1912,8 @@ class RunSampleAssociation(BaseClass):
# id = Column(INTEGER, unique=True, nullable=False) #: id to be used for inheriting purposes # id = Column(INTEGER, unique=True, nullable=False) #: id to be used for inheriting purposes
sample_id = Column(INTEGER, ForeignKey("_sample.id"), primary_key=True) #: id of associated sample sample_id = Column(INTEGER, ForeignKey("_sample.id"), primary_key=True) #: id of associated sample
run_id = Column(INTEGER, ForeignKey("_run.id"), primary_key=True) #: id of associated procedure run_id = Column(INTEGER, ForeignKey("_run.id"), primary_key=True) #: id of associated procedure
row = Column(INTEGER) #: row on the 96 well plate # row = Column(INTEGER) #: row on the 96 well plate
column = Column(INTEGER) #: column on the 96 well plate # column = Column(INTEGER) #: column on the 96 well plate
# misc_info = Column(JSON) # misc_info = Column(JSON)
# NOTE: reference to the Submission object # NOTE: reference to the Submission object

View File

@@ -5,6 +5,8 @@ from __future__ import annotations
import logging, re import logging, re
from pathlib import Path from pathlib import Path
from typing import Generator, Tuple, TYPE_CHECKING from typing import Generator, Tuple, TYPE_CHECKING
from openpyxl.reader.excel import load_workbook
from pandas import DataFrame from pandas import DataFrame
from backend.validators import pydant from backend.validators import pydant
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -78,8 +80,6 @@ class DefaultKEYVALUEParser(DefaultParser):
sheet="Sample List" sheet="Sample List"
)] )]
@property @property
def parsed_info(self) -> Generator[Tuple, None, None]: def parsed_info(self) -> Generator[Tuple, None, None]:
for item in self.range_dict: for item in self.range_dict:
@@ -91,7 +91,10 @@ class DefaultKEYVALUEParser(DefaultParser):
key = re.sub(r"\(.*\)", "", key) key = re.sub(r"\(.*\)", "", key)
key = key.lower().replace(":", "").strip().replace(" ", "_") key = key.lower().replace(":", "").strip().replace(" ", "_")
value = item['worksheet'].cell(row, item['value_column']).value value = item['worksheet'].cell(row, item['value_column']).value
value = dict(value=value, missing=False if value else True) missing = False if value else True
location_map = dict(row=row, key_column=item['key_column'], value_column=item['value_column'], sheet=item['sheet'])
value = dict(value=value, location=location_map, missing=missing)
logger.debug(f"Yieldings {value} for {key}")
yield key, value yield key, value
@@ -126,5 +129,5 @@ class DefaultTABLEParser(DefaultParser):
def to_pydantic(self, **kwargs): def to_pydantic(self, **kwargs):
return [self._pyd_object(**output) for output in self.parsed_info] return [self._pyd_object(**output) for output in self.parsed_info]
from .clientsubmission_parser import * from .clientsubmission_parser import ClientSubmissionSampleParser, ClientSubmissionInfoParser
from backend.excel.parsers.results_parsers.pcr_results_parser import PCRInfoParser, PCRSampleParser from backend.excel.parsers.results_parsers.pcr_results_parser import PCRInfoParser, PCRSampleParser

View File

@@ -96,8 +96,6 @@ class ClientSubmissionInfoParser(DefaultKEYVALUEParser, SubmissionTyperMixin):
# TODO: check if run with name already exists # TODO: check if run with name already exists
add_run = QuestionAsker(title="Add Run?", message="We've detected a sheet corresponding to an associated procedure type.\nWould you like to add a new run?") add_run = QuestionAsker(title="Add Run?", message="We've detected a sheet corresponding to an associated procedure type.\nWould you like to add a new run?")
if add_run.accepted: if add_run.accepted:
# NOTE: recruit parser. # NOTE: recruit parser.
try: try:
manager = getattr(procedure_managers, name) manager = getattr(procedure_managers, name)

View File

@@ -27,6 +27,7 @@ logger = logging.getLogger(f"procedure.{__name__}")
class PydBaseClass(BaseModel, extra='allow', validate_assignment=True): class PydBaseClass(BaseModel, extra='allow', validate_assignment=True):
_sql_object: ClassVar = None _sql_object: ClassVar = None
# _misc_info: dict|None = None
@model_validator(mode="before") @model_validator(mode="before")
@classmethod @classmethod
@@ -67,6 +68,10 @@ class PydBaseClass(BaseModel, extra='allow', validate_assignment=True):
def __init__(self, **data): def __init__(self, **data):
# NOTE: Grab the sql model for validation purposes. # NOTE: Grab the sql model for validation purposes.
self.__class__._sql_object = getattr(models, self.__class__.__name__.replace("Pyd", "")) self.__class__._sql_object = getattr(models, self.__class__.__name__.replace("Pyd", ""))
# try:
# self.template_file = self.__class__._sql_object.template_file
# except AttributeError:
# pass
super().__init__(**data) super().__init__(**data)
def filter_field(self, key: str) -> Any: def filter_field(self, key: str) -> Any:
@@ -105,6 +110,12 @@ class PydBaseClass(BaseModel, extra='allow', validate_assignment=True):
output = {k: getattr(self, k) for k in fields} output = {k: getattr(self, k) for k in fields}
else: else:
output = {k: self.filter_field(k) for k in fields} output = {k: self.filter_field(k) for k in fields}
if hasattr(self, "misc_info") and "info_placement" in self.misc_info:
for k, v in output.items():
try:
output[k]['location'] = [item['location'] for item in self.misc_info['info_placement'] if item['name'] == k]
except (TypeError, KeyError):
continue
return output return output
def to_sql(self): def to_sql(self):
@@ -301,6 +312,7 @@ class PydSample(PydBaseClass):
sql._misc_info["submission_rank"] = self.submission_rank sql._misc_info["submission_rank"] = self.submission_rank
return sql return sql
class PydTips(BaseModel): class PydTips(BaseModel):
name: str name: str
lot: str | None = Field(default=None) lot: str | None = Field(default=None)
@@ -1662,7 +1674,7 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
class PydClientSubmission(PydBaseClass): class PydClientSubmission(PydBaseClass):
# sql_object: ClassVar = ClientSubmission # sql_object: ClassVar = ClientSubmission
filepath: Path filepath: Path | None = Field(default=None)
submissiontype: dict | None submissiontype: dict | None
submitted_date: dict | None = Field(default=dict(value=date.today(), missing=True), validate_default=True) submitted_date: dict | None = Field(default=dict(value=date.today(), missing=True), validate_default=True)
clientlab: dict | None clientlab: dict | None
@@ -1673,6 +1685,39 @@ class PydClientSubmission(PydBaseClass):
contact: dict | None = Field(default=dict(value=None, missing=True), validate_default=True) contact: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
submitter_plate_id: dict | None = Field(default=dict(value=None, missing=True), validate_default=True) submitter_plate_id: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
@field_validator("submitted_date", mode="before")
@classmethod
def enforce_submitted_date(cls, value):
match value:
case str():
value = dict(value=datetime.strptime(value, "%Y-%m-%d %H:%M:%S"), missing=False)
case date() | datetime():
value = dict(value=value, missing=False)
case _:
pass
return value
@field_validator("submitter_plate_id", mode="before")
@classmethod
def enforce_submitter_plate_id(cls, value):
if isinstance(value, str):
value = dict(value=value, missing=False)
return value
@field_validator("submission_category", mode="before")
@classmethod
def enforce_submission_category_id(cls, value):
if isinstance(value, str):
value = dict(value=value, missing=False)
return value
@field_validator("sample_count", mode="before")
@classmethod
def enforce_sample_count(cls, value):
if isinstance(value, str) or isinstance(value, int):
value = dict(value=value, missing=False)
return value
@field_validator("sample_count") @field_validator("sample_count")
@classmethod @classmethod
def enforce_integer(cls, value): def enforce_integer(cls, value):
@@ -1700,7 +1745,7 @@ class PydClientSubmission(PydBaseClass):
except TypeError: except TypeError:
check = True check = True
if check: if check:
return dict(value=date.today(), missing=True) value.update(dict(value=date.today(), missing=True))
else: else:
match value['value']: match value['value']:
case str(): case str():
@@ -1735,6 +1780,29 @@ class PydClientSubmission(PydBaseClass):
from frontend.widgets.submission_widget import ClientSubmissionFormWidget from frontend.widgets.submission_widget import ClientSubmissionFormWidget
return ClientSubmissionFormWidget(parent=parent, clientsubmission=self, samples=samples, disable=disable) return ClientSubmissionFormWidget(parent=parent, clientsubmission=self, samples=samples, disable=disable)
def to_sql(self):
sql = super().to_sql()
if "info_placement" not in sql._misc_info:
sql._misc_info['info_placement'] = []
info_placement = []
for k in list(self.model_fields.keys()) + list(self.model_extra.keys()):
attribute = getattr(self, k)
match k:
case "filepath":
sql._misc_info[k] = attribute.__str__()
continue
case _:
pass
logger.debug(f"Setting {k} to {attribute}")
if isinstance(attribute, dict):
if "location" in attribute:
info_placement.append(dict(name=k, location=attribute['location']))
else:
info_placement.append(dict(name=k, location=None))
max_row = max([value['location']['row'] for value in info_placement if value])
sql._misc_info['info_placement'] = info_placement
return sql
class PydResults(PydBaseClass, arbitrary_types_allowed=True): class PydResults(PydBaseClass, arbitrary_types_allowed=True):
results: dict = Field(default={}) results: dict = Field(default={})

View File

@@ -35,8 +35,8 @@ class SampleChecker(QDialog):
self.channel = QWebChannel() self.channel = QWebChannel()
self.channel.registerObject('backend', self) self.channel.registerObject('backend', self)
# NOTE: Used to maintain javascript functions. # NOTE: Used to maintain javascript functions.
template = env.get_template("sample_checker.html") # template = env.get_template("sample_checker.html")
template_path = Path(template.environment.loader.__getattribute__("searchpath")[0]) # template_path = Path(template.environment.loader.__getattribute__("searchpath")[0])
# with open(template_path.joinpath("css", "styles.css"), "r") as f: # with open(template_path.joinpath("css", "styles.css"), "r") as f:
# css = [f.read()] # css = [f.read()]
try: try:

View File

@@ -472,6 +472,10 @@ class SubmissionFormWidget(QWidget):
self.missing: bool = value['missing'] self.missing: bool = value['missing']
except (TypeError, KeyError): except (TypeError, KeyError):
self.missing: bool = True self.missing: bool = True
try:
self.location: dict|None = value['location']
except (TypeError, KeyError):
self.location: dict|None = None
if self.input is not None: if self.input is not None:
layout.addWidget(self.label) layout.addWidget(self.label)
layout.addWidget(self.input) layout.addWidget(self.input)
@@ -501,7 +505,7 @@ class SubmissionFormWidget(QWidget):
value = self.input.date().toPyDate() value = self.input.date().toPyDate()
case _: case _:
return None, None return None, None
return self.input.objectName(), dict(value=value, missing=self.missing) return self.input.objectName(), dict(value=value, missing=self.missing, location=self.location)
def set_widget(self, parent: QWidget, key: str, value: dict, def set_widget(self, parent: QWidget, key: str, value: dict,
submission_type: str | SubmissionType | None = None, submission_type: str | SubmissionType | None = None,
@@ -518,6 +522,7 @@ class SubmissionFormWidget(QWidget):
Returns: Returns:
QWidget: Form object QWidget: Form object
""" """
if isinstance(submission_type, str): if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type) submission_type = SubmissionType.query(name=submission_type)
if sub_obj is None: if sub_obj is None:
@@ -843,6 +848,7 @@ class ClientSubmissionFormWidget(SubmissionFormWidget):
value = getattr(self, item) value = getattr(self, item)
info[item] = value info[item] = value
for k, v in info.items(): for k, v in info.items():
logger.debug(f"Setting pyd {k} to {v}")
self.pyd.__setattr__(k, v) self.pyd.__setattr__(k, v)
report.add_result(report) report.add_result(report)
return report return report

View File

@@ -1,28 +0,0 @@
{% extends "basicsubmission_details.html" %}
<head>
{% block head %}
{{ super() }}
{% endblock %}
</head>
<body>
{% block body %}
{{ super() }}
{% if sub['controls'] %}
<h3><u>Attached Controls:</u></h3>
{% for item in sub['controls'] %}
<p>&nbsp;&nbsp;&nbsp;<b>{{ item['name'] }}:</b> {{ item['type'] }} (Targets: {{ item['targets'] }})</p>
{% if item['kraken'] %}
<p>&nbsp;&nbsp;&nbsp;{{ item['name'] }} Top 10 Kraken Results:</p>
<p>{% for genera in item['kraken'] %}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{{ genera['name'] }}: {{ genera['kraken_count'] }} ({{ genera['kraken_percent'] }})<br>
{% endfor %}</p>
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}
{% block signing_button %}
{{ super() }}
{% endblock %}
</body>

View File

@@ -10,12 +10,8 @@
<h2><u>Equipment Details for {{ equipment['name'] }}</u></h2> <h2><u>Equipment Details for {{ equipment['name'] }}</u></h2>
{{ super() }} {{ super() }}
<p>{% for key, value in equipment.items() if key not in equipment['excluded'] %} <p>{% for key, value in equipment.items() if key not in equipment['excluded'] %}
<!-- &nbsp;&nbsp;&nbsp;&nbsp;<b>{{ key | replace("_", " ") | title }}: </b>{% if permission and key in reagent['editable']%}<input type={% if key=='expiry' %}"date"{% else %}"text"{% endif %} id="{{ key }}" name="{{ key }}" value="{{ value }}">{% else %}{{ value }}{% endif %}<br>-->
&nbsp;&nbsp;&nbsp;&nbsp;<b>{{ key | replace("_", " ") | title }}: </b>{{ value }}<br> &nbsp;&nbsp;&nbsp;&nbsp;<b>{{ key | replace("_", " ") | title }}: </b>{{ value }}<br>
{% endfor %}</p> {% endfor %}</p>
<!-- {% if permission %}-->
<!-- <button type="button" id="save_btn">Save</button>-->
<!-- {% endif %}-->
{% if equipment['submissions'] %}<h2>Submissions:</h2> {% if equipment['submissions'] %}<h2>Submissions:</h2>
{% for submission in equipment['submissions'] %} {% for submission in equipment['submissions'] %}
<p><b><a class="data-link" id="{{ submission['plate'] }}">{{ submission['plate'] }}:</a></b> <a class="data-link process" id="{{ submission['process'] }}">{{ submission['process'] }}</a></p> <p><b><a class="data-link" id="{{ submission['plate'] }}">{{ submission['plate'] }}:</a></b> <a class="data-link process" id="{{ submission['process'] }}">{{ submission['process'] }}</a></p>

View File

@@ -1,4 +1,4 @@
Sample name: {{ fields['submitter_id'] }}<br> Sample name: {{ fields['submitter_id'] }}<br>
{% if fields['organism'] %}Organism: {{ fields['organism'] }}<br>{% endif %} {% if fields['organism'] %}Organism: {{ fields['organism'] }}<br>{% endif %}
{% if fields['concentration'] %}Concentration: {{ fields['concentration'] }}<br>{% endif %} {% if fields['concentration'] %}Concentration: {{ fields['concentration'] }}<br>{% endif %}
Well: {{ fields['well'] }}<!--{{ fields['column'] }}--> Well: {{ fields['well'] }}

View File

@@ -1,37 +0,0 @@
{% extends "basicsubmission_details.html" %}
<head>
{% block head %}
{{ super() }}
{% endblock %}
</head>
<body>
{% block body %}
{{ super() }}
{% if sub['pcr_info'] %}
{% for entry in sub['pcr_info'] %}
{% if 'comment' not in entry.keys() %}
<h3><u>qPCR Momentum Status:</u></h3>
{% else %}
<h3><u>qPCR Status:</u></h3>
{% endif %}
<p>{% for key, value in entry.items() if key != 'imported_by'%}
{% if "column" in key %}
&nbsp;&nbsp;&nbsp;&nbsp;<b>{{ key|replace('_', ' ')|title() }}:</b> {{ value }}uL<br>
{% else %}
&nbsp;&nbsp;&nbsp;&nbsp;<b>{{ key|replace('_', ' ')|title() }}:</b> {{ value }}<br>
{% endif %}
{% endfor %}</p>
{% endfor %}
{% endif %}
{% if sub['origin_plate'] %}
<br/>
<h3><u>24 Well Plate:</u></h3>
{{ sub['origin_plate'] }}
{% endif %}
{% endblock %}
{% block signing_button %}
{{ super() }}
{% endblock %}
</body>

View File

@@ -1,41 +0,0 @@
{% extends "basicsubmission_details.html" %}
<head>
{% block head %}
{{ super() }}
{% endblock %}
</head>
<body>
{% block body %}
{{ super() }}
{% if sub['gel_info'] %}
<br/>
<h3><u>Gel Box:</u></h3>
{% if sub['gel_image_actual'] %}
<br/>
<img align='left' height="400px" width="600px" src="data:image/jpeg;base64,{{ sub['gel_image_actual'] | safe }}">
{% endif %}
<br/>
<table style="width:100%; border: 1px solid black; border-collapse: collapse;">
<tr>
{% for header in sub['headers'] %}
<th>{{ header }}</th>
{% endfor %}
</tr>
{% for field in sub['gel_info'] %}
<tr>
<td>{{ field['name'] }}</td>
{% for item in field['values'] %}
<td>{{ item['value'] }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<br/>
{% endif %}
{% endblock %}
{% block signing_button %}
{{ super() }}
{% endblock %}
</body>