aboutsummaryrefslogtreecommitdiff
path: root/qc_app
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2024-03-29 13:14:14 +0300
committerFrederick Muriuki Muriithi2024-03-29 13:14:14 +0300
commit6326357250711650bc8b4c0b90f8c0b94d72c39e (patch)
tree26bfb9f169f45176dbc439839b7d96d07de52627 /qc_app
parent62e54fd5b457d67f3f01a35e28eae94d653d9908 (diff)
downloadgn-uploader-6326357250711650bc8b4c0b90f8c0b94d72c39e.tar.gz
Add UI to select/create tissue.
Diffstat (limited to 'qc_app')
-rw-r--r--qc_app/db/tissues.py31
-rw-r--r--qc_app/templates/rqtl2/create-tissue-success.html104
-rw-r--r--qc_app/templates/rqtl2/select-probeset-study-id.html20
-rw-r--r--qc_app/templates/rqtl2/select-tissue.html112
-rw-r--r--qc_app/upload/rqtl2.py104
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."""