From 1696242aa80f489a8ed4e5a01a30a1fd813dd4f3 Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 30 Sep 2024 16:36:53 -0500 Subject: Initialise views for a specific phenotype Each phenotype is independent, of all others, and they are only put into datasets mostly for easy coralling of phenotypes related to a specific populations. As such, the system will probably need to provide a way to view (and possibly edit) each phenotype independent of all the others. This also fits in with the auth. --- uploader/phenotypes/models.py | 109 +++++++++++++++++++++- uploader/phenotypes/views.py | 48 ++++++++++ uploader/templates/phenotypes/view-dataset.html | 8 +- uploader/templates/phenotypes/view-phenotype.html | 43 +++++++++ 4 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 uploader/templates/phenotypes/view-phenotype.html (limited to 'uploader') 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( + "/populations//phenotypes/datasets" + "//phenotype/", + 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%} {{pheno.sequence_number}} - {{pheno.InbredSetCode}}_{{pheno["pxr.Id"]}} + + {{pheno.InbredSetCode}}_{{pheno["pxr.Id"]}} {{pheno.Post_publication_description or pheno.Pre_publication_abbreviation or pheno.Original_description}} {%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%} + +{%endblock%} + +{%block contents%} +{{flash_all_messages()}} + +
+ {{resource}} +
+ +
+

Dataset

+ {{dataset}} +
+ +
+

Phenotype

+ {{phenotype}} +
+{%endblock%} + +{%block sidebarcontents%} +{{display_population_card(species, population)}} +{%endblock%} -- cgit v1.2.3