Upgrade of WastewaterArtic parsers/writers for flexibility.
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
## 202407.02
|
||||||
|
|
||||||
|
- HTML template for 'About'.
|
||||||
|
- More flexible custom parsers/writers due to custom info items.
|
||||||
|
|
||||||
## 202407.01
|
## 202407.01
|
||||||
|
|
||||||
- Better documentation.
|
- Better documentation.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
## Startup:
|
## Startup:
|
||||||
1. Open the app using the shortcut in the Submissions folder. For example: L:\\Robotics Laboratory Support\\Submissions\\submissions_v122b.exe - Shortcut.lnk (Version may have changed).
|
1. Open the app using the shortcut in the Submissions folder. For example: L:\\Robotics Laboratory Support\\Submissions\\submissions_v122b.exe - Shortcut.lnk (Version may have changed).
|
||||||
a. Ignore the large black window of fast scrolling text, it is there for debugging purposes.
|
1. Ignore the large black window of fast scrolling text, it is there for debugging purposes.
|
||||||
b. The 'Submissions' tab should be open by default.
|
2. The 'Submissions' tab should be open by default.
|
||||||
|
|
||||||
## Logging in New Run:
|
## Logging in New Run:
|
||||||
*should fit 90% of usage cases*
|
*should fit 90% of usage cases*
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
## Importing PCR results (Wastewater only):
|
## Importing PCR results (Wastewater only):
|
||||||
|
|
||||||
This is meant to import .xslx files created from the Design & Analysis Software
|
This is meant to import .xslx files created from the Design & Analysis Software
|
||||||
|
|
||||||
1. Click on 'File' -> 'Import PCR Results'.
|
1. Click on 'File' -> 'Import PCR Results'.
|
||||||
2. Use the file dialog to locate the .xlsx file you want to import.
|
2. Use the file dialog to locate the .xlsx file you want to import.
|
||||||
3. Click 'Okay'.
|
3. Click 'Okay'.
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ def get_week_of_month() -> int:
|
|||||||
if day in week:
|
if day in week:
|
||||||
return ii + 1
|
return ii + 1
|
||||||
|
|
||||||
|
# Automatically completes project info for help menu and compiling.
|
||||||
__project__ = "submissions"
|
__project__ = "submissions"
|
||||||
__version__ = f"{year}{str(month).zfill(2)}.{get_week_of_month()}b"
|
__version__ = f"{year}{str(month).zfill(2)}.{get_week_of_month()}b"
|
||||||
__author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"}
|
__author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"}
|
||||||
__copyright__ = f"2022-{date.today().year}, Government of Canada"
|
__copyright__ = f"2022-{date.today().year}, Government of Canada"
|
||||||
|
__github__ = "https://github.com/landowark/submissions"
|
||||||
|
|
||||||
project_path = Path(__file__).parents[2].absolute()
|
project_path = Path(__file__).parents[2].absolute()
|
||||||
|
|
||||||
|
|||||||
@@ -672,8 +672,8 @@ class SubmissionType(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: Map of locations
|
dict: Map of locations
|
||||||
"""
|
"""
|
||||||
info = self.info_map
|
info = {k:v for k,v in self.info_map.items() if k != "custom"}
|
||||||
# logger.debug(f"Info map: {info}")
|
logger.debug(f"Info map: {info}")
|
||||||
output = {}
|
output = {}
|
||||||
match mode:
|
match mode:
|
||||||
case "read":
|
case "read":
|
||||||
@@ -681,6 +681,7 @@ class SubmissionType(BaseClass):
|
|||||||
case "write":
|
case "write":
|
||||||
output = {k: v[mode] + v['read'] for k, v in info.items() if v[mode] or v['read']}
|
output = {k: v[mode] + v['read'] for k, v in info.items() if v[mode] or v['read']}
|
||||||
output = {k: v for k, v in output.items() if all([isinstance(item, dict) for item in v])}
|
output = {k: v for k, v in output.items() if all([isinstance(item, dict) for item in v])}
|
||||||
|
output['custom'] = self.info_map['custom']
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def construct_sample_map(self) -> dict:
|
def construct_sample_map(self) -> dict:
|
||||||
|
|||||||
@@ -685,7 +685,7 @@ class BasicSubmission(BaseClass):
|
|||||||
|
|
||||||
# Child class custom functions
|
# Child class custom functions
|
||||||
@classmethod
|
@classmethod
|
||||||
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None) -> dict:
|
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None, custom_fields:dict={}) -> dict:
|
||||||
"""
|
"""
|
||||||
Update submission dictionary with type specific information
|
Update submission dictionary with type specific information
|
||||||
|
|
||||||
@@ -731,7 +731,7 @@ class BasicSubmission(BaseClass):
|
|||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def custom_info_writer(cls, input_excel: Workbook, info: dict | None = None, backup: bool = False) -> Workbook:
|
def custom_info_writer(cls, input_excel: Workbook, info: dict | None = None, backup: bool = False, custom_fields:dict={}) -> Workbook:
|
||||||
"""
|
"""
|
||||||
Adds custom autofill methods for submission
|
Adds custom autofill methods for submission
|
||||||
|
|
||||||
@@ -1307,6 +1307,7 @@ class BacterialCulture(BasicSubmission):
|
|||||||
row = idx.index.to_list()[0]
|
row = idx.index.to_list()[0]
|
||||||
return row + 1
|
return row + 1
|
||||||
|
|
||||||
|
|
||||||
class Wastewater(BasicSubmission):
|
class Wastewater(BasicSubmission):
|
||||||
"""
|
"""
|
||||||
derivative submission type from BasicSubmission
|
derivative submission type from BasicSubmission
|
||||||
@@ -1345,7 +1346,7 @@ class Wastewater(BasicSubmission):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None) -> dict:
|
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None, custom_fields:dict={}) -> dict:
|
||||||
"""
|
"""
|
||||||
Update submission dictionary with type specific information. Extends parent
|
Update submission dictionary with type specific information. Extends parent
|
||||||
|
|
||||||
@@ -1550,7 +1551,6 @@ class Wastewater(BasicSubmission):
|
|||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WastewaterArtic(BasicSubmission):
|
class WastewaterArtic(BasicSubmission):
|
||||||
"""
|
"""
|
||||||
derivative submission type for artic wastewater
|
derivative submission type for artic wastewater
|
||||||
@@ -1597,7 +1597,7 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None) -> dict:
|
def custom_info_parser(cls, input_dict: dict, xl: Workbook | None = None, custom_fields:dict={}) -> dict:
|
||||||
"""
|
"""
|
||||||
Update submission dictionary with type specific information
|
Update submission dictionary with type specific information
|
||||||
|
|
||||||
@@ -1608,16 +1608,20 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: Updated sample dictionary
|
dict: Updated sample dictionary
|
||||||
"""
|
"""
|
||||||
|
# TODO: Clean up and move range start/stops to db somehow.
|
||||||
input_dict = super().custom_info_parser(input_dict)
|
input_dict = super().custom_info_parser(input_dict)
|
||||||
ws = xl['Egel results']
|
egel_section = custom_fields['egel_results']
|
||||||
data = [ws.cell(row=ii, column=jj) for jj in range(15, 27) for ii in range(10, 18)]
|
ws = xl[egel_section['sheet']]
|
||||||
|
data = [ws.cell(row=ii, column=jj) for jj in range(egel_section['start_column'], egel_section['end_column']) for ii in range(egel_section['start_row'], egel_section['end_row'])]
|
||||||
data = [cell for cell in data if cell.value is not None and "NTC" in cell.value]
|
data = [cell for cell in data if cell.value is not None and "NTC" in cell.value]
|
||||||
input_dict['gel_controls'] = [
|
input_dict['gel_controls'] = [
|
||||||
dict(sample_id=cell.value, location=f"{row_map[cell.row - 9]}{str(cell.column - 14).zfill(2)}") for cell in
|
dict(sample_id=cell.value, location=f"{row_map[cell.row - 9]}{str(cell.column - 14).zfill(2)}") for cell in
|
||||||
data]
|
data]
|
||||||
ws = xl['First Strand List']
|
# NOTE: Get source plate information
|
||||||
data = [dict(plate=ws.cell(row=ii, column=3).value, starting_sample=ws.cell(row=ii, column=4).value) for ii in
|
source_plates_section = custom_fields['source_plates']
|
||||||
range(8, 11)]
|
ws = xl[source_plates_section['sheet']]
|
||||||
|
data = [dict(plate=ws.cell(row=ii, column=source_plates_section['plate_column']).value, starting_sample=ws.cell(row=ii, column=source_plates_section['starting_sample_column']).value) for ii in
|
||||||
|
range(source_plates_section['start_row'], source_plates_section['end_row'])]
|
||||||
for datum in data:
|
for datum in data:
|
||||||
if datum['plate'] in ["None", None, ""]:
|
if datum['plate'] in ["None", None, ""]:
|
||||||
continue
|
continue
|
||||||
@@ -1808,7 +1812,7 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def custom_info_writer(cls, input_excel: Workbook, info: dict | None = None, backup: bool = False) -> Workbook:
|
def custom_info_writer(cls, input_excel: Workbook, info: dict | None = None, backup: bool = False, custom_fields:dict={}) -> Workbook:
|
||||||
"""
|
"""
|
||||||
Adds custom autofill methods for submission. Extends Parent
|
Adds custom autofill methods for submission. Extends Parent
|
||||||
|
|
||||||
@@ -1822,30 +1826,33 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
"""
|
"""
|
||||||
input_excel = super().custom_info_writer(input_excel, info, backup)
|
input_excel = super().custom_info_writer(input_excel, info, backup)
|
||||||
# logger.debug(f"Info:\n{pformat(info)}")
|
# logger.debug(f"Info:\n{pformat(info)}")
|
||||||
|
# logger.debug(f"Custom fields:\n{pformat(custom_fields)}")
|
||||||
# NOTE: check for source plate information
|
# NOTE: check for source plate information
|
||||||
|
source_plates_section = custom_fields['source_plates']
|
||||||
if check_key_or_attr(key='source_plates', interest=info, check_none=True):
|
if check_key_or_attr(key='source_plates', interest=info, check_none=True):
|
||||||
worksheet = input_excel['First Strand List']
|
worksheet = input_excel[source_plates_section['sheet']]
|
||||||
start_row = 8
|
start_row = source_plates_section['start_row']
|
||||||
# NOTE: write source plates to First strand list
|
# NOTE: write source plates to First strand list
|
||||||
for iii, plate in enumerate(info['source_plates']['value']):
|
for iii, plate in enumerate(info['source_plates']['value']):
|
||||||
# logger.debug(f"Plate: {plate}")
|
# logger.debug(f"Plate: {plate}")
|
||||||
row = start_row + iii
|
row = start_row + iii
|
||||||
try:
|
try:
|
||||||
worksheet.cell(row=row, column=3, value=plate['plate'])
|
worksheet.cell(row=row, column=source_plates_section['plate_column'], value=plate['plate'])
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
worksheet.cell(row=row, column=4, value=plate['starting_sample'])
|
worksheet.cell(row=row, column=source_plates_section['starting_sample_column'], value=plate['starting_sample'])
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
# NOTE: check for gel information
|
# NOTE: check for gel information
|
||||||
|
egel_section = custom_fields['egel_results']
|
||||||
if check_key_or_attr(key='gel_info', interest=info, check_none=True):
|
if check_key_or_attr(key='gel_info', interest=info, check_none=True):
|
||||||
# logger.debug(f"Gel info check passed.")
|
# logger.debug(f"Gel info check passed.")
|
||||||
# NOTE: print json field gel results to Egel results
|
# NOTE: print json field gel results to Egel results
|
||||||
worksheet = input_excel['Egel results']
|
worksheet = input_excel[egel_section['sheet']]
|
||||||
# TODO: Move all this into a seperate function?
|
# TODO: Move all this into a seperate function?
|
||||||
start_row = 21
|
start_row = egel_section['start_row']
|
||||||
start_column = 15
|
start_column = egel_section['start_column']
|
||||||
for row, ki in enumerate(info['gel_info']['value'], start=1):
|
for row, ki in enumerate(info['gel_info']['value'], start=1):
|
||||||
# logger.debug(f"ki: {ki}")
|
# logger.debug(f"ki: {ki}")
|
||||||
# logger.debug(f"vi: {vi}")
|
# logger.debug(f"vi: {vi}")
|
||||||
@@ -1862,14 +1869,14 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.error(f"Failed {kj['name']} with value {kj['value']} to row {row}, column {column}")
|
logger.error(f"Failed {kj['name']} with value {kj['value']} to row {row}, column {column}")
|
||||||
if check_key_or_attr(key='gel_image_path', interest=info, check_none=True):
|
if check_key_or_attr(key='gel_image_path', interest=info, check_none=True):
|
||||||
worksheet = input_excel['Egel results']
|
worksheet = input_excel[egel_section['sheet']]
|
||||||
# logger.debug(f"We got an image: {info['gel_image']}")
|
# logger.debug(f"We got an image: {info['gel_image']}")
|
||||||
with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped:
|
with ZipFile(cls.__directory_path__.joinpath("submission_imgs.zip")) as zipped:
|
||||||
z = zipped.extract(info['gel_image_path']['value'], Path(TemporaryDirectory().name))
|
z = zipped.extract(info['gel_image_path']['value'], Path(TemporaryDirectory().name))
|
||||||
img = OpenpyxlImage(z)
|
img = OpenpyxlImage(z)
|
||||||
img.height = 400 # insert image height in pixels as float or int (e.g. 305.5)
|
img.height = 400 # insert image height in pixels as float or int (e.g. 305.5)
|
||||||
img.width = 600
|
img.width = 600
|
||||||
img.anchor = 'B9'
|
img.anchor = egel_section['img_anchor']
|
||||||
worksheet.add_image(img)
|
worksheet.add_image(img)
|
||||||
return input_excel
|
return input_excel
|
||||||
|
|
||||||
|
|||||||
@@ -212,12 +212,14 @@ class InfoParser(object):
|
|||||||
"""
|
"""
|
||||||
dicto = {}
|
dicto = {}
|
||||||
# NOTE: This loop parses generic info
|
# NOTE: This loop parses generic info
|
||||||
# logger.debug(f"Map: {self.map}")
|
logger.debug(f"Map: {self.map}")
|
||||||
for sheet in self.xl.sheetnames:
|
for sheet in self.xl.sheetnames:
|
||||||
ws = self.xl[sheet]
|
ws = self.xl[sheet]
|
||||||
relevant = []
|
relevant = []
|
||||||
for k, v in self.map.items():
|
for k, v in self.map.items():
|
||||||
# NOTE: If the value is hardcoded put it in the dictionary directly.
|
# NOTE: If the value is hardcoded put it in the dictionary directly.
|
||||||
|
if k == "custom":
|
||||||
|
continue
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
dicto[k] = dict(value=v, missing=False)
|
dicto[k] = dict(value=v, missing=False)
|
||||||
continue
|
continue
|
||||||
@@ -265,7 +267,7 @@ class InfoParser(object):
|
|||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
continue
|
continue
|
||||||
# Return after running the parser components held in submission object.
|
# Return after running the parser components held in submission object.
|
||||||
return self.sub_object.custom_info_parser(input_dict=dicto, xl=self.xl)
|
return self.sub_object.custom_info_parser(input_dict=dicto, xl=self.xl, custom_fields=self.map['custom'])
|
||||||
|
|
||||||
|
|
||||||
class ReagentParser(object):
|
class ReagentParser(object):
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ from pathlib import Path
|
|||||||
# from pathlib import Path
|
# from pathlib import Path
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import List
|
from typing import List
|
||||||
from collections import OrderedDict
|
|
||||||
from jinja2 import TemplateNotFound
|
|
||||||
from openpyxl import load_workbook, Workbook
|
from openpyxl import load_workbook, Workbook
|
||||||
from backend.db.models import SubmissionType, KitType, BasicSubmission
|
from backend.db.models import SubmissionType, KitType, BasicSubmission
|
||||||
from backend.validators.pydant import PydSubmission
|
from backend.validators.pydant import PydSubmission
|
||||||
@@ -135,8 +133,8 @@ class InfoWriter(object):
|
|||||||
self.submission_type = submission_type
|
self.submission_type = submission_type
|
||||||
self.sub_object = sub_object
|
self.sub_object = sub_object
|
||||||
self.xl = xl
|
self.xl = xl
|
||||||
info_map = submission_type.construct_info_map(mode='write')
|
self.info_map = submission_type.construct_info_map(mode='write')
|
||||||
self.info = self.reconcile_map(info_dict, info_map)
|
self.info = self.reconcile_map(info_dict, self.info_map)
|
||||||
# logger.debug(pformat(self.info))
|
# logger.debug(pformat(self.info))
|
||||||
|
|
||||||
def reconcile_map(self, info_dict: dict, info_map: dict) -> dict:
|
def reconcile_map(self, info_dict: dict, info_map: dict) -> dict:
|
||||||
@@ -154,6 +152,8 @@ class InfoWriter(object):
|
|||||||
for k, v in info_dict.items():
|
for k, v in info_dict.items():
|
||||||
if v is None:
|
if v is None:
|
||||||
continue
|
continue
|
||||||
|
if k == "custom":
|
||||||
|
continue
|
||||||
dicto = {}
|
dicto = {}
|
||||||
try:
|
try:
|
||||||
dicto['locations'] = info_map[k]
|
dicto['locations'] = info_map[k]
|
||||||
@@ -187,7 +187,7 @@ class InfoWriter(object):
|
|||||||
logger.debug(f"Writing {k} to {loc['sheet']}, row: {loc['row']}, column: {loc['column']}")
|
logger.debug(f"Writing {k} to {loc['sheet']}, row: {loc['row']}, column: {loc['column']}")
|
||||||
sheet = self.xl[loc['sheet']]
|
sheet = self.xl[loc['sheet']]
|
||||||
sheet.cell(row=loc['row'], column=loc['column'], value=v['value'])
|
sheet.cell(row=loc['row'], column=loc['column'], value=v['value'])
|
||||||
return self.sub_object.custom_info_writer(self.xl, info=self.info)
|
return self.sub_object.custom_info_writer(self.xl, info=self.info, custom_fields=self.info_map['custom'])
|
||||||
|
|
||||||
|
|
||||||
class ReagentWriter(object):
|
class ReagentWriter(object):
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ from PyQt6.QtWidgets import (
|
|||||||
)
|
)
|
||||||
from PyQt6.QtGui import QAction
|
from PyQt6.QtGui import QAction
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tools import check_if_app, Settings, Report
|
|
||||||
|
from markdown import markdown
|
||||||
|
|
||||||
|
from tools import check_if_app, Settings, Report, jinja_template_loading
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from .pop_ups import AlertPop
|
from .pop_ups import AlertPop, HTMLPop
|
||||||
from .misc import LogParser
|
from .misc import LogParser
|
||||||
import logging, webbrowser, sys, shutil
|
import logging, webbrowser, sys, shutil
|
||||||
from .submission_table import SubmissionsSheet
|
from .submission_table import SubmissionsSheet
|
||||||
@@ -31,22 +34,22 @@ class App(QMainWindow):
|
|||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.last_dir = ctx.directory_path
|
self.last_dir = ctx.directory_path
|
||||||
self.report = Report()
|
self.report = Report()
|
||||||
# indicate version and connected database in title bar
|
# NOTE: indicate version and connected database in title bar
|
||||||
try:
|
try:
|
||||||
self.title = f"Submissions App (v{ctx.package.__version__}) - {ctx.database_path}"
|
self.title = f"Submissions App (v{ctx.package.__version__}) - {ctx.database_path}"
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
self.title = f"Submissions App"
|
self.title = f"Submissions App"
|
||||||
# set initial app position and size
|
# NOTE: set initial app position and size
|
||||||
self.left = 0
|
self.left = 0
|
||||||
self.top = 0
|
self.top = 0
|
||||||
self.width = 1300
|
self.width = 1300
|
||||||
self.height = 1000
|
self.height = 1000
|
||||||
self.setWindowTitle(self.title)
|
self.setWindowTitle(self.title)
|
||||||
self.setGeometry(self.left, self.top, self.width, self.height)
|
self.setGeometry(self.left, self.top, self.width, self.height)
|
||||||
# insert tabs into main app
|
# NOTE: insert tabs into main app
|
||||||
self.table_widget = AddSubForm(self)
|
self.table_widget = AddSubForm(self)
|
||||||
self.setCentralWidget(self.table_widget)
|
self.setCentralWidget(self.table_widget)
|
||||||
# run initial setups
|
# NOTE: run initial setups
|
||||||
self._createActions()
|
self._createActions()
|
||||||
self._createMenuBar()
|
self._createMenuBar()
|
||||||
self._createToolBar()
|
self._createToolBar()
|
||||||
@@ -126,8 +129,11 @@ class App(QMainWindow):
|
|||||||
"""
|
"""
|
||||||
Show the 'about' message
|
Show the 'about' message
|
||||||
"""
|
"""
|
||||||
output = f"Version: {self.ctx.package.__version__}\n\nAuthor: {self.ctx.package.__author__['name']} - {self.ctx.package.__author__['email']}\n\nCopyright: {self.ctx.package.__copyright__}"
|
j_env = jinja_template_loading()
|
||||||
about = AlertPop(message=output, status="Information")
|
template = j_env.get_template("project.html")
|
||||||
|
html = template.render(info=self.ctx.package.__dict__)
|
||||||
|
# logger.debug(html)
|
||||||
|
about = HTMLPop(html=html, title="About")
|
||||||
about.exec()
|
about.exec()
|
||||||
|
|
||||||
def openDocs(self):
|
def openDocs(self):
|
||||||
@@ -143,11 +149,21 @@ class App(QMainWindow):
|
|||||||
|
|
||||||
def openGithub(self):
|
def openGithub(self):
|
||||||
"""
|
"""
|
||||||
Opens the instructions html page
|
Opens the github page
|
||||||
"""
|
"""
|
||||||
url = "https://github.com/landowark/submissions"
|
url = "https://github.com/landowark/submissions"
|
||||||
webbrowser.get('windows-default').open(url)
|
webbrowser.get('windows-default').open(url)
|
||||||
|
|
||||||
|
def openInstructions(self):
|
||||||
|
if check_if_app():
|
||||||
|
url = Path(sys._MEIPASS).joinpath("files", "README.md")
|
||||||
|
else:
|
||||||
|
url = Path("README.md")
|
||||||
|
with open(url, "r", encoding="utf-8") as f:
|
||||||
|
html = markdown(f.read())
|
||||||
|
instr = HTMLPop(html=html, title="Instructions")
|
||||||
|
instr.exec()
|
||||||
|
|
||||||
|
|
||||||
def result_reporter(self):
|
def result_reporter(self):
|
||||||
"""
|
"""
|
||||||
@@ -199,35 +215,35 @@ class AddSubForm(QWidget):
|
|||||||
# logger.debug(f"Initializating subform...")
|
# logger.debug(f"Initializating subform...")
|
||||||
super(QWidget, self).__init__(parent)
|
super(QWidget, self).__init__(parent)
|
||||||
self.layout = QVBoxLayout(self)
|
self.layout = QVBoxLayout(self)
|
||||||
# Initialize tab screen
|
# NOTE: Initialize tab screen
|
||||||
self.tabs = QTabWidget()
|
self.tabs = QTabWidget()
|
||||||
self.tab1 = QWidget()
|
self.tab1 = QWidget()
|
||||||
self.tab2 = QWidget()
|
self.tab2 = QWidget()
|
||||||
self.tab3 = QWidget()
|
self.tab3 = QWidget()
|
||||||
self.tab4 = QWidget()
|
self.tab4 = QWidget()
|
||||||
self.tabs.resize(300,200)
|
self.tabs.resize(300,200)
|
||||||
# Add tabs
|
# NOTE: Add tabs
|
||||||
self.tabs.addTab(self.tab1,"Submissions")
|
self.tabs.addTab(self.tab1,"Submissions")
|
||||||
self.tabs.addTab(self.tab2,"Controls")
|
self.tabs.addTab(self.tab2,"Controls")
|
||||||
self.tabs.addTab(self.tab3, "Add SubmissionType")
|
self.tabs.addTab(self.tab3, "Add SubmissionType")
|
||||||
self.tabs.addTab(self.tab4, "Add Kit")
|
self.tabs.addTab(self.tab4, "Add Kit")
|
||||||
# Create submission adder form
|
# NOTE: Create submission adder form
|
||||||
self.formwidget = SubmissionFormContainer(self)
|
self.formwidget = SubmissionFormContainer(self)
|
||||||
self.formlayout = QVBoxLayout(self)
|
self.formlayout = QVBoxLayout(self)
|
||||||
self.formwidget.setLayout(self.formlayout)
|
self.formwidget.setLayout(self.formlayout)
|
||||||
self.formwidget.setFixedWidth(300)
|
self.formwidget.setFixedWidth(300)
|
||||||
# Make scrollable interior for form
|
# NOTE: Make scrollable interior for form
|
||||||
self.interior = QScrollArea(self.tab1)
|
self.interior = QScrollArea(self.tab1)
|
||||||
self.interior.setWidgetResizable(True)
|
self.interior.setWidgetResizable(True)
|
||||||
self.interior.setFixedWidth(325)
|
self.interior.setFixedWidth(325)
|
||||||
self.interior.setWidget(self.formwidget)
|
self.interior.setWidget(self.formwidget)
|
||||||
# Create sheet to hold existing submissions
|
# NOTE: Create sheet to hold existing submissions
|
||||||
self.sheetwidget = QWidget(self)
|
self.sheetwidget = QWidget(self)
|
||||||
self.sheetlayout = QVBoxLayout(self)
|
self.sheetlayout = QVBoxLayout(self)
|
||||||
self.sheetwidget.setLayout(self.sheetlayout)
|
self.sheetwidget.setLayout(self.sheetlayout)
|
||||||
self.sub_wid = SubmissionsSheet(parent=parent)
|
self.sub_wid = SubmissionsSheet(parent=parent)
|
||||||
self.sheetlayout.addWidget(self.sub_wid)
|
self.sheetlayout.addWidget(self.sub_wid)
|
||||||
# Create layout of first tab to hold form and sheet
|
# NOTE: Create layout of first tab to hold form and sheet
|
||||||
self.tab1.layout = QHBoxLayout(self)
|
self.tab1.layout = QHBoxLayout(self)
|
||||||
self.tab1.setLayout(self.tab1.layout)
|
self.tab1.setLayout(self.tab1.layout)
|
||||||
self.tab1.layout.addWidget(self.interior)
|
self.tab1.layout.addWidget(self.interior)
|
||||||
@@ -236,7 +252,7 @@ class AddSubForm(QWidget):
|
|||||||
self.controls_viewer = ControlsViewer(self)
|
self.controls_viewer = ControlsViewer(self)
|
||||||
self.tab2.layout.addWidget(self.controls_viewer)
|
self.tab2.layout.addWidget(self.controls_viewer)
|
||||||
self.tab2.setLayout(self.tab2.layout)
|
self.tab2.setLayout(self.tab2.layout)
|
||||||
# create custom widget to add new tabs
|
# NOTE: create custom widget to add new tabs
|
||||||
ST_adder = SubmissionTypeAdder(self)
|
ST_adder = SubmissionTypeAdder(self)
|
||||||
self.tab3.layout = QVBoxLayout(self)
|
self.tab3.layout = QVBoxLayout(self)
|
||||||
self.tab3.layout.addWidget(ST_adder)
|
self.tab3.layout.addWidget(ST_adder)
|
||||||
@@ -245,6 +261,6 @@ class AddSubForm(QWidget):
|
|||||||
self.tab4.layout = QVBoxLayout(self)
|
self.tab4.layout = QVBoxLayout(self)
|
||||||
self.tab4.layout.addWidget(kit_adder)
|
self.tab4.layout.addWidget(kit_adder)
|
||||||
self.tab4.setLayout(self.tab4.layout)
|
self.tab4.setLayout(self.tab4.layout)
|
||||||
# add tabs to main widget
|
# NOTE: add tabs to main widget
|
||||||
self.layout.addWidget(self.tabs)
|
self.layout.addWidget(self.tabs)
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Gel box for artic quality control
|
|||||||
"""
|
"""
|
||||||
from PyQt6.QtWidgets import (QWidget, QDialog, QGridLayout,
|
from PyQt6.QtWidgets import (QWidget, QDialog, QGridLayout,
|
||||||
QLabel, QLineEdit, QDialogButtonBox,
|
QLabel, QLineEdit, QDialogButtonBox,
|
||||||
QTextEdit
|
QTextEdit, QComboBox
|
||||||
)
|
)
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from PyQt6.QtGui import QIcon
|
from PyQt6.QtGui import QIcon
|
||||||
@@ -119,8 +119,11 @@ class ControlsForm(QWidget):
|
|||||||
self.layout.addWidget(label, iii, 1, 1, 1)
|
self.layout.addWidget(label, iii, 1, 1, 1)
|
||||||
for iii in range(3):
|
for iii in range(3):
|
||||||
for jjj in range(3):
|
for jjj in range(3):
|
||||||
widge = QLineEdit()
|
# widge = QLineEdit()
|
||||||
widge.setText("Neg")
|
widge = QComboBox()
|
||||||
|
widge.addItems(['Neg', 'Pos'])
|
||||||
|
widge.setCurrentIndex(0)
|
||||||
|
widge.setEditable(True)
|
||||||
widge.setObjectName(f"{rows[iii]} : {columns[jjj]}")
|
widge.setObjectName(f"{rows[iii]} : {columns[jjj]}")
|
||||||
self.layout.addWidget(widge, iii+1, jjj+2, 1, 1)
|
self.layout.addWidget(widge, iii+1, jjj+2, 1, 1)
|
||||||
self.layout.addWidget(QLabel("Comments:"), 0,5,1,1)
|
self.layout.addWidget(QLabel("Comments:"), 0,5,1,1)
|
||||||
@@ -137,13 +140,13 @@ class ControlsForm(QWidget):
|
|||||||
List[dict]: output of values
|
List[dict]: output of values
|
||||||
"""
|
"""
|
||||||
output = []
|
output = []
|
||||||
for le in self.findChildren(QLineEdit):
|
for le in self.findChildren(QComboBox):
|
||||||
label = [item.strip() for item in le.objectName().split(" : ")]
|
label = [item.strip() for item in le.objectName().split(" : ")]
|
||||||
try:
|
try:
|
||||||
dicto = [item for item in output if item['name']==label[0]][0]
|
dicto = [item for item in output if item['name']==label[0]][0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
dicto = dict(name=label[0], values=[])
|
dicto = dict(name=label[0], values=[])
|
||||||
dicto['values'].append(dict(name=label[1], value=le.text()))
|
dicto['values'].append(dict(name=label[1], value=le.currentText()))
|
||||||
if label[0] not in [item['name'] for item in output]:
|
if label[0] not in [item['name'] for item in output]:
|
||||||
output.append(dicto)
|
output.append(dicto)
|
||||||
# logger.debug(pformat(output))
|
# logger.debug(pformat(output))
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from PyQt6.QtWidgets import (
|
|||||||
QLabel, QVBoxLayout, QDialog,
|
QLabel, QVBoxLayout, QDialog,
|
||||||
QDialogButtonBox, QMessageBox, QComboBox
|
QDialogButtonBox, QMessageBox, QComboBox
|
||||||
)
|
)
|
||||||
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
from tools import jinja_template_loading
|
from tools import jinja_template_loading
|
||||||
import logging
|
import logging
|
||||||
from backend.db import models
|
from backend.db import models
|
||||||
@@ -19,7 +21,7 @@ class QuestionAsker(QDialog):
|
|||||||
"""
|
"""
|
||||||
dialog to ask yes/no questions
|
dialog to ask yes/no questions
|
||||||
"""
|
"""
|
||||||
def __init__(self, title:str, message:str) -> QDialog:
|
def __init__(self, title:str, message:str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
# NOTE: set yes/no buttons
|
# NOTE: set yes/no buttons
|
||||||
@@ -39,7 +41,7 @@ class AlertPop(QMessageBox):
|
|||||||
"""
|
"""
|
||||||
Dialog to show an alert.
|
Dialog to show an alert.
|
||||||
"""
|
"""
|
||||||
def __init__(self, message:str, status:Literal['Information', 'Question', 'Warning', 'Critical'], owner:str|None=None) -> QMessageBox:
|
def __init__(self, message:str, status:Literal['Information', 'Question', 'Warning', 'Critical'], owner:str|None=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
# NOTE: select icon by string
|
# NOTE: select icon by string
|
||||||
icon = getattr(QMessageBox.Icon, status)
|
icon = getattr(QMessageBox.Icon, status)
|
||||||
@@ -47,12 +49,25 @@ class AlertPop(QMessageBox):
|
|||||||
self.setInformativeText(message)
|
self.setInformativeText(message)
|
||||||
self.setWindowTitle(f"{owner} - {status.title()}")
|
self.setWindowTitle(f"{owner} - {status.title()}")
|
||||||
|
|
||||||
|
class HTMLPop(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, html:str, owner:str|None=None, title:str="python"):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.webview = QWebEngineView(parent=self)
|
||||||
|
self.layout = QVBoxLayout()
|
||||||
|
self.setWindowTitle(title)
|
||||||
|
self.webview.setHtml(html)
|
||||||
|
self.webview.setMinimumSize(600, 500)
|
||||||
|
self.webview.setMaximumSize(600, 500)
|
||||||
|
self.layout.addWidget(self.webview)
|
||||||
|
|
||||||
|
|
||||||
class ObjectSelector(QDialog):
|
class ObjectSelector(QDialog):
|
||||||
"""
|
"""
|
||||||
dialog to input BaseClass type manually
|
dialog to input BaseClass type manually
|
||||||
"""
|
"""
|
||||||
def __init__(self, title:str, message:str, obj_type:str|models.BaseClass) -> QDialog:
|
def __init__(self, title:str, message:str, obj_type:str|models.BaseClass):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
self.widget = QComboBox()
|
self.widget = QComboBox()
|
||||||
|
|||||||
14
src/submissions/templates/project.html
Normal file
14
src/submissions/templates/project.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Version: {{ info['__version__'] }}</p>
|
||||||
|
<p>Author: {{ info['__author__']['name'] }} - <a href="mailto:{{ info['__author__']['email' ] }}">{{ info['__author__']['email'] }}</a></p>
|
||||||
|
<p>Copyright: {{ info['__copyright__'] }}</p>
|
||||||
|
<p>Github: <a id="link" href="{{ info['__github__'] }}">{{ info['__github__'] }}</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user