about summary refs log tree commit diff
path: root/uploader
diff options
context:
space:
mode:
Diffstat (limited to 'uploader')
-rw-r--r--uploader/phenotypes/models.py109
-rw-r--r--uploader/phenotypes/views.py48
-rw-r--r--uploader/templates/phenotypes/view-dataset.html8
-rw-r--r--uploader/templates/phenotypes/view-phenotype.html43
4 files changed, 206 insertions, 2 deletions
diff --git a/uploader/phenotypes/models.py b/uploader/phenotypes/models.py
index 4ba0d08..eb5a189 100644
--- a/uploader/phenotypes/models.py
+++ b/uploader/phenotypes/models.py
@@ -1,8 +1,9 @@
 """Database and utility functions for phenotypes."""
 from typing import Optional
+from functools import reduce
 
 import MySQLdb as mdb
-from MySQLdb.cursors import DictCursor
+from MySQLdb.cursors import Cursor, DictCursor
 
 from uploader.db_utils import debug_query
 
@@ -70,6 +71,112 @@ def dataset_phenotypes(conn: mdb.Connection,
         return tuple(dict(row) for row in cursor.fetchall())
 
 
+def __phenotype_se__(cursor: Cursor,
+                     species_id: int,
+                     population_id: int,
+                     dataset_id: int,
+                     xref_id: str) -> dict:
+    """Fetch standard-error values (if they exist) for a phenotype."""
+    _sequery = (
+        "SELECT pxr.Id AS xref_id, pxr.DataId, pse.error, nst.count "
+        "FROM Phenotype AS pheno "
+        "INNER JOIN PublishXRef AS pxr ON pheno.Id=pxr.PhenotypeId "
+        "INNER JOIN PublishSE AS pse ON pxr.DataId=pse.DataId "
+        "INNER JOIN NStrain AS nst ON pse.DataId=nst.DataId "
+        "INNER JOIN Strain AS str ON nst.StrainId=str.Id "
+        "INNER JOIN StrainXRef AS sxr ON str.Id=sxr.StrainId "
+        "INNER JOIN PublishFreeze AS pf ON sxr.InbredSetId=pf.InbredSetId "
+        "INNER JOIN InbredSet AS iset ON pf.InbredSetId=iset.InbredSetId "
+        "WHERE (str.SpeciesId, pxr.InbredSetId, pf.Id, pxr.Id)=(%s, %s, %s, %s)")
+    cursor.execute(_sequery,
+                   (species_id, population_id, dataset_id, xref_id))
+    return {row["xref_id"]: dict(row) for row in cursor.fetchall()}
+
+def __organise_by_phenotype__(pheno, row):
+    """Organise disparate data rows into phenotype 'objects'."""
+    _pheno = pheno.get(row["Id"])
+    return {
+        **pheno,
+        row["Id"]: {
+            "Id": row["Id"],
+            "Pre_publication_description": row["Pre_publication_description"],
+            "Post_publication_description": row["Post_publication_description"],
+            "Original_description": row["Original_description"],
+            "Units": row["Units"],
+            "Pre_publication_abbreviation": row["Pre_publication_abbreviation"],
+            "Post_publication_abbreviation": row["Post_publication_abbreviation"],
+            "data": {
+                #TOD0: organise these by DataId and StrainId
+                **(_pheno["data"] if bool(_pheno) else {}),
+                row["pxr.Id"]: {
+                    "xref_id": row["pxr.Id"],
+                    "DataId": row["DataId"],
+                    "mean": row["mean"],
+                    "Locus": row["Locus"],
+                    "LRS": row["LRS"],
+                    "additive": row["additive"],
+                    "Sequence": row["Sequence"],
+                    "comments": row["comments"],
+                    "value": row["value"],
+                    "StrainName": row["Name"],
+                    "StrainName2": row["Name2"],
+                    "StrainSymbol": row["Symbol"],
+                    "StrainAlias": row["Alias"]
+                }
+            }
+        }
+    }
+
+
+def __merge_pheno_data_and_se__(data, sedata) -> dict:
+    """Merge phenotype data with the standard errors."""
+    return {
+        key: {**value, **sedata.get(key, {})}
+        for key, value in data.items()
+    }
+
+
+def phenotype_by_id(
+        conn: mdb.Connection,
+        species_id: int,
+        population_id: int,
+        dataset_id: int,
+        xref_id
+) -> Optional[dict]:
+    """Fetch a specific phenotype."""
+    _dataquery = ("SELECT pheno.*, pxr.*, pd.*, str.*, iset.InbredSetCode "
+                  "FROM Phenotype AS pheno "
+                  "INNER JOIN PublishXRef AS pxr ON pheno.Id=pxr.PhenotypeId "
+                  "INNER JOIN PublishData AS pd ON pxr.DataId=pd.Id "
+                  "INNER JOIN Strain AS str ON pd.StrainId=str.Id "
+                  "INNER JOIN StrainXRef AS sxr ON str.Id=sxr.StrainId "
+                  "INNER JOIN PublishFreeze AS pf ON sxr.InbredSetId=pf.InbredSetId "
+                  "INNER JOIN InbredSet AS iset ON pf.InbredSetId=iset.InbredSetId "
+                  "WHERE "
+                  "(str.SpeciesId, pxr.InbredSetId, pf.Id, pxr.Id)=(%s, %s, %s, %s)")
+    with conn.cursor(cursorclass=DictCursor) as cursor:
+        cursor.execute(_dataquery,
+                       (species_id, population_id, dataset_id, xref_id))
+        _pheno: dict = reduce(__organise_by_phenotype__, cursor.fetchall(), {})
+        if bool(_pheno) and len(_pheno.keys()) == 1:
+            _pheno = tuple(_pheno.values())[0]
+            return {
+                **_pheno,
+                "data": tuple(__merge_pheno_data_and_se__(
+                    _pheno["data"],
+                    __phenotype_se__(cursor,
+                                     species_id,
+                                     population_id,
+                                     dataset_id,
+                                     xref_id)).items())
+            }
+        if bool(_pheno) and len(_pheno.keys()) > 1:
+            raise Exception(
+                "We found more than one phenotype with the same identifier!")
+
+    return None
+
+
 def phenotypes_data(conn: mdb.Connection,
                     population_id: int,
                     dataset_id: int,
diff --git a/uploader/phenotypes/views.py b/uploader/phenotypes/views.py
index a79e863..7fc3b08 100644
--- a/uploader/phenotypes/views.py
+++ b/uploader/phenotypes/views.py
@@ -9,15 +9,18 @@ from flask import (flash,
                    render_template,
                    current_app as app)
 
+from uploader.oauth2.client import oauth2_post
 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.monadic_requests import make_either_error_handler
 from uploader.request_checks import with_species, with_population
 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 (dataset_by_id,
+                     phenotype_by_id,
                      phenotypes_count,
                      dataset_phenotypes,
                      datasets_by_population)
@@ -168,3 +171,48 @@ def view_dataset(# pylint: disable=[unused-argument]
                                                       limit=count),
                                    start=start_at+1),
                                activelink="view-dataset")
+
+
+@phenotypesbp.route(
+    "<int:species_id>/populations/<int:population_id>/phenotypes/datasets"
+    "/<int:dataset_id>/phenotype/<xref_id>",
+    methods=["GET"])
+@require_login
+@with_dataset(
+    species_redirect_uri="species.populations.phenotypes.index",
+    population_redirect_uri="species.populations.phenotypes.select_population",
+    redirect_uri="species.populations.phenotypes.list_datasets")
+def view_phenotype(# pylint: disable=[unused-argument]
+        species: dict,
+        population: dict,
+        dataset: dict,
+        xref_id: int,
+        **kwargs
+):
+    """View an individual phenotype from the dataset."""
+    with database_connection(app.config["SQL_URI"]) as conn:
+        return oauth2_post(
+            "/auth/resource/phenotypes/individual/linked-resource",
+            json={
+                "species_id": species["SpeciesId"],
+                "population_id": population["Id"],
+                "dataset_id": dataset["Id"],
+                "xref_id": xref_id
+            }
+        ).then(
+            lambda resource: render_template(
+                "phenotypes/view-phenotype.html",
+                species=species,
+                population=population,
+                dataset=dataset,
+                phenotype=phenotype_by_id(conn,
+                                          species["SpeciesId"],
+                                          population["Id"],
+                                          dataset["Id"],
+                                          xref_id),
+                resource=resource,
+                activelink="view-phenotype")
+        ).either(
+            make_either_error_handler(
+                "There was an error fetching the roles and privileges."),
+            lambda resp: resp)
diff --git a/uploader/templates/phenotypes/view-dataset.html b/uploader/templates/phenotypes/view-dataset.html
index fc393d6..959b02a 100644
--- a/uploader/templates/phenotypes/view-dataset.html
+++ b/uploader/templates/phenotypes/view-dataset.html
@@ -69,7 +69,13 @@
       {%for pheno in phenotypes%}
       <tr>
         <td>{{pheno.sequence_number}}</td>
-        <td>{{pheno.InbredSetCode}}_{{pheno["pxr.Id"]}}</td>
+        <td><a href="{{url_for('species.populations.phenotypes.view_phenotype',
+                     species_id=species.SpeciesId,
+                     population_id=population.Id,
+                     dataset_id=dataset.Id,
+                     xref_id=pheno['pxr.Id'])}}"
+               title="View phenotype details">
+            {{pheno.InbredSetCode}}_{{pheno["pxr.Id"]}}</a></td>
         <td>{{pheno.Post_publication_description or pheno.Pre_publication_abbreviation or pheno.Original_description}}</td>
       </tr>
       {%else%}
diff --git a/uploader/templates/phenotypes/view-phenotype.html b/uploader/templates/phenotypes/view-phenotype.html
new file mode 100644
index 0000000..5aef6c1
--- /dev/null
+++ b/uploader/templates/phenotypes/view-phenotype.html
@@ -0,0 +1,43 @@
+{%extends "phenotypes/base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+{%from "populations/macro-display-population-card.html" import display_population_card%}
+
+{%block title%}Phenotypes{%endblock%}
+
+{%block pagetitle%}Phenotypes{%endblock%}
+
+{%block lvl4_breadcrumbs%}
+<li {%if activelink=="view-phenotype"%}
+    class="breadcrumb-item active"
+    {%else%}
+    class="breadcrumb-item"
+    {%endif%}>
+  <a href="{{url_for('species.populations.phenotypes.view_phenotype',
+           species_id=species.SpeciesId,
+           population_id=population.Id,
+           dataset_id=dataset.Id,
+           xref_id=xref_id)}}">View Datasets</a>
+</li>
+{%endblock%}
+
+{%block contents%}
+{{flash_all_messages()}}
+
+<div class="row">
+  {{resource}}
+</div>
+
+<div class="row">
+  <h2>Dataset</h2>
+  {{dataset}}
+</div>
+
+<div class="row">
+  <h2>Phenotype</h2>
+  {{phenotype}}
+</div>
+{%endblock%}
+
+{%block sidebarcontents%}
+{{display_population_card(species, population)}}
+{%endblock%}