diff options
-rw-r--r-- | qc_app/db/tissues.py | 31 | ||||
-rw-r--r-- | qc_app/templates/rqtl2/create-tissue-success.html | 104 | ||||
-rw-r--r-- | qc_app/templates/rqtl2/select-probeset-study-id.html | 20 | ||||
-rw-r--r-- | qc_app/templates/rqtl2/select-tissue.html | 112 | ||||
-rw-r--r-- | qc_app/upload/rqtl2.py | 104 |
5 files changed, 349 insertions, 22 deletions
diff --git a/qc_app/db/tissues.py b/qc_app/db/tissues.py index ebf24fd..062e824 100644 --- a/qc_app/db/tissues.py +++ b/qc_app/db/tissues.py @@ -1,5 +1,5 @@ """Handle db interactions for tissue.""" -from typing import Optional +from typing import Union, Optional import MySQLdb as mdb from MySQLdb.cursors import DictCursor @@ -10,6 +10,7 @@ def all_tissues(conn: mdb.Connection) -> tuple[dict, ...]: cursor.execute("SELECT * FROM Tissue ORDER BY TissueName") return tuple(dict(row) for row in cursor.fetchall()) + def tissue_by_id(conn: mdb.Connection, tissueid: int) -> Optional[dict]: """Retrieve a tissue by its ID""" with conn.cursor(cursorclass=DictCursor) as cursor: @@ -20,3 +21,31 @@ def tissue_by_id(conn: mdb.Connection, tissueid: int) -> Optional[dict]: return dict(result) return None + + +def create_new_tissue( + conn: mdb.Connection, + name: str, + shortname: str, + birnlexid: Optional[str] = None, + birnlexname: Optional[str] = None +) -> dict[str, Union[int, str, None]]: + """Add a new tissue, organ or biological material to the database.""" + with conn.cursor() as cursor: + cursor.execute( + "INSERT INTO " + "Tissue(TissueName, Name, Short_Name, BIRN_lex_ID, BIRN_lex_Name) " + "VALUES (%s, %s, %s, %s, %s)", + (name, name, shortname, birnlexid, birnlexname)) + tissueid = cursor.lastrowid + cursor.execute("UPDATE Tissue SET TissueId=%s WHERE Id=%s", + (tissueid, tissueid)) + return { + "Id": tissueid, + "TissueId": tissueid, + "TissueName": name, + "Name": name, + "Short_Name": shortname, + "BIRN_lex_ID": birnlexid, + "BIRN_lex_Name": birnlexname + } diff --git a/qc_app/templates/rqtl2/create-tissue-success.html b/qc_app/templates/rqtl2/create-tissue-success.html new file mode 100644 index 0000000..ea05624 --- /dev/null +++ b/qc_app/templates/rqtl2/create-tissue-success.html @@ -0,0 +1,104 @@ +{%extends "base.html"%} +{%from "flash_messages.html" import flash_all_messages%} + +{%block title%}Upload R/qtl2 Bundle{%endblock%} + +{%block contents%} +<h2 class="heading">Select Tissue</h2> + +<div class="explainer"> + <p>You have successfully added a new tissue, organ or biological material with + the following details:</p> +</div> + +{{flash_all_messages()}} + +<form id="frm-create-tissue-display" + method="POST" + action="#"> + <legend class="heading">Create Tissue</legend> + + <input type="hidden" name="species_id" value="{{species.SpeciesId}}" /> + <input type="hidden" name="population_id" + value="{{population.InbredSetId}}" /> + <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" /> + <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" /> + <input type="hidden" name="tissueid" value="{{tissue.Id}}" /> + + <fieldset> + <label>Name</label> + <label>{{tissue.TissueName}}</label> + </fieldset> + + <fieldset> + <label>Short Name</label> + <label>{{tissue.Short_Name}}</label> + </fieldset> + + {%if tissue.BIRN_lex_ID%} + <fieldset> + <label>BIRN Lex ID</label> + <label>{{tissue.BIRN_lex_ID}}</label> + </fieldset> + {%endif%} + + {%if tissue.BIRN_lex_Name%} + <fieldset> + <label>BIRN Lex Name</label> + <label>{{tissue.BIRN_lex_Name}}</label> + </fieldset> + {%endif%} +</form> + +<div id="action-buttons" + style="width:65ch;display:inline-grid;column-gap:5px;"> + + <form id="frm-create-tissue-success-continue" + method="POST" + action="{{url_for('upload.rqtl2.select_dataset_info', + species_id=species.SpeciesId, + population_id=population.InbredSetId)}}" + style="display: inline; width: 100%; grid-column: 1 / 2; + padding-top: 0.5em; text-align: center; border: none; + background-color: inherit;"> + + <input type="hidden" name="species_id" value="{{species.SpeciesId}}" /> + <input type="hidden" name="population_id" + value="{{population.InbredSetId}}" /> + <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" /> + <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" /> + <input type="hidden" name="tissueid" value="{{tissue.Id}}" /> + + <input type="submit" + value="continue" + class="btn btn-main form-col-2" /> + </form> + + <p style="display:inline;width:100%;grid-column:2/3;text-align:center; + color:#336699;font-weight:bold;"> + OR + </p> + + <form id="frm-create-tissue-success-select-existing" + method="POST" + action="{{url_for('upload.rqtl2.select_tissue', + species_id=species.SpeciesId, + population_id=population.InbredSetId)}}" + style="display: inline; width: 100%; grid-column: 3 / 4; + padding-top: 0.5em; text-align: center; border: none; + background-color: inherit;"> + + <input type="hidden" name="species_id" value="{{species.SpeciesId}}" /> + <input type="hidden" name="population_id" + value="{{population.InbredSetId}}" /> + <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" /> + <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" /> + + <input type="submit" + value="select from existing tissues" + class="btn btn-main form-col-2" /> + </form> + +</div> + +{%endblock%} diff --git a/qc_app/templates/rqtl2/select-probeset-study-id.html b/qc_app/templates/rqtl2/select-probeset-study-id.html index 3bbb94a..23bbbf0 100644 --- a/qc_app/templates/rqtl2/select-probeset-study-id.html +++ b/qc_app/templates/rqtl2/select-probeset-study-id.html @@ -26,6 +26,7 @@ <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" /> <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" /> + <input type="hidden" name="tissueid" value="{{tissue.Id}}" /> <fieldset> <label for="select:probe-study">Study</label> @@ -71,6 +72,7 @@ <input type="hidden" name="rqtl2_bundle_file" value="{{rqtl2_bundle_file}}" /> <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" /> + <input type="hidden" name="tissueid" value="{{tissue.Id}}" /> <fieldset> <label for="select:platform">Platform</label> @@ -90,24 +92,6 @@ </fieldset> <fieldset> - <label for="select:tissue">Tissue</label> - <select id="select:tissue" - name="tissueid" - required="required" - {%if tissues | length == 0%}disabled="disabled"{%endif%}> - <option value="">Select tissue</option> - {%for tissue in tissues%} - <option value="{{tissue.Id}}"> - {{tissue.TissueName}} ({{tissue.Short_Name}}) - </option> - {%endfor%} - </select> - <span class="form-col-2"> - Select the Tissue to which the data is concerned. - </span> - </fieldset> - - <fieldset> <label for="txt:studyname">Study name</label> <input type="text" id="txt:studyname" name="studyname" placeholder="Name of the study. (Required)" diff --git a/qc_app/templates/rqtl2/select-tissue.html b/qc_app/templates/rqtl2/select-tissue.html new file mode 100644 index 0000000..6f7d134 --- /dev/null +++ b/qc_app/templates/rqtl2/select-tissue.html @@ -0,0 +1,112 @@ +{%extends "base.html"%} +{%from "flash_messages.html" import flash_messages%} + +{%block title%}Upload R/qtl2 Bundle{%endblock%} + +{%block contents%} +<h2 class="heading">Tissue</h2> + +<div class="explainer"> + <p>The data you are uploading concerns a tissue, cell, organ, or other + biological material used in an experiment.</p> + <p>Select the appropriate biological material below</p> +</div> + +{%if tissues | length > 0%} +<form method="POST" + action="{{url_for('upload.rqtl2.select_tissue', + species_id=species.SpeciesId, population_id=population.Id)}}" + id="frm:select-probeset-dataset"> + <legend class="heading">Select from existing ProbeSet datasets</legend> + {{flash_messages("error-select-tissue")}} + + <input type="hidden" name="species_id" value="{{species.SpeciesId}}" /> + <input type="hidden" name="population_id" + value="{{population.InbredSetId}}" /> + <input type="hidden" name="rqtl2_bundle_file" + value="{{rqtl2_bundle_file}}" /> + <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" /> + + <fieldset> + <label for="select-tissue">Tissue</label> + <select id="select-tissue" + name="tissueid" + required="required" + {%if tissues | length == 0%}disabled="disabled"{%endif%}> + <option value="">Select a tissue</option> + {%for tissue in tissues%} + <option value={{tissue.Id}}> + {{tissue.Name}} + {%if tissue.Short_Name%} + -- ({{tissue.Short_Name}}) + {%endif%} + </option> + {%endfor%} + </select> + + <span class="form-col-2">Select from existing biological material.</span> + </fieldset> + + <fieldset> + <input type="submit" + value="use selected" + class="btn btn-main form-col-2" /> + </fieldset> +</form> + +<p style="color:#FE3535; padding-left:20em; font-weight:bolder;">OR</p> +{%endif%} + +<div class="explainer"> + <p>If you cannot find the biological material in the drop-down above, add it + to the system below.</p> +</div> + +<form method="POST" + action="{{url_for('upload.rqtl2.create_tissue', + species_id=species.SpeciesId, population_id=population.Id)}}" + id="frm:create-probeset-dataset"> + <legend class="heading">Add new tissue, organ or biological material</legend> + {{flash_messages("error-create-tissue")}} + + <input type="hidden" name="species_id" value="{{species.SpeciesId}}" /> + <input type="hidden" name="population_id" + value="{{population.InbredSetId}}" /> + <input type="hidden" name="rqtl2_bundle_file" + value="{{rqtl2_bundle_file}}" /> + <input type="hidden" name="geno-dataset-id" value="{{geno_dataset.Id}}" /> + + <fieldset> + <label for="tissue-name">name</label> + <input type="text" + id="txt-tissuename" + name="tissuename" + required="required" + title = "A name to identify the tissue, organ or biological material." /> + + <span class="form-col-2"> + A name to identify the tissue, organ or biological material. + </span> + </fieldset> + + <fieldset> + <label for="txt-shortname">short name</label> + <input type="text" id="txt-tissueshortname" name="tissueshortname" + required="required" + maxlength="7" + title="A short name (e.g. 'Mam') for the biological material." /> + + <span class="form-col-2"> + Provide a short name for the tissue, organ or biological material used in + the experiment. + </span> + </fieldset> + + <fieldset> + <input type="submit" + value="add new material" + class="btn btn-main form-col-2" /> + </fieldset> +</form> + +{%endblock%} diff --git a/qc_app/upload/rqtl2.py b/qc_app/upload/rqtl2.py index c05f675..7a53756 100644 --- a/qc_app/upload/rqtl2.py +++ b/qc_app/upload/rqtl2.py @@ -28,9 +28,9 @@ from qc_app.files import save_file, fullpath from qc_app.dbinsert import species as all_species from qc_app.db_utils import with_db_connection, database_connection -from qc_app.db.tissues import all_tissues, tissue_by_id from qc_app.db.platforms import platform_by_id, platforms_by_species from qc_app.db.averaging import averaging_methods, averaging_method_by_id +from qc_app.db.tissues import all_tissues, tissue_by_id, create_new_tissue from qc_app.db import ( species_by_id, save_population, @@ -65,6 +65,7 @@ def select_species(): flash("Invalid species or no species selected!", "alert-error error-rqtl2") return redirect(url_for("upload.rqtl2.select_species")) + @rqtl2.route("/upload/species/<int:species_id>/select-population", methods=["GET", "POST"]) def select_population(species_id: int): @@ -93,6 +94,7 @@ def select_population(species_id: int): species_id=species["SpeciesId"], population_id=population["InbredSetId"])) + @rqtl2.route("/upload/species/<int:species_id>/create-population", methods=["POST"]) def create_population(species_id: int): @@ -129,9 +131,11 @@ def create_population(species_id: int): pgsrc="create-population"), code=307) + class __RequestError__(Exception): #pylint: disable=[invalid-name] """Internal class to avoid pylint's `too-many-return-statements` error.""" + @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" "/rqtl2-bundle"), methods=["GET", "POST"]) @@ -197,6 +201,7 @@ def upload_rqtl2_bundle(species_id: int, population_id: int): return redirect(url_for( "upload.rqtl2.rqtl2_bundle_qc_status", jobid=jobid)) + @rqtl2.route("/upload/species/rqtl2-bundle/qc-status/<uuid:jobid>", methods=["GET", "POST"]) def rqtl2_bundle_qc_status(jobid: UUID): @@ -255,15 +260,18 @@ def rqtl2_bundle_qc_status(jobid: UUID): except jobs.JobNotFound: return render_template("rqtl2/no-such-job.html", jobid=jobid) + def form_errors(formargs, *errorcheckers) -> Iterable[tuple[str, Response]]: """Retrieve all errors from the form inputs""" return (checker(formargs) for checker in errorcheckers) + def redirect_on_error(flaskroute, **kwargs): """Utility to redirect on error""" return redirect(url_for(flaskroute, **kwargs, pgsrc="error"), code=(307 if request.method == "POST" else 302)) + def check_species(conn: mdb.Connection, formargs: dict) -> Optional[ tuple[str, Response]]: """ @@ -280,6 +288,7 @@ def check_species(conn: mdb.Connection, formargs: dict) -> Optional[ return None + def check_population(conn: mdb.Connection, formargs: dict, species_id) -> Optional[tuple[str, Response]]: @@ -299,6 +308,7 @@ def check_population(conn: mdb.Connection, return None + def check_r_qtl2_bundle(formargs: dict, species_id, population_id) -> Optional[tuple[str, Response]]: @@ -315,6 +325,7 @@ def check_r_qtl2_bundle(formargs: dict, return None + def check_geno_dataset(formargs: dict, species_id, population_id) -> Optional[tuple[str, Response]]: @@ -328,6 +339,7 @@ def check_geno_dataset(formargs: dict, return None + def check_probe_study(formargs: dict, species_id, population_id) -> Optional[tuple[str, Response]]: @@ -340,6 +352,7 @@ def check_probe_study(formargs: dict, return None + def check_probe_dataset(formargs: dict, species_id, population_id) -> Optional[tuple[str, Response]]: @@ -352,6 +365,7 @@ def check_probe_dataset(formargs: dict, return None + def with_errors(conn: mdb.Connection, endpointthunk: Callable, *checkerstrings): """Run 'endpointthunk' with error checking.""" formargs = {**dict(request.args), **dict(request.form)} @@ -378,6 +392,7 @@ def with_errors(conn: mdb.Connection, endpointthunk: Callable, *checkerstrings): return endpointthunk() + @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" "/rqtl2-bundle/select-geno-dataset"), methods=["POST"]) @@ -406,6 +421,7 @@ def select_geno_dataset(species_id: int, population_id: int): return with_errors( conn, __thunk__, "species", "population", "rqtl2_bundle_file", "geno_dataset") + @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" "/rqtl2-bundle/create-geno-dataset"), methods=["POST"]) @@ -457,6 +473,71 @@ def create_geno_dataset(species_id: int, population_id: int): return with_errors(conn, __thunk__, "species", "population", "rqtl2_bundle_file") + +@rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" + "/rqtl2-bundle/select-tissue"), + methods=["POST"]) +def select_tissue(species_id: int, population_id: int): + """Select from existing tissues.""" + with database_connection(app.config["SQL_URI"]) as conn: + def __thunk__(): + if not bool(request.form.get("tissueid", "").strip()): + flash("Invalid tissue selection!", + "alert-error error-select-tissue error-rqtl2") + + return redirect(url_for("upload.rqtl2.select_dataset_info", + species_id=species_id, + population_id=population_id, + pgsrc="upload.rqtl2.select_geno_dataset"), + code=307) + + return with_errors( + conn, __thunk__, "species", "population", "rqtl2_bundle_file", "geno_dataset") + +@rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" + "/rqtl2-bundle/create-tissue"), + methods=["POST"]) +def create_tissue(species_id: int, population_id: int): + """Add new tissue, organ or biological material to the system.""" + form = request.form + datasetinfopage = redirect( + url_for("upload.rqtl2.select_dataset_info", + species_id=species_id, + population_id=population_id, + pgsrc="upload.rqtl2.select_geno_dataset"), + code=307) + with database_connection(app.config["SQL_URI"]) as conn: + tissuename = form.get("tissuename", "").strip() + tissueshortname = form.get("tissueshortname", "").strip() + if not bool(tissuename): + flash("Organ/Tissue name MUST be provided.", + "alert-error error-create-tissue error-rqtl2") + return datasetinfopage + + if not bool(tissueshortname): + flash("Organ/Tissue short name MUST be provided.", + "alert-error error-create-tissue error-rqtl2") + return datasetinfopage + + try: + tissue = create_new_tissue(conn, tissuename, tissueshortname) + flash("Tissue created successfully!", "alert-success") + return render_template( + "rqtl2/create-tissue-success.html", + species=species_by_id(conn, species_id), + population=population_by_species_and_id( + conn, species_id, population_id), + rqtl2_bundle_file=request.form["rqtl2_bundle_file"], + geno_dataset=geno_dataset_by_id( + conn, + int(request.form["geno-dataset-id"])), + tissue=tissue) + except mdb.IntegrityError as _ierr: + flash("Tissue/Organ with that short name already exists!", + "alert-error error-create-tissue error-rqtl2") + return datasetinfopage + + @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" "/rqtl2-bundle/select-probeset-study"), methods=["POST"]) @@ -477,6 +558,7 @@ def select_probeset_study(species_id: int, population_id: int): "rqtl2_bundle_file", "geno_dataset", "probeset_study") + @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" "/rqtl2-bundle/select-probeset-dataset"), methods=["POST"]) @@ -498,6 +580,7 @@ def select_probeset_dataset(species_id: int, population_id: int): "rqtl2_bundle_file", "geno_dataset", "probeset_study", "probeset_dataset") + @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" "/rqtl2-bundle/create-probeset-study"), methods=["POST"]) @@ -540,6 +623,7 @@ def create_probeset_study(species_id: int, population_id: int): return with_errors(conn, __thunk__, "species", "population", "rqtl2_bundle_file", "geno_dataset") + @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" "/rqtl2-bundle/create-probeset-dataset"), methods=["POST"]) @@ -597,6 +681,7 @@ def create_probeset_dataset(species_id: int, population_id: int):#pylint: disabl "rqtl2_bundle_file", "geno_dataset", "probeset_study") + @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" "/rqtl2-bundle/dataset-info"), methods=["POST"]) @@ -625,6 +710,19 @@ def select_dataset_info(species_id: int, population_id: int): geno_dataset = geno_dataset_by_id(conn, int(form["geno-dataset-id"])) if "pheno" in cdata and not bool(form.get("probe-study-id")): + tissue = tissue_by_id(conn, form.get("tissueid", "").strip()) + if not bool(tissue): + return render_template( + "rqtl2/select-tissue.html", + species=species, + population=population, + rqtl2_bundle_file=thefile.name, + geno_dataset=geno_dataset, + studies=probeset_studies_by_species_and_population( + conn, species_id, population_id), + platforms=platforms_by_species(conn, species_id), + tissues=all_tissues(conn)) + return render_template( "rqtl2/select-probeset-study-id.html", species=species, @@ -634,7 +732,7 @@ def select_dataset_info(species_id: int, population_id: int): studies=probeset_studies_by_species_and_population( conn, species_id, population_id), platforms=platforms_by_species(conn, species_id), - tissues=all_tissues(conn)) + tissue=tissue) probeset_study = probeset_study_by_id( conn, int(form["probe-study-id"])) @@ -666,7 +764,6 @@ def select_dataset_info(species_id: int, population_id: int): "rqtl2_bundle_file") - @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" "/rqtl2-bundle/confirm-bundle-details"), methods=["POST"]) @@ -719,6 +816,7 @@ def confirm_bundle_details(species_id: int, population_id: int): "rqtl2_bundle_file", "geno_dataset", "probeset_study", "probeset_dataset") + @rqtl2.route("/status/<uuid:jobid>") def rqtl2_processing_status(jobid: UUID): """Retrieve the status of the job processing the uploaded R/qtl2 bundle.""" |