Ui interface updates.
This commit is contained in:
@@ -13,6 +13,7 @@ from sqlalchemy import Column, INTEGER, String, JSON
|
||||
from sqlalchemy.ext.associationproxy import AssociationProxy
|
||||
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session, InstrumentedAttribute, ColumnProperty
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.exc import ArgumentError
|
||||
from typing import Any, List, ClassVar
|
||||
from pathlib import Path
|
||||
@@ -237,10 +238,10 @@ class BaseClass(Base):
|
||||
@classmethod
|
||||
def query_or_create(cls, **kwargs) -> Tuple[Any, bool]:
|
||||
new = False
|
||||
allowed = [k for k, v in cls.__dict__.items() if isinstance(v, InstrumentedAttribute)]
|
||||
allowed = [k for k, v in cls.__dict__.items() if isinstance(v, InstrumentedAttribute) or isinstance(v, hybrid_property)]
|
||||
# and not isinstance(v.property, _RelationshipDeclared)]
|
||||
sanitized_kwargs = {k: v for k, v in kwargs.items() if k in allowed}
|
||||
# logger.debug(f"Sanitized kwargs: {sanitized_kwargs}")
|
||||
logger.debug(f"Sanitized kwargs: {sanitized_kwargs}")
|
||||
instance = cls.query(**sanitized_kwargs)
|
||||
if not instance or isinstance(instance, list):
|
||||
instance = cls()
|
||||
@@ -273,7 +274,7 @@ class BaseClass(Base):
|
||||
return cls.execute_query(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def execute_query(cls, query: Query = None, model=None, limit: int = 0, **kwargs) -> Any | List[Any]:
|
||||
def execute_query(cls, query: Query = None, model=None, limit: int = 0, offset:int|None=None, **kwargs) -> Any | List[Any]:
|
||||
"""
|
||||
Execute sqlalchemy query with relevant defaults.
|
||||
|
||||
@@ -291,22 +292,32 @@ class BaseClass(Base):
|
||||
# logger.debug(f"Model: {model}")
|
||||
if query is None:
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
else:
|
||||
logger.debug(f"Incoming query: {query}")
|
||||
singles = cls.get_default_info('singles')
|
||||
for k, v in kwargs.items():
|
||||
|
||||
logger.info(f"Using key: {k} with value: {v}")
|
||||
logger.info(f"Using key: {k} with value: {v} against {cls}")
|
||||
try:
|
||||
attr = getattr(cls, k)
|
||||
except (ArgumentError, AttributeError) as e:
|
||||
logger.error(f"Attribute {k} unavailable due to:\n\t{e}\n.")
|
||||
continue
|
||||
# NOTE: account for attrs that use list.
|
||||
if attr.property.uselist:
|
||||
try:
|
||||
check = attr.property.uselist
|
||||
except AttributeError:
|
||||
check = False
|
||||
if check:
|
||||
logger.debug("Got uselist")
|
||||
query = query.filter(attr.contains(v))
|
||||
else:
|
||||
logger.debug("Single item.")
|
||||
query = query.filter(attr == v)
|
||||
except (ArgumentError, AttributeError) as e:
|
||||
logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.")
|
||||
if k in singles:
|
||||
logger.warning(f"{k} is in singles. Returning only one value.")
|
||||
limit = 1
|
||||
if offset:
|
||||
query.offset(offset)
|
||||
with query.session.no_autoflush:
|
||||
match limit:
|
||||
case 0:
|
||||
@@ -476,13 +487,13 @@ class BaseClass(Base):
|
||||
# logger.debug(f"Attempting to set: {key} to {value}")
|
||||
if key.startswith("_"):
|
||||
return super().__setattr__(key, value)
|
||||
try:
|
||||
# try:
|
||||
check = not hasattr(self, key)
|
||||
except:
|
||||
return
|
||||
# except:
|
||||
# return
|
||||
if check:
|
||||
try:
|
||||
json.dumps(value)
|
||||
value = json.dumps(value)
|
||||
except TypeError:
|
||||
value = str(value)
|
||||
self._misc_info.update({key: value})
|
||||
@@ -612,6 +623,7 @@ class BaseClass(Base):
|
||||
if dlg.exec():
|
||||
pass
|
||||
|
||||
|
||||
class LogMixin(Base):
|
||||
tracking_exclusion: ClassVar = ['artic_technician', 'clientsubmissionsampleassociation',
|
||||
'submission_reagent_associations', 'submission_equipment_associations',
|
||||
|
||||
@@ -149,12 +149,14 @@ class ClientSubmission(BaseClass, LogMixin):
|
||||
pass
|
||||
# query = query.order_by(cls.submitted_date.desc())
|
||||
# NOTE: Split query results into pages of size {page_size}
|
||||
if page_size > 0:
|
||||
query = query.limit(page_size)
|
||||
if page_size > 0 and limit == 0:
|
||||
limit = page_size
|
||||
page = page - 1
|
||||
if page is not None:
|
||||
query = query.offset(page * page_size)
|
||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||
offset = page * page_size
|
||||
else:
|
||||
offset = None
|
||||
return cls.execute_query(query=query, limit=limit, offset=offset, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def submissions_to_df(cls, submissiontype: str | None = None, limit: int = 0,
|
||||
@@ -269,7 +271,9 @@ class ClientSubmission(BaseClass, LogMixin):
|
||||
try:
|
||||
assert isinstance(sample, Sample)
|
||||
except AssertionError:
|
||||
logger.warning(f"Converting {sample} to sql.")
|
||||
sample = sample.to_sql()
|
||||
logger.debug(sample.__dict__)
|
||||
try:
|
||||
row = sample._misc_info['row']
|
||||
except (KeyError, AttributeError):
|
||||
@@ -278,10 +282,12 @@ class ClientSubmission(BaseClass, LogMixin):
|
||||
column = sample._misc_info['column']
|
||||
except KeyError:
|
||||
column = 0
|
||||
logger.debug(f"Sample: {sample}")
|
||||
submission_rank = sample._misc_info['submission_rank']
|
||||
assoc = ClientSubmissionSampleAssociation(
|
||||
sample=sample,
|
||||
submission=self,
|
||||
submission_rank=sample._misc_info['submission_rank'],
|
||||
submission_rank=submission_rank,
|
||||
row=row,
|
||||
column=column
|
||||
)
|
||||
@@ -310,6 +316,7 @@ class ClientSubmission(BaseClass, LogMixin):
|
||||
for sample in active_samples:
|
||||
sample = sample.to_sql()
|
||||
logger.debug(f"Sample: {sample.id}")
|
||||
if sample not in run.sample:
|
||||
assoc = run.add_sample(sample)
|
||||
assoc.save()
|
||||
else:
|
||||
|
||||
@@ -43,9 +43,10 @@ class DefaultParser(object):
|
||||
"""
|
||||
self.proceduretype = proceduretype
|
||||
try:
|
||||
self._pyd_object = getattr(pydant, f"Pyd{self.__class__.__name__.replace('Parser', '')}")
|
||||
except AttributeError:
|
||||
self._pyd_object = pydant.PydResults
|
||||
self._pyd_object = getattr(pydant, f"Pyd{self.__class__.__name__.replace('Parser', '').replace('Info', '')}")
|
||||
except AttributeError as e:
|
||||
logger.error(f"Couldn't get pyd object: Pyd{self.__class__.__name__.replace('Parser', '').replace('Info', '')}")
|
||||
self._pyd_object = getattr(pydant, self.__class__.pyd_name)
|
||||
self.workbook = load_workbook(self.filepath, data_only=True)
|
||||
if not range_dict:
|
||||
self.range_dict = self.__class__.default_range_dict
|
||||
@@ -118,7 +119,7 @@ class DefaultTABLEParser(DefaultParser):
|
||||
if isinstance(key, str):
|
||||
key = key.lower().replace(" ", "_")
|
||||
key = re.sub(r"_(\(.*\)|#)", "", key)
|
||||
logger.debug(f"Row {ii} values: {key}: {value}")
|
||||
# logger.debug(f"Row {ii} values: {key}: {value}")
|
||||
output[key] = value
|
||||
yield output
|
||||
|
||||
@@ -126,4 +127,4 @@ class DefaultTABLEParser(DefaultParser):
|
||||
return [self._pyd_object(**output) for output in self.parsed_info]
|
||||
|
||||
from .clientsubmission_parser import *
|
||||
from backend.excel.parsers.results_parsers.pcr_results_parser import *
|
||||
from backend.excel.parsers.results_parsers.pcr_results_parser import PCRInfoParser, PCRSampleParser
|
||||
|
||||
@@ -74,6 +74,8 @@ class ClientSubmissionInfoParser(DefaultKEYVALUEParser, SubmissionTyperMixin):
|
||||
Object for retrieving submitter info from "sample list" sheet
|
||||
"""
|
||||
|
||||
pyd_name = "PydClientSubmission"
|
||||
|
||||
default_range_dict = [dict(
|
||||
start_row=2,
|
||||
end_row=18,
|
||||
@@ -110,6 +112,8 @@ class ClientSubmissionSampleParser(DefaultTABLEParser, SubmissionTyperMixin):
|
||||
Object for retrieving submitter samples from "sample list" sheet
|
||||
"""
|
||||
|
||||
pyd_name = "PydSample"
|
||||
|
||||
default_range_dict = [dict(
|
||||
header_row=19,
|
||||
end_row=115,
|
||||
@@ -126,7 +130,7 @@ class ClientSubmissionSampleParser(DefaultTABLEParser, SubmissionTyperMixin):
|
||||
def parsed_info(self) -> Generator[dict, None, None]:
|
||||
output = super().parsed_info
|
||||
for ii, sample in enumerate(output):
|
||||
logger.debug(f"Parsed info sample: {sample}")
|
||||
# logger.debug(f"Parsed info sample: {sample}")
|
||||
if isinstance(sample["row"], str) and sample["row"].lower() in ascii_lowercase[0:8]:
|
||||
try:
|
||||
sample["row"] = row_keys[sample["row"]]
|
||||
|
||||
@@ -290,6 +290,16 @@ class PydSample(PydBaseClass):
|
||||
value = row_keys[value]
|
||||
return value
|
||||
|
||||
def improved_dict(self, dictionaries: bool = True) -> dict:
|
||||
output = super().improved_dict(dictionaries=dictionaries)
|
||||
output['name'] = self.sample_id
|
||||
del output['sampletype']
|
||||
return output
|
||||
|
||||
def to_sql(self):
|
||||
sql = super().to_sql()
|
||||
sql._misc_info["submission_rank"] = self.submission_rank
|
||||
return sql
|
||||
|
||||
class PydTips(BaseModel):
|
||||
name: str
|
||||
|
||||
@@ -90,7 +90,7 @@ class ProcedureCreation(QDialog):
|
||||
plate_map=self.plate_map,
|
||||
edit=self.edit
|
||||
)
|
||||
with open("web.html", "w") as f:
|
||||
with open("procedure_creation_rendered.html", "w") as f:
|
||||
f.write(html)
|
||||
self.webview.setHtml(html)
|
||||
|
||||
|
||||
@@ -5,11 +5,9 @@ from PyQt6.QtCore import Qt, pyqtSlot
|
||||
from PyQt6.QtWebChannel import QWebChannel
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QGridLayout
|
||||
|
||||
from backend.db.models import ClientSubmission
|
||||
from backend.validators import PydSample, RSLNamer
|
||||
from tools import get_application_from_parent, jinja_template_loading
|
||||
|
||||
from tools import get_application_from_parent, jinja_template_loading, render_details_template
|
||||
|
||||
env = jinja_template_loading()
|
||||
|
||||
@@ -24,6 +22,7 @@ class SampleChecker(QDialog):
|
||||
self.rsl_plate_number = RSLNamer.construct_new_plate_name(clientsubmission.to_dict())
|
||||
else:
|
||||
self.rsl_plate_number = clientsubmission
|
||||
logger.debug(f"RSL Plate number: {self.rsl_plate_number}")
|
||||
self.samples = samples
|
||||
self.setWindowTitle(title)
|
||||
self.app = get_application_from_parent(parent)
|
||||
@@ -38,22 +37,27 @@ class SampleChecker(QDialog):
|
||||
# NOTE: Used to maintain javascript functions.
|
||||
template = env.get_template("sample_checker.html")
|
||||
template_path = Path(template.environment.loader.__getattribute__("searchpath")[0])
|
||||
with open(template_path.joinpath("css", "styles.css"), "r") as f:
|
||||
css = f.read()
|
||||
# with open(template_path.joinpath("css", "styles.css"), "r") as f:
|
||||
# css = [f.read()]
|
||||
try:
|
||||
samples = self.formatted_list
|
||||
except AttributeError as e:
|
||||
logger.error(f"Problem getting sample list: {e}")
|
||||
samples = []
|
||||
html = template.render(samples=samples, css=css, rsl_plate_number=self.rsl_plate_number)
|
||||
# html = template.render(samples=samples, css=css, rsl_plate_number=self.rsl_plate_number)
|
||||
html = render_details_template(template_name="sample_checker", samples=samples, rsl_plate_number=self.rsl_plate_number)
|
||||
self.webview.setHtml(html)
|
||||
self.webview.page().setWebChannel(self.channel)
|
||||
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.buttonBox, 11, 9, 1, 1, alignment=Qt.AlignmentFlag.AlignRight)
|
||||
self.setLayout(self.layout)
|
||||
self.webview.page().setWebChannel(self.channel)
|
||||
|
||||
with open("sample_checker_rendered.html", "w") as f:
|
||||
f.write(html)
|
||||
logger.debug(f"HTML sample checker written!")
|
||||
|
||||
@pyqtSlot(str, str, str)
|
||||
def text_changed(self, submission_rank: str, key: str, new_value: str):
|
||||
@@ -65,8 +69,8 @@ class SampleChecker(QDialog):
|
||||
return
|
||||
item.__setattr__(key, new_value)
|
||||
|
||||
@pyqtSlot(str, bool)
|
||||
def enable_sample(self, submission_rank: str, enabled: bool):
|
||||
@pyqtSlot(int, bool)
|
||||
def enable_sample(self, submission_rank: int, enabled: bool):
|
||||
logger.debug(f"Name: {submission_rank}, Enabled: {enabled}")
|
||||
try:
|
||||
item = next((sample for sample in self.samples if int(submission_rank) == sample.submission_rank))
|
||||
|
||||
@@ -325,8 +325,9 @@ class SubmissionsTree(QTreeView):
|
||||
"""
|
||||
indexes = self.selectedIndexes()
|
||||
dicto = next((item.data(1) for item in indexes if item.data(1)))
|
||||
logger.debug(f"Dicto: {pformat(dicto)}")
|
||||
query_obj = dicto['item_type'].query(name=dicto['query_str'], limit=1)
|
||||
logger.debug(query_obj)
|
||||
logger.debug(f"Querying: {query_obj}")
|
||||
# NOTE: Convert to data in id column (i.e. column 0)
|
||||
# id = id.sibling(id.row(), 0).data()
|
||||
# logger.debug(id.model().query_group_object(id.row()))
|
||||
|
||||
@@ -141,6 +141,11 @@ class SubmissionFormContainer(QWidget):
|
||||
checker = SampleChecker(self, "Sample Checker", self.pydsamples)
|
||||
if checker.exec():
|
||||
# logger.debug(pformat(self.pydclientsubmission.sample))
|
||||
try:
|
||||
assert isinstance(self.pydclientsubmission, PydClientSubmission)
|
||||
except AssertionError as e:
|
||||
logger.error(f"Got wrong type for {self.pydclientsubmission}: {type(self.pydclientsubmission)}")
|
||||
raise e
|
||||
self.form = self.pydclientsubmission.to_form(parent=self)
|
||||
self.form.samples = self.pydsamples
|
||||
self.layout().addWidget(self.form)
|
||||
|
||||
@@ -24,23 +24,23 @@
|
||||
|
||||
{% block script %}
|
||||
{% if not child %}
|
||||
<script>
|
||||
var coll = document.getElementsByClassName("collapsible");
|
||||
var i;
|
||||
<!--<script>-->
|
||||
<!--var coll = document.getElementsByClassName("collapsible");-->
|
||||
<!--var i;-->
|
||||
|
||||
for (i = 0; i < coll.length; i++) {
|
||||
coll[i].addEventListener("click", function() {
|
||||
this.classList.toggle("active");
|
||||
var content = this.nextElementSibling;
|
||||
if (content.style.display === "block") {
|
||||
content.style.display = "none";
|
||||
} else {
|
||||
content.style.display = "block";
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
<!--for (i = 0; i < coll.length; i++) {-->
|
||||
<!-- coll[i].addEventListener("click", function() {-->
|
||||
<!-- this.classList.toggle("active");-->
|
||||
<!-- var content = this.nextElementSibling;-->
|
||||
<!-- if (content.style.display === "block") {-->
|
||||
<!-- content.style.display = "none";-->
|
||||
<!-- } else {-->
|
||||
<!-- content.style.display = "block";-->
|
||||
<!-- }-->
|
||||
<!-- });-->
|
||||
<!--}-->
|
||||
<!--</script>-->
|
||||
<!--{% endif %}-->
|
||||
{% for j in js%}
|
||||
|
||||
<script>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "details.html" %}
|
||||
<html>
|
||||
<head>
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
@@ -9,10 +10,9 @@
|
||||
{% block body %}
|
||||
<h2><u>Sample Checker</u></h2>
|
||||
<br>
|
||||
{% if rsl_plate_num %}
|
||||
{% if rsl_plate_number %}
|
||||
<label for="rsl_plate_number">RSL Plate Number:</label><br>
|
||||
<input type="text" id="rsl_plate_number" name="sample_id" value="{{ rsl_plate_number }}" size="40">
|
||||
|
||||
{% endif %}
|
||||
<br>
|
||||
<p>Take a moment to verify sample names.</p>
|
||||
@@ -20,11 +20,9 @@
|
||||
<form>
|
||||
  Submitter ID<br/><!--              Row           Column<br/>-->
|
||||
{% for sample in samples %}
|
||||
{% if rsl_plate_num %}<input type="checkbox" id="{{ sample['submission_rank'] }}_enabled" name="vehicle1" value="Bike" {% if sample['enabled'] %}checked{% endif %}>{% endif %}
|
||||
{% if rsl_plate_number %}<input type="checkbox" id="{{ sample['submission_rank'] }}_enabled" name="vehicle1" value="Bike" {% if sample['enabled'] %}checked{% endif %}>{% endif %}
|
||||
{{ '%02d' % sample['submission_rank'] }}
|
||||
<input type="text" id="{{ sample['submission_rank'] }}_id" name="sample_id" value="{{ sample['sample_id'] }}" size="40" style="color:{{ sample['color'] }};" {% if rsl_plate_num %}disabled{% endif %}>
|
||||
<!-- <input type="number" id="{{ sample['submission_rank'] }}_row" name="row" value="{{ sample['row'] }}" size="5", min="1">-->
|
||||
<!-- <input type="number" id="{{ sample['submission_rank'] }}_col" name="column" value="{{ sample['column'] }}" size="5", min="1">-->
|
||||
<input type="text" id="{{ sample['submission_rank'] }}_id" name="sample_id" value="{{ sample['sample_id'] }}" size="40" style="color:{{ sample['color'] }};" {% if rsl_plate_number %}disabled{% endif %}>
|
||||
<br/>
|
||||
{% endfor %}
|
||||
</form>
|
||||
@@ -32,23 +30,31 @@
|
||||
</body>
|
||||
|
||||
{% block script %}
|
||||
{{ super() }}
|
||||
|
||||
<script>
|
||||
{% for sample in samples %}
|
||||
document.getElementById("{{ sample['submission_rank'] }}_id").addEventListener("input", function(){
|
||||
backend.text_changed("{{ sample['submission_rank'] }}", this.name, this.value);
|
||||
});
|
||||
{% if rsl_plate_num %}
|
||||
document.getElementById("{{ sample['submission_rank'] }}_enabled").addEventListener("input", function(){
|
||||
backend.enable_sample("{{ sample['submission_rank'] }}", this.checked);
|
||||
{% if rsl_plate_number %}
|
||||
document.getElementById("{{ sample['submission_rank'] }}_enabled").addEventListener("change", function(){
|
||||
console.log(typeof({{ sample['submission_rank'] }}) + " " + typeof(this.checked));
|
||||
backend.enable_sample({{ sample['submission_rank'] }}, this.checked);
|
||||
});
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById("rsl_plate_number").addEventListener("input", function(){
|
||||
console.log(this.value);
|
||||
console.log(typeof(this.value));
|
||||
backend.set_rsl_plate_number(this.value);
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
backend.activate_export(false);
|
||||
}, false);
|
||||
document.getElementById("rsl_plate_num").addEventListener("input", function(){
|
||||
backend.set_rsl_plate_num(this.value);
|
||||
});
|
||||
</script>
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user