diff options
| -rw-r--r-- | scripts/load_phenotypes_to_db.py | 49 | ||||
| -rw-r--r-- | scripts/rqtl2/phenotypes_qc.py | 7 | ||||
| -rw-r--r-- | scripts/run_qtlreaper.py | 20 | ||||
| -rw-r--r-- | uploader/__init__.py | 9 | ||||
| -rw-r--r-- | uploader/default_settings.py | 3 | ||||
| -rw-r--r-- | uploader/genotypes/models.py | 24 | ||||
| -rw-r--r-- | uploader/genotypes/views.py | 50 | ||||
| -rw-r--r-- | uploader/phenotypes/models.py | 30 | ||||
| -rw-r--r-- | uploader/publications/models.py | 14 | ||||
| -rw-r--r-- | uploader/publications/views.py | 6 | ||||
| -rw-r--r-- | uploader/request_checks.py | 54 | ||||
| -rw-r--r-- | uploader/templates/genotypes/create-dataset.html | 6 | ||||
| -rw-r--r-- | uploader/templates/genotypes/index.html | 32 | ||||
| -rw-r--r-- | uploader/templates/genotypes/list-genotypes.html | 179 | ||||
| -rw-r--r-- | uploader/templates/genotypes/view-dataset.html | 5 | ||||
| -rw-r--r-- | uploader/templates/phenotypes/create-dataset.html | 2 | ||||
| -rw-r--r-- | uploader/templates/phenotypes/view-dataset.html | 26 | ||||
| -rw-r--r-- | uploader/templates/publications/create-publication.html | 31 |
18 files changed, 354 insertions, 193 deletions
diff --git a/scripts/load_phenotypes_to_db.py b/scripts/load_phenotypes_to_db.py index 9b70fed..31eb715 100644 --- a/scripts/load_phenotypes_to_db.py +++ b/scripts/load_phenotypes_to_db.py @@ -7,6 +7,7 @@ import logging import argparse from pathlib import Path from zipfile import ZipFile +from datetime import datetime from typing import Any, Iterable from urllib.parse import urljoin from functools import reduce, partial @@ -204,6 +205,8 @@ def update_auth(# pylint: disable=[too-many-locals,too-many-positional-arguments dataset, xrefdata): """Grant the user access to their data.""" + logger.info("Updating authorisation for the data.") + logger.debug("Resource details for the authorisation: %s", resource_details) authserver, token = auth_details _tries = 0 _delay = 1 @@ -215,14 +218,14 @@ def update_auth(# pylint: disable=[too-many-locals,too-many-positional-arguments return urljoin(authserver, endpoint) def __fetch_user_details__(): - logger.debug("… Fetching user details") + logger.info("… Fetching user details") return mrequests.get( authserveruri("/auth/user/"), headers=headers ) def __link_data__(user): - logger.debug("… linking uploaded data to user's group") + logger.info("… linking uploaded data to user's group") return mrequests.post( authserveruri("/auth/data/link/phenotype"), headers=headers, @@ -245,7 +248,7 @@ def update_auth(# pylint: disable=[too-many-locals,too-many-positional-arguments }).then(lambda ld_results: (user, ld_results)) def __fetch_phenotype_category_details__(user, linkeddata): - logger.debug("… fetching phenotype category details") + logger.info("… fetching phenotype category details") return mrequests.get( authserveruri("/auth/resource/categories"), headers=headers @@ -258,7 +261,7 @@ def update_auth(# pylint: disable=[too-many-locals,too-many-positional-arguments ) def __create_resource__(user, linkeddata, category): - logger.debug("… creating authorisation resource object") + logger.info("… creating authorisation resource object") return mrequests.post( authserveruri("/auth/resource/create"), headers=headers, @@ -269,7 +272,7 @@ def update_auth(# pylint: disable=[too-many-locals,too-many-positional-arguments }).then(lambda cr_results: (user, linkeddata, cr_results)) def __attach_data_to_resource__(user, linkeddata, resource): - logger.debug("… attaching data to authorisation resource object") + logger.info("… attaching data to authorisation resource object") return mrequests.post( authserveruri("/auth/resource/data/link"), headers=headers, @@ -286,8 +289,8 @@ def update_auth(# pylint: disable=[too-many-locals,too-many-positional-arguments # This is hacky. If the auth already exists, something went wrong # somewhere. # This needs investigation to recover correctly. - logger.info( - "The authorisation for the data was already set up.") + logger.error( + "Error: The authorisation for the data was already set up.") return 0 logger.error("ERROR: Updating the authorisation for the data failed.") logger.debug( @@ -459,6 +462,25 @@ if __name__ == "__main__": logging.getLogger("uploader.phenotypes.models").setLevel(log_level) + def __parse_resource_details__(meta) -> dict: + """Parse out details regarding the wrapper resource from the metadata.""" + _key_mappings_ = { + # allow both 'data_*' and 'data*' for the metadata. + "data_description": "description", + "datadescription": "description" + } + return { + "resource_name": meta.get( + "dataname", + meta.get("data_name", + "Unnamed phenotypes - " + datetime.now().isoformat())), + "resource_metadata": { + rkey: meta[mkey] + for mkey, rkey in _key_mappings_.items() if mkey in meta + } + } + + def main(): """Entry-point for this script.""" args = parse_args() @@ -515,19 +537,6 @@ if __name__ == "__main__": logger.info("Updating authorisation.") _job_metadata = job["metadata"] - def __parse_resource_details__(meta) -> dict: - _key_mappings_ = { - "data_description": "description", - } - return { - "resource_name": _job_metadata["dataname"], - "resource_metadata": { - rkey: meta[mkey] - for mkey, rkey in _key_mappings_.items() - if mkey in meta - } - } - return update_auth((_job_metadata["authserver"], _job_metadata["token"]), __parse_resource_details__(_job_metadata), diff --git a/scripts/rqtl2/phenotypes_qc.py b/scripts/rqtl2/phenotypes_qc.py index 72d6c83..084c876 100644 --- a/scripts/rqtl2/phenotypes_qc.py +++ b/scripts/rqtl2/phenotypes_qc.py @@ -198,7 +198,7 @@ def qc_phenocovar_file( "-", "-", (f"File {filepath.name} is missing the {heading} heading " - "in the header line."))),) + "in the header row/line."))),) def collect_errors(errors_and_linecount, line): _errs, _lc = errors_and_linecount @@ -312,8 +312,9 @@ def qc_pheno_file(# pylint: disable=[too-many-locals, too-many-arguments, too-ma "header row", "-", ", ".join(_absent), - ("The following phenotype names do not exist in any of the " - f"provided phenocovar files: ({', '.join(_absent)})"))),) + ("The following trait names/identifiers do not exist in any of " + "the provided descriptions/covariates files: " + f"({', '.join(_absent)})"))),) def collect_errors(errors_and_linecount, line): _errs, _lc = errors_and_linecount diff --git a/scripts/run_qtlreaper.py b/scripts/run_qtlreaper.py index ab19da0..2269ea6 100644 --- a/scripts/run_qtlreaper.py +++ b/scripts/run_qtlreaper.py @@ -6,6 +6,7 @@ import time import secrets import logging import subprocess +import multiprocessing from pathlib import Path from functools import reduce from typing import Union, Iterator @@ -147,13 +148,17 @@ def dispatch(args: Namespace) -> int: _qtlreaper_main_output = args.working_dir.joinpath( f"main-output-{secrets.token_urlsafe(15)}.tsv")#type: ignore[attr-defined] + _qtlreaper_permu_output = args.working_dir.joinpath( + f"permu-output-{secrets.token_urlsafe(15)}.tsv") logger.debug("Main output filename: %s", _qtlreaper_main_output) with subprocess.Popen( ("qtlreaper", "--n_permutations", "1000", "--geno", _genofile, "--traits", _traitsfile, - "--main_output", _qtlreaper_main_output), + "--main_output", _qtlreaper_main_output, + "--permu_output", _qtlreaper_permu_output, + "--threads", str(int(1+(multiprocessing.cpu_count()/2)))), env=({**os.environ, "RUST_BACKTRACE": "full"} if logger.getEffectiveLevel() == logging.DEBUG else dict(os.environ))) as _qtlreaper: @@ -172,8 +177,17 @@ def dispatch(args: Namespace) -> int: logger.debug("Cleaning up temporary files.") # short-circuits to delete file if exists - _traitsfile.exists() and _traitsfile.unlink() - _qtlreaper_main_output.exists() and _qtlreaper_main_output.unlink() + if _traitsfile.exists(): + _traitsfile.unlink() + logger.info("Deleted generated traits' file for QTLReaper.") + + if _qtlreaper_main_output.exists(): + _qtlreaper_main_output.unlink() + logger.info("Deleted QTLReaper's main output file.") + + if _qtlreaper_permu_output.exists(): + _qtlreaper_permu_output.unlink() + logger.info("Deleted QTLReaper's permutations file.") if _qtlreaper.returncode != 0: return _qtlreaper.returncode diff --git a/uploader/__init__.py b/uploader/__init__.py index e00c726..afaa78d 100644 --- a/uploader/__init__.py +++ b/uploader/__init__.py @@ -131,13 +131,8 @@ def create_app(config: Optional[dict] = None): default_timeout=int(app.config["SESSION_FILESYSTEM_CACHE_TIMEOUT"])) setup_logging(app) - setup_modules_logging(app.logger, ( - "uploader.base_routes", - "uploader.flask_extensions", - "uploader.publications.models", - "uploader.publications.datatables", - "uploader.phenotypes.models", - "uploader.phenotypes.views")) + setup_modules_logging( + app.logger, tuple(app.config.get("LOGGABLE_MODULES", []))) # setup jinja2 symbols app.add_template_global(user_logged_in) diff --git a/uploader/default_settings.py b/uploader/default_settings.py index 6381a67..04e1c0a 100644 --- a/uploader/default_settings.py +++ b/uploader/default_settings.py @@ -39,3 +39,6 @@ JWKS_DELETION_AGE_DAYS = 14 # Days (from creation) to keep a JWK around before d ## --- Feature flags --- FEATURE_FLAGS_HTTP: list[str] = [] + +## --- Modules for which to log output --- +LOGGABLE_MODULES: list[str] = [] diff --git a/uploader/genotypes/models.py b/uploader/genotypes/models.py index 4c3e634..34d2cfe 100644 --- a/uploader/genotypes/models.py +++ b/uploader/genotypes/models.py @@ -31,16 +31,28 @@ def genotype_markers( species_id: int, offset: int = 0, limit: Optional[int] = None -) -> tuple[dict, ...]: +) -> tuple[tuple[dict, ...], int]: """Retrieve markers from the database.""" - _query = "SELECT * FROM Geno WHERE SpeciesId=%s" - if bool(limit) and limit > 0:# type: ignore[operator] - _query = _query + f" LIMIT {limit} OFFSET {offset}" + _query_template = ( + "SELECT %%COLS%% FROM Geno AS gno " + "WHERE gno.SpeciesId=%s " + "%%LIMIT%%") with conn.cursor(cursorclass=DictCursor) as cursor: - cursor.execute(_query, (species_id,)) + cursor.execute( + _query_template.replace("%%LIMIT%%", "").replace( + "%%COLS%%", "COUNT(gno.Id) AS total_records"), + (species_id,)) + _total_records = cursor.fetchone()["total_records"] + cursor.execute( + _query_template.replace("%%COLS%%", "gno.*").replace( + "%%LIMIT%%", + (f"LIMIT {int(limit)} OFFSET {int(offset)}" + if bool(limit) and limit > 0 + else "")), + (species_id,)) debug_query(cursor, app.logger) - return tuple(dict(row) for row in cursor.fetchall()) + return tuple(dict(row) for row in cursor.fetchall()), _total_records def genotype_dataset( diff --git a/uploader/genotypes/views.py b/uploader/genotypes/views.py index 3fa2131..f27671c 100644 --- a/uploader/genotypes/views.py +++ b/uploader/genotypes/views.py @@ -6,6 +6,7 @@ from pymonad.either import Left, Right, Either from gn_libs.mysqldb import database_connection from flask import (flash, request, + jsonify, redirect, Blueprint, render_template, @@ -19,8 +20,8 @@ from uploader.route_utils import generic_select_population from uploader.datautils import safe_int, enumerate_sequence 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.population.models import population_by_species_and_id +from uploader.request_checks import with_species, with_dataset, with_population from .models import (genotype_markers, genotype_dataset, @@ -56,34 +57,31 @@ def list_genotypes(species: dict, population: dict, **kwargs):# pylint: disable= @genotypesbp.route( - "/<int:species_id>/populations/<int:population_id>/genotypes/list-markers", + "/<int:species_id>/populations/<int:population_id>/genotypes/<int:dataset_id>/list-markers", methods=["GET"]) @require_login -@with_population(species_redirect_uri="species.list_species", - redirect_uri="species.populations.list_species_populations") -def list_markers( - species: dict, - population: dict, - **kwargs -):# pylint: disable=[unused-argument] - """List a species' genetic markers.""" +@with_species(redirect_uri="species.populations.genotypes.list_genotypes") +def list_markers(species: dict, **_kwargs): + """List the markers that exist for this species.""" + args = request.args + offset = int(args.get("start") or 0) with database_connection(app.config["SQL_URI"]) as conn: - start_from = max(safe_int(request.args.get("start_from") or 0), 0) - count = safe_int(request.args.get("count") or 20) - return render_template("genotypes/list-markers.html", - species=species, - population=population, - total_markers=genotype_markers_count( - conn, species["SpeciesId"]), - start_from=start_from, - count=count, - markers=enumerate_sequence( - genotype_markers(conn, - species["SpeciesId"], - offset=start_from, - limit=count), - start=start_from+1), - activelink="list-markers") + markers, total_records = genotype_markers( + conn, + species["SpeciesId"], + offset=offset, + limit=int(args.get("length") or 0)) + return jsonify({ + **({"draw": int(args.get("draw"))} + if bool(args.get("draw") or False) + else {}), + "recordsTotal": total_records, + "recordsFiltered": len(markers), + "markers": tuple({**marker, "index": idx} + for idx, marker in + enumerate(markers, start=offset+1)) + }) + @genotypesbp.route( "/<int:species_id>/populations/<int:population_id>/genotypes/datasets/" diff --git a/uploader/phenotypes/models.py b/uploader/phenotypes/models.py index 3946a0f..3d656d2 100644 --- a/uploader/phenotypes/models.py +++ b/uploader/phenotypes/models.py @@ -96,20 +96,34 @@ def dataset_phenotypes(# pylint: disable=[too-many-arguments, too-many-positiona xref_ids: tuple[int, ...] = tuple() ) -> tuple[dict, ...]: """Fetch the actual phenotypes.""" - _query = ( - "SELECT pheno.*, pxr.Id AS xref_id, pxr.InbredSetId, ist.InbredSetCode " + _narrow_by_ids = ( + f" AND pxr.Id IN ({', '.join(['%s'] * len(xref_ids))})" + if len(xref_ids) > 0 else "") + _narrow_by_limit = ( + f" LIMIT {limit} OFFSET {offset}" if bool(limit) else "") + _pub_query = ( + "SELECT pub.* " + "FROM PublishXRef AS pxr " + "INNER JOIN Publication AS pub ON pxr.PublicationId=pub.Id " + "WHERE pxr.InbredSetId=%s") + _narrow_by_ids + _pheno_query = (( + "SELECT pheno.*, pxr.Id AS xref_id, pxr.InbredSetId, pxr.PublicationId, " + "ist.InbredSetCode " "FROM Phenotype AS pheno " "INNER JOIN PublishXRef AS pxr ON pheno.Id=pxr.PhenotypeId " "INNER JOIN PublishFreeze AS pf ON pxr.InbredSetId=pf.InbredSetId " "INNER JOIN InbredSet AS ist ON pf.InbredSetId=ist.Id " - "WHERE pxr.InbredSetId=%s AND pf.Id=%s") + ( - f" AND pxr.Id IN ({', '.join(['%s'] * len(xref_ids))})" - if len(xref_ids) > 0 else "") + ( - f" LIMIT {limit} OFFSET {offset}" if bool(limit) else "") + "WHERE pxr.InbredSetId=%s AND pf.Id=%s") + + _narrow_by_ids + + _narrow_by_limit) with conn.cursor(cursorclass=DictCursor) as cursor: - cursor.execute(_query, (population_id, dataset_id) + xref_ids) + cursor.execute(_pub_query, (population_id,) + xref_ids) debug_query(cursor, logger) - return tuple(dict(row) for row in cursor.fetchall()) + _pubs = {row["Id"]: dict(row) for row in cursor.fetchall()} + cursor.execute(_pheno_query, (population_id, dataset_id) + xref_ids) + debug_query(cursor, logger) + return tuple({**dict(row), "publication": _pubs[row["PublicationId"]]} + for row in cursor.fetchall()) def __phenotype_se__(cursor: BaseCursor, xref_id, dataids_and_strainids): diff --git a/uploader/publications/models.py b/uploader/publications/models.py index dcfa02b..d913144 100644 --- a/uploader/publications/models.py +++ b/uploader/publications/models.py @@ -101,6 +101,20 @@ def fetch_publication_by_id(conn: Connection, publication_id: int) -> dict: return dict(_res) if _res else {} +def fetch_publications_by_ids( + conn: Connection, publications_ids: tuple[int, ...] +) -> tuple[dict, ...]: + """Fetch publications with the given IDs.""" + if len(publications_ids) == 0: + return tuple() + + with conn.cursor(cursorclass=DictCursor) as cursor: + paramstr = ", ".join(["%s"] * len(publications_ids)) + cursor.execute(f"SELECT * FROM Publication WHERE Id IN ({paramstr})", + tuple(publications_ids)) + return tuple(dict(row) for row in cursor.fetchall()) + + def fetch_publication_phenotypes( conn: Connection, publication_id: int) -> Iterable[dict]: """Fetch all phenotypes linked to this publication.""" diff --git a/uploader/publications/views.py b/uploader/publications/views.py index d9eb294..89e9f5d 100644 --- a/uploader/publications/views.py +++ b/uploader/publications/views.py @@ -1,5 +1,6 @@ """Endpoints for publications""" import json +import datetime from gn_libs.mysqldb import database_connection from flask import ( @@ -89,9 +90,12 @@ def create_publication(): } if request.method == "GET": + now = datetime.datetime.now() return render_template( "publications/create-publication.html", - get_args=_get_args) + get_args=_get_args, + current_year=now.year, + current_month=now.strftime("%B")) form = request.form authors = form.get("publication-authors").encode("utf8") if authors is None or authors == "": diff --git a/uploader/request_checks.py b/uploader/request_checks.py index f1d8027..84935f9 100644 --- a/uploader/request_checks.py +++ b/uploader/request_checks.py @@ -2,14 +2,20 @@ These are useful for reusability, and hence maintainability of the code. """ +import logging + +from typing import Callable from functools import wraps -from gn_libs.mysqldb import database_connection +from gn_libs.mysqldb import Connection, database_connection from flask import flash, url_for, redirect, current_app as app from uploader.species.models import species_by_id from uploader.population.models import population_by_species_and_id +logger = logging.getLogger(__name__) + + def with_species(redirect_uri: str): """Ensure the species actually exists.""" def __decorator__(function): @@ -28,7 +34,7 @@ def with_species(redirect_uri: str): "alert-danger") return redirect(url_for(redirect_uri)) except ValueError as _verr: - app.logger.debug( + logger.debug( "Exception converting value to integer: %s", kwargs.get("species_id"), exc_info=True) @@ -63,7 +69,7 @@ def with_population(species_redirect_uri: str, redirect_uri: str): "alert-danger") return select_population_uri except ValueError as _verr: - app.logger.debug( + logger.debug( "Exception converting value to integer: %s", kwargs.get("population_id"), exc_info=True) @@ -73,3 +79,45 @@ def with_population(species_redirect_uri: str, redirect_uri: str): return function(**{**kwargs, "population": population}) return __with_population__ return __decorator__ + + +def with_dataset( + species_redirect_uri: str, + population_redirect_uri: str, + redirect_uri: str, + dataset_by_id: Callable[ + [Connection, int, int, int], + dict] +): + """Ensure the dataset actually exists.""" + def __decorator__(func): + @wraps(func) + @with_population(species_redirect_uri, population_redirect_uri) + def __with_dataset__(**kwargs): + try: + _spcid = int(kwargs["species_id"]) + _popid = int(kwargs["population_id"]) + _dsetid = int(kwargs.get("dataset_id")) + select_dataset_uri = redirect(url_for( + redirect_uri, species_id=_spcid, population_id=_popid)) + if not bool(_dsetid): + flash("You need to select a valid 'dataset_id' value.", + "alert-danger") + return select_dataset_uri + with database_connection(app.config["SQL_URI"]) as conn: + dataset = dataset_by_id(conn, _spcid, _popid, _dsetid) + if not bool(dataset): + flash("You must select a valid dataset.", + "alert-danger") + return select_dataset_uri + except ValueError as _verr: + logger.debug( + "Exception converting 'dataset_id' to integer: %s", + kwargs.get("dataset_id"), + exc_info=True) + flash("Expected 'dataset_id' value to be an integer." + "alert-danger") + return select_dataset_uri + return func(**{**kwargs, "dataset": dataset}) + return __with_dataset__ + return __decorator__ diff --git a/uploader/templates/genotypes/create-dataset.html b/uploader/templates/genotypes/create-dataset.html index 5a6d3d2..ff174fb 100644 --- a/uploader/templates/genotypes/create-dataset.html +++ b/uploader/templates/genotypes/create-dataset.html @@ -41,9 +41,9 @@ <small class="form-text text-muted"> <p>This is a short representative, but constrained name for the genotype dataset.<br /> - The field will only accept letters ('A-Za-z'), numbers (0-9), hyphens - and underscores. Any other character will cause the name to be - rejected.</p></small> + It is used internally by the Genenetwork system. Do not change this + value.</p> + </small> </div> <div class="form-group"> diff --git a/uploader/templates/genotypes/index.html b/uploader/templates/genotypes/index.html deleted file mode 100644 index b50ebc5..0000000 --- a/uploader/templates/genotypes/index.html +++ /dev/null @@ -1,32 +0,0 @@ -{%extends "genotypes/base.html"%} -{%from "flash_messages.html" import flash_all_messages%} -{%from "species/macro-select-species.html" import select_species_form%} - -{%block title%}Genotypes{%endblock%} - -{%block pagetitle%}Genotypes{%endblock%} - - -{%block contents%} -{{flash_all_messages()}} - -<div class="row"> - <p> - This section allows you to upload genotype information for your experiments, - in the case that you have not previously done so. - </p> - <p> - We'll need to link the genotypes to the species and population, so do please - go ahead and select those in the next two steps. - </p> -</div> - -<div class="row"> - {{select_species_form(url_for("species.populations.genotypes.index"), - species)}} -</div> -{%endblock%} - -{%block javascript%} -<script type="text/javascript" src="/static/js/species.js"></script> -{%endblock%} diff --git a/uploader/templates/genotypes/list-genotypes.html b/uploader/templates/genotypes/list-genotypes.html index be297a4..131576f 100644 --- a/uploader/templates/genotypes/list-genotypes.html +++ b/uploader/templates/genotypes/list-genotypes.html @@ -54,12 +54,54 @@ </div> <div class="row"> - <h2 class="subheading">Genetic Markers</h2> + <h2>Genotype Dataset</h2> +</div> + +{%if dataset is not none%} + +<div class="row"> + <h3>Dataset Details</h3> + <table class="table"> + <thead> + <tr> + <th>Name</th> + <th>Full Name</th> + </tr> + </thead> - <table id="tbl-genetic-markers" class="table compact stripe cell-border" - data-genetic-markers='{{genetic_markers | tojson}}'> + <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}}'" + target="_blank"> + {{dataset.FullName}}</a></td> + </tr> + </tbody> + </table> + + <p> + To see more information regarding this dataset (e.g. which markers have + sample allele data, the allele data itself, etc) click on the "Full Name" + link above.</p> +</div> + +<div class="row"> + <h3>Genotype Markers</h3> + + <div class="row"> + <p> + The table below lists all of the markers that exist for species + {{species.SpeciesName}} ({{species.FullName}}), regardless of whether + (or not) we have corresponding sample allele data for a particular marker. + </p> + <table id="tbl-genetic-markers" class="table compact stripe cell-border"> <thead> <tr> + <th title="">#</th> <th title="">Index</th> <th title="">Marker Name</th> <th title="Chromosome">Chr</th> @@ -78,57 +120,24 @@ <td></td> <td></td> <td></td> + <td></td> </tr> {%endfor%} </tbody> </table> </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> +{%else%} <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 not none%} - <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> + Your genotype data will need to be under a dataset. Unfortunately there is + currently no dataset defined for this population. + </p> + <p class="text-warning"> <span class="glyphicon glyphicon-exclamation-sign"></span> - There is no genotype dataset defined for this population. + Click the button below to define the genotype dataset for this population. </p> <p> <a href="{{url_for('species.populations.genotypes.create_dataset', @@ -137,35 +146,81 @@ 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> -<div class="row text-warning"> - <p> - <span class="glyphicon glyphicon-exclamation-sign"></span> - <strong>NOTE</strong>: Currently the GN2 (and related) system(s) expect a - single genotype dataset. If there is more than one, the system apparently - fails in unpredictable ways. - </p> - <p>Fix this to allow multiple datasets, each with a different assembly from - all the rest.</p> + +{%endif%} + +<div class="row"> + <h2>Notes</h2> + <div class="row text-danger"> + <h3>Genetic Markers: 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 text-warning"> + <h3>Genotype Dataset</h3> + <p> + <span class="glyphicon glyphicon-exclamation-sign"></span> + <strong>NOTE</strong>: Currently the GN2 (and related) system(s) expect a + single genotype dataset per population. If there is more than one, the + system apparently fails in unpredictable ways. + </p> + </div> </div> + {%endblock%} {%block javascript%} <script type="text/javascript"> + $(function() { var dtGeneticMarkers = buildDataTable( "#tbl-genetic-markers", - JSON.parse($("#tbl-genetic-markers").attr("data-genetic-markers")), + [], [ + { + data: function(marker) { + return `<input type="checkbox" name="selected-markers" ` + + `id="chk-selected-markers-${marker.Id}-${marker.GenoFreezeId}" ` + + `value="${marker.Id}_${marker.GenoFreezeId}" ` + + `class="chk-row-select" />`; + } + }, {data: 'index'}, - {data: 'Name'}, - {data: 'Chr'}, - {data: 'Mb'}, - {data: 'Source'}, - {data: 'Source2'} - ]); - }) + {data: "Name", searchable: true}, + {data: "Chr", searchable: true}, + {data: "Mb", searchable: true}, + {data: "Source", searchable: true}, + {data: "Source2", searchable: true} + ], + { + ajax: { + url: "{{url_for('species.populations.genotypes.list_markers', species_id=species.SpeciesId, population_id=population.Id, dataset_id=dataset.Id)}}", + dataSrc: "markers" + }, + paging: true, + scroller: true, + scrollY: "50vh", + scrollCollapse: true, + layout: { + top: "info", + topStart: null, + topEnd: null, + bottom: null, + bottomStart: null, + bottomEnd: null + } + }); + }); + </script> {%endblock%} diff --git a/uploader/templates/genotypes/view-dataset.html b/uploader/templates/genotypes/view-dataset.html index 1c4eccf..d95a8e3 100644 --- a/uploader/templates/genotypes/view-dataset.html +++ b/uploader/templates/genotypes/view-dataset.html @@ -46,8 +46,9 @@ <div class="row"> <h2>Genotype Data</h2> - <p class="text-danger"> - Provide link to enable uploading of genotype data here.</p> + <div class="col" style="margin-bottom: 3px;"> + <a href="#" class="btn btn-primary not-implemented">upload genotypes</a> + </div> </div> {%endblock%} diff --git a/uploader/templates/phenotypes/create-dataset.html b/uploader/templates/phenotypes/create-dataset.html index 6eced05..9963953 100644 --- a/uploader/templates/phenotypes/create-dataset.html +++ b/uploader/templates/phenotypes/create-dataset.html @@ -49,7 +49,7 @@ class="form-control" {%endif%} required="required" - disabled="disabled" /> + readonly="readonly" /> <small class="form-text text-muted"> <p>A short representative name for the dataset.</p> <p>Recommended: Use the population name and append "Publish" at the end. diff --git a/uploader/templates/phenotypes/view-dataset.html b/uploader/templates/phenotypes/view-dataset.html index 3bb2586..fc84757 100644 --- a/uploader/templates/phenotypes/view-dataset.html +++ b/uploader/templates/phenotypes/view-dataset.html @@ -148,14 +148,36 @@ return `<a href="${url.toString()}" target="_blank">` + `${pheno.InbredSetCode}_${pheno.xref_id}` + `</a>`; - } + }, + title: "Record", + visible: true, + searchable: true }, { data: function(pheno) { return (pheno.Post_publication_description || pheno.Original_description || pheno.Pre_publication_description); - } + }, + title: "Description", + visible: true, + searchable: true + }, + { + data: function(pheno) { + return pheno.publication.Title; + }, + title: "Publication Title", + visible: false, + searchable: true + }, + { + data: function(pheno) { + return pheno.publication.Authors; + }, + title: "Authors", + visible: false, + searchable: true } ], { diff --git a/uploader/templates/publications/create-publication.html b/uploader/templates/publications/create-publication.html index fb0127d..da5889e 100644 --- a/uploader/templates/publications/create-publication.html +++ b/uploader/templates/publications/create-publication.html @@ -91,22 +91,22 @@ class="col-sm-2 col-form-label"> Month</label> <div class="col-sm-4"> - <select class="form-control" + <select class="form-select" id="select-publication-month" name="publication-month"> <option value="">Select a month</option> - <option value="january">January</option> - <option value="february">February</option> - <option value="march">March</option> - <option value="april">April</option> - <option value="may">May</option> - <option value="june">June</option> - <option value="july">July</option> - <option value="august">August</option> - <option value="september">September</option> - <option value="october">October</option> - <option value="november">November</option> - <option value="december">December</option> + <option {%if current_month | lower == "january"%}selected="selected"{%endif%}value="january">January</option> + <option {%if current_month | lower == "february"%}selected="selected"{%endif%}value="february">February</option> + <option {%if current_month | lower == "march"%}selected="selected"{%endif%}value="march">March</option> + <option {%if current_month | lower == "april"%}selected="selected"{%endif%}value="april">April</option> + <option {%if current_month | lower == "may"%}selected="selected"{%endif%}value="may">May</option> + <option {%if current_month | lower == "june"%}selected="selected"{%endif%}value="june">June</option> + <option {%if current_month | lower == "july"%}selected="selected"{%endif%}value="july">July</option> + <option {%if current_month | lower == "august"%}selected="selected"{%endif%}value="august">August</option> + <option {%if current_month | lower == "september"%}selected="selected"{%endif%}value="september">September</option> + <option {%if current_month | lower == "october"%}selected="selected"{%endif%}value="october">October</option> + <option {%if current_month | lower == "november"%}selected="selected"{%endif%}value="november">November</option> + <option {%if current_month | lower == "december"%}selected="selected"{%endif%}value="december">December</option> </select> <span class="form-text text-muted">Month of publication</span> </div> @@ -119,7 +119,10 @@ id="txt-publication-year" name="publication-year" class="form-control" - min="1960" /> + min="1960" + max="{{current_year}}" + value="{{current_year or ''}}" + required="required" /> <span class="form-text text-muted">Year of publication</span> </div> </div> |
