about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2024-01-16 12:48:43 +0300
committerFrederick Muriuki Muriithi2024-01-16 12:53:46 +0300
commit6222ebc5ca0fdeaac9ce7addd07ee4dd900a1afb (patch)
tree7fc9add59eaf6d505a8dcfcc20b5af9c11bb211b
parentd809997e8ca76d2441b58693078c6c9698e769bb (diff)
downloadgn-uploader-6222ebc5ca0fdeaac9ce7addd07ee4dd900a1afb.tar.gz
UI: Create UI to select from existing genotype datasets.
-rw-r--r--qc_app/db/__init__.py1
-rw-r--r--qc_app/db/datasets.py16
-rw-r--r--qc_app/files.py5
-rw-r--r--qc_app/static/css/styles.css7
-rw-r--r--qc_app/templates/rqtl2/select-geno-dataset.html55
-rw-r--r--qc_app/templates/rqtl2/upload-rqtl2-bundle-step-02.html36
-rw-r--r--qc_app/upload/rqtl2.py73
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!!!"