From 5e96d27f3d96c84fc5a15d7040843b379b701d20 Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Wed, 12 Jun 2024 12:48:56 -0500 Subject: 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. --- qc_app/samples.py | 240 ++++++++++++++---------- qc_app/templates/index.html | 19 +- qc_app/templates/samples/select-population.html | 6 +- qc_app/templates/samples/select-species.html | 29 +++ qc_app/templates/samples/upload-samples.html | 6 +- 5 files changed, 178 insertions(+), 122 deletions(-) create mode 100644 qc_app/templates/samples/select-species.html 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//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//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//populations//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/", 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 @@

This section gives you the opportunity to upload any missing samples

-
- upload samples -
- - -
- -
- -
-
+ + upload Samples/Cases {%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 @@
-
+ select grouping/population {{flash_messages("error-select-population")}} @@ -43,7 +44,8 @@

OR

- + create new grouping/population {{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%} +

upload samples/cases

+ +

We need to know what species your data belongs to.

+ +{{flash_all_messages()}} + + + upload samples +
+ + +
+ +
+ +
+
+{%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 @@
upload samples
@@ -69,7 +71,7 @@ name="separator" required="required" class="form-col-2"> - + -- cgit v1.2.3