diff options
-rw-r--r-- | uploader/base_routes.py | 5 | ||||
-rw-r--r-- | uploader/genotypes/models.py | 63 | ||||
-rw-r--r-- | uploader/genotypes/views.py | 69 | ||||
-rw-r--r-- | uploader/species/views.py | 1 | ||||
-rw-r--r-- | uploader/static/css/styles.css | 6 | ||||
-rw-r--r-- | uploader/templates/base.html | 6 | ||||
-rw-r--r-- | uploader/templates/genotypes/index.html | 8 | ||||
-rw-r--r-- | uploader/templates/genotypes/list-genotypes.html | 136 | ||||
-rw-r--r-- | uploader/templates/genotypes/list-markers.html | 102 | ||||
-rw-r--r-- | uploader/templates/index.html | 80 | ||||
-rw-r--r-- | uploader/templates/login.html | 2 | ||||
-rw-r--r-- | uploader/templates/species/macro-select-species.html | 2 |
12 files changed, 461 insertions, 19 deletions
diff --git a/uploader/base_routes.py b/uploader/base_routes.py index 88247b2..a20b7ff 100644 --- a/uploader/base_routes.py +++ b/uploader/base_routes.py @@ -1,5 +1,6 @@ """Basic routes required for all pages""" import os +from urllib.parse import urljoin from flask import ( Blueprint, @@ -23,7 +24,9 @@ def favicon(): @base.route("/", methods=["GET"]) def index(): """Load the landing page""" - return render_template("index.html" if user_logged_in() else "login.html") + return render_template("index.html" if user_logged_in() else "login.html", + gn2server_intro=urljoin(app.config["GN2_SERVER_URL"], + "/intro")) def appenv(): """Get app's guix environment path.""" diff --git a/uploader/genotypes/models.py b/uploader/genotypes/models.py new file mode 100644 index 0000000..29acd0b --- /dev/null +++ b/uploader/genotypes/models.py @@ -0,0 +1,63 @@ +"""Functions for handling genotypes.""" +from typing import Optional + +import MySQLdb as mdb +from MySQLdb.cursors import DictCursor + +from uploader.db_utils import debug_query + +def genocode_by_population( + conn: mdb.Connection, population_id: int) -> tuple[dict, ...]: + """Get the allele/genotype codes.""" + with conn.cursor(cursorclass=DictCursor) as cursor: + cursor.execute("SELECT * FROM GenoCode WHERE InbredSetId=%s", + (population_id,)) + return tuple(dict(item) for item in cursor.fetchall()) + + +def genotype_markers_count(conn: mdb.Connection, species_id: int) -> int: + """Find the total count of the genotype markers for a species.""" + with conn.cursor(cursorclass=DictCursor) as cursor: + cursor.execute( + "SELECT COUNT(Name) AS markers_count FROM Geno WHERE SpeciesId=%s", + (species_id,)) + return int(cursor.fetchone()["markers_count"]) + + +def genotype_markers( + conn: mdb.Connection, + species_id: int, + offset: int = 0, + limit: Optional[int] = None +) -> tuple[dict, ...]: + """Retrieve markers from the database.""" + _query = "SELECT * FROM Geno WHERE SpeciesId=%s" + if bool(limit) and limit > 0: + _query = _query + f" LIMIT {limit} OFFSET {offset}" + + with conn.cursor(cursorclass=DictCursor) as cursor: + cursor.execute(_query, (species_id,)) + debug_query(cursor) + return tuple(dict(row) for row in cursor.fetchall()) + + +def genotype_dataset( + conn: mdb.Connection, + species_id: int, + population_id: int +) -> Optional[dict]: + """Retrieve genotype datasets from the database. + + Apparently, you should only ever have one genotype dataset for a population. + """ + with conn.cursor(cursorclass=DictCursor) as cursor: + cursor.execute( + "SELECT gf.* FROM Species AS s INNER JOIN InbredSet AS iset " + "ON s.Id=iset.SpeciesId INNER JOIN GenoFreeze AS gf " + "ON iset.Id=gf.InbredSetId " + "WHERE s.Id=%s AND iset.Id=%s", + (species_id, population_id)) + result = cursor.fetchone() + if bool(result): + return dict(result) + return None diff --git a/uploader/genotypes/views.py b/uploader/genotypes/views.py index 885e008..8752b02 100644 --- a/uploader/genotypes/views.py +++ b/uploader/genotypes/views.py @@ -7,13 +7,18 @@ from flask import (flash, render_template, current_app as app) -from uploader.datautils import order_by_family from uploader.authorisation import require_login from uploader.db_utils import database_connection from uploader.species.models import all_species, species_by_id +from uploader.datautils import safe_int, order_by_family, enumerate_sequence from uploader.population.models import (populations_by_species, population_by_species_and_id) +from .models import (genotype_markers, + genotype_dataset, + genotype_markers_count, + genocode_by_population) + genotypesbp = Blueprint("genotypes", __name__) @genotypesbp.route("populations/genotypes", methods=["GET"]) @@ -72,4 +77,64 @@ def select_population(species_id: int): @require_login def list_genotypes(species_id: int, population_id: int): """List genotype details for species and population.""" - return f"Would list geno info for population {population_id} from species {species_id}" + with database_connection(app.config["SQL_URI"]) as conn: + species = species_by_id(conn, species_id) + if not bool(species): + flash("Invalid species provided!", "alert-danger") + return redirect(url_for("species.populations.genotypes.index")) + + population = population_by_species_and_id( + conn, species_id, population_id) + if not bool(population): + flash("Invalid population selected!", "alert-danger") + return redirect(url_for( + "species.populations.genotypes.select_population", + species_id=species_id)) + + return render_template("genotypes/list-genotypes.html", + species=species, + population=population, + genocode=genocode_by_population( + conn, population_id), + total_markers=genotype_markers_count( + conn, species_id), + dataset=genotype_dataset( + conn, species_id, population_id), + activelink="list-genotypes") + + +@genotypesbp.route("/<int:species_id>/genotypes/list-markers", methods=["GET"]) +@require_login +def list_markers(species_id: int): + """List a species' genetic markers.""" + with database_connection(app.config["SQL_URI"]) as conn: + species = species_by_id(conn, species_id) + if not bool(species): + flash("Invalid species provided!", "alert-danger") + return redirect(url_for("species.populations.genotypes.index")) + + start_from = safe_int(request.args.get("start_from") or 0) + if start_from < 0: + start_from = 0 + count = safe_int(request.args.get("count") or 20) + markers = enumerate_sequence( + genotype_markers(conn, species_id, offset=start_from, limit=count), + start=start_from+1) + return render_template("genotypes/list-markers.html", + species=species, + total_markers=genotype_markers_count( + conn, species_id), + start_from=start_from, + count=count, + markers=markers, + activelink="list-markers") + +@genotypesbp.route( + "/<int:species_id>/populations/<int:population_id>/genotypes/datasets/" + "<int:dataset_id>/view", + methods=["GET"]) +@require_login +def view_dataset(species_id: int, population_id: int, dataset_id: int): + """View details regarding a specific dataset.""" + return (f"Genotype dataset '{dataset_id}, from population '{population_id}' " + f"of species '{species_id}'.") diff --git a/uploader/species/views.py b/uploader/species/views.py index 08d3728..f478505 100644 --- a/uploader/species/views.py +++ b/uploader/species/views.py @@ -27,6 +27,7 @@ render_template = make_template_renderer("species") @speciesbp.route("/", methods=["GET"]) +@require_login def list_species(): """List and display all the species in the database.""" with database_connection(app.config["SQL_URI"]) as conn: diff --git a/uploader/static/css/styles.css b/uploader/static/css/styles.css index 30d5808..fab42b8 100644 --- a/uploader/static/css/styles.css +++ b/uploader/static/css/styles.css @@ -39,6 +39,10 @@ body { border-color: #FFFFFF; vertical-align: middle; margin: 0.2em; + border-style: solid; + border-width: 2px; + border-radius: 0.5em; + text-align: center; } #header .header-nav a { @@ -102,7 +106,7 @@ dd { padding-bottom: 1em; } -input[type="submit"] { +input[type="submit"], .btn { text-transform: capitalize; } diff --git a/uploader/templates/base.html b/uploader/templates/base.html index 3af14ef..171e8fa 100644 --- a/uploader/templates/base.html +++ b/uploader/templates/base.html @@ -28,7 +28,7 @@ <span class="header col-lg-9">GeneNetwork Data Quality Control and Upload</span> <nav class="header-nav col-lg-3"> <ul class="nav justify-content-end"> - <li class="btn"> + <li> {%if user_logged_in()%} <a href="{{url_for('oauth2.logout')}}" title="Log out of the system">{{user_email()}} — Log Out</a> @@ -48,10 +48,10 @@ title="View and manage species information.">Species</a></li> <li><a href="{{url_for('species.populations.index')}}" title="View and manage species populations.">Populations</a></li> - <li><a href="{{url_for('species.populations.genotypes.index')}}" - title="Upload Genotype data.">Genotype Data</a></li> <li><a href="{{url_for('species.populations.samples.index')}}" title="Upload population samples.">Samples</a></li> + <li><a href="{{url_for('species.populations.genotypes.index')}}" + title="Upload Genotype data.">Genotype Data</a></li> <li><a href="{{url_for('expression-data.index.index')}}" title="Upload expression data.">Expression Data</a></li> <li><a href="#" diff --git a/uploader/templates/genotypes/index.html b/uploader/templates/genotypes/index.html index 9ffea73..e749f5a 100644 --- a/uploader/templates/genotypes/index.html +++ b/uploader/templates/genotypes/index.html @@ -25,12 +25,4 @@ {{select_species_form(url_for("species.populations.genotypes.index"), species)}} </div> - -<div class="row"> - <h3>Some Important Concepts to Consider/Remember</h3> - <ul> - <li>Reference vs. Non-reference alleles</li> - <li>In <em>GenoCode</em> table, items are ordered by <strong>InbredSet</strong></li> - </ul> -</div> {%endblock%} diff --git a/uploader/templates/genotypes/list-genotypes.html b/uploader/templates/genotypes/list-genotypes.html new file mode 100644 index 0000000..3780f85 --- /dev/null +++ b/uploader/templates/genotypes/list-genotypes.html @@ -0,0 +1,136 @@ +{%extends "genotypes/base.html"%} +{%from "flash_messages.html" import flash_all_messages%} +{%from "populations/macro-display-population-card.html" import display_population_card%} + +{%block title%}Genotypes{%endblock%} + +{%block pagetitle%}Genotypes{%endblock%} + +{%block lvl4_breadcrumbs%} +<li {%if activelink=="list-genotypes"%} + class="breadcrumb-item active" + {%else%} + class="breadcrumb-item" + {%endif%}> + <a href="{{url_for('species.populations.genotypes.list_genotypes', + species_id=species.SpeciesId, + population_id=population.Id)}}">List genotypes</a> +</li> +{%endblock%} + +{%block contents%} +{{flash_all_messages()}} + +<div class="row"> + <h2>Genetic Markers</h2> + <p>There are a total of {{total_markers}} currently registered genetic markers + for the "{{species.FullName}}" species. You can click + <a href="{{url_for('species.populations.genotypes.list_markers', + species_id=species.SpeciesId)}}" + title="View genetic markers for species '{{species.FullName}}"> + this link to view the genetic markers + </a>. + </p> +</div> + +<div class="row"> + <h2>Genotype Encoding</h2> + <p> + The genotype encoding used for the "{{population.FullName}}" population from + the "{{species.FullName}}" species is as shown in the table below. + </p> + <table class="table"> + + <thead> + <tr> + <th>Allele Type</th> + <th>Allele Symbol</th> + <th>Allele Value</th> + </tr> + </thead> + + <tbody> + {%for row in genocode%} + <tr> + <td>{{row.AlleleType}}</td> + <td>{{row.AlleleSymbol}}</td> + <td>{{row.DatabaseValue if row.DatabaseValue is not none else "NULL"}}</td> + </tr> + {%else%} + <tr> + <td colspan="7" class="text-info"> + <span class="glyphicon glyphicon-exclamation-sign"></span> + There is no explicit genotype encoding defined for this population. + </td> + </tr> + {%endfor%} + </tbody> + </table> + + {%if genocode | length < 1%} + <a href="#add-genotype-encoding" + title="Add a genotype encoding system for this population" + class="btn btn-primary"> + add genotype encoding + </a> + {%endif%} +</div> + +<div class="row text-danger"> + <h3>Some Important Concepts to Consider/Remember</h3> + <ul> + <li>Reference vs. Non-reference alleles</li> + <li>In <em>GenoCode</em> table, items are ordered by <strong>InbredSet</strong></li> + </ul> + <h3>Possible references</h3> + <ul> + <li>https://mr-dictionary.mrcieu.ac.uk/term/genotype/</li> + <li>https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7363099/</li> + </ul> +</div> + +<div class="row"> + <h2>Genotype Datasets</h2> + + <p>The genotype data is organised under various genotype datasets. You can + click on the link for the relevant dataset to view a little more information + about it.</p> + + {%if dataset is defined %} + <table class="table"> + <thead> + <tr> + <th>Name</th> + <th>Full Name</th> + </tr> + </thead> + + <tbody> + <tr> + <td>{{dataset.Name}}</td> + <td><a href="{{url_for('species.populations.genotypes.view_dataset', + species_id=species.SpeciesId, + population_id=population.Id, + dataset_id=dataset.Id)}}" + title="View details regarding and manage dataset '{{dataset.FullName}}'"> + {{dataset.FullName}}</a></td> + </tr> + </tbody> + </table> + {%else%} + <p class="text-warning"> + <span class="glyphicon glyphicon-exclamation-sign"></span> + There is no genotype dataset defined for this population. + </p> + <p> + <a href="#create-new-genotype-dataset" + title="Create a new genotype dataset for the '{{population.FullName}}' population for the '{{species.FullName}}' species." + class="btn btn-primary"> + create new genotype dataset</a></p> + {%endif%} +</div> +{%endblock%} + +{%block sidebarcontents%} +{{display_population_card(species, population)}} +{%endblock%} diff --git a/uploader/templates/genotypes/list-markers.html b/uploader/templates/genotypes/list-markers.html new file mode 100644 index 0000000..9198b44 --- /dev/null +++ b/uploader/templates/genotypes/list-markers.html @@ -0,0 +1,102 @@ +{%extends "genotypes/base.html"%} +{%from "flash_messages.html" import flash_all_messages%} +{%from "species/macro-display-species-card.html" import display_species_card%} + +{%block title%}Genotypes: List Markers{%endblock%} + +{%block pagetitle%}Genotypes: List Markers{%endblock%} + +{%block lvl4_breadcrumbs%} +<li {%if activelink=="list-markers"%} + class="breadcrumb-item active" + {%else%} + class="breadcrumb-item" + {%endif%}> + <a href="{{url_for('species.populations.genotypes.list_markers', + species_id=species.SpeciesId)}}">List markers</a> +</li> +{%endblock%} + +{%block contents%} +{{flash_all_messages()}} + +{%if markers | length > 0%} +<div class="row"> + <p> + There are a total of {{total_markers}} genotype markers for this species. + </p> + <div class="row"> + <div class="col-md-2" style="text-align: start;"> + {%if start_from > 0%} + <a href="{{url_for('species.populations.genotypes.list_markers', + species_id=species.SpeciesId, + start_from=start_from-count, + count=count)}}"> + <span class="glyphicon glyphicon-backward"></span> + Previous + </a> + {%endif%} + </div> + <div class="col-md-8" style="text-align: center;"> + Displaying markers {{start_from+1}} to {{start_from+count if start_from+count < total_markers else total_markers}} of + {{total_markers}} + </div> + <div class="col-md-2" style="text-align: end;"> + {%if start_from + count < total_markers%} + <a href="{{url_for('species.populations.genotypes.list_markers', + species_id=species.SpeciesId, + start_from=start_from+count, + count=count)}}"> + Next + <span class="glyphicon glyphicon-forward"></span> + </a> + {%endif%} + </div> + </div> + <table class="table"> + <thead> + <tr> + <th title="">#</th> + <th title="">Marker Name</th> + <th title="Chromosome">Chr</th> + <th title="Physical location of the marker in megabasepairs"> + Location (Mb)</th> + <th title="">Source</th> + <th title="">Source2</th> + </thead> + + <tbody> + {%for marker in markers%} + <tr> + <td>{{marker.sequence_number}}</td> + <td>{{marker.Marker_Name}}</td> + <td>{{marker.Chr}}</td> + <td>{{marker.Mb}}</td> + <td>{{marker.Source}}</td> + <td>{{marker.Source2}}</td> + </tr> + {%endfor%} + </tbody> + </table> +</div> +{%else%} +<div class="row"> + <p class="text-warning"> + <span class="glyphicon glyphicon-exclamation-sign"></span> + This species does not currently have any genetic markers uploaded, therefore, + there is nothing to display here. + </p> + <p> + <a href="#add-genetic-markers-for-species-{{species.SpeciesId}}" + title="Add genetic markers for this species" + class="btn btn-primary"> + add genetic markers + </a> + </p> +</div> +{%endif%} +{%endblock%} + +{%block sidebarcontents%} +{{display_species_card(species)}} +{%endblock%} diff --git a/uploader/templates/index.html b/uploader/templates/index.html index e3f5af4..48848a3 100644 --- a/uploader/templates/index.html +++ b/uploader/templates/index.html @@ -18,9 +18,87 @@ <div class="explainer"> <p>Welcome to the <strong>GeneNetwork Data Quality Control and Upload System</strong>. This system is provided to help in uploading your data onto GeneNetwork where you can do analysis on it.</p> - <p>Click on the menu items on the left to select the kind of data you want to upload.</p> + <p>The sections below provide an overview of what features the menu items on + the left provide to you. Please peruse the information to get a good + big-picture understanding of what the system provides you and how to get + the most out of it.</p> {%block extrapageinfo%}{%endblock%} + + <h2>Species</h2> + + <p>The GeneNetwork service provides datasets and tools for doing genetic + studies — from + <a href="{{gn2server_intro}}" + target="_blank" + title="GeneNetwork introduction — opens in a new tab."> + its introduction</a>: + + <blockquote class="blockquote"> + <p>GeneNetwork is a group of linked data sets and tools used to study + complex networks of genes, molecules, and higher order gene function + and phenotypes. …</p> + </blockquote> + </p> + + <p>With this in mind, it follows that the data in the system is centered + aroud a variety of species. The <strong>species section</strong> will + list the currently available species in the system, and give you the + ability to add new ones, if the one you want to work on does not currently + exist on GeneNetwork</p> + + <h2>Populations</h2> + + <p>Your studies will probably focus on a particular subset of the entire + species you are interested in – your population.</p> + <p>Populations are a way to organise the species data so as to link data to + specific know populations for a particular species, e.g. The BXD + population of mice (Mus musculus)</p> + <p>In older GeneNetwork documentation, you might run into the term + <em>InbredSet</em>. Should you run into it, it is a term that we've + deprecated that essentially just means the population.</p> + + <h2>Samples</h2> + + <p>These are the samples or individuals (sometimes cases) that were involved + in the experiment, and from whom the data was derived.</p> + + <h2>Genotype Data</h2> + + <p>This section will allow you to view and upload the genetic markers for + your species, and the genotype encodings used for your particular + population.</p> + <p>While, technically, genetic markers relate to the species in general, and + not to a particular population, the data (allele information) itself + relates to the particular population it was generated from – + specifically, to the actual individuals used in the experiment.</p> + <p>This is the reason why the genotype data information comes under the + population, and will check for the prior existence of the related + samples/individuals before attempting an upload of your data.</p> + + <h2>Expression Data</h2> + + <p class="text-danger"> + <span class="glyphicon glyphicon-exclamation-sign"></span> + <strong>TODO</strong>: Document this …</p> + + <h2>Phenotype Data</h2> + + <p class="text-danger"> + <span class="glyphicon glyphicon-exclamation-sign"></span> + <strong>TODO</strong>: Document this …</p> + + <h2>Individual Data</h2> + + <p class="text-danger"> + <span class="glyphicon glyphicon-exclamation-sign"></span> + <strong>TODO</strong>: Document this …</p> + + <h2>RNA-Seq Data</h2> + + <p class="text-danger"> + <span class="glyphicon glyphicon-exclamation-sign"></span> + <strong>TODO</strong>: Document this …</p> </div> </div> diff --git a/uploader/templates/login.html b/uploader/templates/login.html index bbca42f..1f71416 100644 --- a/uploader/templates/login.html +++ b/uploader/templates/login.html @@ -5,7 +5,7 @@ {%block pagetitle%}log in{%endblock%} {%block extrapageinfo%} -<p> +<p class="text-dark text-primary"> You <strong>do need to be logged in</strong> to upload data onto this system. Please do that by clicking the "Log In" button at the top of the page.</p> {%endblock%} diff --git a/uploader/templates/species/macro-select-species.html b/uploader/templates/species/macro-select-species.html index 6955134..dd086c0 100644 --- a/uploader/templates/species/macro-select-species.html +++ b/uploader/templates/species/macro-select-species.html @@ -1,8 +1,6 @@ {%macro select_species_form(form_action, species)%} {%if species | length > 0%} <form method="GET" action="{{form_action}}"> - <legend>Select Species</legend> - <div class="form-group"> <label for="select-species" class="form-label">Species</label> <select id="select-species" |