From e49b4367f1dab1c3acb3cd5d71ba09359c5ab4ee Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Thu, 29 Aug 2024 15:21:27 -0500 Subject: Initialise package for dealing with Species. --- uploader/__init__.py | 2 + uploader/db/species.py | 22 ----- uploader/dbinsert.py | 1 + uploader/expression_data/index.py | 2 +- uploader/expression_data/rqtl2.py | 1 + uploader/expression_data/samples.py | 1 + uploader/species/__init__.py | 2 + uploader/species/models.py | 22 +++++ uploader/species/views.py | 91 +++++++++++++++++++ uploader/templates/base.html | 10 ++- uploader/templates/species/base.html | 11 +++ uploader/templates/species/create-species.html | 116 +++++++++++++++++++++++++ uploader/templates/species/list-species.html | 68 +++++++++++++++ 13 files changed, 325 insertions(+), 24 deletions(-) delete mode 100644 uploader/db/species.py create mode 100644 uploader/species/__init__.py create mode 100644 uploader/species/models.py create mode 100644 uploader/species/views.py create mode 100644 uploader/templates/species/base.html create mode 100644 uploader/templates/species/create-species.html create mode 100644 uploader/templates/species/list-species.html (limited to 'uploader') diff --git a/uploader/__init__.py b/uploader/__init__.py index 941765d..347f170 100644 --- a/uploader/__init__.py +++ b/uploader/__init__.py @@ -10,6 +10,7 @@ from flask_session import Session from uploader.oauth2.client import user_logged_in, authserver_authorise_uri from .base_routes import base +from .species import speciesbp from .dbinsert import dbinsertbp from .oauth2.views import oauth2 from .expression_data import exprdatabp @@ -81,6 +82,7 @@ def create_app(): # setup blueprints app.register_blueprint(base, url_prefix="/") app.register_blueprint(oauth2, url_prefix="/oauth2") + app.register_blueprint(speciesbp, url_prefix="/species") app.register_blueprint(dbinsertbp, url_prefix="/dbinsert") app.register_blueprint(exprdatabp, url_prefix="/expression-data") diff --git a/uploader/db/species.py b/uploader/db/species.py deleted file mode 100644 index 653e59b..0000000 --- a/uploader/db/species.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Database functions for species.""" -import MySQLdb as mdb -from MySQLdb.cursors import DictCursor - -def species(conn: mdb.Connection) -> tuple: - "Retrieve the species from the database." - with conn.cursor(cursorclass=DictCursor) as cursor: - cursor.execute( - "SELECT SpeciesId, SpeciesName, LOWER(Name) AS Name, MenuName, " - "FullName FROM Species") - return tuple(cursor.fetchall()) - - return tuple() - -def species_by_id(conn: mdb.Connection, speciesid) -> dict: - "Retrieve the species from the database by id." - with conn.cursor(cursorclass=DictCursor) as cursor: - cursor.execute( - "SELECT SpeciesId, SpeciesName, LOWER(Name) AS Name, MenuName, " - "FullName FROM Species WHERE SpeciesId=%s", - (speciesid,)) - return cursor.fetchone() diff --git a/uploader/dbinsert.py b/uploader/dbinsert.py index 559dc5e..328ccc8 100644 --- a/uploader/dbinsert.py +++ b/uploader/dbinsert.py @@ -14,6 +14,7 @@ from flask import ( from uploader.authorisation import require_login from uploader.db_utils import with_db_connection, database_connection from uploader.db import species, species_by_id, populations_by_species +from uploader.species.models import species_by_id, all_species as species from . import jobs diff --git a/uploader/expression_data/index.py b/uploader/expression_data/index.py index 799989e..db23136 100644 --- a/uploader/expression_data/index.py +++ b/uploader/expression_data/index.py @@ -14,7 +14,7 @@ from flask import ( render_template, current_app as app) -from uploader.db import species +from uploader.species.models import all_species as species from uploader.authorisation import require_login from uploader.db_utils import with_db_connection diff --git a/uploader/expression_data/rqtl2.py b/uploader/expression_data/rqtl2.py index 48df66c..975fc1f 100644 --- a/uploader/expression_data/rqtl2.py +++ b/uploader/expression_data/rqtl2.py @@ -41,6 +41,7 @@ from uploader.db import ( save_population, populations_by_species, population_by_species_and_id,) +from uploader.species.models import species_by_id from uploader.db.datasets import ( geno_dataset_by_id, geno_datasets_by_species_and_population, diff --git a/uploader/expression_data/samples.py b/uploader/expression_data/samples.py index 95b9b73..0715d14 100644 --- a/uploader/expression_data/samples.py +++ b/uploader/expression_data/samples.py @@ -34,6 +34,7 @@ from uploader.db import ( population_by_id, populations_by_species, species as fetch_species) +from uploader.species.models import species_by_id, all_species as fetch_species samples = Blueprint("samples", __name__) diff --git a/uploader/species/__init__.py b/uploader/species/__init__.py new file mode 100644 index 0000000..83f2165 --- /dev/null +++ b/uploader/species/__init__.py @@ -0,0 +1,2 @@ +"""Package to handle creation and management of species.""" +from .views import speciesbp diff --git a/uploader/species/models.py b/uploader/species/models.py new file mode 100644 index 0000000..98337a3 --- /dev/null +++ b/uploader/species/models.py @@ -0,0 +1,22 @@ +"""Database functions for species.""" +import MySQLdb as mdb +from MySQLdb.cursors import DictCursor + +def all_species(conn: mdb.Connection) -> tuple: + "Retrieve the species from the database." + with conn.cursor(cursorclass=DictCursor) as cursor: + cursor.execute( + "SELECT SpeciesId, SpeciesName, LOWER(Name) AS Name, MenuName, " + "FullName, TaxonomyId FROM Species") + return tuple(cursor.fetchall()) + + return tuple() + +def species_by_id(conn: mdb.Connection, speciesid) -> dict: + "Retrieve the species from the database by id." + with conn.cursor(cursorclass=DictCursor) as cursor: + cursor.execute( + "SELECT SpeciesId, SpeciesName, LOWER(Name) AS Name, MenuName, " + "FullName FROM Species WHERE SpeciesId=%s", + (speciesid,)) + return cursor.fetchone() diff --git a/uploader/species/views.py b/uploader/species/views.py new file mode 100644 index 0000000..af66de3 --- /dev/null +++ b/uploader/species/views.py @@ -0,0 +1,91 @@ +"""Endpoints handling species.""" +from flask import (flash, + request, + url_for, + redirect, + Blueprint, + current_app as app, + render_template as flask_render_template) + +from uploader.db_utils import database_connection + +from .models import all_species, save_species, species_by_id + + +speciesbp = Blueprint("species", __name__) + + +def render_template(template, **kwargs): + """Render template for species.""" + return flask_render_template(template, **kwargs, activelink="species") + + +@speciesbp.route("/", methods=["GET"]) +def list_species(): + """List and display all the species in the database.""" + with database_connection(app.config["SQL_URI"]) as conn: + return render_template("species/list-species.html", + allspecies=all_species(conn)) + +@speciesbp.route("/", methods=["GET"]) +def view_species(species_id: int): + """View details of a particular species and menus to act upon it.""" + with database_connection(app.config["SQL_URI"]) as conn: + return species_by_id(conn, species_id) + +@speciesbp.route("/create", methods=["GET", "POST"]) +def create_species(): + """Create a new species.""" + # We can use uniprot's API to fetch the details with something like + # https://rest.uniprot.org/taxonomy/ e.g. + # https://rest.uniprot.org/taxonomy/6239 + if request.method == "GET": + return render_template("species/create-species.html") + + with (database_connection(app.config["SQL_URI"]) as conn, + conn.cursor() as cursor): + error = False + taxon_id = request.form.get("taxon_id", "").strip() or None + + common_name = request.form.get("common_name", "").strip() + if not bool(common_name): + flash("The common species name MUST be provided.", "alert-danger") + error = True + + scientific_name = request.form.get("scientific_name", "").strip() + if not bool(scientific_name): + flash("The species' scientific name MUST be provided.", + "alert-danger") + error = True + + parts = tuple(name.strip() for name in scientific_name.split(" ")) + if len(parts) != 2 or not all(bool(name) for name in parts): + flash("The scientific name you provided is invalid.", "alert-danger") + error = True + + cursor.execute( + "SELECT * FROM Species WHERE FullName=%s", (scientific_name,)) + res = cursor.fetchone() + if bool(res): + flash("A species already exists with the provided scientific name.", + "alert-danger") + error = True + + if bool(taxon_id): + cursor.execute( + "SELECT * FROM Species WHERE TaxonomyId=%s", (taxon_id,)) + res = cursor.fetchone() + if bool(res): + flash("A species already exists with the provided scientific name.", + "alert-danger") + error = True + + if error: + return redirect(url_for("species.create_species", + common_name=common_name, + scientific_name=scientific_name, + taxon_id=taxon_id)) + + species = save_species(conn, common_name, scientific_name, taxon_id) + flash("Species saved successfully!", "alert-success") + return redirect(url_for("species.view_species", species_id=species["species_id"])) diff --git a/uploader/templates/base.html b/uploader/templates/base.html index 58227f4..7431767 100644 --- a/uploader/templates/base.html +++ b/uploader/templates/base.html @@ -44,6 +44,7 @@ @@ -54,7 +55,14 @@

GN Uploader: {%block pagetitle%}{%endblock%}

diff --git a/uploader/templates/species/base.html b/uploader/templates/species/base.html new file mode 100644 index 0000000..b77cc8b --- /dev/null +++ b/uploader/templates/species/base.html @@ -0,0 +1,11 @@ +{%extends "base.html"%} + +{%block breadcrumbs%} + +{%endblock%} diff --git a/uploader/templates/species/create-species.html b/uploader/templates/species/create-species.html new file mode 100644 index 0000000..b96e2d3 --- /dev/null +++ b/uploader/templates/species/create-species.html @@ -0,0 +1,116 @@ +{%extends "species/base.html"%} +{%from "flash_messages.html" import flash_all_messages%} + +{%block title%}Create Species{%endblock%} + +{%block pagetitle%}Create Species{%endblock%} + +{%block breadcrumbs%} + +{%endblock%} + +{%block contents%} +
+
+ Create Species + + {{flash_all_messages()}} + +
+ +
+ + + + +
+ Provide the taxonomy ID for + your species that can be used to link to external sites like NCBI. Enter + the taxonomy ID and click "Search" to auto-fill the form with data. +
+ While it is recommended to provide a value for this field, doing so is + optional. +
+
+ +
+ + + Provide the common, possibly + non-scientific name for the species here, e.g. Human, Mouse, etc. +
+ +
+ + + Provide the scientific name for the + species you are creating, e.g. Homo sapiens, Mus musculus, etc. +
+ +
+ +
+ +
+
+{%endblock%} + +{%block javascript%} + +{%endblock%} diff --git a/uploader/templates/species/list-species.html b/uploader/templates/species/list-species.html new file mode 100644 index 0000000..42094c1 --- /dev/null +++ b/uploader/templates/species/list-species.html @@ -0,0 +1,68 @@ +{%extends "species/base.html"%} +{%from "flash_messages.html" import flash_all_messages%} + +{%block title%}List Species{%endblock%} + +{%block pagetitle%}List Species{%endblock%} + +{%block contents%} +
+

+ All data in GeneNetwork revolves around species. This is the core of the + system.

+

Here you can see a list of all the species available in GeneNetwork. + Click on the link besides each species to view greater detail on the species, + and access further operations that are possible for said species.

+
+ +
+

If you cannot find the species you are looking for below, click the button + below to create it

+

Create Species

+
+ +
+ + + + + + + + + + + + {%for species in allspecies%} + + + + + + + {%else%} + + + + {%endfor%} + +
Available Species
Common NameScientific NameTaxonIdUse
{{species["SpeciesName"]}}{{species["FullName"]}} + {{species["TaxonomyId"]}} + + + {{species["SpeciesName"]}} ({{species["FullName"]}}) + +
+

+ + There were no species found! +

+
+
+{%endblock%} -- cgit v1.2.3