aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--uploader/base_routes.py5
-rw-r--r--uploader/genotypes/models.py63
-rw-r--r--uploader/genotypes/views.py69
-rw-r--r--uploader/species/views.py1
-rw-r--r--uploader/static/css/styles.css6
-rw-r--r--uploader/templates/base.html6
-rw-r--r--uploader/templates/genotypes/index.html8
-rw-r--r--uploader/templates/genotypes/list-genotypes.html136
-rw-r--r--uploader/templates/genotypes/list-markers.html102
-rw-r--r--uploader/templates/index.html80
-rw-r--r--uploader/templates/login.html2
-rw-r--r--uploader/templates/species/macro-select-species.html2
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()}} &mdash; 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 &mdash; 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. &hellip;</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 &ndash; 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 &ndash;
+ 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 &hellip;</p>
+
+ <h2>Phenotype Data</h2>
+
+ <p class="text-danger">
+ <span class="glyphicon glyphicon-exclamation-sign"></span>
+ <strong>TODO</strong>: Document this &hellip;</p>
+
+ <h2>Individual Data</h2>
+
+ <p class="text-danger">
+ <span class="glyphicon glyphicon-exclamation-sign"></span>
+ <strong>TODO</strong>: Document this &hellip;</p>
+
+ <h2>RNA-Seq Data</h2>
+
+ <p class="text-danger">
+ <span class="glyphicon glyphicon-exclamation-sign"></span>
+ <strong>TODO</strong>: Document this &hellip;</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"