diff options
-rw-r--r-- | qc_app/db/__init__.py | 1 | ||||
-rw-r--r-- | qc_app/db/datasets.py | 16 | ||||
-rw-r--r-- | qc_app/files.py | 5 | ||||
-rw-r--r-- | qc_app/static/css/styles.css | 7 | ||||
-rw-r--r-- | qc_app/templates/rqtl2/select-geno-dataset.html | 55 | ||||
-rw-r--r-- | qc_app/templates/rqtl2/upload-rqtl2-bundle-step-02.html | 36 | ||||
-rw-r--r-- | qc_app/upload/rqtl2.py | 73 |
7 files changed, 190 insertions, 3 deletions
diff --git a/qc_app/db/__init__.py b/qc_app/db/__init__.py index 0b48461..270f1a0 100644 --- a/qc_app/db/__init__.py +++ b/qc_app/db/__init__.py @@ -5,3 +5,4 @@ from .populations import ( population_by_id, populations_by_species, population_by_species_and_id) +from .datasets import geno_dataset_by_species_and_population diff --git a/qc_app/db/datasets.py b/qc_app/db/datasets.py new file mode 100644 index 0000000..3a27706 --- /dev/null +++ b/qc_app/db/datasets.py @@ -0,0 +1,16 @@ +"""Functions for accessing the database relating to datasets.""" +import MySQLdb as mdb +from MySQLdb.cursors import DictCursor + +def geno_dataset_by_species_and_population( + conn: mdb.Connection, + speciesid: int, + populationid: int) -> tuple[dict, ...]: + """Retrieve all genotypes datasets by species and population""" + with conn.cursor(cursorclass=DictCursor) as cursor: + cursor.execute( + "SELECT gf.* FROM InbredSet AS iset INNER JOIN GenoFreeze AS gf " + "ON iset.InbredSetId=gf.InbredSetId " + "WHERE iset.SpeciesId=%(sid)s AND iset.InbredSetId=%(pid)s", + {"sid": speciesid, "pid": populationid}) + return tuple(dict(row) for row in cursor.fetchall()) diff --git a/qc_app/files.py b/qc_app/files.py index 205a39c..baac5ec 100644 --- a/qc_app/files.py +++ b/qc_app/files.py @@ -3,6 +3,7 @@ import hashlib from pathlib import Path from typing import Union from datetime import datetime +from flask import current_app from werkzeug.utils import secure_filename from werkzeug.datastructures import FileStorage @@ -21,3 +22,7 @@ def save_file(fileobj: FileStorage, upload_dir: Path) -> Union[Path, bool]: filepath = Path(upload_dir, filename) fileobj.save(filepath) return filepath + +def fullpath(filename: str): + """Get a file's full path. This makes use of `flask.current_app`.""" + return Path(current_app.config["UPLOAD_FOLDER"], filename).absolute() diff --git a/qc_app/static/css/styles.css b/qc_app/static/css/styles.css index f4112d1..af5281e 100644 --- a/qc_app/static/css/styles.css +++ b/qc_app/static/css/styles.css @@ -71,6 +71,11 @@ fieldset { background-color: #AAEEAA; } +.alert-warning { + background-color: #F9FAB6; + border-color: #E8E9C5; +} + table { margin-left: 1em; border-collapse: collapse; @@ -130,7 +135,7 @@ form fieldset:nth-child(odd) { @media(min-width: 1250px) { form { - width: 40%; + width: 65ch; } .explainer { diff --git a/qc_app/templates/rqtl2/select-geno-dataset.html b/qc_app/templates/rqtl2/select-geno-dataset.html new file mode 100644 index 0000000..c8623b3 --- /dev/null +++ b/qc_app/templates/rqtl2/select-geno-dataset.html @@ -0,0 +1,55 @@ +{%extends "base.html"%} +{%from "flash_messages.html" import flash_messages%} + +{%block title%}Upload R/qtl2 Bundle{%endblock%} + +{%block contents%} +<h2 class="heading">Select Genotypes Dataset</h2> + +<div class="explainer"> + <p>Your R/qtl2 files bundle contains a "geno" specification. You will + therefore need to select from one of the existing Genotype datasets or + create a new one.</p> + <p>This is the dataset where your data will be organised under.</p> +</div> + +<form id="frm-upload-rqtl2-bundle" + action="{{url_for('upload.rqtl2.select_geno_dataset', + species_id=species.SpeciesId, + population_id=population.InbredSetId)}}" + method="POST" + enctype="multipart/form-data"> + <legend class="heading">select from existing genotype datasets</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}}" /> + + {{flash_messages("error-rqtl2")}} + + <fieldset> + <legend>Datasets</legend> + <label for="select:geno-datasets">Dataset</label> + <select id="select:geno-datasets" + name="geno-datasets" + required="required" + {%if datasets | length == 0%} + disabled="disabled" + {%endif%}> + <option value="">Select dataset</option> + {%for dset in datasets%} + <option value="{{dset['Id']}}">{{dset["Name"]}} ({{dset["FullName"]}})</option> + {%endfor%} + </select> + </fieldset> + + <fieldset> + <input type="submit" + value="select dataset" + class="btn btn-main form-col-2" /> + </fieldset> +</form> + +{%endblock%} diff --git a/qc_app/templates/rqtl2/upload-rqtl2-bundle-step-02.html b/qc_app/templates/rqtl2/upload-rqtl2-bundle-step-02.html new file mode 100644 index 0000000..9269a3c --- /dev/null +++ b/qc_app/templates/rqtl2/upload-rqtl2-bundle-step-02.html @@ -0,0 +1,36 @@ +{%extends "base.html"%} +{%from "flash_messages.html" import flash_messages%} + +{%block title%}Upload R/qtl2 Bundle{%endblock%} + +{%block contents%} +<h2 class="heading">Upload R/qtl2 Bundle</h2> + +<div class="explainer"> + <p>You have successfully uploaded the zipped bundle of R/qtl2 files.</p> + <p>The next step is to select the various extra information we need to figure + out what to do with the data. You will select/create the relevant studies + and/or datasets to organise the data in the steps that follow.</p> + <p>Click "Continue" below to proceed.</p> +</div> + +<form id="frm-upload-rqtl2-bundle" + action="{{url_for('upload.rqtl2.select_dataset_info', + species_id=species.SpeciesId, + population_id=population.InbredSetId)}}" + method="POST" + enctype="multipart/form-data"> + <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}}" /> + + {{flash_messages("error-rqtl2")}} + + <fieldset> + <input type="submit" value="continue" 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 0eca6ae..e020d5a 100644 --- a/qc_app/upload/rqtl2.py +++ b/qc_app/upload/rqtl2.py @@ -1,5 +1,6 @@ """Module to handle uploading of R/qtl2 bundles.""" from pathlib import Path +from typing import Optional from zipfile import ZipFile, is_zipfile from flask import ( @@ -7,6 +8,7 @@ from flask import ( request, url_for, redirect, + Response, Blueprint, render_template, current_app as app) @@ -14,14 +16,15 @@ from flask import ( from r_qtl import r_qtl2 from r_qtl.errors import InvalidFormat -from qc_app.files import save_file +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 import ( species_by_id, save_population, populations_by_species, - population_by_species_and_id) + population_by_species_and_id, + geno_dataset_by_species_and_population) rqtl2 = Blueprint("rqtl2", __name__) @@ -158,3 +161,69 @@ def upload_rqtl2_bundle(species_id: int, population_id: int): except (InvalidFormat, __RequestError__) as exc: flash("".join(exc.args), "alert-error alert-danger error-rqtl2") return this_page_with_errors + +def check_errors(conn, *args, **kwargs) -> Optional[Response]: + """Check for select errors in the forms and return a page to redirect to.""" + species_id = kwargs.get("species_id") or request.form.get("species_id") + population_id = (kwargs.get("population_id") + or request.form.get("population_id")) + species = species_by_id(conn, species_id) + population = population_by_species_and_id(conn, species_id, population_id) + + if "species" in args and not bool(species): + flash("Invalid species!", "alert-error error-rqtl2") + return redirect(url_for("upload.rqtl2.select_species")) + + if "population" in args and not bool(population): + flash("Invalid Population!", "alert-error error-rqtl2") + return redirect( + url_for("upload.rqtl2.select_population", pgsrc="error"), + code=307) + + if ("rqtl2_bundle_file" in args + and not bool(request.form.get("rqtl2_bundle_file"))): + flash("There is no file to process.", + "alert-error alert-danger error-rqtl2") + return redirect(url_for("upload.rqtl2.upload_rqtl2_bundle", + species_id=species_id, + population_id=population_id, + pgsrc="error"), + code=307) + + return False + +@rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" + "/rqtl2-bundle/dataset-info"), + methods=["POST"]) +def select_dataset_info(species_id: int, population_id: int): + """ + If `geno` files exist in the R/qtl2 bundle, prompt user to provide the + dataset the genotypes belong to. + """ + form = request.form + with database_connection(app.config["SQL_URI"]) as conn: + error_page = check_errors(conn, "species", "population", "rqtl2_bundle_file") + if bool(error_page): + return error_page + + thefile = fullpath(form["rqtl2_bundle_file"]) + with ZipFile(str(thefile), "r") as zfile: + cdata = r_qtl2.control_data(zfile) + if "geno" in cdata and not bool(form.get("geno_datasetid")): + return render_template( + "rqtl2/select-geno-dataset.html", + species=species_by_id(conn, species_id), + population=population_by_species_and_id( + conn, species_id, population_id), + rqtl2_bundle_file=thefile.name, + datasets=geno_dataset_by_species_and_population( + conn, species_id, population_id)) + + return "All data points collected. Should proceed to launching the job." + +@rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>" + "/rqtl2-bundle/select-geno-dataset"), + methods=["POST"]) +def select_geno_dataset(species_id: int, population_id: int) -> Response: + """Select from existing geno datasets.""" + return "IMPLEMENT THIS!!!" |