about summary refs log tree commit diff
path: root/uploader/samples/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'uploader/samples/views.py')
-rw-r--r--uploader/samples/views.py211
1 files changed, 96 insertions, 115 deletions
diff --git a/uploader/samples/views.py b/uploader/samples/views.py
index 6e3dc4b..4705a96 100644
--- a/uploader/samples/views.py
+++ b/uploader/samples/views.py
@@ -3,50 +3,51 @@ import os
 import sys
 import uuid
 from pathlib import Path
-from typing import Iterator
 
-import MySQLdb as mdb
 from redis import Redis
-from MySQLdb.cursors import DictCursor
-from flask import (
-    flash,
-    request,
-    url_for,
-    redirect,
-    Blueprint,
-    render_template,
-    current_app as app)
+from flask import (flash,
+                   request,
+                   url_for,
+                   redirect,
+                   Blueprint,
+                   current_app as app)
 
 from uploader import jobs
 from uploader.files import save_file
-from uploader.datautils import order_by_family
+from uploader.ui import make_template_renderer
 from uploader.authorisation import require_login
 from uploader.input_validation import is_integer_input
-from uploader.db_utils import (
-    with_db_connection,
-    database_connection,
-    with_redis_connection)
-from uploader.species.models import (all_species,
-                                     species_by_id,
-                                     order_species_by_family)
-from uploader.population.models import(save_population,
-                                       population_by_id,
-                                       populations_by_species,
-                                       population_by_species_and_id)
+from uploader.population.models import population_by_id
+from uploader.route_utils import generic_select_population
+from uploader.datautils import safe_int, enumerate_sequence
+from uploader.species.models import all_species, species_by_id
+from uploader.request_checks import with_species, with_population
+from uploader.db_utils import (with_db_connection,
+                               database_connection,
+                               with_redis_connection)
 
 from .models import samples_by_species_and_population
 
 samplesbp = Blueprint("samples", __name__)
+render_template = make_template_renderer("samples")
 
 @samplesbp.route("/samples", methods=["GET"])
+@require_login
 def index():
     """Direct entry-point for uploading/handling the samples."""
     with database_connection(app.config["SQL_URI"]) as conn:
         if not bool(request.args.get("species_id")):
             return render_template(
                 "samples/index.html",
-                species=order_species_by_family(all_species(conn)),
+                species=all_species(conn),
                 activelink="samples")
+
+        species_id = request.args.get("species_id")
+        if species_id == "CREATE-SPECIES":
+            return redirect(url_for(
+                "species.create_species",
+                return_to="species.populations.samples.select_population"))
+
         species = species_by_id(conn, request.args.get("species_id"))
         if not bool(species):
             flash("No such species!", "alert-danger")
@@ -56,61 +57,35 @@ def index():
 
 
 @samplesbp.route("<int:species_id>/samples/select-population", methods=["GET"])
-def select_population(species_id: int):
+@require_login
+@with_species(redirect_uri="species.populations.samples.index")
+def select_population(species: dict, **kwargs):# pylint: disable=[unused-argument]
     """Select the population to use for the samples."""
-    with database_connection(app.config["SQL_URI"]) as conn:
-        species = species_by_id(conn, species_id)
-        if not bool(species):
-            flash("Invalid species!", "alert-danger")
-            return redirect(url_for("species.populations.samples.index"))
-
-        if not bool(request.args.get("population_id")):
-            return render_template("samples/select-population.html",
-                                   species=species,
-                                   populations=order_by_family(
-                                       populations_by_species(
-                                           conn,
-                                           species_id),
-                                       order_key="FamilyOrder"),
-                                   activelink="samples")
-
-        population = population_by_id(conn, request.args.get("population_id"))
-        if not bool(population):
-            flash("Population not found!", "alert-danger")
-            return redirect(url_for(
-                "species.populations.samples.select_population",
-                species_id=species_id))
-
-        return redirect(url_for("species.populations.samples.list_samples",
-                                species_id=species_id,
-                                population_id=population["Id"]))
+    return generic_select_population(
+        species,
+        "samples/select-population.html",
+        request.args.get("population_id") or "",
+        "species.populations.samples.select_population",
+        "species.populations.samples.list_samples",
+        "samples",
+        "Population not found!")
 
 @samplesbp.route("<int:species_id>/populations/<int:population_id>/samples")
-def list_samples(species_id: int, population_id: int):
+@require_login
+@with_population(
+    species_redirect_uri="species.populations.samples.index",
+    redirect_uri="species.populations.samples.select_population")
+def list_samples(species: dict, population: dict, **kwargs):# pylint: disable=[unused-argument]
     """
     List the samples in a particular population and give the ability to upload
     new ones.
     """
     with database_connection(app.config["SQL_URI"]) as conn:
-        species = species_by_id(conn, species_id)
-        if not bool(species):
-            flash("Invalid species!", "alert-danger")
-            return redirect(url_for("species.populations.samples.index"))
-
-        population = population_by_id(conn, population_id)
-        if not bool(population):
-            flash("Population not found!", "alert-danger")
-            return redirect(url_for(
-                "species.populations.samples.select_population",
-                species_id=species_id))
-
-        all_samples = samples_by_species_and_population(
-            conn, species_id, population_id)
+        all_samples = enumerate_sequence(samples_by_species_and_population(
+            conn, species["SpeciesId"], population["Id"]))
         total_samples = len(all_samples)
-        offset = int(request.args.get("from") or 0)
-        if offset < 0:
-            offset = 0
-        count = int(request.args.get("count") or 10)
+        offset = max(safe_int(request.args.get("from") or 0), 0)
+        count = int(request.args.get("count") or 20)
         return render_template("samples/list-samples.html",
                                species=species,
                                population=population,
@@ -121,7 +96,7 @@ def list_samples(species_id: int, population_id: int):
                                activelink="list-samples")
 
 
-def build_sample_upload_job(# pylint: disable=[too-many-arguments]
+def build_sample_upload_job(# pylint: disable=[too-many-arguments, too-many-positional-arguments]
         speciesid: int,
         populationid: int,
         samplesfile: Path,
@@ -184,7 +159,7 @@ def upload_samples(species_id: int, population_id: int):#pylint: disable=[too-ma
               "alert-error")
         return samples_uploads_page
 
-    firstlineheading = (request.form.get("first_line_heading") == "on")
+    firstlineheading = request.form.get("first_line_heading") == "on"
 
     separator = request.form.get("separator", ",")
     if separator == "other":
@@ -197,6 +172,11 @@ def upload_samples(species_id: int, population_id: int):#pylint: disable=[too-ma
 
     redisuri = app.config["REDIS_URL"]
     with Redis.from_url(redisuri, decode_responses=True) as rconn:
+        #T0DO: Add a QC step here — what do we check?
+        # 1. Does any sample in the uploaded file exist within the database?
+        #    If yes, what is/are its/their species and population?
+        # 2. If yes 1. above, provide error with notes on which species and
+        #    populations already own the samples.
         the_job = jobs.launch_job(
             jobs.initialise_job(
                 rconn,
@@ -224,56 +204,54 @@ def upload_samples(species_id: int, population_id: int):#pylint: disable=[too-ma
 @samplesbp.route("<int:species_id>/populations/<int:population_id>/"
                  "upload-samples/status/<uuid:job_id>",
                  methods=["GET"])
-def upload_status(species_id: int, population_id: int, job_id: uuid.UUID):
+@require_login
+@with_population(species_redirect_uri="species.populations.samples.index",
+                 redirect_uri="species.populations.samples.select_population")
+def upload_status(species: dict, population: dict, job_id: uuid.UUID, **kwargs):# pylint: disable=[unused-argument]
     """Check on the status of a samples upload job."""
-    with database_connection(app.config["SQL_URI"]) as conn:
-        species = species_by_id(conn, species_id)
-        if not bool(species):
-            flash("You must provide a valid species.", "alert-danger")
-            return redirect(url_for("species.populations.samples.index"))
+    job = with_redis_connection(lambda rconn: jobs.job(
+        rconn, jobs.jobsnamespace(), job_id))
+    if job:
+        status = job["status"]
+        if status == "success":
+            return render_template("samples/upload-success.html",
+                                   job=job,
+                                   species=species,
+                                   population=population,)
 
-        population = population_by_species_and_id(
-            conn, species_id, population_id)
-        if not bool(population):
-            flash("You must provide a valid population.", "alert-danger")
+        if status == "error":
             return redirect(url_for(
-                "species.populations.samples.select_population",
-                species_id=species_id))
-
-        job = with_redis_connection(lambda rconn: jobs.job(
-            rconn, jobs.jobsnamespace(), job_id))
-        if job:
-            status = job["status"]
-            if status == "success":
-                return render_template("samples/upload-success.html",
-                                       job=job,
-                                       species=species,
-                                       population=population,)
-
-            if status == "error":
+                "species.populations.samples.upload_failure",
+                species_id=species["SpeciesId"],
+                population_id=population["Id"],
+                job_id=job_id))
+
+        error_filename = Path(jobs.error_filename(
+            job_id, f"{app.config['UPLOAD_FOLDER']}/job_errors"))
+        if error_filename.exists():
+            stat = os.stat(error_filename)
+            if stat.st_size > 0:
                 return redirect(url_for(
-                    "species.populations.samples.upload_failure", job_id=job_id))
+                    "samples.upload_failure", job_id=job_id))
 
-            error_filename = Path(jobs.error_filename(
-                job_id, f"{app.config['UPLOAD_FOLDER']}/job_errors"))
-            if error_filename.exists():
-                stat = os.stat(error_filename)
-                if stat.st_size > 0:
-                    return redirect(url_for(
-                        "samples.upload_failure", job_id=job_id))
+        return render_template("samples/upload-progress.html",
+                               species=species,
+                               population=population,
+                               job=job) # maybe also handle this?
 
-            return render_template("samples/upload-progress.html",
-                                   species=species,
-                                   population=population,
-                                   job=job) # maybe also handle this?
+    return render_template("no_such_job.html",
+                           job_id=job_id,
+                           species=species,
+                           population=population), 400
 
-        return render_template("no_such_job.html",
-                               job_id=job_id,
-                               species=species,
-                               population=population), 400
 
-@samplesbp.route("/upload/failure/<uuid:job_id>", methods=["GET"])
-def upload_failure(job_id: uuid.UUID):
+@samplesbp.route("<int:species_id>/populations/<int:population_id>/"
+                 "upload-samples/failure/<uuid:job_id>",
+                 methods=["GET"])
+@require_login
+@with_population(species_redirect_uri="species.populations.samples.index",
+                 redirect_uri="species.populations.samples.select_population")
+def upload_failure(species: dict, population: dict, job_id: uuid.UUID, **kwargs):# pylint: disable=[unused-argument]
     """Display the errors of the samples upload failure."""
     job = with_redis_connection(lambda rconn: jobs.job(
         rconn, jobs.jobsnamespace(), job_id))
@@ -287,4 +265,7 @@ def upload_failure(job_id: uuid.UUID):
         if stat.st_size > 0:
             return render_template("worker_failure.html", job_id=job_id)
 
-    return render_template("samples/upload-failure.html", job=job)
+    return render_template("samples/upload-failure.html",
+                           species=species,
+                           population=population,
+                           job=job)