From 6222ebc5ca0fdeaac9ce7addd07ee4dd900a1afb Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Tue, 16 Jan 2024 12:48:43 +0300 Subject: UI: Create UI to select from existing genotype datasets. --- qc_app/db/__init__.py | 1 + qc_app/db/datasets.py | 16 +++++ qc_app/files.py | 5 ++ qc_app/static/css/styles.css | 7 ++- qc_app/templates/rqtl2/select-geno-dataset.html | 55 ++++++++++++++++ .../rqtl2/upload-rqtl2-bundle-step-02.html | 36 +++++++++++ qc_app/upload/rqtl2.py | 73 +++++++++++++++++++++- 7 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 qc_app/db/datasets.py create mode 100644 qc_app/templates/rqtl2/select-geno-dataset.html create mode 100644 qc_app/templates/rqtl2/upload-rqtl2-bundle-step-02.html 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%} +

Select Genotypes Dataset

+ +
+

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.

+

This is the dataset where your data will be organised under.

+
+ +
+ select from existing genotype datasets + + + + + + {{flash_messages("error-rqtl2")}} + +
+ Datasets + + +
+ +
+ +
+
+ +{%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%} +

Upload R/qtl2 Bundle

+ +
+

You have successfully uploaded the zipped bundle of R/qtl2 files.

+

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.

+

Click "Continue" below to proceed.

+
+ +
+ + + + + {{flash_messages("error-rqtl2")}} + +
+ +
+
+ +{%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//population/" + "/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//population/" + "/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!!!" -- cgit v1.2.3