Upgrade of WastewaterArtic parsers/writers for flexibility.

This commit is contained in:
lwark
2024-07-09 13:09:27 -05:00
parent e589420815
commit 54e1e55804
11 changed files with 122 additions and 56 deletions

View File

@@ -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.

View File

@@ -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'.

View File

@@ -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()

View File

@@ -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:

View File

@@ -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

View File

@@ -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):

View File

@@ -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):

View File

@@ -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)

View File

@@ -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))

View File

@@ -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()

View 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>