"""Views dealing with populations/inbredsets""" import re import json import base64 from flask import (flash, request, url_for, redirect, Blueprint, current_app as app) from uploader.ui import make_template_renderer from uploader.authorisation import require_login from uploader.db_utils import database_connection from uploader.samples.views import samplesbp from uploader.species.models import (all_species, species_by_id, order_species_by_family) from .models import (save_population, population_families, populations_by_species, population_genetic_types, population_by_species_and_id) __active_link__ = "populations" popbp = Blueprint("populations", __name__) popbp.register_blueprint(samplesbp, url_prefix="/") render_template = make_template_renderer("populations") @popbp.route("/populations", methods=["GET", "POST"]) @require_login def index(): """Entry point for populations.""" with database_connection(app.config["SQL_URI"]) as conn: if not bool(request.args.get("species_id")): return render_template( "populations/index.html", species=order_species_by_family(all_species(conn))) species = species_by_id(conn, request.args.get("species_id")) if not bool(species): flash("Invalid species identifier provided!", "alert-danger") return redirect(url_for("species.populations.index")) return redirect(url_for("species.populations.list_species_populations", species_id=species["SpeciesId"])) @popbp.route("//populations", methods=["GET"]) @require_login def list_species_populations(species_id: int): """List a particular species' populations.""" with database_connection(app.config["SQL_URI"]) as conn: species = species_by_id(conn, species_id) if not bool(species): flash("No species was found for given ID.", "alert-danger") return redirect(url_for("species.populations.index")) return render_template( "populations/list-populations.html", species=species, populations=populations_by_species(conn, species_id), activelink="list-populations") def valid_population_name(population_name: str) -> bool: """ Check whether the given name is a valid population name. Parameters ---------- population_name: a string of characters. Checks For ---------- * The name MUST start with an alphabet [a-zA-Z] * The name MUST end with an alphabet [a-zA-Z] or number [0-9] * The name MUST be composed of alphabets [a-zA-Z], numbers [0-9], underscores (_) and/or hyphens (-). Returns ------- Boolean indicating whether or not the name is valid. """ pattern = re.compile(r"^[a-zA-Z]+[a-zA-Z0-9_-]*[a-zA-Z0-9]$") return bool(pattern.match(population_name)) @popbp.route("//populations/create", methods=["GET", "POST"]) @require_login def create_population(species_id: int): """Create a new population.""" with database_connection(app.config["SQL_URI"]) as conn: species = species_by_id(conn, species_id) if request.method == "GET": error_values = request.args.get("error_values") if not bool(error_values): error_values = base64.b64encode( '{"errors":{}, "error_values": {}}'.encode("utf8") ).decode("utf8") error_values = json.loads(base64.b64decode( error_values.encode("utf8")).decode("utf8")) return render_template( "populations/create-population.html", species=species, families = population_families(conn), genetic_types = population_genetic_types(conn), mapping_methods=( {"id": "0", "value": "No mapping support"}, {"id": "1", "value": "GEMMA, QTLReaper, R/qtl"}, {"id": "2", "value": "GEMMA"}, {"id": "3", "value": "R/qtl"}, {"id": "4", "value": "GEMMA, PLINK"}), activelink="create-population", **error_values) if not bool(species): flash("You must select a species.", "alert-danger") return redirect(url_for("species.populations.index")) errors = tuple() population_name = (request.form.get( "population_name") or "").strip() if not bool(population_name): errors = errors + (("population_name", "You must provide a name for the population!"),) if not valid_population_name(population_name): errors = errors + (( "population_name", "The population name can only contain letters, numbers, " "hyphens and underscores."),) population_fullname = (request.form.get( "population_fullname") or "").strip() if not bool(population_fullname): errors = errors + ( ("population_fullname", "Full Name MUST be provided."),) if bool(errors): values = base64.b64encode( json.dumps({ "errors": dict(errors), "error_values": dict(request.form) }).encode("utf8")) return redirect(url_for("species.populations.create_population", species_id=species["SpeciesId"], error_values=values)) new_population = save_population(conn, { "SpeciesId": species["SpeciesId"], "Name": population_name, "InbredSetName": population_fullname, "FullName": population_fullname, "InbredSetCode": request.form.get("population_code") or None, "Description": request.form.get("population_description") or None, "Family": request.form.get("population_family") or None, "MappingMethodId": request.form.get("population_mapping_method_id"), "GeneticType": request.form.get("population_genetic_type") or None }) return redirect(url_for("species.populations.view_population", species_id=species["SpeciesId"], population_id=new_population["InbredSetId"])) @popbp.route("//populations/", methods=["GET"]) @require_login def view_population(species_id: int, population_id: int): """View the details of a population.""" with database_connection(app.config["SQL_URI"]) as conn: species = species_by_id(conn, species_id) population = population_by_species_and_id(conn, species_id, population_id) error = False if not bool(species): flash("You must select a species.", "alert-danger") error = True if not bool(population): flash("You must select a population.", "alert-danger") error = True if error: return redirect(url_for("species.populations.index")) return render_template("populations/view-population.html", species=species, population=population, activelink="view-population")