about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2024-06-12 12:48:56 -0500
committerFrederick Muriuki Muriithi2024-06-12 12:48:56 -0500
commit5e96d27f3d96c84fc5a15d7040843b379b701d20 (patch)
treead283df6ef65ed66f800d79446f04b19c2d08768
parent3abf09f5d5a0ab7fb3b1ca2be981c364ef68a8cb (diff)
downloadgn-uploader-5e96d27f3d96c84fc5a15d7040843b379b701d20.tar.gz
Make URI and UI correspond to each other.
Formerly, the URI and UI were not corresponding to each other,
e.g. the URI /upload/samples/select_species would display the UI for
selecting/creating the population. This was very confusing. This
commit fixes that.

The commit also adds in user input validation to catch input errors.
-rw-r--r--qc_app/samples.py240
-rw-r--r--qc_app/templates/index.html19
-rw-r--r--qc_app/templates/samples/select-population.html6
-rw-r--r--qc_app/templates/samples/select-species.html29
-rw-r--r--qc_app/templates/samples/upload-samples.html6
5 files changed, 178 insertions, 122 deletions
diff --git a/qc_app/samples.py b/qc_app/samples.py
index e7be458..804f262 100644
--- a/qc_app/samples.py
+++ b/qc_app/samples.py
@@ -22,7 +22,7 @@ from functional_tools import take
 
 from qc_app import jobs
 from qc_app.files import save_file
-from qc_app.input_validation import is_empty_input, is_integer_input
+from qc_app.input_validation import is_integer_input
 from qc_app.db_utils import (
     with_db_connection,
     database_connection,
@@ -31,13 +31,18 @@ from qc_app.db import (
     species_by_id,
     save_population,
     population_by_id,
-    populations_by_species)
+    populations_by_species,
+    species as fetch_species)
 
 samples = Blueprint("samples", __name__)
 
-@samples.route("/upload/species", methods=["POST"])
+@samples.route("/upload/species", methods=["GET", "POST"])
 def select_species():
     """Select the species."""
+    if request.method == "GET":
+        return render_template("samples/select-species.html",
+                               species=with_db_connection(fetch_species))
+
     index_page = redirect(url_for("entry.upload_file"))
     species_id = request.form.get("species_id")
     if bool(species_id):
@@ -45,23 +50,31 @@ def select_species():
         species = with_db_connection(
             lambda conn: species_by_id(conn, species_id))
         if bool(species):
-            return render_template(
-                "samples/select-population.html",
-                species=species,
-                populations=with_db_connection(
-                    lambda conn: populations_by_species(conn, species_id)))
+            return redirect(url_for(
+                "samples.select_population", species_id=species_id))
         flash("Invalid species selected!", "alert-error")
     flash("You need to select a species", "alert-error")
     return index_page
 
-@samples.route("/upload/create-population", methods=["POST"])
-def create_population():
+@samples.route("/upload/species/<int:species_id>/create-population",
+               methods=["POST"])
+def create_population(species_id: int):
     """Create new grouping/population."""
+    if not is_integer_input(species_id):
+        flash("You did not provide a valid species. Please select one to "
+              "continue.",
+              "alert-danger")
+        return redirect(url_for("samples.select_species"))
+    species = with_db_connection(lambda conn: species_by_id(conn, species_id))
+    if not bool(species):
+        flash("Species with given ID was not found.", "alert-danger")
+        return redirect(url_for("samples.select_species"))
+
     species_page = redirect(url_for("samples.select_species"), code=307)
     with database_connection(app.config["SQL_URI"]) as conn:
-        species = species_by_id(conn, request.form.get("species_id"))
-        pop_name = request.form.get("inbredset_name").strip()
-        pop_fullname = request.form.get("inbredset_fullname").strip()
+        species = species_by_id(conn, species_id)
+        pop_name = request.form.get("inbredset_name", "").strip()
+        pop_fullname = request.form.get("inbredset_fullname", "").strip()
 
         if not bool(species):
             flash("Invalid species!", "alert-error error-create-population")
@@ -81,33 +94,50 @@ def create_population():
         })
 
         flash("Grouping/Population created successfully.", "alert-success")
-        return render_template(
-            "samples/upload-samples.html",
-            species=species,
-            population=with_db_connection(
-                lambda conn: population_by_id(conn, pop["population_id"])))
+        return redirect(url_for("samples.upload_samples",
+                                species_id=species_id,
+                                population_id=pop["population_id"]))
 
-@samples.route("/upload/select-population", methods=["POST"])
-def select_population():
+@samples.route("/upload/species/<int:species_id>/population",
+               methods=["GET", "POST"])
+def select_population(species_id: int):
     """Select from existing groupings/populations."""
-    species_page = redirect(url_for("samples.select_species"), code=307)
-    with database_connection(app.config["SQL_URI"]) as conn:
-        species = species_by_id(conn, request.form.get("species_id"))
-        pop_id = int(request.form.get("inbredset_id"))
-        population = with_db_connection(lambda conn: population_by_id(conn, pop_id))
-
+    if not is_integer_input(species_id):
+        flash("You did not provide a valid species. Please select one to "
+              "continue.",
+              "alert-danger")
+        return redirect(url_for("samples.select_species"))
+    species = with_db_connection(lambda conn: species_by_id(conn, species_id))
     if not bool(species):
-        flash("Invalid species!", "alert-error error-select-population")
-        return species_page
+        flash("Species with given ID was not found.", "alert-danger")
+        return redirect(url_for("samples.select_species"))
 
+    if request.method == "GET":
+        return render_template(
+            "samples/select-population.html",
+            species=species,
+            populations=with_db_connection(
+                lambda conn: populations_by_species(conn, species_id)))
+
+    population_page = redirect(url_for(
+        "samples.select_population", species_id=species_id), code=307)
+    _population_id = request.form.get("inbredset_id")
+    if not is_integer_input(_population_id):
+        flash("You did not provide a valid population. Please select one to "
+              "continue.",
+              "alert-danger")
+        return population_page
+    population = with_db_connection(
+        lambda conn: population_by_id(conn, _population_id))
     if not bool(population):
         flash("Invalid grouping/population!",
               "alert-error error-select-population")
-        return species_page
+        return population_page
 
-    return render_template("samples/upload-samples.html",
-                           species=species,
-                           population=population)
+    return redirect(url_for("samples.upload_samples",
+                            species_id=species_id,
+                            population_id=_population_id),
+                    code=307)
 
 def read_samples_file(filepath, separator: str, firstlineheading: bool, **kwargs) -> Iterator[dict]:
     """Read the samples file."""
@@ -201,77 +231,83 @@ def build_sample_upload_job(# pylint: disable=[too-many-arguments]
         f"--quotechar={quotechar}"
     ] + (["--firstlineheading"] if firstlineheading else [])
 
-@samples.route("/upload/samples", methods=["POST"])
-def upload_samples():
+@samples.route("/upload/species/<int:species_id>/populations/<int:population_id>/samples",
+               methods=["GET", "POST"])
+def upload_samples(species_id: int, population_id: int):#pylint: disable=[too-many-return-statements]
     """Upload the samples."""
-    samples_uploads_page = redirect(url_for("samples.select_population"),
-                                    code=307)
-
-    with database_connection(app.config["SQL_URI"]) as conn:
-        _speciesid = request.form.get("species_id")
-        if is_integer_input(_speciesid):
-            flash("You did not provide a valid species. Please select one to "
-                  "continue.",
-                  "alert-danger")
-            return redirect(url_for("entry.upload_file"))
-        species = species_by_id(conn, _speciesid)
-        if not bool(species):
-            flash("Invalid species!", "alert-error")
-            return samples_uploads_page
-
-        _population_id = request.form.get("inbredset_id")
-        if not is_integer_input(_population_id):
-            flash("You did not provide a valid population. Please select one "
-                  "to continue.",
-                  "alert-danger")
-            return redirect("samples.select_species", code=307)
-        population = with_db_connection(
-            lambda conn: population_by_id(
-                conn, int(_population_id)))
-        if not bool(population):
-            flash("Invalid grouping/population!", "alert-error")
-            return samples_uploads_page
-
-        try:
-            samples_file = save_file(request.files["samples_file"],
-                                     Path(app.config["UPLOAD_FOLDER"]))
-        except AssertionError:
-            flash("You need to provide a file with the samples data.",
-                  "alert-error")
-            return samples_uploads_page
-
-        firstlineheading = (request.form.get("first_line_heading") == "on")
-
-        separator = request.form.get("separator")
-        if separator == "other":
-            separator = request.form.get("other_separator")
-        if not bool(separator):
-            flash("You need to provide a separator character.", "alert-error")
-            return samples_uploads_page
-
-        quotechar = (request.form.get("field_delimiter", '"') or '"')
-
-        redisuri = app.config["REDIS_URL"]
-        with Redis.from_url(redisuri, decode_responses=True) as rconn:
-            the_job = jobs.launch_job(
-                jobs.initialise_job(
-                    rconn,
-                    jobs.jobsnamespace(),
-                    str(uuid.uuid4()),
-                    build_sample_upload_job(
-                        species["SpeciesId"],
-                        population["InbredSetId"],
-                        samples_file,
-                        separator,
-                        firstlineheading,
-                        quotechar),
-                    "samples_upload",
-                    app.config["JOBS_TTL_SECONDS"],
-                    {"job_name": f"Samples Upload: {samples_file.name}"}),
-                redisuri,
-                f"{app.config['UPLOAD_FOLDER']}/job_errors")
-            return redirect(url_for(
-                "samples.upload_status", job_id=the_job["jobid"]))
+    samples_uploads_page = redirect(url_for("samples.upload_samples",
+                                            species_id=species_id,
+                                            population_id=population_id))
+    if not is_integer_input(species_id):
+        flash("You did not provide a valid species. Please select one to "
+              "continue.",
+              "alert-danger")
+        return redirect(url_for("samples.select_species"))
+    species = with_db_connection(lambda conn: species_by_id(conn, species_id))
+    if not bool(species):
+        flash("Species with given ID was not found.", "alert-danger")
+        return redirect(url_for("samples.select_species"))
+
+    if not is_integer_input(population_id):
+        flash("You did not provide a valid population. Please select one "
+              "to continue.",
+              "alert-danger")
+        return redirect(url_for("samples.select_population",
+                                species_id=species_id),
+                        code=307)
+    population = with_db_connection(
+        lambda conn: population_by_id(conn, int(population_id)))
+    if not bool(population):
+        flash("Invalid grouping/population!", "alert-error")
+        return redirect(url_for("samples.select_population",
+                                species_id=species_id),
+                        code=307)
+
+    if request.method == "GET" or request.files.get("samples_file") is None:
+        return render_template("samples/upload-samples.html",
+                               species=species,
+                               population=population)
+
+    try:
+        samples_file = save_file(request.files["samples_file"],
+                                 Path(app.config["UPLOAD_FOLDER"]))
+    except AssertionError:
+        flash("You need to provide a file with the samples data.",
+              "alert-error")
+        return samples_uploads_page
+
+    firstlineheading = (request.form.get("first_line_heading") == "on")
+
+    separator = request.form.get("separator", ",")
+    if separator == "other":
+        separator = request.form.get("other_separator", ",")
+    if not bool(separator):
+        flash("You need to provide a separator character.", "alert-error")
+        return samples_uploads_page
+
+    quotechar = (request.form.get("field_delimiter", '"') or '"')
+
+    redisuri = app.config["REDIS_URL"]
+    with Redis.from_url(redisuri, decode_responses=True) as rconn:
+        the_job = jobs.launch_job(
+            jobs.initialise_job(
+                rconn,
+                jobs.jobsnamespace(),
+                str(uuid.uuid4()),
+                build_sample_upload_job(
+                    species["SpeciesId"],
+                    population["InbredSetId"],
+                    samples_file,
+                    separator,
+                    firstlineheading,
+                    quotechar),
+                "samples_upload",
+                app.config["JOBS_TTL_SECONDS"],
+                {"job_name": f"Samples Upload: {samples_file.name}"}),
+            redisuri,
+            f"{app.config['UPLOAD_FOLDER']}/job_errors")
+        return redirect(url_for(
+            "samples.upload_status", job_id=the_job["jobid"]))
 
 @samples.route("/upload/status/<uuid:job_id>", methods=["GET"])
 def upload_status(job_id: uuid.UUID):
diff --git a/qc_app/templates/index.html b/qc_app/templates/index.html
index 588133a..6e98849 100644
--- a/qc_app/templates/index.html
+++ b/qc_app/templates/index.html
@@ -114,22 +114,9 @@
   <p>This section gives you the opportunity to upload any missing samples</p>
 </div>
 
-<form method="POST" action="{{url_for('samples.select_species')}}">
-  <legend class="heading">upload samples</legend>
-  <fieldset>
-    <label for="select_species02">Species</label>
-    <select id="select_species02" name="species_id" required="required">
-      <option value="">Select species</option>
-      {%for spec in species%}
-      <option value="{{spec.SpeciesId}}">{{spec.MenuName}}</option>
-      {%endfor%}
-    </select>
-  </fieldset>
-
-  <fieldset>
-    <input type="submit" value="submit" class="btn btn-main form-col-2" />
-  </fieldset>
-</form>
+<a href={{url_for("samples.select_species")}}
+   title="Upload samples/cases/individuals for your data">
+  upload Samples/Cases</a>
 
 {%endblock%}
 
diff --git a/qc_app/templates/samples/select-population.html b/qc_app/templates/samples/select-population.html
index 24decb4..4bb4cc1 100644
--- a/qc_app/templates/samples/select-population.html
+++ b/qc_app/templates/samples/select-population.html
@@ -15,7 +15,8 @@
 
 <hr />
 
-<form method="POST" action="{{url_for('samples.select_population')}}">
+<form method="POST" action="{{url_for('samples.select_population',
+                            species_id=species.SpeciesId)}}">
   <legend class="heading">select grouping/population</legend>
   {{flash_messages("error-select-population")}}
 
@@ -43,7 +44,8 @@
 
 <p style="color:#FE3535; padding-left:20em; font-weight:bolder;">OR</p>
 
-<form method="POST" action="{{url_for('samples.create_population')}}">
+<form method="POST" action="{{url_for('samples.create_population',
+                            species_id=species.SpeciesId)}}">
   <legend class="heading">create new grouping/population</legend>
   {{flash_messages("error-create-population")}}
 
diff --git a/qc_app/templates/samples/select-species.html b/qc_app/templates/samples/select-species.html
new file mode 100644
index 0000000..6aa4781
--- /dev/null
+++ b/qc_app/templates/samples/select-species.html
@@ -0,0 +1,29 @@
+{%extends "base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}Select Grouping/Population{%endblock%}
+
+{%block contents%}
+<h2 class="heading">upload samples/cases</h2>
+
+<p>We need to know what species your data belongs to.</p>
+
+{{flash_all_messages()}}
+
+<form method="POST" action="{{url_for('samples.select_species')}}">
+  <legend class="heading">upload samples</legend>
+  <fieldset>
+    <label for="select_species02">Species</label>
+    <select id="select_species02" name="species_id" required="required">
+      <option value="">Select species</option>
+      {%for spec in species%}
+      <option value="{{spec.SpeciesId}}">{{spec.MenuName}}</option>
+      {%endfor%}
+    </select>
+  </fieldset>
+
+  <fieldset>
+    <input type="submit" value="submit" class="btn btn-main form-col-2" />
+  </fieldset>
+</form>
+{%endblock%}
diff --git a/qc_app/templates/samples/upload-samples.html b/qc_app/templates/samples/upload-samples.html
index 5d1ec4c..d04df61 100644
--- a/qc_app/templates/samples/upload-samples.html
+++ b/qc_app/templates/samples/upload-samples.html
@@ -40,7 +40,9 @@
 
 <form id="form-samples"
       method="POST"
-      action="{{url_for('samples.upload_samples')}}"
+      action="{{url_for('samples.upload_samples',
+              species_id=species.SpeciesId,
+              population_id=population.InbredSetId)}}"
       enctype="multipart/form-data">
   <legend class="heading">upload samples</legend>
   <fieldset>
@@ -69,7 +71,7 @@
 	    name="separator"
 	    required="required"
 	    class="form-col-2">
-      <option value="">Select separator for your file</option>
+      <option value="">Select separator for your file: (default is comma)</option>
       <option value="&#x0009;">TAB</option>
       <option value="&#x0020;">Space</option>
       <option value=",">Comma</option>