From 3381675c413a16453c162b1c4060ddd9f1495f14 Mon Sep 17 00:00:00 2001 From: KeironO Date: Mon, 27 Apr 2020 01:54:57 +0100 Subject: [PATCH 01/10] [I] Added pronto stuff to Numeric Custom Attribute addition --- limbus/app/__init__.py | 7 +- limbus/app/attribute/__init__.py | 6 + limbus/app/attribute/api.py | 0 limbus/app/attribute/enums.py | 14 ++ limbus/app/attribute/forms.py | 80 +++++++++++ limbus/app/attribute/models.py | 90 ++++++++++++ limbus/app/attribute/routes.py | 31 +++++ limbus/app/attribute/views.py | 0 limbus/app/dynform/__init__.py | 67 --------- limbus/app/patientconsentform/models.py | 1 + limbus/app/sample/enums.py | 5 +- limbus/app/sample/forms.py | 32 +---- limbus/app/sample/models/__init__.py | 1 - limbus/app/sample/models/attributes.py | 118 ---------------- limbus/app/sample/models/consent.py | 10 ++ limbus/app/sample/routes/__init__.py | 1 - limbus/app/sample/routes/add.py | 4 - limbus/app/sample/routes/attributes.py | 131 ------------------ limbus/app/sample/views/attributes.py | 59 -------- limbus/app/sample/views/sample.py | 1 - .../app/templates/attribute/add/numeric.html | 20 +++ limbus/app/templates/attribute/add/one.html | 22 +++ limbus/app/templates/attribute/index.html | 24 ++++ limbus/app/templates/sample/index.html | 2 +- limbus/app/templates/template.html | 2 - 25 files changed, 304 insertions(+), 424 deletions(-) create mode 100644 limbus/app/attribute/__init__.py create mode 100644 limbus/app/attribute/api.py create mode 100644 limbus/app/attribute/enums.py create mode 100644 limbus/app/attribute/forms.py create mode 100644 limbus/app/attribute/models.py create mode 100644 limbus/app/attribute/routes.py create mode 100644 limbus/app/attribute/views.py delete mode 100644 limbus/app/dynform/__init__.py delete mode 100644 limbus/app/sample/models/attributes.py delete mode 100644 limbus/app/sample/routes/attributes.py delete mode 100644 limbus/app/sample/views/attributes.py create mode 100644 limbus/app/templates/attribute/add/numeric.html create mode 100644 limbus/app/templates/attribute/add/one.html create mode 100644 limbus/app/templates/attribute/index.html diff --git a/limbus/app/__init__.py b/limbus/app/__init__.py index a4f5ba802..dc2015ad6 100644 --- a/limbus/app/__init__.py +++ b/limbus/app/__init__.py @@ -16,6 +16,7 @@ from .misc import misc as misc_blueprint from .setup import setup as setup_blueprint from .auth import auth as auth_blueprint +from .attribute import attribute as attribute_blueprint from .document import document as doc_blueprint from .sample import sample as sample_blueprint from .donor import donor as donor_blueprint @@ -24,8 +25,6 @@ from .processing import processing as processing_blueprint from .storage import storage as storage_blueprint -from .demo import demo as demo_blueprint - def create_app(): app = Flask(__name__, instance_relative_config=True) @@ -52,10 +51,12 @@ def create_app(): from app.patientconsentform import models as pcf_models from app.processing import models as processing_models from app.storage import models as storage_models + from app.attribute import models as attribute_models app.register_blueprint(misc_blueprint) app.register_blueprint(setup_blueprint, url_prefix="/setup") app.register_blueprint(auth_blueprint) + app.register_blueprint(attribute_blueprint, url_prefix="/attributes") app.register_blueprint(processing_blueprint, url_prefix="/processing") app.register_blueprint(doc_blueprint, url_prefix="/documents") app.register_blueprint(sample_blueprint, url_prefix="/samples") @@ -64,8 +65,6 @@ def create_app(): app.register_blueprint(pcf_blueprint, url_prefix="/pcf") app.register_blueprint(storage_blueprint, url_prefix="/storage") - app.register_blueprint(demo_blueprint, url_prefix="/demo") - from app.admin import add_admin_views add_admin_views() diff --git a/limbus/app/attribute/__init__.py b/limbus/app/attribute/__init__.py new file mode 100644 index 000000000..6b7b01418 --- /dev/null +++ b/limbus/app/attribute/__init__.py @@ -0,0 +1,6 @@ +from flask import Blueprint + +attribute = Blueprint("attribute", __name__) + +from .routes import * +from .api import * \ No newline at end of file diff --git a/limbus/app/attribute/api.py b/limbus/app/attribute/api.py new file mode 100644 index 000000000..e69de29bb diff --git a/limbus/app/attribute/enums.py b/limbus/app/attribute/enums.py new file mode 100644 index 000000000..a542cd0ed --- /dev/null +++ b/limbus/app/attribute/enums.py @@ -0,0 +1,14 @@ +from ..FormEnum import FormEnum + +class CustomAttributeTypes(FormEnum): + TEXT = "Text" + NUMERIC = "Numeric" + OPTION = "Option" + +class CustomAttributeElementTypes(FormEnum): + ALL = "All" + SAMPLE = "Sample" + PROTOCOL = "Protocol" + TRANSFER = "Transfer" + DONOR = "Donor" + STORAGE = "Storage" \ No newline at end of file diff --git a/limbus/app/attribute/forms.py b/limbus/app/attribute/forms.py new file mode 100644 index 000000000..c48f4a8c2 --- /dev/null +++ b/limbus/app/attribute/forms.py @@ -0,0 +1,80 @@ +from flask_wtf import FlaskForm +from wtforms import ( + SelectField, + StringField, + SubmitField, + DateField, + BooleanField, + IntegerField, +) + +from wtforms.validators import DataRequired + +from .enums import * + +# Pronto stuff here. +import pronto + +# Loading UO into memory upon creation of flask instance. +uo = pronto.Ontology.from_obo_library("uo.obo") + +def create_uo_params(): + def _get_leafs(node): + leafs = [] + def _traverse(n): + n = [x for x in n.subclasses()] + if len(n[1:]) > 1: + for _n in n[1:]: + _traverse(_n) + elif len(n) == 1: + leafs.extend(n) + else: + pass + + _traverse(node) + return list(set(leafs)) + + + # Loading UO into memory upon creation of flask instance. + uo_ontology = pronto.Ontology.from_obo_library("uo.obo") + units = _get_leafs(uo_ontology["UO:0000000"]) + prefixs = _get_leafs(uo_ontology["UO:0000046"]) + + return units, prefixs + +units, prefixs = create_uo_params() + + +class EnumFromOntology: + def __init__(self, ontology_list): + self.ontology_list = ontology_list + + def choices(self): + return [(term.id, term.name) for term in self.ontology_list] + +class CustomAttributeCreationForm(FlaskForm): + term = StringField("Attribute Term", validators=[DataRequired()]) + description = StringField("Attribute Description") + accession = StringField("Ontology Accession") + ref = StringField("Ontology Reference") + type = SelectField("Attribute Type", choices=CustomAttributeTypes.choices()) + element = SelectField("Element", choices=CustomAttributeElementTypes.choices()) + required = BooleanField("Required") + submit = SubmitField("Submit") + +class CustomTextAttributeCreationForm(FlaskForm): + max_length = IntegerField("Max Length", validators=[DataRequired()]) + submit = SubmitField("Submit") + +class CustomNumericAttributionCreationForm(FlaskForm): + measurement = SelectField("Measurement", + choices=EnumFromOntology(units).choices() + ) + + prefix = SelectField("Prefix", choices=EnumFromOntology(prefixs).choices()) + + + submit = SubmitField("Submit") + +class CustomOptionAttributionCreationForm(FlaskForm): + pass diff --git a/limbus/app/attribute/models.py b/limbus/app/attribute/models.py new file mode 100644 index 000000000..2b82eea5f --- /dev/null +++ b/limbus/app/attribute/models.py @@ -0,0 +1,90 @@ +from .enums import CustomAttributeTypes +from app import db + +class CustomAttributes(db.Model): + __tablename__ = "custom_attributes" + + id = db.Column(db.Integer, primary_key=True) + description = db.Column(db.String(1024)) + + term = db.Column(db.String(128)) + accession = db.Column(db.String(64)) + ref = db.Column(db.String(64)) + + required = db.Column(db.Boolean(), default=False) + + type = db.Column(db.Enum(CustomAttributeTypes)) + + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) + creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) + update_date = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now(), nullable=False) + + + +class CustomAttributeTextSetting(db.Model): + __tablename__ = "custom_attribute_text_settings" + + id = db.Column(db.Integer, primary_key=True) + max_length = db.Column(db.Integer, nullable=False) + + custom_attribute_id = db.Column(db.Integer, db.ForeignKey("custom_attributes.id")) + + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) + creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) + update_date = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now(), nullable=False) + + +class CustomAttributeNumericSetting(db.Model): + __tablename__ = "sample_attribute_numeric_settings" + + id = db.Column(db.Integer, primary_key=True) + + custom_attribute_id = db.Column(db.Integer, db.ForeignKey("custom_attributes.id")) + + creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) + update_date = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now(), nullable=False) + + +class CustomAttributeOption(db.Model): + __tablename__ = "custom_attribute_options" + + id = db.Column(db.Integer, primary_key=True) + + term = db.Column(db.String(128)) + accession = db.Column(db.String(64)) + ref = db.Column(db.String(64)) + + custom_attribute_id = db.Column(db.Integer, db.ForeignKey("sample_attributes.id")) + + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) + creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) + + update_date = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now(), nullable=False) + + + +class SampleAttributeTextValue(db.Model): + __tablename__ = "custom_attribute_text_values" + + id = db.Column(db.Integer, primary_key=True) + + value = db.Column(db.String) + custom_attribute_id = db.Column(db.Integer, db.ForeignKey("custom_attributes.id")) + + sample_id = db.Column(db.Integer, db.ForeignKey("samples.id")) + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) + + creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) + update_date = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now(), nullable=False) + +class CustomAttributeOptionValue(db.Model): + __tablename__ = "custom_attribute_option_values" + id = db.Column(db.Integer, primary_key=True) + + sample_option_id = db.Column(db.Integer, db.ForeignKey("custom_attribute_options.id")) + + custom_attribute_id = db.Column(db.Integer, db.ForeignKey("custom_attributes.id")) + sample_id = db.Column(db.Integer, db.ForeignKey("samples.id")) + + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) + creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) \ No newline at end of file diff --git a/limbus/app/attribute/routes.py b/limbus/app/attribute/routes.py new file mode 100644 index 000000000..6ca9d5b94 --- /dev/null +++ b/limbus/app/attribute/routes.py @@ -0,0 +1,31 @@ +from ..attribute import attribute + +from flask import render_template + +from .forms import CustomAttributeCreationForm, CustomNumericAttributionCreationForm + +@attribute.route("/") +def index(): + return render_template("attribute/index.html") + +@attribute.route("/add") +def add_one(): + form = CustomAttributeCreationForm() + + if form.validate_on_submit(): + pass + + return render_template("attribute/add/one.html", form=form) + +@attribute.route("/add/numeric/") +def add_numeric(hash): + form = CustomNumericAttributionCreationForm() + return render_template("attribute/add/numeric.html", form=form) + +@attribute.route("/add/option/") +def add_option(hash): + pass + +@attribute.route("/add/textual/") +def add_textual(hash): + pass \ No newline at end of file diff --git a/limbus/app/attribute/views.py b/limbus/app/attribute/views.py new file mode 100644 index 000000000..e69de29bb diff --git a/limbus/app/dynform/__init__.py b/limbus/app/dynform/__init__.py deleted file mode 100644 index cc5b9e076..000000000 --- a/limbus/app/dynform/__init__.py +++ /dev/null @@ -1,67 +0,0 @@ -# This is where all of the dynamic form generation stuff can be found. - -from ..sample.enums import SampleAttributeTypes -from ..sample.models import SampleAttributeOption -from wtforms import ( - SelectField, - StringField, - SubmitField, - DateField, - BooleanField, - TextAreaField, - TextField, -) -from flask import session - -from .. import db - -import inflect - -p = inflect.engine() - - -class DynamicAttributeFormGenerator: - def __init__(self, query, form): - self._query = query - self._form = form - - def _iterate_query(self): - for attr in self._query: - if attr.type == SampleAttributeTypes.TEXT: - setattr( - self._form, p.number_to_words(attr.id), TextAreaField(attr.term) - ) - elif attr.type == SampleAttributeTypes.OPTION: - options = ( - db.session.query(SampleAttributeOption) - .filter(SampleAttributeOption.sample_attribute_id == attr.id) - .all() - ) - setattr( - self._form, - p.number_to_words(attr.id), - SelectField(attr.term, coerce=int, choices=[(x.id, x.term) for x in options]), - ) - - def _inject_submit(self): - setattr(self._form, "submit", SubmitField()) - - def make_form(self): - self._iterate_query() - self._inject_submit() - return self._form() - - -class DynamicSelectFormGenerator: - def __init(self, query, form): - pass - - def _inject_submit(self): - setattr(self._form, "submit", SubmitField()) - - -def clear_session(hash: str) -> None: - # Clear cookie session. - for k, v in list(session.items()): - if k.startswith(hash): - del session[k] diff --git a/limbus/app/patientconsentform/models.py b/limbus/app/patientconsentform/models.py index 1b1655704..e0a4169cb 100644 --- a/limbus/app/patientconsentform/models.py +++ b/limbus/app/patientconsentform/models.py @@ -35,3 +35,4 @@ class ConsentFormTemplateQuestion(db.Model): nullable=False, ) uploader = db.Column(db.Integer, db.ForeignKey("users.id")) + diff --git a/limbus/app/sample/enums.py b/limbus/app/sample/enums.py index 52e2064fd..c5c3ebc6b 100644 --- a/limbus/app/sample/enums.py +++ b/limbus/app/sample/enums.py @@ -88,7 +88,4 @@ class SampleStatus(FormEnum): MIS = "Missing" NPR = "Not Processed" -class SampleAttributeTypes(FormEnum): - OPTION = "Option" - TEXT = "Text" - NUMERIC = "Numeric" + diff --git a/limbus/app/sample/forms.py b/limbus/app/sample/forms.py index b72dece13..2609a9d23 100644 --- a/limbus/app/sample/forms.py +++ b/limbus/app/sample/forms.py @@ -52,36 +52,6 @@ class SampleCreationForm(FlaskForm): disposal_date = DateField("Disposal Date") -class SampleAttributeCreationForm(FlaskForm): - term = StringField("Attribute Term", validators=[DataRequired()]) - term_type = SelectField( - "Attribute Type", - validators=[DataRequired()], - choices=[(x.name, x.value) for x in SampleAttributeTypes], - ) - required = BooleanField("Required") - submit = SubmitField("Submit") - - -class SampleAttributionCreationFormText(FlaskForm): - max_length = StringField( - "Maximum Length", - validators=[DataRequired()], - description="The maximum length of characters that a user can enter", - default="1024", - ) - submit = SubmitField("Submit") - - -class SampleAttributeCreationFormNumeric(FlaskForm): - type = SelectField( - "Unit of Measurement", - validators=[DataRequired()], - choices=[(x.name, x.value) for x in UnitsOfMeasurement], - ) - submit = SubmitField("Submit") - - def DynamicAttributeSelectForm(query, attr): class StaticForm(FlaskForm): pass @@ -93,7 +63,7 @@ class StaticForm(FlaskForm): setattr(bool, "_required", attribute.required) # Sett additional attrs. - setattr(StaticForm, p.number_to_words(attribute.id), bool) + setattr(StaticForm, attribute.id, bool) setattr(StaticForm, "submit", SubmitField()) return StaticForm() diff --git a/limbus/app/sample/models/__init__.py b/limbus/app/sample/models/__init__.py index 821bc1a78..832efe0ee 100644 --- a/limbus/app/sample/models/__init__.py +++ b/limbus/app/sample/models/__init__.py @@ -1,4 +1,3 @@ -from .attributes import * from .base import * from .processing import * from .types import * diff --git a/limbus/app/sample/models/attributes.py b/limbus/app/sample/models/attributes.py deleted file mode 100644 index ebfb9e4b4..000000000 --- a/limbus/app/sample/models/attributes.py +++ /dev/null @@ -1,118 +0,0 @@ -from app import db -from ..enums import * - -class SampleAttribute(db.Model): - __tablename__ = "sample_attributes" - - id = db.Column(db.Integer, primary_key=True) - term = db.Column(db.String(128)) - accession = db.Column(db.String(64)) - ref = db.Column(db.String(64)) - type = db.Column(db.Enum(SampleAttributeTypes)) - - author_id = db.Column(db.Integer, db.ForeignKey("users.id")) - creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) - update_date = db.Column( - db.DateTime, - server_default=db.func.now(), - server_onupdate=db.func.now(), - nullable=False, - ) - required = db.Column(db.Boolean(), default=False) - - -class SampleAttributeTextSetting(db.Model): - __tablename__ = "sample_attribute_text_settings" - - id = db.Column(db.Integer, primary_key=True) - max_length = db.Column(db.Integer, nullable=False) - - sample_attribute_id = db.Column(db.Integer, db.ForeignKey("sample_attributes.id")) - - creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) - update_date = db.Column( - db.DateTime, - server_default=db.func.now(), - server_onupdate=db.func.now(), - nullable=False, - ) - - - -class SampleAttributeNumericSetting(db.Model): - __tablename__ = "sample_attribute_numeric_settings" - - id = db.Column(db.Integer, primary_key=True) - - sample_attribute_id = db.Column(db.Integer, db.ForeignKey("sample_attributes.id")) - creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) - update_date = db.Column( - db.DateTime, - server_default=db.func.now(), - server_onupdate=db.func.now(), - nullable=False, - ) - - -class SampleAttributeOption(db.Model): - __tablename__ = "sample_attribute_options" - - id = db.Column(db.Integer, primary_key=True) - - term = db.Column(db.String(128)) - accession = db.Column(db.String(64)) - ref = db.Column(db.String(64)) - - sample_attribute_id = db.Column(db.Integer, db.ForeignKey("sample_attributes.id")) - - author_id = db.Column(db.Integer, db.ForeignKey("users.id")) - creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) - - update_date = db.Column( - db.DateTime, - server_default=db.func.now(), - server_onupdate=db.func.now(), - nullable=False, - ) - - -class SampleAttributeTextValue(db.Model): - __tablename__ = "sample_attribute_text_values" - - id = db.Column(db.Integer, primary_key=True) - - value = db.Column(db.String()) - - sample_attribute_id = db.Column(db.Integer, db.ForeignKey("sample_attributes.id")) - sample_id = db.Column(db.Integer, db.ForeignKey("samples.id")) - - author_id = db.Column(db.Integer, db.ForeignKey("users.id")) - creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) - - -class SampleAttributeOptionValue(db.Model): - __tablename__ = "sample_attribute_option_value" - id = db.Column(db.Integer, primary_key=True) - - sample_option_id = db.Column( - db.Integer, db.ForeignKey("sample_attribute_options.id") - ) - sample_attribute_id = db.Column(db.Integer, db.ForeignKey("sample_attributes.id")) - sample_id = db.Column(db.Integer, db.ForeignKey("samples.id")) - - author_id = db.Column(db.Integer, db.ForeignKey("users.id")) - creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) - - -class SamplePatientConsentFormAnswersAssociation(db.Model): - __tablename__ = "pcf_answers" - - id = db.Column(db.Integer, primary_key=True) - - sample_pcf_association_id = db.Column( - db.Integer, db.ForeignKey("sample_pcf_associations.id") - ) - checked = db.Column(db.Integer, db.ForeignKey("consent_form_template_questions.id")) - - author_id = db.Column(db.Integer, db.ForeignKey("users.id")) - creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) diff --git a/limbus/app/sample/models/consent.py b/limbus/app/sample/models/consent.py index c9551c2d9..b62428f26 100644 --- a/limbus/app/sample/models/consent.py +++ b/limbus/app/sample/models/consent.py @@ -15,4 +15,14 @@ class SamplePatientConsentFormTemplateAssociation(db.Model): creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) +class SamplePatientConsentFormAnswersAssociation(db.Model): + __tablename__ = "pcf_answers" + id = db.Column(db.Integer, primary_key=True) + + sample_pcf_association_id = db.Column( + db.Integer, db.ForeignKey("sample_pcf_associations.id") + ) + + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) + creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) diff --git a/limbus/app/sample/routes/__init__.py b/limbus/app/sample/routes/__init__.py index 56bc67d8b..1c7cfb165 100644 --- a/limbus/app/sample/routes/__init__.py +++ b/limbus/app/sample/routes/__init__.py @@ -4,7 +4,6 @@ from ..views import SamplesIndexView -from .attributes import * from .sample import * from .add import * from .aliquot import * diff --git a/limbus/app/sample/routes/add.py b/limbus/app/sample/routes/add.py index 04a7aa8c3..d5b7b015f 100644 --- a/limbus/app/sample/routes/add.py +++ b/limbus/app/sample/routes/add.py @@ -12,10 +12,6 @@ from ...misc.generators import generate_random_hash -# TODO: Take a look at this. -from ...dynform import DynamicAttributeFormGenerator, clear_session - - @sample.route("add/one", methods=["GET", "POST"]) @login_required def add_sample_pcf(): diff --git a/limbus/app/sample/routes/attributes.py b/limbus/app/sample/routes/attributes.py deleted file mode 100644 index 8bdbc3682..000000000 --- a/limbus/app/sample/routes/attributes.py +++ /dev/null @@ -1,131 +0,0 @@ -from .. import sample -from flask import render_template, session, redirect, url_for, request, jsonify -from flask_login import login_required, current_user -from ... import db - -from ...dynform import clear_session - -from ...misc.generators import generate_random_hash - -from ..models import * - -from ..views.attributes import SampleAttributesIndexView, SampleAttributeView - - -from ..forms import ( - SampleAttributeCreationForm, - SampleAttributionCreationFormText, - SampleAttributeCreationFormNumeric, -) - -@sample.route("attribute/") -@login_required -def attribute_portal(): - sample_attributes = SampleAttributesIndexView().get_attributes() - return render_template( - "sample/attribute/index.html", sample_attributes=sample_attributes - ) - - -@sample.route("attribute/view/LIMBSATTR-") -@login_required -def view_attribute(attribute_id): - attribute = SampleAttributeView(attribute_id).get_attributes() - return render_template( - "sample/attribute/view.html", - attribute=attribute - ) - -@sample.route("attribute/add/one", methods=["GET", "POST"]) -@login_required -def add_attribute(): - form = SampleAttributeCreationForm() - - if form.validate_on_submit(): - hash = generate_random_hash() - session["%s attribute_details" % (hash)] = { - "term": form.term.data, - "type": form.term_type.data, - "required": form.required.data, - } - - if form.term_type.data == "OPTION": - return redirect(url_for("sample.add_attribute_step_two_option", hash=hash)) - else: - return redirect(url_for("sample.add_attribute_step_two", hash=hash)) - return render_template("sample/attribute/add/one.html", form=form) - - -@sample.route("attribute/add/two/", methods=["GET", "POST"]) -@login_required -def add_attribute_step_two(hash): - attribute_details = session["%s attribute_details" % (hash)] - - if attribute_details["type"] == "TEXT": - form = SampleAttributionCreationFormText() - else: - form = SampleAttributeCreationFormNumeric() - - if form.validate_on_submit(): - - sample_attribute = SampleAttribute( - term=attribute_details["term"], - type=attribute_details["type"], - required=attribute_details["required"], - author_id=current_user.id, - ) - - db.session.add(sample_attribute) - db.session.flush() - - if attribute_details["type"] == "TEXT": - sample_attribute_setting = SampleAttributeTextSetting( - max_length=form.max_length.data, sample_attribute_id=sample_attribute.id - ) - db.session.add(sample_attribute_setting) - db.session.commit() - clear_session(hash) - return redirect(url_for("sample.attribute_portal")) - - elif attribute_details["type"] == "NUMERIC": - # TODO: - pass - - return render_template("sample/attribute/add/two.html", form=form, hash=hash) - - -@sample.route("attribute/add/two_option/", methods=["GET", "POST"]) -@login_required -def add_attribute_step_two_option(hash): - - if request.method == "POST": - attribute_details = session["%s attribute_details" % (hash)] - - options = request.form.getlist("options[]") - - sample_attribute = SampleAttribute( - term=attribute_details["term"], - type=attribute_details["type"], - author_id=current_user.id, - ) - db.session.add(sample_attribute) - db.session.flush() - - for option in options: - sao = SampleAttributeOption( - term=option, - author_id=current_user.id, - sample_attribute_id=sample_attribute.id, - ) - - db.session.add(sao) - - db.session.commit() - - clear_session(hash) - - resp = jsonify({"redirect": url_for("sample.attribute_portal", _external=True)}) - - return resp, 201, {"ContentType": "application/json"} - else: - return render_template("sample/attribute/add/two_option.html", hash=hash) diff --git a/limbus/app/sample/views/attributes.py b/limbus/app/sample/views/attributes.py deleted file mode 100644 index f41af360b..000000000 --- a/limbus/app/sample/views/attributes.py +++ /dev/null @@ -1,59 +0,0 @@ -from ... import db -from ..models import SampleAttribute -from ...auth.models import User -from ...ViewClass import ViewClass - -class SampleAttributesIndexView(ViewClass): - - def get_attributes(self) -> dict: - attributes = ( - db.session.query(SampleAttribute, User) - .filter(SampleAttribute.author_id == User.id) - .all() - ) - - data = {} - - for attr, user in attributes: - data[attr.id] = { - "term" : attr.term, - "type" : attr.type, - "creation_date" : attr.creation_date, - "required" : attr.required, - "author_id" : { - "id": user.id, - "name": user.name, - "gravatar": user.gravatar() - } - } - - return data - -class SampleAttributeView(ViewClass): - def __init__(self, attribute_id): - self.attribute = attribute_id - - def get_attributes(self) -> dict: - - attribute, user = ( - db.session.query(SampleAttribute, User) - .filter(SampleAttribute.author_id == User.id) - .first_or_404() - ) - - data = { - "id" : attribute.id, - "term" : attribute.term, - "creation_date": attribute.creation_date, - "type": attribute.type, - "author_id" : { - "id": user.id, - "name": user.name, - "gravatar": user.gravatar() - } - } - - - return data - - diff --git a/limbus/app/sample/views/sample.py b/limbus/app/sample/views/sample.py index 3c1b15893..c8be15c18 100644 --- a/limbus/app/sample/views/sample.py +++ b/limbus/app/sample/views/sample.py @@ -118,7 +118,6 @@ def get_attributes(self) -> dict: self.db_sessions["sample"] = sample self.db_sessions["user"] = user - data["custom_attributes"] = self._get_custom_attrs() data["storage_info"] = self._get_storage() diff --git a/limbus/app/templates/attribute/add/numeric.html b/limbus/app/templates/attribute/add/numeric.html new file mode 100644 index 000000000..ce19a42c8 --- /dev/null +++ b/limbus/app/templates/attribute/add/numeric.html @@ -0,0 +1,20 @@ +{% extends "template.html" %} + + +{% block body %} + +
+ +
+

Numeric: Add Attribute

+
+ + +
+ {{ form_field(form.measurement) }} + {{ form_field(form.prefix) }} + {{ form_field(form.submit) }} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/limbus/app/templates/attribute/add/one.html b/limbus/app/templates/attribute/add/one.html new file mode 100644 index 000000000..19b7cf339 --- /dev/null +++ b/limbus/app/templates/attribute/add/one.html @@ -0,0 +1,22 @@ +{% extends "template.html" %} + + +{% block body %} + +
+ +
+

Add Attribute

+
+ + +
+ {{ form_field(form.term) }} + {{ form_field(form.description) }} + {{ form_field(form.element) }} + {{ form_field(form.required) }} + {{ form_field(form.submit) }} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/limbus/app/templates/attribute/index.html b/limbus/app/templates/attribute/index.html new file mode 100644 index 000000000..f009a0b67 --- /dev/null +++ b/limbus/app/templates/attribute/index.html @@ -0,0 +1,24 @@ +{% extends "template.html" %} + + +{% block body %} + +
+ +
+

Attribute Portal

+ +
+ + +
+{% endblock %} \ No newline at end of file diff --git a/limbus/app/templates/sample/index.html b/limbus/app/templates/sample/index.html index 62ffb1807..51fc55da7 100644 --- a/limbus/app/templates/sample/index.html +++ b/limbus/app/templates/sample/index.html @@ -32,7 +32,7 @@

Sample At

This is where Sample Attribute information can be found.

diff --git a/limbus/app/templates/template.html b/limbus/app/templates/template.html index 2ba6e82c1..418bc4d0b 100644 --- a/limbus/app/templates/template.html +++ b/limbus/app/templates/template.html @@ -45,7 +45,6 @@ -
+ + {{ form.csrf_token() }} + {{ form_field(form.term) }} {{ form_field(form.description) }} {{ form_field(form.element) }} + {{ form_field(form.type) }} {{ form_field(form.required) }} {{ form_field(form.submit) }}
diff --git a/limbus/app/templates/attribute/add/numeric.html b/limbus/app/templates/attribute/add/numeric.html index ce19a42c8..99e4018f0 100644 --- a/limbus/app/templates/attribute/add/numeric.html +++ b/limbus/app/templates/attribute/add/numeric.html @@ -10,11 +10,27 @@

Numeric: Add Attribute

-
- {{ form_field(form.measurement) }} - {{ form_field(form.prefix) }} + + {{ form.csrf_token }} + +
+
+ {{ form_field(form.requires_measurement) }} + {{ form_field(form.measurement) }} + +
+
+ {{ form_field(form.requires_prefix) }} + {{ form_field(form.prefix) }} +
+
+ {{ form_field(form.submit) }}
+{% endblock %} + +{% block javascript %} + {% endblock %} \ No newline at end of file diff --git a/limbus/app/templates/sample/attribute/add/two_option.html b/limbus/app/templates/attribute/add/option.html similarity index 53% rename from limbus/app/templates/sample/attribute/add/two_option.html rename to limbus/app/templates/attribute/add/option.html index 75062bcc4..baf867ee4 100644 --- a/limbus/app/templates/sample/attribute/add/two_option.html +++ b/limbus/app/templates/attribute/add/option.html @@ -1,10 +1,11 @@ {% extends "template.html" %} -{% block title %}Add Sample Attribute : Sample Acquisition Portal{% endblock %} {% block body %} +
+

Step Two: Add Option[s] @@ -39,32 +40,33 @@

- - {% endblock %} From aab07bb8aab82ba55ad402ad93925ee09be734c0 Mon Sep 17 00:00:00 2001 From: KeironO Date: Tue, 28 Apr 2020 00:42:36 +0100 Subject: [PATCH 06/10] [I] Just need to extend the add --- limbus/app/attribute/forms.py | 6 ------ limbus/app/sample/forms.py | 8 ++++++++ limbus/app/sample/routes/add.py | 2 +- limbus/app/templates/sample/sample/add/step_six.html | 4 ++++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/limbus/app/attribute/forms.py b/limbus/app/attribute/forms.py index 7e8b2e362..6abe8ad5b 100644 --- a/limbus/app/attribute/forms.py +++ b/limbus/app/attribute/forms.py @@ -98,12 +98,6 @@ class StaticForm(FlaskForm): return StaticForm() -class FinalSampleForm: - elements = { - "collection_date": DateField("Collection Date", validators=[DataRequired()]), - "submit": SubmitField("Submit") - } - def CustomAttributeGeneratedForm(form, attribute_ids: [] = []) -> FlaskForm: class StaticForm(FlaskForm): pass diff --git a/limbus/app/sample/forms.py b/limbus/app/sample/forms.py index c0485eba5..f370d8d98 100644 --- a/limbus/app/sample/forms.py +++ b/limbus/app/sample/forms.py @@ -135,6 +135,14 @@ class StaticForm(FlaskForm): +class FinalSampleForm: + elements = { + "collection_date": DateField("Collection Date", validators=[DataRequired()]), + "disposal_instruction": SelectField("Disposal Instructions", choices=DisposalInstruction.choices()), + "disposal_date" : DateField("Disposal Date"), + "submit": SubmitField("Submit") + } + def SampleAliquotingForm(sample_type, default_type) -> FlaskForm: diff --git a/limbus/app/sample/routes/add.py b/limbus/app/sample/routes/add.py index 04a5d29a2..a8c3067b2 100644 --- a/limbus/app/sample/routes/add.py +++ b/limbus/app/sample/routes/add.py @@ -8,7 +8,7 @@ from ..models import * from ..forms import * -from ...attribute.forms import CustomAttributeSelectForm, CustomAttributeGeneratedForm, FinalSampleForm +from ...attribute.forms import CustomAttributeSelectForm, CustomAttributeGeneratedForm from ...attribute.enums import CustomAttributeElementTypes from ...processing.models import ProcessingTemplate diff --git a/limbus/app/templates/sample/sample/add/step_six.html b/limbus/app/templates/sample/sample/add/step_six.html index 28a8a1250..a13657f8c 100644 --- a/limbus/app/templates/sample/sample/add/step_six.html +++ b/limbus/app/templates/sample/sample/add/step_six.html @@ -13,13 +13,17 @@

Step Six: Enter Sample Attribute Information

{{ form.csrf_token }} {{ form_field(form.collection_date) }} + {{ form_field(form.disposal_instruction) }} + {{ form_field(form.disposal_date) }}

Custom Attributes

+ {% for attribute in form %} {% if attribute.render_kw._custom_val %} {{ form_field(attribute) }} {% endif %} {% endfor %} + {{ form_field(form.submit) }}
From 3ef1487193356a2918338a63c6205450b2f019fb Mon Sep 17 00:00:00 2001 From: KeironO Date: Tue, 28 Apr 2020 15:01:17 +0100 Subject: [PATCH 07/10] [I] Working Sample submission --- limbus/app/attribute/models.py | 28 -------- limbus/app/sample/models/__init__.py | 1 + limbus/app/sample/models/attribute.py | 42 ++++++++++++ limbus/app/sample/routes/add.py | 67 ++++++++----------- .../sample/sample/add/step_five.html | 2 +- 5 files changed, 73 insertions(+), 67 deletions(-) create mode 100644 limbus/app/sample/models/attribute.py diff --git a/limbus/app/attribute/models.py b/limbus/app/attribute/models.py index 8c99e0347..78a0dc604 100644 --- a/limbus/app/attribute/models.py +++ b/limbus/app/attribute/models.py @@ -65,31 +65,3 @@ class CustomAttributeOption(db.Model): creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) update_date = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now(), nullable=False) - - - -class SampleAttributeTextValue(db.Model): - __tablename__ = "custom_attribute_text_values" - - id = db.Column(db.Integer, primary_key=True) - - value = db.Column(db.String) - custom_attribute_id = db.Column(db.Integer, db.ForeignKey("custom_attributes.id")) - - sample_id = db.Column(db.Integer, db.ForeignKey("samples.id")) - author_id = db.Column(db.Integer, db.ForeignKey("users.id")) - - creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) - update_date = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now(), nullable=False) - -class CustomAttributeOptionValue(db.Model): - __tablename__ = "custom_attribute_option_values" - id = db.Column(db.Integer, primary_key=True) - - custom_option_id = db.Column(db.Integer, db.ForeignKey("custom_attribute_options.id")) - - custom_attribute_id = db.Column(db.Integer, db.ForeignKey("custom_attributes.id")) - sample_id = db.Column(db.Integer, db.ForeignKey("samples.id")) - - author_id = db.Column(db.Integer, db.ForeignKey("users.id")) - creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) \ No newline at end of file diff --git a/limbus/app/sample/models/__init__.py b/limbus/app/sample/models/__init__.py index 832efe0ee..1a1b25c6c 100644 --- a/limbus/app/sample/models/__init__.py +++ b/limbus/app/sample/models/__init__.py @@ -1,5 +1,6 @@ from .base import * from .processing import * +from .attribute import * from .types import * from .consent import * from .document import * diff --git a/limbus/app/sample/models/attribute.py b/limbus/app/sample/models/attribute.py new file mode 100644 index 000000000..eb340fbd5 --- /dev/null +++ b/limbus/app/sample/models/attribute.py @@ -0,0 +1,42 @@ +from app import db + +class SampleToCustomAttributeTextValue(db.Model): + __tablename__ = "sample_to_custom_attribute_text_values" + + id = db.Column(db.Integer, primary_key=True) + + value = db.Column(db.String) + custom_attribute_id = db.Column(db.Integer, db.ForeignKey("custom_attributes.id")) + + sample_id = db.Column(db.Integer, db.ForeignKey("samples.id")) + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) + + creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) + update_date = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now(), nullable=False) + +class SampleToCustomAttributeNumericValue(db.Model): + __tablename__ = "sample_to_custom_attribute_numeric_values" + + id = db.Column(db.Integer, primary_key=True) + + value = db.Column(db.String) + custom_attribute_id = db.Column(db.Integer, db.ForeignKey("custom_attributes.id")) + + sample_id = db.Column(db.Integer, db.ForeignKey("samples.id")) + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) + + creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) + update_date = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now(), nullable=False) + + +class SampleToCustomAttributeOptionValue(db.Model): + __tablename__ = "sample_to_custom_attribute_option_values" + id = db.Column(db.Integer, primary_key=True) + + custom_option_id = db.Column(db.Integer, db.ForeignKey("custom_attribute_options.id")) + + custom_attribute_id = db.Column(db.Integer, db.ForeignKey("custom_attributes.id")) + sample_id = db.Column(db.Integer, db.ForeignKey("samples.id")) + + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) + creation_date = db.Column(db.DateTime, server_default=db.func.now(), nullable=False) \ No newline at end of file diff --git a/limbus/app/sample/routes/add.py b/limbus/app/sample/routes/add.py index a8c3067b2..ec48b8d19 100644 --- a/limbus/app/sample/routes/add.py +++ b/limbus/app/sample/routes/add.py @@ -13,6 +13,7 @@ from ...processing.models import ProcessingTemplate from ...misc.generators import generate_random_hash +from ...misc import clear_session @sample.route("add/one", methods=["GET", "POST"]) @@ -201,6 +202,8 @@ def add_sample_form(hash): sample_type = sample_type_info["sample_type"] + print(">>>> TEST", form.collection_date.data ,type(form.collection_date.data)) + sample = Sample( sample_type=sample_type, quantity=sample_type_info["quantity"], @@ -243,44 +246,32 @@ def add_sample_form(hash): for attr in form: - if attr.id not in [ - "csrf_token", - "biobank_accession_number", - "sample_status", - "batch_number", - "submit", - "sample_type", - "collection_date", - "disposal_instruction", - ]: - if attr.type in ["TextAreaField", "StringField"]: - attr_value = SampleAttributeTextValue( - value=attr.data, - sample_attribute_id=session["%s converted_ids" % (hash)][ - attr.id - ], - sample_id=sample.id, - author_id=current_user.id, - ) - db.session.add(attr_value) - - elif attr.type in ["SelectField"]: - option = ( - db.session.query(SampleAttributeOption) - .filter(SampleAttributeOption.term == attr.data) - .first() - ) - - option_value = SampleAttributeOptionValue( - sample_attribute_id=session["%s converted_ids" % (hash)][ - attr.id - ], - sample_id=sample.id, - sample_option_id=option.id, - author_id=current_user.id, - ) - - db.session.add(option_value) + if hasattr(attr, "render_kw") and attr.render_kw != None: + if "_custom_val" in attr.render_kw: + if attr.type == "StringField": + ca_v = SampleToCustomAttributeTextValue( + value = attr.data, + custom_attribute_id = attr.id, + sample_id = sample.id, + author_id = current_user.id + ) + + elif attr == "SelectField": + ca_v = SampleToCustomAttributeOptionValue( + custom_option_id = attr.data, + custom_attribute_id = attr.id, + sample_id = sample.id, + author_id = current_user.id + ) + else: + ca_v = SampleToCustomAttributeNumericValue( + value = attr.data, + custom_attribute_id = attr.id, + sample_id = sample.id, + author_id = current_user.id + ) + + db.session.add(ca_v) consent_info = session["%s consent_info" % (hash)] diff --git a/limbus/app/templates/sample/sample/add/step_five.html b/limbus/app/templates/sample/sample/add/step_five.html index 92ee3db7f..1383c5376 100644 --- a/limbus/app/templates/sample/sample/add/step_five.html +++ b/limbus/app/templates/sample/sample/add/step_five.html @@ -22,7 +22,7 @@

{{ page_title }}

[ "There are no suitable patient consent form templates found.", "Please add one before adding a sample.", - ], 'sample.add_attribute', "New Attribute", True, True + ], 'attribute.add', "New Attribute", True, True ) }} {% endif %}
From d6d1c82700067845cd8c26fe0d042deefd6ddc7c Mon Sep 17 00:00:00 2001 From: KeironO Date: Tue, 28 Apr 2020 15:06:04 +0100 Subject: [PATCH 08/10] [I] Added attribute portal to navbar --- limbus/app/templates/template.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/limbus/app/templates/template.html b/limbus/app/templates/template.html index 418bc4d0b..4a77aad30 100644 --- a/limbus/app/templates/template.html +++ b/limbus/app/templates/template.html @@ -50,9 +50,16 @@ Consent Management + + +