From 0d6c9ab0b34199e94aaf262f7a7eb7f6e0f77b72 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 29 Apr 2019 11:12:48 -0500 Subject: Started API readme file --- api_readme.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 api_readme.md diff --git a/api_readme.md b/api_readme.md new file mode 100644 index 00000000..eef0b2d7 --- /dev/null +++ b/api_readme.md @@ -0,0 +1,62 @@ +# API Command Documentation # + +## Fetch Species List ## + +To get a list of species with data available in GN (and their associated names and ids): +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/species +[ + { + "FullName": "Mus musculus", + "Id": 1, + "Name": "mouse", + "TaxonomyId": 10090 + }, + ... + { + "FullName": "Populus trichocarpa", + "Id": 10, + "Name": "poplar", + "TaxonomyId": 3689 + } +] +``` + +Or to get a single species info: +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/species/mouse +``` +OR +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/species/mouse.json +``` + +*For all queries where the last field is a user-specified name/ID, there will be the option to append a file format type. Currently there is only JSON (and it will default to JSON if none is provided), but other formats will be added later* + +## Fetch Groups/RISets ## + +This query can optionally filter by species: + +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/groups (for all species) +``` +OR +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/mouse/groups (for just mouse groups/RISets) +[ { "DisplayName": "BXD", "FullName": "BXD RI Family", "GeneticType": "riset", "Id": 1, "MappingMethodId": "1", "Name": "BXD", "SpeciesId": 1, "public": 2 }, ... { "DisplayName": "AIL LGSM F34 and F39-43 (GBS)", "FullName": "AIL LGSM F34 and F39-43 (GBS)", "GeneticType": "intercross", "Id": 72, "MappingMethodId": "2", "Name": "AIL-LGSM-F34-F39-43-GBS", "SpeciesId": 1, "public": 2 } ] +``` + +## Fetch Datasets ## +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/datasets/bxd +``` +OR +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/datasets/mouse/bxd +[ { "AvgID": 1, "CreateTime": "Fri, 01 Aug 2003 00:00:00 GMT", "DataScale": "log2", "FullName": "UTHSC/ETHZ/EPFL BXD Liver Polar Metabolites Extraction A, CD Cohorts (Mar 2017) log2", "Id": 1, "Long_Abbreviation": "BXDMicroArray_ProbeSet_August03", "ProbeFreezeId": 3, "ShortName": "Brain U74Av2 08/03 MAS5", "Short_Abbreviation": "Br_U_0803_M", "confidentiality": 0, "public": 0 }, ... { "AvgID": 3, "CreateTime": "Tue, 14 Aug 2018 00:00:00 GMT", "DataScale": "log2", "FullName": "EPFL/LISP BXD CD Liver Affy Mouse Gene 1.0 ST (Aug18) RMA", "Id": 859, "Long_Abbreviation": "EPFLMouseLiverCDRMAApr18", "ProbeFreezeId": 181, "ShortName": "EPFL/LISP BXD CD Liver Affy Mouse Gene 1.0 ST (Aug18) RMA", "Short_Abbreviation": "EPFLMouseLiverCDRMA0818", "confidentiality": 0, "public": 1 } ] +``` +(I added the option to specify species just in case we end up with the same group name across multiple species at some point, though it's currently unnecessary) + + + + -- cgit v1.2.3 From 7daf96560d0b1f945a0a5fd0aed4a9961b718635 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 29 Apr 2019 11:48:51 -0500 Subject: Added the rest of the currently existing commands --- api_readme.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/api_readme.md b/api_readme.md index eef0b2d7..00ba25d7 100644 --- a/api_readme.md +++ b/api_readme.md @@ -1,25 +1,11 @@ -# API Command Documentation # +# API Query Documentation # ## Fetch Species List ## To get a list of species with data available in GN (and their associated names and ids): ``` curl http://gn2-zach.genenetwork.org/api/v_pre1/species -[ - { - "FullName": "Mus musculus", - "Id": 1, - "Name": "mouse", - "TaxonomyId": 10090 - }, - ... - { - "FullName": "Populus trichocarpa", - "Id": 10, - "Name": "poplar", - "TaxonomyId": 3689 - } -] +[ { "FullName": "Mus musculus", "Id": 1, "Name": "mouse", "TaxonomyId": 10090 }, ... { "FullName": "Populus trichocarpa", "Id": 10, "Name": "poplar", "TaxonomyId": 3689 } ] ``` Or to get a single species info: @@ -57,6 +43,60 @@ curl http://gn2-zach.genenetwork.org/api/v_pre1/datasets/mouse/bxd ``` (I added the option to specify species just in case we end up with the same group name across multiple species at some point, though it's currently unnecessary) +## Fetch Individual Dataset Info ## +### For mRNA Assay/"ProbeSet" ### +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/dataset/HC_M2_0606_P +``` +OR +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/dataset/bxd/HC_M2_0606_P``` +{ "confidential": 0, "data_scale": "log2", "dataset_type": "mRNA expression", "full_name": "Hippocampus Consortium M430v2 (Jun06) PDNN", "id": 112, "name": "HC_M2_0606_P", "public": 2, "short_name": "Hippocampus M430v2 BXD 06/06 PDNN", "tissue": "Hippocampus mRNA", "tissue_id": 9 } +``` +(This also has the option to specify group/riset) +### For "Phenotypes" (basically non-mRNA Expression; stuff like weight, sex, etc) ### +For these traits, the query fetches publication info and takes the group and phenotype 'ID' as input. For example: +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/dataset/bxd/10001 +{ "dataset_type": "phenotype", "description": "Central nervous system, morphology: Cerebellum weight, whole, bilateral in adults of both sexes [mg]", "id": 10001, "name": "CBLWT2", "pubmed_id": 11438585, "title": "Genetic control of the mouse cerebellum: identification of quantitative trait loci modulating size and architecture", "year": "2001" } +``` +## Fetch Sample Data for Single Trait ## +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/sample_data/HC_M2_0606_P/1436869_at +[ { "data_id": 23415463, "sample_name": "129S1/SvImJ", "sample_name_2": "129S1/SvImJ", "se": 0.123, "value": 8.201 }, { "data_id": 23415463, "sample_name": "A/J", "sample_name_2": "A/J", "se": 0.046, "value": 8.413 }, { "data_id": 23415463, "sample_name": "AKR/J", "sample_name_2": "AKR/J", "se": 0.134, "value": 8.856 }, ... ] +``` + +## Fetch Trait Info (Name, Description, Location, etc) ## +### For mRNA Expression/"ProbeSet" ### +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/trait/HC_M2_0606_P/1436869_at +{ "additive": -0.214087568058076, "alias": "HHG1; HLP3; HPE3; SMMCI; Dsh; Hhg1", "chr": "5", "description": "sonic hedgehog (hedgehog)", "id": 99602, "locus": "rs8253327", "lrs": 12.7711275309832, "mb": 28.457155, "mean": 9.27909090909091, "name": "1436869_at", "p_value": 0.306, "se": null, "symbol": "Shh" } +``` + +### For "Phenotypes" ### +For phenotypes this just gets the max LRS, its location, and additive effect (as calculated by qtlreaper) + +Since each group/riset only has one phenotype "dataset", this query takes either the group/riset name or the group/riset name + "Publish" (for example "BXDPublish", which is the dataset name in the DB) as input +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/trait/BXD/10001 +{ "additive": 2.39444435069444, "id": 4, "locus": "rs48756159", "lrs": 13.4974911471087 } +``` + +## Calculate Correlation ## +Currently only Pearson Sample and Tissue correlations are implemented + +This query currently takes the following parameters (though more will be added): +* trait_id (*required*) - ID for trait used for correlation +* db (*required*) - DB name for the trait above (this is the Short_Abbreviation listed when you query for datasets) +* target_db (*required*) - Target DB name to be correlated against +* type - Sample or Tissue (default = Sample) +* return - Number of results to return (default = 500) + +Example query: +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/correlation?trait_id=1427571_at&db=HC_M2_0606_P&target_db=BXDPublish&type=sample&return_count=100 +[ { "#_strains": 6, "p_value": 0.004804664723032055, "sample_r": -0.942857142857143, "trait": 20511 }, { "#_strains": 6, "p_value": 0.004804664723032055, "sample_r": -0.942857142857143, "trait": 20724 }, { "#_strains": 12, "p_value": 1.8288943424888848e-05, "sample_r": -0.9233615170820528, "trait": 13536 }, { "#_strains": 7, "p_value": 0.006807187408935392, "sample_r": 0.8928571428571429, "trait": 10157 }, { "#_strains": 7, "p_value": 0.006807187408935392, "sample_r": -0.8928571428571429, "trait": 20392 }, ... ] +``` -- cgit v1.2.3 From c39a7ca68c2584c49a86fac2d05144a1b32ccd56 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 3 May 2019 11:06:47 -0500 Subject: Added examples for fetching sample data & genotypes --- api_readme.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/api_readme.md b/api_readme.md index 00ba25d7..d0102d67 100644 --- a/api_readme.md +++ b/api_readme.md @@ -32,6 +32,12 @@ curl http://gn2-zach.genenetwork.org/api/v_pre1/mouse/groups (for just mouse gro [ { "DisplayName": "BXD", "FullName": "BXD RI Family", "GeneticType": "riset", "Id": 1, "MappingMethodId": "1", "Name": "BXD", "SpeciesId": 1, "public": 2 }, ... { "DisplayName": "AIL LGSM F34 and F39-43 (GBS)", "FullName": "AIL LGSM F34 and F39-43 (GBS)", "GeneticType": "intercross", "Id": 72, "MappingMethodId": "2", "Name": "AIL-LGSM-F34-F39-43-GBS", "SpeciesId": 1, "public": 2 } ] ``` +## Fetch Genotypes for Group/RISet ## +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/genotypes/BXD +``` +Returns a CSV file with metadata in the first few rows, sample/strain names as columns, and markers as rows. Currently only works for genotypes we have stored in .geno files; I'll add the option to download BIMBAM files soon. + ## Fetch Datasets ## ``` curl http://gn2-zach.genenetwork.org/api/v_pre1/datasets/bxd @@ -43,6 +49,13 @@ curl http://gn2-zach.genenetwork.org/api/v_pre1/datasets/mouse/bxd ``` (I added the option to specify species just in case we end up with the same group name across multiple species at some point, though it's currently unnecessary) +## Fetch Sample Data for Dataset ## +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/sample_data/HSNIH-PalmerPublish.csv +``` + +Returns a CSV file with sample/strain names as the columns and trait IDs as rows + ## Fetch Individual Dataset Info ## ### For mRNA Assay/"ProbeSet" ### -- cgit v1.2.3 From 7749cec3336f5ecd3233be7f91cf5750a270c37d Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 21 May 2019 15:40:29 -0500 Subject: Added mapping and some other changes to readme --- api_readme.md | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/api_readme.md b/api_readme.md index d0102d67..96e8b246 100644 --- a/api_readme.md +++ b/api_readme.md @@ -1,5 +1,7 @@ # API Query Documentation # - +--- +# Fetching Dataset/Trait info/data # +--- ## Fetch Species List ## To get a list of species with data available in GN (and their associated names and ids): @@ -98,14 +100,52 @@ curl http://gn2-zach.genenetwork.org/api/v_pre1/trait/BXD/10001 { "additive": 2.39444435069444, "id": 4, "locus": "rs48756159", "lrs": 13.4974911471087 } ``` +--- + +# Analyses # +--- +## Mapping ## +Currently two mapping tools can be used - GEMMA and R/qtl. qtlreaper will be added later with Christian Fischer's RUST implementation - https://github.com/chfi/rust-qtlreaper + +Each method's query takes the following parameters respectively (more will be added): +### GEMMA ### +* trait_id (*required*) - ID for trait being mapped +* db (*required*) - DB name for trait above (Short_Abbreviation listed when you query for datasets) +* use_loco - Whether to use LOCO (leave one chromosome out) method (default = false) +* maf - minor allele frequency (default = 0.01) + +Example query: +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/mapping?trait_id=10015&db=BXDPublish&method=gemma&use_loco=true +``` + +### R/qtl ### +(See the R/qtl guide for information on some of these options - http://www.rqtl.org/manual/qtl-manual.pdf) +* trait_id (*required*) - ID for trait being mapped +* db (*required*) - DB name for trait above (Short_Abbreviation listed when you query for datasets) +* rqtl_method - hk (default) | ehk | em | imp | mr | mr-imp | mr-argmax ; Corresponds to the "method" option for the R/qtl scanone function. +* rqtl_model - normal (default) | binary | 2-part | np ; corresponds to the "model" option for the R/qtl scanone function +* num_perm - number of permutations; 0 by default +* control_marker - Name of marker to use as control; this relies on the user knowing the name of the marker they want to use as a covariate +* interval_mapping - Whether to use interval mapping; "false" by default +* pair_scan - *NYI* + +Example query: +``` +curl http://gn2-zach.genenetwork.org/api/v_pre1/mapping?trait_id=1418701_at&db=HC_M2_0606_P&method=rqtl&num_perm=100 +``` + +Some combinations of methods/models may not make sense. The R/qtl manual should be referred to for any questions on its use (specifically the scanone function in this case) + ## Calculate Correlation ## -Currently only Pearson Sample and Tissue correlations are implemented +Currently only Sample and Tissue correlations are implemented This query currently takes the following parameters (though more will be added): * trait_id (*required*) - ID for trait used for correlation * db (*required*) - DB name for the trait above (this is the Short_Abbreviation listed when you query for datasets) * target_db (*required*) - Target DB name to be correlated against -* type - Sample or Tissue (default = Sample) +* type - sample (default) | tissue +* method - pearson (default) | spearman * return - Number of results to return (default = 500) Example query: -- cgit v1.2.3 From 7dd9a3f8d232f3f747167de082eff22c30657ea0 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 21 Apr 2020 16:42:22 -0500 Subject: Testing --- wqflask/wqflask/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html index ec55ebeb..90be6111 100644 --- a/wqflask/wqflask/templates/base.html +++ b/wqflask/wqflask/templates/base.html @@ -4,7 +4,7 @@ - {% block title %}{% endblock %} GeneNetwork + {% block title %}{% endblock %} GeneNetwork2 -- cgit v1.2.3 From 1444f5618361a1c91bd792cfab8c8c53beb3421c Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 21 Apr 2020 16:43:11 -0500 Subject: Testing 2 --- wqflask/wqflask/templates/tutorials.html | 1 + 1 file changed, 1 insertion(+) create mode 100644 wqflask/wqflask/templates/tutorials.html diff --git a/wqflask/wqflask/templates/tutorials.html b/wqflask/wqflask/templates/tutorials.html new file mode 100644 index 00000000..44e6a2c4 --- /dev/null +++ b/wqflask/wqflask/templates/tutorials.html @@ -0,0 +1 @@ +Testing123 -- cgit v1.2.3 From 2e4ca1f6732d14c0ce0d953f1c08ecb000a94747 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Thu, 30 Apr 2020 13:44:52 -0500 Subject: GN2 install checklist --- doc/README.org | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/doc/README.org b/doc/README.org index 828ed2cd..b0f1c56b 100644 --- a/doc/README.org +++ b/doc/README.org @@ -658,24 +658,24 @@ The following derivations would be built: Let's see how fast we can deploy a second copy of GN2. -- [-] Base install - + [X] First install a Debian server with GNU Guix on board - + [X] Get Guix build going - - [X] Build the correct version of Guix - - [X] Check out the correct gn-stable version of guix-bioinformatics http://git.genenetwork.org/pjotrp/guix-bioinformatics - - [X] guix package -i genenetwork2 -p /usr/local/guix-profiles/gn2-stable - + [X] Create a gn2 user and home with space - + [X] Install redis (currently debian) - - [X] add to systemd - - [X] update redis.cnf - - [X] update database - + [X] Install mariadb (currently debian mariadb-server) - - [X] add to systemd - - [X] system stop mysql - - [X] update mysql.cnf - - [X] update database (see gn-services/services/mariadb.md) - - [X] check tables +- [ ] Base install + + [ ] First install a Debian server with GNU Guix on board + + [ ] Get Guix build going + - [ ] Build the correct version of Guix + - [ ] Check out the correct gn-stable version of guix-bioinformatics http://git.genenetwork.org/pjotrp/guix-bioinformatics + - [ ] guix package -i genenetwork2 -p /usr/local/guix-profiles/gn2-stable + + [ ] Create a gn2 user and home with space + + [ ] Install redis (currently debian) + - [ ] add to systemd + - [ ] update redis.cnf + - [ ] update database + + [ ] Install mariadb (currently debian mariadb-server) + - [ ] add to systemd + - [ ] system stop mysql + - [ ] update mysql.cnf + - [ ] update database (see gn-services/services/mariadb.md) + - [ ] check tables + [ ] run gn2 (rust-qtlreaper not working) - + [X] update nginx + + [ ] update nginx + [ ] install genenetwork3 - [ ] add to systemd -- cgit v1.2.3 From ff94904574c51eeb7aecb327d6f2679fa4a60fb4 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 28 May 2020 20:24:01 -0500 Subject: Added lines calling proxy for publish datasets + added some resource redis queries and a missing import for the hmac functions --- wqflask/base/trait.py | 12 ++++++---- wqflask/utility/hmac.py | 2 ++ wqflask/utility/redis_tools.py | 54 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index e454c593..1b7cb23c 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -5,9 +5,6 @@ import resource import codecs import requests -import redis -Redis = redis.StrictRedis() - from base import webqtlConfig from base.webqtlCaseData import webqtlCaseData from base.data_set import create_dataset @@ -15,6 +12,8 @@ from db import webqtlDatabaseFunction from utility import webqtlUtil from utility import hmac from utility.tools import GN2_BASE_URL +from utility.redis_tools import get_redis_conn +Redis = get_redis_conn() from wqflask import app @@ -349,8 +348,13 @@ def jsonable_table_row(trait, dataset_name, index): def retrieve_trait_info(trait, dataset, get_qtl_info=False): assert dataset, "Dataset doesn't exist" - + if dataset.type == 'Publish': + resource_id = hmac.data_hmac("{}:{}".format(dataset.id, trait.name)) + + the_url = "http://localhost:8080/run_action/?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id) + trait_data = json.loads(requests.get("http://localhost:8080/run_action/?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id))) + query = """ SELECT PublishXRef.Id, InbredSet.InbredSetCode, Publication.PubMed_ID, diff --git a/wqflask/utility/hmac.py b/wqflask/utility/hmac.py index d8a0eace..b08be97e 100644 --- a/wqflask/utility/hmac.py +++ b/wqflask/utility/hmac.py @@ -3,6 +3,8 @@ from __future__ import print_function, division, absolute_import import hmac import hashlib +from flask import url_for + from wqflask import app def hmac_creation(stringy): diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py index ca42f7b7..15841032 100644 --- a/wqflask/utility/redis_tools.py +++ b/wqflask/utility/redis_tools.py @@ -1,17 +1,25 @@ from __future__ import print_function, division, absolute_import +import uuid import simplejson as json import redis # used for collections -Redis = redis.StrictRedis() import logging from flask import (render_template, flash) +from utility import hmac + from utility.logger import getLogger logger = getLogger(__name__) +def get_redis_conn(): + Redis = redis.StrictRedis(port=6380) + return Redis + +Redis = get_redis_conn() + def is_redis_available(): try: Redis.ping() @@ -70,14 +78,15 @@ def check_verification_code(code): email_address = None user_details = None email_address = Redis.hget("verification_codes", code) - return email_address if email_address: user_details = get_user_by_unique_column('email_address', email_address) - return user_details + if user_details: + return user_details + else: + return None else: return None - flash("Invalid code: Password reset code does not exist or might have expired!", "error") def get_user_groups(user_id): #ZS: Get the groups where a user is an admin or a member and return lists corresponding to those two sets of groups @@ -167,4 +176,39 @@ def change_group_name(user_id, group_id, new_name): group_info["name"] = new_name return group_info else: - return None \ No newline at end of file + return None + +def get_resources(): + resource_list = Redis.hgetall("resources") + return resource_list + +def get_resource_id(dataset_type, dataset_id, trait_id = None, all_resources = None): + if not all_resources: + all_resources = get_resources() + + resource_list = [[key, json.loads(value)] for key, value in all_resources.items()] + + if not trait_id: + matched_resources = [resource[0] for resource in resource_list if resource[1]['data']['dataset'] == dataset_id] + else: + matched_resources = [resource[0] for resource in resource_list if resource[1]['data']['dataset'] == dataset_id and resource[1]['data']['trait'] == trait_id] + + if len(matched_resources): + return matched_resources[0] + else: + return False + +def get_resource_info(resource_id): + resource_info = Redis.hget("resources", resource_id) + return json.loads(resource_info) + +def add_resource(resource_info): + + if 'trait' in resource_info['data']: + resource_id = hmac.data_hmac('{}:{}'.format(str(resource_info['data']['dataset']), str(resource_info['data']['trait']))) + else: + resource_id = hmac.data_hmac('{}'.format(str(resource_info['data']['dataset']))) + + Redis.hset("resources", resource_id, json.dumps(resource_info)) + + return resource_info -- cgit v1.2.3 From 1a663f987bf3a640d21c2c89402318d5433efd9e Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 4 Jun 2020 14:23:30 -0500 Subject: Really should have split this into many more commits: - Now use proxy to pull trait data and hide traits/results that the user doesn't have view permission for - Created a factory method for creating trait ob so it can return None when user doesn't have view permissions (this is why such a large number of files are changed) - Added metadata to permutation export - Added current group management code - Added fixed password verification e-mail code --- wqflask/base/trait.py | 177 ++++---- wqflask/utility/helper_functions.py | 11 +- wqflask/utility/redis_tools.py | 58 +-- wqflask/wqflask/api/correlation.py | 472 ++++++++++----------- wqflask/wqflask/api/gen_menu.py | 11 +- wqflask/wqflask/api/mapping.py | 4 +- wqflask/wqflask/collect.py | 15 +- .../comparison_bar_chart/comparison_bar_chart.py | 4 +- wqflask/wqflask/correlation/corr_scatter_plot.py | 8 +- wqflask/wqflask/correlation/show_corr_results.py | 8 +- .../wqflask/correlation_matrix/show_corr_matrix.py | 22 +- wqflask/wqflask/ctl/ctl_analysis.py | 10 +- wqflask/wqflask/do_search.py | 11 +- wqflask/wqflask/gsearch.py | 13 +- .../marker_regression/display_mapping_results.py | 6 + wqflask/wqflask/marker_regression/gemma_mapping.py | 4 +- wqflask/wqflask/marker_regression/rqtl_mapping.py | 64 ++- wqflask/wqflask/marker_regression/run_mapping.py | 3 +- wqflask/wqflask/network_graph/network_graph.py | 4 +- wqflask/wqflask/search_results.py | 106 ++--- wqflask/wqflask/show_trait/export_trait_data.py | 4 +- wqflask/wqflask/show_trait/show_trait.py | 21 +- wqflask/wqflask/templates/admin/group_manager.html | 45 +- wqflask/wqflask/templates/correlation_page.html | 1 + wqflask/wqflask/templates/email/verification.txt | 7 - wqflask/wqflask/templates/gsearch_pheno.html | 2 +- wqflask/wqflask/templates/mapping_results.html | 31 +- wqflask/wqflask/user_login.py | 43 +- wqflask/wqflask/user_session.py | 18 +- wqflask/wqflask/views.py | 71 +++- 30 files changed, 637 insertions(+), 617 deletions(-) delete mode 100644 wqflask/wqflask/templates/email/verification.txt diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index 1b7cb23c..b133bf21 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -11,6 +11,7 @@ from base.data_set import create_dataset from db import webqtlDatabaseFunction from utility import webqtlUtil from utility import hmac +from utility.authentication_tools import check_resource_availability from utility.tools import GN2_BASE_URL from utility.redis_tools import get_redis_conn Redis = get_redis_conn() @@ -21,11 +22,33 @@ import simplejson as json from MySQLdb import escape_string as escape from pprint import pformat as pf -from flask import Flask, g, request, url_for +from flask import Flask, g, request, url_for, redirect from utility.logger import getLogger logger = getLogger(__name__ ) +def create_trait(**kw): + assert bool(kw.get('dataset')) != bool(kw.get('dataset_name')), "Needs dataset ob. or name"; + + permitted = True + if kw.get('name'): + if kw.get('dataset_name'): + if kw.get('dataset_name') != "Temp": + dataset = create_dataset(kw.get('dataset_name')) + else: + dataset = kw.get('dataset') + + if kw.get('dataset_name') != "Temp": + if dataset.type == 'Publish': + permitted = check_resource_availability(dataset, kw.get('name')) + else: + permitted = check_resource_availability(dataset) + + if permitted: + return GeneralTrait(**kw) + else: + return None + class GeneralTrait(object): """ Trait class defines a trait in webqtl, can be either Microarray, @@ -50,6 +73,7 @@ class GeneralTrait(object): self.haveinfo = kw.get('haveinfo', False) self.sequence = kw.get('sequence') # Blat sequence, available for ProbeSet self.data = kw.get('data', {}) + self.view = True # Sets defaults self.locus = None @@ -77,6 +101,7 @@ class GeneralTrait(object): # So we could add a simple if statement to short-circuit this if necessary if self.dataset.type != "Temp": self = retrieve_trait_info(self, self.dataset, get_qtl_info=get_qtl_info) + if get_sample_info != False: self = retrieve_sample_data(self, self.dataset) @@ -212,26 +237,28 @@ def get_sample_data(): trait = params['trait'] dataset = params['dataset'] - trait_ob = GeneralTrait(name=trait, dataset_name=dataset) - - trait_dict = {} - trait_dict['name'] = trait - trait_dict['db'] = dataset - trait_dict['type'] = trait_ob.dataset.type - trait_dict['group'] = trait_ob.dataset.group.name - trait_dict['tissue'] = trait_ob.dataset.tissue - trait_dict['species'] = trait_ob.dataset.group.species - trait_dict['url'] = url_for('show_trait_page', trait_id = trait, dataset = dataset) - trait_dict['description'] = trait_ob.description_display - if trait_ob.dataset.type == "ProbeSet": - trait_dict['symbol'] = trait_ob.symbol - trait_dict['location'] = trait_ob.location_repr - elif trait_ob.dataset.type == "Publish": - if trait_ob.pubmed_id: - trait_dict['pubmed_link'] = trait_ob.pubmed_link - trait_dict['pubmed_text'] = trait_ob.pubmed_text - - return json.dumps([trait_dict, {key: value.value for key, value in trait_ob.data.iteritems() }]) + trait_ob = create_trait(name=trait, dataset_name=dataset) + if trait_ob: + trait_dict = {} + trait_dict['name'] = trait + trait_dict['db'] = dataset + trait_dict['type'] = trait_ob.dataset.type + trait_dict['group'] = trait_ob.dataset.group.name + trait_dict['tissue'] = trait_ob.dataset.tissue + trait_dict['species'] = trait_ob.dataset.group.species + trait_dict['url'] = url_for('show_trait_page', trait_id = trait, dataset = dataset) + trait_dict['description'] = trait_ob.description_display + if trait_ob.dataset.type == "ProbeSet": + trait_dict['symbol'] = trait_ob.symbol + trait_dict['location'] = trait_ob.location_repr + elif trait_ob.dataset.type == "Publish": + if trait_ob.pubmed_id: + trait_dict['pubmed_link'] = trait_ob.pubmed_link + trait_dict['pubmed_text'] = trait_ob.pubmed_text + + return json.dumps([trait_dict, {key: value.value for key, value in trait_ob.data.iteritems() }]) + else: + return None def jsonable(trait): """Return a dict suitable for using as json @@ -350,91 +377,36 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): assert dataset, "Dataset doesn't exist" if dataset.type == 'Publish': - resource_id = hmac.data_hmac("{}:{}".format(dataset.id, trait.name)) - - the_url = "http://localhost:8080/run_action/?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id) - trait_data = json.loads(requests.get("http://localhost:8080/run_action/?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id))) - - query = """ - SELECT - PublishXRef.Id, InbredSet.InbredSetCode, Publication.PubMed_ID, - Phenotype.Pre_publication_description, Phenotype.Post_publication_description, Phenotype.Original_description, - Phenotype.Pre_publication_abbreviation, Phenotype.Post_publication_abbreviation, PublishXRef.mean, - Phenotype.Lab_code, Phenotype.Submitter, Phenotype.Owner, Phenotype.Authorized_Users, - Publication.Authors, Publication.Title, Publication.Abstract, - Publication.Journal, Publication.Volume, Publication.Pages, - Publication.Month, Publication.Year, PublishXRef.Sequence, - Phenotype.Units, PublishXRef.comments - FROM - PublishXRef, Publication, Phenotype, PublishFreeze, InbredSet - WHERE - PublishXRef.Id = %s AND - Phenotype.Id = PublishXRef.PhenotypeId AND - Publication.Id = PublishXRef.PublicationId AND - PublishXRef.InbredSetId = PublishFreeze.InbredSetId AND - PublishXRef.InbredSetId = InbredSet.Id AND - PublishFreeze.Id = %s - """ % (trait.name, dataset.id) - - logger.sql(query) - trait_info = g.db.execute(query).fetchone() - - - #XZ, 05/08/2009: Xiaodong add this block to use ProbeSet.Id to find the probeset instead of just using ProbeSet.Name - #XZ, 05/08/2009: to avoid the problem of same probeset name from different platforms. + resource_id = hmac.hmac_creation("{}:{}:{}".format('dataset-publish', dataset.id, trait.name)) + the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id) elif dataset.type == 'ProbeSet': - display_fields_string = ', ProbeSet.'.join(dataset.display_fields) - display_fields_string = 'ProbeSet.' + display_fields_string - query = """ - SELECT %s - FROM ProbeSet, ProbeSetFreeze, ProbeSetXRef - WHERE - ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id AND - ProbeSetXRef.ProbeSetId = ProbeSet.Id AND - ProbeSetFreeze.Name = '%s' AND - ProbeSet.Name = '%s' - """ % (escape(display_fields_string), - escape(dataset.name), - escape(str(trait.name))) - logger.sql(query) - trait_info = g.db.execute(query).fetchone() - #XZ, 05/08/2009: We also should use Geno.Id to find marker instead of just using Geno.Name - # to avoid the problem of same marker name from different species. - elif dataset.type == 'Geno': - display_fields_string = string.join(dataset.display_fields,',Geno.') - display_fields_string = 'Geno.' + display_fields_string - query = """ - SELECT %s - FROM Geno, GenoFreeze, GenoXRef - WHERE - GenoXRef.GenoFreezeId = GenoFreeze.Id AND - GenoXRef.GenoId = Geno.Id AND - GenoFreeze.Name = '%s' AND - Geno.Name = '%s' - """ % (escape(display_fields_string), - escape(dataset.name), - escape(trait.name)) - logger.sql(query) - trait_info = g.db.execute(query).fetchone() - else: #Temp type - query = """SELECT %s FROM %s WHERE Name = %s""" - logger.sql(query) - trait_info = g.db.execute(query, - (string.join(dataset.display_fields,','), - dataset.type, trait.name)).fetchone() + resource_id = hmac.hmac_creation("{}:{}".format('dataset-probeset', dataset.id)) + the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view&trait={}".format(resource_id, g.user_session.user_id, trait.name) + else: + resource_id = hmac.hmac_creation("{}:{}".format('dataset-geno', dataset.id)) + the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view&trait={}".format(resource_id, g.user_session.user_id, trait.name) + + try: + response = requests.get(the_url).content + if response.strip() == "no-access": + trait.view = False + return trait + except: + resource_info = get_resource_info(resource_id) + default_permissions = resource_info['default_mask']['data'] + if 'view' not in default_persmissions: + trait.view = False + return trait + + trait_info = json.loads(response) if trait_info: trait.haveinfo = True - #XZ: assign SQL query result to trait attributes. for i, field in enumerate(dataset.display_fields): holder = trait_info[i] - # if isinstance(trait_info[i], basestring): - # logger.debug("HOLDER:", holder) - # logger.debug("HOLDER2:", holder.decode(encoding='latin1')) - # holder = unicode(trait_info[i], "utf-8", "ignore") - if isinstance(trait_info[i], basestring): - holder = holder.encode('latin1') + #if isinstance(trait_info[i], basestring): + # holder = holder.encode('latin1') setattr(trait, field, holder) if dataset.type == 'Publish': @@ -453,13 +425,6 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): if trait.confidential: trait.abbreviation = trait.pre_publication_abbreviation trait.description_display = trait.pre_publication_description - - #if not webqtlUtil.hasAccessToConfidentialPhenotypeTrait( - # privilege=self.dataset.privilege, - # userName=self.dataset.userName, - # authorized_users=self.authorized_users): - # - # description = self.pre_publication_description else: trait.abbreviation = trait.post_publication_abbreviation if description: diff --git a/wqflask/utility/helper_functions.py b/wqflask/utility/helper_functions.py index e7c04fef..9ce809b6 100644 --- a/wqflask/utility/helper_functions.py +++ b/wqflask/utility/helper_functions.py @@ -1,7 +1,7 @@ from __future__ import absolute_import, print_function, division -from base.trait import GeneralTrait from base import data_set +from base.trait import create_trait from base.species import TheSpecies from utility import hmac @@ -11,7 +11,6 @@ from flask import Flask, g import logging logger = logging.getLogger(__name__ ) - def get_species_dataset_trait(self, start_vars): #assert type(read_genotype) == type(bool()), "Expecting boolean value for read_genotype" if "temp_trait" in start_vars.keys(): @@ -24,7 +23,7 @@ def get_species_dataset_trait(self, start_vars): logger.debug("After creating dataset") self.species = TheSpecies(dataset=self.dataset) logger.debug("After creating species") - self.this_trait = GeneralTrait(dataset=self.dataset, + self.this_trait = create_trait(dataset=self.dataset, name=start_vars['trait_id'], cellid=None, get_qtl_info=True) @@ -34,7 +33,6 @@ def get_species_dataset_trait(self, start_vars): #self.dataset.group.read_genotype_file() #self.genotype = self.dataset.group.genotype - def get_trait_db_obs(self, trait_db_list): if isinstance(trait_db_list, basestring): trait_db_list = trait_db_list.split(",") @@ -49,10 +47,11 @@ def get_trait_db_obs(self, trait_db_list): dataset_ob = data_set.create_dataset(dataset_name=dataset_name, dataset_type="Temp", group_name=trait_name.split("_")[2]) else: dataset_ob = data_set.create_dataset(dataset_name) - trait_ob = GeneralTrait(dataset=dataset_ob, + trait_ob = create_trait(dataset=dataset_ob, name=trait_name, cellid=None) - self.trait_list.append((trait_ob, dataset_ob)) + if trait_ob: + self.trait_list.append((trait_ob, dataset_ob)) def get_species_groups(): diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py index 15841032..0ad96879 100644 --- a/wqflask/utility/redis_tools.py +++ b/wqflask/utility/redis_tools.py @@ -2,6 +2,7 @@ from __future__ import print_function, division, absolute_import import uuid import simplejson as json +import datetime import redis # used for collections @@ -96,15 +97,22 @@ def get_user_groups(user_id): for key in groups_list: group_ob = json.loads(groups_list[key]) group_admins = set(group_ob['admins']) - group_users = set(group_ob['users']) + group_members = set(group_ob['members']) if user_id in group_admins: admin_group_ids.append(group_ob['id']) - elif user_id in group_users: + elif user_id in group_members: user_group_ids.append(group_ob['id']) else: continue - return admin_group_ids, user_group_ids + admin_groups = [] + user_groups = [] + for the_id in admin_group_ids: + admin_groups.append(get_group_info(the_id)) + for the_id in user_group_ids: + user_groups.append(get_group_info(the_id)) + + return admin_groups, user_groups def get_group_info(group_id): group_json = Redis.hget("groups", group_id) @@ -114,18 +122,18 @@ def get_group_info(group_id): return group_info -def create_group(admin_member_ids, user_member_ids = [], group_name = ""): +def create_group(admin_user_ids, member_user_ids = [], group_name = "Default Group Name"): group_id = str(uuid.uuid4()) new_group = { "id" : group_id, - "admins": admin_member_ids, - "users" : user_member_ids, + "admins": admin_user_ids, + "members" : member_user_ids, "name" : group_name, "created_timestamp": datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p'), "changed_timestamp": datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') } - Redis.hset("groups", group_id, new_group) + Redis.hset("groups", group_id, json.dumps(new_group)) return new_group @@ -144,7 +152,7 @@ def add_users_to_group(user_id, group_id, user_emails = [], admins = False): #ZS if admins: group_users = set(group_info["admins"]) else: - group_users = set(group_info["users"]) + group_users = set(group_info["members"]) for email in user_emails: user_id = get_user_id("email_address", email) @@ -153,7 +161,7 @@ def add_users_to_group(user_id, group_id, user_emails = [], admins = False): #ZS if admins: group_info["admins"] = list(group_users) else: - group_info["users"] = list(group_users) + group_info["members"] = list(group_users) group_info["changed_timestamp"] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') Redis.hset("groups", group_id, json.dumps(group_info)) @@ -161,7 +169,7 @@ def add_users_to_group(user_id, group_id, user_emails = [], admins = False): #ZS else: return None -def remove_users_from_group(user_id, users_to_remove_ids, group_id, user_type = "users"): #ZS: User type is because I assume admins can remove other admins +def remove_users_from_group(user_id, users_to_remove_ids, group_id, user_type = "members"): #ZS: User type is because I assume admins can remove other admins group_info = get_group_info(group_id) if user_id in group_info["admins"]: group_users = set(group_info[user_type]) @@ -174,6 +182,7 @@ def change_group_name(user_id, group_id, new_name): group_info = get_group_info(group_id) if user_id in group_info["admins"]: group_info["name"] = new_name + Redis.hset("groups", group_id, json.dumps(group_info)) return group_info else: return None @@ -182,22 +191,21 @@ def get_resources(): resource_list = Redis.hgetall("resources") return resource_list -def get_resource_id(dataset_type, dataset_id, trait_id = None, all_resources = None): - if not all_resources: - all_resources = get_resources() - - resource_list = [[key, json.loads(value)] for key, value in all_resources.items()] - - if not trait_id: - matched_resources = [resource[0] for resource in resource_list if resource[1]['data']['dataset'] == dataset_id] - else: - matched_resources = [resource[0] for resource in resource_list if resource[1]['data']['dataset'] == dataset_id and resource[1]['data']['trait'] == trait_id] - - if len(matched_resources): - return matched_resources[0] +def get_resource_id(dataset, trait_id=None): + if dataset.type == "Publish": + if trait_id: + resource_id = hmac.hmac_creation("{}:{}:{}".format('dataset-publish', dataset.id, trait_id)) + else: + return False + elif dataset.type == "ProbeSet": + resource_id = hmac.hmac_creation("{}:{}".format('dataset-probeset', dataset.id)) + elif dataset.type == "Geno": + resource_id = hmac.hmac_creation("{}:{}".format('dataset-geno', dataset.id)) else: return False + return resource_id + def get_resource_info(resource_id): resource_info = Redis.hget("resources", resource_id) return json.loads(resource_info) @@ -205,9 +213,9 @@ def get_resource_info(resource_id): def add_resource(resource_info): if 'trait' in resource_info['data']: - resource_id = hmac.data_hmac('{}:{}'.format(str(resource_info['data']['dataset']), str(resource_info['data']['trait']))) + resource_id = hmac.hmac_creation('{}:{}:{}'.format(str(resource_info['type']), str(resource_info['data']['dataset']), str(resource_info['data']['trait']))) else: - resource_id = hmac.data_hmac('{}'.format(str(resource_info['data']['dataset']))) + resource_id = hmac.hmac_creation('{}:{}'.format(str(resource_info['type']), str(resource_info['data']['dataset']))) Redis.hset("resources", resource_id, json.dumps(resource_info)) diff --git a/wqflask/wqflask/api/correlation.py b/wqflask/wqflask/api/correlation.py index 66eb94ac..7f5312c1 100644 --- a/wqflask/wqflask/api/correlation.py +++ b/wqflask/wqflask/api/correlation.py @@ -1,237 +1,237 @@ -from __future__ import absolute_import, division, print_function - -import collections - -import scipy - -from MySQLdb import escape_string as escape - -from flask import g - -from base import data_set -from base.trait import GeneralTrait, retrieve_sample_data - -from wqflask.correlation.show_corr_results import generate_corr_json -from wqflask.correlation import correlation_functions - -from utility import webqtlUtil, helper_functions, corr_result_helpers -from utility.benchmark import Bench - -import utility.logger -logger = utility.logger.getLogger(__name__ ) - -def do_correlation(start_vars): - assert('db' in start_vars) - assert('target_db' in start_vars) - assert('trait_id' in start_vars) - - this_dataset = data_set.create_dataset(dataset_name = start_vars['db']) - target_dataset = data_set.create_dataset(dataset_name = start_vars['target_db']) - this_trait = GeneralTrait(dataset = this_dataset, name = start_vars['trait_id']) - this_trait = retrieve_sample_data(this_trait, this_dataset) - - corr_params = init_corr_params(start_vars) - - corr_results = calculate_results(this_trait, this_dataset, target_dataset, corr_params) - #corr_results = collections.OrderedDict(sorted(corr_results.items(), key=lambda t: -abs(t[1][0]))) - - final_results = [] - for _trait_counter, trait in enumerate(corr_results.keys()[:corr_params['return_count']]): - if corr_params['type'] == "tissue": - [sample_r, num_overlap, sample_p, symbol] = corr_results[trait] - result_dict = { - "trait" : trait, - "sample_r" : sample_r, - "#_strains" : num_overlap, - "p_value" : sample_p, - "symbol" : symbol - } - elif corr_params['type'] == "literature" or corr_params['type'] == "lit": - [gene_id, sample_r] = corr_results[trait] - result_dict = { - "trait" : trait, - "sample_r" : sample_r, - "gene_id" : gene_id - } - else: - [sample_r, sample_p, num_overlap] = corr_results[trait] - result_dict = { - "trait" : trait, - "sample_r" : sample_r, - "#_strains" : num_overlap, - "p_value" : sample_p - } - - final_results.append(result_dict) - - # json_corr_results = generate_corr_json(final_corr_results, this_trait, this_dataset, target_dataset, for_api = True) - - return final_results - -def calculate_results(this_trait, this_dataset, target_dataset, corr_params): - corr_results = {} - - target_dataset.get_trait_data() - - if corr_params['type'] == "tissue": - trait_symbol_dict = this_dataset.retrieve_genes("Symbol") - corr_results = do_tissue_correlation_for_all_traits(this_trait, trait_symbol_dict, corr_params) - sorted_results = collections.OrderedDict(sorted(corr_results.items(), - key=lambda t: -abs(t[1][1]))) - elif corr_params['type'] == "literature" or corr_params['type'] == "lit": #ZS: Just so a user can use either "lit" or "literature" - trait_geneid_dict = this_dataset.retrieve_genes("GeneId") - corr_results = do_literature_correlation_for_all_traits(this_trait, this_dataset, trait_geneid_dict, corr_params) - sorted_results = collections.OrderedDict(sorted(corr_results.items(), - key=lambda t: -abs(t[1][1]))) - else: - for target_trait, target_vals in target_dataset.trait_data.iteritems(): - result = get_sample_r_and_p_values(this_trait, this_dataset, target_vals, target_dataset, corr_params['type']) - if result is not None: - corr_results[target_trait] = result - - sorted_results = collections.OrderedDict(sorted(corr_results.items(), key=lambda t: -abs(t[1][0]))) - - return sorted_results - -def do_tissue_correlation_for_all_traits(this_trait, trait_symbol_dict, corr_params, tissue_dataset_id=1): - #Gets tissue expression values for the primary trait - primary_trait_tissue_vals_dict = correlation_functions.get_trait_symbol_and_tissue_values(symbol_list = [this_trait.symbol]) - - if this_trait.symbol.lower() in primary_trait_tissue_vals_dict: - primary_trait_tissue_values = primary_trait_tissue_vals_dict[this_trait.symbol.lower()] - - corr_result_tissue_vals_dict = correlation_functions.get_trait_symbol_and_tissue_values(symbol_list=trait_symbol_dict.values()) - - tissue_corr_data = {} - for trait, symbol in trait_symbol_dict.iteritems(): - if symbol and symbol.lower() in corr_result_tissue_vals_dict: - this_trait_tissue_values = corr_result_tissue_vals_dict[symbol.lower()] - - result = correlation_functions.cal_zero_order_corr_for_tiss(primary_trait_tissue_values, - this_trait_tissue_values, - corr_params['method']) - - tissue_corr_data[trait] = [result[0], result[1], result[2], symbol] - - return tissue_corr_data - -def do_literature_correlation_for_all_traits(this_trait, target_dataset, trait_geneid_dict, corr_params): - input_trait_mouse_gene_id = convert_to_mouse_gene_id(target_dataset.group.species.lower(), this_trait.geneid) - - lit_corr_data = {} - for trait, gene_id in trait_geneid_dict.iteritems(): - mouse_gene_id = convert_to_mouse_gene_id(target_dataset.group.species.lower(), gene_id) - - if mouse_gene_id and str(mouse_gene_id).find(";") == -1: - result = g.db.execute( - """SELECT value - FROM LCorrRamin3 - WHERE GeneId1='%s' and - GeneId2='%s' - """ % (escape(mouse_gene_id), escape(input_trait_mouse_gene_id)) - ).fetchone() - if not result: - result = g.db.execute("""SELECT value - FROM LCorrRamin3 - WHERE GeneId2='%s' and - GeneId1='%s' - """ % (escape(mouse_gene_id), escape(input_trait_mouse_gene_id)) - ).fetchone() - if result: - lit_corr = result.value - lit_corr_data[trait] = [gene_id, lit_corr] - else: - lit_corr_data[trait] = [gene_id, 0] - else: - lit_corr_data[trait] = [gene_id, 0] - - return lit_corr_data - -def get_sample_r_and_p_values(this_trait, this_dataset, target_vals, target_dataset, type): - """ - Calculates the sample r (or rho) and p-value - - Given a primary trait and a target trait's sample values, - calculates either the pearson r or spearman rho and the p-value - using the corresponding scipy functions. - """ - - this_trait_vals = [] - shared_target_vals = [] - for i, sample in enumerate(target_dataset.group.samplelist): - if sample in this_trait.data: - this_sample_value = this_trait.data[sample].value - target_sample_value = target_vals[i] - this_trait_vals.append(this_sample_value) - shared_target_vals.append(target_sample_value) - - this_trait_vals, shared_target_vals, num_overlap = corr_result_helpers.normalize_values(this_trait_vals, shared_target_vals) - - if type == 'pearson': - sample_r, sample_p = scipy.stats.pearsonr(this_trait_vals, shared_target_vals) - else: - sample_r, sample_p = scipy.stats.spearmanr(this_trait_vals, shared_target_vals) - - if num_overlap > 5: - if scipy.isnan(sample_r): - return None - else: - return [sample_r, sample_p, num_overlap] - -def convert_to_mouse_gene_id(species=None, gene_id=None): - """If the species is rat or human, translate the gene_id to the mouse geneid - - If there is no input gene_id or there's no corresponding mouse gene_id, return None - - """ - if not gene_id: - return None - - mouse_gene_id = None - - if species == 'mouse': - mouse_gene_id = gene_id - - elif species == 'rat': - - query = """SELECT mouse - FROM GeneIDXRef - WHERE rat='%s'""" % escape(gene_id) - - result = g.db.execute(query).fetchone() - if result != None: - mouse_gene_id = result.mouse - - elif species == 'human': - - query = """SELECT mouse - FROM GeneIDXRef - WHERE human='%s'""" % escape(gene_id) - - result = g.db.execute(query).fetchone() - if result != None: - mouse_gene_id = result.mouse - - return mouse_gene_id - -def init_corr_params(start_vars): - method = "pearson" - if 'method' in start_vars: - method = start_vars['method'] - - type = "sample" - if 'type' in start_vars: - type = start_vars['type'] - - return_count = 500 - if 'return_count' in start_vars: - assert(start_vars['return_count'].isdigit()) - return_count = int(start_vars['return_count']) - - corr_params = { - 'method' : method, - 'type' : type, - 'return_count' : return_count - } - +from __future__ import absolute_import, division, print_function + +import collections + +import scipy + +from MySQLdb import escape_string as escape + +from flask import g + +from base import data_set +from base.trait import create_trait, retrieve_sample_data + +from wqflask.correlation.show_corr_results import generate_corr_json +from wqflask.correlation import correlation_functions + +from utility import webqtlUtil, helper_functions, corr_result_helpers +from utility.benchmark import Bench + +import utility.logger +logger = utility.logger.getLogger(__name__ ) + +def do_correlation(start_vars): + assert('db' in start_vars) + assert('target_db' in start_vars) + assert('trait_id' in start_vars) + + this_dataset = data_set.create_dataset(dataset_name = start_vars['db']) + target_dataset = data_set.create_dataset(dataset_name = start_vars['target_db']) + this_trait = create_trait(dataset = this_dataset, name = start_vars['trait_id']) + this_trait = retrieve_sample_data(this_trait, this_dataset) + + corr_params = init_corr_params(start_vars) + + corr_results = calculate_results(this_trait, this_dataset, target_dataset, corr_params) + #corr_results = collections.OrderedDict(sorted(corr_results.items(), key=lambda t: -abs(t[1][0]))) + + final_results = [] + for _trait_counter, trait in enumerate(corr_results.keys()[:corr_params['return_count']]): + if corr_params['type'] == "tissue": + [sample_r, num_overlap, sample_p, symbol] = corr_results[trait] + result_dict = { + "trait" : trait, + "sample_r" : sample_r, + "#_strains" : num_overlap, + "p_value" : sample_p, + "symbol" : symbol + } + elif corr_params['type'] == "literature" or corr_params['type'] == "lit": + [gene_id, sample_r] = corr_results[trait] + result_dict = { + "trait" : trait, + "sample_r" : sample_r, + "gene_id" : gene_id + } + else: + [sample_r, sample_p, num_overlap] = corr_results[trait] + result_dict = { + "trait" : trait, + "sample_r" : sample_r, + "#_strains" : num_overlap, + "p_value" : sample_p + } + + final_results.append(result_dict) + + # json_corr_results = generate_corr_json(final_corr_results, this_trait, this_dataset, target_dataset, for_api = True) + + return final_results + +def calculate_results(this_trait, this_dataset, target_dataset, corr_params): + corr_results = {} + + target_dataset.get_trait_data() + + if corr_params['type'] == "tissue": + trait_symbol_dict = this_dataset.retrieve_genes("Symbol") + corr_results = do_tissue_correlation_for_all_traits(this_trait, trait_symbol_dict, corr_params) + sorted_results = collections.OrderedDict(sorted(corr_results.items(), + key=lambda t: -abs(t[1][1]))) + elif corr_params['type'] == "literature" or corr_params['type'] == "lit": #ZS: Just so a user can use either "lit" or "literature" + trait_geneid_dict = this_dataset.retrieve_genes("GeneId") + corr_results = do_literature_correlation_for_all_traits(this_trait, this_dataset, trait_geneid_dict, corr_params) + sorted_results = collections.OrderedDict(sorted(corr_results.items(), + key=lambda t: -abs(t[1][1]))) + else: + for target_trait, target_vals in target_dataset.trait_data.iteritems(): + result = get_sample_r_and_p_values(this_trait, this_dataset, target_vals, target_dataset, corr_params['type']) + if result is not None: + corr_results[target_trait] = result + + sorted_results = collections.OrderedDict(sorted(corr_results.items(), key=lambda t: -abs(t[1][0]))) + + return sorted_results + +def do_tissue_correlation_for_all_traits(this_trait, trait_symbol_dict, corr_params, tissue_dataset_id=1): + #Gets tissue expression values for the primary trait + primary_trait_tissue_vals_dict = correlation_functions.get_trait_symbol_and_tissue_values(symbol_list = [this_trait.symbol]) + + if this_trait.symbol.lower() in primary_trait_tissue_vals_dict: + primary_trait_tissue_values = primary_trait_tissue_vals_dict[this_trait.symbol.lower()] + + corr_result_tissue_vals_dict = correlation_functions.get_trait_symbol_and_tissue_values(symbol_list=trait_symbol_dict.values()) + + tissue_corr_data = {} + for trait, symbol in trait_symbol_dict.iteritems(): + if symbol and symbol.lower() in corr_result_tissue_vals_dict: + this_trait_tissue_values = corr_result_tissue_vals_dict[symbol.lower()] + + result = correlation_functions.cal_zero_order_corr_for_tiss(primary_trait_tissue_values, + this_trait_tissue_values, + corr_params['method']) + + tissue_corr_data[trait] = [result[0], result[1], result[2], symbol] + + return tissue_corr_data + +def do_literature_correlation_for_all_traits(this_trait, target_dataset, trait_geneid_dict, corr_params): + input_trait_mouse_gene_id = convert_to_mouse_gene_id(target_dataset.group.species.lower(), this_trait.geneid) + + lit_corr_data = {} + for trait, gene_id in trait_geneid_dict.iteritems(): + mouse_gene_id = convert_to_mouse_gene_id(target_dataset.group.species.lower(), gene_id) + + if mouse_gene_id and str(mouse_gene_id).find(";") == -1: + result = g.db.execute( + """SELECT value + FROM LCorrRamin3 + WHERE GeneId1='%s' and + GeneId2='%s' + """ % (escape(mouse_gene_id), escape(input_trait_mouse_gene_id)) + ).fetchone() + if not result: + result = g.db.execute("""SELECT value + FROM LCorrRamin3 + WHERE GeneId2='%s' and + GeneId1='%s' + """ % (escape(mouse_gene_id), escape(input_trait_mouse_gene_id)) + ).fetchone() + if result: + lit_corr = result.value + lit_corr_data[trait] = [gene_id, lit_corr] + else: + lit_corr_data[trait] = [gene_id, 0] + else: + lit_corr_data[trait] = [gene_id, 0] + + return lit_corr_data + +def get_sample_r_and_p_values(this_trait, this_dataset, target_vals, target_dataset, type): + """ + Calculates the sample r (or rho) and p-value + + Given a primary trait and a target trait's sample values, + calculates either the pearson r or spearman rho and the p-value + using the corresponding scipy functions. + """ + + this_trait_vals = [] + shared_target_vals = [] + for i, sample in enumerate(target_dataset.group.samplelist): + if sample in this_trait.data: + this_sample_value = this_trait.data[sample].value + target_sample_value = target_vals[i] + this_trait_vals.append(this_sample_value) + shared_target_vals.append(target_sample_value) + + this_trait_vals, shared_target_vals, num_overlap = corr_result_helpers.normalize_values(this_trait_vals, shared_target_vals) + + if type == 'pearson': + sample_r, sample_p = scipy.stats.pearsonr(this_trait_vals, shared_target_vals) + else: + sample_r, sample_p = scipy.stats.spearmanr(this_trait_vals, shared_target_vals) + + if num_overlap > 5: + if scipy.isnan(sample_r): + return None + else: + return [sample_r, sample_p, num_overlap] + +def convert_to_mouse_gene_id(species=None, gene_id=None): + """If the species is rat or human, translate the gene_id to the mouse geneid + + If there is no input gene_id or there's no corresponding mouse gene_id, return None + + """ + if not gene_id: + return None + + mouse_gene_id = None + + if species == 'mouse': + mouse_gene_id = gene_id + + elif species == 'rat': + + query = """SELECT mouse + FROM GeneIDXRef + WHERE rat='%s'""" % escape(gene_id) + + result = g.db.execute(query).fetchone() + if result != None: + mouse_gene_id = result.mouse + + elif species == 'human': + + query = """SELECT mouse + FROM GeneIDXRef + WHERE human='%s'""" % escape(gene_id) + + result = g.db.execute(query).fetchone() + if result != None: + mouse_gene_id = result.mouse + + return mouse_gene_id + +def init_corr_params(start_vars): + method = "pearson" + if 'method' in start_vars: + method = start_vars['method'] + + type = "sample" + if 'type' in start_vars: + type = start_vars['type'] + + return_count = 500 + if 'return_count' in start_vars: + assert(start_vars['return_count'].isdigit()) + return_count = int(start_vars['return_count']) + + corr_params = { + 'method' : method, + 'type' : type, + 'return_count' : return_count + } + return corr_params \ No newline at end of file diff --git a/wqflask/wqflask/api/gen_menu.py b/wqflask/wqflask/api/gen_menu.py index c7bcb65d..bdcc3bf7 100644 --- a/wqflask/wqflask/api/gen_menu.py +++ b/wqflask/wqflask/api/gen_menu.py @@ -126,9 +126,7 @@ def build_types(species, group): InbredSet.Name = '{1}' AND ProbeFreeze.TissueId = Tissue.Id AND ProbeFreeze.InbredSetId = InbredSet.Id AND - ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id AND - ProbeSetFreeze.public > 0 AND - ProbeSetFreeze.confidentiality < 1 + ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id ORDER BY Tissue.Name""".format(species, group) results = [] @@ -194,9 +192,7 @@ def build_datasets(species, group, type_name): FROM InfoFiles, GenoFreeze, InbredSet WHERE InbredSet.Name = '{}' AND GenoFreeze.InbredSetId = InbredSet.Id AND - InfoFiles.InfoPageName = GenoFreeze.ShortName AND - GenoFreeze.public > 0 AND - GenoFreeze.confidentiality < 1 + InfoFiles.InfoPageName = GenoFreeze.ShortName ORDER BY GenoFreeze.CreateTime DESC""".format(group)).fetchone() if results != None: @@ -214,8 +210,7 @@ def build_datasets(species, group, type_name): Species.Id = InbredSet.SpeciesId AND InbredSet.Name = '{1}' AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id and Tissue.Name = '{2}' AND - ProbeFreeze.TissueId = Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id AND - ProbeSetFreeze.confidentiality < 1 and ProbeSetFreeze.public > 0 + ProbeFreeze.TissueId = Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id ORDER BY ProbeSetFreeze.CreateTime DESC""".format(species, group, type_name)).fetchall() datasets = [] diff --git a/wqflask/wqflask/api/mapping.py b/wqflask/wqflask/api/mapping.py index d830cefc..92c27c9b 100644 --- a/wqflask/wqflask/api/mapping.py +++ b/wqflask/wqflask/api/mapping.py @@ -4,7 +4,7 @@ import string from base import data_set from base import webqtlConfig -from base.trait import GeneralTrait, retrieve_sample_data +from base.trait import create_trait, retrieve_sample_data from utility import helper_functions from wqflask.marker_regression import gemma_mapping, rqtl_mapping, qtlreaper_mapping, plink_mapping @@ -18,7 +18,7 @@ def do_mapping_for_api(start_vars): dataset = data_set.create_dataset(dataset_name = start_vars['db']) dataset.group.get_markers() - this_trait = GeneralTrait(dataset = dataset, name = start_vars['trait_id']) + this_trait = create_trait(dataset = dataset, name = start_vars['trait_id']) this_trait = retrieve_sample_data(this_trait, dataset) samples = [] diff --git a/wqflask/wqflask/collect.py b/wqflask/wqflask/collect.py index b22e0004..4fb8e69b 100644 --- a/wqflask/wqflask/collect.py +++ b/wqflask/wqflask/collect.py @@ -14,9 +14,6 @@ import urlparse import simplejson as json -import redis -Redis = redis.StrictRedis() - from flask import (Flask, g, render_template, url_for, request, make_response, redirect, flash, jsonify) @@ -30,8 +27,10 @@ from wqflask import model from utility import Bunch, Struct, hmac from utility.formatting import numify +from utility.redis_tools import get_redis_conn +Redis = get_redis_conn() -from base import trait +from base.trait import create_trait, retrieve_trait_info, jsonable from base.data_set import create_dataset import logging @@ -208,14 +207,14 @@ def view_collection(): if dataset_name == "Temp": group = name.split("_")[2] dataset = create_dataset(dataset_name, dataset_type = "Temp", group_name = group) - trait_ob = trait.GeneralTrait(name=name, dataset=dataset) + trait_ob = create_trait(name=name, dataset=dataset) else: dataset = create_dataset(dataset_name) - trait_ob = trait.GeneralTrait(name=name, dataset=dataset) - trait_ob = trait.retrieve_trait_info(trait_ob, dataset, get_qtl_info=True) + trait_ob = create_trait(name=name, dataset=dataset) + trait_ob = retrieve_trait_info(trait_ob, dataset, get_qtl_info=True) trait_obs.append(trait_ob) - json_version.append(trait.jsonable(trait_ob)) + json_version.append(jsonable(trait_ob)) collection_info = dict(trait_obs=trait_obs, uc = uc) diff --git a/wqflask/wqflask/comparison_bar_chart/comparison_bar_chart.py b/wqflask/wqflask/comparison_bar_chart/comparison_bar_chart.py index 21eb1493..5d74dc9d 100644 --- a/wqflask/wqflask/comparison_bar_chart/comparison_bar_chart.py +++ b/wqflask/wqflask/comparison_bar_chart/comparison_bar_chart.py @@ -37,7 +37,7 @@ from pprint import pformat as pf import reaper -from base.trait import GeneralTrait +from base.trait import create_trait from base import data_set from utility import webqtlUtil, helper_functions, corr_result_helpers from db import webqtlDatabaseFunction @@ -108,7 +108,7 @@ class ComparisonBarChart(object): trait_name, dataset_name = trait_db.split(":") #print("dataset_name:", dataset_name) dataset_ob = data_set.create_dataset(dataset_name) - trait_ob = GeneralTrait(dataset=dataset_ob, + trait_ob = create_trait(dataset=dataset_ob, name=trait_name, cellid=None) self.trait_list.append((trait_ob, dataset_ob)) diff --git a/wqflask/wqflask/correlation/corr_scatter_plot.py b/wqflask/wqflask/correlation/corr_scatter_plot.py index dfb81c54..04ec427d 100644 --- a/wqflask/wqflask/correlation/corr_scatter_plot.py +++ b/wqflask/wqflask/correlation/corr_scatter_plot.py @@ -4,7 +4,7 @@ import math from flask import g -from base.trait import GeneralTrait +from base.trait import create_trait from base import data_set from utility import corr_result_helpers from scipy import stats @@ -20,9 +20,9 @@ class CorrScatterPlot(object): self.data_set_1 = data_set.create_dataset(params['dataset_1']) self.data_set_2 = data_set.create_dataset(params['dataset_2']) #self.data_set_3 = data_set.create_dataset(params['dataset_3']) - self.trait_1 = GeneralTrait(name=params['trait_1'], dataset=self.data_set_1) - self.trait_2 = GeneralTrait(name=params['trait_2'], dataset=self.data_set_2) - #self.trait_3 = GeneralTrait(name=params['trait_3'], dataset=self.data_set_3) + self.trait_1 = create_trait(name=params['trait_1'], dataset=self.data_set_1) + self.trait_2 = create_trait(name=params['trait_2'], dataset=self.data_set_2) + #self.trait_3 = create_trait(name=params['trait_3'], dataset=self.data_set_3) samples_1, samples_2, num_overlap = corr_result_helpers.normalize_values_with_samples(self.trait_1.data, self.trait_2.data) diff --git a/wqflask/wqflask/correlation/show_corr_results.py b/wqflask/wqflask/correlation/show_corr_results.py index b099b83d..7eab7184 100644 --- a/wqflask/wqflask/correlation/show_corr_results.py +++ b/wqflask/wqflask/correlation/show_corr_results.py @@ -47,7 +47,7 @@ import reaper from base import webqtlConfig from utility.THCell import THCell from utility.TDCell import TDCell -from base.trait import GeneralTrait +from base.trait import create_trait from base import data_set from utility import webqtlUtil, helper_functions, corr_result_helpers, hmac from db import webqtlDatabaseFunction @@ -97,7 +97,7 @@ class CorrelationResults(object): if start_vars['dataset'] == "Temp": self.dataset = data_set.create_dataset(dataset_name = "Temp", dataset_type = "Temp", group_name = start_vars['group']) self.trait_id = start_vars['trait_id'] - self.this_trait = GeneralTrait(dataset=self.dataset, + self.this_trait = create_trait(dataset=self.dataset, name=self.trait_id, cellid=None) else: @@ -199,7 +199,9 @@ class CorrelationResults(object): range_chr_as_int = order_id for _trait_counter, trait in enumerate(self.correlation_data.keys()[:self.return_number]): - trait_object = GeneralTrait(dataset=self.target_dataset, name=trait, get_qtl_info=True, get_sample_info=False) + trait_object = create_trait(dataset=self.target_dataset, name=trait, get_qtl_info=True, get_sample_info=False) + if not trait_object: + continue if self.target_dataset.type == "ProbeSet" or self.target_dataset.type == "Geno": #ZS: Convert trait chromosome to an int for the location range option diff --git a/wqflask/wqflask/correlation_matrix/show_corr_matrix.py b/wqflask/wqflask/correlation_matrix/show_corr_matrix.py index b5c45d05..2b9467d1 100644 --- a/wqflask/wqflask/correlation_matrix/show_corr_matrix.py +++ b/wqflask/wqflask/correlation_matrix/show_corr_matrix.py @@ -43,14 +43,16 @@ from pprint import pformat as pf import reaper -import redis -Redis = redis.StrictRedis() +from utility.redis_tools import get_redis_conn +Redis = get_redis_conn() +THIRTY_DAYS = 60 * 60 * 24 * 30 from utility.THCell import THCell from utility.TDCell import TDCell from base.trait import GeneralTrait from base import data_set from utility import webqtlUtil, helper_functions, corr_result_helpers + from db import webqtlDatabaseFunction import utility.webqtlUtil #this is for parallel computing only. from wqflask.correlation import correlation_functions @@ -204,20 +206,6 @@ class CorrelationMatrix(object): samples = self.all_sample_list, sample_data = self.sample_data,) # corr_results = [result[1] for result in result_row for result_row in self.corr_results]) - - def get_trait_db_obs(self, trait_db_list): - - self.trait_list = [] - for i, trait_db in enumerate(trait_db_list): - if i == (len(trait_db_list) - 1): - break - trait_name, dataset_name = trait_db.split(":") - #print("dataset_name:", dataset_name) - dataset_ob = data_set.create_dataset(dataset_name) - trait_ob = GeneralTrait(dataset=dataset_ob, - name=trait_name, - cellid=None) - self.trait_list.append((trait_ob, dataset_ob)) def calculate_pca(self, cols, corr_eigen_value, corr_eigen_vectors): base = importr('base') @@ -257,7 +245,7 @@ class CorrelationMatrix(object): this_vals_string += "x " this_vals_string = this_vals_string[:-1] - Redis.set(trait_id, this_vals_string) + Redis.set(trait_id, this_vals_string, ex=THIRTY_DAYS) self.pca_trait_ids.append(trait_id) return pca diff --git a/wqflask/wqflask/ctl/ctl_analysis.py b/wqflask/wqflask/ctl/ctl_analysis.py index 4415b86a..35067036 100644 --- a/wqflask/wqflask/ctl/ctl_analysis.py +++ b/wqflask/wqflask/ctl/ctl_analysis.py @@ -17,7 +17,7 @@ import csv import itertools from base import data_set -from base import trait as TRAIT +from base.trait import create_trait, retrieve_sample_data from utility import helper_functions from utility.tools import locate, GN2_BRANCH_URL @@ -122,8 +122,8 @@ class CTL(object): logger.debug("retrieving data for", trait) if trait != "": ts = trait.split(':') - gt = TRAIT.GeneralTrait(name = ts[0], dataset_name = ts[1]) - gt = TRAIT.retrieve_sample_data(gt, dataset, individuals) + gt = create_trait(name = ts[0], dataset_name = ts[1]) + gt = retrieve_sample_data(gt, dataset, individuals) for ind in individuals: if ind in gt.data.keys(): traits.append(gt.data[ind].value) @@ -180,8 +180,8 @@ class CTL(object): logger.debug(significant[0][x], significant[1][x], significant[2][x]) # Debug to console tsS = significant[0][x].split(':') # Source tsT = significant[2][x].split(':') # Target - gtS = TRAIT.GeneralTrait(name = tsS[0], dataset_name = tsS[1]) # Retrieve Source info from the DB - gtT = TRAIT.GeneralTrait(name = tsT[0], dataset_name = tsT[1]) # Retrieve Target info from the DB + gtS = create_trait(name = tsS[0], dataset_name = tsS[1]) # Retrieve Source info from the DB + gtT = create_trait(name = tsT[0], dataset_name = tsT[1]) # Retrieve Target info from the DB self.addNode(gtS) self.addNode(gtT) self.addEdge(gtS, gtT, significant, x) diff --git a/wqflask/wqflask/do_search.py b/wqflask/wqflask/do_search.py index b0ca5ced..1e15d28f 100644 --- a/wqflask/wqflask/do_search.py +++ b/wqflask/wqflask/do_search.py @@ -34,10 +34,7 @@ class DoSearch(object): self.search_type = search_type if self.dataset: - logger.debug("self.dataset is boo: ", type(self.dataset), pf(self.dataset)) - logger.debug("self.dataset.group is: ", pf(self.dataset.group)) #Get group information for dataset and the species id - self.species_id = webqtlDatabaseFunction.retrieve_species_id(self.dataset.group.name) def execute(self, query): @@ -54,10 +51,6 @@ class DoSearch(object): return keyword - #def escape(self, stringy): - # """Shorter name than self.db_conn.escape_string""" - # return escape(str(stringy)) - def mescape(self, *items): """Multiple escape""" escaped = [escape(str(item)) for item in items] @@ -71,8 +64,6 @@ class DoSearch(object): @classmethod def get_search(cls, search_type): - logger.debug("search_types are:", pf(cls.search_types)) - search_type_string = search_type['dataset_type'] if 'key' in search_type and search_type['key'] != None: search_type_string += '_' + search_type['key'] @@ -648,7 +639,7 @@ class CisTransLrsSearch(DoSearch): escape(self.dataset.type), chromosome) else: - location_clause = "(ABS(%s.Mb-Geno.Mb) %s %s and %s.Chr = Geno.Chr) or (%s.Chr != Geno.Chr)" % (escape(self.dataset.type), the_operator, escape(str(self.mb_buffer)), escape(self.dataset.type)) + location_clause = "(ABS(%s.Mb-Geno.Mb) %s %s and %s.Chr = Geno.Chr) or (%s.Chr != Geno.Chr)" % (escape(self.dataset.type), the_operator, escape(str(self.mb_buffer)), escape(self.dataset.type), escape(self.dataset.type)) where_clause = sub_clause + """ %sXRef.Locus = Geno.name and Geno.SpeciesId = %s and diff --git a/wqflask/wqflask/gsearch.py b/wqflask/wqflask/gsearch.py index 04e3d578..c65a1415 100644 --- a/wqflask/wqflask/gsearch.py +++ b/wqflask/wqflask/gsearch.py @@ -4,7 +4,7 @@ import json from flask import Flask, g from base.data_set import create_dataset -from base.trait import GeneralTrait +from base.trait import create_trait from db import webqtlDatabaseFunction from base import webqtlConfig @@ -96,7 +96,9 @@ class GSearch(object): #dataset = create_dataset(line[3], "ProbeSet", get_samplelist=False) #trait_id = line[4] #with Bench("Building trait object"): - trait_ob = GeneralTrait(dataset_name=this_trait['dataset'], name=this_trait['name'], get_qtl_info=True, get_sample_info=False) + trait_ob = create_trait(dataset_name=this_trait['dataset'], name=this_trait['name'], get_qtl_info=True, get_sample_info=False) + if not trait_ob: + continue max_lrs_text = "N/A" if trait_ob.locus_chr != "" and trait_ob.locus_mb != "": max_lrs_text = "Chr" + str(trait_ob.locus_chr) + ": " + str(trait_ob.locus_mb) @@ -210,13 +212,12 @@ class GSearch(object): if line[11] != "" and line[11] != None: this_trait['additive'] = '%.3f' % line[11] - #dataset = create_dataset(line[2], "Publish") - #trait_id = line[3] - #this_trait = GeneralTrait(dataset=dataset, name=trait_id, get_qtl_info=True, get_sample_info=False) this_trait['max_lrs_text'] = "N/A" + trait_ob = create_trait(dataset_name=this_trait['dataset'], name=this_trait['name'], get_qtl_info=True, get_sample_info=False) + if not trait_ob: + continue if this_trait['dataset'] == this_trait['group'] + "Publish": try: - trait_ob = GeneralTrait(dataset_name=this_trait['dataset'], name=this_trait['name'], get_qtl_info=True, get_sample_info=False) if trait_ob.locus_chr != "" and trait_ob.locus_mb != "": this_trait['max_lrs_text'] = "Chr" + str(trait_ob.locus_chr) + ": " + str(trait_ob.locus_mb) except: diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index a648667b..74fa4329 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -246,6 +246,12 @@ class DisplayMappingResults(object): if 'output_files' in start_vars: self.output_files = ",".join(start_vars['output_files']) + self.categorical_vars = "" + self.perm_strata = "" + if 'perm_strata' in start_vars.keys() and 'categorical_vars' in start_vars.keys(): + self.categorical_vars = start_vars['categorical_vars'] + self.perm_strata = start_vars['perm_strata'] + self.selectedChr = int(start_vars['selected_chr']) self.strainlist = start_vars['samples'] diff --git a/wqflask/wqflask/marker_regression/gemma_mapping.py b/wqflask/wqflask/marker_regression/gemma_mapping.py index e2b15c26..88d27517 100644 --- a/wqflask/wqflask/marker_regression/gemma_mapping.py +++ b/wqflask/wqflask/marker_regression/gemma_mapping.py @@ -1,7 +1,7 @@ import os, math, string, random, json from base import webqtlConfig -from base.trait import GeneralTrait +from base.trait import create_trait from base.data_set import create_dataset from utility.tools import flat_files, GEMMA_COMMAND, GEMMA_WRAPPER_COMMAND, TEMPDIR, WEBSERVER_MODE @@ -129,7 +129,7 @@ def gen_covariates_file(this_dataset, covariates, samples): this_covariate_data = [] trait_name = covariate.split(":")[0] dataset_ob = create_dataset(covariate.split(":")[1]) - trait_ob = GeneralTrait(dataset=dataset_ob, + trait_ob = create_trait(dataset=dataset_ob, name=trait_name, cellid=None) diff --git a/wqflask/wqflask/marker_regression/rqtl_mapping.py b/wqflask/wqflask/marker_regression/rqtl_mapping.py index e4a4d127..c5590a85 100644 --- a/wqflask/wqflask/marker_regression/rqtl_mapping.py +++ b/wqflask/wqflask/marker_regression/rqtl_mapping.py @@ -6,7 +6,7 @@ import json from flask import g from base.webqtlConfig import TMPDIR -from base.trait import GeneralTrait +from base.trait import create_trait from base.data_set import create_dataset from utility import webqtlUtil from utility.tools import locate, TEMPDIR @@ -86,7 +86,6 @@ def run_rqtl_geno(vals, samples, dataset, mapping_scale, method, model, permChec cross_object = add_phenotype(cross_object, pheno_string, "the_pheno") # Add the phenotype cross_object = add_names(cross_object, names_string, "the_names") # Add the phenotype logger.info("Added pheno and names"); - # Scan for QTLs marker_covars = create_marker_covariates(control_marker, cross_object) # Create the additive covariate markers logger.info("Marker covars done"); if cofactors != "": @@ -115,6 +114,7 @@ def run_rqtl_geno(vals, samples, dataset, mapping_scale, method, model, permChec else: if do_control == "true" or cofactors != "": logger.info("Using covariate"); result_data_frame = scanone(cross_object, pheno = "the_pheno", addcovar = covars, model=model, method=method) + ro.r('save.image(file = "/home/zas1024/gn2-zach/itp_cofactor_test.RData")') else: logger.info("No covariates"); result_data_frame = scanone(cross_object, pheno = "the_pheno", model=model, method=method) @@ -295,7 +295,7 @@ def add_cofactors(cross, this_dataset, covariates, samples): covar_as_string = "c(" trait_name = covariate.split(":")[0] dataset_ob = create_dataset(covariate.split(":")[1]) - trait_ob = GeneralTrait(dataset=dataset_ob, + trait_ob = create_trait(dataset=dataset_ob, name=trait_name, cellid=None) @@ -321,27 +321,27 @@ def add_cofactors(cross, this_dataset, covariates, samples): datatype = get_trait_data_type(covariate) logger.info("Covariate: " + covariate + " is of type: " + datatype); if(datatype == "categorical"): # Cat variable - logger.info("call of add_categorical_covar"); - cross, col_names = add_categorical_covar(cross, covar_as_string, i) # Expand and add it to the cross - logger.info("add_categorical_covar returned"); - for z, col_name in enumerate(col_names): # Go through the additional covar names + logger.info("call of add_categorical_covar"); + cross, col_names = add_categorical_covar(cross, covar_as_string, i) # Expand and add it to the cross + logger.info("add_categorical_covar returned"); + for z, col_name in enumerate(col_names): # Go through the additional covar names + if i < (len(covariate_list) - 1): + covar_name_string += '"' + col_name + '", ' + else: + if(z < (len(col_names) -1)): + covar_name_string += '"' + col_name + '", ' + else: + covar_name_string += '"' + col_name + '"' + + logger.info("covar_name_string:" + covar_name_string) + else: + col_name = "covar_" + str(i) + cross = add_phenotype(cross, covar_as_string, col_name) if i < (len(covariate_list) - 1): - covar_name_string += '"' + col_name + '", ' - else: - if(z < (len(col_names) -1)): covar_name_string += '"' + col_name + '", ' - else: + else: covar_name_string += '"' + col_name + '"' - logger.info("covar_name_string:" + covar_name_string); - else: - col_name = "covar_" + str(i) - cross = add_phenotype(cross, covar_as_string, col_name) - if i < (len(covariate_list) - 1): - covar_name_string += '"' + col_name + '", ' - else: - covar_name_string += '"' + col_name + '"' - covar_name_string += ")" logger.info("covar_name_string:" + covar_name_string); covars_ob = pull_var("trait_covars", cross, covar_name_string) @@ -350,9 +350,13 @@ def add_cofactors(cross, this_dataset, covariates, samples): def create_marker_covariates(control_marker, cross): ro.globalenv["the_cross"] = cross ro.r('genotypes <- pull.geno(the_cross)') # Get the genotype matrix - userinputS = control_marker.replace(" ", "").split(",") # TODO: sanitize user input, Never Ever trust a user - covariate_names = ', '.join('"{0}"'.format(w) for w in userinputS) - ro.r('covnames <- c(' + covariate_names + ')') + userinput_sanitized = control_marker.replace(" ", "").split(",") # TODO: sanitize user input, Never Ever trust a user + logger.debug(userinput_sanitized) + if len(userinput_sanitized) > 0: + covariate_names = ', '.join('"{0}"'.format(w) for w in userinput_sanitized) + ro.r('covnames <- c(' + covariate_names + ')') + else: + ro.r('covnames <- c()') ro.r('covInGeno <- which(covnames %in% colnames(genotypes))') ro.r('covnames <- covnames[covInGeno]') ro.r("cat('covnames (purged): ', covnames,'\n')") @@ -404,16 +408,4 @@ def process_rqtl_results(result, species_name): # TODO: how to make this marker['lod_score'] = output[i][2] qtl_results.append(marker) - return qtl_results - -def get_trait_data_type(trait_db_string): - # Get a trait's type (numeric, categorical, etc) from the DB - the_query = "SELECT value FROM TraitMetadata WHERE type='trait_data_type'" - results_json = g.db.execute(the_query).fetchone() - - results_ob = json.loads(results_json[0]) - - if trait_db_string in results_ob: - return results_ob[trait_db_string] - else: - return "numeric" + return qtl_results \ No newline at end of file diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index 5f7710ab..0711b852 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -161,7 +161,7 @@ class RunMapping(object): self.num_perm = 0 self.perm_output = [] self.bootstrap_results = [] - self.covariates = start_vars['covariates'] if "covariates" in start_vars else None + self.covariates = start_vars['covariates'] if "covariates" in start_vars else "" #ZS: This is passed to GN1 code for single chr mapping self.selected_chr = -1 @@ -467,6 +467,7 @@ class RunMapping(object): #mapping_scale = self.mapping_scale, #chromosomes = chromosome_mb_lengths, #qtl_results = self.qtl_results, + categorical_vars = self.categorical_vars, chr_lengths = chr_lengths, num_perm = self.num_perm, perm_results = self.perm_output, diff --git a/wqflask/wqflask/network_graph/network_graph.py b/wqflask/wqflask/network_graph/network_graph.py index 152e4168..f41f3017 100644 --- a/wqflask/wqflask/network_graph/network_graph.py +++ b/wqflask/wqflask/network_graph/network_graph.py @@ -44,7 +44,7 @@ import reaper from utility.THCell import THCell from utility.TDCell import TDCell -from base.trait import GeneralTrait +from base.trait import create_trait from base import data_set from utility import webqtlUtil, helper_functions, corr_result_helpers from utility.tools import GN2_BRANCH_URL @@ -217,7 +217,7 @@ class NetworkGraph(object): break trait_name, dataset_name = trait_db.split(":") dataset_ob = data_set.create_dataset(dataset_name) - trait_ob = GeneralTrait(dataset=dataset_ob, + trait_ob = create_trait(dataset=dataset_ob, name=trait_name, cellid=None) self.trait_list.append((trait_ob, dataset_ob)) \ No newline at end of file diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index 8f702d58..de4b01eb 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -1,15 +1,9 @@ -# from __future__ import absolute_import, print_function, division +from __future__ import absolute_import, print_function, division - -import os -import cPickle import re import uuid from math import * import time -import math -import datetime -import collections import re import requests @@ -18,18 +12,16 @@ from pprint import pformat as pf import json from base.data_set import create_dataset -from base import trait +from base.trait import create_trait from wqflask import parser from wqflask import do_search -from utility import webqtlUtil,tools from db import webqtlDatabaseFunction -from flask import render_template, Flask, g +from flask import Flask, g -from utility import formatting -from utility import hmac +from utility import hmac, helper_functions from utility.tools import GN2_BASE_URL -from utility.type_checking import is_float, is_int, is_str, get_float, get_int, get_string +from utility.type_checking import is_str from utility.logger import getLogger logger = getLogger(__name__ ) @@ -86,7 +78,7 @@ views.py). try: self.search() except: - self.search_term_exists = False + self.search_term_exists = False if self.search_term_exists: self.gen_search_result() @@ -113,50 +105,49 @@ views.py). trait_dict = {} trait_id = result[0] - trait_dict['index'] = index + 1 - this_trait = trait.GeneralTrait(dataset=self.dataset, name=trait_id, get_qtl_info=True, get_sample_info=False) - trait_dict['name'] = this_trait.name - if this_trait.dataset.type == "Publish": - trait_dict['display_name'] = this_trait.display_name - else: - trait_dict['display_name'] = this_trait.name - trait_dict['dataset'] = this_trait.dataset.name - trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(this_trait.name, this_trait.dataset.name)) - if this_trait.dataset.type == "ProbeSet": - trait_dict['symbol'] = this_trait.symbol - trait_dict['description'] = this_trait.description_display.decode('utf-8', 'replace') - trait_dict['location'] = this_trait.location_repr - trait_dict['mean'] = "N/A" - trait_dict['additive'] = "N/A" - if this_trait.mean != "" and this_trait.mean != None: - trait_dict['mean'] = '%.3f' % this_trait.mean - trait_dict['lrs_score'] = this_trait.LRS_score_repr - trait_dict['lrs_location'] = this_trait.LRS_location_repr - if this_trait.additive != "": - trait_dict['additive'] = '%.3f' % this_trait.additive - elif this_trait.dataset.type == "Geno": - trait_dict['location'] = this_trait.location_repr - elif this_trait.dataset.type == "Publish": - trait_dict['description'] = this_trait.description_display - trait_dict['authors'] = this_trait.authors - trait_dict['pubmed_id'] = "N/A" - if this_trait.pubmed_id: - trait_dict['pubmed_id'] = this_trait.pubmed_id - trait_dict['pubmed_link'] = this_trait.pubmed_link - trait_dict['pubmed_text'] = this_trait.pubmed_text - trait_dict['mean'] = "N/A" - if this_trait.mean != "" and this_trait.mean != None: - trait_dict['mean'] = '%.3f' % this_trait.mean - trait_dict['lrs_score'] = this_trait.LRS_score_repr - trait_dict['lrs_location'] = this_trait.LRS_location_repr - trait_dict['additive'] = "N/A" - if this_trait.additive != "": - trait_dict['additive'] = '%.3f' % this_trait.additive - trait_list.append(trait_dict) - #json_trait_list.append(trait.jsonable_table_row(this_trait, self.dataset.name, index + 1)) + this_trait = create_trait(dataset=self.dataset, name=trait_id, get_qtl_info=True, get_sample_info=False) + if this_trait: + trait_dict['index'] = index + 1 + trait_dict['name'] = this_trait.name + if this_trait.dataset.type == "Publish": + trait_dict['display_name'] = this_trait.display_name + else: + trait_dict['display_name'] = this_trait.name + trait_dict['dataset'] = this_trait.dataset.name + trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(this_trait.name, this_trait.dataset.name)) + if this_trait.dataset.type == "ProbeSet": + trait_dict['symbol'] = this_trait.symbol + trait_dict['description'] = this_trait.description_display.decode('utf-8', 'replace') + trait_dict['location'] = this_trait.location_repr + trait_dict['mean'] = "N/A" + trait_dict['additive'] = "N/A" + if this_trait.mean != "" and this_trait.mean != None: + trait_dict['mean'] = '%.3f' % this_trait.mean + trait_dict['lrs_score'] = this_trait.LRS_score_repr + trait_dict['lrs_location'] = this_trait.LRS_location_repr + if this_trait.additive != "": + trait_dict['additive'] = '%.3f' % this_trait.additive + elif this_trait.dataset.type == "Geno": + trait_dict['location'] = this_trait.location_repr + elif this_trait.dataset.type == "Publish": + trait_dict['description'] = this_trait.description_display + trait_dict['authors'] = this_trait.authors + trait_dict['pubmed_id'] = "N/A" + if this_trait.pubmed_id: + trait_dict['pubmed_id'] = this_trait.pubmed_id + trait_dict['pubmed_link'] = this_trait.pubmed_link + trait_dict['pubmed_text'] = this_trait.pubmed_text + trait_dict['mean'] = "N/A" + if this_trait.mean != "" and this_trait.mean != None: + trait_dict['mean'] = '%.3f' % this_trait.mean + trait_dict['lrs_score'] = this_trait.LRS_score_repr + trait_dict['lrs_location'] = this_trait.LRS_location_repr + trait_dict['additive'] = "N/A" + if this_trait.additive != "": + trait_dict['additive'] = '%.3f' % this_trait.additive + trait_list.append(trait_dict) self.trait_list = json.dumps(trait_list) - #self.json_trait_list = json.dumps(json_trait_list) def search(self): """ @@ -234,7 +225,6 @@ views.py). self.header_fields = the_search.header_fields def get_search_ob(self, a_search): - logger.debug("[kodak] item is:", pf(a_search)) search_term = a_search['search_term'] search_operator = a_search['separator'] search_type = {} @@ -243,12 +233,10 @@ views.py). search_type['key'] = a_search['key'].upper() else: search_type['key'] = None - logger.debug("search_type is:", pf(search_type)) search_ob = do_search.DoSearch.get_search(search_type) if search_ob: search_class = getattr(do_search, search_ob) - logger.debug("search_class is: ", pf(search_class)) the_search = search_class(search_term, search_operator, self.dataset, diff --git a/wqflask/wqflask/show_trait/export_trait_data.py b/wqflask/wqflask/show_trait/export_trait_data.py index 107f87c6..253c887b 100644 --- a/wqflask/wqflask/show_trait/export_trait_data.py +++ b/wqflask/wqflask/show_trait/export_trait_data.py @@ -4,7 +4,7 @@ import simplejson as json from pprint import pformat as pf -from base.trait import GeneralTrait +from base.trait import create_trait from base import data_set def export_sample_table(targs): @@ -26,7 +26,7 @@ def export_sample_table(targs): def get_export_metadata(trait_id, dataset_name): dataset = data_set.create_dataset(dataset_name) - this_trait = GeneralTrait(dataset=dataset, + this_trait = create_trait(dataset=dataset, name=trait_id, cellid=None, get_qtl_info=False) diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index 29b2f77e..c77e247f 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -10,9 +10,6 @@ import json as json from collections import OrderedDict -import redis -Redis = redis.StrictRedis() - import numpy as np import scipy.stats as ss @@ -21,11 +18,15 @@ from flask import Flask, g from base import webqtlConfig from base import webqtlCaseData from wqflask.show_trait.SampleList import SampleList -from utility import webqtlUtil, Plot, Bunch, helper_functions -from utility.tools import locate_ignore_error -from base.trait import GeneralTrait +from base.trait import create_trait from base import data_set from db import webqtlDatabaseFunction +from utility import webqtlUtil, Plot, Bunch, helper_functions +from utility.authentication_tools import check_owner +from utility.tools import locate_ignore_error +from utility.redis_tools import get_redis_conn, get_resource_id +Redis = get_redis_conn() +ONE_YEAR = 60 * 60 * 24 * 365 from pprint import pformat as pf @@ -55,9 +56,9 @@ class ShowTrait(object): self.temp_group = kw['group'] self.dataset = data_set.create_dataset(dataset_name = "Temp", dataset_type = "Temp", group_name = self.temp_group) # Put values in Redis so they can be looked up later if added to a collection - Redis.set(self.trait_id, kw['trait_paste']) + Redis.set(self.trait_id, kw['trait_paste'], ex=ONE_YEAR) self.trait_vals = kw['trait_paste'].split() - self.this_trait = GeneralTrait(dataset=self.dataset, + self.this_trait = create_trait(dataset=self.dataset, name=self.trait_id, cellid=None) else: @@ -66,11 +67,13 @@ class ShowTrait(object): self.temp_species = self.trait_id.split("_")[1] self.temp_group = self.trait_id.split("_")[2] self.dataset = data_set.create_dataset(dataset_name = "Temp", dataset_type = "Temp", group_name = self.temp_group) - self.this_trait = GeneralTrait(dataset=self.dataset, + self.this_trait = create_trait(dataset=self.dataset, name=self.trait_id, cellid=None) self.trait_vals = Redis.get(self.trait_id).split() + self.resource_id = check_owner(self.dataset, self.trait_id) + #ZS: Get verify/rna-seq link URLs try: blatsequence = self.this_trait.sequence diff --git a/wqflask/wqflask/templates/admin/group_manager.html b/wqflask/wqflask/templates/admin/group_manager.html index ac5c1350..b7df1aad 100644 --- a/wqflask/wqflask/templates/admin/group_manager.html +++ b/wqflask/wqflask/templates/admin/group_manager.html @@ -2,17 +2,25 @@ {% block title %}Group Manager{% endblock %} {% block content %} - {{ header("List of groups", "" )}} -
-
+ +
+ {% if admin_groups|length == 0 and user_groups|length == 0 %} +

You currently aren't a member or admin of any groups.

+
+ + {% else %}

Admin Groups


- + {% if admin_groups|length == 0 %} +

You currently aren't the administrator of any groups.

+ {% else %} +
@@ -26,7 +34,7 @@ {% for group in admin_groups %} - + @@ -36,12 +44,16 @@ {% endfor %}
{{ loop.index }} {{ group.name }} {{ group.admins|length + group.users|length }}
+ {% endif %}

User Groups


- + {% if user_groups|length == 0 %} +

You currently aren't a member of any groups.

+ {% else %} +
@@ -65,12 +77,12 @@ {% endfor %}
+ {% endif %} + {% endif %}
- - {% endblock %} @@ -79,7 +91,6 @@ - {% endblock %} diff --git a/wqflask/wqflask/templates/correlation_page.html b/wqflask/wqflask/templates/correlation_page.html index 1c84239c..71705390 100644 --- a/wqflask/wqflask/templates/correlation_page.html +++ b/wqflask/wqflask/templates/correlation_page.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% block title %}Correlation Results{% endblock %} {% block css %} diff --git a/wqflask/wqflask/templates/email/verification.txt b/wqflask/wqflask/templates/email/verification.txt deleted file mode 100644 index 76149a3a..00000000 --- a/wqflask/wqflask/templates/email/verification.txt +++ /dev/null @@ -1,7 +0,0 @@ -Thank you for signing up for GeneNetwork. - -We need to verify your email address. - -To do that please click the following link, or cut and paste it into your browser window: - -{{ url_for_hmac("verify_email", code = verification_code, _external=True )}} diff --git a/wqflask/wqflask/templates/gsearch_pheno.html b/wqflask/wqflask/templates/gsearch_pheno.html index 05b2f988..04b45659 100644 --- a/wqflask/wqflask/templates/gsearch_pheno.html +++ b/wqflask/wqflask/templates/gsearch_pheno.html @@ -31,7 +31,7 @@

-
+
diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html index b4429b46..c5d49168 100644 --- a/wqflask/wqflask/templates/mapping_results.html +++ b/wqflask/wqflask/templates/mapping_results.html @@ -41,7 +41,8 @@ - + + @@ -464,13 +465,27 @@ {% if mapping_method != "gemma" and mapping_method != "plink" %} $('#download_perm').click(function(){ - var num_perm, perm_data; - num_perm = js_data.num_perm - perm_data = js_data.perm_results - json_perm_data = JSON.stringify(perm_data); - $('input[name=perm_results]').val(json_perm_data); - $('#marker_regression_form').attr('action', '/export_perm_data'); - return $('#marker_regression_form').submit(); + perm_info_dict = { + perm_data: js_data.perm_results, + num_perm: "{{ nperm }}", + trait_name: "{{ this_trait.display_name }}", + trait_description: "{{ this_trait.description_display }}", + cofactors: "{{ covariates }}", + n_samples: {{ n_samples }}, + n_genotypes: {{ qtl_results|length }}, + {% if genofile_string is defined %} + genofile: "{{ genofile_string }}", + {% else %} + genofile: "", + {% endif %} + units_linkage: "{{ LRS_LOD }}", + strat_cofactors: js_data.categorical_vars + } + json_perm_data = JSON.stringify(perm_info_dict); + + $('input[name=perm_info]').val(json_perm_data); + $('#marker_regression_form').attr('action', '/export_perm_data'); + return $('#marker_regression_form').submit(); }); modebar_options = { diff --git a/wqflask/wqflask/user_login.py b/wqflask/wqflask/user_login.py index edd272c2..cfee0079 100644 --- a/wqflask/wqflask/user_login.py +++ b/wqflask/wqflask/user_login.py @@ -12,9 +12,6 @@ import requests import simplejson as json -import redis # used for collections -Redis = redis.StrictRedis() - from flask import (Flask, g, render_template, url_for, request, make_response, redirect, flash, abort) @@ -23,7 +20,8 @@ from wqflask import pbkdf2 from wqflask.user_session import UserSession from utility import hmac -from utility.redis_tools import is_redis_available, get_user_id, get_user_by_unique_column, set_user_attribute, save_user, save_verification_code, check_verification_code, get_user_collections, save_collections +from utility.redis_tools import is_redis_available, get_redis_conn, get_user_id, get_user_by_unique_column, set_user_attribute, save_user, save_verification_code, check_verification_code, get_user_collections, save_collections +Redis = get_redis_conn() from utility.logger import getLogger logger = getLogger(__name__) @@ -127,7 +125,7 @@ def send_email(toaddr, msg, fromaddr="no-reply@genenetwork.org"): server.quit() logger.info("Successfully sent email to "+toaddr) -def send_verification_email(user_details, template_name = "email/verification.txt", key_prefix = "verification_code", subject = "GeneNetwork email verification"): +def send_verification_email(user_details, template_name = "email/user_verification.txt", key_prefix = "verification_code", subject = "GeneNetwork e-mail verification"): verification_code = str(uuid.uuid4()) key = key_prefix + ":" + verification_code @@ -141,6 +139,21 @@ def send_verification_email(user_details, template_name = "email/verification.tx send_email(recipient, subject, body) return {"recipient": recipient, "subject": subject, "body": body} +@app.route("/manage/verify_email") +def verify_email(): + if 'code' in request.args: + user_details = check_verification_code(request.args['code']) + if user_details: + # As long as they have access to the email account + # We might as well log them in + session_id_signed = get_signed_session_id(user_details) + flash("Thank you for logging in {}.".format(user_details['full_name']), "alert-success") + response = make_response(redirect(url_for('index_page', import_collections = import_col, anon_id = anon_id))) + response.set_cookie(UserSession.user_cookie_name, session_id_signed, max_age=None) + return response + else: + flash("Invalid code: Password reset code does not exist or might have expired!", "error") + @app.route("/n/login", methods=('GET', 'POST')) def login(): params = request.form if request.form else request.args @@ -204,7 +217,7 @@ def login(): response.set_cookie(UserSession.user_cookie_name, session_id_signed, max_age=None) return response else: - email_ob = send_verification_email(user_details) + email_ob = send_verification_email(user_details, template_name = "email/user_verification.txt") return render_template("newsecurity/verification_still_needed.html", subject=email_ob['subject']) else: # Incorrect password #ZS: It previously seemed to store that there was an incorrect log-in attempt here, but it did so in the MySQL DB so this might need to be reproduced with Redis @@ -374,16 +387,13 @@ def password_reset(): hmac = request.args.get('hm') if verification_code: - user_email = check_verification_code(verification_code) - if user_email: - user_details = get_user_by_unique_column('email_address', user_email) - if user_details: - return render_template( - "new_security/password_reset.html", user_encode=user_details["email_address"]) - else: - flash("Invalid code: User no longer exists!", "error") + user_details = check_verification_code(verification_code) + if user_details: + return render_template( + "new_security/password_reset.html", user_encode=user_details["email_address"]) else: flash("Invalid code: Password reset code does not exist or might have expired!", "error") + return redirect(url_for("login")) else: return redirect(url_for("login")) @@ -394,6 +404,7 @@ def password_reset_step2(): errors = [] user_email = request.form['user_encode'] + user_id = get_user_id("email_address", user_email) password = request.form['password'] encoded_password = set_password(password) @@ -401,9 +412,7 @@ def password_reset_step2(): set_user_attribute(user_id, "password", encoded_password) flash("Password changed successfully. You can now sign in.", "alert-info") - response = make_response(redirect(url_for('login'))) - - return response + return redirect(url_for('login')) def register_user(params): thank_you_mode = False diff --git a/wqflask/wqflask/user_session.py b/wqflask/wqflask/user_session.py index 50419146..ec6d4ae3 100644 --- a/wqflask/wqflask/user_session.py +++ b/wqflask/wqflask/user_session.py @@ -6,10 +6,6 @@ import uuid import simplejson as json -import redis # used for collections -Redis = redis.StrictRedis() - - from flask import (Flask, g, render_template, url_for, request, make_response, redirect, flash, abort) @@ -17,7 +13,8 @@ from wqflask import app from utility import hmac #from utility.elasticsearch_tools import get_elasticsearch_connection -from utility.redis_tools import get_user_id, get_user_by_unique_column, get_user_collections, save_collections +from utility.redis_tools import get_redis_conn, get_user_id, get_user_collections, save_collections +Redis = get_redis_conn() from utility.logger import getLogger logger = getLogger(__name__) @@ -29,6 +26,11 @@ THIRTY_DAYS = 60 * 60 * 24 * 30 def get_user_session(): logger.info("@app.before_request get_session") g.user_session = UserSession() + #ZS: I think this should solve the issue of deleting the cookie and redirecting to the home page when a user's session has expired + if not g.user_session: + response = make_response(redirect(url_for('login'))) + response.set_cookie('session_id_v2', '', expires=0) + return response @app.after_request def set_user_session(response): @@ -37,7 +39,6 @@ def set_user_session(response): response.set_cookie(g.user_session.cookie_name, g.user_session.cookie) return response - def verify_cookie(cookie): the_uuid, separator, the_signature = cookie.partition(':') assert len(the_uuid) == 36, "Is session_id a uuid?" @@ -88,14 +89,11 @@ class UserSession(object): user_id = str(uuid.uuid4())) Redis.hmset(self.redis_key, self.record) Redis.expire(self.redis_key, THIRTY_DAYS) - response = make_response(redirect(url_for('login'))) - response.set_cookie(self.user_cookie_name, '', expires=0) ########### Grrr...this won't work because of the way flask handles cookies # Delete the cookie flash("Due to inactivity your session has expired. If you'd like please login again.") - return response - #return + return None else: self.record = dict(login_time = time.time(), user_type = "anon", diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 938570f3..24a4dcee 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -23,16 +23,13 @@ import uuid import simplejson as json import yaml -#Switching from Redis to StrictRedis; might cause some issues -import redis -Redis = redis.StrictRedis() - import flask import base64 import array import sqlalchemy from wqflask import app -from flask import g, Response, request, make_response, render_template, send_from_directory, jsonify, redirect +from flask import g, Response, request, make_response, render_template, send_from_directory, jsonify, redirect, url_for +from wqflask import group_manager from wqflask import search_results from wqflask import export_traits from wqflask import gsearch @@ -55,11 +52,13 @@ from wqflask.correlation import corr_scatter_plot from wqflask.wgcna import wgcna_analysis from wqflask.ctl import ctl_analysis from wqflask.snp_browser import snp_browser -#from wqflask.trait_submission import submit_trait from utility import temp_data from utility.tools import SQL_URI,TEMPDIR,USE_REDIS,USE_GN_SERVER,GN_SERVER_URL,GN_VERSION,JS_TWITTER_POST_FETCHER_PATH,JS_GUIX_PATH, CSS_PATH from utility.helper_functions import get_species_groups +from utility.authentication_tools import check_resource_availability +from utility.redis_tools import get_redis_conn +Redis = get_redis_conn() from base.webqtlConfig import GENERATED_IMAGE_DIR from utility.benchmark import Bench @@ -87,6 +86,24 @@ def connect_db(): g.db = g._database = sqlalchemy.create_engine(SQL_URI, encoding="latin1") logger.debug(g.db) +@app.before_request +def check_access_permissions(): + logger.debug("@app.before_request check_access_permissions") + if "temp_trait" in request.args: + if request.args['temp_trait'] == "True": + pass + else: + if 'dataset' in request.args: + dataset = create_dataset(request.args['dataset']) + logger.debug("USER:", Redis.hget("users")) + if 'trait_id' in request.args: + available = check_resource_availability(dataset, request.args['trait_id']) + else: + available = check_resource_availability(dataset) + + if not available: + return redirect(url_for("no_access_page")) + @app.teardown_appcontext def shutdown_session(exception=None): db = getattr(g, '_database', None) @@ -120,6 +137,10 @@ def handle_bad_request(e): resp.set_cookie(err_msg[:32],animation) return resp +@app.route("/authentication_needed") +def no_access_page(): + return render_template("new_security/not_authenticated.html") + @app.route("/") def index_page(): logger.info("Sending index_page") @@ -401,25 +422,43 @@ def export_traits_csv(): def export_perm_data(): """CSV file consisting of the permutation data for the mapping results""" logger.info(request.url) - num_perm = float(request.form['num_perm']) - perm_data = json.loads(request.form['perm_results']) + perm_info = json.loads(request.form['perm_info']) + + now = datetime.datetime.now() + time_str = now.strftime('%H:%M_%d%B%Y') + + file_name = "Permutation_" + perm_info['num_perm'] + "_" + perm_info['trait_name'] + "_" + time_str + + the_rows = [ + ["#Permutation Test"], + ["#File_name: " + file_name], + ["#Metadata: From GeneNetwork.org"], + ["#Trait_ID: " + perm_info['trait_name']], + ["#Trait_description: " + perm_info['trait_description']], + ["#N_permutations: " + str(perm_info['num_perm'])], + ["#Cofactors: " + perm_info['cofactors']], + ["#N_cases: " + str(perm_info['n_samples'])], + ["#N_genotypes: " + str(perm_info['n_genotypes'])], + ["#Genotype_file: " + perm_info['genofile']], + ["#Units_linkage: " + perm_info['units_linkage']], + ["#Permutation_stratified_by: " + ", ".join([ str(cofactor) for cofactor in perm_info['strat_cofactors']])], + ["#RESULTS_1: Suggestive LRS(p=0.63) = " + str(np.percentile(np.array(perm_info['perm_data']), 67))], + ["#RESULTS_2: Significant LRS(p=0.05) = " + str(np.percentile(np.array(perm_info['perm_data']), 95))], + ["#RESULTS_3: Highly Significant LRS(p=0.01) = " + str(np.percentile(np.array(perm_info['perm_data']), 99))], + ["#Comment: Results sorted from low to high peak linkage"] + ] buff = StringIO.StringIO() writer = csv.writer(buff) - writer.writerow(["Suggestive LRS (p=0.63) = " + str(np.percentile(np.array(perm_data), 67))]) - writer.writerow(["Significant LRS (p=0.05) = " + str(np.percentile(np.array(perm_data), 95))]) - writer.writerow(["Highly Significant LRS (p=0.01) = " + str(np.percentile(np.array(perm_data), 99))]) - writer.writerow("") - writer.writerow([str(num_perm) + " Permutations"]) - writer.writerow("") - for item in perm_data: + writer.writerows(the_rows) + for item in perm_info['perm_data']: writer.writerow([item]) csv_data = buff.getvalue() buff.close() return Response(csv_data, mimetype='text/csv', - headers={"Content-Disposition":"attachment;filename=perm_data.csv"}) + headers={"Content-Disposition":"attachment;filename=" + file_name + ".csv"}) @app.route("/show_temp_trait", methods=('POST',)) def show_temp_trait_page(): -- cgit v1.2.3 From 218576a04f90cc0bc9e53685323e1caa8cffe986 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 4 Jun 2020 15:50:56 -0500 Subject: Added back in trait info queries for situations where the proxy isn't running --- wqflask/base/trait.py | 94 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 14 deletions(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index b133bf21..405c4ebf 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -1,9 +1,11 @@ from __future__ import absolute_import, division, print_function +import os import string import resource import codecs import requests +import random from base import webqtlConfig from base.webqtlCaseData import webqtlCaseData @@ -12,8 +14,8 @@ from db import webqtlDatabaseFunction from utility import webqtlUtil from utility import hmac from utility.authentication_tools import check_resource_availability -from utility.tools import GN2_BASE_URL -from utility.redis_tools import get_redis_conn +from utility.tools import GN2_BASE_URL, GN_VERSION +from utility.redis_tools import get_redis_conn, get_resource_id, get_resource_info Redis = get_redis_conn() from wqflask import app @@ -22,7 +24,7 @@ import simplejson as json from MySQLdb import escape_string as escape from pprint import pformat as pf -from flask import Flask, g, request, url_for, redirect +from flask import Flask, g, request, url_for, redirect, make_response, render_template from utility.logger import getLogger logger = getLogger(__name__ ) @@ -45,7 +47,10 @@ def create_trait(**kw): permitted = check_resource_availability(dataset) if permitted: - return GeneralTrait(**kw) + the_trait = GeneralTrait(**kw) + if the_trait.dataset.type != "Temp": + the_trait = retrieve_trait_info(the_trait, the_trait.dataset, get_qtl_info=kw.get('get_qtl_info')) + return the_trait else: return None @@ -99,9 +104,6 @@ class GeneralTrait(object): # Todo: These two lines are necessary most of the time, but perhaps not all of the time # So we could add a simple if statement to short-circuit this if necessary - if self.dataset.type != "Temp": - self = retrieve_trait_info(self, self.dataset, get_qtl_info=get_qtl_info) - if get_sample_info != False: self = retrieve_sample_data(self, self.dataset) @@ -373,17 +375,15 @@ def jsonable_table_row(trait, dataset_name, index): else: return dict() + def retrieve_trait_info(trait, dataset, get_qtl_info=False): assert dataset, "Dataset doesn't exist" + resource_id = get_resource_id(dataset, trait.name) if dataset.type == 'Publish': - resource_id = hmac.hmac_creation("{}:{}:{}".format('dataset-publish', dataset.id, trait.name)) the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id) - elif dataset.type == 'ProbeSet': - resource_id = hmac.hmac_creation("{}:{}".format('dataset-probeset', dataset.id)) - the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view&trait={}".format(resource_id, g.user_session.user_id, trait.name) else: - resource_id = hmac.hmac_creation("{}:{}".format('dataset-geno', dataset.id)) + the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view&trait={}".format(resource_id, g.user_session.user_id, trait.name) try: @@ -394,11 +394,77 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): except: resource_info = get_resource_info(resource_id) default_permissions = resource_info['default_mask']['data'] - if 'view' not in default_persmissions: + if 'view' not in default_permissions: trait.view = False return trait - trait_info = json.loads(response) + if dataset.type == 'Publish': + query = """ + SELECT + PublishXRef.Id, Publication.PubMed_ID, + Phenotype.Pre_publication_description, Phenotype.Post_publication_description, Phenotype.Original_description, + Phenotype.Pre_publication_abbreviation, Phenotype.Post_publication_abbreviation, + Phenotype.Lab_code, Phenotype.Submitter, Phenotype.Owner, Phenotype.Authorized_Users, + Publication.Authors, Publication.Title, Publication.Abstract, + Publication.Journal, Publication.Volume, Publication.Pages, + Publication.Month, Publication.Year, PublishXRef.Sequence, + Phenotype.Units, PublishXRef.comments + FROM + PublishXRef, Publication, Phenotype, PublishFreeze + WHERE + PublishXRef.Id = %s AND + Phenotype.Id = PublishXRef.PhenotypeId AND + Publication.Id = PublishXRef.PublicationId AND + PublishXRef.InbredSetId = PublishFreeze.InbredSetId AND + PublishFreeze.Id = %s + """ % (trait.name, dataset.id) + + logger.sql(query) + trait_info = g.db.execute(query).fetchone() + + + #XZ, 05/08/2009: Xiaodong add this block to use ProbeSet.Id to find the probeset instead of just using ProbeSet.Name + #XZ, 05/08/2009: to avoid the problem of same probeset name from different platforms. + elif dataset.type == 'ProbeSet': + display_fields_string = ', ProbeSet.'.join(dataset.display_fields) + display_fields_string = 'ProbeSet.' + display_fields_string + query = """ + SELECT %s + FROM ProbeSet, ProbeSetFreeze, ProbeSetXRef + WHERE + ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id AND + ProbeSetXRef.ProbeSetId = ProbeSet.Id AND + ProbeSetFreeze.Name = '%s' AND + ProbeSet.Name = '%s' + """ % (escape(display_fields_string), + escape(dataset.name), + escape(str(trait.name))) + logger.sql(query) + trait_info = g.db.execute(query).fetchone() + #XZ, 05/08/2009: We also should use Geno.Id to find marker instead of just using Geno.Name + # to avoid the problem of same marker name from different species. + elif dataset.type == 'Geno': + display_fields_string = string.join(dataset.display_fields,',Geno.') + display_fields_string = 'Geno.' + display_fields_string + query = """ + SELECT %s + FROM Geno, GenoFreeze, GenoXRef + WHERE + GenoXRef.GenoFreezeId = GenoFreeze.Id AND + GenoXRef.GenoId = Geno.Id AND + GenoFreeze.Name = '%s' AND + Geno.Name = '%s' + """ % (escape(display_fields_string), + escape(dataset.name), + escape(trait.name)) + logger.sql(query) + trait_info = g.db.execute(query).fetchone() + else: #Temp type + query = """SELECT %s FROM %s WHERE Name = %s""" + logger.sql(query) + trait_info = g.db.execute(query, + (string.join(dataset.display_fields,','), + dataset.type, trait.name)).fetchone() if trait_info: trait.haveinfo = True -- cgit v1.2.3 From a302a2b0ac0e7c0f26a0d063c3f2b057f61d47f1 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 5 Jun 2020 16:52:56 -0500 Subject: Commiting other current group/resource management code, plus the new files --- wqflask/base/trait.py | 2 + wqflask/maintenance/set_resource_defaults.py | 155 +++++++++++++++++++++ wqflask/utility/authentication_tools.py | 46 ++++++ wqflask/utility/redis_tools.py | 37 +++-- wqflask/wqflask/group_manager.py | 77 ++++++++++ wqflask/wqflask/resource_manager.py | 72 ++++++++++ .../wqflask/static/new/javascript/group_manager.js | 38 +++++ wqflask/wqflask/templates/admin/create_group.html | 89 ++++++++++++ wqflask/wqflask/templates/admin/group_manager.html | 68 ++++----- .../wqflask/templates/admin/manage_resource.html | 92 ++++++++++++ .../wqflask/templates/admin/search_for_groups.html | 64 +++++++++ .../templates/admin/select_group_to_add.html | 54 +++++++ .../templates/new_security/not_authenticated.html | 11 ++ wqflask/wqflask/templates/show_trait_details.html | 5 + wqflask/wqflask/views.py | 3 +- 15 files changed, 764 insertions(+), 49 deletions(-) create mode 100644 wqflask/maintenance/set_resource_defaults.py create mode 100644 wqflask/utility/authentication_tools.py create mode 100644 wqflask/wqflask/group_manager.py create mode 100644 wqflask/wqflask/resource_manager.py create mode 100644 wqflask/wqflask/static/new/javascript/group_manager.js create mode 100644 wqflask/wqflask/templates/admin/create_group.html create mode 100644 wqflask/wqflask/templates/admin/manage_resource.html create mode 100644 wqflask/wqflask/templates/admin/search_for_groups.html create mode 100644 wqflask/wqflask/templates/admin/select_group_to_add.html create mode 100644 wqflask/wqflask/templates/new_security/not_authenticated.html diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index 405c4ebf..2a945588 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -391,6 +391,8 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): if response.strip() == "no-access": trait.view = False return trait + else: + trait_info = json.loads(response) except: resource_info = get_resource_info(resource_id) default_permissions = resource_info['default_mask']['data'] diff --git a/wqflask/maintenance/set_resource_defaults.py b/wqflask/maintenance/set_resource_defaults.py new file mode 100644 index 00000000..ba102d9c --- /dev/null +++ b/wqflask/maintenance/set_resource_defaults.py @@ -0,0 +1,155 @@ +""" + +Script that sets default resource access masks for use with the DB proxy + +Defaults will be: +Owner - omni_gn +Mask - Public/non-confidential: { data: "view", + metadata: "view", + admin: "not-admin" } + Private/confidentia: { data: "no-access", + metadata: "no-access", + admin: "not-admin" } + +To run: +./bin/genenetwork2 ~/my_settings.py -c ./wqflask/maintenance/gen_select_dataset.py + +""" + +from __future__ import print_function, division + +import sys +import json + +# NEW: Note we prepend the current path - otherwise a guix instance of GN2 may be used instead +sys.path.insert(0,'./') + +# NEW: import app to avoid a circular dependency on utility.tools +from wqflask import app + +from utility.tools import SQL_URI +from utility.redis_tools import get_redis_conn, get_user_id, add_resource, get_resources +Redis = get_redis_conn() + +import MySQLdb + +import urlparse + +from utility.logger import getLogger +logger = getLogger(__name__) + +def parse_db_uri(): + """Converts a database URI to the db name, host name, user name, and password""" + + parsed_uri = urlparse.urlparse(SQL_URI) + + db_conn_info = dict( + db = parsed_uri.path[1:], + host = parsed_uri.hostname, + user = parsed_uri.username, + passwd = parsed_uri.password) + + print(db_conn_info) + return db_conn_info + +def insert_probeset_resources(default_owner_id): + current_resources = Redis.hgetall("resources") + Cursor.execute(""" SELECT + ProbeSetFreeze.Id, ProbeSetFreeze.Name, ProbeSetFreeze.confidentiality, ProbeSetFreeze.public + FROM + ProbeSetFreeze""") + + resource_results = Cursor.fetchall() + for i, resource in enumerate(resource_results): + if i % 20 == 0: + print(i) + resource_ob = {} + resource_ob['name'] = resource[1] + resource_ob['owner_id'] = default_owner_id + resource_ob['data'] = { "dataset" : str(resource[0])} + resource_ob['type'] = "dataset-probeset" + if resource[2] < 1 and resource[3] > 0: + resource_ob['default_mask'] = { "data": ["no-access", "view"] } + else: + resource_ob['default_mask'] = { "data": ["no-access"] } + resource_ob['group_masks'] = {} + + add_resource(resource_ob) + +def insert_publish_resources(default_owner_id): + current_resources = Redis.hgetall("resources") + Cursor.execute(""" SELECT + PublishXRef.Id, PublishFreeze.Id, InbredSet.InbredSetCode + FROM + PublishXRef, PublishFreeze, InbredSet, Publication + WHERE + PublishFreeze.InbredSetId = PublishXRef.InbredSetId AND + InbredSet.Id = PublishXRef.InbredSetId AND + Publication.Id = PublishXRef.PublicationId""") + + resource_results = Cursor.fetchall() + for resource in resource_results: + if resource[2]: + resource_ob = {} + if resource[2]: + resource_ob['name'] = resource[2] + "_" + str(resource[0]) + else: + resource_ob['name'] = str(resource[0]) + resource_ob['owner_id'] = default_owner_id + resource_ob['data'] = { "dataset" : str(resource[1]) , + "trait" : str(resource[0])} + resource_ob['type'] = "dataset-publish" + resource_ob['default_mask'] = { "data": "view" } + + resource_ob['group_masks'] = {} + + add_resource(resource_ob) + else: + continue + +def insert_geno_resources(default_owner_id): + current_resources = Redis.hgetall("resources") + Cursor.execute(""" SELECT + GenoFreeze.Id, GenoFreeze.ShortName, GenoFreeze.confidentiality + FROM + GenoFreeze""") + + resource_results = Cursor.fetchall() + for i, resource in enumerate(resource_results): + if i % 20 == 0: + print(i) + resource_ob = {} + resource_ob['name'] = resource[1] + resource_ob['owner_id'] = default_owner_id + resource_ob['data'] = { "dataset" : str(resource[0]) } + resource_ob['type'] = "dataset-geno" + if resource[2] < 1: + resource_ob['default_mask'] = { "data": "view" } + else: + resource_ob['default_mask'] = { "data": "no-access" } + resource_ob['group_masks'] = {} + + add_resource(resource_ob) + +def insert_resources(default_owner_id): + current_resources = get_resources() + print("START") + insert_publish_resources(default_owner_id) + print("AFTER PUBLISH") + insert_geno_resources(default_owner_id) + print("AFTER GENO") + insert_probeset_resources(default_owner_id) + print("AFTER PROBESET") + +def main(): + """Generates and outputs (as json file) the data for the main dropdown menus on the home page""" + + Redis.delete("resources") + + owner_id = get_user_id("email_address", "zachary.a.sloan@gmail.com") + insert_resources(owner_id) + +if __name__ == '__main__': + Conn = MySQLdb.Connect(**parse_db_uri()) + Cursor = Conn.cursor() + main() \ No newline at end of file diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py new file mode 100644 index 00000000..537881a5 --- /dev/null +++ b/wqflask/utility/authentication_tools.py @@ -0,0 +1,46 @@ +from __future__ import absolute_import, print_function, division + +import json +import requests + +from base import data_set + +from utility import hmac +from utility.redis_tools import get_redis_conn, get_resource_info, get_resource_id + +from flask import Flask, g, redirect, url_for + +import logging +logger = logging.getLogger(__name__ ) + +def check_resource_availability(dataset, trait_id=None): + resource_id = get_resource_id(dataset, trait_id) + + if resource_id: + the_url = "http://localhost:8080/available?resource={}&user={}".format(resource_id, g.user_session.user_id) + try: + response = json.loads(requests.get(the_url).content)['data'] + except: + resource_info = get_resource_info(resource_id) + response = resource_info['default_mask']['data'] + + if 'view' in response: + return True + else: + return redirect(url_for("no_access_page")) + + return True + +def check_owner(dataset=None, trait_id=None, resource_id=None): + if resource_id: + resource_info = get_resource_info(resource_id) + if g.user_session.user_id == resource_info['owner_id']: + return resource_id + else: + resource_id = get_resource_id(dataset, trait_id) + if resource_id: + resource_info = get_resource_info(resource_id) + if g.user_session.user_id == resource_info['owner_id']: + return resource_id + + return False \ No newline at end of file diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py index 0ad96879..bc30a0af 100644 --- a/wqflask/utility/redis_tools.py +++ b/wqflask/utility/redis_tools.py @@ -95,14 +95,17 @@ def get_user_groups(user_id): user_group_ids = [] #ZS: Group IDs where user is a regular user groups_list = Redis.hgetall("groups") for key in groups_list: - group_ob = json.loads(groups_list[key]) - group_admins = set(group_ob['admins']) - group_members = set(group_ob['members']) - if user_id in group_admins: - admin_group_ids.append(group_ob['id']) - elif user_id in group_members: - user_group_ids.append(group_ob['id']) - else: + try: + group_ob = json.loads(groups_list[key]) + group_admins = set(group_ob['admins']) + group_members = set(group_ob['members']) + if user_id in group_admins: + admin_group_ids.append(group_ob['id']) + elif user_id in group_members: + user_group_ids.append(group_ob['id']) + else: + continue + except: continue admin_groups = [] @@ -122,6 +125,24 @@ def get_group_info(group_id): return group_info +def get_group_by_unique_column(column_name, column_value): + """ Get group by column; not sure if there's a faster way to do this """ + + matched_groups = [] + + all_group_list = Redis.hgetall("groups") + for key in all_group_list: + group_info = json.loads(all_group_list[key]) + if column_name == "admins" or column_name == "members": #ZS: Since these fields are lists, search in the list + if column_value in group_info[column_name]: + matched_groups.append(group_info) + else: + if group_info[column_name] == column_value: + matched_groups.append(group_info) + + return matched_groups + + def create_group(admin_user_ids, member_user_ids = [], group_name = "Default Group Name"): group_id = str(uuid.uuid4()) new_group = { diff --git a/wqflask/wqflask/group_manager.py b/wqflask/wqflask/group_manager.py new file mode 100644 index 00000000..f41ae56d --- /dev/null +++ b/wqflask/wqflask/group_manager.py @@ -0,0 +1,77 @@ + +from __future__ import print_function, division, absolute_import + +from flask import (Flask, g, render_template, url_for, request, make_response, + redirect, flash) + +from wqflask import app +from wqflask.user_login import send_verification_email + +from utility.redis_tools import get_user_groups, get_group_info, create_group, delete_group, add_users_to_group, remove_users_from_group, \ + change_group_name, save_verification_code, check_verification_code, get_user_by_unique_column + +from utility.logger import getLogger +logger = getLogger(__name__) + +@app.route("/groups/manage", methods=('GET', 'POST')) +def manage_groups(): + params = request.form if request.form else request.args + if "add_new_group" in params: + return redirect(url_for('add_group')) + else: + admin_groups, user_groups = get_user_groups(g.user_session.user_id) + return render_template("admin/group_manager.html", admin_groups=admin_groups, user_groups=user_groups) + +@app.route("/groups/remove", methods=('POST',)) +def remove_groups(): + group_ids_to_remove = request.form['selected_group_ids'] + for group_id in group_ids_to_remove.split(":"): + delete_group(g.user_session.user_id, group_id) + + return redirect(url_for('manage_groups')) + +@app.route("/groups/create", methods=('GET', 'POST')) +def add_group(): + params = request.form if request.form else request.args + if "group_name" in params: + member_user_ids = set() + admin_user_ids = set() + admin_user_ids.add(g.user_session.user_id) #ZS: Always add the user creating the group as an admin + if "admin_emails" in params: + admin_emails = params['admin_emails_to_add'].split(",") + for email in admin_emails: + user_details = get_user_by_unique_column("email_address", email) + if user_details: + admin_user_ids.add(user_details['user_id']) + #send_group_invites(params['group_id'], user_email_list = admin_emails, user_type="admins") + if "user_emails" in params: + member_emails = params['member_emails_to_add'].split(",") + for email in member_emails: + user_details = get_user_by_unique_column("email_address", email) + if user_details: + member_user_ids.add(user_details['user_id']) + #send_group_invites(params['group_id'], user_email_list = user_emails, user_type="members") + + create_group(list(admin_user_ids), list(member_user_ids), params['group_name']) + return redirect(url_for('manage_groups')) + else: + return render_template("admin/create_group.html") + +#ZS: Will integrate this later, for now just letting users be added directly +def send_group_invites(group_id, user_email_list = [], user_type="members"): + for user_email in user_email_list: + user_details = get_user_by_unique_column("email_address", user_email) + if user_details: + group_info = get_group_info(group_id) + #ZS: Probably not necessary since the group should normally always exist if group_id is being passed here, + # but it's technically possible to hit it if Redis is cleared out before submitting the new users or something + if group_info: + #ZS: Don't add user if they're already an admin or if they're being added a regular user and are already a regular user, + # but do add them if they're a regular user and are added as an admin + if (user_details['user_id'] in group_info['admins']) or \ + ((user_type == "members") and (user_details['user_id'] in group_info['members'])): + continue + else: + send_verification_email(user_details, template_name = "email/group_verification.txt", key_prefix = "verification_code", subject = "You've been invited to join a GeneNetwork user group") + +#@app.route() \ No newline at end of file diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py new file mode 100644 index 00000000..7d88b8ed --- /dev/null +++ b/wqflask/wqflask/resource_manager.py @@ -0,0 +1,72 @@ +from __future__ import print_function, division, absolute_import + +from flask import (Flask, g, render_template, url_for, request, make_response, + redirect, flash) + +from wqflask import app + +from utility.authentication_tools import check_owner +from utility.redis_tools import get_resource_info, get_group_info, get_group_by_unique_column, get_user_id + +from utility.logger import getLogger +logger = getLogger(__name__) + +@app.route("/resources/manage", methods=('GET', 'POST')) +def view_resource(): + params = request.form if request.form else request.args + if 'resource_id' in request.args: + resource_id = request.args['resource_id'] + if check_owner(resource_id=resource_id): + resource_info = get_resource_info(resource_id) + group_masks = resource_info['group_masks'] + group_masks_with_names = get_group_names(group_masks) + default_mask = resource_info['default_mask']['data'] + return render_template("admin/manage_resource.html", resource_id = resource_id, resource_info=resource_info, default_mask=default_mask, group_masks=group_masks_with_names) + else: + return redirect(url_for("no_access_page")) + +@app.route("/resources/add_group", methods=('POST',)) +def add_group_to_resource(): + resource_id = request.form['resource_id'] + if check_owner(resource_id=resource_id): + if all(key in request.form for key in ('group_id', 'group_name', 'user_name', 'user_email')): + group_list = [] + if request.form['group_id'] != "": + the_group = get_group_info(request.form['group_id']) + if the_group: + group_list.append(the_group) + if request.form['group_name'] != "": + matched_groups = get_group_by_unique_column("name", request.form['group_name']) + for group in matched_groups: + group_list.append(group) + if request.form['user_name'] != "": + user_id = get_user_id("user_name", request.form['user_name']) + if user_id: + matched_groups = get_group_by_unique_column("admins", user_id) + matched_groups += get_group_by_unique_column("members", user_id) + for group in matched_groups: + group_list.append(group) + if request.form['user_email'] != "": + user_id = get_user_id("email_address", request.form['user_email']) + if user_id: + matched_groups = get_group_by_unique_column("admins", user_id) + matched_groups += get_group_by_unique_column("members", user_id) + for group in matched_groups: + group_list.append(group) + return render_template("admin/select_group_to_add.html", group_list=group_list, resource_id = resource_id) + elif 'selected_group' in request.form: + group_id = request.form['selected_group'] + return render_template("admin/set_group_privileges.html", resource_id = resource_id, group_id = group_id) + else: + return render_template("admin/search_for_groups.html", resource_id = resource_id) + else: + return redirect(url_for("no_access_page")) + +def get_group_names(group_masks): + group_masks_with_names = {} + for group_id, group_mask in group_masks.iteritems(): + this_mask = group_mask + group_name = get_group_info(group_id)['name'] + this_mask['name'] = group_name + + return group_masks_with_names \ No newline at end of file diff --git a/wqflask/wqflask/static/new/javascript/group_manager.js b/wqflask/wqflask/static/new/javascript/group_manager.js new file mode 100644 index 00000000..5e82d104 --- /dev/null +++ b/wqflask/wqflask/static/new/javascript/group_manager.js @@ -0,0 +1,38 @@ +$('#add_to_admins').click(function() { + add_emails('admin') +}) + +$('#add_to_members').click(function() { + add_emails('member') +}) + +$('#clear_admins').click(function(){ + clear_emails('admin') +}) + +$('#clear_members').click(function(){ + clear_emails('member') +}) + + +function add_emails(user_type){ + var email_address = $('input[name=user_email]').val(); + var email_list_string = $('input[name=' + user_type + '_emails_to_add]').val() + console.log(email_list_string) + if (email_list_string == ""){ + var email_set = new Set(); + } else { + var email_set = new Set(email_list_string.split(",")) + } + email_set.add(email_address) + + $('input[name=' + user_type + '_emails_to_add]').val(Array.from(email_set).join(',')) + + var emails_display_string = Array.from(email_set).join('\n') + $('.added_' + user_type + 's').val(emails_display_string) +} + +function clear_emails(user_type){ + $('input[name=' + user_type + '_emails_to_add]').val("") + $('.added_' + user_type + 's').val("") +} \ No newline at end of file diff --git a/wqflask/wqflask/templates/admin/create_group.html b/wqflask/wqflask/templates/admin/create_group.html new file mode 100644 index 00000000..55c3fa0b --- /dev/null +++ b/wqflask/wqflask/templates/admin/create_group.html @@ -0,0 +1,89 @@ +{% extends "base.html" %} +{% block title %}Group Manager{% endblock %} +{% block content %} + +
+ +
+ + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ +
+ + + + + +{% endblock %} + +{% block js %} + + + + + +{% endblock %} diff --git a/wqflask/wqflask/templates/admin/group_manager.html b/wqflask/wqflask/templates/admin/group_manager.html index b7df1aad..23d8205a 100644 --- a/wqflask/wqflask/templates/admin/group_manager.html +++ b/wqflask/wqflask/templates/admin/group_manager.html @@ -1,15 +1,23 @@ {% extends "base.html" %} {% block title %}Group Manager{% endblock %} +{% block css %} + + + +{% endblock %} {% block content %}
-
+
{% if admin_groups|length == 0 and user_groups|length == 0 %}

You currently aren't a member or admin of any groups.


@@ -20,7 +28,7 @@ {% if admin_groups|length == 0 %}

You currently aren't the administrator of any groups.

{% else %} -

Loading...
+
@@ -29,17 +37,19 @@ + {% for group in admin_groups %} - + - + + {% endfor %} @@ -47,13 +57,13 @@ {% endif %}
-
+

User Groups


{% if user_groups|length == 0 %}

You currently aren't a member of any groups.

{% else %} -
# Members Created Last ChangedGroup ID
{{ loop.index }}{{ loop.index }} {{ group.name }}{{ group.admins|length + group.users|length }}{{ group.admins|length + group.users|length }} {{ group.created_timestamp }} {{ group.changed_timestamp }}{{ group.id }}
+
@@ -88,48 +98,26 @@ {% endblock %} {% block js %} - - - + + + + +{% endblock %} diff --git a/wqflask/wqflask/templates/admin/search_for_groups.html b/wqflask/wqflask/templates/admin/search_for_groups.html new file mode 100644 index 00000000..89eb11dd --- /dev/null +++ b/wqflask/wqflask/templates/admin/search_for_groups.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} +{% block title %}Resource Manager{% endblock %} +{% block content %} + +
+ + + +
+
+
+
+

Search by:

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+ + + +{% endblock %} + +{% block js %} + + + + + +{% endblock %} diff --git a/wqflask/wqflask/templates/admin/select_group_to_add.html b/wqflask/wqflask/templates/admin/select_group_to_add.html new file mode 100644 index 00000000..df70fb2f --- /dev/null +++ b/wqflask/wqflask/templates/admin/select_group_to_add.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} +{% block title %}Matched Groups{% endblock %} +{% block css %} + + + +{% endblock %} +{% block content %} + +
+

The following groups were found:

+
+
+ +
+ {% if group_list|length > 0 %} + +
+ + + + + + + + + + {% for group in group_list %} + + + + + + + {% endfor %} + +
NameCreatedLast Changed
{% if 'name' in group %}{{ group.name }}{% else %}N/A{% endif %}{% if 'created_timestamp' in group %}{{ group.created_timestamp }}{% else %}N/A{% endif %}{% if 'changed_timestamp' in group %}{{ group.changed_timestamp }}{% else %}N/A{% endif %}
+ {% else %} +

No matching groups were found.

+ {% endif %} +
+ +
+ + + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/wqflask/wqflask/templates/new_security/not_authenticated.html b/wqflask/wqflask/templates/new_security/not_authenticated.html new file mode 100644 index 00000000..7d0d3060 --- /dev/null +++ b/wqflask/wqflask/templates/new_security/not_authenticated.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% block title %}Authentication Needed{% endblock %} +{% block content %} +
+ +

Please contact the data's owner or GN administrators if you believe you should have access to this data.

+
+ +{% endblock %} \ No newline at end of file diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html index 878b6ced..5c315878 100644 --- a/wqflask/wqflask/templates/show_trait_details.html +++ b/wqflask/wqflask/templates/show_trait_details.html @@ -248,6 +248,11 @@ + {% if resource_id %} + + + + {% endif %} diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 24a4dcee..ee827ba3 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -30,6 +30,7 @@ import sqlalchemy from wqflask import app from flask import g, Response, request, make_response, render_template, send_from_directory, jsonify, redirect, url_for from wqflask import group_manager +from wqflask import resource_manager from wqflask import search_results from wqflask import export_traits from wqflask import gsearch @@ -89,13 +90,13 @@ def connect_db(): @app.before_request def check_access_permissions(): logger.debug("@app.before_request check_access_permissions") + available = True if "temp_trait" in request.args: if request.args['temp_trait'] == "True": pass else: if 'dataset' in request.args: dataset = create_dataset(request.args['dataset']) - logger.debug("USER:", Redis.hget("users")) if 'trait_id' in request.args: available = check_resource_availability(dataset, request.args['trait_id']) else: -- cgit v1.2.3 From ccae8abe246af7b8f27fbcb9a37a3d242e594126 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 17 Jun 2020 13:10:11 -0500 Subject: Fixed issue where removing traits and then running a tool from collection page still included the removed traits --- wqflask/wqflask/collect.py | 5 +---- wqflask/wqflask/templates/collections/view.html | 7 ++++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/wqflask/wqflask/collect.py b/wqflask/wqflask/collect.py index 4fb8e69b..e8459821 100644 --- a/wqflask/wqflask/collect.py +++ b/wqflask/wqflask/collect.py @@ -159,13 +159,10 @@ def remove_traits(): uc_id = params['uc_id'] traits_to_remove = params.getlist('traits[]') traits_to_remove = process_traits(traits_to_remove) - logger.debug("\n\n after processing, traits_to_remove:", traits_to_remove) members_now = g.user_session.remove_traits_from_collection(uc_id, traits_to_remove) - # We need to return something so we'll return this...maybe in the future - # we can use it to check the results - return str(len(members_now)) + return redirect(url_for("view_collection", uc_id=uc_id)) @app.route("/collections/delete", methods=('POST',)) diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index 60a1a081..ec0e0220 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -80,7 +80,7 @@ - +
@@ -230,6 +230,11 @@ return submit_special(url) }); + $("#remove").on("click", function() { + url = $(this).data("url") + return submit_special(url) + }); + $("#change_collection_name").on("click", function() { if ($('input[name=new_collection_name]').css('display') == 'none') { $('input[name=new_collection_name]').css('display', 'inline'); -- cgit v1.2.3 From 7438f3c45122f02c25155b42ede703ad2845649c Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 17 Jun 2020 13:40:10 -0500 Subject: Fixed issue caused by mapping scale sometimes being wrong + fixed Verify link --- wqflask/wqflask/marker_regression/gemma_mapping.py | 2 -- wqflask/wqflask/marker_regression/run_mapping.py | 1 + wqflask/wqflask/show_trait/show_trait.py | 8 ++++---- .../wqflask/static/new/javascript/show_trait_mapping_tools.js | 4 ++-- wqflask/wqflask/templates/show_trait_mapping_tools.html | 11 +++++++++++ 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/wqflask/wqflask/marker_regression/gemma_mapping.py b/wqflask/wqflask/marker_regression/gemma_mapping.py index 88d27517..b858b573 100644 --- a/wqflask/wqflask/marker_regression/gemma_mapping.py +++ b/wqflask/wqflask/marker_regression/gemma_mapping.py @@ -136,9 +136,7 @@ def gen_covariates_file(this_dataset, covariates, samples): #trait_samples = this_dataset.group.all_samples_ordered() this_dataset.group.get_samplelist() trait_samples = this_dataset.group.samplelist - logger.debug("SAMPLES:", trait_samples) trait_sample_data = trait_ob.data - logger.debug("SAMPLE DATA:", trait_sample_data) for index, sample in enumerate(trait_samples): if sample in samples: if sample in trait_sample_data: diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index 0711b852..c859fdaa 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -162,6 +162,7 @@ class RunMapping(object): self.perm_output = [] self.bootstrap_results = [] self.covariates = start_vars['covariates'] if "covariates" in start_vars else "" + self.categorical_vars = [] #ZS: This is passed to GN1 code for single chr mapping self.selected_chr = -1 diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index c77e247f..ed4ff0ad 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -22,7 +22,7 @@ from base.trait import create_trait from base import data_set from db import webqtlDatabaseFunction from utility import webqtlUtil, Plot, Bunch, helper_functions -from utility.authentication_tools import check_owner +from utility.authentication_tools import check_owner_or_admin from utility.tools import locate_ignore_error from utility.redis_tools import get_redis_conn, get_resource_id Redis = get_redis_conn() @@ -72,11 +72,11 @@ class ShowTrait(object): cellid=None) self.trait_vals = Redis.get(self.trait_id).split() - self.resource_id = check_owner(self.dataset, self.trait_id) + self.admin_status = check_owner_or_admin(self.dataset, self.trait_id) #ZS: Get verify/rna-seq link URLs try: - blatsequence = self.this_trait.sequence + blatsequence = self.this_trait.blatseq if not blatsequence: #XZ, 06/03/2009: ProbeSet name is not unique among platforms. We should use ProbeSet Id instead. query1 = """SELECT Probe.Sequence, Probe.Name @@ -256,7 +256,7 @@ class ShowTrait(object): hddn['export_data'] = "" hddn['export_format'] = "excel" if len(self.scales_in_geno) < 2: - hddn['mapping_scale'] = self.scales_in_geno[self.scales_in_geno.keys()[0]][0] + hddn['mapping_scale'] = self.scales_in_geno[self.scales_in_geno.keys()[0]][0][0] # We'll need access to this_trait and hddn in the Jinja2 Template, so we put it inside self self.hddn = hddn diff --git a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js index 01da3f74..db17af40 100644 --- a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js +++ b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js @@ -3,9 +3,9 @@ var block_outliers, composite_mapping_fields, do_ajax_post, get_progress, mapping_method_fields, open_mapping_results, outlier_text, showalert, submit_special, toggle_enable_disable, update_time_remaining; submit_special = function(url) { - console.log("In submit_special"); $("#trait_data_form").attr("action", url); - return $("#trait_data_form").submit(); + $("#trait_data_form").submit(); + return false; }; update_time_remaining = function(percent_complete) { diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html index a2416ced..e851a81e 100755 --- a/wqflask/wqflask/templates/show_trait_mapping_tools.html +++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html @@ -140,6 +140,17 @@
+ {% else %} +
+ +
+ +
+
{% endif %}
-- cgit v1.2.3 From ea47eb228b1224ea83e3f50a056bf715b3bf5ec6 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 17 Jun 2020 14:49:40 -0500 Subject: Adding all the authentication stuff --- wqflask/base/data_set.py | 36 +-- wqflask/base/trait.py | 5 +- wqflask/maintenance/set_resource_defaults.py | 307 ++++++++++----------- wqflask/utility/authentication_tools.py | 132 ++++++--- wqflask/utility/redis_tools.py | 67 ++++- wqflask/wqflask/docs.py | 4 +- wqflask/wqflask/group_manager.py | 220 ++++++++++----- wqflask/wqflask/resource_manager.py | 204 +++++++++----- .../wqflask/static/new/javascript/group_manager.js | 74 ++--- .../static/new/javascript/search_results.js | 1 - .../templates/admin/change_resource_owner.html | 116 ++++++++ wqflask/wqflask/templates/admin/create_group.html | 178 ++++++------ wqflask/wqflask/templates/admin/group_manager.html | 18 +- .../wqflask/templates/admin/manage_resource.html | 200 ++++++++------ .../wqflask/templates/admin/search_for_groups.html | 198 ++++++++----- .../templates/admin/select_group_to_add.html | 54 ---- .../templates/admin/set_group_privileges.html | 102 +++++++ wqflask/wqflask/templates/admin/view_group.html | 238 ++++++++++++++++ wqflask/wqflask/templates/base.html | 5 + .../wqflask/templates/set_group_privileges.html | 77 ++++++ wqflask/wqflask/templates/show_trait_details.html | 4 +- wqflask/wqflask/views.py | 2 +- 22 files changed, 1516 insertions(+), 726 deletions(-) create mode 100644 wqflask/wqflask/templates/admin/change_resource_owner.html delete mode 100644 wqflask/wqflask/templates/admin/select_group_to_add.html create mode 100644 wqflask/wqflask/templates/admin/set_group_privileges.html create mode 100644 wqflask/wqflask/templates/admin/view_group.html create mode 100644 wqflask/wqflask/templates/set_group_privileges.html diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 1457ba8d..92dc8615 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -486,25 +486,18 @@ class DatasetGroup(object): def datasets(group_name, this_group = None): key = "group_dataset_menu:v2:" + group_name - logger.debug("key is2:", key) dataset_menu = [] - logger.debug("[tape4] webqtlConfig.PUBLICTHRESH:", webqtlConfig.PUBLICTHRESH) - logger.debug("[tape4] type webqtlConfig.PUBLICTHRESH:", type(webqtlConfig.PUBLICTHRESH)) the_results = fetchall(''' (SELECT '#PublishFreeze',PublishFreeze.FullName,PublishFreeze.Name FROM PublishFreeze,InbredSet WHERE PublishFreeze.InbredSetId = InbredSet.Id and InbredSet.Name = '%s' - and PublishFreeze.public > %s - and PublishFreeze.confidentiality < 1 ORDER BY PublishFreeze.Id ASC) UNION (SELECT '#GenoFreeze',GenoFreeze.FullName,GenoFreeze.Name FROM GenoFreeze, InbredSet WHERE GenoFreeze.InbredSetId = InbredSet.Id - and InbredSet.Name = '%s' - and GenoFreeze.public > %s - and GenoFreeze.confidentiality < 1) + and InbredSet.Name = '%s') UNION (SELECT Tissue.Name, ProbeSetFreeze.FullName,ProbeSetFreeze.Name FROM ProbeSetFreeze, ProbeFreeze, InbredSet, Tissue @@ -512,12 +505,10 @@ def datasets(group_name, this_group = None): and ProbeFreeze.TissueId = Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id and InbredSet.Name like %s - and ProbeSetFreeze.public > %s - and ProbeSetFreeze.confidentiality < 1 ORDER BY Tissue.Name, ProbeSetFreeze.OrderList DESC) - ''' % (group_name, webqtlConfig.PUBLICTHRESH, - group_name, webqtlConfig.PUBLICTHRESH, - "'" + group_name + "'", webqtlConfig.PUBLICTHRESH)) + ''' % (group_name, + group_name, + "'" + group_name + "'")) sorted_results = sorted(the_results, key=lambda kv: kv[0]) @@ -637,29 +628,25 @@ class DataSet(object): """ - try: if self.type == "ProbeSet": query_args = tuple(escape(x) for x in ( - str(webqtlConfig.PUBLICTHRESH), self.name, self.name, self.name)) self.id, self.name, self.fullname, self.shortname, self.data_scale, self.tissue = fetch1(""" -SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, ProbeSetFreeze.FullName, ProbeSetFreeze.ShortName, ProbeSetFreeze.DataScale, Tissue.Name -FROM ProbeSetFreeze, ProbeFreeze, Tissue -WHERE ProbeSetFreeze.public > %s -AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id -AND ProbeFreeze.TissueId = Tissue.Id -AND (ProbeSetFreeze.Name = '%s' OR ProbeSetFreeze.FullName = '%s' OR ProbeSetFreeze.ShortName = '%s') + SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, ProbeSetFreeze.FullName, ProbeSetFreeze.ShortName, ProbeSetFreeze.DataScale, Tissue.Name + FROM ProbeSetFreeze, ProbeFreeze, Tissue + WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id + AND ProbeFreeze.TissueId = Tissue.Id + AND (ProbeSetFreeze.Name = '%s' OR ProbeSetFreeze.FullName = '%s' OR ProbeSetFreeze.ShortName = '%s') """ % (query_args),"/dataset/"+self.name+".json", lambda r: (r["id"],r["name"],r["full_name"],r["short_name"],r["data_scale"],r["tissue"]) ) else: query_args = tuple(escape(x) for x in ( (self.type + "Freeze"), - str(webqtlConfig.PUBLICTHRESH), self.name, self.name, self.name)) @@ -668,9 +655,8 @@ AND (ProbeSetFreeze.Name = '%s' OR ProbeSetFreeze.FullName = '%s' OR ProbeSetFre self.id, self.name, self.fullname, self.shortname = fetchone(""" SELECT Id, Name, FullName, ShortName FROM %s - WHERE public > %s AND - (Name = '%s' OR FullName = '%s' OR ShortName = '%s') - """ % (query_args)) + WHERE (Name = '%s' OR FullName = '%s' OR ShortName = '%s') + """ % (query_args)) except TypeError: logger.debug("Dataset {} is not yet available in GeneNetwork.".format(self.name)) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index 2a945588..7700ecd5 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -46,9 +46,10 @@ def create_trait(**kw): else: permitted = check_resource_availability(dataset) - if permitted: + if permitted != "no-access": the_trait = GeneralTrait(**kw) if the_trait.dataset.type != "Temp": + the_trait = retrieve_trait_info(the_trait, the_trait.dataset, get_qtl_info=kw.get('get_qtl_info')) return the_trait else: @@ -383,7 +384,6 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): if dataset.type == 'Publish': the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id) else: - the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view&trait={}".format(resource_id, g.user_session.user_id, trait.name) try: @@ -424,7 +424,6 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): logger.sql(query) trait_info = g.db.execute(query).fetchone() - #XZ, 05/08/2009: Xiaodong add this block to use ProbeSet.Id to find the probeset instead of just using ProbeSet.Name #XZ, 05/08/2009: to avoid the problem of same probeset name from different platforms. elif dataset.type == 'ProbeSet': diff --git a/wqflask/maintenance/set_resource_defaults.py b/wqflask/maintenance/set_resource_defaults.py index ba102d9c..344e6a23 100644 --- a/wqflask/maintenance/set_resource_defaults.py +++ b/wqflask/maintenance/set_resource_defaults.py @@ -1,155 +1,154 @@ -""" - -Script that sets default resource access masks for use with the DB proxy - -Defaults will be: -Owner - omni_gn -Mask - Public/non-confidential: { data: "view", - metadata: "view", - admin: "not-admin" } - Private/confidentia: { data: "no-access", - metadata: "no-access", - admin: "not-admin" } - -To run: -./bin/genenetwork2 ~/my_settings.py -c ./wqflask/maintenance/gen_select_dataset.py - -""" - -from __future__ import print_function, division - -import sys -import json - -# NEW: Note we prepend the current path - otherwise a guix instance of GN2 may be used instead -sys.path.insert(0,'./') - -# NEW: import app to avoid a circular dependency on utility.tools -from wqflask import app - -from utility.tools import SQL_URI -from utility.redis_tools import get_redis_conn, get_user_id, add_resource, get_resources -Redis = get_redis_conn() - -import MySQLdb - -import urlparse - -from utility.logger import getLogger -logger = getLogger(__name__) - -def parse_db_uri(): - """Converts a database URI to the db name, host name, user name, and password""" - - parsed_uri = urlparse.urlparse(SQL_URI) - - db_conn_info = dict( - db = parsed_uri.path[1:], - host = parsed_uri.hostname, - user = parsed_uri.username, - passwd = parsed_uri.password) - - print(db_conn_info) - return db_conn_info - -def insert_probeset_resources(default_owner_id): - current_resources = Redis.hgetall("resources") - Cursor.execute(""" SELECT - ProbeSetFreeze.Id, ProbeSetFreeze.Name, ProbeSetFreeze.confidentiality, ProbeSetFreeze.public - FROM - ProbeSetFreeze""") - - resource_results = Cursor.fetchall() - for i, resource in enumerate(resource_results): - if i % 20 == 0: - print(i) - resource_ob = {} - resource_ob['name'] = resource[1] - resource_ob['owner_id'] = default_owner_id - resource_ob['data'] = { "dataset" : str(resource[0])} - resource_ob['type'] = "dataset-probeset" - if resource[2] < 1 and resource[3] > 0: - resource_ob['default_mask'] = { "data": ["no-access", "view"] } - else: - resource_ob['default_mask'] = { "data": ["no-access"] } - resource_ob['group_masks'] = {} - - add_resource(resource_ob) - -def insert_publish_resources(default_owner_id): - current_resources = Redis.hgetall("resources") - Cursor.execute(""" SELECT - PublishXRef.Id, PublishFreeze.Id, InbredSet.InbredSetCode - FROM - PublishXRef, PublishFreeze, InbredSet, Publication - WHERE - PublishFreeze.InbredSetId = PublishXRef.InbredSetId AND - InbredSet.Id = PublishXRef.InbredSetId AND - Publication.Id = PublishXRef.PublicationId""") - - resource_results = Cursor.fetchall() - for resource in resource_results: - if resource[2]: - resource_ob = {} - if resource[2]: - resource_ob['name'] = resource[2] + "_" + str(resource[0]) - else: - resource_ob['name'] = str(resource[0]) - resource_ob['owner_id'] = default_owner_id - resource_ob['data'] = { "dataset" : str(resource[1]) , - "trait" : str(resource[0])} - resource_ob['type'] = "dataset-publish" - resource_ob['default_mask'] = { "data": "view" } - - resource_ob['group_masks'] = {} - - add_resource(resource_ob) - else: - continue - -def insert_geno_resources(default_owner_id): - current_resources = Redis.hgetall("resources") - Cursor.execute(""" SELECT - GenoFreeze.Id, GenoFreeze.ShortName, GenoFreeze.confidentiality - FROM - GenoFreeze""") - - resource_results = Cursor.fetchall() - for i, resource in enumerate(resource_results): - if i % 20 == 0: - print(i) - resource_ob = {} - resource_ob['name'] = resource[1] - resource_ob['owner_id'] = default_owner_id - resource_ob['data'] = { "dataset" : str(resource[0]) } - resource_ob['type'] = "dataset-geno" - if resource[2] < 1: - resource_ob['default_mask'] = { "data": "view" } - else: - resource_ob['default_mask'] = { "data": "no-access" } - resource_ob['group_masks'] = {} - - add_resource(resource_ob) - -def insert_resources(default_owner_id): - current_resources = get_resources() - print("START") - insert_publish_resources(default_owner_id) - print("AFTER PUBLISH") - insert_geno_resources(default_owner_id) - print("AFTER GENO") - insert_probeset_resources(default_owner_id) - print("AFTER PROBESET") - -def main(): - """Generates and outputs (as json file) the data for the main dropdown menus on the home page""" - - Redis.delete("resources") - - owner_id = get_user_id("email_address", "zachary.a.sloan@gmail.com") - insert_resources(owner_id) - -if __name__ == '__main__': - Conn = MySQLdb.Connect(**parse_db_uri()) - Cursor = Conn.cursor() +""" + +Script that sets default resource access masks for use with the DB proxy + +Defaults will be: +Owner - omni_gn +Mask - Public/non-confidential: { data: "view", + metadata: "view", + admin: "not-admin" } + Private/confidentia: { data: "no-access", + metadata: "no-access", + admin: "not-admin" } + +To run: +./bin/genenetwork2 ~/my_settings.py -c ./wqflask/maintenance/gen_select_dataset.py + +""" + +from __future__ import print_function, division + +import sys +import json + +# NEW: Note we prepend the current path - otherwise a guix instance of GN2 may be used instead +sys.path.insert(0,'./') + +# NEW: import app to avoid a circular dependency on utility.tools +from wqflask import app + +from utility.tools import SQL_URI +from utility.redis_tools import get_redis_conn, get_user_id, add_resource, get_resources +Redis = get_redis_conn() + +import MySQLdb + +import urlparse + +from utility.logger import getLogger +logger = getLogger(__name__) + +def parse_db_uri(): + """Converts a database URI to the db name, host name, user name, and password""" + + parsed_uri = urlparse.urlparse(SQL_URI) + + db_conn_info = dict( + db = parsed_uri.path[1:], + host = parsed_uri.hostname, + user = parsed_uri.username, + passwd = parsed_uri.password) + + print(db_conn_info) + return db_conn_info + +def insert_probeset_resources(default_owner_id): + current_resources = Redis.hgetall("resources") + Cursor.execute(""" SELECT + ProbeSetFreeze.Id, ProbeSetFreeze.Name, ProbeSetFreeze.confidentiality, ProbeSetFreeze.public + FROM + ProbeSetFreeze""") + + resource_results = Cursor.fetchall() + for i, resource in enumerate(resource_results): + resource_ob = {} + resource_ob['name'] = resource[1] + resource_ob['owner_id'] = default_owner_id + resource_ob['data'] = { "dataset" : str(resource[0])} + resource_ob['type'] = "dataset-probeset" + if resource[2] < 1 and resource[3] > 0: + resource_ob['default_mask'] = { "data": "view" } + else: + resource_ob['default_mask'] = { "data": "no-access" } + resource_ob['group_masks'] = {} + + add_resource(resource_ob) + +def insert_publish_resources(default_owner_id): + current_resources = Redis.hgetall("resources") + Cursor.execute(""" SELECT + PublishXRef.Id, PublishFreeze.Id, InbredSet.InbredSetCode + FROM + PublishXRef, PublishFreeze, InbredSet, Publication + WHERE + PublishFreeze.InbredSetId = PublishXRef.InbredSetId AND + InbredSet.Id = PublishXRef.InbredSetId AND + Publication.Id = PublishXRef.PublicationId""") + + resource_results = Cursor.fetchall() + for resource in resource_results: + if resource[2]: + resource_ob = {} + if resource[2]: + resource_ob['name'] = resource[2] + "_" + str(resource[0]) + else: + resource_ob['name'] = str(resource[0]) + resource_ob['owner_id'] = default_owner_id + resource_ob['data'] = { "dataset" : str(resource[1]) , + "trait" : str(resource[0])} + resource_ob['type'] = "dataset-publish" + resource_ob['default_mask'] = { "data": "view" } + + resource_ob['group_masks'] = {} + + add_resource(resource_ob) + else: + continue + +def insert_geno_resources(default_owner_id): + current_resources = Redis.hgetall("resources") + Cursor.execute(""" SELECT + GenoFreeze.Id, GenoFreeze.ShortName, GenoFreeze.confidentiality + FROM + GenoFreeze""") + + resource_results = Cursor.fetchall() + for i, resource in enumerate(resource_results): + resource_ob = {} + resource_ob['name'] = resource[1] + if resource[1] == "HET3-ITPGeno": + resource_ob['owner_id'] = "73a3f093-ca13-4ae0-a179-9a446f709f6e" + else: + resource_ob['owner_id'] = default_owner_id + resource_ob['data'] = { "dataset" : str(resource[0]) } + resource_ob['type'] = "dataset-geno" + if resource[2] < 1: + resource_ob['default_mask'] = { "data": "view" } + else: + resource_ob['default_mask'] = { "data": "no-access" } + resource_ob['group_masks'] = {} + + add_resource(resource_ob) + +def insert_resources(default_owner_id): + current_resources = get_resources() + print("START") + insert_publish_resources(default_owner_id) + print("AFTER PUBLISH") + insert_geno_resources(default_owner_id) + print("AFTER GENO") + insert_probeset_resources(default_owner_id) + print("AFTER PROBESET") + +def main(): + """Generates and outputs (as json file) the data for the main dropdown menus on the home page""" + + Redis.delete("resources") + + owner_id = get_user_id("email_address", "zachary.a.sloan@gmail.com") + insert_resources(owner_id) + +if __name__ == '__main__': + Conn = MySQLdb.Connect(**parse_db_uri()) + Cursor = Conn.cursor() main() \ No newline at end of file diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index 537881a5..07ceacc0 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -1,46 +1,86 @@ -from __future__ import absolute_import, print_function, division - -import json -import requests - -from base import data_set - -from utility import hmac -from utility.redis_tools import get_redis_conn, get_resource_info, get_resource_id - -from flask import Flask, g, redirect, url_for - -import logging -logger = logging.getLogger(__name__ ) - -def check_resource_availability(dataset, trait_id=None): - resource_id = get_resource_id(dataset, trait_id) - - if resource_id: - the_url = "http://localhost:8080/available?resource={}&user={}".format(resource_id, g.user_session.user_id) - try: - response = json.loads(requests.get(the_url).content)['data'] - except: - resource_info = get_resource_info(resource_id) - response = resource_info['default_mask']['data'] - - if 'view' in response: - return True - else: - return redirect(url_for("no_access_page")) - - return True - -def check_owner(dataset=None, trait_id=None, resource_id=None): - if resource_id: - resource_info = get_resource_info(resource_id) - if g.user_session.user_id == resource_info['owner_id']: - return resource_id - else: - resource_id = get_resource_id(dataset, trait_id) - if resource_id: - resource_info = get_resource_info(resource_id) - if g.user_session.user_id == resource_info['owner_id']: - return resource_id - - return False \ No newline at end of file +from __future__ import absolute_import, print_function, division + +import json +import requests + +from base import data_set + +from utility import hmac +from utility.redis_tools import get_redis_conn, get_resource_info, get_resource_id + +from flask import Flask, g, redirect, url_for + +import logging +logger = logging.getLogger(__name__ ) + +def check_resource_availability(dataset, trait_id=None): + resource_id = get_resource_id(dataset, trait_id) + + response = None + if resource_id: + resource_info = get_resource_info(resource_id) + + the_url = "http://localhost:8080/available?resource={}&user={}".format(resource_id, g.user_session.user_id) + try: + response = json.loads(requests.get(the_url).content)['data'] + except: + response = resource_info['default_mask']['data'] + + if 'edit' in response: + return "edit" + elif 'view' in response: + return "view" + else: + return "no-access" + + return False + +def check_admin(resource_id=None): + + return "not-admin" + + # ZS: commented out until proxy can return this + # the_url = "http://localhost:8080/available?resource={}&user={}".format(resource_id, g.user_session.user_id) + # try: + # response = json.loads(requests.get(the_url).content) + # except: + # response = resource_info['default_mask']['admin'] + + # if 'edit-admins' in response: + # return "edit-admins" + # elif 'edit-access' in response: + # return "edit-access" + # else: + # return "not-admin" + +def check_owner(dataset=None, trait_id=None, resource_id=None): + if resource_id: + resource_info = get_resource_info(resource_id) + if g.user_session.user_id == resource_info['owner_id']: + return resource_id + else: + resource_id = get_resource_id(dataset, trait_id) + if resource_id: + resource_info = get_resource_info(resource_id) + if g.user_session.user_id == resource_info['owner_id']: + return resource_id + + return False + +def check_owner_or_admin(dataset=None, trait_id=None, resource_id=None): + if resource_id: + resource_info = get_resource_info(resource_id) + if g.user_session.user_id == resource_info['owner_id']: + return [resource_id, "owner"] + else: + return [resource_id, check_admin(resource_id)] + else: + resource_id = get_resource_id(dataset, trait_id) + if resource_id: + resource_info = get_resource_info(resource_id) + if g.user_session.user_id == resource_info['owner_id']: + return [resource_id, "owner"] + else: + return [resource_id, check_admin(resource_id)] + + return [resource_id, "not-admin"] \ No newline at end of file diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py index bc30a0af..c6d221ff 100644 --- a/wqflask/utility/redis_tools.py +++ b/wqflask/utility/redis_tools.py @@ -16,7 +16,7 @@ from utility.logger import getLogger logger = getLogger(__name__) def get_redis_conn(): - Redis = redis.StrictRedis(port=6380) + Redis = redis.StrictRedis(port=6379) return Redis Redis = get_redis_conn() @@ -51,6 +51,27 @@ def get_user_by_unique_column(column_name, column_value): return item_details +def get_users_like_unique_column(column_name, column_value): + """ + Like previous function, but this only checks if the input is a subset of a field and can return multiple results + """ + matched_users = [] + + if column_value != "": + user_list = Redis.hgetall("users") + if column_name != "user_id": + for key in user_list: + user_ob = json.loads(user_list[key]) + if column_name in user_ob: + if column_value in user_ob[column_name]: + matched_users.append(user_ob) + else: + matched_users.append(json.loads(user_list[column_value])) + + return matched_users + +# def search_users_by_unique_column(column_name, column_value): + def set_user_attribute(user_id, column_name, column_value): user_info = json.loads(Redis.hget("users", user_id)) user_info[column_name] = column_value @@ -142,6 +163,28 @@ def get_group_by_unique_column(column_name, column_value): return matched_groups +def get_groups_like_unique_column(column_name, column_value): + """ + Like previous function, but this only checks if the input is a subset of a field and can return multiple results + """ + matched_groups = [] + + if column_value != "": + group_list = Redis.hgetall("groups") + if column_name != "group_id": + for key in group_list: + group_info = json.loads(group_list[key]) + if column_name == "admins" or column_name == "members": #ZS: Since these fields are lists, search in the list + if column_value in group_info[column_name]: + matched_groups.append(group_info) + else: + if column_name in group_info: + if column_value in group_info[column_name]: + matched_groups.append(group_info) + else: + matched_groups.append(json.loads(group_list[column_value])) + + return matched_groups def create_group(admin_user_ids, member_user_ids = [], group_name = "Default Group Name"): group_id = str(uuid.uuid4()) @@ -192,9 +235,13 @@ def add_users_to_group(user_id, group_id, user_emails = [], admins = False): #ZS def remove_users_from_group(user_id, users_to_remove_ids, group_id, user_type = "members"): #ZS: User type is because I assume admins can remove other admins group_info = get_group_info(group_id) + if user_id in group_info["admins"]: + users_to_remove_set = set(users_to_remove_ids) + if user_type == "admins" and user_id in users_to_remove_set: #ZS: Make sure an admin can't remove themselves from a group, since I imagine we don't want groups to be able to become admin-less + users_to_remove_set.remove(user_id) group_users = set(group_info[user_type]) - group_users -= set(users_to_remove_ids) + group_users -= users_to_remove_set group_info[user_type] = list(group_users) group_info["changed_timestamp"] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') Redis.hset("groups", group_id, json.dumps(group_info)) @@ -232,7 +279,6 @@ def get_resource_info(resource_id): return json.loads(resource_info) def add_resource(resource_info): - if 'trait' in resource_info['data']: resource_id = hmac.hmac_creation('{}:{}:{}'.format(str(resource_info['type']), str(resource_info['data']['dataset']), str(resource_info['data']['trait']))) else: @@ -241,3 +287,18 @@ def add_resource(resource_info): Redis.hset("resources", resource_id, json.dumps(resource_info)) return resource_info + +def add_access_mask(resource_id, group_id, access_mask): + the_resource = get_resource_info(resource_id) + the_resource['group_masks'][group_id] = access_mask + + Redis.hset("resources", resource_id, json.dumps(the_resource)) + + return the_resource + +def change_resource_owner(resource_id, new_owner_id): + the_resource= get_resource_info(resource_id) + the_resource['owner_id'] = new_owner_id + + Redis.delete("resource") + Redis.hset("resources", resource_id, json.dumps(the_resource)) \ No newline at end of file diff --git a/wqflask/wqflask/docs.py b/wqflask/wqflask/docs.py index 0187f32e..78407e22 100644 --- a/wqflask/wqflask/docs.py +++ b/wqflask/wqflask/docs.py @@ -1,5 +1,7 @@ from __future__ import absolute_import, print_function, division +import codecs + from flask import g from utility.logger import getLogger @@ -20,7 +22,7 @@ class Docs(object): self.content = "" else: self.title = result[0] - self.content = result[1] + self.content = result[1].encode("latin1") self.editable = "false" # ZS: Removing option to edit to see if text still gets vandalized diff --git a/wqflask/wqflask/group_manager.py b/wqflask/wqflask/group_manager.py index f41ae56d..9afc016b 100644 --- a/wqflask/wqflask/group_manager.py +++ b/wqflask/wqflask/group_manager.py @@ -1,77 +1,145 @@ - -from __future__ import print_function, division, absolute_import - -from flask import (Flask, g, render_template, url_for, request, make_response, - redirect, flash) - -from wqflask import app -from wqflask.user_login import send_verification_email - -from utility.redis_tools import get_user_groups, get_group_info, create_group, delete_group, add_users_to_group, remove_users_from_group, \ - change_group_name, save_verification_code, check_verification_code, get_user_by_unique_column - -from utility.logger import getLogger -logger = getLogger(__name__) - -@app.route("/groups/manage", methods=('GET', 'POST')) -def manage_groups(): - params = request.form if request.form else request.args - if "add_new_group" in params: - return redirect(url_for('add_group')) - else: - admin_groups, user_groups = get_user_groups(g.user_session.user_id) - return render_template("admin/group_manager.html", admin_groups=admin_groups, user_groups=user_groups) - -@app.route("/groups/remove", methods=('POST',)) -def remove_groups(): - group_ids_to_remove = request.form['selected_group_ids'] - for group_id in group_ids_to_remove.split(":"): - delete_group(g.user_session.user_id, group_id) - - return redirect(url_for('manage_groups')) - -@app.route("/groups/create", methods=('GET', 'POST')) -def add_group(): - params = request.form if request.form else request.args - if "group_name" in params: - member_user_ids = set() - admin_user_ids = set() - admin_user_ids.add(g.user_session.user_id) #ZS: Always add the user creating the group as an admin - if "admin_emails" in params: - admin_emails = params['admin_emails_to_add'].split(",") - for email in admin_emails: - user_details = get_user_by_unique_column("email_address", email) - if user_details: - admin_user_ids.add(user_details['user_id']) - #send_group_invites(params['group_id'], user_email_list = admin_emails, user_type="admins") - if "user_emails" in params: - member_emails = params['member_emails_to_add'].split(",") - for email in member_emails: - user_details = get_user_by_unique_column("email_address", email) - if user_details: - member_user_ids.add(user_details['user_id']) - #send_group_invites(params['group_id'], user_email_list = user_emails, user_type="members") - - create_group(list(admin_user_ids), list(member_user_ids), params['group_name']) - return redirect(url_for('manage_groups')) - else: - return render_template("admin/create_group.html") - -#ZS: Will integrate this later, for now just letting users be added directly -def send_group_invites(group_id, user_email_list = [], user_type="members"): - for user_email in user_email_list: - user_details = get_user_by_unique_column("email_address", user_email) - if user_details: - group_info = get_group_info(group_id) - #ZS: Probably not necessary since the group should normally always exist if group_id is being passed here, - # but it's technically possible to hit it if Redis is cleared out before submitting the new users or something - if group_info: - #ZS: Don't add user if they're already an admin or if they're being added a regular user and are already a regular user, - # but do add them if they're a regular user and are added as an admin - if (user_details['user_id'] in group_info['admins']) or \ - ((user_type == "members") and (user_details['user_id'] in group_info['members'])): - continue - else: - send_verification_email(user_details, template_name = "email/group_verification.txt", key_prefix = "verification_code", subject = "You've been invited to join a GeneNetwork user group") - + +from __future__ import print_function, division, absolute_import + +from flask import (Flask, g, render_template, url_for, request, make_response, + redirect, flash) + +from wqflask import app +from wqflask.user_login import send_verification_email + +from utility.redis_tools import get_user_groups, get_group_info, create_group, delete_group, add_users_to_group, remove_users_from_group, \ + change_group_name, save_verification_code, check_verification_code, get_user_by_unique_column, get_resources, get_resource_info + +from utility.logger import getLogger +logger = getLogger(__name__) + +@app.route("/groups/manage", methods=('GET', 'POST')) +def manage_groups(): + params = request.form if request.form else request.args + if "add_new_group" in params: + return redirect(url_for('add_group')) + else: + admin_groups, user_groups = get_user_groups(g.user_session.user_id) + return render_template("admin/group_manager.html", admin_groups=admin_groups, user_groups=user_groups) + +@app.route("/groups/view", methods=('GET', 'POST')) +def view_group(): + params = request.form if request.form else request.args + group_id = params['id'] + group_info = get_group_info(group_id) + admins_info = [] + user_is_admin = False + if g.user_session.user_id in group_info['admins']: + user_is_admin = True + for user_id in group_info['admins']: + if user_id: + user_info = get_user_by_unique_column("user_id", user_id) + admins_info.append(user_info) + members_info = [] + for user_id in group_info['members']: + if user_id: + user_info = get_user_by_unique_column("user_id", user_id) + members_info.append(user_info) + + #ZS: This whole part might not scale well with many resources + resources_info = [] + all_resources = get_resources() + for resource_id in all_resources: + resource_info = get_resource_info(resource_id) + group_masks = resource_info['group_masks'] + if group_id in group_masks: + this_resource = {} + privileges = group_masks[group_id] + this_resource['id'] = resource_id + this_resource['name'] = resource_info['name'] + this_resource['data'] = privileges['data'] + this_resource['metadata'] = privileges['metadata'] + this_resource['admin'] = privileges['admin'] + resources_info.append(this_resource) + + return render_template("admin/view_group.html", group_info=group_info, admins=admins_info, members=members_info, user_is_admin=user_is_admin, resources=resources_info) + +@app.route("/groups/remove", methods=('POST',)) +def remove_groups(): + group_ids_to_remove = request.form['selected_group_ids'] + for group_id in group_ids_to_remove.split(":"): + delete_group(g.user_session.user_id, group_id) + + return redirect(url_for('manage_groups')) + +@app.route("/groups/remove_users", methods=('POST',)) +def remove_users(): + group_id = request.form['group_id'] + admin_ids_to_remove = request.form['selected_admin_ids'] + member_ids_to_remove = request.form['selected_member_ids'] + + remove_users_from_group(g.user_session.user_id, admin_ids_to_remove.split(":"), group_id, user_type="admins") + remove_users_from_group(g.user_session.user_id, member_ids_to_remove.split(":"), group_id, user_type="members") + + return redirect(url_for('view_group', id=group_id)) + +@app.route("/groups/add_", methods=('POST',)) +def add_users(user_type='members'): + group_id = request.form['group_id'] + if user_type == "admins": + user_emails = request.form['admin_emails_to_add'].split(",") + add_users_to_group(g.user_session.user_id, group_id, user_emails, admins = True) + elif user_type == "members": + user_emails = request.form['member_emails_to_add'].split(",") + add_users_to_group(g.user_session.user_id, group_id, user_emails, admins = False) + + return redirect(url_for('view_group', id=group_id)) + +@app.route("/groups/change_name", methods=('POST',)) +def change_name(): + group_id = request.form['group_id'] + new_name = request.form['new_name'] + group_info = change_group_name(g.user_session.user_id, group_id, new_name) + + return new_name + +@app.route("/groups/create", methods=('GET', 'POST')) +def add_or_edit_group(): + params = request.form if request.form else request.args + if "group_name" in params: + member_user_ids = set() + admin_user_ids = set() + admin_user_ids.add(g.user_session.user_id) #ZS: Always add the user creating the group as an admin + if "admin_emails_to_add" in params: + admin_emails = params['admin_emails_to_add'].split(",") + for email in admin_emails: + user_details = get_user_by_unique_column("email_address", email) + if user_details: + admin_user_ids.add(user_details['user_id']) + #send_group_invites(params['group_id'], user_email_list = admin_emails, user_type="admins") + if "member_emails_to_add" in params: + member_emails = params['member_emails_to_add'].split(",") + for email in member_emails: + user_details = get_user_by_unique_column("email_address", email) + if user_details: + member_user_ids.add(user_details['user_id']) + #send_group_invites(params['group_id'], user_email_list = user_emails, user_type="members") + + create_group(list(admin_user_ids), list(member_user_ids), params['group_name']) + return redirect(url_for('manage_groups')) + else: + return render_template("admin/create_group.html") + +#ZS: Will integrate this later, for now just letting users be added directly +def send_group_invites(group_id, user_email_list = [], user_type="members"): + for user_email in user_email_list: + user_details = get_user_by_unique_column("email_address", user_email) + if user_details: + group_info = get_group_info(group_id) + #ZS: Probably not necessary since the group should normally always exist if group_id is being passed here, + # but it's technically possible to hit it if Redis is cleared out before submitting the new users or something + if group_info: + #ZS: Don't add user if they're already an admin or if they're being added a regular user and are already a regular user, + # but do add them if they're a regular user and are added as an admin + if (user_details['user_id'] in group_info['admins']) or \ + ((user_type == "members") and (user_details['user_id'] in group_info['members'])): + continue + else: + send_verification_email(user_details, template_name = "email/group_verification.txt", key_prefix = "verification_code", subject = "You've been invited to join a GeneNetwork user group") + #@app.route() \ No newline at end of file diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 7d88b8ed..0f9f5c9d 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -1,72 +1,134 @@ -from __future__ import print_function, division, absolute_import - -from flask import (Flask, g, render_template, url_for, request, make_response, - redirect, flash) - -from wqflask import app - -from utility.authentication_tools import check_owner -from utility.redis_tools import get_resource_info, get_group_info, get_group_by_unique_column, get_user_id - -from utility.logger import getLogger -logger = getLogger(__name__) - -@app.route("/resources/manage", methods=('GET', 'POST')) -def view_resource(): - params = request.form if request.form else request.args - if 'resource_id' in request.args: - resource_id = request.args['resource_id'] - if check_owner(resource_id=resource_id): - resource_info = get_resource_info(resource_id) - group_masks = resource_info['group_masks'] - group_masks_with_names = get_group_names(group_masks) - default_mask = resource_info['default_mask']['data'] - return render_template("admin/manage_resource.html", resource_id = resource_id, resource_info=resource_info, default_mask=default_mask, group_masks=group_masks_with_names) - else: - return redirect(url_for("no_access_page")) - -@app.route("/resources/add_group", methods=('POST',)) -def add_group_to_resource(): - resource_id = request.form['resource_id'] - if check_owner(resource_id=resource_id): - if all(key in request.form for key in ('group_id', 'group_name', 'user_name', 'user_email')): - group_list = [] - if request.form['group_id'] != "": - the_group = get_group_info(request.form['group_id']) - if the_group: - group_list.append(the_group) - if request.form['group_name'] != "": - matched_groups = get_group_by_unique_column("name", request.form['group_name']) - for group in matched_groups: - group_list.append(group) - if request.form['user_name'] != "": - user_id = get_user_id("user_name", request.form['user_name']) - if user_id: - matched_groups = get_group_by_unique_column("admins", user_id) - matched_groups += get_group_by_unique_column("members", user_id) - for group in matched_groups: - group_list.append(group) - if request.form['user_email'] != "": - user_id = get_user_id("email_address", request.form['user_email']) - if user_id: - matched_groups = get_group_by_unique_column("admins", user_id) - matched_groups += get_group_by_unique_column("members", user_id) - for group in matched_groups: - group_list.append(group) - return render_template("admin/select_group_to_add.html", group_list=group_list, resource_id = resource_id) - elif 'selected_group' in request.form: - group_id = request.form['selected_group'] - return render_template("admin/set_group_privileges.html", resource_id = resource_id, group_id = group_id) - else: - return render_template("admin/search_for_groups.html", resource_id = resource_id) - else: - return redirect(url_for("no_access_page")) - -def get_group_names(group_masks): - group_masks_with_names = {} - for group_id, group_mask in group_masks.iteritems(): - this_mask = group_mask - group_name = get_group_info(group_id)['name'] - this_mask['name'] = group_name - +from __future__ import print_function, division, absolute_import + +import json + +from flask import (Flask, g, render_template, url_for, request, make_response, + redirect, flash) + +from wqflask import app + +from utility.authentication_tools import check_owner_or_admin +from utility.redis_tools import get_resource_info, get_group_info, get_groups_like_unique_column, get_user_id, get_user_by_unique_column, get_users_like_unique_column, add_access_mask, add_resource, change_resource_owner + +from utility.logger import getLogger +logger = getLogger(__name__) + +@app.route("/resources/manage", methods=('GET', 'POST')) +def manage_resource(): + params = request.form if request.form else request.args + if 'resource_id' in request.args: + resource_id = request.args['resource_id'] + admin_status = check_owner_or_admin(resource_id=resource_id)[1] + + resource_info = get_resource_info(resource_id) + group_masks = resource_info['group_masks'] + group_masks_with_names = get_group_names(group_masks) + default_mask = resource_info['default_mask']['data'] + owner_id = resource_info['owner_id'] + owner_info = get_user_by_unique_column("user_id", owner_id) + + if 'name' in owner_info: + owner_display_name = owner_info['full_name'] + elif 'user_name' in owner_info: + owner_display_name = owner_info['user_name'] + elif 'email_address' in owner_info: + owner_display_name = owner_info['email_address'] + else: + owner_display_name = None + + return render_template("admin/manage_resource.html", owner_name = owner_display_name, resource_id = resource_id, resource_info=resource_info, default_mask=default_mask, group_masks=group_masks_with_names, admin_status=admin_status) + +@app.route("/search_for_users", methods=('POST',)) +def search_for_user(): + params = request.form + user_list = [] + user_list += get_users_like_unique_column("full_name", params['user_name']) + user_list += get_users_like_unique_column("email_address", params['user_email']) + + return json.dumps(user_list) + +@app.route("/search_for_groups", methods=('POST',)) +def search_for_groups(): + params = request.form + group_list = [] + group_list += get_groups_like_unique_column("id", params['group_id']) + group_list += get_groups_like_unique_column("name", params['group_name']) + + user_list = [] + user_list += get_users_like_unique_column("full_name", params['user_name']) + user_list += get_users_like_unique_column("email_address", params['user_email']) + for user in user_list: + group_list += get_groups_like_unique_column("admins", user['user_id']) + group_list += get_groups_like_unique_column("members", user['user_id']) + + return json.dumps(group_list) + +@app.route("/resources/change_owner", methods=('POST',)) +def change_owner(): + resource_id = request.form['resource_id'] + if 'new_owner' in request.form: + admin_status = check_owner_or_admin(resource_id=resource_id)[1] + if admin_status == "owner": + new_owner_id = request.form['new_owner'] + change_resource_owner(resource_id, new_owner_id) + flash("The resource's owner has beeen changed.", "alert-info") + return redirect(url_for("manage_resource", resource_id=resource_id)) + else: + flash("You lack the permissions to make this change.", "error") + return redirect(url_for("manage_resource", resource_id=resource_id)) + else: + return render_template("admin/change_resource_owner.html", resource_id = resource_id) + +@app.route("/resources/change_default_privileges", methods=('POST',)) +def change_default_privileges(): + resource_id = request.form['resource_id'] + admin_status = check_owner_or_admin(resource_id=resource_id) + if admin_status == "owner" or admin_status == "edit-admins": + resource_info = get_resource_info(resource_id) + default_mask = resource_info['default_mask'] + if request.form['open_to_public'] == "True": + default_mask['data'] = 'view' + else: + default_mask['data'] = 'no-access' + resource_info['default_mask'] = default_mask + add_resource(resource_info) + flash("Your changes have been saved.", "alert-info") + return redirect(url_for("manage_resource", resource_id=resource_id)) + else: + return redirect(url_for("no_access_page")) + +@app.route("/resources/add_group", methods=('POST',)) +def add_group_to_resource(): + resource_id = request.form['resource_id'] + admin_status = check_owner_or_admin(resource_id=resource_id)[1] + if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access": + if 'selected_group' in request.form: + group_id = request.form['selected_group'] + resource_info = get_resource_info(resource_id) + default_privileges = resource_info['default_mask'] + return render_template("admin/set_group_privileges.html", resource_id = resource_id, group_id = group_id, default_privileges = default_privileges) + elif all(key in request.form for key in ('data_privilege', 'metadata_privilege', 'admin_privilege')): + group_id = request.form['group_id'] + group_name = get_group_info(group_id)['name'] + access_mask = { + 'data': request.form['data_privilege'], + 'metadata': request.form['metadata_privilege'], + 'admin': request.form['admin_privilege'] + } + add_access_mask(resource_id, group_id, access_mask) + flash("Privileges have been added for group {}.".format(group_name), "alert-info") + return redirect(url_for("manage_resource", resource_id=resource_id)) + else: + return render_template("admin/search_for_groups.html", resource_id = resource_id) + else: + return redirect(url_for("no_access_page")) + +def get_group_names(group_masks): + group_masks_with_names = {} + for group_id, group_mask in group_masks.iteritems(): + this_mask = group_mask + group_name = get_group_info(group_id)['name'] + this_mask['name'] = group_name + group_masks_with_names[group_id] = this_mask + return group_masks_with_names \ No newline at end of file diff --git a/wqflask/wqflask/static/new/javascript/group_manager.js b/wqflask/wqflask/static/new/javascript/group_manager.js index 5e82d104..4c172cbf 100644 --- a/wqflask/wqflask/static/new/javascript/group_manager.js +++ b/wqflask/wqflask/static/new/javascript/group_manager.js @@ -1,38 +1,38 @@ -$('#add_to_admins').click(function() { - add_emails('admin') -}) - -$('#add_to_members').click(function() { - add_emails('member') -}) - -$('#clear_admins').click(function(){ - clear_emails('admin') -}) - -$('#clear_members').click(function(){ - clear_emails('member') -}) - - -function add_emails(user_type){ - var email_address = $('input[name=user_email]').val(); - var email_list_string = $('input[name=' + user_type + '_emails_to_add]').val() - console.log(email_list_string) - if (email_list_string == ""){ - var email_set = new Set(); - } else { - var email_set = new Set(email_list_string.split(",")) - } - email_set.add(email_address) - - $('input[name=' + user_type + '_emails_to_add]').val(Array.from(email_set).join(',')) - - var emails_display_string = Array.from(email_set).join('\n') - $('.added_' + user_type + 's').val(emails_display_string) -} - -function clear_emails(user_type){ - $('input[name=' + user_type + '_emails_to_add]').val("") - $('.added_' + user_type + 's').val("") +$('#add_to_admins').click(function() { + add_emails('admin') +}) + +$('#add_to_members').click(function() { + add_emails('member') +}) + +$('#clear_admins').click(function(){ + clear_emails('admin') +}) + +$('#clear_members').click(function(){ + clear_emails('member') +}) + + +function add_emails(user_type){ + var email_address = $('input[name=user_email]').val(); + var email_list_string = $('input[name=' + user_type + '_emails_to_add]').val().trim() + console.log(email_list_string) + if (email_list_string == ""){ + var email_set = new Set(); + } else { + var email_set = new Set(email_list_string.split(",")) + } + email_set.add(email_address) + + $('input[name=' + user_type + '_emails_to_add]').val(Array.from(email_set).join(',')) + + var emails_display_string = Array.from(email_set).join('\n') + $('.added_' + user_type + 's').val(emails_display_string) +} + +function clear_emails(user_type){ + $('input[name=' + user_type + '_emails_to_add]').val("") + $('.added_' + user_type + 's').val("") } \ No newline at end of file diff --git a/wqflask/wqflask/static/new/javascript/search_results.js b/wqflask/wqflask/static/new/javascript/search_results.js index 8fa698b4..115dac13 100644 --- a/wqflask/wqflask/static/new/javascript/search_results.js +++ b/wqflask/wqflask/static/new/javascript/search_results.js @@ -296,7 +296,6 @@ $(function() { $("#deselect_all").click(deselect_all); $("#invert").click(invert); $("#add").click(add); - $("#remove").click(remove); $("#submit_bnw").click(submit_bnw); $("#export_traits").click(export_traits); $('.trait_checkbox, .btn').click(change_buttons); diff --git a/wqflask/wqflask/templates/admin/change_resource_owner.html b/wqflask/wqflask/templates/admin/change_resource_owner.html new file mode 100644 index 00000000..ae9409b0 --- /dev/null +++ b/wqflask/wqflask/templates/admin/change_resource_owner.html @@ -0,0 +1,116 @@ +{% extends "base.html" %} +{% block title %}Resource Manager{% endblock %} +{% block css %} + + +{% endblock %} +{% block content %} + +
+ +
+ +
+
+
+
+

Search for user by either name or e-mail:

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+
+ + + +{% endblock %} + +{% block js %} + + + +{% endblock %} diff --git a/wqflask/wqflask/templates/admin/create_group.html b/wqflask/wqflask/templates/admin/create_group.html index 55c3fa0b..5a6929fb 100644 --- a/wqflask/wqflask/templates/admin/create_group.html +++ b/wqflask/wqflask/templates/admin/create_group.html @@ -1,89 +1,89 @@ -{% extends "base.html" %} -{% block title %}Group Manager{% endblock %} -{% block content %} - -
- -
- - -
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
-
-
- - - - - -{% endblock %} - -{% block js %} - - - - - -{% endblock %} +{% extends "base.html" %} +{% block title %}Group Manager{% endblock %} +{% block content %} + +
+ +
+ + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+ + + + + +{% endblock %} + +{% block js %} + + + + + +{% endblock %} diff --git a/wqflask/wqflask/templates/admin/group_manager.html b/wqflask/wqflask/templates/admin/group_manager.html index 23d8205a..70d55684 100644 --- a/wqflask/wqflask/templates/admin/group_manager.html +++ b/wqflask/wqflask/templates/admin/group_manager.html @@ -23,7 +23,7 @@
{% else %} -

Admin Groups

+

Admin Groups


{% if admin_groups|length == 0 %}

You currently aren't the administrator of any groups.

@@ -45,8 +45,8 @@ {{ loop.index }} - {{ group.name }} - {{ group.admins|length + group.users|length }} + {{ group.name }} + {{ group.admins|length + group.members|length }} {{ group.created_timestamp }} {{ group.changed_timestamp }} {{ group.id }} @@ -58,7 +58,7 @@

-

User Groups

+

User Groups


{% if user_groups|length == 0 %}

You currently aren't a member of any groups.

@@ -80,7 +80,7 @@ {{ loop.index }} {{ group.name }} - {{ group.admins|length + group.users|length }} + {{ group.admins|length + group.members|length }} {{ group.created_timestamp }} {{ group.changed_timestamp }} @@ -103,10 +103,14 @@ - - - - -{% endblock %} +{% extends "base.html" %} +{% block title %}Resource Manager{% endblock %} +{% block css %} + + +{% endblock %} +{% block content %} + +
+ {{ flash_me() }} + +
+ +
+
+
+
+ +
+ {{ resource_info.name }} +
+
+ {% if admin_status == "owner" %} +
+ +
+ + +
+
+
+ +
+ +
+
+ {% endif %} +
+
+
+ {% if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access" %} +
+
+ +
+ {% if group_masks|length > 0 %} +

Current Group Permissions

+
+ + + + + + + + + + + {% for key, value in group_masks.iteritems() %} + + + + + + + {% endfor %} + +
NameDataMetadataAdmin
{{ value.name }}{{ value.data }}{{ value.metadata }}{{ value.admin }}
+ {% else %} +

No groups are currently added to this resource.

+ {% endif %} +
+ {% endif %} +
+
+ + + + + +{% endblock %} + +{% block js %} + + + +{% endblock %} diff --git a/wqflask/wqflask/templates/admin/search_for_groups.html b/wqflask/wqflask/templates/admin/search_for_groups.html index 89eb11dd..f304a172 100644 --- a/wqflask/wqflask/templates/admin/search_for_groups.html +++ b/wqflask/wqflask/templates/admin/search_for_groups.html @@ -1,64 +1,134 @@ -{% extends "base.html" %} -{% block title %}Resource Manager{% endblock %} -{% block content %} - -
- -
- -
-
-
-
-

Search by:

-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
-
-
- - - -{% endblock %} - -{% block js %} - - - - - -{% endblock %} +{% extends "base.html" %} +{% block title %}Resource Manager{% endblock %} +{% block css %} + + +{% endblock %} +{% block content %} + +
+ +
+ +
+
+
+
+

Search by:

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+
+ + + +{% endblock %} + +{% block js %} + + + + + +{% endblock %} diff --git a/wqflask/wqflask/templates/admin/select_group_to_add.html b/wqflask/wqflask/templates/admin/select_group_to_add.html deleted file mode 100644 index df70fb2f..00000000 --- a/wqflask/wqflask/templates/admin/select_group_to_add.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "base.html" %} -{% block title %}Matched Groups{% endblock %} -{% block css %} - - - -{% endblock %} -{% block content %} - -
-

The following groups were found:

-
-
- -
- {% if group_list|length > 0 %} - - - - - - - - - - - - {% for group in group_list %} - - - - - - - {% endfor %} - -
NameCreatedLast Changed
{% if 'name' in group %}{{ group.name }}{% else %}N/A{% endif %}{% if 'created_timestamp' in group %}{{ group.created_timestamp }}{% else %}N/A{% endif %}{% if 'changed_timestamp' in group %}{{ group.changed_timestamp }}{% else %}N/A{% endif %}
- {% else %} -

No matching groups were found.

- {% endif %} -
-
-
- - - -{% endblock %} - -{% block js %} - - -{% endblock %} diff --git a/wqflask/wqflask/templates/admin/set_group_privileges.html b/wqflask/wqflask/templates/admin/set_group_privileges.html new file mode 100644 index 00000000..bc52788f --- /dev/null +++ b/wqflask/wqflask/templates/admin/set_group_privileges.html @@ -0,0 +1,102 @@ +{% extends "base.html" %} +{% block title %}Set Group Privileges{% endblock %} +{% block css %} + + + +{% endblock %} +{% block content %} + +
+

Group Privileges

+
+
+ + +
+ +
+

Data and Metadata Privileges

+ + + + + + + + + + + + + {% if 'data' in default_privileges %} + + + + {% else %} + + + + {% endif %} + + + + {% if 'metadata' in default_privileges %} + + + + {% else %} + + + + {% endif %} + + +
No-AccessViewEdit
Data:
Metadata:
+
+

Admin Privileges

+ + + + + + + + + + + + + {% if 'admin' in default_privileges %} + + + + {% else %} + + + + {% endif %} + + +
Not AdminEdit AccessEdit Admins
Admin:
+
+
+
+ + + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/wqflask/wqflask/templates/admin/view_group.html b/wqflask/wqflask/templates/admin/view_group.html new file mode 100644 index 00000000..b797cd70 --- /dev/null +++ b/wqflask/wqflask/templates/admin/view_group.html @@ -0,0 +1,238 @@ +{% extends "base.html" %} +{% block title %}View and Edit Group{% endblock %} +{% block css %} + + + +{% endblock %} +{% block content %} + +
+ +
+ + + +
+
+
+

Admins

+
+ + + + + + + + + + + + {% for admin in admins %} + + + + + + + + {% endfor %} + +
IndexNameEmail AddressOrganization
{{ loop.index }}{% if 'full_name' in admin %}{{ admin.full_name }}{% else %}N/A{% endif %}{% if 'email_address' in admin %}{{ admin.email_address }}{% else %}N/A{% endif %}{% if 'organization' in admin %}{{ admin.organization }}{% else %}N/A{% endif %}
+ {% if user_is_admin == true %} +
+ E-mail of user to add to admins (multiple e-mails can be added separated by commas): + +
+
+ +
+ {% endif %} +
+
+
+ {% if members|length > 0 %} +

Members

+
+ + + + + + + + + + + + {% for member in members %} + + + + + + + + {% endfor %} + +
IndexNameEmail AddressOrganization
{{ loop.index }}{% if 'full_name' in member %}{{ member.full_name }}{% else %}N/A{% endif %}{% if 'email_address' in member %}{{ member.email_address }}{% else %}N/A{% endif %}{% if 'organization' in member %}{{ member.organization }}{% else %}N/A{% endif %}
+ {% if user_is_admin == true %} +
+ E-mail of user to add to members (multiple e-mails can be added separated by commas): + +
+
+ +
+ {% endif %} + {% else %} + There are currently no members in this group. + {% endif %} +
+
+
+

Resources

+
+ {% if resources|length > 0 %} + + + + + + + + + + + + {% for resource in resources %} + + + + + + + + {% endfor %} + +
IndexNameDataMetadataAdmin
{{ loop.index }}{% if 'name' in resource %}{{ resource.name }}{% else %}N/A{% endif %}{% if 'data' in resource %}{{ resource.data }}{% else %}N/A{% endif %}{% if 'metadata' in resource %}{{ resource.metadata }}{% else %}N/A{% endif %}{% if 'admin' in resource %}{{ resource.admin }}{% else %}N/A{% endif %}
+ {% else %} + There are currently no resources associated with this group. + {% endif %} +
+
+
+
+ + + +{% endblock %} + +{% block js %} + + + +{% endblock %} diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html index 07c1b48e..262d9ee5 100644 --- a/wqflask/wqflask/templates/base.html +++ b/wqflask/wqflask/templates/base.html @@ -94,6 +94,11 @@ Sign in {% endif %} + {% if g.user_session.logged_in %} +
  • + Manage Groups +
  • + {% endif %} {% endif %} +
    +

    Group Privileges

    +
    +
    + +
    + +
    +

    Data and Metadata Privileges

    + + + + + + + + + + + + + + + + + + + + + + + +
    No-AccessViewEdit
    Data:
    Metadata:
    +
    +

    Admin Privileges

    + + + + + + + + + + + + + + + + + +
    Not AdminEdit AccessEdit Admins
    Admin:
    +
    +
    +
    + + + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html index 5c315878..5e0bae79 100644 --- a/wqflask/wqflask/templates/show_trait_details.html +++ b/wqflask/wqflask/templates/show_trait_details.html @@ -248,8 +248,8 @@ - {% if resource_id %} - + {% if admin_status[1] == "owner" or admin_status[1] == "edit-admins" or admin_status[1] == "edit-access" %} + {% endif %} diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index ee827ba3..dc431aa9 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -102,7 +102,7 @@ def check_access_permissions(): else: available = check_resource_availability(dataset) - if not available: + if available == "no-access": return redirect(url_for("no_access_page")) @app.teardown_appcontext -- cgit v1.2.3 From 15f5df7fe795a32e2d61dd11f825e53b1a1175ec Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 17 Jun 2020 15:21:41 -0500 Subject: Fixed issue where removing traits from collection didn't work; previous fix wasn't working for some reason --- wqflask/wqflask/collect.py | 2 +- .../static/new/javascript/search_results.js | 34 ---------------------- wqflask/wqflask/templates/collections/view.html | 5 ++++ 3 files changed, 6 insertions(+), 35 deletions(-) diff --git a/wqflask/wqflask/collect.py b/wqflask/wqflask/collect.py index e8459821..42a09fed 100644 --- a/wqflask/wqflask/collect.py +++ b/wqflask/wqflask/collect.py @@ -157,7 +157,7 @@ def remove_traits(): params = request.form uc_id = params['uc_id'] - traits_to_remove = params.getlist('traits[]') + traits_to_remove = params['trait_list'] traits_to_remove = process_traits(traits_to_remove) members_now = g.user_session.remove_traits_from_collection(uc_id, traits_to_remove) diff --git a/wqflask/wqflask/static/new/javascript/search_results.js b/wqflask/wqflask/static/new/javascript/search_results.js index 115dac13..39aae113 100644 --- a/wqflask/wqflask/static/new/javascript/search_results.js +++ b/wqflask/wqflask/static/new/javascript/search_results.js @@ -137,40 +137,6 @@ $(function() { } }; - remove = function() { - var traits, uc_id; - checked_traits = $("#trait_table input:checked"); - traits = checked_traits.map(function() { - return $(this).val(); - }).get(); - console.log("checked length is:", traits.length); - console.log("checked is:", traits); - if ( $("#uc_id").length ) { - uc_id = $("#uc_id").val(); - return $.ajax({ - type: "POST", - url: "/collections/remove", - data: { - uc_id: uc_id, - traits: traits - }, - success: removed_traits - }); - } - else { - collection_name = $("#collection_name").val(); - return $.ajax({ - type: "POST", - url: "/collections/remove", - data: { - collection_name: collection_name, - traits: traits - }, - success: removed_traits - }); - } - }; - submit_bnw = function() { trait_data = get_traits_from_table("trait_table", "submit_bnw") } diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index ec0e0220..b56a89da 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -232,6 +232,11 @@ $("#remove").on("click", function() { url = $(this).data("url") + traits = $("#trait_table input:checked").map(function() { + return $(this).val(); + }).get(); + $("#trait_list").val(traits) + return submit_special(url) }); -- cgit v1.2.3 From fcb3cb1105cf2a1d97c1a08fa636b118ed231ffa Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 17 Jun 2020 16:28:15 -0500 Subject: A user's id is now set as a parameter if it doesn't already exist --- wqflask/maintenance/set_resource_defaults.py | 8 +++--- wqflask/utility/authentication_tools.py | 30 ++++++++++++---------- wqflask/utility/redis_tools.py | 4 +++ wqflask/wqflask/group_manager.py | 4 +-- wqflask/wqflask/templates/admin/group_manager.html | 16 +++++++----- 5 files changed, 36 insertions(+), 26 deletions(-) diff --git a/wqflask/maintenance/set_resource_defaults.py b/wqflask/maintenance/set_resource_defaults.py index 344e6a23..0c221bbf 100644 --- a/wqflask/maintenance/set_resource_defaults.py +++ b/wqflask/maintenance/set_resource_defaults.py @@ -27,8 +27,9 @@ sys.path.insert(0,'./') # NEW: import app to avoid a circular dependency on utility.tools from wqflask import app +from utility import hmac from utility.tools import SQL_URI -from utility.redis_tools import get_redis_conn, get_user_id, add_resource, get_resources +from utility.redis_tools import get_redis_conn, get_user_id, add_resource, get_resources, get_resource_info Redis = get_redis_conn() import MySQLdb @@ -117,7 +118,7 @@ def insert_geno_resources(default_owner_id): resource_ob = {} resource_ob['name'] = resource[1] if resource[1] == "HET3-ITPGeno": - resource_ob['owner_id'] = "73a3f093-ca13-4ae0-a179-9a446f709f6e" + resource_ob['owner_id'] = "c5ce8c56-78a6-474f-bcaf-7129d97f56ae" else: resource_ob['owner_id'] = default_owner_id resource_ob['data'] = { "dataset" : str(resource[0]) } @@ -145,7 +146,8 @@ def main(): Redis.delete("resources") - owner_id = get_user_id("email_address", "zachary.a.sloan@gmail.com") + owner_id = "c5ce8c56-78a6-474f-bcaf-7129d97f56ae" + insert_resources(owner_id) if __name__ == '__main__': diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index 07ceacc0..dfa0e2d9 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -7,6 +7,7 @@ from base import data_set from utility import hmac from utility.redis_tools import get_redis_conn, get_resource_info, get_resource_id +Redis = get_redis_conn() from flask import Flask, g, redirect, url_for @@ -14,8 +15,12 @@ import logging logger = logging.getLogger(__name__ ) def check_resource_availability(dataset, trait_id=None): - resource_id = get_resource_id(dataset, trait_id) + #ZS: Check if super-user - we should probably come up with some way to integrate this into the proxy + if g.user_session.user_id in Redis.smembers("super_users"): + return "edit" + + resource_id = get_resource_id(dataset, trait_id) response = None if resource_id: resource_info = get_resource_info(resource_id) @@ -68,19 +73,16 @@ def check_owner(dataset=None, trait_id=None, resource_id=None): return False def check_owner_or_admin(dataset=None, trait_id=None, resource_id=None): - if resource_id: - resource_info = get_resource_info(resource_id) - if g.user_session.user_id == resource_info['owner_id']: - return [resource_id, "owner"] - else: - return [resource_id, check_admin(resource_id)] - else: + if not resource_id: resource_id = get_resource_id(dataset, trait_id) - if resource_id: - resource_info = get_resource_info(resource_id) - if g.user_session.user_id == resource_info['owner_id']: - return [resource_id, "owner"] - else: - return [resource_id, check_admin(resource_id)] + + if g.user_session.user_id in Redis.smembers("super_users"): + return [resource_id, "owner"] + + resource_info = get_resource_info(resource_id) + if g.user_session.user_id == resource_info['owner_id']: + return [resource_id, "owner"] + else: + return [resource_id, check_admin(resource_id)] return [resource_id, "not-admin"] \ No newline at end of file diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py index c6d221ff..9d09a66b 100644 --- a/wqflask/utility/redis_tools.py +++ b/wqflask/utility/redis_tools.py @@ -30,6 +30,7 @@ def is_redis_available(): def get_user_id(column_name, column_value): user_list = Redis.hgetall("users") + key_list = [] for key in user_list: user_ob = json.loads(user_list[key]) if column_name in user_ob and user_ob[column_name] == column_value: @@ -62,6 +63,9 @@ def get_users_like_unique_column(column_name, column_value): if column_name != "user_id": for key in user_list: user_ob = json.loads(user_list[key]) + if "user_id" not in user_ob: + set_user_attribute(key, "user_id", key) + user_ob["user_id"] = key if column_name in user_ob: if column_value in user_ob[column_name]: matched_users.append(user_ob) diff --git a/wqflask/wqflask/group_manager.py b/wqflask/wqflask/group_manager.py index 9afc016b..24848ed8 100644 --- a/wqflask/wqflask/group_manager.py +++ b/wqflask/wqflask/group_manager.py @@ -19,8 +19,8 @@ def manage_groups(): if "add_new_group" in params: return redirect(url_for('add_group')) else: - admin_groups, user_groups = get_user_groups(g.user_session.user_id) - return render_template("admin/group_manager.html", admin_groups=admin_groups, user_groups=user_groups) + admin_groups, member_groups = get_user_groups(g.user_session.user_id) + return render_template("admin/group_manager.html", admin_groups=admin_groups, member_groups=member_groups) @app.route("/groups/view", methods=('GET', 'POST')) def view_group(): diff --git a/wqflask/wqflask/templates/admin/group_manager.html b/wqflask/wqflask/templates/admin/group_manager.html index 70d55684..c8ed6851 100644 --- a/wqflask/wqflask/templates/admin/group_manager.html +++ b/wqflask/wqflask/templates/admin/group_manager.html @@ -10,18 +10,20 @@
    - {% if admin_groups|length == 0 and user_groups|length == 0 %} + {% if admin_groups|length == 0 and member_groups|length == 0 %}

    You currently aren't a member or admin of any groups.


    - + {% else %}

    Admin Groups


    @@ -60,10 +62,10 @@

    User Groups


    - {% if user_groups|length == 0 %} + {% if member_groups|length == 0 %}

    You currently aren't a member of any groups.

    {% else %} - +
    @@ -75,7 +77,7 @@ - {% for group in user_groups %} + {% for group in member_groups %} @@ -107,8 +109,8 @@ 'sDom': 'tr' }); {% endif %} - {% if user_groups|length != 0 %} - $('#user_groups').dataTable({ + {% if member_groups|length != 0 %} + $('#member_groups').dataTable({ 'sDom': 'tr' }); {% endif %} -- cgit v1.2.3 From 75802ed1f9e5d955987bf5f5eb78a9cb120116ec Mon Sep 17 00:00:00 2001 From: zsloan Date: Sat, 20 Jun 2020 17:33:22 -0500 Subject: Added some admin functionality and fixed issue with temp traits --- wqflask/base/trait.py | 17 ++++-- wqflask/base/webqtlConfig.py | 4 ++ wqflask/maintenance/set_resource_defaults.py | 20 +++++-- wqflask/utility/authentication_tools.py | 79 +++++++++++++++------------- wqflask/wqflask/resource_manager.py | 6 +-- wqflask/wqflask/views.py | 20 ++++--- 6 files changed, 90 insertions(+), 56 deletions(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index 7700ecd5..c2b8b910 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -42,11 +42,11 @@ def create_trait(**kw): if kw.get('dataset_name') != "Temp": if dataset.type == 'Publish': - permitted = check_resource_availability(dataset, kw.get('name')) + permissions = check_resource_availability(dataset, kw.get('name')) else: - permitted = check_resource_availability(dataset) + permissions = check_resource_availability(dataset) - if permitted != "no-access": + if "view" in permissions['data']: the_trait = GeneralTrait(**kw) if the_trait.dataset.type != "Temp": @@ -382,9 +382,16 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): resource_id = get_resource_id(dataset, trait.name) if dataset.type == 'Publish': - the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id) + the_url = "http://localhost:8081/run-action?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id) else: - the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view&trait={}".format(resource_id, g.user_session.user_id, trait.name) + the_url = "http://localhost:8081/run-action?resource={}&user={}&branch=data&action=view&trait={}".format(resource_id, g.user_session.user_id, trait.name) + + response = requests.get(the_url).content + if response.strip() == "no-access": + trait.view = False + return trait + else: + trait_info = json.loads(response) try: response = requests.get(the_url).content diff --git a/wqflask/base/webqtlConfig.py b/wqflask/base/webqtlConfig.py index 55407123..3d86bc22 100644 --- a/wqflask/base/webqtlConfig.py +++ b/wqflask/base/webqtlConfig.py @@ -17,6 +17,10 @@ DEBUG = 1 #USER privilege USERDICT = {'guest':1,'user':2, 'admin':3, 'root':4} +#Set privileges +SUPER_PRIVILEGES = {'data': ['no-access', 'view', 'edit'], 'metadata': ['no-access', 'view', 'edit'], 'admin': ['not-admin', 'edit-access', 'edit-admins']} +DEFAULT_PRIVILEGES = {'data': ['no-access', 'view'], 'metadata': ['no-access', 'view'], 'admin': ['not-admin']} + #minimum number of informative strains KMININFORMATIVE = 5 diff --git a/wqflask/maintenance/set_resource_defaults.py b/wqflask/maintenance/set_resource_defaults.py index 0c221bbf..ddb3b17b 100644 --- a/wqflask/maintenance/set_resource_defaults.py +++ b/wqflask/maintenance/set_resource_defaults.py @@ -68,9 +68,13 @@ def insert_probeset_resources(default_owner_id): resource_ob['data'] = { "dataset" : str(resource[0])} resource_ob['type'] = "dataset-probeset" if resource[2] < 1 and resource[3] > 0: - resource_ob['default_mask'] = { "data": "view" } + resource_ob['default_mask'] = { "data": "view", + "metadata": "view", + "admin": "not-admin"} else: - resource_ob['default_mask'] = { "data": "no-access" } + resource_ob['default_mask'] = { "data": "no-access", + "metadata": "no-access", + "admin": "not-admin"} resource_ob['group_masks'] = {} add_resource(resource_ob) @@ -98,7 +102,9 @@ def insert_publish_resources(default_owner_id): resource_ob['data'] = { "dataset" : str(resource[1]) , "trait" : str(resource[0])} resource_ob['type'] = "dataset-publish" - resource_ob['default_mask'] = { "data": "view" } + resource_ob['default_mask'] = { "data": "view", + "metadata": "view", + "admin": "not-admin"} resource_ob['group_masks'] = {} @@ -124,9 +130,13 @@ def insert_geno_resources(default_owner_id): resource_ob['data'] = { "dataset" : str(resource[0]) } resource_ob['type'] = "dataset-geno" if resource[2] < 1: - resource_ob['default_mask'] = { "data": "view" } + resource_ob['default_mask'] = { "data": "view", + "metadata": "view", + "admin": "not-admin"} else: - resource_ob['default_mask'] = { "data": "no-access" } + resource_ob['default_mask'] = { "data": "no-access", + "metadata": "no-access", + "admin": "not-admin"} resource_ob['group_masks'] = {} add_resource(resource_ob) diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index dfa0e2d9..6c88949b 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -3,7 +3,7 @@ from __future__ import absolute_import, print_function, division import json import requests -from base import data_set +from base import data_set, webqtlConfig from utility import hmac from utility.redis_tools import get_redis_conn, get_resource_info, get_resource_id @@ -18,45 +18,47 @@ def check_resource_availability(dataset, trait_id=None): #ZS: Check if super-user - we should probably come up with some way to integrate this into the proxy if g.user_session.user_id in Redis.smembers("super_users"): - return "edit" + return webqtlConfig.SUPER_PRIVILEGES - resource_id = get_resource_id(dataset, trait_id) response = None - if resource_id: - resource_info = get_resource_info(resource_id) - - the_url = "http://localhost:8080/available?resource={}&user={}".format(resource_id, g.user_session.user_id) - try: - response = json.loads(requests.get(the_url).content)['data'] - except: - response = resource_info['default_mask']['data'] - if 'edit' in response: - return "edit" - elif 'view' in response: - return "view" - else: - return "no-access" + #At least for now assume temporary entered traits are accessible#At least for now assume temporary entered traits are accessible + if type(dataset) == str: + return webqtlConfig.DEFAULT_PRIVILEGES + if dataset.type == "Temp": + return webqtlConfig.DEFAULT_PRIVILEGES - return False + resource_id = get_resource_id(dataset, trait_id) -def check_admin(resource_id=None): + if resource_id: + resource_info = get_resource_info(resource_id) + else: + return response #ZS: Need to substitute in something that creates the resource in Redis later - return "not-admin" + the_url = "http://localhost:8081/available?resource={}&user={}".format(resource_id, g.user_session.user_id) + try: + response = json.loads(requests.get(the_url).content) + except: + response = resource_info['default_mask'] - # ZS: commented out until proxy can return this - # the_url = "http://localhost:8080/available?resource={}&user={}".format(resource_id, g.user_session.user_id) - # try: - # response = json.loads(requests.get(the_url).content) - # except: - # response = resource_info['default_mask']['admin'] + if response: + return response + else: #ZS: No idea how this would happen, but just in case + return False - # if 'edit-admins' in response: - # return "edit-admins" - # elif 'edit-access' in response: - # return "edit-access" - # else: - # return "not-admin" +def check_admin(resource_id=None): + the_url = "http://localhost:8081/available?resource={}&user={}".format(resource_id, g.user_session.user_id) + try: + response = json.loads(requests.get(the_url).content)['admin'] + except: + response = resource_info['default_mask']['admin'] + + if 'edit-admins' in response: + return "edit-admins" + elif 'edit-access' in response: + return "edit-access" + else: + return "not-admin" def check_owner(dataset=None, trait_id=None, resource_id=None): if resource_id: @@ -74,15 +76,18 @@ def check_owner(dataset=None, trait_id=None, resource_id=None): def check_owner_or_admin(dataset=None, trait_id=None, resource_id=None): if not resource_id: - resource_id = get_resource_id(dataset, trait_id) + if dataset.type == "Temp": + return "not-admin" + else: + resource_id = get_resource_id(dataset, trait_id) if g.user_session.user_id in Redis.smembers("super_users"): - return [resource_id, "owner"] + return "owner" resource_info = get_resource_info(resource_id) if g.user_session.user_id == resource_info['owner_id']: - return [resource_id, "owner"] + return "owner" else: - return [resource_id, check_admin(resource_id)] + return check_admin(resource_id) - return [resource_id, "not-admin"] \ No newline at end of file + return "not-admin" \ No newline at end of file diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 0f9f5c9d..39a07310 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -18,7 +18,7 @@ def manage_resource(): params = request.form if request.form else request.args if 'resource_id' in request.args: resource_id = request.args['resource_id'] - admin_status = check_owner_or_admin(resource_id=resource_id)[1] + admin_status = check_owner_or_admin(resource_id=resource_id) resource_info = get_resource_info(resource_id) group_masks = resource_info['group_masks'] @@ -67,7 +67,7 @@ def search_for_groups(): def change_owner(): resource_id = request.form['resource_id'] if 'new_owner' in request.form: - admin_status = check_owner_or_admin(resource_id=resource_id)[1] + admin_status = check_owner_or_admin(resource_id=resource_id) if admin_status == "owner": new_owner_id = request.form['new_owner'] change_resource_owner(resource_id, new_owner_id) @@ -100,7 +100,7 @@ def change_default_privileges(): @app.route("/resources/add_group", methods=('POST',)) def add_group_to_resource(): resource_id = request.form['resource_id'] - admin_status = check_owner_or_admin(resource_id=resource_id)[1] + admin_status = check_owner_or_admin(resource_id=resource_id) if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access": if 'selected_group' in request.form: group_id = request.form['selected_group'] diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index dc431aa9..bc01839b 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -96,13 +96,21 @@ def check_access_permissions(): pass else: if 'dataset' in request.args: - dataset = create_dataset(request.args['dataset']) - if 'trait_id' in request.args: - available = check_resource_availability(dataset, request.args['trait_id']) + if request.args['dataset'] == "Temp": + permissions = check_resource_availability("Temp") else: - available = check_resource_availability(dataset) - - if available == "no-access": + dataset = create_dataset(request.args['dataset']) + + if dataset.type == "Temp": + permissions = False + if 'trait_id' in request.args: + permissions = check_resource_availability(dataset, request.args['trait_id']) + elif dataset.type != "Publish": + permissions = check_resource_availability(dataset) + else: + return None + + if 'view' not in permissions['data']: return redirect(url_for("no_access_page")) @app.teardown_appcontext -- cgit v1.2.3 From 51417c06061246bc92be89db198b3e74e7126035 Mon Sep 17 00:00:00 2001 From: zsloan Date: Sat, 20 Jun 2020 17:47:38 -0500 Subject: Fixed ports for proxy (though I need to add the port to global variables) and also simplified the check_owner_or_admin function a little --- wqflask/base/trait.py | 4 ++-- wqflask/utility/authentication_tools.py | 4 ++-- wqflask/wqflask/show_trait/show_trait.py | 3 ++- wqflask/wqflask/templates/show_trait_details.html | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index c2b8b910..0e26ca2c 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -382,9 +382,9 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): resource_id = get_resource_id(dataset, trait.name) if dataset.type == 'Publish': - the_url = "http://localhost:8081/run-action?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id) + the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view".format(resource_id, g.user_session.user_id) else: - the_url = "http://localhost:8081/run-action?resource={}&user={}&branch=data&action=view&trait={}".format(resource_id, g.user_session.user_id, trait.name) + the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view&trait={}".format(resource_id, g.user_session.user_id, trait.name) response = requests.get(the_url).content if response.strip() == "no-access": diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index 6c88949b..06b2854a 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -35,7 +35,7 @@ def check_resource_availability(dataset, trait_id=None): else: return response #ZS: Need to substitute in something that creates the resource in Redis later - the_url = "http://localhost:8081/available?resource={}&user={}".format(resource_id, g.user_session.user_id) + the_url = "http://localhost:8080/available?resource={}&user={}".format(resource_id, g.user_session.user_id) try: response = json.loads(requests.get(the_url).content) except: @@ -47,7 +47,7 @@ def check_resource_availability(dataset, trait_id=None): return False def check_admin(resource_id=None): - the_url = "http://localhost:8081/available?resource={}&user={}".format(resource_id, g.user_session.user_id) + the_url = "http://localhost:8080/available?resource={}&user={}".format(resource_id, g.user_session.user_id) try: response = json.loads(requests.get(the_url).content)['admin'] except: diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index ed4ff0ad..4698807a 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -72,7 +72,8 @@ class ShowTrait(object): cellid=None) self.trait_vals = Redis.get(self.trait_id).split() - self.admin_status = check_owner_or_admin(self.dataset, self.trait_id) + self.resource_id = get_resource_id(self.dataset, self.trait_id) + self.admin_status = check_owner_or_admin(resource_id=self.resource_id) #ZS: Get verify/rna-seq link URLs try: diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html index 5e0bae79..965c0340 100644 --- a/wqflask/wqflask/templates/show_trait_details.html +++ b/wqflask/wqflask/templates/show_trait_details.html @@ -248,8 +248,8 @@ - {% if admin_status[1] == "owner" or admin_status[1] == "edit-admins" or admin_status[1] == "edit-access" %} - + {% if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access" %} + {% endif %} -- cgit v1.2.3 From a13e2c5856acd05610a5966d3f8ecc17038e4735 Mon Sep 17 00:00:00 2001 From: zsloan Date: Sat, 20 Jun 2020 17:53:51 -0500 Subject: Missed one issue introduced by last commit --- wqflask/wqflask/show_trait/show_trait.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index 4698807a..5fc69cab 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -49,18 +49,23 @@ class ShowTrait(object): self.temp_trait = False self.trait_id = kw['trait_id'] helper_functions.get_species_dataset_trait(self, kw) + self.resource_id = get_resource_id(self.dataset, self.trait_id) + self.admin_status = check_owner_or_admin(resource_id=self.resource_id) elif 'group' in kw: self.temp_trait = True self.trait_id = "Temp_"+kw['species']+ "_" + kw['group'] + "_" + datetime.datetime.now().strftime("%m%d%H%M%S") self.temp_species = kw['species'] self.temp_group = kw['group'] self.dataset = data_set.create_dataset(dataset_name = "Temp", dataset_type = "Temp", group_name = self.temp_group) + # Put values in Redis so they can be looked up later if added to a collection Redis.set(self.trait_id, kw['trait_paste'], ex=ONE_YEAR) self.trait_vals = kw['trait_paste'].split() self.this_trait = create_trait(dataset=self.dataset, name=self.trait_id, cellid=None) + + self.admin_status = check_owner_or_admin(dataset=self.dataset, trait_id=self.trait_id) else: self.temp_trait = True self.trait_id = kw['trait_id'] @@ -70,10 +75,9 @@ class ShowTrait(object): self.this_trait = create_trait(dataset=self.dataset, name=self.trait_id, cellid=None) - self.trait_vals = Redis.get(self.trait_id).split() - self.resource_id = get_resource_id(self.dataset, self.trait_id) - self.admin_status = check_owner_or_admin(resource_id=self.resource_id) + self.trait_vals = Redis.get(self.trait_id).split() + self.admin_status = check_owner_or_admin(dataset=self.dataset, trait_id=self.trait_id) #ZS: Get verify/rna-seq link URLs try: -- cgit v1.2.3 From 01c3acf485de10fb1696fc24471751d4d3ab8e05 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 22 Jun 2020 13:30:01 -0500 Subject: Fixed issue with temp traits not working for trait page functions like correlation or mapping --- wqflask/base/data_set.py | 3 +++ wqflask/wqflask/static/new/javascript/show_trait.js | 3 ++- wqflask/wqflask/views.py | 5 ++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 92dc8615..2272b6ee 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -65,6 +65,9 @@ logger = getLogger(__name__ ) DS_NAME_MAP = {} def create_dataset(dataset_name, dataset_type = None, get_samplelist = True, group_name = None): + if dataset_name == "Temp": + dataset_type = "Temp" + if not dataset_type: dataset_type = Dataset_Getter(dataset_name) diff --git a/wqflask/wqflask/static/new/javascript/show_trait.js b/wqflask/wqflask/static/new/javascript/show_trait.js index c0b2e6db..738cd536 100644 --- a/wqflask/wqflask/static/new/javascript/show_trait.js +++ b/wqflask/wqflask/static/new/javascript/show_trait.js @@ -515,7 +515,8 @@ $('select[name=corr_type]').change(on_corr_method_change); submit_special = function(url) { $("#trait_data_form").attr("action", url); - return $("#trait_data_form").submit(); + $("#trait_data_form").submit(); + return false }; var corr_input_list = ['corr_type', 'primary_samples', 'trait_id', 'dataset', 'group', 'tool_used', 'form_url', 'corr_sample_method', 'corr_samples_group', 'corr_dataset', 'min_expr', diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index bc01839b..80164a22 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -641,7 +641,10 @@ def loading_page(): if 'num_vals' in start_vars: num_vals = int(start_vars['num_vals']) else: - dataset = create_dataset(start_vars['dataset']) + if 'group' in start_vars: + dataset = create_dataset(start_vars['dataset'], group_name = start_vars['group']) + else: + dataset = create_dataset(start_vars['dataset']) genofile_samplelist = [] samples = start_vars['primary_samples'].split(",") if 'genofile' in start_vars: -- cgit v1.2.3 From 10a281056b19ee150b471afdf016251d5f9ead32 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 23 Jun 2020 17:25:54 -0500 Subject: Changed back previous 'fix' that was intended to fix issue where two tabs were opened when doing mapping/correlations; apparently this is a chrome specific problem so it will need to be deal twiht differently --- wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js | 2 +- wqflask/wqflask/static/new/javascript/show_trait.js | 1 - wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js b/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js index 8cd6dac3..934cc14d 100644 --- a/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js +++ b/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js @@ -110,7 +110,7 @@ submit_click = function() { trait_click = function() { var dataset, this_trait_url, trait; trait = $(this).parent().find('.trait').text(); - dataset = $(this).parent().find('.dataset').text(); + dataset = $(this).parent().find('.dataset').data("dataset"); $("input[name=covariates]").val(trait + ":" + dataset) $(".selected_covariates").text(trait) return $.colorbox.close(); diff --git a/wqflask/wqflask/static/new/javascript/show_trait.js b/wqflask/wqflask/static/new/javascript/show_trait.js index 738cd536..c0784073 100644 --- a/wqflask/wqflask/static/new/javascript/show_trait.js +++ b/wqflask/wqflask/static/new/javascript/show_trait.js @@ -516,7 +516,6 @@ $('select[name=corr_type]').change(on_corr_method_change); submit_special = function(url) { $("#trait_data_form").attr("action", url); $("#trait_data_form").submit(); - return false }; var corr_input_list = ['corr_type', 'primary_samples', 'trait_id', 'dataset', 'group', 'tool_used', 'form_url', 'corr_sample_method', 'corr_samples_group', 'corr_dataset', 'min_expr', diff --git a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js index db17af40..4dce0705 100644 --- a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js +++ b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js @@ -5,7 +5,6 @@ submit_special = function(url) { $("#trait_data_form").attr("action", url); $("#trait_data_form").submit(); - return false; }; update_time_remaining = function(percent_complete) { -- cgit v1.2.3 From 9b1aecdbb51c7cb843ca79ab430d8dc2b9d3767e Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 24 Jun 2020 15:35:47 -0500 Subject: Fixed issue where scatterplot had different N than correlation results + fixed some aesthetic issues with correlatoin results table --- wqflask/wqflask/correlation/corr_scatter_plot.py | 23 ++++++++++++++++------- wqflask/wqflask/templates/correlation_page.html | 24 ++++++++---------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/wqflask/wqflask/correlation/corr_scatter_plot.py b/wqflask/wqflask/correlation/corr_scatter_plot.py index 04ec427d..819836b1 100644 --- a/wqflask/wqflask/correlation/corr_scatter_plot.py +++ b/wqflask/wqflask/correlation/corr_scatter_plot.py @@ -4,7 +4,7 @@ import math from flask import g -from base.trait import create_trait +from base.trait import create_trait, retrieve_sample_data from base import data_set from utility import corr_result_helpers from scipy import stats @@ -17,12 +17,21 @@ class CorrScatterPlot(object): """Page that displays a correlation scatterplot with a line fitted to it""" def __init__(self, params): - self.data_set_1 = data_set.create_dataset(params['dataset_1']) - self.data_set_2 = data_set.create_dataset(params['dataset_2']) - #self.data_set_3 = data_set.create_dataset(params['dataset_3']) - self.trait_1 = create_trait(name=params['trait_1'], dataset=self.data_set_1) - self.trait_2 = create_trait(name=params['trait_2'], dataset=self.data_set_2) - #self.trait_3 = create_trait(name=params['trait_3'], dataset=self.data_set_3) + self.dataset_1 = data_set.create_dataset(params['dataset_1']) + self.dataset_2 = data_set.create_dataset(params['dataset_2']) + #self.dataset_3 = data_set.create_dataset(params['dataset_3']) + self.trait_1 = create_trait(name=params['trait_1'], dataset=self.dataset_1) + self.trait_2 = create_trait(name=params['trait_2'], dataset=self.dataset_2) + #self.trait_3 = create_trait(name=params['trait_3'], dataset=self.dataset_3) + + primary_samples = self.dataset_1.group.samplelist + if self.dataset_1.group.parlist != None: + primary_samples += self.dataset_1.group.parlist + if self.dataset_1.group.f1list != None: + primary_samples += self.dataset_1.group.f1list + + self.trait_1 = retrieve_sample_data(self.trait_1, self.dataset_1, primary_samples) + self.trait_2 = retrieve_sample_data(self.trait_2, self.dataset_2, primary_samples) samples_1, samples_2, num_overlap = corr_result_helpers.normalize_values_with_samples(self.trait_1.data, self.trait_2.data) diff --git a/wqflask/wqflask/templates/correlation_page.html b/wqflask/wqflask/templates/correlation_page.html index 71705390..f429948d 100644 --- a/wqflask/wqflask/templates/correlation_page.html +++ b/wqflask/wqflask/templates/correlation_page.html @@ -4,6 +4,7 @@ + {% endblock %} {% block content %}
    @@ -114,8 +115,8 @@
    Show/Hide Columns:
    -
    -
    {{ loop.index }}
    +
    +
    @@ -143,7 +144,7 @@ - + {% if trait.lit_corr == "" or trait.lit_corr == 0.000 %} @@ -167,8 +168,8 @@ {% else %} {% endif %} - - + + - {% for key, value in group_masks.iteritems() %} + {% for key, value in group_masks.items() %} diff --git a/wqflask/wqflask/templates/loading.html b/wqflask/wqflask/templates/loading.html index 15ab4080..9b335dfe 100644 --- a/wqflask/wqflask/templates/loading.html +++ b/wqflask/wqflask/templates/loading.html @@ -1,7 +1,7 @@ Loading {{ start_vars.tool_used }} Results - {% for key, value in start_vars.iteritems() %} + {% for key, value in start_vars.items() %} {% endfor %}
    @@ -44,4 +44,4 @@ $("#loading_form").attr("action", "{{ start_vars.form_url }}"); setTimeout(function(){ $("#loading_form").submit()}, 350); - \ No newline at end of file + -- cgit v1.2.3 From e102c7c0a7d1ad3c305ed8db0d19fa12cfa5cb38 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 27 Aug 2020 03:19:23 +0300 Subject: Remove relative imports * wqflask/runserver.py: Relative imports leads to import errors. --- wqflask/runserver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/runserver.py b/wqflask/runserver.py index a81d62b8..df957bd9 100644 --- a/wqflask/runserver.py +++ b/wqflask/runserver.py @@ -7,9 +7,9 @@ # # /sbin/iptables -A INPUT -p tcp -i eth0 -s ! 71.236.239.43 --dport 5003 -j DROP -from .wqflask import app -from .utility.startup_config import app_config -from .utility.tools import WEBSERVER_MODE, SERVER_PORT +from wqflask import app +from utility.startup_config import app_config +from utility.tools import WEBSERVER_MODE, SERVER_PORT import logging -- cgit v1.2.3 From 1e030b2543c22a8148f39657b5921724d9512d40 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 27 Aug 2020 03:22:06 +0300 Subject: Remove python2 string processing to utf-8 * wqflask/base/trait.py: Remove python2 codecs.BOM_UTF8 methods. * wqflask/wqflask/search_results.py: Remove utf-8 decode method on string. --- wqflask/base/trait.py | 4 ---- wqflask/wqflask/search_results.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index e3507ae1..6950cf11 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -488,10 +488,6 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): else: trait.description_display = "" - trait.abbreviation = str(str(trait.abbreviation).strip(codecs.BOM_UTF8), 'utf-8', errors="replace") - trait.description_display = str(str(trait.description_display).strip(codecs.BOM_UTF8), 'utf-8', errors="replace") - trait.authors = str(str(trait.authors).strip(codecs.BOM_UTF8), 'utf-8', errors="replace") - if not trait.year.isdigit(): trait.pubmed_text = "N/A" else: diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index f6c677a8..0de14f15 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -115,7 +115,7 @@ views.py). trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(this_trait.name, this_trait.dataset.name)) if this_trait.dataset.type == "ProbeSet": trait_dict['symbol'] = this_trait.symbol - trait_dict['description'] = this_trait.description_display.decode('utf-8', 'replace') + trait_dict['description'] = this_trait.description_display trait_dict['location'] = this_trait.location_repr trait_dict['mean'] = "N/A" trait_dict['additive'] = "N/A" -- cgit v1.2.3 From c6c80dde5cfb03c73b61523950af0ca6d208aea4 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 27 Aug 2020 03:25:06 +0300 Subject: Update imports * wqflask/wqflask/api/router.py: Break imports into separate lines and remove unused ones. --- wqflask/wqflask/api/router.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/wqflask/wqflask/api/router.py b/wqflask/wqflask/api/router.py index acf7ce4a..8e59ef27 100644 --- a/wqflask/wqflask/api/router.py +++ b/wqflask/wqflask/api/router.py @@ -1,14 +1,21 @@ # GN2 API -import os, io, csv, json, datetime, requests, yaml -import zlib +import os +import io +import csv +import json +import datetime +import requests + from zipfile import ZipFile, ZIP_DEFLATED -import io import flask -from flask import g, Response, request, make_response, render_template, send_from_directory, jsonify, redirect, send_file -import sqlalchemy +from flask import g +from flask import request +from flask import make_response +from flask import send_file + from wqflask import app from wqflask.api import correlation, mapping, gen_menu -- cgit v1.2.3 From b46faf479e129801f4bec2457444b5341e8aeabf Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 27 Aug 2020 03:26:36 +0300 Subject: Add a check for a "uuid" object * wqflask/wqflask/views.py(json_default_handler): Check for "uuid.UUID" instance to avoid TypeError. --- wqflask/wqflask/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 0224da4a..557ff523 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -930,8 +930,8 @@ def json_default_handler(obj): if hasattr(obj, 'isoformat'): return obj.isoformat() # Handle integer keys for dictionaries - elif isinstance(obj, int): - return str(int) + elif isinstance(obj, int) or isinstance(obj, uuid.UUID): + return str(obj) # Handle custom objects if hasattr(obj, '__dict__'): return obj.__dict__ -- cgit v1.2.3 From 70dbeeb5832711ed5271434e482c18bc7ea095b8 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 27 Aug 2020 03:28:43 +0300 Subject: Add check for empty group_code when adding a new resource * wqflask/utility/authentication_tools.py(add_new_resource): If group_code is "None", an error is thrown when you try to: `group_code + "_" + str(trait_id)` --- wqflask/utility/authentication_tools.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index 0e499180..73c39399 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -49,7 +49,10 @@ def add_new_resource(dataset, trait_id=None): } if dataset.type == "Publish": - resource_ob['name'] = get_group_code(dataset) + "_" + str(trait_id) + group_code = get_group_code(dataset) + if group_code is None: + group_code = "" + resource_ob['name'] = group_code + "_" + str(trait_id) resource_ob['data'] = { 'dataset': dataset.id, 'trait' : trait_id @@ -74,7 +77,6 @@ def add_new_resource(dataset, trait_id=None): def get_group_code(dataset): results = g.db.execute("SELECT InbredSetCode from InbredSet where Name='{}'".format(dataset.group.name)).fetchone() - return results[0] def check_admin(resource_id=None): -- cgit v1.2.3 From 433b41086d3f61a94778d7b244c0d0276794e59e Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 27 Aug 2020 15:07:43 -0500 Subject: Added manager_user.html because it was missing from git --- wqflask/wqflask/templates/admin/manage_user.html | 79 ++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 wqflask/wqflask/templates/admin/manage_user.html diff --git a/wqflask/wqflask/templates/admin/manage_user.html b/wqflask/wqflask/templates/admin/manage_user.html new file mode 100644 index 00000000..7afe075f --- /dev/null +++ b/wqflask/wqflask/templates/admin/manage_user.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} +{% block title %}View and Edit Group{% endblock %} +{% block css %} + + + +{% endblock %} +{% block content %} + +
    + {% if 'full_name' in user_details %} + + {% endif %} + +
    +
    +
    +
    + +
    + {% if 'email_address' in user_details %}{{ user_details.email_address }}{% else %}N/A{% endif %} +
    +
    +
    + +
    + {% if 'full_name' in user_details %}{{ user_details.full_name }}{% else %}N/A{% endif %} + +
    +
    +
    + +
    + {% if 'organization' in user_details %}{{ user_details.organization }}{% else %}N/A{% endif %} + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    + + + +{% endblock %} + +{% block js %} + + + +{% endblock %} -- cgit v1.2.3 From 0b16990d169718d2aab6139c36655e042fb0f234 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 27 Aug 2020 15:13:14 -0500 Subject: Added function for sending an invitation e-mail * wqflask/wqflask/user_login.py - Added send_invitation_email function, which does what it says --- wqflask/wqflask/user_login.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wqflask/wqflask/user_login.py b/wqflask/wqflask/user_login.py index cfee0079..9331f369 100644 --- a/wqflask/wqflask/user_login.py +++ b/wqflask/wqflask/user_login.py @@ -139,6 +139,12 @@ def send_verification_email(user_details, template_name = "email/user_verificati send_email(recipient, subject, body) return {"recipient": recipient, "subject": subject, "body": body} +def send_invitation_email(user_email, temp_password, template_name = "email/user_invitation.txt", subject = "You've been added to a GeneNetwork user group"): + recipient = user_email + body = render_template(template_name, temp_password) + send_email(recipient, subject, body) + return {"recipient": recipient, "subject": subject, "body": body} + @app.route("/manage/verify_email") def verify_email(): if 'code' in request.args: -- cgit v1.2.3 From dbc07d3f16b7c5337747af40a4d24c2ef27d2145 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 27 Aug 2020 15:15:05 -0500 Subject: Added logic that creates a user account if an e-mail is added to a group that isn't already associated with a GN2 account and sends the user an e-mail; still need to test * wqflask/wqflask/group_manager.py - Added logic sending an invitation e-mail if user_details aren't found for any of the e-mails provided when adding users to a group --- wqflask/wqflask/group_manager.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/group_manager.py b/wqflask/wqflask/group_manager.py index 24848ed8..99d5db26 100644 --- a/wqflask/wqflask/group_manager.py +++ b/wqflask/wqflask/group_manager.py @@ -1,13 +1,15 @@ from __future__ import print_function, division, absolute_import +import random, string + from flask import (Flask, g, render_template, url_for, request, make_response, redirect, flash) from wqflask import app -from wqflask.user_login import send_verification_email +from wqflask.user_login import send_verification_email, send_invitation_email, basic_info, set_password -from utility.redis_tools import get_user_groups, get_group_info, create_group, delete_group, add_users_to_group, remove_users_from_group, \ +from utility.redis_tools import get_user_groups, get_group_info, save_user, create_group, delete_group, add_users_to_group, remove_users_from_group, \ change_group_name, save_verification_code, check_verification_code, get_user_by_unique_column, get_resources, get_resource_info from utility.logger import getLogger @@ -141,5 +143,16 @@ def send_group_invites(group_id, user_email_list = [], user_type="members"): continue else: send_verification_email(user_details, template_name = "email/group_verification.txt", key_prefix = "verification_code", subject = "You've been invited to join a GeneNetwork user group") + else: + temp_password = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)) + user_details = { + 'user_id': str(uuid.uuid4()), + 'email_address': user_email, + 'registration_info': basic_info(), + 'password': set_password(temp_password), + 'confirmed': 0 + } + save_user(user_details, user_details['user_id']) + send_invitation_email(user_email, temp_password) #@app.route() \ No newline at end of file -- cgit v1.2.3 From 2425530b4b1c8f907d94cfadbd375c578e72ae40 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 20 May 2020 17:20:59 +0300 Subject: Delete dataTables and it's extensions from git * wqflask/wqflask/static/new/packages/DataTables/: Delete folder. --- .../new/packages/DataTables/css/demo_page.css | 122 - .../new/packages/DataTables/css/demo_table.css | 577 - .../new/packages/DataTables/css/demo_table_jui.css | 501 - .../packages/DataTables/css/jquery.dataTables.css | 471 - .../DataTables/css/jquery.dataTables.min.css | 1 - .../css/jquery.dataTables_themeroller.css | 416 - .../DataTables/extensions/buttons.bootstrap.css | 89 - .../DataTables/extensions/buttons.colVis.min.js | 5 - .../DataTables/extensions/buttons.dataTables.css | 297 - .../extensions/dataTables.buttons.min.js | 35 - .../DataTables/extensions/dataTables.colReorder.js | 1372 -- .../DataTables/extensions/dataTables.colResize.js | 846 - .../DataTables/extensions/dataTables.colResize2.js | 943 -- .../extensions/dataTables.fixedHeader.css | 4 - .../extensions/dataTables.fixedHeader.min.js | 14 - .../extensions/scroller.dataTables.min.css | 1 - .../packages/DataTables/images/Sorting icons.psd | Bin 27490 -> 0 bytes .../packages/DataTables/images/back_disabled.png | Bin 1361 -> 0 bytes .../packages/DataTables/images/back_enabled.png | Bin 1379 -> 0 bytes .../DataTables/images/back_enabled_hover.png | Bin 1375 -> 0 bytes .../new/packages/DataTables/images/favicon.ico | Bin 894 -> 0 bytes .../DataTables/images/forward_disabled.png | Bin 1363 -> 0 bytes .../packages/DataTables/images/forward_enabled.png | Bin 1380 -> 0 bytes .../DataTables/images/forward_enabled_hover.png | Bin 1379 -> 0 bytes .../new/packages/DataTables/images/sort_asc.png | Bin 1118 -> 0 bytes .../DataTables/images/sort_asc_disabled.png | Bin 1050 -> 0 bytes .../new/packages/DataTables/images/sort_both.png | Bin 1136 -> 0 bytes .../new/packages/DataTables/images/sort_desc.png | Bin 1127 -> 0 bytes .../DataTables/images/sort_desc_disabled.png | Bin 1045 -> 0 bytes .../DataTables/js/dataTables.formattedNumbers.js | 14 - .../DataTables/js/dataTables.naturalSort.js | 68 - .../DataTables/js/dataTables.scientific.js | 13 - .../packages/DataTables/js/jquery.dataTables.js | 15278 ------------------- .../DataTables/js/jquery.dataTables.min.js | 166 - .../static/new/packages/DataTables/js/jquery.js | 5 - .../new/packages/DataTables/src/DataTables.js | 284 - .../packages/DataTables/src/api/api.internal.js | 130 - .../new/packages/DataTables/src/api/api.methods.js | 1294 -- .../new/packages/DataTables/src/api/api.static.js | 98 - .../new/packages/DataTables/src/core/core.ajax.js | 185 - .../packages/DataTables/src/core/core.columns.js | 373 - .../DataTables/src/core/core.constructor.js | 428 - .../new/packages/DataTables/src/core/core.data.js | 634 - .../new/packages/DataTables/src/core/core.draw.js | 792 - .../packages/DataTables/src/core/core.filter.js | 399 - .../new/packages/DataTables/src/core/core.info.js | 111 - .../new/packages/DataTables/src/core/core.init.js | 153 - .../packages/DataTables/src/core/core.length.js | 122 - .../new/packages/DataTables/src/core/core.page.js | 119 - .../DataTables/src/core/core.processing.js | 44 - .../packages/DataTables/src/core/core.scrolling.js | 511 - .../packages/DataTables/src/core/core.sizing.js | 403 - .../new/packages/DataTables/src/core/core.sort.js | 451 - .../new/packages/DataTables/src/core/core.state.js | 216 - .../packages/DataTables/src/core/core.support.js | 346 - .../new/packages/DataTables/src/ext/ext.classes.js | 112 - .../new/packages/DataTables/src/ext/ext.paging.js | 262 - .../new/packages/DataTables/src/ext/ext.sorting.js | 86 - .../new/packages/DataTables/src/ext/ext.types.js | 88 - .../packages/DataTables/src/model/model.column.js | 263 - .../DataTables/src/model/model.defaults.columns.js | 759 - .../DataTables/src/model/model.defaults.js | 1952 --- .../new/packages/DataTables/src/model/model.ext.js | 528 - .../new/packages/DataTables/src/model/model.row.js | 64 - .../packages/DataTables/src/model/model.search.js | 40 - .../DataTables/src/model/model.settings.js | 882 -- .../packages/DataTables/unit_testing/controller.js | 94 - .../DataTables/unit_testing/controller.php | 100 - .../packages/DataTables/unit_testing/index.html | 7 - .../DataTables/unit_testing/performance/draw.html | 482 - .../DataTables/unit_testing/performance/large.php | 108 - .../DataTables/unit_testing/performance/page.html | 477 - .../DataTables/unit_testing/performance/sort.html | 477 - .../unit_testing/templates/-complex_header.php | 469 - .../DataTables/unit_testing/templates/2512.php | 464 - .../DataTables/unit_testing/templates/6776.php | 116 - .../unit_testing/templates/complex_header_2.php | 485 - .../unit_testing/templates/deferred_table.php | 132 - .../DataTables/unit_testing/templates/dom_data.php | 465 - .../unit_testing/templates/dom_data_th.php | 465 - .../templates/dom_data_two_headers.php | 472 - .../unit_testing/templates/dymanic_table.php | 45 - .../unit_testing/templates/empty_table.php | 55 - .../unit_testing/templates/html_table.php | 66 - .../DataTables/unit_testing/templates/js_data.php | 124 - .../unit_testing/templates/js_data_mixed_types.php | 124 - .../unit_testing/templates/two_tables.php | 227 - .../unit_testing/tests/1_dom/_zero_config.js | 437 - .../tests_onhold/1_dom/-complex_header.js | 52 - .../unit_testing/tests_onhold/1_dom/-iDraw.js | 41 - .../unit_testing/tests_onhold/1_dom/2512.js | 17 - .../unit_testing/tests_onhold/1_dom/2530-2.js | 15 - .../unit_testing/tests_onhold/1_dom/2530.js | 29 - .../unit_testing/tests_onhold/1_dom/2569.js | 36 - .../unit_testing/tests_onhold/1_dom/2600.js | 44 - .../unit_testing/tests_onhold/1_dom/2608.js | 54 - .../unit_testing/tests_onhold/1_dom/2635.js | 40 - .../tests_onhold/1_dom/2746-stable-sort.js | 199 - .../unit_testing/tests_onhold/1_dom/2799.js | 14 - .../tests_onhold/1_dom/2840-restore-table-width.js | 19 - .../tests_onhold/1_dom/2914-state-save-sort.js | 39 - .../tests_onhold/1_dom/5396-fnUpdate-arrays.js | 103 - .../1_dom/5508-xscroll-zero-content.js | 23 - .../1_dom/6776-scrolling-table-grows.js | 64 - .../tests_onhold/1_dom/_zero_config.js | 437 - .../unit_testing/tests_onhold/1_dom/aaSorting.js | 183 - .../tests_onhold/1_dom/aaSortingFixed.js | 60 - .../tests_onhold/1_dom/aoColumns.bSearchable.js | 67 - .../tests_onhold/1_dom/aoColumns.bSortable.js | 105 - .../tests_onhold/1_dom/aoColumns.bUseRendered.js | 145 - .../tests_onhold/1_dom/aoColumns.bVisible.js | 132 - .../tests_onhold/1_dom/aoColumns.bVisible2.js | 268 - .../tests_onhold/1_dom/aoColumns.fnRender.js | 176 - .../tests_onhold/1_dom/aoColumns.iDataSort.js | 88 - .../tests_onhold/1_dom/aoColumns.sClass.js | 111 - .../tests_onhold/1_dom/aoColumns.sName.js | 27 - .../tests_onhold/1_dom/aoColumns.sTitle.js | 78 - .../tests_onhold/1_dom/aoColumns.sWidth.js | 84 - .../tests_onhold/1_dom/aoSearchCols.js | 112 - .../tests_onhold/1_dom/asStripClasses.js | 106 - .../unit_testing/tests_onhold/1_dom/bAutoWidth.js | 138 - .../unit_testing/tests_onhold/1_dom/bFilter.js | 40 - .../tests_onhold/1_dom/bInfiniteScroll.js | 130 - .../unit_testing/tests_onhold/1_dom/bInfo.js | 40 - .../unit_testing/tests_onhold/1_dom/bJQueryUI.js | 40 - .../tests_onhold/1_dom/bLengthChange.js | 71 - .../unit_testing/tests_onhold/1_dom/bPaginate.js | 55 - .../unit_testing/tests_onhold/1_dom/bProcessing.js | 99 - .../unit_testing/tests_onhold/1_dom/bServerSide.js | 18 - .../unit_testing/tests_onhold/1_dom/bSort.js | 101 - .../tests_onhold/1_dom/bSortCellsTop.js | 77 - .../tests_onhold/1_dom/bSortClasses.js | 128 - .../tests_onhold/1_dom/fnCookieCallback.js | 97 - .../tests_onhold/1_dom/fnCreatedCell.js | 151 - .../tests_onhold/1_dom/fnCreatedRow.js | 115 - .../unit_testing/tests_onhold/1_dom/fnDeleteRow.js | 30 - .../tests_onhold/1_dom/fnDrawCallback.js | 80 - .../unit_testing/tests_onhold/1_dom/fnFilter.js | 16 - .../tests_onhold/1_dom/fnFooterCallback.js | 227 - .../tests_onhold/1_dom/fnHeaderCallback.js | 227 - .../tests_onhold/1_dom/fnInfoCallback.js | 115 - .../tests_onhold/1_dom/fnInitComplete.js | 94 - .../tests_onhold/1_dom/fnRowCallback.js | 105 - .../tests_onhold/1_dom/fnSetColumnVis.js | 120 - .../tests_onhold/1_dom/fnSetColumnVis2.js | 236 - .../tests_onhold/1_dom/html-autodetect-sort.js | 57 - .../tests_onhold/1_dom/iDisplayLength.js | 76 - .../tests_onhold/1_dom/oLanguage.oPaginate.js | 80 - .../tests_onhold/1_dom/oLanguage.sInfo.js | 109 - .../tests_onhold/1_dom/oLanguage.sInfoEmpty.js | 75 - .../tests_onhold/1_dom/oLanguage.sInfoPostFix.js | 73 - .../tests_onhold/1_dom/oLanguage.sLengthMenu.js | 106 - .../tests_onhold/1_dom/oLanguage.sProcessing.js | 47 - .../tests_onhold/1_dom/oLanguage.sSearch.js | 66 - .../tests_onhold/1_dom/oLanguage.sUrl.js | 59 - .../tests_onhold/1_dom/oLanguage.sZeroRecords.js | 45 - .../unit_testing/tests_onhold/1_dom/oSearch.js | 101 - .../unit_testing/tests_onhold/1_dom/sAjaxSource.js | 18 - .../unit_testing/tests_onhold/1_dom/sDom.js | 319 - .../tests_onhold/1_dom/sPaginationType.js | 122 - .../unit_testing/tests_onhold/1_dom/sScrollXY.js | 63 - .../unit_testing/tests_onhold/1_dom/th_in_body.js | 437 - .../2_js/6872-default-content-missing-props.js | 285 - .../2_js/8549--string-sorting-nonstrings.js | 47 - .../unit_testing/tests_onhold/2_js/_zero_config.js | 440 - .../unit_testing/tests_onhold/2_js/aaSorting.js | 198 - .../tests_onhold/2_js/aaSortingFixed.js | 64 - .../tests_onhold/2_js/aoColumns.bSearchable.js | 71 - .../tests_onhold/2_js/aoColumns.bSortable.js | 109 - .../tests_onhold/2_js/aoColumns.bUseRendered.js | 148 - .../tests_onhold/2_js/aoColumns.bVisible.js | 110 - .../tests_onhold/2_js/aoColumns.fnRender.js | 156 - .../tests_onhold/2_js/aoColumns.iDataSort.js | 90 - .../tests_onhold/2_js/aoColumns.sClass.js | 115 - .../tests_onhold/2_js/aoColumns.sName.js | 28 - .../tests_onhold/2_js/aoColumns.sTitle.js | 82 - .../tests_onhold/2_js/aoColumns.sWidth.js | 87 - .../unit_testing/tests_onhold/2_js/aoSearchCols.js | 119 - .../tests_onhold/2_js/asStripClasses.js | 100 - .../unit_testing/tests_onhold/2_js/bAutoWidth.js | 142 - .../unit_testing/tests_onhold/2_js/bFilter.js | 44 - .../unit_testing/tests_onhold/2_js/bInfo.js | 44 - .../tests_onhold/2_js/bLengthChange.js | 75 - .../unit_testing/tests_onhold/2_js/bPaginate.js | 59 - .../unit_testing/tests_onhold/2_js/bProcessing.js | 103 - .../unit_testing/tests_onhold/2_js/bServerSide.js | 20 - .../unit_testing/tests_onhold/2_js/bSort.js | 99 - .../unit_testing/tests_onhold/2_js/bSortClasses.js | 132 - .../tests_onhold/2_js/fnCreatedCell.js | 158 - .../unit_testing/tests_onhold/2_js/fnCreatedRow.js | 121 - .../tests_onhold/2_js/fnDrawCallback.js | 85 - .../tests_onhold/2_js/fnFooterCallback.js | 240 - .../tests_onhold/2_js/fnHeaderCallback.js | 240 - .../tests_onhold/2_js/fnInitComplete.js | 83 - .../tests_onhold/2_js/fnRowCallback.js | 112 - .../tests_onhold/2_js/iDisplayLength.js | 81 - .../tests_onhold/2_js/js_data_mixed_types.js | 392 - .../tests_onhold/2_js/oLanguage.oPaginate.js | 84 - .../tests_onhold/2_js/oLanguage.sInfo.js | 117 - .../tests_onhold/2_js/oLanguage.sInfoEmpty.js | 79 - .../tests_onhold/2_js/oLanguage.sInfoPostFix.js | 78 - .../tests_onhold/2_js/oLanguage.sLengthMenu.js | 111 - .../tests_onhold/2_js/oLanguage.sProcessing.js | 49 - .../tests_onhold/2_js/oLanguage.sSearch.js | 70 - .../tests_onhold/2_js/oLanguage.sUrl.js | 62 - .../tests_onhold/2_js/oLanguage.sZeroRecords.js | 48 - .../unit_testing/tests_onhold/2_js/oSearch.js | 108 - .../unit_testing/tests_onhold/2_js/sAjaxSource.js | 20 - .../unit_testing/tests_onhold/2_js/sDom.js | 262 - .../tests_onhold/2_js/sPaginationType.js | 125 - .../tests_onhold/3_ajax/_zero_config.js | 440 - .../unit_testing/tests_onhold/3_ajax/aaSorting.js | 198 - .../tests_onhold/3_ajax/aaSortingFixed.js | 67 - .../tests_onhold/3_ajax/aoColumns.bSearchable.js | 76 - .../tests_onhold/3_ajax/aoColumns.bSortable.js | 109 - .../tests_onhold/3_ajax/aoColumns.bUseRendered.js | 148 - .../tests_onhold/3_ajax/aoColumns.bVisible.js | 124 - .../tests_onhold/3_ajax/aoColumns.fnRender.js | 156 - .../tests_onhold/3_ajax/aoColumns.iDataSort.js | 90 - .../tests_onhold/3_ajax/aoColumns.sClass.js | 115 - .../tests_onhold/3_ajax/aoColumns.sName.js | 28 - .../tests_onhold/3_ajax/aoColumns.sTitle.js | 82 - .../tests_onhold/3_ajax/aoColumns.sWidth.js | 87 - .../tests_onhold/3_ajax/aoSearchCols.js | 119 - .../tests_onhold/3_ajax/asStripClasses.js | 105 - .../unit_testing/tests_onhold/3_ajax/bAutoWidth.js | 142 - .../unit_testing/tests_onhold/3_ajax/bFilter.js | 44 - .../unit_testing/tests_onhold/3_ajax/bInfo.js | 44 - .../tests_onhold/3_ajax/bLengthChange.js | 75 - .../unit_testing/tests_onhold/3_ajax/bPaginate.js | 59 - .../tests_onhold/3_ajax/bProcessing.js | 103 - .../tests_onhold/3_ajax/bServerSide.js | 20 - .../unit_testing/tests_onhold/3_ajax/bSort.js | 99 - .../tests_onhold/3_ajax/bSortClasses.js | 132 - .../tests_onhold/3_ajax/fnCreatedCell.js | 183 - .../tests_onhold/3_ajax/fnCreatedRow.js | 142 - .../tests_onhold/3_ajax/fnDrawCallback.js | 98 - .../tests_onhold/3_ajax/fnHeaderCallback.js | 191 - .../tests_onhold/3_ajax/fnInitComplete.js | 100 - .../tests_onhold/3_ajax/fnRowCallback.js | 112 - .../tests_onhold/3_ajax/fnServerData.js | 64 - .../tests_onhold/3_ajax/iDisplayLength.js | 81 - .../tests_onhold/3_ajax/oLanguage.oPaginate.js | 84 - .../tests_onhold/3_ajax/oLanguage.sInfo.js | 117 - .../tests_onhold/3_ajax/oLanguage.sInfoEmpty.js | 79 - .../tests_onhold/3_ajax/oLanguage.sInfoPostFix.js | 78 - .../tests_onhold/3_ajax/oLanguage.sLengthMenu.js | 111 - .../3_ajax/oLanguage.sLoadingRecords.js | 65 - .../tests_onhold/3_ajax/oLanguage.sProcessing.js | 49 - .../tests_onhold/3_ajax/oLanguage.sSearch.js | 70 - .../tests_onhold/3_ajax/oLanguage.sUrl.js | 62 - .../tests_onhold/3_ajax/oLanguage.sZeroRecords.js | 48 - .../unit_testing/tests_onhold/3_ajax/oSearch.js | 108 - .../tests_onhold/3_ajax/sAjaxDataProp.js | 139 - .../tests_onhold/3_ajax/sAjaxDataProp2.js | 139 - .../tests_onhold/3_ajax/sAjaxSource.js | 22 - .../unit_testing/tests_onhold/3_ajax/sDom.js | 262 - .../tests_onhold/3_ajax/sPaginationType.js | 134 - .../tests_onhold/4_server-side/-iDraw.js | 44 - .../tests_onhold/4_server-side/2440.js | 32 - .../tests_onhold/4_server-side/2569.js | 47 - .../tests_onhold/4_server-side/2600.js | 47 - .../tests_onhold/4_server-side/_zero_config.js | 424 - .../tests_onhold/4_server-side/aaSorting.js | 212 - .../tests_onhold/4_server-side/aaSortingFixed.js | 67 - .../4_server-side/aoColumns.bSearchable.js | 25 - .../4_server-side/aoColumns.bSortable.js | 112 - .../4_server-side/aoColumns.bUseRendered.js | 43 - .../4_server-side/aoColumns.bVisible.js | 123 - .../4_server-side/aoColumns.fnRender.js | 162 - .../tests_onhold/4_server-side/aoColumns.sClass.js | 118 - .../tests_onhold/4_server-side/aoColumns.sName.js | 29 - .../tests_onhold/4_server-side/aoColumns.sTitle.js | 85 - .../tests_onhold/4_server-side/aoColumns.sWidth.js | 90 - .../tests_onhold/4_server-side/aoSearchCols.js | 70 - .../tests_onhold/4_server-side/asStripClasses.js | 109 - .../tests_onhold/4_server-side/bAutoWidth.js | 145 - .../tests_onhold/4_server-side/bFilter.js | 47 - .../tests_onhold/4_server-side/bInfiniteScroll.js | 168 - .../tests_onhold/4_server-side/bInfo.js | 47 - .../tests_onhold/4_server-side/bLengthChange.js | 78 - .../tests_onhold/4_server-side/bPaginate.js | 62 - .../tests_onhold/4_server-side/bProcessing.js | 106 - .../tests_onhold/4_server-side/bServerSide.js | 21 - .../tests_onhold/4_server-side/bSort.js | 102 - .../tests_onhold/4_server-side/bSortClasses.js | 135 - .../tests_onhold/4_server-side/fnCreatedCell.js | 190 - .../tests_onhold/4_server-side/fnCreatedRow.js | 148 - .../tests_onhold/4_server-side/fnDrawCallback.js | 89 - .../tests_onhold/4_server-side/fnHeaderCallback.js | 191 - .../tests_onhold/4_server-side/fnInitComplete.js | 89 - .../tests_onhold/4_server-side/fnRowCallback.js | 118 - .../tests_onhold/4_server-side/iDeferLoading.js | 95 - .../tests_onhold/4_server-side/iDisplayLength.js | 85 - .../4_server-side/oLanguage.oPaginate.js | 86 - .../tests_onhold/4_server-side/oLanguage.sInfo.js | 124 - .../4_server-side/oLanguage.sInfoEmpty.js | 82 - .../4_server-side/oLanguage.sInfoPostFix.js | 82 - .../4_server-side/oLanguage.sLengthMenu.js | 115 - .../4_server-side/oLanguage.sProcessing.js | 51 - .../4_server-side/oLanguage.sSearch.js | 73 - .../tests_onhold/4_server-side/oLanguage.sUrl.js | 64 - .../4_server-side/oLanguage.sZeroRecords.js | 58 - .../tests_onhold/4_server-side/oSearch.js | 100 - .../tests_onhold/4_server-side/sAjaxDataProp.js | 146 - .../tests_onhold/4_server-side/sAjaxSource.js | 23 - .../tests_onhold/4_server-side/sDom.js | 269 - .../tests_onhold/4_server-side/sPaginationType.js | 138 - .../tests_onhold/5_ajax_objects/_zero_config.js | 847 - .../_zero_config_arrays_subobjects.js | 961 -- .../5_ajax_objects/_zero_config_deep.js | 1075 -- .../5_ajax_objects/_zero_config_null_source.js | 458 - .../5_ajax_objects/_zero_config_objects.js | 847 - .../_zero_config_objects_subarrays.js | 961 -- .../tests_onhold/5_ajax_objects/aaSorting.js | 296 - .../tests_onhold/5_ajax_objects/aaSortingFixed.js | 88 - .../5_ajax_objects/aoColumns.bSearchable.js | 83 - .../5_ajax_objects/aoColumns.bSortable.js | 116 - .../5_ajax_objects/aoColumns.bUseRendered.js | 155 - .../5_ajax_objects/aoColumns.bVisible.js | 131 - .../5_ajax_objects/aoColumns.fnRender.js | 177 - .../5_ajax_objects/aoColumns.iDataSort.js | 90 - .../5_ajax_objects/aoColumns.sClass.js | 122 - .../tests_onhold/5_ajax_objects/aoColumns.sName.js | 28 - .../5_ajax_objects/aoColumns.sTitle.js | 89 - .../5_ajax_objects/aoColumns.sWidth.js | 87 - .../tests_onhold/5_ajax_objects/aoSearchCols.js | 161 - .../tests_onhold/5_ajax_objects/asStripClasses.js | 133 - .../tests_onhold/5_ajax_objects/bAutoWidth.js | 163 - .../tests_onhold/5_ajax_objects/bFilter.js | 65 - .../tests_onhold/5_ajax_objects/bInfo.js | 65 - .../tests_onhold/5_ajax_objects/bLengthChange.js | 96 - .../tests_onhold/5_ajax_objects/bPaginate.js | 80 - .../tests_onhold/5_ajax_objects/bProcessing.js | 124 - .../tests_onhold/5_ajax_objects/bServerSide.js | 27 - .../tests_onhold/5_ajax_objects/bSort.js | 120 - .../tests_onhold/5_ajax_objects/bSortClasses.js | 153 - .../tests_onhold/5_ajax_objects/fnDrawCallback.js | 126 - .../5_ajax_objects/fnHeaderCallback.js | 254 - .../tests_onhold/5_ajax_objects/fnInitComplete.js | 135 - .../tests_onhold/5_ajax_objects/fnRowCallback.js | 154 - .../tests_onhold/5_ajax_objects/fnServerData.js | 92 - .../tests_onhold/5_ajax_objects/iDisplayLength.js | 109 - .../5_ajax_objects/oLanguage.oPaginate.js | 98 - .../tests_onhold/5_ajax_objects/oLanguage.sInfo.js | 166 - .../5_ajax_objects/oLanguage.sInfoEmpty.js | 100 - .../5_ajax_objects/oLanguage.sInfoPostFix.js | 106 - .../5_ajax_objects/oLanguage.sLengthMenu.js | 139 - .../5_ajax_objects/oLanguage.sProcessing.js | 63 - .../5_ajax_objects/oLanguage.sSearch.js | 91 - .../tests_onhold/5_ajax_objects/oLanguage.sUrl.js | 76 - .../5_ajax_objects/oLanguage.sZeroRecords.js | 62 - .../tests_onhold/5_ajax_objects/oSearch.js | 150 - .../tests_onhold/5_ajax_objects/sAjaxSource.js | 29 - .../tests_onhold/5_ajax_objects/sDom.js | 311 - .../tests_onhold/5_ajax_objects/sPaginationType.js | 148 - .../6_delayed_rendering/_zero_config.js | 403 - .../tests_onhold/6_delayed_rendering/aaSorting.js | 212 - .../6_delayed_rendering/aaSortingFixed.js | 70 - .../6_delayed_rendering/aoColumns.bSearchable.js | 79 - .../6_delayed_rendering/aoColumns.bSortable.js | 112 - .../6_delayed_rendering/aoColumns.bUseRendered.js | 151 - .../6_delayed_rendering/aoColumns.bVisible.js | 127 - .../6_delayed_rendering/aoColumns.fnRender.js | 190 - .../6_delayed_rendering/aoColumns.iDataSort.js | 92 - .../6_delayed_rendering/aoColumns.sClass.js | 118 - .../6_delayed_rendering/aoColumns.sName.js | 29 - .../6_delayed_rendering/aoColumns.sTitle.js | 85 - .../6_delayed_rendering/aoColumns.sWidth.js | 90 - .../6_delayed_rendering/aoSearchCols.js | 125 - .../6_delayed_rendering/asStripClasses.js | 109 - .../tests_onhold/6_delayed_rendering/bAutoWidth.js | 145 - .../tests_onhold/6_delayed_rendering/bFilter.js | 47 - .../tests_onhold/6_delayed_rendering/bInfo.js | 47 - .../6_delayed_rendering/bLengthChange.js | 78 - .../tests_onhold/6_delayed_rendering/bPaginate.js | 62 - .../6_delayed_rendering/bProcessing.js | 106 - .../6_delayed_rendering/bServerSide.js | 21 - .../tests_onhold/6_delayed_rendering/bSort.js | 102 - .../6_delayed_rendering/bSortClasses.js | 135 - .../6_delayed_rendering/fnDrawCallback.js | 102 - .../6_delayed_rendering/fnHeaderCallback.js | 200 - .../6_delayed_rendering/fnInitComplete.js | 105 - .../6_delayed_rendering/fnRowCallback.js | 118 - .../6_delayed_rendering/fnServerData.js | 68 - .../6_delayed_rendering/iDisplayLength.js | 85 - .../6_delayed_rendering/oLanguage.oPaginate.js | 86 - .../6_delayed_rendering/oLanguage.sInfo.js | 124 - .../6_delayed_rendering/oLanguage.sInfoEmpty.js | 82 - .../6_delayed_rendering/oLanguage.sInfoPostFix.js | 82 - .../6_delayed_rendering/oLanguage.sLengthMenu.js | 115 - .../6_delayed_rendering/oLanguage.sProcessing.js | 51 - .../6_delayed_rendering/oLanguage.sSearch.js | 73 - .../6_delayed_rendering/oLanguage.sUrl.js | 64 - .../6_delayed_rendering/oLanguage.sZeroRecords.js | 50 - .../tests_onhold/6_delayed_rendering/oSearch.js | 114 - .../6_delayed_rendering/sAjaxDataProp.js | 140 - .../6_delayed_rendering/sAjaxDataProp2.js | 140 - .../6_delayed_rendering/sAjaxSource.js | 23 - .../tests_onhold/6_delayed_rendering/sDom.js | 269 - .../6_delayed_rendering/sPaginationType.js | 136 - .../packages/DataTables/unit_testing/unit_test.js | 409 - 402 files changed, 78265 deletions(-) delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/css/demo_page.css delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/css/demo_table.css delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/css/demo_table_jui.css delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables.css delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables.min.css delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables_themeroller.css delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.bootstrap.css delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.colVis.min.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.dataTables.css delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.buttons.min.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.colReorder.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.colResize.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.colResize2.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.fixedHeader.css delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.fixedHeader.min.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/extensions/scroller.dataTables.min.css delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/Sorting icons.psd delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/back_disabled.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/back_enabled.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/back_enabled_hover.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/favicon.ico delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/forward_disabled.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/forward_enabled.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/forward_enabled_hover.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/sort_asc.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/sort_asc_disabled.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/sort_both.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/sort_desc.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/images/sort_desc_disabled.png delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/js/dataTables.formattedNumbers.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/js/dataTables.naturalSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/js/dataTables.scientific.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/js/jquery.dataTables.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/js/jquery.dataTables.min.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/js/jquery.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/DataTables.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/api/api.internal.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/api/api.methods.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/api/api.static.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.ajax.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.columns.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.constructor.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.data.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.draw.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.filter.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.info.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.init.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.length.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.page.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.processing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.scrolling.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.sizing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.sort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.state.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/core/core.support.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/ext/ext.classes.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/ext/ext.paging.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/ext/ext.sorting.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/ext/ext.types.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/model/model.column.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/model/model.defaults.columns.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/model/model.defaults.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/model/model.ext.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/model/model.row.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/model/model.search.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/src/model/model.settings.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/controller.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/controller.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/index.html delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/performance/draw.html delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/performance/large.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/performance/page.html delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/performance/sort.html delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/-complex_header.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/2512.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/6776.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/complex_header_2.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/deferred_table.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/dom_data.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/dom_data_th.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/dom_data_two_headers.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/dymanic_table.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/empty_table.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/html_table.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/js_data.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/js_data_mixed_types.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/templates/two_tables.php delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests/1_dom/_zero_config.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/-complex_header.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/-iDraw.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2512.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2530-2.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2530.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2569.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2600.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2608.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2635.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2746-stable-sort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2799.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2840-restore-table-width.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/2914-state-save-sort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/5396-fnUpdate-arrays.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/5508-xscroll-zero-content.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/6776-scrolling-table-grows.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/_zero_config.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aaSorting.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aaSortingFixed.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.bSearchable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.bSortable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.bUseRendered.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.bVisible.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.bVisible2.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.fnRender.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.iDataSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.sClass.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.sName.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.sTitle.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoColumns.sWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/aoSearchCols.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/asStripClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bAutoWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bFilter.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bInfiniteScroll.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bJQueryUI.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bLengthChange.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bServerSide.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bSortCellsTop.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/bSortClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnCookieCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnCreatedCell.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnCreatedRow.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnDeleteRow.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnDrawCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnFilter.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnFooterCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnHeaderCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnInfoCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnInitComplete.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnRowCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnSetColumnVis.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/fnSetColumnVis2.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/html-autodetect-sort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/iDisplayLength.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/oLanguage.oPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/oLanguage.sInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/oLanguage.sInfoEmpty.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/oLanguage.sInfoPostFix.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/oLanguage.sLengthMenu.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/oLanguage.sProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/oLanguage.sSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/oLanguage.sUrl.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/oLanguage.sZeroRecords.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/oSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/sAjaxSource.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/sDom.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/sPaginationType.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/sScrollXY.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/1_dom/th_in_body.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/6872-default-content-missing-props.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/8549--string-sorting-nonstrings.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/_zero_config.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aaSorting.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aaSortingFixed.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoColumns.bSearchable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoColumns.bSortable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoColumns.bUseRendered.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoColumns.bVisible.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoColumns.fnRender.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoColumns.iDataSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoColumns.sClass.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoColumns.sName.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoColumns.sTitle.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoColumns.sWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/aoSearchCols.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/asStripClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/bAutoWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/bFilter.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/bInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/bLengthChange.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/bPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/bProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/bServerSide.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/bSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/bSortClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/fnCreatedCell.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/fnCreatedRow.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/fnDrawCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/fnFooterCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/fnHeaderCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/fnInitComplete.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/fnRowCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/iDisplayLength.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/js_data_mixed_types.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/oLanguage.oPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/oLanguage.sInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/oLanguage.sInfoEmpty.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/oLanguage.sInfoPostFix.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/oLanguage.sLengthMenu.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/oLanguage.sProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/oLanguage.sSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/oLanguage.sUrl.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/oLanguage.sZeroRecords.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/oSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/sAjaxSource.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/sDom.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/2_js/sPaginationType.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/_zero_config.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aaSorting.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aaSortingFixed.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoColumns.bSearchable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoColumns.bSortable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoColumns.bUseRendered.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoColumns.bVisible.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoColumns.fnRender.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoColumns.iDataSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoColumns.sClass.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoColumns.sName.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoColumns.sTitle.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoColumns.sWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/aoSearchCols.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/asStripClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/bAutoWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/bFilter.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/bInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/bLengthChange.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/bPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/bProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/bServerSide.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/bSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/bSortClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/fnCreatedCell.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/fnCreatedRow.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/fnDrawCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/fnHeaderCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/fnInitComplete.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/fnRowCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/fnServerData.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/iDisplayLength.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oLanguage.oPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oLanguage.sInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oLanguage.sInfoEmpty.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oLanguage.sInfoPostFix.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oLanguage.sLengthMenu.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oLanguage.sLoadingRecords.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oLanguage.sProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oLanguage.sSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oLanguage.sUrl.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oLanguage.sZeroRecords.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/oSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/sAjaxDataProp.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/sAjaxDataProp2.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/sAjaxSource.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/sDom.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/3_ajax/sPaginationType.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/-iDraw.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/2440.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/2569.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/2600.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/_zero_config.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aaSorting.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aaSortingFixed.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aoColumns.bSearchable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aoColumns.bSortable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aoColumns.bUseRendered.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aoColumns.bVisible.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aoColumns.fnRender.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aoColumns.sClass.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aoColumns.sName.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aoColumns.sTitle.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aoColumns.sWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/aoSearchCols.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/asStripClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/bAutoWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/bFilter.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/bInfiniteScroll.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/bInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/bLengthChange.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/bPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/bProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/bServerSide.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/bSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/bSortClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/fnCreatedCell.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/fnCreatedRow.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/fnDrawCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/fnHeaderCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/fnInitComplete.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/fnRowCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/iDeferLoading.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/iDisplayLength.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/oLanguage.oPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/oLanguage.sInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/oLanguage.sInfoEmpty.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/oLanguage.sInfoPostFix.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/oLanguage.sLengthMenu.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/oLanguage.sProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/oLanguage.sSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/oLanguage.sUrl.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/oLanguage.sZeroRecords.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/oSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/sAjaxDataProp.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/sAjaxSource.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/sDom.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/4_server-side/sPaginationType.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/_zero_config.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/_zero_config_arrays_subobjects.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/_zero_config_deep.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/_zero_config_null_source.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/_zero_config_objects.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/_zero_config_objects_subarrays.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aaSorting.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aaSortingFixed.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoColumns.bSearchable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoColumns.bSortable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoColumns.bUseRendered.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoColumns.bVisible.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoColumns.fnRender.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoColumns.iDataSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoColumns.sClass.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoColumns.sName.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoColumns.sTitle.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoColumns.sWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/aoSearchCols.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/asStripClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/bAutoWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/bFilter.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/bInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/bLengthChange.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/bPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/bProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/bServerSide.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/bSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/bSortClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/fnDrawCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/fnHeaderCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/fnInitComplete.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/fnRowCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/fnServerData.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/iDisplayLength.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/oLanguage.oPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/oLanguage.sInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/oLanguage.sInfoEmpty.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/oLanguage.sInfoPostFix.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/oLanguage.sLengthMenu.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/oLanguage.sProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/oLanguage.sSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/oLanguage.sUrl.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/oLanguage.sZeroRecords.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/oSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/sAjaxSource.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/sDom.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/5_ajax_objects/sPaginationType.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/_zero_config.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aaSorting.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aaSortingFixed.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoColumns.bSearchable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoColumns.bSortable.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoColumns.bUseRendered.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoColumns.bVisible.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoColumns.fnRender.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoColumns.iDataSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoColumns.sClass.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoColumns.sName.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoColumns.sTitle.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoColumns.sWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/aoSearchCols.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/asStripClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/bAutoWidth.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/bFilter.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/bInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/bLengthChange.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/bPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/bProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/bServerSide.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/bSort.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/bSortClasses.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/fnDrawCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/fnHeaderCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/fnInitComplete.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/fnRowCallback.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/fnServerData.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/iDisplayLength.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/oLanguage.oPaginate.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/oLanguage.sInfo.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/oLanguage.sInfoEmpty.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/oLanguage.sInfoPostFix.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/oLanguage.sLengthMenu.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/oLanguage.sProcessing.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/oLanguage.sSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/oLanguage.sUrl.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/oLanguage.sZeroRecords.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/oSearch.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/sAjaxDataProp.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/sAjaxDataProp2.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/sAjaxSource.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/sDom.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/tests_onhold/6_delayed_rendering/sPaginationType.js delete mode 100644 wqflask/wqflask/static/new/packages/DataTables/unit_testing/unit_test.js diff --git a/wqflask/wqflask/static/new/packages/DataTables/css/demo_page.css b/wqflask/wqflask/static/new/packages/DataTables/css/demo_page.css deleted file mode 100644 index ba5b2a6c..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/css/demo_page.css +++ /dev/null @@ -1,122 +0,0 @@ - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * General page setup - */ -#dt_example { - font: 80%/1.45em "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; - margin: 0; - padding: 0; - color: #333; - background-color: #fff; -} - - -#dt_example #container { - width: 800px; - margin: 30px auto; - padding: 0; -} - - -#dt_example #footer { - margin: 50px auto 0 auto; - padding: 0; -} - -#dt_example #demo { - margin: 30px auto 0 auto; -} - -#dt_example .demo_jui { - margin: 30px auto 0 auto; -} - -#dt_example .big { - font-size: 1.3em; - font-weight: bold; - line-height: 1.6em; - color: #4E6CA3; -} - -#dt_example .spacer { - height: 20px; - clear: both; -} - -#dt_example .clear { - clear: both; -} - -#dt_example pre { - padding: 15px; - background-color: #F5F5F5; - border: 1px solid #CCCCCC; -} - -#dt_example h1 { - margin-top: 2em; - font-size: 1.3em; - font-weight: normal; - line-height: 1.6em; - color: #4E6CA3; - border-bottom: 1px solid #B0BED9; - clear: both; -} - -#dt_example h2 { - font-size: 1.2em; - font-weight: normal; - line-height: 1.6em; - color: #4E6CA3; - clear: both; -} - -#dt_example a { - color: #0063DC; - text-decoration: none; -} - -#dt_example a:hover { - text-decoration: underline; -} - -#dt_example ul { - color: #4E6CA3; -} - -.css_right { - float: right; -} - -.css_left { - float: left; -} - -.demo_links { - float: left; - width: 50%; - margin-bottom: 1em; -} - -#demo_info { - padding: 5px; - border: 1px solid #B0BED9; - height: 100px; - width: 100%; - overflow: auto; -} - -#dt_example code { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - padding: 2px 4px !important; - white-space: nowrap; - font-size: 0.9em; - - color: #D14; - background-color: #F7F7F9; - - border: 1px solid #E1E1E8; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} diff --git a/wqflask/wqflask/static/new/packages/DataTables/css/demo_table.css b/wqflask/wqflask/static/new/packages/DataTables/css/demo_table.css deleted file mode 100644 index 12f352da..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/css/demo_table.css +++ /dev/null @@ -1,577 +0,0 @@ -/* - * File: demo_table.css - * CVS: $Id$ - * Description: CSS descriptions for DataTables demo pages - * Author: Allan Jardine - * Created: Tue May 12 06:47:22 BST 2009 - * Modified: $Date$ by $Author$ - * Language: CSS - * Project: DataTables - * - * Copyright 2009 Allan Jardine. All Rights Reserved. - * - * *************************************************************************** - * DESCRIPTION - * - * The styles given here are suitable for the demos that are used with the standard DataTables - * distribution (see www.datatables.net). You will most likely wish to modify these styles to - * meet the layout requirements of your site. - * - * Common issues: - * 'full_numbers' pagination - I use an extra selector on the body tag to ensure that there is - * no conflict between the two pagination types. If you want to use full_numbers pagination - * ensure that you either have "example_alt_pagination" as a body class name, or better yet, - * modify that selector. - * Note that the path used for Images is relative. All images are by default located in - * ../images/ - relative to this CSS file. - */ - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables features - */ - -.dataTables_wrapper { - position: relative; - clear: both; - zoom: 1; /* Feeling sorry for IE */ -} - -.dataTables_processing { - position: absolute; - top: 50%; - left: 50%; - width: 250px; - height: 30px; - margin-left: -125px; - margin-top: -15px; - padding: 14px 0 2px 0; - border: 1px solid #ddd; - text-align: center; - color: #999; - font-size: 14px; - background-color: white; -} - -.dataTables_length { - width: 40%; - float: left; -} - -.dataTables_filter { - width: 50%; - float: right; - text-align: right; -} - -.dataTables_info { - width: 60%; - float: left; -} - -.dataTables_paginate { - float: right; - text-align: right; -} - -/* Pagination nested */ -.paginate_disabled_previous, .paginate_enabled_previous, -.paginate_disabled_next, .paginate_enabled_next { - height: 19px; - float: left; - cursor: pointer; - *cursor: hand; - color: #111 !important; -} -.paginate_disabled_previous:hover, .paginate_enabled_previous:hover, -.paginate_disabled_next:hover, .paginate_enabled_next:hover { - text-decoration: none !important; -} -.paginate_disabled_previous:active, .paginate_enabled_previous:active, -.paginate_disabled_next:active, .paginate_enabled_next:active { - outline: none; -} - -.paginate_disabled_previous, -.paginate_disabled_next { - color: #666 !important; -} -.paginate_disabled_previous, .paginate_enabled_previous { - padding-left: 23px; -} -.paginate_disabled_next, .paginate_enabled_next { - padding-right: 23px; - margin-left: 10px; -} - -.paginate_disabled_previous { - background: url('../images/back_disabled.png') no-repeat top left; -} - -.paginate_enabled_previous { - background: url('../images/back_enabled.png') no-repeat top left; -} -.paginate_enabled_previous:hover { - background: url('../images/back_enabled_hover.png') no-repeat top left; -} - -.paginate_disabled_next { - background: url('../images/forward_disabled.png') no-repeat top right; -} - -.paginate_enabled_next { - background: url('../images/forward_enabled.png') no-repeat top right; -} -.paginate_enabled_next:hover { - background: url('../images/forward_enabled_hover.png') no-repeat top right; -} - - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables display - */ -table.display { - margin: 0 auto; - clear: both; - width: 100%; - - /* Note Firefox 3.5 and before have a bug with border-collapse - * ( https://bugzilla.mozilla.org/show%5Fbug.cgi?id=155955 ) - * border-spacing: 0; is one possible option. Conditional-css.com is - * useful for this kind of thing - * - * Further note IE 6/7 has problems when calculating widths with border width. - * It subtracts one px relative to the other browsers from the first column, and - * adds one to the end... - * - * If you want that effect I'd suggest setting a border-top/left on th/td's and - * then filling in the gaps with other borders. - */ -} - -table.display thead th { - padding: 3px 18px 3px 10px; - border-bottom: 1px solid black; - font-weight: bold; - cursor: pointer; - * cursor: hand; -} - -table.display tfoot th { - padding: 3px 18px 3px 10px; - border-top: 1px solid black; - font-weight: bold; -} - -table.display tr.heading2 td { - border-bottom: 1px solid #aaa; -} - -table.display td { - padding: 3px 10px; -} - -table.display td.center { - text-align: center; -} - - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables sorting - */ - -.sorting_asc { - background: url('../images/sort_asc.png') no-repeat center right; -} - -.sorting_desc { - background: url('../images/sort_desc.png') no-repeat center right; -} - -.sorting { - background: url('../images/sort_both.png') no-repeat center right; -} - -.sorting_asc_disabled { - background: url('../images/sort_asc_disabled.png') no-repeat center right; -} - -.sorting_desc_disabled { - background: url('../images/sort_desc_disabled.png') no-repeat center right; -} - -table.display thead th:active, -table.display thead td:active { - outline: none; -} - - - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables row classes - */ -table.display tr.odd.gradeA { - background-color: #ddffdd; -} - -table.display tr.even.gradeA { - background-color: #eeffee; -} - -table.display tr.odd.gradeC { - background-color: #ddddff; -} - -table.display tr.even.gradeC { - background-color: #eeeeff; -} - -table.display tr.odd.gradeX { - background-color: #ffdddd; -} - -table.display tr.even.gradeX { - background-color: #ffeeee; -} - -table.display tr.odd.gradeU { - background-color: #ddd; -} - -table.display tr.even.gradeU { - background-color: #eee; -} - - -tr.odd { - background-color: #E2E4FF; -} - -tr.even { - background-color: white; -} - - - - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Misc - */ -.dataTables_scroll { - clear: both; -} - -.dataTables_scrollBody { - *margin-top: -1px; - -webkit-overflow-scrolling: touch; -} - -.top, .bottom { - padding: 15px; - background-color: #F5F5F5; - border: 1px solid #CCCCCC; -} - -.top .dataTables_info { - float: none; -} - -.clear { - clear: both; -} - -.dataTables_empty { - text-align: center; -} - -tfoot input { - margin: 0.5em 0; - width: 100%; - color: #444; -} - -tfoot input.search_init { - color: #999; -} - -td.group { - background-color: #d1cfd0; - border-bottom: 2px solid #A19B9E; - border-top: 2px solid #A19B9E; -} - -td.details { - background-color: #d1cfd0; - border: 2px solid #A19B9E; -} - - -.example_alt_pagination div.dataTables_info { - width: 40%; -} - -.paging_full_numbers { - width: 400px; - height: 22px; - line-height: 22px; -} - -.paging_full_numbers a:active { - outline: none -} - -.paging_full_numbers a:hover { - text-decoration: none; -} - -.paging_full_numbers a.paginate_button, - .paging_full_numbers a.paginate_active { - border: 1px solid #aaa; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - padding: 2px 5px; - margin: 0 3px; - cursor: pointer; - *cursor: hand; - color: #333 !important; -} - -.paging_full_numbers a.paginate_button { - background-color: #ddd; -} - -.paging_full_numbers a.paginate_button:hover { - background-color: #ccc; - text-decoration: none !important; -} - -.paging_full_numbers a.paginate_active { - background-color: #99B3FF; -} - -table.display tr.even.row_selected td { - background-color: #B0BED9; -} - -table.display tr.odd.row_selected td { - background-color: #9FAFD1; -} - - -/* - * Sorting classes for columns - */ -/* For the standard odd/even */ -tr.odd td.sorting_1 { - background-color: #D3D6FF; -} - -tr.odd td.sorting_2 { - background-color: #DADCFF; -} - -tr.odd td.sorting_3 { - background-color: #E0E2FF; -} - -tr.even td.sorting_1 { - background-color: #EAEBFF; -} - -tr.even td.sorting_2 { - background-color: #F2F3FF; -} - -tr.even td.sorting_3 { - background-color: #F9F9FF; -} - - -/* For the Conditional-CSS grading rows */ -/* - Colour calculations (based off the main row colours) - Level 1: - dd > c4 - ee > d5 - Level 2: - dd > d1 - ee > e2 - */ -tr.odd.gradeA td.sorting_1 { - background-color: #c4ffc4; -} - -tr.odd.gradeA td.sorting_2 { - background-color: #d1ffd1; -} - -tr.odd.gradeA td.sorting_3 { - background-color: #d1ffd1; -} - -tr.even.gradeA td.sorting_1 { - background-color: #d5ffd5; -} - -tr.even.gradeA td.sorting_2 { - background-color: #e2ffe2; -} - -tr.even.gradeA td.sorting_3 { - background-color: #e2ffe2; -} - -tr.odd.gradeC td.sorting_1 { - background-color: #c4c4ff; -} - -tr.odd.gradeC td.sorting_2 { - background-color: #d1d1ff; -} - -tr.odd.gradeC td.sorting_3 { - background-color: #d1d1ff; -} - -tr.even.gradeC td.sorting_1 { - background-color: #d5d5ff; -} - -tr.even.gradeC td.sorting_2 { - background-color: #e2e2ff; -} - -tr.even.gradeC td.sorting_3 { - background-color: #e2e2ff; -} - -tr.odd.gradeX td.sorting_1 { - background-color: #ffc4c4; -} - -tr.odd.gradeX td.sorting_2 { - background-color: #ffd1d1; -} - -tr.odd.gradeX td.sorting_3 { - background-color: #ffd1d1; -} - -tr.even.gradeX td.sorting_1 { - background-color: #ffd5d5; -} - -tr.even.gradeX td.sorting_2 { - background-color: #ffe2e2; -} - -tr.even.gradeX td.sorting_3 { - background-color: #ffe2e2; -} - -tr.odd.gradeU td.sorting_1 { - background-color: #c4c4c4; -} - -tr.odd.gradeU td.sorting_2 { - background-color: #d1d1d1; -} - -tr.odd.gradeU td.sorting_3 { - background-color: #d1d1d1; -} - -tr.even.gradeU td.sorting_1 { - background-color: #d5d5d5; -} - -tr.even.gradeU td.sorting_2 { - background-color: #e2e2e2; -} - -tr.even.gradeU td.sorting_3 { - background-color: #e2e2e2; -} - - -/* - * Row highlighting example - */ -.ex_highlight #example tbody tr.even:hover, #example tbody tr.even td.highlighted { - background-color: #ECFFB3; -} - -.ex_highlight #example tbody tr.odd:hover, #example tbody tr.odd td.highlighted { - background-color: #E6FF99; -} - -.ex_highlight_row #example tr.even:hover { - background-color: #ECFFB3; -} - -.ex_highlight_row #example tr.even:hover td.sorting_1 { - background-color: #DDFF75; -} - -.ex_highlight_row #example tr.even:hover td.sorting_2 { - background-color: #E7FF9E; -} - -.ex_highlight_row #example tr.even:hover td.sorting_3 { - background-color: #E2FF89; -} - -.ex_highlight_row #example tr.odd:hover { - background-color: #E6FF99; -} - -.ex_highlight_row #example tr.odd:hover td.sorting_1 { - background-color: #D6FF5C; -} - -.ex_highlight_row #example tr.odd:hover td.sorting_2 { - background-color: #E0FF84; -} - -.ex_highlight_row #example tr.odd:hover td.sorting_3 { - background-color: #DBFF70; -} - - -/* - * KeyTable - */ -table.KeyTable td { - border: 3px solid transparent; -} - -table.KeyTable td.focus { - border: 3px solid #3366FF; -} - -table.display tr.gradeA { - background-color: #eeffee; -} - -table.display tr.gradeC { - background-color: #ddddff; -} - -table.display tr.gradeX { - background-color: #ffdddd; -} - -table.display tr.gradeU { - background-color: #ddd; -} - -div.box { - height: 100px; - padding: 10px; - overflow: auto; - border: 1px solid #8080FF; - background-color: #E5E5FF; -} diff --git a/wqflask/wqflask/static/new/packages/DataTables/css/demo_table_jui.css b/wqflask/wqflask/static/new/packages/DataTables/css/demo_table_jui.css deleted file mode 100644 index a210af51..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/css/demo_table_jui.css +++ /dev/null @@ -1,501 +0,0 @@ -/* - * File: demo_table_jui.css - * CVS: $Id$ - * Description: CSS descriptions for DataTables demo pages - * Author: Allan Jardine - * Created: Tue May 12 06:47:22 BST 2009 - * Modified: $Date$ by $Author$ - * Language: CSS - * Project: DataTables - * - * Copyright 2009 Allan Jardine. All Rights Reserved. - * - * *************************************************************************** - * DESCRIPTION - * - * The styles given here are suitable for the demos that are used with the standard DataTables - * distribution (see www.datatables.net). You will most likely wish to modify these styles to - * meet the layout requirements of your site. - * - * Common issues: - * 'full_numbers' pagination - I use an extra selector on the body tag to ensure that there is - * no conflict between the two pagination types. If you want to use full_numbers pagination - * ensure that you either have "example_alt_pagination" as a body class name, or better yet, - * modify that selector. - * Note that the path used for Images is relative. All images are by default located in - * ../images/ - relative to this CSS file. - */ - - -/* - * jQuery UI specific styling - */ - -.paging_two_button .ui-button { - float: left; - cursor: pointer; - * cursor: hand; -} - -.paging_full_numbers .ui-button { - padding: 2px 6px; - margin: 0; - cursor: pointer; - * cursor: hand; - color: #333 !important; -} - -.dataTables_paginate .ui-button { - margin-right: -0.1em !important; -} - -.paging_full_numbers { - width: 350px !important; -} - -.dataTables_wrapper .ui-toolbar { - padding: 5px; -} - -.dataTables_paginate { - width: auto; -} - -.dataTables_info { - padding-top: 3px; -} - -table.display thead th { - padding: 3px 0px 3px 10px; - cursor: pointer; - * cursor: hand; -} - -div.dataTables_wrapper .ui-widget-header { - font-weight: normal; -} - - -/* - * Sort arrow icon positioning - */ -table.display thead th div.DataTables_sort_wrapper { - position: relative; - padding-right: 20px; -} - -table.display thead th div.DataTables_sort_wrapper span { - position: absolute; - top: 50%; - margin-top: -8px; - right: 0; -} - - - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * Everything below this line is the same as demo_table.css. This file is - * required for 'cleanliness' of the markup - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables features - */ - -.dataTables_wrapper { - position: relative; - clear: both; -} - -.dataTables_processing { - position: absolute; - top: 0px; - left: 50%; - width: 250px; - margin-left: -125px; - border: 1px solid #ddd; - text-align: center; - color: #999; - font-size: 11px; - padding: 2px 0; -} - -.dataTables_length { - width: 40%; - float: left; -} - -.dataTables_filter { - width: 50%; - float: right; - text-align: right; -} - -.dataTables_info { - width: 50%; - float: left; -} - -.dataTables_paginate { - float: right; - text-align: right; -} - - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables display - */ -table.display { - margin: 0 auto; - width: 100%; - clear: both; - border-collapse: collapse; -} - -table.display tfoot th { - padding: 3px 0px 3px 10px; - font-weight: bold; - font-weight: normal; -} - -table.display tr.heading2 td { - border-bottom: 1px solid #aaa; -} - -table.display td { - padding: 3px 10px; -} - -table.display td.center { - text-align: center; -} - - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables sorting - */ - -.sorting_asc { - background: url('../images/sort_asc.png') no-repeat center right; -} - -.sorting_desc { - background: url('../images/sort_desc.png') no-repeat center right; -} - -.sorting { - background: url('../images/sort_both.png') no-repeat center right; -} - -.sorting_asc_disabled { - background: url('../images/sort_asc_disabled.png') no-repeat center right; -} - -.sorting_desc_disabled { - background: url('../images/sort_desc_disabled.png') no-repeat center right; -} - - - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables row classes - */ -table.display tr.odd.gradeA { - background-color: #ddffdd; -} - -table.display tr.even.gradeA { - background-color: #eeffee; -} - - - - -table.display tr.odd.gradeA { - background-color: #ddffdd; -} - -table.display tr.even.gradeA { - background-color: #eeffee; -} - -table.display tr.odd.gradeC { - background-color: #ddddff; -} - -table.display tr.even.gradeC { - background-color: #eeeeff; -} - -table.display tr.odd.gradeX { - background-color: #ffdddd; -} - -table.display tr.even.gradeX { - background-color: #ffeeee; -} - -table.display tr.odd.gradeU { - background-color: #ddd; -} - -table.display tr.even.gradeU { - background-color: #eee; -} - - -tr.odd { - background-color: #E2E4FF; -} - -tr.even { - background-color: white; -} - - - - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Misc - */ -.dataTables_scroll { - clear: both; -} - -.dataTables_scrollBody { - -webkit-overflow-scrolling: touch; -} - -.top, .bottom { - padding: 15px; - background-color: #F5F5F5; - border: 1px solid #CCCCCC; -} - -.top .dataTables_info { - float: none; -} - -.clear { - clear: both; -} - -.dataTables_empty { - text-align: center; -} - -tfoot input { - margin: 0.5em 0; - width: 100%; - color: #444; -} - -tfoot input.search_init { - color: #999; -} - -td.group { - background-color: #d1cfd0; - border-bottom: 2px solid #A19B9E; - border-top: 2px solid #A19B9E; -} - -td.details { - background-color: #d1cfd0; - border: 2px solid #A19B9E; -} - - -.example_alt_pagination div.dataTables_info { - width: 40%; -} - -.paging_full_numbers a.paginate_button, - .paging_full_numbers a.paginate_active { - border: 1px solid #aaa; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - padding: 2px 5px; - margin: 0 3px; - cursor: pointer; - *cursor: hand; - color: #333 !important; -} - -.paging_full_numbers a.paginate_button { - background-color: #ddd; -} - -.paging_full_numbers a.paginate_button:hover { - background-color: #ccc; - text-decoration: none !important; -} - -.paging_full_numbers a.paginate_active { - background-color: #99B3FF; -} - -table.display tr.even.row_selected td { - background-color: #B0BED9; -} - -table.display tr.odd.row_selected td { - background-color: #9FAFD1; -} - - -/* - * Sorting classes for columns - */ -/* For the standard odd/even */ -tr.odd td.sorting_1 { - background-color: #D3D6FF; -} - -tr.odd td.sorting_2 { - background-color: #DADCFF; -} - -tr.odd td.sorting_3 { - background-color: #E0E2FF; -} - -tr.even td.sorting_1 { - background-color: #EAEBFF; -} - -tr.even td.sorting_2 { - background-color: #F2F3FF; -} - -tr.even td.sorting_3 { - background-color: #F9F9FF; -} - - -/* For the Conditional-CSS grading rows */ -/* - Colour calculations (based off the main row colours) - Level 1: - dd > c4 - ee > d5 - Level 2: - dd > d1 - ee > e2 - */ -tr.odd.gradeA td.sorting_1 { - background-color: #c4ffc4; -} - -tr.odd.gradeA td.sorting_2 { - background-color: #d1ffd1; -} - -tr.odd.gradeA td.sorting_3 { - background-color: #d1ffd1; -} - -tr.even.gradeA td.sorting_1 { - background-color: #d5ffd5; -} - -tr.even.gradeA td.sorting_2 { - background-color: #e2ffe2; -} - -tr.even.gradeA td.sorting_3 { - background-color: #e2ffe2; -} - -tr.odd.gradeC td.sorting_1 { - background-color: #c4c4ff; -} - -tr.odd.gradeC td.sorting_2 { - background-color: #d1d1ff; -} - -tr.odd.gradeC td.sorting_3 { - background-color: #d1d1ff; -} - -tr.even.gradeC td.sorting_1 { - background-color: #d5d5ff; -} - -tr.even.gradeC td.sorting_2 { - background-color: #e2e2ff; -} - -tr.even.gradeC td.sorting_3 { - background-color: #e2e2ff; -} - -tr.odd.gradeX td.sorting_1 { - background-color: #ffc4c4; -} - -tr.odd.gradeX td.sorting_2 { - background-color: #ffd1d1; -} - -tr.odd.gradeX td.sorting_3 { - background-color: #ffd1d1; -} - -tr.even.gradeX td.sorting_1 { - background-color: #ffd5d5; -} - -tr.even.gradeX td.sorting_2 { - background-color: #ffe2e2; -} - -tr.even.gradeX td.sorting_3 { - background-color: #ffe2e2; -} - -tr.odd.gradeU td.sorting_1 { - background-color: #c4c4c4; -} - -tr.odd.gradeU td.sorting_2 { - background-color: #d1d1d1; -} - -tr.odd.gradeU td.sorting_3 { - background-color: #d1d1d1; -} - -tr.even.gradeU td.sorting_1 { - background-color: #d5d5d5; -} - -tr.even.gradeU td.sorting_2 { - background-color: #e2e2e2; -} - -tr.even.gradeU td.sorting_3 { - background-color: #e2e2e2; -} - - -/* - * Row highlighting example - */ -.ex_highlight #example tbody tr.even:hover, #example tbody tr.even td.highlighted { - background-color: #ECFFB3; -} - -.ex_highlight #example tbody tr.odd:hover, #example tbody tr.odd td.highlighted { - background-color: #E6FF99; -} \ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables.css b/wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables.css deleted file mode 100644 index f376d15e..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables.css +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Table styles - */ -table.dataTable { - //width: 100%; - margin: 0 auto; - clear: both; - border-collapse: separate; - border-spacing: 0; - /* - * Header and footer styles - */ - /* - * Body styles - */ -} -td.highlight { - background-color: whitesmoke !important; -} -table.dataTable thead th, -table.dataTable tfoot th { - font-weight: bold; -} -table.dataTable thead th, -table.dataTable thead td { - font: bold 12px/20px Arial, Sans-serif; - color: #000000; - background-color: #ffffff; - border-collapse: collapse; - border-bottom: #cccccc 2px solid; - //padding: 0; - padding: 10px 18px 4px 10px; - //border-bottom: 1px solid #111; -} -table.dataTable thead th:active, -table.dataTable thead td:active { - outline: none; -} -table.dataTable tfoot th, -table.dataTable tfoot td { - font: bold 12px/20px Arial, Sans-serif; - color: #000000; - background-color: #ffffff; - border-collapse: collapse; - border-top: #cccccc 2px solid; - //padding: 0; - padding: 10px 18px 6px 18px; - //border-top: 1px solid #111; -} -table.dataTable thead .sorting, -table.dataTable thead .sorting_asc, -table.dataTable thead .sorting_desc { - cursor: pointer; - *cursor: hand; -} -table.dataTable thead .sorting, -table.dataTable thead .sorting_asc, -table.dataTable thead .sorting_desc, -table.dataTable thead .sorting_asc_disabled, -table.dataTable thead .sorting_desc_disabled { - background-repeat: no-repeat; - background-position: center right; -} -table.dataTable thead .sorting { - background-image: url("../images/sort_both.png"); -} -table.dataTable thead .sorting_asc { - background-image: url("../images/sort_asc.png"); -} -table.dataTable thead .sorting_desc { - background-image: url("../images/sort_desc.png"); -} -table.dataTable thead .sorting_asc_disabled { - background-image: url("../images/sort_asc_disabled.png"); -} -table.dataTable thead .sorting_desc_disabled { - background-image: url("../images/sort_desc_disabled.png"); -} -table.dataTable tbody tr { - background-color: #ffffff; -} -table.dataTable tbody tr.selected { - background-color: #ffee99; -} -table.dataTable tbody th, -table.dataTable tbody td { - font: 12px Arial, Sans-serif; - padding: 8px 20px 6px 10px; -} -table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { - border-top: 1px solid #ddd; -} -table.dataTable.row-border tbody tr:first-child th, -table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, -table.dataTable.display tbody tr:first-child td { - border-top: none; -} -table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { - border-top: 1px solid #ddd; - border-right: 1px solid #ddd; -} -table.dataTable.cell-border tbody tr th:first-child, -table.dataTable.cell-border tbody tr td:first-child { - border-left: 1px solid #ddd; -} -table.dataTable.cell-border tbody tr:first-child th, -table.dataTable.cell-border tbody tr:first-child td { - border-top: none; -} -table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd { - background-color: #f9f9f9; -} -table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected { - background-color: #FFEE99; -} -table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover { - background-color: whitesmoke; -} -table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected { - background-color: #FFEE99; -} -table.dataTable.order-column tbody tr > .sorting_1, -table.dataTable.order-column tbody tr > .sorting_2, -table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1, -table.dataTable.display tbody tr > .sorting_2, -table.dataTable.display tbody tr > .sorting_3 { - background-color: #f9f9f9; -} -table.dataTable.order-column tbody tr.selected > .sorting_1, -table.dataTable.order-column tbody tr.selected > .sorting_2, -table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1, -table.dataTable.display tbody tr.selected > .sorting_2, -table.dataTable.display tbody tr.selected > .sorting_3 { - background-color: #acbad4; -} -table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 { - background-color: #f1f1f1; -} -table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 { - background-color: #f3f3f3; -} -table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 { - background-color: whitesmoke; -} -table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 { - background-color: #ffe047; -} -table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 { - background-color: #a7b5ce; -} -table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 { - background-color: #a9b6d0; -} -table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 { - background-color: #f9f9f9; -} -table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 { - background-color: #fbfbfb; -} -table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 { - background-color: #fdfdfd; -} -table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 { - background-color: #ffe047; -} -table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 { - background-color: #adbbd6; -} -table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 { - background-color: #afbdd8; -} -table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { - background-color: #eaeaea; -} -table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { - background-color: #ebebeb; -} -table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { - background-color: #eeeeee; -} -table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { - background-color: #ffe047; -} -table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { - background-color: #a2afc8; -} -table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { - background-color: #a4b2cb; -} -table.dataTable.no-footer { - border-bottom: 1px solid #111; -} -table.dataTable.nowrap th, table.dataTable.nowrap td { - white-space: nowrap; -} -table.dataTable.compact thead th, -table.dataTable.compact thead td { - padding: 4px 17px 4px 4px; -} -table.dataTable.compact tfoot th, -table.dataTable.compact tfoot td { - padding: 4px; -} -table.dataTable.compact tbody th, -table.dataTable.compact tbody td { - padding: 4px; -} -table.dataTable th.dt-left, -table.dataTable td.dt-left { - text-align: left; -} -table.dataTable th.dt-center, -table.dataTable td.dt-center, -table.dataTable td.dataTables_empty { - text-align: center; -} -table.dataTable th.dt-right, -table.dataTable td.dt-right { - text-align: right; -} -table.dataTable th.dt-justify, -table.dataTable td.dt-justify { - text-align: justify; -} -table.dataTable th.dt-nowrap, -table.dataTable td.dt-nowrap { - white-space: nowrap; -} -table.dataTable thead th.dt-head-left, -table.dataTable thead td.dt-head-left, -table.dataTable tfoot th.dt-head-left, -table.dataTable tfoot td.dt-head-left { - text-align: left; -} -table.dataTable thead th.dt-head-center, -table.dataTable thead td.dt-head-center, -table.dataTable tfoot th.dt-head-center, -table.dataTable tfoot td.dt-head-center { - text-align: center; -} -table.dataTable thead th.dt-head-right, -table.dataTable thead td.dt-head-right, -table.dataTable tfoot th.dt-head-right, -table.dataTable tfoot td.dt-head-right { - text-align: right; -} -table.dataTable thead th.dt-head-justify, -table.dataTable thead td.dt-head-justify, -table.dataTable tfoot th.dt-head-justify, -table.dataTable tfoot td.dt-head-justify { - text-align: justify; -} -table.dataTable thead th.dt-head-nowrap, -table.dataTable thead td.dt-head-nowrap, -table.dataTable tfoot th.dt-head-nowrap, -table.dataTable tfoot td.dt-head-nowrap { - white-space: nowrap; -} -table.dataTable tbody th.dt-body-left, -table.dataTable tbody td.dt-body-left { - text-align: left; -} -table.dataTable tbody th.dt-body-center, -table.dataTable tbody td.dt-body-center { - text-align: center; -} -table.dataTable tbody th.dt-body-right, -table.dataTable tbody td.dt-body-right { - text-align: right; -} -table.dataTable tbody th.dt-body-justify, -table.dataTable tbody td.dt-body-justify { - text-align: justify; -} -table.dataTable tbody th.dt-body-nowrap, -table.dataTable tbody td.dt-body-nowrap { - white-space: nowrap; -} - -table.dataTable, -table.dataTable th, -table.dataTable td { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -/* - * Control feature layout - */ -.dataTables_wrapper { - position: relative; - clear: both; - *zoom: 1; - zoom: 1; -} -.dataTables_wrapper .dataTables_length { - float: left; -} -.dataTables_wrapper .dataTables_filter { - float: right; - text-align: right; -} -.dataTables_wrapper .dataTables_filter input { - margin-left: 0.5em; -} -.dataTables_wrapper .dataTables_info { - clear: both; - float: left; - padding-top: 0.755em; -} -.dataTables_wrapper .dataTables_paginate { - float: right; - text-align: right; - padding-top: 0.25em; -} -.dataTables_wrapper .dataTables_paginate .paginate_button { - box-sizing: border-box; - display: inline-block; - min-width: 1.5em; - padding: 0.5em 1em; - margin-left: 2px; - text-align: center; - text-decoration: none !important; - cursor: pointer; - *cursor: hand; - color: #333 !important; - border: 1px solid transparent; -} -.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { - color: #333 !important; - border: 1px solid #cacaca; - background-color: white; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc)); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%); - /* IE10+ */ - background: -o-linear-gradient(top, white 0%, #dcdcdc 100%); - /* Opera 11.10+ */ - background: linear-gradient(to bottom, white 0%, #dcdcdc 100%); - /* W3C */ -} -.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { - cursor: default; - color: #666 !important; - border: 1px solid transparent; - background: transparent; - box-shadow: none; -} -.dataTables_wrapper .dataTables_paginate .paginate_button:hover { - color: white !important; - border: 1px solid #111; - background-color: #585858; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #585858 0%, #111 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(top, #585858 0%, #111 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(top, #585858 0%, #111 100%); - /* IE10+ */ - background: -o-linear-gradient(top, #585858 0%, #111 100%); - /* Opera 11.10+ */ - background: linear-gradient(to bottom, #585858 0%, #111 100%); - /* W3C */ -} -.dataTables_wrapper .dataTables_paginate .paginate_button:active { - outline: none; - background-color: #2b2b2b; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* IE10+ */ - background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); - /* Opera 11.10+ */ - background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); - /* W3C */ - box-shadow: inset 0 0 3px #111; -} -.dataTables_wrapper .dataTables_paginate .ellipsis { - padding: 0 1em; -} -.dataTables_wrapper .dataTables_processing { - position: absolute; - top: 50%; - left: 50%; - width: 100%; - height: 40px; - margin-left: -50%; - margin-top: -25px; - padding-top: 20px; - text-align: center; - font-size: 1.2em; - background-color: white; - background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - /* IE10+ */ - background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - /* Opera 11.10+ */ - background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - /* W3C */ -} -.dataTables_wrapper .dataTables_length, -.dataTables_wrapper .dataTables_filter, -.dataTables_wrapper .dataTables_info, -.dataTables_wrapper .dataTables_processing, -.dataTables_wrapper .dataTables_paginate { - color: #333; -} -.dataTables_wrapper .dataTables_scroll { - clear: both; -} -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { - *margin-top: -1px; - -webkit-overflow-scrolling: touch; -} -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th > div.dataTables_sizing, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td > div.dataTables_sizing { - height: 0; - overflow: hidden; - margin: 0 !important; - padding: 0 !important; -} -.dataTables_wrapper.no-footer .dataTables_scrollBody { - border-bottom: 1px solid #111; -} -.dataTables_wrapper.no-footer div.dataTables_scrollHead table, -.dataTables_wrapper.no-footer div.dataTables_scrollBody table { - border-bottom: none; -} -.dataTables_wrapper:after { - visibility: hidden; - display: block; - content: ""; - clear: both; - height: 0; -} - -@media screen and (max-width: 767px) { - .dataTables_wrapper .dataTables_info, - .dataTables_wrapper .dataTables_paginate { - float: none; - text-align: center; - } - .dataTables_wrapper .dataTables_paginate { - margin-top: 0.5em; - } -} -@media screen and (max-width: 640px) { - .dataTables_wrapper .dataTables_length, - .dataTables_wrapper .dataTables_filter { - float: none; - text-align: center; - } - .dataTables_wrapper .dataTables_filter { - margin-top: 0.5em; - } -} diff --git a/wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables.min.css b/wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables.min.css deleted file mode 100644 index 2edb32f7..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables.min.css +++ /dev/null @@ -1 +0,0 @@ -table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc{cursor:pointer;*cursor:hand}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("../images/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("../images/sort_asc.png")}table.dataTable thead .sorting_desc{background-image:url("../images/sort_desc.png")}table.dataTable thead .sorting_asc_disabled{background-image:url("../images/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("../images/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#fff}table.dataTable tbody tr.selected{background-color:#B0BED9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#abb9d3}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f5f5f5}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#a9b7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#f9f9f9}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad4}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:#f5f5f5}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b3cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a7b5ce}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b6d0}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#f9f9f9}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fbfbfb}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fdfdfd}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad4}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#adbbd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ebebeb}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#eee}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a1aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a2afc8}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a4b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:0.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:0.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:0.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:0.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #cacaca;background-color:#fff;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-o-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:linear-gradient(to bottom, #fff 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0)));background:-webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table,.dataTables_wrapper.no-footer div.dataTables_scrollBody table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:0.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:0.5em}} diff --git a/wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables_themeroller.css b/wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables_themeroller.css deleted file mode 100644 index 1426a44a..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/css/jquery.dataTables_themeroller.css +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Table styles - */ -table.dataTable { - width: 100%; - margin: 0 auto; - clear: both; - border-collapse: separate; - border-spacing: 0; - /* - * Header and footer styles - */ - /* - * Body styles - */ -} -table.dataTable thead th, -table.dataTable thead td, -table.dataTable tfoot th, -table.dataTable tfoot td { - padding: 4px 10px; -} -table.dataTable thead th, -table.dataTable tfoot th { - font-weight: bold; -} -table.dataTable thead th:active, -table.dataTable thead td:active { - outline: none; -} -table.dataTable thead .sorting_asc, -table.dataTable thead .sorting_desc, -table.dataTable thead .sorting { - cursor: pointer; - *cursor: hand; -} -table.dataTable thead th div.DataTables_sort_wrapper { - position: relative; - padding-right: 10px; -} -table.dataTable thead th div.DataTables_sort_wrapper span { - position: absolute; - top: 50%; - margin-top: -8px; - right: -5px; -} -table.dataTable thead th.ui-state-default { - border-right-width: 0; -} -table.dataTable thead th.ui-state-default:last-child { - border-right-width: 1px; -} -table.dataTable tbody tr { - background-color: #ffffff; -} -table.dataTable tbody tr.selected { - background-color: #B0BED9; -} -table.dataTable tbody th, -table.dataTable tbody td { - padding: 8px 10px; -} -table.dataTable th.center, -table.dataTable td.center, -table.dataTable td.dataTables_empty { - text-align: center; -} -table.dataTable th.right, -table.dataTable td.right { - text-align: right; -} -table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { - border-top: 1px solid #ddd; -} -table.dataTable.row-border tbody tr:first-child th, -table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, -table.dataTable.display tbody tr:first-child td { - border-top: none; -} -table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { - border-top: 1px solid #ddd; - border-right: 1px solid #ddd; -} -table.dataTable.cell-border tbody tr th:first-child, -table.dataTable.cell-border tbody tr td:first-child { - border-left: 1px solid #ddd; -} -table.dataTable.cell-border tbody tr:first-child th, -table.dataTable.cell-border tbody tr:first-child td { - border-top: none; -} -table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd { - background-color: #f9f9f9; -} -table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected { - background-color: #abb9d3; -} -table.dataTable.hover tbody tr:hover, -table.dataTable.hover tbody tr.odd:hover, -table.dataTable.hover tbody tr.even:hover, table.dataTable.display tbody tr:hover, -table.dataTable.display tbody tr.odd:hover, -table.dataTable.display tbody tr.even:hover { - background-color: whitesmoke; -} -table.dataTable.hover tbody tr:hover.selected, -table.dataTable.hover tbody tr.odd:hover.selected, -table.dataTable.hover tbody tr.even:hover.selected, table.dataTable.display tbody tr:hover.selected, -table.dataTable.display tbody tr.odd:hover.selected, -table.dataTable.display tbody tr.even:hover.selected { - background-color: #a9b7d1; -} -table.dataTable.order-column tbody tr > .sorting_1, -table.dataTable.order-column tbody tr > .sorting_2, -table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1, -table.dataTable.display tbody tr > .sorting_2, -table.dataTable.display tbody tr > .sorting_3 { - background-color: #f9f9f9; -} -table.dataTable.order-column tbody tr.selected > .sorting_1, -table.dataTable.order-column tbody tr.selected > .sorting_2, -table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1, -table.dataTable.display tbody tr.selected > .sorting_2, -table.dataTable.display tbody tr.selected > .sorting_3 { - background-color: #acbad4; -} -table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 { - background-color: #f1f1f1; -} -table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 { - background-color: #f3f3f3; -} -table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 { - background-color: whitesmoke; -} -table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 { - background-color: #a6b3cd; -} -table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 { - background-color: #a7b5ce; -} -table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 { - background-color: #a9b6d0; -} -table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 { - background-color: #f9f9f9; -} -table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 { - background-color: #fbfbfb; -} -table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 { - background-color: #fdfdfd; -} -table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 { - background-color: #acbad4; -} -table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 { - background-color: #adbbd6; -} -table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 { - background-color: #afbdd8; -} -table.dataTable.display tbody tr:hover > .sorting_1, -table.dataTable.display tbody tr.odd:hover > .sorting_1, -table.dataTable.display tbody tr.even:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1, -table.dataTable.order-column.hover tbody tr.odd:hover > .sorting_1, -table.dataTable.order-column.hover tbody tr.even:hover > .sorting_1 { - background-color: #eaeaea; -} -table.dataTable.display tbody tr:hover > .sorting_2, -table.dataTable.display tbody tr.odd:hover > .sorting_2, -table.dataTable.display tbody tr.even:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2, -table.dataTable.order-column.hover tbody tr.odd:hover > .sorting_2, -table.dataTable.order-column.hover tbody tr.even:hover > .sorting_2 { - background-color: #ebebeb; -} -table.dataTable.display tbody tr:hover > .sorting_3, -table.dataTable.display tbody tr.odd:hover > .sorting_3, -table.dataTable.display tbody tr.even:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3, -table.dataTable.order-column.hover tbody tr.odd:hover > .sorting_3, -table.dataTable.order-column.hover tbody tr.even:hover > .sorting_3 { - background-color: #eeeeee; -} -table.dataTable.display tbody tr:hover.selected > .sorting_1, -table.dataTable.display tbody tr.odd:hover.selected > .sorting_1, -table.dataTable.display tbody tr.even:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1, -table.dataTable.order-column.hover tbody tr.odd:hover.selected > .sorting_1, -table.dataTable.order-column.hover tbody tr.even:hover.selected > .sorting_1 { - background-color: #a1aec7; -} -table.dataTable.display tbody tr:hover.selected > .sorting_2, -table.dataTable.display tbody tr.odd:hover.selected > .sorting_2, -table.dataTable.display tbody tr.even:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2, -table.dataTable.order-column.hover tbody tr.odd:hover.selected > .sorting_2, -table.dataTable.order-column.hover tbody tr.even:hover.selected > .sorting_2 { - background-color: #a2afc8; -} -table.dataTable.display tbody tr:hover.selected > .sorting_3, -table.dataTable.display tbody tr.odd:hover.selected > .sorting_3, -table.dataTable.display tbody tr.even:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3, -table.dataTable.order-column.hover tbody tr.odd:hover.selected > .sorting_3, -table.dataTable.order-column.hover tbody tr.even:hover.selected > .sorting_3 { - background-color: #a4b2cb; -} -table.dataTable.nowrap th, table.dataTable.nowrap td { - white-space: nowrap; -} -table.dataTable.compact thead th, -table.dataTable.compact thead td { - padding: 5px 9px; -} -table.dataTable.compact tfoot th, -table.dataTable.compact tfoot td { - padding: 5px 9px 3px 9px; -} -table.dataTable.compact tbody th, -table.dataTable.compact tbody td { - padding: 4px 5px; -} -table.dataTable th.dt-left, -table.dataTable td.dt-left { - text-align: left; -} -table.dataTable th.dt-center, -table.dataTable td.dt-center, -table.dataTable td.dataTables_empty { - text-align: center; -} -table.dataTable th.dt-right, -table.dataTable td.dt-right { - text-align: right; -} -table.dataTable th.dt-justify, -table.dataTable td.dt-justify { - text-align: justify; -} -table.dataTable th.dt-nowrap, -table.dataTable td.dt-nowrap { - white-space: nowrap; -} -table.dataTable thead th.dt-head-left, -table.dataTable thead td.dt-head-left, -table.dataTable tfoot th.dt-head-left, -table.dataTable tfoot td.dt-head-left { - text-align: left; -} -table.dataTable thead th.dt-head-center, -table.dataTable thead td.dt-head-center, -table.dataTable tfoot th.dt-head-center, -table.dataTable tfoot td.dt-head-center { - text-align: center; -} -table.dataTable thead th.dt-head-right, -table.dataTable thead td.dt-head-right, -table.dataTable tfoot th.dt-head-right, -table.dataTable tfoot td.dt-head-right { - text-align: right; -} -table.dataTable thead th.dt-head-justify, -table.dataTable thead td.dt-head-justify, -table.dataTable tfoot th.dt-head-justify, -table.dataTable tfoot td.dt-head-justify { - text-align: justify; -} -table.dataTable thead th.dt-head-nowrap, -table.dataTable thead td.dt-head-nowrap, -table.dataTable tfoot th.dt-head-nowrap, -table.dataTable tfoot td.dt-head-nowrap { - white-space: nowrap; -} -table.dataTable tbody th.dt-body-left, -table.dataTable tbody td.dt-body-left { - text-align: left; -} -table.dataTable tbody th.dt-body-center, -table.dataTable tbody td.dt-body-center { - text-align: center; -} -table.dataTable tbody th.dt-body-right, -table.dataTable tbody td.dt-body-right { - text-align: right; -} -table.dataTable tbody th.dt-body-justify, -table.dataTable tbody td.dt-body-justify { - text-align: justify; -} -table.dataTable tbody th.dt-body-nowrap, -table.dataTable tbody td.dt-body-nowrap { - white-space: nowrap; -} - -table.dataTable, -table.dataTable th, -table.dataTable td { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -/* - * Control feature layout - */ -.dataTables_wrapper { - position: relative; - clear: both; - *zoom: 1; - zoom: 1; -} -.dataTables_wrapper .dataTables_length { - float: left; -} -.dataTables_wrapper .dataTables_filter { - float: right; - text-align: right; -} -.dataTables_wrapper .dataTables_filter input { - margin-left: 0.5em; -} -.dataTables_wrapper .dataTables_info { - clear: both; - float: left; - padding-top: 0.55em; -} -.dataTables_wrapper .dataTables_paginate { - float: right; - text-align: right; -} -.dataTables_wrapper .dataTables_paginate .fg-button { - box-sizing: border-box; - display: inline-block; - min-width: 1.5em; - padding: 0.5em; - margin-left: 2px; - text-align: center; - text-decoration: none !important; - cursor: pointer; - *cursor: hand; - color: #333 !important; - border: 1px solid transparent; -} -.dataTables_wrapper .dataTables_paginate .fg-button:active { - outline: none; -} -.dataTables_wrapper .dataTables_paginate .fg-button:first-child { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; -} -.dataTables_wrapper .dataTables_paginate .fg-button:last-child { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; -} -.dataTables_wrapper .dataTables_processing { - position: absolute; - top: 50%; - left: 50%; - width: 100%; - height: 40px; - margin-left: -50%; - margin-top: -25px; - padding-top: 20px; - text-align: center; - font-size: 1.2em; - background-color: white; - background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); - /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - /* Chrome10+,Safari5.1+ */ - background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - /* FF3.6+ */ - background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - /* IE10+ */ - background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - /* Opera 11.10+ */ - background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - /* W3C */ -} -.dataTables_wrapper .dataTables_length, -.dataTables_wrapper .dataTables_filter, -.dataTables_wrapper .dataTables_info, -.dataTables_wrapper .dataTables_processing, -.dataTables_wrapper .dataTables_paginate { - color: #333; -} -.dataTables_wrapper .dataTables_scroll { - clear: both; -} -.dataTables_wrapper .dataTables_scrollBody { - *margin-top: -1px; - -webkit-overflow-scrolling: touch; -} -.dataTables_wrapper .ui-widget-header { - font-weight: normal; -} -.dataTables_wrapper .ui-toolbar { - padding: 8px; -} -.dataTables_wrapper:after { - visibility: hidden; - display: block; - content: ""; - clear: both; - height: 0; -} - -@media screen and (max-width: 767px) { - .dataTables_wrapper .dataTables_length, - .dataTables_wrapper .dataTables_filter, - .dataTables_wrapper .dataTables_info, - .dataTables_wrapper .dataTables_paginate { - float: none; - text-align: center; - } - .dataTables_wrapper .dataTables_filter, - .dataTables_wrapper .dataTables_paginate { - margin-top: 0.5em; - } -} diff --git a/wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.bootstrap.css b/wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.bootstrap.css deleted file mode 100644 index 461dd2b0..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.bootstrap.css +++ /dev/null @@ -1,89 +0,0 @@ -div.dt-button-info { - position: fixed; - top: 50%; - left: 50%; - width: 400px; - margin-top: -100px; - margin-left: -200px; - background-color: white; - border: 2px solid #111; - box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.3); - border-radius: 3px; - text-align: center; - z-index: 21; -} -div.dt-button-info h2 { - padding: 0.5em; - margin: 0; - font-weight: normal; - border-bottom: 1px solid #ddd; - background-color: #f3f3f3; -} -div.dt-button-info > div { - padding: 1em; -} - -ul.dt-button-collection.dropdown-menu { - display: block; - z-index: 2002; - -webkit-column-gap: 8px; - -moz-column-gap: 8px; - -ms-column-gap: 8px; - -o-column-gap: 8px; - column-gap: 8px; -} -ul.dt-button-collection.dropdown-menu.fixed { - position: fixed; - top: 50%; - left: 50%; - margin-left: -75px; -} -ul.dt-button-collection.dropdown-menu.fixed.two-column { - margin-left: -150px; -} -ul.dt-button-collection.dropdown-menu.fixed.three-column { - margin-left: -225px; -} -ul.dt-button-collection.dropdown-menu.fixed.four-column { - margin-left: -300px; -} -ul.dt-button-collection.dropdown-menu > * { - -webkit-column-break-inside: avoid; - break-inside: avoid; -} -ul.dt-button-collection.dropdown-menu.two-column { - width: 300px; - padding-bottom: 1px; - -webkit-column-count: 2; - -moz-column-count: 2; - -ms-column-count: 2; - -o-column-count: 2; - column-count: 2; -} -ul.dt-button-collection.dropdown-menu.three-column { - width: 450px; - padding-bottom: 1px; - -webkit-column-count: 3; - -moz-column-count: 3; - -ms-column-count: 3; - -o-column-count: 3; - column-count: 3; -} -ul.dt-button-collection.dropdown-menu.four-column { - width: 600px; - padding-bottom: 1px; - -webkit-column-count: 4; - -moz-column-count: 4; - -ms-column-count: 4; - -o-column-count: 4; - column-count: 4; -} - -div.dt-button-background { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 2001; -} diff --git a/wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.colVis.min.js b/wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.colVis.min.js deleted file mode 100644 index 072d6c9a..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.colVis.min.js +++ /dev/null @@ -1,5 +0,0 @@ -(function(g){"function"===typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(d){return g(d,window,document)}):"object"===typeof exports?module.exports=function(d,e){d||(d=window);if(!e||!e.fn.dataTable)e=require("datatables.net")(d,e).$;e.fn.dataTable.Buttons||require("datatables.net-buttons")(d,e);return g(e,d,d.document)}:g(jQuery,window,document)})(function(g,d,e,h){d=g.fn.dataTable;g.extend(d.ext.buttons,{colvis:function(a,b){return{extend:"collection", -text:function(a){return a.i18n("buttons.colvis","Column visibility")},className:"buttons-colvis",buttons:[{extend:"columnsToggle",columns:b.columns}]}},columnsToggle:function(a,b){return a.columns(b.columns).indexes().map(function(a){return{extend:"columnToggle",columns:a}}).toArray()},columnToggle:function(a,b){return{extend:"columnVisibility",columns:b.columns}},columnsVisibility:function(a,b){return a.columns(b.columns).indexes().map(function(a){return{extend:"columnVisibility",columns:a,visibility:b.visibility}}).toArray()}, -columnVisibility:{columns:h,text:function(a,b,c){return c._columnText(a,c.columns)},className:"buttons-columnVisibility",action:function(a,b,c,f){a=b.columns(f.columns);b=a.visible();a.visible(f.visibility!==h?f.visibility:!(b.length&&b[0]))},init:function(a,b,c){var f=this;a.on("column-visibility.dt"+c.namespace,function(b,d){d.bDestroying||f.active(a.column(c.columns).visible())}).on("column-reorder.dt"+c.namespace,function(b,d,e){1===a.columns(c.columns).count()&&("number"===typeof c.columns&& -(c.columns=e.mapping[c.columns]),b=a.column(c.columns),f.text(c._columnText(a,c.columns)),f.active(b.visible()))});this.active(a.column(c.columns).visible())},destroy:function(a,b,c){a.off("column-visibility.dt"+c.namespace).off("column-reorder.dt"+c.namespace)},_columnText:function(a,b){var c=a.column(b).index();return a.settings()[0].aoColumns[c].sTitle.replace(/\n/g," ").replace(/<.*?>/g,"").replace(/^\s+|\s+$/g,"")}},colvisRestore:{className:"buttons-colvisRestore",text:function(a){return a.i18n("buttons.colvisRestore", -"Restore visibility")},init:function(a,b,c){c._visOriginal=a.columns().indexes().map(function(b){return a.column(b).visible()}).toArray()},action:function(a,b,c,d){b.columns().every(function(a){a=b.colReorder&&b.colReorder.transpose?b.colReorder.transpose(a,"toOriginal"):a;this.visible(d._visOriginal[a])})}},colvisGroup:{className:"buttons-colvisGroup",action:function(a,b,c,d){b.columns(d.show).visible(!0,!1);b.columns(d.hide).visible(!1,!1);b.columns.adjust()},show:[],hide:[]}});return d.Buttons}); \ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.dataTables.css b/wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.dataTables.css deleted file mode 100644 index 15b6c263..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/extensions/buttons.dataTables.css +++ /dev/null @@ -1,297 +0,0 @@ -div.dt-button-info { - position: fixed; - top: 50%; - left: 50%; - width: 400px; - margin-top: -100px; - margin-left: -200px; - background-color: white; - border: 2px solid #111; - box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.3); - border-radius: 3px; - text-align: center; - z-index: 21; -} -div.dt-button-info h2 { - padding: 0.5em; - margin: 0; - font-weight: normal; - border-bottom: 1px solid #ddd; - background-color: #f3f3f3; -} -div.dt-button-info > div { - padding: 1em; -} - -button.dt-button, -div.dt-button, -a.dt-button { - position: relative; - display: inline-block; - box-sizing: border-box; - margin-right: 0.333em; - padding: 0.5em 1em; - border: 1px solid #999; - border-radius: 2px; - cursor: pointer; - font-size: 0.88em; - color: black; - white-space: nowrap; - overflow: hidden; - background-color: #e9e9e9; - /* Fallback */ - background-image: -webkit-linear-gradient(top, white 0%, #e9e9e9 100%); - /* Chrome 10+, Saf5.1+, iOS 5+ */ - background-image: -moz-linear-gradient(top, white 0%, #e9e9e9 100%); - /* FF3.6 */ - background-image: -ms-linear-gradient(top, white 0%, #e9e9e9 100%); - /* IE10 */ - background-image: -o-linear-gradient(top, white 0%, #e9e9e9 100%); - /* Opera 11.10+ */ - background-image: linear-gradient(top, white 0%, #e9e9e9 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='white', EndColorStr='#e9e9e9'); - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - text-decoration: none; - outline: none; -} -button.dt-button.disabled, -div.dt-button.disabled, -a.dt-button.disabled { - color: #999; - border: 1px solid #d0d0d0; - cursor: default; - background-color: #f9f9f9; - /* Fallback */ - background-image: -webkit-linear-gradient(top, #ffffff 0%, #f9f9f9 100%); - /* Chrome 10+, Saf5.1+, iOS 5+ */ - background-image: -moz-linear-gradient(top, #ffffff 0%, #f9f9f9 100%); - /* FF3.6 */ - background-image: -ms-linear-gradient(top, #ffffff 0%, #f9f9f9 100%); - /* IE10 */ - background-image: -o-linear-gradient(top, #ffffff 0%, #f9f9f9 100%); - /* Opera 11.10+ */ - background-image: linear-gradient(top, #ffffff 0%, #f9f9f9 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#ffffff', EndColorStr='#f9f9f9'); -} -button.dt-button:active:not(.disabled), button.dt-button.active:not(.disabled), -div.dt-button:active:not(.disabled), -div.dt-button.active:not(.disabled), -a.dt-button:active:not(.disabled), -a.dt-button.active:not(.disabled) { - background-color: #e2e2e2; - /* Fallback */ - background-image: -webkit-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%); - /* Chrome 10+, Saf5.1+, iOS 5+ */ - background-image: -moz-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%); - /* FF3.6 */ - background-image: -ms-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%); - /* IE10 */ - background-image: -o-linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%); - /* Opera 11.10+ */ - background-image: linear-gradient(top, #f3f3f3 0%, #e2e2e2 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#f3f3f3', EndColorStr='#e2e2e2'); - box-shadow: inset 1px 1px 3px #999999; -} -button.dt-button:active:not(.disabled):hover:not(.disabled), button.dt-button.active:not(.disabled):hover:not(.disabled), -div.dt-button:active:not(.disabled):hover:not(.disabled), -div.dt-button.active:not(.disabled):hover:not(.disabled), -a.dt-button:active:not(.disabled):hover:not(.disabled), -a.dt-button.active:not(.disabled):hover:not(.disabled) { - box-shadow: inset 1px 1px 3px #999999; - background-color: #cccccc; - /* Fallback */ - background-image: -webkit-linear-gradient(top, #eaeaea 0%, #cccccc 100%); - /* Chrome 10+, Saf5.1+, iOS 5+ */ - background-image: -moz-linear-gradient(top, #eaeaea 0%, #cccccc 100%); - /* FF3.6 */ - background-image: -ms-linear-gradient(top, #eaeaea 0%, #cccccc 100%); - /* IE10 */ - background-image: -o-linear-gradient(top, #eaeaea 0%, #cccccc 100%); - /* Opera 11.10+ */ - background-image: linear-gradient(top, #eaeaea 0%, #cccccc 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#eaeaea', EndColorStr='#cccccc'); -} -button.dt-button:hover, -div.dt-button:hover, -a.dt-button:hover { - text-decoration: none; -} -button.dt-button:hover:not(.disabled), -div.dt-button:hover:not(.disabled), -a.dt-button:hover:not(.disabled) { - border: 1px solid #666; - background-color: #e0e0e0; - /* Fallback */ - background-image: -webkit-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%); - /* Chrome 10+, Saf5.1+, iOS 5+ */ - background-image: -moz-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%); - /* FF3.6 */ - background-image: -ms-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%); - /* IE10 */ - background-image: -o-linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%); - /* Opera 11.10+ */ - background-image: linear-gradient(top, #f9f9f9 0%, #e0e0e0 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#f9f9f9', EndColorStr='#e0e0e0'); -} -button.dt-button:focus:not(.disabled), -div.dt-button:focus:not(.disabled), -a.dt-button:focus:not(.disabled) { - border: 1px solid #426c9e; - text-shadow: 0 1px 0 #c4def1; - outline: none; - background-color: #79ace9; - /* Fallback */ - background-image: -webkit-linear-gradient(top, #bddef4 0%, #79ace9 100%); - /* Chrome 10+, Saf5.1+, iOS 5+ */ - background-image: -moz-linear-gradient(top, #bddef4 0%, #79ace9 100%); - /* FF3.6 */ - background-image: -ms-linear-gradient(top, #bddef4 0%, #79ace9 100%); - /* IE10 */ - background-image: -o-linear-gradient(top, #bddef4 0%, #79ace9 100%); - /* Opera 11.10+ */ - background-image: linear-gradient(top, #bddef4 0%, #79ace9 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#bddef4', EndColorStr='#79ace9'); -} - -.dt-button embed { - outline: none; -} - -div.dt-buttons { - position: relative; - float: left; -} -div.dt-buttons.buttons-right { - float: right; -} - -div.dt-button-collection { - position: absolute; - top: 0; - left: 0; - width: 150px; - margin-top: 3px; - padding: 8px 8px 4px 8px; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.4); - background-color: white; - overflow: hidden; - z-index: 2002; - border-radius: 5px; - box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.3); - z-index: 2002; - -webkit-column-gap: 8px; - -moz-column-gap: 8px; - -ms-column-gap: 8px; - -o-column-gap: 8px; - column-gap: 8px; -} -div.dt-button-collection button.dt-button, -div.dt-button-collection div.dt-button, -div.dt-button-collection a.dt-button { - position: relative; - left: 0; - right: 0; - display: block; - float: none; - margin-bottom: 4px; - margin-right: 0; -} -div.dt-button-collection button.dt-button:active:not(.disabled), div.dt-button-collection button.dt-button.active:not(.disabled), -div.dt-button-collection div.dt-button:active:not(.disabled), -div.dt-button-collection div.dt-button.active:not(.disabled), -div.dt-button-collection a.dt-button:active:not(.disabled), -div.dt-button-collection a.dt-button.active:not(.disabled) { - background-color: #dadada; - /* Fallback */ - background-image: -webkit-linear-gradient(top, #f0f0f0 0%, #dadada 100%); - /* Chrome 10+, Saf5.1+, iOS 5+ */ - background-image: -moz-linear-gradient(top, #f0f0f0 0%, #dadada 100%); - /* FF3.6 */ - background-image: -ms-linear-gradient(top, #f0f0f0 0%, #dadada 100%); - /* IE10 */ - background-image: -o-linear-gradient(top, #f0f0f0 0%, #dadada 100%); - /* Opera 11.10+ */ - background-image: linear-gradient(top, #f0f0f0 0%, #dadada 100%); - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#f0f0f0', EndColorStr='#dadada'); - box-shadow: inset 1px 1px 3px #666; -} -div.dt-button-collection.fixed { - position: fixed; - top: 50%; - left: 50%; - margin-left: -75px; -} -div.dt-button-collection.fixed.two-column { - margin-left: -150px; -} -div.dt-button-collection.fixed.three-column { - margin-left: -225px; -} -div.dt-button-collection.fixed.four-column { - margin-left: -300px; -} -div.dt-button-collection > * { - -webkit-column-break-inside: avoid; - break-inside: avoid; -} -div.dt-button-collection.two-column { - width: 300px; - padding-bottom: 1px; - -webkit-column-count: 2; - -moz-column-count: 2; - -ms-column-count: 2; - -o-column-count: 2; - column-count: 2; -} -div.dt-button-collection.three-column { - width: 450px; - padding-bottom: 1px; - -webkit-column-count: 3; - -moz-column-count: 3; - -ms-column-count: 3; - -o-column-count: 3; - column-count: 3; -} -div.dt-button-collection.four-column { - width: 600px; - padding-bottom: 1px; - -webkit-column-count: 4; - -moz-column-count: 4; - -ms-column-count: 4; - -o-column-count: 4; - column-count: 4; -} - -div.dt-button-background { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.7); - /* Fallback */ - background: -ms-radial-gradient(center, ellipse farthest-corner, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%); - /* IE10 Consumer Preview */ - background: -moz-radial-gradient(center, ellipse farthest-corner, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%); - /* Firefox */ - background: -o-radial-gradient(center, ellipse farthest-corner, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%); - /* Opera */ - background: -webkit-gradient(radial, center center, 0, center center, 497, color-stop(0, rgba(0, 0, 0, 0.3)), color-stop(1, rgba(0, 0, 0, 0.7))); - /* Webkit (Safari/Chrome 10) */ - background: -webkit-radial-gradient(center, ellipse farthest-corner, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%); - /* Webkit (Chrome 11+) */ - background: radial-gradient(ellipse farthest-corner at center, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.7) 100%); - /* W3C Markup, IE10 Release Preview */ - z-index: 2001; -} - -@media screen and (max-width: 640px) { - div.dt-buttons { - float: none !important; - text-align: center; - } -} diff --git a/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.buttons.min.js b/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.buttons.min.js deleted file mode 100644 index ae3fb9c8..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.buttons.min.js +++ /dev/null @@ -1,35 +0,0 @@ -/*! - Buttons for DataTables 1.2.4 - Š2016 SpryMedia Ltd - datatables.net/license -*/ -(function(d){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(n){return d(n,window,document)}):"object"===typeof exports?module.exports=function(n,o){n||(n=window);if(!o||!o.fn.dataTable)o=require("datatables.net")(n,o).$;return d(o,n,n.document)}:d(jQuery,window,document)})(function(d,n,o,m){var i=d.fn.dataTable,u=0,v=0,j=i.ext.buttons,l=function(a,b){!0===b&&(b={});d.isArray(b)&&(b={buttons:b});this.c=d.extend(!0,{},l.defaults,b);b.buttons&&(this.c.buttons=b.buttons); -this.s={dt:new i.Api(a),buttons:[],listenKeys:"",namespace:"dtb"+u++};this.dom={container:d("<"+this.c.dom.container.tag+"/>").addClass(this.c.dom.container.className)};this._constructor()};d.extend(l.prototype,{action:function(a,b){var c=this._nodeToButton(a);if(b===m)return c.conf.action;c.conf.action=b;return this},active:function(a,b){var c=this._nodeToButton(a),e=this.c.dom.button.active,c=d(c.node);if(b===m)return c.hasClass(e);c.toggleClass(e,b===m?!0:b);return this},add:function(a,b){var c= -this.s.buttons;if("string"===typeof b){for(var e=b.split("-"),c=this.s,d=0,h=e.length-1;d").addClass(s.className);p.conf._collection=p.collection;this._expandButton(p.buttons,p.conf.buttons,!0,e)}k.init&&k.init.call(g.button(p.node),g,d(p.node),k);h++}}}},_buildButton:function(a,b){var c=this.c.dom.button,e=this.c.dom.buttonLiner,g=this.c.dom.collection,h=this.s.dt,f=function(b){return"function"===typeof b?b(h,k,a):b};b&&g.button&&(c=g.button);b&&g.buttonLiner&&(e=g.buttonLiner);if(a.available&&!a.available(h,a))return!1;var r=function(a, -b,c,e){e.action.call(b.button(c),a,b,c,e);d(b.table().node()).triggerHandler("buttons-action.dt",[b.button(c),b,c,e])},k=d("<"+c.tag+"/>").addClass(c.className).attr("tabindex",this.s.dt.settings()[0].iTabIndex).attr("aria-controls",this.s.dt.table().node().id).on("click.dtb",function(b){b.preventDefault();!k.hasClass(c.disabled)&&a.action&&r(b,h,k,a);k.blur()}).on("keyup.dtb",function(b){b.keyCode===13&&!k.hasClass(c.disabled)&&a.action&&r(b,h,k,a)});"a"===c.tag.toLowerCase()&&k.attr("href","#"); -e.tag?(g=d("<"+e.tag+"/>").html(f(a.text)).addClass(e.className),"a"===e.tag.toLowerCase()&&g.attr("href","#"),k.append(g)):k.html(f(a.text));!1===a.enabled&&k.addClass(c.disabled);a.className&&k.addClass(a.className);a.titleAttr&&k.attr("title",a.titleAttr);a.namespace||(a.namespace=".dt-button-"+v++);e=(e=this.c.dom.buttonContainer)&&e.tag?d("<"+e.tag+"/>").addClass(e.className).append(k):k;this._addKey(a);return{conf:a,node:k.get(0),inserter:e,buttons:[],inCollection:b,collection:null}},_nodeToButton:function(a, -b){b||(b=this.s.buttons);for(var c=0,e=b.length;c").addClass(b).css("display","none").appendTo("body").fadeIn(c):d("body > div."+b).fadeOut(c,function(){d(this).removeClass(b).remove()})};l.instanceSelector=function(a,b){if(!a)return d.map(b,function(a){return a.inst});var c= -[],e=d.map(b,function(a){return a.name}),g=function(a){if(d.isArray(a))for(var f=0,r=a.length;fg&&e._collection.css("left",a.left-(c-g))):(a=e._collection.height()/2,a>d(n).height()/2&&(a=d(n).height()/2),e._collection.css("marginTop",-1*a));e.background&&l.background(!0,e.backgroundClassName,e.fade);setTimeout(function(){d("div.dt-button-background").on("click.dtb-collection", -function(){});d("body").on("click.dtb-collection",function(a){var c=d.fn.addBack?"addBack":"andSelf";if(!d(a.target).parents()[c]().filter(e._collection).length){e._collection.fadeOut(e.fade,function(){e._collection.detach()});d("div.dt-button-background").off("click.dtb-collection");l.background(false,e.backgroundClassName,e.fade);d("body").off("click.dtb-collection");b.off("buttons-action.b-internal")}})},10);if(e.autoClose)b.on("buttons-action.b-internal",function(){d("div.dt-button-background").click()})}, -background:!0,collectionLayout:"",backgroundClassName:"dt-button-background",autoClose:!1,fade:400},copy:function(a,b){if(j.copyHtml5)return"copyHtml5";if(j.copyFlash&&j.copyFlash.available(a,b))return"copyFlash"},csv:function(a,b){if(j.csvHtml5&&j.csvHtml5.available(a,b))return"csvHtml5";if(j.csvFlash&&j.csvFlash.available(a,b))return"csvFlash"},excel:function(a,b){if(j.excelHtml5&&j.excelHtml5.available(a,b))return"excelHtml5";if(j.excelFlash&&j.excelFlash.available(a,b))return"excelFlash"},pdf:function(a, -b){if(j.pdfHtml5&&j.pdfHtml5.available(a,b))return"pdfHtml5";if(j.pdfFlash&&j.pdfFlash.available(a,b))return"pdfFlash"},pageLength:function(a){var a=a.settings()[0].aLengthMenu,b=d.isArray(a[0])?a[0]:a,c=d.isArray(a[0])?a[1]:a,e=function(a){return a.i18n("buttons.pageLength",{"-1":"Show all rows",_:"Show %d rows"},a.page.len())};return{extend:"collection",text:e,className:"buttons-page-length",autoClose:!0,buttons:d.map(b,function(a,b){return{text:c[b],className:"button-page-length",action:function(b, -c){c.page.len(a).draw()},init:function(b,c,e){var d=this,c=function(){d.active(b.page.len()===a)};b.on("length.dt"+e.namespace,c);c()},destroy:function(a,b,c){a.off("length.dt"+c.namespace)}}}),init:function(a,b,c){var d=this;a.on("length.dt"+c.namespace,function(){d.text(e(a))})},destroy:function(a,b,c){a.off("length.dt"+c.namespace)}}}});i.Api.register("buttons()",function(a,b){b===m&&(b=a,a=m);this.selector.buttonGroup=a;var c=this.iterator(!0,"table",function(c){if(c._buttons)return l.buttonSelector(l.instanceSelector(a, -c._buttons),b)},!0);c._groupSelector=a;return c});i.Api.register("button()",function(a,b){var c=this.buttons(a,b);1').html(a?"

    "+a+"

    ":"").append(d("
    ")["string"===typeof b?"html":"append"](b)).css("display","none").appendTo("body").fadeIn();c!==m&&0!==c&&(q=setTimeout(function(){e.buttons.info(!1)},c));return this});i.Api.register("buttons.exportData()",function(a){if(this.context.length){for(var b=new i.Api(this.context[0]), -c=d.extend(!0,{},{rows:null,columns:"",modifier:{search:"applied",order:"applied"},orthogonal:"display",stripHtml:!0,stripNewlines:!0,decodeEntities:!0,trim:!0,format:{header:function(a){return e(a)},footer:function(a){return e(a)},body:function(a){return e(a)}}},a),e=function(a){if("string"!==typeof a)return a;c.stripHtml&&(a=a.replace(/<[^>]*>/g,""));c.trim&&(a=a.replace(/^\s+|\s+$/g,""));c.stripNewlines&&(a=a.replace(/\n/g," "));c.decodeEntities&&(t.innerHTML=a,a=t.value);return a},a=b.columns(c.columns).indexes().map(function(a){var d= -b.column(a).header();return c.format.header(d.innerHTML,a,d)}).toArray(),g=b.table().footer()?b.columns(c.columns).indexes().map(function(a){var d=b.column(a).footer();return c.format.footer(d?d.innerHTML:"",a,d)}).toArray():null,h=b.rows(c.rows,c.modifier).indexes().toArray(),f=b.cells(h,c.columns),h=f.render(c.orthogonal).toArray(),f=f.nodes().toArray(),j=a.length,k=0")[0];d.fn.dataTable.Buttons=l;d.fn.DataTable.Buttons=l;d(o).on("init.dt plugin-init.dt",function(a,b){if("dt"===a.namespace){var c=b.oInit.buttons||i.defaults.buttons;c&&!b._buttons&&(new l(b,c)).container()}});i.ext.feature.push({fnInit:function(a){var a=new i.Api(a),b=a.init().buttons||i.defaults.buttons;return(new l(a,b)).container()},cFeature:"B"});return l}); \ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.colReorder.js b/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.colReorder.js deleted file mode 100644 index 58e7656e..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.colReorder.js +++ /dev/null @@ -1,1372 +0,0 @@ -/*! ColReorder 1.1.3 - * ©2010-2014 SpryMedia Ltd - datatables.net/license - */ - -/** - * @summary ColReorder - * @description Provide the ability to reorder columns in a DataTable - * @version 1.1.3 - * @file dataTables.colReorder.js - * @author SpryMedia Ltd (www.sprymedia.co.uk) - * @contact www.sprymedia.co.uk/contact - * @copyright Copyright 2010-2014 SpryMedia Ltd. - * - * This source file is free software, available under the following license: - * MIT license - http://datatables.net/license/mit - * - * This source file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. - * - * For details please refer to: http://www.datatables.net - */ - -(function(window, document, undefined) { - - -/** - * Switch the key value pairing of an index array to be value key (i.e. the old value is now the - * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ]. - * @method fnInvertKeyValues - * @param array aIn Array to switch around - * @returns array - */ -function fnInvertKeyValues( aIn ) -{ - var aRet=[]; - for ( var i=0, iLen=aIn.length ; i= iCols ) - { - this.oApi._fnLog( oSettings, 1, "ColReorder 'from' index is out of bounds: "+iFrom ); - return; - } - - if ( iTo < 0 || iTo >= iCols ) - { - this.oApi._fnLog( oSettings, 1, "ColReorder 'to' index is out of bounds: "+iTo ); - return; - } - - /* - * Calculate the new column array index, so we have a mapping between the old and new - */ - var aiMapping = []; - for ( i=0, iLen=iCols ; i this.s.fixed-1 && i < iLen - this.s.fixedRight ) - { - this._fnMouseListener( i, this.s.dt.aoColumns[i].nTh ); - } - - /* Mark the original column order for later reference */ - this.s.dt.aoColumns[i]._ColReorder_iOrigCol = i; - } - - /* State saving */ - this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) { - that._fnStateSave.call( that, oData ); - }, "ColReorder_State" ); - - /* An initial column order has been specified */ - var aiOrder = null; - if ( this.s.init.aiOrder ) - { - aiOrder = this.s.init.aiOrder.slice(); - } - - /* State loading, overrides the column order given */ - if ( this.s.dt.oLoadedState && typeof this.s.dt.oLoadedState.ColReorder != 'undefined' && - this.s.dt.oLoadedState.ColReorder.length == this.s.dt.aoColumns.length ) - { - aiOrder = this.s.dt.oLoadedState.ColReorder; - } - - /* If we have an order to apply - do so */ - if ( aiOrder ) - { - /* We might be called during or after the DataTables initialisation. If before, then we need - * to wait until the draw is done, if after, then do what we need to do right away - */ - if ( !that.s.dt._bInitComplete ) - { - var bDone = false; - this.s.dt.aoDrawCallback.push( { - "fn": function () { - if ( !that.s.dt._bInitComplete && !bDone ) - { - bDone = true; - var resort = fnInvertKeyValues( aiOrder ); - that._fnOrderColumns.call( that, resort ); - } - }, - "sName": "ColReorder_Pre" - } ); - } - else - { - var resort = fnInvertKeyValues( aiOrder ); - that._fnOrderColumns.call( that, resort ); - } - } - else { - this._fnSetColumnIndexes(); - } - }, - - - /** - * Set the column order from an array - * @method _fnOrderColumns - * @param array a An array of integers which dictate the column order that should be applied - * @returns void - * @private - */ - "_fnOrderColumns": function ( a ) - { - if ( a.length != this.s.dt.aoColumns.length ) - { - this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "ColReorder - array reorder does not "+ - "match known number of columns. Skipping." ); - return; - } - - for ( var i=0, iLen=a.length ; i
    ') - .addClass( 'DTCR_pointer' ) - .css( { - position: 'absolute', - top: scrolling ? - $('div.dataTables_scroll', this.s.dt.nTableWrapper).offset().top : - $(this.s.dt.nTable).offset().top, - height : scrolling ? - $('div.dataTables_scroll', this.s.dt.nTableWrapper).height() : - $(this.s.dt.nTable).height() - } ) - .appendTo( 'body' ); - }, - - /** - * Clean up ColReorder memory references and event handlers - * @method _fnDestroy - * @returns void - * @private - */ - "_fnDestroy": function () - { - var i, iLen; - - for ( i=0, iLen=this.s.dt.aoDrawCallback.length ; i 0 && colStates.length === currCols.length) { - colStates.forEach(function(state, index) { - var col = that.s.dt.aoColumns[index]; - if (state.width) { - col.sWidthOrig = col.sWidth = state.width; - } - }); - } - } - }, - - /** - * Remove events of type from obj add it to restoreEvents array to be restored at a later time - * @param until string flag when to restore the event - * @param obj Object to remove events from - * @param type type of event to remove - * @param namespace namespace of the event being removed - */ - "_fnDelayEvents": function (until, obj, type, namespace) { - var that = this; - //Get the events for the object - var events = $._data($(obj).get(0), 'events'); - $.each(events, function (i, o) { - //If the event type matches - if (i == type) { - //Loop through the possible many events with that type - $.each(o, function (k, v) { - //Somehow it is possible for the event to be undefined make sure it is defined first - if (v) { - if (namespace) { - //Add the event to the array of events to be restored later - that.dom.restoreEvents.push({"until": until, "obj": obj, "type": v.type, "namespace": v.namespace, "handler": v.handler}); - //If the namespace matches - if (v.namespace == namespace) { - //Turn off/unbind the event - $(obj).off(type + "." + namespace); - } - } else { - //Add the event to the array of events to be restored later - that.dom.restoreEvents.push({"until": until, "obj": obj, "type": v.type, "namespace": null, "handler": v.handler}); - //Turn off/unbind the event - $(obj).off(type); - } - } - }); - } - }); - }, - - /** - * Loop through restoreEvents array and restore the events on the elements provided - */ - "_fnRestoreEvents": function (until) { - var that = this; - //Loop through the events to be restored - var i; - for (i = that.dom.restoreEvents.length; i--;) { - if (that.dom.restoreEvents[i].until == undefined || that.dom.restoreEvents[i].until == null || that.dom.restoreEvents[i].until == until) { - if (that.dom.restoreEvents[i].namespace) { - //Turn on the event for the object provided - $(that.dom.restoreEvents[i].obj).off(that.dom.restoreEvents[i].type + "." + that.dom.restoreEvents[i].namespace).on(that.dom.restoreEvents[i].type + "." + that.dom.restoreEvents[i].namespace, that.dom.restoreEvents[i].handler); - that.dom.restoreEvents.splice(i, 1); - } else { - //Turn on the event for the object provided - $(that.dom.restoreEvents[i].obj).off(that.dom.restoreEvents[i].type).on(that.dom.restoreEvents[i].type, that.dom.restoreEvents[i].handler); - that.dom.restoreEvents.splice(i, 1); - } - } - } - }, - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Mouse drop and drag - */ - - "_fnSetupMouseListeners":function() { - var that = this; - $(that.s.dt.nTableWrapper).off("mouseenter.ColResize").on("mouseenter.ColResize","th",function(e) { - e.preventDefault(); - that._fnMouseEnter.call(that, e, this); - }); - $(that.s.dt.nTableWrapper).off("mouseleave.ColResize").on("mouseleave.ColResize","th",function(e) { - e.preventDefault(); - that._fnMouseLeave.call(that, e, this); - }); - }, - - /** - * Add mouse listeners to the resize handle on TH element - * @method _fnMouseListener - * @param i Column index - * @param nTh TH resize handle element clicked on - * @returns void - * @private - */ - "_fnMouseListener": function (i, nTh) { - var that = this; - $(nTh).off('mouseenter.ColResize').on('mouseenter.ColResize', function (e) { - e.preventDefault(); - that._fnMouseEnter.call(that, e, nTh); - }); - $(nTh).off('mouseleave.ColResize').on('mouseleave.ColResize', function (e) { - e.preventDefault(); - that._fnMouseLeave.call(that, e, nTh); - }); - }, - - /** - * - * @param e Mouse event - * @param nTh TH element that the mouse is over - */ - "_fnMouseEnter": function (e, nTh) { - var that = this; - if(!that.s.isMousedown) { - //Once the mouse has entered the cell add mouse move event to see if the mouse is over resize handle - $(nTh).off('mousemove.ColResizeHandle').on('mousemove.ColResizeHandle', function (e) { - e.preventDefault(); - that._fnResizeHandleCheck.call(that, e, nTh); - }); - } - }, - - /** - * Clear mouse events when the mouse has left the th - * @param e Mouse event - * @param nTh TH element that the mouse has just left - */ - "_fnMouseLeave": function (e, nTh) { - //Once the mouse has left the TH make suure to remove the mouse move listener - $(nTh).off('mousemove.ColResizeHandle'); - }, - - /** - * Mouse down on a TH element in the table header - * @method _fnMouseDown - * @param event e Mouse event - * @param element nTh TH element to be resized - * @returns void - * @private - */ - "_fnMouseDown": function (e, nTh) { - var that = this; - - that.s.isMousedown = true; - - /* Store information about the mouse position */ - var target = $(e.target).closest('th, td'); - var offset = target.offset(); - - /* Store information about the mouse position for resize calculations in mouse move function */ - this.s.mouse.startX = e.pageX; - this.s.mouse.startY = e.pageY; - - //Store the indexes of the columns the mouse is down on - var idx = that.dom.resizeCol[0].cellIndex; - - // the last column has no 'right-side' neighbour - // with fixed this can make the table smaller - if (that.dom.resizeColNeighbour[0] === undefined){ - var idxNeighbour = 0; - } else { - var idxNeighbour = that.dom.resizeColNeighbour[0].cellIndex; - } - - - - if (idx === undefined) { - return; - } - - this.s.mouse.targetIndex = idx; - this.s.mouse.targetColumn = this.s.dt.aoColumns[ idx ]; - - this.s.mouse.neighbourIndex = idxNeighbour; - this.s.mouse.neighbourColumn = this.s.dt.aoColumns[ idxNeighbour ]; - - /* Add event handlers to the document */ - $(document) - .off('mousemove.ColResize').on('mousemove.ColResize', function (e) { - that._fnMouseMove.call(that, e); - }) - .off('mouseup.ColResize').on('mouseup.ColResize', function (e) { - that._fnMouseUp.call(that, e); - }); - }, - - /** - * Deal with a mouse move event while dragging to resize a column - * @method _fnMouseMove - * @param e Mouse event - * @returns void - * @private - */ - "_fnMouseMove": function (e) { - var that = this; - - var offset = $(that.s.mouse.targetColumn.nTh).offset(); - var relativeX = (e.pageX - offset.left); - var distFromLeft = relativeX; - var distFromRight = $(that.s.mouse.targetColumn.nTh).outerWidth() - relativeX - 1; - - //Change in mouse x position - var dx = e.pageX - that.s.mouse.startX; - //Get the minimum width of the column (default minimum 10px) - var minColumnWidth = Math.max(parseInt($(that.s.mouse.targetColumn.nTh).css('min-width')), 10); - //Store the previous width of the column - var prevWidth = $(that.s.mouse.targetColumn.nTh).width(); - //As long as the cursor is past the handle, resize the columns - if ((dx > 0 && distFromRight <= 0) || (dx < 0 && distFromRight >= 0)) { - if (!that.s.init.tableWidthFixed) { - //As long as the width is larger than the minimum - var newColWidth = Math.max(minColumnWidth, prevWidth + dx); - //Get the width difference (take into account the columns minimum width) - var widthDiff = newColWidth - prevWidth; - var colResizeIdx = parseInt(that.dom.resizeCol.attr("data-column-index")); - //Set datatable column widths - that.s.mouse.targetColumn.sWidthOrig = that.s.mouse.targetColumn.sWidth = that.s.mouse.targetColumn.width = newColWidth + "px"; - var domCols = $(that.s.dt.nTableWrapper).find("th[data-column-index='"+colResizeIdx+"']"); - //For each table expand the width by the same amount as the column - //This accounts for other datatable plugins like FixedColumns - domCols.parents("table").each(function() { - if(!$(this).parent().hasClass("DTFC_LeftBodyLiner")) { - var newWidth = $(this).width() + widthDiff; - $(this).width(newWidth); - } else { - var newWidth =$(that.s.dt.nTableWrapper).find(".DTFC_LeftHeadWrapper").children("table").width(); - $(this).parents(".DTFC_LeftWrapper").width(newWidth); - $(this).parent().width(newWidth+15); - $(this).width(newWidth); - } - }); - //Apply the new width to the columns after the table has been resized - domCols.width(that.s.mouse.targetColumn.width); - } else { - //A neighbour column must exist in order to resize a column in a table with a fixed width - if (that.s.mouse.neighbourColumn) { - //Get the minimum width of the neighbor column (default minimum 10px) - var minColumnNeighbourWidth = Math.max(parseInt($(that.s.mouse.neighbourColumn.nTh).css('min-width')), 10); - //Store the previous width of the neighbour column - var prevNeighbourWidth = $(that.s.mouse.neighbourColumn.nTh).width(); - //As long as the width is larger than the minimum - var newColWidth = Math.max(minColumnWidth, prevWidth + dx); - var newColNeighbourWidth = Math.max(minColumnNeighbourWidth, prevNeighbourWidth - dx); - //Get the width difference (take into account the columns minimum width) - var widthDiff = newColWidth - prevWidth; - var widthDiffNeighbour = newColNeighbourWidth - prevNeighbourWidth; - //Get the column index for the column being changed - var colResizeIdx = parseInt(that.dom.resizeCol.attr("data-column-index")); - var neighbourColResizeIdx = parseInt(that.dom.resizeColNeighbour.attr("data-column-index")); - //Set datatable column widths - that.s.mouse.neighbourColumn.sWidthOrig = that.s.mouse.neighbourColumn.sWidth = that.s.mouse.neighbourColumn.width = newColNeighbourWidth + "px"; - that.s.mouse.targetColumn.sWidthOrig = that.s.mouse.targetColumn.sWidth = that.s.mouse.targetColumn.width = newColWidth + "px"; - //Get list of columns based on column index in all affected tables tables. This accounts for other plugins like FixedColumns - var domNeighbourCols = $(that.s.dt.nTableWrapper).find("th[data-column-index='" + neighbourColResizeIdx + "']"); - var domCols = $(that.s.dt.nTableWrapper).find("th[data-column-index='" + colResizeIdx + "']"); - //If dx if positive (the width is getting larger) shrink the neighbour columns first - if(dx>0) { - domNeighbourCols.width(that.s.mouse.neighbourColumn.width); - domCols.width(that.s.mouse.targetColumn.width); - } else { - //Apply the new width to the columns then to the neighbour columns - domCols.width(that.s.mouse.targetColumn.width); - domNeighbourCols.width(that.s.mouse.neighbourColumn.width); - } - } - } - } - that.s.mouse.startX = e.pageX; - }, - - /** - * Check to see if the mouse is over the resize handle area - * @param e - * @param nTh - */ - "_fnResizeHandleCheck": function (e, nTh) { - var that = this; - - var offset = $(nTh).offset(); - var relativeX = (e.pageX - offset.left); - var relativeY = (e.pageY - offset.top); - var distFromLeft = relativeX; - var distFromRight = $(nTh).outerWidth() - relativeX - 1; - - var handleBuffer = this.s.init.handleWidth / 2; - var leftHandleOn = distFromLeft < handleBuffer; - var rightHandleOn = distFromRight < handleBuffer; - - //If this is the first table cell - if ($(nTh).prev("th").length == 0) { - if(this.s.init.rtl) - rightHandleOn = false; - else - leftHandleOn = false; - } - //If this is the last cell and the table is fixed width don't let them expand the last cell directly - if ($(nTh).next("th").length == 0 && this.s.init.tableWidthFixed) { - if(this.s.init.rtl) - leftHandleOn = false; - else - rightHandleOn = false; - } - - var resizeAvailable = leftHandleOn||rightHandleOn; - - //If table is in right to left mode flip which TH is being resized - if (that.s.init.rtl) { - //Handle is to the left - if (leftHandleOn) { - that.dom.resizeCol = $(nTh); - that.dom.resizeColNeighbour = $(nTh).next(); - } else if (rightHandleOn) { - that.dom.resizeCol = $(nTh).prev(); - that.dom.resizeColNeighbour = $(nTh); - } - } else { - //Handle is to the right - if (rightHandleOn) { - that.dom.resizeCol = $(nTh); - that.dom.resizeColNeighbour = $(nTh).next(); - } else if (leftHandleOn) { - that.dom.resizeCol = $(nTh).prev(); - that.dom.resizeColNeighbour = $(nTh); - } - } - - //If table width is fixed make sure both columns are resizable else just check the one. - if(this.s.init.tableWidthFixed) - resizeAvailable &= this.s.init.exclude.indexOf(parseInt($(that.dom.resizeCol).attr("data-column-index"))) == -1 && this.s.init.exclude.indexOf(parseInt($(that.dom.resizeColNeighbour).attr("data-column-index"))) == -1; - else - resizeAvailable &= this.s.init.exclude.indexOf(parseInt($(that.dom.resizeCol).attr("data-column-index"))) == -1; - - $(nTh).off('mousedown.ColResize'); - if (resizeAvailable) { - $(nTh).css("cursor", "ew-resize"); - - //Delay other mousedown events from the Reorder plugin - that._fnDelayEvents(null, nTh, "mousedown", "ColReorder"); - that._fnDelayEvents("click", nTh, "click", "DT"); - - $(nTh).off('mousedown.ColResize').on('mousedown.ColResize', function (e) { - e.preventDefault(); - that._fnMouseDown.call(that, e, nTh); - }) - .off('click.ColResize').on('click.ColResize', function (e) { - that._fnClick.call(that, e); - }); - } else { - $(nTh).css("cursor", "pointer"); - $(nTh).off('mousedown.ColResize click.ColResize'); - //Restore any events that were removed - that._fnRestoreEvents(); - //This is to restore column sorting on click functionality - if (!that.s.isMousedown) - //Restore click event if mouse is not down - this._fnRestoreEvents("click"); - } - }, - - "_fnClick": function (e) { - var that = this; - that.s.isMousedown = false; - e.stopImmediatePropagation(); - }, - - /** - * Finish off the mouse drag - * @method _fnMouseUp - * @param e Mouse event - * @returns void - * @private - */ - "_fnMouseUp": function (e) { - var that = this; - that.s.isMousedown = false; - - //Fix width of column to be the size the dom is limited to (for when user sets min-width on a column) - that.s.mouse.targetColumn.width = that.dom.resizeCol.width(); - - $(document).off('mousemove.ColResize mouseup.ColResize'); - this.s.dt.oInstance.fnAdjustColumnSizing(); - //Table width fix, prevents extra gaps between tables - var LeftWrapper = $(that.s.dt.nTableWrapper).find(".DTFC_LeftWrapper"); - var DTFC_LeftWidth = LeftWrapper.width(); - LeftWrapper.children(".DTFC_LeftHeadWrapper").children("table").width(DTFC_LeftWidth); - - if (that.s.init.resizeCallback) { - that.s.init.resizeCallback.call(that, that.s.mouse.targetColumn); - } - }, - - /** - * Clean up ColResize memory references and event handlers - * @method _fnDestroy - * @returns void - * @private - */ - "_fnDestroy": function () { - var i, iLen; - - for (i = 0, iLen = this.s.dt.aoDrawCallback.length; i < iLen; i++) { - if (this.s.dt.aoDrawCallback[i].sName === 'ColResize_Pre') { - this.s.dt.aoDrawCallback.splice(i, 1); - break; - } - } - - $(this.s.dt.nTHead).find('*').off('.ColResize'); - - $.each(this.s.dt.aoColumns, function (i, column) { - $(column.nTh).removeAttr('data-column-index'); - }); - - this.s.dt._colResize = null; - this.s = null; - }, - - - /** - * Add a data attribute to the column headers, so we know the index of - * the row to be reordered. This allows fast detection of the index, and - * for this plug-in to work with FixedHeader which clones the nodes. - * @private - */ - "_fnSetColumnIndexes": function () { - $.each(this.s.dt.aoColumns, function (i, column) { - $(column.nTh).attr('data-column-index', i); - }); - } - }; - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Static parameters - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - - /** - * ColResize default settings for initialisation - * @namespace - * @static - */ - ColResize.defaults = { - /** - * Callback function that is fired when columns are resized - * @type function():void - * @default null - * @static - */ - "resizeCallback": null, - - /** - * Exclude array for columns that are not resizable - * @property exclude - * @type array of indexes that are excluded from resizing - * @default [] - */ - "exclude": [], - - /** - * Check to see if user is using a fixed table width or dynamic - * if true: - * -Columns will resize themselves and their neighbour - * -If neighbour is excluded resize will not occur - * if false: - * -Columns will resize themselves and increase or decrease the width of the table accordingly - */ - "tableWidthFixed": true, - - /** - * Width of the resize handle in pixels - * @property handleWidth - * @type int (pixels) - * @default 10 - */ - "handleWidth": 10, - - /** - * Right to left support, when true flips which column they are resizing on mouse down - * @property rtl - * @type bool - * @default false - */ - "rtl": false - }; - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Constants - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - /** - * ColResize version - * @constant version - * @type String - * @default As code - */ - ColResize.version = "0.0.10"; - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * DataTables interfaces - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - - // Expose - $.fn.dataTable.ColResize = ColResize; - $.fn.DataTable.ColResize = ColResize; - - - // Register a new feature with DataTables - if (typeof $.fn.dataTable == "function" && - typeof $.fn.dataTableExt.fnVersionCheck == "function" && - $.fn.dataTableExt.fnVersionCheck('1.9.3')) { - $.fn.dataTableExt.aoFeatures.push({ - "fnInit": function (settings) { - var table = settings.oInstance; - - if (!settings._colResize) { - var dtInit = settings.oInit; - var opts = dtInit.colResize || dtInit.oColResize || {}; - - new ColResize(settings, opts); - } - else { - table.oApi._fnLog(settings, 1, "ColResize attempted to initialise twice. Ignoring second"); - } - - return null; - /* No node for DataTables to insert */ - }, - "cFeature": "Z", - "sFeature": "ColResize" - }); - } else { - alert("Warning: ColResize requires DataTables 1.9.3 or greater - www.datatables.net/download"); - } - - -// API augmentation - if ($.fn.dataTable.Api) { - $.fn.dataTable.Api.register('colResize.reset()', function () { - return this.iterator('table', function (ctx) { - ctx._colResize.fnReset(); - }); - }); - } - - return ColResize; - }; // /factory - - -// Define as an AMD module if possible -if ( typeof define === 'function' && define.amd ) { - define( ['jquery', 'datatables'], factory ); -} -else if ( typeof exports === 'object' ) { - // Node/CommonJS - factory( require('jquery'), require('datatables') ); -} -else if (jQuery && !jQuery.fn.dataTable.ColResize) { - // Otherwise simply initialise as normal, stopping multiple evaluation - factory(jQuery, jQuery.fn.dataTable); -} - - -})(window, document); \ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.colResize2.js b/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.colResize2.js deleted file mode 100644 index 6ef9907a..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.colResize2.js +++ /dev/null @@ -1,943 +0,0 @@ -var dt; -(function (dt) { - var ColResize = (function () { - function ColResize($, api, settings) { - this.$ = $; - this.tableSize = -1; - this.initialized = false; - this.dt = {}; - this.dom = { - fixedLayout: false, - fixedHeader: null, - winResizeTimer: null, - mouse: { - startX: -1, - startWidth: null - }, - table: { - prevWidth: null - }, - origState: true, - resize: false, - scrollHead: null, - scrollHeadTable: null, - scrollFoot: null, - scrollFootTable: null, - scrollFootInner: null, - scrollBody: null, - scrollBodyTable: null, - scrollX: false, - scrollY: false - }; - this.settings = this.$.extend(true, {}, dt.ColResize.defaultSettings, settings); - this.dt.settings = api.settings()[0]; - this.dt.api = api; - this.dt.settings.colResize = this; - this.registerCallbacks(); - } - ColResize.prototype.initialize = function () { - var _this = this; - this.$.each(this.dt.settings.aoColumns, function (i, col) { - return _this.setupColumn(col); - }); - - //Initialize fixedHeader if specified - if (this.settings.fixedHeader) - this.setupFixedHeader(); - - //Save scroll head and body if found - this.dom.scrollHead = this.$('div.' + this.dt.settings.oClasses.sScrollHead, this.dt.settings.nTableWrapper); - this.dom.scrollHeadInner = this.$('div.' + this.dt.settings.oClasses.sScrollHeadInner, this.dom.scrollHead); - this.dom.scrollHeadTable = this.$('div.' + this.dt.settings.oClasses.sScrollHeadInner + ' > table', this.dom.scrollHead); - - this.dom.scrollFoot = this.$('div.' + this.dt.settings.oClasses.sScrollFoot, this.dt.settings.nTableWrapper); - this.dom.scrollFootInner = this.$('div.' + this.dt.settings.oClasses.sScrollFootInner, this.dom.scrollFoot); - this.dom.scrollFootTable = this.$('div.' + this.dt.settings.oClasses.sScrollFootInner + ' > table', this.dom.scrollFoot); - - this.dom.scrollBody = this.$('div.' + this.dt.settings.oClasses.sScrollBody, this.dt.settings.nTableWrapper); - this.dom.scrollBodyTable = this.$('> table', this.dom.scrollBody); - this.dt.api.on('draw.dt', this.onDraw.bind(this)); - if (this.dom.scrollFootTable) { - this.dt.api.on('colPinFcDraw.dt', function (e, colPin, data) { - if (data.leftClone.header) - _this.$('tfoot', data.leftClone.header).remove(); - if (data.leftClone.footer) - _this.$('thead', data.leftClone.footer).remove(); - if (data.rightClone.header) - _this.$('tfoot', data.rightClone.header).remove(); - if (data.rightClone.footer) - _this.$('thead', data.rightClone.footer).remove(); - }); - } - - this.dom.scrollX = this.dt.settings.oInit.sScrollX === undefined ? false : true; - this.dom.scrollY = this.dt.settings.oInit.sScrollY === undefined ? false : true; - - //SaveTableWidth - this.dt.settings.sTableWidthOrig = this.$(this.dt.settings.nTable).width(); - this.updateTableSize(); - - this.dt.settings.oFeatures.bAutoWidthOrig = this.dt.settings.oFeatures.bAutoWidth; - this.dt.settings.oFeatures.bAutoWidth = false; - - if (this.dt.settings.oInit.bStateSave && this.dt.settings.oLoadedState) { - this.loadState(this.dt.settings.oLoadedState); - } - - this.onDraw(); - this.dom.table.preWidth = parseFloat(this.dom.scrollBodyTable.css('width')); - - if (!this.dom.scrollX && this.dom.scrollY && this.settings.fixedLayout && this.dt.settings._reszEvt) { - //We have to manually resize columns on window resize - var eventName = 'resize.DT-' + this.dt.settings.sInstance; - this.$(window).off(eventName); - this.$(window).on(eventName, function () { - _this.proportionallyColumnSizing(); - //api._fnAdjustColumnSizing(this.dt.settings); - }); - } - - if (this.dom.scrollX || this.dom.scrollY) { - this.dt.api.on('column-sizing.dt', this.fixFootAndHeadTables.bind(this)); - this.dt.api.on('column-visibility.dt', this.fixFootAndHeadTables.bind(this)); - } - - this.initialized = true; - this.dt.settings.oApi._fnCallbackFire(this.dt.settings, 'colResizeInitCompleted', 'colResizeInitCompleted', [this]); - }; - - ColResize.prototype.setupColumn = function (col) { - var _this = this; - var $th = this.$(col.nTh); - if (col.resizable === false) - return; - - // listen to mousemove event for resize - $th.on('mousemove.ColResize', function (e) { - if (_this.dom.resize || col.resizable === false) - return; - - /* Store information about the mouse position */ - var $thTarget = e.target.nodeName.toUpperCase() == 'TH' ? _this.$(e.target) : _this.$(e.target).closest('TH'); - var offset = $thTarget.offset(); - var nLength = $thTarget.innerWidth(); - - /* are we on the col border (if so, resize col) */ - if (Math.abs(e.pageX - Math.round(offset.left + nLength)) <= 5) { - $thTarget.css({ 'cursor': 'col-resize' }); - } else - $thTarget.css({ 'cursor': 'pointer' }); - }); - - //Save the original width - col._ColResize_sWidthOrig = col.sWidthOrig; - col.initWidth = $th.width(); - col.minWidthOrig = col.minWidth; - - $th.on('dblclick.ColResize', function (e) { - _this.onDblClick(e, $th, col); - }); - - $th.off('mousedown.ColReorder'); - - // listen to mousedown event - $th.on('mousedown.ColResize', function (e) { - return _this.onMouseDown(e, col); - }); - }; - - ColResize.prototype.setupFixedHeader = function () { - var fhSettings = this.settings.fixedHeader === true ? undefined : this.settings.fixedHeader; - - //If left or right option was set to true disable resizing for the first or last column - if (this.$.isPlainObject(fhSettings)) { - var columns = this.dt.settings.aoColumns; - if (fhSettings.left === true) - columns[0].resizable = false; - if (fhSettings.right === true) - columns[columns.length - 1].resizable = false; - } - - this.dom.fixedHeader = new this.$.fn.dataTable.FixedHeader(this.dt.api, fhSettings); - var origUpdateClones = this.dom.fixedHeader._fnUpdateClones; - var that = this; - - //FixeHeader doesn't have any callback after updating the clones so we have to wrap the orig function - this.dom.fixedHeader._fnUpdateClones = function () { - origUpdateClones.apply(this, arguments); - that.memorizeFixedHeaderNodes(); - }; - - //As we missed the first call of _fnUpdateClones we have to call memorizeFixedHeaderNodes function manually - this.memorizeFixedHeaderNodes(); - }; - - ColResize.prototype.memorizeFixedHeaderNodes = function () { - var _this = this; - var fhSettings = this.dom.fixedHeader.fnGetSettings(); - var fhCache = fhSettings.aoCache; - var i, col; - for (i = 0; i < fhCache.length; i++) { - var type = fhCache[i].sType; - var propName; - var selector; - switch (type) { - case 'fixedHeader': - propName = 'fhTh'; - selector = 'thead>tr>th'; - this.dt.settings.fhTHead = fhCache[i].nNode; - break; - case 'fixedFooter': - propName = 'fhTf'; - selector = 'thead>tr>th'; - - //prepend a cloned thead to the floating footer table so that resizing will work correctly - var tfoot = this.$(fhCache[i].nNode); - var thead = this.$(this.dt.settings.nTHead).clone().css({ height: 0, visibility: 'hidden' }); - this.$('tr', thead).css('height', 0); - this.$('tr>th', thead).css({ - 'height': 0, - 'padding-bottom': 0, - 'padding-top': 0, - 'border-bottom-width': 0, - 'border-top-width': 0, - 'line-height': 0 - }); - tfoot.prepend(thead); - this.$('tfoot>tr>th', tfoot).css('width', ''); - this.dt.settings.fhTFoot = fhCache[i].nNode; - break; - default: - continue; - } - - this.$(selector, fhCache[i].nNode).each(function (j, th) { - col = _this.getVisibleColumn(j); - col[propName] = th; - }); - } - }; - - //zero based index - ColResize.prototype.getVisibleColumn = function (idx) { - var columns = this.dt.settings.aoColumns; - var currVisColIdx = -1; - for (var i = 0; i < columns.length; i++) { - if (!columns[i].bVisible) - continue; - currVisColIdx++; - if (currVisColIdx == idx) - return columns[i]; - } - return null; - }; - - ColResize.prototype.updateTableSize = function () { - if (this.dom.scrollX && this.dom.scrollHeadTable.length) - this.tableSize = this.dom.scrollHeadTable.width(); - else - this.tableSize = -1; - }; - - ColResize.prototype.proportionallyColumnSizing = function () { - var _this = this; - var prevWidths = [], newWidths = [], prevWidth, newWidth, newTableWidth, prevTableWidth, moveLength, multiplier, cWidth, i, j, delay = 500, prevTotalColWidths = 0, currTotalColWidths, columnRestWidths = [], columns = this.dt.settings.aoColumns, bodyTableColumns = this.$('thead th', this.dom.scrollBodyTable), headTableColumns = this.$('thead th', this.dom.scrollHeadTable), footTableColumns = this.dom.scrollFootTable.length ? this.$('thead th', this.dom.scrollFootTable) : [], visColumns = []; - - for (i = 0; i < columns.length; i++) { - if (!columns[i].bVisible) - continue; - visColumns.push(columns[i]); - columnRestWidths.push(0); //set default value - } - - for (i = 0; i < bodyTableColumns.length; i++) { - cWidth = parseFloat(bodyTableColumns[i].style.width); - prevTotalColWidths += cWidth; - prevWidths.push(cWidth); - } - - for (i = 0; i < bodyTableColumns.length; i++) { - bodyTableColumns[i].style.width = ''; - } - - //Get the new table width calculated by the browser - newTableWidth = parseFloat(this.dom.scrollBodyTable.css('width')); - - //Get the old table width - prevTableWidth = this.dom.table.preWidth; - moveLength = newTableWidth - prevTableWidth; - if (moveLength == 0) { - for (i = 0; i < bodyTableColumns.length; i++) { - bodyTableColumns[i].style.width = prevWidths[i] + 'px'; - } - return; - } - - //var tot = 0; - currTotalColWidths = prevTotalColWidths; - for (i = 0; i < visColumns.length; i++) { - //For each column calculate the new width - prevWidth = prevWidths[i]; - multiplier = (+(prevWidth / prevTotalColWidths).toFixed(2)); - - //tot += multiplier; - newWidth = prevWidth + (moveLength * multiplier) + columnRestWidths[i]; - currTotalColWidths -= prevWidth; - - //Check whether the column can be resized to the new calculated value - //if not, set it to the min or max width depends on the moveLength value - if (!this.canResizeColumn(visColumns[i], newWidth)) { - cWidth = moveLength > 0 ? this.getColumnMaxWidth(visColumns[i]) : this.getColumnMinWidth(visColumns[i]); - var rest = newWidth - cWidth; - newWidth = cWidth; - - for (j = (i + 1); j < visColumns.length; j++) { - columnRestWidths[j] += rest * (+(visColumns[j] / currTotalColWidths).toFixed(2)); - } - } - newWidths.push(newWidth); - } - - //Apply the calculated column widths to the headers cells - var tablesWidth = this.dom.scrollBodyTable.outerWidth() + 'px'; - for (i = 0; i < headTableColumns.length; i++) { - headTableColumns[i].style.width = newWidths[i] + 'px'; - } - this.dom.scrollHeadTable.css('width', tablesWidth); - this.dom.scrollHeadInner.css('width', tablesWidth); - - for (i = 0; i < bodyTableColumns.length; i++) { - bodyTableColumns[i].style.width = newWidths[i] + 'px'; - } - - if (this.dom.scrollFootTable.length) { - for (i = 0; i < footTableColumns.length; i++) { - footTableColumns[i].style.width = newWidths[i] + 'px'; - } - this.dom.scrollFootTable[0].style.width = tablesWidth; - this.dom.scrollFootInner[0].style.width = tablesWidth; - } - - //console.log('moveLength: ' + moveLength + ' multiplier: ' + tot); - //console.log(newWidths); - this.dom.table.preWidth = newTableWidth; - - //Call afterResizing function after the window stops resizing - if (this.dom.winResizeTimer) - clearTimeout(this.dom.winResizeTimer); - this.dom.winResizeTimer = setTimeout(function () { - _this.afterResizing(); - _this.dom.winResizeTimer = null; - }, delay); - }; - - ColResize.prototype.getColumnIndex = function (col) { - //Get the current column position - var colIdx = -1; - for (var i = 0; i < this.dt.settings.aoColumns.length; i++) { - if (this.dt.settings.aoColumns[i] === col) { - colIdx = i; - break; - } - } - return colIdx; - }; - - ColResize.prototype.getColumnEvent = function (th, type, ns) { - var event; - var thEvents = this.$._data(th, "events"); - this.$.each(thEvents[type] || [], function (idx, handler) { - if (handler.namespace === ns) - event = handler; - }); - return event; - }; - - ColResize.prototype.loadState = function (data) { - var _this = this; - var i, col; - - var onInit = function () { - if (_this.settings.fixedLayout) { - _this.setTablesLayout('fixed'); - } else { - _this.setTablesLayout('auto'); - } - if (!data.colResize) { - if (_this.dt.settings.oFeatures.bAutoWidthOrig) - _this.dt.settings.oFeatures.bAutoWidth = true; - else if (_this.dt.settings.sTableWidthOrig) - _this.$(_this.dt.settings.nTable).width(_this.dt.settings.sTableWidthOrig); - - for (i = 0; i < _this.dt.settings.aoColumns.length; i++) { - col = _this.dt.settings.aoColumns[i]; - if (col._ColResize_sWidthOrig) { - col.sWidthOrig = col._ColResize_sWidthOrig; - } - } - _this.dom.origState = true; - } else { - var columns = data.colResize.columns || []; - var wMap = {}; - - if (_this.dt.settings.oFeatures.bAutoWidth) { - _this.dt.settings.oFeatures.bAutoWidth = false; - } - - if (_this.dom.scrollX && data.colResize.tableSize > 0) { - _this.dom.scrollHeadTable.width(data.colResize.tableSize); - _this.dom.scrollHeadInner.width(data.colResize.tableSize); - _this.dom.scrollBodyTable.width(data.colResize.tableSize); - _this.dom.scrollFootTable.width(data.colResize.tableSize); - } - - for (i = 0; i < columns.length; i++) { - wMap[i] = columns[i]; - } - for (i = 0; i < _this.dt.settings.aoColumns.length; i++) { - col = _this.dt.settings.aoColumns[i]; - var idx = col._ColReorder_iOrigCol != null ? col._ColReorder_iOrigCol : i; - col.sWidth = wMap[idx]; - col.sWidthOrig = wMap[idx]; - col.nTh.style.width = columns[i]; - - //Check for FixedHeader - if (col.fhTh) - col.fhTh.style.width = columns[i]; - if (col.fhTf) - col.fhTf.style.width = columns[i]; - } - _this.dom.origState = false; - } - - _this.dt.api.columns.adjust(); - if (_this.dom.scrollX || _this.dom.scrollY) - _this.dt.api.draw(false); - }; - - if (this.initialized) { - onInit(); - return; - } - this.dt.settings.oApi._fnCallbackReg(this.dt.settings, 'colResizeInitCompleted', function () { - onInit(); - }, "ColResize_Init"); - }; - - ColResize.prototype.saveState = function (data) { - if (!this.dt.settings._bInitComplete) { - var oData = this.dt.settings.fnStateLoadCallback.call(this.dt.settings.oInstance, this.dt.settings); - if (oData && oData.colResize) - data.colResize = oData.colResize; - return; - } - this.updateTableSize(); - data.colResize = { - columns: [], - tableSize: this.tableSize - }; - - data.colResize.columns.length = this.dt.settings.aoColumns.length; - for (var i = 0; i < this.dt.settings.aoColumns.length; i++) { - var col = this.dt.settings.aoColumns[i]; - var idx = col._ColReorder_iOrigCol != null ? col._ColReorder_iOrigCol : i; - data.colResize.columns[idx] = col.sWidth; - } - }; - - ColResize.prototype.registerCallbacks = function () { - var _this = this; - /* State saving */ - this.dt.settings.oApi._fnCallbackReg(this.dt.settings, 'aoStateSaveParams', function (oS, oData) { - _this.saveState(oData); - }, "ColResize_StateSave"); - - /* State loading */ - this.dt.settings.oApi._fnCallbackReg(this.dt.settings, 'aoStateLoaded', function (oS, oData) { - _this.loadState(oData); - }, "ColResize_StateLoad"); - - if (this.$.fn.DataTable.models.oSettings.remoteStateInitCompleted !== undefined) { - //Integrate with remote state - this.dt.settings.oApi._fnCallbackReg(this.dt.settings, 'remoteStateLoadedParams', function (s, data) { - _this.loadState(data); - }, "ColResize_StateLoad"); - this.dt.settings.oApi._fnCallbackReg(this.dt.settings, 'remoteStateSaveParams', function (s, data) { - _this.saveState(data); - }, "ColResize_StateSave"); - } - }; - - ColResize.prototype.setTablesLayout = function (value) { - if (this.dom.scrollX || this.dom.scrollY) { - this.dom.scrollHeadTable.css('table-layout', value); - this.dom.scrollBodyTable.css('table-layout', value); - this.dom.scrollFootTable.css('table-layout', value); - } else { - this.$(this.dt.settings.nTable).css('table-layout', value); - } - this.dom.fixedLayout = value == 'fixed'; - }; - - //only when scrollX or scrollY are enabled - ColResize.prototype.fixFootAndHeadTables = function (e) { - var _this = this; - if (e != null && e.target !== this.dt.settings.nTable) - return; - - if (this.dom.scrollFootTable.length) { - this.$('thead', this.dom.scrollFootTable).remove(); - this.dom.scrollFootTable.prepend(this.$('thead', this.dom.scrollBodyTable).clone()); - } - this.$('tfoot', this.dom.scrollHeadTable).remove(); - this.dom.scrollHeadTable.append(this.$('tfoot', this.dom.scrollBodyTable).clone()); - var removeFooterWidth = function (table) { - _this.$('tfoot>tr>th', table).each(function (i, th) { - _this.$(th).css('width', ''); - }); - }; - - //Remove all tfoot headers widths - removeFooterWidth(this.dom.scrollFootTable); - removeFooterWidth(this.dom.scrollBodyTable); - removeFooterWidth(this.dom.scrollHeadTable); - }; - - ColResize.prototype.onDraw = function (e) { - if (e != null && e.target !== this.dt.settings.nTable) - return; - if (this.dom.scrollX || this.dom.scrollY) { - this.fixFootAndHeadTables(); - - //Fix the header table padding - if (this.dt.settings._bInitComplete) { - var borderWidth = this.dom.scrollHeadTable.outerWidth() - this.dom.scrollHeadTable.innerWidth(); - var paddingType = this.dt.settings.oBrowser.bScrollbarLeft ? 'padding-left' : 'padding-right'; - var paddingVal = parseFloat(this.dom.scrollHeadInner.css(paddingType)); - this.dom.scrollHeadInner.css(paddingType, (paddingVal + borderWidth) + 'px'); - } - } - - var autoWidthTypes = []; - if (this.settings.dblClick == 'autoMinFit' || !this.settings.fixedLayout) - autoWidthTypes.push('autoMinWidth'); - if (this.settings.dblClick == 'autoFit') - autoWidthTypes.push('autoWidth'); - - //Call this only once so that the table will be cloned only one time - if (autoWidthTypes.length) - this.updateColumnsAutoWidth(autoWidthTypes); - - if (!this.settings.fixedLayout) { - var columns = this.dt.settings.aoColumns; - var i; - for (i = 0; i < columns.length; i++) { - if (!columns[i].bVisible) - continue; - columns[i].minWidth = Math.max((columns[i].minWidthOrig || 0), columns[i].autoMinWidth); - - //We have to resize if the current column width if is less that the column minWidth - if (this.$(columns[i].nTh).width() < columns[i].minWidth) - this.resize(columns[i], columns[i].minWidth); - } - } else { - if (!this.dom.fixedLayout) { - this.setTablesLayout('fixed'); - this.afterResizing(); - } - } - }; - - ColResize.prototype.getTableAutoColWidths = function (table, types) { - var widths = {}, i, colIdx; - var $table = this.$(table); - for (i = 0; i < types.length; i++) { - widths[types[i]] = []; - } - if (!types.length || !$table.length) - return widths; - - var clnTable = $table.clone().removeAttr('id').css('table-layout', 'auto'); - - // Remove any assigned widths from the footer (from scrolling) - clnTable.find('thead th, tfoot th, tfoot td').css('width', ''); - var container = this.$('
    ').css({ - 'position': 'absolute', - 'width': '9999px', - 'height': '9999px' - }); - container.append(clnTable); - this.$(this.dt.settings.nTableWrapper).append(container); - - var headerCols = this.$('thead>tr>th', clnTable); - - for (i = 0; i < types.length; i++) { - var type = types[i]; - var fn = ''; - switch (type) { - case 'autoMinWidth': - clnTable.css('width', '1px'); - fn = 'width'; - break; - case 'autoWidth': - clnTable.css('width', 'auto'); - fn = 'outerWidth'; - break; - default: - throw 'Invalid widthType ' + type; - } - for (colIdx = 0; colIdx < headerCols.length; colIdx++) { - widths[type].push(this.$(headerCols[colIdx])[fn]()); - } - } - - container.remove(); - return widths; - }; - - ColResize.prototype.updateColumnsAutoWidth = function (types) { - var columns = this.dt.settings.aoColumns; - var i, j, colLen, type, visColIdx = 0; - var widths = {}; - if (this.dom.scrollX || this.dom.scrollY) { - var headWidths = this.getTableAutoColWidths(this.dom.scrollHeadTable, types); - var bodyWidths = this.getTableAutoColWidths(this.dom.scrollBodyTable, types); - var footWidths = this.getTableAutoColWidths(this.dom.scrollFootTable, types); - - for (i = 0; i < types.length; i++) { - type = types[i]; - widths[type] = []; - footWidths[type].length = headWidths[type].length; - colLen = headWidths[type].length; - for (j = 0; j < colLen; j++) { - widths[type].push(Math.max(headWidths[type][j], bodyWidths[type][j], (footWidths[type][j] || 0))); - } - } - } else { - widths = this.getTableAutoColWidths(this.dt.settings.nTable, types); - } - - for (i = 0; i < columns.length; i++) { - if (!columns[i].bVisible) - continue; - for (j = 0; j < types.length; j++) { - type = types[j]; - columns[i][type] = widths[type][visColIdx]; - } - visColIdx++; - } - }; - - ColResize.prototype.overrideClickHander = function (col, $th) { - var dtClickEvent = this.getColumnEvent($th.get(0), 'click', 'DT'); - - //Remove the DataTables event so that ordering will not occur - if (dtClickEvent) { - $th.off('click.DT'); - this.$(document).one('click.ColResize', function (e) { - $th.on('click.DT', dtClickEvent.handler); - }); - } - }; - - ColResize.prototype.onDblClick = function (e, $th, col) { - if (e.target !== $th.get(0)) - return; - if ($th.css('cursor') != 'col-resize') - return; - - var width; - switch (this.settings.dblClick) { - case 'autoMinFit': - width = col.autoMinWidth; - break; - case 'autoFit': - width = col.autoWidth; - break; - default: - width = col.initWidth; - } - this.resize(col, width); - }; - - ColResize.prototype.onMouseDown = function (e, col) { - var _this = this; - if (e.target !== col.nTh && e.target !== col.fhTh) - return true; - - var $th = e.target === col.nTh ? this.$(col.nTh) : this.$(col.fhTh); - - if ($th.css('cursor') != 'col-resize' || col.resizable === false) { - var colReorder = this.dt.settings._colReorder; - if (colReorder) { - colReorder._fnMouseDown.call(colReorder, e, e.target); //Here we fix the e.preventDefault() in ColReorder so that we can have working inputs in header - } - return true; - } - this.dom.mouse.startX = e.pageX; - this.dom.mouse.prevX = e.pageX; - this.dom.mouse.startWidth = $th.width(); - this.dom.resize = true; - - this.beforeResizing(col); - - /* Add event handlers to the document */ - this.$(document).on('mousemove.ColResize', function (event) { - _this.onMouseMove(event, col); - }); - this.overrideClickHander(col, $th); - this.$(document).one('mouseup.ColResize', function (event) { - _this.onMouseUp(event, col); - }); - - return false; - }; - - ColResize.prototype.resize = function (col, width) { - var colWidth = this.$(col.nTh).width(); - var moveLength = width - this.$(col.nTh).width(); - this.beforeResizing(col); - var resized = this.resizeColumn(col, colWidth, moveLength, moveLength); - this.afterResizing(); - return resized; - }; - - ColResize.prototype.beforeResizing = function (col) { - //if (this.settings.fixedLayout && !this.dom.fixedLayout) - // this.setTablesLayout('fixed'); - }; - - ColResize.prototype.afterResizing = function () { - var i; - var columns = this.dt.settings.aoColumns; - for (i = 0; i < columns.length; i++) { - if (!columns[i].bVisible) - continue; - columns[i].sWidth = this.$(columns[i].nTh).css('width'); - } - - //Update the internal storage of the table's width (in case we changed it because the user resized some column and scrollX was enabled - this.updateTableSize(); - - //Save the state - this.dt.settings.oInstance.oApi._fnSaveState(this.dt.settings); - this.dom.origState = false; - }; - - ColResize.prototype.onMouseUp = function (e, col) { - this.$(document).off('mousemove.ColResize'); - if (!this.dom.resize) - return; - this.dom.resize = false; - this.afterResizing(); - }; - - ColResize.prototype.canResizeColumn = function (col, newWidth) { - return (col.resizable === undefined || col.resizable) && this.settings.minWidth <= newWidth && (!col.minWidth || col.minWidth <= newWidth) && (!this.settings.maxWidth || this.settings.maxWidth >= newWidth) && (!col.maxWidth || col.maxWidth >= newWidth); - }; - - ColResize.prototype.getColumnMaxWidth = function (col) { - return col.maxWidth ? col.maxWidth : this.settings.maxWidth; - }; - - ColResize.prototype.getColumnMinWidth = function (col) { - return col.minWidth ? col.minWidth : this.settings.minWidth; - }; - - ColResize.prototype.getPrevResizableColumnIdx = function (col, moveLength) { - var columns = this.dt.settings.aoColumns; - var colIdx = ColResizeHelper.indexOf(columns, col); - for (var i = colIdx; i >= 0; i--) { - if (!columns[i].bVisible) - continue; - var newWidth = this.$(columns[i].nTh).width() + moveLength; - if (this.canResizeColumn(columns[i], newWidth)) - return i; - } - return -1; - }; - - ColResize.prototype.getNextResizableColumnIdx = function (col, moveLength) { - var columns = this.dt.settings.aoColumns; - var colIdx = ColResizeHelper.indexOf(columns, col); - for (var i = (colIdx + 1); i < columns.length; i++) { - if (!columns[i].bVisible) - continue; - var newWidth = this.$(columns[i].nTh).width() - moveLength; - if (this.canResizeColumn(columns[i], newWidth)) - return i; - } - return -1; - }; - - ColResize.prototype.resizeColumn = function (col, startWidth, moveLength, lastMoveLength) { - if (moveLength == 0 || lastMoveLength == 0 || col.resizable === false) - return false; - var i; - var columns = this.dt.settings.aoColumns; - var headCol = this.$(col.nTh); - var headColNext = headCol.next(); - var colIdx = this.getColumnIndex(col); - var thWidth = startWidth + moveLength; - var thNextWidth; - var nextColIdx; - - if (!this.dom.scrollX) { - if (lastMoveLength < 0) { - thWidth = headColNext.width() - lastMoveLength; - nextColIdx = this.getPrevResizableColumnIdx(col, lastMoveLength); - if (nextColIdx < 0) - return false; - headCol = headColNext; - colIdx = colIdx + 1; - headColNext = this.$(columns[nextColIdx].nTh); - thNextWidth = headColNext.width() + lastMoveLength; - } else { - thWidth = headCol.width() + lastMoveLength; - nextColIdx = this.getNextResizableColumnIdx(col, lastMoveLength); - - //If there is no columns that can be shrinked dont resize anymore - if (nextColIdx < 0) - return false; - headColNext = this.$(columns[nextColIdx].nTh); - thNextWidth = headColNext.width() - lastMoveLength; - - if ((this.settings.maxWidth && this.settings.maxWidth < thWidth) || col.maxWidth && col.maxWidth < thWidth) - return false; - } - if (!this.canResizeColumn(columns[nextColIdx], thNextWidth) || !this.canResizeColumn(columns[colIdx], thWidth)) - return false; - headColNext.width(thNextWidth); - - //If fixed header is present we have to resize the cloned column too - if (this.dom.fixedHeader) { - this.$(columns[nextColIdx].fhTh).width(thNextWidth); - this.$(columns[colIdx].fhTh).width(thWidth); - - //If fixedfooter was enabled resize that too - if (columns[nextColIdx].fhTf) { - this.$(columns[nextColIdx].fhTf).width(thNextWidth); - this.$(columns[colIdx].fhTf).width(thWidth); - } - } - } else { - if (!this.canResizeColumn(col, thWidth)) - return false; - var tSize = this.tableSize + moveLength + 'px'; - this.dom.scrollHeadInner.css('width', tSize); - this.dom.scrollHeadTable.css('width', tSize); - this.dom.scrollBodyTable.css('width', tSize); - this.dom.scrollFootTable.css('width', tSize); - } - headCol.width(thWidth); - - //scrollX or scrollY enabled - if (this.dom.scrollBody.length) { - var colDomIdx = 0; - for (i = 0; i < this.dt.settings.aoColumns.length && i != colIdx; i++) { - if (this.dt.settings.aoColumns[i].bVisible) - colDomIdx++; - } - - //Get the table - var bodyCol = this.$('thead>tr>th:nth(' + colDomIdx + ')', this.dom.scrollBodyTable); - var footCol = this.$('thead>tr>th:nth(' + colDomIdx + ')', this.dom.scrollFootTable); - - //This will happen only when scrollY is used without scrollX - if (!this.dom.scrollX) { - var nextColDomIdx = 0; - for (i = 0; i < this.dt.settings.aoColumns.length && i != nextColIdx; i++) { - if (this.dt.settings.aoColumns[i].bVisible) - nextColDomIdx++; - } - var bodyColNext = this.$('thead>tr>th:nth(' + nextColDomIdx + ')', this.dom.scrollBodyTable); - var footColNext = this.$('thead>tr>th:nth(' + nextColDomIdx + ')', this.dom.scrollFootTable); - - bodyColNext.width(thNextWidth); - if (thWidth > 0) - bodyCol.width(thWidth); - - footColNext.width(thNextWidth); - if (thWidth > 0) - footCol.width(thWidth); - } - - //Resize the table and the column - if (this.dom.scrollX && thWidth > 0) { - bodyCol.width(thWidth); - footCol.width(thWidth); - } - } - return true; - }; - - ColResize.prototype.onMouseMove = function (e, col) { - var moveLength = e.pageX - this.dom.mouse.startX; - var lastMoveLength = e.pageX - this.dom.mouse.prevX; - this.resizeColumn(col, this.dom.mouse.startWidth, moveLength, lastMoveLength); - this.dom.mouse.prevX = e.pageX; - }; - - ColResize.prototype.destroy = function () { - }; - ColResize.defaultSettings = { - minWidth: 1, - maxWidth: null, - fixedLayout: true, - fixedHeader: null, - dblClick: 'initWidth' - }; - return ColResize; - })(); - dt.ColResize = ColResize; - - var ColResizeHelper = (function () { - function ColResizeHelper() { - } - ColResizeHelper.indexOf = function (arr, item, equalFun) { - if (typeof equalFun === "undefined") { equalFun = null; } - for (var i = 0; i < arr.length; i++) { - if (equalFun) { - if (equalFun(arr[i], item)) - return i; - } else if (arr[i] === item) - return i; - } - return -1; - }; - return ColResizeHelper; - })(); - dt.ColResizeHelper = ColResizeHelper; -})(dt || (dt = {})); - -(function ($, window, document, undefined) { - //Register events - $.fn.DataTable.models.oSettings.colResizeInitCompleted = []; - - //Register api function - $.fn.DataTable.Api.register('colResize.init()', function (settings) { - var colResize = new dt.ColResize($, this, settings); - if (this.settings()[0]._bInitComplete) - colResize.initialize(); - else - this.one('init.dt', function () { - colResize.initialize(); - }); - return null; - }); - - $.fn.DataTable.Api.register('column().resize()', function (width) { - var oSettings = this.settings()[0]; - var colResize = oSettings.colResize; - return colResize.resize(oSettings.aoColumns[this[0][0]], width); - }); - - //Add as feature - $.fn.dataTable.ext.feature.push({ - "fnInit": function (oSettings) { - return oSettings.oInstance.api().colResize.init(oSettings.oInit.colResize); - }, - "cFeature": "J", - "sFeature": "ColResize" - }); -}(jQuery, window, document, undefined)); \ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.fixedHeader.css b/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.fixedHeader.css deleted file mode 100644 index 4001ab12..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.fixedHeader.css +++ /dev/null @@ -1,4 +0,0 @@ -div.FixedHeader_Cloned th, -div.FixedHeader_Cloned td { - background-color: white !important; -} diff --git a/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.fixedHeader.min.js b/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.fixedHeader.min.js deleted file mode 100644 index e8d19608..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/extensions/dataTables.fixedHeader.min.js +++ /dev/null @@ -1,14 +0,0 @@ -/*! - FixedHeader 3.0.0 - ©2009-2015 SpryMedia Ltd - datatables.net/license -*/ -(function(h,j){var g=function(e,i){var g=0,f=function(b,a){if(!(this instanceof f))throw"FixedHeader must be initialised with the 'new' keyword.";!0===a&&(a={});b=new i.Api(b);this.c=e.extend(!0,{},f.defaults,a);this.s={dt:b,position:{theadTop:0,tbodyTop:0,tfootTop:0,tfootBottom:0,width:0,left:0,tfootHeight:0,theadHeight:0,windowHeight:e(h).height(),visible:!0},headerMode:null,footerMode:null,namespace:".dtfc"+g++};this.dom={floatingHeader:null,thead:e(b.table().header()),tbody:e(b.table().body()), -tfoot:e(b.table().footer()),header:{host:null,floating:null,placeholder:null},footer:{host:null,floating:null,placeholder:null}};this.dom.header.host=this.dom.thead.parent();this.dom.footer.host=this.dom.tfoot.parent();var c=b.settings()[0];if(c._fixedHeader)throw"FixedHeader already initialised on table "+c.nTable.id;c._fixedHeader=this;this._constructor()};f.prototype={update:function(){this._positions();this._scroll(!0)},_constructor:function(){var b=this,a=this.s.dt;e(h).on("scroll"+this.s.namespace, -function(){b._scroll()}).on("resize"+this.s.namespace,function(){b.s.position.windowHeight=e(h).height();b._positions();b._scroll(!0)});a.on("column-reorder.dt.dtfc column-visibility.dt.dtfc",function(){b._positions();b._scroll(!0)}).on("draw.dtfc",function(){b._positions();b._scroll()});a.on("destroy.dtfc",function(){a.off(".dtfc");e(h).off(this.s.namespace)});this._positions();this._scroll()},_clone:function(b,a){var c=this.s.dt,d=this.dom[b],k="header"===b?this.dom.thead:this.dom.tfoot;!a&&d.floating? -d.floating.removeClass("fixedHeader-floating fixedHeader-locked"):(d.floating&&(d.placeholder.remove(),d.floating.children().detach(),d.floating.remove()),d.floating=e(c.table().node().cloneNode(!1)).removeAttr("id").append(k).appendTo("body"),d.placeholder=k.clone(!1),d.host.append(d.placeholder),"footer"===b&&this._footerMatch(d.placeholder,d.floating))},_footerMatch:function(b,a){var c=function(d){var c=e(d,b).map(function(){return e(this).width()}).toArray();e(d,a).each(function(a){e(this).width(c[a])})}; -c("th");c("td")},_footerUnsize:function(){var b=this.dom.footer.floating;b&&e("th, td",b).css("width","")},_modeChange:function(b,a,c){var d=this.dom[a],e=this.s.position;"in-place"===b?(d.placeholder&&(d.placeholder.remove(),d.placeholder=null),d.host.append("header"===a?this.dom.thead:this.dom.tfoot),d.floating&&(d.floating.remove(),d.floating=null),"footer"===a&&this._footerUnsize()):"in"===b?(this._clone(a,c),d.floating.addClass("fixedHeader-floating").css("header"===a?"top":"bottom",this.c[a+ -"Offset"]).css("left",e.left+"px").css("width",e.width+"px"),"footer"===a&&d.floating.css("top","")):"below"===b?(this._clone(a,c),d.floating.addClass("fixedHeader-locked").css("top",e.tfootTop-e.theadHeight).css("left",e.left+"px").css("width",e.width+"px")):"above"===b&&(this._clone(a,c),d.floating.addClass("fixedHeader-locked").css("top",e.tbodyTop).css("left",e.left+"px").css("width",e.width+"px"));this.s[a+"Mode"]=b},_positions:function(){var b=this.s.dt.table(),a=this.s.position,c=this.dom, -b=e(b.node()),d=b.children("thead"),f=b.children("tfoot"),c=c.tbody;a.visible=b.is(":visible");a.width=b.outerWidth();a.left=b.offset().left;a.theadTop=d.offset().top;a.tbodyTop=c.offset().top;a.theadHeight=a.tbodyTop-a.theadTop;f.length?(a.tfootTop=f.offset().top,a.tfootBottom=a.tfootTop+f.outerHeight(),a.tfootHeight=a.tfootBottom-a.tfootTop):(a.tfootTop=a.tbodyTop+c.outerHeight(),a.tfootBottom=a.tfootTop,a.tfootHeight=a.tfootTop)},_scroll:function(b){var a=e(j).scrollTop(),c=this.s.position,d;this.c.header&& -(d=!c.visible||a<=c.theadTop-this.c.headerOffset?"in-place":a<=c.tfootTop-c.theadHeight-this.c.headerOffset?"in":"below",(b||d!==this.s.headerMode)&&this._modeChange(d,"header",b));this.c.footer&&this.dom.tfoot.length&&(a=!c.visible||a+c.windowHeight>=c.tfootBottom+this.c.footerOffset?"in-place":c.windowHeight+a>c.tbodyTop+c.tfootHeight+this.c.footerOffset?"in":"above",(b||a!==this.s.footerMode)&&this._modeChange(a,"footer",b))}};f.version="3.0.0";f.defaults={header:!0,footer:!1,headerOffset:0,footerOffset:0}; -e.fn.dataTable.FixedHeader=f;e.fn.DataTable.FixedHeader=f;e(j).on("init.dt.dtb",function(b,a){if("dt"===b.namespace){var c=a.oInit.fixedHeader||i.defaults.fixedHeader;c&&!a._buttons&&new f(a,c)}});i.Api.register("fixedHeader()",function(){});i.Api.register("fixedHeader.adjust()",function(){return this.iterator("table",function(b){(b=b._fixedHeader)&&b.update()})});return f};"function"===typeof define&&define.amd?define(["jquery","datatables"],g):"object"===typeof exports?g(require("jquery"),require("datatables")): -jQuery&&!jQuery.fn.dataTable.FixedHeader&&g(jQuery,jQuery.fn.dataTable)})(window,document); diff --git a/wqflask/wqflask/static/new/packages/DataTables/extensions/scroller.dataTables.min.css b/wqflask/wqflask/static/new/packages/DataTables/extensions/scroller.dataTables.min.css deleted file mode 100644 index 284cb376..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/extensions/scroller.dataTables.min.css +++ /dev/null @@ -1 +0,0 @@ -div.DTS{display:block !important}div.DTS tbody th,div.DTS tbody td{white-space:nowrap}div.DTS div.DTS_Loading{z-index:1}div.DTS div.dataTables_scrollBody{background:repeating-linear-gradient(45deg, #edeeff, #edeeff 10px, #fff 10px, #fff 20px)}div.DTS div.dataTables_scrollBody table{z-index:2}div.DTS div.dataTables_paginate,div.DTS div.dataTables_length{display:none} \ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/Sorting icons.psd b/wqflask/wqflask/static/new/packages/DataTables/images/Sorting icons.psd deleted file mode 100644 index 53b2e068..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/Sorting icons.psd and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/back_disabled.png b/wqflask/wqflask/static/new/packages/DataTables/images/back_disabled.png deleted file mode 100644 index 881de797..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/back_disabled.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/back_enabled.png b/wqflask/wqflask/static/new/packages/DataTables/images/back_enabled.png deleted file mode 100644 index c608682b..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/back_enabled.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/back_enabled_hover.png b/wqflask/wqflask/static/new/packages/DataTables/images/back_enabled_hover.png deleted file mode 100644 index d300f106..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/back_enabled_hover.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/favicon.ico b/wqflask/wqflask/static/new/packages/DataTables/images/favicon.ico deleted file mode 100644 index 6eeaa2a0..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/favicon.ico and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/forward_disabled.png b/wqflask/wqflask/static/new/packages/DataTables/images/forward_disabled.png deleted file mode 100644 index 6a6ded7d..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/forward_disabled.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/forward_enabled.png b/wqflask/wqflask/static/new/packages/DataTables/images/forward_enabled.png deleted file mode 100644 index a4e6b538..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/forward_enabled.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/forward_enabled_hover.png b/wqflask/wqflask/static/new/packages/DataTables/images/forward_enabled_hover.png deleted file mode 100644 index fc46c5eb..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/forward_enabled_hover.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/sort_asc.png b/wqflask/wqflask/static/new/packages/DataTables/images/sort_asc.png deleted file mode 100644 index a88d7975..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/sort_asc.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/sort_asc_disabled.png b/wqflask/wqflask/static/new/packages/DataTables/images/sort_asc_disabled.png deleted file mode 100644 index 4e144cf0..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/sort_asc_disabled.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/sort_both.png b/wqflask/wqflask/static/new/packages/DataTables/images/sort_both.png deleted file mode 100644 index 18670406..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/sort_both.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/sort_desc.png b/wqflask/wqflask/static/new/packages/DataTables/images/sort_desc.png deleted file mode 100644 index def071ed..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/sort_desc.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/images/sort_desc_disabled.png b/wqflask/wqflask/static/new/packages/DataTables/images/sort_desc_disabled.png deleted file mode 100644 index 7824973c..00000000 Binary files a/wqflask/wqflask/static/new/packages/DataTables/images/sort_desc_disabled.png and /dev/null differ diff --git a/wqflask/wqflask/static/new/packages/DataTables/js/dataTables.formattedNumbers.js b/wqflask/wqflask/static/new/packages/DataTables/js/dataTables.formattedNumbers.js deleted file mode 100644 index 7795b491..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/js/dataTables.formattedNumbers.js +++ /dev/null @@ -1,14 +0,0 @@ -jQuery.extend( jQuery.fn.dataTableExt.oSort, { - "formatted-num-pre": function ( a ) { - a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); - return parseFloat( a ); - }, - - "formatted-num-asc": function ( a, b ) { - return a - b; - }, - - "formatted-num-desc": function ( a, b ) { - return b - a; - } -} ); \ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/DataTables/js/dataTables.naturalSort.js b/wqflask/wqflask/static/new/packages/DataTables/js/dataTables.naturalSort.js deleted file mode 100644 index e96c0af3..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/js/dataTables.naturalSort.js +++ /dev/null @@ -1,68 +0,0 @@ -(function() { - -/* - * Natural Sort algorithm for Javascript - Version 0.7 - Released under MIT license - * Author: Jim Palmer (based on chunking idea from Dave Koelle) - * Contributors: Mike Grier (mgrier.com), Clint Priest, Kyle Adams, guillermo - * See: http://js-naturalsort.googlecode.com/svn/trunk/naturalSort.js - */ -function naturalSort (a, b) { - var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi, - sre = /(^[ ]*|[ ]*$)/g, - dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, - hre = /^0x[0-9a-f]+$/i, - ore = /^0/, - // convert all to strings and trim() - x = a.toString().replace(sre, '') || '', - y = b.toString().replace(sre, '') || '', - // chunk/tokenize - xN = x.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'), - yN = y.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'), - // numeric, hex or date detection - xD = parseInt(x.match(hre)) || (xN.length != 1 && x.match(dre) && Date.parse(x)), - yD = parseInt(y.match(hre)) || xD && y.match(dre) && Date.parse(y) || null; - - // first try and sort Hex codes or Dates - if (yD) - if ( xD < yD ) return -1; - else if ( xD > yD ) return 1; - - // natural sorting through split numeric strings and default strings - for(var cLoc=0, numS=Math.max(xN.length, yN.length); cLoc < numS; cLoc++) { - // find floats not starting with '0', string or 0 if not defined (Clint Priest) - var oFxNcL = !(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc]) || xN[cLoc] || 0; - var oFyNcL = !(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc]) || yN[cLoc] || 0; - // handle numeric vs string comparison - number < string - (Kyle Adams) - if (isNaN(oFxNcL) !== isNaN(oFyNcL)) return (isNaN(oFxNcL)) ? 1 : -1; - // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2' - else if (typeof oFxNcL !== typeof oFyNcL) { - oFxNcL += ''; - oFyNcL += ''; - } - if (oFxNcL < oFyNcL) return -1; - if (oFxNcL > oFyNcL) return 1; - } - - return 0; -} - -jQuery.extend( jQuery.fn.dataTableExt.oSort, { - "natural-asc": function ( a, b ) { - // first check if null or n/a - if (a == "N/A" || a == "NA" || a == "" || a == "--") return 1; - else if (b == "N/A" || b == "NA" || b == "" || b == "--") return -1; - else { - return naturalSort(a,b); - } - }, - - "natural-desc": function ( a, b ) { - if (a == "N/A" || a == "NA" || a == "" || a == "--") return 1; - else if (b == "N/A" || b == "NA" || b == "" || b == "--") return -1; - else { - return naturalSort(a,b) * -1; - } - } -} ); - -}()); \ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/DataTables/js/dataTables.scientific.js b/wqflask/wqflask/static/new/packages/DataTables/js/dataTables.scientific.js deleted file mode 100644 index 82c55fda..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/js/dataTables.scientific.js +++ /dev/null @@ -1,13 +0,0 @@ -jQuery.extend( jQuery.fn.dataTableExt.oSort, { - "scientific-pre": function ( a ) { - return parseFloat(a); - }, - - "scientific-asc": function ( a, b ) { - return ((a < b) ? -1 : ((a > b) ? 1 : 0)); - }, - - "scientific-desc": function ( a, b ) { - return ((a < b) ? 1 : ((a > b) ? -1 : 0)); - } -} ); \ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/DataTables/js/jquery.dataTables.js b/wqflask/wqflask/static/new/packages/DataTables/js/jquery.dataTables.js deleted file mode 100644 index 5b032aee..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/js/jquery.dataTables.js +++ /dev/null @@ -1,15278 +0,0 @@ -/*! DataTables 1.10.12 - * ©2008-2015 SpryMedia Ltd - datatables.net/license - */ - -/** - * @summary DataTables - * @description Paginate, search and order HTML tables - * @version 1.10.12 - * @file jquery.dataTables.js - * @author SpryMedia Ltd (www.sprymedia.co.uk) - * @contact www.sprymedia.co.uk/contact - * @copyright Copyright 2008-2015 SpryMedia Ltd. - * - * This source file is free software, available under the following license: - * MIT license - http://datatables.net/license - * - * This source file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. - * - * For details please refer to: http://www.datatables.net - */ - -/*jslint evil: true, undef: true, browser: true */ -/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ - -(function( factory ) { - "use strict"; - - if ( typeof define === 'function' && define.amd ) { - // AMD - define( ['jquery'], function ( $ ) { - return factory( $, window, document ); - } ); - } - else if ( typeof exports === 'object' ) { - // CommonJS - module.exports = function (root, $) { - if ( ! root ) { - // CommonJS environments without a window global must pass a - // root. This will give an error otherwise - root = window; - } - - if ( ! $ ) { - $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window - require('jquery') : - require('jquery')( root ); - } - - return factory( $, root, root.document ); - }; - } - else { - // Browser - factory( jQuery, window, document ); - } -} -(function( $, window, document, undefined ) { - "use strict"; - - /** - * DataTables is a plug-in for the jQuery Javascript library. It is a highly - * flexible tool, based upon the foundations of progressive enhancement, - * which will add advanced interaction controls to any HTML table. For a - * full list of features please refer to - * [DataTables.net](href="http://datatables.net). - * - * Note that the `DataTable` object is not a global variable but is aliased - * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may - * be accessed. - * - * @class - * @param {object} [init={}] Configuration object for DataTables. Options - * are defined by {@link DataTable.defaults} - * @requires jQuery 1.7+ - * - * @example - * // Basic initialisation - * $(document).ready( function { - * $('#example').dataTable(); - * } ); - * - * @example - * // Initialisation with configuration options - in this case, disable - * // pagination and sorting. - * $(document).ready( function { - * $('#example').dataTable( { - * "paginate": false, - * "sort": false - * } ); - * } ); - */ - var DataTable = function ( options ) - { - /** - * Perform a jQuery selector action on the table's TR elements (from the tbody) and - * return the resulting jQuery object. - * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on - * @param {object} [oOpts] Optional parameters for modifying the rows to be included - * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter - * criterion ("applied") or all TR elements (i.e. no filter). - * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. - * Can be either 'current', whereby the current sorting of the table is used, or - * 'original' whereby the original order the data was read into the table is used. - * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page - * ("current") or not ("all"). If 'current' is given, then order is assumed to be - * 'current' and filter is 'applied', regardless of what they might be given as. - * @returns {object} jQuery object, filtered by the given selector. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Highlight every second row - * oTable.$('tr:odd').css('backgroundColor', 'blue'); - * } ); - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Filter to rows with 'Webkit' in them, add a background colour and then - * // remove the filter, thus highlighting the 'Webkit' rows only. - * oTable.fnFilter('Webkit'); - * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); - * oTable.fnFilter(''); - * } ); - */ - this.$ = function ( sSelector, oOpts ) - { - return this.api(true).$( sSelector, oOpts ); - }; - - - /** - * Almost identical to $ in operation, but in this case returns the data for the matched - * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes - * rather than any descendants, so the data can be obtained for the row/cell. If matching - * rows are found, the data returned is the original data array/object that was used to - * create the row (or a generated array if from a DOM source). - * - * This method is often useful in-combination with $ where both functions are given the - * same parameters and the array indexes will match identically. - * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on - * @param {object} [oOpts] Optional parameters for modifying the rows to be included - * @param {string} [oOpts.filter=none] Select elements that meet the current filter - * criterion ("applied") or all elements (i.e. no filter). - * @param {string} [oOpts.order=current] Order of the data in the processed array. - * Can be either 'current', whereby the current sorting of the table is used, or - * 'original' whereby the original order the data was read into the table is used. - * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page - * ("current") or not ("all"). If 'current' is given, then order is assumed to be - * 'current' and filter is 'applied', regardless of what they might be given as. - * @returns {array} Data for the matched elements. If any elements, as a result of the - * selector, were not TR, TD or TH elements in the DataTable, they will have a null - * entry in the array. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Get the data from the first row in the table - * var data = oTable._('tr:first'); - * - * // Do something useful with the data - * alert( "First cell is: "+data[0] ); - * } ); - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Filter to 'Webkit' and get all data for - * oTable.fnFilter('Webkit'); - * var data = oTable._('tr', {"search": "applied"}); - * - * // Do something with the data - * alert( data.length+" rows matched the search" ); - * } ); - */ - this._ = function ( sSelector, oOpts ) - { - return this.api(true).rows( sSelector, oOpts ).data(); - }; - - - /** - * Create a DataTables Api instance, with the currently selected tables for - * the Api's context. - * @param {boolean} [traditional=false] Set the API instance's context to be - * only the table referred to by the `DataTable.ext.iApiIndex` option, as was - * used in the API presented by DataTables 1.9- (i.e. the traditional mode), - * or if all tables captured in the jQuery object should be used. - * @return {DataTables.Api} - */ - this.api = function ( traditional ) - { - return traditional ? - new _Api( - _fnSettingsFromNode( this[ _ext.iApiIndex ] ) - ) : - new _Api( this ); - }; - - - /** - * Add a single new row or multiple rows of data to the table. Please note - * that this is suitable for client-side processing only - if you are using - * server-side processing (i.e. "bServerSide": true), then to add data, you - * must add it to the data source, i.e. the server-side, through an Ajax call. - * @param {array|object} data The data to be added to the table. This can be: - *
      - *
    • 1D array of data - add a single row with the data provided
    • - *
    • 2D array of arrays - add multiple rows in a single call
    • - *
    • object - data object when using mData
    • - *
    • array of objects - multiple data objects when using mData
    • - *
    - * @param {bool} [redraw=true] redraw the table or not - * @returns {array} An array of integers, representing the list of indexes in - * aoData ({@link DataTable.models.oSettings}) that have been added to - * the table. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * // Global var for counter - * var giCount = 2; - * - * $(document).ready(function() { - * $('#example').dataTable(); - * } ); - * - * function fnClickAddRow() { - * $('#example').dataTable().fnAddData( [ - * giCount+".1", - * giCount+".2", - * giCount+".3", - * giCount+".4" ] - * ); - * - * giCount++; - * } - */ - this.fnAddData = function( data, redraw ) - { - var api = this.api( true ); - - /* Check if we want to add multiple rows or not */ - var rows = $.isArray(data) && ( $.isArray(data[0]) || $.isPlainObject(data[0]) ) ? - api.rows.add( data ) : - api.row.add( data ); - - if ( redraw === undefined || redraw ) { - api.draw(); - } - - return rows.flatten().toArray(); - }; - - - /** - * This function will make DataTables recalculate the column sizes, based on the data - * contained in the table and the sizes applied to the columns (in the DOM, CSS or - * through the sWidth parameter). This can be useful when the width of the table's - * parent element changes (for example a window resize). - * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable( { - * "sScrollY": "200px", - * "bPaginate": false - * } ); - * - * $(window).bind('resize', function () { - * oTable.fnAdjustColumnSizing(); - * } ); - * } ); - */ - this.fnAdjustColumnSizing = function ( bRedraw ) - { - var api = this.api( true ).columns.adjust(); - var settings = api.settings()[0]; - var scroll = settings.oScroll; - - if ( bRedraw === undefined || bRedraw ) { - api.draw( false ); - } - else if ( scroll.sX !== "" || scroll.sY !== "" ) { - /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ - _fnScrollDraw( settings ); - } - }; - - - /** - * Quickly and simply clear a table - * @param {bool} [bRedraw=true] redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) - * oTable.fnClearTable(); - * } ); - */ - this.fnClearTable = function( bRedraw ) - { - var api = this.api( true ).clear(); - - if ( bRedraw === undefined || bRedraw ) { - api.draw(); - } - }; - - - /** - * The exact opposite of 'opening' a row, this function will close any rows which - * are currently 'open'. - * @param {node} nTr the table row to 'close' - * @returns {int} 0 on success, or 1 if failed (can't find the row) - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnClose = function( nTr ) - { - this.api( true ).row( nTr ).child.hide(); - }; - - - /** - * Remove a row for the table - * @param {mixed} target The index of the row from aoData to be deleted, or - * the TR element you want to delete - * @param {function|null} [callBack] Callback function - * @param {bool} [redraw=true] Redraw the table or not - * @returns {array} The row that was deleted - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Immediately remove the first row - * oTable.fnDeleteRow( 0 ); - * } ); - */ - this.fnDeleteRow = function( target, callback, redraw ) - { - var api = this.api( true ); - var rows = api.rows( target ); - var settings = rows.settings()[0]; - var data = settings.aoData[ rows[0][0] ]; - - rows.remove(); - - if ( callback ) { - callback.call( this, settings, data ); - } - - if ( redraw === undefined || redraw ) { - api.draw(); - } - - return data; - }; - - - /** - * Restore the table to it's original state in the DOM by removing all of DataTables - * enhancements, alterations to the DOM structure of the table and event listeners. - * @param {boolean} [remove=false] Completely remove the table from the DOM - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * // This example is fairly pointless in reality, but shows how fnDestroy can be used - * var oTable = $('#example').dataTable(); - * oTable.fnDestroy(); - * } ); - */ - this.fnDestroy = function ( remove ) - { - this.api( true ).destroy( remove ); - }; - - - /** - * Redraw the table - * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) - * oTable.fnDraw(); - * } ); - */ - this.fnDraw = function( complete ) - { - // Note that this isn't an exact match to the old call to _fnDraw - it takes - // into account the new data, but can hold position. - this.api( true ).draw( complete ); - }; - - - /** - * Filter the input based on data - * @param {string} sInput String to filter the table on - * @param {int|null} [iColumn] Column to limit filtering to - * @param {bool} [bRegex=false] Treat as regular expression or not - * @param {bool} [bSmart=true] Perform smart filtering or not - * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) - * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sometime later - filter... - * oTable.fnFilter( 'test string' ); - * } ); - */ - this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive ) - { - var api = this.api( true ); - - if ( iColumn === null || iColumn === undefined ) { - api.search( sInput, bRegex, bSmart, bCaseInsensitive ); - } - else { - api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive ); - } - - api.draw(); - }; - - - /** - * Get the data for the whole table, an individual row or an individual cell based on the - * provided parameters. - * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as - * a TR node then the data source for the whole row will be returned. If given as a - * TD/TH cell node then iCol will be automatically calculated and the data for the - * cell returned. If given as an integer, then this is treated as the aoData internal - * data index for the row (see fnGetPosition) and the data for that row used. - * @param {int} [col] Optional column index that you want the data of. - * @returns {array|object|string} If mRow is undefined, then the data for all rows is - * returned. If mRow is defined, just data for that row, and is iCol is - * defined, only data for the designated cell is returned. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * // Row data - * $(document).ready(function() { - * oTable = $('#example').dataTable(); - * - * oTable.$('tr').click( function () { - * var data = oTable.fnGetData( this ); - * // ... do something with the array / object of data for the row - * } ); - * } ); - * - * @example - * // Individual cell data - * $(document).ready(function() { - * oTable = $('#example').dataTable(); - * - * oTable.$('td').click( function () { - * var sData = oTable.fnGetData( this ); - * alert( 'The cell clicked on had the value of '+sData ); - * } ); - * } ); - */ - this.fnGetData = function( src, col ) - { - var api = this.api( true ); - - if ( src !== undefined ) { - var type = src.nodeName ? src.nodeName.toLowerCase() : ''; - - return col !== undefined || type == 'td' || type == 'th' ? - api.cell( src, col ).data() : - api.row( src ).data() || null; - } - - return api.data().toArray(); - }; - - - /** - * Get an array of the TR nodes that are used in the table's body. Note that you will - * typically want to use the '$' API method in preference to this as it is more - * flexible. - * @param {int} [iRow] Optional row index for the TR element you want - * @returns {array|node} If iRow is undefined, returns an array of all TR elements - * in the table's body, or iRow is defined, just the TR element requested. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Get the nodes from the table - * var nNodes = oTable.fnGetNodes( ); - * } ); - */ - this.fnGetNodes = function( iRow ) - { - var api = this.api( true ); - - return iRow !== undefined ? - api.row( iRow ).node() : - api.rows().nodes().flatten().toArray(); - }; - - - /** - * Get the array indexes of a particular cell from it's DOM element - * and column index including hidden columns - * @param {node} node this can either be a TR, TD or TH in the table's body - * @returns {int} If nNode is given as a TR, then a single index is returned, or - * if given as a cell, an array of [row index, column index (visible), - * column index (all)] is given. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * $('#example tbody td').click( function () { - * // Get the position of the current data from the node - * var aPos = oTable.fnGetPosition( this ); - * - * // Get the data array for this row - * var aData = oTable.fnGetData( aPos[0] ); - * - * // Update the data array and return the value - * aData[ aPos[1] ] = 'clicked'; - * this.innerHTML = 'clicked'; - * } ); - * - * // Init DataTables - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnGetPosition = function( node ) - { - var api = this.api( true ); - var nodeName = node.nodeName.toUpperCase(); - - if ( nodeName == 'TR' ) { - return api.row( node ).index(); - } - else if ( nodeName == 'TD' || nodeName == 'TH' ) { - var cell = api.cell( node ).index(); - - return [ - cell.row, - cell.columnVisible, - cell.column - ]; - } - return null; - }; - - - /** - * Check to see if a row is 'open' or not. - * @param {node} nTr the table row to check - * @returns {boolean} true if the row is currently open, false otherwise - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnIsOpen = function( nTr ) - { - return this.api( true ).row( nTr ).child.isShown(); - }; - - - /** - * This function will place a new row directly after a row which is currently - * on display on the page, with the HTML contents that is passed into the - * function. This can be used, for example, to ask for confirmation that a - * particular record should be deleted. - * @param {node} nTr The table row to 'open' - * @param {string|node|jQuery} mHtml The HTML to put into the row - * @param {string} sClass Class to give the new TD cell - * @returns {node} The row opened. Note that if the table row passed in as the - * first parameter, is not found in the table, this method will silently - * return. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnOpen = function( nTr, mHtml, sClass ) - { - return this.api( true ) - .row( nTr ) - .child( mHtml, sClass ) - .show() - .child()[0]; - }; - - - /** - * Change the pagination - provides the internal logic for pagination in a simple API - * function. With this function you can have a DataTables table go to the next, - * previous, first or last pages. - * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" - * or page number to jump to (integer), note that page 0 is the first page. - * @param {bool} [bRedraw=true] Redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * oTable.fnPageChange( 'next' ); - * } ); - */ - this.fnPageChange = function ( mAction, bRedraw ) - { - var api = this.api( true ).page( mAction ); - - if ( bRedraw === undefined || bRedraw ) { - api.draw(false); - } - }; - - - /** - * Show a particular column - * @param {int} iCol The column whose display should be changed - * @param {bool} bShow Show (true) or hide (false) the column - * @param {bool} [bRedraw=true] Redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Hide the second column after initialisation - * oTable.fnSetColumnVis( 1, false ); - * } ); - */ - this.fnSetColumnVis = function ( iCol, bShow, bRedraw ) - { - var api = this.api( true ).column( iCol ).visible( bShow ); - - if ( bRedraw === undefined || bRedraw ) { - api.columns.adjust().draw(); - } - }; - - - /** - * Get the settings for a particular table for external manipulation - * @returns {object} DataTables settings object. See - * {@link DataTable.models.oSettings} - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * var oSettings = oTable.fnSettings(); - * - * // Show an example parameter from the settings - * alert( oSettings._iDisplayStart ); - * } ); - */ - this.fnSettings = function() - { - return _fnSettingsFromNode( this[_ext.iApiIndex] ); - }; - - - /** - * Sort the table by a particular column - * @param {int} iCol the data index to sort on. Note that this will not match the - * 'display index' if you have hidden data entries - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sort immediately with columns 0 and 1 - * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); - * } ); - */ - this.fnSort = function( aaSort ) - { - this.api( true ).order( aaSort ).draw(); - }; - - - /** - * Attach a sort listener to an element for a given column - * @param {node} nNode the element to attach the sort listener to - * @param {int} iColumn the column that a click on this node will sort on - * @param {function} [fnCallback] callback function when sort is run - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sort on column 1, when 'sorter' is clicked on - * oTable.fnSortListener( document.getElementById('sorter'), 1 ); - * } ); - */ - this.fnSortListener = function( nNode, iColumn, fnCallback ) - { - this.api( true ).order.listener( nNode, iColumn, fnCallback ); - }; - - - /** - * Update a table cell or row - this method will accept either a single value to - * update the cell with, an array of values with one element for each column or - * an object in the same format as the original data source. The function is - * self-referencing in order to make the multi column updates easier. - * @param {object|array|string} mData Data to update the cell/row with - * @param {node|int} mRow TR element you want to update or the aoData index - * @param {int} [iColumn] The column to update, give as null or undefined to - * update a whole row. - * @param {bool} [bRedraw=true] Redraw the table or not - * @param {bool} [bAction=true] Perform pre-draw actions or not - * @returns {int} 0 on success, 1 on error - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell - * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row - * } ); - */ - this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction ) - { - var api = this.api( true ); - - if ( iColumn === undefined || iColumn === null ) { - api.row( mRow ).data( mData ); - } - else { - api.cell( mRow, iColumn ).data( mData ); - } - - if ( bAction === undefined || bAction ) { - api.columns.adjust(); - } - - if ( bRedraw === undefined || bRedraw ) { - api.draw(); - } - return 0; - }; - - - /** - * Provide a common method for plug-ins to check the version of DataTables being used, in order - * to ensure compatibility. - * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the - * formats "X" and "X.Y" are also acceptable. - * @returns {boolean} true if this version of DataTables is greater or equal to the required - * version, or false if this version of DataTales is not suitable - * @method - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * alert( oTable.fnVersionCheck( '1.9.0' ) ); - * } ); - */ - this.fnVersionCheck = _ext.fnVersionCheck; - - - var _that = this; - var emptyInit = options === undefined; - var len = this.length; - - if ( emptyInit ) { - options = {}; - } - - this.oApi = this.internal = _ext.internal; - - // Extend with old style plug-in API methods - for ( var fn in DataTable.ext.internal ) { - if ( fn ) { - this[fn] = _fnExternApiFunc(fn); - } - } - - this.each(function() { - // For each initialisation we want to give it a clean initialisation - // object that can be bashed around - var o = {}; - var oInit = len > 1 ? // optimisation for single table case - _fnExtend( o, options, true ) : - options; - - /*global oInit,_that,emptyInit*/ - var i=0, iLen, j, jLen, k, kLen; - var sId = this.getAttribute( 'id' ); - var bInitHandedOff = false; - var defaults = DataTable.defaults; - var $this = $(this); - - - /* Sanity check */ - if ( this.nodeName.toLowerCase() != 'table' ) - { - _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); - return; - } - - /* Backwards compatibility for the defaults */ - _fnCompatOpts( defaults ); - _fnCompatCols( defaults.column ); - - /* Convert the camel-case defaults to Hungarian */ - _fnCamelToHungarian( defaults, defaults, true ); - _fnCamelToHungarian( defaults.column, defaults.column, true ); - - /* Setting up the initialisation object */ - _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ) ); - - - - /* Check to see if we are re-initialising a table */ - var allSettings = DataTable.settings; - for ( i=0, iLen=allSettings.length ; i').appendTo(this); - } - oSettings.nTHead = thead[0]; - - var tbody = $this.children('tbody'); - if ( tbody.length === 0 ) - { - tbody = $('
    ').appendTo(this); - } - oSettings.nTBody = tbody[0]; - - var tfoot = $this.children('tfoot'); - if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) - { - // If we are a scrolling table, and no footer has been given, then we need to create - // a tfoot element for the caption element to be appended to - tfoot = $('').appendTo(this); - } - - if ( tfoot.length === 0 || tfoot.children().length === 0 ) { - $this.addClass( oClasses.sNoFooter ); - } - else if ( tfoot.length > 0 ) { - oSettings.nTFoot = tfoot[0]; - _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot ); - } - - /* Check if there is data passing into the constructor */ - if ( oInit.aaData ) - { - for ( i=0 ; i/g; - var _re_date_start = /^[\w\+\-]/; - var _re_date_end = /[\w\+\-]$/; - - // Escape regular expression special characters - var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); - - // http://en.wikipedia.org/wiki/Foreign_exchange_market - // - \u20BD - Russian ruble. - // - \u20a9 - South Korean Won - // - \u20BA - Turkish Lira - // - \u20B9 - Indian Rupee - // - R - Brazil (R$) and South Africa - // - fr - Swiss Franc - // - kr - Swedish krona, Norwegian krone and Danish krone - // - \u2009 is thin space and \u202F is narrow no-break space, both used in many - // standards as thousands separators. - var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi; - - - var _empty = function ( d ) { - return !d || d === true || d === '-' ? true : false; - }; - - - var _intVal = function ( s ) { - var integer = parseInt( s, 10 ); - return !isNaN(integer) && isFinite(s) ? integer : null; - }; - - // Convert from a formatted number with characters other than `.` as the - // decimal place, to a Javascript number - var _numToDecimal = function ( num, decimalPoint ) { - // Cache created regular expressions for speed as this function is called often - if ( ! _re_dic[ decimalPoint ] ) { - _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); - } - return typeof num === 'string' && decimalPoint !== '.' ? - num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : - num; - }; - - - var _isNumber = function ( d, decimalPoint, formatted ) { - var strType = typeof d === 'string'; - - // If empty return immediately so there must be a number if it is a - // formatted string (this stops the string "k", or "kr", etc being detected - // as a formatted number for currency - if ( _empty( d ) ) { - return true; - } - - if ( decimalPoint && strType ) { - d = _numToDecimal( d, decimalPoint ); - } - - if ( formatted && strType ) { - d = d.replace( _re_formatted_numeric, '' ); - } - - return !isNaN( parseFloat(d) ) && isFinite( d ); - }; - - - // A string without HTML in it can be considered to be HTML still - var _isHtml = function ( d ) { - return _empty( d ) || typeof d === 'string'; - }; - - - var _htmlNumeric = function ( d, decimalPoint, formatted ) { - if ( _empty( d ) ) { - return true; - } - - var html = _isHtml( d ); - return ! html ? - null : - _isNumber( _stripHtml( d ), decimalPoint, formatted ) ? - true : - null; - }; - - - var _pluck = function ( a, prop, prop2 ) { - var out = []; - var i=0, ien=a.length; - - // Could have the test in the loop for slightly smaller code, but speed - // is essential here - if ( prop2 !== undefined ) { - for ( ; i') - .css( { - position: 'fixed', - top: 0, - left: 0, - height: 1, - width: 1, - overflow: 'hidden' - } ) - .append( - $('
    ') - .css( { - position: 'absolute', - top: 1, - left: 1, - width: 100, - overflow: 'scroll' - } ) - .append( - $('
    ') - .css( { - width: '100%', - height: 10 - } ) - ) - ) - .appendTo( 'body' ); - - var outer = n.children(); - var inner = outer.children(); - - // Numbers below, in order, are: - // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth - // - // IE6 XP: 100 100 100 83 - // IE7 Vista: 100 100 100 83 - // IE 8+ Windows: 83 83 100 83 - // Evergreen Windows: 83 83 100 83 - // Evergreen Mac with scrollbars: 85 85 100 85 - // Evergreen Mac without scrollbars: 100 100 100 100 - - // Get scrollbar width - browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; - - // IE6/7 will oversize a width 100% element inside a scrolling element, to - // include the width of the scrollbar, while other browsers ensure the inner - // element is contained without forcing scrolling - browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100; - - // In rtl text layout, some browsers (most, but not all) will place the - // scrollbar on the left, rather than the right. - browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1; - - // IE8- don't provide height and width for getBoundingClientRect - browser.bBounding = n[0].getBoundingClientRect().width ? true : false; - - n.remove(); - } - - $.extend( settings.oBrowser, DataTable.__browser ); - settings.oScroll.iBarWidth = DataTable.__browser.barWidth; - } - - - /** - * Array.prototype reduce[Right] method, used for browsers which don't support - * JS 1.6. Done this way to reduce code size, since we iterate either way - * @param {object} settings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnReduce ( that, fn, init, start, end, inc ) - { - var - i = start, - value, - isSet = false; - - if ( init !== undefined ) { - value = init; - isSet = true; - } - - while ( i !== end ) { - if ( ! that.hasOwnProperty(i) ) { - continue; - } - - value = isSet ? - fn( value, that[i], i, that ) : - that[i]; - - isSet = true; - i += inc; - } - - return value; - } - - /** - * Add a column to the list used for the table with default values - * @param {object} oSettings dataTables settings object - * @param {node} nTh The th element for this column - * @memberof DataTable#oApi - */ - function _fnAddColumn( oSettings, nTh ) - { - // Add column to aoColumns array - var oDefaults = DataTable.defaults.column; - var iCol = oSettings.aoColumns.length; - var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { - "nTh": nTh ? nTh : document.createElement('th'), - "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', - "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], - "mData": oDefaults.mData ? oDefaults.mData : iCol, - idx: iCol - } ); - oSettings.aoColumns.push( oCol ); - - // Add search object for column specific search. Note that the `searchCols[ iCol ]` - // passed into extend can be undefined. This allows the user to give a default - // with only some of the parameters defined, and also not give a default - var searchCols = oSettings.aoPreSearchCols; - searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] ); - - // Use the default column options function to initialise classes etc - _fnColumnOptions( oSettings, iCol, $(nTh).data() ); - } - - - /** - * Apply options for a column - * @param {object} oSettings dataTables settings object - * @param {int} iCol column index to consider - * @param {object} oOptions object with sType, bVisible and bSearchable etc - * @memberof DataTable#oApi - */ - function _fnColumnOptions( oSettings, iCol, oOptions ) - { - var oCol = oSettings.aoColumns[ iCol ]; - var oClasses = oSettings.oClasses; - var th = $(oCol.nTh); - - // Try to get width information from the DOM. We can't get it from CSS - // as we'd need to parse the CSS stylesheet. `width` option can override - if ( ! oCol.sWidthOrig ) { - // Width attribute - oCol.sWidthOrig = th.attr('width') || null; - - // Style attribute - var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); - if ( t ) { - oCol.sWidthOrig = t[1]; - } - } - - /* User specified column options */ - if ( oOptions !== undefined && oOptions !== null ) - { - // Backwards compatibility - _fnCompatCols( oOptions ); - - // Map camel case parameters to their Hungarian counterparts - _fnCamelToHungarian( DataTable.defaults.column, oOptions ); - - /* Backwards compatibility for mDataProp */ - if ( oOptions.mDataProp !== undefined && !oOptions.mData ) - { - oOptions.mData = oOptions.mDataProp; - } - - if ( oOptions.sType ) - { - oCol._sManualType = oOptions.sType; - } - - // `class` is a reserved word in Javascript, so we need to provide - // the ability to use a valid name for the camel case input - if ( oOptions.className && ! oOptions.sClass ) - { - oOptions.sClass = oOptions.className; - } - - $.extend( oCol, oOptions ); - _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); - - /* iDataSort to be applied (backwards compatibility), but aDataSort will take - * priority if defined - */ - if ( oOptions.iDataSort !== undefined ) - { - oCol.aDataSort = [ oOptions.iDataSort ]; - } - _fnMap( oCol, oOptions, "aDataSort" ); - } - - /* Cache the data get and set functions for speed */ - var mDataSrc = oCol.mData; - var mData = _fnGetObjectDataFn( mDataSrc ); - var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; - - var attrTest = function( src ) { - return typeof src === 'string' && src.indexOf('@') !== -1; - }; - oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && ( - attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) - ); - oCol._setter = null; - - oCol.fnGetData = function (rowData, type, meta) { - var innerData = mData( rowData, type, undefined, meta ); - - return mRender && type ? - mRender( innerData, type, rowData, meta ) : - innerData; - }; - oCol.fnSetData = function ( rowData, val, meta ) { - return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta ); - }; - - // Indicate if DataTables should read DOM data as an object or array - // Used in _fnGetRowElements - if ( typeof mDataSrc !== 'number' ) { - oSettings._rowReadObject = true; - } - - /* Feature sorting overrides column specific when off */ - if ( !oSettings.oFeatures.bSort ) - { - oCol.bSortable = false; - th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called - } - - /* Check that the class assignment is correct for sorting */ - var bAsc = $.inArray('asc', oCol.asSorting) !== -1; - var bDesc = $.inArray('desc', oCol.asSorting) !== -1; - if ( !oCol.bSortable || (!bAsc && !bDesc) ) - { - oCol.sSortingClass = oClasses.sSortableNone; - oCol.sSortingClassJUI = ""; - } - else if ( bAsc && !bDesc ) - { - oCol.sSortingClass = oClasses.sSortableAsc; - oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; - } - else if ( !bAsc && bDesc ) - { - oCol.sSortingClass = oClasses.sSortableDesc; - oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; - } - else - { - oCol.sSortingClass = oClasses.sSortable; - oCol.sSortingClassJUI = oClasses.sSortJUI; - } - } - - - /** - * Adjust the table column widths for new data. Note: you would probably want to - * do a redraw after calling this function! - * @param {object} settings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnAdjustColumnSizing ( settings ) - { - /* Not interested in doing column width calculation if auto-width is disabled */ - if ( settings.oFeatures.bAutoWidth !== false ) - { - var columns = settings.aoColumns; - - _fnCalculateColumnWidths( settings ); - for ( var i=0 , iLen=columns.length ; i
    ').addClass( k ); - $('td', created) - .addClass( k ) - .html( r ) - [0].colSpan = _fnVisbleColumns( ctx ); - - rows.push( created[0] ); - } - }; - - addRow( data, klass ); - - if ( row._details ) { - row._details.remove(); - } - - row._details = $(rows); - - // If the children were already shown, that state should be retained - if ( row._detailsShow ) { - row._details.insertAfter( row.nTr ); - } - }; - - - var __details_remove = function ( api, idx ) - { - var ctx = api.context; - - if ( ctx.length ) { - var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ]; - - if ( row && row._details ) { - row._details.remove(); - - row._detailsShow = undefined; - row._details = undefined; - } - } - }; - - - var __details_display = function ( api, show ) { - var ctx = api.context; - - if ( ctx.length && api.length ) { - var row = ctx[0].aoData[ api[0] ]; - - if ( row._details ) { - row._detailsShow = show; - - if ( show ) { - row._details.insertAfter( row.nTr ); - } - else { - row._details.detach(); - } - - __details_events( ctx[0] ); - } - } - }; - - - var __details_events = function ( settings ) - { - var api = new _Api( settings ); - var namespace = '.dt.DT_details'; - var drawEvent = 'draw'+namespace; - var colvisEvent = 'column-visibility'+namespace; - var destroyEvent = 'destroy'+namespace; - var data = settings.aoData; - - api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent ); - - if ( _pluck( data, '_details' ).length > 0 ) { - // On each draw, insert the required elements into the document - api.on( drawEvent, function ( e, ctx ) { - if ( settings !== ctx ) { - return; - } - - api.rows( {page:'current'} ).eq(0).each( function (idx) { - // Internal data grab - var row = data[ idx ]; - - if ( row._detailsShow ) { - row._details.insertAfter( row.nTr ); - } - } ); - } ); - - // Column visibility change - update the colspan - api.on( colvisEvent, function ( e, ctx, idx, vis ) { - if ( settings !== ctx ) { - return; - } - - // Update the colspan for the details rows (note, only if it already has - // a colspan) - var row, visible = _fnVisbleColumns( ctx ); - - for ( var i=0, ien=data.length ; i=0 count from left, <0 count from right) - * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right) - * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right) - * "{string}:name" - column name - * "{string}" - jQuery selector on column header nodes - * - */ - - // can be an array of these items, comma separated list, or an array of comma - // separated lists - - var __re_column_selector = /^(.+):(name|visIdx|visible)$/; - - - // r1 and r2 are redundant - but it means that the parameters match for the - // iterator callback in columns().data() - var __columnData = function ( settings, column, r1, r2, rows ) { - var a = []; - for ( var row=0, ien=rows.length ; row= 0 ? - selInt : // Count from left - columns.length + selInt // Count from right (+ because its a negative value) - ]; - } - - // Selector = function - if ( typeof s === 'function' ) { - var rows = _selector_row_indexes( settings, opts ); - - return $.map( columns, function (col, idx) { - return s( - idx, - __columnData( settings, idx, 0, 0, rows ), - nodes[ idx ] - ) ? idx : null; - } ); - } - - // jQuery or string selector - var match = typeof s === 'string' ? - s.match( __re_column_selector ) : - ''; - - if ( match ) { - switch( match[2] ) { - case 'visIdx': - case 'visible': - var idx = parseInt( match[1], 10 ); - // Visible index given, convert to column index - if ( idx < 0 ) { - // Counting from the right - var visColumns = $.map( columns, function (col,i) { - return col.bVisible ? i : null; - } ); - return [ visColumns[ visColumns.length + idx ] ]; - } - // Counting from the left - return [ _fnVisibleToColumnIndex( settings, idx ) ]; - - case 'name': - // match by name. `names` is column index complete and in order - return $.map( names, function (name, i) { - return name === match[1] ? i : null; - } ); - - default: - return []; - } - } - - // Cell in the table body - if ( s.nodeName && s._DT_CellIndex ) { - return [ s._DT_CellIndex.column ]; - } - - // jQuery selector on the TH elements for the columns - var jqResult = $( nodes ) - .filter( s ) - .map( function () { - return $.inArray( this, nodes ); // `nodes` is column index complete and in order - } ) - .toArray(); - - if ( jqResult.length || ! s.nodeName ) { - return jqResult; - } - - // Otherwise a node which might have a `dt-column` data attribute, or be - // a child or such an element - var host = $(s).closest('*[data-dt-column]'); - return host.length ? - [ host.data('dt-column') ] : - []; - }; - - return _selector_run( 'column', selector, run, settings, opts ); - }; - - - var __setColumnVis = function ( settings, column, vis ) { - var - cols = settings.aoColumns, - col = cols[ column ], - data = settings.aoData, - row, cells, i, ien, tr; - - // Get - if ( vis === undefined ) { - return col.bVisible; - } - - // Set - // No change - if ( col.bVisible === vis ) { - return; - } - - if ( vis ) { - // Insert column - // Need to decide if we should use appendChild or insertBefore - var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 ); - - for ( i=0, ien=data.length ; i iThat; - } - - return true; - }; - - - /** - * Check if a `
    {{ trait.description_display }} {{ trait.location_repr }} {{ '%0.3f' % trait.mean|float }}{{ '%0.3f'|format(trait.sample_r) }}{{ '%0.3f'|format(trait.sample_r) }} {{ trait.num_overlap }} {{ '%0.3e'|format(trait.sample_p) }}N/A{% if trait.description_display|length > 50 %}{{ trait.description_display[:50] }}...{% else %}{{ trait.description_display }}{% endif %}{{ trait.authors }}{% if trait.description_display|length > 70 %}{{ trait.description_display[:70] }}...{% else %}{{ trait.description_display }}{% endif %}{% if trait.authors.split(',') > 6 %}{{ trait.authors.split(',')[:6]|join(', ') }}, et al.{% else %}{{ trait.authors }}{% endif %} {{ trait.pubmed_text }} @@ -211,7 +212,6 @@ @@ -427,10 +427,6 @@ { "type": "natural" }, { "type": "natural" } ], - "createdRow": function ( row, data, index ) { - $('td', row).eq(4).text(decodeURIComponent(escape($('td', row).eq(4).text()))); - $('td', row).eq(5).text(decodeURIComponent(escape($('td', row).eq(5).text()))); - }, "order": [[9, "asc" ]], "sDom": "Btir", "iDisplayLength": -1, -- cgit v1.2.3 From ffbc58301edb70d4cd84d5741eaaeff7381a24e4 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 15 Jul 2020 13:49:34 -0500 Subject: Fixed issue where DataTable was getting initialized twice when adding cofactors to a scatterplot + fixed issue where sizev and datav sometimes weren't set when drawing scatterplot --- .../static/new/javascript/draw_corr_scatterplot.js | 5 +-- .../new/javascript/get_traits_from_collection.js | 48 +++++++++++----------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/wqflask/wqflask/static/new/javascript/draw_corr_scatterplot.js b/wqflask/wqflask/static/new/javascript/draw_corr_scatterplot.js index 956e0467..1bae8773 100644 --- a/wqflask/wqflask/static/new/javascript/draw_corr_scatterplot.js +++ b/wqflask/wqflask/static/new/javascript/draw_corr_scatterplot.js @@ -337,14 +337,13 @@ function getdata() { continue } + sizev = 10; + datav = 0; if (size_cofactor_vals.length > 0){ if (cofactor_samples.indexOf(js_data.indIDs[j])) { datav = size_cofactor_vals[j] sizev = map1to2(datamin, datamax, sizemin, sizemax, datav); } - } else { - datav = 0; - sizev = 10; } x_values.push(js_data.data[0][j]) diff --git a/wqflask/wqflask/static/new/javascript/get_traits_from_collection.js b/wqflask/wqflask/static/new/javascript/get_traits_from_collection.js index 6f03b98f..4ec62157 100644 --- a/wqflask/wqflask/static/new/javascript/get_traits_from_collection.js +++ b/wqflask/wqflask/static/new/javascript/get_traits_from_collection.js @@ -41,29 +41,31 @@ $('#trait_table').dataTable( { "orderClasses": true } ); -$('#collection_table').dataTable( { - "createdRow": function ( row, data, index ) { - if ($('td', row).eq(2).text().length > 40) { - $('td', row).eq(2).text($('td', row).eq(2).text().substring(0, 40)); - $('td', row).eq(2).text($('td', row).eq(2).text() + '...') - } - if ($('td', row).eq(4).text().length > 50) { - $('td', row).eq(4).text($('td', row).eq(4).text().substring(0, 50)); - $('td', row).eq(4).text($('td', row).eq(4).text() + '...') - } - }, - "columnDefs": [ { - "targets": 0, - "orderable": false - } ], - "order": [[1, "asc" ]], - "sDom": "ZRtr", - "iDisplayLength": -1, - "autoWidth": true, - "bSortClasses": false, - "paging": false, - "orderClasses": true -} ); +if ( ! $.fn.DataTable.isDataTable( '#collection_table' ) ) { + $('#collection_table').dataTable( { + "createdRow": function ( row, data, index ) { + if ($('td', row).eq(2).text().length > 40) { + $('td', row).eq(2).text($('td', row).eq(2).text().substring(0, 40)); + $('td', row).eq(2).text($('td', row).eq(2).text() + '...') + } + if ($('td', row).eq(4).text().length > 50) { + $('td', row).eq(4).text($('td', row).eq(4).text().substring(0, 50)); + $('td', row).eq(4).text($('td', row).eq(4).text() + '...') + } + }, + "columnDefs": [ { + "targets": 0, + "orderable": false + } ], + "order": [[1, "asc" ]], + "sDom": "ZRtr", + "iDisplayLength": -1, + "autoWidth": true, + "bSortClasses": false, + "paging": false, + "orderClasses": true + } ); +} collection_click = function() { var this_collection_url; -- cgit v1.2.3 From 811c55fb8614725e89b7a9604b6f97e4f0bc65da Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 15 Jul 2020 13:52:20 -0500 Subject: Increased width for Min Expr since it was getting cut off if you added more than 3 digits --- wqflask/wqflask/templates/show_trait_calculate_correlations.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/show_trait_calculate_correlations.html b/wqflask/wqflask/templates/show_trait_calculate_correlations.html index a9b371b8..1378b91b 100644 --- a/wqflask/wqflask/templates/show_trait_calculate_correlations.html +++ b/wqflask/wqflask/templates/show_trait_calculate_correlations.html @@ -78,7 +78,7 @@
    - +
    diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html index 4d51adff..366904b6 100755 --- a/wqflask/wqflask/templates/show_trait_mapping_tools.html +++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html @@ -99,7 +99,7 @@
    - +
    +
    @@ -224,7 +224,7 @@
    - +
    @@ -387,7 +387,7 @@
    - +
    @@ -422,4 +422,4 @@ {% else %} Mapping options are disabled for data not matched with genotypes. {% endif %} - + \ No newline at end of file -- cgit v1.2.3 From bd411b8de6dbc2f9b7f5592936c181c599afcdd6 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 13 Jul 2020 00:44:56 -0500 Subject: Added pagination to search pages and moved info to both top and bottom of each table --- wqflask/wqflask/templates/gsearch_gene.html | 5 +++-- wqflask/wqflask/templates/gsearch_pheno.html | 5 +++-- wqflask/wqflask/templates/search_result_page.html | 18 ++++++++++-------- wqflask/wqflask/templates/snp_browser.html | 4 ++-- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/wqflask/wqflask/templates/gsearch_gene.html b/wqflask/wqflask/templates/gsearch_gene.html index 8c261eec..62ef1a7b 100644 --- a/wqflask/wqflask/templates/gsearch_gene.html +++ b/wqflask/wqflask/templates/gsearch_gene.html @@ -249,10 +249,11 @@ } ], 'order': [[1, "asc" ]], - 'sDom': "tir", + 'sDom': "pitirp", 'autoWidth': true, + 'iDisplayLength': 500, 'deferRender': true, - 'paging': false, + 'paging': true, 'orderClasses': true, 'processing': true, 'language': { diff --git a/wqflask/wqflask/templates/gsearch_pheno.html b/wqflask/wqflask/templates/gsearch_pheno.html index 04b45659..f5058158 100644 --- a/wqflask/wqflask/templates/gsearch_pheno.html +++ b/wqflask/wqflask/templates/gsearch_pheno.html @@ -250,10 +250,11 @@ } ], 'order': [[1, "asc" ]], - 'sDom': "tir", + 'sDom': "pitirp", 'autoWidth': false, 'deferRender': true, - 'paging': false, + 'iDisplayLength': 500, + 'paging': true, 'orderClasses': true, 'processing': true, 'language': { diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index 3dfae3dd..1f76ea82 100644 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -377,11 +377,12 @@ } else{ author_string = data.authors } - try { - return decodeURIComponent(escape(author_string)) - } catch(err){ - return author_string - } + return author_string + // try { + // return decodeURIComponent(escape(author_string)) + // } catch(err){ + // return author_string + // } } }, { @@ -441,12 +442,13 @@ postfixButtons: [ 'colvisRestore' ] } ], - 'sDom': "Bitir", + 'sDom': "Bpitirp", {% else %} - 'sDom': "itir", + 'sDom': "pitirp", {% endif %} + 'iDisplayLength': 500, 'deferRender': true, - 'paging': false, + 'paging': true, 'orderClasses': true, 'processing': true, 'language': { diff --git a/wqflask/wqflask/templates/snp_browser.html b/wqflask/wqflask/templates/snp_browser.html index 88cb4d31..4537cd06 100644 --- a/wqflask/wqflask/templates/snp_browser.html +++ b/wqflask/wqflask/templates/snp_browser.html @@ -406,8 +406,8 @@ ], {% endif %} 'order': [[1, "asc" ]], - 'sDom': "rti", - 'iDisplayLength': -1, + 'sDom': "rtip", + 'iDisplayLength': 500, 'processing': true, 'language': { 'loadingRecords': ' ', -- cgit v1.2.3 From 55221dca1c1adb6559957262cac21b023dfa11e7 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 14 Jul 2020 12:12:21 -0500 Subject: Added binary casting to phenotype trait info query to fix unicode issue --- wqflask/base/trait.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index ecb9dcd9..f9da7b87 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -395,11 +395,14 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): query = """ SELECT PublishXRef.Id, InbredSet.InbredSetCode, Publication.PubMed_ID, - Phenotype.Pre_publication_description, Phenotype.Post_publication_description, Phenotype.Original_description, - Phenotype.Pre_publication_abbreviation, Phenotype.Post_publication_abbreviation, PublishXRef.mean, + CAST(Phenotype.Pre_publication_description AS BINARY), + CAST(Phenotype.Post_publication_description AS BINARY), + CAST(Phenotype.Original_description AS BINARY), + CAST(Phenotype.Pre_publication_abbreviation AS BINARY), + CAST(Phenotype.Post_publication_abbreviation AS BINARY), PublishXRef.mean, Phenotype.Lab_code, Phenotype.Submitter, Phenotype.Owner, Phenotype.Authorized_Users, - Publication.Authors, Publication.Title, Publication.Abstract, - Publication.Journal, Publication.Volume, Publication.Pages, + CAST(Publication.Authors AS BINARY), CAST(Publication.Title AS BINARY), CAST(Publication.Abstract AS BINARY), + CAST(Publication.Journal AS BINARY), Publication.Volume, Publication.Pages, Publication.Month, Publication.Year, PublishXRef.Sequence, Phenotype.Units, PublishXRef.comments FROM @@ -462,9 +465,6 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): trait.haveinfo = True for i, field in enumerate(dataset.display_fields): holder = trait_info[i] - # if isinstance(trait_info[i], basestring): - # holder = unicode(holder.strip(codecs.BOM_UTF8), 'utf-8', "ignore") - setattr(trait, field, holder) if dataset.type == 'Publish': -- cgit v1.2.3 From 97d5ba1edde511133c5483bd41b5903a089d3c7e Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 15 Jul 2020 12:28:55 -0500 Subject: decodeURIComponent was throwing an error after the query-level fix to the unicode issue, so I removed it --- wqflask/wqflask/templates/correlation_page.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/wqflask/wqflask/templates/correlation_page.html b/wqflask/wqflask/templates/correlation_page.html index 3d750bea..df3e43f1 100644 --- a/wqflask/wqflask/templates/correlation_page.html +++ b/wqflask/wqflask/templates/correlation_page.html @@ -200,7 +200,7 @@ {% endblock %} {% block js %} - + @@ -427,10 +427,6 @@ { "type": "natural" }, { "type": "natural" } ], - "createdRow": function ( row, data, index ) { - $('td', row).eq(4).text(decodeURIComponent(escape($('td', row).eq(4).text()))); - $('td', row).eq(5).text(decodeURIComponent(escape($('td', row).eq(5).text()))); - }, "order": [[9, "asc" ]], "sDom": "Btir", "iDisplayLength": -1, -- cgit v1.2.3 From 34aa368b6a17fdfa6817633714558a989300cbc1 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 15 Jul 2020 13:49:34 -0500 Subject: Fixed issue where DataTable was getting initialized twice when adding cofactors to a scatterplot + fixed issue where sizev and datav sometimes weren't set when drawing scatterplot --- .../static/new/javascript/draw_corr_scatterplot.js | 5 +-- .../new/javascript/get_traits_from_collection.js | 48 +++++++++++----------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/wqflask/wqflask/static/new/javascript/draw_corr_scatterplot.js b/wqflask/wqflask/static/new/javascript/draw_corr_scatterplot.js index 956e0467..1bae8773 100644 --- a/wqflask/wqflask/static/new/javascript/draw_corr_scatterplot.js +++ b/wqflask/wqflask/static/new/javascript/draw_corr_scatterplot.js @@ -337,14 +337,13 @@ function getdata() { continue } + sizev = 10; + datav = 0; if (size_cofactor_vals.length > 0){ if (cofactor_samples.indexOf(js_data.indIDs[j])) { datav = size_cofactor_vals[j] sizev = map1to2(datamin, datamax, sizemin, sizemax, datav); } - } else { - datav = 0; - sizev = 10; } x_values.push(js_data.data[0][j]) diff --git a/wqflask/wqflask/static/new/javascript/get_traits_from_collection.js b/wqflask/wqflask/static/new/javascript/get_traits_from_collection.js index 6f03b98f..4ec62157 100644 --- a/wqflask/wqflask/static/new/javascript/get_traits_from_collection.js +++ b/wqflask/wqflask/static/new/javascript/get_traits_from_collection.js @@ -41,29 +41,31 @@ $('#trait_table').dataTable( { "orderClasses": true } ); -$('#collection_table').dataTable( { - "createdRow": function ( row, data, index ) { - if ($('td', row).eq(2).text().length > 40) { - $('td', row).eq(2).text($('td', row).eq(2).text().substring(0, 40)); - $('td', row).eq(2).text($('td', row).eq(2).text() + '...') - } - if ($('td', row).eq(4).text().length > 50) { - $('td', row).eq(4).text($('td', row).eq(4).text().substring(0, 50)); - $('td', row).eq(4).text($('td', row).eq(4).text() + '...') - } - }, - "columnDefs": [ { - "targets": 0, - "orderable": false - } ], - "order": [[1, "asc" ]], - "sDom": "ZRtr", - "iDisplayLength": -1, - "autoWidth": true, - "bSortClasses": false, - "paging": false, - "orderClasses": true -} ); +if ( ! $.fn.DataTable.isDataTable( '#collection_table' ) ) { + $('#collection_table').dataTable( { + "createdRow": function ( row, data, index ) { + if ($('td', row).eq(2).text().length > 40) { + $('td', row).eq(2).text($('td', row).eq(2).text().substring(0, 40)); + $('td', row).eq(2).text($('td', row).eq(2).text() + '...') + } + if ($('td', row).eq(4).text().length > 50) { + $('td', row).eq(4).text($('td', row).eq(4).text().substring(0, 50)); + $('td', row).eq(4).text($('td', row).eq(4).text() + '...') + } + }, + "columnDefs": [ { + "targets": 0, + "orderable": false + } ], + "order": [[1, "asc" ]], + "sDom": "ZRtr", + "iDisplayLength": -1, + "autoWidth": true, + "bSortClasses": false, + "paging": false, + "orderClasses": true + } ); +} collection_click = function() { var this_collection_url; -- cgit v1.2.3 From f5ffdbaaab2cfd6123bd2a454de37efa0f1a13b1 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 15 Jul 2020 13:52:20 -0500 Subject: Increased width for Min Expr since it was getting cut off if you added more than 3 digits --- wqflask/wqflask/templates/show_trait_calculate_correlations.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/show_trait_calculate_correlations.html b/wqflask/wqflask/templates/show_trait_calculate_correlations.html index a9b371b8..1378b91b 100644 --- a/wqflask/wqflask/templates/show_trait_calculate_correlations.html +++ b/wqflask/wqflask/templates/show_trait_calculate_correlations.html @@ -78,7 +78,7 @@
    - +
    -- cgit v1.2.3 From 64c0c61ee3842ea63921ebb73827333d91cf99cc Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 17 Jul 2020 02:47:31 +0300 Subject: Add basic unittests --- test/unittest/__init__.py | 0 test/unittest/base/__init__.py | 0 test/unittest/base/test_data_set.py | 8 ++++++++ test/unittest/base/test_general_object.py | 21 +++++++++++++++++++++ 4 files changed, 29 insertions(+) create mode 100644 test/unittest/__init__.py create mode 100644 test/unittest/base/__init__.py create mode 100644 test/unittest/base/test_data_set.py create mode 100644 test/unittest/base/test_general_object.py diff --git a/test/unittest/__init__.py b/test/unittest/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/unittest/base/__init__.py b/test/unittest/base/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/unittest/base/test_data_set.py b/test/unittest/base/test_data_set.py new file mode 100644 index 00000000..6537efa6 --- /dev/null +++ b/test/unittest/base/test_data_set.py @@ -0,0 +1,8 @@ +import unittest + +import wqflask.base.data_set +print dir() + +class TestDataSet(unittest.TestCase): + def test_add(self): + self.assertEqual(3, 4) diff --git a/test/unittest/base/test_general_object.py b/test/unittest/base/test_general_object.py new file mode 100644 index 00000000..eaefdec9 --- /dev/null +++ b/test/unittest/base/test_general_object.py @@ -0,0 +1,21 @@ +import unittest + +from wqflask.base.GeneralObject import GeneralObject + + +class TestGeneralObjectTests(unittest.TestCase): + """ + Test the GeneralObject base class + """ + + def test_object_contents(self): + """Test whether base contents are stored properly""" + test_obj = GeneralObject("a", "b", "c") + self.assertEqual("abc", ''.join(test_obj.contents)) + + def test_object_dict(self): + """Test whether the base class is printed properly""" + test_obj = GeneralObject("a", name="test", value=1) + self.assertEqual(str(test_obj), "value = 1\nname = test\n") + self.assertEqual( + repr(test_obj), "value = 1\nname = test\ncontents = ['a']\n") -- cgit v1.2.3 From b11d28f4cd90d7f5599324961ec405c5d026b309 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sat, 18 Jul 2020 03:41:00 +0300 Subject: Remove unused import --- wqflask/base/data_set.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 2272b6ee..116d1d24 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -47,8 +47,6 @@ from utility import chunks from utility import gen_geno_ob from utility.tools import locate, locate_ignore_error, flat_files -from wqflask.api import gen_menu - from maintenance import get_group_samplelists from MySQLdb import escape_string as escape @@ -64,6 +62,9 @@ logger = getLogger(__name__ ) # Each subclass will add to this DS_NAME_MAP = {} +def my_add(x, y): + return x + y + def create_dataset(dataset_name, dataset_type = None, get_samplelist = True, group_name = None): if dataset_name == "Temp": dataset_type = "Temp" -- cgit v1.2.3 From b362f2586c182c0400bd40e260c9d25240858790 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sat, 18 Jul 2020 03:41:38 +0300 Subject: Remove unused config --- wqflask/wqflask/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wqflask/wqflask/__init__.py b/wqflask/wqflask/__init__.py index 62e98b36..d729aef5 100644 --- a/wqflask/wqflask/__init__.py +++ b/wqflask/wqflask/__init__.py @@ -12,7 +12,6 @@ logging.basicConfig(level=logging.INFO) app = Flask(__name__) -app.config.from_object('cfg.default_settings') # Get the defaults from cfg.default_settings app.config.from_envvar('GN2_SETTINGS') # See http://flask.pocoo.org/docs/config/#configuring-from-files # Note no longer use the badly named WQFLASK_OVERRIDES (nyi) @@ -22,4 +21,4 @@ app.jinja_env.globals.update( ) from wqflask.api import router -import wqflask.views \ No newline at end of file +import wqflask.views -- cgit v1.2.3 From f4e61929d0eb490dbf7e8ddc03a20d42d44b6f5c Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 12:36:06 +0300 Subject: Add work-around for failed imports in unittest * wqflask/utility/tools.py: Unittests will use `from wqflask.wqflask import app` and the gn2 script will use `from wqflask import app` --- wqflask/utility/tools.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py index 77db5d53..37f9d8fe 100644 --- a/wqflask/utility/tools.py +++ b/wqflask/utility/tools.py @@ -5,7 +5,10 @@ import os import sys import json -from wqflask import app +try: + from wqflask import app +except ImportError: + from wqflask.wqflask import app # Use the standard logger here to avoid a circular dependency import logging -- cgit v1.2.3 From 5db20299b046e37fcb3517e89c3ecf527e7db657 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 12:40:37 +0300 Subject: Remove mock directory * wqflask/mock: Delete it. Causes name conflicts with Python's mock module --- wqflask/mock/__init__.py | 0 wqflask/mock/es_double.py | 15 --------------- 2 files changed, 15 deletions(-) delete mode 100644 wqflask/mock/__init__.py delete mode 100644 wqflask/mock/es_double.py diff --git a/wqflask/mock/__init__.py b/wqflask/mock/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/wqflask/mock/es_double.py b/wqflask/mock/es_double.py deleted file mode 100644 index 6ef8a1b9..00000000 --- a/wqflask/mock/es_double.py +++ /dev/null @@ -1,15 +0,0 @@ -class ESDouble(object): - def __init__(self): - self.items = {} - - def ping(self): - return true - - def create(self, index, doc_type, body, id): - self.items["index"] = {doc_type: {"id": id, "_source": data}} - - def search(self, index, doc_type, body): - return { - "hits": { - "hits": self.items[index][doc_type][body] - }} -- cgit v1.2.3 From 244761531dd6f4f019add26d837b6b79530e0f17 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 12:42:52 +0300 Subject: Make test file a module * test/__init__.py: Add it. Makes the test folder discoverable by unittest --- test/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/__init__.py diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 511acaccdad93d2766f973ada11ae2b85367e6ef Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 13:22:46 +0300 Subject: Apply autopep-8 * wqflask/base/GeneralObject.py: Replace tabs with 4 spaces --- wqflask/base/GeneralObject.py | 80 +++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/wqflask/base/GeneralObject.py b/wqflask/base/GeneralObject.py index 02a1ef06..37bfeda3 100644 --- a/wqflask/base/GeneralObject.py +++ b/wqflask/base/GeneralObject.py @@ -25,44 +25,44 @@ # Last updated by GeneNetwork Core Team 2010/10/20 class GeneralObject: - """ - Base class to define an Object. - a = [Spam(1, 4), Spam(9, 3), Spam(4,6)] - a.sort(lambda x, y: cmp(x.eggs, y.eggs)) - """ + """ + Base class to define an Object. + a = [Spam(1, 4), Spam(9, 3), Spam(4,6)] + a.sort(lambda x, y: cmp(x.eggs, y.eggs)) + """ - def __init__(self, *args, **kw): - self.contents = list(args) - for name, value in kw.items(): - setattr(self, name, value) - - def __setitem__(self, key, value): - setattr(self, key, value) - - def __getitem__(self, key): - return getattr(self, key) - - def __getattr__(self, key): - if key in self.__dict__.keys(): - return self.__dict__[key] - else: - return eval("self.__dict__.%s" % key) - - def __len__(self): - return len(self.__dict__) - 1 - - def __str__(self): - s = '' - for key in self.__dict__.keys(): - if key != 'contents': - s += '%s = %s\n' % (key,self.__dict__[key]) - return s - - def __repr__(self): - s = '' - for key in self.__dict__.keys(): - s += '%s = %s\n' % (key,self.__dict__[key]) - return s - - def __cmp__(self,other): - return len(self.__dict__.keys()).__cmp__(len(other.__dict__.keys())) \ No newline at end of file + def __init__(self, *args, **kw): + self.contents = list(args) + for name, value in kw.items(): + setattr(self, name, value) + + def __setitem__(self, key, value): + setattr(self, key, value) + + def __getitem__(self, key): + return getattr(self, key) + + def __getattr__(self, key): + if key in self.__dict__.keys(): + return self.__dict__[key] + else: + return eval("self.__dict__.%s" % key) + + def __len__(self): + return len(self.__dict__) - 1 + + def __str__(self): + s = '' + for key in self.__dict__.keys(): + if key != 'contents': + s += '%s = %s\n' % (key, self.__dict__[key]) + return s + + def __repr__(self): + s = '' + for key in self.__dict__.keys(): + s += '%s = %s\n' % (key, self.__dict__[key]) + return s + + def __cmp__(self, other): + return len(self.__dict__.keys()).__cmp__(len(other.__dict__.keys())) -- cgit v1.2.3 From 2cbda12fe9fa9fca4d27796b2a8eb719e659dc7f Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 21:53:30 +0300 Subject: Revert "Add work-around for failed imports in unittest" This reverts commit d5e87fa6fe7546b46790f512d984a5501223082f. --- wqflask/utility/tools.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py index 37f9d8fe..77db5d53 100644 --- a/wqflask/utility/tools.py +++ b/wqflask/utility/tools.py @@ -5,10 +5,7 @@ import os import sys import json -try: - from wqflask import app -except ImportError: - from wqflask.wqflask import app +from wqflask import app # Use the standard logger here to avoid a circular dependency import logging -- cgit v1.2.3 From 7dff20b84aaab9c5c9e15269a1716cd14d953db1 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 22:40:14 +0300 Subject: Apply autopep-8 * wqflask/base/data_set.py: Apply autopep-8 --- wqflask/base/data_set.py | 261 +++++++++++++++++++++++++---------------------- 1 file changed, 141 insertions(+), 120 deletions(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 116d1d24..f9705a22 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -19,6 +19,23 @@ # This module is used by GeneNetwork project (www.genenetwork.org) from __future__ import absolute_import, print_function, division +from db.call import fetchall, fetchone, fetch1 +from utility.logger import getLogger +from utility.tools import USE_GN_SERVER, USE_REDIS, flat_files, flat_file_exists, GN2_BASE_URL +from db.gn_server import menu_main +from pprint import pformat as pf +from MySQLdb import escape_string as escape +from maintenance import get_group_samplelists +from utility.tools import locate, locate_ignore_error, flat_files +from utility import gen_geno_ob +from utility import chunks +from utility.benchmark import Bench +from utility import webqtlUtil +from db import webqtlDatabaseFunction +from base import species +from base import webqtlConfig +import reaper +from flask import Flask, g import os import math import string @@ -34,38 +51,15 @@ import itertools from redis import Redis Redis = Redis() -from flask import Flask, g - -import reaper - -from base import webqtlConfig -from base import species -from db import webqtlDatabaseFunction -from utility import webqtlUtil -from utility.benchmark import Bench -from utility import chunks -from utility import gen_geno_ob -from utility.tools import locate, locate_ignore_error, flat_files - -from maintenance import get_group_samplelists -from MySQLdb import escape_string as escape -from pprint import pformat as pf -from db.gn_server import menu_main -from db.call import fetchall,fetchone,fetch1 - -from utility.tools import USE_GN_SERVER, USE_REDIS, flat_files, flat_file_exists, GN2_BASE_URL -from utility.logger import getLogger -logger = getLogger(__name__ ) +logger = getLogger(__name__) # Used by create_database to instantiate objects # Each subclass will add to this DS_NAME_MAP = {} -def my_add(x, y): - return x + y -def create_dataset(dataset_name, dataset_type = None, get_samplelist = True, group_name = None): +def create_dataset(dataset_name, dataset_type=None, get_samplelist=True, group_name=None): if dataset_name == "Temp": dataset_type = "Temp" @@ -79,6 +73,7 @@ def create_dataset(dataset_name, dataset_type = None, get_samplelist = True, gro else: return dataset_class(dataset_name, get_samplelist) + class Dataset_Types(object): def __init__(self): @@ -101,9 +96,10 @@ Publish or ProbeSet. E.g. data = Redis.get("dataset_structure") if data: self.datasets = json.loads(data) - else: #ZS: I don't think this should ever run unless Redis is emptied + else: # ZS: I don't think this should ever run unless Redis is emptied try: - data = json.loads(requests.get(GN2_BASE_URL + "/api/v_pre1/gen_dropdown", timeout = 5).content) + data = json.loads(requests.get( + GN2_BASE_URL + "/api/v_pre1/gen_dropdown", timeout=5).content) for species in data['datasets']: for group in data['datasets'][species]: for dataset_type in data['datasets'][species][group]: @@ -122,7 +118,7 @@ Publish or ProbeSet. E.g. Redis.set("dataset_structure", json.dumps(self.datasets)) # Set LOG_LEVEL_DEBUG=5 to see the following: - logger.debugf(5, "datasets",self.datasets) + logger.debugf(5, "datasets", self.datasets) def __call__(self, name): if name not in self.datasets: @@ -155,7 +151,7 @@ Publish or ProbeSet. E.g. Redis.set("dataset_structure", json.dumps(self.datasets)) return self.datasets[name] - #ZS: For when there isn't an InfoFiles ID; not sure if this and the preceding query are both necessary + # ZS: For when there isn't an InfoFiles ID; not sure if this and the preceding query are both necessary other_pheno_query = """SELECT PublishFreeze.Name FROM PublishFreeze, InbredSet WHERE InbredSet.Name = '{}' AND @@ -167,7 +163,7 @@ Publish or ProbeSet. E.g. Redis.set("dataset_structure", json.dumps(self.datasets)) return self.datasets[name] - geno_query = """ + geno_query = """ SELECT GenoFreeze.Id FROM @@ -182,14 +178,16 @@ Publish or ProbeSet. E.g. Redis.set("dataset_structure", json.dumps(self.datasets)) return self.datasets[name] - #ZS: It shouldn't ever reach this + # ZS: It shouldn't ever reach this return None else: return self.datasets[name] + # Do the intensive work at startup one time only Dataset_Getter = Dataset_Types() + def create_datasets_list(): if USE_REDIS: key = "all_datasets" @@ -209,10 +207,10 @@ def create_datasets_list(): for dataset_type in type_dict: query = "SELECT Name FROM {}".format(type_dict[dataset_type]) for result in fetchall(query): - #The query at the beginning of this function isn't - #necessary here, but still would rather just reuse - #it logger.debug("type: {}\tname: - #{}".format(dataset_type, result.Name)) + # The query at the beginning of this function isn't + # necessary here, but still would rather just reuse + # it logger.debug("type: {}\tname: + # {}".format(dataset_type, result.Name)) dataset = create_dataset(result.Name, dataset_type) datasets.append(dataset) @@ -239,8 +237,9 @@ def mescape(*items): class Markers(object): """Todo: Build in cacheing so it saves us reading the same file more than once""" + def __init__(self, name): - json_data_fh = open(locate(name + ".json",'genotype/json')) + json_data_fh = open(locate(name + ".json", 'genotype/json')) markers = [] with open("%s/%s_snps.txt" % (flat_files('genotype/bimbam'), name), 'r') as bimbam_fh: @@ -272,7 +271,7 @@ class Markers(object): if type(p_values) is list: # THIS IS only needed for the case when we are limiting the number of p-values calculated - #if len(self.markers) > len(p_values): + # if len(self.markers) > len(p_values): # self.markers = self.markers[:len(p_values)] for marker, p_value in itertools.izip(self.markers, p_values): @@ -284,7 +283,7 @@ class Markers(object): marker['lrs_value'] = 0 else: marker['lod_score'] = -math.log10(marker['p_value']) - #Using -log(p) for the LRS; need to ask Rob how he wants to get LRS from p-values + # Using -log(p) for the LRS; need to ask Rob how he wants to get LRS from p-values marker['lrs_value'] = -math.log10(marker['p_value']) * 4.61 elif type(p_values) is dict: filtered_markers = [] @@ -299,18 +298,20 @@ class Markers(object): marker['lrs_value'] = 0 else: marker['lod_score'] = -math.log10(marker['p_value']) - #Using -log(p) for the LRS; need to ask Rob how he wants to get LRS from p-values - marker['lrs_value'] = -math.log10(marker['p_value']) * 4.61 + # Using -log(p) for the LRS; need to ask Rob how he wants to get LRS from p-values + marker['lrs_value'] = - \ + math.log10(marker['p_value']) * 4.61 filtered_markers.append(marker) - #else: + # else: #logger.debug("marker {} NOT in p_values".format(i)) - #self.markers.remove(marker) + # self.markers.remove(marker) #del self.markers[i] self.markers = filtered_markers + class HumanMarkers(Markers): - def __init__(self, name, specified_markers = []): + def __init__(self, name, specified_markers=[]): marker_data_fh = open(flat_files('mapping') + '/' + name + '.bim') self.markers = [] for line in marker_data_fh: @@ -333,7 +334,6 @@ class HumanMarkers(Markers): #logger.debug("markers is: ", pf(self.markers)) - def add_pvalues(self, p_values): super(HumanMarkers, self).add_pvalues(p_values) @@ -346,12 +346,15 @@ class DatasetGroup(object): has multiple datasets associated with it. """ + def __init__(self, dataset, name=None): """This sets self.group and self.group_id""" if name == None: - self.name, self.id, self.genetic_type = fetchone(dataset.query_for_group) + self.name, self.id, self.genetic_type = fetchone( + dataset.query_for_group) else: - self.name, self.id, self.genetic_type = fetchone("SELECT InbredSet.Name, InbredSet.Id, InbredSet.GeneticType FROM InbredSet where Name='%s'" % name) + self.name, self.id, self.genetic_type = fetchone( + "SELECT InbredSet.Name, InbredSet.Id, InbredSet.GeneticType FROM InbredSet where Name='%s'" % name) if self.name == 'BXD300': self.name = "BXD" @@ -370,7 +373,8 @@ class DatasetGroup(object): def get_mapping_methods(self): - mapping_id = g.db.execute("select MappingMethodId from InbredSet where Name= '%s'" % self.name).fetchone()[0] + mapping_id = g.db.execute( + "select MappingMethodId from InbredSet where Name= '%s'" % self.name).fetchone()[0] if mapping_id == "1": mapping_names = ["GEMMA", "QTLReaper", "R/qtl"] elif mapping_id == "2": @@ -434,9 +438,10 @@ class DatasetGroup(object): else: logger.debug("Cache not hit") - genotype_fn = locate_ignore_error(self.name+".geno",'genotype') + genotype_fn = locate_ignore_error(self.name+".geno", 'genotype') if genotype_fn: - self.samplelist = get_group_samplelists.get_samplelist("geno", genotype_fn) + self.samplelist = get_group_samplelists.get_samplelist( + "geno", genotype_fn) else: self.samplelist = None @@ -452,15 +457,16 @@ class DatasetGroup(object): def read_genotype_file(self, use_reaper=False): '''Read genotype from .geno file instead of database''' - #genotype_1 is Dataset Object without parents and f1 - #genotype_2 is Dataset Object with parents and f1 (not for intercross) + # genotype_1 is Dataset Object without parents and f1 + # genotype_2 is Dataset Object with parents and f1 (not for intercross) #genotype_1 = reaper.Dataset() # reaper barfs on unicode filenames, so here we ensure it's a string if self.genofile: - if "RData" in self.genofile: #ZS: This is a temporary fix; I need to change the way the JSON files that point to multiple genotype files are structured to point to other file types like RData - full_filename = str(locate(self.genofile.split(".")[0] + ".geno", 'genotype')) + if "RData" in self.genofile: # ZS: This is a temporary fix; I need to change the way the JSON files that point to multiple genotype files are structured to point to other file types like RData + full_filename = str( + locate(self.genofile.split(".")[0] + ".geno", 'genotype')) else: full_filename = str(locate(self.genofile, 'genotype')) else: @@ -473,11 +479,12 @@ class DatasetGroup(object): genotype_1 = gen_geno_ob.genotype(full_filename) if genotype_1.type == "group" and self.parlist: - genotype_2 = genotype_1.add(Mat=self.parlist[0], Pat=self.parlist[1]) #, F1=_f1) + genotype_2 = genotype_1.add( + Mat=self.parlist[0], Pat=self.parlist[1]) # , F1=_f1) else: genotype_2 = genotype_1 - #determine default genotype object + # determine default genotype object if self.incparentsf1 and genotype_1.type != "intercross": genotype = genotype_2 else: @@ -488,7 +495,8 @@ class DatasetGroup(object): return genotype -def datasets(group_name, this_group = None): + +def datasets(group_name, this_group=None): key = "group_dataset_menu:v2:" + group_name dataset_menu = [] the_results = fetchall(''' @@ -511,12 +519,13 @@ def datasets(group_name, this_group = None): and InbredSet.Name like %s ORDER BY Tissue.Name, ProbeSetFreeze.OrderList DESC) ''' % (group_name, - group_name, - "'" + group_name + "'")) + group_name, + "'" + group_name + "'")) sorted_results = sorted(the_results, key=lambda kv: kv[0]) - pheno_inserted = False #ZS: This is kind of awkward, but need to ensure Phenotypes show up before Genotypes in dropdown + # ZS: This is kind of awkward, but need to ensure Phenotypes show up before Genotypes in dropdown + pheno_inserted = False geno_inserted = False for dataset_item in sorted_results: tissue_name = dataset_item[0] @@ -524,13 +533,16 @@ def datasets(group_name, this_group = None): dataset_short = dataset_item[2] if tissue_name in ['#PublishFreeze', '#GenoFreeze']: if tissue_name == '#PublishFreeze' and (dataset_short == group_name + 'Publish'): - dataset_menu.insert(0, dict(tissue=None, datasets=[(dataset, dataset_short)])) + dataset_menu.insert( + 0, dict(tissue=None, datasets=[(dataset, dataset_short)])) pheno_inserted = True elif pheno_inserted and tissue_name == '#GenoFreeze': - dataset_menu.insert(1, dict(tissue=None, datasets=[(dataset, dataset_short)])) + dataset_menu.insert( + 1, dict(tissue=None, datasets=[(dataset, dataset_short)])) geno_inserted = True else: - dataset_menu.append(dict(tissue=None, datasets=[(dataset, dataset_short)])) + dataset_menu.append( + dict(tissue=None, datasets=[(dataset, dataset_short)])) else: tissue_already_exists = False for i, tissue_dict in enumerate(dataset_menu): @@ -543,7 +555,7 @@ def datasets(group_name, this_group = None): dataset_menu[i]['datasets'].append((dataset, dataset_short)) else: dataset_menu.append(dict(tissue=tissue_name, - datasets=[(dataset, dataset_short)])) + datasets=[(dataset, dataset_short)])) if USE_REDIS: Redis.set(key, pickle.dumps(dataset_menu, pickle.HIGHEST_PROTOCOL)) @@ -555,6 +567,7 @@ def datasets(group_name, this_group = None): else: return dataset_menu + class DataSet(object): """ DataSet class defines a dataset in webqtl, can be either Microarray, @@ -562,7 +575,7 @@ class DataSet(object): """ - def __init__(self, name, get_samplelist = True, group_name = None): + def __init__(self, name, get_samplelist=True, group_name=None): assert name, "Need a name" self.name = name @@ -570,22 +583,23 @@ class DataSet(object): self.shortname = None self.fullname = None self.type = None - self.data_scale = None #ZS: For example log2 + self.data_scale = None # ZS: For example log2 self.setup() - if self.type == "Temp": #Need to supply group name as input if temp trait - self.group = DatasetGroup(self, name=group_name) # sets self.group and self.group_id and gets genotype + if self.type == "Temp": # Need to supply group name as input if temp trait + # sets self.group and self.group_id and gets genotype + self.group = DatasetGroup(self, name=group_name) else: self.check_confidentiality() self.retrieve_other_names() - self.group = DatasetGroup(self) # sets self.group and self.group_id and gets genotype + # sets self.group and self.group_id and gets genotype + self.group = DatasetGroup(self) self.accession_id = self.get_accession_id() if get_samplelist == True: - self.group.get_samplelist() + self.group.get_samplelist() self.species = species.TheSpecies(self) - def get_desc(self): """Gets overridden later, at least for Temp...used by trait's get_given_name""" return None @@ -645,8 +659,9 @@ class DataSet(object): WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id AND ProbeFreeze.TissueId = Tissue.Id AND (ProbeSetFreeze.Name = '%s' OR ProbeSetFreeze.FullName = '%s' OR ProbeSetFreeze.ShortName = '%s') - """ % (query_args),"/dataset/"+self.name+".json", - lambda r: (r["id"],r["name"],r["full_name"],r["short_name"],r["data_scale"],r["tissue"]) + """ % (query_args), "/dataset/"+self.name+".json", + lambda r: (r["id"], r["name"], r["full_name"], + r["short_name"], r["data_scale"], r["tissue"]) ) else: query_args = tuple(escape(x) for x in ( @@ -663,7 +678,8 @@ class DataSet(object): """ % (query_args)) except TypeError: - logger.debug("Dataset {} is not yet available in GeneNetwork.".format(self.name)) + logger.debug( + "Dataset {} is not yet available in GeneNetwork.".format(self.name)) pass def get_trait_data(self, sample_list=None): @@ -721,7 +737,7 @@ class DataSet(object): and {}.Id = {}XRef.{}Id order by {}.Id """.format(*mescape(self.type, self.type, self.type, self.name, - dataset_type, self.type, dataset_type, dataset_type)) + dataset_type, self.type, dataset_type, dataset_type)) else: query += """ WHERE {}XRef.{}FreezeId = {}Freeze.Id @@ -729,7 +745,7 @@ class DataSet(object): and {}.Id = {}XRef.{}Id order by {}.Id """.format(*mescape(self.type, self.type, self.type, self.type, - self.name, dataset_type, self.type, self.type, dataset_type)) + self.name, dataset_type, self.type, self.type, dataset_type)) #logger.debug("trait data query: ", query) @@ -749,6 +765,7 @@ class DataSet(object): self.trait_data[trait_name] += ( trait_sample_data[chunk_counter][trait_counter][data_start_pos:]) + class PhenotypeDataSet(DataSet): DS_NAME_MAP['Publish'] = 'PhenotypeDataSet' @@ -758,16 +775,16 @@ class PhenotypeDataSet(DataSet): # Fields in the database table self.search_fields = ['Phenotype.Post_publication_description', - 'Phenotype.Pre_publication_description', - 'Phenotype.Pre_publication_abbreviation', - 'Phenotype.Post_publication_abbreviation', - 'PublishXRef.mean', - 'Phenotype.Lab_code', - 'Publication.PubMed_ID', - 'Publication.Abstract', - 'Publication.Title', - 'Publication.Authors', - 'PublishXRef.Id'] + 'Phenotype.Pre_publication_description', + 'Phenotype.Pre_publication_abbreviation', + 'Phenotype.Post_publication_abbreviation', + 'PublishXRef.mean', + 'Phenotype.Lab_code', + 'Publication.PubMed_ID', + 'Publication.Abstract', + 'Publication.Title', + 'Publication.Authors', + 'PublishXRef.Id'] # Figure out what display_fields is self.display_fields = ['name', 'group_code', @@ -789,13 +806,13 @@ class PhenotypeDataSet(DataSet): # Fields displayed in the search results table header self.header_fields = ['Index', - 'Record', - 'Description', - 'Authors', - 'Year', - 'Max LRS', - 'Max LRS Location', - 'Additive Effect'] + 'Record', + 'Description', + 'Authors', + 'Year', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'] self.type = 'Publish' @@ -813,7 +830,7 @@ class PhenotypeDataSet(DataSet): # (Urgently?) Need to write this pass - def get_trait_info(self, trait_list, species = ''): + def get_trait_info(self, trait_list, species=''): for this_trait in trait_list: if not this_trait.haveinfo: @@ -821,9 +838,9 @@ class PhenotypeDataSet(DataSet): description = this_trait.post_publication_description - #If the dataset is confidential and the user has access to confidential - #phenotype traits, then display the pre-publication description instead - #of the post-publication description + # If the dataset is confidential and the user has access to confidential + # phenotype traits, then display the pre-publication description instead + # of the post-publication description if this_trait.confidential: this_trait.description_display = "" continue # for now, because no authorization features @@ -848,7 +865,7 @@ class PhenotypeDataSet(DataSet): if this_trait.pubmed_id: this_trait.pubmed_link = webqtlConfig.PUBMEDLINK_URL % this_trait.pubmed_id - #LRS and its location + # LRS and its location this_trait.LRS_score_repr = "N/A" this_trait.LRS_location_repr = "N/A" @@ -868,7 +885,8 @@ class PhenotypeDataSet(DataSet): LRS_Mb = result[1] this_trait.LRS_score_repr = LRS_score_repr = '%3.1f' % this_trait.lrs - this_trait.LRS_location_repr = LRS_location_repr = 'Chr%s: %.6f' % (LRS_Chr, float(LRS_Mb)) + this_trait.LRS_location_repr = LRS_location_repr = 'Chr%s: %.6f' % ( + LRS_Chr, float(LRS_Mb)) def retrieve_sample_data(self, trait): query = """ @@ -935,7 +953,8 @@ class GenotypeDataSet(DataSet): this_trait.retrieveInfo() if this_trait.chr and this_trait.mb: - this_trait.location_repr = 'Chr%s: %.6f' % (this_trait.chr, float(this_trait.mb) ) + this_trait.location_repr = 'Chr%s: %.6f' % ( + this_trait.chr, float(this_trait.mb)) def retrieve_sample_data(self, trait): query = """ @@ -1004,14 +1023,14 @@ class MrnaAssayDataSet(DataSet): # Fields displayed in the search results table header self.header_fields = ['Index', - 'Record', - 'Symbol', - 'Description', - 'Location', - 'Mean', - 'Max LRS', - 'Max LRS Location', - 'Additive Effect'] + 'Record', + 'Symbol', + 'Description', + 'Location', + 'Mean', + 'Max LRS', + 'Max LRS Location', + 'Additive Effect'] # Todo: Obsolete or rename this field self.type = 'ProbeSet' @@ -1027,7 +1046,6 @@ class MrnaAssayDataSet(DataSet): ProbeSetFreeze.Name = "%s" ''' % escape(self.name) - def check_confidentiality(self): return geno_mrna_confidentiality(self) @@ -1045,10 +1063,12 @@ class MrnaAssayDataSet(DataSet): if not this_trait.symbol: this_trait.symbol = "N/A" - #XZ, 12/08/2008: description - #XZ, 06/05/2009: Rob asked to add probe target description - description_string = unicode(str(this_trait.description).strip(codecs.BOM_UTF8), 'utf-8') - target_string = unicode(str(this_trait.probe_target_description).strip(codecs.BOM_UTF8), 'utf-8') + # XZ, 12/08/2008: description + # XZ, 06/05/2009: Rob asked to add probe target description + description_string = unicode( + str(this_trait.description).strip(codecs.BOM_UTF8), 'utf-8') + target_string = unicode( + str(this_trait.probe_target_description).strip(codecs.BOM_UTF8), 'utf-8') if len(description_string) > 1 and description_string != 'None': description_display = description_string @@ -1063,11 +1083,12 @@ class MrnaAssayDataSet(DataSet): this_trait.description_display = description_display if this_trait.chr and this_trait.mb: - this_trait.location_repr = 'Chr%s: %.6f' % (this_trait.chr, float(this_trait.mb)) + this_trait.location_repr = 'Chr%s: %.6f' % ( + this_trait.chr, float(this_trait.mb)) - #Get mean expression value + # Get mean expression value query = ( - """select ProbeSetXRef.mean from ProbeSetXRef, ProbeSet + """select ProbeSetXRef.mean from ProbeSetXRef, ProbeSet where ProbeSetXRef.ProbeSetFreezeId = %s and ProbeSet.Id = ProbeSetXRef.ProbeSetId and ProbeSet.Name = '%s' @@ -1083,11 +1104,11 @@ class MrnaAssayDataSet(DataSet): if mean: this_trait.mean = "%2.3f" % mean - #LRS and its location + # LRS and its location this_trait.LRS_score_repr = 'N/A' this_trait.LRS_location_repr = 'N/A' - #Max LRS and its Locus location + # Max LRS and its Locus location if this_trait.lrs and this_trait.locus: query = """ select Geno.Chr, Geno.Mb from Geno, Species @@ -1101,7 +1122,8 @@ class MrnaAssayDataSet(DataSet): if result: lrs_chr, lrs_mb = result this_trait.LRS_score_repr = '%3.1f' % this_trait.lrs - this_trait.LRS_location_repr = 'Chr%s: %.6f' % (lrs_chr, float(lrs_mb)) + this_trait.LRS_location_repr = 'Chr%s: %.6f' % ( + lrs_chr, float(lrs_mb)) return trait_list @@ -1162,7 +1184,6 @@ class TempDataSet(DataSet): self.fullname = 'Temporary Storage' self.shortname = 'Temp' - @staticmethod def handle_pca(desc): if 'PCA' in desc: @@ -1203,7 +1224,7 @@ def geno_mrna_confidentiality(ob): #logger.debug("dataset_table [%s]: %s" % (type(dataset_table), dataset_table)) query = '''SELECT Id, Name, FullName, confidentiality, - AuthorisedUsers FROM %s WHERE Name = "%s"''' % (dataset_table,ob.name) + AuthorisedUsers FROM %s WHERE Name = "%s"''' % (dataset_table, ob.name) logger.sql(query) result = g.db.execute(query) -- cgit v1.2.3 From 3d6483e8fd59d2e77149aa5d78ec32448be7338c Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 22:46:49 +0300 Subject: Move tests to module * test/unittest/: Move to wqflask/tests/ --- test/unittest/__init__.py | 0 test/unittest/base/__init__.py | 0 test/unittest/base/test_data_set.py | 8 -------- test/unittest/base/test_general_object.py | 21 --------------------- wqflask/tests/__init__.py | 0 wqflask/tests/base/__init__.py | 0 wqflask/tests/base/test_general_object.py | 21 +++++++++++++++++++++ 7 files changed, 21 insertions(+), 29 deletions(-) delete mode 100644 test/unittest/__init__.py delete mode 100644 test/unittest/base/__init__.py delete mode 100644 test/unittest/base/test_data_set.py delete mode 100644 test/unittest/base/test_general_object.py create mode 100644 wqflask/tests/__init__.py create mode 100644 wqflask/tests/base/__init__.py create mode 100644 wqflask/tests/base/test_general_object.py diff --git a/test/unittest/__init__.py b/test/unittest/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/test/unittest/base/__init__.py b/test/unittest/base/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/test/unittest/base/test_data_set.py b/test/unittest/base/test_data_set.py deleted file mode 100644 index 6537efa6..00000000 --- a/test/unittest/base/test_data_set.py +++ /dev/null @@ -1,8 +0,0 @@ -import unittest - -import wqflask.base.data_set -print dir() - -class TestDataSet(unittest.TestCase): - def test_add(self): - self.assertEqual(3, 4) diff --git a/test/unittest/base/test_general_object.py b/test/unittest/base/test_general_object.py deleted file mode 100644 index eaefdec9..00000000 --- a/test/unittest/base/test_general_object.py +++ /dev/null @@ -1,21 +0,0 @@ -import unittest - -from wqflask.base.GeneralObject import GeneralObject - - -class TestGeneralObjectTests(unittest.TestCase): - """ - Test the GeneralObject base class - """ - - def test_object_contents(self): - """Test whether base contents are stored properly""" - test_obj = GeneralObject("a", "b", "c") - self.assertEqual("abc", ''.join(test_obj.contents)) - - def test_object_dict(self): - """Test whether the base class is printed properly""" - test_obj = GeneralObject("a", name="test", value=1) - self.assertEqual(str(test_obj), "value = 1\nname = test\n") - self.assertEqual( - repr(test_obj), "value = 1\nname = test\ncontents = ['a']\n") diff --git a/wqflask/tests/__init__.py b/wqflask/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wqflask/tests/base/__init__.py b/wqflask/tests/base/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wqflask/tests/base/test_general_object.py b/wqflask/tests/base/test_general_object.py new file mode 100644 index 00000000..699cb079 --- /dev/null +++ b/wqflask/tests/base/test_general_object.py @@ -0,0 +1,21 @@ +import unittest + +from base.GeneralObject import GeneralObject + + +class TestGeneralObjectTests(unittest.TestCase): + """ + Test the GeneralObject base class + """ + + def test_object_contents(self): + """Test whether base contents are stored properly""" + test_obj = GeneralObject("a", "b", "c") + self.assertEqual("abc", ''.join(test_obj.contents)) + + def test_object_dict(self): + """Test whether the base class is printed properly""" + test_obj = GeneralObject("a", name="test", value=1) + self.assertEqual(str(test_obj), "value = 1\nname = test\n") + self.assertEqual( + repr(test_obj), "value = 1\nname = test\ncontents = ['a']\n") -- cgit v1.2.3 From b4d35b413df6ac11648030afd9ceb76e05e0e0f5 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 23:36:48 +0300 Subject: Test utility methods for chunking * wqflask/tests/utility/test_chunks.py: New test --- wqflask/tests/utility/__init__.py | 0 wqflask/tests/utility/test_chunks.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 wqflask/tests/utility/__init__.py create mode 100644 wqflask/tests/utility/test_chunks.py diff --git a/wqflask/tests/utility/__init__.py b/wqflask/tests/utility/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wqflask/tests/utility/test_chunks.py b/wqflask/tests/utility/test_chunks.py new file mode 100644 index 00000000..8d90a1ec --- /dev/null +++ b/wqflask/tests/utility/test_chunks.py @@ -0,0 +1,19 @@ +"""Test chunking""" + +import unittest + +from utility.chunks import divide_into_chunks + + +class TestChunks(unittest.TestCase): + "Test Utility method for chunking" + def test_divide_into_chunks(self): + "Check that a list is chunked correctly" + self.assertEqual(divide_into_chunks([1, 2, 7, 3, 22, 8, 5, 22, 333], 3), + [[1, 2, 7], [3, 22, 8], [5, 22, 333]]) + self.assertEqual(divide_into_chunks([1, 2, 7, 3, 22, 8, 5, 22, 333], 4), + [[1, 2, 7], [3, 22, 8], [5, 22, 333]]) + self.assertEqual(divide_into_chunks([1, 2, 7, 3, 22, 8, 5, 22, 333], 5), + [[1, 2], [7, 3], [22, 8], [5, 22], [333]]) + self.assertEqual(divide_into_chunks([], 5), + [[]]) -- cgit v1.2.3 From d26ca838b6e823303c4b903c286fcc452caed0ad Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 23:37:21 +0300 Subject: Remove unused doc-tests * wqflask/utility/chunks.py: Remove test code from module --- wqflask/utility/chunks.py | 63 ----------------------------------------------- 1 file changed, 63 deletions(-) diff --git a/wqflask/utility/chunks.py b/wqflask/utility/chunks.py index b0e33c08..d91b9bf4 100644 --- a/wqflask/utility/chunks.py +++ b/wqflask/utility/chunks.py @@ -31,66 +31,3 @@ def divide_into_chunks(the_list, number_chunks): chunks.append(the_list[counter:counter+chunksize]) return chunks - -def _confirm_chunk(original, result): - all_chunked = [] - for chunk in result: - all_chunked.extend(chunk) - print("length of all chunked:", len(all_chunked)) - assert original == all_chunked, "You didn't chunk right" - - -def _chunk_test(divide_func): - import random - random.seed(7) - - number_exact = 0 - total_amount_off = 0 - - for test in range(1, 1001): - print("\n\ntest:", test) - number_chunks = random.randint(1, 20) - number_elements = random.randint(0, 100) - the_list = list(range(1, number_elements)) - result = divide_func(the_list, number_chunks) - - print("Dividing list of length {} into approximately {} chunks - got {} chunks".format( - len(the_list), number_chunks, len(result))) - print("result:", result) - - _confirm_chunk(the_list, result) - - amount_off = abs(number_chunks - len(result)) - if amount_off == 0: - number_exact += 1 - else: - total_amount_off += amount_off - - - print("\n{} exact out of {} [Total amount off: {}]".format(number_exact, - test, - total_amount_off)) - assert number_exact == 558 - assert total_amount_off == 1580 - return number_exact, total_amount_off - - -def _main(): - info = dict() - #funcs = (("sam", sam_divide_into_chunks), ("zach", zach_divide_into_chunks)) - funcs = (("only one", divide_into_chunks),) - for name, func in funcs: - start = time.time() - number_exact, total_amount_off = _chunk_test(func) - took = time.time() - start - info[name] = dict(number_exact=number_exact, - total_amount_off=total_amount_off, - took=took) - - print("info is:", info) - -if __name__ == '__main__': - _main() - print("\nConfirming doctests...") - import doctest - doctest.testmod() -- cgit v1.2.3 From 539e6ac3f211391cf241f2f2b70ed7dbd327fc28 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Jul 2020 00:43:12 +0300 Subject: Add unittests for *utility/corestats* --- wqflask/tests/utility/test_corestats.py | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 wqflask/tests/utility/test_corestats.py diff --git a/wqflask/tests/utility/test_corestats.py b/wqflask/tests/utility/test_corestats.py new file mode 100644 index 00000000..cf91a248 --- /dev/null +++ b/wqflask/tests/utility/test_corestats.py @@ -0,0 +1,55 @@ +"""Test Core Stats""" + +import unittest + +from utility.corestats import Stats + + +class TestChunks(unittest.TestCase): + "Test Utility method for chunking" + + def setUp(self): + self.stat_test = Stats((x for x in range(1, 11))) + + def test_stats_sum(self): + """ Test sequence sum """ + self.assertEqual(self.stat_test.sum(), 55) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.sum(), None) + + def test_stats_count(self): + """ Test sequence count """ + self.assertEqual(self.stat_test.count(), 10) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.count(), 0) + + def test_stats_min(self): + """ Test min value in sequence""" + self.assertEqual(self.stat_test.min(), 1) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.min(), None) + + def test_stats_max(self): + """ Test max value in sequence """ + self.assertEqual(self.stat_test.max(), 10) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.max(), None) + + def test_stats_avg(self): + """ Test avg of sequence """ + self.assertEqual(self.stat_test.avg(), 5.5) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.avg(), None) + + def test_stats_stdev(self): + """ Test standard deviation of sequence """ + self.assertEqual(self.stat_test.stdev(), 3.0276503540974917) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.stdev(), None) + + def test_stats_percentile(self): + """ Test percentile of sequence """ + self.assertEqual(self.stat_test.percentile(20), 3.0) + self.assertEqual(self.stat_test.percentile(101), None) + self.stat_test = Stats([]) + self.assertEqual(self.stat_test.percentile(20), None) -- cgit v1.2.3 From 01bbbc1ee82b43505e65446e4657ca7790453fdf Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Jul 2020 01:33:10 +0300 Subject: Add unittests for *utility/corr_result_helper* --- wqflask/tests/utility/test_corr_result_helpers.py | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 wqflask/tests/utility/test_corr_result_helpers.py diff --git a/wqflask/tests/utility/test_corr_result_helpers.py b/wqflask/tests/utility/test_corr_result_helpers.py new file mode 100644 index 00000000..e196fbdf --- /dev/null +++ b/wqflask/tests/utility/test_corr_result_helpers.py @@ -0,0 +1,32 @@ +""" Test correlation helper methods """ + +import unittest +from utility.corr_result_helpers import normalize_values, common_keys, normalize_values_with_samples + + +class TestCorrelationHelpers(unittest.TestCase): + """Test methods for normalising lists""" + + def test_normalize_values(self): + """Test that a list is normalised correctly""" + self.assertEqual( + normalize_values([2.3, None, None, 3.2, 4.1, 5], [ + 3.4, 7.2, 1.3, None, 6.2, 4.1]), + ([2.3, 4.1, 5], [3.4, 6.2, 4.1], 3) + ) + + def test_common_keys(self): + """Test that common keys are returned as a list""" + a = dict(BXD1=9.113, BXD2=9.825, BXD14=8.985, BXD15=9.300) + b = dict(BXD1=9.723, BXD3=9.825, BXD14=9.124, BXD16=9.300) + self.assertEqual(sorted(common_keys(a, b)), ['BXD1', 'BXD14']) + + def test_normalize_values_with_samples(self): + """Test that a sample(dict) is normalised correctly""" + self.assertEqual( + normalize_values_with_samples( + dict(BXD1=9.113, BXD2=9.825, BXD14=8.985, + BXD15=9.300, BXD20=9.300), + dict(BXD1=9.723, BXD3=9.825, BXD14=9.124, BXD16=9.300)), + (({'BXD1': 9.113, 'BXD14': 8.985}, {'BXD1': 9.723, 'BXD14': 9.124}, 2)) + ) -- cgit v1.2.3 From b39ef1e464208cf8806dc73cfe9684183ce7c9a2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Jul 2020 01:37:52 +0300 Subject: Simplify normalize_values * wqflask/utility/corr_result_helpers.py(normalize_values): Replace loop with zip form --- wqflask/utility/corr_result_helpers.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/wqflask/utility/corr_result_helpers.py b/wqflask/utility/corr_result_helpers.py index b543c589..69c6fe1b 100644 --- a/wqflask/utility/corr_result_helpers.py +++ b/wqflask/utility/corr_result_helpers.py @@ -14,15 +14,11 @@ def normalize_values(a_values, b_values): min_length = min(len(a_values), len(b_values)) a_new = [] b_new = [] - for counter in range(min_length): - if (a_values[counter] or a_values[counter] == 0) and (b_values[counter] or b_values[counter] == 0): - a_new.append(a_values[counter]) - b_new.append(b_values[counter]) - - num_overlap = len(a_new) - assert num_overlap == len(b_new), "Lengths should be the same" - - return a_new, b_new, num_overlap + for a, b in zip(a_values, b_values): + if not (a == None or b == None): + a_new.append(a) + b_new.append(b) + return a_new, b_new, len(a_new) def common_keys(a_samples, b_samples): -- cgit v1.2.3 From f99df1fa0f2163a93f7e194beeb65f0e1d542594 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Jul 2020 01:40:03 +0300 Subject: Remove unused doc-tests * wqflask/utility/corr_result_helpers.py: Delete doc-test --- wqflask/utility/corr_result_helpers.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/wqflask/utility/corr_result_helpers.py b/wqflask/utility/corr_result_helpers.py index 69c6fe1b..09017e4a 100644 --- a/wqflask/utility/corr_result_helpers.py +++ b/wqflask/utility/corr_result_helpers.py @@ -44,9 +44,3 @@ def normalize_values_with_samples(a_samples, b_samples): assert num_overlap == len(b_new), "Lengths should be the same" return a_new, b_new, num_overlap - - - -if __name__ == '__main__': - import doctest - doctest.testmod() \ No newline at end of file -- cgit v1.2.3 From 2b7d50f9ac6d0f4f6a032e60053b5923c292a0a1 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Jul 2020 01:41:14 +0300 Subject: Remove unused assert * wqflask/utility/corr_result_helpers.py(normalize_values): At no one point will that assert be hit --- wqflask/utility/corr_result_helpers.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wqflask/utility/corr_result_helpers.py b/wqflask/utility/corr_result_helpers.py index 09017e4a..a43edbd4 100644 --- a/wqflask/utility/corr_result_helpers.py +++ b/wqflask/utility/corr_result_helpers.py @@ -33,14 +33,10 @@ def common_keys(a_samples, b_samples): def normalize_values_with_samples(a_samples, b_samples): common_samples = common_keys(a_samples, b_samples) - a_new = {} b_new = {} for sample in common_samples: a_new[sample] = a_samples[sample] b_new[sample] = b_samples[sample] - num_overlap = len(a_new) - assert num_overlap == len(b_new), "Lengths should be the same" - return a_new, b_new, num_overlap -- cgit v1.2.3 From 82ee315583281b93e1eff9640ce04e44bc70ac58 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Jul 2020 01:41:45 +0300 Subject: Remove redundant variable * wqflask/utility/corr_result_helpers.py(normalize_values_with_values): Remove `num_overlap` --- wqflask/utility/corr_result_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/utility/corr_result_helpers.py b/wqflask/utility/corr_result_helpers.py index a43edbd4..ea3ababf 100644 --- a/wqflask/utility/corr_result_helpers.py +++ b/wqflask/utility/corr_result_helpers.py @@ -39,4 +39,4 @@ def normalize_values_with_samples(a_samples, b_samples): a_new[sample] = a_samples[sample] b_new[sample] = b_samples[sample] - return a_new, b_new, num_overlap + return a_new, b_new, len(a_new) -- cgit v1.2.3 From aed1b7f12f653694c22ab389a403120e143a0669 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Jul 2020 17:03:37 +0300 Subject: Add zero to num_repr dictionary * wqflask/utility/formatting.py(numify): Update `num_repr` to have a zero --- wqflask/utility/formatting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wqflask/utility/formatting.py b/wqflask/utility/formatting.py index e53dda22..1c0269e9 100644 --- a/wqflask/utility/formatting.py +++ b/wqflask/utility/formatting.py @@ -28,7 +28,8 @@ def numify(number, singular=None, plural=None): '12,334 hippopotami' """ - num_repr = {1 : "one", + num_repr = {0 : "zero", + 1 : "one", 2 : "two", 3 : "three", 4 : "four", -- cgit v1.2.3 From 073e8c9813505fc00372b2ec21f3e4a87b5be9aa Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Jul 2020 17:05:11 +0300 Subject: Apply autopep-8 * wqflask/utility/formatting.py: apply it --- wqflask/utility/formatting.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/wqflask/utility/formatting.py b/wqflask/utility/formatting.py index 1c0269e9..1da3e9b7 100644 --- a/wqflask/utility/formatting.py +++ b/wqflask/utility/formatting.py @@ -28,22 +28,20 @@ def numify(number, singular=None, plural=None): '12,334 hippopotami' """ - num_repr = {0 : "zero", - 1 : "one", - 2 : "two", - 3 : "three", - 4 : "four", - 5 : "five", - 6 : "six", - 7 : "seven", - 8 : "eight", - 9 : "nine", - 10 : "ten", - 11 : "eleven", - 12 : "twelve"} - - #Below line commented out cause doesn't work in Python 2.4 - #assert all((singular, plural)) or not any((singular, plural)), "Need to pass two words or none" + num_repr = {0: "zero", + 1: "one", + 2: "two", + 3: "three", + 4: "four", + 5: "five", + 6: "six", + 7: "seven", + 8: "eight", + 9: "nine", + 10: "ten", + 11: "eleven", + 12: "twelve"} + if number == 1: word = singular else: -- cgit v1.2.3 From 87fd995209aba0df1f95c1d5d5dd7eefd60d0906 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Jul 2020 17:22:14 +0300 Subject: Add unittests for *utility/test_numify* --- wqflask/tests/utility/test_numify.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 wqflask/tests/utility/test_numify.py diff --git a/wqflask/tests/utility/test_numify.py b/wqflask/tests/utility/test_numify.py new file mode 100644 index 00000000..9d3033d1 --- /dev/null +++ b/wqflask/tests/utility/test_numify.py @@ -0,0 +1,33 @@ +import unittest +from utility.formatting import numify, commify + + +class TestFormatting(unittest.TestCase): + """Test formatting numbers by numifying or commifying""" + + def test_numify(self): + "Test that a number is correctly converted to a English readable string" + self.assertEqual(numify(1, 'item', 'items'), + 'one item') + self.assertEqual(numify(2, 'book'), 'two') + self.assertEqual(numify(2, 'book', 'books'), 'two books') + self.assertEqual(numify(0, 'book', 'books'), 'zero books') + self.assertEqual(numify(0), 'zero') + self.assertEqual(numify(5), 'five') + self.assertEqual(numify(14, 'book', 'books'), '14 books') + self.assertEqual(numify(999, 'book', 'books'), '999 books') + self.assertEqual(numify(1000000, 'book', 'books'), '1,000,000 books') + self.assertEqual(numify(1956), '1956') + + def test_commify(self): + "Test that commas are added correctly" + self.assertEqual(commify(1), '1') + self.assertEqual(commify(123), '123') + self.assertEqual(commify(1234), '1234') + self.assertEqual(commify(12345), '12,345') + self.assertEqual(commify(1234567890), '1,234,567,890') + self.assertEqual(commify(123.0), '123.0') + self.assertEqual(commify(1234.5), '1234.5') + self.assertEqual(commify(1234.56789), '1234.56789') + self.assertEqual(commify(123456.789), '123,456.789') + self.assertEqual(commify(None), None) -- cgit v1.2.3 From 1a0380b9f778600f4ed0838a2dfaf4fc3d7bc768 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 23 Jul 2020 02:44:42 +0300 Subject: Inject redis instance into DatasetType class * wqflask/base/data_set.py(DatasetType): - Rename Redis instance to r to avoid confusion and name collisions - Inject the redis instance into Dataset_Types class to make it easier to test - Rename Dataset_Types class to DatasetType class --- wqflask/base/data_set.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index f9705a22..21ace006 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -49,8 +49,8 @@ import cPickle as pickle import itertools from redis import Redis -Redis = Redis() +r = Redis() logger = getLogger(__name__) @@ -74,9 +74,9 @@ def create_dataset(dataset_name, dataset_type=None, get_samplelist=True, group_n return dataset_class(dataset_name, get_samplelist) -class Dataset_Types(object): +class DatasetType: - def __init__(self): + def __init__(self, redis_instance): """Create a dictionary of samples where the value is set to Geno, Publish or ProbeSet. E.g. @@ -91,9 +91,9 @@ Publish or ProbeSet. E.g. 'B139_K_1206_R': 'ProbeSet' ... """ + self.redis_instance = redis_instance self.datasets = {} - - data = Redis.get("dataset_structure") + data = redis_instance.get("dataset_structure") if data: self.datasets = json.loads(data) else: # ZS: I don't think this should ever run unless Redis is emptied @@ -115,7 +115,7 @@ Publish or ProbeSet. E.g. except: pass - Redis.set("dataset_structure", json.dumps(self.datasets)) + redis_instance.set("dataset_structure", json.dumps(self.datasets)) # Set LOG_LEVEL_DEBUG=5 to see the following: logger.debugf(5, "datasets", self.datasets) @@ -134,7 +134,7 @@ Publish or ProbeSet. E.g. results = g.db.execute(mrna_expr_query).fetchall() if len(results): self.datasets[name] = "ProbeSet" - Redis.set("dataset_structure", json.dumps(self.datasets)) + redis_instance.set("dataset_structure", json.dumps(self.datasets)) return self.datasets[name] group_name = name.replace("Publish", "") @@ -148,7 +148,7 @@ Publish or ProbeSet. E.g. results = g.db.execute(pheno_query).fetchall() if len(results): self.datasets[name] = "Publish" - Redis.set("dataset_structure", json.dumps(self.datasets)) + redis_instance.set("dataset_structure", json.dumps(self.datasets)) return self.datasets[name] # ZS: For when there isn't an InfoFiles ID; not sure if this and the preceding query are both necessary @@ -160,7 +160,7 @@ Publish or ProbeSet. E.g. results = g.db.execute(other_pheno_query).fetchall() if len(results): self.datasets[name] = "Publish" - Redis.set("dataset_structure", json.dumps(self.datasets)) + redis_instance.set("dataset_structure", json.dumps(self.datasets)) return self.datasets[name] geno_query = """ @@ -175,7 +175,7 @@ Publish or ProbeSet. E.g. results = g.db.execute(geno_query).fetchall() if len(results): self.datasets[name] = "Geno" - Redis.set("dataset_structure", json.dumps(self.datasets)) + self.redis_instance.set("dataset_structure", json.dumps(self.datasets)) return self.datasets[name] # ZS: It shouldn't ever reach this @@ -185,13 +185,13 @@ Publish or ProbeSet. E.g. # Do the intensive work at startup one time only -Dataset_Getter = Dataset_Types() +Dataset_Getter = DatasetType(r) def create_datasets_list(): if USE_REDIS: key = "all_datasets" - result = Redis.get(key) + result = r.get(key) if result: logger.debug("Redis cache hit") @@ -215,8 +215,8 @@ def create_datasets_list(): datasets.append(dataset) if USE_REDIS: - Redis.set(key, pickle.dumps(datasets, pickle.HIGHEST_PROTOCOL)) - Redis.expire(key, 60*60) + r.set(key, pickle.dumps(datasets, pickle.HIGHEST_PROTOCOL)) + r.expire(key, 60*60) return datasets @@ -431,7 +431,7 @@ class DatasetGroup(object): result = None key = "samplelist:v3:" + self.name if USE_REDIS: - result = Redis.get(key) + result = r.get(key) if result is not None: self.samplelist = json.loads(result) @@ -446,8 +446,8 @@ class DatasetGroup(object): self.samplelist = None if USE_REDIS: - Redis.set(key, json.dumps(self.samplelist)) - Redis.expire(key, 60*5) + r.set(key, json.dumps(self.samplelist)) + r.expire(key, 60*5) def all_samples_ordered(self): result = [] @@ -558,8 +558,8 @@ def datasets(group_name, this_group=None): datasets=[(dataset, dataset_short)])) if USE_REDIS: - Redis.set(key, pickle.dumps(dataset_menu, pickle.HIGHEST_PROTOCOL)) - Redis.expire(key, 60*5) + r.set(key, pickle.dumps(dataset_menu, pickle.HIGHEST_PROTOCOL)) + r.expire(key, 60*5) if this_group != None: this_group._datasets = dataset_menu -- cgit v1.2.3 From 16aaf9885971993863b5e2e8a1b99f8479e2bbb9 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 23 Jul 2020 02:52:41 +0300 Subject: Add unittests for *base/data_set* --- wqflask/tests/base/test_data_set.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 wqflask/tests/base/test_data_set.py diff --git a/wqflask/tests/base/test_data_set.py b/wqflask/tests/base/test_data_set.py new file mode 100644 index 00000000..44a54c7e --- /dev/null +++ b/wqflask/tests/base/test_data_set.py @@ -0,0 +1,35 @@ +import unittest +import mock + +from wqflask import app + +from base.data_set import DatasetType + + +class TestDataSetTypes(unittest.TestCase): + def setUp(self): + self.app_context = app.app_context() + self.app_context.push() + + def tearDown(self): + self.app_context.pop() + + @mock.patch('base.data_set.g') + def test_data_set_type(self, db_mock): + with app.app_context(): + db_mock.get = mock.Mock() + r = mock.Mock() + r.get.return_value = """ + { + "AD-cases-controls-MyersGeno": "Geno", + "AD-cases-controls-MyersPublish": "Publish", + "AKXDGeno": "Geno", + "AXBXAGeno": "Geno", + "AXBXAPublish": "Publish", + "Aging-Brain-UCIPublish": "Publish", + "All Phenotypes": "Publish", + "B139_K_1206_M": "ProbeSet", + "B139_K_1206_R": "ProbeSet" + } + """ + self.assertEqual(DatasetType(r)("All Phenotypes"), "Publish") -- cgit v1.2.3 From a6075ce879c338532de39e100f09d92c17b566e7 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 23 Jul 2020 02:54:59 +0300 Subject: Rename test_numify.py to test_formatting.py --- wqflask/tests/utility/test_formatting.py | 33 ++++++++++++++++++++++++++++++++ wqflask/tests/utility/test_numify.py | 33 -------------------------------- 2 files changed, 33 insertions(+), 33 deletions(-) create mode 100644 wqflask/tests/utility/test_formatting.py delete mode 100644 wqflask/tests/utility/test_numify.py diff --git a/wqflask/tests/utility/test_formatting.py b/wqflask/tests/utility/test_formatting.py new file mode 100644 index 00000000..9d3033d1 --- /dev/null +++ b/wqflask/tests/utility/test_formatting.py @@ -0,0 +1,33 @@ +import unittest +from utility.formatting import numify, commify + + +class TestFormatting(unittest.TestCase): + """Test formatting numbers by numifying or commifying""" + + def test_numify(self): + "Test that a number is correctly converted to a English readable string" + self.assertEqual(numify(1, 'item', 'items'), + 'one item') + self.assertEqual(numify(2, 'book'), 'two') + self.assertEqual(numify(2, 'book', 'books'), 'two books') + self.assertEqual(numify(0, 'book', 'books'), 'zero books') + self.assertEqual(numify(0), 'zero') + self.assertEqual(numify(5), 'five') + self.assertEqual(numify(14, 'book', 'books'), '14 books') + self.assertEqual(numify(999, 'book', 'books'), '999 books') + self.assertEqual(numify(1000000, 'book', 'books'), '1,000,000 books') + self.assertEqual(numify(1956), '1956') + + def test_commify(self): + "Test that commas are added correctly" + self.assertEqual(commify(1), '1') + self.assertEqual(commify(123), '123') + self.assertEqual(commify(1234), '1234') + self.assertEqual(commify(12345), '12,345') + self.assertEqual(commify(1234567890), '1,234,567,890') + self.assertEqual(commify(123.0), '123.0') + self.assertEqual(commify(1234.5), '1234.5') + self.assertEqual(commify(1234.56789), '1234.56789') + self.assertEqual(commify(123456.789), '123,456.789') + self.assertEqual(commify(None), None) diff --git a/wqflask/tests/utility/test_numify.py b/wqflask/tests/utility/test_numify.py deleted file mode 100644 index 9d3033d1..00000000 --- a/wqflask/tests/utility/test_numify.py +++ /dev/null @@ -1,33 +0,0 @@ -import unittest -from utility.formatting import numify, commify - - -class TestFormatting(unittest.TestCase): - """Test formatting numbers by numifying or commifying""" - - def test_numify(self): - "Test that a number is correctly converted to a English readable string" - self.assertEqual(numify(1, 'item', 'items'), - 'one item') - self.assertEqual(numify(2, 'book'), 'two') - self.assertEqual(numify(2, 'book', 'books'), 'two books') - self.assertEqual(numify(0, 'book', 'books'), 'zero books') - self.assertEqual(numify(0), 'zero') - self.assertEqual(numify(5), 'five') - self.assertEqual(numify(14, 'book', 'books'), '14 books') - self.assertEqual(numify(999, 'book', 'books'), '999 books') - self.assertEqual(numify(1000000, 'book', 'books'), '1,000,000 books') - self.assertEqual(numify(1956), '1956') - - def test_commify(self): - "Test that commas are added correctly" - self.assertEqual(commify(1), '1') - self.assertEqual(commify(123), '123') - self.assertEqual(commify(1234), '1234') - self.assertEqual(commify(12345), '12,345') - self.assertEqual(commify(1234567890), '1,234,567,890') - self.assertEqual(commify(123.0), '123.0') - self.assertEqual(commify(1234.5), '1234.5') - self.assertEqual(commify(1234.56789), '1234.56789') - self.assertEqual(commify(123456.789), '123,456.789') - self.assertEqual(commify(None), None) -- cgit v1.2.3 From 12d19ea8ca44ad1b8b483bc0a11f28c92a75de00 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 24 Jul 2020 01:09:11 +0300 Subject: Add basic config for coverage tool * .coveragerc: add it --- .coveragerc | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..8fbf85ce --- /dev/null +++ b/.coveragerc @@ -0,0 +1,28 @@ +[run] +branch = True +omit = + */site-packages/* + tests/* + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = +# Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + +ignore_errors = False + +[html] +directory = coverage_html_report -- cgit v1.2.3 From 6fcac789a4664903b8588cfa1bb321fdb081ec05 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 24 Jul 2020 01:12:33 +0300 Subject: Add generated coverage reports and binary to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 701623e7..8183b308 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ dist/ EGG-INFO/ wqflask/output/* wqflask/wqflask/static/output/* +wqflask/.coverage +wqflask/coverage_html_report \ No newline at end of file -- cgit v1.2.3 From a9282de4f07f37f120b165ff2650468e014f1984 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 24 Jul 2020 01:40:31 +0300 Subject: Move .coveragerc to wqflask/ * .coveragerc: Move .coveragerc to wqflask so that the coverage test tool can pick it up automatically without having to add an `--rc-file` flag --- .coveragerc | 28 ---------------------------- wqflask/.coveragerc | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) delete mode 100644 .coveragerc create mode 100644 wqflask/.coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 8fbf85ce..00000000 --- a/.coveragerc +++ /dev/null @@ -1,28 +0,0 @@ -[run] -branch = True -omit = - */site-packages/* - tests/* - -[report] -# Regexes for lines to exclude from consideration -exclude_lines = -# Have to re-enable the standard pragma - pragma: no cover - - # Don't complain about missing debug-only code: - def __repr__ - if self\.debug - - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - - # Don't complain if non-runnable code isn't run: - if 0: - if __name__ == .__main__.: - -ignore_errors = False - -[html] -directory = coverage_html_report diff --git a/wqflask/.coveragerc b/wqflask/.coveragerc new file mode 100644 index 00000000..939e51b9 --- /dev/null +++ b/wqflask/.coveragerc @@ -0,0 +1,28 @@ +[run] +branch = True + +[report] +omit = + */site-packages/* + tests/* +# Regexes for lines to exclude from consideration +exclude_lines = +# Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + +ignore_errors = False + +[html] +directory = coverage_html_report \ No newline at end of file -- cgit v1.2.3 From f95a42b0a9445a58e68fc83e9b1411bedef67904 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 24 Jul 2020 01:43:26 +0300 Subject: Add more tests for GeneralObject * wqflask/tests/base/test_general_object.py: test object's magic methods --- wqflask/tests/base/test_general_object.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wqflask/tests/base/test_general_object.py b/wqflask/tests/base/test_general_object.py index 699cb079..df5791e0 100644 --- a/wqflask/tests/base/test_general_object.py +++ b/wqflask/tests/base/test_general_object.py @@ -12,6 +12,7 @@ class TestGeneralObjectTests(unittest.TestCase): """Test whether base contents are stored properly""" test_obj = GeneralObject("a", "b", "c") self.assertEqual("abc", ''.join(test_obj.contents)) + self.assertEqual(len(test_obj), 0) def test_object_dict(self): """Test whether the base class is printed properly""" @@ -19,3 +20,8 @@ class TestGeneralObjectTests(unittest.TestCase): self.assertEqual(str(test_obj), "value = 1\nname = test\n") self.assertEqual( repr(test_obj), "value = 1\nname = test\ncontents = ['a']\n") + self.assertEqual(len(test_obj), 2) + self.assertEqual(getattr(test_obj, "value"), 1) + self.assertEqual(test_obj["value"], 1) + test_obj["test"] = 1 + self.assertEqual(test_obj["test"], 1) -- cgit v1.2.3 From 09bc3137328fbefe41044b5124f3c6a7abaa8982 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 24 Jul 2020 02:14:50 +0300 Subject: Add more tests for general_object * wqflask/tests/base/test_general_object.py: test getattr() and `==` --- wqflask/tests/base/test_general_object.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/wqflask/tests/base/test_general_object.py b/wqflask/tests/base/test_general_object.py index df5791e0..c7701021 100644 --- a/wqflask/tests/base/test_general_object.py +++ b/wqflask/tests/base/test_general_object.py @@ -21,7 +21,21 @@ class TestGeneralObjectTests(unittest.TestCase): self.assertEqual( repr(test_obj), "value = 1\nname = test\ncontents = ['a']\n") self.assertEqual(len(test_obj), 2) - self.assertEqual(getattr(test_obj, "value"), 1) self.assertEqual(test_obj["value"], 1) test_obj["test"] = 1 self.assertEqual(test_obj["test"], 1) + + def test_get_attribute(self): + "Test that getattr works" + test_obj = GeneralObject("a", name="test", value=1) + self.assertEqual(getattr(test_obj, "value", None), 1) + self.assertEqual(getattr(test_obj, "non-existent", None), None) + + def test_object_comparisons(self): + "Test that 2 objects of the same length are equal" + test_obj1 = GeneralObject("a", name="test", value=1) + test_obj2 = GeneralObject("b", name="test2", value=2) + test_obj3 = GeneralObject("a", name="test", x=1, y=2) + self.assertTrue(test_obj1 == test_obj2 ) + self.assertFalse(test_obj1 == test_obj3 ) + -- cgit v1.2.3 From ef62b2c146bb1c8960a3ee700c544d61519998b2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 24 Jul 2020 02:15:59 +0300 Subject: Remove unreachable conditional * wqflask/base/GeneralObject.py(__getattr__): remove if statement that is unreachable --- wqflask/base/GeneralObject.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wqflask/base/GeneralObject.py b/wqflask/base/GeneralObject.py index 37bfeda3..0fccaab3 100644 --- a/wqflask/base/GeneralObject.py +++ b/wqflask/base/GeneralObject.py @@ -43,10 +43,7 @@ class GeneralObject: return getattr(self, key) def __getattr__(self, key): - if key in self.__dict__.keys(): - return self.__dict__[key] - else: - return eval("self.__dict__.%s" % key) + return eval("self.__dict__.%s" % key) def __len__(self): return len(self.__dict__) - 1 -- cgit v1.2.3 From 8e3756b8b8094c5d025da31c54c1d0d95a55b0dc Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 17 Jul 2020 02:47:31 +0300 Subject: Add basic unittests --- test/unittest/__init__.py | 0 test/unittest/base/__init__.py | 0 test/unittest/base/test_general_object.py | 21 +++++++++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 test/unittest/__init__.py create mode 100644 test/unittest/base/__init__.py create mode 100644 test/unittest/base/test_general_object.py diff --git a/test/unittest/__init__.py b/test/unittest/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/unittest/base/__init__.py b/test/unittest/base/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/unittest/base/test_general_object.py b/test/unittest/base/test_general_object.py new file mode 100644 index 00000000..eaefdec9 --- /dev/null +++ b/test/unittest/base/test_general_object.py @@ -0,0 +1,21 @@ +import unittest + +from wqflask.base.GeneralObject import GeneralObject + + +class TestGeneralObjectTests(unittest.TestCase): + """ + Test the GeneralObject base class + """ + + def test_object_contents(self): + """Test whether base contents are stored properly""" + test_obj = GeneralObject("a", "b", "c") + self.assertEqual("abc", ''.join(test_obj.contents)) + + def test_object_dict(self): + """Test whether the base class is printed properly""" + test_obj = GeneralObject("a", name="test", value=1) + self.assertEqual(str(test_obj), "value = 1\nname = test\n") + self.assertEqual( + repr(test_obj), "value = 1\nname = test\ncontents = ['a']\n") -- cgit v1.2.3 From b8e17aee9000943e0fd379b5ef006d76314733e4 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 12:36:06 +0300 Subject: Add work-around for failed imports in unittest * wqflask/utility/tools.py: Unittests will use `from wqflask.wqflask import app` and the gn2 script will use `from wqflask import app` --- wqflask/utility/tools.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py index 77db5d53..37f9d8fe 100644 --- a/wqflask/utility/tools.py +++ b/wqflask/utility/tools.py @@ -5,7 +5,10 @@ import os import sys import json -from wqflask import app +try: + from wqflask import app +except ImportError: + from wqflask.wqflask import app # Use the standard logger here to avoid a circular dependency import logging -- cgit v1.2.3 From 98035fccfd7960e6992e1e47afc32b56d54ff074 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Jul 2020 21:53:30 +0300 Subject: Revert "Add work-around for failed imports in unittest" This reverts commit d5e87fa6fe7546b46790f512d984a5501223082f. --- wqflask/utility/tools.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py index 37f9d8fe..77db5d53 100644 --- a/wqflask/utility/tools.py +++ b/wqflask/utility/tools.py @@ -5,10 +5,7 @@ import os import sys import json -try: - from wqflask import app -except ImportError: - from wqflask.wqflask import app +from wqflask import app # Use the standard logger here to avoid a circular dependency import logging -- cgit v1.2.3 From 70a8b445df32b7ed15612ffa745269959eb9159b Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Jul 2020 17:22:14 +0300 Subject: Add unittests for *utility/test_numify* --- wqflask/tests/utility/test_numify.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 wqflask/tests/utility/test_numify.py diff --git a/wqflask/tests/utility/test_numify.py b/wqflask/tests/utility/test_numify.py new file mode 100644 index 00000000..9d3033d1 --- /dev/null +++ b/wqflask/tests/utility/test_numify.py @@ -0,0 +1,33 @@ +import unittest +from utility.formatting import numify, commify + + +class TestFormatting(unittest.TestCase): + """Test formatting numbers by numifying or commifying""" + + def test_numify(self): + "Test that a number is correctly converted to a English readable string" + self.assertEqual(numify(1, 'item', 'items'), + 'one item') + self.assertEqual(numify(2, 'book'), 'two') + self.assertEqual(numify(2, 'book', 'books'), 'two books') + self.assertEqual(numify(0, 'book', 'books'), 'zero books') + self.assertEqual(numify(0), 'zero') + self.assertEqual(numify(5), 'five') + self.assertEqual(numify(14, 'book', 'books'), '14 books') + self.assertEqual(numify(999, 'book', 'books'), '999 books') + self.assertEqual(numify(1000000, 'book', 'books'), '1,000,000 books') + self.assertEqual(numify(1956), '1956') + + def test_commify(self): + "Test that commas are added correctly" + self.assertEqual(commify(1), '1') + self.assertEqual(commify(123), '123') + self.assertEqual(commify(1234), '1234') + self.assertEqual(commify(12345), '12,345') + self.assertEqual(commify(1234567890), '1,234,567,890') + self.assertEqual(commify(123.0), '123.0') + self.assertEqual(commify(1234.5), '1234.5') + self.assertEqual(commify(1234.56789), '1234.56789') + self.assertEqual(commify(123456.789), '123,456.789') + self.assertEqual(commify(None), None) -- cgit v1.2.3 From bcf98cc6c1f0208cc8f9a21d36196627e1d6e6b6 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 23 Jul 2020 02:54:59 +0300 Subject: Rename test_numify.py to test_formatting.py --- wqflask/tests/utility/test_numify.py | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 wqflask/tests/utility/test_numify.py diff --git a/wqflask/tests/utility/test_numify.py b/wqflask/tests/utility/test_numify.py deleted file mode 100644 index 9d3033d1..00000000 --- a/wqflask/tests/utility/test_numify.py +++ /dev/null @@ -1,33 +0,0 @@ -import unittest -from utility.formatting import numify, commify - - -class TestFormatting(unittest.TestCase): - """Test formatting numbers by numifying or commifying""" - - def test_numify(self): - "Test that a number is correctly converted to a English readable string" - self.assertEqual(numify(1, 'item', 'items'), - 'one item') - self.assertEqual(numify(2, 'book'), 'two') - self.assertEqual(numify(2, 'book', 'books'), 'two books') - self.assertEqual(numify(0, 'book', 'books'), 'zero books') - self.assertEqual(numify(0), 'zero') - self.assertEqual(numify(5), 'five') - self.assertEqual(numify(14, 'book', 'books'), '14 books') - self.assertEqual(numify(999, 'book', 'books'), '999 books') - self.assertEqual(numify(1000000, 'book', 'books'), '1,000,000 books') - self.assertEqual(numify(1956), '1956') - - def test_commify(self): - "Test that commas are added correctly" - self.assertEqual(commify(1), '1') - self.assertEqual(commify(123), '123') - self.assertEqual(commify(1234), '1234') - self.assertEqual(commify(12345), '12,345') - self.assertEqual(commify(1234567890), '1,234,567,890') - self.assertEqual(commify(123.0), '123.0') - self.assertEqual(commify(1234.5), '1234.5') - self.assertEqual(commify(1234.56789), '1234.56789') - self.assertEqual(commify(123456.789), '123,456.789') - self.assertEqual(commify(None), None) -- cgit v1.2.3 From 54956208cad93565e555431845c1e0eb77e66a21 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 24 Jul 2020 01:09:11 +0300 Subject: Add basic config for coverage tool * .coveragerc: add it --- .coveragerc | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..8fbf85ce --- /dev/null +++ b/.coveragerc @@ -0,0 +1,28 @@ +[run] +branch = True +omit = + */site-packages/* + tests/* + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = +# Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + +ignore_errors = False + +[html] +directory = coverage_html_report -- cgit v1.2.3 From ea8782da5c43e695ba11ff348aeb45b24db07010 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 24 Jul 2020 01:40:31 +0300 Subject: Move .coveragerc to wqflask/ * .coveragerc: Move .coveragerc to wqflask so that the coverage test tool can pick it up automatically without having to add an `--rc-file` flag --- .coveragerc | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 8fbf85ce..00000000 --- a/.coveragerc +++ /dev/null @@ -1,28 +0,0 @@ -[run] -branch = True -omit = - */site-packages/* - tests/* - -[report] -# Regexes for lines to exclude from consideration -exclude_lines = -# Have to re-enable the standard pragma - pragma: no cover - - # Don't complain about missing debug-only code: - def __repr__ - if self\.debug - - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - - # Don't complain if non-runnable code isn't run: - if 0: - if __name__ == .__main__.: - -ignore_errors = False - -[html] -directory = coverage_html_report -- cgit v1.2.3 From e8ca99cc7a8e3fd9af0c477da423b60cc1eb654c Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 24 Jul 2020 15:18:44 -0500 Subject: Fixed queries that were wrongly returning Data IDs as Ns --- wqflask/base/data_set.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 2272b6ee..e9deb8a9 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -939,7 +939,7 @@ class GenotypeDataSet(DataSet): def retrieve_sample_data(self, trait): query = """ SELECT - Strain.Name, GenoData.value, GenoSE.error, GenoData.Id, Strain.Name2 + Strain.Name, GenoData.value, GenoSE.error, "N/A", Strain.Name2 FROM (GenoData, GenoFreeze, Strain, Geno, GenoXRef) left join GenoSE on @@ -1107,11 +1107,14 @@ class MrnaAssayDataSet(DataSet): def retrieve_sample_data(self, trait): query = """ SELECT - Strain.Name, ProbeSetData.value, ProbeSetSE.error, ProbeSetData.Id, Strain.Name2 + Strain.Name, ProbeSetData.value, ProbeSetSE.error, NStrain.count, Strain.Name2 FROM (ProbeSetData, ProbeSetFreeze, Strain, ProbeSet, ProbeSetXRef) left join ProbeSetSE on (ProbeSetSE.DataId = ProbeSetData.Id AND ProbeSetSE.StrainId = ProbeSetData.StrainId) + left join NStrain on + (NStrain.DataId = ProbeSetData.Id AND + NStrain.StrainId = ProbeSetData.StrainId) WHERE ProbeSet.Name = '%s' AND ProbeSetXRef.ProbeSetId = ProbeSet.Id AND ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id AND -- cgit v1.2.3 From 747eb82d0260c7bf9340e8402b78416373692b0a Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 24 Jul 2020 15:20:21 -0500 Subject: Added to menu option for user to change some basic user information, like name/organization --- wqflask/wqflask/templates/base.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html index 262d9ee5..5b9700b5 100644 --- a/wqflask/wqflask/templates/base.html +++ b/wqflask/wqflask/templates/base.html @@ -96,7 +96,11 @@ {% if g.user_session.logged_in %}
  • - Manage Groups + +
  • {% endif %} {% endif %} -- cgit v1.2.3 From a478d8171e075f68e5d9d970d67fa8e508b18628 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 24 Jul 2020 15:22:06 -0500 Subject: Minor aesthetic indentation change in html --- wqflask/wqflask/templates/show_trait.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wqflask/wqflask/templates/show_trait.html b/wqflask/wqflask/templates/show_trait.html index 7380d198..c37a41e2 100644 --- a/wqflask/wqflask/templates/show_trait.html +++ b/wqflask/wqflask/templates/show_trait.html @@ -21,8 +21,7 @@ {% endif %}
    - +
    {% for key in hddn %} -- cgit v1.2.3 From 60d8968c4fc2ca15fc9cf2f63c0b34a25f6fb053 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 24 Jul 2020 16:56:25 -0500 Subject: Added missing JS library that caused adding markers to collections to not work --- wqflask/wqflask/templates/mapping_results.html | 1 + 1 file changed, 1 insertion(+) diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html index 132d5249..81803deb 100644 --- a/wqflask/wqflask/templates/mapping_results.html +++ b/wqflask/wqflask/templates/mapping_results.html @@ -333,6 +333,7 @@ {% block js %} + -- cgit v1.2.3 From 5eb26c5a209f3a3c54cf6fe623e5372188bdd1bc Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sat, 25 Jul 2020 00:59:46 +0300 Subject: Update import statement in test_general_object.py * test/unittest/base/test_general_object.py: replace the import statement to use the module path instead of the path referenced from PYTHONPATH --- test/unittest/base/test_general_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unittest/base/test_general_object.py b/test/unittest/base/test_general_object.py index eaefdec9..699cb079 100644 --- a/test/unittest/base/test_general_object.py +++ b/test/unittest/base/test_general_object.py @@ -1,6 +1,6 @@ import unittest -from wqflask.base.GeneralObject import GeneralObject +from base.GeneralObject import GeneralObject class TestGeneralObjectTests(unittest.TestCase): -- cgit v1.2.3 From 9b1dd2f67d51de2d6cdb687ee332597ba010a0ac Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Sat, 25 Jul 2020 11:54:10 +0100 Subject: Removing bimbam check. We'll get rid of bimbam anyway. --- wqflask/base/webqtlConfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/base/webqtlConfig.py b/wqflask/base/webqtlConfig.py index 3d86bc22..862ac881 100644 --- a/wqflask/base/webqtlConfig.py +++ b/wqflask/base/webqtlConfig.py @@ -85,7 +85,7 @@ assert_writable_dir(GENERATED_TEXT_DIR) # Flat file directories GENODIR = flat_files('genotype')+'/' assert_dir(GENODIR) -assert_dir(GENODIR+'bimbam') # for gemma +# assert_dir(GENODIR+'bimbam') # for gemma # JSON genotypes are OBSOLETE JSON_GENODIR = flat_files('genotype/json')+'/' -- cgit v1.2.3 From 869e3a2d7f1ef0a44cc90612a46b02d7b01bab88 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Sat, 25 Jul 2020 13:35:02 +0100 Subject: Animated gifs --- wqflask/wqflask/static/gif/error/m001.gif | Bin 0 -> 273531 bytes wqflask/wqflask/static/gif/error/m002.gif | Bin 0 -> 1799777 bytes wqflask/wqflask/static/gif/error/m003.gif | Bin 0 -> 2238947 bytes wqflask/wqflask/static/gif/error/m004.gif | Bin 0 -> 2090274 bytes wqflask/wqflask/static/gif/error/m005.gif | Bin 0 -> 947565 bytes wqflask/wqflask/static/gif/error/m006.gif | Bin 0 -> 121116 bytes wqflask/wqflask/static/gif/error/m007.gif | Bin 0 -> 41670 bytes wqflask/wqflask/static/gif/error/m008.gif | Bin 0 -> 732023 bytes wqflask/wqflask/static/gif/error/mouse-wheel.gif | Bin 0 -> 2001764 bytes 9 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 wqflask/wqflask/static/gif/error/m001.gif create mode 100644 wqflask/wqflask/static/gif/error/m002.gif create mode 100644 wqflask/wqflask/static/gif/error/m003.gif create mode 100644 wqflask/wqflask/static/gif/error/m004.gif create mode 100644 wqflask/wqflask/static/gif/error/m005.gif create mode 100644 wqflask/wqflask/static/gif/error/m006.gif create mode 100644 wqflask/wqflask/static/gif/error/m007.gif create mode 100644 wqflask/wqflask/static/gif/error/m008.gif create mode 100644 wqflask/wqflask/static/gif/error/mouse-wheel.gif diff --git a/wqflask/wqflask/static/gif/error/m001.gif b/wqflask/wqflask/static/gif/error/m001.gif new file mode 100644 index 00000000..81c8ba26 Binary files /dev/null and b/wqflask/wqflask/static/gif/error/m001.gif differ diff --git a/wqflask/wqflask/static/gif/error/m002.gif b/wqflask/wqflask/static/gif/error/m002.gif new file mode 100644 index 00000000..7232c58b Binary files /dev/null and b/wqflask/wqflask/static/gif/error/m002.gif differ diff --git a/wqflask/wqflask/static/gif/error/m003.gif b/wqflask/wqflask/static/gif/error/m003.gif new file mode 100644 index 00000000..2384ceb6 Binary files /dev/null and b/wqflask/wqflask/static/gif/error/m003.gif differ diff --git a/wqflask/wqflask/static/gif/error/m004.gif b/wqflask/wqflask/static/gif/error/m004.gif new file mode 100644 index 00000000..d77c708d Binary files /dev/null and b/wqflask/wqflask/static/gif/error/m004.gif differ diff --git a/wqflask/wqflask/static/gif/error/m005.gif b/wqflask/wqflask/static/gif/error/m005.gif new file mode 100644 index 00000000..1b2de7ec Binary files /dev/null and b/wqflask/wqflask/static/gif/error/m005.gif differ diff --git a/wqflask/wqflask/static/gif/error/m006.gif b/wqflask/wqflask/static/gif/error/m006.gif new file mode 100644 index 00000000..f354a4cc Binary files /dev/null and b/wqflask/wqflask/static/gif/error/m006.gif differ diff --git a/wqflask/wqflask/static/gif/error/m007.gif b/wqflask/wqflask/static/gif/error/m007.gif new file mode 100644 index 00000000..ba2eeb37 Binary files /dev/null and b/wqflask/wqflask/static/gif/error/m007.gif differ diff --git a/wqflask/wqflask/static/gif/error/m008.gif b/wqflask/wqflask/static/gif/error/m008.gif new file mode 100644 index 00000000..4cdec5cb Binary files /dev/null and b/wqflask/wqflask/static/gif/error/m008.gif differ diff --git a/wqflask/wqflask/static/gif/error/mouse-wheel.gif b/wqflask/wqflask/static/gif/error/mouse-wheel.gif new file mode 100644 index 00000000..164220e7 Binary files /dev/null and b/wqflask/wqflask/static/gif/error/mouse-wheel.gif differ -- cgit v1.2.3 From 91e52482071ce90acf3607597de2ae79e298fd67 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Sat, 25 Jul 2020 16:25:14 +0100 Subject: The variable is never defined, so let's default to "none" --- wqflask/utility/authentication_tools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index ed7462d1..9a2a5ccd 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -45,7 +45,7 @@ def check_resource_availability(dataset, trait_id=None): def add_new_resource(dataset, trait_id=None): resource_ob = { - 'owner_id' : webqtlConfig.DEFAULT_OWNER_ID, + 'owner_id' : "none", # webqtlConfig.DEFAULT_OWNER_ID, 'default_mask': webqtlConfig.DEFAULT_PRIVILEGES, 'group_masks' : {} } @@ -84,6 +84,7 @@ def check_admin(resource_id=None): try: response = json.loads(requests.get(the_url).content)['admin'] except: + logger.debug(resource_info) response = resource_info['default_mask']['admin'] if 'edit-admins' in response: @@ -124,4 +125,4 @@ def check_owner_or_admin(dataset=None, trait_id=None, resource_id=None): else: return check_admin(resource_id) - return "not-admin" \ No newline at end of file + return "not-admin" -- cgit v1.2.3 From 918dcce5f42fcbe9327eb30bac06a69000a1fc8b Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Sat, 25 Jul 2020 16:47:24 +0100 Subject: Updating docs and show how to include a local Python module --- README.md | 21 +-- bin/genenetwork2 | 4 +- doc/README.org | 482 ++++++---------------------------------------------- doc/development.org | 55 ++++++ 4 files changed, 114 insertions(+), 448 deletions(-) diff --git a/README.md b/README.md index 47519118..a2a4625a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ developers). See the [installation docs](doc/README.org). ## Run -Once installed GN2 can be run online through a browser interface +Once having installed GN2 it can be run through a browser +interface ```sh genenetwork2 @@ -35,18 +36,12 @@ For full examples (you may need to set a number of environment variables), including running scripts and a Python REPL, also see the startup script [./bin/genenetwork2](https://github.com/genenetwork/genenetwork2/blob/testing/bin/genenetwork2). -Also Mysql and Elasticsearch need to be running, see +Also mariadb and redis need to be running, see [INSTALL](./doc/README.org). ## Testing -We are building 'Mechanical Rob' automated testing using Python -[requests](https://github.com/genenetwork/genenetwork2/tree/master/test/lib) -which can be run with something like - -```sh -env GN2_PROFILE=~/opt/gn-latest ./bin/genenetwork2 ./etc/default_settings.py -c ../test/requests/test-website.py -a http://localhost:5003 -``` +We are building 'Mechanical Rob' automated testing using Python. The GN2_PROFILE is the Guix profile that contains all dependencies. The ./bin/genenetwork2 script sets up the environment @@ -73,10 +68,10 @@ Contribute to GN2 source code by forking the [github repository](https://github.com/genenetwork/genenetwork2/) with git and sending us pull requests. -For development GN2 has a -[mailing list](http://listserv.uthsc.edu/mailman/listinfo/genenetwork-dev) -and an active IRC channel #genenetwork on freenode.net with a -[web interface](http://webchat.freenode.net/). +For development GN2 has a [mailing +list](http://listserv.uthsc.edu/mailman/listinfo/genenetwork-dev) and +an active IRC channel #genenetwork on freenode.net with a [web +interface](http://webchat.freenode.net/). ## License diff --git a/bin/genenetwork2 b/bin/genenetwork2 index 3ae08e0a..dd6db7d6 100755 --- a/bin/genenetwork2 +++ b/bin/genenetwork2 @@ -121,7 +121,7 @@ if [ -z $GN2_PROFILE ]; then read -p "PRESS [ENTER] TO CONTINUE..." else export PATH=$GN2_PROFILE/bin:$PATH - export PYTHONPATH="$GN2_PROFILE/lib/python2.7/site-packages" # never inject another PYTHONPATH!! + export PYTHONPATH="$PYTHON_GN_PATH:$GN2_PROFILE/lib/python2.7/site-packages" # never inject another PYTHONPATH!! export R_LIBS_SITE=$GN2_PROFILE/site-library export JS_GUIX_PATH=$GN2_PROFILE/share/genenetwork2/javascript export GUIX_GTK3_PATH="$GN2_PROFILE/lib/gtk-3.0" @@ -154,7 +154,7 @@ if [ ! -d $R_LIBS_SITE ] ; then fi # We may change this one: -export PYTHONPATH=$GN2_BASE_DIR/wqflask:$PYTHONPATH +export PYTHONPATH=$PYTHON_GN_PATH:$GN2_BASE_DIR/wqflask:$PYTHONPATH # Our UNIX TMPDIR defaults to /tmp - change this on a shared server if [ -z $TMPDIR ]; then diff --git a/doc/README.org b/doc/README.org index c1b1c2b6..46df03c7 100644 --- a/doc/README.org +++ b/doc/README.org @@ -4,9 +4,12 @@ - [[#introduction][Introduction]] - [[#install][Install]] - [[#running-gn2][Running GN2]] + - [[#run-gn-proxy][Run gn-proxy]] + - [[#run-redis][Run Redis]] - [[#run-mariadb-server][Run MariaDB server]] - [[#install-mariadb-with-gnu-guix][Install MariaDB with GNU GUIx]] - [[#load-the-small-database-in-mysql][Load the small database in MySQL]] + - [[#get-genotype-files][Get genotype files]] - [[#gn2-dependency-graph][GN2 Dependency Graph]] - [[#working-with-the-gn2-source-code][Working with the GN2 source code]] - [[#read-more][Read more]] @@ -16,7 +19,6 @@ - [[#cant-run-a-module][Can't run a module]] - [[#rpy2-error-show-now-found][Rpy2 error 'show' now found]] - [[#mysql-cant-connect-server-through-socket-error][Mysql can't connect server through socket ERROR]] - - [[#irc-session][IRC session]] - [[#notes][NOTES]] - [[#deploying-gn2-official][Deploying GN2 official]] @@ -57,28 +59,39 @@ or you can set environment variables to override individual parameters, e.g. the debug and logging switches can be particularly useful when developing GN2. -* Running Redis +* Run gn-proxy -Install redis. Make sure you add the setting: +GeneNetwork requires a separate gn-proxy server which handles +authorisation and access control. For instructions see the [[https://github.com/genenetwork/gn-proxy][README]]. -: appendonly yes +* Run Redis + +Redis part of GN2 deployment and will be started by the ./bin/genenetwork2 +startup script. * Run MariaDB server ** Install MariaDB with GNU GUIx -/Note: we moved to MariaDB/ - These are the steps you can take to install a fresh installation of mariadb (which comes as part of the GNU Guix genenetwork2 install). -As root configure and run +As root configure the Guix profile + +: . ~/opt/genenetwork2/etc/profile + +and run for example #+BEGIN_SRC bash adduser mariadb && addgroup mariadb -mysqld --datadir=/home/mariadb/database --initialize-insecure -mkdir -p /var/run/mariadbd -chown mariadb.mariadb /var/run/mariadbd -mysqld -u mariadb --datadir=/home/mariadb/database/mariadb --explicit_defaults_for_timestamp -P 12048" +mkdir -p /export2/mariadb/database +chown mariadb.mariadb -R /export2/mariadb/ +mkdir -p /var/run/mysqld +chown mariadb.mariadb /var/run/mysqld +su mariadb +mysql --version + mysql Ver 15.1 Distrib 10.1.45-MariaDB, for Linux (x86_64) using readline 5.1 +mysql_install_db --user=mariadb --datadir=/export2/mariadb/database +mysqld -u mariadb --datadir=/exportdb/mariadb/database/mariadb --explicit_defaults_for_timestamp -P 12048" #+END_SRC If you want to run as root you may have to set @@ -120,21 +133,22 @@ material. Download one database from -[[http://files.genenetwork.org/raw_database/]] +http://ipfs.genenetwork.org/ipfs/QmRUmYu6ogxEdzZeE8PuXMGCDa8M3y2uFcfo4zqQRbpxtk -[[https://s3.amazonaws.com/genenetwork2/db_webqtl_s.zip]] +After installation unzip the database binary in the MySQL directory -Check the md5sum. - -After installation inflate the database binary in the MySQL directory - -: cd ~/mysql -: chown -R mysql:mysql db_webqtl_s/ -: chmod 700 db_webqtl_s/ -: chmod 660 db_webqtl_s/* +#+BEGIN_SRC sh +cd ~/mysql +p7zip -d db_webqtl_s.7z +chown -R mysql:mysql db_webqtl_s/ +chmod 700 db_webqtl_s/ +chmod 660 db_webqtl_s/* +#+END_SRC restart MySQL service (mysqld). Login as root +: mysql_upgrade -u root --force + : myslq -u root and @@ -151,7 +165,7 @@ and Set permissions and match password in your settings file below: -: mysql> grant all privileges on db_webqtl_s.* to gn2@"localhost" identified by 'mysql_password'; +: mysql> grant all privileges on db_webqtl_s.* to gn2@"localhost" identified by 'webqtl'; You may need to change "localhost" to whatever domain you are connecting from (mysql will give an error). @@ -163,6 +177,17 @@ configuration (see below). Note for the plant database you can rename it to db_webqtl_s, or change the settings in etc/default_settings.py to match your path. +* Get genotype files + +The script looks for genotype files. You can find them in +http://ipfs.genenetwork.org/ipfs/QmXQy3DAUWJuYxubLHLkPMNCEVq1oV7844xWG2d1GSPFPL + +#+BEGIN_SRC sh +mkdir -p $HOME/genotype_files +cd $HOME/genotype_files + +#+END_SRC + * GN2 Dependency Graph Graph of all runtime dependencies as installed by GNU Guix. @@ -249,415 +274,6 @@ if that works run genenetwork after setting SQL_URI to something like : export SQL_URI=mysql://gn2:mysql_password@127.0.0.1/db_webqtl_s - -* IRC session - -Here an IRC session where we installed GN2 from scratch using GNU Guix -and a download of the test database. - -#+begin_src - time to get binary install sorted :) [07:03] - Guix is designed for distributed installation servers - we have one on guix.genenetwork.org - it contains all the prebuild packages - for GN - okay [07:04] - let's step back however [07:05] - I presume the environment is set with all guix package --search-paths - right? - yep - set to the ones in ~/.guix-profile/ - good, and you are in gn-deploy-guix repo [07:06] - yep [07:07] - git log shows - -Author: David Thompson -Date: Sun Mar 27 21:20:19 2016 -0400 - - yes - env GUIX_PACKAGE_PATH=../guix-bioinformatics ./pre-inst-env guix - package -A genenetwork2 [07:08] - shows - -genenetwork2 2.0-a8fcff4 out ../guix-bioinformatics/gn/packages/genenetwork.scm:144:2 -genenetwork2-database-small 1.0 out ../guix-bioinformatics/gn/packages/genenetwork.scm:270:4 -genenetwork2-files-small 1.0 out ../guix-bioinformatics/gn/packages/genenetwork.scm:228:4 - - yeah [07:09] - OK, we are in sync. This means we should be able to install the exact - same software - I need to start up my guix daemon - I usually run it in a screen - screen -S guix-daemon - hah, I don't have screen installed yet [07:11] - comes with guix ;) [07:12] - no worries, you can run it any way you want - $HOME/.guix-profile/bin/guix-daemon --build-users-group=guixbuild - then something's weird, because it says I don't have it - oh, you need to install it first [07:13] - guix package -A screen - screen 4.3.1 out gnu/packages/screen.scm:34:2 - but you can skip this install, for now - alright [07:14] - env GUIX_PACKAGE_PATH=../guix-bioinformatics ./pre-inst-env guix - package -i genenetwork2 --dry-run - substitute: updating list of substitutes from - 'https://mirror.hydra.gnu.org'... 79.1% - you see that? - followed by [07:15] -substitute: updating list of substitutes from -'https://hydra.gnu.org'... 100.0% -The following derivations would be built: - /gnu/store/rk7nw0rjqqsha958m649wrykadx6mmhl-profile.drv - -/gnu/store/7b0qjybvfx8syzvfs7p5rdablwhbkbvs-module-import-compiled.drv - /gnu/store/cy9zahbbf23d3cqyy404lk9f50z192kp-module-import.drv - /gnu/store/ibdn603i8grf0jziy5gjsly34wx82lmk-gtk-icon-themes.drv - - which should have the same HASH values /gnu/store/7b0qjybvf... etc. - [07:16] - profile has a different hash - but the next ones? - they're the same - not sure why profile differs. Do you see the contact with - mirror.hydra.org? [07:17] - yeah - OK, that means you set the key correctly for that one :) - alright we are at the same state now. You can see most packages need - to be rebuild because they are no longer cached as binaries on hydra - [07:18] - things move fast... - hehe - let me also do the same on my laptop - which I have staged before - [07:19] - btw, to set the path I often do [07:20] - export - PATH="/home/wrk/.guix-profile/bin:/home/wrk/.guix-profile/sbin":$PATH - to keep things like 'screen' from Debian - Once past building guix itself that is normally OK [07:21] - ah, okay - will do that - the guix build requires certain versions of tools, so you don't want - to mix foreign tools in [07:23] - makes sense [07:24] - On my laptop I am trying the main updating list of substitutes from - 'http://hydra.gnu.org'... 10.5% [07:27] - it is a bit slow, but let's see if there is a difference with the - mirror - you can see there are two servers here. Actually with recent daemons, - if the mirror fails it will try the main server [07:28] - I documented the use of a caching server here [07:29] - https://github.com/pjotrp/guix-notes/blob/master/REPRODUCIBLE.org - this is exactly what we are doing now - alrighty [07:35] - To see if a remote server has a guix server running it should respond - [07:36] - lynx http://guix.genenetwork.org:8080 --dump - Resource not found: / - - you see that? - yes [07:37] - good. The main hydra server is too slow. So on my laptop I forced - using the mirror with [07:38] - env GUIX_PACKAGE_PATH=../guix-bioinformatics/ ./pre-inst-env guix - package -i genenetwork2 --dry-run - --substitute-urls="http://mirror.hydra.gnu.org" - - the list looks the same to me [07:40] - me too - note that some packages will be built and some downloaded, right? - [07:41] - yes - atlas is actually a binary on my system [07:43] - I mean in that list - so, it should not build. Same as yours? - yeah, atlas and r-gtable are the ones to be downloaded - You should not have seen that error ;) - we should try and install it this way, try [07:44] - env GUIX_PACKAGE_PATH=../guix-bioinformatics ./pre-inst-env guix - package -i genenetwork2 --cores=4 --max-jobs=4 --keep-going [07:46] - set CPUs and max-jobs to something sensible - Does your VM have multiple cores? - note you can always press Ctrl-C during install - it doesn't, I'll reboot it and give it another core [07:47] - Hey [07:48] - I'm here - Will be stepping away for some breakfast - Can you do the same as us - Can you see the irc log - Alright - Yes, I can - Please email me a copy in five minutes - user01: so when I use the GN server [07:56] - env GUIX_PACKAGE_PATH=../guix-bioinformatics ./pre-inst-env guix - package -i genenetwork2 --dry-run - --substitute-urls=http://guix.genenetwork.org:8080 - I don't need to build anything [07:57] - (this won't work for you, yet) - to get it to work you need to 'trust' it [07:58] - but, first get the build going - I'll have a coffee while you and get building - yeah it's doing its thing now [08:01] - cool [08:02] - in a separate terminal you can try and install with the gn mirror - [08:05] - I'll send you the public key and you can paste it as said - https://github.com/pjotrp/guix-notes/blob/master/REPRODUCIBLE.org - [08:06] - alright - should be in the E-mail [08:09] - getting it working it kinda nasty since the server gives no feedback - it works when you see no more in the build list ;) [08:11] - btw, you can install software in parallel. Guix does that. - even the same packages - so keep building ;) - try and do this with Debian... - coffee for me [08:12] - the first build failed [08:15] - OK, Dennis fixed that one yesterday [08:27] - the problem is that sometime source tarballs disappear [08:28] - R is notorious for that - haha, that's inconvenient.. - well, it is good that Guix catches them - but we do not cache sources - binaries are cached - to some degree - so we don't have to rebuild - those [08:29] - time to use the guix cache at guix.genenetwork.org - try and install the key (it is in the E-mail) - and see what this lists [08:31] - env GUIX_PACKAGE_PATH=../guix-bioinformatics ./pre-inst-env guix - package -i genenetwork2 - --substitute-urls=http://guix.genenetwork.org --dry-run - should be all binary installs - it's not.. [08:32] - if I remove --substitute-urls, the list changes, does that mean I - have the key set up correctly at least? [08:33] - dunno [08:35] - how many packages does it want to build? - should be zero - four - Ah, that is OK - those are default profile things - genenetwork2 is among the ones to be downloaded so [08:36] - remove --dry-run - yeah, good sign :) - we'll still hit a snag, but run it - should be fast - doing it [08:37] - it worked! [08:38] - I think [08:39] - heh [08:40] - you mean it is finished? - yep - type genenetwork2 - complains about not being able to connect to the database [08:41] - last snag :) - no database - well, we succeeded in installing a same-byte install of a very - complex system :) [08:42] - (always take time to congratulate yourself) - now we need to install mysql - hehe :) - this can be done throug guix or through debian [08:43] - the latter is a bit easier here, so let's do that - fun note: you can mix debian and guix - Follow instructions on [08:44] - - https://github.com/genenetwork/genenetwork2/tree/staging/doc#run-mysql-server - apt-get install mysql-common [08:45] - may do it - You can also install with guix, but I need to document that - btw your internet must be fast :) [08:46] - hehe it is ;) - when the database is installed [08:48] - be sure to set the password as instructed [08:50] - when mysql is set the genenetwork2 command should fire up the web - server on localhost:5003 [08:58] - btw my internet is way slower :) [09:00] - I'm back [09:04] - fixed router firmware upgrade problem - unbricking - tssk [09:07] - I'll never leave routers to update themselves again [09:08] - self-brick highway - Resuming [09:09] - auto-updates are evil - always switch them off - user02: can you install genenetwork like user has done? [09:10] - pretty well documented here now :) - Yes I can [09:11] - Already installed key - user02: you are getting binary packages only now? [09:13] - That's the sanest way to go now - seriously - everything should be pre-built from guix.genenetwork.org - you are downloading? - yes [09:15] - cool. Maybe an idea to set up a server - for your own use - Stuck at downloading preprocesscore - should not [09:24] - what does env GUIX_PACKAGE_PATH=../guix-bioinformatics/ - ./pre-inst-env guix package -i genenetwork2 - --substitute-urls="http://guix.genenetwork.org" --dry-run - [09:25] - say for r-prepocesscore - download or build? - mine says download [09:26] - it only lists the derivatives to be built - nothing else happens [09:27] - OK, so there is a problem - your key may not be working - everything should be listed as 'to be download' [09:28] - Hmm - Ah - I know where I messed up - where? - I did add the key - However - (I am documenting) - I did not tell guix to trust it - yes - and there is another potential problem - Remember the documentation on installing guix? - You have to tell guix to trust the default key [09:29] - Right? - So in this case - read the IRC log - That step is mandatory - user01: how are you doing? - user02: - https://github.com/pjotrp/guix-notes/blob/master/REPRODUCIBLE.org#using-gnu-guix-archive - [09:30] - a little bit left on the db download - user02: you should see no more building - user02: another issue may be that you updated r-preprocesscore - package in guix-buinformatics [09:32] - all downstream packages will want to rebuild - no, not really - It's not even installed - checkout a branch of the the old version - make sure we are in synch - should be at - /gnu/store/y1f3r2xs3fhyadd46nd2aqbr2p9qv2ra-r-biocpreprocesscore-1.32.0 - [09:33] - - pjotrp: Possibly we should use the archive utility of Guix to do - deployment to avoid such out-of-sync differences :) [09:34] - maybe. I did not get archive to update profiles properly [09:37] - Also it is good that they get to understand guix - this way - carved in stone, eh [09:38] - Yeah, all good [09:39] - My mistake was skipping the guix archive part - Can we begin with the install? - It's telling me of derivatives that will be downloaded [09:40] - So we're good - Here goes - yeeha [09:42] - pjotrp, where is this guix.genenetwork.org located at? - Tennessee - It's...it's....sloooooooowwwwwwwwwwwwww - not from Europe - is it downloading at all? - It should be extended - Yes...like at 100KB/s [09:43] - tear-jerker - Verizon problems - who's the host? - I am getting 500Kb/s - UT - Guix's servers can run off more than one server, right? - I'd like to host that particular server here - For speed - yes - Sooner or later - It will be a necessity [09:45] - exactly what I am doing - this is our server - guix.genenetwork.org:8080 - All done installing [09:46] - what? - Now the databases - what do you mean by slow exactly? - Yes, it's installed - can you run genenetwork2 - setting variables - If I try running it now, it will fail as I don't have the DBs [09:47] - cool - you had a lot of prebuilt packages already - OK, follow the instructions I wrote above - now everything seems to be working for me :) - OK - user01: excellent! - you see a webserver? - yep, can connect to localhost:5003 [09:48] - So now you are running a guix copy of GN2 - you can see where it lives with `which genenetwork2` or ls -l - ~/.guix-profile/bin/genenetwork2 [09:49] - - /gnu/store/1kma5xszvzsvmbb4k699h7gvdncw901i-genenetwork2-2.0-a8fcff4/bin/genenetwork2 - it is a script - written by guix, open it [09:50] - inside it points to paths and our script at - - /gnu/store/1kma5xszvzsvmbb4k699h7gvdncw901i-genenetwork2-2.0-a8fcff4/bin/.genenetwork2-real - if you open that you can see how the webserver is started [09:51] - next step is to run a recent version of GN2 - okay [09:52] - See - https://github.com/genenetwork/genenetwork2/tree/staging/doc#run-your-own-copy-of-gn2 - but do not checkout that genetwork2_diet - we reverted to the main tree - clone git@github.com:genenetwork/genenetwork2.git [09:53] - instead and checkout the staging branch - that is effectively my branch [09:54] - when that is done you should be able to fire up the webserver from - there [09:55] - using ./bin/genenetwork2 - now installing DBs - Downloading - annoyingly the source tree is ~700Mb [09:56] - Can it also be done by installing the guix package - genenetwork2-database-small? - I changed it in the diet version to 8Mb, but I had to revert - I need to make my VM bigger... - user02: not ready [09:57] - ok - user01: sorry - user01: you could mount a local dir inside the VM for development - that would allow you to use MAC tools for editing - just an idea - yeah, I figure I'll do something like that - do you use emacs? [09:58] - yep - that can also run on remote files over ssh - that's an alternative - kudos for using emacs :), wdyt user03 - 79 minutes to go downloading the db - user02: sorry about that [09:59] - it is 2GB - user, you can also mount the directory via sshfs - Mac OSX runs OpenSSH - user02: sopa - You can therefore mount a directory outside the VM to the VM via - sshfs [10:00] - yes, 3 options now - That way, you can set up a VM only for it's logic - Apps + the OS it runs [10:01] - For data, let it reside on physical host accessible via sshfs - Use this Arch wiki reference: - https://wiki.archlinux.org/index.php/SSHFS - I edited that last somewhere in 2015, may have been updated since - then - alright, cool! [10:04] - user01: you are almost done [10:06] - I wrote an elixir package for guix :) - env GUIX_PACKAGE_PATH=../guix-bioinformatics/ ./pre-inst-env guix - package -A elixir - --substitute-urls="http://guix.genenetwork.org" [10:08] - elixir 1.2.3 out - ../guix-bioinformatics/gn/packages/elixir.scm:31:2 - - I am building it on guix.genenetwork.org right now [10:09] - nice [10:10] -#+end_src - * NOTES ** Deploying GN2 official @@ -671,7 +287,7 @@ Let's see how fast we can deploy a second copy of GN2. - [ ] Check out the correct gn-stable version of guix-bioinformatics http://git.genenetwork.org/pjotrp/guix-bioinformatics - [ ] guix package -i genenetwork2 -p /usr/local/guix-profiles/gn2-stable + [ ] Create a gn2 user and home with space - + [ ] Install redis (currently debian) + + [ ] Install redis - [ ] add to systemd - [ ] update redis.cnf - [ ] update database @@ -681,7 +297,7 @@ Let's see how fast we can deploy a second copy of GN2. - [ ] update mysql.cnf - [ ] update database (see gn-services/services/mariadb.md) - [ ] check tables - + [ ] run gn2 (rust-qtlreaper not working) + + [ ] run gn2 + [ ] update nginx + [ ] install genenetwork3 - [ ] add to systemd diff --git a/doc/development.org b/doc/development.org index 5e6e318b..e65ccd58 100644 --- a/doc/development.org +++ b/doc/development.org @@ -41,3 +41,58 @@ JS_GN_PATH (3) is for development purposes. By default is is set to $HOME/genenetwork/javascript. Say you are working on an updated version of a JS module not yet in (1) you can simply check out that module in that path and it should show up. + +* Python modules + +Python modules are automatically found in the Guix profile. + +For development purposes it may be useful to try some Python package. +Obviously this is only a temporary measure and when you decide to +include the package it should be packaged in [[http://git.genenetwork.org/guix-bioinformatics/guix-bioinformatics][our GNU Guix software +stack]]! + +To add packages you need to make sure the correct Python is used (currently +Python 2.7) to install a package. E.g.. + +#+BEGIN_SRC sh +python --version + Python 2.7.16 +pip --version + pip 18.1 from /usr/lib/python2.7/dist-packages/pip (python 2.7) +#+END_SRC + +You can install a Python package locally with pip, e.g. + +#+BEGIN_SRC sh +pip install hjson +#+END_SRC + +This installed in ~$HOME/.local/lib/python2.7/site-packages~. To add +the search path for GeneNetwork use the environment variable + +#+BEGIN_SRC sh +export PYTHON_GN_PATH=$HOME/.local/lib/python2.7/site-packages +#+END_SRC + +Now you should be able to do + +#+BEGIN_SRC python +import hjson +#+END_SRC + +In fact you can kick off a Python shell with something like + +#+BEGIN_SRC python +env SERVER_PORT=5013 WEBSERVER_MODE=DEBUG LOG_LEVEL=DEBUG \ + SQL_URI=mysql://gn2:webqtl@localhost/db_webqtl_s \ + GN2_PROFILE=~/opt/genenetwork2 \ + ./bin/genenetwork2 ./etc/default_settings.py -c +Python 2.7.17 (default, Jan 1 1970, 00:00:01) +[GCC 7.5.0] on linux2 +Type "help", "copyright", "credits" or "license" for more information. +>>> import hjson +#+END_SRC + +It should now also work in GN2. + +* TODO External tools -- cgit v1.2.3 From 5a63137af7e383bbae867c4385c42cacf0d78b8e Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sun, 26 Jul 2020 23:04:29 +0300 Subject: Delete tests/unittests folder * tests/unittests: Delete it. All unittests were moved to wqflask/tests in 3d6483e8 --- test/unittest/__init__.py | 0 test/unittest/base/__init__.py | 0 test/unittest/base/test_general_object.py | 21 --------------------- 3 files changed, 21 deletions(-) delete mode 100644 test/unittest/__init__.py delete mode 100644 test/unittest/base/__init__.py delete mode 100644 test/unittest/base/test_general_object.py diff --git a/test/unittest/__init__.py b/test/unittest/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/test/unittest/base/__init__.py b/test/unittest/base/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/test/unittest/base/test_general_object.py b/test/unittest/base/test_general_object.py deleted file mode 100644 index 699cb079..00000000 --- a/test/unittest/base/test_general_object.py +++ /dev/null @@ -1,21 +0,0 @@ -import unittest - -from base.GeneralObject import GeneralObject - - -class TestGeneralObjectTests(unittest.TestCase): - """ - Test the GeneralObject base class - """ - - def test_object_contents(self): - """Test whether base contents are stored properly""" - test_obj = GeneralObject("a", "b", "c") - self.assertEqual("abc", ''.join(test_obj.contents)) - - def test_object_dict(self): - """Test whether the base class is printed properly""" - test_obj = GeneralObject("a", name="test", value=1) - self.assertEqual(str(test_obj), "value = 1\nname = test\n") - self.assertEqual( - repr(test_obj), "value = 1\nname = test\ncontents = ['a']\n") -- cgit v1.2.3 From c1143ffc544abf9c4f12d6676d2584ef1d1cb95e Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sun, 26 Jul 2020 23:18:54 +0300 Subject: Document how to run tests * README.md: - Add details on how to run unit tests - Update link that points to Mechanical Rob --- README.md | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 47519118..45aca1b2 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,13 @@ Also Mysql and Elasticsearch need to be running, see ## Testing +To have tests pass, the redis and mariadb instance should be running, because of +asserts sprinkled in the code base(these will be removed in due time). + +#### Mechanical Rob We are building 'Mechanical Rob' automated testing using Python -[requests](https://github.com/genenetwork/genenetwork2/tree/master/test/lib) -which can be run with something like +[requests](https://github.com/genenetwork/genenetwork2/tree/testing/test/requests) +which can be run with: ```sh env GN2_PROFILE=~/opt/gn-latest ./bin/genenetwork2 ./etc/default_settings.py -c ../test/requests/test-website.py -a http://localhost:5003 @@ -54,6 +58,32 @@ and executes test-website.py in a Python interpreter. The -a switch says to run all tests and the URL points to the running GN2 http server. +#### Unit tests + +To run unittests, first `cd` into the genenetwork2 directory: + +```sh +# You can use the coverage tool to run the tests +# You could omit the -v which makes the output verbose +runcmd coverage run -m unittest discover -v + +# Alternatively, you could run the unittests using: +runpython -m unittest discover -v + +# To generate a report in wqflask/coverage_html_report/: +runcmd coverage html +``` + +The `runcmd` and `runpython` are shell aliases defined in the following way: + +```sh +alias runpython="env GN2_PROFILE=~/opt/gn-latest TMPDIR=/tmp SERVER_PORT=5004 GENENETWORK_FILES=/gnu/data/gn2_data/ ./bin/genenetwork2 + +alias runcmd="time env GN2_PROFILE=~/opt/gn-latest TMPDIR=//tmp SERVER_PORT=5004 GENENETWORK_FILES=/gnu/data/gn2_data/ ./bin/genenetwork2 ./etc/default_settings.py -cli" +``` + +Replace some of the env variables as per your use case. + ## Documentation User documentation can be found -- cgit v1.2.3 From e7c02e570635f6aebe2e2596bfb2e6231a7569e8 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 27 Jul 2020 00:31:12 +0300 Subject: Document how to run tests * doc/testing.org: - Update how Mechanical Robs works. Python replaced Ruby as the testing tool - Add section that describes how to run unit tests --- doc/testing.org | 66 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/doc/testing.org b/doc/testing.org index 1d5cc8b8..d5ab117d 100644 --- a/doc/testing.org +++ b/doc/testing.org @@ -1,43 +1,67 @@ #+TITLE: Testing GN2 * Table of Contents :TOC: - - [[#introduction][Introduction]] - - [[#run-tests][Run tests]] - - [[#setup][Setup]] - - [[#running][Running]] +- [[#introduction][Introduction]] +- [[#run-tests][Run tests]] + - [[#setup][Setup]] + - [[#running][Running]] * Introduction -For integration testing we currently use the brilliant Ruby Mechanize -gem against the small database; a setup we call mechanical Rob because -it emulates someone clicking through the website and checking results. +For integration testing, we currently use [[https://github.com/genenetwork/genenetwork2/tree/testing/test/requests][Mechanica Rob]] against the +small [[https://github.com/genenetwork/genenetwork2/blob/testing/doc/database.org][database]]; a setup we call Mechanical Rob because it emulates +someone clicking through the website and checking results. -These scripts invoke calls to a running webserver and test the -response. If a page changes or is broken tests will break and we are -informed. In principle, Mechanical Rob is run before code merges are -committed to the main server. +These scripts invoke calls to a running webserver and test the response. +If a page changes or breaks, tests will fail. In principle, Mechanical +Rob runs before code merges get committed to the main server. -In the future we may move to Python mechanize - it'll be easy to mix -the Ruby and Python versions. +For unit tests, we use python's =unittest= framework. Coverage reports +get generated using [[https://coverage.readthedocs.io/en/coverage-5.2.1/][coverage.py]] which you could also use to run +unit tests. When adding new functionality, it is advisable to add +unit tests. * Run tests ** Setup -Mechanize is not yet included in Guix deployment. +Everything required for testing is already package with guix: +: ./pre-ins-env guix package -i genenetwork2 -p ~/opt/genenetwork2 ** Running -Run the tests from the root of the genenetwork2 source tree as, for -example, +Run the tests from the root of the genenetwork2 source tree as. Ensure +that Redis and Mariadb are running. -: ./bin/test-website http://localhost:5003/ (default) +To run Mechanical Rob: +: time env GN2_PROFILE=~/opt/genenetwork2 TMPDIR=~/tmp SERVER_PORT=5004 GENENETWORK_FILES=/gnu/data/gn2_data/ ./bin/genenetwork2 ./etc/default_settings.py -c ~/projects/genenetwork2/test/requests/test-website.py -a http://localhost:5004 -If you are using the small deployment database you can use +Use these aliases for the following examples. -: ./bin/test-website --skip -n +#+begin_src sh +alias runpython="env GN2_PROFILE=~/opt/gn-latest TMPDIR=/tmp SERVER_PORT=5004 GENENETWORK_FILES=/gnu/data/gn2_data/ ./bin/genenetwork2 -To run individual tests on localhost you can do +alias runcmd="time env GN2_PROFILE=~/opt/gn-latest TMPDIR=//tmp SERVER_PORT=5004 GENENETWORK_FILES=/gnu/data/gn2_data/ ./bin/genenetwork2 ./etc/default_settings.py -cli" +#+end_src -: ruby -Itest -Itest/lib test/lib/mapping.rb --name="/Mapping/" +You could use them in your =.bashrc= or =.zshrc= file. + +To run unit tests: + +: runpython -m unittest discover -v + +Or alternatively using the coverage tool: + +: runcmd coverage run -m unittest discover -v + +To generate a html coverage report in =wqflask/coverage_html_report/= + +: runcmd coverage html + +To output the report to =STDOUT=: + +: runcmd coverage report + +All the configs for running the coverage tool are in +=wqflask/.coveragerc= -- cgit v1.2.3 From d63e7554d6dfce4e80c2570667a0fa371235beb7 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 27 Jul 2020 14:31:31 +0300 Subject: Apply py-lint * wqflask/tests/base/test_data_set.py: Apply pylint --- wqflask/tests/base/test_data_set.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/wqflask/tests/base/test_data_set.py b/wqflask/tests/base/test_data_set.py index 44a54c7e..74799e49 100644 --- a/wqflask/tests/base/test_data_set.py +++ b/wqflask/tests/base/test_data_set.py @@ -1,3 +1,5 @@ +"""Tests for wqflask/base/data_set.py""" + import unittest import mock @@ -5,8 +7,10 @@ from wqflask import app from base.data_set import DatasetType - + class TestDataSetTypes(unittest.TestCase): + """Tests for the DataSetType class""" + def setUp(self): self.app_context = app.app_context() self.app_context.push() @@ -16,10 +20,14 @@ class TestDataSetTypes(unittest.TestCase): @mock.patch('base.data_set.g') def test_data_set_type(self, db_mock): + """Test that DatasetType returns correctly if the Redis Instance is not empty + and the name variable exists in the dictionary + + """ with app.app_context(): db_mock.get = mock.Mock() - r = mock.Mock() - r.get.return_value = """ + redis_mock = mock.Mock() + redis_mock.get.return_value = """ { "AD-cases-controls-MyersGeno": "Geno", "AD-cases-controls-MyersPublish": "Publish", @@ -32,4 +40,6 @@ class TestDataSetTypes(unittest.TestCase): "B139_K_1206_R": "ProbeSet" } """ - self.assertEqual(DatasetType(r)("All Phenotypes"), "Publish") + self.assertEqual(DatasetType(redis_mock) + ("All Phenotypes"), "Publish") + -- cgit v1.2.3 From 5cad720187b3c53b6d64c64d45be4bc020eed52d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 27 Jul 2020 14:32:26 +0300 Subject: Add test case for empty redis instance for DatasetType * wqflask/tests/base/test_data_set.py(tests): Check correct results are returned when Redis is empty * wqflask/tests/base/data.py(tests): New file. Adds json test data. --- wqflask/tests/base/data.py | 110 ++++++++++++++++++++++++++++++++++++ wqflask/tests/base/test_data_set.py | 16 +++++- 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 wqflask/tests/base/data.py diff --git a/wqflask/tests/base/data.py b/wqflask/tests/base/data.py new file mode 100644 index 00000000..06a5a989 --- /dev/null +++ b/wqflask/tests/base/data.py @@ -0,0 +1,110 @@ +gen_menu_json = """ +{ + "datasets": { + "human": { + "HLC": { + "Liver mRNA": [ + [ + "320", + "HLC_0311", + "GSE9588 Human Liver Normal (Mar11) Both Sexes" + ] + ], + "Phenotypes": [ + [ + "635", + "HLCPublish", + "HLC Published Phenotypes" + ] + ] + } + }, + "mouse": { + "BXD": { + "Genotypes": [ + [ + "600", + "BXDGeno", + "BXD Genotypes" + ] + ], + "Hippocampus mRNA": [ + [ + "112", + "HC_M2_0606_P", + "Hippocampus Consortium M430v2 (Jun06) PDNN" + ] + ], + "Phenotypes": [ + [ + "602", + "BXDPublish", + "BXD Published Phenotypes" + ] + ] + } + } + }, + "groups": { + "human": [ + [ + "HLC", + "Liver: Normal Gene Expression with Genotypes (Merck)", + "Family:None" + ] + ], + "mouse": [ + [ + "BXD", + "BXD", + "Family:None" + ] + ] + }, + "species": [ + [ + "human", + "Human" + ], + [ + "mouse", + "Mouse" + ] + ], + "types": { + "human": { + "HLC": [ + [ + "Phenotypes", + "Traits and Cofactors", + "Phenotypes" + ], + [ + "Liver mRNA", + "Liver mRNA", + "Molecular Trait Datasets" + ] + ] + }, + "mouse": { + "BXD": [ + [ + "Phenotypes", + "Traits and Cofactors", + "Phenotypes" + ], + [ + "Genotypes", + "DNA Markers and SNPs", + "Genotypes" + ], + [ + "Hippocampus mRNA", + "Hippocampus mRNA", + "Molecular Trait Datasets" + ] + ] + } + } +} +""" diff --git a/wqflask/tests/base/test_data_set.py b/wqflask/tests/base/test_data_set.py index 74799e49..835d786a 100644 --- a/wqflask/tests/base/test_data_set.py +++ b/wqflask/tests/base/test_data_set.py @@ -4,7 +4,7 @@ import unittest import mock from wqflask import app - +from data import gen_menu_json from base.data_set import DatasetType @@ -43,3 +43,17 @@ class TestDataSetTypes(unittest.TestCase): self.assertEqual(DatasetType(redis_mock) ("All Phenotypes"), "Publish") + @mock.patch('base.data_set.requests.get') + def test_data_set_type_with_empty_redis(self, request_mock): + """Test that DatasetType returns correctly if the Redis Instance is empty and + the name variable exists in the dictionary + + """ + with app.app_context(): + request_mock.return_value.content = gen_menu_json + redis_mock = mock.Mock() + redis_mock.get.return_value = None + data_set = DatasetType(redis_mock) + self.assertEqual(data_set("BXDGeno"), "Geno") + self.assertEqual(data_set("BXDPublish"), "Publish") + self.assertEqual(data_set("HLC_0311"), "ProbeSet") -- cgit v1.2.3 From 957e05d33eb423df99181a99e7c25891810a21f7 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 27 Jul 2020 15:06:02 +0300 Subject: Check that Redis is called correctly * wqflask/tests/base/test_data_set.py: assert that `set` and `get` are called correctly --- wqflask/tests/base/test_data_set.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wqflask/tests/base/test_data_set.py b/wqflask/tests/base/test_data_set.py index 835d786a..3ac1b6d6 100644 --- a/wqflask/tests/base/test_data_set.py +++ b/wqflask/tests/base/test_data_set.py @@ -42,6 +42,7 @@ class TestDataSetTypes(unittest.TestCase): """ self.assertEqual(DatasetType(redis_mock) ("All Phenotypes"), "Publish") + redis_mock.get.assert_called_once_with("dataset_structure") @mock.patch('base.data_set.requests.get') def test_data_set_type_with_empty_redis(self, request_mock): @@ -57,3 +58,6 @@ class TestDataSetTypes(unittest.TestCase): self.assertEqual(data_set("BXDGeno"), "Geno") self.assertEqual(data_set("BXDPublish"), "Publish") self.assertEqual(data_set("HLC_0311"), "ProbeSet") + redis_mock.set.assert_called_once_with( + "dataset_structure", + '{"BXDGeno": "Geno", "BXDPublish": "Publish", "HLCPublish": "Publish", "HLC_0311": "ProbeSet", "HC_M2_0606_P": "ProbeSet"}') -- cgit v1.2.3 From 441e2dfa2772794673b69cc72f8561fc026d077b Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 27 Jul 2020 18:35:45 +0300 Subject: Use the correct redis instance inside object * wqflask/base/data_set.py (DatasetType): Use object's redis instance --- wqflask/base/data_set.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 5d562871..bce94aa8 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -93,7 +93,7 @@ Publish or ProbeSet. E.g. """ self.redis_instance = redis_instance self.datasets = {} - data = redis_instance.get("dataset_structure") + data = self.redis_instance.get("dataset_structure") if data: self.datasets = json.loads(data) else: # ZS: I don't think this should ever run unless Redis is emptied @@ -115,7 +115,7 @@ Publish or ProbeSet. E.g. except: pass - redis_instance.set("dataset_structure", json.dumps(self.datasets)) + self.redis_instance.set("dataset_structure", json.dumps(self.datasets)) # Set LOG_LEVEL_DEBUG=5 to see the following: logger.debugf(5, "datasets", self.datasets) -- cgit v1.2.3 From 79b8cf45c1c40b9c20278762b6e8f587a2820b43 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 27 Jul 2020 19:10:36 +0300 Subject: Test that non-existent keys in Dataset are set correctly * wqflask/tests/base/test_data_set.py: Add more tests. --- wqflask/tests/base/test_data_set.py | 103 +++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 13 deletions(-) diff --git a/wqflask/tests/base/test_data_set.py b/wqflask/tests/base/test_data_set.py index 3ac1b6d6..94780a5d 100644 --- a/wqflask/tests/base/test_data_set.py +++ b/wqflask/tests/base/test_data_set.py @@ -12,6 +12,19 @@ class TestDataSetTypes(unittest.TestCase): """Tests for the DataSetType class""" def setUp(self): + self.test_dataset = """ + { + "AD-cases-controls-MyersGeno": "Geno", + "AD-cases-controls-MyersPublish": "Publish", + "AKXDGeno": "Geno", + "AXBXAGeno": "Geno", + "AXBXAPublish": "Publish", + "Aging-Brain-UCIPublish": "Publish", + "All Phenotypes": "Publish", + "B139_K_1206_M": "ProbeSet", + "B139_K_1206_R": "ProbeSet" + } + """ self.app_context = app.app_context() self.app_context.push() @@ -27,19 +40,7 @@ class TestDataSetTypes(unittest.TestCase): with app.app_context(): db_mock.get = mock.Mock() redis_mock = mock.Mock() - redis_mock.get.return_value = """ - { - "AD-cases-controls-MyersGeno": "Geno", - "AD-cases-controls-MyersPublish": "Publish", - "AKXDGeno": "Geno", - "AXBXAGeno": "Geno", - "AXBXAPublish": "Publish", - "Aging-Brain-UCIPublish": "Publish", - "All Phenotypes": "Publish", - "B139_K_1206_M": "ProbeSet", - "B139_K_1206_R": "ProbeSet" - } - """ + redis_mock.get.return_value = self.test_dataset self.assertEqual(DatasetType(redis_mock) ("All Phenotypes"), "Publish") redis_mock.get.assert_called_once_with("dataset_structure") @@ -61,3 +62,79 @@ class TestDataSetTypes(unittest.TestCase): redis_mock.set.assert_called_once_with( "dataset_structure", '{"BXDGeno": "Geno", "BXDPublish": "Publish", "HLCPublish": "Publish", "HLC_0311": "ProbeSet", "HC_M2_0606_P": "ProbeSet"}') + + @mock.patch('base.data_set.g') + def test_set_dataset_key_mrna(self, db_mock): + with app.app_context(): + db_mock.db.execute.return_value = [1, 2, 3] + redis_mock = mock.Mock() + redis_mock.get.return_value = self.test_dataset + data_set = DatasetType(redis_mock) + data_set.set_dataset_key("mrna_expr", "Test") + self.assertEqual(data_set("Test"), "ProbeSet") + redis_mock.set.assert_called_once_with( + "dataset_structure", + '{"Aging-Brain-UCIPublish": "Publish", "AKXDGeno": "Geno", "B139_K_1206_M": "ProbeSet", "AD-cases-controls-MyersGeno": "Geno", "AD-cases-controls-MyersPublish": "Publish", "All Phenotypes": "Publish", "Test": "ProbeSet", "AXBXAPublish": "Publish", "B139_K_1206_R": "ProbeSet", "AXBXAGeno": "Geno"}') + expected_db_call = """""" + db_mock.db.execute.assert_called_with( + ("SELECT ProbeSetFreeze.Id FROM ProbeSetFreeze " + + "WHERE ProbeSetFreeze.Name = \"Test\" ") + ) + + @mock.patch('base.data_set.g') + def test_set_dataset_key_pheno(self, db_mock): + with app.app_context(): + db_mock.db.execute.return_value = [1, 2, 3] + redis_mock = mock.Mock() + redis_mock.get.return_value = self.test_dataset + data_set = DatasetType(redis_mock) + data_set.set_dataset_key("pheno", "Test") + self.assertEqual(data_set("Test"), "Publish") + redis_mock.set.assert_called_once_with( + "dataset_structure", + '{"Aging-Brain-UCIPublish": "Publish", "AKXDGeno": "Geno", "B139_K_1206_M": "ProbeSet", "AD-cases-controls-MyersGeno": "Geno", "AD-cases-controls-MyersPublish": "Publish", "All Phenotypes": "Publish", "Test": "Publish", "AXBXAPublish": "Publish", "B139_K_1206_R": "ProbeSet", "AXBXAGeno": "Geno"}') + expected_db_call = """""" + db_mock.db.execute.assert_called_with( + ("SELECT InfoFiles.GN_AccesionId " + + "FROM InfoFiles, PublishFreeze, InbredSet " + + "WHERE InbredSet.Name = 'Test' AND " + "PublishFreeze.InbredSetId = InbredSet.Id AND " + + "InfoFiles.InfoPageName = PublishFreeze.Name") + ) + + @mock.patch('base.data_set.g') + def test_set_dataset_other_pheno(self, db_mock): + with app.app_context(): + db_mock.db.execute.return_value = [1, 2, 3] + redis_mock = mock.Mock() + redis_mock.get.return_value = self.test_dataset + data_set = DatasetType(redis_mock) + data_set.set_dataset_key("other_pheno", "Test") + self.assertEqual(data_set("Test"), "Publish") + redis_mock.set.assert_called_once_with( + "dataset_structure", + '{"Aging-Brain-UCIPublish": "Publish", "AKXDGeno": "Geno", "B139_K_1206_M": "ProbeSet", "AD-cases-controls-MyersGeno": "Geno", "AD-cases-controls-MyersPublish": "Publish", "All Phenotypes": "Publish", "Test": "Publish", "AXBXAPublish": "Publish", "B139_K_1206_R": "ProbeSet", "AXBXAGeno": "Geno"}') + expected_db_call = """""" + db_mock.db.execute.assert_called_with( + ("SELECT PublishFreeze.Name " + + "FROM PublishFreeze, InbredSet " + + "WHERE InbredSet.Name = 'Test' AND " + "PublishFreeze.InbredSetId = InbredSet.Id") + ) + + @mock.patch('base.data_set.g') + def test_set_dataset_geno(self, db_mock): + with app.app_context(): + db_mock.db.execute.return_value = [1, 2, 3] + redis_mock = mock.Mock() + redis_mock.get.return_value = self.test_dataset + data_set = DatasetType(redis_mock) + data_set.set_dataset_key("geno", "Test") + self.assertEqual(data_set("Test"), "Geno") + redis_mock.set.assert_called_once_with( + "dataset_structure", + '{"Aging-Brain-UCIPublish": "Publish", "AKXDGeno": "Geno", "B139_K_1206_M": "ProbeSet", "AD-cases-controls-MyersGeno": "Geno", "AD-cases-controls-MyersPublish": "Publish", "All Phenotypes": "Publish", "Test": "Geno", "AXBXAPublish": "Publish", "B139_K_1206_R": "ProbeSet", "AXBXAGeno": "Geno"}') + expected_db_call = """""" + db_mock.db.execute.assert_called_with( + ("SELECT GenoFreeze.Id FROM GenoFreeze WHERE GenoFreeze.Name = \"Test\" ") + ) -- cgit v1.2.3 From fea8444fb7b0224fd44711853904822ee8b43f4f Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 27 Jul 2020 19:11:11 +0300 Subject: Abstract away redundant code into set_dataset_key method * wqflask/base/data_set.py (Dataset): - Add set_dataset_key - Remove __call__ method --- wqflask/base/data_set.py | 109 ++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 62 deletions(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index bce94aa8..cfba9104 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -117,71 +117,56 @@ Publish or ProbeSet. E.g. self.redis_instance.set("dataset_structure", json.dumps(self.datasets)) - # Set LOG_LEVEL_DEBUG=5 to see the following: - logger.debugf(5, "datasets", self.datasets) + def set_dataset_key(self, t, name): + """If name is not in the object's dataset dictionary, set it, and update + dataset_structure in Redis + + args: + t: Type of dataset structure which can be: 'mrna_expr', 'pheno', + 'other_pheno', 'geno' + name: The name of the key to inserted in the datasets dictionary + + """ + sql_query_mapping = { + 'mrna_expr': ("""SELECT ProbeSetFreeze.Id FROM """ + + """ProbeSetFreeze WHERE ProbeSetFreeze.Name = "{}" """), + 'pheno': ("""SELECT InfoFiles.GN_AccesionId """ + + """FROM InfoFiles, PublishFreeze, InbredSet """ + + """WHERE InbredSet.Name = '{}' AND """ + + """PublishFreeze.InbredSetId = InbredSet.Id AND """ + + """InfoFiles.InfoPageName = PublishFreeze.Name"""), + 'other_pheno': ("""SELECT PublishFreeze.Name """ + + """FROM PublishFreeze, InbredSet """ + + """WHERE InbredSet.Name = '{}' AND """ + + """PublishFreeze.InbredSetId = InbredSet.Id"""), + 'geno': ("""SELECT GenoFreeze.Id FROM GenoFreeze WHERE """ + + """GenoFreeze.Name = "{}" """) + } + + dataset_name_mapping = { + "mrna_expr": "ProbeSet", + "pheno": "Publish", + "other_pheno": "Publish", + "geno": "Geno", + } + + if t in ['pheno', 'other_pheno']: + name = name.replace("Publish", "") + if bool(len(g.db.execute(sql_query_mapping[t].format(name)))): + self.datasets[name] = dataset_name_mapping[t] + self.redis_instance.set("dataset_structure", json.dumps(self.datasets)) + return True + + return None def __call__(self, name): + if name not in self.datasets: - mrna_expr_query = """ - SELECT - ProbeSetFreeze.Id - FROM - ProbeSetFreeze - WHERE - ProbeSetFreeze.Name = "{0}" - """.format(name) - - results = g.db.execute(mrna_expr_query).fetchall() - if len(results): - self.datasets[name] = "ProbeSet" - redis_instance.set("dataset_structure", json.dumps(self.datasets)) - return self.datasets[name] - - group_name = name.replace("Publish", "") - - pheno_query = """SELECT InfoFiles.GN_AccesionId - FROM InfoFiles, PublishFreeze, InbredSet - WHERE InbredSet.Name = '{0}' AND - PublishFreeze.InbredSetId = InbredSet.Id AND - InfoFiles.InfoPageName = PublishFreeze.Name""".format(group_name) - - results = g.db.execute(pheno_query).fetchall() - if len(results): - self.datasets[name] = "Publish" - redis_instance.set("dataset_structure", json.dumps(self.datasets)) - return self.datasets[name] - - # ZS: For when there isn't an InfoFiles ID; not sure if this and the preceding query are both necessary - other_pheno_query = """SELECT PublishFreeze.Name - FROM PublishFreeze, InbredSet - WHERE InbredSet.Name = '{}' AND - PublishFreeze.InbredSetId = InbredSet.Id""".format(group_name) - - results = g.db.execute(other_pheno_query).fetchall() - if len(results): - self.datasets[name] = "Publish" - redis_instance.set("dataset_structure", json.dumps(self.datasets)) - return self.datasets[name] - - geno_query = """ - SELECT - GenoFreeze.Id - FROM - GenoFreeze - WHERE - GenoFreeze.Name = "{0}" - """.format(name) - - results = g.db.execute(geno_query).fetchall() - if len(results): - self.datasets[name] = "Geno" - self.redis_instance.set("dataset_structure", json.dumps(self.datasets)) - return self.datasets[name] - - # ZS: It shouldn't ever reach this - return None - else: - return self.datasets[name] + for t in ["mrna_expr", "pheno", "other_pheno", "geno"]: + # This has side-effects, with the end result being a truth-y value + if(self.set_dataset_key(t, name)): + break + return self.datasets.get(name, None) # Return None if name has not been set # Do the intensive work at startup one time only -- cgit v1.2.3 From 7732204662bf395eb8ed55b6d26fd208998c1067 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 27 Jul 2020 19:48:16 +0300 Subject: Add unittests for WebqtlCaseData * wqflask/tests/base/test_webqtl_case_data.py: Add it --- wqflask/tests/base/test_webqtl_case_data.py | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 wqflask/tests/base/test_webqtl_case_data.py diff --git a/wqflask/tests/base/test_webqtl_case_data.py b/wqflask/tests/base/test_webqtl_case_data.py new file mode 100644 index 00000000..8e8ba482 --- /dev/null +++ b/wqflask/tests/base/test_webqtl_case_data.py @@ -0,0 +1,39 @@ +"""Tests for wqflask/base/webqtlCaseData.py""" +import unittest + +from wqflask import app # Required because of utility.tools in webqtlCaseData.py +from base.webqtlCaseData import webqtlCaseData + +class TestWebqtlCaseData(unittest.TestCase): + """Tests for WebqtlCaseData class""" + + def setUp(self): + self.w = webqtlCaseData(name="Test", + value=0, + variance=0.0, + num_cases=10, + name2="Test2") + + def test_webqtl_case_data_repr(self): + self.assertEqual( + repr(self.w), + " value=0.000 variance=0.000 ndata=10 name=Test name2=Test2" + ) + + def test_class_outlier(self): + self.assertEqual(self.w.class_outlier, "") + + def test_display_value(self): + self.assertEqual(self.w.display_value, "0.000") + self.w.value = None + self.assertEqual(self.w.display_value, "x") + + def test_display_variance(self): + self.assertEqual(self.w.display_variance, "0.000") + self.w.variance = None + self.assertEqual(self.w.display_variance, "x") + + def test_display_num_cases(self): + self.assertEqual(self.w.display_num_cases, "10") + self.w.num_cases = None + self.assertEqual(self.w.display_num_cases, "x") -- cgit v1.2.3 From c1e22edf5f08b6d9c79a6608b8faf434578e246d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 27 Jul 2020 21:00:24 +0300 Subject: Clean up webqtlCaseData class * wqflask/base/webqtlCaseData.py (webqtlCaseData): - Remove obsolete 'Object' from Class inheritance - Replace 'str' with 'case_data_string' variable- it collides with python in-builts - Use pythonic 'is Not None' form - Remove redundancy in 'if' forms - Update copyright header --- wqflask/base/webqtlCaseData.py | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/wqflask/base/webqtlCaseData.py b/wqflask/base/webqtlCaseData.py index d8487f01..2844cedd 100644 --- a/wqflask/base/webqtlCaseData.py +++ b/wqflask/base/webqtlCaseData.py @@ -19,8 +19,7 @@ # This module is used by GeneNetwork project (www.genenetwork.org) # # Created by GeneNetwork Core Team 2010/08/10 -# -# Last updated by GeneNetwork Core Team 2010/10/20 + from utility.logger import getLogger logger = getLogger(__name__) @@ -29,7 +28,7 @@ import utility.tools utility.tools.show_settings() -class webqtlCaseData(object): +class webqtlCaseData: """one case data in one trait""" def __init__(self, name, value=None, variance=None, num_cases=None, name2=None): @@ -43,44 +42,40 @@ class webqtlCaseData(object): self.outlier = None # Not set to True/False until later def __repr__(self): - str = " " - if self.value != None: - str += "value=%2.3f" % self.value - if self.variance != None: - str += " variance=%2.3f" % self.variance + case_data_string = " " + if self.value is not None: + case_data_string += "value=%2.3f" % self.value + if self.variance is not None: + case_data_string += " variance=%2.3f" % self.variance if self.num_cases: - str += " ndata=%s" % self.num_cases + case_data_string += " ndata=%s" % self.num_cases if self.name: - str += " name=%s" % self.name + case_data_string += " name=%s" % self.name if self.name2: - str += " name2=%s" % self.name2 - return str + case_data_string += " name2=%s" % self.name2 + return case_data_string @property def class_outlier(self): """Template helper""" if self.outlier: return "outlier" - else: - return "" + return "" @property def display_value(self): - if self.value != None: + if self.value is not None: return "%2.3f" % self.value - else: - return "x" + return "x" @property def display_variance(self): - if self.variance != None: + if self.variance is not None: return "%2.3f" % self.variance - else: - return "x" + return "x" @property def display_num_cases(self): - if self.num_cases != None: + if self.num_cases is not None: return "%s" % self.num_cases - else: - return "x" + return "x" -- cgit v1.2.3 From 92088d72ad284a664eb1a53127c13ca2e6b4f602 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 28 Jul 2020 12:41:49 -0500 Subject: Setting owner to None since super-users should allow for people like Rob or me to edit resource owners --- wqflask/utility/authentication_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index ed7462d1..deddaec3 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -45,7 +45,7 @@ def check_resource_availability(dataset, trait_id=None): def add_new_resource(dataset, trait_id=None): resource_ob = { - 'owner_id' : webqtlConfig.DEFAULT_OWNER_ID, + 'owner_id' : "None", 'default_mask': webqtlConfig.DEFAULT_PRIVILEGES, 'group_masks' : {} } -- cgit v1.2.3 From 06a3758eedbc6df53caab3b1ef320df781b71e83 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 28 Jul 2020 14:15:03 -0500 Subject: Fixes issue where zooming in on mapping results in some browsers caused the original tab's contents to be removed * wqflask/wqflask/templates/mapping_results.html: minor change to add spaces for readability * wqflask/wqflask/marker_regression/display_mapping_results.py: Removed "return" from chrView and rangeView since it appears to remove original tab's contents in some browers when submitting form --- wqflask/wqflask/marker_regression/display_mapping_results.py | 2 +- wqflask/wqflask/templates/mapping_results.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 89f56c30..9ac4946b 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -2133,7 +2133,7 @@ class DisplayMappingResults(object): #add by NL 09-03-2010 HREF = "javascript:chrView(%d,%s);" % (i,self.ChrLengthMbList) #HREF = "javascript:changeView(%d,%s);" % (i,self.ChrLengthMbList) - Areas = HT.Area(shape='rect',coords=COORDS,href=HREF) + Areas = HT.Area(shape='rect', coords=COORDS, href=HREF) gifmap.areas.append(Areas) startPosX += (self.ChrLengthDistList[i]+self.GraphInterval)*plotXScale diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html index 81803deb..6348ca31 100644 --- a/wqflask/wqflask/templates/mapping_results.html +++ b/wqflask/wqflask/templates/mapping_results.html @@ -445,7 +445,7 @@ $('input[name=chr_mb_list]').val(chr_mb_list) $('#marker_regression_form').attr('action', '/loading'); - return $('#marker_regression_form').submit(); + $('#marker_regression_form').submit(); }; rangeView = function(this_chr, start_mb, end_mb) { @@ -455,7 +455,7 @@ //$('input[name=mb_range]').val(start_mb + "," + end_mb) $('#marker_regression_form').attr('action', '/loading'); - return $('#marker_regression_form').submit(); + $('#marker_regression_form').submit(); }; remap = function() { -- cgit v1.2.3 From b55792f2daf648affe52d78c7f3a480f6550dfc8 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 29 Jul 2020 22:09:21 +0300 Subject: Add initial gen_menu test * wqflask/tests/api/__init__.py: Add it * wqflask/tests/api/test_gen_menu.py: Add test for get_species --- wqflask/tests/api/__init__.py | 0 wqflask/tests/api/test_gen_menu.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 wqflask/tests/api/__init__.py create mode 100644 wqflask/tests/api/test_gen_menu.py diff --git a/wqflask/tests/api/__init__.py b/wqflask/tests/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py new file mode 100644 index 00000000..81908129 --- /dev/null +++ b/wqflask/tests/api/test_gen_menu.py @@ -0,0 +1,19 @@ +"""Test cases for wqflask.api.gen_menu""" +import unittest +import mock + +from wqflask.api.gen_menu import get_species + +class TestGenMenu(unittest.TestCase): + """Tests for the gen_menu module""" + + @mock.patch('wqflask.api.gen_menu.g') + def test_get_species(self, db_mock): + """Test that assertion is raised when dataset and dataset_name are defined""" + db_mock.db.execute.return_value.fetchall.return_value = (('human', 'Human'), + ('mouse', 'Mouse')) + self.assertEqual(get_species(), + [['human', 'Human'], ['mouse', 'Mouse']]) + db_mock.db.execute.assert_called_once_with( + "SELECT Name, MenuName FROM Species ORDER BY OrderId" + ) -- cgit v1.2.3 From 3425e7a5dbf5a6983424659c05c258e7a132edf3 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 29 Jul 2020 22:19:13 +0300 Subject: Add test for "get_groups" * wqflask/tests/api/test_gen_menu.py: test that "get_groups" uses the correct sql query and returns the correct group data structure. --- wqflask/tests/api/test_gen_menu.py | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py index 81908129..a2a93da7 100644 --- a/wqflask/tests/api/test_gen_menu.py +++ b/wqflask/tests/api/test_gen_menu.py @@ -3,10 +3,28 @@ import unittest import mock from wqflask.api.gen_menu import get_species +from wqflask.api.gen_menu import get_groups + class TestGenMenu(unittest.TestCase): """Tests for the gen_menu module""" + def setUp(self): + self.test_group = { + 'mouse': [ + ['H_T1', + 'H_T', + 'Family:DescriptionA' + ], + ['H_T2', "H_T'", 'Family:None'] + ], + 'human': [ + ['BXD', 'BXD', 'Family:None'], + ['HLC', 'Liver: Normal Gene Expression with Genotypes (Merck)', + 'Family:Test'] + ] + } + @mock.patch('wqflask.api.gen_menu.g') def test_get_species(self, db_mock): """Test that assertion is raised when dataset and dataset_name are defined""" @@ -17,3 +35,30 @@ class TestGenMenu(unittest.TestCase): db_mock.db.execute.assert_called_once_with( "SELECT Name, MenuName FROM Species ORDER BY OrderId" ) + + @mock.patch('wqflask.api.gen_menu.g') + def test_get_groups(self, db_mock): + """Test that species groups are grouped correctly""" + db_mock.db.execute.return_value.fetchall.side_effect = [ + # Mouse + (('BXD', 'BXD', None), + ('HLC', 'Liver: Normal Gene Expression with Genotypes (Merck)', 'Test')), + # Human + (('H_T1', "H_T", "DescriptionA"), + ('H_T2', "H_T'", None)) + ] + + self.assertEqual(get_groups([["human", "Human"], ["mouse", "Mouse"]]), + self.test_group) + + for name in ["mouse", "human"]: + db_mock.db.execute.assert_any_call( + ("SELECT InbredSet.Name, InbredSet.FullName, " + + "IFNULL(InbredSet.Family, 'None') " + + "FROM InbredSet, Species WHERE Species.Name " + + "= '{}' AND InbredSet.SpeciesId = Species.Id GROUP by " + + "InbredSet.Name ORDER BY IFNULL(InbredSet.FamilyOrder, " + + "InbredSet.FullName) ASC, IFNULL(InbredSet.Family, " + + "InbredSet.FullName) ASC, InbredSet.FullName ASC, " + + "InbredSet.MenuOrderId ASC").format(name) + ) -- cgit v1.2.3 From d50f72869b8d0a7078f0481aad2b04346cd56e3f Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 29 Jul 2020 22:28:38 +0300 Subject: Add tests for "phenotypes_exist" and "genotypes_exist" * wqflask/tests/api/test_gen_menu.py: Add new tests --- wqflask/tests/api/test_gen_menu.py | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py index a2a93da7..be66a4c6 100644 --- a/wqflask/tests/api/test_gen_menu.py +++ b/wqflask/tests/api/test_gen_menu.py @@ -4,6 +4,8 @@ import mock from wqflask.api.gen_menu import get_species from wqflask.api.gen_menu import get_groups +from wqflask.api.gen_menu import phenotypes_exist +from wqflask.api.gen_menu import genotypes_exist class TestGenMenu(unittest.TestCase): @@ -62,3 +64,50 @@ class TestGenMenu(unittest.TestCase): "InbredSet.FullName) ASC, InbredSet.FullName ASC, " + "InbredSet.MenuOrderId ASC").format(name) ) + + @mock.patch('wqflask.api.gen_menu.g') + def test_phenotypes_exist_called_with_correct_query(self, db_mock): + """Test that phenotypes_exist is called with the correct query""" + db_mock.db.execute.return_value.fetchone.return_value = None + phenotypes_exist("test") + db_mock.db.execute.assert_called_with( + "SELECT Name FROM PublishFreeze WHERE PublishFreeze.Name = 'testPublish'" + ) + + @mock.patch('wqflask.api.gen_menu.g') + def test_phenotypes_exist_with_falsy_values(self, db_mock): + """Test that phenotype check returns correctly when given a None value""" + for x in [None, False, (), [], ""]: + db_mock.db.execute.return_value.fetchone.return_value = x + self.assertFalse(phenotypes_exist("test")) + + @mock.patch('wqflask.api.gen_menu.g') + def test_phenotypes_exist_with_truthy_value(self, db_mock): + """Test that phenotype check returns correctly when given Truthy """ + for x in ["x", ("result"), ["result"], [1]]: + db_mock.db.execute.return_value.fetchone.return_value = (x) + self.assertTrue(phenotypes_exist("test")) + + @mock.patch('wqflask.api.gen_menu.g') + def test_genotypes_exist_called_with_correct_query(self, db_mock): + """Test that genotypes_exist is called with the correct query""" + db_mock.db.execute.return_value.fetchone.return_value = None + genotypes_exist("test") + db_mock.db.execute.assert_called_with( + "SELECT Name FROM GenoFreeze WHERE GenoFreeze.Name = 'testGeno'" + ) + + @mock.patch('wqflask.api.gen_menu.g') + def test_genotypes_exist_with_falsy_values(self, db_mock): + """Test that genotype check returns correctly when given a None value""" + for x in [None, False, (), [], ""]: + db_mock.db.execute.return_value.fetchone.return_value = x + self.assertFalse(genotypes_exist("test")) + + @mock.patch('wqflask.api.gen_menu.g') + def test_genotypes_exist_with_truthy_value(self, db_mock): + """Test that genotype check returns correctly when given Truthy """ + for x in ["x", ("result"), ["result"], [1]]: + db_mock.db.execute.return_value.fetchone.return_value = (x) + self.assertTrue(phenotypes_exist("test")) + -- cgit v1.2.3 From 36c85b7f82c584b869182f097cd0b8577dfef0d4 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 29 Jul 2020 22:12:53 +0300 Subject: Reformat sql queries to be one line * wqflask/wqflask/api/gen_menu.py (get_species, get_groups, phenotypes_exist) (genotypes_exist)[sql]: Strip white spaces and newlines from query and make the sql query one line. --- wqflask/wqflask/api/gen_menu.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/wqflask/wqflask/api/gen_menu.py b/wqflask/wqflask/api/gen_menu.py index cfce0c8e..69a927cd 100644 --- a/wqflask/wqflask/api/gen_menu.py +++ b/wqflask/wqflask/api/gen_menu.py @@ -31,9 +31,7 @@ def gen_dropdown_json(): def get_species(): """Build species list""" - results = g.db.execute("""SELECT Name, MenuName - FROM Species - ORDER BY OrderId""").fetchall() + results = g.db.execute("SELECT Name, MenuName FROM Species ORDER BY OrderId").fetchall() species = [] for result in results: @@ -47,12 +45,12 @@ def get_groups(species): for species_name, _species_full_name in species: groups[species_name] = [] - results = g.db.execute("""SELECT InbredSet.Name, InbredSet.FullName, IFNULL(InbredSet.Family, 'None') - FROM InbredSet, Species - WHERE Species.Name = '{}' AND - InbredSet.SpeciesId = Species.Id - GROUP by InbredSet.Name - ORDER BY IFNULL(InbredSet.FamilyOrder, InbredSet.FullName) ASC, IFNULL(InbredSet.Family, InbredSet.FullName) ASC, InbredSet.FullName ASC, InbredSet.MenuOrderId ASC""".format(species_name)).fetchall() + results = g.db.execute( + ("SELECT InbredSet.Name, InbredSet.FullName, IFNULL(InbredSet.Family, 'None') " + + "FROM InbredSet, Species WHERE Species.Name = '{}' AND InbredSet.SpeciesId = " + + "Species.Id GROUP by InbredSet.Name ORDER BY IFNULL(InbredSet.FamilyOrder, " + + "InbredSet.FullName) ASC, IFNULL(InbredSet.Family, InbredSet.FullName) ASC, " + + "InbredSet.FullName ASC, InbredSet.MenuOrderId ASC").format(species_name)).fetchall() for result in results: family_name = "Family:" + str(result[2]) @@ -92,9 +90,9 @@ def get_types(groups): return types def phenotypes_exist(group_name): - results = g.db.execute("""SELECT Name - FROM PublishFreeze - WHERE PublishFreeze.Name = '{}'""".format(group_name+"Publish")).fetchone() + results = g.db.execute( + ("SELECT Name FROM PublishFreeze " + + "WHERE PublishFreeze.Name = '{}'").format(group_name+"Publish")).fetchone() if results != None: return True @@ -102,9 +100,9 @@ def phenotypes_exist(group_name): return False def genotypes_exist(group_name): - results = g.db.execute("""SELECT Name - FROM GenoFreeze - WHERE GenoFreeze.Name = '{}'""".format(group_name+"Geno")).fetchone() + results = g.db.execute( + ("SELECT Name FROM GenoFreeze " + + "WHERE GenoFreeze.Name = '{}'").format(group_name+"Geno")).fetchone() if results != None: return True -- cgit v1.2.3 From 81dc6616d3cb52be43e8a9c8341f97ab131fc8e7 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 29 Jul 2020 23:14:51 +0300 Subject: Update how boolean is returned * wqflask/wqflask/api/gen_menu.py (phenotypes_exist, genotypes_exist): Convert sql result to boolean instead of only checking for "None". --- wqflask/wqflask/api/gen_menu.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/wqflask/wqflask/api/gen_menu.py b/wqflask/wqflask/api/gen_menu.py index 69a927cd..0adf3cee 100644 --- a/wqflask/wqflask/api/gen_menu.py +++ b/wqflask/wqflask/api/gen_menu.py @@ -93,21 +93,15 @@ def phenotypes_exist(group_name): results = g.db.execute( ("SELECT Name FROM PublishFreeze " + "WHERE PublishFreeze.Name = '{}'").format(group_name+"Publish")).fetchone() + return bool(results) - if results != None: - return True - else: - return False def genotypes_exist(group_name): results = g.db.execute( ("SELECT Name FROM GenoFreeze " + "WHERE GenoFreeze.Name = '{}'").format(group_name+"Geno")).fetchone() + return bool(results) - if results != None: - return True - else: - return False def build_types(species, group): """Fetches tissues -- cgit v1.2.3 From 317d7a78930ecf4be8ec80bd688c14baa8852f04 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 29 Jul 2020 18:50:08 -0500 Subject: Fixed error that occured for new resources in check_admin * wqflask/utility/authentication_tools.py: the code forgot to get resource_info when a resource wasn't in Redis yet, causing an error when it was referenced, so I added a line getting the resource info (and also a couple comments for other lines related to authentication) --- wqflask/utility/authentication_tools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index 9a2a5ccd..ece7022c 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -24,9 +24,9 @@ def check_resource_availability(dataset, trait_id=None): resource_id = get_resource_id(dataset, trait_id) - if resource_id: + if resource_id: #ZS: This should never be false, but it's technically possible if a non-Temp dataset somehow had a type other than Publish/ProbeSet/Geno resource_info = get_resource_info(resource_id) - if not resource_info: + if not resource_info: #ZS: If resource isn't already in redis, add it with default privileges resource_info = add_new_resource(dataset, trait_id) #ZS: Check if super-user - we should probably come up with some way to integrate this into the proxy @@ -84,7 +84,7 @@ def check_admin(resource_id=None): try: response = json.loads(requests.get(the_url).content)['admin'] except: - logger.debug(resource_info) + resource_info = get_resource_info(resource_id) response = resource_info['default_mask']['admin'] if 'edit-admins' in response: -- cgit v1.2.3 From 99fae55d98e7042e0caf29c34fb77ee17efaafa2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 30 Jul 2020 16:13:39 +0300 Subject: Add tests for "build_datasets" * wqflask/tests/api/test_gen_menu.py: Add new tests for "build_datasets" function --- wqflask/tests/api/test_gen_menu.py | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py index be66a4c6..ec380b6f 100644 --- a/wqflask/tests/api/test_gen_menu.py +++ b/wqflask/tests/api/test_gen_menu.py @@ -6,6 +6,7 @@ from wqflask.api.gen_menu import get_species from wqflask.api.gen_menu import get_groups from wqflask.api.gen_menu import phenotypes_exist from wqflask.api.gen_menu import genotypes_exist +from wqflask.api.gen_menu import build_datasets class TestGenMenu(unittest.TestCase): @@ -111,3 +112,68 @@ class TestGenMenu(unittest.TestCase): db_mock.db.execute.return_value.fetchone.return_value = (x) self.assertTrue(phenotypes_exist("test")) + + @mock.patch('wqflask.api.gen_menu.g') + def test_build_datasets_with_type_phenotypes(self, db_mock): + """Test that correct dataset is returned for a phenotype type""" + db_mock.db.execute.return_value.fetchall.return_value = ( + (602, "BXDPublish", "BXD Published Phenotypes"), + ) + self.assertEqual(build_datasets("Mouse", "BXD", "Phenotypes"), + [['602', "BXDPublish", "BXD Published Phenotypes"]]) + db_mock.db.execute.assert_called_with( + "SELECT InfoFiles.GN_AccesionId, PublishFreeze.Name, " + + "PublishFreeze.FullName FROM InfoFiles, PublishFreeze, " + + "InbredSet WHERE InbredSet.Name = 'BXD' AND " + + "PublishFreeze.InbredSetId = InbredSet.Id AND " + + "InfoFiles.InfoPageName = PublishFreeze.Name " + + "ORDER BY PublishFreeze.CreateTime ASC" + ) + self.assertEqual(build_datasets("Mouse", "MDP", "Phenotypes"), + [['602', "BXDPublish", "Mouse Phenome Database"]]) + + db_mock.db.execute.return_value.fetchall.return_value = () + db_mock.db.execute.return_value.fetchone.return_value = ( + "BXDPublish", "Mouse Phenome Database" + ) + self.assertEqual(build_datasets("Mouse", "MDP", "Phenotypes"), + [["None", "BXDPublish", "Mouse Phenome Database"]]) + + @mock.patch('wqflask.api.gen_menu.g') + def test_build_datasets_with_type_genotypes(self, db_mock): + """Test that correct dataset is returned for a phenotype type""" + db_mock.db.execute.return_value.fetchone.return_value = ( + 635, "HLCPublish", "HLC Published Genotypes" + ) + + self.assertEqual(build_datasets("Mouse", "HLC", "Genotypes"), + [["635", "HLCGeno", "HLC Genotypes"]]) + db_mock.db.execute.assert_called_with( + "SELECT InfoFiles.GN_AccesionId FROM InfoFiles, GenoFreeze, InbredSet " + + "WHERE InbredSet.Name = 'HLC' AND GenoFreeze.InbredSetId = InbredSet.Id AND " + + "InfoFiles.InfoPageName = GenoFreeze.ShortName " + + "ORDER BY GenoFreeze.CreateTime DESC" + ) + db_mock.db.execute.return_value.fetchone.return_value = () + self.assertEqual(build_datasets("Mouse", "HLC", "Genotypes"), + [["None", "HLCGeno", "HLC Genotypes"]]) + + @mock.patch('wqflask.api.gen_menu.g') + def test_build_datasets_with_type_mrna(self, db_mock): + """Test that correct dataset is returned for a mRNA expression/ Probeset""" + db_mock.db.execute.return_value.fetchall.return_value = ( + (112, "HC_M2_0606_P", + "Hippocampus Consortium M430v2 (Jun06) PDNN"), ) + self.assertEqual(build_datasets("Mouse", "HLC", "mRNA"), [[ + "112", 'HC_M2_0606_P', "Hippocampus Consortium M430v2 (Jun06) PDNN" + ]]) + db_mock.db.execute.assert_called_once_with( + "SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, " + + "ProbeSetFreeze.FullName FROM ProbeSetFreeze, " + + "ProbeFreeze, InbredSet, Tissue, Species WHERE " + + "Species.Name = 'Mouse' AND Species.Id = " + + "InbredSet.SpeciesId AND InbredSet.Name = 'HLC' AND " + + "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id and " + + "Tissue.Name = 'mRNA' AND ProbeFreeze.TissueId = " + + "Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id " + + "ORDER BY ProbeSetFreeze.CreateTime DESC") -- cgit v1.2.3 From 43bfbc5c17806fd84dd4582d5f40d56ec0ab23d2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 30 Jul 2020 16:16:53 +0300 Subject: Reformat sql queries to be one line * wqflask/wqflask/api/gen_menu.py (build_datasets, build_types): Strip white spaces and newlines from query and make the sql query one line. --- wqflask/wqflask/api/gen_menu.py | 77 +++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/wqflask/wqflask/api/gen_menu.py b/wqflask/wqflask/api/gen_menu.py index 0adf3cee..1a121c33 100644 --- a/wqflask/wqflask/api/gen_menu.py +++ b/wqflask/wqflask/api/gen_menu.py @@ -111,15 +111,14 @@ def build_types(species, group): """ - query = """SELECT DISTINCT Tissue.Name - FROM ProbeFreeze, ProbeSetFreeze, InbredSet, Tissue, Species - WHERE Species.Name = '{0}' AND - Species.Id = InbredSet.SpeciesId AND - InbredSet.Name = '{1}' AND - ProbeFreeze.TissueId = Tissue.Id AND - ProbeFreeze.InbredSetId = InbredSet.Id AND - ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id - ORDER BY Tissue.Name""".format(species, group) + query = ("SELECT DISTINCT Tissue.Name " + + "FROM ProbeFreeze, ProbeSetFreeze, InbredSet, " + + "Tissue, Species WHERE Species.Name = '{0}' " + + "AND Species.Id = InbredSet.SpeciesId AND " + + "InbredSet.Name = '{1}' AND ProbeFreeze.TissueId = " + + "Tissue.Id AND ProbeFreeze.InbredSetId = InbredSet.Id " + + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + + "ORDER BY Tissue.Name").format(species, group) results = [] for result in g.db.execute(query).fetchall(): @@ -150,14 +149,14 @@ def build_datasets(species, group, type_name): dataset_text = dataset_value = None datasets = [] if type_name == "Phenotypes": - results = g.db.execute("""SELECT InfoFiles.GN_AccesionId, PublishFreeze.Name, PublishFreeze.FullName - FROM InfoFiles, PublishFreeze, InbredSet - WHERE InbredSet.Name = '{}' AND - PublishFreeze.InbredSetId = InbredSet.Id AND - InfoFiles.InfoPageName = PublishFreeze.Name - ORDER BY PublishFreeze.CreateTime ASC""".format(group)).fetchall() - if len(results) > 0: + results = g.db.execute( + ("SELECT InfoFiles.GN_AccesionId, PublishFreeze.Name, " + + "PublishFreeze.FullName FROM InfoFiles, PublishFreeze, " + + "InbredSet WHERE InbredSet.Name = '{}' AND " + + "PublishFreeze.InbredSetId = InbredSet.Id AND " + + "InfoFiles.InfoPageName = PublishFreeze.Name " + + "ORDER BY PublishFreeze.CreateTime ASC").format(group)).fetchall() for result in results: dataset_id = str(result[0]) dataset_value = str(result[1]) @@ -168,11 +167,13 @@ def build_datasets(species, group, type_name): dataset_text = str(result[2]) datasets.append([dataset_id, dataset_value, dataset_text]) else: - result = g.db.execute("""SELECT PublishFreeze.Name, PublishFreeze.FullName - FROM PublishFreeze, InbredSet - WHERE InbredSet.Name = '{}' AND - PublishFreeze.InbredSetId = InbredSet.Id - ORDER BY PublishFreeze.CreateTime ASC""".format(group)).fetchone() + result = g.db.execute( + ("SELECT PublishFreeze.Name, PublishFreeze.FullName " + "FROM PublishFreeze, InbredSet " + "WHERE InbredSet.Name = '{}' AND " + "PublishFreeze.InbredSetId = InbredSet.Id " + "ORDER BY PublishFreeze.CreateTime ASC") + .format(group)).fetchone() dataset_id = "None" dataset_value = str(result[0]) @@ -180,14 +181,14 @@ def build_datasets(species, group, type_name): datasets.append([dataset_id, dataset_value, dataset_text]) elif type_name == "Genotypes": - results = g.db.execute("""SELECT InfoFiles.GN_AccesionId - FROM InfoFiles, GenoFreeze, InbredSet - WHERE InbredSet.Name = '{}' AND - GenoFreeze.InbredSetId = InbredSet.Id AND - InfoFiles.InfoPageName = GenoFreeze.ShortName - ORDER BY GenoFreeze.CreateTime DESC""".format(group)).fetchone() - - if results != None: + results = g.db.execute( + ("SELECT InfoFiles.GN_AccesionId " + + "FROM InfoFiles, GenoFreeze, InbredSet " + + "WHERE InbredSet.Name = '{}' AND " + + "GenoFreeze.InbredSetId = InbredSet.Id AND " + + "InfoFiles.InfoPageName = GenoFreeze.ShortName " + + "ORDER BY GenoFreeze.CreateTime DESC").format(group)).fetchone() + dataset_id = str(results[0]) else: dataset_id = "None" @@ -196,14 +197,16 @@ def build_datasets(species, group, type_name): datasets.append([dataset_id, dataset_value, dataset_text]) else: # for mRNA expression/ProbeSet - results = g.db.execute("""SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, ProbeSetFreeze.FullName - FROM ProbeSetFreeze, ProbeFreeze, InbredSet, Tissue, Species - WHERE Species.Name = '{0}' AND - Species.Id = InbredSet.SpeciesId AND - InbredSet.Name = '{1}' AND - ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id and Tissue.Name = '{2}' AND - ProbeFreeze.TissueId = Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id - ORDER BY ProbeSetFreeze.CreateTime DESC""".format(species, group, type_name)).fetchall() + results = g.db.execute( + ("SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, " + + "ProbeSetFreeze.FullName FROM ProbeSetFreeze, " + + "ProbeFreeze, InbredSet, Tissue, Species WHERE " + + "Species.Name = '{0}' AND Species.Id = " + + "InbredSet.SpeciesId AND InbredSet.Name = '{1}' " + + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + + "and Tissue.Name = '{2}' AND ProbeFreeze.TissueId = " + + "Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id " + + "ORDER BY ProbeSetFreeze.CreateTime DESC").format(species, group, type_name)).fetchall() datasets = [] for dataset_info in results: -- cgit v1.2.3 From 7f4b4b19c049a20c3040af22e104ac1d5fbff160 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 30 Jul 2020 16:20:01 +0300 Subject: Clean up "if ... else" conditional * wqflask/wqflask/api/gen_menu.py (build_datasets): Make conditionals more Pythonic --- wqflask/wqflask/api/gen_menu.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wqflask/wqflask/api/gen_menu.py b/wqflask/wqflask/api/gen_menu.py index 1a121c33..d265a8ee 100644 --- a/wqflask/wqflask/api/gen_menu.py +++ b/wqflask/wqflask/api/gen_menu.py @@ -149,7 +149,6 @@ def build_datasets(species, group, type_name): dataset_text = dataset_value = None datasets = [] if type_name == "Phenotypes": - if len(results) > 0: results = g.db.execute( ("SELECT InfoFiles.GN_AccesionId, PublishFreeze.Name, " + "PublishFreeze.FullName FROM InfoFiles, PublishFreeze, " + @@ -157,14 +156,14 @@ def build_datasets(species, group, type_name): "PublishFreeze.InbredSetId = InbredSet.Id AND " + "InfoFiles.InfoPageName = PublishFreeze.Name " + "ORDER BY PublishFreeze.CreateTime ASC").format(group)).fetchall() + if bool(results): for result in results: dataset_id = str(result[0]) dataset_value = str(result[1]) + dataset_text = str(result[2]) if group == 'MDP': dataset_text = "Mouse Phenome Database" - else: - #dataset_text = "%s Phenotypes" % group - dataset_text = str(result[2]) + datasets.append([dataset_id, dataset_value, dataset_text]) else: result = g.db.execute( @@ -189,9 +188,10 @@ def build_datasets(species, group, type_name): "InfoFiles.InfoPageName = GenoFreeze.ShortName " + "ORDER BY GenoFreeze.CreateTime DESC").format(group)).fetchone() + dataset_id = "None" + if bool(results): dataset_id = str(results[0]) - else: - dataset_id = "None" + dataset_value = "%sGeno" % group dataset_text = "%s Genotypes" % group datasets.append([dataset_id, dataset_value, dataset_text]) @@ -215,4 +215,4 @@ def build_datasets(species, group, type_name): this_dataset_info.append(str(info)) datasets.append(this_dataset_info) - return datasets \ No newline at end of file + return datasets -- cgit v1.2.3 From 61634e932f8e03f5f53a94a56ff23f50dba891d2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 3 Aug 2020 11:24:40 +0300 Subject: Add test for "build_types" * wqflask/tests/api/test_gen_menu.py: New test --- wqflask/tests/api/test_gen_menu.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py index ec380b6f..24899765 100644 --- a/wqflask/tests/api/test_gen_menu.py +++ b/wqflask/tests/api/test_gen_menu.py @@ -7,6 +7,7 @@ from wqflask.api.gen_menu import get_groups from wqflask.api.gen_menu import phenotypes_exist from wqflask.api.gen_menu import genotypes_exist from wqflask.api.gen_menu import build_datasets +from wqflask.api.gen_menu import build_types class TestGenMenu(unittest.TestCase): @@ -177,3 +178,29 @@ class TestGenMenu(unittest.TestCase): "Tissue.Name = 'mRNA' AND ProbeFreeze.TissueId = " + "Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id " + "ORDER BY ProbeSetFreeze.CreateTime DESC") + + @mock.patch('wqflask.api.gen_menu.build_datasets') + @mock.patch('wqflask.api.gen_menu.g') + def test_build_types(self, db_mock, datasets_mock): + """Test that correct tissue metadata is returned""" + datasets_mock.return_value = [ + ["112", 'HC_M2_0606_P', "Hippocampus Consortium M430v2 (Jun06) PDNN"] + ] + db_mock.db.execute.return_value.fetchall.return_value = ( + ('Mouse Tissue'), ('Human Tissue'), ('Rat Tissue') + ) + self.assertEqual(build_types('mouse', 'random group'), + [['M', 'M', 'Molecular Trait Datasets'], + ['H', 'H', 'Molecular Trait Datasets'], + ['R', 'R', 'Molecular Trait Datasets']]) + db_mock.db.execute.assert_called_once_with( + "SELECT DISTINCT Tissue.Name " + + "FROM ProbeFreeze, ProbeSetFreeze, InbredSet, " + + "Tissue, Species WHERE Species.Name = 'mouse' " + + "AND Species.Id = InbredSet.SpeciesId AND " + + "InbredSet.Name = 'random group' AND " + + "ProbeFreeze.TissueId = Tissue.Id AND " + + "ProbeFreeze.InbredSetId = InbredSet.Id AND " + + "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + + "ORDER BY Tissue.Name" + ) -- cgit v1.2.3 From bde13316e67bb56eb4a00b365ebb343ab6d00274 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 3 Aug 2020 13:10:31 +0300 Subject: Add tests for "get_types" * wqflask/tests/api/test_gen_menu.py: Add new tests --- wqflask/tests/api/test_gen_menu.py | 105 +++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py index 24899765..0379e297 100644 --- a/wqflask/tests/api/test_gen_menu.py +++ b/wqflask/tests/api/test_gen_menu.py @@ -4,6 +4,7 @@ import mock from wqflask.api.gen_menu import get_species from wqflask.api.gen_menu import get_groups +from wqflask.api.gen_menu import get_types from wqflask.api.gen_menu import phenotypes_exist from wqflask.api.gen_menu import genotypes_exist from wqflask.api.gen_menu import build_datasets @@ -204,3 +205,107 @@ class TestGenMenu(unittest.TestCase): "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "ORDER BY Tissue.Name" ) + + @mock.patch('wqflask.api.gen_menu.build_types') + @mock.patch('wqflask.api.gen_menu.genotypes_exist') + @mock.patch('wqflask.api.gen_menu.phenotypes_exist') + def test_get_types_with_existing_genotype_and_phenotypes( + self, + phenotypes_exist_mock, + genotypes_exist_mock, + build_types_mock): + """Test that build types are constructed correctly if phenotypes and genotypes + exist + + """ + phenotypes_exist_mock.return_value = True + genotypes_exist_mock.return_value = True + + expected_result = { + 'mouse': { + 'H_T2': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']], + 'H_T1': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']] + }, + 'human': { + 'HLC': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']], + 'BXD': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']] + } + } + + build_types_mock.return_value = [ + ['M', 'M', 'Molecular Trait Datasets'] + ] + self.assertEqual(get_types(self.test_group), expected_result) + + @mock.patch('wqflask.api.gen_menu.build_types') + @mock.patch('wqflask.api.gen_menu.genotypes_exist') + @mock.patch('wqflask.api.gen_menu.phenotypes_exist') + def test_get_types_with_buildtype_and_non_existent_genotype_and_phenotypes( + self, + phenotypes_exist_mock, + genotypes_exist_mock, + build_types_mock): + """Test that build types are constructed correctly if phenotypes_exist and + genotypes_exist are false but build_type is falsy + + """ + phenotypes_exist_mock.return_value = False + genotypes_exist_mock.return_value = False + + build_types_mock.return_value = [] + self.assertEqual(get_types(self.test_group), { + 'mouse': {}, + 'human': {} + }) + + @mock.patch('wqflask.api.gen_menu.build_types') + @mock.patch('wqflask.api.gen_menu.genotypes_exist') + @mock.patch('wqflask.api.gen_menu.phenotypes_exist') + def test_get_types_with_non_existent_genotype_phenotypes_and_buildtype( + self, + phenotypes_exist_mock, + genotypes_exist_mock, + build_types_mock): + """Test that build types are constructed correctly if phenotypes_exist, + genotypes_exist and build_types are truthy + + """ + phenotypes_exist_mock.return_value = False + genotypes_exist_mock.return_value = False + + build_types_mock.return_value = [ + ['M', 'M', 'Molecular Trait Datasets'] + ] + expected_result = { + 'mouse': { + 'H_T2': [['M', 'M', 'Molecular Trait Datasets']], + 'H_T1': [['M', 'M', 'Molecular Trait Datasets']]}, + 'human': { + 'HLC': [['M', 'M', 'Molecular Trait Datasets']], + 'BXD': [['M', 'M', 'Molecular Trait Datasets']]}} + self.assertEqual(get_types(self.test_group), + expected_result) -- cgit v1.2.3 From b4a6a2cdc523d09334a33ca9a4efe7bc0e77d3de Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 3 Aug 2020 13:14:39 +0300 Subject: Remove unreached "else" branch * wqflask/wqflask/api/gen_menu.py(get_types): - Remove unnecessary comment. - Remove unreachable "else" branch. --- wqflask/wqflask/api/gen_menu.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/wqflask/wqflask/api/gen_menu.py b/wqflask/wqflask/api/gen_menu.py index d265a8ee..e2d8d88a 100644 --- a/wqflask/wqflask/api/gen_menu.py +++ b/wqflask/wqflask/api/gen_menu.py @@ -76,11 +76,7 @@ def get_types(groups): types_list = build_types(species, group_name) if len(types_list) > 0: types[species][group_name] += types_list - else: - if not phenotypes_exist(group_name) and not genotypes_exist(group_name): - types[species].pop(group_name, None) - groups[species] = list(group for group in groups[species] if group[0] != group_name) - else: #ZS: This whole else statement might be unnecessary, need to check + else: types_list = build_types(species, group_name) if len(types_list) > 0: types[species][group_name] = types_list -- cgit v1.2.3 From 31e582e4d1e3fc2d36a72f9c775bde40ab61d878 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 3 Aug 2020 15:48:35 +0300 Subject: Update how boolean is returned * wqflask/wqflask/api/gen_menu.py (get_datasets): convert return value directly using "bool" to be more general and cater for cases wher "None" is returned --- wqflask/wqflask/api/gen_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/api/gen_menu.py b/wqflask/wqflask/api/gen_menu.py index e2d8d88a..82c5d9be 100644 --- a/wqflask/wqflask/api/gen_menu.py +++ b/wqflask/wqflask/api/gen_menu.py @@ -134,7 +134,7 @@ def get_datasets(types): datasets[species][group] = {} for type_name in type_list: these_datasets = build_datasets(species, group, type_name[0]) - if len(these_datasets) > 0: + if bool(these_datasets): datasets[species][group][type_name[0]] = these_datasets return datasets -- cgit v1.2.3 From 899cf35a1dce7ad18645d00238fb39917f8e6e4f Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 3 Aug 2020 15:55:32 +0300 Subject: Add tests for "get_datasets" * wqflask/tests/api/test_gen_menu.py: Add new tests --- wqflask/tests/api/test_gen_menu.py | 74 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py index 0379e297..a606a300 100644 --- a/wqflask/tests/api/test_gen_menu.py +++ b/wqflask/tests/api/test_gen_menu.py @@ -5,6 +5,7 @@ import mock from wqflask.api.gen_menu import get_species from wqflask.api.gen_menu import get_groups from wqflask.api.gen_menu import get_types +from wqflask.api.gen_menu import get_datasets from wqflask.api.gen_menu import phenotypes_exist from wqflask.api.gen_menu import genotypes_exist from wqflask.api.gen_menu import build_datasets @@ -30,6 +31,41 @@ class TestGenMenu(unittest.TestCase): ] } + self.test_type = { + 'mouse': { + 'H_T2': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']], + 'H_T1': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']] + }, + 'human': { + 'HLC': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']], + 'BXD': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']] + } + } + @mock.patch('wqflask.api.gen_menu.g') def test_get_species(self, db_mock): """Test that assertion is raised when dataset and dataset_name are defined""" @@ -309,3 +345,41 @@ class TestGenMenu(unittest.TestCase): 'BXD': [['M', 'M', 'Molecular Trait Datasets']]}} self.assertEqual(get_types(self.test_group), expected_result) + + @mock.patch('wqflask.api.gen_menu.build_datasets') + def test_get_datasets_with_existent_datasets(self, + build_datasets_mock): + """Test correct dataset is returned with existent build_datasets""" + build_datasets_mock.return_value = "Test" + expected_result = { + 'mouse': { + 'H_T2': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}, + 'H_T1': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}}, + 'human': {'HLC': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}, + 'BXD': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}}} + self.maxDiff = None + self.assertEqual(get_datasets(self.test_type), + expected_result) + + @mock.patch('wqflask.api.gen_menu.build_datasets') + def test_get_datasets_with_non_existent_datasets(self, + build_datasets_mock): + """Test correct dataset is returned with non-existent build_datasets""" + build_datasets_mock.return_value = None + expected_result = { + 'mouse': { + 'H_T2': {}, + 'H_T1': {}}, + 'human': {'HLC': {}, + 'BXD': {}}} + self.assertEqual(get_datasets(self.test_type), + expected_result) + -- cgit v1.2.3 From 452f5442fec13afce07997494adc46fb4bbee9d7 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 3 Aug 2020 16:07:08 +0300 Subject: Add test for "gen_dropdown_json" * wqflask/tests/api/test_gen_menu.py: Add new test --- wqflask/tests/api/test_gen_menu.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py index a606a300..92485d38 100644 --- a/wqflask/tests/api/test_gen_menu.py +++ b/wqflask/tests/api/test_gen_menu.py @@ -2,6 +2,7 @@ import unittest import mock +from wqflask.api.gen_menu import gen_dropdown_json from wqflask.api.gen_menu import get_species from wqflask.api.gen_menu import get_groups from wqflask.api.gen_menu import get_types @@ -383,3 +384,31 @@ class TestGenMenu(unittest.TestCase): self.assertEqual(get_datasets(self.test_type), expected_result) + @mock.patch('wqflask.api.gen_menu.get_datasets') + @mock.patch('wqflask.api.gen_menu.get_types') + @mock.patch('wqflask.api.gen_menu.get_groups') + @mock.patch('wqflask.api.gen_menu.get_species') + def test_gen_dropdown_json(self, + species_mock, + groups_mock, + types_mock, + datasets_mock): + "Test that the correct dictionary is constructed properly" + species_mock.return_value = ("speciesA speciesB speciesC speciesD" + .split(" ")) + datasets_mock.return_value = ("datasetA datasetB datasetC datasetD" + .split(" ")) + groups_mock.return_value = ("groupA groupB groupC groupD" + .split(" ")) + types_mock.return_value = ("typeA typeB typeC typeD" + .split(" ")) + datasets_mock.return_value = ("datasetA datasetB datasetC datasetD" + .split(" ")) + + expected_result = { + 'datasets': ['datasetA', 'datasetB', 'datasetC', 'datasetD'], + 'types': ['typeA', 'typeB', 'typeC', 'typeD'], + 'groups': ['groupA', 'groupB', 'groupC', 'groupD'], + 'species': ['speciesA', 'speciesB', 'speciesC', 'speciesD']} + + self.assertEqual(gen_dropdown_json(), expected_result) -- cgit v1.2.3 From 30341891c22e2161c1217f808ed05748c91036e2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 3 Aug 2020 16:46:33 +0300 Subject: Move variable to setUp * wqflask/tests/api/test_gen_menu.py: Move variable --- wqflask/tests/api/test_gen_menu.py | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py index 92485d38..ca841708 100644 --- a/wqflask/tests/api/test_gen_menu.py +++ b/wqflask/tests/api/test_gen_menu.py @@ -258,40 +258,7 @@ class TestGenMenu(unittest.TestCase): phenotypes_exist_mock.return_value = True genotypes_exist_mock.return_value = True - expected_result = { - 'mouse': { - 'H_T2': [('Phenotypes', - 'Traits and Cofactors', - 'Phenotypes'), - ('Genotypes', - 'DNA Markers and SNPs', - 'Genotypes'), - ['M', 'M', 'Molecular Trait Datasets']], - 'H_T1': [('Phenotypes', - 'Traits and Cofactors', - 'Phenotypes'), - ('Genotypes', - 'DNA Markers and SNPs', - 'Genotypes'), - ['M', 'M', 'Molecular Trait Datasets']] - }, - 'human': { - 'HLC': [('Phenotypes', - 'Traits and Cofactors', - 'Phenotypes'), - ('Genotypes', - 'DNA Markers and SNPs', - 'Genotypes'), - ['M', 'M', 'Molecular Trait Datasets']], - 'BXD': [('Phenotypes', - 'Traits and Cofactors', - 'Phenotypes'), - ('Genotypes', - 'DNA Markers and SNPs', - 'Genotypes'), - ['M', 'M', 'Molecular Trait Datasets']] - } - } + expected_result = self.test_type build_types_mock.return_value = [ ['M', 'M', 'Molecular Trait Datasets'] -- cgit v1.2.3 From 77a55f3cfe70dfbe319c28380eb16a4f9516366c Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 3 Aug 2020 16:47:42 +0300 Subject: Add extra test for "build_datasets" * wqflask/tests/api/test_gen_menu.py: Add test. --- wqflask/tests/api/test_gen_menu.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py index ca841708..4a928d12 100644 --- a/wqflask/tests/api/test_gen_menu.py +++ b/wqflask/tests/api/test_gen_menu.py @@ -178,6 +178,25 @@ class TestGenMenu(unittest.TestCase): self.assertEqual(build_datasets("Mouse", "MDP", "Phenotypes"), [["None", "BXDPublish", "Mouse Phenome Database"]]) + @mock.patch('wqflask.api.gen_menu.g') + def test_build_datasets_with_type_phenotypes_and_no_results(self, db_mock): + """Test that correct dataset is returned for a phenotype type with no + results + + """ + db_mock.db.execute.return_value.fetchall.return_value = None + db_mock.db.execute.return_value.fetchone.return_value = (121, + "text value") + self.assertEqual(build_datasets("Mouse", "BXD", "Phenotypes"), + [["None", "121", "text value"]]) + db_mock.db.execute.assert_called_with( + "SELECT PublishFreeze.Name, PublishFreeze.FullName " + "FROM PublishFreeze, InbredSet " + "WHERE InbredSet.Name = 'BXD' AND " + "PublishFreeze.InbredSetId = InbredSet.Id " + "ORDER BY PublishFreeze.CreateTime ASC" + ) + @mock.patch('wqflask.api.gen_menu.g') def test_build_datasets_with_type_genotypes(self, db_mock): """Test that correct dataset is returned for a phenotype type""" -- cgit v1.2.3 From 8fa2d7cc9d3d6599d1c44a6f5e76decf932b2cbd Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 3 Aug 2020 16:54:57 +0300 Subject: Fix some errors generated by running pylint * wqflask/wqflask/api/gen_menu.py: Apply pylint. * wqflask/tests/api/test_gen_menu.py: Apply pylint. --- wqflask/tests/api/test_gen_menu.py | 33 +++++++++------ wqflask/wqflask/api/gen_menu.py | 83 +++++++++++++++++++++----------------- 2 files changed, 66 insertions(+), 50 deletions(-) diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py index 4a928d12..79c77fec 100644 --- a/wqflask/tests/api/test_gen_menu.py +++ b/wqflask/tests/api/test_gen_menu.py @@ -69,9 +69,11 @@ class TestGenMenu(unittest.TestCase): @mock.patch('wqflask.api.gen_menu.g') def test_get_species(self, db_mock): - """Test that assertion is raised when dataset and dataset_name are defined""" - db_mock.db.execute.return_value.fetchall.return_value = (('human', 'Human'), - ('mouse', 'Mouse')) + """Test that assertion is raised when dataset and dataset_name + are defined""" + db_mock.db.execute.return_value.fetchall.return_value = ( + ('human', 'Human'), + ('mouse', 'Mouse')) self.assertEqual(get_species(), [['human', 'Human'], ['mouse', 'Mouse']]) db_mock.db.execute.assert_called_once_with( @@ -84,7 +86,8 @@ class TestGenMenu(unittest.TestCase): db_mock.db.execute.return_value.fetchall.side_effect = [ # Mouse (('BXD', 'BXD', None), - ('HLC', 'Liver: Normal Gene Expression with Genotypes (Merck)', 'Test')), + ('HLC', 'Liver: Normal Gene Expression with Genotypes (Merck)', + 'Test')), # Human (('H_T1', "H_T", "DescriptionA"), ('H_T2', "H_T'", None)) @@ -111,12 +114,14 @@ class TestGenMenu(unittest.TestCase): db_mock.db.execute.return_value.fetchone.return_value = None phenotypes_exist("test") db_mock.db.execute.assert_called_with( - "SELECT Name FROM PublishFreeze WHERE PublishFreeze.Name = 'testPublish'" + "SELECT Name FROM PublishFreeze " + "WHERE PublishFreeze.Name = 'testPublish'" ) @mock.patch('wqflask.api.gen_menu.g') def test_phenotypes_exist_with_falsy_values(self, db_mock): - """Test that phenotype check returns correctly when given a None value""" + """Test that phenotype check returns correctly when given + a None value""" for x in [None, False, (), [], ""]: db_mock.db.execute.return_value.fetchone.return_value = x self.assertFalse(phenotypes_exist("test")) @@ -139,7 +144,8 @@ class TestGenMenu(unittest.TestCase): @mock.patch('wqflask.api.gen_menu.g') def test_genotypes_exist_with_falsy_values(self, db_mock): - """Test that genotype check returns correctly when given a None value""" + """Test that genotype check returns correctly when given + a None value""" for x in [None, False, (), [], ""]: db_mock.db.execute.return_value.fetchone.return_value = x self.assertFalse(genotypes_exist("test")) @@ -151,7 +157,6 @@ class TestGenMenu(unittest.TestCase): db_mock.db.execute.return_value.fetchone.return_value = (x) self.assertTrue(phenotypes_exist("test")) - @mock.patch('wqflask.api.gen_menu.g') def test_build_datasets_with_type_phenotypes(self, db_mock): """Test that correct dataset is returned for a phenotype type""" @@ -207,8 +212,9 @@ class TestGenMenu(unittest.TestCase): self.assertEqual(build_datasets("Mouse", "HLC", "Genotypes"), [["635", "HLCGeno", "HLC Genotypes"]]) db_mock.db.execute.assert_called_with( - "SELECT InfoFiles.GN_AccesionId FROM InfoFiles, GenoFreeze, InbredSet " + - "WHERE InbredSet.Name = 'HLC' AND GenoFreeze.InbredSetId = InbredSet.Id AND " + + "SELECT InfoFiles.GN_AccesionId FROM InfoFiles, " + "GenoFreeze, InbredSet WHERE InbredSet.Name = 'HLC' AND " + "GenoFreeze.InbredSetId = InbredSet.Id AND " "InfoFiles.InfoPageName = GenoFreeze.ShortName " + "ORDER BY GenoFreeze.CreateTime DESC" ) @@ -218,7 +224,8 @@ class TestGenMenu(unittest.TestCase): @mock.patch('wqflask.api.gen_menu.g') def test_build_datasets_with_type_mrna(self, db_mock): - """Test that correct dataset is returned for a mRNA expression/ Probeset""" + """Test that correct dataset is returned for a mRNA + expression/ Probeset""" db_mock.db.execute.return_value.fetchall.return_value = ( (112, "HC_M2_0606_P", "Hippocampus Consortium M430v2 (Jun06) PDNN"), ) @@ -241,7 +248,8 @@ class TestGenMenu(unittest.TestCase): def test_build_types(self, db_mock, datasets_mock): """Test that correct tissue metadata is returned""" datasets_mock.return_value = [ - ["112", 'HC_M2_0606_P', "Hippocampus Consortium M430v2 (Jun06) PDNN"] + ["112", 'HC_M2_0606_P', + "Hippocampus Consortium M430v2 (Jun06) PDNN"] ] db_mock.db.execute.return_value.fetchall.return_value = ( ('Mouse Tissue'), ('Human Tissue'), ('Rat Tissue') @@ -352,7 +360,6 @@ class TestGenMenu(unittest.TestCase): 'BXD': {'Genotypes': 'Test', 'M': 'Test', 'Phenotypes': 'Test'}}} - self.maxDiff = None self.assertEqual(get_datasets(self.test_type), expected_result) diff --git a/wqflask/wqflask/api/gen_menu.py b/wqflask/wqflask/api/gen_menu.py index 82c5d9be..45814ed9 100644 --- a/wqflask/wqflask/api/gen_menu.py +++ b/wqflask/wqflask/api/gen_menu.py @@ -1,21 +1,12 @@ from __future__ import print_function, division -import sys - from flask import g -from utility.tools import locate, locate_ignore_error, TEMPDIR, SQL_URI -from utility.benchmark import Bench - -import MySQLdb - -import urlparse - -import utility.logger -logger = utility.logger.getLogger(__name__ ) def gen_dropdown_json(): - """Generates and outputs (as json file) the data for the main dropdown menus on the home page""" + """Generates and outputs (as json file) the data for the main dropdown menus on + the home page + """ species = get_species() groups = get_groups(species) @@ -29,9 +20,11 @@ def gen_dropdown_json(): return data + def get_species(): """Build species list""" - results = g.db.execute("SELECT Name, MenuName FROM Species ORDER BY OrderId").fetchall() + results = g.db.execute( + "SELECT Name, MenuName FROM Species ORDER BY OrderId").fetchall() species = [] for result in results: @@ -39,6 +32,7 @@ def get_species(): return species + def get_groups(species): """Build groups list""" groups = {} @@ -46,18 +40,23 @@ def get_groups(species): groups[species_name] = [] results = g.db.execute( - ("SELECT InbredSet.Name, InbredSet.FullName, IFNULL(InbredSet.Family, 'None') " + - "FROM InbredSet, Species WHERE Species.Name = '{}' AND InbredSet.SpeciesId = " + - "Species.Id GROUP by InbredSet.Name ORDER BY IFNULL(InbredSet.FamilyOrder, " + - "InbredSet.FullName) ASC, IFNULL(InbredSet.Family, InbredSet.FullName) ASC, " + - "InbredSet.FullName ASC, InbredSet.MenuOrderId ASC").format(species_name)).fetchall() + ("SELECT InbredSet.Name, InbredSet.FullName, " + "IFNULL(InbredSet.Family, 'None') " + "FROM InbredSet, Species WHERE Species.Name = '{}' " + "AND InbredSet.SpeciesId = Species.Id GROUP by InbredSet.Name " + "ORDER BY IFNULL(InbredSet.FamilyOrder, InbredSet.FullName) " + "ASC, IFNULL(InbredSet.Family, InbredSet.FullName) ASC, " + "InbredSet.FullName ASC, InbredSet.MenuOrderId ASC") + .format(species_name)).fetchall() for result in results: family_name = "Family:" + str(result[2]) - groups[species_name].append([str(result[0]), str(result[1]), family_name]) + groups[species_name].append( + [str(result[0]), str(result[1]), family_name]) return groups + def get_types(groups): """Build types list""" types = {} @@ -66,12 +65,15 @@ def get_types(groups): types[species] = {} for group_name, _group_full_name, _family_name in group_dict: if phenotypes_exist(group_name): - types[species][group_name] = [("Phenotypes", "Traits and Cofactors", "Phenotypes")] + types[species][group_name] = [ + ("Phenotypes", "Traits and Cofactors", "Phenotypes")] if genotypes_exist(group_name): if group_name in types[species]: - types[species][group_name] += [("Genotypes", "DNA Markers and SNPs", "Genotypes")] + types[species][group_name] += [ + ("Genotypes", "DNA Markers and SNPs", "Genotypes")] else: - types[species][group_name] = [("Genotypes", "DNA Markers and SNPs", "Genotypes")] + types[species][group_name] = [ + ("Genotypes", "DNA Markers and SNPs", "Genotypes")] if group_name in types[species]: types_list = build_types(species, group_name) if len(types_list) > 0: @@ -82,13 +84,17 @@ def get_types(groups): types[species][group_name] = types_list else: types[species].pop(group_name, None) - groups[species] = list(group for group in groups[species] if group[0] != group_name) + groups[species] = list( + group for group in groups[species] + if group[0] != group_name) return types + def phenotypes_exist(group_name): results = g.db.execute( - ("SELECT Name FROM PublishFreeze " + - "WHERE PublishFreeze.Name = '{}'").format(group_name+"Publish")).fetchone() + ("SELECT Name FROM PublishFreeze " + "WHERE PublishFreeze.Name = " + "'{}'").format(group_name+"Publish")).fetchone() return bool(results) @@ -118,13 +124,15 @@ def build_types(species, group): results = [] for result in g.db.execute(query).fetchall(): - if len(result): + if bool(result): these_datasets = build_datasets(species, group, result[0]) if len(these_datasets) > 0: - results.append([str(result[0]), str(result[0]), "Molecular Trait Datasets"]) + results.append([str(result[0]), str(result[0]), + "Molecular Trait Datasets"]) return results + def get_datasets(types): """Build datasets list""" datasets = {} @@ -192,17 +200,18 @@ def build_datasets(species, group, type_name): dataset_text = "%s Genotypes" % group datasets.append([dataset_id, dataset_value, dataset_text]) - else: # for mRNA expression/ProbeSet + else: # for mRNA expression/ProbeSet results = g.db.execute( - ("SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, " + - "ProbeSetFreeze.FullName FROM ProbeSetFreeze, " + - "ProbeFreeze, InbredSet, Tissue, Species WHERE " + - "Species.Name = '{0}' AND Species.Id = " + - "InbredSet.SpeciesId AND InbredSet.Name = '{1}' " + - "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + - "and Tissue.Name = '{2}' AND ProbeFreeze.TissueId = " + - "Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id " + - "ORDER BY ProbeSetFreeze.CreateTime DESC").format(species, group, type_name)).fetchall() + ("SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, " + "ProbeSetFreeze.FullName FROM ProbeSetFreeze, " + "ProbeFreeze, InbredSet, Tissue, Species WHERE " + "Species.Name = '{0}' AND Species.Id = " + "InbredSet.SpeciesId AND InbredSet.Name = '{1}' " + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + "and Tissue.Name = '{2}' AND ProbeFreeze.TissueId = " + "Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id " + "ORDER BY ProbeSetFreeze.CreateTime " + "DESC").format(species, group, type_name)).fetchall() datasets = [] for dataset_info in results: -- cgit v1.2.3 From 549726ec3c4c176f855b65ade299701d1865b261 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 3 Aug 2020 17:11:06 +0300 Subject: Remove "+" when concatenating strings * wqflask/wqflask/api/gen_menu.py (build_types, build_datasets): Replace '+' when joining strings inside brackets --- wqflask/wqflask/api/gen_menu.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/wqflask/wqflask/api/gen_menu.py b/wqflask/wqflask/api/gen_menu.py index 45814ed9..cc11e14b 100644 --- a/wqflask/wqflask/api/gen_menu.py +++ b/wqflask/wqflask/api/gen_menu.py @@ -113,13 +113,13 @@ def build_types(species, group): """ - query = ("SELECT DISTINCT Tissue.Name " + - "FROM ProbeFreeze, ProbeSetFreeze, InbredSet, " + - "Tissue, Species WHERE Species.Name = '{0}' " + - "AND Species.Id = InbredSet.SpeciesId AND " + - "InbredSet.Name = '{1}' AND ProbeFreeze.TissueId = " + - "Tissue.Id AND ProbeFreeze.InbredSetId = InbredSet.Id " + - "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + + query = ("SELECT DISTINCT Tissue.Name " + "FROM ProbeFreeze, ProbeSetFreeze, InbredSet, " + "Tissue, Species WHERE Species.Name = '{0}' " + "AND Species.Id = InbredSet.SpeciesId AND " + "InbredSet.Name = '{1}' AND ProbeFreeze.TissueId = " + "Tissue.Id AND ProbeFreeze.InbredSetId = InbredSet.Id " + "AND ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " "ORDER BY Tissue.Name").format(species, group) results = [] @@ -154,11 +154,11 @@ def build_datasets(species, group, type_name): datasets = [] if type_name == "Phenotypes": results = g.db.execute( - ("SELECT InfoFiles.GN_AccesionId, PublishFreeze.Name, " + - "PublishFreeze.FullName FROM InfoFiles, PublishFreeze, " + - "InbredSet WHERE InbredSet.Name = '{}' AND " + - "PublishFreeze.InbredSetId = InbredSet.Id AND " + - "InfoFiles.InfoPageName = PublishFreeze.Name " + + ("SELECT InfoFiles.GN_AccesionId, PublishFreeze.Name, " + "PublishFreeze.FullName FROM InfoFiles, PublishFreeze, " + "InbredSet WHERE InbredSet.Name = '{}' AND " + "PublishFreeze.InbredSetId = InbredSet.Id AND " + "InfoFiles.InfoPageName = PublishFreeze.Name " "ORDER BY PublishFreeze.CreateTime ASC").format(group)).fetchall() if bool(results): for result in results: -- cgit v1.2.3 From 497a64f4c24644217fe74178976ba5cb9e675308 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 3 Aug 2020 11:05:27 -0500 Subject: Fixed issue where DataTables was initialized twice when selecting cofactors, leading to an error in non-Chrome browsers * wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js - Added a line that checks if the table already exists before initializing it; this issue is caused by a circular import issue, but I'm not sure how to get around it yet --- .../javascript/get_covariates_from_collection.js | 48 +++++++++++----------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js b/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js index 934cc14d..a8a3041d 100644 --- a/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js +++ b/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js @@ -37,29 +37,31 @@ $('#trait_table').dataTable( { "orderClasses": true } ); -$('#collection_table').dataTable( { - "createdRow": function ( row, data, index ) { - if ($('td', row).eq(2).text().length > 40) { - $('td', row).eq(2).text($('td', row).eq(2).text().substring(0, 40)); - $('td', row).eq(2).text($('td', row).eq(2).text() + '...') - } - if ($('td', row).eq(4).text().length > 50) { - $('td', row).eq(4).text($('td', row).eq(4).text().substring(0, 50)); - $('td', row).eq(4).text($('td', row).eq(4).text() + '...') - } - }, - "columnDefs": [ { - "targets": 0, - "orderable": false - } ], - "order": [[1, "asc" ]], - "sDom": "ZRtr", - "iDisplayLength": -1, - "autoWidth": true, - "bSortClasses": false, - "paging": false, - "orderClasses": true -} ); +if ( ! $.fn.DataTable.isDataTable( '#collection_table' ) ) { + $('#collection_table').dataTable( { + "createdRow": function ( row, data, index ) { + if ($('td', row).eq(2).text().length > 40) { + $('td', row).eq(2).text($('td', row).eq(2).text().substring(0, 40)); + $('td', row).eq(2).text($('td', row).eq(2).text() + '...') + } + if ($('td', row).eq(4).text().length > 50) { + $('td', row).eq(4).text($('td', row).eq(4).text().substring(0, 50)); + $('td', row).eq(4).text($('td', row).eq(4).text() + '...') + } + }, + "columnDefs": [ { + "targets": 0, + "orderable": false + } ], + "order": [[1, "asc" ]], + "sDom": "ZRtr", + "iDisplayLength": -1, + "autoWidth": true, + "bSortClasses": false, + "paging": false, + "orderClasses": true + } ); +} collection_click = function() { var this_collection_url; -- cgit v1.2.3 From b950eb2f79632ca433b76dac8f063847266c54fc Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 3 Aug 2020 11:23:54 -0500 Subject: Fixed table width issue when there's an N column * wqflask/wqflask/show_trait/show_trait.py - Just added more pixels in the function which determines the overall table width --- wqflask/wqflask/show_trait/show_trait.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index 5fc69cab..f188fd9d 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -546,9 +546,9 @@ def get_table_widths(sample_groups, has_num_cases=False): trait_table_width = 380 if sample_groups[0].se_exists(): - trait_table_width += 70 + trait_table_width += 80 if has_num_cases: - trait_table_width += 30 + trait_table_width += 80 trait_table_width += len(sample_groups[0].attributes)*70 trait_table_width = str(trait_table_width) + "px" -- cgit v1.2.3 From 7884399b779e839f87a2667591aebf7d293b8f33 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 4 Aug 2020 12:37:45 -0500 Subject: Reduced table cell margin a bit to make cell contents better align with header * wqflask/wqflask/static/new/css/show_trait.css - Changed right margin from 20px to 15px to better account for the width of the sort arrows in the header cell --- wqflask/wqflask/static/new/css/show_trait.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/static/new/css/show_trait.css b/wqflask/wqflask/static/new/css/show_trait.css index d3e6672a..338302af 100644 --- a/wqflask/wqflask/static/new/css/show_trait.css +++ b/wqflask/wqflask/static/new/css/show_trait.css @@ -36,7 +36,7 @@ table.dataTable thead th, table.dataTable tfoot { } table.dataTable tbody td { - padding: 4px 20px 2px 10px; + padding: 4px 15px 2px 10px; } table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { -- cgit v1.2.3 From db0f9ebcb3b9bed2f8dfe2f9a2370dc1a86204b3 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 4 Aug 2020 12:40:31 -0500 Subject: Simplified logic in the @before_request that checks user permissions * wqflask/wqflask/views.py - Removed an if statement that was unnecessary, because it already checks if dataset is either the string "Temp" or of type "Temp" --- wqflask/wqflask/views.py | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 57183eed..dde22bf7 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -61,7 +61,7 @@ from utility.authentication_tools import check_resource_availability from utility.redis_tools import get_redis_conn Redis = get_redis_conn() -from base.webqtlConfig import GENERATED_IMAGE_DIR +from base.webqtlConfig import GENERATED_IMAGE_DIR, DEFAULT_PRIVILEGES from utility.benchmark import Bench from pprint import pformat as pf @@ -91,27 +91,20 @@ def connect_db(): def check_access_permissions(): logger.debug("@app.before_request check_access_permissions") available = True - if "temp_trait" in request.args: - if request.args['temp_trait'] == "True": - pass - else: - if 'dataset' in request.args: - if request.args['dataset'] == "Temp": - permissions = check_resource_availability("Temp") - else: - dataset = create_dataset(request.args['dataset']) - - if dataset.type == "Temp": - permissions = False - if 'trait_id' in request.args: - permissions = check_resource_availability(dataset, request.args['trait_id']) - elif dataset.type != "Publish": - permissions = check_resource_availability(dataset) - else: - return None - - if 'view' not in permissions['data']: - return redirect(url_for("no_access_page")) + if 'dataset' in request.args: + permissions = DEFAULT_PRIVILEGES + if request.args['dataset'] != "Temp": + dataset = create_dataset(request.args['dataset']) + + if dataset.type == "Temp": + permissions = DEFAULT_PRIVILEGES + elif 'trait_id' in request.args: + permissions = check_resource_availability(dataset, request.args['trait_id']) + elif dataset.type != "Publish": + permissions = check_resource_availability(dataset) + + if 'view' not in permissions['data']: + return redirect(url_for("no_access_page")) @app.teardown_appcontext def shutdown_session(exception=None): -- cgit v1.2.3 From 1e421a063d750df485a768aa6da14b3db592d409 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 5 Aug 2020 17:56:07 -0500 Subject: Set maximum width for trait page details in order to avoid long summaries looking strange * wqflask/wqflask/templates/show_trait_details.html - Set min-width of table containing details to 1400px --- wqflask/wqflask/templates/show_trait_details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html index 58353f05..8b3e4907 100644 --- a/wqflask/wqflask/templates/show_trait_details.html +++ b/wqflask/wqflask/templates/show_trait_details.html @@ -1,4 +1,4 @@ - +
    -- cgit v1.2.3 From 85a384dc779a49f02ccee3088356e4873eacbd9a Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 6 Aug 2020 12:29:13 -0500 Subject: Made a change that should fix the issue with the Y axis ticks extending too high in mapping results * wqflask/wqflask/marker_regression/display_mapping_results.py - When in ful genome view, I changed it to subject the yTopOffset when drawing the Y axis; single chromosome view seems to work okay so it's conditional on that --- .../wqflask/marker_regression/display_mapping_results.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 9ac4946b..75c1a8cd 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -1737,9 +1737,17 @@ class DisplayMappingResults(object): LRSLODFont=pid.Font(ttf="verdana", size=18*zoom*1.5, bold=0) yZero = yTopOffset + plotHeight - LRSHeightThresh = drawAreaHeight - AdditiveHeightThresh = drawAreaHeight/2 - DominanceHeightThresh = drawAreaHeight/2 + # LRSHeightThresh = drawAreaHeight + # AdditiveHeightThresh = drawAreaHeight/2 + # DominanceHeightThresh = drawAreaHeight/2 + if self.selectedChr == 1: + LRSHeightThresh = drawAreaHeight - yTopOffset + 30*(zoom - 1) + AdditiveHeightThresh = LRSHeightThresh/2 + DominanceHeightThresh = LRSHeightThresh/2 + else: + LRSHeightThresh = drawAreaHeight + AdditiveHeightThresh = drawAreaHeight/2 + DominanceHeightThresh = drawAreaHeight/2 # LRSHeightThresh = (yZero - yTopOffset + 30*(zoom - 1)) # AdditiveHeightThresh = LRSHeightThresh/2 # DominanceHeightThresh = LRSHeightThresh/2 @@ -1900,7 +1908,7 @@ class DisplayMappingResults(object): startPosX += newStartPosX oldStartPosX = newStartPosX - #ZS: This is beause the chromosome value stored in qtlresult['chr'] can be (for example) either X or 20 depending upon the mapping method/scale used + #ZS: This is because the chromosome value stored in qtlresult['chr'] can be (for example) either X or 20 depending upon the mapping method/scale used this_chr = str(self.ChrList[self.selectedChr][0]) if self.plotScale != "physic": this_chr = str(self.ChrList[self.selectedChr][1]+1) -- cgit v1.2.3 From 39f2d8b8599a8daae2c55c3a2bf48366164c6ca2 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 6 Aug 2020 13:54:18 -0500 Subject: Added endpoint for changing some user details * wqflask/wqflask/user_session.py - Added endpoint for /manage/user that lets the user change their full_name or organization. Still need to add something that lets them change their e-mail, since that will require a confirmation --- wqflask/wqflask/user_session.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/user_session.py b/wqflask/wqflask/user_session.py index ba659fe5..3aa2c151 100644 --- a/wqflask/wqflask/user_session.py +++ b/wqflask/wqflask/user_session.py @@ -13,7 +13,7 @@ from wqflask import app from utility import hmac #from utility.elasticsearch_tools import get_elasticsearch_connection -from utility.redis_tools import get_redis_conn, get_user_id, get_user_collections, save_collections +from utility.redis_tools import get_redis_conn, get_user_id, get_user_by_unique_column, set_user_attribute, get_user_collections, save_collections Redis = get_redis_conn() from utility.logger import getLogger @@ -53,6 +53,18 @@ def create_signed_cookie(): logger.debug("uuid_signed:", uuid_signed) return the_uuid, uuid_signed +@app.route("/user/manage", methods=('GET','POST')) +def manage_user(): + params = request.form if request.form else request.args + if 'new_full_name' in params: + set_user_attribute(g.user_session.user_id, 'full_name', params['new_full_name']) + if 'new_organization' in params: + set_user_attribute(g.user_session.user_id, 'organization', params['new_organization']) + + user_details = get_user_by_unique_column("user_id", g.user_session.user_id) + + return render_template("admin/manage_user.html", user_details = user_details) + class UserSession(object): """Logged in user handling""" -- cgit v1.2.3 From abbceb6cde72282b8b85e9452d45e3d6ecc41da5 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 7 Aug 2020 15:07:52 +0300 Subject: Move wqflask/tests/api to wqflask/tests/wqflask * wqflask/tests(api): Move all the files here to tests/wqflask. The test dir should mirror the actual module structure --- wqflask/tests/api/__init__.py | 0 wqflask/tests/api/test_gen_menu.py | 407 ----------------------------- wqflask/tests/wqflask/__init__.py | 0 wqflask/tests/wqflask/api/__init__.py | 0 wqflask/tests/wqflask/api/test_gen_menu.py | 407 +++++++++++++++++++++++++++++ 5 files changed, 407 insertions(+), 407 deletions(-) delete mode 100644 wqflask/tests/api/__init__.py delete mode 100644 wqflask/tests/api/test_gen_menu.py create mode 100644 wqflask/tests/wqflask/__init__.py create mode 100644 wqflask/tests/wqflask/api/__init__.py create mode 100644 wqflask/tests/wqflask/api/test_gen_menu.py diff --git a/wqflask/tests/api/__init__.py b/wqflask/tests/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/wqflask/tests/api/test_gen_menu.py b/wqflask/tests/api/test_gen_menu.py deleted file mode 100644 index 79c77fec..00000000 --- a/wqflask/tests/api/test_gen_menu.py +++ /dev/null @@ -1,407 +0,0 @@ -"""Test cases for wqflask.api.gen_menu""" -import unittest -import mock - -from wqflask.api.gen_menu import gen_dropdown_json -from wqflask.api.gen_menu import get_species -from wqflask.api.gen_menu import get_groups -from wqflask.api.gen_menu import get_types -from wqflask.api.gen_menu import get_datasets -from wqflask.api.gen_menu import phenotypes_exist -from wqflask.api.gen_menu import genotypes_exist -from wqflask.api.gen_menu import build_datasets -from wqflask.api.gen_menu import build_types - - -class TestGenMenu(unittest.TestCase): - """Tests for the gen_menu module""" - - def setUp(self): - self.test_group = { - 'mouse': [ - ['H_T1', - 'H_T', - 'Family:DescriptionA' - ], - ['H_T2', "H_T'", 'Family:None'] - ], - 'human': [ - ['BXD', 'BXD', 'Family:None'], - ['HLC', 'Liver: Normal Gene Expression with Genotypes (Merck)', - 'Family:Test'] - ] - } - - self.test_type = { - 'mouse': { - 'H_T2': [('Phenotypes', - 'Traits and Cofactors', - 'Phenotypes'), - ('Genotypes', - 'DNA Markers and SNPs', - 'Genotypes'), - ['M', 'M', 'Molecular Trait Datasets']], - 'H_T1': [('Phenotypes', - 'Traits and Cofactors', - 'Phenotypes'), - ('Genotypes', - 'DNA Markers and SNPs', - 'Genotypes'), - ['M', 'M', 'Molecular Trait Datasets']] - }, - 'human': { - 'HLC': [('Phenotypes', - 'Traits and Cofactors', - 'Phenotypes'), - ('Genotypes', - 'DNA Markers and SNPs', - 'Genotypes'), - ['M', 'M', 'Molecular Trait Datasets']], - 'BXD': [('Phenotypes', - 'Traits and Cofactors', - 'Phenotypes'), - ('Genotypes', - 'DNA Markers and SNPs', - 'Genotypes'), - ['M', 'M', 'Molecular Trait Datasets']] - } - } - - @mock.patch('wqflask.api.gen_menu.g') - def test_get_species(self, db_mock): - """Test that assertion is raised when dataset and dataset_name - are defined""" - db_mock.db.execute.return_value.fetchall.return_value = ( - ('human', 'Human'), - ('mouse', 'Mouse')) - self.assertEqual(get_species(), - [['human', 'Human'], ['mouse', 'Mouse']]) - db_mock.db.execute.assert_called_once_with( - "SELECT Name, MenuName FROM Species ORDER BY OrderId" - ) - - @mock.patch('wqflask.api.gen_menu.g') - def test_get_groups(self, db_mock): - """Test that species groups are grouped correctly""" - db_mock.db.execute.return_value.fetchall.side_effect = [ - # Mouse - (('BXD', 'BXD', None), - ('HLC', 'Liver: Normal Gene Expression with Genotypes (Merck)', - 'Test')), - # Human - (('H_T1', "H_T", "DescriptionA"), - ('H_T2', "H_T'", None)) - ] - - self.assertEqual(get_groups([["human", "Human"], ["mouse", "Mouse"]]), - self.test_group) - - for name in ["mouse", "human"]: - db_mock.db.execute.assert_any_call( - ("SELECT InbredSet.Name, InbredSet.FullName, " + - "IFNULL(InbredSet.Family, 'None') " + - "FROM InbredSet, Species WHERE Species.Name " + - "= '{}' AND InbredSet.SpeciesId = Species.Id GROUP by " + - "InbredSet.Name ORDER BY IFNULL(InbredSet.FamilyOrder, " + - "InbredSet.FullName) ASC, IFNULL(InbredSet.Family, " + - "InbredSet.FullName) ASC, InbredSet.FullName ASC, " + - "InbredSet.MenuOrderId ASC").format(name) - ) - - @mock.patch('wqflask.api.gen_menu.g') - def test_phenotypes_exist_called_with_correct_query(self, db_mock): - """Test that phenotypes_exist is called with the correct query""" - db_mock.db.execute.return_value.fetchone.return_value = None - phenotypes_exist("test") - db_mock.db.execute.assert_called_with( - "SELECT Name FROM PublishFreeze " - "WHERE PublishFreeze.Name = 'testPublish'" - ) - - @mock.patch('wqflask.api.gen_menu.g') - def test_phenotypes_exist_with_falsy_values(self, db_mock): - """Test that phenotype check returns correctly when given - a None value""" - for x in [None, False, (), [], ""]: - db_mock.db.execute.return_value.fetchone.return_value = x - self.assertFalse(phenotypes_exist("test")) - - @mock.patch('wqflask.api.gen_menu.g') - def test_phenotypes_exist_with_truthy_value(self, db_mock): - """Test that phenotype check returns correctly when given Truthy """ - for x in ["x", ("result"), ["result"], [1]]: - db_mock.db.execute.return_value.fetchone.return_value = (x) - self.assertTrue(phenotypes_exist("test")) - - @mock.patch('wqflask.api.gen_menu.g') - def test_genotypes_exist_called_with_correct_query(self, db_mock): - """Test that genotypes_exist is called with the correct query""" - db_mock.db.execute.return_value.fetchone.return_value = None - genotypes_exist("test") - db_mock.db.execute.assert_called_with( - "SELECT Name FROM GenoFreeze WHERE GenoFreeze.Name = 'testGeno'" - ) - - @mock.patch('wqflask.api.gen_menu.g') - def test_genotypes_exist_with_falsy_values(self, db_mock): - """Test that genotype check returns correctly when given - a None value""" - for x in [None, False, (), [], ""]: - db_mock.db.execute.return_value.fetchone.return_value = x - self.assertFalse(genotypes_exist("test")) - - @mock.patch('wqflask.api.gen_menu.g') - def test_genotypes_exist_with_truthy_value(self, db_mock): - """Test that genotype check returns correctly when given Truthy """ - for x in ["x", ("result"), ["result"], [1]]: - db_mock.db.execute.return_value.fetchone.return_value = (x) - self.assertTrue(phenotypes_exist("test")) - - @mock.patch('wqflask.api.gen_menu.g') - def test_build_datasets_with_type_phenotypes(self, db_mock): - """Test that correct dataset is returned for a phenotype type""" - db_mock.db.execute.return_value.fetchall.return_value = ( - (602, "BXDPublish", "BXD Published Phenotypes"), - ) - self.assertEqual(build_datasets("Mouse", "BXD", "Phenotypes"), - [['602', "BXDPublish", "BXD Published Phenotypes"]]) - db_mock.db.execute.assert_called_with( - "SELECT InfoFiles.GN_AccesionId, PublishFreeze.Name, " + - "PublishFreeze.FullName FROM InfoFiles, PublishFreeze, " + - "InbredSet WHERE InbredSet.Name = 'BXD' AND " + - "PublishFreeze.InbredSetId = InbredSet.Id AND " + - "InfoFiles.InfoPageName = PublishFreeze.Name " + - "ORDER BY PublishFreeze.CreateTime ASC" - ) - self.assertEqual(build_datasets("Mouse", "MDP", "Phenotypes"), - [['602', "BXDPublish", "Mouse Phenome Database"]]) - - db_mock.db.execute.return_value.fetchall.return_value = () - db_mock.db.execute.return_value.fetchone.return_value = ( - "BXDPublish", "Mouse Phenome Database" - ) - self.assertEqual(build_datasets("Mouse", "MDP", "Phenotypes"), - [["None", "BXDPublish", "Mouse Phenome Database"]]) - - @mock.patch('wqflask.api.gen_menu.g') - def test_build_datasets_with_type_phenotypes_and_no_results(self, db_mock): - """Test that correct dataset is returned for a phenotype type with no - results - - """ - db_mock.db.execute.return_value.fetchall.return_value = None - db_mock.db.execute.return_value.fetchone.return_value = (121, - "text value") - self.assertEqual(build_datasets("Mouse", "BXD", "Phenotypes"), - [["None", "121", "text value"]]) - db_mock.db.execute.assert_called_with( - "SELECT PublishFreeze.Name, PublishFreeze.FullName " - "FROM PublishFreeze, InbredSet " - "WHERE InbredSet.Name = 'BXD' AND " - "PublishFreeze.InbredSetId = InbredSet.Id " - "ORDER BY PublishFreeze.CreateTime ASC" - ) - - @mock.patch('wqflask.api.gen_menu.g') - def test_build_datasets_with_type_genotypes(self, db_mock): - """Test that correct dataset is returned for a phenotype type""" - db_mock.db.execute.return_value.fetchone.return_value = ( - 635, "HLCPublish", "HLC Published Genotypes" - ) - - self.assertEqual(build_datasets("Mouse", "HLC", "Genotypes"), - [["635", "HLCGeno", "HLC Genotypes"]]) - db_mock.db.execute.assert_called_with( - "SELECT InfoFiles.GN_AccesionId FROM InfoFiles, " - "GenoFreeze, InbredSet WHERE InbredSet.Name = 'HLC' AND " - "GenoFreeze.InbredSetId = InbredSet.Id AND " - "InfoFiles.InfoPageName = GenoFreeze.ShortName " + - "ORDER BY GenoFreeze.CreateTime DESC" - ) - db_mock.db.execute.return_value.fetchone.return_value = () - self.assertEqual(build_datasets("Mouse", "HLC", "Genotypes"), - [["None", "HLCGeno", "HLC Genotypes"]]) - - @mock.patch('wqflask.api.gen_menu.g') - def test_build_datasets_with_type_mrna(self, db_mock): - """Test that correct dataset is returned for a mRNA - expression/ Probeset""" - db_mock.db.execute.return_value.fetchall.return_value = ( - (112, "HC_M2_0606_P", - "Hippocampus Consortium M430v2 (Jun06) PDNN"), ) - self.assertEqual(build_datasets("Mouse", "HLC", "mRNA"), [[ - "112", 'HC_M2_0606_P', "Hippocampus Consortium M430v2 (Jun06) PDNN" - ]]) - db_mock.db.execute.assert_called_once_with( - "SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, " + - "ProbeSetFreeze.FullName FROM ProbeSetFreeze, " + - "ProbeFreeze, InbredSet, Tissue, Species WHERE " + - "Species.Name = 'Mouse' AND Species.Id = " + - "InbredSet.SpeciesId AND InbredSet.Name = 'HLC' AND " + - "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id and " + - "Tissue.Name = 'mRNA' AND ProbeFreeze.TissueId = " + - "Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id " + - "ORDER BY ProbeSetFreeze.CreateTime DESC") - - @mock.patch('wqflask.api.gen_menu.build_datasets') - @mock.patch('wqflask.api.gen_menu.g') - def test_build_types(self, db_mock, datasets_mock): - """Test that correct tissue metadata is returned""" - datasets_mock.return_value = [ - ["112", 'HC_M2_0606_P', - "Hippocampus Consortium M430v2 (Jun06) PDNN"] - ] - db_mock.db.execute.return_value.fetchall.return_value = ( - ('Mouse Tissue'), ('Human Tissue'), ('Rat Tissue') - ) - self.assertEqual(build_types('mouse', 'random group'), - [['M', 'M', 'Molecular Trait Datasets'], - ['H', 'H', 'Molecular Trait Datasets'], - ['R', 'R', 'Molecular Trait Datasets']]) - db_mock.db.execute.assert_called_once_with( - "SELECT DISTINCT Tissue.Name " + - "FROM ProbeFreeze, ProbeSetFreeze, InbredSet, " + - "Tissue, Species WHERE Species.Name = 'mouse' " + - "AND Species.Id = InbredSet.SpeciesId AND " + - "InbredSet.Name = 'random group' AND " + - "ProbeFreeze.TissueId = Tissue.Id AND " + - "ProbeFreeze.InbredSetId = InbredSet.Id AND " + - "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + - "ORDER BY Tissue.Name" - ) - - @mock.patch('wqflask.api.gen_menu.build_types') - @mock.patch('wqflask.api.gen_menu.genotypes_exist') - @mock.patch('wqflask.api.gen_menu.phenotypes_exist') - def test_get_types_with_existing_genotype_and_phenotypes( - self, - phenotypes_exist_mock, - genotypes_exist_mock, - build_types_mock): - """Test that build types are constructed correctly if phenotypes and genotypes - exist - - """ - phenotypes_exist_mock.return_value = True - genotypes_exist_mock.return_value = True - - expected_result = self.test_type - - build_types_mock.return_value = [ - ['M', 'M', 'Molecular Trait Datasets'] - ] - self.assertEqual(get_types(self.test_group), expected_result) - - @mock.patch('wqflask.api.gen_menu.build_types') - @mock.patch('wqflask.api.gen_menu.genotypes_exist') - @mock.patch('wqflask.api.gen_menu.phenotypes_exist') - def test_get_types_with_buildtype_and_non_existent_genotype_and_phenotypes( - self, - phenotypes_exist_mock, - genotypes_exist_mock, - build_types_mock): - """Test that build types are constructed correctly if phenotypes_exist and - genotypes_exist are false but build_type is falsy - - """ - phenotypes_exist_mock.return_value = False - genotypes_exist_mock.return_value = False - - build_types_mock.return_value = [] - self.assertEqual(get_types(self.test_group), { - 'mouse': {}, - 'human': {} - }) - - @mock.patch('wqflask.api.gen_menu.build_types') - @mock.patch('wqflask.api.gen_menu.genotypes_exist') - @mock.patch('wqflask.api.gen_menu.phenotypes_exist') - def test_get_types_with_non_existent_genotype_phenotypes_and_buildtype( - self, - phenotypes_exist_mock, - genotypes_exist_mock, - build_types_mock): - """Test that build types are constructed correctly if phenotypes_exist, - genotypes_exist and build_types are truthy - - """ - phenotypes_exist_mock.return_value = False - genotypes_exist_mock.return_value = False - - build_types_mock.return_value = [ - ['M', 'M', 'Molecular Trait Datasets'] - ] - expected_result = { - 'mouse': { - 'H_T2': [['M', 'M', 'Molecular Trait Datasets']], - 'H_T1': [['M', 'M', 'Molecular Trait Datasets']]}, - 'human': { - 'HLC': [['M', 'M', 'Molecular Trait Datasets']], - 'BXD': [['M', 'M', 'Molecular Trait Datasets']]}} - self.assertEqual(get_types(self.test_group), - expected_result) - - @mock.patch('wqflask.api.gen_menu.build_datasets') - def test_get_datasets_with_existent_datasets(self, - build_datasets_mock): - """Test correct dataset is returned with existent build_datasets""" - build_datasets_mock.return_value = "Test" - expected_result = { - 'mouse': { - 'H_T2': {'Genotypes': 'Test', - 'M': 'Test', - 'Phenotypes': 'Test'}, - 'H_T1': {'Genotypes': 'Test', - 'M': 'Test', - 'Phenotypes': 'Test'}}, - 'human': {'HLC': {'Genotypes': 'Test', - 'M': 'Test', - 'Phenotypes': 'Test'}, - 'BXD': {'Genotypes': 'Test', - 'M': 'Test', - 'Phenotypes': 'Test'}}} - self.assertEqual(get_datasets(self.test_type), - expected_result) - - @mock.patch('wqflask.api.gen_menu.build_datasets') - def test_get_datasets_with_non_existent_datasets(self, - build_datasets_mock): - """Test correct dataset is returned with non-existent build_datasets""" - build_datasets_mock.return_value = None - expected_result = { - 'mouse': { - 'H_T2': {}, - 'H_T1': {}}, - 'human': {'HLC': {}, - 'BXD': {}}} - self.assertEqual(get_datasets(self.test_type), - expected_result) - - @mock.patch('wqflask.api.gen_menu.get_datasets') - @mock.patch('wqflask.api.gen_menu.get_types') - @mock.patch('wqflask.api.gen_menu.get_groups') - @mock.patch('wqflask.api.gen_menu.get_species') - def test_gen_dropdown_json(self, - species_mock, - groups_mock, - types_mock, - datasets_mock): - "Test that the correct dictionary is constructed properly" - species_mock.return_value = ("speciesA speciesB speciesC speciesD" - .split(" ")) - datasets_mock.return_value = ("datasetA datasetB datasetC datasetD" - .split(" ")) - groups_mock.return_value = ("groupA groupB groupC groupD" - .split(" ")) - types_mock.return_value = ("typeA typeB typeC typeD" - .split(" ")) - datasets_mock.return_value = ("datasetA datasetB datasetC datasetD" - .split(" ")) - - expected_result = { - 'datasets': ['datasetA', 'datasetB', 'datasetC', 'datasetD'], - 'types': ['typeA', 'typeB', 'typeC', 'typeD'], - 'groups': ['groupA', 'groupB', 'groupC', 'groupD'], - 'species': ['speciesA', 'speciesB', 'speciesC', 'speciesD']} - - self.assertEqual(gen_dropdown_json(), expected_result) diff --git a/wqflask/tests/wqflask/__init__.py b/wqflask/tests/wqflask/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wqflask/tests/wqflask/api/__init__.py b/wqflask/tests/wqflask/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wqflask/tests/wqflask/api/test_gen_menu.py b/wqflask/tests/wqflask/api/test_gen_menu.py new file mode 100644 index 00000000..79c77fec --- /dev/null +++ b/wqflask/tests/wqflask/api/test_gen_menu.py @@ -0,0 +1,407 @@ +"""Test cases for wqflask.api.gen_menu""" +import unittest +import mock + +from wqflask.api.gen_menu import gen_dropdown_json +from wqflask.api.gen_menu import get_species +from wqflask.api.gen_menu import get_groups +from wqflask.api.gen_menu import get_types +from wqflask.api.gen_menu import get_datasets +from wqflask.api.gen_menu import phenotypes_exist +from wqflask.api.gen_menu import genotypes_exist +from wqflask.api.gen_menu import build_datasets +from wqflask.api.gen_menu import build_types + + +class TestGenMenu(unittest.TestCase): + """Tests for the gen_menu module""" + + def setUp(self): + self.test_group = { + 'mouse': [ + ['H_T1', + 'H_T', + 'Family:DescriptionA' + ], + ['H_T2', "H_T'", 'Family:None'] + ], + 'human': [ + ['BXD', 'BXD', 'Family:None'], + ['HLC', 'Liver: Normal Gene Expression with Genotypes (Merck)', + 'Family:Test'] + ] + } + + self.test_type = { + 'mouse': { + 'H_T2': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']], + 'H_T1': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']] + }, + 'human': { + 'HLC': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']], + 'BXD': [('Phenotypes', + 'Traits and Cofactors', + 'Phenotypes'), + ('Genotypes', + 'DNA Markers and SNPs', + 'Genotypes'), + ['M', 'M', 'Molecular Trait Datasets']] + } + } + + @mock.patch('wqflask.api.gen_menu.g') + def test_get_species(self, db_mock): + """Test that assertion is raised when dataset and dataset_name + are defined""" + db_mock.db.execute.return_value.fetchall.return_value = ( + ('human', 'Human'), + ('mouse', 'Mouse')) + self.assertEqual(get_species(), + [['human', 'Human'], ['mouse', 'Mouse']]) + db_mock.db.execute.assert_called_once_with( + "SELECT Name, MenuName FROM Species ORDER BY OrderId" + ) + + @mock.patch('wqflask.api.gen_menu.g') + def test_get_groups(self, db_mock): + """Test that species groups are grouped correctly""" + db_mock.db.execute.return_value.fetchall.side_effect = [ + # Mouse + (('BXD', 'BXD', None), + ('HLC', 'Liver: Normal Gene Expression with Genotypes (Merck)', + 'Test')), + # Human + (('H_T1', "H_T", "DescriptionA"), + ('H_T2', "H_T'", None)) + ] + + self.assertEqual(get_groups([["human", "Human"], ["mouse", "Mouse"]]), + self.test_group) + + for name in ["mouse", "human"]: + db_mock.db.execute.assert_any_call( + ("SELECT InbredSet.Name, InbredSet.FullName, " + + "IFNULL(InbredSet.Family, 'None') " + + "FROM InbredSet, Species WHERE Species.Name " + + "= '{}' AND InbredSet.SpeciesId = Species.Id GROUP by " + + "InbredSet.Name ORDER BY IFNULL(InbredSet.FamilyOrder, " + + "InbredSet.FullName) ASC, IFNULL(InbredSet.Family, " + + "InbredSet.FullName) ASC, InbredSet.FullName ASC, " + + "InbredSet.MenuOrderId ASC").format(name) + ) + + @mock.patch('wqflask.api.gen_menu.g') + def test_phenotypes_exist_called_with_correct_query(self, db_mock): + """Test that phenotypes_exist is called with the correct query""" + db_mock.db.execute.return_value.fetchone.return_value = None + phenotypes_exist("test") + db_mock.db.execute.assert_called_with( + "SELECT Name FROM PublishFreeze " + "WHERE PublishFreeze.Name = 'testPublish'" + ) + + @mock.patch('wqflask.api.gen_menu.g') + def test_phenotypes_exist_with_falsy_values(self, db_mock): + """Test that phenotype check returns correctly when given + a None value""" + for x in [None, False, (), [], ""]: + db_mock.db.execute.return_value.fetchone.return_value = x + self.assertFalse(phenotypes_exist("test")) + + @mock.patch('wqflask.api.gen_menu.g') + def test_phenotypes_exist_with_truthy_value(self, db_mock): + """Test that phenotype check returns correctly when given Truthy """ + for x in ["x", ("result"), ["result"], [1]]: + db_mock.db.execute.return_value.fetchone.return_value = (x) + self.assertTrue(phenotypes_exist("test")) + + @mock.patch('wqflask.api.gen_menu.g') + def test_genotypes_exist_called_with_correct_query(self, db_mock): + """Test that genotypes_exist is called with the correct query""" + db_mock.db.execute.return_value.fetchone.return_value = None + genotypes_exist("test") + db_mock.db.execute.assert_called_with( + "SELECT Name FROM GenoFreeze WHERE GenoFreeze.Name = 'testGeno'" + ) + + @mock.patch('wqflask.api.gen_menu.g') + def test_genotypes_exist_with_falsy_values(self, db_mock): + """Test that genotype check returns correctly when given + a None value""" + for x in [None, False, (), [], ""]: + db_mock.db.execute.return_value.fetchone.return_value = x + self.assertFalse(genotypes_exist("test")) + + @mock.patch('wqflask.api.gen_menu.g') + def test_genotypes_exist_with_truthy_value(self, db_mock): + """Test that genotype check returns correctly when given Truthy """ + for x in ["x", ("result"), ["result"], [1]]: + db_mock.db.execute.return_value.fetchone.return_value = (x) + self.assertTrue(phenotypes_exist("test")) + + @mock.patch('wqflask.api.gen_menu.g') + def test_build_datasets_with_type_phenotypes(self, db_mock): + """Test that correct dataset is returned for a phenotype type""" + db_mock.db.execute.return_value.fetchall.return_value = ( + (602, "BXDPublish", "BXD Published Phenotypes"), + ) + self.assertEqual(build_datasets("Mouse", "BXD", "Phenotypes"), + [['602', "BXDPublish", "BXD Published Phenotypes"]]) + db_mock.db.execute.assert_called_with( + "SELECT InfoFiles.GN_AccesionId, PublishFreeze.Name, " + + "PublishFreeze.FullName FROM InfoFiles, PublishFreeze, " + + "InbredSet WHERE InbredSet.Name = 'BXD' AND " + + "PublishFreeze.InbredSetId = InbredSet.Id AND " + + "InfoFiles.InfoPageName = PublishFreeze.Name " + + "ORDER BY PublishFreeze.CreateTime ASC" + ) + self.assertEqual(build_datasets("Mouse", "MDP", "Phenotypes"), + [['602', "BXDPublish", "Mouse Phenome Database"]]) + + db_mock.db.execute.return_value.fetchall.return_value = () + db_mock.db.execute.return_value.fetchone.return_value = ( + "BXDPublish", "Mouse Phenome Database" + ) + self.assertEqual(build_datasets("Mouse", "MDP", "Phenotypes"), + [["None", "BXDPublish", "Mouse Phenome Database"]]) + + @mock.patch('wqflask.api.gen_menu.g') + def test_build_datasets_with_type_phenotypes_and_no_results(self, db_mock): + """Test that correct dataset is returned for a phenotype type with no + results + + """ + db_mock.db.execute.return_value.fetchall.return_value = None + db_mock.db.execute.return_value.fetchone.return_value = (121, + "text value") + self.assertEqual(build_datasets("Mouse", "BXD", "Phenotypes"), + [["None", "121", "text value"]]) + db_mock.db.execute.assert_called_with( + "SELECT PublishFreeze.Name, PublishFreeze.FullName " + "FROM PublishFreeze, InbredSet " + "WHERE InbredSet.Name = 'BXD' AND " + "PublishFreeze.InbredSetId = InbredSet.Id " + "ORDER BY PublishFreeze.CreateTime ASC" + ) + + @mock.patch('wqflask.api.gen_menu.g') + def test_build_datasets_with_type_genotypes(self, db_mock): + """Test that correct dataset is returned for a phenotype type""" + db_mock.db.execute.return_value.fetchone.return_value = ( + 635, "HLCPublish", "HLC Published Genotypes" + ) + + self.assertEqual(build_datasets("Mouse", "HLC", "Genotypes"), + [["635", "HLCGeno", "HLC Genotypes"]]) + db_mock.db.execute.assert_called_with( + "SELECT InfoFiles.GN_AccesionId FROM InfoFiles, " + "GenoFreeze, InbredSet WHERE InbredSet.Name = 'HLC' AND " + "GenoFreeze.InbredSetId = InbredSet.Id AND " + "InfoFiles.InfoPageName = GenoFreeze.ShortName " + + "ORDER BY GenoFreeze.CreateTime DESC" + ) + db_mock.db.execute.return_value.fetchone.return_value = () + self.assertEqual(build_datasets("Mouse", "HLC", "Genotypes"), + [["None", "HLCGeno", "HLC Genotypes"]]) + + @mock.patch('wqflask.api.gen_menu.g') + def test_build_datasets_with_type_mrna(self, db_mock): + """Test that correct dataset is returned for a mRNA + expression/ Probeset""" + db_mock.db.execute.return_value.fetchall.return_value = ( + (112, "HC_M2_0606_P", + "Hippocampus Consortium M430v2 (Jun06) PDNN"), ) + self.assertEqual(build_datasets("Mouse", "HLC", "mRNA"), [[ + "112", 'HC_M2_0606_P', "Hippocampus Consortium M430v2 (Jun06) PDNN" + ]]) + db_mock.db.execute.assert_called_once_with( + "SELECT ProbeSetFreeze.Id, ProbeSetFreeze.Name, " + + "ProbeSetFreeze.FullName FROM ProbeSetFreeze, " + + "ProbeFreeze, InbredSet, Tissue, Species WHERE " + + "Species.Name = 'Mouse' AND Species.Id = " + + "InbredSet.SpeciesId AND InbredSet.Name = 'HLC' AND " + + "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id and " + + "Tissue.Name = 'mRNA' AND ProbeFreeze.TissueId = " + + "Tissue.Id and ProbeFreeze.InbredSetId = InbredSet.Id " + + "ORDER BY ProbeSetFreeze.CreateTime DESC") + + @mock.patch('wqflask.api.gen_menu.build_datasets') + @mock.patch('wqflask.api.gen_menu.g') + def test_build_types(self, db_mock, datasets_mock): + """Test that correct tissue metadata is returned""" + datasets_mock.return_value = [ + ["112", 'HC_M2_0606_P', + "Hippocampus Consortium M430v2 (Jun06) PDNN"] + ] + db_mock.db.execute.return_value.fetchall.return_value = ( + ('Mouse Tissue'), ('Human Tissue'), ('Rat Tissue') + ) + self.assertEqual(build_types('mouse', 'random group'), + [['M', 'M', 'Molecular Trait Datasets'], + ['H', 'H', 'Molecular Trait Datasets'], + ['R', 'R', 'Molecular Trait Datasets']]) + db_mock.db.execute.assert_called_once_with( + "SELECT DISTINCT Tissue.Name " + + "FROM ProbeFreeze, ProbeSetFreeze, InbredSet, " + + "Tissue, Species WHERE Species.Name = 'mouse' " + + "AND Species.Id = InbredSet.SpeciesId AND " + + "InbredSet.Name = 'random group' AND " + + "ProbeFreeze.TissueId = Tissue.Id AND " + + "ProbeFreeze.InbredSetId = InbredSet.Id AND " + + "ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id " + + "ORDER BY Tissue.Name" + ) + + @mock.patch('wqflask.api.gen_menu.build_types') + @mock.patch('wqflask.api.gen_menu.genotypes_exist') + @mock.patch('wqflask.api.gen_menu.phenotypes_exist') + def test_get_types_with_existing_genotype_and_phenotypes( + self, + phenotypes_exist_mock, + genotypes_exist_mock, + build_types_mock): + """Test that build types are constructed correctly if phenotypes and genotypes + exist + + """ + phenotypes_exist_mock.return_value = True + genotypes_exist_mock.return_value = True + + expected_result = self.test_type + + build_types_mock.return_value = [ + ['M', 'M', 'Molecular Trait Datasets'] + ] + self.assertEqual(get_types(self.test_group), expected_result) + + @mock.patch('wqflask.api.gen_menu.build_types') + @mock.patch('wqflask.api.gen_menu.genotypes_exist') + @mock.patch('wqflask.api.gen_menu.phenotypes_exist') + def test_get_types_with_buildtype_and_non_existent_genotype_and_phenotypes( + self, + phenotypes_exist_mock, + genotypes_exist_mock, + build_types_mock): + """Test that build types are constructed correctly if phenotypes_exist and + genotypes_exist are false but build_type is falsy + + """ + phenotypes_exist_mock.return_value = False + genotypes_exist_mock.return_value = False + + build_types_mock.return_value = [] + self.assertEqual(get_types(self.test_group), { + 'mouse': {}, + 'human': {} + }) + + @mock.patch('wqflask.api.gen_menu.build_types') + @mock.patch('wqflask.api.gen_menu.genotypes_exist') + @mock.patch('wqflask.api.gen_menu.phenotypes_exist') + def test_get_types_with_non_existent_genotype_phenotypes_and_buildtype( + self, + phenotypes_exist_mock, + genotypes_exist_mock, + build_types_mock): + """Test that build types are constructed correctly if phenotypes_exist, + genotypes_exist and build_types are truthy + + """ + phenotypes_exist_mock.return_value = False + genotypes_exist_mock.return_value = False + + build_types_mock.return_value = [ + ['M', 'M', 'Molecular Trait Datasets'] + ] + expected_result = { + 'mouse': { + 'H_T2': [['M', 'M', 'Molecular Trait Datasets']], + 'H_T1': [['M', 'M', 'Molecular Trait Datasets']]}, + 'human': { + 'HLC': [['M', 'M', 'Molecular Trait Datasets']], + 'BXD': [['M', 'M', 'Molecular Trait Datasets']]}} + self.assertEqual(get_types(self.test_group), + expected_result) + + @mock.patch('wqflask.api.gen_menu.build_datasets') + def test_get_datasets_with_existent_datasets(self, + build_datasets_mock): + """Test correct dataset is returned with existent build_datasets""" + build_datasets_mock.return_value = "Test" + expected_result = { + 'mouse': { + 'H_T2': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}, + 'H_T1': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}}, + 'human': {'HLC': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}, + 'BXD': {'Genotypes': 'Test', + 'M': 'Test', + 'Phenotypes': 'Test'}}} + self.assertEqual(get_datasets(self.test_type), + expected_result) + + @mock.patch('wqflask.api.gen_menu.build_datasets') + def test_get_datasets_with_non_existent_datasets(self, + build_datasets_mock): + """Test correct dataset is returned with non-existent build_datasets""" + build_datasets_mock.return_value = None + expected_result = { + 'mouse': { + 'H_T2': {}, + 'H_T1': {}}, + 'human': {'HLC': {}, + 'BXD': {}}} + self.assertEqual(get_datasets(self.test_type), + expected_result) + + @mock.patch('wqflask.api.gen_menu.get_datasets') + @mock.patch('wqflask.api.gen_menu.get_types') + @mock.patch('wqflask.api.gen_menu.get_groups') + @mock.patch('wqflask.api.gen_menu.get_species') + def test_gen_dropdown_json(self, + species_mock, + groups_mock, + types_mock, + datasets_mock): + "Test that the correct dictionary is constructed properly" + species_mock.return_value = ("speciesA speciesB speciesC speciesD" + .split(" ")) + datasets_mock.return_value = ("datasetA datasetB datasetC datasetD" + .split(" ")) + groups_mock.return_value = ("groupA groupB groupC groupD" + .split(" ")) + types_mock.return_value = ("typeA typeB typeC typeD" + .split(" ")) + datasets_mock.return_value = ("datasetA datasetB datasetC datasetD" + .split(" ")) + + expected_result = { + 'datasets': ['datasetA', 'datasetB', 'datasetC', 'datasetD'], + 'types': ['typeA', 'typeB', 'typeC', 'typeD'], + 'groups': ['groupA', 'groupB', 'groupC', 'groupD'], + 'species': ['speciesA', 'speciesB', 'speciesC', 'speciesD']} + + self.assertEqual(gen_dropdown_json(), expected_result) -- cgit v1.2.3 From 3980446f45ed93a3b9ebed7e5ae9a4cdbec00feb Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Thu, 14 Feb 2019 16:34:39 +0300 Subject: Update the colours to use Pillow's ImageColor * wqflask/wqflask/marker_regression/display_mapping_results.py: Use Pillow's ImageColor module in place of the older Piddle colours. --- .../marker_regression/display_mapping_results.py | 185 ++++++++++++--------- 1 file changed, 103 insertions(+), 82 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 9ac4946b..25846099 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -27,7 +27,7 @@ import datetime import string from math import * -import piddle as pid +from PIL import (ImageColor) import sys,os import cPickle import httplib @@ -49,6 +49,27 @@ from base.webqtlConfig import TMPDIR, GENERATED_TEXT_DIR, GENERATED_IMAGE_DIR import utility.logger logger = utility.logger.getLogger(__name__ ) +RED = ImageColor.getrgb("red") +BLUE = ImageColor.getrgb("blue") +GRAY = ImageColor.getrgb("gray") +GOLD = ImageColor.getrgb("gold") +BLACK = ImageColor.getrgb("black") +GREEN = ImageColor.getrgb("green") +PURPLE = ImageColor.getrgb("purple") +ORANGE = ImageColor.getrgb("orange") +YELLOW = ImageColor.getrgb("yellow") +DARKRED = ImageColor.getrgb("darkred") +DARKBLUE = ImageColor.getrgb("darkblue") +DARKGRAY = ImageColor.getrgb("darkgray") +DEEPPINK = ImageColor.getrgb("deeppink") +DARKGREEN = ImageColor.getrgb("darkgreen") +GAINSBORO = ImageColor.getrgb("gainsboro") +LIGHTBLUE = ImageColor.getrgb("lightblue") +DARKORANGE = ImageColor.getrgb("darkorange") +DARKVIOLET = ImageColor.getrgb("darkviolet") +MEDIUMPURPLE = ImageColor.getrgb("mediumpurple") +# ---- END: Define common colours ---- # + ######################################### # Inteval Mapping Plot Page ######################################### @@ -96,47 +117,47 @@ class DisplayMappingResults(object): LODFACTOR = 4.61 - SNP_COLOR = pid.orange # Color for the SNP "seismograph" - TRANSCRIPT_LOCATION_COLOR = pid.mediumpurple + SNP_COLOR = ORANGE # Color for the SNP "seismograph" + TRANSCRIPT_LOCATION_COLOR = MEDIUMPURPLE - BOOTSTRAP_BOX_COLOR = pid.yellow - LRS_COLOR = pid.HexColor(0x0000FF) - SIGNIFICANT_COLOR = pid.HexColor(0xEBC7C7) - SUGGESTIVE_COLOR = pid.gainsboro + BOOTSTRAP_BOX_COLOR = YELLOW + LRS_COLOR = ImageColor.getrgb("#0000FF") + SIGNIFICANT_COLOR = ImageColor.getrgb("#EBC7C7") + SUGGESTIVE_COLOR = GAINSBORO SIGNIFICANT_WIDTH = 5 SUGGESTIVE_WIDTH = 5 - ADDITIVE_COLOR_POSITIVE = pid.green - ADDITIVE_COLOR_NEGATIVE = pid.orange - DOMINANCE_COLOR_POSITIVE = pid.darkviolet - DOMINANCE_COLOR_NEGATIVE = pid.red + ADDITIVE_COLOR_POSITIVE = GREEN + ADDITIVE_COLOR_NEGATIVE = ORANGE + DOMINANCE_COLOR_POSITIVE = DARKVIOLET + DOMINANCE_COLOR_NEGATIVE = RED ## BEGIN HaplotypeAnalyst - HAPLOTYPE_POSITIVE = pid.green - HAPLOTYPE_NEGATIVE = pid.red - HAPLOTYPE_HETEROZYGOUS = pid.blue - HAPLOTYPE_RECOMBINATION = pid.darkgray + HAPLOTYPE_POSITIVE = GREEN + HAPLOTYPE_NEGATIVE = RED + HAPLOTYPE_HETEROZYGOUS = BLUE + HAPLOTYPE_RECOMBINATION = DARKGRAY ## END HaplotypeAnalyst - TOP_RIGHT_INFO_COLOR = pid.black + TOP_RIGHT_INFO_COLOR = BLACK - CLICKABLE_WEBQTL_REGION_COLOR = pid.HexColor(0xF5D3D3) - CLICKABLE_WEBQTL_REGION_OUTLINE_COLOR = pid.HexColor(0xFCE9E9) - CLICKABLE_WEBQTL_TEXT_COLOR = pid.HexColor(0x912828) + CLICKABLE_WEBQTL_REGION_COLOR = ImageColor.getrgb("#F5D3D3") + CLICKABLE_WEBQTL_REGION_OUTLINE_COLOR = ImageColor.getrgb("#FCE9E9") + CLICKABLE_WEBQTL_TEXT_COLOR = ImageColor.getrgb("#912828") - CLICKABLE_PHENOGEN_REGION_COLOR = pid.HexColor(0xA2FB94) - CLICKABLE_PHENOGEN_REGION_OUTLINE_COLOR = pid.HexColor(0xCEFEC7) - CLICKABLE_PHENOGEN_TEXT_COLOR = pid.HexColor(0x1FD504) + CLICKABLE_PHENOGEN_REGION_COLOR = ImageColor.getrgb("#A2FB94") + CLICKABLE_PHENOGEN_REGION_OUTLINE_COLOR = ImageColor.getrgb("#CEFEC7") + CLICKABLE_PHENOGEN_TEXT_COLOR = ImageColor.getrgb("#1FD504") - CLICKABLE_UCSC_REGION_COLOR = pid.HexColor(0xDDDDEE) - CLICKABLE_UCSC_REGION_OUTLINE_COLOR = pid.HexColor(0xEDEDFF) - CLICKABLE_UCSC_TEXT_COLOR = pid.HexColor(0x333366) + CLICKABLE_UCSC_REGION_COLOR = ImageColor.getrgb("#DDDDEE") + CLICKABLE_UCSC_REGION_OUTLINE_COLOR = ImageColor.getrgb("#EDEDFF") + CLICKABLE_UCSC_TEXT_COLOR = ImageColor.getrgb("#333366") - CLICKABLE_ENSEMBL_REGION_COLOR = pid.HexColor(0xEEEEDD) - CLICKABLE_ENSEMBL_REGION_OUTLINE_COLOR = pid.HexColor(0xFEFEEE) - CLICKABLE_ENSEMBL_TEXT_COLOR = pid.HexColor(0x555500) + CLICKABLE_ENSEMBL_REGION_COLOR = ImageColor.getrgb("#EEEEDD") + CLICKABLE_ENSEMBL_REGION_OUTLINE_COLOR = ImageColor.getrgb("#FEFEEE") + CLICKABLE_ENSEMBL_TEXT_COLOR = ImageColor.getrgb("#555500") - GRAPH_BACK_LIGHT_COLOR = pid.HexColor(0xFBFBFF) - GRAPH_BACK_DARK_COLOR = pid.HexColor(0xF1F1F9) + GRAPH_BACK_LIGHT_COLOR = ImageColor.getrgb("#FBFBFF") + GRAPH_BACK_DARK_COLOR = ImageColor.getrgb("#F1F1F9") HELP_PAGE_REF = '/glossary.html' @@ -698,23 +719,23 @@ class DisplayMappingResults(object): bootOffset = 50*fontZoom bootScaleFont=pid.Font(ttf="verdana",size=13*fontZoom,bold=0) - canvas.drawRect(canvas.size[0]-bootOffset,yZero-bootHeightThresh,canvas.size[0]-bootOffset-15*zoom,yZero,fillColor = pid.yellow) - canvas.drawLine(canvas.size[0]-bootOffset+4, yZero, canvas.size[0]-bootOffset, yZero, color=pid.black) - canvas.drawString('0%' ,canvas.size[0]-bootOffset+10,yZero+5,font=bootScaleFont,color=pid.black) + canvas.drawRect(canvas.size[0]-bootOffset,yZero-bootHeightThresh,canvas.size[0]-bootOffset-15*zoom,yZero,fillColor = YELLOW) + canvas.drawLine(canvas.size[0]-bootOffset+4, yZero, canvas.size[0]-bootOffset, yZero, color=BLACK) + canvas.drawString('0%' ,canvas.size[0]-bootOffset+10,yZero+5,font=bootScaleFont,color=BLACK) for item in bootScale: if item == 0: continue bootY = yZero-bootHeightThresh*item/highestPercent - canvas.drawLine(canvas.size[0]-bootOffset+4,bootY,canvas.size[0]-bootOffset,bootY,color=pid.black) - canvas.drawString('%2.1f'%item ,canvas.size[0]-bootOffset+10,bootY+5,font=bootScaleFont,color=pid.black) + canvas.drawLine(canvas.size[0]-bootOffset+4,bootY,canvas.size[0]-bootOffset,bootY,color=BLACK) + canvas.drawString('%2.1f'%item ,canvas.size[0]-bootOffset+10,bootY+5,font=bootScaleFont,color=BLACK) if self.legendChecked: startPosY = 30 nCol = 2 smallLabelFont = pid.Font(ttf="trebuc", size=12*fontZoom, bold=1) leftOffset = xLeftOffset+(nCol-1)*200 - canvas.drawRect(leftOffset,startPosY-6, leftOffset+12,startPosY+6, fillColor=pid.yellow) - canvas.drawString('Frequency of the Peak LRS',leftOffset+ 20, startPosY+5,font=smallLabelFont,color=pid.black) + canvas.drawRect(leftOffset,startPosY-6, leftOffset+12,startPosY+6, fillColor=YELLOW) + canvas.drawString('Frequency of the Peak LRS',leftOffset+ 20, startPosY+5,font=smallLabelFont,color=BLACK) def drawProbeSetPosition(self, canvas, plotXScale, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): if len(self.traitList) != 1: @@ -784,7 +805,7 @@ class DisplayMappingResults(object): break if locPixel >= 0 and self.plotScale == 'physic': traitPixel = ((locPixel, yZero), (locPixel-7, yZero+14), (locPixel+7, yZero+14)) - canvas.drawPolygon(traitPixel, edgeColor=pid.black, fillColor=self.TRANSCRIPT_LOCATION_COLOR, closed=1) + canvas.drawPolygon(traitPixel, edgeColor=BLACK, fillColor=self.TRANSCRIPT_LOCATION_COLOR, closed=1) if self.legendChecked: startPosY = 15 @@ -794,7 +815,7 @@ class DisplayMappingResults(object): leftOffset = xLeftOffset else: leftOffset = xLeftOffset+(nCol-1)*200*fontZoom - canvas.drawPolygon(((leftOffset+7, startPosY-7), (leftOffset, startPosY+7), (leftOffset+14, startPosY+7)), edgeColor=pid.black, fillColor=self.TRANSCRIPT_LOCATION_COLOR, closed=1) + canvas.drawPolygon(((leftOffset+7, startPosY-7), (leftOffset, startPosY+7), (leftOffset+14, startPosY+7)), edgeColor=BLACK, fillColor=self.TRANSCRIPT_LOCATION_COLOR, closed=1) canvas.drawString("Sequence Site", (leftOffset+15), (startPosY+5), smallLabelFont, self.TOP_RIGHT_INFO_COLOR) def drawSNPTrackNew(self, canvas, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): @@ -863,7 +884,7 @@ class DisplayMappingResults(object): nameWidths.append(nameWidth) canvas.drawRect(rightShift,yPaddingTop+kstep*15, rectWidth+rightShift,yPaddingTop+10+kstep*15, fillColor=thisLRSColor) - canvas.drawString(name,rectWidth+2+rightShift,yPaddingTop+10+kstep*15,font=colorFont,color=pid.black) + canvas.drawString(name,rectWidth+2+rightShift,yPaddingTop+10+kstep*15,font=colorFont,color=BLACK) if thisTrait.db: COORDS = "%d,%d,%d,%d" %(rectWidth+2+rightShift,yPaddingTop+kstep*15,rectWidth+2+rightShift+nameWidth,yPaddingTop+10+kstep*15,) HREF= "javascript:showDatabase3('%s','%s','%s','');" % (showLocusForm, thisTrait.db.name, thisTrait.name) @@ -884,21 +905,21 @@ class DisplayMappingResults(object): stepPosY = 12*fontZoom if self.manhattan_plot != True: canvas.drawLine(xLeftOffset,startPosY,xLeftOffset+32,startPosY,color=self.LRS_COLOR, width=2) - canvas.drawString(self.LRS_LOD, xLeftOffset+40,startPosY+5,font=labelFont,color=pid.black) + canvas.drawString(self.LRS_LOD, xLeftOffset+40,startPosY+5,font=labelFont,color=BLACK) startPosY += stepPosY if self.additiveChecked: startPosX = xLeftOffset canvas.drawLine(startPosX,startPosY,startPosX+17,startPosY,color=self.ADDITIVE_COLOR_POSITIVE, width=2) canvas.drawLine(startPosX+18,startPosY,startPosX+32,startPosY,color=self.ADDITIVE_COLOR_NEGATIVE, width=2) - canvas.drawString('Additive Effect',startPosX+40,startPosY+5,font=labelFont,color=pid.black) + canvas.drawString('Additive Effect',startPosX+40,startPosY+5,font=labelFont,color=BLACK) if self.genotype.type == 'intercross' and self.dominanceChecked: startPosX = xLeftOffset startPosY += stepPosY canvas.drawLine(startPosX,startPosY,startPosX+17,startPosY,color=self.DOMINANCE_COLOR_POSITIVE, width=4) canvas.drawLine(startPosX+18,startPosY,startPosX+35,startPosY,color=self.DOMINANCE_COLOR_NEGATIVE, width=4) - canvas.drawString('Dominance Effect',startPosX+42,startPosY+5,font=labelFont,color=pid.black) + canvas.drawString('Dominance Effect',startPosX+42,startPosY+5,font=labelFont,color=BLACK) if self.haplotypeAnalystChecked: startPosY += stepPosY @@ -907,18 +928,18 @@ class DisplayMappingResults(object): canvas.drawLine(startPosX+18,startPosY,startPosX+35,startPosY,color=self.HAPLOTYPE_NEGATIVE, width=4) canvas.drawLine(startPosX+36,startPosY,startPosX+53,startPosY,color=self.HAPLOTYPE_HETEROZYGOUS, width=4) canvas.drawLine(startPosX+54,startPosY,startPosX+67,startPosY,color=self.HAPLOTYPE_RECOMBINATION, width=4) - canvas.drawString('Haplotypes (Pat, Mat, Het, Unk)',startPosX+76,startPosY+5,font=labelFont,color=pid.black) + canvas.drawString('Haplotypes (Pat, Mat, Het, Unk)',startPosX+76,startPosY+5,font=labelFont,color=BLACK) if self.permChecked and self.nperm > 0: startPosY += stepPosY startPosX = xLeftOffset canvas.drawLine(startPosX, startPosY, startPosX + 32, startPosY, color=self.SIGNIFICANT_COLOR, width=self.SIGNIFICANT_WIDTH) canvas.drawLine(startPosX, startPosY + stepPosY, startPosX + 32, startPosY + stepPosY, color=self.SUGGESTIVE_COLOR, width=self.SUGGESTIVE_WIDTH) - canvas.drawString('Significant %s = %2.2f' % (self.LRS_LOD, self.significant),xLeftOffset+42,startPosY +5,font=labelFont,color=pid.black) - canvas.drawString('Suggestive %s = %2.2f' % (self.LRS_LOD, self.suggestive),xLeftOffset+42,startPosY + 5 +stepPosY,font=labelFont,color=pid.black) + canvas.drawString('Significant %s = %2.2f' % (self.LRS_LOD, self.significant),xLeftOffset+42,startPosY +5,font=labelFont,color=BLACK) + canvas.drawString('Suggestive %s = %2.2f' % (self.LRS_LOD, self.suggestive),xLeftOffset+42,startPosY + 5 +stepPosY,font=labelFont,color=BLACK) labelFont = pid.Font(ttf="verdana",size=12*fontZoom) - labelColor = pid.black + labelColor = BLACK if self.dataset.type == "Publish" or self.dataset.type == "Geno": dataset_label = self.dataset.fullname else: @@ -1042,19 +1063,19 @@ class DisplayMappingResults(object): densities=[1.0000000000000001e-05, 0.094094033555233408, 0.3306166377816987, 0.88246026851027781, 2.6690084029581951, 4.1, 61.0] if SNPdensity < densities[0]: - myColor = pid.black + myColor = BLACK elif SNPdensity < densities[1]: - myColor = pid.purple + myColor = PURPLE elif SNPdensity < densities[2]: - myColor = pid.darkblue + myColor = DARKBLUE elif SNPdensity < densities[3]: - myColor = pid.darkgreen + myColor = DARKGREEN elif SNPdensity < densities[4]: - myColor = pid.gold + myColor = GOLD elif SNPdensity < densities[5]: - myColor = pid.darkorange + myColor = DARKORANGE else: - myColor = pid.darkred + myColor = DARKRED outlineColor = myColor fillColor = myColor @@ -1086,14 +1107,14 @@ class DisplayMappingResults(object): elif (geneStartPix < xLeftOffset): geneStartPix = xLeftOffset; # clip the first in-range gene - outlineColor = pid.darkblue - fillColor = pid.darkblue + outlineColor = DARKBLUE + fillColor = DARKBLUE TITLE = "Gene: %s\nFrom %2.3f to %2.3f Mb (%s)" % (geneSymbol, float(txStart), float(txEnd), strand) # NL: 06-02-2011 Rob required to change this link for gene related HREF=geneNCBILink %geneSymbol else: - outlineColor = pid.orange - fillColor = pid.orange + outlineColor = ORANGE + fillColor = ORANGE TITLE = "Gene: %s" % geneSymbol #Draw Genes @@ -1287,7 +1308,7 @@ class DisplayMappingResults(object): drawit = 1 if drawit == 1: - myColor = pid.darkblue + myColor = DARKBLUE outlineColor = myColor fillColor = myColor @@ -1335,8 +1356,8 @@ class DisplayMappingResults(object): canvas.drawLine(drawStart, geneYLocation+7+2*ind*self.EACH_GENE_HEIGHT*zoom, drawEnd, geneYLocation+7+2*ind*self.EACH_GENE_HEIGHT*zoom, color = mylineColor, width=zoom*(self.EACH_GENE_HEIGHT+2)) - fillColor=pid.black - outlineColor=pid.black + fillColor=BLACK + outlineColor=BLACK if lastGene == 0: canvas.drawRect(geneStartPix, geneYLocation+2*ind*self.EACH_GENE_HEIGHT*zoom, geneEndPix, geneYLocation+2*ind*self.EACH_GENE_HEIGHT+ 2*self.EACH_GENE_HEIGHT*zoom, edgeColor = outlineColor, fillColor = fillColor) @@ -1355,7 +1376,7 @@ class DisplayMappingResults(object): if lastGene == 0: - canvas.drawString("%s" % (_chr[j].name), geneStartPix , geneYLocation+17+2*maxind*self.EACH_GENE_HEIGHT*zoom, font=pid.Font(ttf="verdana", size=12, bold=0), color=pid.black, angle=-90) + canvas.drawString("%s" % (_chr[j].name), geneStartPix , geneYLocation+17+2*maxind*self.EACH_GENE_HEIGHT*zoom, font=pid.Font(ttf="verdana", size=12, bold=0), color=BLACK, angle=-90) oldgeneEndPix = geneEndPix; oldgeno = _chr[j].genotype @@ -1384,8 +1405,8 @@ class DisplayMappingResults(object): expr = item.value # Place where font is hardcoded - canvas.drawString("%s" % (samplelist[j]), (xLeftOffset + plotWidth + 10) , geneYLocation+8+2*ind*self.EACH_GENE_HEIGHT*zoom, font=pid.Font(ttf="verdana", size=12, bold=0), color=pid.black) - canvas.drawString("%2.2f" % (expr), (xLeftOffset + plotWidth + 60) , geneYLocation+8+2*ind*self.EACH_GENE_HEIGHT*zoom, font=pid.Font(ttf="verdana", size=12, bold=0), color=pid.black) + canvas.drawString("%s" % (samplelist[j]), (xLeftOffset + plotWidth + 10) , geneYLocation+8+2*ind*self.EACH_GENE_HEIGHT*zoom, font=pid.Font(ttf="verdana", size=12, bold=0), color=BLACK) + canvas.drawString("%2.2f" % (expr), (xLeftOffset + plotWidth + 60) , geneYLocation+8+2*ind*self.EACH_GENE_HEIGHT*zoom, font=pid.Font(ttf="verdana", size=12, bold=0), color=BLACK) ## END HaplotypeAnalyst @@ -1487,14 +1508,14 @@ class DisplayMappingResults(object): chrFont = pid.Font(ttf="verdana", size=26*zoom, bold=1) traitFont = pid.Font(ttf="verdana", size=14, bold=0) chrX = xLeftOffset + plotWidth - 2 - canvas.stringWidth("Chr %s" % self.ChrList[self.selectedChr][0], font=chrFont) - canvas.drawString("Chr %s" % self.ChrList[self.selectedChr][0], chrX, ensemblPaddingTop-5, font=chrFont, color=pid.gray) + canvas.drawString("Chr %s" % self.ChrList[self.selectedChr][0], chrX, ensemblPaddingTop-5, font=chrFont, color=GRAY) # end of drawBrowserClickableRegions else: #draw the gray text chrFont = pid.Font(ttf="verdana", size=26*zoom, bold=1) traitFont = pid.Font(ttf="verdana", size=14, bold=0) chrX = xLeftOffset + (plotWidth - canvas.stringWidth("Chr %s" % currentChromosome, font=chrFont))/2 - canvas.drawString("Chr %s" % currentChromosome, chrX, 32, font=chrFont, color=pid.gray) + canvas.drawString("Chr %s" % currentChromosome, chrX, 32, font=chrFont, color=GRAY) # end of drawBrowserClickableRegions pass @@ -1517,8 +1538,8 @@ class DisplayMappingResults(object): MBLabelFont = pid.Font(ttf="verdana", size=15*zoom, bold=0) xMajorTickHeight = 10 * zoom # How high the tick extends below the axis xMinorTickHeight = 5*zoom - xAxisTickMarkColor = pid.black - xAxisLabelColor = pid.black + xAxisTickMarkColor = BLACK + xAxisLabelColor = BLACK fontHeight = 12*fontZoom # How tall the font that we're using is spacingFromLabelToAxis = 5 @@ -1565,14 +1586,14 @@ class DisplayMappingResults(object): else: distScale = 5 for j, tickdists in enumerate(range(distScale, int(ceil(distLen)), distScale)): - canvas.drawLine(startPosX + tickdists*plotXScale, yZero, startPosX + tickdists*plotXScale, yZero + 7, color=pid.black, width=1*zoom) + canvas.drawLine(startPosX + tickdists*plotXScale, yZero, startPosX + tickdists*plotXScale, yZero + 7, color=BLACK, width=1*zoom) if j % 2 == 0: - canvas.drawString(str(tickdists), startPosX+tickdists*plotXScale, yZero + 10*zoom, color=pid.black, font=MBLabelFont, angle=270) + canvas.drawString(str(tickdists), startPosX+tickdists*plotXScale, yZero + 10*zoom, color=BLACK, font=MBLabelFont, angle=270) startPosX += (self.ChrLengthDistList[i]+self.GraphInterval)*plotXScale megabaseLabelFont = pid.Font(ttf="verdana", size=18*zoom*1.5, bold=0) canvas.drawString("Megabases", xLeftOffset + (plotWidth - canvas.stringWidth("Megabases", font=megabaseLabelFont))/2, - strYLoc + canvas.fontHeight(MBLabelFont)+ 10*(zoom%2) + 10, font=megabaseLabelFont, color=pid.black) + strYLoc + canvas.fontHeight(MBLabelFont)+ 10*(zoom%2) + 10, font=megabaseLabelFont, color=BLACK) pass else: strYLoc = yZero + spacingFromLabelToAxis + canvas.fontHeight(MBLabelFont) + 8 @@ -1619,7 +1640,7 @@ class DisplayMappingResults(object): LRectWidth = 10 LRectHeight = 3 offsetA = -stepA - lineColor = pid.lightblue + lineColor = LIGHTBLUE startPosX = xLeftOffset for j, ChrInfo in enumerate(ChrAInfo): @@ -1642,11 +1663,11 @@ class DisplayMappingResults(object): yZero+25, color=lineColor) canvas.drawLine(xLeftOffset+offsetA,yZero+25,xLeftOffset+offsetA,\ yZero+40+Zorder*(LRectWidth+3),color=lineColor) - rectColor = pid.orange + rectColor = ORANGE else: canvas.drawLine(xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3)-3,\ xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3),color=lineColor) - rectColor = pid.deeppink + rectColor = DEEPPINK canvas.drawRect(xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3),\ xLeftOffset+offsetA-LRectHeight,yZero+40+Zorder*(LRectWidth+3)+LRectWidth,\ edgeColor=rectColor,fillColor=rectColor,edgeWidth = 0) @@ -1663,9 +1684,9 @@ class DisplayMappingResults(object): centimorganLabelFont = pid.Font(ttf="verdana", size=18*zoom*1.5, bold=0) canvas.drawString("Centimorgans", xLeftOffset + (plotWidth - canvas.stringWidth("Megabases", font=centimorganLabelFont))/2, - strYLoc + canvas.fontHeight(MBLabelFont)+ 10*(zoom%2) + 10, font=centimorganLabelFont, color=pid.black) + strYLoc + canvas.fontHeight(MBLabelFont)+ 10*(zoom%2) + 10, font=centimorganLabelFont, color=BLACK) - canvas.drawLine(xLeftOffset, yZero, xLeftOffset+plotWidth, yZero, color=pid.black, width=X_AXIS_THICKNESS) # Draw the X axis itself + canvas.drawLine(xLeftOffset, yZero, xLeftOffset+plotWidth, yZero, color=BLACK, width=X_AXIS_THICKNESS) # Draw the X axis itself def drawQTL(self, canvas, drawAreaHeight, gifmap, plotXScale, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): @@ -1774,7 +1795,7 @@ class DisplayMappingResults(object): #draw the "LRS" or "LOD" string to the left of the axis canvas.drawString(self.LRS_LOD, xLeftOffset - max_lrs_width - 15*(zoom-1), \ - yZero - 150 - 300*(zoom - 1), font=LRSLODFont, color=pid.black, angle=90) + yZero - 150 - 300*(zoom - 1), font=LRSLODFont, color=BLACK, angle=90) for item in LRSAxisList: if LRS_LOD_Max == 0.0: @@ -1955,9 +1976,9 @@ class DisplayMappingResults(object): if self.manhattan_plot == True: if self.selectedChr == -1 and (previous_chr_as_int % 2 == 1): - point_color = pid.red + point_color = RED else: - point_color = pid.blue + point_color = BLUE final_x_pos = Xc-canvas.stringWidth("5",font=symbolFont)/2+1 if final_x_pos > (xLeftOffset + plotWidth): @@ -2121,13 +2142,13 @@ class DisplayMappingResults(object): #draw the shaded boxes and the sig/sug thick lines canvas.drawRect(startPosX, yTopOffset, startPosX + self.ChrLengthDistList[i]*plotXScale, \ - yBottom, edgeColor=pid.gainsboro,fillColor=theBackColor) + yBottom, edgeColor=GAINSBORO,fillColor=theBackColor) chrNameWidth = canvas.stringWidth(_chr.name, font=chrLabelFont) chrStartPix = startPosX + (self.ChrLengthDistList[i]*plotXScale -chrNameWidth)/2 chrEndPix = startPosX + (self.ChrLengthDistList[i]*plotXScale +chrNameWidth)/2 - canvas.drawString(_chr.name, chrStartPix, yTopOffset + 20 ,font = chrLabelFont,color=pid.black) + canvas.drawString(_chr.name, chrStartPix, yTopOffset + 20 ,font = chrLabelFont,color=BLACK) COORDS = "%d,%d,%d,%d" %(chrStartPix, yTopOffset, chrEndPix,yTopOffset +20) #add by NL 09-03-2010 @@ -2379,4 +2400,4 @@ class DisplayMappingResults(object): lCorr = lCorr[0] break except: raise #lCorr = None - return lCorr \ No newline at end of file + return lCorr -- cgit v1.2.3 From 49dfdc0c986901683f3e00e65f922e2318e0f330 Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Fri, 15 Feb 2019 08:41:28 +0300 Subject: Migrate code from Piddle to Pillow * wqflask/wqflask/marker_regression/display_mapping_results.py: Add font files (DisplayMappingResults): Update the code from the Piddle way of things to the Pillow way of things, for example, replace: - pid.drawRect(...) with im_drawer.rectangle(...) - pid.drawString(...) with im_drawer.text(...) - pid.drawPolygon(...) with im_drawer.polygon(...) etc. * wqflask/utility/Plot.py: Migrate code from the older, unsupported Piddle to the newer Pillow library. --- wqflask/utility/Plot.py | 87 ++- .../marker_regression/display_mapping_results.py | 757 ++++++++++++++++----- 2 files changed, 637 insertions(+), 207 deletions(-) diff --git a/wqflask/utility/Plot.py b/wqflask/utility/Plot.py index 9bc84d22..c557bcc6 100644 --- a/wqflask/utility/Plot.py +++ b/wqflask/utility/Plot.py @@ -26,7 +26,7 @@ from __future__ import print_function -import piddle as pid +from PIL import (Image, ImageColor, ImageDraw, ImageFont) from pprint import pformat as pf from math import * @@ -42,6 +42,17 @@ from base import webqtlConfig import utility.logger logger = utility.logger.getLogger(__name__ ) +# ---- Define common colours ---- # +BLUE = ImageColor.getrgb("blue") +BLACK = ImageColor.getrgb("black") +# ---- END: Define common colours ---- # + +# ---- FONT FILES ---- # +VERDANA_FILE = "fonts/ttf/verdana.ttf" +COUR_FILE = "fonts/ttf/courbd.ttf" +TAHOMA_FILE = "fonts/ttf/tahoma.ttf" +# ---- END: FONT FILES ---- # + def cformat(d, rank=0): 'custom string format' strD = "%2.6f" % d @@ -114,7 +125,8 @@ def find_outliers(vals): # parameter: data is either object returned by reaper permutation function (called by MarkerRegressionPage.py) # or the first object returned by direct (pair-scan) permu function (called by DirectPlotPage.py) -def plotBar(canvas, data, barColor=pid.blue, axesColor=pid.black, labelColor=pid.black, XLabel=None, YLabel=None, title=None, offset= (60, 20, 40, 40), zoom = 1): +def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLabel=None, YLabel=None, title=None, offset= (60, 20, 40, 40), zoom = 1): + im_drawer = ImageDraw.Draw(canvas) xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset plotWidth = canvas.size[0] - xLeftOffset - xRightOffset @@ -161,43 +173,67 @@ def plotBar(canvas, data, barColor=pid.blue, axesColor=pid.black, labelColor=pid if count: xc = (dataXY[i]-xLow)*xScale+xLeftOffset yc =-(count-yLow)*yScale+yTopOffset+plotHeight - canvas.drawRect(xc+2,yc,xc+barWidth-2,yTopOffset+plotHeight,edgeColor=barColor,fillColor=barColor) + im_drawer.rectangle( + xy=((xc+2,yc),(xc+barWidth-2,yTopOffset+plotHeight)), + outline=barColor, fill=barColor) #draw drawing region - canvas.drawRect(xLeftOffset, yTopOffset, xLeftOffset+plotWidth, yTopOffset+plotHeight) + im_drawer.rectangle( + xy=((xLeftOffset, yTopOffset), (xLeftOffset+plotWidth, yTopOffset+plotHeight)) + ) #draw scale - scaleFont=pid.Font(ttf="cour",size=11,bold=1) + scaleFont=ImageFont.truetype(font=COUR_FILE,size=11) x=xLow for i in range(int(stepX)+1): xc=xLeftOffset+(x-xLow)*xScale - canvas.drawLine(xc,yTopOffset+plotHeight,xc,yTopOffset+plotHeight+5, color=axesColor) + im_drawer.line( + xy=((xc,yTopOffset+plotHeight),(xc,yTopOffset+plotHeight+5)), + fill=axesColor) strX = cformat(d=x, rank=0) - canvas.drawString(strX,xc-canvas.stringWidth(strX,font=scaleFont)/2,yTopOffset+plotHeight+14,font=scaleFont) + im_drawer.text( + text=strX, + xy=(xc-im_drawer.textsize(strX,font=scaleFont)[0]/2, + yTopOffset+plotHeight+14),font=scaleFont) x+= (xTop - xLow)/stepX y=yLow for i in range(int(stepY)+1): yc=yTopOffset+plotHeight-(y-yLow)*yScale - canvas.drawLine(xLeftOffset,yc,xLeftOffset-5,yc, color=axesColor) + im_drawer.line(xy=((xLeftOffset,yc),(xLeftOffset-5,yc)), fill=axesColor) strY = "%d" %y - canvas.drawString(strY,xLeftOffset-canvas.stringWidth(strY,font=scaleFont)-6,yc+5,font=scaleFont) + im_drawer.text( + text=strY, + xy=(xLeftOffset-im_drawer.textsize(strY,font=scaleFont)[0]-6,yc+5), + font=scaleFont) y+= (yTop - yLow)/stepY #draw label - labelFont=pid.Font(ttf="tahoma",size=17,bold=0) + labelFont=ImageFont.truetype(font=TAHOMA_FILE,size=17) if XLabel: - canvas.drawString(XLabel,xLeftOffset+(plotWidth-canvas.stringWidth(XLabel,font=labelFont))/2.0, - yTopOffset+plotHeight+yBottomOffset-10,font=labelFont,color=labelColor) + im_drawer.text( + text=XLabel, + xy=(xLeftOffset+( + plotWidth-im_drawer.textsize(XLabel,font=labelFont)[0])/2.0, + yTopOffset+plotHeight+yBottomOffset-10), + font=labelFont,fill=labelColor) if YLabel: - canvas.drawString(YLabel, 19, yTopOffset+plotHeight-(plotHeight-canvas.stringWidth(YLabel,font=labelFont))/2.0, - font=labelFont,color=labelColor,angle=90) - - labelFont=pid.Font(ttf="verdana",size=16,bold=0) + im_drawer.text( + text=YLabel, + xy=(19, + yTopOffset+plotHeight-(plotHeight-im_drawer.textsize( + YLabel,font=labelFont)[0])/2.0), + font=labelFont,fill=labelColor,angle=90) + + labelFont=ImageFont.truetype(font=VERDANA_FILE,size=16) if title: - canvas.drawString(title,xLeftOffset+(plotWidth-canvas.stringWidth(title,font=labelFont))/2.0, - 20,font=labelFont,color=labelColor) + im_drawer.text( + text=title, + xy=(xLeftOffset+(plotWidth-im_drawer.textsize( + title,font=labelFont)[0])/2.0, + 20), + font=labelFont,fill=labelColor) # This function determines the scale of the plot def detScaleOld(min,max): @@ -265,16 +301,21 @@ def greenfunc(x): def colorSpectrum(n=100): multiple = 10 if n == 1: - return [pid.Color(1,0,0)] + return [ImageColor.getrgb("rgb(100%,0%,0%)")] elif n == 2: - return [pid.Color(1,0,0),pid.Color(0,0,1)] + return [ImageColor.getrgb("100%,0%,0%)"), + ImageColor.getrgb("rgb(0%,0%,100%)")] elif n == 3: - return [pid.Color(1,0,0),pid.Color(0,1,0),pid.Color(0,0,1)] + return [ImageColor.getrgb("rgb(100%,0%,0%)"), + ImageColor.getrgb("rgb(0%,100%,0%)"), + ImageColor.getrgb("rgb(0%,0%,100%)")] N = n*multiple out = [None]*N; for i in range(N): x = float(i)/N - out[i] = pid.Color(redfunc(x), greenfunc(x), bluefunc(x)); + out[i] = ImageColor.getrgb("rgb({}%,{}%,{}%".format( + *[int(i*100) for i in ( + redfunc(x), greenfunc(x), bluefunc(x))])) out2 = [out[0]] step = N/float(n-1) j = 0 @@ -290,4 +331,4 @@ def _test(): if __name__=="__main__": - _test() \ No newline at end of file + _test() diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 25846099..39067fc5 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -27,7 +27,7 @@ import datetime import string from math import * -from PIL import (ImageColor) +from PIL import (Image,ImageDraw,ImageFont,ImageColor) import sys,os import cPickle import httplib @@ -70,6 +70,13 @@ DARKVIOLET = ImageColor.getrgb("darkviolet") MEDIUMPURPLE = ImageColor.getrgb("mediumpurple") # ---- END: Define common colours ---- # +# ---- FONT FILES ---- # +VERDANA_FILE = "fonts/ttf/verdana.ttf" +TREBUC_FILE = "fonts/ttf/trebucbd.ttf" +FNT_BS_FILE = "fonts/ttf/fnt_bs.ttf" +ARIAL_FILE = "fonts/ttf/arial.ttf" +# ---- END: FONT FILES ---- # + ######################################### # Inteval Mapping Plot Page ######################################### @@ -487,21 +494,28 @@ class DisplayMappingResults(object): # Plots goes here ################################################################ showLocusForm = "" - intCanvas = pid.PILCanvas(size=(self.graphWidth, self.graphHeight)) + intCanvas = Image.new("RGBA", size=(self.graphWidth, self.graphHeight)) with Bench("Drawing Plot"): gifmap = self.plotIntMapping(intCanvas, startMb = self.startMb, endMb = self.endMb, showLocusForm= showLocusForm) self.gifmap = gifmap.__str__() self.filename= webqtlUtil.genRandStr("Itvl_") - intCanvas.save(os.path.join(webqtlConfig.GENERATED_IMAGE_DIR, self.filename), format='png') + intCanvas.save( + "{}.png".format( + os.path.join(webqtlConfig.GENERATED_IMAGE_DIR, self.filename)), + format='png') intImg=HT.Image('/image/'+self.filename+'.png', border=0, usemap='#WebQTLImageMap') #Scales plot differently for high resolution if self.draw2X: - intCanvasX2 = pid.PILCanvas(size=(self.graphWidth*2,self.graphHeight*2)) + intCanvasX2 = Image.new("RGBA", size=(self.graphWidth*2,self.graphHeight*2)) gifmapX2 = self.plotIntMapping(intCanvasX2, startMb = self.startMb, endMb = self.endMb, showLocusForm= showLocusForm, zoom=2) - intCanvasX2.save(os.path.join(webqtlConfig.GENERATED_IMAGE_DIR, self.filename+"X2"), format='png') + intCanvasX2.save( + "{}.png".format( + os.path.join(webqtlConfig.GENERATED_IMAGE_DIR, + self.filename+"X2")), + format='png') ################################################################ # Outputs goes here @@ -529,6 +543,7 @@ class DisplayMappingResults(object): btminfo.append(HT.BR(), 'Mapping using genotype data as a trait will result in infinity LRS at one locus. In order to display the result properly, all LRSs higher than 100 are capped at 100.') def plotIntMapping(self, canvas, offset= (80, 120, 20, 100), zoom = 1, startMb = None, endMb = None, showLocusForm = ""): + im_drawer = ImageDraw.Draw(canvas) #calculating margins xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset if self.multipleInterval: @@ -622,6 +637,7 @@ class DisplayMappingResults(object): return gifmap def drawBootStrapResult(self, canvas, nboot, drawAreaHeight, plotXScale, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): + im_drawer = ImageDraw.Draw(canvas) xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset plotWidth = canvas.size[0] - xLeftOffset - xRightOffset plotHeight = canvas.size[1] - yTopOffset - yBottomOffset @@ -708,8 +724,10 @@ class DisplayMappingResults(object): if item[1] > xLeftOffset+plotWidth: item[1] = xLeftOffset+plotWidth if item[0] != item[1]: - canvas.drawRect(item[0], yZero, item[1], yZero - item[2]*bootHeightThresh/maxBootCount, - fillColor=self.BOOTSTRAP_BOX_COLOR) + im_drawer.rectangle( + xy=((item[0], yZero), + (item[1], yZero - item[2]*bootHeightThresh/maxBootCount)), + fill=self.BOOTSTRAP_BOX_COLOR) ###draw boot scale highestPercent = (maxBootCount*100.0)/nboot @@ -718,26 +736,43 @@ class DisplayMappingResults(object): bootScale = bootScale[:-1] + [highestPercent] bootOffset = 50*fontZoom - bootScaleFont=pid.Font(ttf="verdana",size=13*fontZoom,bold=0) - canvas.drawRect(canvas.size[0]-bootOffset,yZero-bootHeightThresh,canvas.size[0]-bootOffset-15*zoom,yZero,fillColor = YELLOW) - canvas.drawLine(canvas.size[0]-bootOffset+4, yZero, canvas.size[0]-bootOffset, yZero, color=BLACK) - canvas.drawString('0%' ,canvas.size[0]-bootOffset+10,yZero+5,font=bootScaleFont,color=BLACK) + bootScaleFont=ImageFont.truetype(font=VERDANA_FILE,size=13*fontZoom) + im_drawer.rectangle( + xy=((canvas.size[0]-bootOffset, yZero-bootHeightThresh), + (canvas.size[0]-bootOffset-15*zoom,yZero)), + fill = YELLOW) + im_drawer.line( + xy=((canvas.size[0]-bootOffset+4, yZero), + (canvas.size[0]-bootOffset, yZero)), + fill=BLACK) + im_drawer.text(xy=(canvas.size[0]-bootOffset+10,yZero+5), text='0%', + font=bootScaleFont, fill=BLACK) + for item in bootScale: if item == 0: continue bootY = yZero-bootHeightThresh*item/highestPercent - canvas.drawLine(canvas.size[0]-bootOffset+4,bootY,canvas.size[0]-bootOffset,bootY,color=BLACK) - canvas.drawString('%2.1f'%item ,canvas.size[0]-bootOffset+10,bootY+5,font=bootScaleFont,color=BLACK) + im_drawer.line( + xy=((canvas.size[0]-bootOffset+4,bootY), + (canvas.size[0]-bootOffset,bootY)), + fill=BLACK) + im_drawer.text(xy=(canvas.size[0]-bootOffset+10,bootY+5), + text='%2.1f'%item, font=bootScaleFont, color=BLACK) if self.legendChecked: startPosY = 30 nCol = 2 - smallLabelFont = pid.Font(ttf="trebuc", size=12*fontZoom, bold=1) + smallLabelFont = ImageFont.truetype(font=TREBUC_FILE, size=12*fontZoom) leftOffset = xLeftOffset+(nCol-1)*200 - canvas.drawRect(leftOffset,startPosY-6, leftOffset+12,startPosY+6, fillColor=YELLOW) - canvas.drawString('Frequency of the Peak LRS',leftOffset+ 20, startPosY+5,font=smallLabelFont,color=BLACK) + im_drawer.rectangle( + xy=((leftOffset,startPosY-6), (leftOffset+12,startPosY+6)), + fill=YELLOW) + im_drawer.text(xy=(leftOffset+ 20, startPosY+5), + text='Frequency of the Peak LRS', + font=smallLabelFont, color=BLACK) def drawProbeSetPosition(self, canvas, plotXScale, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): + im_drawer = ImageDraw.Draw(canvas) if len(self.traitList) != 1: return @@ -805,12 +840,15 @@ class DisplayMappingResults(object): break if locPixel >= 0 and self.plotScale == 'physic': traitPixel = ((locPixel, yZero), (locPixel-7, yZero+14), (locPixel+7, yZero+14)) - canvas.drawPolygon(traitPixel, edgeColor=BLACK, fillColor=self.TRANSCRIPT_LOCATION_COLOR, closed=1) + im_drawer.polygon( + xy=traitPixel, outline=BLACK, + fill=self.TRANSCRIPT_LOCATION_COLOR#, closed=1 + ) if self.legendChecked: startPosY = 15 nCol = 2 - smallLabelFont = pid.Font(ttf="trebuc", size=12*fontZoom, bold=1) + smallLabelFont = ImageFont.truetype(font=TREBUC_FILE, size=12*fontZoom) if self.manhattan_plot: leftOffset = xLeftOffset else: @@ -819,6 +857,7 @@ class DisplayMappingResults(object): canvas.drawString("Sequence Site", (leftOffset+15), (startPosY+5), smallLabelFont, self.TOP_RIGHT_INFO_COLOR) def drawSNPTrackNew(self, canvas, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): + im_drawer = ImageDraw.Draw(canvas) if self.plotScale != 'physic' or self.selectedChr == -1 or not self.diffCol: return @@ -856,12 +895,15 @@ class DisplayMappingResults(object): if maxCount>0: for i in range(xLeftOffset, xLeftOffset + plotWidth): snpDensity = float(SNPCounts[i-xLeftOffset]*SNP_HEIGHT_MODIFIER/maxCount) - canvas.drawLine(i, drawSNPLocationY+(snpDensity)*zoom, i, drawSNPLocationY-(snpDensity)*zoom, color=self.SNP_COLOR, width=1) + im_drawer.line( + xy=((i, drawSNPLocationY+(snpDensity)*zoom), + (i, drawSNPLocationY-(snpDensity)*zoom)), + fill=self.SNP_COLOR, width=1) def drawMultiTraitName(self, fd, canvas, gifmap, showLocusForm, offset= (40, 120, 80, 10), zoom = 1): nameWidths = [] yPaddingTop = 10 - colorFont=pid.Font(ttf="trebuc",size=12,bold=1) + colorFont=ImageFont.truetype(font=TREBUC_FILE,size=12) if len(self.qtlresults) >20 and self.selectedChr > -1: rightShift = 20 rightShiftStep = 60 @@ -880,11 +922,16 @@ class DisplayMappingResults(object): rightShift += rightShiftStep name = thisTrait.displayName() - nameWidth = canvas.stringWidth(name,font=colorFont) + nameWidth, nameHeight = im_drawer.textsize(name,font=colorFont) nameWidths.append(nameWidth) - canvas.drawRect(rightShift,yPaddingTop+kstep*15, rectWidth+rightShift,yPaddingTop+10+kstep*15, fillColor=thisLRSColor) - canvas.drawString(name,rectWidth+2+rightShift,yPaddingTop+10+kstep*15,font=colorFont,color=BLACK) + im_drawer.rectangle( + xy=((rightShift,yPaddingTop+kstep*15), + (rectWidth+rightShift,yPaddingTop+10+kstep*15)), + fill=thisLRSColor) + im_drawer.text( + text=name,xy=(rectWidth+2+rightShift,yPaddingTop+10+kstep*15), + font=colorFont,fill=BLACK) if thisTrait.db: COORDS = "%d,%d,%d,%d" %(rectWidth+2+rightShift,yPaddingTop+kstep*15,rectWidth+2+rightShift+nameWidth,yPaddingTop+10+kstep*15,) HREF= "javascript:showDatabase3('%s','%s','%s','');" % (showLocusForm, thisTrait.db.name, thisTrait.name) @@ -892,6 +939,7 @@ class DisplayMappingResults(object): gifmap.areas.append(Areas) def drawLegendPanel(self, canvas, offset= (40, 120, 80, 10), zoom = 1): + im_drawer = ImageDraw.Draw(canvas) xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset plotWidth = canvas.size[0] - xLeftOffset - xRightOffset plotHeight = canvas.size[1] - yTopOffset - yBottomOffset @@ -900,45 +948,80 @@ class DisplayMappingResults(object): if zoom == 2: fontZoom = 1.5 - labelFont=pid.Font(ttf="trebuc",size=12*fontZoom, bold=1) + labelFont=ImageFont.truetype(font=TREBUC_FILE,size=12*fontZoom) startPosY = 15 stepPosY = 12*fontZoom if self.manhattan_plot != True: - canvas.drawLine(xLeftOffset,startPosY,xLeftOffset+32,startPosY,color=self.LRS_COLOR, width=2) - canvas.drawString(self.LRS_LOD, xLeftOffset+40,startPosY+5,font=labelFont,color=BLACK) + im_drawer.line( + xy=((xLeftOffset,startPosY),(xLeftOffset+32,startPosY)), + fill=self.LRS_COLOR, width=2) + im_drawer.text( + text=self.LRS_LOD, xy=(xLeftOffset+40,startPosY+5), + font=labelFont,fill=BLACK) startPosY += stepPosY if self.additiveChecked: startPosX = xLeftOffset - canvas.drawLine(startPosX,startPosY,startPosX+17,startPosY,color=self.ADDITIVE_COLOR_POSITIVE, width=2) - canvas.drawLine(startPosX+18,startPosY,startPosX+32,startPosY,color=self.ADDITIVE_COLOR_NEGATIVE, width=2) - canvas.drawString('Additive Effect',startPosX+40,startPosY+5,font=labelFont,color=BLACK) + im_drawer.line( + xy=((startPosX,startPosY),(startPosX+17,startPosY)), + fill=self.ADDITIVE_COLOR_POSITIVE, width=2) + im_drawer.line( + xy=((startPosX+18,startPosY),(startPosX+32,startPosY)), + fill=self.ADDITIVE_COLOR_NEGATIVE, width=2) + im_drawer.text( + text='Additive Effect',xy=(startPosX+40,startPosY+5), + font=labelFont,fill=BLACK) if self.genotype.type == 'intercross' and self.dominanceChecked: startPosX = xLeftOffset startPosY += stepPosY - canvas.drawLine(startPosX,startPosY,startPosX+17,startPosY,color=self.DOMINANCE_COLOR_POSITIVE, width=4) - canvas.drawLine(startPosX+18,startPosY,startPosX+35,startPosY,color=self.DOMINANCE_COLOR_NEGATIVE, width=4) - canvas.drawString('Dominance Effect',startPosX+42,startPosY+5,font=labelFont,color=BLACK) + im_drawer.line( + xy=((startPosX,startPosY),(startPosX+17,startPosY)), + fill=self.DOMINANCE_COLOR_POSITIVE, width=4) + im_drawer.line( + xy=((startPosX+18,startPosY),(startPosX+35,startPosY)), + fill=self.DOMINANCE_COLOR_NEGATIVE, width=4) + im_drawer.text( + text='Dominance Effect', xy=(startPosX+42,startPosY+5), + font=labelFont,fill=BLACK) if self.haplotypeAnalystChecked: startPosY += stepPosY startPosX = xLeftOffset - canvas.drawLine(startPosX,startPosY,startPosX+17,startPosY,color=self.HAPLOTYPE_POSITIVE, width=4) - canvas.drawLine(startPosX+18,startPosY,startPosX+35,startPosY,color=self.HAPLOTYPE_NEGATIVE, width=4) - canvas.drawLine(startPosX+36,startPosY,startPosX+53,startPosY,color=self.HAPLOTYPE_HETEROZYGOUS, width=4) - canvas.drawLine(startPosX+54,startPosY,startPosX+67,startPosY,color=self.HAPLOTYPE_RECOMBINATION, width=4) - canvas.drawString('Haplotypes (Pat, Mat, Het, Unk)',startPosX+76,startPosY+5,font=labelFont,color=BLACK) + im_drawer.line( + xy=((startPosX,startPosY),(startPosX+17,startPosY)), + fill=self.HAPLOTYPE_POSITIVE, width=4) + im_drawer.line( + xy=((startPosX+18,startPosY),(startPosX+35,startPosY)), + fill=self.HAPLOTYPE_NEGATIVE, width=4) + im_drawer.line( + xy=((startPosX+36,startPosY),(startPosX+53,startPosY)), + fill=self.HAPLOTYPE_HETEROZYGOUS, width=4) + im_drawer.line( + xy=((startPosX+54,startPosY),(startPosX+67,startPosY)), + fill=self.HAPLOTYPE_RECOMBINATION, width=4) + im_drawer.text( + text='Haplotypes (Pat, Mat, Het, Unk)', + xy=(startPosX+76,startPosY+5),font=labelFont,fill=BLACK) if self.permChecked and self.nperm > 0: startPosY += stepPosY startPosX = xLeftOffset - canvas.drawLine(startPosX, startPosY, startPosX + 32, startPosY, color=self.SIGNIFICANT_COLOR, width=self.SIGNIFICANT_WIDTH) - canvas.drawLine(startPosX, startPosY + stepPosY, startPosX + 32, startPosY + stepPosY, color=self.SUGGESTIVE_COLOR, width=self.SUGGESTIVE_WIDTH) - canvas.drawString('Significant %s = %2.2f' % (self.LRS_LOD, self.significant),xLeftOffset+42,startPosY +5,font=labelFont,color=BLACK) - canvas.drawString('Suggestive %s = %2.2f' % (self.LRS_LOD, self.suggestive),xLeftOffset+42,startPosY + 5 +stepPosY,font=labelFont,color=BLACK) - - labelFont = pid.Font(ttf="verdana",size=12*fontZoom) + im_drawer.line( + xy=((startPosX, startPosY),( startPosX + 32, startPosY)), + fill=self.SIGNIFICANT_COLOR, width=self.SIGNIFICANT_WIDTH) + im_drawer.line( + xy=((startPosX, startPosY + stepPosY),( startPosX + 32, startPosY + stepPosY)), + fill=self.SUGGESTIVE_COLOR, width=self.SUGGESTIVE_WIDTH) + im_drawer.text( + text='Significant %s = %2.2f' % (self.LRS_LOD,self.significant), + xy=(xLeftOffset+42,startPosY +5),font=labelFont,fill=BLACK) + im_drawer.text( + text='Suggestive %s = %2.2f' % (self.LRS_LOD, self.suggestive), + xy=(xLeftOffset+42,startPosY + 5 +stepPosY),font=labelFont, + fill=BLACK) + + labelFont = ImageFont.truetype(font=VERDANA_FILE,size=12*fontZoom) labelColor = BLACK if self.dataset.type == "Publish" or self.dataset.type == "Geno": dataset_label = self.dataset.fullname @@ -1000,20 +1083,32 @@ class DisplayMappingResults(object): identification += "Trait: %s" % (self.this_trait.name) identification += " with %s samples" % (self.n_samples) - d = 4+ max(canvas.stringWidth(identification, font=labelFont), canvas.stringWidth(string1, font=labelFont), canvas.stringWidth(string2, font=labelFont)) - canvas.drawString(identification,canvas.size[0] - xRightOffset-d,20*fontZoom,font=labelFont,color=labelColor) - else: - d = 4+ max(canvas.stringWidth(string1, font=labelFont), canvas.stringWidth(string2, font=labelFont)) - canvas.drawString(string1,canvas.size[0] - xRightOffset-d,35*fontZoom,font=labelFont,color=labelColor) - canvas.drawString(string2,canvas.size[0] - xRightOffset-d,50*fontZoom,font=labelFont,color=labelColor) - canvas.drawString(string3,canvas.size[0] - xRightOffset-d,65*fontZoom,font=labelFont,color=labelColor) - if string4 != '': - canvas.drawString(string4,canvas.size[0] - xRightOffset-d,80*fontZoom,font=labelFont,color=labelColor) - canvas.drawString("Created at: " + str(datetime.datetime.now()).split('.')[0], canvas.size[0] - xRightOffset-d,95*fontZoom,font=labelFont,color=labelColor) + d = 4+ max( + im_drawer.textsize(identification, font=labelFont)[0], + im_drawer.textsize(string1, font=labelFont)[0], + im_drawer.textsize(string2, font=labelFont)[0]) + im_drawer.text( + text=identification, + xy=(canvas.size[0] - xRightOffset-d,20*fontZoom),font=labelFont, + fill=labelColor) else: - canvas.drawString("Created at: " + str(datetime.datetime.now()).split('.')[0], canvas.size[0] - xRightOffset-d,80*fontZoom,font=labelFont,color=labelColor) + d = 4+ max( + im_drawer.textsize(string1, font=labelFont)[0], + im_drawer.textsize(string2, font=labelFont)[0]) + im_drawer.text( + text=string1,xy=(canvas.size[0] - xRightOffset-d,35*fontZoom), + font=labelFont,fill=labelColor) + im_drawer.text( + text=string2,xy=(canvas.size[0] - xRightOffset-d,50*fontZoom), + font=labelFont,fill=labelColor) + if string3 != '': + im_drawer.text( + text=string3,xy=(canvas.size[0] - xRightOffset-d,65*fontZoom), + font=labelFont,fill=labelColor) + def drawGeneBand(self, canvas, gifmap, plotXScale, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): + im_drawer = ImageDraw.Draw(canvas) if self.plotScale != 'physic' or self.selectedChr == -1 or not self.geneCol: return @@ -1126,11 +1221,15 @@ class DisplayMappingResults(object): #draw the detail view if self.endMb - self.startMb <= self.DRAW_DETAIL_MB and geneEndPix - geneStartPix > self.EACH_GENE_ARROW_SPACING * 3: - utrColor = pid.Color(0.66, 0.66, 0.66) - arrowColor = pid.Color(0.7, 0.7, 0.7) + utrColor = ImageColor.getrgb("rgb(66%, 66%, 66%)") + arrowColor = ImageColor.getrgb("rgb(70%, 70%, 70%)") #draw the line that runs the entire length of the gene - canvas.drawLine(geneStartPix, geneYLocation + self.EACH_GENE_HEIGHT/2*zoom, geneEndPix, geneYLocation + self.EACH_GENE_HEIGHT/2*zoom, color=outlineColor, width=1) + im_drawer.line( + xy=( + (geneStartPix, geneYLocation + self.EACH_GENE_HEIGHT/2*zoom), + ( geneEndPix, geneYLocation + self.EACH_GENE_HEIGHT/2*zoom)), + fill=outlineColor, width=1) #draw the arrows if geneEndPix - geneStartPix < 1: @@ -1141,11 +1240,30 @@ class DisplayMappingResults(object): if (xCoord % self.EACH_GENE_ARROW_SPACING == 0 and xCoord + self.EACH_GENE_ARROW_SPACING < geneEndPix-geneStartPix) or xCoord == 0: if strand == "+": - canvas.drawLine(geneStartPix + xCoord, geneYLocation, geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, geneYLocation +(self.EACH_GENE_HEIGHT / 2)*zoom, color=arrowColor, width=1) - canvas.drawLine(geneStartPix + xCoord, geneYLocation + self.EACH_GENE_HEIGHT*zoom, geneStartPix + xCoord+self.EACH_GENE_ARROW_WIDTH, geneYLocation + (self.EACH_GENE_HEIGHT / 2) * zoom, color=arrowColor, width=1) + im_drawer.line( + xy=((geneStartPix + xCoord, geneYLocation), + (geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation +(self.EACH_GENE_HEIGHT / 2)*zoom)), + fill=arrowColor, width=1) + im_drawer.line( + xy=((geneStartPix + xCoord, + geneYLocation + self.EACH_GENE_HEIGHT*zoom), + (geneStartPix + xCoord+self.EACH_GENE_ARROW_WIDTH, + geneYLocation + (self.EACH_GENE_HEIGHT / 2) * zoom)), + fill=arrowColor, width=1) else: - canvas.drawLine(geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, geneYLocation, geneStartPix + xCoord, geneYLocation +(self.EACH_GENE_HEIGHT / 2)*zoom, color=arrowColor, width=1) - canvas.drawLine(geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, geneYLocation + self.EACH_GENE_HEIGHT*zoom, geneStartPix + xCoord, geneYLocation + (self.EACH_GENE_HEIGHT / 2)*zoom, color=arrowColor, width=1) + im_drawer.line( + xy=((geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation), + ( geneStartPix + xCoord, + geneYLocation +(self.EACH_GENE_HEIGHT / 2)*zoom)), + fill=arrowColor, width=1) + im_drawer.line( + xy=((geneStartPix + xCoord + self.EACH_GENE_ARROW_WIDTH, + geneYLocation + self.EACH_GENE_HEIGHT*zoom), + ( geneStartPix + xCoord, + geneYLocation + (self.EACH_GENE_HEIGHT / 2)*zoom)), + fill=arrowColor, width=1) #draw the blocks for the exon regions for i in range(0, len(exonStarts)): @@ -1159,7 +1277,10 @@ class DisplayMappingResults(object): exonEndPix = xLeftOffset + plotWidth if (exonStartPix > xLeftOffset + plotWidth): exonStartPix = xLeftOffset + plotWidth - canvas.drawRect(exonStartPix, geneYLocation, exonEndPix, (geneYLocation + self.EACH_GENE_HEIGHT*zoom), edgeColor = outlineColor, fillColor = fillColor) + im_drawer.rectangle( + xy=((exonStartPix, geneYLocation), + (exonEndPix, (geneYLocation + self.EACH_GENE_HEIGHT*zoom))), + outline = outlineColor, fill = fillColor) #draw gray blocks for 3' and 5' UTR blocks if cdsStart and cdsEnd: @@ -1173,14 +1294,16 @@ class DisplayMappingResults(object): utrEndPix = xLeftOffset + plotWidth if (utrStartPix > xLeftOffset + plotWidth): utrStartPix = xLeftOffset + plotWidth - #canvas.drawRect(utrStartPix, geneYLocation, utrEndPix, (geneYLocation+self.EACH_GENE_HEIGHT*zoom), edgeColor=utrColor, fillColor =utrColor) if self.endMb - self.startMb <= self.DRAW_UTR_LABELS_MB: if strand == "-": labelText = "3'" else: labelText = "5'" - canvas.drawString(labelText, utrStartPix-9, geneYLocation+self.EACH_GENE_HEIGHT, pid.Font(face="helvetica", size=2)) + im_drawer.text( + text=labelText, + xy=(utrStartPix-9, geneYLocation+self.EACH_GENE_HEIGHT), + font=ImageFont.truetype(font=ARIAL_FILE, size=2)) #the second UTR region @@ -1194,18 +1317,23 @@ class DisplayMappingResults(object): utrEndPix = xLeftOffset + plotWidth if (utrStartPix > xLeftOffset + plotWidth): utrStartPix = xLeftOffset + plotWidth - #canvas.drawRect(utrStartPix, geneYLocation, utrEndPix, (geneYLocation+self.EACH_GENE_HEIGHT*zoom), edgeColor=utrColor, fillColor =utrColor) if self.endMb - self.startMb <= self.DRAW_UTR_LABELS_MB: if strand == "-": labelText = "5'" else: labelText = "3'" - canvas.drawString(labelText, utrEndPix+2, geneYLocation+self.EACH_GENE_HEIGHT, pid.Font(face="helvetica", size=2)) + im_drawer.text( + text=labelText, + xy=(utrEndPix+2,geneYLocation+self.EACH_GENE_HEIGHT), + font=ImageFont.truetype(font=ARIAL_FILE, size=2)) #draw the genes as rectangles else: - canvas.drawRect(geneStartPix, geneYLocation, geneEndPix, (geneYLocation + self.EACH_GENE_HEIGHT*zoom), edgeColor = outlineColor, fillColor = fillColor) + im_drawer.rectangle( + xy=((geneStartPix, geneYLocation), + (geneEndPix, (geneYLocation + self.EACH_GENE_HEIGHT*zoom))), + outline= outlineColor, fill = fillColor) COORDS = "%d, %d, %d, %d" %(geneStartPix, geneYLocation, geneEndPix, (geneYLocation + self.EACH_GENE_HEIGHT)) # NL: 06-02-2011 Rob required to display NCBI info in a new window @@ -1354,12 +1482,22 @@ class DisplayMappingResults(object): else: mylineColor = self.HAPLOTYPE_RECOMBINATION # XZ: Unknown - canvas.drawLine(drawStart, geneYLocation+7+2*ind*self.EACH_GENE_HEIGHT*zoom, drawEnd, geneYLocation+7+2*ind*self.EACH_GENE_HEIGHT*zoom, color = mylineColor, width=zoom*(self.EACH_GENE_HEIGHT+2)) + im_drawer.line( + xy=((drawStart, + geneYLocation+7+2*ind*self.EACH_GENE_HEIGHT*zoom), + (drawEnd, + geneYLocation+7+2*ind*self.EACH_GENE_HEIGHT*zoom)), + fill= mylineColor, width=zoom*(self.EACH_GENE_HEIGHT+2)) fillColor=BLACK outlineColor=BLACK if lastGene == 0: - canvas.drawRect(geneStartPix, geneYLocation+2*ind*self.EACH_GENE_HEIGHT*zoom, geneEndPix, geneYLocation+2*ind*self.EACH_GENE_HEIGHT+ 2*self.EACH_GENE_HEIGHT*zoom, edgeColor = outlineColor, fillColor = fillColor) + im_drawer.rectangle( + xy=((geneStartPix, + geneYLocation+2*ind*self.EACH_GENE_HEIGHT*zoom), + (geneEndPix, + geneYLocation+2*ind*self.EACH_GENE_HEIGHT+ 2*self.EACH_GENE_HEIGHT*zoom)), + outline=outlineColor, fill=fillColor) COORDS = "%d, %d, %d, %d" %(geneStartPix, geneYLocation+ind*self.EACH_GENE_HEIGHT, geneEndPix+1, (geneYLocation + ind*self.EACH_GENE_HEIGHT)) @@ -1372,11 +1510,21 @@ class DisplayMappingResults(object): if (plotRight < (xLeftOffset + plotWidth - 3)) and (lastGene == 0): drawEnd = xLeftOffset + plotWidth - 6 mylineColor = self.HAPLOTYPE_RECOMBINATION - canvas.drawLine(plotRight, geneYLocation+7+2*ind*self.EACH_GENE_HEIGHT*zoom, drawEnd, geneYLocation+7+2*ind*self.EACH_GENE_HEIGHT*zoom, color = mylineColor, width=zoom*(self.EACH_GENE_HEIGHT+2)) + im_drawer.line( + xy=((plotRight, + geneYLocation+7+2*ind*self.EACH_GENE_HEIGHT*zoom), + (drawEnd, + geneYLocation+7+2*ind*self.EACH_GENE_HEIGHT*zoom)), + fill= mylineColor, width=zoom*(self.EACH_GENE_HEIGHT+2)) if lastGene == 0: - canvas.drawString("%s" % (_chr[j].name), geneStartPix , geneYLocation+17+2*maxind*self.EACH_GENE_HEIGHT*zoom, font=pid.Font(ttf="verdana", size=12, bold=0), color=BLACK, angle=-90) + im_drawer.text( + text="%s" % (_chr[j].name), + xy=(geneStartPix, + geneYLocation+17+2*maxind*self.EACH_GENE_HEIGHT*zoom), + font=ImageFont.truetype(font=VERDANA_FILE, size=12), + fill=BLACK, angle=-90) oldgeneEndPix = geneEndPix; oldgeno = _chr[j].genotype @@ -1405,12 +1553,23 @@ class DisplayMappingResults(object): expr = item.value # Place where font is hardcoded - canvas.drawString("%s" % (samplelist[j]), (xLeftOffset + plotWidth + 10) , geneYLocation+8+2*ind*self.EACH_GENE_HEIGHT*zoom, font=pid.Font(ttf="verdana", size=12, bold=0), color=BLACK) - canvas.drawString("%2.2f" % (expr), (xLeftOffset + plotWidth + 60) , geneYLocation+8+2*ind*self.EACH_GENE_HEIGHT*zoom, font=pid.Font(ttf="verdana", size=12, bold=0), color=BLACK) + im_drawer.text( + text="%s" % (samplelist[j]), + xy=((xLeftOffset + plotWidth + 10), + geneYLocation+8+2*ind*self.EACH_GENE_HEIGHT*zoom), + font=ImageFont.truetype(font=VERDANA_FILE, size=12), + fill=BLACK) + im_drawer.text( + text="%2.2f" % (expr), + xy=((xLeftOffset + plotWidth + 60), + geneYLocation+8+2*ind*self.EACH_GENE_HEIGHT*zoom), + font=ImageFont.truetype(font=VERDANA_FILE, size=12), + fill=BLACK) ## END HaplotypeAnalyst def drawClickBand(self, canvas, gifmap, plotXScale, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): + im_drawer = ImageDraw.Draw(canvas) if self.plotScale != 'physic' or self.selectedChr == -1: return @@ -1426,7 +1585,7 @@ class DisplayMappingResults(object): # but it makes the HTML huge, and takes forever to render the page in the first place) # Draw the bands that you can click on to go to UCSC / Ensembl MAX_CLICKABLE_REGION_DIVISIONS = 100 - clickableRegionLabelFont=pid.Font(ttf="verdana", size=9, bold=0) + clickableRegionLabelFont=ImageFont.truetype(font=VERDANA_FILE, size=9) pixelStep = max(5, int(float(plotWidth)/MAX_CLICKABLE_REGION_DIVISIONS)) # pixelStep: every N pixels, we make a new clickable area for the user to go to that area of the genome. @@ -1463,8 +1622,14 @@ class DisplayMappingResults(object): WEBQTL_TITLE = "Click to view this section of the genome in WebQTL" gifmap.areas.append(HT.Area(shape='rect',coords=WEBQTL_COORDS,href=WEBQTL_HREF, title=WEBQTL_TITLE)) - canvas.drawRect(xBrowse1, paddingTop, xBrowse2, (paddingTop + self.BAND_HEIGHT), edgeColor=self.CLICKABLE_WEBQTL_REGION_COLOR, fillColor=self.CLICKABLE_WEBQTL_REGION_COLOR) - canvas.drawLine(xBrowse1, paddingTop, xBrowse1, (paddingTop + self.BAND_HEIGHT), color=self.CLICKABLE_WEBQTL_REGION_OUTLINE_COLOR) + im_drawer.rectangle( + xy=((xBrowse1, paddingTop), + (xBrowse2, (paddingTop + self.BAND_HEIGHT))), + outline=self.CLICKABLE_WEBQTL_REGION_COLOR, + fill=self.CLICKABLE_WEBQTL_REGION_COLOR) + im_drawer.line( + xy=((xBrowse1, paddingTop),( xBrowse1, (paddingTop + self.BAND_HEIGHT))), + fill=self.CLICKABLE_WEBQTL_REGION_OUTLINE_COLOR) if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": PHENOGEN_COORDS = "%d, %d, %d, %d" % (xBrowse1, phenogenPaddingTop, xBrowse2, (phenogenPaddingTop+self.BAND_HEIGHT)) @@ -1474,8 +1639,14 @@ class DisplayMappingResults(object): PHENOGEN_HREF = "https://phenogen.org/gene.jsp?speciesCB=Mm&auto=Y&geneTxt=chr%s:%d-%d&genomeVer=mm10" % (self.selectedChr, max(0, calBase-flankingWidthInBases), calBase+flankingWidthInBases) PHENOGEN_TITLE = "Click to view this section of the genome in PhenoGen" gifmap.areas.append(HT.Area(shape='rect',coords=PHENOGEN_COORDS,href=PHENOGEN_HREF, title=PHENOGEN_TITLE)) - canvas.drawRect(xBrowse1, phenogenPaddingTop, xBrowse2, (phenogenPaddingTop+self.BAND_HEIGHT), edgeColor=self.CLICKABLE_PHENOGEN_REGION_COLOR, fillColor=self.CLICKABLE_PHENOGEN_REGION_COLOR) - canvas.drawLine(xBrowse1, phenogenPaddingTop, xBrowse1, (phenogenPaddingTop+self.BAND_HEIGHT), color=self.CLICKABLE_PHENOGEN_REGION_OUTLINE_COLOR) + im_drawer.rectangle( + xy=((xBrowse1, phenogenPaddingTop), + (xBrowse2, (phenogenPaddingTop+self.BAND_HEIGHT))), + outline=self.CLICKABLE_PHENOGEN_REGION_COLOR, + fill=self.CLICKABLE_PHENOGEN_REGION_COLOR) + im_drawer.line( + xy=((xBrowse1, phenogenPaddingTop),( xBrowse1, (phenogenPaddingTop+self.BAND_HEIGHT))), + fill=self.CLICKABLE_PHENOGEN_REGION_OUTLINE_COLOR) UCSC_COORDS = "%d, %d, %d, %d" %(xBrowse1, ucscPaddingTop, xBrowse2, (ucscPaddingTop+self.BAND_HEIGHT)) if self.dataset.group.species == "mouse": @@ -1484,8 +1655,15 @@ class DisplayMappingResults(object): UCSC_HREF = "http://genome.ucsc.edu/cgi-bin/hgTracks?db=%s&position=chr%s:%d-%d" % (self._ucscDb, self.selectedChr, max(0, calBase-flankingWidthInBases), calBase+flankingWidthInBases) UCSC_TITLE = "Click to view this section of the genome in the UCSC Genome Browser" gifmap.areas.append(HT.Area(shape='rect',coords=UCSC_COORDS,href=UCSC_HREF, title=UCSC_TITLE)) - canvas.drawRect(xBrowse1, ucscPaddingTop, xBrowse2, (ucscPaddingTop+self.BAND_HEIGHT), edgeColor=self.CLICKABLE_UCSC_REGION_COLOR, fillColor=self.CLICKABLE_UCSC_REGION_COLOR) - canvas.drawLine(xBrowse1, ucscPaddingTop, xBrowse1, (ucscPaddingTop+self.BAND_HEIGHT), color=self.CLICKABLE_UCSC_REGION_OUTLINE_COLOR) + im_drawer.rectangle( + xy=((xBrowse1, ucscPaddingTop), + (xBrowse2, (ucscPaddingTop+self.BAND_HEIGHT))), + outline=self.CLICKABLE_UCSC_REGION_COLOR, + fill=self.CLICKABLE_UCSC_REGION_COLOR) + im_drawer.line( + xy=((xBrowse1, ucscPaddingTop), + (xBrowse1, (ucscPaddingTop+self.BAND_HEIGHT))), + fill=self.CLICKABLE_UCSC_REGION_OUTLINE_COLOR) ENSEMBL_COORDS = "%d, %d, %d, %d" %(xBrowse1, ensemblPaddingTop, xBrowse2, (ensemblPaddingTop+self.BAND_HEIGHT)) if self.dataset.group.species == "mouse": @@ -1494,32 +1672,59 @@ class DisplayMappingResults(object): ENSEMBL_HREF = "http://www.ensembl.org/Rattus_norvegicus/contigview?chr=%s&start=%d&end=%d" % (self.selectedChr, max(0, calBase-flankingWidthInBases), calBase+flankingWidthInBases) ENSEMBL_TITLE = "Click to view this section of the genome in the Ensembl Genome Browser" gifmap.areas.append(HT.Area(shape='rect',coords=ENSEMBL_COORDS,href=ENSEMBL_HREF, title=ENSEMBL_TITLE)) - canvas.drawRect(xBrowse1, ensemblPaddingTop, xBrowse2, (ensemblPaddingTop+self.BAND_HEIGHT), edgeColor=self.CLICKABLE_ENSEMBL_REGION_COLOR, fillColor=self.CLICKABLE_ENSEMBL_REGION_COLOR) - canvas.drawLine(xBrowse1, ensemblPaddingTop, xBrowse1, (ensemblPaddingTop+self.BAND_HEIGHT), color=self.CLICKABLE_ENSEMBL_REGION_OUTLINE_COLOR) + im_drawer.rectangle( + xy=((xBrowse1, ensemblPaddingTop), + (xBrowse2, (ensemblPaddingTop+self.BAND_HEIGHT))), + outline=self.CLICKABLE_ENSEMBL_REGION_COLOR, + fill=self.CLICKABLE_ENSEMBL_REGION_COLOR) + im_drawer.line( + xy=((xBrowse1, ensemblPaddingTop), + (xBrowse1, (ensemblPaddingTop+self.BAND_HEIGHT))), + fill=self.CLICKABLE_ENSEMBL_REGION_OUTLINE_COLOR) # end for - canvas.drawString("Click to view the corresponding section of the genome in an 8x expanded WebQTL map", (xLeftOffset + 10), paddingTop + self.BAND_HEIGHT/2, font=clickableRegionLabelFont, color=self.CLICKABLE_WEBQTL_TEXT_COLOR) + im_drawer.text( + text="Click to view the corresponding section of the genome in an 8x expanded WebQTL map", + xy=((xLeftOffset + 10), paddingTop + self.BAND_HEIGHT/2), + font=clickableRegionLabelFont, + fill=self.CLICKABLE_WEBQTL_TEXT_COLOR) if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": - canvas.drawString("Click to view the corresponding section of the genome in PhenoGen", (xLeftOffset + 10), phenogenPaddingTop + self.BAND_HEIGHT/2, font=clickableRegionLabelFont, color=self.CLICKABLE_PHENOGEN_TEXT_COLOR) - canvas.drawString("Click to view the corresponding section of the genome in the UCSC Genome Browser", (xLeftOffset + 10), ucscPaddingTop + self.BAND_HEIGHT/2, font=clickableRegionLabelFont, color=self.CLICKABLE_UCSC_TEXT_COLOR) - canvas.drawString("Click to view the corresponding section of the genome in the Ensembl Genome Browser", (xLeftOffset+10), ensemblPaddingTop + self.BAND_HEIGHT/2, font=clickableRegionLabelFont, color=self.CLICKABLE_ENSEMBL_TEXT_COLOR) + im_drawer.text( + text="Click to view the corresponding section of the genome in PhenoGen", + xy=((xLeftOffset + 10), phenogenPaddingTop + self.BAND_HEIGHT/2), + font=clickableRegionLabelFont, fill=self.CLICKABLE_PHENOGEN_TEXT_COLOR) + im_drawer.text( + text="Click to view the corresponding section of the genome in the UCSC Genome Browser", + xy=((xLeftOffset + 10), ucscPaddingTop + self.BAND_HEIGHT/2), + font=clickableRegionLabelFont, fill=self.CLICKABLE_UCSC_TEXT_COLOR) + im_drawer.text( + text="Click to view the corresponding section of the genome in the Ensembl Genome Browser", + xy=((xLeftOffset+10), ensemblPaddingTop + self.BAND_HEIGHT/2), + font=clickableRegionLabelFont, fill=self.CLICKABLE_ENSEMBL_TEXT_COLOR) #draw the gray text - chrFont = pid.Font(ttf="verdana", size=26*zoom, bold=1) - traitFont = pid.Font(ttf="verdana", size=14, bold=0) - chrX = xLeftOffset + plotWidth - 2 - canvas.stringWidth("Chr %s" % self.ChrList[self.selectedChr][0], font=chrFont) - canvas.drawString("Chr %s" % self.ChrList[self.selectedChr][0], chrX, ensemblPaddingTop-5, font=chrFont, color=GRAY) + chrFont = ImageFont.truetype(font=VERDANA_FILE, size=26*zoom) + traitFont = ImageFont.truetype(font=VERDANA_FILE, size=14) + chrX = xLeftOffset + plotWidth - 2 - im_drawer.textsize( + "Chr %s" % self.ChrList[self.selectedChr][0], font=chrFont)[0] + im_drawer.text( + text="Chr %s" % self.ChrList[self.selectedChr][0], + xy=(chrX, ensemblPaddingTop-5), font=chrFont, fill=GRAY) # end of drawBrowserClickableRegions else: #draw the gray text - chrFont = pid.Font(ttf="verdana", size=26*zoom, bold=1) - traitFont = pid.Font(ttf="verdana", size=14, bold=0) - chrX = xLeftOffset + (plotWidth - canvas.stringWidth("Chr %s" % currentChromosome, font=chrFont))/2 - canvas.drawString("Chr %s" % currentChromosome, chrX, 32, font=chrFont, color=GRAY) + chrFont = ImageFont.truetype(font=VERDANA_FILE, size=26*zoom) + traitFont = ImageFont.truetype(font=VERDANA_FILE, size=14) + chrX = xLeftOffset + (plotWidth - im_drawer.textsize( + "Chr %s" % currentChromosome, font=chrFont)[0])/2 + im_drawer.text( + text="Chr %s" % currentChromosome, xy=(chrX, 32), font=chrFont, + fill=GRAY) # end of drawBrowserClickableRegions pass def drawXAxis(self, canvas, drawAreaHeight, gifmap, plotXScale, showLocusForm, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): + im_drawer = ImageDraw.Draw(canvas) xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset plotWidth = canvas.size[0] - xLeftOffset - xRightOffset plotHeight = canvas.size[1] - yTopOffset - yBottomOffset @@ -1535,7 +1740,7 @@ class DisplayMappingResults(object): X_AXIS_THICKNESS = 1*zoom # ======= Alex: Draw the X-axis labels (megabase location) - MBLabelFont = pid.Font(ttf="verdana", size=15*zoom, bold=0) + MBLabelFont = ImageFont.truetype(font=VERDANA_FILE, size=15*zoom) xMajorTickHeight = 10 * zoom # How high the tick extends below the axis xMinorTickHeight = 5*zoom xAxisTickMarkColor = BLACK @@ -1544,7 +1749,7 @@ class DisplayMappingResults(object): spacingFromLabelToAxis = 5 if self.plotScale == 'physic': - strYLoc = yZero + spacingFromLabelToAxis + canvas.fontHeight(MBLabelFont) + strYLoc = yZero + spacingFromLabelToAxis + MBLabelFont.font.height ###Physical single chromosome view if self.selectedChr > -1: XScale = Plot.detScale(startMb, endMb) @@ -1565,13 +1770,21 @@ class DisplayMappingResults(object): continue Xc = xLeftOffset + plotXScale*(_Mb - startMb) if counter % NUM_MINOR_TICKS == 0: # Draw a MAJOR mark, not just a minor tick mark - canvas.drawLine(Xc, yZero, Xc, yZero+xMajorTickHeight, color=xAxisTickMarkColor, width=X_MAJOR_TICK_THICKNESS) # Draw the MAJOR tick mark + im_drawer.line(xy=((Xc,yZero), + (Xc,yZero+xMajorTickHeight)), + fill=xAxisTickMarkColor, + width=X_MAJOR_TICK_THICKNESS) # Draw the MAJOR tick mark labelStr = str(formatStr % _Mb) # What Mbase location to put on the label - strWidth = canvas.stringWidth(labelStr, font=MBLabelFont) + strWidth, strHeight = im_drawer.textsize(labelStr, font=MBLabelFont) drawStringXc = (Xc - (strWidth / 2.0)) - canvas.drawString(labelStr, drawStringXc, strYLoc, font=MBLabelFont, color=xAxisLabelColor, angle=0) + im_drawer.text(xy=(drawStringXc, strYLoc), + text=labelStr, font=MBLabelFont, + fill=xAxisLabelColor) else: - canvas.drawLine(Xc, yZero, Xc, yZero+xMinorTickHeight, color=xAxisTickMarkColor, width=X_MINOR_TICK_THICKNESS) # Draw the MINOR tick mark + im_drawer.line(xy=((Xc,yZero), + (Xc,yZero+xMinorTickHeight)), + fill=xAxisTickMarkColor, + width=X_MINOR_TICK_THICKNESS) # Draw the MINOR tick mark ###Physical genome wide view else: @@ -1586,17 +1799,27 @@ class DisplayMappingResults(object): else: distScale = 5 for j, tickdists in enumerate(range(distScale, int(ceil(distLen)), distScale)): - canvas.drawLine(startPosX + tickdists*plotXScale, yZero, startPosX + tickdists*plotXScale, yZero + 7, color=BLACK, width=1*zoom) + im_drawer.line( + xy=((startPosX+tickdists*plotXScale, yZero), + (startPosX+tickdists*plotXScale, yZero + 7)), + fill=BLACK, width=1*zoom) if j % 2 == 0: - canvas.drawString(str(tickdists), startPosX+tickdists*plotXScale, yZero + 10*zoom, color=BLACK, font=MBLabelFont, angle=270) + im_drawer.text( + xy=(startPosX+tickdists*plotXScale, yZero+10*zoom), + text=str(tickdists), fill=BLACK, font=MBLabelFont, angle=270) startPosX += (self.ChrLengthDistList[i]+self.GraphInterval)*plotXScale - megabaseLabelFont = pid.Font(ttf="verdana", size=18*zoom*1.5, bold=0) - canvas.drawString("Megabases", xLeftOffset + (plotWidth - canvas.stringWidth("Megabases", font=megabaseLabelFont))/2, - strYLoc + canvas.fontHeight(MBLabelFont)+ 10*(zoom%2) + 10, font=megabaseLabelFont, color=BLACK) + megabaseLabelFont = ImageFont.truetype(font=VERDANA_FILE, size=int(18*zoom*1.5)) + im_drawer.text( + text="Megabases", + xy=( + xLeftOffset+(plotWidth-im_drawer.textsize( + "Megabases",font=megabaseLabelFont)[0])/2, + strYLoc+MBLabelFont.font.height+10*(zoom%2)+ 10), + font=megabaseLabelFont, fill=BLACK) pass else: - strYLoc = yZero + spacingFromLabelToAxis + canvas.fontHeight(MBLabelFont) + 8 + strYLoc = yZero + spacingFromLabelToAxis + MBLabelFont.font.height + 8 ChrAInfo = [] preLpos = -1 distinctCount = 0.0 @@ -1659,18 +1882,26 @@ class DisplayMappingResults(object): else: Zorder = 0 if differ: - canvas.drawLine(startPosX+Lpos,yZero,xLeftOffset+offsetA,\ - yZero+25, color=lineColor) - canvas.drawLine(xLeftOffset+offsetA,yZero+25,xLeftOffset+offsetA,\ - yZero+40+Zorder*(LRectWidth+3),color=lineColor) + im_drawer.line( + xy=((startPosX+Lpos,yZero),(xLeftOffset+offsetA,\ + yZero+25)), + fill=lineColor) + im_drawer.line( + xy=((xLeftOffset+offsetA,yZero+25),(xLeftOffset+offsetA,\ + yZero+40+Zorder*(LRectWidth+3))), + fill=lineColor) rectColor = ORANGE else: - canvas.drawLine(xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3)-3,\ - xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3),color=lineColor) + im_drawer.line( + xy=((xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3)-3),(\ + xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3))), + fill=lineColor) rectColor = DEEPPINK - canvas.drawRect(xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3),\ - xLeftOffset+offsetA-LRectHeight,yZero+40+Zorder*(LRectWidth+3)+LRectWidth,\ - edgeColor=rectColor,fillColor=rectColor,edgeWidth = 0) + im_drawer.rectangle( + xy=((xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3)), + (xLeftOffset+offsetA-LRectHeight, + yZero+40+Zorder*(LRectWidth+3)+LRectWidth)), + outline=rectColor,fill=rectColor,width = 0) COORDS="%d,%d,%d,%d"%(xLeftOffset+offsetA-LRectHeight, yZero+40+Zorder*(LRectWidth+3),\ xLeftOffset+offsetA,yZero+40+Zorder*(LRectWidth+3)+LRectWidth) HREF="/show_trait?trait_id=%s&dataset=%s" % (Lname, self.dataset.group.name+"Geno") @@ -1679,17 +1910,25 @@ class DisplayMappingResults(object): gifmap.areas.append(Areas) ##piddle bug if j == 0: - canvas.drawLine(startPosX,yZero,startPosX,yZero+40, color=lineColor) + im_drawer.line( + xy=((startPosX,yZero),(startPosX,yZero+40)), + fill=lineColor) startPosX += (self.ChrLengthDistList[j]+self.GraphInterval)*plotXScale - centimorganLabelFont = pid.Font(ttf="verdana", size=18*zoom*1.5, bold=0) - canvas.drawString("Centimorgans", xLeftOffset + (plotWidth - canvas.stringWidth("Megabases", font=centimorganLabelFont))/2, - strYLoc + canvas.fontHeight(MBLabelFont)+ 10*(zoom%2) + 10, font=centimorganLabelFont, color=BLACK) + centimorganLabelFont = ImageFont.truetype(font=VERDANA_FILE, size=18*zoom*1.5) + im_drawer.text( + text="Centimorgans", + xy=(xLeftOffset+(plotWidth-im_drawer.textsize( + "Megabases", font=centimorganLabelFont)[0])/2, + strYLoc + MBLabelFont.font.height+ 10*(zoom%2) + 10), + font=centimorganLabelFont, fill=BLACK) - canvas.drawLine(xLeftOffset, yZero, xLeftOffset+plotWidth, yZero, color=BLACK, width=X_AXIS_THICKNESS) # Draw the X axis itself + im_drawer.line(xy=((xLeftOffset,yZero), (xLeftOffset+plotWidth,yZero)), + fill=BLACK, width=X_AXIS_THICKNESS) # Draw the X axis itself def drawQTL(self, canvas, drawAreaHeight, gifmap, plotXScale, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): + im_drawer = ImageDraw.Draw(canvas) xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset plotWidth = canvas.size[0] - xLeftOffset - xRightOffset plotHeight = canvas.size[1] - yTopOffset - yBottomOffset @@ -1787,29 +2026,40 @@ class DisplayMappingResults(object): else: all_int = False break - - if all_int: - max_lrs_width = canvas.stringWidth("%d" % LRS_LOD_Max, font=LRSScaleFont) + 40 - else: - max_lrs_width = canvas.stringWidth("%2.1f" % LRS_LOD_Max, font=LRSScaleFont) + 30 + # TODO(PIL): Convert from PIL + # if all_int: + # max_lrs_width = canvas.stringWidth("%d" % LRS_LOD_Max, font=LRSScaleFont) + 40 + # else: + # max_lrs_width = canvas.stringWidth("%2.1f" % LRS_LOD_Max, font=LRSScaleFont) + 30 #draw the "LRS" or "LOD" string to the left of the axis - canvas.drawString(self.LRS_LOD, xLeftOffset - max_lrs_width - 15*(zoom-1), \ - yZero - 150 - 300*(zoom - 1), font=LRSLODFont, color=BLACK, angle=90) + LRSScaleFont=ImageFont.truetype(font=VERDANA_FILE, size=16*zoom) + LRSLODFont=ImageFont.truetype(font=VERDANA_FILE, size=int(18*zoom*1.5)) + yZero = yTopOffset + plotHeight + + im_drawer.text( + text=self.LRS_LOD, + xy=(xLeftOffset - im_drawer.textsize("999.99", font=LRSScaleFont)[0] - 15*(zoom-1), + yZero - 150 - 300*(zoom - 1)), + font=LRSLODFont, fill=BLACK, angle=90) for item in LRSAxisList: if LRS_LOD_Max == 0.0: LRS_LOD_Max = 0.000001 yTopOffset + 30*(zoom - 1) yLRS = yZero - (item/LRS_LOD_Max) * LRSHeightThresh - #yLRS = yZero - (item/LRSAxisList[-1]) * LRSHeightThresh - canvas.drawLine(xLeftOffset, yLRS, xLeftOffset - 4, yLRS, color=self.LRS_COLOR, width=1*zoom) + im_drawer.line(xy=((xLeftOffset,yLRS), (xLeftOffset-4,yLRS)), + fill=self.LRS_COLOR, width=1*zoom) if all_int: scaleStr = "%d" % item else: scaleStr = "%2.1f" % item #Draw the LRS/LOD Y axis label - canvas.drawString(scaleStr, xLeftOffset-4-canvas.stringWidth(scaleStr, font=LRSScaleFont)-5, yLRS+3, font=LRSScaleFont, color=self.LRS_COLOR) + im_drawer.text( + text=scaleStr, + xy=(xLeftOffset-4-im_drawer.textsize(scaleStr, font=LRSScaleFont)[0]-5, + yLRS+3), + font=LRSScaleFont, fill=self.LRS_COLOR) if self.permChecked and self.nperm > 0 and not self.multipleInterval: significantY = yZero - self.significant*LRSHeightThresh/LRS_LOD_Max @@ -1824,10 +2074,19 @@ class DisplayMappingResults(object): #ZS: I don't know if what I did here with this inner function is clever or overly complicated, but it's the only way I could think of to avoid duplicating the code inside this function def add_suggestive_significant_lines_and_legend(start_pos_x, chr_length_dist): rightEdge = int(start_pos_x + chr_length_dist*plotXScale - self.SUGGESTIVE_WIDTH/1.5) - canvas.drawLine(start_pos_x+self.SUGGESTIVE_WIDTH/1.5, suggestiveY, rightEdge, suggestiveY, color=self.SUGGESTIVE_COLOR, - width=self.SUGGESTIVE_WIDTH*zoom, clipX=(xLeftOffset, xLeftOffset + plotWidth-2)) - canvas.drawLine(start_pos_x+self.SUGGESTIVE_WIDTH/1.5, significantY, rightEdge, significantY, color=self.SIGNIFICANT_COLOR, - width=self.SIGNIFICANT_WIDTH*zoom, clipX=(xLeftOffset, xLeftOffset + plotWidth-2)) + im_drawer.line( + xy=((start_pos_x+self.SUGGESTIVE_WIDTH/1.5, suggestiveY), + (rightEdge, suggestiveY)), + fill=self.SUGGESTIVE_COLOR, width=self.SUGGESTIVE_WIDTH*zoom + #,clipX=(xLeftOffset, xLeftOffset + plotWidth-2) + ) + im_drawer.line( + xy=((start_pos_x+self.SUGGESTIVE_WIDTH/1.5, significantY), + (rightEdge, significantY)), + fill=self.SIGNIFICANT_COLOR, + width=self.SIGNIFICANT_WIDTH*zoom + #, clipX=(xLeftOffset, xLeftOffset + plotWidth-2) + ) sugg_coords = "%d, %d, %d, %d" % (start_pos_x, suggestiveY-2, rightEdge + 2*zoom, suggestiveY+2) sig_coords = "%d, %d, %d, %d" % (start_pos_x, significantY-2, rightEdge + 2*zoom, significantY+2) if self.LRS_LOD == 'LRS': @@ -1868,7 +2127,7 @@ class DisplayMappingResults(object): AdditiveCoordXY = [] DominanceCoordXY = [] - symbolFont = pid.Font(ttf="fnt_bs", size=5,bold=0) #ZS: For Manhattan Plot + symbolFont = ImageFont.truetype(font=FNT_BS_FILE, size=5) #ZS: For Manhattan Plot previous_chr = 1 previous_chr_as_int = 0 @@ -1880,7 +2139,12 @@ class DisplayMappingResults(object): thisLRSColor = self.colorCollection[0] if qtlresult['chr'] != previous_chr and self.selectedChr == -1: if self.manhattan_plot != True: - canvas.drawPolygon(LRSCoordXY,edgeColor=thisLRSColor,closed=0, edgeWidth=lrsEdgeWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.polygon( + xy=LRSCoordXY, + outline=thisLRSColor + # , closed=0, edgeWidth=lrsEdgeWidth, + # clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) if not self.multipleInterval and self.additiveChecked: plusColor = self.ADDITIVE_COLOR_POSITIVE @@ -1895,22 +2159,58 @@ class DisplayMappingResults(object): else: Xcm = (yZero-Yc0)/((Yc-Yc0)/(Xc-Xc0)) +Xc0 if Yc0 < yZero: - canvas.drawLine(Xc0, Yc0, Xcm, yZero, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) - canvas.drawLine(Xcm, yZero, Xc, yZero-(Yc-yZero), color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, Yc0), (Xcm, yZero)), + fill=plusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, yZero-(Yc-yZero))), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: - canvas.drawLine(Xc0, yZero - (Yc0-yZero), Xcm, yZero, color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) - canvas.drawLine(Xcm, yZero, Xc, Yc, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, yZero-(Yc0-yZero)), + (Xcm, yZero)), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, Yc)), + fill=plusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) elif (Yc0-yZero)*(Yc-yZero) > 0: if Yc < yZero: - canvas.drawLine(Xc0, Yc0, Xc, Yc, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), + fill=plusColor, + width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: - canvas.drawLine(Xc0, yZero - (Yc0-yZero), Xc, yZero - (Yc-yZero), color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, yZero - (Yc0-yZero)), + (Xc, yZero - (Yc-yZero))), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: minYc = min(Yc-yZero, Yc0-yZero) if minYc < 0: - canvas.drawLine(Xc0, Yc0, Xc, Yc, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), + fill=plusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: - canvas.drawLine(Xc0, yZero - (Yc0-yZero), Xc, yZero - (Yc-yZero), color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, yZero - (Yc0-yZero)), + (Xc, yZero - (Yc-yZero))), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) LRSCoordXY = [] AdditiveCoordXY = [] @@ -1979,15 +2279,12 @@ class DisplayMappingResults(object): point_color = RED else: point_color = BLUE - - final_x_pos = Xc-canvas.stringWidth("5",font=symbolFont)/2+1 - if final_x_pos > (xLeftOffset + plotWidth): - continue - #break ZS: This is temporary until issue with sorting for GEMMA is fixed - elif final_x_pos < xLeftOffset: - continue - else: - canvas.drawString("5", final_x_pos,Yc+2,color=point_color, font=symbolFont) + im_drawer.text( + text="5", + xy=( + Xc-im_drawer.textsize("5",font=symbolFont)[0]/2+1, + Yc+2), + fill=point_color, font=symbolFont) else: LRSCoordXY.append((Xc, Yc)) @@ -2000,7 +2297,11 @@ class DisplayMappingResults(object): m += 1 if self.manhattan_plot != True: - canvas.drawPolygon(LRSCoordXY,edgeColor=thisLRSColor,closed=0, edgeWidth=lrsEdgeWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.polygon( + xy=LRSCoordXY, + outline=thisLRSColor + #, closed=0, edgeWidth=lrsEdgeWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) if not self.multipleInterval and self.additiveChecked: plusColor = self.ADDITIVE_COLOR_POSITIVE @@ -2015,22 +2316,57 @@ class DisplayMappingResults(object): else: Xcm = (yZero-Yc0)/((Yc-Yc0)/(Xc-Xc0)) +Xc0 if Yc0 < yZero: - canvas.drawLine(Xc0, Yc0, Xcm, yZero, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) - canvas.drawLine(Xcm, yZero, Xc, yZero-(Yc-yZero), color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, Yc0), (Xcm, yZero)), + fill=plusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, yZero-(Yc-yZero))), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: - canvas.drawLine(Xc0, yZero - (Yc0-yZero), Xcm, yZero, color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) - canvas.drawLine(Xcm, yZero, Xc, Yc, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, yZero - (Yc0-yZero)), + (Xcm, yZero)), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, Yc)), + fill=plusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) elif (Yc0-yZero)*(Yc-yZero) > 0: if Yc < yZero: - canvas.drawLine(Xc0, Yc0, Xc, Yc, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), fill=plusColor, + width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: - canvas.drawLine(Xc0, yZero - (Yc0-yZero), Xc, yZero - (Yc-yZero), color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0,yZero-(Yc0-yZero)), + (Xc,yZero-(Yc-yZero))), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: minYc = min(Yc-yZero, Yc0-yZero) if minYc < 0: - canvas.drawLine(Xc0, Yc0, Xc, Yc, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), + fill=plusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: - canvas.drawLine(Xc0, yZero - (Yc0-yZero), Xc, yZero - (Yc-yZero), color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, yZero - (Yc0-yZero)), + (Xc, yZero - (Yc-yZero))), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) if not self.multipleInterval and INTERCROSS and self.dominanceChecked: plusColor = self.DOMINANCE_COLOR_POSITIVE @@ -2045,27 +2381,61 @@ class DisplayMappingResults(object): else: Xcm = (yZero-Yc0)/((Yc-Yc0)/(Xc-Xc0)) +Xc0 if Yc0 < yZero: - canvas.drawLine(Xc0, Yc0, Xcm, yZero, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) - canvas.drawLine(Xcm, yZero, Xc, yZero-(Yc-yZero), color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, Yc0), (Xcm, yZero)), + fill=plusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, yZero-(Yc-yZero))), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: - canvas.drawLine(Xc0, yZero - (Yc0-yZero), Xcm, yZero, color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) - canvas.drawLine(Xcm, yZero, Xc, Yc, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, yZero - (Yc0-yZero)), (Xcm, yZero)), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) + im_drawer.line( + xy=((Xcm, yZero), (Xc, Yc)), + fill=plusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) elif (Yc0-yZero)*(Yc-yZero) > 0: if Yc < yZero: - canvas.drawLine(Xc0, Yc0, Xc, Yc, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), + fill=plusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: - canvas.drawLine(Xc0, yZero - (Yc0-yZero), Xc, yZero - (Yc-yZero), color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, yZero - (Yc0-yZero)), + (Xc, yZero - (Yc-yZero))), + fill=minusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: minYc = min(Yc-yZero, Yc0-yZero) if minYc < 0: - canvas.drawLine(Xc0, Yc0, Xc, Yc, color=plusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, Yc0), (Xc, Yc)), + fill=plusColor, width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) else: - canvas.drawLine(Xc0, yZero - (Yc0-yZero), Xc, yZero - (Yc-yZero), color=minusColor, width=lineWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth)) + im_drawer.line( + xy=((Xc0, yZero - (Yc0-yZero)), + (Xc, yZero - (Yc-yZero))), fill=minusColor, + width=lineWidth + #, clipX=(xLeftOffset, xLeftOffset + plotWidth) + ) ###draw additive scale if not self.multipleInterval and self.additiveChecked: - additiveScaleFont=pid.Font(ttf="verdana",size=16*zoom,bold=0) + additiveScaleFont=ImageFont.truetype(font=VERDANA_FILE,size=16*zoom) additiveScale = Plot.detScaleOld(0,additiveMax) additiveStep = (additiveScale[1]-additiveScale[0])/additiveScale[2] additiveAxisList = Plot.frange(0, additiveScale[1], additiveStep) @@ -2074,13 +2444,24 @@ class DisplayMappingResults(object): additiveAxisList.append(additiveScale[1]) for item in additiveAxisList: additiveY = yZero - item*addPlotScale - canvas.drawLine(xLeftOffset + plotWidth,additiveY,xLeftOffset+4+ plotWidth,additiveY,color=self.ADDITIVE_COLOR_POSITIVE, width=1*zoom) + im_drawer.line( + xy=((xLeftOffset + plotWidth,additiveY), + (xLeftOffset+4+ plotWidth,additiveY)), + fill=self.ADDITIVE_COLOR_POSITIVE, width=1*zoom) scaleStr = "%2.3f" % item - canvas.drawString(scaleStr,xLeftOffset + plotWidth +6,additiveY+5,font=additiveScaleFont,color=self.ADDITIVE_COLOR_POSITIVE) + im_drawer.text( + text=scaleStr, + xy=(xLeftOffset + plotWidth +6,additiveY+5), + font=additiveScaleFont,fill=self.ADDITIVE_COLOR_POSITIVE) - canvas.drawLine(xLeftOffset+plotWidth,additiveY,xLeftOffset+plotWidth,yZero,color=self.ADDITIVE_COLOR_POSITIVE, width=1*zoom) + im_drawer.line( + xy=((xLeftOffset+plotWidth,additiveY), + (xLeftOffset+plotWidth,yZero)), + fill=self.ADDITIVE_COLOR_POSITIVE, width=1*zoom) - canvas.drawLine(xLeftOffset, yZero, xLeftOffset, yTopOffset + 30*(zoom - 1), color=self.LRS_COLOR, width=1*zoom) #the blue line running up the y axis + im_drawer.line( + xy=((xLeftOffset, yZero), (xLeftOffset, yTopOffset + 30*(zoom - 1))), + fill=self.LRS_COLOR, width=1*zoom) #the blue line running up the y axis def drawGraphBackground(self, canvas, gifmap, offset= (80, 120, 80, 50), zoom = 1, startMb = None, endMb = None): @@ -2088,6 +2469,7 @@ class DisplayMappingResults(object): ##multiple Chromosome view ##single Chromosome Physical ##single Chromosome Genetic + im_drawer = ImageDraw.Draw(canvas) xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset plotWidth = canvas.size[0] - xLeftOffset - xRightOffset plotHeight = canvas.size[1] - yTopOffset - yBottomOffset @@ -2114,8 +2496,10 @@ class DisplayMappingResults(object): else: theBackColor = self.GRAPH_BACK_LIGHT_COLOR i += 1 - canvas.drawRect(startPix, yTopOffset, min(startPix+spacingAmt, xLeftOffset+plotWidth), \ - yBottom, edgeColor=theBackColor, fillColor=theBackColor) + im_drawer.rectangle( + [(startPix, yTopOffset), + (min(startPix+spacingAmt, xLeftOffset+plotWidth), yBottom)], + outline=theBackColor, fill=theBackColor) drawRegionDistance = self.ChrLengthDistList[self.ChrList[self.selectedChr][1]] self.ChrLengthDistList = [drawRegionDistance] @@ -2132,7 +2516,7 @@ class DisplayMappingResults(object): chrFontZoom = 2 else: chrFontZoom = 1 - chrLabelFont=pid.Font(ttf="verdana",size=24*chrFontZoom,bold=0) + chrLabelFont=ImageFont.truetype(font=VERDANA_FILE,size=24*chrFontZoom) for i, _chr in enumerate(self.genotype): if (i % 2 == 0): @@ -2141,14 +2525,18 @@ class DisplayMappingResults(object): theBackColor = self.GRAPH_BACK_LIGHT_COLOR #draw the shaded boxes and the sig/sug thick lines - canvas.drawRect(startPosX, yTopOffset, startPosX + self.ChrLengthDistList[i]*plotXScale, \ - yBottom, edgeColor=GAINSBORO,fillColor=theBackColor) + im_drawer.rectangle( + ((startPosX, yTopOffset), + (startPosX + self.ChrLengthDistList[i]*plotXScale, yBottom)), + outline=GAINSBORO, + fill=theBackColor) - chrNameWidth = canvas.stringWidth(_chr.name, font=chrLabelFont) + chrNameWidth, chrNameHeight = im_drawer.textsize(_chr.name, font=chrLabelFont) chrStartPix = startPosX + (self.ChrLengthDistList[i]*plotXScale -chrNameWidth)/2 chrEndPix = startPosX + (self.ChrLengthDistList[i]*plotXScale +chrNameWidth)/2 - canvas.drawString(_chr.name, chrStartPix, yTopOffset + 20 ,font = chrLabelFont,color=BLACK) + im_drawer.text(xy=(chrStartPix, yTopOffset + 20), + text=_chr.name, font=chrLabelFont, fill=BLACK) COORDS = "%d,%d,%d,%d" %(chrStartPix, yTopOffset, chrEndPix,yTopOffset +20) #add by NL 09-03-2010 @@ -2164,7 +2552,7 @@ class DisplayMappingResults(object): ######################################### # Permutation Graph ######################################### - myCanvas = pid.PILCanvas(size=(500,300)) + myCanvas = Image.new("RGBA", size=(500, 300)) if 'lod_score' in self.qtlresults[0] and self.LRS_LOD == "LRS": perm_output = [value*4.61 for value in self.perm_output] elif 'lod_score' not in self.qtlresults[0] and self.LRS_LOD == "LOD": @@ -2174,7 +2562,8 @@ class DisplayMappingResults(object): filename= webqtlUtil.genRandStr("Reg_") Plot.plotBar(myCanvas, perm_output, XLabel=self.LRS_LOD, YLabel='Frequency', title=' Histogram of Permutation Test') - myCanvas.save(GENERATED_IMAGE_DIR+filename, format='gif') + myCanvas.save("{}.gif".format(GENERATED_IMAGE_DIR+filename), + format='gif') return filename -- cgit v1.2.3 From 3d5bff591766d6cea659f041c87b6ef4ff359bee Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Mon, 18 Feb 2019 14:34:08 +0300 Subject: Add missing font files * wqflask/fonts/ttf: Add ttf font files. --- wqflask/fonts/ttf/arial.ttf | Bin 0 -> 151232 bytes wqflask/fonts/ttf/courbd.ttf | Bin 0 -> 181388 bytes wqflask/fonts/ttf/fnt_bs.ttf | Bin 0 -> 20988 bytes wqflask/fonts/ttf/tahoma.ttf | Bin 0 -> 249012 bytes wqflask/fonts/ttf/trebucbd.ttf | Bin 0 -> 123828 bytes wqflask/fonts/ttf/verdana.ttf | Bin 0 -> 139640 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 wqflask/fonts/ttf/arial.ttf create mode 100644 wqflask/fonts/ttf/courbd.ttf create mode 100644 wqflask/fonts/ttf/fnt_bs.ttf create mode 100644 wqflask/fonts/ttf/tahoma.ttf create mode 100644 wqflask/fonts/ttf/trebucbd.ttf create mode 100644 wqflask/fonts/ttf/verdana.ttf diff --git a/wqflask/fonts/ttf/arial.ttf b/wqflask/fonts/ttf/arial.ttf new file mode 100644 index 00000000..bf0d4a95 Binary files /dev/null and b/wqflask/fonts/ttf/arial.ttf differ diff --git a/wqflask/fonts/ttf/courbd.ttf b/wqflask/fonts/ttf/courbd.ttf new file mode 100644 index 00000000..c8081467 Binary files /dev/null and b/wqflask/fonts/ttf/courbd.ttf differ diff --git a/wqflask/fonts/ttf/fnt_bs.ttf b/wqflask/fonts/ttf/fnt_bs.ttf new file mode 100644 index 00000000..712c38cf Binary files /dev/null and b/wqflask/fonts/ttf/fnt_bs.ttf differ diff --git a/wqflask/fonts/ttf/tahoma.ttf b/wqflask/fonts/ttf/tahoma.ttf new file mode 100644 index 00000000..bb314be3 Binary files /dev/null and b/wqflask/fonts/ttf/tahoma.ttf differ diff --git a/wqflask/fonts/ttf/trebucbd.ttf b/wqflask/fonts/ttf/trebucbd.ttf new file mode 100644 index 00000000..1ab1ae0a Binary files /dev/null and b/wqflask/fonts/ttf/trebucbd.ttf differ diff --git a/wqflask/fonts/ttf/verdana.ttf b/wqflask/fonts/ttf/verdana.ttf new file mode 100644 index 00000000..754a9b7b Binary files /dev/null and b/wqflask/fonts/ttf/verdana.ttf differ -- cgit v1.2.3 From d1e5ab462e9e8d147a4eedae57a541084f1bcdcc Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Sat, 23 Feb 2019 07:20:18 +0300 Subject: Update SNP Browser to Pillow * wqflask/wqflask/snp_browser/snp_browser.py: Use newer, and supported Pillow in place of obsoleted Piddle library. --- wqflask/wqflask/snp_browser/snp_browser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/snp_browser/snp_browser.py b/wqflask/wqflask/snp_browser/snp_browser.py index 73ab8798..1d28d76a 100644 --- a/wqflask/wqflask/snp_browser/snp_browser.py +++ b/wqflask/wqflask/snp_browser/snp_browser.py @@ -3,7 +3,7 @@ from __future__ import absolute_import, print_function, division from flask import Flask, g, url_for import string -import piddle as pid +from PIL import (Image) from utility.logger import getLogger logger = getLogger(__name__ ) @@ -629,7 +629,7 @@ class SnpBrowser(object): canvas_width = 900 canvas_height = 200 - snp_canvas = pid.PILCanvas(size=(canvas_width, canvas_height)) + snp_canvas = Image.new("RGBA", size=(canvas_width, canvas_height)) left_offset, right_offset, top_offset, bottom_offset = (30, 30, 40, 50) plot_width = canvas_width - left_offset - right_offset plot_height = canvas_height - top_offset - bottom_offset -- cgit v1.2.3 From 95dbb4a18a205b475ca8eb72210d7d2193062295 Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Sat, 23 Feb 2019 07:32:29 +0300 Subject: Fix font paths * wqflask/utility/Plot.py: Update font paths. * wqflask/fonts/ttf: Update font files. --- wqflask/fonts/ttf/arial.ttf | Bin 151232 -> 0 bytes wqflask/fonts/ttf/courbd.ttf | Bin 181388 -> 0 bytes wqflask/fonts/ttf/fnt_bs.ttf | Bin 20988 -> 0 bytes wqflask/fonts/ttf/tahoma.ttf | Bin 249012 -> 0 bytes wqflask/fonts/ttf/trebucbd.ttf | Bin 123828 -> 0 bytes wqflask/fonts/ttf/verdana.ttf | Bin 139640 -> 0 bytes wqflask/utility/Plot.py | 6 +++--- wqflask/wqflask/static/fonts/courbd.ttf | Bin 0 -> 181388 bytes wqflask/wqflask/static/fonts/tahoma.ttf | Bin 0 -> 249012 bytes 9 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 wqflask/fonts/ttf/arial.ttf delete mode 100644 wqflask/fonts/ttf/courbd.ttf delete mode 100644 wqflask/fonts/ttf/fnt_bs.ttf delete mode 100644 wqflask/fonts/ttf/tahoma.ttf delete mode 100644 wqflask/fonts/ttf/trebucbd.ttf delete mode 100644 wqflask/fonts/ttf/verdana.ttf create mode 100644 wqflask/wqflask/static/fonts/courbd.ttf create mode 100644 wqflask/wqflask/static/fonts/tahoma.ttf diff --git a/wqflask/fonts/ttf/arial.ttf b/wqflask/fonts/ttf/arial.ttf deleted file mode 100644 index bf0d4a95..00000000 Binary files a/wqflask/fonts/ttf/arial.ttf and /dev/null differ diff --git a/wqflask/fonts/ttf/courbd.ttf b/wqflask/fonts/ttf/courbd.ttf deleted file mode 100644 index c8081467..00000000 Binary files a/wqflask/fonts/ttf/courbd.ttf and /dev/null differ diff --git a/wqflask/fonts/ttf/fnt_bs.ttf b/wqflask/fonts/ttf/fnt_bs.ttf deleted file mode 100644 index 712c38cf..00000000 Binary files a/wqflask/fonts/ttf/fnt_bs.ttf and /dev/null differ diff --git a/wqflask/fonts/ttf/tahoma.ttf b/wqflask/fonts/ttf/tahoma.ttf deleted file mode 100644 index bb314be3..00000000 Binary files a/wqflask/fonts/ttf/tahoma.ttf and /dev/null differ diff --git a/wqflask/fonts/ttf/trebucbd.ttf b/wqflask/fonts/ttf/trebucbd.ttf deleted file mode 100644 index 1ab1ae0a..00000000 Binary files a/wqflask/fonts/ttf/trebucbd.ttf and /dev/null differ diff --git a/wqflask/fonts/ttf/verdana.ttf b/wqflask/fonts/ttf/verdana.ttf deleted file mode 100644 index 754a9b7b..00000000 Binary files a/wqflask/fonts/ttf/verdana.ttf and /dev/null differ diff --git a/wqflask/utility/Plot.py b/wqflask/utility/Plot.py index c557bcc6..e7115036 100644 --- a/wqflask/utility/Plot.py +++ b/wqflask/utility/Plot.py @@ -48,9 +48,9 @@ BLACK = ImageColor.getrgb("black") # ---- END: Define common colours ---- # # ---- FONT FILES ---- # -VERDANA_FILE = "fonts/ttf/verdana.ttf" -COUR_FILE = "fonts/ttf/courbd.ttf" -TAHOMA_FILE = "fonts/ttf/tahoma.ttf" +VERDANA_FILE = "./wqflask/static/fonts/verdana.ttf" +COUR_FILE = "./wqflask/static/fonts/courbd.ttf" +TAHOMA_FILE = "./wqflask/static/fonts/tahoma.ttf" # ---- END: FONT FILES ---- # def cformat(d, rank=0): diff --git a/wqflask/wqflask/static/fonts/courbd.ttf b/wqflask/wqflask/static/fonts/courbd.ttf new file mode 100644 index 00000000..c8081467 Binary files /dev/null and b/wqflask/wqflask/static/fonts/courbd.ttf differ diff --git a/wqflask/wqflask/static/fonts/tahoma.ttf b/wqflask/wqflask/static/fonts/tahoma.ttf new file mode 100644 index 00000000..bb314be3 Binary files /dev/null and b/wqflask/wqflask/static/fonts/tahoma.ttf differ -- cgit v1.2.3 From dcd053e0249b14c938d94eb749a8b4095c80be29 Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Fri, 8 Mar 2019 10:39:53 +0300 Subject: Create new utility module for drawing * wqflask/utility/pillow_utils.py: Create a module to hold some utility functions for drawing with Pillow. Initialise the module with a function to draw rotated text. --- wqflask/utility/pillow_utils.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 wqflask/utility/pillow_utils.py diff --git a/wqflask/utility/pillow_utils.py b/wqflask/utility/pillow_utils.py new file mode 100644 index 00000000..4a666e4b --- /dev/null +++ b/wqflask/utility/pillow_utils.py @@ -0,0 +1,16 @@ +from PIL import Image, ImageColor, ImageDraw, ImageFont + +import utility.logger +logger = utility.logger.getLogger(__name__ ) + +BLACK = ImageColor.getrgb("black") +# def draw_rotated_text(canvas: Image, text: str, font: ImageFont, xy: tuple, fill: ImageColor=BLACK, angle: int=-90): +def draw_rotated_text(canvas, text, font, xy, fill=BLACK, angle=-90): + # type: (Image, str, ImageFont, tuple, ImageColor, int) + """Utility function draw rotated text""" + tmp_img = Image.new("RGBA", font.getsize(text), color=(0,0,0,0)) + draw_text = ImageDraw.Draw(tmp_img) + draw_text.text(text=text, xy=(0,0), font=font, fill=fill) + tmp_img2 = tmp_img.rotate(angle, expand=1) + tmp_img2.save("/tmp/{}.png".format(text), format="png") + canvas.paste(im=tmp_img2, box=tuple([int(i) for i in xy])) -- cgit v1.2.3 From 6e1102e36737a3f48a74cf431819c269c54f2601 Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Fri, 8 Mar 2019 10:41:40 +0300 Subject: Use draw_rotated_text() Use the new draw_rotated_text() function to draw the text rotated as was formerly done. * wqflask/utility/Plot.py (plotBar): Use draw_rotated_text(). * wqflask/wqflask/marker_regression/display_mapping_results.py: (DisplayMappingResults) Use draw_rotated_text(). --- wqflask/utility/Plot.py | 14 ++++++------- .../marker_regression/display_mapping_results.py | 24 +++++++++++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/wqflask/utility/Plot.py b/wqflask/utility/Plot.py index e7115036..d4373412 100644 --- a/wqflask/utility/Plot.py +++ b/wqflask/utility/Plot.py @@ -38,7 +38,7 @@ from numarray import ones, array, dot, swapaxes import webqtlUtil import corestats from base import webqtlConfig - +from utility.pillow_utils import draw_rotated_text import utility.logger logger = utility.logger.getLogger(__name__ ) @@ -219,12 +219,12 @@ def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLab font=labelFont,fill=labelColor) if YLabel: - im_drawer.text( - text=YLabel, - xy=(19, - yTopOffset+plotHeight-(plotHeight-im_drawer.textsize( - YLabel,font=labelFont)[0])/2.0), - font=labelFont,fill=labelColor,angle=90) + draw_rotated_text(canvas, text=YLabel, + xy=(19, + yTopOffset+plotHeight-( + plotHeight-im_drawer.textsize( + YLabel,font=labelFont)[0])/2.0), + font=labelFont, fill=labelColor, angle=90) labelFont=ImageFont.truetype(font=VERDANA_FILE,size=16) if title: diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 39067fc5..b03902a5 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -45,6 +45,7 @@ from utility import Plot from utility.benchmark import Bench from wqflask.interval_analyst import GeneUtil from base.webqtlConfig import TMPDIR, GENERATED_TEXT_DIR, GENERATED_IMAGE_DIR +from utility.pillow_utils import draw_rotated_text import utility.logger logger = utility.logger.getLogger(__name__ ) @@ -1519,11 +1520,12 @@ class DisplayMappingResults(object): if lastGene == 0: - im_drawer.text( - text="%s" % (_chr[j].name), + draw_rotated_text( + canvas, text="%s" % (_chr[j].name), + font=ImageFont.truetype(font=VERDANA_FILE, + size=12), xy=(geneStartPix, geneYLocation+17+2*maxind*self.EACH_GENE_HEIGHT*zoom), - font=ImageFont.truetype(font=VERDANA_FILE, size=12), fill=BLACK, angle=-90) oldgeneEndPix = geneEndPix; @@ -1804,9 +1806,10 @@ class DisplayMappingResults(object): (startPosX+tickdists*plotXScale, yZero + 7)), fill=BLACK, width=1*zoom) if j % 2 == 0: - im_drawer.text( - xy=(startPosX+tickdists*plotXScale, yZero+10*zoom), - text=str(tickdists), fill=BLACK, font=MBLabelFont, angle=270) + draw_rotated_text( + canvas, text=str(tickdists), font=MBLabelFont, + xy=(startPosX+tickdists*plotXScale, + yZero+10*zoom), fill=BLACK, angle=270) startPosX += (self.ChrLengthDistList[i]+self.GraphInterval)*plotXScale megabaseLabelFont = ImageFont.truetype(font=VERDANA_FILE, size=int(18*zoom*1.5)) @@ -2037,11 +2040,12 @@ class DisplayMappingResults(object): LRSLODFont=ImageFont.truetype(font=VERDANA_FILE, size=int(18*zoom*1.5)) yZero = yTopOffset + plotHeight - im_drawer.text( - text=self.LRS_LOD, - xy=(xLeftOffset - im_drawer.textsize("999.99", font=LRSScaleFont)[0] - 15*(zoom-1), + draw_rotated_text( + canvas, text=self.LRS_LOD, font=LRSLODFont, + xy=(xLeftOffset - im_drawer.textsize( + "999.99", font=LRSScaleFont)[0] - 15*(zoom-1), yZero - 150 - 300*(zoom - 1)), - font=LRSLODFont, fill=BLACK, angle=90) + fill=BLACK, angle=90) for item in LRSAxisList: if LRS_LOD_Max == 0.0: -- cgit v1.2.3 From 97646f40bd8bff699f9ee2721fc9c960162ad3eb Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Fri, 8 Mar 2019 10:57:40 +0300 Subject: Replace "color" with "fill" keyword argument * wqflask/wqflask/marker_regression/display_mapping_results.py: (DisplayMappingResults): Pillow uses 'fill' in place of 'color'. --- wqflask/wqflask/marker_regression/display_mapping_results.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index b03902a5..9a96cd52 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -758,7 +758,7 @@ class DisplayMappingResults(object): (canvas.size[0]-bootOffset,bootY)), fill=BLACK) im_drawer.text(xy=(canvas.size[0]-bootOffset+10,bootY+5), - text='%2.1f'%item, font=bootScaleFont, color=BLACK) + text='%2.1f'%item, font=bootScaleFont, fill=BLACK) if self.legendChecked: startPosY = 30 @@ -770,7 +770,7 @@ class DisplayMappingResults(object): fill=YELLOW) im_drawer.text(xy=(leftOffset+ 20, startPosY+5), text='Frequency of the Peak LRS', - font=smallLabelFont, color=BLACK) + font=smallLabelFont, fill=BLACK) def drawProbeSetPosition(self, canvas, plotXScale, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): im_drawer = ImageDraw.Draw(canvas) -- cgit v1.2.3 From e3c0745fdc3bf33cb10256643f3b1469ee6614c4 Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Sat, 9 Mar 2019 08:33:19 +0300 Subject: Add draw_open_polygon() utility * wqflask/utility/pillow_utils.py: New method. --- wqflask/utility/pillow_utils.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/wqflask/utility/pillow_utils.py b/wqflask/utility/pillow_utils.py index 4a666e4b..dfbf3e19 100644 --- a/wqflask/utility/pillow_utils.py +++ b/wqflask/utility/pillow_utils.py @@ -4,6 +4,8 @@ import utility.logger logger = utility.logger.getLogger(__name__ ) BLACK = ImageColor.getrgb("black") +WHITE = ImageColor.getrgb("white") + # def draw_rotated_text(canvas: Image, text: str, font: ImageFont, xy: tuple, fill: ImageColor=BLACK, angle: int=-90): def draw_rotated_text(canvas, text, font, xy, fill=BLACK, angle=-90): # type: (Image, str, ImageFont, tuple, ImageColor, int) @@ -14,3 +16,10 @@ def draw_rotated_text(canvas, text, font, xy, fill=BLACK, angle=-90): tmp_img2 = tmp_img.rotate(angle, expand=1) tmp_img2.save("/tmp/{}.png".format(text), format="png") canvas.paste(im=tmp_img2, box=tuple([int(i) for i in xy])) + +# def draw_open_polygon(canvas: Image, xy: tuple, fill: ImageColor=WHITE, outline: ImageColor=BLACK): +def draw_open_polygon(canvas, xy, fill=None, outline=BLACK, width=0): + # type: (Image, tuple, ImageColor, ImageColor) + draw_ctx = ImageDraw.Draw(canvas) + draw_ctx.polygon(xy, fill=fill) + draw_ctx.line(xy, fill=outline, width=width) -- cgit v1.2.3 From 5a19179f0b93a80a4c6cc22eb81c0395e1879026 Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Sat, 9 Mar 2019 08:34:13 +0300 Subject: Use new `draw_open_polygon()` utility * wqflask/wqflask/marker_regression/display_mapping_results.py: Use the new `draw_open_polygon()` utility to draw open polygons. --- .../marker_regression/display_mapping_results.py | 48 ++++++++++++++-------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 9a96cd52..b754662f 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -45,7 +45,7 @@ from utility import Plot from utility.benchmark import Bench from wqflask.interval_analyst import GeneUtil from base.webqtlConfig import TMPDIR, GENERATED_TEXT_DIR, GENERATED_IMAGE_DIR -from utility.pillow_utils import draw_rotated_text +from utility.pillow_utils import draw_rotated_text, draw_open_polygon import utility.logger logger = utility.logger.getLogger(__name__ ) @@ -841,10 +841,8 @@ class DisplayMappingResults(object): break if locPixel >= 0 and self.plotScale == 'physic': traitPixel = ((locPixel, yZero), (locPixel-7, yZero+14), (locPixel+7, yZero+14)) - im_drawer.polygon( - xy=traitPixel, outline=BLACK, - fill=self.TRANSCRIPT_LOCATION_COLOR#, closed=1 - ) + draw_open_polygon(canvas, xy=traitPixel, outline=BLACK, + fill=self.TRANSCRIPT_LOCATION_COLOR) if self.legendChecked: startPosY = 15 @@ -854,8 +852,18 @@ class DisplayMappingResults(object): leftOffset = xLeftOffset else: leftOffset = xLeftOffset+(nCol-1)*200*fontZoom - canvas.drawPolygon(((leftOffset+7, startPosY-7), (leftOffset, startPosY+7), (leftOffset+14, startPosY+7)), edgeColor=BLACK, fillColor=self.TRANSCRIPT_LOCATION_COLOR, closed=1) - canvas.drawString("Sequence Site", (leftOffset+15), (startPosY+5), smallLabelFont, self.TOP_RIGHT_INFO_COLOR) + draw_open_polygon( + canvas, + xy=( + (leftOffset+7, startPosY-7), + (leftOffset, startPosY+7), + (leftOffset+14, startPosY+7)), + outline=BLACK, fill=self.TRANSCRIPT_LOCATION_COLOR + ) + im_drawer.text( + text="Sequence Site", + xy=(leftOffset+15,startPosY+5), font=smallLabelFont, + fill=self.TOP_RIGHT_INFO_COLOR) def drawSNPTrackNew(self, canvas, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): im_drawer = ImageDraw.Draw(canvas) @@ -2143,12 +2151,14 @@ class DisplayMappingResults(object): thisLRSColor = self.colorCollection[0] if qtlresult['chr'] != previous_chr and self.selectedChr == -1: if self.manhattan_plot != True: - im_drawer.polygon( - xy=LRSCoordXY, - outline=thisLRSColor - # , closed=0, edgeWidth=lrsEdgeWidth, - # clipX=(xLeftOffset, xLeftOffset + plotWidth) - ) + # im_drawer.polygon( + # xy=LRSCoordXY, + # outline=thisLRSColor + # # , closed=0, edgeWidth=lrsEdgeWidth, + # # clipX=(xLeftOffset, xLeftOffset + plotWidth) + # ) + draw_open_polygon(canvas, xy=LRSCoordXY, + outline=thisLRSColor, width=lrsEdgeWidth) if not self.multipleInterval and self.additiveChecked: plusColor = self.ADDITIVE_COLOR_POSITIVE @@ -2301,11 +2311,13 @@ class DisplayMappingResults(object): m += 1 if self.manhattan_plot != True: - im_drawer.polygon( - xy=LRSCoordXY, - outline=thisLRSColor - #, closed=0, edgeWidth=lrsEdgeWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth) - ) + # im_drawer.polygon( + # xy=LRSCoordXY, + # outline=thisLRSColor + # #, closed=0, edgeWidth=lrsEdgeWidth, clipX=(xLeftOffset, xLeftOffset + plotWidth) + # ) + draw_open_polygon(canvas, xy=LRSCoordXY, outline=thisLRSColor, + width=lrsEdgeWidth) if not self.multipleInterval and self.additiveChecked: plusColor = self.ADDITIVE_COLOR_POSITIVE -- cgit v1.2.3 From 882d8425d62bafdad653b1d939af6cfefb6cc66d Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Sat, 9 Mar 2019 09:06:20 +0300 Subject: Give rectangles black outlines * wqflask/wqflask/marker_regression/display_mapping_results.py: Give the rectangles black outlines to correspond to earlier drawings. --- wqflask/wqflask/marker_regression/display_mapping_results.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index b754662f..6cac51ee 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -728,7 +728,7 @@ class DisplayMappingResults(object): im_drawer.rectangle( xy=((item[0], yZero), (item[1], yZero - item[2]*bootHeightThresh/maxBootCount)), - fill=self.BOOTSTRAP_BOX_COLOR) + fill=self.BOOTSTRAP_BOX_COLOR, outline=BLACK) ###draw boot scale highestPercent = (maxBootCount*100.0)/nboot @@ -741,7 +741,7 @@ class DisplayMappingResults(object): im_drawer.rectangle( xy=((canvas.size[0]-bootOffset, yZero-bootHeightThresh), (canvas.size[0]-bootOffset-15*zoom,yZero)), - fill = YELLOW) + fill = YELLOW, outline=BLACK) im_drawer.line( xy=((canvas.size[0]-bootOffset+4, yZero), (canvas.size[0]-bootOffset, yZero)), @@ -767,7 +767,7 @@ class DisplayMappingResults(object): leftOffset = xLeftOffset+(nCol-1)*200 im_drawer.rectangle( xy=((leftOffset,startPosY-6), (leftOffset+12,startPosY+6)), - fill=YELLOW) + fill=YELLOW, outline=BLACK) im_drawer.text(xy=(leftOffset+ 20, startPosY+5), text='Frequency of the Peak LRS', font=smallLabelFont, fill=BLACK) @@ -937,7 +937,7 @@ class DisplayMappingResults(object): im_drawer.rectangle( xy=((rightShift,yPaddingTop+kstep*15), (rectWidth+rightShift,yPaddingTop+10+kstep*15)), - fill=thisLRSColor) + fill=thisLRSColor, outline=BLACK) im_drawer.text( text=name,xy=(rectWidth+2+rightShift,yPaddingTop+10+kstep*15), font=colorFont,fill=BLACK) -- cgit v1.2.3 From a1b31f6f217ff8c84e981bbc79784672c9b982dd Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Sat, 9 Mar 2019 11:38:11 +0300 Subject: Use TEXT_Y_DISPLACEMENT instead of magic numbers * wqflask/wqflask/marker_regression/display_mapping_results.py: Use TEXT_Y_DISPLACEMENT instead of magic numbers to help with making it clear that the drawn text is displaced somewhat. --- .../marker_regression/display_mapping_results.py | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 6cac51ee..4d9649bd 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -746,7 +746,8 @@ class DisplayMappingResults(object): xy=((canvas.size[0]-bootOffset+4, yZero), (canvas.size[0]-bootOffset, yZero)), fill=BLACK) - im_drawer.text(xy=(canvas.size[0]-bootOffset+10,yZero+5), text='0%', + TEXT_Y_DISPLACEMENT = -8 + im_drawer.text(xy=(canvas.size[0]-bootOffset+10,yZero+TEXT_Y_DISPLACEMENT), text='0%', font=bootScaleFont, fill=BLACK) for item in bootScale: @@ -757,7 +758,7 @@ class DisplayMappingResults(object): xy=((canvas.size[0]-bootOffset+4,bootY), (canvas.size[0]-bootOffset,bootY)), fill=BLACK) - im_drawer.text(xy=(canvas.size[0]-bootOffset+10,bootY+5), + im_drawer.text(xy=(canvas.size[0]-bootOffset+10,bootY+TEXT_Y_DISPLACEMENT), text='%2.1f'%item, font=bootScaleFont, fill=BLACK) if self.legendChecked: @@ -768,7 +769,7 @@ class DisplayMappingResults(object): im_drawer.rectangle( xy=((leftOffset,startPosY-6), (leftOffset+12,startPosY+6)), fill=YELLOW, outline=BLACK) - im_drawer.text(xy=(leftOffset+ 20, startPosY+5), + im_drawer.text(xy=(leftOffset+ 20, startPosY+TEXT_Y_DISPLACEMENT), text='Frequency of the Peak LRS', font=smallLabelFont, fill=BLACK) @@ -860,9 +861,10 @@ class DisplayMappingResults(object): (leftOffset+14, startPosY+7)), outline=BLACK, fill=self.TRANSCRIPT_LOCATION_COLOR ) + TEXT_Y_DISPLACEMENT = -8 im_drawer.text( text="Sequence Site", - xy=(leftOffset+15,startPosY+5), font=smallLabelFont, + xy=(leftOffset+15,startPosY+TEXT_Y_DISPLACEMENT), font=smallLabelFont, fill=self.TOP_RIGHT_INFO_COLOR) def drawSNPTrackNew(self, canvas, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): @@ -953,6 +955,7 @@ class DisplayMappingResults(object): plotWidth = canvas.size[0] - xLeftOffset - xRightOffset plotHeight = canvas.size[1] - yTopOffset - yBottomOffset yZero = canvas.size[1] - yBottomOffset + TEXT_Y_DISPLACEMENT = -8 fontZoom = zoom if zoom == 2: fontZoom = 1.5 @@ -965,7 +968,7 @@ class DisplayMappingResults(object): xy=((xLeftOffset,startPosY),(xLeftOffset+32,startPosY)), fill=self.LRS_COLOR, width=2) im_drawer.text( - text=self.LRS_LOD, xy=(xLeftOffset+40,startPosY+5), + text=self.LRS_LOD, xy=(xLeftOffset+40,startPosY+TEXT_Y_DISPLACEMENT), font=labelFont,fill=BLACK) startPosY += stepPosY @@ -978,7 +981,7 @@ class DisplayMappingResults(object): xy=((startPosX+18,startPosY),(startPosX+32,startPosY)), fill=self.ADDITIVE_COLOR_NEGATIVE, width=2) im_drawer.text( - text='Additive Effect',xy=(startPosX+40,startPosY+5), + text='Additive Effect',xy=(startPosX+40,startPosY+TEXT_Y_DISPLACEMENT), font=labelFont,fill=BLACK) if self.genotype.type == 'intercross' and self.dominanceChecked: @@ -1024,10 +1027,10 @@ class DisplayMappingResults(object): fill=self.SUGGESTIVE_COLOR, width=self.SUGGESTIVE_WIDTH) im_drawer.text( text='Significant %s = %2.2f' % (self.LRS_LOD,self.significant), - xy=(xLeftOffset+42,startPosY +5),font=labelFont,fill=BLACK) + xy=(xLeftOffset+42,startPosY+TEXT_Y_DISPLACEMENT),font=labelFont,fill=BLACK) im_drawer.text( text='Suggestive %s = %2.2f' % (self.LRS_LOD, self.suggestive), - xy=(xLeftOffset+42,startPosY + 5 +stepPosY),font=labelFont, + xy=(xLeftOffset+42,startPosY + TEXT_Y_DISPLACEMENT +stepPosY),font=labelFont, fill=BLACK) labelFont = ImageFont.truetype(font=VERDANA_FILE,size=12*fontZoom) @@ -2067,10 +2070,11 @@ class DisplayMappingResults(object): else: scaleStr = "%2.1f" % item #Draw the LRS/LOD Y axis label + TEXT_Y_DISPLACEMENT = -10 im_drawer.text( text=scaleStr, xy=(xLeftOffset-4-im_drawer.textsize(scaleStr, font=LRSScaleFont)[0]-5, - yLRS+3), + yLRS+TEXT_Y_DISPLACEMENT), font=LRSScaleFont, fill=self.LRS_COLOR) if self.permChecked and self.nperm > 0 and not self.multipleInterval: @@ -2456,6 +2460,7 @@ class DisplayMappingResults(object): additiveStep = (additiveScale[1]-additiveScale[0])/additiveScale[2] additiveAxisList = Plot.frange(0, additiveScale[1], additiveStep) addPlotScale = AdditiveHeightThresh/additiveMax + TEXT_Y_DISPLACEMENT = -8 additiveAxisList.append(additiveScale[1]) for item in additiveAxisList: @@ -2467,7 +2472,7 @@ class DisplayMappingResults(object): scaleStr = "%2.3f" % item im_drawer.text( text=scaleStr, - xy=(xLeftOffset + plotWidth +6,additiveY+5), + xy=(xLeftOffset + plotWidth +6,additiveY+TEXT_Y_DISPLACEMENT), font=additiveScaleFont,fill=self.ADDITIVE_COLOR_POSITIVE) im_drawer.line( @@ -2551,7 +2556,8 @@ class DisplayMappingResults(object): chrStartPix = startPosX + (self.ChrLengthDistList[i]*plotXScale -chrNameWidth)/2 chrEndPix = startPosX + (self.ChrLengthDistList[i]*plotXScale +chrNameWidth)/2 - im_drawer.text(xy=(chrStartPix, yTopOffset + 20), + TEXT_Y_DISPLACEMENT = 0 + im_drawer.text(xy=(chrStartPix, yTopOffset + TEXT_Y_DISPLACEMENT), text=_chr.name, font=chrLabelFont, fill=BLACK) COORDS = "%d,%d,%d,%d" %(chrStartPix, yTopOffset, chrEndPix,yTopOffset +20) -- cgit v1.2.3 From b48698b2dfcf04b2016eb92e3de764d8ab176aab Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Sat, 9 Mar 2019 12:03:05 +0300 Subject: Fix placement for axes labels * wqflask/wqflask/marker_regression/display_mapping_results.py: Fix the placement for the axes labels. --- wqflask/wqflask/marker_regression/display_mapping_results.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 4d9649bd..2f235896 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -1823,13 +1823,14 @@ class DisplayMappingResults(object): yZero+10*zoom), fill=BLACK, angle=270) startPosX += (self.ChrLengthDistList[i]+self.GraphInterval)*plotXScale + TEXT_Y_DISPLACEMENT = -15 megabaseLabelFont = ImageFont.truetype(font=VERDANA_FILE, size=int(18*zoom*1.5)) im_drawer.text( text="Megabases", xy=( xLeftOffset+(plotWidth-im_drawer.textsize( "Megabases",font=megabaseLabelFont)[0])/2, - strYLoc+MBLabelFont.font.height+10*(zoom%2)+ 10), + strYLoc+MBLabelFont.font.height+10*(zoom%2)+TEXT_Y_DISPLACEMENT), font=megabaseLabelFont, fill=BLACK) pass else: @@ -2051,11 +2052,13 @@ class DisplayMappingResults(object): LRSLODFont=ImageFont.truetype(font=VERDANA_FILE, size=int(18*zoom*1.5)) yZero = yTopOffset + plotHeight + TEXT_X_DISPLACEMENT = -20 + TEXT_Y_DISPLACEMENT = -215 draw_rotated_text( canvas, text=self.LRS_LOD, font=LRSLODFont, xy=(xLeftOffset - im_drawer.textsize( - "999.99", font=LRSScaleFont)[0] - 15*(zoom-1), - yZero - 150 - 300*(zoom - 1)), + "999.99", font=LRSScaleFont)[0] - 15*(zoom-1) + TEXT_X_DISPLACEMENT, + yZero + TEXT_Y_DISPLACEMENT - 300*(zoom - 1)), fill=BLACK, angle=90) for item in LRSAxisList: -- cgit v1.2.3 From 7f982bfb55f7832f63bb960e084f5ca6d40eb519 Mon Sep 17 00:00:00 2001 From: Muriithi Frederick Muriuki Date: Thu, 21 Mar 2019 09:45:08 +0300 Subject: Change Y Coodinate system * wqflask/wqflask/marker_regression/display_mapping_results.py: Piddle seemed to centre text about the given Y coordinate, while Pillow requires specification to top-left corner of the text. This slight change means text all over the image is displaced somewhat, and this fixes the coordinate system for some of the text. --- wqflask/wqflask/marker_regression/display_mapping_results.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 2f235896..9b406b72 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -1698,21 +1698,21 @@ class DisplayMappingResults(object): im_drawer.text( text="Click to view the corresponding section of the genome in an 8x expanded WebQTL map", - xy=((xLeftOffset + 10), paddingTop + self.BAND_HEIGHT/2), + xy=((xLeftOffset + 10), paddingTop),# + self.BAND_HEIGHT/2), font=clickableRegionLabelFont, fill=self.CLICKABLE_WEBQTL_TEXT_COLOR) if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": im_drawer.text( text="Click to view the corresponding section of the genome in PhenoGen", - xy=((xLeftOffset + 10), phenogenPaddingTop + self.BAND_HEIGHT/2), + xy=((xLeftOffset + 10), phenogenPaddingTop),# + self.BAND_HEIGHT/2), font=clickableRegionLabelFont, fill=self.CLICKABLE_PHENOGEN_TEXT_COLOR) im_drawer.text( text="Click to view the corresponding section of the genome in the UCSC Genome Browser", - xy=((xLeftOffset + 10), ucscPaddingTop + self.BAND_HEIGHT/2), + xy=((xLeftOffset + 10), ucscPaddingTop),# + self.BAND_HEIGHT/2), font=clickableRegionLabelFont, fill=self.CLICKABLE_UCSC_TEXT_COLOR) im_drawer.text( text="Click to view the corresponding section of the genome in the Ensembl Genome Browser", - xy=((xLeftOffset+10), ensemblPaddingTop + self.BAND_HEIGHT/2), + xy=((xLeftOffset+10), ensemblPaddingTop),# + self.BAND_HEIGHT/2), font=clickableRegionLabelFont, fill=self.CLICKABLE_ENSEMBL_TEXT_COLOR) #draw the gray text -- cgit v1.2.3 From 3a8409757e2e095cd18c483de07af1eed8719c02 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sat, 8 Aug 2020 03:36:31 +0300 Subject: Add a basic test for test_display_marking_results.py * wqflask/tests/wqflask/marker_regression/__init__.py: Add it * wqflask/tests/wqflask/marker_regression/test_display_marking_results.py: Check that PIL colors are being used instead of the deprecated Piddle colors. --- wqflask/tests/wqflask/marker_regression/__init__.py | 0 .../wqflask/marker_regression/test_display_marking_results.py | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 wqflask/tests/wqflask/marker_regression/__init__.py create mode 100644 wqflask/tests/wqflask/marker_regression/test_display_marking_results.py diff --git a/wqflask/tests/wqflask/marker_regression/__init__.py b/wqflask/tests/wqflask/marker_regression/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wqflask/tests/wqflask/marker_regression/test_display_marking_results.py b/wqflask/tests/wqflask/marker_regression/test_display_marking_results.py new file mode 100644 index 00000000..67da508b --- /dev/null +++ b/wqflask/tests/wqflask/marker_regression/test_display_marking_results.py @@ -0,0 +1,9 @@ +import unittest + +from wqflask.marker_regression.display_mapping_results import DisplayMappingResults + +class TestDisplayMappingResults(unittest.TestCase): + def test_pil_colors(self): + """Test that colors use PILLOW color format""" + self.assertEqual(DisplayMappingResults.CLICKABLE_WEBQTL_REGION_COLOR, + (245, 211, 211)) -- cgit v1.2.3 From c1b1c914b10781715bb6a2e7fc061e6e506fb92f Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Mon, 18 Feb 2019 10:10:56 +0000 Subject: Add fonts for pillow * wqflask/wqflask/marker_regression/display_mapping_results.py: replace piddle method with PIL.ImageFont * wqflask/wqflask/static/fonts: Add fonts in path --- .../marker_regression/display_mapping_results.py | 15 +++++++++------ wqflask/wqflask/static/fonts/README | 1 + wqflask/wqflask/static/fonts/arial.ttf | Bin 0 -> 151232 bytes wqflask/wqflask/static/fonts/fnt_bs.ttf | Bin 0 -> 20988 bytes wqflask/wqflask/static/fonts/trebucbd.ttf | Bin 0 -> 123828 bytes wqflask/wqflask/static/fonts/verdana.ttf | Bin 0 -> 139640 bytes 6 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 wqflask/wqflask/static/fonts/README create mode 100644 wqflask/wqflask/static/fonts/arial.ttf create mode 100644 wqflask/wqflask/static/fonts/fnt_bs.ttf create mode 100644 wqflask/wqflask/static/fonts/trebucbd.ttf create mode 100644 wqflask/wqflask/static/fonts/verdana.ttf diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 9b406b72..6f50601d 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -72,10 +72,13 @@ MEDIUMPURPLE = ImageColor.getrgb("mediumpurple") # ---- END: Define common colours ---- # # ---- FONT FILES ---- # -VERDANA_FILE = "fonts/ttf/verdana.ttf" -TREBUC_FILE = "fonts/ttf/trebucbd.ttf" -FNT_BS_FILE = "fonts/ttf/fnt_bs.ttf" -ARIAL_FILE = "fonts/ttf/arial.ttf" +VERDANA_FILE = "./wqflask/static/fonts/verdana.ttf" +TREBUC_FILE = "./wqflask/static/fonts/trebucbd.ttf" +FNT_BS_FILE = "./wqflask/static/fonts/fnt_bs.ttf" +ARIAL_FILE = "./wqflask/static/fonts/arial.ttf" + +assert(os.path.isfile(VERDANA_FILE)) + # ---- END: FONT FILES ---- # ######################################### @@ -2008,8 +2011,8 @@ class DisplayMappingResults(object): js_data['max_score'] = LRS_LOD_Max self.js_data = json.dumps(js_data) - LRSScaleFont=pid.Font(ttf="verdana", size=16*zoom, bold=0) - LRSLODFont=pid.Font(ttf="verdana", size=18*zoom*1.5, bold=0) + LRSScaleFont=ImageFont.truetype(font=VERDANA_FILE, size=16*zoom) + LRSLODFont=ImageFont.truetype(font=VERDANA_FILE, size=int(18*zoom*1.5)) yZero = yTopOffset + plotHeight LRSHeightThresh = drawAreaHeight diff --git a/wqflask/wqflask/static/fonts/README b/wqflask/wqflask/static/fonts/README new file mode 100644 index 00000000..75a3e444 --- /dev/null +++ b/wqflask/wqflask/static/fonts/README @@ -0,0 +1 @@ +These fonts are used by pillow for 'interval mapping' diff --git a/wqflask/wqflask/static/fonts/arial.ttf b/wqflask/wqflask/static/fonts/arial.ttf new file mode 100644 index 00000000..bf0d4a95 Binary files /dev/null and b/wqflask/wqflask/static/fonts/arial.ttf differ diff --git a/wqflask/wqflask/static/fonts/fnt_bs.ttf b/wqflask/wqflask/static/fonts/fnt_bs.ttf new file mode 100644 index 00000000..712c38cf Binary files /dev/null and b/wqflask/wqflask/static/fonts/fnt_bs.ttf differ diff --git a/wqflask/wqflask/static/fonts/trebucbd.ttf b/wqflask/wqflask/static/fonts/trebucbd.ttf new file mode 100644 index 00000000..1ab1ae0a Binary files /dev/null and b/wqflask/wqflask/static/fonts/trebucbd.ttf differ diff --git a/wqflask/wqflask/static/fonts/verdana.ttf b/wqflask/wqflask/static/fonts/verdana.ttf new file mode 100644 index 00000000..754a9b7b Binary files /dev/null and b/wqflask/wqflask/static/fonts/verdana.ttf differ -- cgit v1.2.3 From bdbb0fab5b30a9413491c0ad36585d6c5c671d22 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 17 Aug 2020 12:52:22 -0500 Subject: Placed X/Y trait info before the table on the correlation scatterplot page * wqflask/wqflask/templates/corr_scatterplot.html - reordered table and X/Y trait info divs --- wqflask/wqflask/templates/corr_scatterplot.html | 108 ++++++++++++------------ 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/wqflask/wqflask/templates/corr_scatterplot.html b/wqflask/wqflask/templates/corr_scatterplot.html index 5877e367..6f0511e1 100644 --- a/wqflask/wqflask/templates/corr_scatterplot.html +++ b/wqflask/wqflask/templates/corr_scatterplot.html @@ -132,44 +132,8 @@

    -
    -
    -
    Group {{ this_trait.dataset.group.species[0]|upper }}{{ this_trait.dataset.group.species[1:] }}: {{ this_trait.dataset.group.name }} group
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    StatisticValue
    Number{{jsdata.num_overlap}}
    Slope{{ jsdata.slope_string }}
    Intercept{{'%0.3f' % jsdata.intercept}}
    r value{{'%0.3f' % jsdata.r_value}}
    P value{% if jsdata.p_value < 0.001 %}{{'%0.3e' % jsdata.p_value}}{% else %}{{'%0.3f' % jsdata.p_value}}{% endif %}
    - Regression Line -
    - y = {{ jsdata.slope_string }} * x {% if jsdata.intercept < 0 %}- {{'%0.3f' % (jsdata.intercept * -1)}}{% else %}+ {{'%0.3f' % jsdata.intercept}}{% endif %} -
    -
    -
    +
    +
    {% if trait_1.dataset.type == "ProbeSet" %}
    X axis: @@ -240,15 +204,7 @@
    {% endif %}
    -
    -
    - -
    -
    -
    -
    -
    -
    +
    @@ -260,31 +216,39 @@ - + - + - + - + - +
    StatisticValue
    Slope{{ jsdata.srslope_string }}{{ jsdata.slope_string }}
    Intercept{{'%0.3f' % jsdata.srintercept}}{{'%0.3f' % jsdata.intercept}}
    r value{{'%0.3f' % jsdata.srr_value}}{{'%0.3f' % jsdata.r_value}}
    P value{% if jsdata.srp_value < 0.001 %}{{'%0.3e' % jsdata.srp_value}}{% else %}{{'%0.3f' % jsdata.srp_value}}{% endif %}{% if jsdata.p_value < 0.001 %}{{'%0.3e' % jsdata.p_value}}{% else %}{{'%0.3f' % jsdata.p_value}}{% endif %}
    Regression Line
    - y = {{ jsdata.srslope_string }} * x {% if jsdata.srintercept < 0 %}- {{'%0.3f' % (jsdata.srintercept * -1)}}{% else %}+ {{'%0.3f' % jsdata.srintercept}}{% endif %} + y = {{ jsdata.slope_string }} * x {% if jsdata.intercept < 0 %}- {{'%0.3f' % (jsdata.intercept * -1)}}{% else %}+ {{'%0.3f' % jsdata.intercept}}{% endif %}
    +
    -
    +
    + +
    +
    +
    +
    +
    +
    {% if trait_1.dataset.type == "ProbeSet" %}
    X axis: @@ -335,6 +299,42 @@
    {% endif %}
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    StatisticValue
    Number{{jsdata.num_overlap}}
    Slope{{ jsdata.srslope_string }}
    Intercept{{'%0.3f' % jsdata.srintercept}}
    r value{{'%0.3f' % jsdata.srr_value}}
    P value{% if jsdata.srp_value < 0.001 %}{{'%0.3e' % jsdata.srp_value}}{% else %}{{'%0.3f' % jsdata.srp_value}}{% endif %}
    + Regression Line +
    + y = {{ jsdata.srslope_string }} * x {% if jsdata.srintercept < 0 %}- {{'%0.3f' % (jsdata.srintercept * -1)}}{% else %}+ {{'%0.3f' % jsdata.srintercept}}{% endif %} +
    +
    -- cgit v1.2.3 From d157019159ae3eb2e3efb02d874a1b4edfc559cb Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 12 Aug 2020 15:46:13 -0500 Subject: Fix positioning for many instances of text in the mapping figure * wqflask/wqflask/marker_regression/display_mapping_results.py: Many text instances had their positions changed by the switch from piddle to PIL. Change various Y offsets and in some cases changing the logic of the way text is positioned related to its length and font size. * wqflask/wqflask/static/fonts/verdanab.ttf: Add bold Verdana, since there needs to be a separate font file for bold text. --- .../marker_regression/display_mapping_results.py | 40 ++++++++++++--------- wqflask/wqflask/static/fonts/verdanab.ttf | Bin 0 -> 156340 bytes 2 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 wqflask/wqflask/static/fonts/verdanab.ttf diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 6f50601d..bda899fb 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -73,6 +73,7 @@ MEDIUMPURPLE = ImageColor.getrgb("mediumpurple") # ---- FONT FILES ---- # VERDANA_FILE = "./wqflask/static/fonts/verdana.ttf" +VERDANA_BOLD_FILE = "./wqflask/static/fonts/verdanab.ttf" TREBUC_FILE = "./wqflask/static/fonts/trebucbd.ttf" FNT_BS_FILE = "./wqflask/static/fonts/fnt_bs.ttf" ARIAL_FILE = "./wqflask/static/fonts/arial.ttf" @@ -1719,18 +1720,16 @@ class DisplayMappingResults(object): font=clickableRegionLabelFont, fill=self.CLICKABLE_ENSEMBL_TEXT_COLOR) #draw the gray text - chrFont = ImageFont.truetype(font=VERDANA_FILE, size=26*zoom) - traitFont = ImageFont.truetype(font=VERDANA_FILE, size=14) + chrFont = ImageFont.truetype(font=VERDANA_BOLD_FILE, size=26*zoom) chrX = xLeftOffset + plotWidth - 2 - im_drawer.textsize( "Chr %s" % self.ChrList[self.selectedChr][0], font=chrFont)[0] im_drawer.text( text="Chr %s" % self.ChrList[self.selectedChr][0], - xy=(chrX, ensemblPaddingTop-5), font=chrFont, fill=GRAY) + xy=(chrX, phenogenPaddingTop), font=chrFont, fill=GRAY) # end of drawBrowserClickableRegions else: #draw the gray text chrFont = ImageFont.truetype(font=VERDANA_FILE, size=26*zoom) - traitFont = ImageFont.truetype(font=VERDANA_FILE, size=14) chrX = xLeftOffset + (plotWidth - im_drawer.textsize( "Chr %s" % currentChromosome, font=chrFont)[0])/2 im_drawer.text( @@ -1751,7 +1750,7 @@ class DisplayMappingResults(object): #Parameters NUM_MINOR_TICKS = 5 # Number of minor ticks between major ticks - X_MAJOR_TICK_THICKNESS = 2 + X_MAJOR_TICK_THICKNESS = 3 X_MINOR_TICK_THICKNESS = 1 X_AXIS_THICKNESS = 1*zoom @@ -1762,10 +1761,10 @@ class DisplayMappingResults(object): xAxisTickMarkColor = BLACK xAxisLabelColor = BLACK fontHeight = 12*fontZoom # How tall the font that we're using is - spacingFromLabelToAxis = 5 + spacingFromLabelToAxis = 10 if self.plotScale == 'physic': - strYLoc = yZero + spacingFromLabelToAxis + MBLabelFont.font.height + strYLoc = yZero + MBLabelFont.font.height/2 ###Physical single chromosome view if self.selectedChr > -1: XScale = Plot.detScale(startMb, endMb) @@ -1826,18 +1825,17 @@ class DisplayMappingResults(object): yZero+10*zoom), fill=BLACK, angle=270) startPosX += (self.ChrLengthDistList[i]+self.GraphInterval)*plotXScale - TEXT_Y_DISPLACEMENT = -15 megabaseLabelFont = ImageFont.truetype(font=VERDANA_FILE, size=int(18*zoom*1.5)) im_drawer.text( text="Megabases", xy=( xLeftOffset+(plotWidth-im_drawer.textsize( "Megabases",font=megabaseLabelFont)[0])/2, - strYLoc+MBLabelFont.font.height+10*(zoom%2)+TEXT_Y_DISPLACEMENT), + strYLoc+MBLabelFont.font.height+10*(zoom%2)), font=megabaseLabelFont, fill=BLACK) pass else: - strYLoc = yZero + spacingFromLabelToAxis + MBLabelFont.font.height + 8 + strYLoc = yZero + spacingFromLabelToAxis + MBLabelFont.font.height/2 ChrAInfo = [] preLpos = -1 distinctCount = 0.0 @@ -1933,12 +1931,12 @@ class DisplayMappingResults(object): fill=lineColor) startPosX += (self.ChrLengthDistList[j]+self.GraphInterval)*plotXScale - centimorganLabelFont = ImageFont.truetype(font=VERDANA_FILE, size=18*zoom*1.5) + centimorganLabelFont = ImageFont.truetype(font=VERDANA_FILE, size=int(18*zoom*1.5)) im_drawer.text( text="Centimorgans", xy=(xLeftOffset+(plotWidth-im_drawer.textsize( - "Megabases", font=centimorganLabelFont)[0])/2, - strYLoc + MBLabelFont.font.height+ 10*(zoom%2) + 10), + "Centimorgans", font=centimorganLabelFont)[0])/2, + strYLoc + MBLabelFont.font.height+ 10*(zoom%2)), font=centimorganLabelFont, fill=BLACK) im_drawer.line(xy=((xLeftOffset,yZero), (xLeftOffset+plotWidth,yZero)), @@ -2055,8 +2053,16 @@ class DisplayMappingResults(object): LRSLODFont=ImageFont.truetype(font=VERDANA_FILE, size=int(18*zoom*1.5)) yZero = yTopOffset + plotHeight - TEXT_X_DISPLACEMENT = -20 - TEXT_Y_DISPLACEMENT = -215 + # TEXT_X_DISPLACEMENT = -20 + #TEXT_Y_DISPLACEMENT = -215 + if all_int: + TEXT_X_DISPLACEMENT = -12 + else: + TEXT_X_DISPLACEMENT = -30 + if self.LRS_LOD == "-log(p)": + TEXT_Y_DISPLACEMENT = -242 + else: + TEXT_Y_DISPLACEMENT = -210 draw_rotated_text( canvas, text=self.LRS_LOD, font=LRSLODFont, xy=(xLeftOffset - im_drawer.textsize( @@ -2140,7 +2146,7 @@ class DisplayMappingResults(object): else: if self.additiveChecked: additiveMax = max(map(lambda X : abs(X['additive']), self.qtlresults)) - lrsEdgeWidth = 2 + lrsEdgeWidth = 3 if zoom == 2: lrsEdgeWidth = 2 * lrsEdgeWidth @@ -2307,7 +2313,7 @@ class DisplayMappingResults(object): text="5", xy=( Xc-im_drawer.textsize("5",font=symbolFont)[0]/2+1, - Yc+2), + Yc-4), fill=point_color, font=symbolFont) else: LRSCoordXY.append((Xc, Yc)) diff --git a/wqflask/wqflask/static/fonts/verdanab.ttf b/wqflask/wqflask/static/fonts/verdanab.ttf new file mode 100644 index 00000000..1a99258f Binary files /dev/null and b/wqflask/wqflask/static/fonts/verdanab.ttf differ -- cgit v1.2.3 From 5c5aefc79d45d66ab30bdbbc5dab24b3f5f79dc2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 17 Aug 2020 20:35:53 +0300 Subject: Apply pep8 * scripts/maintenance/readProbeSetSE_v7.py: Apply pep8 to file to replace tabs with spaces and use correct indentation. --- scripts/maintenance/readProbeSetSE_v7.py | 258 ++++++++++++++++--------------- 1 file changed, 130 insertions(+), 128 deletions(-) diff --git a/scripts/maintenance/readProbeSetSE_v7.py b/scripts/maintenance/readProbeSetSE_v7.py index fd6f0bb8..2a1d44ff 100755 --- a/scripts/maintenance/readProbeSetSE_v7.py +++ b/scripts/maintenance/readProbeSetSE_v7.py @@ -13,12 +13,12 @@ import time def translateAlias(str): - if str == "B6": - return "C57BL/6J" - elif str == "D2": - return "DBA/2J" - else: - return str + if str == "B6": + return "C57BL/6J" + elif str == "D2": + return "DBA/2J" + else: + return str ######################################################################## # @@ -26,25 +26,27 @@ def translateAlias(str): # ######################################################################## + dataStart = 1 -GeneChipId = int( raw_input("Enter GeneChipId:") ) -ProbeSetFreezeId = int( raw_input("Enter ProbeSetFreezeId:") ) +GeneChipId = int(raw_input("Enter GeneChipId:")) +ProbeSetFreezeId = int(raw_input("Enter ProbeSetFreezeId:")) input_file_name = raw_input("Enter file name with suffix:") fp = open("%s" % input_file_name, 'rb') try: - passwd = getpass.getpass('Please enter mysql password here : ') - con = MySQLdb.Connect(db='db_webqtl',host='localhost', user='username',passwd=passwd) + passwd = getpass.getpass('Please enter mysql password here : ') + con = MySQLdb.Connect(db='db_webqtl', host='localhost', + user='username', passwd=passwd) - db = con.cursor() - print "You have successfully connected to mysql.\n" + db = con.cursor() + print("You have successfully connected to mysql.\n") except: - print "You entered incorrect password.\n" - sys.exit(0) - + print("You entered incorrect password.\n") + sys.exit(0) + time0 = time.time() ######################################################################## # @@ -52,9 +54,9 @@ time0 = time.time() # ######################################################################## -#GeneChipId = 4 -#dataStart = 1 -#ProbeSetFreezeId = 359 #JAX Liver 6C Affy M430 2.0 (Jul11) MDP +#GeneChipId = 4 +#dataStart = 1 +# ProbeSetFreezeId = 359 #JAX Liver 6C Affy M430 2.0 (Jul11) MDP #fp = open("GSE10493_AllSamples_6C_Z_AvgSE.txt", 'rb') @@ -64,191 +66,191 @@ time0 = time.time() # generate the gene list of expression data here # ######################################################################### -print 'Checking if each line have same number of members' +print('Checking if each line have same number of members') GeneList = [] isCont = 1 header = fp.readline() -header = string.split(string.strip(header),'\t') +header = string.split(string.strip(header), '\t') header = map(string.strip, header) nfield = len(header) line = fp.readline() -kj=0 +kj = 0 while line: - line2 = string.split(string.strip(line),'\t') - line2 = map(string.strip, line2) - if len(line2) != nfield: - print "Error : " + line - isCont = 0 + line2 = string.split(string.strip(line), '\t') + line2 = map(string.strip, line2) + if len(line2) != nfield: + isCont = 0 + print("Error : " + line) - GeneList.append(line2[0]) - line = fp.readline() + GeneList.append(line2[0]) + line = fp.readline() - kj+=1 - if kj%100000 == 0: - print 'checked ',kj,' lines' + kj += 1 + if kj % 100000 == 0: + print('checked ', kj, ' lines') GeneList = map(string.lower, GeneList) GeneList.sort() - -if isCont==0: - sys.exit(0) +if isCont == 0: + sys.exit(0) -print 'used ',time.time()-time0,' seconds' + +print('used ', time.time()-time0, ' seconds') ######################################################################### # # Check if each strain exist in database # generate the string id list of expression data here # ######################################################################### -print 'Checking if each strain exist in database' +print('Checking if each strain exist in database') isCont = 1 fp.seek(0) header = fp.readline() -header = string.split(string.strip(header),'\t') +header = string.split(string.strip(header), '\t') header = map(string.strip, header) header = map(translateAlias, header) header = header[dataStart:] Ids = [] for item in header: - try: - db.execute('select Id from Strain where Name = "%s"' % item) - Ids.append(db.fetchall()[0][0]) - except: - print item,'does not exist, check the if the strain name is correct' - isCont=0 + try: + db.execute('select Id from Strain where Name = "%s"' % item) + Ids.append(db.fetchall()[0][0]) + except: + isCont = 0 + print(item, 'does not exist, check the if the strain name is correct') -if isCont==0: - sys.exit(0) +if isCont == 0: + sys.exit(0) -print 'used ',time.time()-time0,' seconds' +print('used ', time.time()-time0, ' seconds') ######################################################################## # # Check if each ProbeSet exist in database # ######################################################################## -print 'Check if each ProbeSet exist in database' +print('Check if each ProbeSet exist in database') ##---- find PID is name or target ----## line = fp.readline() line = fp.readline() -line2 = string.split(string.strip(line),'\t') +line2 = string.split(string.strip(line), '\t') line2 = map(string.strip, line2) PId = line2[0] -db.execute('select Id from ProbeSet where Name="%s" and ChipId=%d' % (PId, GeneChipId)) +db.execute('select Id from ProbeSet where Name="%s" and ChipId=%d' % + (PId, GeneChipId)) results = db.fetchall() IdStr = 'TargetId' -if len(results)>0: - IdStr = 'Name' +if len(results) > 0: + IdStr = 'Name' ##---- get Name/TargetId list from database ----## -db.execute('select distinct(%s) from ProbeSet where ChipId=%d order by %s' % (IdStr, GeneChipId, IdStr)) +db.execute('select distinct(%s) from ProbeSet where ChipId=%d order by %s' % ( + IdStr, GeneChipId, IdStr)) results = db.fetchall() - + Names = [] for item in results: - Names.append(item[0]) -Names = map(string.lower, Names) -Names.sort() # -- Fixed the lower case problem of ProbeSets affx-mur_b2_at doesn't exist --# + Names.append(item[0]) + Names = map(string.lower, Names) + Names.sort() # -- Fixed the lower case problem of ProbeSets affx-mur_b2_at doesn't exist --# ##---- compare genelist with names ----## -x=y=0 -x1=-1 -GeneList2=[] -while xNames[y]: - y += 1 - - if x%100000==0: - print 'check Name, checked %d lines'%x - -while x Names[y]: + y += 1 + + if x % 100000 == 0: + print('check Name, checked %d lines' % x) + +while x < len(GeneList): + GeneList2.append(GeneList[x]) + x += 1 + +isCont = 1 ferror = open("ProbeSetError.txt", "wb") for item in GeneList2: - ferror.write(item + " doesn't exist \n") - print item, " doesn't exist" - isCont = 0 - -if isCont==0: - sys.exit(0) + ferror.write(item + " doesn't exist \n") + isCont = 0 + + print(item, " doesn't exist") +if isCont == 0: + sys.exit(0) -print 'used ',time.time()-time0,' seconds' +print('used ', time.time()-time0, ' seconds') ############################# -#Insert new Data into SE +# Insert new Data into SE ############################ db.execute(""" - select ProbeSet.%s, ProbeSetXRef.DataId from ProbeSet, ProbeSetXRef - where ProbeSet.Id=ProbeSetXRef.ProbeSetId and ProbeSetXRef.ProbeSetFreezeId=%d""" - % (IdStr, ProbeSetFreezeId)) + select ProbeSet.%s, ProbeSetXRef.DataId from ProbeSet, ProbeSetXRef + where ProbeSet.Id=ProbeSetXRef.ProbeSetId and ProbeSetXRef.ProbeSetFreezeId=%d""" + % (IdStr, ProbeSetFreezeId)) results = db.fetchall() ProbeNameId = {} for Name, Id in results: - ProbeNameId[Name] = Id + ProbeNameId[Name] = Id ferror = open("ProbeError.txt", "wb") DataValues = [] -fp.seek(0) #XZ add this line -line = fp.readline() #XZ add this line +fp.seek(0) # XZ add this line +line = fp.readline() # XZ add this line line = fp.readline() kj = 0 while line: - line2 = string.split(string.strip(line),'\t') - line2 = map(string.strip, line2) - - CellId = line2[0] - if not ProbeNameId.has_key(CellId): - ferror.write(CellId + " doesn't exist\n") - print CellId, " doesn't exist" - else: - DataId = ProbeNameId[CellId] - datasorig = line2[dataStart:] - - i = 0 - for item in datasorig: - if item != '': - value = '('+str(DataId)+','+str(Ids[i])+','+str(item)+')' - DataValues.append(value) - i += 1 - - kj += 1 - if kj % 100 == 0: - Dataitems = ','.join(DataValues) - cmd = 'insert ProbeSetSE values %s' % Dataitems - db.execute(cmd) - - DataValues = [] - print 'inserted ',kj,' lines' - print 'used ',time.time()-time0,' seconds' - line = fp.readline() - -if len(DataValues)>0: - DataValues = ','.join(DataValues) - cmd = 'insert ProbeSetSE values %s' % DataValues - db.execute(cmd) + line2 = string.split(string.strip(line), '\t') + line2 = map(string.strip, line2) + + CellId = line2[0] + if not ProbeNameId.has_key(CellId): + ferror.write(CellId + " doesn't exist\n") + else: + DataId = ProbeNameId[CellId] + datasorig = line2[dataStart:] + + i = 0 + for item in datasorig: + if item != '': + value = '('+str(DataId)+','+str(Ids[i])+','+str(item)+')' + DataValues.append(value) + i += 1 + + kj += 1 + if kj % 100 == 0: + Dataitems = ','.join(DataValues) + cmd = 'insert ProbeSetSE values %s' % Dataitems + db.execute(cmd) + + DataValues = [] + line = fp.readline() + print(CellId, " doesn't exist") + print('inserted ', kj, ' lines') + print('used ', time.time()-time0, ' seconds') + +if len(DataValues) > 0: + DataValues = ','.join(DataValues) + cmd = 'insert ProbeSetSE values %s' % DataValues + db.execute(cmd) con.close() - - -- cgit v1.2.3 From 6c50dff7552df87318253d77ca93e4cc8e26f283 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 17 Aug 2020 20:37:57 +0300 Subject: Replace DOS style line endings with UNIX style ones * scripts/maintenance/readProbeSetSE_v7.py: Run *dos2unix* against file --- scripts/maintenance/readProbeSetSE_v7.py | 512 +++++++++++++++---------------- 1 file changed, 256 insertions(+), 256 deletions(-) diff --git a/scripts/maintenance/readProbeSetSE_v7.py b/scripts/maintenance/readProbeSetSE_v7.py index 2a1d44ff..0b15ce09 100755 --- a/scripts/maintenance/readProbeSetSE_v7.py +++ b/scripts/maintenance/readProbeSetSE_v7.py @@ -1,256 +1,256 @@ -#!/usr/bin/python2 -"""This script use the nearest marker to the transcript as control, increasing permutation rounds according to the p-value""" -######################################################################## -# Last Updated Sep 27, 2011 by Xiaodong -# This version fix the bug that incorrectly exclude the first 2 probesetIDs -######################################################################## - -import string -import sys -import MySQLdb -import getpass -import time - - -def translateAlias(str): - if str == "B6": - return "C57BL/6J" - elif str == "D2": - return "DBA/2J" - else: - return str - -######################################################################## -# -# Indicate Data Start Position, ProbeFreezeId, GeneChipId, DataFile -# -######################################################################## - - -dataStart = 1 - -GeneChipId = int(raw_input("Enter GeneChipId:")) -ProbeSetFreezeId = int(raw_input("Enter ProbeSetFreezeId:")) -input_file_name = raw_input("Enter file name with suffix:") - -fp = open("%s" % input_file_name, 'rb') - - -try: - passwd = getpass.getpass('Please enter mysql password here : ') - con = MySQLdb.Connect(db='db_webqtl', host='localhost', - user='username', passwd=passwd) - - db = con.cursor() - print("You have successfully connected to mysql.\n") -except: - print("You entered incorrect password.\n") - sys.exit(0) - -time0 = time.time() -######################################################################## -# -# Indicate Data Start Position, ProbeFreezeId, GeneChipId, DataFile -# -######################################################################## - -#GeneChipId = 4 -#dataStart = 1 -# ProbeSetFreezeId = 359 #JAX Liver 6C Affy M430 2.0 (Jul11) MDP -#fp = open("GSE10493_AllSamples_6C_Z_AvgSE.txt", 'rb') - - -######################################################################### -# -# Check if each line have same number of members -# generate the gene list of expression data here -# -######################################################################### -print('Checking if each line have same number of members') - -GeneList = [] -isCont = 1 -header = fp.readline() -header = string.split(string.strip(header), '\t') -header = map(string.strip, header) -nfield = len(header) -line = fp.readline() - -kj = 0 -while line: - line2 = string.split(string.strip(line), '\t') - line2 = map(string.strip, line2) - if len(line2) != nfield: - isCont = 0 - print("Error : " + line) - - GeneList.append(line2[0]) - line = fp.readline() - - kj += 1 - if kj % 100000 == 0: - print('checked ', kj, ' lines') - -GeneList = map(string.lower, GeneList) -GeneList.sort() - -if isCont == 0: - sys.exit(0) - - -print('used ', time.time()-time0, ' seconds') -######################################################################### -# -# Check if each strain exist in database -# generate the string id list of expression data here -# -######################################################################### -print('Checking if each strain exist in database') - -isCont = 1 -fp.seek(0) -header = fp.readline() -header = string.split(string.strip(header), '\t') -header = map(string.strip, header) -header = map(translateAlias, header) -header = header[dataStart:] -Ids = [] -for item in header: - try: - db.execute('select Id from Strain where Name = "%s"' % item) - Ids.append(db.fetchall()[0][0]) - except: - isCont = 0 - print(item, 'does not exist, check the if the strain name is correct') - -if isCont == 0: - sys.exit(0) - - -print('used ', time.time()-time0, ' seconds') -######################################################################## -# -# Check if each ProbeSet exist in database -# -######################################################################## -print('Check if each ProbeSet exist in database') - -##---- find PID is name or target ----## -line = fp.readline() -line = fp.readline() -line2 = string.split(string.strip(line), '\t') -line2 = map(string.strip, line2) -PId = line2[0] - -db.execute('select Id from ProbeSet where Name="%s" and ChipId=%d' % - (PId, GeneChipId)) -results = db.fetchall() -IdStr = 'TargetId' -if len(results) > 0: - IdStr = 'Name' - - -##---- get Name/TargetId list from database ----## -db.execute('select distinct(%s) from ProbeSet where ChipId=%d order by %s' % ( - IdStr, GeneChipId, IdStr)) -results = db.fetchall() - -Names = [] -for item in results: - Names.append(item[0]) - Names = map(string.lower, Names) - Names.sort() # -- Fixed the lower case problem of ProbeSets affx-mur_b2_at doesn't exist --# - -##---- compare genelist with names ----## -x = y = 0 -x1 = -1 -GeneList2 = [] -while x < len(GeneList) and y < len(Names): - if GeneList[x] == Names[y]: - x += 1 - y += 1 - elif GeneList[x] < Names[y]: - if x != x1: - GeneList2.append(GeneList[x]) - x1 = x - x += 1 - elif GeneList[x] > Names[y]: - y += 1 - - if x % 100000 == 0: - print('check Name, checked %d lines' % x) - -while x < len(GeneList): - GeneList2.append(GeneList[x]) - x += 1 - -isCont = 1 -ferror = open("ProbeSetError.txt", "wb") -for item in GeneList2: - ferror.write(item + " doesn't exist \n") - isCont = 0 - - print(item, " doesn't exist") -if isCont == 0: - sys.exit(0) - - -print('used ', time.time()-time0, ' seconds') -############################# -# Insert new Data into SE -############################ -db.execute(""" - select ProbeSet.%s, ProbeSetXRef.DataId from ProbeSet, ProbeSetXRef - where ProbeSet.Id=ProbeSetXRef.ProbeSetId and ProbeSetXRef.ProbeSetFreezeId=%d""" - % (IdStr, ProbeSetFreezeId)) -results = db.fetchall() - -ProbeNameId = {} -for Name, Id in results: - ProbeNameId[Name] = Id - -ferror = open("ProbeError.txt", "wb") - -DataValues = [] - -fp.seek(0) # XZ add this line -line = fp.readline() # XZ add this line -line = fp.readline() - -kj = 0 -while line: - line2 = string.split(string.strip(line), '\t') - line2 = map(string.strip, line2) - - CellId = line2[0] - if not ProbeNameId.has_key(CellId): - ferror.write(CellId + " doesn't exist\n") - else: - DataId = ProbeNameId[CellId] - datasorig = line2[dataStart:] - - i = 0 - for item in datasorig: - if item != '': - value = '('+str(DataId)+','+str(Ids[i])+','+str(item)+')' - DataValues.append(value) - i += 1 - - kj += 1 - if kj % 100 == 0: - Dataitems = ','.join(DataValues) - cmd = 'insert ProbeSetSE values %s' % Dataitems - db.execute(cmd) - - DataValues = [] - line = fp.readline() - print(CellId, " doesn't exist") - print('inserted ', kj, ' lines') - print('used ', time.time()-time0, ' seconds') - -if len(DataValues) > 0: - DataValues = ','.join(DataValues) - cmd = 'insert ProbeSetSE values %s' % DataValues - db.execute(cmd) - -con.close() +#!/usr/bin/python2 +"""This script use the nearest marker to the transcript as control, increasing permutation rounds according to the p-value""" +######################################################################## +# Last Updated Sep 27, 2011 by Xiaodong +# This version fix the bug that incorrectly exclude the first 2 probesetIDs +######################################################################## + +import string +import sys +import MySQLdb +import getpass +import time + + +def translateAlias(str): + if str == "B6": + return "C57BL/6J" + elif str == "D2": + return "DBA/2J" + else: + return str + +######################################################################## +# +# Indicate Data Start Position, ProbeFreezeId, GeneChipId, DataFile +# +######################################################################## + + +dataStart = 1 + +GeneChipId = int(raw_input("Enter GeneChipId:")) +ProbeSetFreezeId = int(raw_input("Enter ProbeSetFreezeId:")) +input_file_name = raw_input("Enter file name with suffix:") + +fp = open("%s" % input_file_name, 'rb') + + +try: + passwd = getpass.getpass('Please enter mysql password here : ') + con = MySQLdb.Connect(db='db_webqtl', host='localhost', + user='username', passwd=passwd) + + db = con.cursor() + print("You have successfully connected to mysql.\n") +except: + print("You entered incorrect password.\n") + sys.exit(0) + +time0 = time.time() +######################################################################## +# +# Indicate Data Start Position, ProbeFreezeId, GeneChipId, DataFile +# +######################################################################## + +#GeneChipId = 4 +#dataStart = 1 +# ProbeSetFreezeId = 359 #JAX Liver 6C Affy M430 2.0 (Jul11) MDP +#fp = open("GSE10493_AllSamples_6C_Z_AvgSE.txt", 'rb') + + +######################################################################### +# +# Check if each line have same number of members +# generate the gene list of expression data here +# +######################################################################### +print('Checking if each line have same number of members') + +GeneList = [] +isCont = 1 +header = fp.readline() +header = string.split(string.strip(header), '\t') +header = map(string.strip, header) +nfield = len(header) +line = fp.readline() + +kj = 0 +while line: + line2 = string.split(string.strip(line), '\t') + line2 = map(string.strip, line2) + if len(line2) != nfield: + isCont = 0 + print("Error : " + line) + + GeneList.append(line2[0]) + line = fp.readline() + + kj += 1 + if kj % 100000 == 0: + print('checked ', kj, ' lines') + +GeneList = map(string.lower, GeneList) +GeneList.sort() + +if isCont == 0: + sys.exit(0) + + +print('used ', time.time()-time0, ' seconds') +######################################################################### +# +# Check if each strain exist in database +# generate the string id list of expression data here +# +######################################################################### +print('Checking if each strain exist in database') + +isCont = 1 +fp.seek(0) +header = fp.readline() +header = string.split(string.strip(header), '\t') +header = map(string.strip, header) +header = map(translateAlias, header) +header = header[dataStart:] +Ids = [] +for item in header: + try: + db.execute('select Id from Strain where Name = "%s"' % item) + Ids.append(db.fetchall()[0][0]) + except: + isCont = 0 + print(item, 'does not exist, check the if the strain name is correct') + +if isCont == 0: + sys.exit(0) + + +print('used ', time.time()-time0, ' seconds') +######################################################################## +# +# Check if each ProbeSet exist in database +# +######################################################################## +print('Check if each ProbeSet exist in database') + +##---- find PID is name or target ----## +line = fp.readline() +line = fp.readline() +line2 = string.split(string.strip(line), '\t') +line2 = map(string.strip, line2) +PId = line2[0] + +db.execute('select Id from ProbeSet where Name="%s" and ChipId=%d' % + (PId, GeneChipId)) +results = db.fetchall() +IdStr = 'TargetId' +if len(results) > 0: + IdStr = 'Name' + + +##---- get Name/TargetId list from database ----## +db.execute('select distinct(%s) from ProbeSet where ChipId=%d order by %s' % ( + IdStr, GeneChipId, IdStr)) +results = db.fetchall() + +Names = [] +for item in results: + Names.append(item[0]) + Names = map(string.lower, Names) + Names.sort() # -- Fixed the lower case problem of ProbeSets affx-mur_b2_at doesn't exist --# + +##---- compare genelist with names ----## +x = y = 0 +x1 = -1 +GeneList2 = [] +while x < len(GeneList) and y < len(Names): + if GeneList[x] == Names[y]: + x += 1 + y += 1 + elif GeneList[x] < Names[y]: + if x != x1: + GeneList2.append(GeneList[x]) + x1 = x + x += 1 + elif GeneList[x] > Names[y]: + y += 1 + + if x % 100000 == 0: + print('check Name, checked %d lines' % x) + +while x < len(GeneList): + GeneList2.append(GeneList[x]) + x += 1 + +isCont = 1 +ferror = open("ProbeSetError.txt", "wb") +for item in GeneList2: + ferror.write(item + " doesn't exist \n") + isCont = 0 + + print(item, " doesn't exist") +if isCont == 0: + sys.exit(0) + + +print('used ', time.time()-time0, ' seconds') +############################# +# Insert new Data into SE +############################ +db.execute(""" + select ProbeSet.%s, ProbeSetXRef.DataId from ProbeSet, ProbeSetXRef + where ProbeSet.Id=ProbeSetXRef.ProbeSetId and ProbeSetXRef.ProbeSetFreezeId=%d""" + % (IdStr, ProbeSetFreezeId)) +results = db.fetchall() + +ProbeNameId = {} +for Name, Id in results: + ProbeNameId[Name] = Id + +ferror = open("ProbeError.txt", "wb") + +DataValues = [] + +fp.seek(0) # XZ add this line +line = fp.readline() # XZ add this line +line = fp.readline() + +kj = 0 +while line: + line2 = string.split(string.strip(line), '\t') + line2 = map(string.strip, line2) + + CellId = line2[0] + if not ProbeNameId.has_key(CellId): + ferror.write(CellId + " doesn't exist\n") + else: + DataId = ProbeNameId[CellId] + datasorig = line2[dataStart:] + + i = 0 + for item in datasorig: + if item != '': + value = '('+str(DataId)+','+str(Ids[i])+','+str(item)+')' + DataValues.append(value) + i += 1 + + kj += 1 + if kj % 100 == 0: + Dataitems = ','.join(DataValues) + cmd = 'insert ProbeSetSE values %s' % Dataitems + db.execute(cmd) + + DataValues = [] + line = fp.readline() + print(CellId, " doesn't exist") + print('inserted ', kj, ' lines') + print('used ', time.time()-time0, ' seconds') + +if len(DataValues) > 0: + DataValues = ','.join(DataValues) + cmd = 'insert ProbeSetSE values %s' % DataValues + db.execute(cmd) + +con.close() -- cgit v1.2.3 From e49ef0954e111ace2044cced0a83b4d9dc00bc72 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 17 Aug 2020 20:45:08 +0300 Subject: Fix malformed docstring * wqflask/utility/Plot.py: Update docstring. --- wqflask/utility/Plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/utility/Plot.py b/wqflask/utility/Plot.py index d4373412..82bf6070 100644 --- a/wqflask/utility/Plot.py +++ b/wqflask/utility/Plot.py @@ -97,7 +97,7 @@ def find_outliers(vals): >>> find_outliers([3.504, 5.234, 6.123, 7.234, 3.542, 5.341, 7.852, 4.555, 12.537]) (11.252500000000001, 0.5364999999999993) - >>> >>> find_outliers([9,12,15,17,31,50,7,5,6,8]) + >>> find_outliers([9,12,15,17,31,50,7,5,6,8]) (32.0, -8.0) If there are no vals, returns None for the upper and lower bounds, -- cgit v1.2.3 From af330a2aa7b36fd0cf8505eb20fa06d2ed58b86b Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 17 Aug 2020 17:41:58 +0300 Subject: Wrap print statements in parentheses --- scripts/maintenance/QTL_Reaper_v6.py | 6 ++-- .../Update_Case_Attributes_MySQL_tab.py | 2 +- scripts/maintenance/delete_genotypes.py | 14 ++++---- scripts/maintenance/delete_phenotypes.py | 14 ++++---- scripts/maintenance/load_genotypes.py | 16 ++++----- scripts/maintenance/load_phenotypes.py | 30 ++++++++-------- scripts/maintenance/readProbeSetMean_v7.py | 40 +++++++++++----------- scripts/maintenance/readProbeSetSE_v7.py | 22 ++++++------ wqflask/run_gunicorn.py | 2 +- wqflask/utility/startup_config.py | 4 +-- wqflask/utility/svg.py | 10 +++--- wqflask/utility/tools.py | 8 ++--- wqflask/wqflask/marker_regression/rqtl_mapping.py | 2 +- wqflask/wqflask/pbkdf2.py | 16 ++++----- wqflask/wqflask/views.py | 4 +-- wqflask/wqflask/wgcna/wgcna_analysis.py | 12 +++---- 16 files changed, 101 insertions(+), 101 deletions(-) diff --git a/scripts/maintenance/QTL_Reaper_v6.py b/scripts/maintenance/QTL_Reaper_v6.py index e50dbd40..7fb56eca 100755 --- a/scripts/maintenance/QTL_Reaper_v6.py +++ b/scripts/maintenance/QTL_Reaper_v6.py @@ -53,7 +53,7 @@ for ProbeSetFreezeId in ProbeSetFreezeIds: #if InbredSetId==12: # InbredSetId=2 - print ProbeSetFreezeId, InbredSets[InbredSetId] + print((ProbeSetFreezeId, InbredSets[InbredSetId])) genotype_1.read(InbredSets[InbredSetId]) locuses = [] @@ -102,7 +102,7 @@ for ProbeSetFreezeId in ProbeSetFreezeIds: kj += 1 if kj%1000==0: - print ProbeSetFreezeId, InbredSets[InbredSetId],kj + print((ProbeSetFreezeId, InbredSets[InbredSetId],kj)) - print ProbeSetFreezeIds + print(ProbeSetFreezeIds) diff --git a/scripts/maintenance/Update_Case_Attributes_MySQL_tab.py b/scripts/maintenance/Update_Case_Attributes_MySQL_tab.py index 0f8602c9..bf796df4 100644 --- a/scripts/maintenance/Update_Case_Attributes_MySQL_tab.py +++ b/scripts/maintenance/Update_Case_Attributes_MySQL_tab.py @@ -24,4 +24,4 @@ for row in csv_data: #close the connection to the database. mydb.commit() cursor.close() -print "Done" \ No newline at end of file +print("Done") \ No newline at end of file diff --git a/scripts/maintenance/delete_genotypes.py b/scripts/maintenance/delete_genotypes.py index fa693f0f..060640e1 100755 --- a/scripts/maintenance/delete_genotypes.py +++ b/scripts/maintenance/delete_genotypes.py @@ -8,13 +8,13 @@ import genotypes def main(argv): # config config = utilities.get_config(argv[1]) - print "config:" + print("config:") for item in config.items('config'): - print "\t%s" % (str(item)) + print(("\t%s" % (str(item)))) # var - print "variable:" + print("variable:") inbredsetid = config.get('config', 'inbredsetid') - print "\tinbredsetid: %s" % inbredsetid + print(("\tinbredsetid: %s" % inbredsetid)) # datafile datafile = open(config.get('config', 'datafile'), 'r') datafile = csv.reader(datafile, delimiter='\t', quotechar='"') @@ -25,9 +25,9 @@ def main(argv): continue genoname = row[0] delrowcount += genotypes.delete(genoname, inbredsetid) - print "deleted %d genotypes" % (delrowcount) + print(("deleted %d genotypes" % (delrowcount))) if __name__ == "__main__": - print "command line arguments:\n\t%s" % sys.argv + print(("command line arguments:\n\t%s" % sys.argv)) main(sys.argv) - print "exit successfully" + print("exit successfully") diff --git a/scripts/maintenance/delete_phenotypes.py b/scripts/maintenance/delete_phenotypes.py index 326c466e..60dbec61 100755 --- a/scripts/maintenance/delete_phenotypes.py +++ b/scripts/maintenance/delete_phenotypes.py @@ -8,13 +8,13 @@ import phenotypes def main(argv): # config config = utilities.get_config(argv[1]) - print "config:" + print("config:") for item in config.items('config'): - print "\t%s" % (str(item)) + print(("\t%s" % (str(item)))) # var - print "variable:" + print("variable:") inbredsetid = config.get('config', 'inbredsetid') - print "\tinbredsetid: %s" % inbredsetid + print(("\tinbredsetid: %s" % inbredsetid)) # datafile datafile = open(config.get('config', 'datafile'), 'r') datafile = csv.reader(datafile, delimiter='\t', quotechar='"') @@ -27,9 +27,9 @@ def main(argv): except: continue delrowcount += phenotypes.delete(publishxrefid=publishxrefid, inbredsetid=inbredsetid) - print "deleted %d phenotypes" % (delrowcount) + print(("deleted %d phenotypes" % (delrowcount))) if __name__ == "__main__": - print "command line arguments:\n\t%s" % sys.argv + print(("command line arguments:\n\t%s" % sys.argv)) main(sys.argv) - print "exit successfully" + print("exit successfully") diff --git a/scripts/maintenance/load_genotypes.py b/scripts/maintenance/load_genotypes.py index 338483f4..c235a31f 100755 --- a/scripts/maintenance/load_genotypes.py +++ b/scripts/maintenance/load_genotypes.py @@ -8,7 +8,7 @@ def main(argv): config = utilities.get_config(argv[1]) print("config file:") for item in config.items('config'): - print("\t%s" % str(item)) + print(("\t%s" % str(item))) parse_genofile(config, fetch_parameters(config)) def fetch_parameters(config): @@ -20,7 +20,7 @@ def fetch_parameters(config): config_dic['genofile'] = config.get('config', 'genofile') print("config dictionary:") for k, v in config_dic.items(): - print("\t%s: %s" % (k, v)) + print(("\t%s: %s" % (k, v))) return config_dic def parse_genofile(config, config_dic): @@ -43,9 +43,9 @@ def parse_genofile(config, config_dic): # print("geno file meta dictionary:") for k, v in meta_dic.items(): - print("\t%s: %s" % (k, v)) + print(("\t%s: %s" % (k, v))) # - print("geno file head:\n\t%s" % line) + print(("geno file head:\n\t%s" % line)) strainnames = line.split()[4:] config_dic['strains'] = datastructure.get_strains_bynames(inbredsetid=config_dic['inbredsetid'], strainnames=strainnames, updatestrainxref="yes") continue @@ -81,7 +81,7 @@ def check_or_insert_geno(config_dic, marker_dic): result = cursor.fetchone() if result: genoid = result[0] - print("get geno record: %d" % genoid) + print(("get geno record: %d" % genoid)) else: sql = """ INSERT INTO Geno @@ -95,7 +95,7 @@ def check_or_insert_geno(config_dic, marker_dic): cursor.execute(sql, (config_dic['speciesid'], marker_dic['locus'], marker_dic['locus'], marker_dic['chromosome'], marker_dic['mb'])) rowcount = cursor.rowcount genoid = con.insert_id() - print("INSERT INTO Geno: %d record: %d" % (rowcount, genoid)) + print(("INSERT INTO Geno: %d record: %d" % (rowcount, genoid))) return genoid def check_genoxref(config_dic, marker_dic): @@ -146,9 +146,9 @@ def insert_genoxref(config_dic, marker_dic): """ cursor.execute(sql, (config_dic['genofreezeid'], marker_dic['genoid'], config_dic['dataid'], marker_dic['cm'], 'N')) rowcount = cursor.rowcount - print("INSERT INTO GenoXRef: %d record" % (rowcount)) + print(("INSERT INTO GenoXRef: %d record" % (rowcount))) if __name__ == "__main__": - print("command line arguments:\n\t%s" % sys.argv) + print(("command line arguments:\n\t%s" % sys.argv)) main(sys.argv) print("exit successfully") diff --git a/scripts/maintenance/load_phenotypes.py b/scripts/maintenance/load_phenotypes.py index c3c6570b..61d527d4 100755 --- a/scripts/maintenance/load_phenotypes.py +++ b/scripts/maintenance/load_phenotypes.py @@ -7,31 +7,31 @@ import datastructure def main(argv): # config config = utilities.get_config(argv[1]) - print "config:" + print("config:") for item in config.items('config'): - print "\t%s" % (str(item)) + print(("\t%s" % (str(item)))) # var inbredsetid = config.get('config', 'inbredsetid') - print "inbredsetid: %s" % inbredsetid + print(("inbredsetid: %s" % inbredsetid)) species = datastructure.get_species(inbredsetid) speciesid = species[0] - print "speciesid: %s" % speciesid + print(("speciesid: %s" % speciesid)) dataid = datastructure.get_nextdataid_phenotype() - print "next data id: %s" % dataid + print(("next data id: %s" % dataid)) cursor, con = utilities.get_cursor() # datafile datafile = open(config.get('config', 'datafile'), 'r') phenotypedata = csv.reader(datafile, delimiter='\t', quotechar='"') phenotypedata_head = phenotypedata.next() - print "phenotypedata head:\n\t%s" % phenotypedata_head + print(("phenotypedata head:\n\t%s" % phenotypedata_head)) strainnames = phenotypedata_head[1:] strains = datastructure.get_strains_bynames(inbredsetid=inbredsetid, strainnames=strainnames, updatestrainxref="yes") # metafile metafile = open(config.get('config', 'metafile'), 'r') phenotypemeta = csv.reader(metafile, delimiter='\t', quotechar='"') phenotypemeta_head = phenotypemeta.next() - print "phenotypemeta head:\n\t%s" % phenotypemeta_head - print + print(("phenotypemeta head:\n\t%s" % phenotypemeta_head)) + print() # load for metarow in phenotypemeta: # @@ -67,7 +67,7 @@ def main(argv): )) rowcount = cursor.rowcount phenotypeid = con.insert_id() - print "INSERT INTO Phenotype: %d record: %d" % (rowcount, phenotypeid) + print(("INSERT INTO Phenotype: %d record: %d" % (rowcount, phenotypeid))) # Publication publicationid = None # reset pubmed_id = utilities.to_db_string(metarow[0], None) @@ -81,7 +81,7 @@ def main(argv): re = cursor.fetchone() if re: publicationid = re[0] - print "get Publication record: %d" % publicationid + print(("get Publication record: %d" % publicationid)) if not publicationid: sql = """ INSERT INTO Publication @@ -109,7 +109,7 @@ def main(argv): )) rowcount = cursor.rowcount publicationid = con.insert_id() - print "INSERT INTO Publication: %d record: %d" % (rowcount, publicationid) + print(("INSERT INTO Publication: %d record: %d" % (rowcount, publicationid))) # data for index, strain in enumerate(strains): # @@ -158,14 +158,14 @@ def main(argv): cursor.execute(sql, (inbredsetid, phenotypeid, publicationid, dataid, "")) rowcount = cursor.rowcount publishxrefid = con.insert_id() - print "INSERT INTO PublishXRef: %d record: %d" % (rowcount, publishxrefid) + print(("INSERT INTO PublishXRef: %d record: %d" % (rowcount, publishxrefid))) # for loop next dataid += 1 - print + print() # release con.close() if __name__ == "__main__": - print "command line arguments:\n\t%s" % sys.argv + print(("command line arguments:\n\t%s" % sys.argv)) main(sys.argv) - print "exit successfully" + print("exit successfully") diff --git a/scripts/maintenance/readProbeSetMean_v7.py b/scripts/maintenance/readProbeSetMean_v7.py index e9c8f25c..e7a4c826 100755 --- a/scripts/maintenance/readProbeSetMean_v7.py +++ b/scripts/maintenance/readProbeSetMean_v7.py @@ -42,9 +42,9 @@ try: con = MySQLdb.Connect(db='db_webqtl',host='localhost', user='username',passwd=passwd) db = con.cursor() - print "You have successfully connected to mysql.\n" + print("You have successfully connected to mysql.\n") except: - print "You entered incorrect password.\n" + print("You entered incorrect password.\n") sys.exit(0) time0 = time.time() @@ -55,7 +55,7 @@ time0 = time.time() # generate the gene list of expression data here # ######################################################################### -print 'Checking if each line have same number of members' +print('Checking if each line have same number of members') GeneList = [] isCont = 1 @@ -70,7 +70,7 @@ while line: line2 = string.split(string.strip(line),'\t') line2 = map(string.strip, line2) if len(line2) != nfield: - print "Error : " + line + print(("Error : " + line)) isCont = 0 GeneList.append(line2[0]) @@ -78,7 +78,7 @@ while line: kj+=1 if kj%100000 == 0: - print 'checked ',kj,' lines' + print(('checked ',kj,' lines')) GeneList = map(string.lower, GeneList) GeneList.sort() @@ -87,14 +87,14 @@ if isCont==0: sys.exit(0) -print 'used ',time.time()-time0,' seconds' +print(('used ',time.time()-time0,' seconds')) ######################################################################### # # Check if each strain exist in database # generate the string id list of expression data here # ######################################################################### -print 'Checking if each strain exist in database' +print('Checking if each strain exist in database') isCont = 1 fp.seek(0) @@ -109,20 +109,20 @@ for item in header: db.execute('select Id from Strain where Name = "%s"' % item) Ids.append(db.fetchall()[0][0]) except: - print item,'does not exist, check the if the strain name is correct' + print((item,'does not exist, check the if the strain name is correct')) isCont=0 if isCont==0: sys.exit(0) -print 'used ',time.time()-time0,' seconds' +print(('used ',time.time()-time0,' seconds')) ######################################################################## # # Check if each ProbeSet exist in database # ######################################################################## -print 'Check if each ProbeSet exist in database' +print('Check if each ProbeSet exist in database') ##---- find PID is name or target ----## line = fp.readline() @@ -146,7 +146,7 @@ Names = [] for item in results: Names.append(item[0]) -print Names +print(Names) Names = map(string.lower, Names) @@ -170,7 +170,7 @@ while x 0: DataValues = ','.join(DataValues) diff --git a/wqflask/run_gunicorn.py b/wqflask/run_gunicorn.py index adffdca3..58108e03 100644 --- a/wqflask/run_gunicorn.py +++ b/wqflask/run_gunicorn.py @@ -7,7 +7,7 @@ # from flask import Flask # application = Flask(__name__) -print "===> Starting up Gunicorn process" +print("===> Starting up Gunicorn process") from wqflask import app from utility.startup_config import app_config diff --git a/wqflask/utility/startup_config.py b/wqflask/utility/startup_config.py index 817284dd..42ead709 100644 --- a/wqflask/utility/startup_config.py +++ b/wqflask/utility/startup_config.py @@ -27,7 +27,7 @@ def app_config(): port = get_setting_int("SERVER_PORT") if get_setting_bool("USE_GN_SERVER"): - print("GN2 API server URL is ["+BLUE+get_setting("GN_SERVER_URL")+ENDC+"]") + print(("GN2 API server URL is ["+BLUE+get_setting("GN_SERVER_URL")+ENDC+"]")) import requests page = requests.get(get_setting("GN_SERVER_URL")) if page.status_code != 200: @@ -36,4 +36,4 @@ def app_config(): # import utility.elasticsearch_tools as es # es.test_elasticsearch_connection() - print("GN2 is running. Visit %s[http://localhost:%s/%s](%s)" % (BLUE,str(port),ENDC,get_setting("WEBSERVER_URL"))) + print(("GN2 is running. Visit %s[http://localhost:%s/%s](%s)" % (BLUE,str(port),ENDC,get_setting("WEBSERVER_URL")))) diff --git a/wqflask/utility/svg.py b/wqflask/utility/svg.py index db13b9d1..6285ea63 100644 --- a/wqflask/utility/svg.py +++ b/wqflask/utility/svg.py @@ -102,7 +102,7 @@ if use_dom_implementation<>0: from xml.dom import implementation from xml.dom.ext import PrettyPrint except: - raise exceptions.ImportError, "PyXML is required for using the dom implementation" + raise exceptions.ImportError("PyXML is required for using the dom implementation") #The implementation is used for the creating the XML document. #The prettyprint module is used for converting the xml document object to a xml file @@ -1018,12 +1018,12 @@ class drawing: PrettyPrint(root,f) f.close() except: - print "Cannot write SVG file: " + filename + print(("Cannot write SVG file: " + filename)) def validate(self): try: import xml.parsers.xmlproc.xmlval except: - raise exceptions.ImportError,'PyXml is required for validating SVG' + raise exceptions.ImportError('PyXml is required for validating SVG') svg=self.toXml() xv=xml.parsers.xmlproc.xmlval.XMLValidator() try: @@ -1031,7 +1031,7 @@ class drawing: except: raise Exception("SVG is not well formed, see messages above") else: - print "SVG well formed" + print("SVG well formed") if __name__=='__main__': @@ -1065,4 +1065,4 @@ if __name__=='__main__': s.addElement(c) d.setSVG(s) - print d.toXml() + print((d.toXml())) diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py index 77db5d53..f790d424 100644 --- a/wqflask/utility/tools.py +++ b/wqflask/utility/tools.py @@ -214,20 +214,20 @@ ENDC = '\033[0m' def show_settings(): from utility.tools import LOG_LEVEL - print("Set global log level to "+BLUE+LOG_LEVEL+ENDC) + print(("Set global log level to "+BLUE+LOG_LEVEL+ENDC)) log_level = getattr(logging, LOG_LEVEL.upper()) logging.basicConfig(level=log_level) logger.info(OVERRIDES) logger.info(BLUE+"Mr. Mojo Risin 2"+ENDC) - print "runserver.py: ****** Webserver configuration - k,v pairs from app.config ******" keylist = app.config.keys() + print("runserver.py: ****** Webserver configuration - k,v pairs from app.config ******") keylist.sort() for k in keylist: try: - print("%s: %s%s%s%s" % (k,BLUE,BOLD,get_setting(k),ENDC)) + print(("%s: %s%s%s%s" % (k,BLUE,BOLD,get_setting(k),ENDC))) except: - print("%s: %s%s%s%s" % (k,GREEN,BOLD,app.config[k],ENDC)) + print(("%s: %s%s%s%s" % (k,GREEN,BOLD,app.config[k],ENDC))) # Cached values diff --git a/wqflask/wqflask/marker_regression/rqtl_mapping.py b/wqflask/wqflask/marker_regression/rqtl_mapping.py index c5590a85..0a5758af 100644 --- a/wqflask/wqflask/marker_regression/rqtl_mapping.py +++ b/wqflask/wqflask/marker_regression/rqtl_mapping.py @@ -42,7 +42,7 @@ def run_rqtl_geno(vals, samples, dataset, mapping_scale, method, model, permChec png = ro.r["png"] # Map the png function dev_off = ro.r["dev.off"] # Map the device off function - print(r_library("qtl")) # Load R/qtl + print((r_library("qtl"))) # Load R/qtl logger.info("QTL library loaded"); diff --git a/wqflask/wqflask/pbkdf2.py b/wqflask/wqflask/pbkdf2.py index f7f61a09..811c83b0 100644 --- a/wqflask/wqflask/pbkdf2.py +++ b/wqflask/wqflask/pbkdf2.py @@ -92,14 +92,14 @@ def test(): def check(data, salt, iterations, keylen, expected): rv = pbkdf2_hex(data, salt, iterations, keylen) if rv != expected: - print 'Test failed:' - print ' Expected: %s' % expected - print ' Got: %s' % rv - print ' Parameters:' - print ' data=%s' % data - print ' salt=%s' % salt - print ' iterations=%d' % iterations - print + print('Test failed:') + print((' Expected: %s' % expected)) + print((' Got: %s' % rv)) + print(' Parameters:') + print((' data=%s' % data)) + print((' salt=%s' % salt)) + print((' iterations=%d' % iterations)) + print() failed.append(1) # From RFC 6070 diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index dde22bf7..d67f1a2e 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -943,5 +943,5 @@ def json_default_handler(obj): # logger.info("Not going to serialize Dataset") # return None else: - raise TypeError, 'Object of type %s with value of %s is not JSON serializable' % ( - type(obj), repr(obj)) + raise TypeError('Object of type %s with value of %s is not JSON serializable' % ( + type(obj), repr(obj))) diff --git a/wqflask/wqflask/wgcna/wgcna_analysis.py b/wqflask/wqflask/wgcna/wgcna_analysis.py index 880a1cb2..d79ad6df 100644 --- a/wqflask/wqflask/wgcna/wgcna_analysis.py +++ b/wqflask/wqflask/wgcna/wgcna_analysis.py @@ -60,7 +60,7 @@ class WGCNA(object): print("Starting WGCNA analysis on dataset") self.r_enableWGCNAThreads() # Enable multi threading self.trait_db_list = [trait.strip() for trait in requestform['trait_list'].split(',')] - print("Retrieved phenotype data from database", requestform['trait_list']) + print(("Retrieved phenotype data from database", requestform['trait_list'])) helper_functions.get_trait_db_obs(self, self.trait_db_list) self.input = {} # self.input contains the phenotype values we need to send to R @@ -101,13 +101,13 @@ class WGCNA(object): if requestform.get('SoftThresholds') is not None: powers = [int(threshold.strip()) for threshold in requestform['SoftThresholds'].rstrip().split(",")] rpow = r_unlist(r_c(powers)) - print "SoftThresholds: {} == {}".format(powers, rpow) + print(("SoftThresholds: {} == {}".format(powers, rpow))) self.sft = self.r_pickSoftThreshold(rM, powerVector = rpow, verbose = 5) - print "PowerEstimate: {}".format(self.sft[0]) + print(("PowerEstimate: {}".format(self.sft[0]))) self.results['PowerEstimate'] = self.sft[0] if self.sft[0][0] is ri.NA_Integer: - print "No power is suitable for the analysis, just use 1" + print("No power is suitable for the analysis, just use 1") self.results['Power'] = 1 # No power could be estimated else: self.results['Power'] = self.sft[0][0] # Use the estimated power @@ -122,7 +122,7 @@ class WGCNA(object): self.results['network'] = network # How many modules and how many gene per module ? - print "WGCNA found {} modules".format(r_table(network[1])) + print(("WGCNA found {} modules".format(r_table(network[1])))) self.results['nmod'] = r_length(r_table(network[1]))[0] # The iconic WCGNA plot of the modules in the hanging tree @@ -135,7 +135,7 @@ class WGCNA(object): sys.stdout.flush() def render_image(self, results): - print("pre-loading imgage results:", self.results['imgloc']) + print(("pre-loading imgage results:", self.results['imgloc'])) imgfile = open(self.results['imgloc'], 'rb') imgdata = imgfile.read() imgB64 = imgdata.encode("base64") -- cgit v1.2.3 From acdc611247c8eaccbba3dec00ad640e10a4c99b1 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 17 Aug 2020 21:45:04 +0300 Subject: Replace "<>" with "!=" * wqflask/utility/svg.py: Use "!=". The operator, "<>" is removed in Python3 --- wqflask/utility/svg.py | 196 ++++++++++++++++++++++++------------------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/wqflask/utility/svg.py b/wqflask/utility/svg.py index 6285ea63..f737d2f2 100644 --- a/wqflask/utility/svg.py +++ b/wqflask/utility/svg.py @@ -97,7 +97,7 @@ use_dom_implementation=0 import exceptions -if use_dom_implementation<>0: +if use_dom_implementation!=0: try: from xml.dom import implementation from xml.dom.ext import PrettyPrint @@ -398,22 +398,22 @@ class rect(SVGelement): """ def __init__(self,x=None,y=None,width=None,height=None,fill=None,stroke=None,stroke_width=None,**args): if width==None or height==None: - if width<>None: raise ValueError, 'height is required' - if height<>None: raise ValueError, 'width is required' + if width!=None: + if height!=None: else: raise ValueError, 'both height and width are required' SVGelement.__init__(self,'rect',{'width':width,'height':height},**args) - if x<>None: + if x!=None: self.attributes['x']=x - if y<>None: + if y!=None: self.attributes['y']=y - if fill<>None: + if fill!=None: self.attributes['fill']=fill - if stroke<>None: + if stroke!=None: self.attributes['stroke']=stroke - if stroke_width<>None: + if stroke_width!=None: self.attributes['stroke-width']=stroke_width class ellipse(SVGelement): @@ -423,22 +423,22 @@ class ellipse(SVGelement): """ def __init__(self,cx=None,cy=None,rx=None,ry=None,fill=None,stroke=None,stroke_width=None,**args): if rx==None or ry== None: - if rx<>None: raise ValueError, 'rx is required' - if ry<>None: raise ValueError, 'ry is required' + if rx!=None: + if ry!=None: else: raise ValueError, 'both rx and ry are required' SVGelement.__init__(self,'ellipse',{'rx':rx,'ry':ry},**args) - if cx<>None: + if cx!=None: self.attributes['cx']=cx - if cy<>None: + if cy!=None: self.attributes['cy']=cy - if fill<>None: + if fill!=None: self.attributes['fill']=fill - if stroke<>None: + if stroke!=None: self.attributes['stroke']=stroke - if stroke_width<>None: + if stroke_width!=None: self.attributes['stroke-width']=stroke_width @@ -451,15 +451,15 @@ class circle(SVGelement): if r==None: raise ValueError, 'r is required' SVGelement.__init__(self,'circle',{'r':r},**args) - if cx<>None: + if cx!=None: self.attributes['cx']=cx - if cy<>None: + if cy!=None: self.attributes['cy']=cy - if fill<>None: + if fill!=None: self.attributes['fill']=fill - if stroke<>None: + if stroke!=None: self.attributes['stroke']=stroke - if stroke_width<>None: + if stroke_width!=None: self.attributes['stroke-width']=stroke_width class point(circle): @@ -478,17 +478,17 @@ class line(SVGelement): """ def __init__(self,x1=None,y1=None,x2=None,y2=None,stroke=None,stroke_width=None,**args): SVGelement.__init__(self,'line',**args) - if x1<>None: + if x1!=None: self.attributes['x1']=x1 - if y1<>None: + if y1!=None: self.attributes['y1']=y1 - if x2<>None: + if x2!=None: self.attributes['x2']=x2 - if y2<>None: + if y2!=None: self.attributes['y2']=y2 - if stroke_width<>None: + if stroke_width!=None: self.attributes['stroke-width']=stroke_width - if stroke<>None: + if stroke!=None: self.attributes['stroke']=stroke class polyline(SVGelement): @@ -498,11 +498,11 @@ class polyline(SVGelement): """ def __init__(self,points,fill=None,stroke=None,stroke_width=None,**args): SVGelement.__init__(self,'polyline',{'points':_xypointlist(points)},**args) - if fill<>None: + if fill!=None: self.attributes['fill']=fill - if stroke_width<>None: + if stroke_width!=None: self.attributes['stroke-width']=stroke_width - if stroke<>None: + if stroke!=None: self.attributes['stroke']=stroke class polygon(SVGelement): @@ -512,11 +512,11 @@ class polygon(SVGelement): """ def __init__(self,points,fill=None,stroke=None,stroke_width=None,**args): SVGelement.__init__(self,'polygon',{'points':_xypointlist(points)},**args) - if fill<>None: + if fill!=None: self.attributes['fill']=fill - if stroke_width<>None: + if stroke_width!=None: self.attributes['stroke-width']=stroke_width - if stroke<>None: + if stroke!=None: self.attributes['stroke']=stroke class path(SVGelement): @@ -526,13 +526,13 @@ class path(SVGelement): """ def __init__(self,pathdata,fill=None,stroke=None,stroke_width=None,id=None,**args): SVGelement.__init__(self,'path',{'d':str(pathdata)},**args) - if stroke<>None: + if stroke!=None: self.attributes['stroke']=stroke - if fill<>None: + if fill!=None: self.attributes['fill']=fill - if stroke_width<>None: + if stroke_width!=None: self.attributes['stroke-width']=stroke_width - if id<>None: + if id!=None: self.attributes['id']=id @@ -543,17 +543,17 @@ class text(SVGelement): """ def __init__(self,x=None,y=None,text=None,font_size=None,font_family=None,text_anchor=None,**args): SVGelement.__init__(self,'text',**args) - if x<>None: + if x!=None: self.attributes['x']=x - if y<>None: + if y!=None: self.attributes['y']=y - if font_size<>None: + if font_size!=None: self.attributes['font-size']=font_size - if font_family<>None: + if font_family!=None: self.attributes['font-family']=font_family - if text<>None: + if text!=None: self.text=text - if text_anchor<>None: + if text_anchor!=None: self.attributes['text-anchor']=text_anchor @@ -564,7 +564,7 @@ class textpath(SVGelement): """ def __init__(self,link,text=None,**args): SVGelement.__init__(self,'textPath',{'xlink:href':link},**args) - if text<>None: + if text!=None: self.text=text class pattern(SVGelement): @@ -576,15 +576,15 @@ class pattern(SVGelement): """ def __init__(self,x=None,y=None,width=None,height=None,patternUnits=None,**args): SVGelement.__init__(self,'pattern',**args) - if x<>None: + if x!=None: self.attributes['x']=x - if y<>None: + if y!=None: self.attributes['y']=y - if width<>None: + if width!=None: self.attributes['width']=width - if height<>None: + if height!=None: self.attributes['height']=height - if patternUnits<>None: + if patternUnits!=None: self.attributes['patternUnits']=patternUnits class title(SVGelement): @@ -595,7 +595,7 @@ class title(SVGelement): """ def __init__(self,text=None,**args): SVGelement.__init__(self,'title',**args) - if text<>None: + if text!=None: self.text=text class description(SVGelement): @@ -606,7 +606,7 @@ class description(SVGelement): """ def __init__(self,text=None,**args): SVGelement.__init__(self,'desc',**args) - if text<>None: + if text!=None: self.text=text class lineargradient(SVGelement): @@ -617,15 +617,15 @@ class lineargradient(SVGelement): """ def __init__(self,x1=None,y1=None,x2=None,y2=None,id=None,**args): SVGelement.__init__(self,'linearGradient',**args) - if x1<>None: + if x1!=None: self.attributes['x1']=x1 - if y1<>None: + if y1!=None: self.attributes['y1']=y1 - if x2<>None: + if x2!=None: self.attributes['x2']=x2 - if y2<>None: + if y2!=None: self.attributes['y2']=y2 - if id<>None: + if id!=None: self.attributes['id']=id class radialgradient(SVGelement): @@ -636,17 +636,17 @@ class radialgradient(SVGelement): """ def __init__(self,cx=None,cy=None,r=None,fx=None,fy=None,id=None,**args): SVGelement.__init__(self,'radialGradient',**args) - if cx<>None: + if cx!=None: self.attributes['cx']=cx - if cy<>None: + if cy!=None: self.attributes['cy']=cy - if r<>None: + if r!=None: self.attributes['r']=r - if fx<>None: + if fx!=None: self.attributes['fx']=fx - if fy<>None: + if fy!=None: self.attributes['fy']=fy - if id<>None: + if id!=None: self.attributes['id']=id class stop(SVGelement): @@ -656,7 +656,7 @@ class stop(SVGelement): """ def __init__(self,offset,stop_color=None,**args): SVGelement.__init__(self,'stop',{'offset':offset},**args) - if stop_color<>None: + if stop_color!=None: self.attributes['stop-color']=stop_color class style(SVGelement): @@ -675,16 +675,16 @@ class image(SVGelement): """ def __init__(self,url,x=None,y=None,width=None,height=None,**args): if width==None or height==None: - if width<>None: raise ValueError, 'height is required' - if height<>None: raise ValueError, 'width is required' + if width!=None: + if height!=None: else: raise ValueError, 'both height and width are required' SVGelement.__init__(self,'image',{'xlink:href':url,'width':width,'height':height},**args) - if x<>None: + if x!=None: self.attributes['x']=x - if y<>None: + if y!=None: self.attributes['y']=y class cursor(SVGelement): @@ -704,17 +704,17 @@ class marker(SVGelement): """ def __init__(self,id=None,viewBox=None,refx=None,refy=None,markerWidth=None,markerHeight=None,**args): SVGelement.__init__(self,'marker',**args) - if id<>None: + if id!=None: self.attributes['id']=id - if viewBox<>None: + if viewBox!=None: self.attributes['viewBox']=_viewboxlist(viewBox) - if refx<>None: + if refx!=None: self.attributes['refX']=refx - if refy<>None: + if refy!=None: self.attributes['refY']=refy - if markerWidth<>None: + if markerWidth!=None: self.attributes['markerWidth']=markerWidth - if markerHeight<>None: + if markerHeight!=None: self.attributes['markerHeight']=markerHeight class group(SVGelement): @@ -725,7 +725,7 @@ class group(SVGelement): """ def __init__(self,id=None,**args): SVGelement.__init__(self,'g',**args) - if id<>None: + if id!=None: self.attributes['id']=id class symbol(SVGelement): @@ -739,9 +739,9 @@ class symbol(SVGelement): def __init__(self,id=None,viewBox=None,**args): SVGelement.__init__(self,'symbol',**args) - if id<>None: + if id!=None: self.attributes['id']=id - if viewBox<>None: + if viewBox!=None: self.attributes['viewBox']=_viewboxlist(viewBox) class defs(SVGelement): @@ -770,14 +770,14 @@ class use(SVGelement): """ def __init__(self,link,x=None,y=None,width=None,height=None,**args): SVGelement.__init__(self,'use',{'xlink:href':link},**args) - if x<>None: + if x!=None: self.attributes['x']=x - if y<>None: + if y!=None: self.attributes['y']=y - if width<>None: + if width!=None: self.attributes['width']=width - if height<>None: + if height!=None: self.attributes['height']=height @@ -796,7 +796,7 @@ class view(SVGelement): a view can be used to create a view with different attributes""" def __init__(self,id=None,**args): SVGelement.__init__(self,'view',**args) - if id<>None: + if id!=None: self.attributes['id']=id class script(SVGelement): @@ -815,11 +815,11 @@ class animate(SVGelement): """ def __init__(self,attribute,fr=None,to=None,dur=None,**args): SVGelement.__init__(self,'animate',{'attributeName':attribute},**args) - if fr<>None: + if fr!=None: self.attributes['from']=fr - if to<>None: + if to!=None: self.attributes['to']=to - if dur<>None: + if dur!=None: self.attributes['dur']=dur class animateMotion(SVGelement): @@ -829,9 +829,9 @@ class animateMotion(SVGelement): """ def __init__(self,pathdata,dur,**args): SVGelement.__init__(self,'animateMotion',**args) - if pathdata<>None: + if pathdata!=None: self.attributes['path']=str(pathdata) - if dur<>None: + if dur!=None: self.attributes['dur']=dur class animateTransform(SVGelement): @@ -842,13 +842,13 @@ class animateTransform(SVGelement): def __init__(self,type=None,fr=None,to=None,dur=None,**args): SVGelement.__init__(self,'animateTransform',{'attributeName':'transform'},**args) #As far as I know the attributeName is always transform - if type<>None: + if type!=None: self.attributes['type']=type - if fr<>None: + if fr!=None: self.attributes['from']=fr - if to<>None: + if to!=None: self.attributes['to']=to - if dur<>None: + if dur!=None: self.attributes['dur']=dur class animateColor(SVGelement): """ac=animateColor(attribute,type,from,to,dur,**args) @@ -857,13 +857,13 @@ class animateColor(SVGelement): """ def __init__(self,attribute,type=None,fr=None,to=None,dur=None,**args): SVGelement.__init__(self,'animateColor',{'attributeName':attribute},**args) - if type<>None: + if type!=None: self.attributes['type']=type - if fr<>None: + if fr!=None: self.attributes['from']=fr - if to<>None: + if to!=None: self.attributes['to']=to - if dur<>None: + if dur!=None: self.attributes['dur']=dur class set(SVGelement): """st=set(attribute,to,during,**args) @@ -872,9 +872,9 @@ class set(SVGelement): """ def __init__(self,attribute,to=None,dur=None,**args): SVGelement.__init__(self,'set',{'attributeName':attribute},**args) - if to<>None: + if to!=None: self.attributes['to']=to - if dur<>None: + if dur!=None: self.attributes['dur']=dur @@ -896,11 +896,11 @@ class svg(SVGelement): """ def __init__(self,viewBox=None, width=None, height=None,**args): SVGelement.__init__(self,'svg',**args) - if viewBox<>None: + if viewBox!=None: self.attributes['viewBox']=_viewboxlist(viewBox) - if width<>None: + if width!=None: self.attributes['width']=width - if height<>None: + if height!=None: self.attributes['height']=height self.namespace="http://www.w3.org/2000/svg" -- cgit v1.2.3 From b0f87da9cbb0a8a462d38472264ed34af5dd8912 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 17 Aug 2020 21:47:49 +0300 Subject: Replace "raw_input" with "input" * scripts/maintenance/readProbeSetMean_v7.py: Use "input". See https://docs.python.org/3/whatsnew/3.0.html#builtins --- scripts/maintenance/readProbeSetMean_v7.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/maintenance/readProbeSetMean_v7.py b/scripts/maintenance/readProbeSetMean_v7.py index e7a4c826..fea26731 100755 --- a/scripts/maintenance/readProbeSetMean_v7.py +++ b/scripts/maintenance/readProbeSetMean_v7.py @@ -31,9 +31,9 @@ def translateAlias(str): dataStart = 1 -GeneChipId = int( raw_input("Enter GeneChipId:") ) -ProbeSetFreezeId = int( raw_input("Enter ProbeSetFreezeId:") ) -input_file_name = raw_input("Enter file name with suffix:") +GeneChipId = int( input("Enter GeneChipId:") ) +ProbeSetFreezeId = int( input("Enter ProbeSetFreezeId:") ) +input_file_name = input("Enter file name with suffix:") fp = open("%s" % input_file_name, 'rb') -- cgit v1.2.3 From 4e10f4bd8fb902810ee033abb8d509ab641308e1 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 18 Aug 2020 17:09:51 +0300 Subject: Apply pep8 * wqflask/utility/svg.py: Apply pep8 to fix indentation error when running `2to3-3.8 -w .`: ```` RefactoringTool: Can't parse ./wqflask/utility/svg.py: IndentationError: unindent does not match any outer indentation level (, line 403) ```` --- wqflask/utility/svg.py | 321 ++++++++++++++++++++++++++++--------------------- 1 file changed, 184 insertions(+), 137 deletions(-) diff --git a/wqflask/utility/svg.py b/wqflask/utility/svg.py index f737d2f2..c850feb8 100644 --- a/wqflask/utility/svg.py +++ b/wqflask/utility/svg.py @@ -25,54 +25,56 @@ # Last updated by GeneNetwork Core Team 2010/10/20 #!/usr/bin/env python -##Copyright (c) 2002, Fedor Baart & Hans de Wit (Stichting Farmaceutische Kengetallen) -##All rights reserved. +# Copyright (c) 2002, Fedor Baart & Hans de Wit (Stichting Farmaceutische Kengetallen) +# All rights reserved. ## -##Redistribution and use in source and binary forms, with or without modification, -##are permitted provided that the following conditions are met: +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: ## -##Redistributions of source code must retain the above copyright notice, this -##list of conditions and the following disclaimer. +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. ## -##Redistributions in binary form must reproduce the above copyright notice, -##this list of conditions and the following disclaimer in the documentation and/or -##other materials provided with the distribution. +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. ## -##Neither the name of the Stichting Farmaceutische Kengetallen nor the names of -##its contributors may be used to endorse or promote products derived from this -##software without specific prior written permission. +# Neither the name of the Stichting Farmaceutische Kengetallen nor the names of +# its contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. ## -##THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -##AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -##IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -##DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -##FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -##DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -##SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -##CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -##OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -##OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Thanks to Gerald Rosennfellner for his help and useful comments. -##Thanks to Gerald Rosennfellner for his help and useful comments. - -__doc__="""Use SVGdraw to generate your SVGdrawings. +import sys +import exceptions +__doc__ = """Use SVGdraw to generate your SVGdrawings. SVGdraw uses an object model drawing and a method toXML to create SVG graphics by using easy to use classes and methods usualy you start by creating a drawing eg d=drawing() - #then you create a SVG root element + # then you create a SVG root element s=svg() - #then you add some elements eg a circle and add it to the svg root element + # then you add some elements eg a circle and add it to the svg root element c=circle() - #you can supply attributes by using named arguments. + # you can supply attributes by using named arguments. c=circle(fill='red',stroke='blue') - #or by updating the attributes attribute: + # or by updating the attributes attribute: c.attributes['stroke-width']=1 s.addElement(c) - #then you add the svg root element to the drawing + # then you add the svg root element to the drawing d.setSVG(s) - #and finaly you xmlify the drawing + # and finaly you xmlify the drawing d.toXml() @@ -82,7 +84,7 @@ This module was created using the SVG specification of www.w3c.org and the O'Reilly (www.oreilly.com) python books as information sources. A svg viewer is available from www.adobe.com""" -__version__="1.0" +__version__ = "1.0" # there are two possibilities to generate svg: # via a dom implementation and directly using text strings @@ -93,33 +95,34 @@ __version__="1.0" # Note that PyXML is required for using the dom implementation. # It is also possible to use the standard minidom. But I didn't try that one. # Anyway the text based approach is about 60 times faster than using the full dom implementation. -use_dom_implementation=0 +use_dom_implementation = 0 -import exceptions -if use_dom_implementation!=0: +if use_dom_implementation != 0: try: from xml.dom import implementation from xml.dom.ext import PrettyPrint except: - raise exceptions.ImportError("PyXML is required for using the dom implementation") -#The implementation is used for the creating the XML document. -#The prettyprint module is used for converting the xml document object to a xml file + raise exceptions.ImportError( + "PyXML is required for using the dom implementation") +# The implementation is used for the creating the XML document. +# The prettyprint module is used for converting the xml document object to a xml file + +assert sys.version_info[0] >= 2 +if sys.version_info[1] < 2: + True = 1 + False = 0 + file = open + +sys.setrecursionlimit = 50 +# The recursion limit is set conservative so mistakes like s=svg() s.addElement(s) +# won't eat up too much processor time. + +# the following code is pasted form xml.sax.saxutils +# it makes it possible to run the code without the xml sax package installed +# To make it possible to have in your text elements, it is necessary to escape the texts + -import sys -assert sys.version_info[0]>=2 -if sys.version_info[1]<2: - True=1 - False=0 - file=open - -sys.setrecursionlimit=50 -#The recursion limit is set conservative so mistakes like s=svg() s.addElement(s) -#won't eat up too much processor time. - -#the following code is pasted form xml.sax.saxutils -#it makes it possible to run the code without the xml sax package installed -#To make it possible to have in your text elements, it is necessary to escape the texts def _escape(data, entities={}): """Escape &, <, and > in a string of data. @@ -127,13 +130,14 @@ def _escape(data, entities={}): the optional entities parameter. The keys and values must all be strings; each key will be replaced with its corresponding value. """ - #data = data.replace("&", "&") + # data = data.replace("&", "&") data = data.replace("<", "<") data = data.replace(">", ">") for chars, entity in entities.items(): data = data.replace(chars, entity) return data + def _quoteattr(data, entities={}): """Escape and quote an attribute value. @@ -156,96 +160,121 @@ def _quoteattr(data, entities={}): return data - def _xypointlist(a): """formats a list of xy pairs""" - s='' - for e in a: #this could be done more elegant - s+=str(e)[1:-1] +' ' + s = '' + for e in a: # this could be done more elegant + s += str(e)[1:-1] + ' ' return s + def _viewboxlist(a): """formats a tuple""" - s='' + s = '' for e in a: - s+=str(e)+' ' + s += str(e)+' ' return s + def _pointlist(a): """formats a list of numbers""" return str(a)[1:-1] + class pathdata: """class used to create a pathdata object which can be used for a path. although most methods are pretty straightforward it might be useful to look at the SVG specification.""" - #I didn't test the methods below. - def __init__(self,x=None,y=None): - self.path=[] + # I didn't test the methods below. + + def __init__(self, x=None, y=None): + self.path = [] if x is not None and y is not None: self.path.append('M '+str(x)+' '+str(y)) + def closepath(self): """ends the path""" self.path.append('z') - def move(self,x,y): + + def move(self, x, y): """move to absolute""" self.path.append('M '+str(x)+' '+str(y)) - def relmove(self,x,y): + + def relmove(self, x, y): """move to relative""" self.path.append('m '+str(x)+' '+str(y)) - def line(self,x,y): + + def line(self, x, y): """line to absolute""" self.path.append('L '+str(x)+' '+str(y)) - def relline(self,x,y): + + def relline(self, x, y): """line to relative""" self.path.append('l '+str(x)+' '+str(y)) - def hline(self,x): + + def hline(self, x): """horizontal line to absolute""" self.path.append('H'+str(x)) - def relhline(self,x): + + def relhline(self, x): """horizontal line to relative""" self.path.append('h'+str(x)) - def vline(self,y): + + def vline(self, y): """verical line to absolute""" self.path.append('V'+str(y)) - def relvline(self,y): + + def relvline(self, y): """vertical line to relative""" self.path.append('v'+str(y)) - def bezier(self,x1,y1,x2,y2,x,y): + + def bezier(self, x1, y1, x2, y2, x, y): """bezier with xy1 and xy2 to xy absolut""" - self.path.append('C'+str(x1)+','+str(y1)+' '+str(x2)+','+str(y2)+' '+str(x)+','+str(y)) - def relbezier(self,x1,y1,x2,y2,x,y): + self.path.append('C'+str(x1)+','+str(y1)+' '+str(x2) + + ','+str(y2)+' '+str(x)+','+str(y)) + + def relbezier(self, x1, y1, x2, y2, x, y): """bezier with xy1 and xy2 to xy relative""" - self.path.append('c'+str(x1)+','+str(y1)+' '+str(x2)+','+str(y2)+' '+str(x)+','+str(y)) - def smbezier(self,x2,y2,x,y): + self.path.append('c'+str(x1)+','+str(y1)+' '+str(x2) + + ','+str(y2)+' '+str(x)+','+str(y)) + + def smbezier(self, x2, y2, x, y): """smooth bezier with xy2 to xy absolut""" self.path.append('S'+str(x2)+','+str(y2)+' '+str(x)+','+str(y)) - def relsmbezier(self,x2,y2,x,y): + + def relsmbezier(self, x2, y2, x, y): """smooth bezier with xy2 to xy relative""" self.path.append('s'+str(x2)+','+str(y2)+' '+str(x)+','+str(y)) - def qbezier(self,x1,y1,x,y): + + def qbezier(self, x1, y1, x, y): """quadratic bezier with xy1 to xy absolut""" self.path.append('Q'+str(x1)+','+str(y1)+' '+str(x)+','+str(y)) - def relqbezier(self,x1,y1,x,y): + + def relqbezier(self, x1, y1, x, y): """quadratic bezier with xy1 to xy relative""" self.path.append('q'+str(x1)+','+str(y1)+' '+str(x)+','+str(y)) - def smqbezier(self,x,y): + + def smqbezier(self, x, y): """smooth quadratic bezier to xy absolut""" self.path.append('T'+str(x)+','+str(y)) - def relsmqbezier(self,x,y): + + def relsmqbezier(self, x, y): """smooth quadratic bezier to xy relative""" self.path.append('t'+str(x)+','+str(y)) - def ellarc(self,rx,ry,xrot,laf,sf,x,y): + + def ellarc(self, rx, ry, xrot, laf, sf, x, y): """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag to xy absolut""" - self.path.append('A'+str(rx)+','+str(ry)+' '+str(xrot)+' '+str(laf)+' '+str(sf)+' '+str(x)+' '+str(y)) - def relellarc(self,rx,ry,xrot,laf,sf,x,y): + self.path.append('A'+str(rx)+','+str(ry)+' '+str(xrot) + + ' '+str(laf)+' '+str(sf)+' '+str(x)+' '+str(y)) + + def relellarc(self, rx, ry, xrot, laf, sf, x, y): """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag to xy relative""" - self.path.append('a'+str(rx)+','+str(ry)+' '+str(xrot)+' '+str(laf)+' '+str(sf)+' '+str(x)+' '+str(y)) + self.path.append('a'+str(rx)+','+str(ry)+' '+str(xrot) + + ' '+str(laf)+' '+str(sf)+' '+str(x)+' '+str(y)) + def __repr__(self): return ' '.join(self.path) - - class SVGelement: """SVGelement(type,attributes,elements,text,namespace,**args) Creates a arbitrary svg element and is intended to be subclassed not used on its own. @@ -256,52 +285,56 @@ class SVGelement: namespace. Note the elements==None, if elements = None:self.elements=[] construction. This is done because if you default to elements=[] every object has a reference to the same empty list.""" - def __init__(self,type='',attributes=None,elements=None,text='',namespace='',cdata=None, **args): - self.type=type - if attributes==None: - self.attributes={} + + def __init__(self, type='', attributes=None, elements=None, text='', namespace='', cdata=None, **args): + self.type = type + if attributes == None: + self.attributes = {} else: - self.attributes=attributes - if elements==None: - self.elements=[] + self.attributes = attributes + if elements == None: + self.elements = [] else: - self.elements=elements - self.text=text - self.namespace=namespace - self.cdata=cdata + self.elements = elements + self.text = text + self.namespace = namespace + self.cdata = cdata for arg in args.keys(): arg2 = arg.replace("__", ":") arg2 = arg2.replace("_", "-") - self.attributes[arg2]=args[arg] - def addElement(self,SVGelement): + self.attributes[arg2] = args[arg] + + def addElement(self, SVGelement): """adds an element to a SVGelement SVGelement.addElement(SVGelement) """ self.elements.append(SVGelement) - def toXml(self,level,f): + def toXml(self, level, f): f.write('\t'*level) f.write('<'+self.type) for attkey in self.attributes.keys(): - f.write(' '+_escape(str(attkey))+'='+_quoteattr(str(self.attributes[attkey]))) + f.write(' '+_escape(str(attkey))+'=' + + _quoteattr(str(self.attributes[attkey]))) if self.namespace: - f.write(' xmlns="'+ _escape(str(self.namespace))+'" xmlns:xlink="http://www.w3.org/1999/xlink"') + f.write(' xmlns="' + _escape(str(self.namespace)) + + '" xmlns:xlink="http://www.w3.org/1999/xlink"') if self.elements or self.text or self.cdata: f.write('>') if self.elements: f.write('\n') for element in self.elements: - element.toXml(level+1,f) + element.toXml(level+1, f) if self.cdata: f.write('\n'+'\t'*(level+1)+'\n') if self.text: - if type(self.text)==type(''): #If the text is only text + if type(self.text) == type(''): # If the text is only text f.write(_escape(str(self.text))) - else: #If the text is a spannedtext class + else: # If the text is a spannedtext class f.write(str(self.text)) if self.elements: f.write('\t'*level+'\n') @@ -312,6 +345,7 @@ class SVGelement: else: f.write('/>\n') + class tspan(SVGelement): """ts=tspan(text='',**args) @@ -323,19 +357,22 @@ class tspan(SVGelement): st.addtspan(ts) t=text(3,5,st) """ - def __init__(self,text=None,**args): - SVGelement.__init__(self,'tspan',**args) - if self.text<>None: - self.text=text + + def __init__(self, text=None, **args): + SVGelement.__init__(self, 'tspan', **args) + if self.text <> None: + self.text = text + def __repr__(self): - s=" --- scripts/maintenance/QTL_Reaper_v6.py | 2 +- scripts/maintenance/readProbeSetMean_v7.py | 16 ++++++++-------- scripts/maintenance/readProbeSetSE_v7.py | 16 ++++++++-------- test/requests/link_checker.py | 3 +-- wqflask/utility/webqtlUtil.py | 2 +- wqflask/wqflask/api/router.py | 2 +- wqflask/wqflask/correlation_matrix/show_corr_matrix.py | 4 ++-- wqflask/wqflask/export_traits.py | 2 +- wqflask/wqflask/interval_analyst/GeneUtil.py | 2 +- .../wqflask/marker_regression/display_mapping_results.py | 10 +++++----- wqflask/wqflask/marker_regression/plink_mapping.py | 4 ++-- wqflask/wqflask/pbkdf2.py | 2 +- wqflask/wqflask/snp_browser/snp_browser.py | 6 +++--- 13 files changed, 35 insertions(+), 36 deletions(-) diff --git a/scripts/maintenance/QTL_Reaper_v6.py b/scripts/maintenance/QTL_Reaper_v6.py index 7fb56eca..2fbeb53b 100755 --- a/scripts/maintenance/QTL_Reaper_v6.py +++ b/scripts/maintenance/QTL_Reaper_v6.py @@ -23,7 +23,7 @@ for item in results: ProbeSetFreezeIds=sys.argv[1:] if ProbeSetFreezeIds: #####convert the Ids to integer - ProbeSetFreezeIds=map(int, ProbeSetFreezeIds) + ProbeSetFreezeIds=list(map(int, ProbeSetFreezeIds)) else: #####get all of the dataset that need be updated diff --git a/scripts/maintenance/readProbeSetMean_v7.py b/scripts/maintenance/readProbeSetMean_v7.py index fea26731..97767715 100755 --- a/scripts/maintenance/readProbeSetMean_v7.py +++ b/scripts/maintenance/readProbeSetMean_v7.py @@ -61,14 +61,14 @@ GeneList = [] isCont = 1 header = fp.readline() header = string.split(string.strip(header),'\t') -header = map(string.strip, header) +header = list(map(string.strip, header)) nfield = len(header) line = fp.readline() kj=0 while line: line2 = string.split(string.strip(line),'\t') - line2 = map(string.strip, line2) + line2 = list(map(string.strip, line2)) if len(line2) != nfield: print(("Error : " + line)) isCont = 0 @@ -80,7 +80,7 @@ while line: if kj%100000 == 0: print(('checked ',kj,' lines')) -GeneList = map(string.lower, GeneList) +GeneList = list(map(string.lower, GeneList)) GeneList.sort() if isCont==0: @@ -100,8 +100,8 @@ isCont = 1 fp.seek(0) header = fp.readline() header = string.split(string.strip(header),'\t') -header = map(string.strip, header) -header = map(translateAlias, header) +header = list(map(string.strip, header)) +header = list(map(translateAlias, header)) header = header[dataStart:] Ids = [] for item in header: @@ -128,7 +128,7 @@ print('Check if each ProbeSet exist in database') line = fp.readline() line = fp.readline() line2 = string.split(string.strip(line),'\t') -line2 = map(string.strip, line2) +line2 = list(map(string.strip, line2)) PId = line2[0] db.execute('select Id from ProbeSet where Name="%s" and ChipId=%d' % (PId, GeneChipId) ) @@ -148,7 +148,7 @@ for item in results: print(Names) -Names = map(string.lower, Names) +Names = list(map(string.lower, Names)) Names.sort() # -- Fixed the lower case problem of ProbeSets affx-mur_b2_at doesn't exist --# @@ -223,7 +223,7 @@ values1 = [] values2 = [] while line: line2 = string.split(string.strip(line),'\t') - line2 = map(string.strip, line2) + line2 = list(map(string.strip, line2)) PId = line2[0] recordId = NameIds[PId] diff --git a/scripts/maintenance/readProbeSetSE_v7.py b/scripts/maintenance/readProbeSetSE_v7.py index 79ed455f..7b2fee87 100755 --- a/scripts/maintenance/readProbeSetSE_v7.py +++ b/scripts/maintenance/readProbeSetSE_v7.py @@ -72,14 +72,14 @@ GeneList = [] isCont = 1 header = fp.readline() header = string.split(string.strip(header), '\t') -header = map(string.strip, header) +header = list(map(string.strip, header)) nfield = len(header) line = fp.readline() kj = 0 while line: line2 = string.split(string.strip(line), '\t') - line2 = map(string.strip, line2) + line2 = list(map(string.strip, line2)) if len(line2) != nfield: isCont = 0 print(("Error : " + line)) @@ -91,7 +91,7 @@ while line: if kj % 100000 == 0: print(('checked ', kj, ' lines')) -GeneList = map(string.lower, GeneList) +GeneList = list(map(string.lower, GeneList)) GeneList.sort() if isCont == 0: @@ -111,8 +111,8 @@ isCont = 1 fp.seek(0) header = fp.readline() header = string.split(string.strip(header), '\t') -header = map(string.strip, header) -header = map(translateAlias, header) +header = list(map(string.strip, header)) +header = list(map(translateAlias, header)) header = header[dataStart:] Ids = [] for item in header: @@ -139,7 +139,7 @@ print('Check if each ProbeSet exist in database') line = fp.readline() line = fp.readline() line2 = string.split(string.strip(line), '\t') -line2 = map(string.strip, line2) +line2 = list(map(string.strip, line2)) PId = line2[0] db.execute('select Id from ProbeSet where Name="%s" and ChipId=%d' % @@ -158,7 +158,7 @@ results = db.fetchall() Names = [] for item in results: Names.append(item[0]) - Names = map(string.lower, Names) + Names = list(map(string.lower, Names)) Names.sort() # -- Fixed the lower case problem of ProbeSets affx-mur_b2_at doesn't exist --# ##---- compare genelist with names ----## @@ -220,7 +220,7 @@ line = fp.readline() kj = 0 while line: line2 = string.split(string.strip(line), '\t') - line2 = map(string.strip, line2) + line2 = list(map(string.strip, line2)) CellId = line2[0] if not ProbeNameId.has_key(CellId): diff --git a/test/requests/link_checker.py b/test/requests/link_checker.py index 715f330c..df4d32d8 100644 --- a/test/requests/link_checker.py +++ b/test/requests/link_checker.py @@ -27,8 +27,7 @@ def get_links(doc): lambda x: not ( is_root_link(x) or is_mailto_link(x)) - , map(lambda y: y.get("href") - , doc.cssselect("a"))) + , [y.get("href") for y in doc.cssselect("a")]) def verify_link(link): if link[0] == "#": diff --git a/wqflask/utility/webqtlUtil.py b/wqflask/utility/webqtlUtil.py index 53661ae4..79991149 100644 --- a/wqflask/utility/webqtlUtil.py +++ b/wqflask/utility/webqtlUtil.py @@ -107,7 +107,7 @@ def hasAccessToConfidentialPhenotypeTrait(privilege, userName, authorized_users) if webqtlConfig.USERDICT[privilege] > webqtlConfig.USERDICT['user']: access_to_confidential_phenotype_trait = 1 else: - AuthorizedUsersList=map(string.strip, string.split(authorized_users, ',')) + AuthorizedUsersList=list(map(string.strip, string.split(authorized_users, ','))) if AuthorizedUsersList.__contains__(userName): access_to_confidential_phenotype_trait = 1 return access_to_confidential_phenotype_trait \ No newline at end of file diff --git a/wqflask/wqflask/api/router.py b/wqflask/wqflask/api/router.py index 6324cabe..3fa1d5ba 100644 --- a/wqflask/wqflask/api/router.py +++ b/wqflask/wqflask/api/router.py @@ -517,7 +517,7 @@ def all_sample_data(dataset_name, file_format = "csv"): line_list.append("x") results_list.append(line_list) - results_list = map(list, zip(*results_list)) + results_list = list(map(list, zip(*results_list))) si = StringIO.StringIO() csv_writer = csv.writer(si) diff --git a/wqflask/wqflask/correlation_matrix/show_corr_matrix.py b/wqflask/wqflask/correlation_matrix/show_corr_matrix.py index 0ac94139..a912344f 100644 --- a/wqflask/wqflask/correlation_matrix/show_corr_matrix.py +++ b/wqflask/wqflask/correlation_matrix/show_corr_matrix.py @@ -278,7 +278,7 @@ def zScore(trait_data_array): stdev = math.sqrt(var/(N-1)) if stdev == 0: stdev = 1e-100 - data2 = map(lambda x:(x-mean)/stdev,data) + data2 = [(x-mean)/stdev for x in data] trait_data_array[i] = data2 i += 1 return trait_data_array @@ -299,7 +299,7 @@ def sortEigenVectors(vector): A.append(item[0]) B.append(item[1]) sum = reduce(lambda x,y: x+y, A, 0.0) - A = map(lambda x:x*100.0/sum, A) + A = [x*100.0/sum for x in A] return [A, B] except: return [] \ No newline at end of file diff --git a/wqflask/wqflask/export_traits.py b/wqflask/wqflask/export_traits.py index 3272c03d..6646cc36 100644 --- a/wqflask/wqflask/export_traits.py +++ b/wqflask/wqflask/export_traits.py @@ -122,7 +122,7 @@ def export_search_results_csv(targs): csv_rows.append(row_contents) - csv_rows = map(list, itertools.izip_longest(*[row for row in csv_rows])) + csv_rows = list(map(list, itertools.izip_longest(*[row for row in csv_rows]))) writer.writerows(csv_rows) csv_data = buff.getvalue() buff.close() diff --git a/wqflask/wqflask/interval_analyst/GeneUtil.py b/wqflask/wqflask/interval_analyst/GeneUtil.py index 2c60dd70..273168a8 100644 --- a/wqflask/wqflask/interval_analyst/GeneUtil.py +++ b/wqflask/wqflask/interval_analyst/GeneUtil.py @@ -24,7 +24,7 @@ def loadGenes(chrName, diffCol, startMb, endMb, species='mouse'): ##List current Species and other Species speciesId = speciesDict[species] - otherSpecies = map(lambda X: [X, speciesDict[X]], speciesDict.keys()) + otherSpecies = [[X, speciesDict[X]] for X in speciesDict.keys()] otherSpecies.remove([species, speciesId]) results = g.db.execute(""" diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index bda899fb..7b6e70d2 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -389,9 +389,9 @@ class DisplayMappingResults(object): Chr_Length.Name in (%s) Order by Chr_Length.OrderId - """ % (self.dataset.group.name, string.join(map(lambda X: "'%s'" % X[0], self.ChrList[1:]), ", "))) + """ % (self.dataset.group.name, string.join(["'%s'" % X[0] for X in self.ChrList[1:]], ", "))) - self.ChrLengthMbList = map(lambda x: x[0]/1000000.0, self.ChrLengthMbList) + self.ChrLengthMbList = [x[0]/1000000.0 for x in self.ChrLengthMbList] self.ChrLengthMbSum = reduce(lambda x, y:x+y, self.ChrLengthMbList, 0.0) if self.ChrLengthMbList: self.MbGraphInterval = self.ChrLengthMbSum/(len(self.ChrLengthMbList)*12) #Empirical Mb interval @@ -1147,8 +1147,8 @@ class DisplayMappingResults(object): tenPercentLength = geneLength*0.0001 SNPdensity = theGO["snpCount"]/geneLength - exonStarts = map(float, theGO['exonStarts'].split(",")[:-1]) - exonEnds = map(float, theGO['exonEnds'].split(",")[:-1]) + exonStarts = list(map(float, theGO['exonStarts'].split(",")[:-1])) + exonEnds = list(map(float, theGO['exonEnds'].split(",")[:-1])) cdsStart = theGO['cdsStart'] cdsEnd = theGO['cdsEnd'] accession = theGO['NM_ID'] @@ -2145,7 +2145,7 @@ class DisplayMappingResults(object): lrsEdgeWidth = 1 else: if self.additiveChecked: - additiveMax = max(map(lambda X : abs(X['additive']), self.qtlresults)) + additiveMax = max([abs(X['additive']) for X in self.qtlresults]) lrsEdgeWidth = 3 if zoom == 2: diff --git a/wqflask/wqflask/marker_regression/plink_mapping.py b/wqflask/wqflask/marker_regression/plink_mapping.py index 2f327faf..9571015e 100644 --- a/wqflask/wqflask/marker_regression/plink_mapping.py +++ b/wqflask/wqflask/marker_regression/plink_mapping.py @@ -84,7 +84,7 @@ def get_samples_from_ped_file(dataset): while line: lineList = string.split(string.strip(line), '\t') - lineList = map(string.strip, lineList) + lineList = list(map(string.strip, lineList)) sample_name = lineList[0] sample_list.append(sample_name) @@ -157,6 +157,6 @@ def parse_plink_output(output_filename, species): def build_line_list(line=None): line_list = string.split(string.strip(line),' ')# irregular number of whitespaces between columns line_list = [item for item in line_list if item <>''] - line_list = map(string.strip, line_list) + line_list = list(map(string.strip, line_list)) return line_list \ No newline at end of file diff --git a/wqflask/wqflask/pbkdf2.py b/wqflask/wqflask/pbkdf2.py index 811c83b0..731c8843 100644 --- a/wqflask/wqflask/pbkdf2.py +++ b/wqflask/wqflask/pbkdf2.py @@ -66,7 +66,7 @@ def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None): def _pseudorandom(x, mac=mac): h = mac.copy() h.update(x) - return map(ord, h.digest()) + return list(map(ord, h.digest())) buf = [] for block in xrange(1, -(-keylen // mac.digest_size) + 1): rv = u = _pseudorandom(salt + _pack_int(block)) diff --git a/wqflask/wqflask/snp_browser/snp_browser.py b/wqflask/wqflask/snp_browser/snp_browser.py index 1d28d76a..b18bfc62 100644 --- a/wqflask/wqflask/snp_browser/snp_browser.py +++ b/wqflask/wqflask/snp_browser/snp_browser.py @@ -459,7 +459,7 @@ class SnpBrowser(object): function_list = [] if function_details: function_list = string.split(string.strip(function_details), ",") - function_list = map(string.strip, function_list) + function_list = list(map(string.strip, function_list)) function_list[0] = function_list[0].title() function_details = ", ".join(item for item in function_list) function_details = function_details.replace("_", " ") @@ -725,11 +725,11 @@ def get_effect_details_by_category(effect_name = None, effect_value = None): codon_effect_group_list = ['Start Lost', 'Stop Gained', 'Stop Lost', 'Nonsynonymous', 'Synonymous'] effect_detail_list = string.split(string.strip(effect_value), '|') - effect_detail_list = map(string.strip, effect_detail_list) + effect_detail_list = list(map(string.strip, effect_detail_list)) for index, item in enumerate(effect_detail_list): item_list = string.split(string.strip(item), ',') - item_list = map(string.strip, item_list) + item_list = list(map(string.strip, item_list)) gene_id = item_list[0] gene_name = item_list[1] -- cgit v1.2.3 From bafbb5b7a4b7db2ca230f292eb45be7e67985259 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 02:05:05 +0300 Subject: Remove erroneous `if .. else` branch * wqflask/utility/svg.py [roct, ellipse, SVGelement]: Raise only a single value error if either height or width is not defined. Fixes parsing error when running `2to3-3.8 -f apply -w .` --- wqflask/utility/svg.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/wqflask/utility/svg.py b/wqflask/utility/svg.py index c850feb8..d66c954e 100644 --- a/wqflask/utility/svg.py +++ b/wqflask/utility/svg.py @@ -445,12 +445,8 @@ class rect(SVGelement): def __init__(self, x=None, y=None, width=None, height=None, fill=None, stroke=None, stroke_width=None, **args): if width == None or height == None: - raise ValueError, 'height is required' - raise ValueError, 'width is required' - if width!=None: - if height!=None: - else: - raise ValueError, 'both height and width are required' + raise ValueError, 'both height and width are required' + SVGelement.__init__(self,'rect',{'width':width,'height':height},**args) if x!=None: self.attributes['x']=x @@ -470,12 +466,8 @@ class ellipse(SVGelement): """ def __init__(self,cx=None,cy=None,rx=None,ry=None,fill=None,stroke=None,stroke_width=None,**args): if rx==None or ry== None: - raise ValueError, 'rx is required' - raise ValueError, 'ry is required' - if rx!=None: - if ry!=None: - else: - raise ValueError, 'both rx and ry are required' + raise ValueError, 'both rx and ry are required' + SVGelement.__init__(self,'ellipse',{'rx':rx,'ry':ry},**args) if cx!=None: self.attributes['cx']=cx @@ -722,12 +714,7 @@ class image(SVGelement): """ def __init__(self,url,x=None,y=None,width=None,height=None,**args): if width==None or height==None: - raise ValueError, 'height is required' - raise ValueError, 'width is required' - if width!=None: - if height!=None: - else: - raise ValueError, 'both height and width are required' + raise ValueError, 'both height and width are required' SVGelement.__init__(self,'image',{'xlink:href':url,'width':width,'height':height},**args) if x!=None: self.attributes['x']=x -- cgit v1.2.3 From ba123e1e0fe693f9778993c3f8e5a70a28658a4c Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 02:31:31 +0300 Subject: Fix dictionary iteration methods Run `2to3-3.8 -f dict -w .` See: and --- scripts/maintenance/load_genotypes.py | 4 +-- wqflask/base/GeneralObject.py | 8 ++--- wqflask/base/trait.py | 4 +-- wqflask/maintenance/gen_select_dataset.py | 6 ++-- .../maintenance/generate_probesetfreeze_file.py | 2 +- wqflask/utility/__init__.py | 4 +-- wqflask/utility/benchmark.py | 4 +-- wqflask/utility/gen_geno_ob.py | 2 +- wqflask/utility/helper_functions.py | 2 +- wqflask/utility/svg.py | 14 ++++---- wqflask/utility/temp_data.py | 2 +- wqflask/utility/tools.py | 2 +- wqflask/wqflask/api/correlation.py | 16 ++++----- wqflask/wqflask/api/gen_menu.py | 6 ++-- wqflask/wqflask/correlation/corr_scatter_plot.py | 6 ++-- wqflask/wqflask/correlation/show_corr_results.py | 30 ++++++++--------- wqflask/wqflask/ctl/ctl_analysis.py | 2 +- wqflask/wqflask/export_traits.py | 4 +-- wqflask/wqflask/heatmap/heatmap.py | 4 +-- wqflask/wqflask/interval_analyst/GeneUtil.py | 2 +- .../marker_regression/display_mapping_results.py | 38 +++++++++++----------- wqflask/wqflask/marker_regression/run_mapping.py | 24 +++++++------- wqflask/wqflask/resource_manager.py | 2 +- wqflask/wqflask/show_trait/export_trait_data.py | 2 +- wqflask/wqflask/show_trait/show_trait.py | 8 ++--- wqflask/wqflask/views.py | 6 ++-- 26 files changed, 102 insertions(+), 102 deletions(-) diff --git a/scripts/maintenance/load_genotypes.py b/scripts/maintenance/load_genotypes.py index c235a31f..51278d48 100755 --- a/scripts/maintenance/load_genotypes.py +++ b/scripts/maintenance/load_genotypes.py @@ -19,7 +19,7 @@ def fetch_parameters(config): config_dic['dataid'] = datastructure.get_nextdataid_genotype() config_dic['genofile'] = config.get('config', 'genofile') print("config dictionary:") - for k, v in config_dic.items(): + for k, v in list(config_dic.items()): print(("\t%s: %s" % (k, v))) return config_dic @@ -42,7 +42,7 @@ def parse_genofile(config, config_dic): if line.lower().startswith("chr"): # print("geno file meta dictionary:") - for k, v in meta_dic.items(): + for k, v in list(meta_dic.items()): print(("\t%s: %s" % (k, v))) # print(("geno file head:\n\t%s" % line)) diff --git a/wqflask/base/GeneralObject.py b/wqflask/base/GeneralObject.py index 0fccaab3..707569db 100644 --- a/wqflask/base/GeneralObject.py +++ b/wqflask/base/GeneralObject.py @@ -33,7 +33,7 @@ class GeneralObject: def __init__(self, *args, **kw): self.contents = list(args) - for name, value in kw.items(): + for name, value in list(kw.items()): setattr(self, name, value) def __setitem__(self, key, value): @@ -50,16 +50,16 @@ class GeneralObject: def __str__(self): s = '' - for key in self.__dict__.keys(): + for key in list(self.__dict__.keys()): if key != 'contents': s += '%s = %s\n' % (key, self.__dict__[key]) return s def __repr__(self): s = '' - for key in self.__dict__.keys(): + for key in list(self.__dict__.keys()): s += '%s = %s\n' % (key, self.__dict__[key]) return s def __cmp__(self, other): - return len(self.__dict__.keys()).__cmp__(len(other.__dict__.keys())) + return len(list(self.__dict__.keys())).__cmp__(len(list(other.__dict__.keys()))) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index 7666348e..e82df226 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -118,7 +118,7 @@ class GeneralTrait(object): vals = [] the_vars = [] sample_aliases = [] - for sample_name, sample_data in self.data.items(): + for sample_name, sample_data in list(self.data.items()): if sample_data.value != None: if not include_variance or sample_data.variance != None: samples.append(sample_name) @@ -260,7 +260,7 @@ def get_sample_data(): trait_dict['pubmed_link'] = trait_ob.pubmed_link trait_dict['pubmed_text'] = trait_ob.pubmed_text - return json.dumps([trait_dict, {key: value.value for key, value in trait_ob.data.iteritems() }]) + return json.dumps([trait_dict, {key: value.value for key, value in list(trait_ob.data.items()) }]) else: return None diff --git a/wqflask/maintenance/gen_select_dataset.py b/wqflask/maintenance/gen_select_dataset.py index 647e58a2..78217587 100644 --- a/wqflask/maintenance/gen_select_dataset.py +++ b/wqflask/maintenance/gen_select_dataset.py @@ -108,7 +108,7 @@ def get_types(groups): """Build types list""" types = {} #print("Groups: ", pf(groups)) - for species, group_dict in groups.iteritems(): + for species, group_dict in list(groups.items()): types[species] = {} for group_name, _group_full_name in group_dict: # make group an alias to shorten the code @@ -195,9 +195,9 @@ def build_types(species, group): def get_datasets(types): """Build datasets list""" datasets = {} - for species, group_dict in types.iteritems(): + for species, group_dict in list(types.items()): datasets[species] = {} - for group, type_list in group_dict.iteritems(): + for group, type_list in list(group_dict.items()): datasets[species][group] = {} for type_name in type_list: these_datasets = build_datasets(species, group, type_name[0]) diff --git a/wqflask/maintenance/generate_probesetfreeze_file.py b/wqflask/maintenance/generate_probesetfreeze_file.py index b7b2dc8e..4231cc7c 100644 --- a/wqflask/maintenance/generate_probesetfreeze_file.py +++ b/wqflask/maintenance/generate_probesetfreeze_file.py @@ -82,7 +82,7 @@ def get_probeset_vals(cursor, dataset_name): def trim_strains(strains, probeset_vals): trimmed_strains = [] #print("probeset_vals is:", pf(probeset_vals)) - first_probeset = list(probeset_vals.itervalues())[0] + first_probeset = list(probeset_vals.values())[0] print("\n**** first_probeset is:", pf(first_probeset)) for strain in strains: print("\n**** strain is:", pf(strain)) diff --git a/wqflask/utility/__init__.py b/wqflask/utility/__init__.py index d9856eed..204ff59a 100644 --- a/wqflask/utility/__init__.py +++ b/wqflask/utility/__init__.py @@ -19,7 +19,7 @@ class Struct(object): ''' def __init__(self, obj): - for k, v in obj.iteritems(): + for k, v in list(obj.items()): if isinstance(v, dict): setattr(self, k, Struct(v)) else: @@ -30,6 +30,6 @@ class Struct(object): def __repr__(self): return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for - (k, v) in self.__dict__.iteritems())) + (k, v) in list(self.__dict__.items()))) diff --git a/wqflask/utility/benchmark.py b/wqflask/utility/benchmark.py index 8f1c916b..221e5151 100644 --- a/wqflask/utility/benchmark.py +++ b/wqflask/utility/benchmark.py @@ -38,9 +38,9 @@ class Bench(object): @classmethod def report(cls): - total_time = sum((time_taken for time_taken in cls.entries.itervalues())) + total_time = sum((time_taken for time_taken in list(cls.entries.values()))) print("\nTiming report\n") - for name, time_taken in cls.entries.iteritems(): + for name, time_taken in list(cls.entries.items()): percent = int(round((time_taken/total_time) * 100)) print("[{}%] {}: {}".format(percent, name, time_taken)) print() diff --git a/wqflask/utility/gen_geno_ob.py b/wqflask/utility/gen_geno_ob.py index 23b0b650..ae42f834 100644 --- a/wqflask/utility/gen_geno_ob.py +++ b/wqflask/utility/gen_geno_ob.py @@ -175,7 +175,7 @@ class Locus(object): start_pos = 3 for allele in marker_row[start_pos:]: - if allele in geno_table.keys(): + if allele in list(geno_table.keys()): self.genotype.append(geno_table[allele]) else: #ZS: Some genotype appears that isn't specified in the metadata, make it unknown self.genotype.append("U") \ No newline at end of file diff --git a/wqflask/utility/helper_functions.py b/wqflask/utility/helper_functions.py index 9ce809b6..9a4a235a 100644 --- a/wqflask/utility/helper_functions.py +++ b/wqflask/utility/helper_functions.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__ ) def get_species_dataset_trait(self, start_vars): #assert type(read_genotype) == type(bool()), "Expecting boolean value for read_genotype" - if "temp_trait" in start_vars.keys(): + if "temp_trait" in list(start_vars.keys()): if start_vars['temp_trait'] == "True": self.dataset = data_set.create_dataset(dataset_name = "Temp", dataset_type = "Temp", group_name = start_vars['group']) else: diff --git a/wqflask/utility/svg.py b/wqflask/utility/svg.py index d66c954e..c6a5c260 100644 --- a/wqflask/utility/svg.py +++ b/wqflask/utility/svg.py @@ -133,7 +133,7 @@ def _escape(data, entities={}): # data = data.replace("&", "&") data = data.replace("<", "<") data = data.replace(">", ">") - for chars, entity in entities.items(): + for chars, entity in list(entities.items()): data = data.replace(chars, entity) return data @@ -299,7 +299,7 @@ class SVGelement: self.text = text self.namespace = namespace self.cdata = cdata - for arg in args.keys(): + for arg in list(args.keys()): arg2 = arg.replace("__", ":") arg2 = arg2.replace("_", "-") self.attributes[arg2] = args[arg] @@ -314,7 +314,7 @@ class SVGelement: def toXml(self, level, f): f.write('\t'*level) f.write('<'+self.type) - for attkey in self.attributes.keys(): + for attkey in list(self.attributes.keys()): f.write(' '+_escape(str(attkey))+'=' + _quoteattr(str(self.attributes[attkey]))) if self.namespace: @@ -365,7 +365,7 @@ class tspan(SVGelement): def __repr__(self): s = "\n" % (item, self.entity[item])) xml.write("]") xml.write(">\n") @@ -1015,7 +1015,7 @@ class drawing: if element.text: textnode=root.createTextNode(element.text) e.appendChild(textnode) - for attribute in element.attributes.keys(): #in element.attributes is supported from python 2.2 + for attribute in list(element.attributes.keys()): #in element.attributes is supported from python 2.2 e.setAttribute(attribute,str(element.attributes[attribute])) if element.elements: for el in element.elements: diff --git a/wqflask/utility/temp_data.py b/wqflask/utility/temp_data.py index 5bf700c9..2f2726c6 100644 --- a/wqflask/utility/temp_data.py +++ b/wqflask/utility/temp_data.py @@ -20,6 +20,6 @@ class TempData(object): if __name__ == "__main__": redis = Redis() - for key in redis.keys(): + for key in list(redis.keys()): for field in redis.hkeys(key): print("{}.{}={}".format(key, field, redis.hget(key, field))) diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py index f790d424..51a87fe1 100644 --- a/wqflask/utility/tools.py +++ b/wqflask/utility/tools.py @@ -220,7 +220,7 @@ def show_settings(): logger.info(OVERRIDES) logger.info(BLUE+"Mr. Mojo Risin 2"+ENDC) - keylist = app.config.keys() + keylist = list(app.config.keys()) print("runserver.py: ****** Webserver configuration - k,v pairs from app.config ******") keylist.sort() for k in keylist: diff --git a/wqflask/wqflask/api/correlation.py b/wqflask/wqflask/api/correlation.py index 7f5312c1..eb05645e 100644 --- a/wqflask/wqflask/api/correlation.py +++ b/wqflask/wqflask/api/correlation.py @@ -36,7 +36,7 @@ def do_correlation(start_vars): #corr_results = collections.OrderedDict(sorted(corr_results.items(), key=lambda t: -abs(t[1][0]))) final_results = [] - for _trait_counter, trait in enumerate(corr_results.keys()[:corr_params['return_count']]): + for _trait_counter, trait in enumerate(list(corr_results.keys())[:corr_params['return_count']]): if corr_params['type'] == "tissue": [sample_r, num_overlap, sample_p, symbol] = corr_results[trait] result_dict = { @@ -76,20 +76,20 @@ def calculate_results(this_trait, this_dataset, target_dataset, corr_params): if corr_params['type'] == "tissue": trait_symbol_dict = this_dataset.retrieve_genes("Symbol") corr_results = do_tissue_correlation_for_all_traits(this_trait, trait_symbol_dict, corr_params) - sorted_results = collections.OrderedDict(sorted(corr_results.items(), + sorted_results = collections.OrderedDict(sorted(list(corr_results.items()), key=lambda t: -abs(t[1][1]))) elif corr_params['type'] == "literature" or corr_params['type'] == "lit": #ZS: Just so a user can use either "lit" or "literature" trait_geneid_dict = this_dataset.retrieve_genes("GeneId") corr_results = do_literature_correlation_for_all_traits(this_trait, this_dataset, trait_geneid_dict, corr_params) - sorted_results = collections.OrderedDict(sorted(corr_results.items(), + sorted_results = collections.OrderedDict(sorted(list(corr_results.items()), key=lambda t: -abs(t[1][1]))) else: - for target_trait, target_vals in target_dataset.trait_data.iteritems(): + for target_trait, target_vals in list(target_dataset.trait_data.items()): result = get_sample_r_and_p_values(this_trait, this_dataset, target_vals, target_dataset, corr_params['type']) if result is not None: corr_results[target_trait] = result - sorted_results = collections.OrderedDict(sorted(corr_results.items(), key=lambda t: -abs(t[1][0]))) + sorted_results = collections.OrderedDict(sorted(list(corr_results.items()), key=lambda t: -abs(t[1][0]))) return sorted_results @@ -100,10 +100,10 @@ def do_tissue_correlation_for_all_traits(this_trait, trait_symbol_dict, corr_par if this_trait.symbol.lower() in primary_trait_tissue_vals_dict: primary_trait_tissue_values = primary_trait_tissue_vals_dict[this_trait.symbol.lower()] - corr_result_tissue_vals_dict = correlation_functions.get_trait_symbol_and_tissue_values(symbol_list=trait_symbol_dict.values()) + corr_result_tissue_vals_dict = correlation_functions.get_trait_symbol_and_tissue_values(symbol_list=list(trait_symbol_dict.values())) tissue_corr_data = {} - for trait, symbol in trait_symbol_dict.iteritems(): + for trait, symbol in list(trait_symbol_dict.items()): if symbol and symbol.lower() in corr_result_tissue_vals_dict: this_trait_tissue_values = corr_result_tissue_vals_dict[symbol.lower()] @@ -119,7 +119,7 @@ def do_literature_correlation_for_all_traits(this_trait, target_dataset, trait_g input_trait_mouse_gene_id = convert_to_mouse_gene_id(target_dataset.group.species.lower(), this_trait.geneid) lit_corr_data = {} - for trait, gene_id in trait_geneid_dict.iteritems(): + for trait, gene_id in list(trait_geneid_dict.items()): mouse_gene_id = convert_to_mouse_gene_id(target_dataset.group.species.lower(), gene_id) if mouse_gene_id and str(mouse_gene_id).find(";") == -1: diff --git a/wqflask/wqflask/api/gen_menu.py b/wqflask/wqflask/api/gen_menu.py index cc11e14b..71d9ee03 100644 --- a/wqflask/wqflask/api/gen_menu.py +++ b/wqflask/wqflask/api/gen_menu.py @@ -61,7 +61,7 @@ def get_types(groups): """Build types list""" types = {} - for species, group_dict in groups.iteritems(): + for species, group_dict in list(groups.items()): types[species] = {} for group_name, _group_full_name, _family_name in group_dict: if phenotypes_exist(group_name): @@ -136,9 +136,9 @@ def build_types(species, group): def get_datasets(types): """Build datasets list""" datasets = {} - for species, group_dict in types.iteritems(): + for species, group_dict in list(types.items()): datasets[species] = {} - for group, type_list in group_dict.iteritems(): + for group, type_list in list(group_dict.items()): datasets[species][group] = {} for type_name in type_list: these_datasets = build_datasets(species, group, type_name[0]) diff --git a/wqflask/wqflask/correlation/corr_scatter_plot.py b/wqflask/wqflask/correlation/corr_scatter_plot.py index 819836b1..57a8d85f 100644 --- a/wqflask/wqflask/correlation/corr_scatter_plot.py +++ b/wqflask/wqflask/correlation/corr_scatter_plot.py @@ -36,13 +36,13 @@ class CorrScatterPlot(object): samples_1, samples_2, num_overlap = corr_result_helpers.normalize_values_with_samples(self.trait_1.data, self.trait_2.data) self.data = [] - self.indIDs = samples_1.keys() + self.indIDs = list(samples_1.keys()) vals_1 = [] - for sample in samples_1.keys(): + for sample in list(samples_1.keys()): vals_1.append(samples_1[sample].value) self.data.append(vals_1) vals_2 = [] - for sample in samples_2.keys(): + for sample in list(samples_2.keys()): vals_2.append(samples_2[sample].value) self.data.append(vals_2) diff --git a/wqflask/wqflask/correlation/show_corr_results.py b/wqflask/wqflask/correlation/show_corr_results.py index de7a1c0c..15a21ee6 100644 --- a/wqflask/wqflask/correlation/show_corr_results.py +++ b/wqflask/wqflask/correlation/show_corr_results.py @@ -145,10 +145,10 @@ class CorrelationResults(object): if corr_samples_group == 'samples_other': primary_samples = [x for x in primary_samples if x not in ( self.dataset.group.parlist + self.dataset.group.f1list)] - self.process_samples(start_vars, self.this_trait.data.keys(), primary_samples) + self.process_samples(start_vars, list(self.this_trait.data.keys()), primary_samples) self.target_dataset = data_set.create_dataset(start_vars['corr_dataset']) - self.target_dataset.get_trait_data(self.sample_data.keys()) + self.target_dataset.get_trait_data(list(self.sample_data.keys())) self.header_fields = get_header_fields(self.target_dataset.type, self.corr_method) @@ -168,41 +168,41 @@ class CorrelationResults(object): tissue_corr_data = self.do_tissue_correlation_for_all_traits() if tissue_corr_data != None: - for trait in tissue_corr_data.keys()[:self.return_number]: + for trait in list(tissue_corr_data.keys())[:self.return_number]: self.get_sample_r_and_p_values(trait, self.target_dataset.trait_data[trait]) else: - for trait, values in self.target_dataset.trait_data.iteritems(): + for trait, values in list(self.target_dataset.trait_data.items()): self.get_sample_r_and_p_values(trait, values) elif self.corr_type == "lit": self.trait_geneid_dict = self.dataset.retrieve_genes("GeneId") lit_corr_data = self.do_lit_correlation_for_all_traits() - for trait in lit_corr_data.keys()[:self.return_number]: + for trait in list(lit_corr_data.keys())[:self.return_number]: self.get_sample_r_and_p_values(trait, self.target_dataset.trait_data[trait]) elif self.corr_type == "sample": - for trait, values in self.target_dataset.trait_data.iteritems(): + for trait, values in list(self.target_dataset.trait_data.items()): self.get_sample_r_and_p_values(trait, values) - self.correlation_data = collections.OrderedDict(sorted(self.correlation_data.items(), + self.correlation_data = collections.OrderedDict(sorted(list(self.correlation_data.items()), key=lambda t: -abs(t[1][0]))) if self.target_dataset.type == "ProbeSet" or self.target_dataset.type == "Geno": #ZS: Convert min/max chromosome to an int for the location range option range_chr_as_int = None - for order_id, chr_info in self.dataset.species.chromosomes.chromosomes.iteritems(): + for order_id, chr_info in list(self.dataset.species.chromosomes.chromosomes.items()): if 'loc_chr' in start_vars: if chr_info.name == self.location_chr: range_chr_as_int = order_id - for _trait_counter, trait in enumerate(self.correlation_data.keys()[:self.return_number]): + for _trait_counter, trait in enumerate(list(self.correlation_data.keys())[:self.return_number]): trait_object = create_trait(dataset=self.target_dataset, name=trait, get_qtl_info=True, get_sample_info=False) if self.target_dataset.type == "ProbeSet" or self.target_dataset.type == "Geno": #ZS: Convert trait chromosome to an int for the location range option chr_as_int = 0 - for order_id, chr_info in self.dataset.species.chromosomes.chromosomes.iteritems(): + for order_id, chr_info in list(self.dataset.species.chromosomes.chromosomes.items()): if chr_info.name == trait_object.chr: chr_as_int = order_id @@ -297,14 +297,14 @@ class CorrelationResults(object): #print("trait_gene_symbols: ", pf(trait_gene_symbols.values())) corr_result_tissue_vals_dict= correlation_functions.get_trait_symbol_and_tissue_values( - symbol_list=self.trait_symbol_dict.values()) + symbol_list=list(self.trait_symbol_dict.values())) #print("corr_result_tissue_vals: ", pf(corr_result_tissue_vals_dict)) #print("trait_gene_symbols: ", pf(trait_gene_symbols)) tissue_corr_data = {} - for trait, symbol in self.trait_symbol_dict.iteritems(): + for trait, symbol in list(self.trait_symbol_dict.items()): if symbol and symbol.lower() in corr_result_tissue_vals_dict: this_trait_tissue_values = corr_result_tissue_vals_dict[symbol.lower()] @@ -314,7 +314,7 @@ class CorrelationResults(object): tissue_corr_data[trait] = [symbol, result[0], result[2]] - tissue_corr_data = collections.OrderedDict(sorted(tissue_corr_data.items(), + tissue_corr_data = collections.OrderedDict(sorted(list(tissue_corr_data.items()), key=lambda t: -abs(t[1][1]))) return tissue_corr_data @@ -359,7 +359,7 @@ class CorrelationResults(object): input_trait_mouse_gene_id = self.convert_to_mouse_gene_id(self.dataset.group.species.lower(), self.this_trait.geneid) lit_corr_data = {} - for trait, gene_id in self.trait_geneid_dict.iteritems(): + for trait, gene_id in list(self.trait_geneid_dict.items()): mouse_gene_id = self.convert_to_mouse_gene_id(self.dataset.group.species.lower(), gene_id) if mouse_gene_id and str(mouse_gene_id).find(";") == -1: @@ -387,7 +387,7 @@ class CorrelationResults(object): else: lit_corr_data[trait] = [gene_id, 0] - lit_corr_data = collections.OrderedDict(sorted(lit_corr_data.items(), + lit_corr_data = collections.OrderedDict(sorted(list(lit_corr_data.items()), key=lambda t: -abs(t[1][1]))) return lit_corr_data diff --git a/wqflask/wqflask/ctl/ctl_analysis.py b/wqflask/wqflask/ctl/ctl_analysis.py index 35067036..f0be7a98 100644 --- a/wqflask/wqflask/ctl/ctl_analysis.py +++ b/wqflask/wqflask/ctl/ctl_analysis.py @@ -125,7 +125,7 @@ class CTL(object): gt = create_trait(name = ts[0], dataset_name = ts[1]) gt = retrieve_sample_data(gt, dataset, individuals) for ind in individuals: - if ind in gt.data.keys(): + if ind in list(gt.data.keys()): traits.append(gt.data[ind].value) else: traits.append("-999") diff --git a/wqflask/wqflask/export_traits.py b/wqflask/wqflask/export_traits.py index 6646cc36..28c6593d 100644 --- a/wqflask/wqflask/export_traits.py +++ b/wqflask/wqflask/export_traits.py @@ -61,7 +61,7 @@ def export_search_results_csv(targs): traits_by_group = sort_traits_by_group(trait_list) file_list = [] - for group in traits_by_group.keys(): + for group in list(traits_by_group.keys()): group_traits = traits_by_group[group] buff = StringIO.StringIO() writer = csv.writer(buff) @@ -135,7 +135,7 @@ def export_search_results_csv(targs): def sort_traits_by_group(trait_list=[]): traits_by_group = {} for trait in trait_list: - if trait.dataset.group.name not in traits_by_group.keys(): + if trait.dataset.group.name not in list(traits_by_group.keys()): traits_by_group[trait.dataset.group.name] = [] traits_by_group[trait.dataset.group.name].append(trait) diff --git a/wqflask/wqflask/heatmap/heatmap.py b/wqflask/wqflask/heatmap/heatmap.py index 5098a184..577426b0 100644 --- a/wqflask/wqflask/heatmap/heatmap.py +++ b/wqflask/wqflask/heatmap/heatmap.py @@ -60,7 +60,7 @@ class Heatmap(object): chrnames = [] self.species = species.TheSpecies(dataset=self.trait_list[0][1]) - for key in self.species.chromosomes.chromosomes.keys(): + for key in list(self.species.chromosomes.chromosomes.keys()): chrnames.append([self.species.chromosomes.chromosomes[key].name, self.species.chromosomes.chromosomes[key].mb_length]) for trait_db in self.trait_list: @@ -93,7 +93,7 @@ class Heatmap(object): pos = [] markernames = [] - for trait in self.trait_results.keys(): + for trait in list(self.trait_results.keys()): lodnames.append(trait) self.dataset.group.get_markers() diff --git a/wqflask/wqflask/interval_analyst/GeneUtil.py b/wqflask/wqflask/interval_analyst/GeneUtil.py index 273168a8..a39e5d0f 100644 --- a/wqflask/wqflask/interval_analyst/GeneUtil.py +++ b/wqflask/wqflask/interval_analyst/GeneUtil.py @@ -24,7 +24,7 @@ def loadGenes(chrName, diffCol, startMb, endMb, species='mouse'): ##List current Species and other Species speciesId = speciesDict[species] - otherSpecies = [[X, speciesDict[X]] for X in speciesDict.keys()] + otherSpecies = [[X, speciesDict[X]] for X in list(speciesDict.keys())] otherSpecies.remove([species, speciesId]) results = g.db.execute(""" diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 7b6e70d2..0328ce85 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -229,7 +229,7 @@ class DisplayMappingResults(object): self.manhattan_plot = start_vars['manhattan_plot'] - if 'permCheck' in start_vars.keys(): + if 'permCheck' in list(start_vars.keys()): self.permChecked = start_vars['permCheck'] else: self.permChecked = False @@ -242,46 +242,46 @@ class DisplayMappingResults(object): else: self.nperm = 0 - if 'bootCheck' in start_vars.keys(): + if 'bootCheck' in list(start_vars.keys()): self.bootChecked = start_vars['bootCheck'] else: self.bootChecked = False - if 'num_bootstrap' in start_vars.keys(): + if 'num_bootstrap' in list(start_vars.keys()): self.nboot = int(start_vars['num_bootstrap']) else: self.nboot = 0 - if 'bootstrap_results' in start_vars.keys(): + if 'bootstrap_results' in list(start_vars.keys()): self.bootResult = start_vars['bootstrap_results'] else: self.bootResult = [] - if 'do_control' in start_vars.keys(): + if 'do_control' in list(start_vars.keys()): self.doControl = start_vars['do_control'] else: self.doControl = "false" - if 'control_marker' in start_vars.keys(): + if 'control_marker' in list(start_vars.keys()): self.controlLocus = start_vars['control_marker'] else: self.controlLocus = "" - if 'covariates' in start_vars.keys(): + if 'covariates' in list(start_vars.keys()): self.covariates = start_vars['covariates'] - if 'maf' in start_vars.keys(): + if 'maf' in list(start_vars.keys()): self.maf = start_vars['maf'] else: self.maf = "" - if 'output_files' in start_vars.keys(): + if 'output_files' in list(start_vars.keys()): self.output_files = start_vars['output_files'] - if 'use_loco' in start_vars.keys() and self.mapping_method == "gemma": + if 'use_loco' in list(start_vars.keys()) and self.mapping_method == "gemma": self.use_loco = start_vars['use_loco'] - if 'reaper_version' in start_vars.keys() and self.mapping_method == "reaper": + if 'reaper_version' in list(start_vars.keys()) and self.mapping_method == "reaper": self.reaper_version = start_vars['reaper_version'] if 'output_files' in start_vars: self.output_files = ",".join(start_vars['output_files']) self.categorical_vars = "" self.perm_strata = "" - if 'perm_strata' in start_vars.keys() and 'categorical_vars' in start_vars.keys(): + if 'perm_strata' in list(start_vars.keys()) and 'categorical_vars' in list(start_vars.keys()): self.categorical_vars = start_vars['categorical_vars'] self.perm_strata = start_vars['perm_strata'] @@ -323,7 +323,7 @@ class DisplayMappingResults(object): self.graphWidth = self.MULT_GRAPH_DEFAULT_WIDTH ## BEGIN HaplotypeAnalyst - if 'haplotypeAnalystCheck' in start_vars.keys(): + if 'haplotypeAnalystCheck' in list(start_vars.keys()): self.haplotypeAnalystChecked = start_vars['haplotypeAnalystCheck'] else: self.haplotypeAnalystChecked = False @@ -331,25 +331,25 @@ class DisplayMappingResults(object): self.graphHeight = self.GRAPH_DEFAULT_HEIGHT self.dominanceChecked = False - if 'LRSCheck' in start_vars.keys(): + if 'LRSCheck' in list(start_vars.keys()): self.LRS_LOD = start_vars['LRSCheck'] else: self.LRS_LOD = start_vars['score_type'] self.intervalAnalystChecked = True self.draw2X = False - if 'additiveCheck' in start_vars.keys(): + if 'additiveCheck' in list(start_vars.keys()): self.additiveChecked = start_vars['additiveCheck'] else: self.additiveChecked = False - if 'viewLegend' in start_vars.keys(): + if 'viewLegend' in list(start_vars.keys()): self.legendChecked = start_vars['viewLegend'] else: self.legendChecked = False - if 'showSNP' in start_vars.keys(): + if 'showSNP' in list(start_vars.keys()): self.SNPChecked = start_vars['showSNP'] else: self.SNPChecked = False - if 'showGenes' in start_vars.keys(): + if 'showGenes' in list(start_vars.keys()): self.geneChecked = start_vars['showGenes'] else: self.geneChecked = False @@ -530,7 +530,7 @@ class DisplayMappingResults(object): showLocusForm = HT.Form(cgi= os.path.join(webqtlConfig.CGIDIR, webqtlConfig.SCRIPTFILE), enctype='multipart/form-data', name=showLocusForm, submit=HT.Input(type='hidden')) hddn = {'FormID':'showDatabase', 'ProbeSetID':'_','database':fd.RISet+"Geno",'CellID':'_', 'RISet':fd.RISet, 'incparentsf1':'ON'} - for key in hddn.keys(): + for key in list(hddn.keys()): showLocusForm.append(HT.Input(name=key, value=hddn[key], type='hidden')) showLocusForm.append(intImg) else: diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index c9d10f7c..145dbc77 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -347,7 +347,7 @@ class RunMapping(object): if marker['chr1'] > 0 or marker['chr1'] == "X" or marker['chr1'] == "X/Y": if marker['chr1'] > highest_chr or marker['chr1'] == "X" or marker['chr1'] == "X/Y": highest_chr = marker['chr1'] - if 'lod_score' in marker.keys(): + if 'lod_score' in list(marker.keys()): self.qtl_results.append(marker) self.trimmed_markers = results @@ -411,7 +411,7 @@ class RunMapping(object): if marker['chr'] > 0 or marker['chr'] == "X" or marker['chr'] == "X/Y": if marker['chr'] > highest_chr or marker['chr'] == "X" or marker['chr'] == "X/Y": highest_chr = marker['chr'] - if ('lod_score' in marker.keys()) or ('lrs_value' in marker.keys()): + if ('lod_score' in list(marker.keys())) or ('lrs_value' in list(marker.keys())): self.qtl_results.append(marker) with Bench("Exporting Results"): @@ -538,28 +538,28 @@ def export_mapping_results(dataset, trait, markers, results_path, mapping_scale, output_file.write("Mb," + score_type) else: output_file.write("Cm," + score_type) - if "additive" in markers[0].keys(): + if "additive" in list(markers[0].keys()): output_file.write(",Additive") - if "dominance" in markers[0].keys(): + if "dominance" in list(markers[0].keys()): output_file.write(",Dominance") output_file.write("\n") for i, marker in enumerate(markers): output_file.write(marker['name'] + "," + str(marker['chr']) + "," + str(marker['Mb']) + ",") - if "lod_score" in marker.keys(): + if "lod_score" in list(marker.keys()): output_file.write(str(marker['lod_score'])) else: output_file.write(str(marker['lrs_value'])) - if "additive" in marker.keys(): + if "additive" in list(marker.keys()): output_file.write("," + str(marker['additive'])) - if "dominance" in marker.keys(): + if "dominance" in list(marker.keys()): output_file.write("," + str(marker['dominance'])) if i < (len(markers) - 1): output_file.write("\n") def trim_markers_for_figure(markers): - if 'p_wald' in markers[0].keys(): + if 'p_wald' in list(markers[0].keys()): score_type = 'p_wald' - elif 'lod_score' in markers[0].keys(): + elif 'lod_score' in list(markers[0].keys()): score_type = 'lod_score' else: score_type = 'lrs_value' @@ -617,7 +617,7 @@ def trim_markers_for_figure(markers): return filtered_markers def trim_markers_for_table(markers): - if 'lod_score' in markers[0].keys(): + if 'lod_score' in list(markers[0].keys()): sorted_markers = sorted(markers, key=lambda k: k['lod_score'], reverse=True) else: sorted_markers = sorted(markers, key=lambda k: k['lrs_value'], reverse=True) @@ -695,10 +695,10 @@ def get_genofile_samplelist(dataset): def get_perm_strata(this_trait, sample_list, categorical_vars, used_samples): perm_strata_strings = [] for sample in used_samples: - if sample in sample_list.sample_attribute_values.keys(): + if sample in list(sample_list.sample_attribute_values.keys()): combined_string = "" for var in categorical_vars: - if var in sample_list.sample_attribute_values[sample].keys(): + if var in list(sample_list.sample_attribute_values[sample].keys()): combined_string += str(sample_list.sample_attribute_values[sample][var]) else: combined_string += "NA" diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 39a07310..6b3e00fb 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -125,7 +125,7 @@ def add_group_to_resource(): def get_group_names(group_masks): group_masks_with_names = {} - for group_id, group_mask in group_masks.iteritems(): + for group_id, group_mask in list(group_masks.items()): this_mask = group_mask group_name = get_group_info(group_id)['name'] this_mask['name'] = group_name diff --git a/wqflask/wqflask/show_trait/export_trait_data.py b/wqflask/wqflask/show_trait/export_trait_data.py index 253c887b..68c3ad7d 100644 --- a/wqflask/wqflask/show_trait/export_trait_data.py +++ b/wqflask/wqflask/show_trait/export_trait_data.py @@ -47,7 +47,7 @@ def get_export_metadata(trait_id, dataset_name): def dict_to_sorted_list(dictionary): - sorted_list = [item for item in dictionary.iteritems()] + sorted_list = [item for item in list(dictionary.items())] sorted_list = sorted(sorted_list, cmp=cmp_samples) sorted_values = [item[1] for item in sorted_list] return sorted_values diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index f188fd9d..c156e61b 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -261,7 +261,7 @@ class ShowTrait(object): hddn['export_data'] = "" hddn['export_format'] = "excel" if len(self.scales_in_geno) < 2: - hddn['mapping_scale'] = self.scales_in_geno[self.scales_in_geno.keys()[0]][0][0] + hddn['mapping_scale'] = self.scales_in_geno[list(self.scales_in_geno.keys())[0]][0][0] # We'll need access to this_trait and hddn in the Jinja2 Template, so we put it inside self self.hddn = hddn @@ -405,7 +405,7 @@ class ShowTrait(object): if not self.temp_trait: other_sample_names = [] - for sample in self.this_trait.data.keys(): + for sample in list(self.this_trait.data.keys()): if (self.this_trait.data[sample].name2 in primary_sample_names) and (self.this_trait.data[sample].name not in primary_sample_names): primary_sample_names.append(self.this_trait.data[sample].name) primary_sample_names.remove(self.this_trait.data[sample].name2) @@ -558,7 +558,7 @@ def get_table_widths(sample_groups, has_num_cases=False): def has_num_cases(this_trait): has_n = False if this_trait.dataset.type != "ProbeSet" and this_trait.dataset.type != "Geno": - for name, sample in this_trait.data.iteritems(): + for name, sample in list(this_trait.data.items()): if sample.num_cases: has_n = True break @@ -611,7 +611,7 @@ def get_categorical_variables(this_trait, sample_list): if len(sample_list.attributes) > 0: for attribute in sample_list.attributes: attribute_vals = [] - for sample_name in this_trait.data.keys(): + for sample_name in list(this_trait.data.keys()): if sample_list.attributes[attribute].name in this_trait.data[sample_name].extra_attributes: attribute_vals.append(this_trait.data[sample_name].extra_attributes[sample_list.attributes[attribute].name]) else: diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index d67f1a2e..394a9e28 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -533,7 +533,7 @@ def heatmap_page(): result = template_vars.__dict__ - for item in template_vars.__dict__.keys(): + for item in list(template_vars.__dict__.keys()): logger.info(" ---**--- {}: {}".format(type(template_vars.__dict__[item]), item)) pickled_result = pickle.dumps(result, pickle.HIGHEST_PROTOCOL) @@ -637,7 +637,7 @@ def loading_page(): if 'wanted_inputs' in initial_start_vars: wanted = initial_start_vars['wanted_inputs'].split(",") start_vars = {} - for key, value in initial_start_vars.iteritems(): + for key, value in list(initial_start_vars.items()): if key in wanted or key.startswith(('value:')): start_vars[key] = value @@ -737,7 +737,7 @@ def mapping_results_page(): 'transform' ) start_vars = {} - for key, value in initial_start_vars.iteritems(): + for key, value in list(initial_start_vars.items()): if key in wanted or key.startswith(('value:')): start_vars[key] = value #logger.debug("Mapping called with start_vars:", start_vars) -- cgit v1.2.3 From 28e75b1c96819eab1e8052a3045ece26e5f35c42 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 02:38:15 +0300 Subject: Wrap `filter()` usage in a `list` call Run `2to3-3.8 -f filter -w .` See --- test/requests/link_checker.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/requests/link_checker.py b/test/requests/link_checker.py index df4d32d8..fa70747d 100644 --- a/test/requests/link_checker.py +++ b/test/requests/link_checker.py @@ -23,11 +23,9 @@ def is_in_page_link(link): return pattern.match(link) def get_links(doc): - return filter( - lambda x: not ( + return [x for x in [y.get("href") for y in doc.cssselect("a")] if not ( is_root_link(x) - or is_mailto_link(x)) - , [y.get("href") for y in doc.cssselect("a")]) + or is_mailto_link(x))] def verify_link(link): if link[0] == "#": @@ -56,9 +54,9 @@ def check_page(host, start_url): print("Checking links host "+host+" in page `"+start_url+"`") doc = parse(start_url).getroot() links = get_links(doc) - in_page_links = filter(is_in_page_link, links) - internal_links = filter(is_internal_link, links) - external_links = filter(lambda x: not (is_internal_link(x) or is_in_page_link(x)), links) + in_page_links = list(filter(is_in_page_link, links)) + internal_links = list(filter(is_internal_link, links)) + external_links = [x for x in links if not (is_internal_link(x) or is_in_page_link(x))] for link in internal_links: verify_link(host+link) -- cgit v1.2.3 From e55b1502340cc99cd8a5d705261a5ff3c87f3718 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 02:41:46 +0300 Subject: Change `dict.has_key(key)` to `key in dict` Run `2to3-3.8 -f has_key -w .` See: --- scripts/maintenance/readProbeSetSE_v7.py | 2 +- wqflask/wqflask/marker_regression/plink_mapping.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/maintenance/readProbeSetSE_v7.py b/scripts/maintenance/readProbeSetSE_v7.py index 7b2fee87..2700a8ef 100755 --- a/scripts/maintenance/readProbeSetSE_v7.py +++ b/scripts/maintenance/readProbeSetSE_v7.py @@ -223,7 +223,7 @@ while line: line2 = list(map(string.strip, line2)) CellId = line2[0] - if not ProbeNameId.has_key(CellId): + if CellId not in ProbeNameId: ferror.write(CellId + " doesn't exist\n") else: DataId = ProbeNameId[CellId] diff --git a/wqflask/wqflask/marker_regression/plink_mapping.py b/wqflask/wqflask/marker_regression/plink_mapping.py index 9571015e..38ef7190 100644 --- a/wqflask/wqflask/marker_regression/plink_mapping.py +++ b/wqflask/wqflask/marker_regression/plink_mapping.py @@ -111,7 +111,7 @@ def parse_plink_output(output_filename, species): line_list = build_line_list(line=line) # only keep the records whose chromosome name is in db - if species.chromosomes.chromosomes.has_key(int(line_list[0])) and line_list[-1] and line_list[-1].strip()!='NA': + if int(line_list[0]) in species.chromosomes.chromosomes and line_list[-1] and line_list[-1].strip()!='NA': chr_name = species.chromosomes.chromosomes[int(line_list[0])] snp = line_list[1] @@ -121,7 +121,7 @@ def parse_plink_output(output_filename, species): if p_value < threshold_p_value: p_value_dict[snp] = float(p_value) - if plink_results.has_key(chr_name): + if chr_name in plink_results: value_list = plink_results[chr_name] # pvalue range is [0,1] -- cgit v1.2.3 From e63331da5ed0a11f2a558d799dd570bf44ad584e Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:01:33 +0300 Subject: Replace `izip` with python's built-in equivalent Run `2to3-3.8 -f itertools -w . && 2to3-3.8 -f itertools_imports -w .` See: and --- wqflask/base/data_set.py | 2 +- wqflask/maintenance/quantile_normalize.py | 4 ++-- wqflask/wqflask/export_traits.py | 2 +- wqflask/wqflask/pbkdf2.py | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index cfba9104..b0119b58 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -259,7 +259,7 @@ class Markers(object): # if len(self.markers) > len(p_values): # self.markers = self.markers[:len(p_values)] - for marker, p_value in itertools.izip(self.markers, p_values): + for marker, p_value in zip(self.markers, p_values): if not p_value: continue marker['p_value'] = float(p_value) diff --git a/wqflask/maintenance/quantile_normalize.py b/wqflask/maintenance/quantile_normalize.py index 41a3aad8..34886f44 100644 --- a/wqflask/maintenance/quantile_normalize.py +++ b/wqflask/maintenance/quantile_normalize.py @@ -3,7 +3,7 @@ from __future__ import absolute_import, print_function, division import sys sys.path.insert(0,'./') -from itertools import izip + import MySQLdb import urlparse @@ -60,7 +60,7 @@ def set_data(dataset_name): sample_list = [] with open(orig_file, 'r') as orig_fh, open('/home/zas1024/cfw_data/quant_norm.csv', 'r') as quant_fh: - for i, (line1, line2) in enumerate(izip(orig_fh, quant_fh)): + for i, (line1, line2) in enumerate(zip(orig_fh, quant_fh)): trait_dict = {} sample_list = [] if i == 0: diff --git a/wqflask/wqflask/export_traits.py b/wqflask/wqflask/export_traits.py index 28c6593d..a8b49829 100644 --- a/wqflask/wqflask/export_traits.py +++ b/wqflask/wqflask/export_traits.py @@ -122,7 +122,7 @@ def export_search_results_csv(targs): csv_rows.append(row_contents) - csv_rows = list(map(list, itertools.izip_longest(*[row for row in csv_rows]))) + csv_rows = list(map(list, itertools.zip_longest(*[row for row in csv_rows]))) writer.writerows(csv_rows) csv_data = buff.getvalue() buff.close() diff --git a/wqflask/wqflask/pbkdf2.py b/wqflask/wqflask/pbkdf2.py index 731c8843..0ed50790 100644 --- a/wqflask/wqflask/pbkdf2.py +++ b/wqflask/wqflask/pbkdf2.py @@ -44,7 +44,7 @@ import hmac import hashlib from struct import Struct from operator import xor -from itertools import izip, starmap +from itertools import starmap _pack_int = Struct('>I').pack @@ -72,7 +72,7 @@ def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None): rv = u = _pseudorandom(salt + _pack_int(block)) for i in xrange(iterations - 1): u = _pseudorandom(''.join(map(chr, u))) - rv = list(starmap(xor, izip(rv, u))) + rv = list(starmap(xor, zip(rv, u))) buf.extend(rv) return ''.join(map(chr, buf))[:keylen] @@ -81,7 +81,7 @@ def safe_str_cmp(a, b): if len(a) != len(b): return False rv = 0 - for x, y in izip(a, b): + for x, y in zip(a, b): rv |= ord(x) ^ ord(y) return rv == 0 -- cgit v1.2.3 From caec08fa1e738fa9bc1b0b6bf626d8325f798712 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:08:08 +0300 Subject: Convert the old not-equal syntax, <>, to != Run `2to3-3.8 -f ne -w .` See: --- wqflask/utility/svg.py | 2 +- wqflask/wqflask/marker_regression/plink_mapping.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/utility/svg.py b/wqflask/utility/svg.py index c6a5c260..c7356e57 100644 --- a/wqflask/utility/svg.py +++ b/wqflask/utility/svg.py @@ -360,7 +360,7 @@ class tspan(SVGelement): def __init__(self, text=None, **args): SVGelement.__init__(self, 'tspan', **args) - if self.text <> None: + if self.text != None: self.text = text def __repr__(self): diff --git a/wqflask/wqflask/marker_regression/plink_mapping.py b/wqflask/wqflask/marker_regression/plink_mapping.py index 38ef7190..d4ee6fe6 100644 --- a/wqflask/wqflask/marker_regression/plink_mapping.py +++ b/wqflask/wqflask/marker_regression/plink_mapping.py @@ -156,7 +156,7 @@ def parse_plink_output(output_filename, species): ####################################################### def build_line_list(line=None): line_list = string.split(string.strip(line),' ')# irregular number of whitespaces between columns - line_list = [item for item in line_list if item <>''] + line_list = [item for item in line_list if item !=''] line_list = list(map(string.strip, line_list)) return line_list \ No newline at end of file -- cgit v1.2.3 From 8be6ecf3d6b70b40be97d4abebb59eabcce8c8f8 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:10:33 +0300 Subject: Convert the use of iterator’s next() methods to the next() function Run `2to3-3.8 -f next -w .` See: --- scripts/maintenance/delete_genotypes.py | 2 +- scripts/maintenance/load_phenotypes.py | 10 +++++----- wqflask/wqflask/collect.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/maintenance/delete_genotypes.py b/scripts/maintenance/delete_genotypes.py index 060640e1..b7f83758 100755 --- a/scripts/maintenance/delete_genotypes.py +++ b/scripts/maintenance/delete_genotypes.py @@ -18,7 +18,7 @@ def main(argv): # datafile datafile = open(config.get('config', 'datafile'), 'r') datafile = csv.reader(datafile, delimiter='\t', quotechar='"') - datafile.next() + next(datafile) delrowcount = 0 for row in datafile: if len(row) == 0: diff --git a/scripts/maintenance/load_phenotypes.py b/scripts/maintenance/load_phenotypes.py index 61d527d4..759d2eec 100755 --- a/scripts/maintenance/load_phenotypes.py +++ b/scripts/maintenance/load_phenotypes.py @@ -22,22 +22,22 @@ def main(argv): # datafile datafile = open(config.get('config', 'datafile'), 'r') phenotypedata = csv.reader(datafile, delimiter='\t', quotechar='"') - phenotypedata_head = phenotypedata.next() + phenotypedata_head = next(phenotypedata) print(("phenotypedata head:\n\t%s" % phenotypedata_head)) strainnames = phenotypedata_head[1:] strains = datastructure.get_strains_bynames(inbredsetid=inbredsetid, strainnames=strainnames, updatestrainxref="yes") # metafile metafile = open(config.get('config', 'metafile'), 'r') phenotypemeta = csv.reader(metafile, delimiter='\t', quotechar='"') - phenotypemeta_head = phenotypemeta.next() + phenotypemeta_head = next(phenotypemeta) print(("phenotypemeta head:\n\t%s" % phenotypemeta_head)) print() # load for metarow in phenotypemeta: # - datarow_value = phenotypedata.next() - datarow_se = phenotypedata.next() - datarow_n = phenotypedata.next() + datarow_value = next(phenotypedata) + datarow_se = next(phenotypedata) + datarow_n = next(phenotypedata) # Phenotype sql = """ INSERT INTO Phenotype diff --git a/wqflask/wqflask/collect.py b/wqflask/wqflask/collect.py index 42a09fed..4c6e38e6 100644 --- a/wqflask/wqflask/collect.py +++ b/wqflask/wqflask/collect.py @@ -193,7 +193,7 @@ def view_collection(): params = request.args uc_id = params['uc_id'] - uc = (collection for collection in g.user_session.user_collections if collection["id"] == uc_id).next() + uc = next((collection for collection in g.user_session.user_collections if collection["id"] == uc_id)) traits = uc["members"] trait_obs = [] -- cgit v1.2.3 From 7e60647223017220747d248ed1c986cc8374435e Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:13:53 +0300 Subject: Wrap `raise` statements in parenthesis Run `2to3-3.8 -f raise -w .` See: --- wqflask/base/trait.py | 2 +- wqflask/utility/svg.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index e82df226..c2d260e3 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -605,6 +605,6 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): if trait.lrs != "": trait.LRS_score_repr = LRS_score_repr = '%3.1f' % trait.lrs else: - raise KeyError, `trait.name`+' information is not found in the database.' + raise KeyError(`trait.name`+' information is not found in the database.') return trait \ No newline at end of file diff --git a/wqflask/utility/svg.py b/wqflask/utility/svg.py index c7356e57..872f22fe 100644 --- a/wqflask/utility/svg.py +++ b/wqflask/utility/svg.py @@ -445,7 +445,7 @@ class rect(SVGelement): def __init__(self, x=None, y=None, width=None, height=None, fill=None, stroke=None, stroke_width=None, **args): if width == None or height == None: - raise ValueError, 'both height and width are required' + raise ValueError('both height and width are required') SVGelement.__init__(self,'rect',{'width':width,'height':height},**args) if x!=None: @@ -466,7 +466,7 @@ class ellipse(SVGelement): """ def __init__(self,cx=None,cy=None,rx=None,ry=None,fill=None,stroke=None,stroke_width=None,**args): if rx==None or ry== None: - raise ValueError, 'both rx and ry are required' + raise ValueError('both rx and ry are required') SVGelement.__init__(self,'ellipse',{'rx':rx,'ry':ry},**args) if cx!=None: @@ -488,7 +488,7 @@ class circle(SVGelement): """ def __init__(self,cx=None,cy=None,r=None,fill=None,stroke=None,stroke_width=None,**args): if r==None: - raise ValueError, 'r is required' + raise ValueError('r is required') SVGelement.__init__(self,'circle',{'r':r},**args) if cx!=None: self.attributes['cx']=cx @@ -714,7 +714,7 @@ class image(SVGelement): """ def __init__(self,url,x=None,y=None,width=None,height=None,**args): if width==None or height==None: - raise ValueError, 'both height and width are required' + raise ValueError('both height and width are required') SVGelement.__init__(self,'image',{'xlink:href':url,'width':width,'height':height},**args) if x!=None: self.attributes['x']=x -- cgit v1.2.3 From e8e7f835861b6e4eb940a9f55b7769d225f2c87d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:16:29 +0300 Subject: Convert `raw_input` to `input` Run `2to3-3.8 -f raw_input -w .` See: --- scripts/maintenance/readProbeSetSE_v7.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/maintenance/readProbeSetSE_v7.py b/scripts/maintenance/readProbeSetSE_v7.py index 2700a8ef..833c3f5f 100755 --- a/scripts/maintenance/readProbeSetSE_v7.py +++ b/scripts/maintenance/readProbeSetSE_v7.py @@ -29,9 +29,9 @@ def translateAlias(str): dataStart = 1 -GeneChipId = int(raw_input("Enter GeneChipId:")) -ProbeSetFreezeId = int(raw_input("Enter ProbeSetFreezeId:")) -input_file_name = raw_input("Enter file name with suffix:") +GeneChipId = int(input("Enter GeneChipId:")) +ProbeSetFreezeId = int(input("Enter ProbeSetFreezeId:")) +input_file_name = input("Enter file name with suffix:") fp = open("%s" % input_file_name, 'rb') -- cgit v1.2.3 From 17a72093f829666cc6e8df722771d31066dd71d0 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:24:27 +0300 Subject: Handle the move of reduce() to functools.reduce() Run `2to3-3.8 -f reduce -w .` See: --- wqflask/wqflask/correlation_matrix/show_corr_matrix.py | 6 +++++- wqflask/wqflask/marker_regression/display_mapping_results.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/correlation_matrix/show_corr_matrix.py b/wqflask/wqflask/correlation_matrix/show_corr_matrix.py index a912344f..e6c817e7 100644 --- a/wqflask/wqflask/correlation_matrix/show_corr_matrix.py +++ b/wqflask/wqflask/correlation_matrix/show_corr_matrix.py @@ -42,6 +42,10 @@ import rpy2.robjects as robjects from pprint import pformat as pf from utility.redis_tools import get_redis_conn +try: # Only import this for Python3 + from functools import reduce +except: + pass Redis = get_redis_conn() THIRTY_DAYS = 60 * 60 * 24 * 30 @@ -302,4 +306,4 @@ def sortEigenVectors(vector): A = [x*100.0/sum for x in A] return [A, B] except: - return [] \ No newline at end of file + return [] diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 0328ce85..c8b9c405 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -48,6 +48,10 @@ from base.webqtlConfig import TMPDIR, GENERATED_TEXT_DIR, GENERATED_IMAGE_DIR from utility.pillow_utils import draw_rotated_text, draw_open_polygon import utility.logger +try: # Only import this for Python3 + from functools import reduce +except: + pass logger = utility.logger.getLogger(__name__ ) RED = ImageColor.getrgb("red") -- cgit v1.2.3 From 1c3ade2f739608563cc1659dd93e5b2abc446046 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:27:21 +0300 Subject: Replace backtick repr with the repr() function * wqflask/base/trait.py: See --- wqflask/base/trait.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index c2d260e3..a513b589 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -605,6 +605,6 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): if trait.lrs != "": trait.LRS_score_repr = LRS_score_repr = '%3.1f' % trait.lrs else: - raise KeyError(`trait.name`+' information is not found in the database.') + raise KeyError(repr(trait.name)+' information is not found in the database.') return trait \ No newline at end of file -- cgit v1.2.3 From 606ec8b9b3bbb8b2269ab546ebafa454f1753176 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:31:53 +0300 Subject: Handle the rename of urllib and urllib2 to the urllib package See: --- wqflask/base/trait.py | 2 +- wqflask/db/call.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index a513b589..2b8f2e72 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -6,7 +6,7 @@ import resource import codecs import requests import random -import urllib +import urllib.request, urllib.parse, urllib.error from base import webqtlConfig from base.webqtlCaseData import webqtlCaseData diff --git a/wqflask/db/call.py b/wqflask/db/call.py index 1a1b3adc..82cfebb4 100644 --- a/wqflask/db/call.py +++ b/wqflask/db/call.py @@ -3,7 +3,7 @@ from flask import g import string -import urllib2 +import urllib.request, urllib.error, urllib.parse import json from utility.tools import USE_GN_SERVER, LOG_SQL, GN_SERVER_URL from utility.benchmark import Bench @@ -59,7 +59,7 @@ def gn_server(path): """ with Bench("GN_SERVER",LOG_SQL): - res = urllib2.urlopen(GN_SERVER_URL+path) + res = urllib.request.urlopen(GN_SERVER_URL+path) rest = res.read() res2 = json.loads(rest) logger.debug(res2) -- cgit v1.2.3 From 9d5dff44fb4d07f926659dde0c6205bf12a1ca5b Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:36:24 +0300 Subject: Rename xrange() to range() and wrap existing range() calls with list See: --- wqflask/maintenance/quantile_normalize.py | 2 +- wqflask/utility/Plot.py | 2 +- wqflask/wqflask/correlation/correlation_functions.py | 4 ++-- wqflask/wqflask/correlation_matrix/show_corr_matrix.py | 6 +++--- wqflask/wqflask/marker_regression/qtlreaper_mapping.py | 2 +- wqflask/wqflask/network_graph/network_graph.py | 4 ++-- wqflask/wqflask/pbkdf2.py | 4 ++-- wqflask/wqflask/search_results.py | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/wqflask/maintenance/quantile_normalize.py b/wqflask/maintenance/quantile_normalize.py index 34886f44..82b695f4 100644 --- a/wqflask/maintenance/quantile_normalize.py +++ b/wqflask/maintenance/quantile_normalize.py @@ -37,7 +37,7 @@ def create_dataframe(input_file): with open(input_file) as f: ncols = len(f.readline().split("\t")) - input_array = np.loadtxt(open(input_file, "rb"), delimiter="\t", skiprows=1, usecols=range(1, ncols)) + input_array = np.loadtxt(open(input_file, "rb"), delimiter="\t", skiprows=1, usecols=list(range(1, ncols))) return pd.DataFrame(input_array) #This function taken from https://github.com/ShawnLYU/Quantile_Normalize diff --git a/wqflask/utility/Plot.py b/wqflask/utility/Plot.py index 82bf6070..c9053dde 100644 --- a/wqflask/utility/Plot.py +++ b/wqflask/utility/Plot.py @@ -86,7 +86,7 @@ def frange(start, end=None, inc=1.0): # Need to adjust the count. AFAICT, it always comes up one short. count += 1 L = [start] * count - for i in xrange(1, count): + for i in range(1, count): L[i] = start + i * inc return L diff --git a/wqflask/wqflask/correlation/correlation_functions.py b/wqflask/wqflask/correlation/correlation_functions.py index 06dec795..abaa212f 100644 --- a/wqflask/wqflask/correlation/correlation_functions.py +++ b/wqflask/wqflask/correlation/correlation_functions.py @@ -50,12 +50,12 @@ from flask import Flask, g def cal_zero_order_corr_for_tiss (primaryValue=[], targetValue=[], method='pearson'): - R_primary = rpy2.robjects.FloatVector(range(len(primaryValue))) + R_primary = rpy2.robjects.FloatVector(list(range(len(primaryValue)))) N = len(primaryValue) for i in range(len(primaryValue)): R_primary[i] = primaryValue[i] - R_target = rpy2.robjects.FloatVector(range(len(targetValue))) + R_target = rpy2.robjects.FloatVector(list(range(len(targetValue)))) for i in range(len(targetValue)): R_target[i]=targetValue[i] diff --git a/wqflask/wqflask/correlation_matrix/show_corr_matrix.py b/wqflask/wqflask/correlation_matrix/show_corr_matrix.py index e6c817e7..832746bb 100644 --- a/wqflask/wqflask/correlation_matrix/show_corr_matrix.py +++ b/wqflask/wqflask/correlation_matrix/show_corr_matrix.py @@ -194,7 +194,7 @@ class CorrelationMatrix(object): if self.do_PCA == True: self.pca_works = "True" self.pca_trait_ids = [] - pca = self.calculate_pca(range(len(self.traits)), corr_eigen_value, corr_eigen_vectors) + pca = self.calculate_pca(list(range(len(self.traits))), corr_eigen_value, corr_eigen_vectors) self.loadings_array = self.process_loadings() else: self.pca_works = "False" @@ -203,8 +203,8 @@ class CorrelationMatrix(object): self.js_data = dict(traits = [trait.name for trait in self.traits], groups = groups, - cols = range(len(self.traits)), - rows = range(len(self.traits)), + cols = list(range(len(self.traits))), + rows = list(range(len(self.traits))), samples = self.all_sample_list, sample_data = self.sample_data,) # corr_results = [result[1] for result in result_row for result_row in self.corr_results]) diff --git a/wqflask/wqflask/marker_regression/qtlreaper_mapping.py b/wqflask/wqflask/marker_regression/qtlreaper_mapping.py index 0c560582..189c1985 100644 --- a/wqflask/wqflask/marker_regression/qtlreaper_mapping.py +++ b/wqflask/wqflask/marker_regression/qtlreaper_mapping.py @@ -228,4 +228,4 @@ def natural_sort(marker_list): """ convert = lambda text: int(text) if text.isdigit() else text.lower() alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', str(marker_list[key]['chr'])) ] - return sorted(range(len(marker_list)), key = alphanum_key) \ No newline at end of file + return sorted(list(range(len(marker_list))), key = alphanum_key) \ No newline at end of file diff --git a/wqflask/wqflask/network_graph/network_graph.py b/wqflask/wqflask/network_graph/network_graph.py index f61c40b4..ac2ff017 100644 --- a/wqflask/wqflask/network_graph/network_graph.py +++ b/wqflask/wqflask/network_graph/network_graph.py @@ -202,8 +202,8 @@ class NetworkGraph(object): self.js_data = dict(traits = [trait.name for trait in self.traits], groups = groups, - cols = range(len(self.traits)), - rows = range(len(self.traits)), + cols = list(range(len(self.traits))), + rows = list(range(len(self.traits))), samples = self.all_sample_list, sample_data = self.sample_data, elements = self.elements,) diff --git a/wqflask/wqflask/pbkdf2.py b/wqflask/wqflask/pbkdf2.py index 0ed50790..917b9d31 100644 --- a/wqflask/wqflask/pbkdf2.py +++ b/wqflask/wqflask/pbkdf2.py @@ -68,9 +68,9 @@ def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None): h.update(x) return list(map(ord, h.digest())) buf = [] - for block in xrange(1, -(-keylen // mac.digest_size) + 1): + for block in range(1, -(-keylen // mac.digest_size) + 1): rv = u = _pseudorandom(salt + _pack_int(block)) - for i in xrange(iterations - 1): + for i in range(iterations - 1): u = _pseudorandom(''.join(map(chr, u))) rv = list(starmap(xor, zip(rv, u))) buf.extend(rv) diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index de4b01eb..5b3946e3 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -266,7 +266,7 @@ def get_GO_symbols(a_search): def insert_newlines(string, every=64): """ This is because it is seemingly impossible to change the width of the description column, so I'm just manually adding line breaks """ lines = [] - for i in xrange(0, len(string), every): + for i in range(0, len(string), every): lines.append(string[i:i+every]) return '\n'.join(lines) -- cgit v1.2.3 From 3aaa28ea762c496eeb84e09e45194e3fd2a51673 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:45:49 +0300 Subject: Make Python more idiomatic Run `2to3-3.8 -f idioms -w .` See: --- scripts/maintenance/readProbeSetMean_v7.py | 6 ++---- scripts/maintenance/readProbeSetSE_v7.py | 6 ++---- wqflask/base/data_set.py | 4 ++-- wqflask/utility/authentication_tools.py | 2 +- wqflask/utility/svg.py | 2 +- wqflask/wqflask/ctl/ctl_analysis.py | 2 +- wqflask/wqflask/show_trait/SampleList.py | 2 +- wqflask/wqflask/show_trait/show_trait.py | 2 +- wqflask/wqflask/user_login.py | 2 +- 9 files changed, 12 insertions(+), 16 deletions(-) diff --git a/scripts/maintenance/readProbeSetMean_v7.py b/scripts/maintenance/readProbeSetMean_v7.py index 97767715..864b4e08 100755 --- a/scripts/maintenance/readProbeSetMean_v7.py +++ b/scripts/maintenance/readProbeSetMean_v7.py @@ -80,8 +80,7 @@ while line: if kj%100000 == 0: print(('checked ',kj,' lines')) -GeneList = list(map(string.lower, GeneList)) -GeneList.sort() +GeneList = sorted(map(string.lower, GeneList)) if isCont==0: sys.exit(0) @@ -148,9 +147,8 @@ for item in results: print(Names) -Names = list(map(string.lower, Names)) +Names = sorted(map(string.lower, Names)) -Names.sort() # -- Fixed the lower case problem of ProbeSets affx-mur_b2_at doesn't exist --# ##---- compare genelist with names ----## diff --git a/scripts/maintenance/readProbeSetSE_v7.py b/scripts/maintenance/readProbeSetSE_v7.py index 833c3f5f..20a846a4 100755 --- a/scripts/maintenance/readProbeSetSE_v7.py +++ b/scripts/maintenance/readProbeSetSE_v7.py @@ -91,8 +91,7 @@ while line: if kj % 100000 == 0: print(('checked ', kj, ' lines')) -GeneList = list(map(string.lower, GeneList)) -GeneList.sort() +GeneList = sorted(map(string.lower, GeneList)) if isCont == 0: sys.exit(0) @@ -158,8 +157,7 @@ results = db.fetchall() Names = [] for item in results: Names.append(item[0]) - Names = list(map(string.lower, Names)) - Names.sort() # -- Fixed the lower case problem of ProbeSets affx-mur_b2_at doesn't exist --# + Names = sorted(map(string.lower, Names)) ##---- compare genelist with names ----## x = y = 0 diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index b0119b58..06e1c551 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -254,7 +254,7 @@ class Markers(object): logger.debug("length of self.markers:", len(self.markers)) logger.debug("length of p_values:", len(p_values)) - if type(p_values) is list: + if isinstance(p_values, list): # THIS IS only needed for the case when we are limiting the number of p-values calculated # if len(self.markers) > len(p_values): # self.markers = self.markers[:len(p_values)] @@ -270,7 +270,7 @@ class Markers(object): marker['lod_score'] = -math.log10(marker['p_value']) # Using -log(p) for the LRS; need to ask Rob how he wants to get LRS from p-values marker['lrs_value'] = -math.log10(marker['p_value']) * 4.61 - elif type(p_values) is dict: + elif isinstance(p_values, dict): filtered_markers = [] for marker in self.markers: #logger.debug("marker[name]", marker['name']) diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index ece7022c..bc03eb55 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -17,7 +17,7 @@ logger = logging.getLogger(__name__ ) def check_resource_availability(dataset, trait_id=None): #At least for now assume temporary entered traits are accessible - if type(dataset) == str: + if isinstance(dataset, str): return webqtlConfig.DEFAULT_PRIVILEGES if dataset.type == "Temp": return webqtlConfig.DEFAULT_PRIVILEGES diff --git a/wqflask/utility/svg.py b/wqflask/utility/svg.py index 872f22fe..874ada9d 100644 --- a/wqflask/utility/svg.py +++ b/wqflask/utility/svg.py @@ -332,7 +332,7 @@ class SVGelement: f.write('\n'+'\t'*(level+2)+line) f.write('\n'+'\t'*(level+1)+']]>\n') if self.text: - if type(self.text) == type(''): # If the text is only text + if isinstance(self.text, type('')): # If the text is only text f.write(_escape(str(self.text))) else: # If the text is a spannedtext class f.write(str(self.text)) diff --git a/wqflask/wqflask/ctl/ctl_analysis.py b/wqflask/wqflask/ctl/ctl_analysis.py index f0be7a98..e58a7b87 100644 --- a/wqflask/wqflask/ctl/ctl_analysis.py +++ b/wqflask/wqflask/ctl/ctl_analysis.py @@ -175,7 +175,7 @@ class CTL(object): sys.stdout.flush() # Create the interactive graph for cytoscape visualization (Nodes and Edges) - if not type(significant) == ri.RNULLType: + if not isinstance(significant, ri.RNULLType): for x in range(len(significant[0])): logger.debug(significant[0][x], significant[1][x], significant[2][x]) # Debug to console tsS = significant[0][x].split(':') # Source diff --git a/wqflask/wqflask/show_trait/SampleList.py b/wqflask/wqflask/show_trait/SampleList.py index ad78ebcc..21ba7f63 100644 --- a/wqflask/wqflask/show_trait/SampleList.py +++ b/wqflask/wqflask/show_trait/SampleList.py @@ -43,7 +43,7 @@ class SampleList(object): for counter, sample_name in enumerate(sample_names, 1): sample_name = sample_name.replace("_2nd_", "") - if type(self.this_trait) is list: #ZS: self.this_trait will be a list if it is a Temp trait + if isinstance(self.this_trait, list): #ZS: self.this_trait will be a list if it is a Temp trait if counter <= len(self.this_trait) and str(self.this_trait[counter-1]).upper() != 'X': sample = webqtlCaseData.webqtlCaseData(name=sample_name, value=float(self.this_trait[counter-1])) else: diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index c156e61b..65899ab2 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -625,7 +625,7 @@ def get_categorical_variables(this_trait, sample_list): def get_genotype_scales(genofiles): geno_scales = {} - if type(genofiles) is list: + if isinstance(genofiles, list): for the_file in genofiles: file_location = the_file['location'] geno_scales[file_location] = get_scales_from_genofile(file_location) diff --git a/wqflask/wqflask/user_login.py b/wqflask/wqflask/user_login.py index cfee0079..04672b45 100644 --- a/wqflask/wqflask/user_login.py +++ b/wqflask/wqflask/user_login.py @@ -193,7 +193,7 @@ def login(): if user_details: submitted_password = params['password'] pwfields = user_details['password'] - if type(pwfields) is str: + if isinstance(pwfields, str): pwfields = json.loads(pwfields) encrypted_pass_fields = encode_password(pwfields, submitted_password) password_match = pbkdf2.safe_str_cmp(encrypted_pass_fields['password'], pwfields['password']) -- cgit v1.2.3 From db41cd49b6d8ccd2c3318209118ffe098bc9293e Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 19 Aug 2020 03:57:05 +0300 Subject: Remove extra whitespace(or add it) from comma separated items See: --- etc/default_settings.py | 2 +- scripts/maintenance/QTL_Reaper_v6.py | 4 +- scripts/maintenance/readProbeSetMean_v7.py | 28 ++-- setup.py | 2 +- wqflask/base/trait.py | 6 +- wqflask/db/call.py | 10 +- wqflask/db/webqtlDatabaseFunction.py | 8 +- wqflask/maintenance/gen_select_dataset.py | 2 +- wqflask/maintenance/quantile_normalize.py | 2 +- wqflask/maintenance/set_resource_defaults.py | 2 +- wqflask/utility/Plot.py | 48 +++--- wqflask/utility/elasticsearch_tools.py | 2 +- wqflask/utility/logger.py | 26 +-- wqflask/utility/pillow_utils.py | 4 +- wqflask/utility/startup_config.py | 2 +- wqflask/utility/svg.py | 130 +++++++------- wqflask/utility/tools.py | 32 ++-- wqflask/utility/webqtlUtil.py | 16 +- wqflask/wqflask/api/router.py | 8 +- wqflask/wqflask/correlation/show_corr_results.py | 12 +- .../wqflask/correlation_matrix/show_corr_matrix.py | 8 +- wqflask/wqflask/do_search.py | 4 +- .../wqflask/external_tools/send_to_geneweaver.py | 8 +- .../marker_regression/display_mapping_results.py | 186 ++++++++++----------- wqflask/wqflask/marker_regression/plink_mapping.py | 6 +- wqflask/wqflask/marker_regression/run_mapping.py | 2 +- wqflask/wqflask/search_results.py | 2 +- wqflask/wqflask/show_trait/SampleList.py | 2 +- wqflask/wqflask/user_manager.py | 2 +- wqflask/wqflask/views.py | 8 +- 30 files changed, 287 insertions(+), 287 deletions(-) diff --git a/etc/default_settings.py b/etc/default_settings.py index f368237b..82c605da 100644 --- a/etc/default_settings.py +++ b/etc/default_settings.py @@ -24,7 +24,7 @@ import os import sys -GN_VERSION = open("../etc/VERSION","r").read() +GN_VERSION = open("../etc/VERSION", "r").read() GN_SERVER_URL = "http://localhost:8880/" # REST API server # ---- MySQL diff --git a/scripts/maintenance/QTL_Reaper_v6.py b/scripts/maintenance/QTL_Reaper_v6.py index 2fbeb53b..35f2d1a1 100755 --- a/scripts/maintenance/QTL_Reaper_v6.py +++ b/scripts/maintenance/QTL_Reaper_v6.py @@ -7,7 +7,7 @@ import reaper import MySQLdb import time -con = MySQLdb.Connect(db='db_webqtl',user='username',passwd='', host="localhost") +con = MySQLdb.Connect(db='db_webqtl', user='username', passwd='', host="localhost") cursor = con.cursor() genotypeDir = '/gnshare/gn/web/genotypes/' @@ -102,7 +102,7 @@ for ProbeSetFreezeId in ProbeSetFreezeIds: kj += 1 if kj%1000==0: - print((ProbeSetFreezeId, InbredSets[InbredSetId],kj)) + print((ProbeSetFreezeId, InbredSets[InbredSetId], kj)) print(ProbeSetFreezeIds) diff --git a/scripts/maintenance/readProbeSetMean_v7.py b/scripts/maintenance/readProbeSetMean_v7.py index 864b4e08..59a51cf9 100755 --- a/scripts/maintenance/readProbeSetMean_v7.py +++ b/scripts/maintenance/readProbeSetMean_v7.py @@ -39,7 +39,7 @@ fp = open("%s" % input_file_name, 'rb') try: passwd = getpass.getpass('Please enter mysql password here : ') - con = MySQLdb.Connect(db='db_webqtl',host='localhost', user='username',passwd=passwd) + con = MySQLdb.Connect(db='db_webqtl', host='localhost', user='username', passwd=passwd) db = con.cursor() print("You have successfully connected to mysql.\n") @@ -60,14 +60,14 @@ print('Checking if each line have same number of members') GeneList = [] isCont = 1 header = fp.readline() -header = string.split(string.strip(header),'\t') +header = string.split(string.strip(header), '\t') header = list(map(string.strip, header)) nfield = len(header) line = fp.readline() kj=0 while line: - line2 = string.split(string.strip(line),'\t') + line2 = string.split(string.strip(line), '\t') line2 = list(map(string.strip, line2)) if len(line2) != nfield: print(("Error : " + line)) @@ -78,7 +78,7 @@ while line: kj+=1 if kj%100000 == 0: - print(('checked ',kj,' lines')) + print(('checked ', kj, ' lines')) GeneList = sorted(map(string.lower, GeneList)) @@ -86,7 +86,7 @@ if isCont==0: sys.exit(0) -print(('used ',time.time()-time0,' seconds')) +print(('used ', time.time()-time0, ' seconds')) ######################################################################### # # Check if each strain exist in database @@ -98,7 +98,7 @@ print('Checking if each strain exist in database') isCont = 1 fp.seek(0) header = fp.readline() -header = string.split(string.strip(header),'\t') +header = string.split(string.strip(header), '\t') header = list(map(string.strip, header)) header = list(map(translateAlias, header)) header = header[dataStart:] @@ -108,14 +108,14 @@ for item in header: db.execute('select Id from Strain where Name = "%s"' % item) Ids.append(db.fetchall()[0][0]) except: - print((item,'does not exist, check the if the strain name is correct')) + print((item, 'does not exist, check the if the strain name is correct')) isCont=0 if isCont==0: sys.exit(0) -print(('used ',time.time()-time0,' seconds')) +print(('used ', time.time()-time0, ' seconds')) ######################################################################## # # Check if each ProbeSet exist in database @@ -126,7 +126,7 @@ print('Check if each ProbeSet exist in database') ##---- find PID is name or target ----## line = fp.readline() line = fp.readline() -line2 = string.split(string.strip(line),'\t') +line2 = string.split(string.strip(line), '\t') line2 = list(map(string.strip, line2)) PId = line2[0] @@ -185,7 +185,7 @@ if isCont==0: sys.exit(0) -print(('used ',time.time()-time0,' seconds')) +print(('used ', time.time()-time0, ' seconds')) ######################################################################### # # Insert data into database @@ -200,7 +200,7 @@ results = db.fetchall() NameIds = {} for item in results: NameIds[item[0]] = item[1] -print(('used ',time.time()-time0,' seconds')) +print(('used ', time.time()-time0, ' seconds')) print('inserting data') @@ -220,7 +220,7 @@ kj = 0 values1 = [] values2 = [] while line: - line2 = string.split(string.strip(line),'\t') + line2 = string.split(string.strip(line), '\t') line2 = list(map(string.strip, line2)) PId = line2[0] recordId = NameIds[PId] @@ -253,8 +253,8 @@ while line: values1=[] values2=[] - print(('Inserted ', kj,' lines')) - print(('used ',time.time()-time0,' seconds')) + print(('Inserted ', kj, ' lines')) + print(('used ', time.time()-time0, ' seconds')) line = fp.readline() diff --git a/setup.py b/setup.py index a9b71fab..8436dcd3 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup(name='genenetwork2', url = "https://github.com/genenetwork/genenetwork2/blob/master/README.md", description = 'Website and tools for genetics.', include_package_data=True, - packages=['wqflask','etc'], + packages=['wqflask', 'etc'], scripts=['bin/genenetwork2'], # package_data = { # 'etc': ['*.py'] diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index 2b8f2e72..05b272c3 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -193,7 +193,7 @@ class GeneralTrait(object): ''' if self.chr and self.mb: - self.location = 'Chr %s @ %s Mb' % (self.chr,self.mb) + self.location = 'Chr %s @ %s Mb' % (self.chr, self.mb) elif self.chr: self.location = 'Chr %s @ Unknown position' % (self.chr) else: @@ -440,7 +440,7 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): #XZ, 05/08/2009: We also should use Geno.Id to find marker instead of just using Geno.Name # to avoid the problem of same marker name from different species. elif dataset.type == 'Geno': - display_fields_string = string.join(dataset.display_fields,',Geno.') + display_fields_string = string.join(dataset.display_fields, ',Geno.') display_fields_string = 'Geno.' + display_fields_string query = """ SELECT %s @@ -459,7 +459,7 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): query = """SELECT %s FROM %s WHERE Name = %s""" logger.sql(query) trait_info = g.db.execute(query, - (string.join(dataset.display_fields,','), + (string.join(dataset.display_fields, ','), dataset.type, trait.name)).fetchone() if trait_info: diff --git a/wqflask/db/call.py b/wqflask/db/call.py index 82cfebb4..3b8f782e 100644 --- a/wqflask/db/call.py +++ b/wqflask/db/call.py @@ -26,8 +26,8 @@ GN_SERVER result when set (which should return a Tuple) else: res2 = result, if LOG_SQL: - logger.debug("Replaced SQL call",query) - logger.debug(path,res2) + logger.debug("Replaced SQL call", query) + logger.debug(path, res2) return res2 else: return fetchone(query) @@ -37,7 +37,7 @@ def fetchone(query): original fetchone, but with logging) """ - with Bench("SQL",LOG_SQL): + with Bench("SQL", LOG_SQL): def helper(query): res = g.db.execute(query) return res.fetchone() @@ -48,7 +48,7 @@ def fetchall(query): original fetchall, but with logging) """ - with Bench("SQL",LOG_SQL): + with Bench("SQL", LOG_SQL): def helper(query): res = g.db.execute(query) return res.fetchall() @@ -58,7 +58,7 @@ def gn_server(path): """Return JSON record by calling GN_SERVER """ - with Bench("GN_SERVER",LOG_SQL): + with Bench("GN_SERVER", LOG_SQL): res = urllib.request.urlopen(GN_SERVER_URL+path) rest = res.read() res2 = json.loads(rest) diff --git a/wqflask/db/webqtlDatabaseFunction.py b/wqflask/db/webqtlDatabaseFunction.py index 8a9dc79d..2805febd 100644 --- a/wqflask/db/webqtlDatabaseFunction.py +++ b/wqflask/db/webqtlDatabaseFunction.py @@ -35,13 +35,13 @@ def retrieve_species(group): """Get the species of a group (e.g. returns string "mouse" on "BXD" """ - result = fetch1("select Species.Name from Species, InbredSet where InbredSet.Name = '%s' and InbredSet.SpeciesId = Species.Id" % (group),"/cross/"+group+".json",lambda r: (r["species"],))[0] - logger.debug("retrieve_species result:",result) + result = fetch1("select Species.Name from Species, InbredSet where InbredSet.Name = '%s' and InbredSet.SpeciesId = Species.Id" % (group), "/cross/"+group+".json", lambda r: (r["species"],))[0] + logger.debug("retrieve_species result:", result) return result def retrieve_species_id(group): - result = fetch1("select SpeciesId from InbredSet where Name = '%s'" % (group),"/cross/"+group+".json",lambda r: (r["species_id"],))[0] - logger.debug("retrieve_species_id result:",result) + result = fetch1("select SpeciesId from InbredSet where Name = '%s'" % (group), "/cross/"+group+".json", lambda r: (r["species_id"],))[0] + logger.debug("retrieve_species_id result:", result) return result diff --git a/wqflask/maintenance/gen_select_dataset.py b/wqflask/maintenance/gen_select_dataset.py index 78217587..d12b328f 100644 --- a/wqflask/maintenance/gen_select_dataset.py +++ b/wqflask/maintenance/gen_select_dataset.py @@ -41,7 +41,7 @@ from __future__ import print_function, division import sys # NEW: Note we prepend the current path - otherwise a guix instance of GN2 may be used instead -sys.path.insert(0,'./') +sys.path.insert(0, './') # NEW: import app to avoid a circular dependency on utility.tools from wqflask import app diff --git a/wqflask/maintenance/quantile_normalize.py b/wqflask/maintenance/quantile_normalize.py index 82b695f4..43edfd13 100644 --- a/wqflask/maintenance/quantile_normalize.py +++ b/wqflask/maintenance/quantile_normalize.py @@ -1,7 +1,7 @@ from __future__ import absolute_import, print_function, division import sys -sys.path.insert(0,'./') +sys.path.insert(0, './') diff --git a/wqflask/maintenance/set_resource_defaults.py b/wqflask/maintenance/set_resource_defaults.py index 54fd8e7e..d53a255b 100644 --- a/wqflask/maintenance/set_resource_defaults.py +++ b/wqflask/maintenance/set_resource_defaults.py @@ -22,7 +22,7 @@ import sys import json # NEW: Note we prepend the current path - otherwise a guix instance of GN2 may be used instead -sys.path.insert(0,'./') +sys.path.insert(0, './') # NEW: import app to avoid a circular dependency on utility.tools from wqflask import app diff --git a/wqflask/utility/Plot.py b/wqflask/utility/Plot.py index c9053dde..4f6e694e 100644 --- a/wqflask/utility/Plot.py +++ b/wqflask/utility/Plot.py @@ -58,7 +58,7 @@ def cformat(d, rank=0): strD = "%2.6f" % d if rank == 0: - while strD[-1] in ('0','.'): + while strD[-1] in ('0', '.'): if strD[-1] == '0' and strD[-2] == '.' and len(strD) <= 4: break elif strD[-1] == '.': @@ -162,7 +162,7 @@ def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLab j = int((item-xLow)/step) Count[j] += 1 - yLow, yTop, stepY=detScale(0,max(Count)) + yLow, yTop, stepY=detScale(0, max(Count)) #draw data xScale = plotWidth/(xTop-xLow) @@ -174,7 +174,7 @@ def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLab xc = (dataXY[i]-xLow)*xScale+xLeftOffset yc =-(count-yLow)*yScale+yTopOffset+plotHeight im_drawer.rectangle( - xy=((xc+2,yc),(xc+barWidth-2,yTopOffset+plotHeight)), + xy=((xc+2, yc), (xc+barWidth-2, yTopOffset+plotHeight)), outline=barColor, fill=barColor) #draw drawing region @@ -183,81 +183,81 @@ def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLab ) #draw scale - scaleFont=ImageFont.truetype(font=COUR_FILE,size=11) + scaleFont=ImageFont.truetype(font=COUR_FILE, size=11) x=xLow for i in range(int(stepX)+1): xc=xLeftOffset+(x-xLow)*xScale im_drawer.line( - xy=((xc,yTopOffset+plotHeight),(xc,yTopOffset+plotHeight+5)), + xy=((xc, yTopOffset+plotHeight), (xc, yTopOffset+plotHeight+5)), fill=axesColor) strX = cformat(d=x, rank=0) im_drawer.text( text=strX, - xy=(xc-im_drawer.textsize(strX,font=scaleFont)[0]/2, - yTopOffset+plotHeight+14),font=scaleFont) + xy=(xc-im_drawer.textsize(strX, font=scaleFont)[0]/2, + yTopOffset+plotHeight+14), font=scaleFont) x+= (xTop - xLow)/stepX y=yLow for i in range(int(stepY)+1): yc=yTopOffset+plotHeight-(y-yLow)*yScale - im_drawer.line(xy=((xLeftOffset,yc),(xLeftOffset-5,yc)), fill=axesColor) + im_drawer.line(xy=((xLeftOffset, yc), (xLeftOffset-5, yc)), fill=axesColor) strY = "%d" %y im_drawer.text( text=strY, - xy=(xLeftOffset-im_drawer.textsize(strY,font=scaleFont)[0]-6,yc+5), + xy=(xLeftOffset-im_drawer.textsize(strY, font=scaleFont)[0]-6, yc+5), font=scaleFont) y+= (yTop - yLow)/stepY #draw label - labelFont=ImageFont.truetype(font=TAHOMA_FILE,size=17) + labelFont=ImageFont.truetype(font=TAHOMA_FILE, size=17) if XLabel: im_drawer.text( text=XLabel, xy=(xLeftOffset+( - plotWidth-im_drawer.textsize(XLabel,font=labelFont)[0])/2.0, + plotWidth-im_drawer.textsize(XLabel, font=labelFont)[0])/2.0, yTopOffset+plotHeight+yBottomOffset-10), - font=labelFont,fill=labelColor) + font=labelFont, fill=labelColor) if YLabel: draw_rotated_text(canvas, text=YLabel, xy=(19, yTopOffset+plotHeight-( plotHeight-im_drawer.textsize( - YLabel,font=labelFont)[0])/2.0), + YLabel, font=labelFont)[0])/2.0), font=labelFont, fill=labelColor, angle=90) - labelFont=ImageFont.truetype(font=VERDANA_FILE,size=16) + labelFont=ImageFont.truetype(font=VERDANA_FILE, size=16) if title: im_drawer.text( text=title, xy=(xLeftOffset+(plotWidth-im_drawer.textsize( - title,font=labelFont)[0])/2.0, + title, font=labelFont)[0])/2.0, 20), - font=labelFont,fill=labelColor) + font=labelFont, fill=labelColor) # This function determines the scale of the plot -def detScaleOld(min,max): +def detScaleOld(min, max): if min>=max: return None elif min == -1.0 and max == 1.0: - return [-1.2,1.2,12] + return [-1.2, 1.2, 12] else: a=max-min b=floor(log10(a)) - c=pow(10.0,b) + c=pow(10.0, b) if a < c*5.0: c/=2.0 #print a,b,c low=c*floor(min/c) high=c*ceil(max/c) - return [low,high,round((high-low)/c)] + return [low, high, round((high-low)/c)] def detScale(min=0,max=0): if min>=max: return None elif min == -1.0 and max == 1.0: - return [-1.2,1.2,12] + return [-1.2, 1.2, 12] else: a=max-min if max != 0: @@ -269,7 +269,7 @@ def detScale(min=0,max=0): min -= 0.1*a a=max-min b=floor(log10(a)) - c=pow(10.0,b) + c=pow(10.0, b) low=c*floor(min/c) high=c*ceil(max/c) n = round((high-low)/c) @@ -287,7 +287,7 @@ def detScale(min=0,max=0): high=c*ceil(max/c) n = round((high-low)/c) - return [low,high,n] + return [low, high, n] def bluefunc(x): return 1.0 / (1.0 + exp(-10*(x-0.6))) @@ -296,7 +296,7 @@ def redfunc(x): return 1.0 / (1.0 + exp(10*(x-0.5))) def greenfunc(x): - return 1 - pow(redfunc(x+0.2),2) - bluefunc(x-0.3) + return 1 - pow(redfunc(x+0.2), 2) - bluefunc(x-0.3) def colorSpectrum(n=100): multiple = 10 diff --git a/wqflask/utility/elasticsearch_tools.py b/wqflask/utility/elasticsearch_tools.py index 15cdd0bc..a5580811 100644 --- a/wqflask/utility/elasticsearch_tools.py +++ b/wqflask/utility/elasticsearch_tools.py @@ -59,7 +59,7 @@ def get_elasticsearch_connection(for_user=True): try: assert(ELASTICSEARCH_HOST) assert(ELASTICSEARCH_PORT) - logger.info("ES HOST",ELASTICSEARCH_HOST) + logger.info("ES HOST", ELASTICSEARCH_HOST) es = Elasticsearch([{ "host": ELASTICSEARCH_HOST, "port": ELASTICSEARCH_PORT diff --git a/wqflask/utility/logger.py b/wqflask/utility/logger.py index 510b1041..e904eb94 100644 --- a/wqflask/utility/logger.py +++ b/wqflask/utility/logger.py @@ -42,10 +42,10 @@ class GNLogger: """ - def __init__(self,name): + def __init__(self, name): self.logger = logging.getLogger(name) - def setLevel(self,value): + def setLevel(self, value): """Set the undelying log level""" self.logger.setLevel(value) @@ -54,7 +54,7 @@ class GNLogger: level=num to filter on LOG_LEVEL_DEBUG. """ - self.collect(self.logger.debug,*args) + self.collect(self.logger.debug, *args) def debug20(self,*args): """Call logging.debug for multiple args. Use level=num to filter on @@ -63,15 +63,15 @@ LOG_LEVEL_DEBUG (NYI). """ if level <= LOG_LEVEL_DEBUG: if self.logger.getEffectiveLevel() < 20: - self.collect(self.logger.debug,*args) + self.collect(self.logger.debug, *args) def info(self,*args): """Call logging.info for multiple args""" - self.collect(self.logger.info,*args) + self.collect(self.logger.info, *args) def warning(self,*args): """Call logging.warning for multiple args""" - self.collect(self.logger.warning,*args) + self.collect(self.logger.warning, *args) # self.logger.warning(self.collect(*args)) def error(self,*args): @@ -79,13 +79,13 @@ LOG_LEVEL_DEBUG (NYI). now = datetime.datetime.utcnow() time_str = now.strftime('%H:%M:%S UTC %Y%m%d') l = [time_str]+list(args) - self.collect(self.logger.error,*l) + self.collect(self.logger.error, *l) def infof(self,*args): """Call logging.info for multiple args lazily""" # only evaluate function when logging if self.logger.getEffectiveLevel() < 30: - self.collectf(self.logger.debug,*args) + self.collectf(self.logger.debug, *args) def debugf(self,level=0,*args): """Call logging.debug for multiple args lazily and handle @@ -95,15 +95,15 @@ LOG_LEVEL_DEBUG (NYI). # only evaluate function when logging if level <= LOG_LEVEL_DEBUG: if self.logger.getEffectiveLevel() < 20: - self.collectf(self.logger.debug,*args) + self.collectf(self.logger.debug, *args) def sql(self, sqlcommand, fun = None): """Log SQL command, optionally invoking a timed fun""" if LOG_SQL: caller = stack()[1][3] - if caller in ['fetchone','fetch1','fetchall']: + if caller in ['fetchone', 'fetch1', 'fetchall']: caller = stack()[2][3] - self.info(caller,sqlcommand) + self.info(caller, sqlcommand) if fun: result = fun(sqlcommand) if LOG_SQL: @@ -119,7 +119,7 @@ LOG_LEVEL_DEBUG (NYI). if isinstance(a, str): out = out + a else: - out = out + pf(a,width=160) + out = out + pf(a, width=160) fun(out) def collectf(self,fun,*args): @@ -134,7 +134,7 @@ LOG_LEVEL_DEBUG (NYI). if isinstance(a, str): out = out + a else: - out = out + pf(a,width=160) + out = out + pf(a, width=160) fun(out) # Get the module logger. You can override log levels at the diff --git a/wqflask/utility/pillow_utils.py b/wqflask/utility/pillow_utils.py index dfbf3e19..1e2ed075 100644 --- a/wqflask/utility/pillow_utils.py +++ b/wqflask/utility/pillow_utils.py @@ -10,9 +10,9 @@ WHITE = ImageColor.getrgb("white") def draw_rotated_text(canvas, text, font, xy, fill=BLACK, angle=-90): # type: (Image, str, ImageFont, tuple, ImageColor, int) """Utility function draw rotated text""" - tmp_img = Image.new("RGBA", font.getsize(text), color=(0,0,0,0)) + tmp_img = Image.new("RGBA", font.getsize(text), color=(0, 0, 0, 0)) draw_text = ImageDraw.Draw(tmp_img) - draw_text.text(text=text, xy=(0,0), font=font, fill=fill) + draw_text.text(text=text, xy=(0, 0), font=font, fill=fill) tmp_img2 = tmp_img.rotate(angle, expand=1) tmp_img2.save("/tmp/{}.png".format(text), format="png") canvas.paste(im=tmp_img2, box=tuple([int(i) for i in xy])) diff --git a/wqflask/utility/startup_config.py b/wqflask/utility/startup_config.py index 42ead709..f1aaebb6 100644 --- a/wqflask/utility/startup_config.py +++ b/wqflask/utility/startup_config.py @@ -36,4 +36,4 @@ def app_config(): # import utility.elasticsearch_tools as es # es.test_elasticsearch_connection() - print(("GN2 is running. Visit %s[http://localhost:%s/%s](%s)" % (BLUE,str(port),ENDC,get_setting("WEBSERVER_URL")))) + print(("GN2 is running. Visit %s[http://localhost:%s/%s](%s)" % (BLUE, str(port), ENDC, get_setting("WEBSERVER_URL")))) diff --git a/wqflask/utility/svg.py b/wqflask/utility/svg.py index 874ada9d..19eda0ce 100644 --- a/wqflask/utility/svg.py +++ b/wqflask/utility/svg.py @@ -447,7 +447,7 @@ class rect(SVGelement): if width == None or height == None: raise ValueError('both height and width are required') - SVGelement.__init__(self,'rect',{'width':width,'height':height},**args) + SVGelement.__init__(self, 'rect', {'width':width,'height':height}, **args) if x!=None: self.attributes['x']=x if y!=None: @@ -468,7 +468,7 @@ class ellipse(SVGelement): if rx==None or ry== None: raise ValueError('both rx and ry are required') - SVGelement.__init__(self,'ellipse',{'rx':rx,'ry':ry},**args) + SVGelement.__init__(self, 'ellipse', {'rx':rx,'ry':ry}, **args) if cx!=None: self.attributes['cx']=cx if cy!=None: @@ -489,7 +489,7 @@ class circle(SVGelement): def __init__(self,cx=None,cy=None,r=None,fill=None,stroke=None,stroke_width=None,**args): if r==None: raise ValueError('r is required') - SVGelement.__init__(self,'circle',{'r':r},**args) + SVGelement.__init__(self, 'circle', {'r':r}, **args) if cx!=None: self.attributes['cx']=cx if cy!=None: @@ -508,7 +508,7 @@ class point(circle): very small rectangle if you use many points because a circle is difficult to render. """ def __init__(self,x,y,fill='black',**args): - circle.__init__(self,x,y,1,fill,**args) + circle.__init__(self, x, y, 1, fill, **args) class line(SVGelement): """l=line(x1,y1,x2,y2,stroke,stroke_width,**args) @@ -516,7 +516,7 @@ class line(SVGelement): A line is defined by a begin x,y pair and an end x,y pair """ def __init__(self,x1=None,y1=None,x2=None,y2=None,stroke=None,stroke_width=None,**args): - SVGelement.__init__(self,'line',**args) + SVGelement.__init__(self, 'line', **args) if x1!=None: self.attributes['x1']=x1 if y1!=None: @@ -536,7 +536,7 @@ class polyline(SVGelement): a polyline is defined by a list of xy pairs """ def __init__(self,points,fill=None,stroke=None,stroke_width=None,**args): - SVGelement.__init__(self,'polyline',{'points':_xypointlist(points)},**args) + SVGelement.__init__(self, 'polyline', {'points':_xypointlist(points)}, **args) if fill!=None: self.attributes['fill']=fill if stroke_width!=None: @@ -550,7 +550,7 @@ class polygon(SVGelement): a polygon is defined by a list of xy pairs """ def __init__(self,points,fill=None,stroke=None,stroke_width=None,**args): - SVGelement.__init__(self,'polygon',{'points':_xypointlist(points)},**args) + SVGelement.__init__(self, 'polygon', {'points':_xypointlist(points)}, **args) if fill!=None: self.attributes['fill']=fill if stroke_width!=None: @@ -564,7 +564,7 @@ class path(SVGelement): a path is defined by a path object and optional width, stroke and fillcolor """ def __init__(self,pathdata,fill=None,stroke=None,stroke_width=None,id=None,**args): - SVGelement.__init__(self,'path',{'d':str(pathdata)},**args) + SVGelement.__init__(self, 'path', {'d':str(pathdata)}, **args) if stroke!=None: self.attributes['stroke']=stroke if fill!=None: @@ -581,7 +581,7 @@ class text(SVGelement): a text element can bge used for displaying text on the screen """ def __init__(self,x=None,y=None,text=None,font_size=None,font_family=None,text_anchor=None,**args): - SVGelement.__init__(self,'text',**args) + SVGelement.__init__(self, 'text', **args) if x!=None: self.attributes['x']=x if y!=None: @@ -602,7 +602,7 @@ class textpath(SVGelement): a textpath places a text on a path which is referenced by a link. """ def __init__(self,link,text=None,**args): - SVGelement.__init__(self,'textPath',{'xlink:href':link},**args) + SVGelement.__init__(self, 'textPath', {'xlink:href':link}, **args) if text!=None: self.text=text @@ -614,7 +614,7 @@ class pattern(SVGelement): in x and y to cover the areas to be painted. """ def __init__(self,x=None,y=None,width=None,height=None,patternUnits=None,**args): - SVGelement.__init__(self,'pattern',**args) + SVGelement.__init__(self, 'pattern', **args) if x!=None: self.attributes['x']=x if y!=None: @@ -633,7 +633,7 @@ class title(SVGelement): add at least one to the root svg element """ def __init__(self,text=None,**args): - SVGelement.__init__(self,'title',**args) + SVGelement.__init__(self, 'title', **args) if text!=None: self.text=text @@ -644,7 +644,7 @@ class description(SVGelement): Add this element before adding other elements. """ def __init__(self,text=None,**args): - SVGelement.__init__(self,'desc',**args) + SVGelement.__init__(self, 'desc', **args) if text!=None: self.text=text @@ -655,7 +655,7 @@ class lineargradient(SVGelement): stop elements van be added to define the gradient colors. """ def __init__(self,x1=None,y1=None,x2=None,y2=None,id=None,**args): - SVGelement.__init__(self,'linearGradient',**args) + SVGelement.__init__(self, 'linearGradient', **args) if x1!=None: self.attributes['x1']=x1 if y1!=None: @@ -674,7 +674,7 @@ class radialgradient(SVGelement): stop elements van be added to define the gradient colors. """ def __init__(self,cx=None,cy=None,r=None,fx=None,fy=None,id=None,**args): - SVGelement.__init__(self,'radialGradient',**args) + SVGelement.__init__(self, 'radialGradient', **args) if cx!=None: self.attributes['cx']=cx if cy!=None: @@ -694,7 +694,7 @@ class stop(SVGelement): Puts a stop color at the specified radius """ def __init__(self,offset,stop_color=None,**args): - SVGelement.__init__(self,'stop',{'offset':offset},**args) + SVGelement.__init__(self, 'stop', {'offset':offset}, **args) if stop_color!=None: self.attributes['stop-color']=stop_color @@ -704,7 +704,7 @@ class style(SVGelement): Add a CDATA element to this element for defing in line stylesheets etc.. """ def __init__(self,type,cdata=None,**args): - SVGelement.__init__(self,'style',{'type':type},cdata=cdata, **args) + SVGelement.__init__(self, 'style', {'type':type}, cdata=cdata, **args) class image(SVGelement): @@ -715,7 +715,7 @@ class image(SVGelement): def __init__(self,url,x=None,y=None,width=None,height=None,**args): if width==None or height==None: raise ValueError('both height and width are required') - SVGelement.__init__(self,'image',{'xlink:href':url,'width':width,'height':height},**args) + SVGelement.__init__(self, 'image', {'xlink:href':url,'width':width,'height':height}, **args) if x!=None: self.attributes['x']=x if y!=None: @@ -727,7 +727,7 @@ class cursor(SVGelement): defines a custom cursor for a element or a drawing """ def __init__(self,url,**args): - SVGelement.__init__(self,'cursor',{'xlink:href':url},**args) + SVGelement.__init__(self, 'cursor', {'xlink:href':url}, **args) class marker(SVGelement): @@ -737,7 +737,7 @@ class marker(SVGelement): add an element to it which should be used as a marker. """ def __init__(self,id=None,viewBox=None,refx=None,refy=None,markerWidth=None,markerHeight=None,**args): - SVGelement.__init__(self,'marker',**args) + SVGelement.__init__(self, 'marker', **args) if id!=None: self.attributes['id']=id if viewBox!=None: @@ -758,7 +758,7 @@ class group(SVGelement): g.addElement(SVGelement) """ def __init__(self,id=None,**args): - SVGelement.__init__(self,'g',**args) + SVGelement.__init__(self, 'g', **args) if id!=None: self.attributes['id']=id @@ -772,7 +772,7 @@ class symbol(SVGelement): """ def __init__(self,id=None,viewBox=None,**args): - SVGelement.__init__(self,'symbol',**args) + SVGelement.__init__(self, 'symbol', **args) if id!=None: self.attributes['id']=id if viewBox!=None: @@ -784,7 +784,7 @@ class defs(SVGelement): container for defining elements """ def __init__(self,**args): - SVGelement.__init__(self,'defs',**args) + SVGelement.__init__(self, 'defs', **args) class switch(SVGelement): """sw=switch(**args) @@ -794,7 +794,7 @@ class switch(SVGelement): Refer to the SVG specification for details. """ def __init__(self,**args): - SVGelement.__init__(self,'switch',**args) + SVGelement.__init__(self, 'switch', **args) class use(SVGelement): @@ -803,7 +803,7 @@ class use(SVGelement): references a symbol by linking to its id and its position, height and width """ def __init__(self,link,x=None,y=None,width=None,height=None,**args): - SVGelement.__init__(self,'use',{'xlink:href':link},**args) + SVGelement.__init__(self, 'use', {'xlink:href':link}, **args) if x!=None: self.attributes['x']=x if y!=None: @@ -822,14 +822,14 @@ class link(SVGelement): a.addElement(SVGelement) """ def __init__(self,link='',**args): - SVGelement.__init__(self,'a',{'xlink:href':link},**args) + SVGelement.__init__(self, 'a', {'xlink:href':link}, **args) class view(SVGelement): """v=view(id,**args) a view can be used to create a view with different attributes""" def __init__(self,id=None,**args): - SVGelement.__init__(self,'view',**args) + SVGelement.__init__(self, 'view', **args) if id!=None: self.attributes['id']=id @@ -840,7 +840,7 @@ class script(SVGelement): """ def __init__(self,type,cdata=None,**args): - SVGelement.__init__(self,'script',{'type':type},cdata=cdata,**args) + SVGelement.__init__(self, 'script', {'type':type}, cdata=cdata, **args) class animate(SVGelement): """an=animate(attribute,from,to,during,**args) @@ -848,7 +848,7 @@ class animate(SVGelement): animates an attribute. """ def __init__(self,attribute,fr=None,to=None,dur=None,**args): - SVGelement.__init__(self,'animate',{'attributeName':attribute},**args) + SVGelement.__init__(self, 'animate', {'attributeName':attribute}, **args) if fr!=None: self.attributes['from']=fr if to!=None: @@ -862,7 +862,7 @@ class animateMotion(SVGelement): animates a SVGelement over the given path in dur seconds """ def __init__(self,pathdata,dur,**args): - SVGelement.__init__(self,'animateMotion',**args) + SVGelement.__init__(self, 'animateMotion', **args) if pathdata!=None: self.attributes['path']=str(pathdata) if dur!=None: @@ -874,7 +874,7 @@ class animateTransform(SVGelement): transform an element from and to a value. """ def __init__(self,type=None,fr=None,to=None,dur=None,**args): - SVGelement.__init__(self,'animateTransform',{'attributeName':'transform'},**args) + SVGelement.__init__(self, 'animateTransform', {'attributeName':'transform'}, **args) # As far as I know the attributeName is always transform if type!=None: self.attributes['type']=type @@ -890,7 +890,7 @@ class animateColor(SVGelement): Animates the color of a element """ def __init__(self,attribute,type=None,fr=None,to=None,dur=None,**args): - SVGelement.__init__(self,'animateColor',{'attributeName':attribute},**args) + SVGelement.__init__(self, 'animateColor', {'attributeName':attribute}, **args) if type!=None: self.attributes['type']=type if fr!=None: @@ -905,7 +905,7 @@ class set(SVGelement): sets an attribute to a value for a """ def __init__(self,attribute,to=None,dur=None,**args): - SVGelement.__init__(self,'set',{'attributeName':attribute},**args) + SVGelement.__init__(self, 'set', {'attributeName':attribute}, **args) if to!=None: self.attributes['to']=to if dur!=None: @@ -929,7 +929,7 @@ class svg(SVGelement): d.toXml() """ def __init__(self,viewBox=None, width=None, height=None,**args): - SVGelement.__init__(self,'svg',**args) + SVGelement.__init__(self, 'svg', **args) if viewBox!=None: self.attributes['viewBox']=_viewboxlist(viewBox) if width!=None: @@ -952,7 +952,7 @@ class drawing: def __init__(self, entity={}): self.svg=None self.entity = entity - def setSVG(self,svg): + def setSVG(self, svg): self.svg=svg # Voeg een element toe aan de grafiek toe. if use_dom_implementation==0: @@ -967,12 +967,12 @@ class drawing: xml.write("\n" % (item, self.entity[item])) xml.write("]") xml.write(">\n") - self.svg.toXml(0,xml) + self.svg.toXml(0, xml) if not filename: if compress: import gzip f=cStringIO.StringIO() - zf=gzip.GzipFile(fileobj=f,mode='wb') + zf=gzip.GzipFile(fileobj=f, mode='wb') zf.write(xml.getvalue()) zf.close() f.seek(0) @@ -982,11 +982,11 @@ class drawing: else: if filename[-4:]=='svgz': import gzip - f=gzip.GzipFile(filename=filename,mode="wb", compresslevel=9) + f=gzip.GzipFile(filename=filename, mode="wb", compresslevel=9) f.write(xml.getvalue()) f.close() else: - f=file(filename,'w') + f=file(filename, 'w') f.write(xml.getvalue()) f.close() @@ -997,40 +997,40 @@ class drawing: writes a svg drawing to the screen or to a file compresses if filename ends with svgz or if compress is true """ - doctype = implementation.createDocumentType('svg',"-//W3C//DTD SVG 1.0//EN""",'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd ') + doctype = implementation.createDocumentType('svg', "-//W3C//DTD SVG 1.0//EN""", 'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd ') global root # root is defined global so it can be used by the appender. Its also possible to use it as an arugument but # that is a bit messy. - root=implementation.createDocument(None,None,doctype) + root=implementation.createDocument(None, None, doctype) # Create the xml document. global appender - def appender(element,elementroot): + def appender(element, elementroot): """This recursive function appends elements to an element and sets the attributes and type. It stops when alle elements have been appended""" if element.namespace: - e=root.createElementNS(element.namespace,element.type) + e=root.createElementNS(element.namespace, element.type) else: e=root.createElement(element.type) if element.text: textnode=root.createTextNode(element.text) e.appendChild(textnode) for attribute in list(element.attributes.keys()): #in element.attributes is supported from python 2.2 - e.setAttribute(attribute,str(element.attributes[attribute])) + e.setAttribute(attribute, str(element.attributes[attribute])) if element.elements: for el in element.elements: - e=appender(el,e) + e=appender(el, e) elementroot.appendChild(e) return elementroot - root=appender(self.svg,root) + root=appender(self.svg, root) if not filename: import cStringIO xml=cStringIO.StringIO() - PrettyPrint(root,xml) + PrettyPrint(root, xml) if compress: import gzip f=cStringIO.StringIO() - zf=gzip.GzipFile(fileobj=f,mode='wb') + zf=gzip.GzipFile(fileobj=f, mode='wb') zf.write(xml.getvalue()) zf.close() f.seek(0) @@ -1043,13 +1043,13 @@ class drawing: import gzip import cStringIO xml=cStringIO.StringIO() - PrettyPrint(root,xml) - f=gzip.GzipFile(filename=filename,mode='wb',compresslevel=9) + PrettyPrint(root, xml) + f=gzip.GzipFile(filename=filename, mode='wb', compresslevel=9) f.write(xml.getvalue()) f.close() else: - f=open(filename,'w') - PrettyPrint(root,f) + f=open(filename, 'w') + PrettyPrint(root, f) f.close() except: print(("Cannot write SVG file: " + filename)) @@ -1070,32 +1070,32 @@ if __name__=='__main__': d=drawing() - s=svg((0,0,100,100)) - r=rect(-100,-100,300,300,'cyan') + s=svg((0, 0, 100, 100)) + r=rect(-100, -100, 300, 300, 'cyan') s.addElement(r) t=title('SVGdraw Demo') s.addElement(t) g=group('animations') - e=ellipse(0,0,5,2) + e=ellipse(0, 0, 5, 2) g.addElement(e) - c=circle(0,0,1,'red') + c=circle(0, 0, 1, 'red') g.addElement(c) - pd=pathdata(0,-10) + pd=pathdata(0, -10) for i in range(6): - pd.relsmbezier(10,5,0,10) - pd.relsmbezier(-10,5,0,10) - an=animateMotion(pd,10) + pd.relsmbezier(10, 5, 0, 10) + pd.relsmbezier(-10, 5, 0, 10) + an=animateMotion(pd, 10) an.attributes['rotate']='auto-reverse' an.attributes['repeatCount']="indefinite" g.addElement(an) s.addElement(g) - for i in range(20,120,20): - u=use('#animations',i,0) + for i in range(20, 120, 20): + u=use('#animations', i, 0) s.addElement(u) - for i in range(0,120,20): - for j in range(5,105,10): - c=circle(i,j,1,'red','black',.5) + for i in range(0, 120, 20): + for j in range(5, 105, 10): + c=circle(i, j, 1, 'red', 'black', .5) s.addElement(c) d.setSVG(s) diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py index 51a87fe1..68ef0f04 100644 --- a/wqflask/utility/tools.py +++ b/wqflask/utility/tools.py @@ -15,7 +15,7 @@ OVERRIDES = {} def app_set(command_id, value): """Set application wide value""" - app.config.setdefault(command_id,value) + app.config.setdefault(command_id, value) return value def get_setting(command_id,guess=None): @@ -45,7 +45,7 @@ def get_setting(command_id,guess=None): def value(command): if command: # sys.stderr.write("Found "+command+"\n") - app_set(command_id,command) + app_set(command_id, command) return command else: return None @@ -68,7 +68,7 @@ def get_setting(command_id,guess=None): def get_setting_bool(id): v = get_setting(id) - if v not in [0,False,'False','FALSE',None]: + if v not in [0, False, 'False', 'FALSE', None]: return True return False @@ -108,16 +108,16 @@ def js_path(module=None): raise "No JS path found for "+module+" (if not in Guix check JS_GN_PATH)" def reaper_command(guess=None): - return get_setting("REAPER_COMMAND",guess) + return get_setting("REAPER_COMMAND", guess) def gemma_command(guess=None): - return assert_bin(get_setting("GEMMA_COMMAND",guess)) + return assert_bin(get_setting("GEMMA_COMMAND", guess)) def gemma_wrapper_command(guess=None): - return assert_bin(get_setting("GEMMA_WRAPPER_COMMAND",guess)) + return assert_bin(get_setting("GEMMA_WRAPPER_COMMAND", guess)) def plink_command(guess=None): - return assert_bin(get_setting("PLINK_COMMAND",guess)) + return assert_bin(get_setting("PLINK_COMMAND", guess)) def flat_file_exists(subdir): base = get_setting("GENENETWORK_FILES") @@ -180,7 +180,7 @@ def locate(name, subdir=None): raise Exception("Can not locate "+name+" in "+base) def locate_phewas(name, subdir=None): - return locate(name,'/phewas/'+subdir) + return locate(name, '/phewas/'+subdir) def locate_ignore_error(name, subdir=None): """ @@ -204,7 +204,7 @@ def tempdir(): """ Get UNIX TMPDIR by default """ - return valid_path(get_setting("TMPDIR","/tmp")) + return valid_path(get_setting("TMPDIR", "/tmp")) BLUE = '\033[94m' GREEN = '\033[92m' @@ -225,9 +225,9 @@ def show_settings(): keylist.sort() for k in keylist: try: - print(("%s: %s%s%s%s" % (k,BLUE,BOLD,get_setting(k),ENDC))) + print(("%s: %s%s%s%s" % (k, BLUE, BOLD, get_setting(k), ENDC))) except: - print(("%s: %s%s%s%s" % (k,GREEN,BOLD,app.config[k],ENDC))) + print(("%s: %s%s%s%s" % (k, GREEN, BOLD, app.config[k], ENDC))) # Cached values @@ -279,10 +279,10 @@ SMTP_CONNECT = get_setting('SMTP_CONNECT') SMTP_USERNAME = get_setting('SMTP_USERNAME') SMTP_PASSWORD = get_setting('SMTP_PASSWORD') -REAPER_COMMAND = app_set("REAPER_COMMAND",reaper_command()) -GEMMA_COMMAND = app_set("GEMMA_COMMAND",gemma_command()) +REAPER_COMMAND = app_set("REAPER_COMMAND", reaper_command()) +GEMMA_COMMAND = app_set("GEMMA_COMMAND", gemma_command()) assert(GEMMA_COMMAND is not None) -PLINK_COMMAND = app_set("PLINK_COMMAND",plink_command()) +PLINK_COMMAND = app_set("PLINK_COMMAND", plink_command()) GEMMA_WRAPPER_COMMAND = gemma_wrapper_command() TEMPDIR = tempdir() # defaults to UNIX TMPDIR assert_dir(TEMPDIR) @@ -295,11 +295,11 @@ assert_dir(JS_GUIX_PATH+'/cytoscape-panzoom') CSS_PATH = JS_GUIX_PATH # The CSS is bundled together with the JS # assert_dir(JS_PATH) -JS_TWITTER_POST_FETCHER_PATH = get_setting("JS_TWITTER_POST_FETCHER_PATH",js_path("javascript-twitter-post-fetcher")) +JS_TWITTER_POST_FETCHER_PATH = get_setting("JS_TWITTER_POST_FETCHER_PATH", js_path("javascript-twitter-post-fetcher")) assert_dir(JS_TWITTER_POST_FETCHER_PATH) assert_file(JS_TWITTER_POST_FETCHER_PATH+"/js/twitterFetcher_min.js") -JS_CYTOSCAPE_PATH = get_setting("JS_CYTOSCAPE_PATH",js_path("cytoscape")) +JS_CYTOSCAPE_PATH = get_setting("JS_CYTOSCAPE_PATH", js_path("cytoscape")) assert_dir(JS_CYTOSCAPE_PATH) assert_file(JS_CYTOSCAPE_PATH+'/cytoscape.min.js') diff --git a/wqflask/utility/webqtlUtil.py b/wqflask/utility/webqtlUtil.py index 79991149..77cc3416 100644 --- a/wqflask/utility/webqtlUtil.py +++ b/wqflask/utility/webqtlUtil.py @@ -41,22 +41,22 @@ ParInfo ={ 'C57BL-6JxC57BL-6NJF2':['', '', 'C57BL/6J', 'C57BL/6NJ'], 'BXD300':['B6D2F1', 'D2B6F1', 'C57BL/6J', 'DBA/2J'], 'B6BTBRF2':['B6BTBRF1', 'BTBRB6F1', 'C57BL/6J', 'BTBRT<+>tf/J'], -'BHHBF2':['B6HF2','HB6F2','C57BL/6J','C3H/HeJ'], -'BHF2':['B6HF2','HB6F2','C57BL/6J','C3H/HeJ'], +'BHHBF2':['B6HF2', 'HB6F2', 'C57BL/6J', 'C3H/HeJ'], +'BHF2':['B6HF2', 'HB6F2', 'C57BL/6J', 'C3H/HeJ'], 'B6D2F2':['B6D2F1', 'D2B6F1', 'C57BL/6J', 'DBA/2J'], 'BDF2-1999':['B6D2F2', 'D2B6F2', 'C57BL/6J', 'DBA/2J'], 'BDF2-2005':['B6D2F1', 'D2B6F1', 'C57BL/6J', 'DBA/2J'], -'CTB6F2':['CTB6F2','B6CTF2','C57BL/6J','Castaneous'], +'CTB6F2':['CTB6F2', 'B6CTF2', 'C57BL/6J', 'Castaneous'], 'CXB':['CBF1', 'BCF1', 'C57BL/6ByJ', 'BALB/cByJ'], 'AXBXA':['ABF1', 'BAF1', 'C57BL/6J', 'A/J'], 'AXB':['ABF1', 'BAF1', 'C57BL/6J', 'A/J'], 'BXA':['BAF1', 'ABF1', 'C57BL/6J', 'A/J'], 'LXS':['LSF1', 'SLF1', 'ISS', 'ILS'], 'HXBBXH':['SHR_BNF1', 'BN_SHRF1', 'BN-Lx/Cub', 'SHR/OlaIpcv'], -'BayXSha':['BayXShaF1', 'ShaXBayF1', 'Bay-0','Shahdara'], -'ColXBur':['ColXBurF1', 'BurXColF1', 'Col-0','Bur-0'], -'ColXCvi':['ColXCviF1', 'CviXColF1', 'Col-0','Cvi'], -'SXM':['SMF1', 'MSF1', 'Steptoe','Morex'], +'BayXSha':['BayXShaF1', 'ShaXBayF1', 'Bay-0', 'Shahdara'], +'ColXBur':['ColXBurF1', 'BurXColF1', 'Col-0', 'Bur-0'], +'ColXCvi':['ColXCviF1', 'CviXColF1', 'Col-0', 'Cvi'], +'SXM':['SMF1', 'MSF1', 'Steptoe', 'Morex'], 'HRDP':['SHR_BNF1', 'BN_SHRF1', 'BN-Lx/Cub', 'SHR/OlaIpcv'] } @@ -91,7 +91,7 @@ def readLineCSV(line): ### dcrowell July 2008 returnList[0]=returnList[0][1:] return returnList -def cmpEigenValue(A,B): +def cmpEigenValue(A, B): try: if A[0] > B[0]: return -1 diff --git a/wqflask/wqflask/api/router.py b/wqflask/wqflask/api/router.py index 3fa1d5ba..b81da0dc 100644 --- a/wqflask/wqflask/api/router.py +++ b/wqflask/wqflask/api/router.py @@ -558,10 +558,10 @@ def trait_sample_data(dataset_name, trait_name, file_format = "json"): sample_list = [] for sample in sample_data: sample_dict = { - "sample_name" : sample[0], - "sample_name_2" : sample[1], - "value" : sample[2], - "data_id" : sample[3], + "sample_name": sample[0], + "sample_name_2": sample[1], + "value": sample[2], + "data_id": sample[3], } if sample[4]: sample_dict["se"] = sample[4] diff --git a/wqflask/wqflask/correlation/show_corr_results.py b/wqflask/wqflask/correlation/show_corr_results.py index 15a21ee6..4116e2df 100644 --- a/wqflask/wqflask/correlation/show_corr_results.py +++ b/wqflask/wqflask/correlation/show_corr_results.py @@ -108,17 +108,17 @@ class CorrelationResults(object): self.sample_data = {} self.corr_type = start_vars['corr_type'] self.corr_method = start_vars['corr_sample_method'] - self.min_expr = get_float(start_vars,'min_expr') - self.p_range_lower = get_float(start_vars,'p_range_lower',-1.0) - self.p_range_upper = get_float(start_vars,'p_range_upper',1.0) + self.min_expr = get_float(start_vars, 'min_expr') + self.p_range_lower = get_float(start_vars, 'p_range_lower', -1.0) + self.p_range_upper = get_float(start_vars, 'p_range_upper', 1.0) if ('loc_chr' in start_vars and 'min_loc_mb' in start_vars and 'max_loc_mb' in start_vars): - self.location_chr = get_string(start_vars,'loc_chr') - self.min_location_mb = get_int(start_vars,'min_loc_mb') - self.max_location_mb = get_int(start_vars,'max_loc_mb') + self.location_chr = get_string(start_vars, 'loc_chr') + self.min_location_mb = get_int(start_vars, 'min_loc_mb') + self.max_location_mb = get_int(start_vars, 'max_loc_mb') else: self.location_chr = self.min_location_mb = self.max_location_mb = None diff --git a/wqflask/wqflask/correlation_matrix/show_corr_matrix.py b/wqflask/wqflask/correlation_matrix/show_corr_matrix.py index 832746bb..b582cd23 100644 --- a/wqflask/wqflask/correlation_matrix/show_corr_matrix.py +++ b/wqflask/wqflask/correlation_matrix/show_corr_matrix.py @@ -275,8 +275,8 @@ def zScore(trait_data_array): i = 0 for data in trait_data_array: N = len(data) - S = reduce(lambda x,y: x+y, data, 0.) - SS = reduce(lambda x,y: x+y*y, data, 0.) + S = reduce(lambda x, y: x+y, data, 0.) + SS = reduce(lambda x, y: x+y*y, data, 0.) mean = S/N var = SS - S*S/N stdev = math.sqrt(var/(N-1)) @@ -294,7 +294,7 @@ def sortEigenVectors(vector): combines = [] i = 0 for item in eigenValues: - combines.append([eigenValues[i],eigenVectors[i]]) + combines.append([eigenValues[i], eigenVectors[i]]) i += 1 combines.sort(webqtlUtil.cmpEigenValue) A = [] @@ -302,7 +302,7 @@ def sortEigenVectors(vector): for item in combines: A.append(item[0]) B.append(item[1]) - sum = reduce(lambda x,y: x+y, A, 0.0) + sum = reduce(lambda x, y: x+y, A, 0.0) A = [x*100.0/sum for x in A] return [A, B] except: diff --git a/wqflask/wqflask/do_search.py b/wqflask/wqflask/do_search.py index 1e15d28f..cc9c1860 100644 --- a/wqflask/wqflask/do_search.py +++ b/wqflask/wqflask/do_search.py @@ -46,8 +46,8 @@ class DoSearch(object): def handle_wildcard(self, str): keyword = str.strip() - keyword = keyword.replace("*",".*") - keyword = keyword.replace("?",".") + keyword = keyword.replace("*", ".*") + keyword = keyword.replace("?", ".") return keyword diff --git a/wqflask/wqflask/external_tools/send_to_geneweaver.py b/wqflask/wqflask/external_tools/send_to_geneweaver.py index 7a5dba73..9844bab4 100644 --- a/wqflask/wqflask/external_tools/send_to_geneweaver.py +++ b/wqflask/wqflask/external_tools/send_to_geneweaver.py @@ -54,10 +54,10 @@ class SendToGeneWeaver(object): trait_name_list = get_trait_name_list(self.trait_list) self.hidden_vars = { - 'client' : "genenetwork", - 'species' : species_name, - 'idtype' : self.chip_name, - 'list' : string.join(trait_name_list, ","), + 'client': "genenetwork", + 'species': species_name, + 'idtype': self.chip_name, + 'list': string.join(trait_name_list, ","), } def get_trait_name_list(trait_list): diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index c8b9c405..f282b010 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -27,8 +27,8 @@ import datetime import string from math import * -from PIL import (Image,ImageDraw,ImageFont,ImageColor) -import sys,os +from PIL import (Image, ImageDraw, ImageFont, ImageColor) +import sys, os import cPickle import httplib import json @@ -426,7 +426,7 @@ class DisplayMappingResults(object): else: continue samplelist = list(self.genotype.prgy) - for j,_geno in enumerate (self.genotype[0][1].genotype): + for j, _geno in enumerate (self.genotype[0][1].genotype): for item in smd: if item.name == samplelist[j]: self.NR_INDIVIDUALS = self.NR_INDIVIDUALS + 1 @@ -518,7 +518,7 @@ class DisplayMappingResults(object): #Scales plot differently for high resolution if self.draw2X: - intCanvasX2 = Image.new("RGBA", size=(self.graphWidth*2,self.graphHeight*2)) + intCanvasX2 = Image.new("RGBA", size=(self.graphWidth*2, self.graphHeight*2)) gifmapX2 = self.plotIntMapping(intCanvasX2, startMb = self.startMb, endMb = self.endMb, showLocusForm= showLocusForm, zoom=2) intCanvasX2.save( "{}.png".format( @@ -745,17 +745,17 @@ class DisplayMappingResults(object): bootScale = bootScale[:-1] + [highestPercent] bootOffset = 50*fontZoom - bootScaleFont=ImageFont.truetype(font=VERDANA_FILE,size=13*fontZoom) + bootScaleFont=ImageFont.truetype(font=VERDANA_FILE, size=13*fontZoom) im_drawer.rectangle( xy=((canvas.size[0]-bootOffset, yZero-bootHeightThresh), - (canvas.size[0]-bootOffset-15*zoom,yZero)), + (canvas.size[0]-bootOffset-15*zoom, yZero)), fill = YELLOW, outline=BLACK) im_drawer.line( xy=((canvas.size[0]-bootOffset+4, yZero), (canvas.size[0]-bootOffset, yZero)), fill=BLACK) TEXT_Y_DISPLACEMENT = -8 - im_drawer.text(xy=(canvas.size[0]-bootOffset+10,yZero+TEXT_Y_DISPLACEMENT), text='0%', + im_drawer.text(xy=(canvas.size[0]-bootOffset+10, yZero+TEXT_Y_DISPLACEMENT), text='0%', font=bootScaleFont, fill=BLACK) for item in bootScale: @@ -763,10 +763,10 @@ class DisplayMappingResults(object): continue bootY = yZero-bootHeightThresh*item/highestPercent im_drawer.line( - xy=((canvas.size[0]-bootOffset+4,bootY), - (canvas.size[0]-bootOffset,bootY)), + xy=((canvas.size[0]-bootOffset+4, bootY), + (canvas.size[0]-bootOffset, bootY)), fill=BLACK) - im_drawer.text(xy=(canvas.size[0]-bootOffset+10,bootY+TEXT_Y_DISPLACEMENT), + im_drawer.text(xy=(canvas.size[0]-bootOffset+10, bootY+TEXT_Y_DISPLACEMENT), text='%2.1f'%item, font=bootScaleFont, fill=BLACK) if self.legendChecked: @@ -775,7 +775,7 @@ class DisplayMappingResults(object): smallLabelFont = ImageFont.truetype(font=TREBUC_FILE, size=12*fontZoom) leftOffset = xLeftOffset+(nCol-1)*200 im_drawer.rectangle( - xy=((leftOffset,startPosY-6), (leftOffset+12,startPosY+6)), + xy=((leftOffset, startPosY-6), (leftOffset+12, startPosY+6)), fill=YELLOW, outline=BLACK) im_drawer.text(xy=(leftOffset+ 20, startPosY+TEXT_Y_DISPLACEMENT), text='Frequency of the Peak LRS', @@ -872,7 +872,7 @@ class DisplayMappingResults(object): TEXT_Y_DISPLACEMENT = -8 im_drawer.text( text="Sequence Site", - xy=(leftOffset+15,startPosY+TEXT_Y_DISPLACEMENT), font=smallLabelFont, + xy=(leftOffset+15, startPosY+TEXT_Y_DISPLACEMENT), font=smallLabelFont, fill=self.TOP_RIGHT_INFO_COLOR) def drawSNPTrackNew(self, canvas, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): @@ -922,7 +922,7 @@ class DisplayMappingResults(object): def drawMultiTraitName(self, fd, canvas, gifmap, showLocusForm, offset= (40, 120, 80, 10), zoom = 1): nameWidths = [] yPaddingTop = 10 - colorFont=ImageFont.truetype(font=TREBUC_FILE,size=12) + colorFont=ImageFont.truetype(font=TREBUC_FILE, size=12) if len(self.qtlresults) >20 and self.selectedChr > -1: rightShift = 20 rightShiftStep = 60 @@ -941,20 +941,20 @@ class DisplayMappingResults(object): rightShift += rightShiftStep name = thisTrait.displayName() - nameWidth, nameHeight = im_drawer.textsize(name,font=colorFont) + nameWidth, nameHeight = im_drawer.textsize(name, font=colorFont) nameWidths.append(nameWidth) im_drawer.rectangle( - xy=((rightShift,yPaddingTop+kstep*15), - (rectWidth+rightShift,yPaddingTop+10+kstep*15)), + xy=((rightShift, yPaddingTop+kstep*15), + (rectWidth+rightShift, yPaddingTop+10+kstep*15)), fill=thisLRSColor, outline=BLACK) im_drawer.text( - text=name,xy=(rectWidth+2+rightShift,yPaddingTop+10+kstep*15), - font=colorFont,fill=BLACK) + text=name, xy=(rectWidth+2+rightShift, yPaddingTop+10+kstep*15), + font=colorFont, fill=BLACK) if thisTrait.db: - COORDS = "%d,%d,%d,%d" %(rectWidth+2+rightShift,yPaddingTop+kstep*15,rectWidth+2+rightShift+nameWidth,yPaddingTop+10+kstep*15,) + COORDS = "%d,%d,%d,%d" %(rectWidth+2+rightShift, yPaddingTop+kstep*15, rectWidth+2+rightShift+nameWidth, yPaddingTop+10+kstep*15,) HREF= "javascript:showDatabase3('%s','%s','%s','');" % (showLocusForm, thisTrait.db.name, thisTrait.name) - Areas = HT.Area(shape='rect',coords=COORDS,href=HREF) + Areas = HT.Area(shape='rect', coords=COORDS, href=HREF) gifmap.areas.append(Areas) def drawLegendPanel(self, canvas, offset= (40, 120, 80, 10), zoom = 1): @@ -968,80 +968,80 @@ class DisplayMappingResults(object): if zoom == 2: fontZoom = 1.5 - labelFont=ImageFont.truetype(font=TREBUC_FILE,size=12*fontZoom) + labelFont=ImageFont.truetype(font=TREBUC_FILE, size=12*fontZoom) startPosY = 15 stepPosY = 12*fontZoom if self.manhattan_plot != True: im_drawer.line( - xy=((xLeftOffset,startPosY),(xLeftOffset+32,startPosY)), + xy=((xLeftOffset, startPosY), (xLeftOffset+32, startPosY)), fill=self.LRS_COLOR, width=2) im_drawer.text( - text=self.LRS_LOD, xy=(xLeftOffset+40,startPosY+TEXT_Y_DISPLACEMENT), - font=labelFont,fill=BLACK) + text=self.LRS_LOD, xy=(xLeftOffset+40, startPosY+TEXT_Y_DISPLACEMENT), + font=labelFont, fill=BLACK) startPosY += stepPosY if self.additiveChecked: startPosX = xLeftOffset im_drawer.line( - xy=((startPosX,startPosY),(startPosX+17,startPosY)), + xy=((startPosX, startPosY), (startPosX+17, startPosY)), fill=self.ADDITIVE_COLOR_POSITIVE, width=2) im_drawer.line( - xy=((startPosX+18,startPosY),(startPosX+32,startPosY)), + xy=((startPosX+18, startPosY), (startPosX+32, startPosY)), fill=self.ADDITIVE_COLOR_NEGATIVE, width=2) im_drawer.text( - text='Additive Effect',xy=(startPosX+40,startPosY+TEXT_Y_DISPLACEMENT), - font=labelFont,fill=BLACK) + text='Additive Effect', xy=(startPosX+40, startPosY+TEXT_Y_DISPLACEMENT), + font=labelFont, fill=BLACK) if self.genotype.type == 'intercross' and self.dominanceChecked: startPosX = xLeftOffset startPosY += stepPosY im_drawer.line( - xy=((startPosX,startPosY),(startPosX+17,startPosY)), + xy=((startPosX, startPosY), (startPosX+17, startPosY)), fill=self.DOMINANCE_COLOR_POSITIVE, width=4) im_drawer.line( - xy=((startPosX+18,startPosY),(startPosX+35,startPosY)), + xy=((startPosX+18, startPosY), (startPosX+35, startPosY)), fill=self.DOMINANCE_COLOR_NEGATIVE, width=4) im_drawer.text( - text='Dominance Effect', xy=(startPosX+42,startPosY+5), - font=labelFont,fill=BLACK) + text='Dominance Effect', xy=(startPosX+42, startPosY+5), + font=labelFont, fill=BLACK) if self.haplotypeAnalystChecked: startPosY += stepPosY startPosX = xLeftOffset im_drawer.line( - xy=((startPosX,startPosY),(startPosX+17,startPosY)), + xy=((startPosX, startPosY), (startPosX+17, startPosY)), fill=self.HAPLOTYPE_POSITIVE, width=4) im_drawer.line( - xy=((startPosX+18,startPosY),(startPosX+35,startPosY)), + xy=((startPosX+18, startPosY), (startPosX+35, startPosY)), fill=self.HAPLOTYPE_NEGATIVE, width=4) im_drawer.line( - xy=((startPosX+36,startPosY),(startPosX+53,startPosY)), + xy=((startPosX+36, startPosY), (startPosX+53, startPosY)), fill=self.HAPLOTYPE_HETEROZYGOUS, width=4) im_drawer.line( - xy=((startPosX+54,startPosY),(startPosX+67,startPosY)), + xy=((startPosX+54, startPosY), (startPosX+67, startPosY)), fill=self.HAPLOTYPE_RECOMBINATION, width=4) im_drawer.text( text='Haplotypes (Pat, Mat, Het, Unk)', - xy=(startPosX+76,startPosY+5),font=labelFont,fill=BLACK) + xy=(startPosX+76, startPosY+5), font=labelFont, fill=BLACK) if self.permChecked and self.nperm > 0: startPosY += stepPosY startPosX = xLeftOffset im_drawer.line( - xy=((startPosX, startPosY),( startPosX + 32, startPosY)), + xy=((startPosX, startPosY), ( startPosX + 32, startPosY)), fill=self.SIGNIFICANT_COLOR, width=self.SIGNIFICANT_WIDTH) im_drawer.line( - xy=((startPosX, startPosY + stepPosY),( startPosX + 32, startPosY + stepPosY)), + xy=((startPosX, startPosY + stepPosY), ( startPosX + 32, startPosY + stepPosY)), fill=self.SUGGESTIVE_COLOR, width=self.SUGGESTIVE_WIDTH) im_drawer.text( - text='Significant %s = %2.2f' % (self.LRS_LOD,self.significant), - xy=(xLeftOffset+42,startPosY+TEXT_Y_DISPLACEMENT),font=labelFont,fill=BLACK) + text='Significant %s = %2.2f' % (self.LRS_LOD, self.significant), + xy=(xLeftOffset+42, startPosY+TEXT_Y_DISPLACEMENT), font=labelFont, fill=BLACK) im_drawer.text( text='Suggestive %s = %2.2f' % (self.LRS_LOD, self.suggestive), - xy=(xLeftOffset+42,startPosY + TEXT_Y_DISPLACEMENT +stepPosY),font=labelFont, + xy=(xLeftOffset+42, startPosY + TEXT_Y_DISPLACEMENT +stepPosY), font=labelFont, fill=BLACK) - labelFont = ImageFont.truetype(font=VERDANA_FILE,size=12*fontZoom) + labelFont = ImageFont.truetype(font=VERDANA_FILE, size=12*fontZoom) labelColor = BLACK if self.dataset.type == "Publish" or self.dataset.type == "Geno": dataset_label = self.dataset.fullname @@ -1109,22 +1109,22 @@ class DisplayMappingResults(object): im_drawer.textsize(string2, font=labelFont)[0]) im_drawer.text( text=identification, - xy=(canvas.size[0] - xRightOffset-d,20*fontZoom),font=labelFont, + xy=(canvas.size[0] - xRightOffset-d, 20*fontZoom), font=labelFont, fill=labelColor) else: d = 4+ max( im_drawer.textsize(string1, font=labelFont)[0], im_drawer.textsize(string2, font=labelFont)[0]) im_drawer.text( - text=string1,xy=(canvas.size[0] - xRightOffset-d,35*fontZoom), - font=labelFont,fill=labelColor) + text=string1, xy=(canvas.size[0] - xRightOffset-d, 35*fontZoom), + font=labelFont, fill=labelColor) im_drawer.text( - text=string2,xy=(canvas.size[0] - xRightOffset-d,50*fontZoom), - font=labelFont,fill=labelColor) + text=string2, xy=(canvas.size[0] - xRightOffset-d, 50*fontZoom), + font=labelFont, fill=labelColor) if string3 != '': im_drawer.text( - text=string3,xy=(canvas.size[0] - xRightOffset-d,65*fontZoom), - font=labelFont,fill=labelColor) + text=string3, xy=(canvas.size[0] - xRightOffset-d, 65*fontZoom), + font=labelFont, fill=labelColor) def drawGeneBand(self, canvas, gifmap, plotXScale, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): @@ -1345,7 +1345,7 @@ class DisplayMappingResults(object): labelText = "3'" im_drawer.text( text=labelText, - xy=(utrEndPix+2,geneYLocation+self.EACH_GENE_HEIGHT), + xy=(utrEndPix+2, geneYLocation+self.EACH_GENE_HEIGHT), font=ImageFont.truetype(font=ARIAL_FILE, size=2)) #draw the genes as rectangles @@ -1357,7 +1357,7 @@ class DisplayMappingResults(object): COORDS = "%d, %d, %d, %d" %(geneStartPix, geneYLocation, geneEndPix, (geneYLocation + self.EACH_GENE_HEIGHT)) # NL: 06-02-2011 Rob required to display NCBI info in a new window - gifmap.areas.append(HT.Area(shape='rect',coords=COORDS,href=HREF, title=TITLE,target="_blank")) + gifmap.areas.append(HT.Area(shape='rect', coords=COORDS, href=HREF, title=TITLE, target="_blank")) ## BEGIN HaplotypeAnalyst def drawHaplotypeBand(self, canvas, gifmap, plotXScale, offset= (40, 120, 80, 10), zoom = 1, startMb = None, endMb = None): @@ -1490,7 +1490,7 @@ class DisplayMappingResults(object): counter = counter + 1 if item.name == samplelist[k]: ind = counter - maxind=max(ind,maxind) + maxind=max(ind, maxind) # lines if (oldgeno[k] == -1 and _geno == -1): @@ -1523,7 +1523,7 @@ class DisplayMappingResults(object): COORDS = "%d, %d, %d, %d" %(geneStartPix, geneYLocation+ind*self.EACH_GENE_HEIGHT, geneEndPix+1, (geneYLocation + ind*self.EACH_GENE_HEIGHT)) TITLE = "Strain: %s, marker (%s) \n Position %2.3f Mb." % (samplelist[k], _chr[j].name, float(txStart)) HREF = '' - gifmap.areas.append(HT.Area(shape='rect',coords=COORDS,href=HREF, title=TITLE)) + gifmap.areas.append(HT.Area(shape='rect', coords=COORDS, href=HREF, title=TITLE)) # if there are no more markers in a chromosome, the plotRight value calculated above will be before the plotWidth # resulting in some empty space on the right side of the plot area. This draws an "unknown" bar from plotRight to the edge. @@ -1642,14 +1642,14 @@ class DisplayMappingResults(object): WEBQTL_HREF = "javascript:rangeView('%s', %f, %f)" % (self.selectedChr - 1, max(0, (calBase-webqtlZoomWidth))/1000000.0, (calBase+webqtlZoomWidth)/1000000.0) WEBQTL_TITLE = "Click to view this section of the genome in WebQTL" - gifmap.areas.append(HT.Area(shape='rect',coords=WEBQTL_COORDS,href=WEBQTL_HREF, title=WEBQTL_TITLE)) + gifmap.areas.append(HT.Area(shape='rect', coords=WEBQTL_COORDS, href=WEBQTL_HREF, title=WEBQTL_TITLE)) im_drawer.rectangle( xy=((xBrowse1, paddingTop), (xBrowse2, (paddingTop + self.BAND_HEIGHT))), outline=self.CLICKABLE_WEBQTL_REGION_COLOR, fill=self.CLICKABLE_WEBQTL_REGION_COLOR) im_drawer.line( - xy=((xBrowse1, paddingTop),( xBrowse1, (paddingTop + self.BAND_HEIGHT))), + xy=((xBrowse1, paddingTop), ( xBrowse1, (paddingTop + self.BAND_HEIGHT))), fill=self.CLICKABLE_WEBQTL_REGION_OUTLINE_COLOR) if self.dataset.group.species == "mouse" or self.dataset.group.species == "rat": @@ -1659,14 +1659,14 @@ class DisplayMappingResults(object): else: PHENOGEN_HREF = "https://phenogen.org/gene.jsp?speciesCB=Mm&auto=Y&geneTxt=chr%s:%d-%d&genomeVer=mm10" % (self.selectedChr, max(0, calBase-flankingWidthInBases), calBase+flankingWidthInBases) PHENOGEN_TITLE = "Click to view this section of the genome in PhenoGen" - gifmap.areas.append(HT.Area(shape='rect',coords=PHENOGEN_COORDS,href=PHENOGEN_HREF, title=PHENOGEN_TITLE)) + gifmap.areas.append(HT.Area(shape='rect', coords=PHENOGEN_COORDS, href=PHENOGEN_HREF, title=PHENOGEN_TITLE)) im_drawer.rectangle( xy=((xBrowse1, phenogenPaddingTop), (xBrowse2, (phenogenPaddingTop+self.BAND_HEIGHT))), outline=self.CLICKABLE_PHENOGEN_REGION_COLOR, fill=self.CLICKABLE_PHENOGEN_REGION_COLOR) im_drawer.line( - xy=((xBrowse1, phenogenPaddingTop),( xBrowse1, (phenogenPaddingTop+self.BAND_HEIGHT))), + xy=((xBrowse1, phenogenPaddingTop), ( xBrowse1, (phenogenPaddingTop+self.BAND_HEIGHT))), fill=self.CLICKABLE_PHENOGEN_REGION_OUTLINE_COLOR) UCSC_COORDS = "%d, %d, %d, %d" %(xBrowse1, ucscPaddingTop, xBrowse2, (ucscPaddingTop+self.BAND_HEIGHT)) @@ -1675,7 +1675,7 @@ class DisplayMappingResults(object): else: UCSC_HREF = "http://genome.ucsc.edu/cgi-bin/hgTracks?db=%s&position=chr%s:%d-%d" % (self._ucscDb, self.selectedChr, max(0, calBase-flankingWidthInBases), calBase+flankingWidthInBases) UCSC_TITLE = "Click to view this section of the genome in the UCSC Genome Browser" - gifmap.areas.append(HT.Area(shape='rect',coords=UCSC_COORDS,href=UCSC_HREF, title=UCSC_TITLE)) + gifmap.areas.append(HT.Area(shape='rect', coords=UCSC_COORDS, href=UCSC_HREF, title=UCSC_TITLE)) im_drawer.rectangle( xy=((xBrowse1, ucscPaddingTop), (xBrowse2, (ucscPaddingTop+self.BAND_HEIGHT))), @@ -1692,7 +1692,7 @@ class DisplayMappingResults(object): else: ENSEMBL_HREF = "http://www.ensembl.org/Rattus_norvegicus/contigview?chr=%s&start=%d&end=%d" % (self.selectedChr, max(0, calBase-flankingWidthInBases), calBase+flankingWidthInBases) ENSEMBL_TITLE = "Click to view this section of the genome in the Ensembl Genome Browser" - gifmap.areas.append(HT.Area(shape='rect',coords=ENSEMBL_COORDS,href=ENSEMBL_HREF, title=ENSEMBL_TITLE)) + gifmap.areas.append(HT.Area(shape='rect', coords=ENSEMBL_COORDS, href=ENSEMBL_HREF, title=ENSEMBL_TITLE)) im_drawer.rectangle( xy=((xBrowse1, ensemblPaddingTop), (xBrowse2, (ensemblPaddingTop+self.BAND_HEIGHT))), @@ -1789,8 +1789,8 @@ class DisplayMappingResults(object): continue Xc = xLeftOffset + plotXScale*(_Mb - startMb) if counter % NUM_MINOR_TICKS == 0: # Draw a MAJOR mark, not just a minor tick mark - im_drawer.line(xy=((Xc,yZero), - (Xc,yZero+xMajorTickHeight)), + im_drawer.line(xy=((Xc, yZero), + (Xc, yZero+xMajorTickHeight)), fill=xAxisTickMarkColor, width=X_MAJOR_TICK_THICKNESS) # Draw the MAJOR tick mark labelStr = str(formatStr % _Mb) # What Mbase location to put on the label @@ -1800,8 +1800,8 @@ class DisplayMappingResults(object): text=labelStr, font=MBLabelFont, fill=xAxisLabelColor) else: - im_drawer.line(xy=((Xc,yZero), - (Xc,yZero+xMinorTickHeight)), + im_drawer.line(xy=((Xc, yZero), + (Xc, yZero+xMinorTickHeight)), fill=xAxisTickMarkColor, width=X_MINOR_TICK_THICKNESS) # Draw the MINOR tick mark @@ -1834,7 +1834,7 @@ class DisplayMappingResults(object): text="Megabases", xy=( xLeftOffset+(plotWidth-im_drawer.textsize( - "Megabases",font=megabaseLabelFont)[0])/2, + "Megabases", font=megabaseLabelFont)[0])/2, strYLoc+MBLabelFont.font.height+10*(zoom%2)), font=megabaseLabelFont, fill=BLACK) pass @@ -1889,7 +1889,7 @@ class DisplayMappingResults(object): for j, ChrInfo in enumerate(ChrAInfo): preLpos = -1 for i, item in enumerate(ChrInfo): - Lname,Lpos = item + Lname, Lpos = item if Lpos != preLpos: offsetA += stepA differ = 1 @@ -1903,17 +1903,17 @@ class DisplayMappingResults(object): Zorder = 0 if differ: im_drawer.line( - xy=((startPosX+Lpos,yZero),(xLeftOffset+offsetA,\ + xy=((startPosX+Lpos, yZero), (xLeftOffset+offsetA,\ yZero+25)), fill=lineColor) im_drawer.line( - xy=((xLeftOffset+offsetA,yZero+25),(xLeftOffset+offsetA,\ + xy=((xLeftOffset+offsetA, yZero+25), (xLeftOffset+offsetA,\ yZero+40+Zorder*(LRectWidth+3))), fill=lineColor) rectColor = ORANGE else: im_drawer.line( - xy=((xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3)-3),(\ + xy=((xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3)-3), (\ xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3))), fill=lineColor) rectColor = DEEPPINK @@ -1921,9 +1921,9 @@ class DisplayMappingResults(object): xy=((xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3)), (xLeftOffset+offsetA-LRectHeight, yZero+40+Zorder*(LRectWidth+3)+LRectWidth)), - outline=rectColor,fill=rectColor,width = 0) + outline=rectColor, fill=rectColor, width = 0) COORDS="%d,%d,%d,%d"%(xLeftOffset+offsetA-LRectHeight, yZero+40+Zorder*(LRectWidth+3),\ - xLeftOffset+offsetA,yZero+40+Zorder*(LRectWidth+3)+LRectWidth) + xLeftOffset+offsetA, yZero+40+Zorder*(LRectWidth+3)+LRectWidth) HREF="/show_trait?trait_id=%s&dataset=%s" % (Lname, self.dataset.group.name+"Geno") #HREF="javascript:showDatabase3('%s','%s','%s','');" % (showLocusForm,fd.RISet+"Geno", Lname) Areas=HT.Area(shape='rect', coords=COORDS, href=HREF, target="_blank", title="Locus : " + Lname) @@ -1931,7 +1931,7 @@ class DisplayMappingResults(object): ##piddle bug if j == 0: im_drawer.line( - xy=((startPosX,yZero),(startPosX,yZero+40)), + xy=((startPosX, yZero), (startPosX, yZero+40)), fill=lineColor) startPosX += (self.ChrLengthDistList[j]+self.GraphInterval)*plotXScale @@ -1943,7 +1943,7 @@ class DisplayMappingResults(object): strYLoc + MBLabelFont.font.height+ 10*(zoom%2)), font=centimorganLabelFont, fill=BLACK) - im_drawer.line(xy=((xLeftOffset,yZero), (xLeftOffset+plotWidth,yZero)), + im_drawer.line(xy=((xLeftOffset, yZero), (xLeftOffset+plotWidth, yZero)), fill=BLACK, width=X_AXIS_THICKNESS) # Draw the X axis itself @@ -2079,7 +2079,7 @@ class DisplayMappingResults(object): LRS_LOD_Max = 0.000001 yTopOffset + 30*(zoom - 1) yLRS = yZero - (item/LRS_LOD_Max) * LRSHeightThresh - im_drawer.line(xy=((xLeftOffset,yLRS), (xLeftOffset-4,yLRS)), + im_drawer.line(xy=((xLeftOffset, yLRS), (xLeftOffset-4, yLRS)), fill=self.LRS_COLOR, width=1*zoom) if all_int: scaleStr = "%d" % item @@ -2127,8 +2127,8 @@ class DisplayMappingResults(object): else: sugg_title = "Suggestive LOD = %0.2f" % (self.suggestive/4.61) sig_title = "Significant LOD = %0.2f" % (self.significant/4.61) - Areas1 = HT.Area(shape='rect',coords=sugg_coords,title=sugg_title) - Areas2 = HT.Area(shape='rect',coords=sig_coords,title=sig_title) + Areas1 = HT.Area(shape='rect', coords=sugg_coords, title=sugg_title) + Areas2 = HT.Area(shape='rect', coords=sig_coords, title=sig_title) gifmap.areas.append(Areas1) gifmap.areas.append(Areas2) @@ -2316,7 +2316,7 @@ class DisplayMappingResults(object): im_drawer.text( text="5", xy=( - Xc-im_drawer.textsize("5",font=symbolFont)[0]/2+1, + Xc-im_drawer.textsize("5", font=symbolFont)[0]/2+1, Yc-4), fill=point_color, font=symbolFont) else: @@ -2383,8 +2383,8 @@ class DisplayMappingResults(object): ) else: im_drawer.line( - xy=((Xc0,yZero-(Yc0-yZero)), - (Xc,yZero-(Yc-yZero))), + xy=((Xc0, yZero-(Yc0-yZero)), + (Xc, yZero-(Yc-yZero))), fill=minusColor, width=lineWidth #, clipX=(xLeftOffset, xLeftOffset + plotWidth) ) @@ -2471,8 +2471,8 @@ class DisplayMappingResults(object): ###draw additive scale if not self.multipleInterval and self.additiveChecked: - additiveScaleFont=ImageFont.truetype(font=VERDANA_FILE,size=16*zoom) - additiveScale = Plot.detScaleOld(0,additiveMax) + additiveScaleFont=ImageFont.truetype(font=VERDANA_FILE, size=16*zoom) + additiveScale = Plot.detScaleOld(0, additiveMax) additiveStep = (additiveScale[1]-additiveScale[0])/additiveScale[2] additiveAxisList = Plot.frange(0, additiveScale[1], additiveStep) addPlotScale = AdditiveHeightThresh/additiveMax @@ -2482,18 +2482,18 @@ class DisplayMappingResults(object): for item in additiveAxisList: additiveY = yZero - item*addPlotScale im_drawer.line( - xy=((xLeftOffset + plotWidth,additiveY), - (xLeftOffset+4+ plotWidth,additiveY)), + xy=((xLeftOffset + plotWidth, additiveY), + (xLeftOffset+4+ plotWidth, additiveY)), fill=self.ADDITIVE_COLOR_POSITIVE, width=1*zoom) scaleStr = "%2.3f" % item im_drawer.text( text=scaleStr, - xy=(xLeftOffset + plotWidth +6,additiveY+TEXT_Y_DISPLACEMENT), - font=additiveScaleFont,fill=self.ADDITIVE_COLOR_POSITIVE) + xy=(xLeftOffset + plotWidth +6, additiveY+TEXT_Y_DISPLACEMENT), + font=additiveScaleFont, fill=self.ADDITIVE_COLOR_POSITIVE) im_drawer.line( - xy=((xLeftOffset+plotWidth,additiveY), - (xLeftOffset+plotWidth,yZero)), + xy=((xLeftOffset+plotWidth, additiveY), + (xLeftOffset+plotWidth, yZero)), fill=self.ADDITIVE_COLOR_POSITIVE, width=1*zoom) im_drawer.line( @@ -2553,7 +2553,7 @@ class DisplayMappingResults(object): chrFontZoom = 2 else: chrFontZoom = 1 - chrLabelFont=ImageFont.truetype(font=VERDANA_FILE,size=24*chrFontZoom) + chrLabelFont=ImageFont.truetype(font=VERDANA_FILE, size=24*chrFontZoom) for i, _chr in enumerate(self.genotype): if (i % 2 == 0): @@ -2575,10 +2575,10 @@ class DisplayMappingResults(object): TEXT_Y_DISPLACEMENT = 0 im_drawer.text(xy=(chrStartPix, yTopOffset + TEXT_Y_DISPLACEMENT), text=_chr.name, font=chrLabelFont, fill=BLACK) - COORDS = "%d,%d,%d,%d" %(chrStartPix, yTopOffset, chrEndPix,yTopOffset +20) + COORDS = "%d,%d,%d,%d" %(chrStartPix, yTopOffset, chrEndPix, yTopOffset +20) #add by NL 09-03-2010 - HREF = "javascript:chrView(%d,%s);" % (i,self.ChrLengthMbList) + HREF = "javascript:chrView(%d,%s);" % (i, self.ChrLengthMbList) #HREF = "javascript:changeView(%d,%s);" % (i,self.ChrLengthMbList) Areas = HT.Area(shape='rect', coords=COORDS, href=HREF) gifmap.areas.append(Areas) @@ -2720,7 +2720,7 @@ class DisplayMappingResults(object): else: chr_as_int = int(theGO["Chromosome"]) - 1 if refGene: - literatureCorrelationString = str(self.getLiteratureCorrelation(self.cursor,refGene,theGO['GeneID']) or "N/A") + literatureCorrelationString = str(self.getLiteratureCorrelation(self.cursor, refGene, theGO['GeneID']) or "N/A") this_row = [selectCheck.__str__(), str(tableIterationsCnt), @@ -2820,8 +2820,8 @@ class DisplayMappingResults(object): lCorr = None try: query = 'SELECT Value FROM LCorrRamin3 WHERE GeneId1 = %s and GeneId2 = %s' - for x,y in [(geneId1,geneId2),(geneId2,geneId1)]: - cursor.execute(query,(x,y)) + for x, y in [(geneId1, geneId2), (geneId2, geneId1)]: + cursor.execute(query, (x, y)) lCorr = cursor.fetchone() if lCorr: lCorr = lCorr[0] diff --git a/wqflask/wqflask/marker_regression/plink_mapping.py b/wqflask/wqflask/marker_regression/plink_mapping.py index d4ee6fe6..2f282adc 100644 --- a/wqflask/wqflask/marker_regression/plink_mapping.py +++ b/wqflask/wqflask/marker_regression/plink_mapping.py @@ -54,7 +54,7 @@ def gen_pheno_txt_file_plink(this_trait, dataset, vals, pheno_filename = ''): for i, sample in enumerate(ped_sample_list): try: value = vals[i] - value = str(value).replace('value=','') + value = str(value).replace('value=', '') value = value.strip() except: value = -9999 @@ -78,7 +78,7 @@ def gen_pheno_txt_file_plink(this_trait, dataset, vals, pheno_filename = ''): # get strain name from ped file in order def get_samples_from_ped_file(dataset): - ped_file= open("{}{}.ped".format(flat_files('mapping'), dataset.group.name),"r") + ped_file= open("{}{}.ped".format(flat_files('mapping'), dataset.group.name), "r") line = ped_file.readline() sample_list=[] @@ -155,7 +155,7 @@ def parse_plink_output(output_filename, species): # output: lineList list ####################################################### def build_line_list(line=None): - line_list = string.split(string.strip(line),' ')# irregular number of whitespaces between columns + line_list = string.split(string.strip(line), ' ')# irregular number of whitespaces between columns line_list = [item for item in line_list if item !=''] line_list = list(map(string.strip, line_list)) diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index 145dbc77..1e6dff57 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -707,7 +707,7 @@ def get_perm_strata(this_trait, sample_list, categorical_vars, used_samples): perm_strata_strings.append(combined_string) - d = dict([(y,x+1) for x,y in enumerate(sorted(set(perm_strata_strings)))]) + d = dict([(y, x+1) for x, y in enumerate(sorted(set(perm_strata_strings)))]) list_to_numbers = [d[x] for x in perm_strata_strings] perm_strata = list_to_numbers diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index 5b3946e3..c07a7670 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -53,7 +53,7 @@ views.py). search = self.search_terms self.original_search_string = self.search_terms # check for dodgy search terms - rx = re.compile(r'.*\W(href|http|sql|select|update)\W.*',re.IGNORECASE) + rx = re.compile(r'.*\W(href|http|sql|select|update)\W.*', re.IGNORECASE) if rx.match(search): logger.info("Regex failed search") self.search_term_exists = False diff --git a/wqflask/wqflask/show_trait/SampleList.py b/wqflask/wqflask/show_trait/SampleList.py index 21ba7f63..f17e825e 100644 --- a/wqflask/wqflask/show_trait/SampleList.py +++ b/wqflask/wqflask/show_trait/SampleList.py @@ -57,7 +57,7 @@ class SampleList(object): sample = webqtlCaseData.webqtlCaseData(name=sample_name) sample.extra_info = {} - if self.dataset.group.name == 'AXBXA' and sample_name in ('AXB18/19/20','AXB13/14','BXA8/17'): + if self.dataset.group.name == 'AXBXA' and sample_name in ('AXB18/19/20', 'AXB13/14', 'BXA8/17'): sample.extra_info['url'] = "/mouseCross.html#AXB/BXA" sample.extra_info['css_class'] = "fs12" diff --git a/wqflask/wqflask/user_manager.py b/wqflask/wqflask/user_manager.py index a871e91a..232cb8da 100644 --- a/wqflask/wqflask/user_manager.py +++ b/wqflask/wqflask/user_manager.py @@ -867,7 +867,7 @@ def forgot_password_submit(): email_address = params['email_address'] next_page = None if email_address != "": - logger.debug("Wants to send password E-mail to ",email_address) + logger.debug("Wants to send password E-mail to ", email_address) user_details = get_user_by_unique_column("email_address", email_address) if user_details: ForgotPasswordEmail(user_details["email_address"]) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 394a9e28..92c20fc7 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -55,7 +55,7 @@ from wqflask.ctl import ctl_analysis from wqflask.snp_browser import snp_browser from utility import temp_data -from utility.tools import SQL_URI,TEMPDIR,USE_REDIS,USE_GN_SERVER,GN_SERVER_URL,GN_VERSION,JS_TWITTER_POST_FETCHER_PATH,JS_GUIX_PATH, CSS_PATH +from utility.tools import SQL_URI, TEMPDIR, USE_REDIS, USE_GN_SERVER, GN_SERVER_URL, GN_VERSION, JS_TWITTER_POST_FETCHER_PATH, JS_GUIX_PATH, CSS_PATH from utility.helper_functions import get_species_groups from utility.authentication_tools import check_resource_availability from utility.redis_tools import get_redis_conn @@ -133,10 +133,10 @@ def handle_bad_request(e): list = [fn for fn in os.listdir("./wqflask/static/gif/error") if fn.endswith(".gif") ] animation = random.choice(list) - resp = make_response(render_template("error.html",message=err_msg,stack=formatted_lines,error_image=animation,version=GN_VERSION)) + resp = make_response(render_template("error.html", message=err_msg, stack=formatted_lines, error_image=animation, version=GN_VERSION)) # logger.error("Set cookie %s with %s" % (err_msg, animation)) - resp.set_cookie(err_msg[:32],animation) + resp.set_cookie(err_msg[:32], animation) return resp @app.route("/authentication_needed") @@ -257,7 +257,7 @@ def docedit(): @app.route('/generated/') def generated_file(filename): logger.info(request.url) - return send_from_directory(GENERATED_IMAGE_DIR,filename) + return send_from_directory(GENERATED_IMAGE_DIR, filename) @app.route("/help") def help(): -- cgit v1.2.3 From 42ba26dbfd73b48ef463965d5ee22c16e159dc55 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 19 Aug 2020 14:14:16 -0500 Subject: Show short labels by default in Correlation Matrix * wqflask/wqflask/templates/correlation_matrix.html - remove display: none for short label div --- wqflask/wqflask/templates/correlation_matrix.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/correlation_matrix.html b/wqflask/wqflask/templates/correlation_matrix.html index cb9fb815..4e17a0b2 100644 --- a/wqflask/wqflask/templates/correlation_matrix.html +++ b/wqflask/wqflask/templates/correlation_matrix.html @@ -36,7 +36,7 @@
    Trait {{ loop.index }}: {{ trait.dataset.name }}  {{ trait.name }} - +
    {% if trait.dataset.type == "ProbeSet" %}Gene Symbol: {{ trait.symbol }}{% elif trait.dataset.type == "Publish" %}Trait Symbol: {{ trait.post_publication_abbreviation }}{% elif trait.dataset.type == "Geno" %}Genotype{% endif %}
    {{ value.name }} {{ value.data }}
    ', { - 'valign': 'top', - 'colSpan': _fnVisbleColumns( oSettings ), - 'class': oSettings.oClasses.sRowEmpty - } ).html( sZero ) )[0]; - } - - /* Header and footer callbacks */ - _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0], - _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); - - _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0], - _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); - - var body = $(oSettings.nTBody); - - body.children().detach(); - body.append( $(anRows) ); - - /* Call all required callback functions for the end of a draw */ - _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] ); - - /* Draw is complete, sorting and filtering must be as well */ - oSettings.bSorted = false; - oSettings.bFiltered = false; - oSettings.bDrawing = false; - } - - - /** - * Redraw the table - taking account of the various features which are enabled - * @param {object} oSettings dataTables settings object - * @param {boolean} [holdPosition] Keep the current paging position. By default - * the paging is reset to the first page - * @memberof DataTable#oApi - */ - function _fnReDraw( settings, holdPosition ) - { - var - features = settings.oFeatures, - sort = features.bSort, - filter = features.bFilter; - - if ( sort ) { - _fnSort( settings ); - } - - if ( filter ) { - _fnFilterComplete( settings, settings.oPreviousSearch ); - } - else { - // No filtering, so we want to just use the display master - settings.aiDisplay = settings.aiDisplayMaster.slice(); - } - - if ( holdPosition !== true ) { - settings._iDisplayStart = 0; - } - - // Let any modules know about the draw hold position state (used by - // scrolling internally) - settings._drawHold = holdPosition; - - _fnDraw( settings ); - - settings._drawHold = false; - } - - - /** - * Add the options to the page HTML for the table - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnAddOptionsHtml ( oSettings ) - { - var classes = oSettings.oClasses; - var table = $(oSettings.nTable); - var holding = $('
    ').insertBefore( table ); // Holding element for speed - var features = oSettings.oFeatures; - - // All DataTables are wrapped in a div - var insert = $('
    ', { - id: oSettings.sTableId+'_wrapper', - 'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' '+classes.sNoFooter) - } ); - - oSettings.nHolding = holding[0]; - oSettings.nTableWrapper = insert[0]; - oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling; - - /* Loop over the user set positioning and place the elements as needed */ - var aDom = oSettings.sDom.split(''); - var featureNode, cOption, nNewNode, cNext, sAttr, j; - for ( var i=0 ; i')[0]; - - /* Check to see if we should append an id and/or a class name to the container */ - cNext = aDom[i+1]; - if ( cNext == "'" || cNext == '"' ) - { - sAttr = ""; - j = 2; - while ( aDom[i+j] != cNext ) - { - sAttr += aDom[i+j]; - j++; - } - - /* Replace jQuery UI constants @todo depreciated */ - if ( sAttr == "H" ) - { - sAttr = classes.sJUIHeader; - } - else if ( sAttr == "F" ) - { - sAttr = classes.sJUIFooter; - } - - /* The attribute can be in the format of "#id.class", "#id" or "class" This logic - * breaks the string into parts and applies them as needed - */ - if ( sAttr.indexOf('.') != -1 ) - { - var aSplit = sAttr.split('.'); - nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1); - nNewNode.className = aSplit[1]; - } - else if ( sAttr.charAt(0) == "#" ) - { - nNewNode.id = sAttr.substr(1, sAttr.length-1); - } - else - { - nNewNode.className = sAttr; - } - - i += j; /* Move along the position array */ - } - - insert.append( nNewNode ); - insert = $(nNewNode); - } - else if ( cOption == '>' ) - { - /* End container div */ - insert = insert.parent(); - } - // @todo Move options into their own plugins? - else if ( cOption == 'l' && features.bPaginate && features.bLengthChange ) - { - /* Length */ - featureNode = _fnFeatureHtmlLength( oSettings ); - } - else if ( cOption == 'f' && features.bFilter ) - { - /* Filter */ - featureNode = _fnFeatureHtmlFilter( oSettings ); - } - else if ( cOption == 'r' && features.bProcessing ) - { - /* pRocessing */ - featureNode = _fnFeatureHtmlProcessing( oSettings ); - } - else if ( cOption == 't' ) - { - /* Table */ - featureNode = _fnFeatureHtmlTable( oSettings ); - } - else if ( cOption == 'i' && features.bInfo ) - { - /* Info */ - featureNode = _fnFeatureHtmlInfo( oSettings ); - } - else if ( cOption == 'p' && features.bPaginate ) - { - /* Pagination */ - featureNode = _fnFeatureHtmlPaginate( oSettings ); - } - else if ( DataTable.ext.feature.length !== 0 ) - { - /* Plug-in features */ - var aoFeatures = DataTable.ext.feature; - for ( var k=0, kLen=aoFeatures.length ; k'; - - var str = language.sSearch; - str = str.match(/_INPUT_/) ? - str.replace('_INPUT_', input) : - str+input; - - var filter = $('
    ', { - 'id': ! features.f ? tableId+'_filter' : null, - 'class': classes.sFilter - } ) - .append( $('
    ').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] : - data.anCells[ colIdx ]; - } - - - /** - * Get the maximum strlen for each data column - * @param {object} settings dataTables settings object - * @param {int} colIdx column of interest - * @returns {string} max string length for each column - * @memberof DataTable#oApi - */ - function _fnGetMaxLenString( settings, colIdx ) - { - var s, max=-1, maxIdx = -1; - - for ( var i=0, ien=settings.aoData.length ; i max ) { - max = s.length; - maxIdx = i; - } - } - - return maxIdx; - } - - - /** - * Append a CSS unit (only if required) to a string - * @param {string} value to css-ify - * @returns {string} value with css unit - * @memberof DataTable#oApi - */ - function _fnStringToCss( s ) - { - if ( s === null ) { - return '0px'; - } - - if ( typeof s == 'number' ) { - return s < 0 ? - '0px' : - s+'px'; - } - - // Check it has a unit character already - return s.match(/\d$/) ? - s+'px' : - s; - } - - - - function _fnSortFlatten ( settings ) - { - var - i, iLen, k, kLen, - aSort = [], - aiOrig = [], - aoColumns = settings.aoColumns, - aDataSort, iCol, sType, srcCol, - fixed = settings.aaSortingFixed, - fixedObj = $.isPlainObject( fixed ), - nestedSort = [], - add = function ( a ) { - if ( a.length && ! $.isArray( a[0] ) ) { - // 1D array - nestedSort.push( a ); - } - else { - // 2D array - $.merge( nestedSort, a ); - } - }; - - // Build the sort array, with pre-fix and post-fix options if they have been - // specified - if ( $.isArray( fixed ) ) { - add( fixed ); - } - - if ( fixedObj && fixed.pre ) { - add( fixed.pre ); - } - - add( settings.aaSorting ); - - if (fixedObj && fixed.post ) { - add( fixed.post ); - } - - for ( i=0 ; iy ? 1 : 0; - if ( test !== 0 ) { - return sort.dir === 'asc' ? test : -test; - } - } - - x = aiOrig[a]; - y = aiOrig[b]; - return xy ? 1 : 0; - } ); - } - else { - // Depreciated - remove in 1.11 (providing a plug-in option) - // Not all sort types have formatting methods, so we have to call their sorting - // methods. - displayMaster.sort( function ( a, b ) { - var - x, y, k, l, test, sort, fn, - len=aSort.length, - dataA = aoData[a]._aSortData, - dataB = aoData[b]._aSortData; - - for ( k=0 ; ky ? 1 : 0; - } ); - } - } - - /* Tell the draw function that we have sorted the data */ - oSettings.bSorted = true; - } - - - function _fnSortAria ( settings ) - { - var label; - var nextSort; - var columns = settings.aoColumns; - var aSort = _fnSortFlatten( settings ); - var oAria = settings.oLanguage.oAria; - - // ARIA attributes - need to loop all columns, to update all (removing old - // attributes as needed) - for ( var i=0, iLen=columns.length ; i/g, "" ); - var th = col.nTh; - - // IE7 is throwing an error when setting these properties with jQuery's - // attr() and removeAttr() methods... - th.removeAttribute('aria-sort'); - - /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */ - if ( col.bSortable ) { - if ( aSort.length > 0 && aSort[0].col == i ) { - th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" ); - nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0]; - } - else { - nextSort = asSorting[0]; - } - - label = sTitle + ( nextSort === "asc" ? - oAria.sSortAscending : - oAria.sSortDescending - ); - } - else { - label = sTitle; - } - - th.setAttribute('aria-label', label); - } - } - - - /** - * Function to run on user sort request - * @param {object} settings dataTables settings object - * @param {node} attachTo node to attach the handler to - * @param {int} colIdx column sorting index - * @param {boolean} [append=false] Append the requested sort to the existing - * sort if true (i.e. multi-column sort) - * @param {function} [callback] callback function - * @memberof DataTable#oApi - */ - function _fnSortListener ( settings, colIdx, append, callback ) - { - var col = settings.aoColumns[ colIdx ]; - var sorting = settings.aaSorting; - var asSorting = col.asSorting; - var nextSortIdx; - var next = function ( a, overflow ) { - var idx = a._idx; - if ( idx === undefined ) { - idx = $.inArray( a[1], asSorting ); - } - - return idx+1 < asSorting.length ? - idx+1 : - overflow ? - null : - 0; - }; - - // Convert to 2D array if needed - if ( typeof sorting[0] === 'number' ) { - sorting = settings.aaSorting = [ sorting ]; - } - - // If appending the sort then we are multi-column sorting - if ( append && settings.oFeatures.bSortMulti ) { - // Are we already doing some kind of sort on this column? - var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') ); - - if ( sortIdx !== -1 ) { - // Yes, modify the sort - nextSortIdx = next( sorting[sortIdx], true ); - - if ( nextSortIdx === null && sorting.length === 1 ) { - nextSortIdx = 0; // can't remove sorting completely - } - - if ( nextSortIdx === null ) { - sorting.splice( sortIdx, 1 ); - } - else { - sorting[sortIdx][1] = asSorting[ nextSortIdx ]; - sorting[sortIdx]._idx = nextSortIdx; - } - } - else { - // No sort on this column yet - sorting.push( [ colIdx, asSorting[0], 0 ] ); - sorting[sorting.length-1]._idx = 0; - } - } - else if ( sorting.length && sorting[0][0] == colIdx ) { - // Single column - already sorting on this column, modify the sort - nextSortIdx = next( sorting[0] ); - - sorting.length = 1; - sorting[0][1] = asSorting[ nextSortIdx ]; - sorting[0]._idx = nextSortIdx; - } - else { - // Single column - sort only on this column - sorting.length = 0; - sorting.push( [ colIdx, asSorting[0] ] ); - sorting[0]._idx = 0; - } - - // Run the sort by calling a full redraw - _fnReDraw( settings ); - - // callback used for async user interaction - if ( typeof callback == 'function' ) { - callback( settings ); - } - } - - - /** - * Attach a sort handler (click) to a node - * @param {object} settings dataTables settings object - * @param {node} attachTo node to attach the handler to - * @param {int} colIdx column sorting index - * @param {function} [callback] callback function - * @memberof DataTable#oApi - */ - function _fnSortAttachListener ( settings, attachTo, colIdx, callback ) - { - var col = settings.aoColumns[ colIdx ]; - - _fnBindAction( attachTo, {}, function (e) { - /* If the column is not sortable - don't to anything */ - if ( col.bSortable === false ) { - return; - } - - // If processing is enabled use a timeout to allow the processing - // display to be shown - otherwise to it synchronously - if ( settings.oFeatures.bProcessing ) { - _fnProcessingDisplay( settings, true ); - - setTimeout( function() { - _fnSortListener( settings, colIdx, e.shiftKey, callback ); - - // In server-side processing, the draw callback will remove the - // processing display - if ( _fnDataSource( settings ) !== 'ssp' ) { - _fnProcessingDisplay( settings, false ); - } - }, 0 ); - } - else { - _fnSortListener( settings, colIdx, e.shiftKey, callback ); - } - } ); - } - - - /** - * Set the sorting classes on table's body, Note: it is safe to call this function - * when bSort and bSortClasses are false - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnSortingClasses( settings ) - { - var oldSort = settings.aLastSort; - var sortClass = settings.oClasses.sSortColumn; - var sort = _fnSortFlatten( settings ); - var features = settings.oFeatures; - var i, ien, colIdx; - - if ( features.bSort && features.bSortClasses ) { - // Remove old sorting classes - for ( i=0, ien=oldSort.length ; i 0 && state.time < +new Date() - (duration*1000) ) { - return; - } - - // Number of columns have changed - all bets are off, no restore of settings - if ( columns.length !== state.columns.length ) { - return; - } - - // Store the saved state so it might be accessed at any time - settings.oLoadedState = $.extend( true, {}, state ); - - // Restore key features - todo - for 1.11 this needs to be done by - // subscribed events - if ( state.start !== undefined ) { - settings._iDisplayStart = state.start; - settings.iInitDisplayStart = state.start; - } - if ( state.length !== undefined ) { - settings._iDisplayLength = state.length; - } - - // Order - if ( state.order !== undefined ) { - settings.aaSorting = []; - $.each( state.order, function ( i, col ) { - settings.aaSorting.push( col[0] >= columns.length ? - [ 0, col[1] ] : - col - ); - } ); - } - - // Search - if ( state.search !== undefined ) { - $.extend( settings.oPreviousSearch, _fnSearchToHung( state.search ) ); - } - - // Columns - for ( i=0, ien=state.columns.length ; i= end ) - { - start = end - len; - } - - // Keep the start record on the current page - start -= (start % len); - - if ( len === -1 || start < 0 ) - { - start = 0; - } - - settings._iDisplayStart = start; - } - - - function _fnRenderer( settings, type ) - { - var renderer = settings.renderer; - var host = DataTable.ext.renderer[type]; - - if ( $.isPlainObject( renderer ) && renderer[type] ) { - // Specific renderer for this type. If available use it, otherwise use - // the default. - return host[renderer[type]] || host._; - } - else if ( typeof renderer === 'string' ) { - // Common renderer - if there is one available for this type use it, - // otherwise use the default - return host[renderer] || host._; - } - - // Use the default - return host._; - } - - - /** - * Detect the data source being used for the table. Used to simplify the code - * a little (ajax) and to make it compress a little smaller. - * - * @param {object} settings dataTables settings object - * @returns {string} Data source - * @memberof DataTable#oApi - */ - function _fnDataSource ( settings ) - { - if ( settings.oFeatures.bServerSide ) { - return 'ssp'; - } - else if ( settings.ajax || settings.sAjaxSource ) { - return 'ajax'; - } - return 'dom'; - } - - - - - /** - * Computed structure of the DataTables API, defined by the options passed to - * `DataTable.Api.register()` when building the API. - * - * The structure is built in order to speed creation and extension of the Api - * objects since the extensions are effectively pre-parsed. - * - * The array is an array of objects with the following structure, where this - * base array represents the Api prototype base: - * - * [ - * { - * name: 'data' -- string - Property name - * val: function () {}, -- function - Api method (or undefined if just an object - * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result - * propExt: [ ... ] -- array - Array of Api object definitions to extend the property - * }, - * { - * name: 'row' - * val: {}, - * methodExt: [ ... ], - * propExt: [ - * { - * name: 'data' - * val: function () {}, - * methodExt: [ ... ], - * propExt: [ ... ] - * }, - * ... - * ] - * } - * ] - * - * @type {Array} - * @ignore - */ - var __apiStruct = []; - - - /** - * `Array.prototype` reference. - * - * @type object - * @ignore - */ - var __arrayProto = Array.prototype; - - - /** - * Abstraction for `context` parameter of the `Api` constructor to allow it to - * take several different forms for ease of use. - * - * Each of the input parameter types will be converted to a DataTables settings - * object where possible. - * - * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one - * of: - * - * * `string` - jQuery selector. Any DataTables' matching the given selector - * with be found and used. - * * `node` - `TABLE` node which has already been formed into a DataTable. - * * `jQuery` - A jQuery object of `TABLE` nodes. - * * `object` - DataTables settings object - * * `DataTables.Api` - API instance - * @return {array|null} Matching DataTables settings objects. `null` or - * `undefined` is returned if no matching DataTable is found. - * @ignore - */ - var _toSettings = function ( mixed ) - { - var idx, jq; - var settings = DataTable.settings; - var tables = $.map( settings, function (el, i) { - return el.nTable; - } ); - - if ( ! mixed ) { - return []; - } - else if ( mixed.nTable && mixed.oApi ) { - // DataTables settings object - return [ mixed ]; - } - else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) { - // Table node - idx = $.inArray( mixed, tables ); - return idx !== -1 ? [ settings[idx] ] : null; - } - else if ( mixed && typeof mixed.settings === 'function' ) { - return mixed.settings().toArray(); - } - else if ( typeof mixed === 'string' ) { - // jQuery selector - jq = $(mixed); - } - else if ( mixed instanceof $ ) { - // jQuery object (also DataTables instance) - jq = mixed; - } - - if ( jq ) { - return jq.map( function(i) { - idx = $.inArray( this, tables ); - return idx !== -1 ? settings[idx] : null; - } ).toArray(); - } - }; - - - /** - * DataTables API class - used to control and interface with one or more - * DataTables enhanced tables. - * - * The API class is heavily based on jQuery, presenting a chainable interface - * that you can use to interact with tables. Each instance of the API class has - * a "context" - i.e. the tables that it will operate on. This could be a single - * table, all tables on a page or a sub-set thereof. - * - * Additionally the API is designed to allow you to easily work with the data in - * the tables, retrieving and manipulating it as required. This is done by - * presenting the API class as an array like interface. The contents of the - * array depend upon the actions requested by each method (for example - * `rows().nodes()` will return an array of nodes, while `rows().data()` will - * return an array of objects or arrays depending upon your table's - * configuration). The API object has a number of array like methods (`push`, - * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`, - * `unique` etc) to assist your working with the data held in a table. - * - * Most methods (those which return an Api instance) are chainable, which means - * the return from a method call also has all of the methods available that the - * top level object had. For example, these two calls are equivalent: - * - * // Not chained - * api.row.add( {...} ); - * api.draw(); - * - * // Chained - * api.row.add( {...} ).draw(); - * - * @class DataTable.Api - * @param {array|object|string|jQuery} context DataTable identifier. This is - * used to define which DataTables enhanced tables this API will operate on. - * Can be one of: - * - * * `string` - jQuery selector. Any DataTables' matching the given selector - * with be found and used. - * * `node` - `TABLE` node which has already been formed into a DataTable. - * * `jQuery` - A jQuery object of `TABLE` nodes. - * * `object` - DataTables settings object - * @param {array} [data] Data to initialise the Api instance with. - * - * @example - * // Direct initialisation during DataTables construction - * var api = $('#example').DataTable(); - * - * @example - * // Initialisation using a DataTables jQuery object - * var api = $('#example').dataTable().api(); - * - * @example - * // Initialisation as a constructor - * var api = new $.fn.DataTable.Api( 'table.dataTable' ); - */ - _Api = function ( context, data ) - { - if ( ! (this instanceof _Api) ) { - return new _Api( context, data ); - } - - var settings = []; - var ctxSettings = function ( o ) { - var a = _toSettings( o ); - if ( a ) { - settings = settings.concat( a ); - } - }; - - if ( $.isArray( context ) ) { - for ( var i=0, ien=context.length ; i idx ? - new _Api( ctx[idx], this[idx] ) : - null; - }, - - - filter: function ( fn ) - { - var a = []; - - if ( __arrayProto.filter ) { - a = __arrayProto.filter.call( this, fn, this ); - } - else { - // Compatibility for browsers without EMCA-252-5 (JS 1.6) - for ( var i=0, ien=this.length ; i 0 ) { - return ctx[0].json; - } - - // else return undefined; - } ); - - - /** - * Get the data submitted in the last Ajax request - */ - _api_register( 'ajax.params()', function () { - var ctx = this.context; - - if ( ctx.length > 0 ) { - return ctx[0].oAjaxData; - } - - // else return undefined; - } ); - - - /** - * Reload tables from the Ajax data source. Note that this function will - * automatically re-draw the table when the remote data has been loaded. - * - * @param {boolean} [reset=true] Reset (default) or hold the current paging - * position. A full re-sort and re-filter is performed when this method is - * called, which is why the pagination reset is the default action. - * @returns {DataTables.Api} this - */ - _api_register( 'ajax.reload()', function ( callback, resetPaging ) { - return this.iterator( 'table', function (settings) { - __reload( settings, resetPaging===false, callback ); - } ); - } ); - - - /** - * Get the current Ajax URL. Note that this returns the URL from the first - * table in the current context. - * - * @return {string} Current Ajax source URL - *//** - * Set the Ajax URL. Note that this will set the URL for all tables in the - * current context. - * - * @param {string} url URL to set. - * @returns {DataTables.Api} this - */ - _api_register( 'ajax.url()', function ( url ) { - var ctx = this.context; - - if ( url === undefined ) { - // get - if ( ctx.length === 0 ) { - return undefined; - } - ctx = ctx[0]; - - return ctx.ajax ? - $.isPlainObject( ctx.ajax ) ? - ctx.ajax.url : - ctx.ajax : - ctx.sAjaxSource; - } - - // set - return this.iterator( 'table', function ( settings ) { - if ( $.isPlainObject( settings.ajax ) ) { - settings.ajax.url = url; - } - else { - settings.ajax = url; - } - // No need to consider sAjaxSource here since DataTables gives priority - // to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any - // value of `sAjaxSource` redundant. - } ); - } ); - - - /** - * Load data from the newly set Ajax URL. Note that this method is only - * available when `ajax.url()` is used to set a URL. Additionally, this method - * has the same effect as calling `ajax.reload()` but is provided for - * convenience when setting a new URL. Like `ajax.reload()` it will - * automatically redraw the table once the remote data has been loaded. - * - * @returns {DataTables.Api} this - */ - _api_register( 'ajax.url().load()', function ( callback, resetPaging ) { - // Same as a reload, but makes sense to present it for easy access after a - // url change - return this.iterator( 'table', function ( ctx ) { - __reload( ctx, resetPaging===false, callback ); - } ); - } ); - - - - - var _selector_run = function ( type, selector, selectFn, settings, opts ) - { - var - out = [], res, - a, i, ien, j, jen, - selectorType = typeof selector; - - // Can't just check for isArray here, as an API or jQuery instance might be - // given with their array like look - if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) { - selector = [ selector ]; - } - - for ( i=0, ien=selector.length ; i 0 ) { - // Assign the first element to the first item in the instance - // and truncate the instance and context - inst[0] = inst[i]; - inst[0].length = 1; - inst.length = 1; - inst.context = [ inst.context[i] ]; - - return inst; - } - } - - // Not found - return an empty instance - inst.length = 0; - return inst; - }; - - - var _selector_row_indexes = function ( settings, opts ) - { - var - i, ien, tmp, a=[], - displayFiltered = settings.aiDisplay, - displayMaster = settings.aiDisplayMaster; - - var - search = opts.search, // none, applied, removed - order = opts.order, // applied, current, index (original - compatibility with 1.9) - page = opts.page; // all, current - - if ( _fnDataSource( settings ) == 'ssp' ) { - // In server-side processing mode, most options are irrelevant since - // rows not shown don't exist and the index order is the applied order - // Removed is a special case - for consistency just return an empty - // array - return search === 'removed' ? - [] : - _range( 0, displayMaster.length ); - } - else if ( page == 'current' ) { - // Current page implies that order=current and fitler=applied, since it is - // fairly senseless otherwise, regardless of what order and search actually - // are - for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i= 0 && search == 'applied') ) - { - a.push( i ); - } - } - } - } - - return a; - }; - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Rows - * - * {} - no selector - use all available rows - * {integer} - row aoData index - * {node} - TR node - * {string} - jQuery selector to apply to the TR elements - * {array} - jQuery array of nodes, or simply an array of TR nodes - * - */ - - - var __row_selector = function ( settings, selector, opts ) - { - var run = function ( sel ) { - var selInt = _intVal( sel ); - var i, ien; - - // Short cut - selector is a number and no options provided (default is - // all records, so no need to check if the index is in there, since it - // must be - dev error if the index doesn't exist). - if ( selInt !== null && ! opts ) { - return [ selInt ]; - } - - var rows = _selector_row_indexes( settings, opts ); - - if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) { - // Selector - integer - return [ selInt ]; - } - else if ( ! sel ) { - // Selector - none - return rows; - } - - // Selector - function - if ( typeof sel === 'function' ) { - return $.map( rows, function (idx) { - var row = settings.aoData[ idx ]; - return sel( idx, row._aData, row.nTr ) ? idx : null; - } ); - } - - // Get nodes in the order from the `rows` array with null values removed - var nodes = _removeEmpty( - _pluck_order( settings.aoData, rows, 'nTr' ) - ); - - // Selector - node - if ( sel.nodeName ) { - if ( sel._DT_RowIndex !== undefined ) { - return [ sel._DT_RowIndex ]; // Property added by DT for fast lookup - } - else if ( sel._DT_CellIndex ) { - return [ sel._DT_CellIndex.row ]; - } - else { - var host = $(sel).closest('*[data-dt-row]'); - return host.length ? - [ host.data('dt-row') ] : - []; - } - } - - // ID selector. Want to always be able to select rows by id, regardless - // of if the tr element has been created or not, so can't rely upon - // jQuery here - hence a custom implementation. This does not match - // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything, - // but to select it using a CSS selector engine (like Sizzle or - // querySelect) it would need to need to be escaped for some characters. - // DataTables simplifies this for row selectors since you can select - // only a row. A # indicates an id any anything that follows is the id - - // unescaped. - if ( typeof sel === 'string' && sel.charAt(0) === '#' ) { - // get row index from id - var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ]; - if ( rowObj !== undefined ) { - return [ rowObj.idx ]; - } - - // need to fall through to jQuery in case there is DOM id that - // matches - } - - // Selector - jQuery selector string, array of nodes or jQuery object/ - // As jQuery's .filter() allows jQuery objects to be passed in filter, - // it also allows arrays, so this will cope with all three options - return $(nodes) - .filter( sel ) - .map( function () { - return this._DT_RowIndex; - } ) - .toArray(); - }; - - return _selector_run( 'row', selector, run, settings, opts ); - }; - - - _api_register( 'rows()', function ( selector, opts ) { - // argument shifting - if ( selector === undefined ) { - selector = ''; - } - else if ( $.isPlainObject( selector ) ) { - opts = selector; - selector = ''; - } - - opts = _selector_opts( opts ); - - var inst = this.iterator( 'table', function ( settings ) { - return __row_selector( settings, selector, opts ); - }, 1 ); - - // Want argument shifting here and in __row_selector? - inst.selector.rows = selector; - inst.selector.opts = opts; - - return inst; - } ); - - _api_register( 'rows().nodes()', function () { - return this.iterator( 'row', function ( settings, row ) { - return settings.aoData[ row ].nTr || undefined; - }, 1 ); - } ); - - _api_register( 'rows().data()', function () { - return this.iterator( true, 'rows', function ( settings, rows ) { - return _pluck_order( settings.aoData, rows, '_aData' ); - }, 1 ); - } ); - - _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) { - return this.iterator( 'row', function ( settings, row ) { - var r = settings.aoData[ row ]; - return type === 'search' ? r._aFilterData : r._aSortData; - }, 1 ); - } ); - - _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) { - return this.iterator( 'row', function ( settings, row ) { - _fnInvalidate( settings, row, src ); - } ); - } ); - - _api_registerPlural( 'rows().indexes()', 'row().index()', function () { - return this.iterator( 'row', function ( settings, row ) { - return row; - }, 1 ); - } ); - - _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) { - var a = []; - var context = this.context; - - // `iterator` will drop undefined values, but in this case we want them - for ( var i=0, ien=context.length ; i
    ` node is a DataTable table already or not. - * - * @param {node|jquery|string} table Table node, jQuery object or jQuery - * selector for the table to test. Note that if more than more than one - * table is passed on, only the first will be checked - * @returns {boolean} true the table given is a DataTable, or false otherwise - * @static - * @dtopt API-Static - * - * @example - * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) { - * $('#example').dataTable(); - * } - */ - DataTable.isDataTable = DataTable.fnIsDataTable = function ( table ) - { - var t = $(table).get(0); - var is = false; - - $.each( DataTable.settings, function (i, o) { - var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null; - var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null; - - if ( o.nTable === t || head === t || foot === t ) { - is = true; - } - } ); - - return is; - }; - - - /** - * Get all DataTable tables that have been initialised - optionally you can - * select to get only currently visible tables. - * - * @param {boolean} [visible=false] Flag to indicate if you want all (default) - * or visible tables only. - * @returns {array} Array of `table` nodes (not DataTable instances) which are - * DataTables - * @static - * @dtopt API-Static - * - * @example - * $.each( $.fn.dataTable.tables(true), function () { - * $(table).DataTable().columns.adjust(); - * } ); - */ - DataTable.tables = DataTable.fnTables = function ( visible ) - { - var api = false; - - if ( $.isPlainObject( visible ) ) { - api = visible.api; - visible = visible.visible; - } - - var a = $.map( DataTable.settings, function (o) { - if ( !visible || (visible && $(o.nTable).is(':visible')) ) { - return o.nTable; - } - } ); - - return api ? - new _Api( a ) : - a; - }; - - - /** - * Convert from camel case parameters to Hungarian notation. This is made public - * for the extensions to provide the same ability as DataTables core to accept - * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase - * parameters. - * - * @param {object} src The model object which holds all parameters that can be - * mapped. - * @param {object} user The object to convert from camel case to Hungarian. - * @param {boolean} force When set to `true`, properties which already have a - * Hungarian value in the `user` object will be overwritten. Otherwise they - * won't be. - */ - DataTable.camelToHungarian = _fnCamelToHungarian; - - - - /** - * - */ - _api_register( '$()', function ( selector, opts ) { - var - rows = this.rows( opts ).nodes(), // Get all rows - jqRows = $(rows); - - return $( [].concat( - jqRows.filter( selector ).toArray(), - jqRows.find( selector ).toArray() - ) ); - } ); - - - // jQuery functions to operate on the tables - $.each( [ 'on', 'one', 'off' ], function (i, key) { - _api_register( key+'()', function ( /* event, handler */ ) { - var args = Array.prototype.slice.call(arguments); - - // Add the `dt` namespace automatically if it isn't already present - if ( ! args[0].match(/\.dt\b/) ) { - args[0] += '.dt'; - } - - var inst = $( this.tables().nodes() ); - inst[key].apply( inst, args ); - return this; - } ); - } ); - - - _api_register( 'clear()', function () { - return this.iterator( 'table', function ( settings ) { - _fnClearTable( settings ); - } ); - } ); - - - _api_register( 'settings()', function () { - return new _Api( this.context, this.context ); - } ); - - - _api_register( 'init()', function () { - var ctx = this.context; - return ctx.length ? ctx[0].oInit : null; - } ); - - - _api_register( 'data()', function () { - return this.iterator( 'table', function ( settings ) { - return _pluck( settings.aoData, '_aData' ); - } ).flatten(); - } ); - - - _api_register( 'destroy()', function ( remove ) { - remove = remove || false; - - return this.iterator( 'table', function ( settings ) { - var orig = settings.nTableWrapper.parentNode; - var classes = settings.oClasses; - var table = settings.nTable; - var tbody = settings.nTBody; - var thead = settings.nTHead; - var tfoot = settings.nTFoot; - var jqTable = $(table); - var jqTbody = $(tbody); - var jqWrapper = $(settings.nTableWrapper); - var rows = $.map( settings.aoData, function (r) { return r.nTr; } ); - var i, ien; - - // Flag to note that the table is currently being destroyed - no action - // should be taken - settings.bDestroying = true; - - // Fire off the destroy callbacks for plug-ins etc - _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] ); - - // If not being removed from the document, make all columns visible - if ( ! remove ) { - new _Api( settings ).columns().visible( true ); - } - - // Blitz all `DT` namespaced events (these are internal events, the - // lowercase, `dt` events are user subscribed and they are responsible - // for removing them - jqWrapper.unbind('.DT').find(':not(tbody *)').unbind('.DT'); - $(window).unbind('.DT-'+settings.sInstance); - - // When scrolling we had to break the table up - restore it - if ( table != thead.parentNode ) { - jqTable.children('thead').detach(); - jqTable.append( thead ); - } - - if ( tfoot && table != tfoot.parentNode ) { - jqTable.children('tfoot').detach(); - jqTable.append( tfoot ); - } - - settings.aaSorting = []; - settings.aaSortingFixed = []; - _fnSortingClasses( settings ); - - $( rows ).removeClass( settings.asStripeClasses.join(' ') ); - - $('th, td', thead).removeClass( classes.sSortable+' '+ - classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone - ); - - if ( settings.bJUI ) { - $('th span.'+classes.sSortIcon+ ', td span.'+classes.sSortIcon, thead).detach(); - $('th, td', thead).each( function () { - var wrapper = $('div.'+classes.sSortJUIWrapper, this); - $(this).append( wrapper.contents() ); - wrapper.detach(); - } ); - } - - // Add the TR elements back into the table in their original order - jqTbody.children().detach(); - jqTbody.append( rows ); - - // Remove the DataTables generated nodes, events and classes - var removedMethod = remove ? 'remove' : 'detach'; - jqTable[ removedMethod ](); - jqWrapper[ removedMethod ](); - - // If we need to reattach the table to the document - if ( ! remove && orig ) { - // insertBefore acts like appendChild if !arg[1] - orig.insertBefore( table, settings.nTableReinsertBefore ); - - // Restore the width of the original table - was read from the style property, - // so we can restore directly to that - jqTable - .css( 'width', settings.sDestroyWidth ) - .removeClass( classes.sTable ); - - // If the were originally stripe classes - then we add them back here. - // Note this is not fool proof (for example if not all rows had stripe - // classes - but it's a good effort without getting carried away - ien = settings.asDestroyStripes.length; - - if ( ien ) { - jqTbody.children().each( function (i) { - $(this).addClass( settings.asDestroyStripes[i % ien] ); - } ); - } - } - - /* Remove the settings object from the settings array */ - var idx = $.inArray( settings, DataTable.settings ); - if ( idx !== -1 ) { - DataTable.settings.splice( idx, 1 ); - } - } ); - } ); - - - // Add the `every()` method for rows, columns and cells in a compact form - $.each( [ 'column', 'row', 'cell' ], function ( i, type ) { - _api_register( type+'s().every()', function ( fn ) { - var opts = this.selector.opts; - var api = this; - - return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) { - // Rows and columns: - // arg1 - index - // arg2 - table counter - // arg3 - loop counter - // arg4 - undefined - // Cells: - // arg1 - row index - // arg2 - column index - // arg3 - table counter - // arg4 - loop counter - fn.call( - api[ type ]( - arg1, - type==='cell' ? arg2 : opts, - type==='cell' ? opts : undefined - ), - arg1, arg2, arg3, arg4 - ); - } ); - } ); - } ); - - - // i18n method for extensions to be able to use the language object from the - // DataTable - _api_register( 'i18n()', function ( token, def, plural ) { - var ctx = this.context[0]; - var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage ); - - if ( resolved === undefined ) { - resolved = def; - } - - if ( plural !== undefined && $.isPlainObject( resolved ) ) { - resolved = resolved[ plural ] !== undefined ? - resolved[ plural ] : - resolved._; - } - - return resolved.replace( '%d', plural ); // nb: plural might be undefined, - } ); - - /** - * Version string for plug-ins to check compatibility. Allowed format is - * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used - * only for non-release builds. See http://semver.org/ for more information. - * @member - * @type string - * @default Version number - */ - DataTable.version = "1.10.12"; - - /** - * Private data store, containing all of the settings objects that are - * created for the tables on a given page. - * - * Note that the `DataTable.settings` object is aliased to - * `jQuery.fn.dataTableExt` through which it may be accessed and - * manipulated, or `jQuery.fn.dataTable.settings`. - * @member - * @type array - * @default [] - * @private - */ - DataTable.settings = []; - - /** - * Object models container, for the various models that DataTables has - * available to it. These models define the objects that are used to hold - * the active state and configuration of the table. - * @namespace - */ - DataTable.models = {}; - - - - /** - * Template object for the way in which DataTables holds information about - * search information for the global filter and individual column filters. - * @namespace - */ - DataTable.models.oSearch = { - /** - * Flag to indicate if the filtering should be case insensitive or not - * @type boolean - * @default true - */ - "bCaseInsensitive": true, - - /** - * Applied search term - * @type string - * @default Empty string - */ - "sSearch": "", - - /** - * Flag to indicate if the search term should be interpreted as a - * regular expression (true) or not (false) and therefore and special - * regex characters escaped. - * @type boolean - * @default false - */ - "bRegex": false, - - /** - * Flag to indicate if DataTables is to use its smart filtering or not. - * @type boolean - * @default true - */ - "bSmart": true - }; - - - - - /** - * Template object for the way in which DataTables holds information about - * each individual row. This is the object format used for the settings - * aoData array. - * @namespace - */ - DataTable.models.oRow = { - /** - * TR element for the row - * @type node - * @default null - */ - "nTr": null, - - /** - * Array of TD elements for each row. This is null until the row has been - * created. - * @type array nodes - * @default [] - */ - "anCells": null, - - /** - * Data object from the original data source for the row. This is either - * an array if using the traditional form of DataTables, or an object if - * using mData options. The exact type will depend on the passed in - * data from the data source, or will be an array if using DOM a data - * source. - * @type array|object - * @default [] - */ - "_aData": [], - - /** - * Sorting data cache - this array is ostensibly the same length as the - * number of columns (although each index is generated only as it is - * needed), and holds the data that is used for sorting each column in the - * row. We do this cache generation at the start of the sort in order that - * the formatting of the sort data need be done only once for each cell - * per sort. This array should not be read from or written to by anything - * other than the master sorting methods. - * @type array - * @default null - * @private - */ - "_aSortData": null, - - /** - * Per cell filtering data cache. As per the sort data cache, used to - * increase the performance of the filtering in DataTables - * @type array - * @default null - * @private - */ - "_aFilterData": null, - - /** - * Filtering data cache. This is the same as the cell filtering cache, but - * in this case a string rather than an array. This is easily computed with - * a join on `_aFilterData`, but is provided as a cache so the join isn't - * needed on every search (memory traded for performance) - * @type array - * @default null - * @private - */ - "_sFilterRow": null, - - /** - * Cache of the class name that DataTables has applied to the row, so we - * can quickly look at this variable rather than needing to do a DOM check - * on className for the nTr property. - * @type string - * @default Empty string - * @private - */ - "_sRowStripe": "", - - /** - * Denote if the original data source was from the DOM, or the data source - * object. This is used for invalidating data, so DataTables can - * automatically read data from the original source, unless uninstructed - * otherwise. - * @type string - * @default null - * @private - */ - "src": null, - - /** - * Index in the aoData array. This saves an indexOf lookup when we have the - * object, but want to know the index - * @type integer - * @default -1 - * @private - */ - "idx": -1 - }; - - - /** - * Template object for the column information object in DataTables. This object - * is held in the settings aoColumns array and contains all the information that - * DataTables needs about each individual column. - * - * Note that this object is related to {@link DataTable.defaults.column} - * but this one is the internal data store for DataTables's cache of columns. - * It should NOT be manipulated outside of DataTables. Any configuration should - * be done through the initialisation options. - * @namespace - */ - DataTable.models.oColumn = { - /** - * Column index. This could be worked out on-the-fly with $.inArray, but it - * is faster to just hold it as a variable - * @type integer - * @default null - */ - "idx": null, - - /** - * A list of the columns that sorting should occur on when this column - * is sorted. That this property is an array allows multi-column sorting - * to be defined for a column (for example first name / last name columns - * would benefit from this). The values are integers pointing to the - * columns to be sorted on (typically it will be a single integer pointing - * at itself, but that doesn't need to be the case). - * @type array - */ - "aDataSort": null, - - /** - * Define the sorting directions that are applied to the column, in sequence - * as the column is repeatedly sorted upon - i.e. the first value is used - * as the sorting direction when the column if first sorted (clicked on). - * Sort it again (click again) and it will move on to the next index. - * Repeat until loop. - * @type array - */ - "asSorting": null, - - /** - * Flag to indicate if the column is searchable, and thus should be included - * in the filtering or not. - * @type boolean - */ - "bSearchable": null, - - /** - * Flag to indicate if the column is sortable or not. - * @type boolean - */ - "bSortable": null, - - /** - * Flag to indicate if the column is currently visible in the table or not - * @type boolean - */ - "bVisible": null, - - /** - * Store for manual type assignment using the `column.type` option. This - * is held in store so we can manipulate the column's `sType` property. - * @type string - * @default null - * @private - */ - "_sManualType": null, - - /** - * Flag to indicate if HTML5 data attributes should be used as the data - * source for filtering or sorting. True is either are. - * @type boolean - * @default false - * @private - */ - "_bAttrSrc": false, - - /** - * Developer definable function that is called whenever a cell is created (Ajax source, - * etc) or processed for input (DOM source). This can be used as a compliment to mRender - * allowing you to modify the DOM element (add background colour for example) when the - * element is available. - * @type function - * @param {element} nTd The TD node that has been created - * @param {*} sData The Data for the cell - * @param {array|object} oData The data for the whole row - * @param {int} iRow The row index for the aoData data store - * @default null - */ - "fnCreatedCell": null, - - /** - * Function to get data from a cell in a column. You should never - * access data directly through _aData internally in DataTables - always use - * the method attached to this property. It allows mData to function as - * required. This function is automatically assigned by the column - * initialisation method - * @type function - * @param {array|object} oData The data array/object for the array - * (i.e. aoData[]._aData) - * @param {string} sSpecific The specific data type you want to get - - * 'display', 'type' 'filter' 'sort' - * @returns {*} The data for the cell from the given row's data - * @default null - */ - "fnGetData": null, - - /** - * Function to set data for a cell in the column. You should never - * set the data directly to _aData internally in DataTables - always use - * this method. It allows mData to function as required. This function - * is automatically assigned by the column initialisation method - * @type function - * @param {array|object} oData The data array/object for the array - * (i.e. aoData[]._aData) - * @param {*} sValue Value to set - * @default null - */ - "fnSetData": null, - - /** - * Property to read the value for the cells in the column from the data - * source array / object. If null, then the default content is used, if a - * function is given then the return from the function is used. - * @type function|int|string|null - * @default null - */ - "mData": null, - - /** - * Partner property to mData which is used (only when defined) to get - * the data - i.e. it is basically the same as mData, but without the - * 'set' option, and also the data fed to it is the result from mData. - * This is the rendering method to match the data method of mData. - * @type function|int|string|null - * @default null - */ - "mRender": null, - - /** - * Unique header TH/TD element for this column - this is what the sorting - * listener is attached to (if sorting is enabled.) - * @type node - * @default null - */ - "nTh": null, - - /** - * Unique footer TH/TD element for this column (if there is one). Not used - * in DataTables as such, but can be used for plug-ins to reference the - * footer for each column. - * @type node - * @default null - */ - "nTf": null, - - /** - * The class to apply to all TD elements in the table's TBODY for the column - * @type string - * @default null - */ - "sClass": null, - - /** - * When DataTables calculates the column widths to assign to each column, - * it finds the longest string in each column and then constructs a - * temporary table and reads the widths from that. The problem with this - * is that "mmm" is much wider then "iiii", but the latter is a longer - * string - thus the calculation can go wrong (doing it properly and putting - * it into an DOM object and measuring that is horribly(!) slow). Thus as - * a "work around" we provide this option. It will append its value to the - * text that is found to be the longest string for the column - i.e. padding. - * @type string - */ - "sContentPadding": null, - - /** - * Allows a default value to be given for a column's data, and will be used - * whenever a null data source is encountered (this can be because mData - * is set to null, or because the data source itself is null). - * @type string - * @default null - */ - "sDefaultContent": null, - - /** - * Name for the column, allowing reference to the column by name as well as - * by index (needs a lookup to work by name). - * @type string - */ - "sName": null, - - /** - * Custom sorting data type - defines which of the available plug-ins in - * afnSortData the custom sorting will use - if any is defined. - * @type string - * @default std - */ - "sSortDataType": 'std', - - /** - * Class to be applied to the header element when sorting on this column - * @type string - * @default null - */ - "sSortingClass": null, - - /** - * Class to be applied to the header element when sorting on this column - - * when jQuery UI theming is used. - * @type string - * @default null - */ - "sSortingClassJUI": null, - - /** - * Title of the column - what is seen in the TH element (nTh). - * @type string - */ - "sTitle": null, - - /** - * Column sorting and filtering type - * @type string - * @default null - */ - "sType": null, - - /** - * Width of the column - * @type string - * @default null - */ - "sWidth": null, - - /** - * Width of the column when it was first "encountered" - * @type string - * @default null - */ - "sWidthOrig": null - }; - - - /* - * Developer note: The properties of the object below are given in Hungarian - * notation, that was used as the interface for DataTables prior to v1.10, however - * from v1.10 onwards the primary interface is camel case. In order to avoid - * breaking backwards compatibility utterly with this change, the Hungarian - * version is still, internally the primary interface, but is is not documented - * - hence the @name tags in each doc comment. This allows a Javascript function - * to create a map from Hungarian notation to camel case (going the other direction - * would require each property to be listed, which would at around 3K to the size - * of DataTables, while this method is about a 0.5K hit. - * - * Ultimately this does pave the way for Hungarian notation to be dropped - * completely, but that is a massive amount of work and will break current - * installs (therefore is on-hold until v2). - */ - - /** - * Initialisation options that can be given to DataTables at initialisation - * time. - * @namespace - */ - DataTable.defaults = { - /** - * An array of data to use for the table, passed in at initialisation which - * will be used in preference to any data which is already in the DOM. This is - * particularly useful for constructing tables purely in Javascript, for - * example with a custom Ajax call. - * @type array - * @default null - * - * @dtopt Option - * @name DataTable.defaults.data - * - * @example - * // Using a 2D array data source - * $(document).ready( function () { - * $('#example').dataTable( { - * "data": [ - * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'], - * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'], - * ], - * "columns": [ - * { "title": "Engine" }, - * { "title": "Browser" }, - * { "title": "Platform" }, - * { "title": "Version" }, - * { "title": "Grade" } - * ] - * } ); - * } ); - * - * @example - * // Using an array of objects as a data source (`data`) - * $(document).ready( function () { - * $('#example').dataTable( { - * "data": [ - * { - * "engine": "Trident", - * "browser": "Internet Explorer 4.0", - * "platform": "Win 95+", - * "version": 4, - * "grade": "X" - * }, - * { - * "engine": "Trident", - * "browser": "Internet Explorer 5.0", - * "platform": "Win 95+", - * "version": 5, - * "grade": "C" - * } - * ], - * "columns": [ - * { "title": "Engine", "data": "engine" }, - * { "title": "Browser", "data": "browser" }, - * { "title": "Platform", "data": "platform" }, - * { "title": "Version", "data": "version" }, - * { "title": "Grade", "data": "grade" } - * ] - * } ); - * } ); - */ - "aaData": null, - - - /** - * If ordering is enabled, then DataTables will perform a first pass sort on - * initialisation. You can define which column(s) the sort is performed - * upon, and the sorting direction, with this variable. The `sorting` array - * should contain an array for each column to be sorted initially containing - * the column's index and a direction string ('asc' or 'desc'). - * @type array - * @default [[0,'asc']] - * - * @dtopt Option - * @name DataTable.defaults.order - * - * @example - * // Sort by 3rd column first, and then 4th column - * $(document).ready( function() { - * $('#example').dataTable( { - * "order": [[2,'asc'], [3,'desc']] - * } ); - * } ); - * - * // No initial sorting - * $(document).ready( function() { - * $('#example').dataTable( { - * "order": [] - * } ); - * } ); - */ - "aaSorting": [[0,'asc']], - - - /** - * This parameter is basically identical to the `sorting` parameter, but - * cannot be overridden by user interaction with the table. What this means - * is that you could have a column (visible or hidden) which the sorting - * will always be forced on first - any sorting after that (from the user) - * will then be performed as required. This can be useful for grouping rows - * together. - * @type array - * @default null - * - * @dtopt Option - * @name DataTable.defaults.orderFixed - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "orderFixed": [[0,'asc']] - * } ); - * } ) - */ - "aaSortingFixed": [], - - - /** - * DataTables can be instructed to load data to display in the table from a - * Ajax source. This option defines how that Ajax call is made and where to. - * - * The `ajax` property has three different modes of operation, depending on - * how it is defined. These are: - * - * * `string` - Set the URL from where the data should be loaded from. - * * `object` - Define properties for `jQuery.ajax`. - * * `function` - Custom data get function - * - * `string` - * -------- - * - * As a string, the `ajax` property simply defines the URL from which - * DataTables will load data. - * - * `object` - * -------- - * - * As an object, the parameters in the object are passed to - * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control - * of the Ajax request. DataTables has a number of default parameters which - * you can override using this option. Please refer to the jQuery - * documentation for a full description of the options available, although - * the following parameters provide additional options in DataTables or - * require special consideration: - * - * * `data` - As with jQuery, `data` can be provided as an object, but it - * can also be used as a function to manipulate the data DataTables sends - * to the server. The function takes a single parameter, an object of - * parameters with the values that DataTables has readied for sending. An - * object may be returned which will be merged into the DataTables - * defaults, or you can add the items to the object that was passed in and - * not return anything from the function. This supersedes `fnServerParams` - * from DataTables 1.9-. - * - * * `dataSrc` - By default DataTables will look for the property `data` (or - * `aaData` for compatibility with DataTables 1.9-) when obtaining data - * from an Ajax source or for server-side processing - this parameter - * allows that property to be changed. You can use Javascript dotted - * object notation to get a data source for multiple levels of nesting, or - * it my be used as a function. As a function it takes a single parameter, - * the JSON returned from the server, which can be manipulated as - * required, with the returned value being that used by DataTables as the - * data source for the table. This supersedes `sAjaxDataProp` from - * DataTables 1.9-. - * - * * `success` - Should not be overridden it is used internally in - * DataTables. To manipulate / transform the data returned by the server - * use `ajax.dataSrc`, or use `ajax` as a function (see below). - * - * `function` - * ---------- - * - * As a function, making the Ajax call is left up to yourself allowing - * complete control of the Ajax request. Indeed, if desired, a method other - * than Ajax could be used to obtain the required data, such as Web storage - * or an AIR database. - * - * The function is given four parameters and no return is required. The - * parameters are: - * - * 1. _object_ - Data to send to the server - * 2. _function_ - Callback function that must be executed when the required - * data has been obtained. That data should be passed into the callback - * as the only parameter - * 3. _object_ - DataTables settings object for the table - * - * Note that this supersedes `fnServerData` from DataTables 1.9-. - * - * @type string|object|function - * @default null - * - * @dtopt Option - * @name DataTable.defaults.ajax - * @since 1.10.0 - * - * @example - * // Get JSON data from a file via Ajax. - * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default). - * $('#example').dataTable( { - * "ajax": "data.json" - * } ); - * - * @example - * // Get JSON data from a file via Ajax, using `dataSrc` to change - * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`) - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "dataSrc": "tableData" - * } - * } ); - * - * @example - * // Get JSON data from a file via Ajax, using `dataSrc` to read data - * // from a plain array rather than an array in an object - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "dataSrc": "" - * } - * } ); - * - * @example - * // Manipulate the data returned from the server - add a link to data - * // (note this can, should, be done using `render` for the column - this - * // is just a simple example of how the data can be manipulated). - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "dataSrc": function ( json ) { - * for ( var i=0, ien=json.length ; iView message'; - * } - * return json; - * } - * } - * } ); - * - * @example - * // Add data to the request - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "data": function ( d ) { - * return { - * "extra_search": $('#extra').val() - * }; - * } - * } - * } ); - * - * @example - * // Send request as POST - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "type": "POST" - * } - * } ); - * - * @example - * // Get the data from localStorage (could interface with a form for - * // adding, editing and removing rows). - * $('#example').dataTable( { - * "ajax": function (data, callback, settings) { - * callback( - * JSON.parse( localStorage.getItem('dataTablesData') ) - * ); - * } - * } ); - */ - "ajax": null, - - - /** - * This parameter allows you to readily specify the entries in the length drop - * down menu that DataTables shows when pagination is enabled. It can be - * either a 1D array of options which will be used for both the displayed - * option and the value, or a 2D array which will use the array in the first - * position as the value, and the array in the second position as the - * displayed options (useful for language strings such as 'All'). - * - * Note that the `pageLength` property will be automatically set to the - * first value given in this array, unless `pageLength` is also provided. - * @type array - * @default [ 10, 25, 50, 100 ] - * - * @dtopt Option - * @name DataTable.defaults.lengthMenu - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]] - * } ); - * } ); - */ - "aLengthMenu": [ 10, 25, 50, 100 ], - - - /** - * The `columns` option in the initialisation parameter allows you to define - * details about the way individual columns behave. For a full list of - * column options that can be set, please see - * {@link DataTable.defaults.column}. Note that if you use `columns` to - * define your columns, you must have an entry in the array for every single - * column that you have in your table (these can be null if you don't which - * to specify any options). - * @member - * - * @name DataTable.defaults.column - */ - "aoColumns": null, - - /** - * Very similar to `columns`, `columnDefs` allows you to target a specific - * column, multiple columns, or all columns, using the `targets` property of - * each object in the array. This allows great flexibility when creating - * tables, as the `columnDefs` arrays can be of any length, targeting the - * columns you specifically want. `columnDefs` may use any of the column - * options available: {@link DataTable.defaults.column}, but it _must_ - * have `targets` defined in each object in the array. Values in the `targets` - * array may be: - *
      - *
    • a string - class name will be matched on the TH for the column
    • - *
    • 0 or a positive integer - column index counting from the left
    • - *
    • a negative integer - column index counting from the right
    • - *
    • the string "_all" - all columns (i.e. assign a default)
    • - *
    - * @member - * - * @name DataTable.defaults.columnDefs - */ - "aoColumnDefs": null, - - - /** - * Basically the same as `search`, this parameter defines the individual column - * filtering state at initialisation time. The array must be of the same size - * as the number of columns, and each element be an object with the parameters - * `search` and `escapeRegex` (the latter is optional). 'null' is also - * accepted and the default will be used. - * @type array - * @default [] - * - * @dtopt Option - * @name DataTable.defaults.searchCols - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "searchCols": [ - * null, - * { "search": "My filter" }, - * null, - * { "search": "^[0-9]", "escapeRegex": false } - * ] - * } ); - * } ) - */ - "aoSearchCols": [], - - - /** - * An array of CSS classes that should be applied to displayed rows. This - * array may be of any length, and DataTables will apply each class - * sequentially, looping when required. - * @type array - * @default null Will take the values determined by the `oClasses.stripe*` - * options - * - * @dtopt Option - * @name DataTable.defaults.stripeClasses - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ] - * } ); - * } ) - */ - "asStripeClasses": null, - - - /** - * Enable or disable automatic column width calculation. This can be disabled - * as an optimisation (it takes some time to calculate the widths) if the - * tables widths are passed in using `columns`. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.autoWidth - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "autoWidth": false - * } ); - * } ); - */ - "bAutoWidth": true, - - - /** - * Deferred rendering can provide DataTables with a huge speed boost when you - * are using an Ajax or JS data source for the table. This option, when set to - * true, will cause DataTables to defer the creation of the table elements for - * each row until they are needed for a draw - saving a significant amount of - * time. - * @type boolean - * @default false - * - * @dtopt Features - * @name DataTable.defaults.deferRender - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajax": "sources/arrays.txt", - * "deferRender": true - * } ); - * } ); - */ - "bDeferRender": false, - - - /** - * Replace a DataTable which matches the given selector and replace it with - * one which has the properties of the new initialisation object passed. If no - * table matches the selector, then the new DataTable will be constructed as - * per normal. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.destroy - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "srollY": "200px", - * "paginate": false - * } ); - * - * // Some time later.... - * $('#example').dataTable( { - * "filter": false, - * "destroy": true - * } ); - * } ); - */ - "bDestroy": false, - - - /** - * Enable or disable filtering of data. Filtering in DataTables is "smart" in - * that it allows the end user to input multiple words (space separated) and - * will match a row containing those words, even if not in the order that was - * specified (this allow matching across multiple columns). Note that if you - * wish to use filtering in DataTables this must remain 'true' - to remove the - * default filtering input box and retain filtering abilities, please use - * {@link DataTable.defaults.dom}. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.searching - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "searching": false - * } ); - * } ); - */ - "bFilter": true, - - - /** - * Enable or disable the table information display. This shows information - * about the data that is currently visible on the page, including information - * about filtered data if that action is being performed. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.info - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "info": false - * } ); - * } ); - */ - "bInfo": true, - - - /** - * Enable jQuery UI ThemeRoller support (required as ThemeRoller requires some - * slightly different and additional mark-up from what DataTables has - * traditionally used). - * @type boolean - * @default false - * - * @dtopt Features - * @name DataTable.defaults.jQueryUI - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "jQueryUI": true - * } ); - * } ); - */ - "bJQueryUI": false, - - - /** - * Allows the end user to select the size of a formatted page from a select - * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`). - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.lengthChange - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "lengthChange": false - * } ); - * } ); - */ - "bLengthChange": true, - - - /** - * Enable or disable pagination. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.paging - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "paging": false - * } ); - * } ); - */ - "bPaginate": true, - - - /** - * Enable or disable the display of a 'processing' indicator when the table is - * being processed (e.g. a sort). This is particularly useful for tables with - * large amounts of data where it can take a noticeable amount of time to sort - * the entries. - * @type boolean - * @default false - * - * @dtopt Features - * @name DataTable.defaults.processing - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "processing": true - * } ); - * } ); - */ - "bProcessing": false, - - - /** - * Retrieve the DataTables object for the given selector. Note that if the - * table has already been initialised, this parameter will cause DataTables - * to simply return the object that has already been set up - it will not take - * account of any changes you might have made to the initialisation object - * passed to DataTables (setting this parameter to true is an acknowledgement - * that you understand this). `destroy` can be used to reinitialise a table if - * you need. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.retrieve - * - * @example - * $(document).ready( function() { - * initTable(); - * tableActions(); - * } ); - * - * function initTable () - * { - * return $('#example').dataTable( { - * "scrollY": "200px", - * "paginate": false, - * "retrieve": true - * } ); - * } - * - * function tableActions () - * { - * var table = initTable(); - * // perform API operations with oTable - * } - */ - "bRetrieve": false, - - - /** - * When vertical (y) scrolling is enabled, DataTables will force the height of - * the table's viewport to the given height at all times (useful for layout). - * However, this can look odd when filtering data down to a small data set, - * and the footer is left "floating" further down. This parameter (when - * enabled) will cause DataTables to collapse the table's viewport down when - * the result set will fit within the given Y height. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.scrollCollapse - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollY": "200", - * "scrollCollapse": true - * } ); - * } ); - */ - "bScrollCollapse": false, - - - /** - * Configure DataTables to use server-side processing. Note that the - * `ajax` parameter must also be given in order to give DataTables a - * source to obtain the required data for each draw. - * @type boolean - * @default false - * - * @dtopt Features - * @dtopt Server-side - * @name DataTable.defaults.serverSide - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "serverSide": true, - * "ajax": "xhr.php" - * } ); - * } ); - */ - "bServerSide": false, - - - /** - * Enable or disable sorting of columns. Sorting of individual columns can be - * disabled by the `sortable` option for each column. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.ordering - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "ordering": false - * } ); - * } ); - */ - "bSort": true, - - - /** - * Enable or display DataTables' ability to sort multiple columns at the - * same time (activated by shift-click by the user). - * @type boolean - * @default true - * - * @dtopt Options - * @name DataTable.defaults.orderMulti - * - * @example - * // Disable multiple column sorting ability - * $(document).ready( function () { - * $('#example').dataTable( { - * "orderMulti": false - * } ); - * } ); - */ - "bSortMulti": true, - - - /** - * Allows control over whether DataTables should use the top (true) unique - * cell that is found for a single column, or the bottom (false - default). - * This is useful when using complex headers. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.orderCellsTop - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "orderCellsTop": true - * } ); - * } ); - */ - "bSortCellsTop": false, - - - /** - * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and - * `sorting\_3` to the columns which are currently being sorted on. This is - * presented as a feature switch as it can increase processing time (while - * classes are removed and added) so for large data sets you might want to - * turn this off. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.orderClasses - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "orderClasses": false - * } ); - * } ); - */ - "bSortClasses": true, - - - /** - * Enable or disable state saving. When enabled HTML5 `localStorage` will be - * used to save table display information such as pagination information, - * display length, filtering and sorting. As such when the end user reloads - * the page the display display will match what thy had previously set up. - * - * Due to the use of `localStorage` the default state saving is not supported - * in IE6 or 7. If state saving is required in those browsers, use - * `stateSaveCallback` to provide a storage solution such as cookies. - * @type boolean - * @default false - * - * @dtopt Features - * @name DataTable.defaults.stateSave - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "stateSave": true - * } ); - * } ); - */ - "bStateSave": false, - - - /** - * This function is called when a TR element is created (and all TD child - * elements have been inserted), or registered if using a DOM source, allowing - * manipulation of the TR element (adding classes etc). - * @type function - * @param {node} row "TR" element for the current row - * @param {array} data Raw data array for this row - * @param {int} dataIndex The index of this row in the internal aoData array - * - * @dtopt Callbacks - * @name DataTable.defaults.createdRow - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "createdRow": function( row, data, dataIndex ) { - * // Bold the grade for all 'A' grade browsers - * if ( data[4] == "A" ) - * { - * $('td:eq(4)', row).html( 'A' ); - * } - * } - * } ); - * } ); - */ - "fnCreatedRow": null, - - - /** - * This function is called on every 'draw' event, and allows you to - * dynamically modify any aspect you want about the created DOM. - * @type function - * @param {object} settings DataTables settings object - * - * @dtopt Callbacks - * @name DataTable.defaults.drawCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "drawCallback": function( settings ) { - * alert( 'DataTables has redrawn the table' ); - * } - * } ); - * } ); - */ - "fnDrawCallback": null, - - - /** - * Identical to fnHeaderCallback() but for the table footer this function - * allows you to modify the table footer on every 'draw' event. - * @type function - * @param {node} foot "TR" element for the footer - * @param {array} data Full table data (as derived from the original HTML) - * @param {int} start Index for the current display starting point in the - * display array - * @param {int} end Index for the current display ending point in the - * display array - * @param {array int} display Index array to translate the visual position - * to the full data array - * - * @dtopt Callbacks - * @name DataTable.defaults.footerCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "footerCallback": function( tfoot, data, start, end, display ) { - * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start; - * } - * } ); - * } ) - */ - "fnFooterCallback": null, - - - /** - * When rendering large numbers in the information element for the table - * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers - * to have a comma separator for the 'thousands' units (e.g. 1 million is - * rendered as "1,000,000") to help readability for the end user. This - * function will override the default method DataTables uses. - * @type function - * @member - * @param {int} toFormat number to be formatted - * @returns {string} formatted string for DataTables to show the number - * - * @dtopt Callbacks - * @name DataTable.defaults.formatNumber - * - * @example - * // Format a number using a single quote for the separator (note that - * // this can also be done with the language.thousands option) - * $(document).ready( function() { - * $('#example').dataTable( { - * "formatNumber": function ( toFormat ) { - * return toFormat.toString().replace( - * /\B(?=(\d{3})+(?!\d))/g, "'" - * ); - * }; - * } ); - * } ); - */ - "fnFormatNumber": function ( toFormat ) { - return toFormat.toString().replace( - /\B(?=(\d{3})+(?!\d))/g, - this.oLanguage.sThousands - ); - }, - - - /** - * This function is called on every 'draw' event, and allows you to - * dynamically modify the header row. This can be used to calculate and - * display useful information about the table. - * @type function - * @param {node} head "TR" element for the header - * @param {array} data Full table data (as derived from the original HTML) - * @param {int} start Index for the current display starting point in the - * display array - * @param {int} end Index for the current display ending point in the - * display array - * @param {array int} display Index array to translate the visual position - * to the full data array - * - * @dtopt Callbacks - * @name DataTable.defaults.headerCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "fheaderCallback": function( head, data, start, end, display ) { - * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records"; - * } - * } ); - * } ) - */ - "fnHeaderCallback": null, - - - /** - * The information element can be used to convey information about the current - * state of the table. Although the internationalisation options presented by - * DataTables are quite capable of dealing with most customisations, there may - * be times where you wish to customise the string further. This callback - * allows you to do exactly that. - * @type function - * @param {object} oSettings DataTables settings object - * @param {int} start Starting position in data for the draw - * @param {int} end End position in data for the draw - * @param {int} max Total number of rows in the table (regardless of - * filtering) - * @param {int} total Total number of rows in the data set, after filtering - * @param {string} pre The string that DataTables has formatted using it's - * own rules - * @returns {string} The string to be displayed in the information element. - * - * @dtopt Callbacks - * @name DataTable.defaults.infoCallback - * - * @example - * $('#example').dataTable( { - * "infoCallback": function( settings, start, end, max, total, pre ) { - * return start +" to "+ end; - * } - * } ); - */ - "fnInfoCallback": null, - - - /** - * Called when the table has been initialised. Normally DataTables will - * initialise sequentially and there will be no need for this function, - * however, this does not hold true when using external language information - * since that is obtained using an async XHR call. - * @type function - * @param {object} settings DataTables settings object - * @param {object} json The JSON object request from the server - only - * present if client-side Ajax sourced data is used - * - * @dtopt Callbacks - * @name DataTable.defaults.initComplete - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "initComplete": function(settings, json) { - * alert( 'DataTables has finished its initialisation.' ); - * } - * } ); - * } ) - */ - "fnInitComplete": null, - - - /** - * Called at the very start of each table draw and can be used to cancel the - * draw by returning false, any other return (including undefined) results in - * the full draw occurring). - * @type function - * @param {object} settings DataTables settings object - * @returns {boolean} False will cancel the draw, anything else (including no - * return) will allow it to complete. - * - * @dtopt Callbacks - * @name DataTable.defaults.preDrawCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "preDrawCallback": function( settings ) { - * if ( $('#test').val() == 1 ) { - * return false; - * } - * } - * } ); - * } ); - */ - "fnPreDrawCallback": null, - - - /** - * This function allows you to 'post process' each row after it have been - * generated for each table draw, but before it is rendered on screen. This - * function might be used for setting the row class name etc. - * @type function - * @param {node} row "TR" element for the current row - * @param {array} data Raw data array for this row - * @param {int} displayIndex The display index for the current table draw - * @param {int} displayIndexFull The index of the data in the full list of - * rows (after filtering) - * - * @dtopt Callbacks - * @name DataTable.defaults.rowCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "rowCallback": function( row, data, displayIndex, displayIndexFull ) { - * // Bold the grade for all 'A' grade browsers - * if ( data[4] == "A" ) { - * $('td:eq(4)', row).html( 'A' ); - * } - * } - * } ); - * } ); - */ - "fnRowCallback": null, - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * This parameter allows you to override the default function which obtains - * the data from the server so something more suitable for your application. - * For example you could use POST data, or pull information from a Gears or - * AIR database. - * @type function - * @member - * @param {string} source HTTP source to obtain the data from (`ajax`) - * @param {array} data A key/value pair object containing the data to send - * to the server - * @param {function} callback to be called on completion of the data get - * process that will draw the data on the page. - * @param {object} settings DataTables settings object - * - * @dtopt Callbacks - * @dtopt Server-side - * @name DataTable.defaults.serverData - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "fnServerData": null, - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * It is often useful to send extra data to the server when making an Ajax - * request - for example custom filtering information, and this callback - * function makes it trivial to send extra information to the server. The - * passed in parameter is the data set that has been constructed by - * DataTables, and you can add to this or modify it as you require. - * @type function - * @param {array} data Data array (array of objects which are name/value - * pairs) that has been constructed by DataTables and will be sent to the - * server. In the case of Ajax sourced data with server-side processing - * this will be an empty array, for server-side processing there will be a - * significant number of parameters! - * @returns {undefined} Ensure that you modify the data array passed in, - * as this is passed by reference. - * - * @dtopt Callbacks - * @dtopt Server-side - * @name DataTable.defaults.serverParams - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "fnServerParams": null, - - - /** - * Load the table state. With this function you can define from where, and how, the - * state of a table is loaded. By default DataTables will load from `localStorage` - * but you might wish to use a server-side database or cookies. - * @type function - * @member - * @param {object} settings DataTables settings object - * @return {object} The DataTables state object to be loaded - * - * @dtopt Callbacks - * @name DataTable.defaults.stateLoadCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoadCallback": function (settings) { - * var o; - * - * // Send an Ajax request to the server to get the data. Note that - * // this is a synchronous request. - * $.ajax( { - * "url": "/state_load", - * "async": false, - * "dataType": "json", - * "success": function (json) { - * o = json; - * } - * } ); - * - * return o; - * } - * } ); - * } ); - */ - "fnStateLoadCallback": function ( settings ) { - try { - return JSON.parse( - (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem( - 'DataTables_'+settings.sInstance+'_'+location.pathname - ) - ); - } catch (e) {} - }, - - - /** - * Callback which allows modification of the saved state prior to loading that state. - * This callback is called when the table is loading state from the stored data, but - * prior to the settings object being modified by the saved state. Note that for - * plug-in authors, you should use the `stateLoadParams` event to load parameters for - * a plug-in. - * @type function - * @param {object} settings DataTables settings object - * @param {object} data The state object that is to be loaded - * - * @dtopt Callbacks - * @name DataTable.defaults.stateLoadParams - * - * @example - * // Remove a saved filter, so filtering is never loaded - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoadParams": function (settings, data) { - * data.oSearch.sSearch = ""; - * } - * } ); - * } ); - * - * @example - * // Disallow state loading by returning false - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoadParams": function (settings, data) { - * return false; - * } - * } ); - * } ); - */ - "fnStateLoadParams": null, - - - /** - * Callback that is called when the state has been loaded from the state saving method - * and the DataTables settings object has been modified as a result of the loaded state. - * @type function - * @param {object} settings DataTables settings object - * @param {object} data The state object that was loaded - * - * @dtopt Callbacks - * @name DataTable.defaults.stateLoaded - * - * @example - * // Show an alert with the filtering value that was saved - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoaded": function (settings, data) { - * alert( 'Saved filter was: '+data.oSearch.sSearch ); - * } - * } ); - * } ); - */ - "fnStateLoaded": null, - - - /** - * Save the table state. This function allows you to define where and how the state - * information for the table is stored By default DataTables will use `localStorage` - * but you might wish to use a server-side database or cookies. - * @type function - * @member - * @param {object} settings DataTables settings object - * @param {object} data The state object to be saved - * - * @dtopt Callbacks - * @name DataTable.defaults.stateSaveCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateSaveCallback": function (settings, data) { - * // Send an Ajax request to the server with the state object - * $.ajax( { - * "url": "/state_save", - * "data": data, - * "dataType": "json", - * "method": "POST" - * "success": function () {} - * } ); - * } - * } ); - * } ); - */ - "fnStateSaveCallback": function ( settings, data ) { - try { - (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem( - 'DataTables_'+settings.sInstance+'_'+location.pathname, - JSON.stringify( data ) - ); - } catch (e) {} - }, - - - /** - * Callback which allows modification of the state to be saved. Called when the table - * has changed state a new state save is required. This method allows modification of - * the state saving object prior to actually doing the save, including addition or - * other state properties or modification. Note that for plug-in authors, you should - * use the `stateSaveParams` event to save parameters for a plug-in. - * @type function - * @param {object} settings DataTables settings object - * @param {object} data The state object to be saved - * - * @dtopt Callbacks - * @name DataTable.defaults.stateSaveParams - * - * @example - * // Remove a saved filter, so filtering is never saved - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateSaveParams": function (settings, data) { - * data.oSearch.sSearch = ""; - * } - * } ); - * } ); - */ - "fnStateSaveParams": null, - - - /** - * Duration for which the saved state information is considered valid. After this period - * has elapsed the state will be returned to the default. - * Value is given in seconds. - * @type int - * @default 7200 (2 hours) - * - * @dtopt Options - * @name DataTable.defaults.stateDuration - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateDuration": 60*60*24; // 1 day - * } ); - * } ) - */ - "iStateDuration": 7200, - - - /** - * When enabled DataTables will not make a request to the server for the first - * page draw - rather it will use the data already on the page (no sorting etc - * will be applied to it), thus saving on an XHR at load time. `deferLoading` - * is used to indicate that deferred loading is required, but it is also used - * to tell DataTables how many records there are in the full table (allowing - * the information element and pagination to be displayed correctly). In the case - * where a filtering is applied to the table on initial load, this can be - * indicated by giving the parameter as an array, where the first element is - * the number of records available after filtering and the second element is the - * number of records without filtering (allowing the table information element - * to be shown correctly). - * @type int | array - * @default null - * - * @dtopt Options - * @name DataTable.defaults.deferLoading - * - * @example - * // 57 records available in the table, no filtering applied - * $(document).ready( function() { - * $('#example').dataTable( { - * "serverSide": true, - * "ajax": "scripts/server_processing.php", - * "deferLoading": 57 - * } ); - * } ); - * - * @example - * // 57 records after filtering, 100 without filtering (an initial filter applied) - * $(document).ready( function() { - * $('#example').dataTable( { - * "serverSide": true, - * "ajax": "scripts/server_processing.php", - * "deferLoading": [ 57, 100 ], - * "search": { - * "search": "my_filter" - * } - * } ); - * } ); - */ - "iDeferLoading": null, - - - /** - * Number of rows to display on a single page when using pagination. If - * feature enabled (`lengthChange`) then the end user will be able to override - * this to a custom setting using a pop-up menu. - * @type int - * @default 10 - * - * @dtopt Options - * @name DataTable.defaults.pageLength - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "pageLength": 50 - * } ); - * } ) - */ - "iDisplayLength": 10, - - - /** - * Define the starting point for data display when using DataTables with - * pagination. Note that this parameter is the number of records, rather than - * the page number, so if you have 10 records per page and want to start on - * the third page, it should be "20". - * @type int - * @default 0 - * - * @dtopt Options - * @name DataTable.defaults.displayStart - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "displayStart": 20 - * } ); - * } ) - */ - "iDisplayStart": 0, - - - /** - * By default DataTables allows keyboard navigation of the table (sorting, paging, - * and filtering) by adding a `tabindex` attribute to the required elements. This - * allows you to tab through the controls and press the enter key to activate them. - * The tabindex is default 0, meaning that the tab follows the flow of the document. - * You can overrule this using this parameter if you wish. Use a value of -1 to - * disable built-in keyboard navigation. - * @type int - * @default 0 - * - * @dtopt Options - * @name DataTable.defaults.tabIndex - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "tabIndex": 1 - * } ); - * } ); - */ - "iTabIndex": 0, - - - /** - * Classes that DataTables assigns to the various components and features - * that it adds to the HTML table. This allows classes to be configured - * during initialisation in addition to through the static - * {@link DataTable.ext.oStdClasses} object). - * @namespace - * @name DataTable.defaults.classes - */ - "oClasses": {}, - - - /** - * All strings that DataTables uses in the user interface that it creates - * are defined in this object, allowing you to modified them individually or - * completely replace them all as required. - * @namespace - * @name DataTable.defaults.language - */ - "oLanguage": { - /** - * Strings that are used for WAI-ARIA labels and controls only (these are not - * actually visible on the page, but will be read by screenreaders, and thus - * must be internationalised as well). - * @namespace - * @name DataTable.defaults.language.aria - */ - "oAria": { - /** - * ARIA label that is added to the table headers when the column may be - * sorted ascending by activing the column (click or return when focused). - * Note that the column header is prefixed to this string. - * @type string - * @default : activate to sort column ascending - * - * @dtopt Language - * @name DataTable.defaults.language.aria.sortAscending - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "aria": { - * "sortAscending": " - click/return to sort ascending" - * } - * } - * } ); - * } ); - */ - "sSortAscending": ": activate to sort column ascending", - - /** - * ARIA label that is added to the table headers when the column may be - * sorted descending by activing the column (click or return when focused). - * Note that the column header is prefixed to this string. - * @type string - * @default : activate to sort column ascending - * - * @dtopt Language - * @name DataTable.defaults.language.aria.sortDescending - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "aria": { - * "sortDescending": " - click/return to sort descending" - * } - * } - * } ); - * } ); - */ - "sSortDescending": ": activate to sort column descending" - }, - - /** - * Pagination string used by DataTables for the built-in pagination - * control types. - * @namespace - * @name DataTable.defaults.language.paginate - */ - "oPaginate": { - /** - * Text to use when using the 'full_numbers' type of pagination for the - * button to take the user to the first page. - * @type string - * @default First - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.first - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "first": "First page" - * } - * } - * } ); - * } ); - */ - "sFirst": "First", - - - /** - * Text to use when using the 'full_numbers' type of pagination for the - * button to take the user to the last page. - * @type string - * @default Last - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.last - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "last": "Last page" - * } - * } - * } ); - * } ); - */ - "sLast": "Last", - - - /** - * Text to use for the 'next' pagination button (to take the user to the - * next page). - * @type string - * @default Next - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.next - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "next": "Next page" - * } - * } - * } ); - * } ); - */ - "sNext": "Next", - - - /** - * Text to use for the 'previous' pagination button (to take the user to - * the previous page). - * @type string - * @default Previous - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.previous - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "previous": "Previous page" - * } - * } - * } ); - * } ); - */ - "sPrevious": "Previous" - }, - - /** - * This string is shown in preference to `zeroRecords` when the table is - * empty of data (regardless of filtering). Note that this is an optional - * parameter - if it is not given, the value of `zeroRecords` will be used - * instead (either the default or given value). - * @type string - * @default No data available in table - * - * @dtopt Language - * @name DataTable.defaults.language.emptyTable - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "emptyTable": "No data available in table" - * } - * } ); - * } ); - */ - "sEmptyTable": "No data available in table", - - - /** - * This string gives information to the end user about the information - * that is current on display on the page. The following tokens can be - * used in the string and will be dynamically replaced as the table - * display updates. This tokens can be placed anywhere in the string, or - * removed as needed by the language requires: - * - * * `\_START\_` - Display index of the first record on the current page - * * `\_END\_` - Display index of the last record on the current page - * * `\_TOTAL\_` - Number of records in the table after filtering - * * `\_MAX\_` - Number of records in the table without filtering - * * `\_PAGE\_` - Current page number - * * `\_PAGES\_` - Total number of pages of data in the table - * - * @type string - * @default Showing _START_ to _END_ of _TOTAL_ entries - * - * @dtopt Language - * @name DataTable.defaults.language.info - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "info": "Showing page _PAGE_ of _PAGES_" - * } - * } ); - * } ); - */ - "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries", - - - /** - * Display information string for when the table is empty. Typically the - * format of this string should match `info`. - * @type string - * @default Showing 0 to 0 of 0 entries - * - * @dtopt Language - * @name DataTable.defaults.language.infoEmpty - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "infoEmpty": "No entries to show" - * } - * } ); - * } ); - */ - "sInfoEmpty": "Showing 0 to 0 of 0 entries", - - - /** - * When a user filters the information in a table, this string is appended - * to the information (`info`) to give an idea of how strong the filtering - * is. The variable _MAX_ is dynamically updated. - * @type string - * @default (filtered from _MAX_ total entries) - * - * @dtopt Language - * @name DataTable.defaults.language.infoFiltered - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "infoFiltered": " - filtering from _MAX_ records" - * } - * } ); - * } ); - */ - "sInfoFiltered": "(filtered from _MAX_ total entries)", - - - /** - * If can be useful to append extra information to the info string at times, - * and this variable does exactly that. This information will be appended to - * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are - * being used) at all times. - * @type string - * @default Empty string - * - * @dtopt Language - * @name DataTable.defaults.language.infoPostFix - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "infoPostFix": "All records shown are derived from real information." - * } - * } ); - * } ); - */ - "sInfoPostFix": "", - - - /** - * This decimal place operator is a little different from the other - * language options since DataTables doesn't output floating point - * numbers, so it won't ever use this for display of a number. Rather, - * what this parameter does is modify the sort methods of the table so - * that numbers which are in a format which has a character other than - * a period (`.`) as a decimal place will be sorted numerically. - * - * Note that numbers with different decimal places cannot be shown in - * the same table and still be sortable, the table must be consistent. - * However, multiple different tables on the page can use different - * decimal place characters. - * @type string - * @default - * - * @dtopt Language - * @name DataTable.defaults.language.decimal - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "decimal": "," - * "thousands": "." - * } - * } ); - * } ); - */ - "sDecimal": "", - - - /** - * DataTables has a build in number formatter (`formatNumber`) which is - * used to format large numbers that are used in the table information. - * By default a comma is used, but this can be trivially changed to any - * character you wish with this parameter. - * @type string - * @default , - * - * @dtopt Language - * @name DataTable.defaults.language.thousands - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "thousands": "'" - * } - * } ); - * } ); - */ - "sThousands": ",", - - - /** - * Detail the action that will be taken when the drop down menu for the - * pagination length option is changed. The '_MENU_' variable is replaced - * with a default select list of 10, 25, 50 and 100, and can be replaced - * with a custom select box if required. - * @type string - * @default Show _MENU_ entries - * - * @dtopt Language - * @name DataTable.defaults.language.lengthMenu - * - * @example - * // Language change only - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "lengthMenu": "Display _MENU_ records" - * } - * } ); - * } ); - * - * @example - * // Language and options change - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "lengthMenu": 'Display records' - * } - * } ); - * } ); - */ - "sLengthMenu": "Show _MENU_ entries", - - - /** - * When using Ajax sourced data and during the first draw when DataTables is - * gathering the data, this message is shown in an empty row in the table to - * indicate to the end user the the data is being loaded. Note that this - * parameter is not used when loading data by server-side processing, just - * Ajax sourced data with client-side processing. - * @type string - * @default Loading... - * - * @dtopt Language - * @name DataTable.defaults.language.loadingRecords - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "loadingRecords": "Please wait - loading..." - * } - * } ); - * } ); - */ - "sLoadingRecords": "Loading...", - - - /** - * Text which is displayed when the table is processing a user action - * (usually a sort command or similar). - * @type string - * @default Processing... - * - * @dtopt Language - * @name DataTable.defaults.language.processing - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "processing": "DataTables is currently busy" - * } - * } ); - * } ); - */ - "sProcessing": "Processing...", - - - /** - * Details the actions that will be taken when the user types into the - * filtering input text box. The variable "_INPUT_", if used in the string, - * is replaced with the HTML text box for the filtering input allowing - * control over where it appears in the string. If "_INPUT_" is not given - * then the input box is appended to the string automatically. - * @type string - * @default Search: - * - * @dtopt Language - * @name DataTable.defaults.language.search - * - * @example - * // Input text box will be appended at the end automatically - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "search": "Filter records:" - * } - * } ); - * } ); - * - * @example - * // Specify where the filter should appear - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "search": "Apply filter _INPUT_ to table" - * } - * } ); - * } ); - */ - "sSearch": "Search:", - - - /** - * Assign a `placeholder` attribute to the search `input` element - * @type string - * @default - * - * @dtopt Language - * @name DataTable.defaults.language.searchPlaceholder - */ - "sSearchPlaceholder": "", - - - /** - * All of the language information can be stored in a file on the - * server-side, which DataTables will look up if this parameter is passed. - * It must store the URL of the language file, which is in a JSON format, - * and the object has the same properties as the oLanguage object in the - * initialiser object (i.e. the above parameters). Please refer to one of - * the example language files to see how this works in action. - * @type string - * @default Empty string - i.e. disabled - * - * @dtopt Language - * @name DataTable.defaults.language.url - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "url": "http://www.sprymedia.co.uk/dataTables/lang.txt" - * } - * } ); - * } ); - */ - "sUrl": "", - - - /** - * Text shown inside the table records when the is no information to be - * displayed after filtering. `emptyTable` is shown when there is simply no - * information in the table at all (regardless of filtering). - * @type string - * @default No matching records found - * - * @dtopt Language - * @name DataTable.defaults.language.zeroRecords - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "zeroRecords": "No records to display" - * } - * } ); - * } ); - */ - "sZeroRecords": "No matching records found" - }, - - - /** - * This parameter allows you to have define the global filtering state at - * initialisation time. As an object the `search` parameter must be - * defined, but all other parameters are optional. When `regex` is true, - * the search string will be treated as a regular expression, when false - * (default) it will be treated as a straight string. When `smart` - * DataTables will use it's smart filtering methods (to word match at - * any point in the data), when false this will not be done. - * @namespace - * @extends DataTable.models.oSearch - * - * @dtopt Options - * @name DataTable.defaults.search - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "search": {"search": "Initial search"} - * } ); - * } ) - */ - "oSearch": $.extend( {}, DataTable.models.oSearch ), - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * By default DataTables will look for the property `data` (or `aaData` for - * compatibility with DataTables 1.9-) when obtaining data from an Ajax - * source or for server-side processing - this parameter allows that - * property to be changed. You can use Javascript dotted object notation to - * get a data source for multiple levels of nesting. - * @type string - * @default data - * - * @dtopt Options - * @dtopt Server-side - * @name DataTable.defaults.ajaxDataProp - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "sAjaxDataProp": "data", - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * You can instruct DataTables to load data from an external - * source using this parameter (use aData if you want to pass data in you - * already have). Simply provide a url a JSON object can be obtained from. - * @type string - * @default null - * - * @dtopt Options - * @dtopt Server-side - * @name DataTable.defaults.ajaxSource - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "sAjaxSource": null, - - - /** - * This initialisation variable allows you to specify exactly where in the - * DOM you want DataTables to inject the various controls it adds to the page - * (for example you might want the pagination controls at the top of the - * table). DIV elements (with or without a custom class) can also be added to - * aid styling. The follow syntax is used: - *
      - *
    • The following options are allowed: - *
        - *
      • 'l' - Length changing
      • - *
      • 'f' - Filtering input
      • - *
      • 't' - The table!
      • - *
      • 'i' - Information
      • - *
      • 'p' - Pagination
      • - *
      • 'r' - pRocessing
      • - *
      - *
    • - *
    • The following constants are allowed: - *
        - *
      • 'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')
      • - *
      • 'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')
      • - *
      - *
    • - *
    • The following syntax is expected: - *
        - *
      • '<' and '>' - div elements
      • - *
      • '<"class" and '>' - div with a class
      • - *
      • '<"#id" and '>' - div with an ID
      • - *
      - *
    • - *
    • Examples: - *
        - *
      • '<"wrapper"flipt>'
      • - *
      • '<lf<t>ip>'
      • - *
      - *
    • - *
    - * @type string - * @default lfrtip (when `jQueryUI` is false) or - * <"H"lfr>t<"F"ip> (when `jQueryUI` is true) - * - * @dtopt Options - * @name DataTable.defaults.dom - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "dom": '<"top"i>rt<"bottom"flp><"clear">' - * } ); - * } ); - */ - "sDom": "lfrtip", - - - /** - * Search delay option. This will throttle full table searches that use the - * DataTables provided search input element (it does not effect calls to - * `dt-api search()`, providing a delay before the search is made. - * @type integer - * @default 0 - * - * @dtopt Options - * @name DataTable.defaults.searchDelay - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "searchDelay": 200 - * } ); - * } ) - */ - "searchDelay": null, - - - /** - * DataTables features four different built-in options for the buttons to - * display for pagination control: - * - * * `simple` - 'Previous' and 'Next' buttons only - * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers - * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons - * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus - * page numbers - * - * Further methods can be added using {@link DataTable.ext.oPagination}. - * @type string - * @default simple_numbers - * - * @dtopt Options - * @name DataTable.defaults.pagingType - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "pagingType": "full_numbers" - * } ); - * } ) - */ - "sPaginationType": "simple_numbers", - - - /** - * Enable horizontal scrolling. When a table is too wide to fit into a - * certain layout, or you have a large number of columns in the table, you - * can enable x-scrolling to show the table in a viewport, which can be - * scrolled. This property can be `true` which will allow the table to - * scroll horizontally when needed, or any CSS unit, or a number (in which - * case it will be treated as a pixel measurement). Setting as simply `true` - * is recommended. - * @type boolean|string - * @default blank string - i.e. disabled - * - * @dtopt Features - * @name DataTable.defaults.scrollX - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollX": true, - * "scrollCollapse": true - * } ); - * } ); - */ - "sScrollX": "", - - - /** - * This property can be used to force a DataTable to use more width than it - * might otherwise do when x-scrolling is enabled. For example if you have a - * table which requires to be well spaced, this parameter is useful for - * "over-sizing" the table, and thus forcing scrolling. This property can by - * any CSS unit, or a number (in which case it will be treated as a pixel - * measurement). - * @type string - * @default blank string - i.e. disabled - * - * @dtopt Options - * @name DataTable.defaults.scrollXInner - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollX": "100%", - * "scrollXInner": "110%" - * } ); - * } ); - */ - "sScrollXInner": "", - - - /** - * Enable vertical scrolling. Vertical scrolling will constrain the DataTable - * to the given height, and enable scrolling for any data which overflows the - * current viewport. This can be used as an alternative to paging to display - * a lot of data in a small area (although paging and scrolling can both be - * enabled at the same time). This property can be any CSS unit, or a number - * (in which case it will be treated as a pixel measurement). - * @type string - * @default blank string - i.e. disabled - * - * @dtopt Features - * @name DataTable.defaults.scrollY - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollY": "200px", - * "paginate": false - * } ); - * } ); - */ - "sScrollY": "", - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * Set the HTTP method that is used to make the Ajax call for server-side - * processing or Ajax sourced data. - * @type string - * @default GET - * - * @dtopt Options - * @dtopt Server-side - * @name DataTable.defaults.serverMethod - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "sServerMethod": "GET", - - - /** - * DataTables makes use of renderers when displaying HTML elements for - * a table. These renderers can be added or modified by plug-ins to - * generate suitable mark-up for a site. For example the Bootstrap - * integration plug-in for DataTables uses a paging button renderer to - * display pagination buttons in the mark-up required by Bootstrap. - * - * For further information about the renderers available see - * DataTable.ext.renderer - * @type string|object - * @default null - * - * @name DataTable.defaults.renderer - * - */ - "renderer": null, - - - /** - * Set the data property name that DataTables should use to get a row's id - * to set as the `id` property in the node. - * @type string - * @default DT_RowId - * - * @name DataTable.defaults.rowId - */ - "rowId": "DT_RowId" - }; - - _fnHungarianMap( DataTable.defaults ); - - - - /* - * Developer note - See note in model.defaults.js about the use of Hungarian - * notation and camel case. - */ - - /** - * Column options that can be given to DataTables at initialisation time. - * @namespace - */ - DataTable.defaults.column = { - /** - * Define which column(s) an order will occur on for this column. This - * allows a column's ordering to take multiple columns into account when - * doing a sort or use the data from a different column. For example first - * name / last name columns make sense to do a multi-column sort over the - * two columns. - * @type array|int - * @default null Takes the value of the column index automatically - * - * @name DataTable.defaults.column.orderData - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderData": [ 0, 1 ], "targets": [ 0 ] }, - * { "orderData": [ 1, 0 ], "targets": [ 1 ] }, - * { "orderData": 2, "targets": [ 2 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "orderData": [ 0, 1 ] }, - * { "orderData": [ 1, 0 ] }, - * { "orderData": 2 }, - * null, - * null - * ] - * } ); - * } ); - */ - "aDataSort": null, - "iDataSort": -1, - - - /** - * You can control the default ordering direction, and even alter the - * behaviour of the sort handler (i.e. only allow ascending ordering etc) - * using this parameter. - * @type array - * @default [ 'asc', 'desc' ] - * - * @name DataTable.defaults.column.orderSequence - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderSequence": [ "asc" ], "targets": [ 1 ] }, - * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] }, - * { "orderSequence": [ "desc" ], "targets": [ 3 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * { "orderSequence": [ "asc" ] }, - * { "orderSequence": [ "desc", "asc", "asc" ] }, - * { "orderSequence": [ "desc" ] }, - * null - * ] - * } ); - * } ); - */ - "asSorting": [ 'asc', 'desc' ], - - - /** - * Enable or disable filtering on the data in this column. - * @type boolean - * @default true - * - * @name DataTable.defaults.column.searchable - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "searchable": false, "targets": [ 0 ] } - * ] } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "searchable": false }, - * null, - * null, - * null, - * null - * ] } ); - * } ); - */ - "bSearchable": true, - - - /** - * Enable or disable ordering on this column. - * @type boolean - * @default true - * - * @name DataTable.defaults.column.orderable - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderable": false, "targets": [ 0 ] } - * ] } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "orderable": false }, - * null, - * null, - * null, - * null - * ] } ); - * } ); - */ - "bSortable": true, - - - /** - * Enable or disable the display of this column. - * @type boolean - * @default true - * - * @name DataTable.defaults.column.visible - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "visible": false, "targets": [ 0 ] } - * ] } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "visible": false }, - * null, - * null, - * null, - * null - * ] } ); - * } ); - */ - "bVisible": true, - - - /** - * Developer definable function that is called whenever a cell is created (Ajax source, - * etc) or processed for input (DOM source). This can be used as a compliment to mRender - * allowing you to modify the DOM element (add background colour for example) when the - * element is available. - * @type function - * @param {element} td The TD node that has been created - * @param {*} cellData The Data for the cell - * @param {array|object} rowData The data for the whole row - * @param {int} row The row index for the aoData data store - * @param {int} col The column index for aoColumns - * - * @name DataTable.defaults.column.createdCell - * @dtopt Columns - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [3], - * "createdCell": function (td, cellData, rowData, row, col) { - * if ( cellData == "1.7" ) { - * $(td).css('color', 'blue') - * } - * } - * } ] - * }); - * } ); - */ - "fnCreatedCell": null, - - - /** - * This parameter has been replaced by `data` in DataTables to ensure naming - * consistency. `dataProp` can still be used, as there is backwards - * compatibility in DataTables for this option, but it is strongly - * recommended that you use `data` in preference to `dataProp`. - * @name DataTable.defaults.column.dataProp - */ - - - /** - * This property can be used to read data from any data source property, - * including deeply nested objects / properties. `data` can be given in a - * number of different ways which effect its behaviour: - * - * * `integer` - treated as an array index for the data source. This is the - * default that DataTables uses (incrementally increased for each column). - * * `string` - read an object property from the data source. There are - * three 'special' options that can be used in the string to alter how - * DataTables reads the data from the source object: - * * `.` - Dotted Javascript notation. Just as you use a `.` in - * Javascript to read from nested objects, so to can the options - * specified in `data`. For example: `browser.version` or - * `browser.name`. If your object parameter name contains a period, use - * `\\` to escape it - i.e. `first\\.name`. - * * `[]` - Array notation. DataTables can automatically combine data - * from and array source, joining the data with the characters provided - * between the two brackets. For example: `name[, ]` would provide a - * comma-space separated list from the source array. If no characters - * are provided between the brackets, the original array source is - * returned. - * * `()` - Function notation. Adding `()` to the end of a parameter will - * execute a function of the name given. For example: `browser()` for a - * simple function on the data source, `browser.version()` for a - * function in a nested property or even `browser().version` to get an - * object property if the function called returns an object. Note that - * function notation is recommended for use in `render` rather than - * `data` as it is much simpler to use as a renderer. - * * `null` - use the original data source for the row rather than plucking - * data directly from it. This action has effects on two other - * initialisation options: - * * `defaultContent` - When null is given as the `data` option and - * `defaultContent` is specified for the column, the value defined by - * `defaultContent` will be used for the cell. - * * `render` - When null is used for the `data` option and the `render` - * option is specified for the column, the whole data source for the - * row is used for the renderer. - * * `function` - the function given will be executed whenever DataTables - * needs to set or get the data for a cell in the column. The function - * takes three parameters: - * * Parameters: - * * `{array|object}` The data source for the row - * * `{string}` The type call data requested - this will be 'set' when - * setting data or 'filter', 'display', 'type', 'sort' or undefined - * when gathering data. Note that when `undefined` is given for the - * type DataTables expects to get the raw data for the object back< - * * `{*}` Data to set when the second parameter is 'set'. - * * Return: - * * The return value from the function is not required when 'set' is - * the type of call, but otherwise the return is what will be used - * for the data requested. - * - * Note that `data` is a getter and setter option. If you just require - * formatting of data for output, you will likely want to use `render` which - * is simply a getter and thus simpler to use. - * - * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The - * name change reflects the flexibility of this property and is consistent - * with the naming of mRender. If 'mDataProp' is given, then it will still - * be used by DataTables, as it automatically maps the old name to the new - * if required. - * - * @type string|int|function|null - * @default null Use automatically calculated column index - * - * @name DataTable.defaults.column.data - * @dtopt Columns - * - * @example - * // Read table data from objects - * // JSON structure for each row: - * // { - * // "engine": {value}, - * // "browser": {value}, - * // "platform": {value}, - * // "version": {value}, - * // "grade": {value} - * // } - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajaxSource": "sources/objects.txt", - * "columns": [ - * { "data": "engine" }, - * { "data": "browser" }, - * { "data": "platform" }, - * { "data": "version" }, - * { "data": "grade" } - * ] - * } ); - * } ); - * - * @example - * // Read information from deeply nested objects - * // JSON structure for each row: - * // { - * // "engine": {value}, - * // "browser": {value}, - * // "platform": { - * // "inner": {value} - * // }, - * // "details": [ - * // {value}, {value} - * // ] - * // } - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajaxSource": "sources/deep.txt", - * "columns": [ - * { "data": "engine" }, - * { "data": "browser" }, - * { "data": "platform.inner" }, - * { "data": "platform.details.0" }, - * { "data": "platform.details.1" } - * ] - * } ); - * } ); - * - * @example - * // Using `data` as a function to provide different information for - * // sorting, filtering and display. In this case, currency (price) - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": function ( source, type, val ) { - * if (type === 'set') { - * source.price = val; - * // Store the computed dislay and filter values for efficiency - * source.price_display = val=="" ? "" : "$"+numberFormat(val); - * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val; - * return; - * } - * else if (type === 'display') { - * return source.price_display; - * } - * else if (type === 'filter') { - * return source.price_filter; - * } - * // 'sort', 'type' and undefined all just use the integer - * return source.price; - * } - * } ] - * } ); - * } ); - * - * @example - * // Using default content - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": null, - * "defaultContent": "Click to edit" - * } ] - * } ); - * } ); - * - * @example - * // Using array notation - outputting a list from an array - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": "name[, ]" - * } ] - * } ); - * } ); - * - */ - "mData": null, - - - /** - * This property is the rendering partner to `data` and it is suggested that - * when you want to manipulate data for display (including filtering, - * sorting etc) without altering the underlying data for the table, use this - * property. `render` can be considered to be the the read only companion to - * `data` which is read / write (then as such more complex). Like `data` - * this option can be given in a number of different ways to effect its - * behaviour: - * - * * `integer` - treated as an array index for the data source. This is the - * default that DataTables uses (incrementally increased for each column). - * * `string` - read an object property from the data source. There are - * three 'special' options that can be used in the string to alter how - * DataTables reads the data from the source object: - * * `.` - Dotted Javascript notation. Just as you use a `.` in - * Javascript to read from nested objects, so to can the options - * specified in `data`. For example: `browser.version` or - * `browser.name`. If your object parameter name contains a period, use - * `\\` to escape it - i.e. `first\\.name`. - * * `[]` - Array notation. DataTables can automatically combine data - * from and array source, joining the data with the characters provided - * between the two brackets. For example: `name[, ]` would provide a - * comma-space separated list from the source array. If no characters - * are provided between the brackets, the original array source is - * returned. - * * `()` - Function notation. Adding `()` to the end of a parameter will - * execute a function of the name given. For example: `browser()` for a - * simple function on the data source, `browser.version()` for a - * function in a nested property or even `browser().version` to get an - * object property if the function called returns an object. - * * `object` - use different data for the different data types requested by - * DataTables ('filter', 'display', 'type' or 'sort'). The property names - * of the object is the data type the property refers to and the value can - * defined using an integer, string or function using the same rules as - * `render` normally does. Note that an `_` option _must_ be specified. - * This is the default value to use if you haven't specified a value for - * the data type requested by DataTables. - * * `function` - the function given will be executed whenever DataTables - * needs to set or get the data for a cell in the column. The function - * takes three parameters: - * * Parameters: - * * {array|object} The data source for the row (based on `data`) - * * {string} The type call data requested - this will be 'filter', - * 'display', 'type' or 'sort'. - * * {array|object} The full data source for the row (not based on - * `data`) - * * Return: - * * The return value from the function is what will be used for the - * data requested. - * - * @type string|int|function|object|null - * @default null Use the data source value. - * - * @name DataTable.defaults.column.render - * @dtopt Columns - * - * @example - * // Create a comma separated list from an array of objects - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajaxSource": "sources/deep.txt", - * "columns": [ - * { "data": "engine" }, - * { "data": "browser" }, - * { - * "data": "platform", - * "render": "[, ].name" - * } - * ] - * } ); - * } ); - * - * @example - * // Execute a function to obtain data - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": null, // Use the full data source object for the renderer's source - * "render": "browserName()" - * } ] - * } ); - * } ); - * - * @example - * // As an object, extracting different data for the different types - * // This would be used with a data source such as: - * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" } - * // Here the `phone` integer is used for sorting and type detection, while `phone_filter` - * // (which has both forms) is used for filtering for if a user inputs either format, while - * // the formatted phone number is the one that is shown in the table. - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": null, // Use the full data source object for the renderer's source - * "render": { - * "_": "phone", - * "filter": "phone_filter", - * "display": "phone_display" - * } - * } ] - * } ); - * } ); - * - * @example - * // Use as a function to create a link from the data source - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": "download_link", - * "render": function ( data, type, full ) { - * return 'Download'; - * } - * } ] - * } ); - * } ); - */ - "mRender": null, - - - /** - * Change the cell type created for the column - either TD cells or TH cells. This - * can be useful as TH cells have semantic meaning in the table body, allowing them - * to act as a header for a row (you may wish to add scope='row' to the TH elements). - * @type string - * @default td - * - * @name DataTable.defaults.column.cellType - * @dtopt Columns - * - * @example - * // Make the first column use TH cells - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "cellType": "th" - * } ] - * } ); - * } ); - */ - "sCellType": "td", - - - /** - * Class to give to each cell in this column. - * @type string - * @default Empty string - * - * @name DataTable.defaults.column.class - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "class": "my_class", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "class": "my_class" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sClass": "", - - /** - * When DataTables calculates the column widths to assign to each column, - * it finds the longest string in each column and then constructs a - * temporary table and reads the widths from that. The problem with this - * is that "mmm" is much wider then "iiii", but the latter is a longer - * string - thus the calculation can go wrong (doing it properly and putting - * it into an DOM object and measuring that is horribly(!) slow). Thus as - * a "work around" we provide this option. It will append its value to the - * text that is found to be the longest string for the column - i.e. padding. - * Generally you shouldn't need this! - * @type string - * @default Empty string - * - * @name DataTable.defaults.column.contentPadding - * @dtopt Columns - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * null, - * null, - * { - * "contentPadding": "mmm" - * } - * ] - * } ); - * } ); - */ - "sContentPadding": "", - - - /** - * Allows a default value to be given for a column's data, and will be used - * whenever a null data source is encountered (this can be because `data` - * is set to null, or because the data source itself is null). - * @type string - * @default null - * - * @name DataTable.defaults.column.defaultContent - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { - * "data": null, - * "defaultContent": "Edit", - * "targets": [ -1 ] - * } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * null, - * null, - * { - * "data": null, - * "defaultContent": "Edit" - * } - * ] - * } ); - * } ); - */ - "sDefaultContent": null, - - - /** - * This parameter is only used in DataTables' server-side processing. It can - * be exceptionally useful to know what columns are being displayed on the - * client side, and to map these to database fields. When defined, the names - * also allow DataTables to reorder information from the server if it comes - * back in an unexpected order (i.e. if you switch your columns around on the - * client-side, your server-side code does not also need updating). - * @type string - * @default Empty string - * - * @name DataTable.defaults.column.name - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "name": "engine", "targets": [ 0 ] }, - * { "name": "browser", "targets": [ 1 ] }, - * { "name": "platform", "targets": [ 2 ] }, - * { "name": "version", "targets": [ 3 ] }, - * { "name": "grade", "targets": [ 4 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "name": "engine" }, - * { "name": "browser" }, - * { "name": "platform" }, - * { "name": "version" }, - * { "name": "grade" } - * ] - * } ); - * } ); - */ - "sName": "", - - - /** - * Defines a data source type for the ordering which can be used to read - * real-time information from the table (updating the internally cached - * version) prior to ordering. This allows ordering to occur on user - * editable elements such as form inputs. - * @type string - * @default std - * - * @name DataTable.defaults.column.orderDataType - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderDataType": "dom-text", "targets": [ 2, 3 ] }, - * { "type": "numeric", "targets": [ 3 ] }, - * { "orderDataType": "dom-select", "targets": [ 4 ] }, - * { "orderDataType": "dom-checkbox", "targets": [ 5 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * null, - * { "orderDataType": "dom-text" }, - * { "orderDataType": "dom-text", "type": "numeric" }, - * { "orderDataType": "dom-select" }, - * { "orderDataType": "dom-checkbox" } - * ] - * } ); - * } ); - */ - "sSortDataType": "std", - - - /** - * The title of this column. - * @type string - * @default null Derived from the 'TH' value for this column in the - * original HTML table. - * - * @name DataTable.defaults.column.title - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "title": "My column title", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "title": "My column title" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sTitle": null, - - - /** - * The type allows you to specify how the data for this column will be - * ordered. Four types (string, numeric, date and html (which will strip - * HTML tags before ordering)) are currently available. Note that only date - * formats understood by Javascript's Date() object will be accepted as type - * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', - * 'numeric', 'date' or 'html' (by default). Further types can be adding - * through plug-ins. - * @type string - * @default null Auto-detected from raw data - * - * @name DataTable.defaults.column.type - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "type": "html", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "type": "html" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sType": null, - - - /** - * Defining the width of the column, this parameter may take any CSS value - * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not - * been given a specific width through this interface ensuring that the table - * remains readable. - * @type string - * @default null Automatic - * - * @name DataTable.defaults.column.width - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "width": "20%", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "width": "20%" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sWidth": null - }; - - _fnHungarianMap( DataTable.defaults.column ); - - - - /** - * DataTables settings object - this holds all the information needed for a - * given table, including configuration, data and current application of the - * table options. DataTables does not have a single instance for each DataTable - * with the settings attached to that instance, but rather instances of the - * DataTable "class" are created on-the-fly as needed (typically by a - * $().dataTable() call) and the settings object is then applied to that - * instance. - * - * Note that this object is related to {@link DataTable.defaults} but this - * one is the internal data store for DataTables's cache of columns. It should - * NOT be manipulated outside of DataTables. Any configuration should be done - * through the initialisation options. - * @namespace - * @todo Really should attach the settings object to individual instances so we - * don't need to create new instances on each $().dataTable() call (if the - * table already exists). It would also save passing oSettings around and - * into every single function. However, this is a very significant - * architecture change for DataTables and will almost certainly break - * backwards compatibility with older installations. This is something that - * will be done in 2.0. - */ - DataTable.models.oSettings = { - /** - * Primary features of DataTables and their enablement state. - * @namespace - */ - "oFeatures": { - - /** - * Flag to say if DataTables should automatically try to calculate the - * optimum table and columns widths (true) or not (false). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bAutoWidth": null, - - /** - * Delay the creation of TR and TD elements until they are actually - * needed by a driven page draw. This can give a significant speed - * increase for Ajax source and Javascript source data, but makes no - * difference at all fro DOM and server-side processing tables. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bDeferRender": null, - - /** - * Enable filtering on the table or not. Note that if this is disabled - * then there is no filtering at all on the table, including fnFilter. - * To just remove the filtering input use sDom and remove the 'f' option. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bFilter": null, - - /** - * Table information element (the 'Showing x of y records' div) enable - * flag. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bInfo": null, - - /** - * Present a user control allowing the end user to change the page size - * when pagination is enabled. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bLengthChange": null, - - /** - * Pagination enabled or not. Note that if this is disabled then length - * changing must also be disabled. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bPaginate": null, - - /** - * Processing indicator enable flag whenever DataTables is enacting a - * user request - typically an Ajax request for server-side processing. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bProcessing": null, - - /** - * Server-side processing enabled flag - when enabled DataTables will - * get all data from the server for every draw - there is no filtering, - * sorting or paging done on the client-side. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bServerSide": null, - - /** - * Sorting enablement flag. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSort": null, - - /** - * Multi-column sorting - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSortMulti": null, - - /** - * Apply a class to the columns which are being sorted to provide a - * visual highlight or not. This can slow things down when enabled since - * there is a lot of DOM interaction. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSortClasses": null, - - /** - * State saving enablement flag. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bStateSave": null - }, - - - /** - * Scrolling settings for a table. - * @namespace - */ - "oScroll": { - /** - * When the table is shorter in height than sScrollY, collapse the - * table container down to the height of the table (when true). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bCollapse": null, - - /** - * Width of the scrollbar for the web-browser's platform. Calculated - * during table initialisation. - * @type int - * @default 0 - */ - "iBarWidth": 0, - - /** - * Viewport width for horizontal scrolling. Horizontal scrolling is - * disabled if an empty string. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - */ - "sX": null, - - /** - * Width to expand the table to when using x-scrolling. Typically you - * should not need to use this. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - * @deprecated - */ - "sXInner": null, - - /** - * Viewport height for vertical scrolling. Vertical scrolling is disabled - * if an empty string. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - */ - "sY": null - }, - - /** - * Language information for the table. - * @namespace - * @extends DataTable.defaults.oLanguage - */ - "oLanguage": { - /** - * Information callback function. See - * {@link DataTable.defaults.fnInfoCallback} - * @type function - * @default null - */ - "fnInfoCallback": null - }, - - /** - * Browser support parameters - * @namespace - */ - "oBrowser": { - /** - * Indicate if the browser incorrectly calculates width:100% inside a - * scrolling element (IE6/7) - * @type boolean - * @default false - */ - "bScrollOversize": false, - - /** - * Determine if the vertical scrollbar is on the right or left of the - * scrolling container - needed for rtl language layout, although not - * all browsers move the scrollbar (Safari). - * @type boolean - * @default false - */ - "bScrollbarLeft": false, - - /** - * Flag for if `getBoundingClientRect` is fully supported or not - * @type boolean - * @default false - */ - "bBounding": false, - - /** - * Browser scrollbar width - * @type integer - * @default 0 - */ - "barWidth": 0 - }, - - - "ajax": null, - - - /** - * Array referencing the nodes which are used for the features. The - * parameters of this object match what is allowed by sDom - i.e. - *
      - *
    • 'l' - Length changing
    • - *
    • 'f' - Filtering input
    • - *
    • 't' - The table!
    • - *
    • 'i' - Information
    • - *
    • 'p' - Pagination
    • - *
    • 'r' - pRocessing
    • - *
    - * @type array - * @default [] - */ - "aanFeatures": [], - - /** - * Store data information - see {@link DataTable.models.oRow} for detailed - * information. - * @type array - * @default [] - */ - "aoData": [], - - /** - * Array of indexes which are in the current display (after filtering etc) - * @type array - * @default [] - */ - "aiDisplay": [], - - /** - * Array of indexes for display - no filtering - * @type array - * @default [] - */ - "aiDisplayMaster": [], - - /** - * Map of row ids to data indexes - * @type object - * @default {} - */ - "aIds": {}, - - /** - * Store information about each column that is in use - * @type array - * @default [] - */ - "aoColumns": [], - - /** - * Store information about the table's header - * @type array - * @default [] - */ - "aoHeader": [], - - /** - * Store information about the table's footer - * @type array - * @default [] - */ - "aoFooter": [], - - /** - * Store the applied global search information in case we want to force a - * research or compare the old search to a new one. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @namespace - * @extends DataTable.models.oSearch - */ - "oPreviousSearch": {}, - - /** - * Store the applied search for each column - see - * {@link DataTable.models.oSearch} for the format that is used for the - * filtering information for each column. - * @type array - * @default [] - */ - "aoPreSearchCols": [], - - /** - * Sorting that is applied to the table. Note that the inner arrays are - * used in the following manner: - *
      - *
    • Index 0 - column number
    • - *
    • Index 1 - current sorting direction
    • - *
    - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @todo These inner arrays should really be objects - */ - "aaSorting": null, - - /** - * Sorting that is always applied to the table (i.e. prefixed in front of - * aaSorting). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @default [] - */ - "aaSortingFixed": [], - - /** - * Classes to use for the striping of a table. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @default [] - */ - "asStripeClasses": null, - - /** - * If restoring a table - we should restore its striping classes as well - * @type array - * @default [] - */ - "asDestroyStripes": [], - - /** - * If restoring a table - we should restore its width - * @type int - * @default 0 - */ - "sDestroyWidth": 0, - - /** - * Callback functions array for every time a row is inserted (i.e. on a draw). - * @type array - * @default [] - */ - "aoRowCallback": [], - - /** - * Callback functions for the header on each draw. - * @type array - * @default [] - */ - "aoHeaderCallback": [], - - /** - * Callback function for the footer on each draw. - * @type array - * @default [] - */ - "aoFooterCallback": [], - - /** - * Array of callback functions for draw callback functions - * @type array - * @default [] - */ - "aoDrawCallback": [], - - /** - * Array of callback functions for row created function - * @type array - * @default [] - */ - "aoRowCreatedCallback": [], - - /** - * Callback functions for just before the table is redrawn. A return of - * false will be used to cancel the draw. - * @type array - * @default [] - */ - "aoPreDrawCallback": [], - - /** - * Callback functions for when the table has been initialised. - * @type array - * @default [] - */ - "aoInitComplete": [], - - - /** - * Callbacks for modifying the settings to be stored for state saving, prior to - * saving state. - * @type array - * @default [] - */ - "aoStateSaveParams": [], - - /** - * Callbacks for modifying the settings that have been stored for state saving - * prior to using the stored values to restore the state. - * @type array - * @default [] - */ - "aoStateLoadParams": [], - - /** - * Callbacks for operating on the settings object once the saved state has been - * loaded - * @type array - * @default [] - */ - "aoStateLoaded": [], - - /** - * Cache the table ID for quick access - * @type string - * @default Empty string - */ - "sTableId": "", - - /** - * The TABLE node for the main table - * @type node - * @default null - */ - "nTable": null, - - /** - * Permanent ref to the thead element - * @type node - * @default null - */ - "nTHead": null, - - /** - * Permanent ref to the tfoot element - if it exists - * @type node - * @default null - */ - "nTFoot": null, - - /** - * Permanent ref to the tbody element - * @type node - * @default null - */ - "nTBody": null, - - /** - * Cache the wrapper node (contains all DataTables controlled elements) - * @type node - * @default null - */ - "nTableWrapper": null, - - /** - * Indicate if when using server-side processing the loading of data - * should be deferred until the second draw. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - * @default false - */ - "bDeferLoading": false, - - /** - * Indicate if all required information has been read in - * @type boolean - * @default false - */ - "bInitialised": false, - - /** - * Information about open rows. Each object in the array has the parameters - * 'nTr' and 'nParent' - * @type array - * @default [] - */ - "aoOpenRows": [], - - /** - * Dictate the positioning of DataTables' control elements - see - * {@link DataTable.model.oInit.sDom}. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - * @default null - */ - "sDom": null, - - /** - * Search delay (in mS) - * @type integer - * @default null - */ - "searchDelay": null, - - /** - * Which type of pagination should be used. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - * @default two_button - */ - "sPaginationType": "two_button", - - /** - * The state duration (for `stateSave`) in seconds. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type int - * @default 0 - */ - "iStateDuration": 0, - - /** - * Array of callback functions for state saving. Each array element is an - * object with the following parameters: - *
      - *
    • function:fn - function to call. Takes two parameters, oSettings - * and the JSON string to save that has been thus far created. Returns - * a JSON string to be inserted into a json object - * (i.e. '"param": [ 0, 1, 2]')
    • - *
    • string:sName - name of callback
    • - *
    - * @type array - * @default [] - */ - "aoStateSave": [], - - /** - * Array of callback functions for state loading. Each array element is an - * object with the following parameters: - *
      - *
    • function:fn - function to call. Takes two parameters, oSettings - * and the object stored. May return false to cancel state loading
    • - *
    • string:sName - name of callback
    • - *
    - * @type array - * @default [] - */ - "aoStateLoad": [], - - /** - * State that was saved. Useful for back reference - * @type object - * @default null - */ - "oSavedState": null, - - /** - * State that was loaded. Useful for back reference - * @type object - * @default null - */ - "oLoadedState": null, - - /** - * Source url for AJAX data for the table. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - * @default null - */ - "sAjaxSource": null, - - /** - * Property from a given object from which to read the table data from. This - * can be an empty string (when not server-side processing), in which case - * it is assumed an an array is given directly. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - */ - "sAjaxDataProp": null, - - /** - * Note if draw should be blocked while getting data - * @type boolean - * @default true - */ - "bAjaxDataGet": true, - - /** - * The last jQuery XHR object that was used for server-side data gathering. - * This can be used for working with the XHR information in one of the - * callbacks - * @type object - * @default null - */ - "jqXHR": null, - - /** - * JSON returned from the server in the last Ajax request - * @type object - * @default undefined - */ - "json": undefined, - - /** - * Data submitted as part of the last Ajax request - * @type object - * @default undefined - */ - "oAjaxData": undefined, - - /** - * Function to get the server-side data. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type function - */ - "fnServerData": null, - - /** - * Functions which are called prior to sending an Ajax request so extra - * parameters can easily be sent to the server - * @type array - * @default [] - */ - "aoServerParams": [], - - /** - * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if - * required). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - */ - "sServerMethod": null, - - /** - * Format numbers for display. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type function - */ - "fnFormatNumber": null, - - /** - * List of options that can be used for the user selectable length menu. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @default [] - */ - "aLengthMenu": null, - - /** - * Counter for the draws that the table does. Also used as a tracker for - * server-side processing - * @type int - * @default 0 - */ - "iDraw": 0, - - /** - * Indicate if a redraw is being done - useful for Ajax - * @type boolean - * @default false - */ - "bDrawing": false, - - /** - * Draw index (iDraw) of the last error when parsing the returned data - * @type int - * @default -1 - */ - "iDrawError": -1, - - /** - * Paging display length - * @type int - * @default 10 - */ - "_iDisplayLength": 10, - - /** - * Paging start point - aiDisplay index - * @type int - * @default 0 - */ - "_iDisplayStart": 0, - - /** - * Server-side processing - number of records in the result set - * (i.e. before filtering), Use fnRecordsTotal rather than - * this property to get the value of the number of records, regardless of - * the server-side processing setting. - * @type int - * @default 0 - * @private - */ - "_iRecordsTotal": 0, - - /** - * Server-side processing - number of records in the current display set - * (i.e. after filtering). Use fnRecordsDisplay rather than - * this property to get the value of the number of records, regardless of - * the server-side processing setting. - * @type boolean - * @default 0 - * @private - */ - "_iRecordsDisplay": 0, - - /** - * Flag to indicate if jQuery UI marking and classes should be used. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bJUI": null, - - /** - * The classes to use for the table - * @type object - * @default {} - */ - "oClasses": {}, - - /** - * Flag attached to the settings object so you can check in the draw - * callback if filtering has been done in the draw. Deprecated in favour of - * events. - * @type boolean - * @default false - * @deprecated - */ - "bFiltered": false, - - /** - * Flag attached to the settings object so you can check in the draw - * callback if sorting has been done in the draw. Deprecated in favour of - * events. - * @type boolean - * @default false - * @deprecated - */ - "bSorted": false, - - /** - * Indicate that if multiple rows are in the header and there is more than - * one unique cell per column, if the top one (true) or bottom one (false) - * should be used for sorting / title by DataTables. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSortCellsTop": null, - - /** - * Initialisation object that is used for the table - * @type object - * @default null - */ - "oInit": null, - - /** - * Destroy callback functions - for plug-ins to attach themselves to the - * destroy so they can clean up markup and events. - * @type array - * @default [] - */ - "aoDestroyCallback": [], - - - /** - * Get the number of records in the current record set, before filtering - * @type function - */ - "fnRecordsTotal": function () - { - return _fnDataSource( this ) == 'ssp' ? - this._iRecordsTotal * 1 : - this.aiDisplayMaster.length; - }, - - /** - * Get the number of records in the current record set, after filtering - * @type function - */ - "fnRecordsDisplay": function () - { - return _fnDataSource( this ) == 'ssp' ? - this._iRecordsDisplay * 1 : - this.aiDisplay.length; - }, - - /** - * Get the display end point - aiDisplay index - * @type function - */ - "fnDisplayEnd": function () - { - var - len = this._iDisplayLength, - start = this._iDisplayStart, - calc = start + len, - records = this.aiDisplay.length, - features = this.oFeatures, - paginate = features.bPaginate; - - if ( features.bServerSide ) { - return paginate === false || len === -1 ? - start + records : - Math.min( start+len, this._iRecordsDisplay ); - } - else { - return ! paginate || calc>records || len===-1 ? - records : - calc; - } - }, - - /** - * The DataTables object for this table - * @type object - * @default null - */ - "oInstance": null, - - /** - * Unique identifier for each instance of the DataTables object. If there - * is an ID on the table node, then it takes that value, otherwise an - * incrementing internal counter is used. - * @type string - * @default null - */ - "sInstance": null, - - /** - * tabindex attribute value that is added to DataTables control elements, allowing - * keyboard navigation of the table and its controls. - */ - "iTabIndex": 0, - - /** - * DIV container for the footer scrolling table if scrolling - */ - "nScrollHead": null, - - /** - * DIV container for the footer scrolling table if scrolling - */ - "nScrollFoot": null, - - /** - * Last applied sort - * @type array - * @default [] - */ - "aLastSort": [], - - /** - * Stored plug-in instances - * @type object - * @default {} - */ - "oPlugins": {}, - - /** - * Function used to get a row's id from the row's data - * @type function - * @default null - */ - "rowIdFn": null, - - /** - * Data location where to store a row's id - * @type string - * @default null - */ - "rowId": null - }; - - /** - * Extension object for DataTables that is used to provide all extension - * options. - * - * Note that the `DataTable.ext` object is available through - * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is - * also aliased to `jQuery.fn.dataTableExt` for historic reasons. - * @namespace - * @extends DataTable.models.ext - */ - - - /** - * DataTables extensions - * - * This namespace acts as a collection area for plug-ins that can be used to - * extend DataTables capabilities. Indeed many of the build in methods - * use this method to provide their own capabilities (sorting methods for - * example). - * - * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy - * reasons - * - * @namespace - */ - DataTable.ext = _ext = { - /** - * Buttons. For use with the Buttons extension for DataTables. This is - * defined here so other extensions can define buttons regardless of load - * order. It is _not_ used by DataTables core. - * - * @type object - * @default {} - */ - buttons: {}, - - - /** - * Element class names - * - * @type object - * @default {} - */ - classes: {}, - - - /** - * DataTables build type (expanded by the download builder) - * - * @type string - */ - builder: "-source-", - - - /** - * Error reporting. - * - * How should DataTables report an error. Can take the value 'alert', - * 'throw', 'none' or a function. - * - * @type string|function - * @default alert - */ - errMode: "alert", - - - /** - * Feature plug-ins. - * - * This is an array of objects which describe the feature plug-ins that are - * available to DataTables. These feature plug-ins are then available for - * use through the `dom` initialisation option. - * - * Each feature plug-in is described by an object which must have the - * following properties: - * - * * `fnInit` - function that is used to initialise the plug-in, - * * `cFeature` - a character so the feature can be enabled by the `dom` - * instillation option. This is case sensitive. - * - * The `fnInit` function has the following input parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * - * And the following return is expected: - * - * * {node|null} The element which contains your feature. Note that the - * return may also be void if your plug-in does not require to inject any - * DOM elements into DataTables control (`dom`) - for example this might - * be useful when developing a plug-in which allows table control via - * keyboard entry - * - * @type array - * - * @example - * $.fn.dataTable.ext.features.push( { - * "fnInit": function( oSettings ) { - * return new TableTools( { "oDTSettings": oSettings } ); - * }, - * "cFeature": "T" - * } ); - */ - feature: [], - - - /** - * Row searching. - * - * This method of searching is complimentary to the default type based - * searching, and a lot more comprehensive as it allows you complete control - * over the searching logic. Each element in this array is a function - * (parameters described below) that is called for every row in the table, - * and your logic decides if it should be included in the searching data set - * or not. - * - * Searching functions have the following input parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * 2. `{array|object}` Data for the row to be processed (same as the - * original format that was passed in as the data source, or an array - * from a DOM data source - * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which - * can be useful to retrieve the `TR` element if you need DOM interaction. - * - * And the following return is expected: - * - * * {boolean} Include the row in the searched result set (true) or not - * (false) - * - * Note that as with the main search ability in DataTables, technically this - * is "filtering", since it is subtractive. However, for consistency in - * naming we call it searching here. - * - * @type array - * @default [] - * - * @example - * // The following example shows custom search being applied to the - * // fourth column (i.e. the data[3] index) based on two input values - * // from the end-user, matching the data in a certain range. - * $.fn.dataTable.ext.search.push( - * function( settings, data, dataIndex ) { - * var min = document.getElementById('min').value * 1; - * var max = document.getElementById('max').value * 1; - * var version = data[3] == "-" ? 0 : data[3]*1; - * - * if ( min == "" && max == "" ) { - * return true; - * } - * else if ( min == "" && version < max ) { - * return true; - * } - * else if ( min < version && "" == max ) { - * return true; - * } - * else if ( min < version && version < max ) { - * return true; - * } - * return false; - * } - * ); - */ - search: [], - - - /** - * Selector extensions - * - * The `selector` option can be used to extend the options available for the - * selector modifier options (`selector-modifier` object data type) that - * each of the three built in selector types offer (row, column and cell + - * their plural counterparts). For example the Select extension uses this - * mechanism to provide an option to select only rows, columns and cells - * that have been marked as selected by the end user (`{selected: true}`), - * which can be used in conjunction with the existing built in selector - * options. - * - * Each property is an array to which functions can be pushed. The functions - * take three attributes: - * - * * Settings object for the host table - * * Options object (`selector-modifier` object type) - * * Array of selected item indexes - * - * The return is an array of the resulting item indexes after the custom - * selector has been applied. - * - * @type object - */ - selector: { - cell: [], - column: [], - row: [] - }, - - - /** - * Internal functions, exposed for used in plug-ins. - * - * Please note that you should not need to use the internal methods for - * anything other than a plug-in (and even then, try to avoid if possible). - * The internal function may change between releases. - * - * @type object - * @default {} - */ - internal: {}, - - - /** - * Legacy configuration options. Enable and disable legacy options that - * are available in DataTables. - * - * @type object - */ - legacy: { - /** - * Enable / disable DataTables 1.9 compatible server-side processing - * requests - * - * @type boolean - * @default null - */ - ajax: null - }, - - - /** - * Pagination plug-in methods. - * - * Each entry in this object is a function and defines which buttons should - * be shown by the pagination rendering method that is used for the table: - * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the - * buttons are displayed in the document, while the functions here tell it - * what buttons to display. This is done by returning an array of button - * descriptions (what each button will do). - * - * Pagination types (the four built in options and any additional plug-in - * options defined here) can be used through the `paginationType` - * initialisation parameter. - * - * The functions defined take two parameters: - * - * 1. `{int} page` The current page index - * 2. `{int} pages` The number of pages in the table - * - * Each function is expected to return an array where each element of the - * array can be one of: - * - * * `first` - Jump to first page when activated - * * `last` - Jump to last page when activated - * * `previous` - Show previous page when activated - * * `next` - Show next page when activated - * * `{int}` - Show page of the index given - * * `{array}` - A nested array containing the above elements to add a - * containing 'DIV' element (might be useful for styling). - * - * Note that DataTables v1.9- used this object slightly differently whereby - * an object with two functions would be defined for each plug-in. That - * ability is still supported by DataTables 1.10+ to provide backwards - * compatibility, but this option of use is now decremented and no longer - * documented in DataTables 1.10+. - * - * @type object - * @default {} - * - * @example - * // Show previous, next and current page buttons only - * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { - * return [ 'previous', page, 'next' ]; - * }; - */ - pager: {}, - - - renderer: { - pageButton: {}, - header: {} - }, - - - /** - * Ordering plug-ins - custom data source - * - * The extension options for ordering of data available here is complimentary - * to the default type based ordering that DataTables typically uses. It - * allows much greater control over the the data that is being used to - * order a column, but is necessarily therefore more complex. - * - * This type of ordering is useful if you want to do ordering based on data - * live from the DOM (for example the contents of an 'input' element) rather - * than just the static string that DataTables knows of. - * - * The way these plug-ins work is that you create an array of the values you - * wish to be ordering for the column in question and then return that - * array. The data in the array much be in the index order of the rows in - * the table (not the currently ordering order!). Which order data gathering - * function is run here depends on the `dt-init columns.orderDataType` - * parameter that is used for the column (if any). - * - * The functions defined take two parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * 2. `{int}` Target column index - * - * Each function is expected to return an array: - * - * * `{array}` Data for the column to be ordering upon - * - * @type array - * - * @example - * // Ordering using `input` node values - * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) - * { - * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { - * return $('input', td).val(); - * } ); - * } - */ - order: {}, - - - /** - * Type based plug-ins. - * - * Each column in DataTables has a type assigned to it, either by automatic - * detection or by direct assignment using the `type` option for the column. - * The type of a column will effect how it is ordering and search (plug-ins - * can also make use of the column type if required). - * - * @namespace - */ - type: { - /** - * Type detection functions. - * - * The functions defined in this object are used to automatically detect - * a column's type, making initialisation of DataTables super easy, even - * when complex data is in the table. - * - * The functions defined take two parameters: - * - * 1. `{*}` Data from the column cell to be analysed - * 2. `{settings}` DataTables settings object. This can be used to - * perform context specific type detection - for example detection - * based on language settings such as using a comma for a decimal - * place. Generally speaking the options from the settings will not - * be required - * - * Each function is expected to return: - * - * * `{string|null}` Data type detected, or null if unknown (and thus - * pass it on to the other type detection functions. - * - * @type array - * - * @example - * // Currency type detection plug-in: - * $.fn.dataTable.ext.type.detect.push( - * function ( data, settings ) { - * // Check the numeric part - * if ( ! $.isNumeric( data.substring(1) ) ) { - * return null; - * } - * - * // Check prefixed by currency - * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { - * return 'currency'; - * } - * return null; - * } - * ); - */ - detect: [], - - - /** - * Type based search formatting. - * - * The type based searching functions can be used to pre-format the - * data to be search on. For example, it can be used to strip HTML - * tags or to de-format telephone numbers for numeric only searching. - * - * Note that is a search is not defined for a column of a given type, - * no search formatting will be performed. - * - * Pre-processing of searching data plug-ins - When you assign the sType - * for a column (or have it automatically detected for you by DataTables - * or a type detection plug-in), you will typically be using this for - * custom sorting, but it can also be used to provide custom searching - * by allowing you to pre-processing the data and returning the data in - * the format that should be searched upon. This is done by adding - * functions this object with a parameter name which matches the sType - * for that target column. This is the corollary of afnSortData - * for searching data. - * - * The functions defined take a single parameter: - * - * 1. `{*}` Data from the column cell to be prepared for searching - * - * Each function is expected to return: - * - * * `{string|null}` Formatted string that will be used for the searching. - * - * @type object - * @default {} - * - * @example - * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { - * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); - * } - */ - search: {}, - - - /** - * Type based ordering. - * - * The column type tells DataTables what ordering to apply to the table - * when a column is sorted upon. The order for each type that is defined, - * is defined by the functions available in this object. - * - * Each ordering option can be described by three properties added to - * this object: - * - * * `{type}-pre` - Pre-formatting function - * * `{type}-asc` - Ascending order function - * * `{type}-desc` - Descending order function - * - * All three can be used together, only `{type}-pre` or only - * `{type}-asc` and `{type}-desc` together. It is generally recommended - * that only `{type}-pre` is used, as this provides the optimal - * implementation in terms of speed, although the others are provided - * for compatibility with existing Javascript sort functions. - * - * `{type}-pre`: Functions defined take a single parameter: - * - * 1. `{*}` Data from the column cell to be prepared for ordering - * - * And return: - * - * * `{*}` Data to be sorted upon - * - * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort - * functions, taking two parameters: - * - * 1. `{*}` Data to compare to the second parameter - * 2. `{*}` Data to compare to the first parameter - * - * And returning: - * - * * `{*}` Ordering match: <0 if first parameter should be sorted lower - * than the second parameter, ===0 if the two parameters are equal and - * >0 if the first parameter should be sorted height than the second - * parameter. - * - * @type object - * @default {} - * - * @example - * // Numeric ordering of formatted numbers with a pre-formatter - * $.extend( $.fn.dataTable.ext.type.order, { - * "string-pre": function(x) { - * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); - * return parseFloat( a ); - * } - * } ); - * - * @example - * // Case-sensitive string ordering, with no pre-formatting method - * $.extend( $.fn.dataTable.ext.order, { - * "string-case-asc": function(x,y) { - * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - * }, - * "string-case-desc": function(x,y) { - * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); - * } - * } ); - */ - order: {} - }, - - /** - * Unique DataTables instance counter - * - * @type int - * @private - */ - _unique: 0, - - - // - // Depreciated - // The following properties are retained for backwards compatiblity only. - // The should not be used in new projects and will be removed in a future - // version - // - - /** - * Version check function. - * @type function - * @depreciated Since 1.10 - */ - fnVersionCheck: DataTable.fnVersionCheck, - - - /** - * Index for what 'this' index API functions should use - * @type int - * @deprecated Since v1.10 - */ - iApiIndex: 0, - - - /** - * jQuery UI class container - * @type object - * @deprecated Since v1.10 - */ - oJUIClasses: {}, - - - /** - * Software version - * @type string - * @deprecated Since v1.10 - */ - sVersion: DataTable.version - }; - - - // - // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts - // - $.extend( _ext, { - afnFiltering: _ext.search, - aTypes: _ext.type.detect, - ofnSearch: _ext.type.search, - oSort: _ext.type.order, - afnSortData: _ext.order, - aoFeatures: _ext.feature, - oApi: _ext.internal, - oStdClasses: _ext.classes, - oPagination: _ext.pager - } ); - - - $.extend( DataTable.ext.classes, { - "sTable": "dataTable", - "sNoFooter": "no-footer", - - /* Paging buttons */ - "sPageButton": "paginate_button", - "sPageButtonActive": "current", - "sPageButtonDisabled": "disabled", - - /* Striping classes */ - "sStripeOdd": "odd", - "sStripeEven": "even", - - /* Empty row */ - "sRowEmpty": "dataTables_empty", - - /* Features */ - "sWrapper": "dataTables_wrapper", - "sFilter": "dataTables_filter", - "sInfo": "dataTables_info", - "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */ - "sLength": "dataTables_length", - "sProcessing": "dataTables_processing", - - /* Sorting */ - "sSortAsc": "sorting_asc", - "sSortDesc": "sorting_desc", - "sSortable": "sorting", /* Sortable in both directions */ - "sSortableAsc": "sorting_asc_disabled", - "sSortableDesc": "sorting_desc_disabled", - "sSortableNone": "sorting_disabled", - "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */ - - /* Filtering */ - "sFilterInput": "", - - /* Page length */ - "sLengthSelect": "", - - /* Scrolling */ - "sScrollWrapper": "dataTables_scroll", - "sScrollHead": "dataTables_scrollHead", - "sScrollHeadInner": "dataTables_scrollHeadInner", - "sScrollBody": "dataTables_scrollBody", - "sScrollFoot": "dataTables_scrollFoot", - "sScrollFootInner": "dataTables_scrollFootInner", - - /* Misc */ - "sHeaderTH": "", - "sFooterTH": "", - - // Deprecated - "sSortJUIAsc": "", - "sSortJUIDesc": "", - "sSortJUI": "", - "sSortJUIAscAllowed": "", - "sSortJUIDescAllowed": "", - "sSortJUIWrapper": "", - "sSortIcon": "", - "sJUIHeader": "", - "sJUIFooter": "" - } ); - - - (function() { - - // Reused strings for better compression. Closure compiler appears to have a - // weird edge case where it is trying to expand strings rather than use the - // variable version. This results in about 200 bytes being added, for very - // little preference benefit since it this run on script load only. - var _empty = ''; - _empty = ''; - - var _stateDefault = _empty + 'ui-state-default'; - var _sortIcon = _empty + 'css_right ui-icon ui-icon-'; - var _headerFooter = _empty + 'fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix'; - - $.extend( DataTable.ext.oJUIClasses, DataTable.ext.classes, { - /* Full numbers paging buttons */ - "sPageButton": "fg-button ui-button "+_stateDefault, - "sPageButtonActive": "ui-state-disabled", - "sPageButtonDisabled": "ui-state-disabled", - - /* Features */ - "sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi "+ - "ui-buttonset-multi paging_", /* Note that the type is postfixed */ - - /* Sorting */ - "sSortAsc": _stateDefault+" sorting_asc", - "sSortDesc": _stateDefault+" sorting_desc", - "sSortable": _stateDefault+" sorting", - "sSortableAsc": _stateDefault+" sorting_asc_disabled", - "sSortableDesc": _stateDefault+" sorting_desc_disabled", - "sSortableNone": _stateDefault+" sorting_disabled", - "sSortJUIAsc": _sortIcon+"triangle-1-n", - "sSortJUIDesc": _sortIcon+"triangle-1-s", - "sSortJUI": _sortIcon+"carat-2-n-s", - "sSortJUIAscAllowed": _sortIcon+"carat-1-n", - "sSortJUIDescAllowed": _sortIcon+"carat-1-s", - "sSortJUIWrapper": "DataTables_sort_wrapper", - "sSortIcon": "DataTables_sort_icon", - - /* Scrolling */ - "sScrollHead": "dataTables_scrollHead "+_stateDefault, - "sScrollFoot": "dataTables_scrollFoot "+_stateDefault, - - /* Misc */ - "sHeaderTH": _stateDefault, - "sFooterTH": _stateDefault, - "sJUIHeader": _headerFooter+" ui-corner-tl ui-corner-tr", - "sJUIFooter": _headerFooter+" ui-corner-bl ui-corner-br" - } ); - - }()); - - - - var extPagination = DataTable.ext.pager; - - function _numbers ( page, pages ) { - var - numbers = [], - buttons = extPagination.numbers_length, - half = Math.floor( buttons / 2 ), - i = 1; - - if ( pages <= buttons ) { - numbers = _range( 0, pages ); - } - else if ( page <= half ) { - numbers = _range( 0, buttons-2 ); - numbers.push( 'ellipsis' ); - numbers.push( pages-1 ); - } - else if ( page >= pages - 1 - half ) { - numbers = _range( pages-(buttons-2), pages ); - numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6 - numbers.splice( 0, 0, 0 ); - } - else { - numbers = _range( page-half+2, page+half-1 ); - numbers.push( 'ellipsis' ); - numbers.push( pages-1 ); - numbers.splice( 0, 0, 'ellipsis' ); - numbers.splice( 0, 0, 0 ); - } - - numbers.DT_el = 'span'; - return numbers; - } - - - $.extend( extPagination, { - simple: function ( page, pages ) { - return [ 'previous', 'next' ]; - }, - - full: function ( page, pages ) { - return [ 'first', 'previous', 'next', 'last' ]; - }, - - numbers: function ( page, pages ) { - return [ _numbers(page, pages) ]; - }, - - simple_numbers: function ( page, pages ) { - return [ 'previous', _numbers(page, pages), 'next' ]; - }, - - full_numbers: function ( page, pages ) { - return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ]; - }, - - // For testing and plug-ins to use - _numbers: _numbers, - - // Number of number buttons (including ellipsis) to show. _Must be odd!_ - numbers_length: 7 - } ); - - - $.extend( true, DataTable.ext.renderer, { - pageButton: { - _: function ( settings, host, idx, buttons, page, pages ) { - var classes = settings.oClasses; - var lang = settings.oLanguage.oPaginate; - var aria = settings.oLanguage.oAria.paginate || {}; - var btnDisplay, btnClass, counter=0; - - var attach = function( container, buttons ) { - var i, ien, node, button; - var clickHandler = function ( e ) { - _fnPageChange( settings, e.data.action, true ); - }; - - for ( i=0, ien=buttons.length ; i' ) - .appendTo( container ); - attach( inner, button ); - } - else { - btnDisplay = null; - btnClass = ''; - - switch ( button ) { - case 'ellipsis': - container.append(''); - break; - - case 'first': - btnDisplay = lang.sFirst; - btnClass = button + (page > 0 ? - '' : ' '+classes.sPageButtonDisabled); - break; - - case 'previous': - btnDisplay = lang.sPrevious; - btnClass = button + (page > 0 ? - '' : ' '+classes.sPageButtonDisabled); - break; - - case 'next': - btnDisplay = lang.sNext; - btnClass = button + (page < pages-1 ? - '' : ' '+classes.sPageButtonDisabled); - break; - - case 'last': - btnDisplay = lang.sLast; - btnClass = button + (page < pages-1 ? - '' : ' '+classes.sPageButtonDisabled); - break; - - default: - btnDisplay = button + 1; - btnClass = page === button ? - classes.sPageButtonActive : ''; - break; - } - - if ( btnDisplay !== null ) { - node = $('', { - 'class': classes.sPageButton+' '+btnClass, - 'aria-controls': settings.sTableId, - 'aria-label': aria[ button ], - 'data-dt-idx': counter, - 'tabindex': settings.iTabIndex, - 'id': idx === 0 && typeof button === 'string' ? - settings.sTableId +'_'+ button : - null - } ) - .html( btnDisplay ) - .appendTo( container ); - - _fnBindAction( - node, {action: button}, clickHandler - ); - - counter++; - } - } - } - }; - - // IE9 throws an 'unknown error' if document.activeElement is used - // inside an iframe or frame. Try / catch the error. Not good for - // accessibility, but neither are frames. - var activeEl; - - try { - // Because this approach is destroying and recreating the paging - // elements, focus is lost on the select button which is bad for - // accessibility. So we want to restore focus once the draw has - // completed - activeEl = $(host).find(document.activeElement).data('dt-idx'); - } - catch (e) {} - - attach( $(host).empty(), buttons ); - - if ( activeEl ) { - $(host).find( '[data-dt-idx='+activeEl+']' ).focus(); - } - } - } - } ); - - - - // Built in type detection. See model.ext.aTypes for information about - // what is required from this methods. - $.extend( DataTable.ext.type.detect, [ - // Plain numbers - first since V8 detects some plain numbers as dates - // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...). - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _isNumber( d, decimal ) ? 'num'+decimal : null; - }, - - // Dates (only those recognised by the browser's Date.parse) - function ( d, settings ) - { - // V8 will remove any unknown characters at the start and end of the - // expression, leading to false matches such as `$245.12` or `10%` being - // a valid date. See forum thread 18941 for detail. - if ( d && !(d instanceof Date) && ( ! _re_date_start.test(d) || ! _re_date_end.test(d) ) ) { - return null; - } - var parsed = Date.parse(d); - return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null; - }, - - // Formatted numbers - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null; - }, - - // HTML numeric - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null; - }, - - // HTML numeric, formatted - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null; - }, - - // HTML (this is strict checking - there must be html) - function ( d, settings ) - { - return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ? - 'html' : null; - } - ] ); - - - - // Filter formatting functions. See model.ext.ofnSearch for information about - // what is required from these methods. - // - // Note that additional search methods are added for the html numbers and - // html formatted numbers by `_addNumericSort()` when we know what the decimal - // place is - - - $.extend( DataTable.ext.type.search, { - html: function ( data ) { - return _empty(data) ? - data : - typeof data === 'string' ? - data - .replace( _re_new_lines, " " ) - .replace( _re_html, "" ) : - ''; - }, - - string: function ( data ) { - return _empty(data) ? - data : - typeof data === 'string' ? - data.replace( _re_new_lines, " " ) : - data; - } - } ); - - - - var __numericReplace = function ( d, decimalPlace, re1, re2 ) { - if ( d !== 0 && (!d || d === '-') ) { - return -Infinity; - } - - // If a decimal place other than `.` is used, it needs to be given to the - // function so we can detect it and replace with a `.` which is the only - // decimal place Javascript recognises - it is not locale aware. - if ( decimalPlace ) { - d = _numToDecimal( d, decimalPlace ); - } - - if ( d.replace ) { - if ( re1 ) { - d = d.replace( re1, '' ); - } - - if ( re2 ) { - d = d.replace( re2, '' ); - } - } - - return d * 1; - }; - - - // Add the numeric 'deformatting' functions for sorting and search. This is done - // in a function to provide an easy ability for the language options to add - // additional methods if a non-period decimal place is used. - function _addNumericSort ( decimalPlace ) { - $.each( - { - // Plain numbers - "num": function ( d ) { - return __numericReplace( d, decimalPlace ); - }, - - // Formatted numbers - "num-fmt": function ( d ) { - return __numericReplace( d, decimalPlace, _re_formatted_numeric ); - }, - - // HTML numeric - "html-num": function ( d ) { - return __numericReplace( d, decimalPlace, _re_html ); - }, - - // HTML numeric, formatted - "html-num-fmt": function ( d ) { - return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric ); - } - }, - function ( key, fn ) { - // Add the ordering method - _ext.type.order[ key+decimalPlace+'-pre' ] = fn; - - // For HTML types add a search formatter that will strip the HTML - if ( key.match(/^html\-/) ) { - _ext.type.search[ key+decimalPlace ] = _ext.type.search.html; - } - } - ); - } - - - // Default sort methods - $.extend( _ext.type.order, { - // Dates - "date-pre": function ( d ) { - return Date.parse( d ) || 0; - }, - - // html - "html-pre": function ( a ) { - return _empty(a) ? - '' : - a.replace ? - a.replace( /<.*?>/g, "" ).toLowerCase() : - a+''; - }, - - // string - "string-pre": function ( a ) { - // This is a little complex, but faster than always calling toString, - // http://jsperf.com/tostring-v-check - return _empty(a) ? - '' : - typeof a === 'string' ? - a.toLowerCase() : - ! a.toString ? - '' : - a.toString(); - }, - - // string-asc and -desc are retained only for compatibility with the old - // sort methods - "string-asc": function ( x, y ) { - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }, - - "string-desc": function ( x, y ) { - return ((x < y) ? 1 : ((x > y) ? -1 : 0)); - } - } ); - - - // Numeric sorting types - order doesn't matter here - _addNumericSort( '' ); - - - $.extend( true, DataTable.ext.renderer, { - header: { - _: function ( settings, cell, column, classes ) { - // No additional mark-up required - // Attach a sort listener to update on sort - note that using the - // `DT` namespace will allow the event to be removed automatically - // on destroy, while the `dt` namespaced event is the one we are - // listening for - $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { - if ( settings !== ctx ) { // need to check this this is the host - return; // table, not a nested one - } - - var colIdx = column.idx; - - cell - .removeClass( - column.sSortingClass +' '+ - classes.sSortAsc +' '+ - classes.sSortDesc - ) - .addClass( columns[ colIdx ] == 'asc' ? - classes.sSortAsc : columns[ colIdx ] == 'desc' ? - classes.sSortDesc : - column.sSortingClass - ); - } ); - }, - - jqueryui: function ( settings, cell, column, classes ) { - $('
    ') - .addClass( classes.sSortJUIWrapper ) - .append( cell.contents() ) - .append( $('') - .addClass( classes.sSortIcon+' '+column.sSortingClassJUI ) - ) - .appendTo( cell ); - - // Attach a sort listener to update on sort - $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { - if ( settings !== ctx ) { - return; - } - - var colIdx = column.idx; - - cell - .removeClass( classes.sSortAsc +" "+classes.sSortDesc ) - .addClass( columns[ colIdx ] == 'asc' ? - classes.sSortAsc : columns[ colIdx ] == 'desc' ? - classes.sSortDesc : - column.sSortingClass - ); - - cell - .find( 'span.'+classes.sSortIcon ) - .removeClass( - classes.sSortJUIAsc +" "+ - classes.sSortJUIDesc +" "+ - classes.sSortJUI +" "+ - classes.sSortJUIAscAllowed +" "+ - classes.sSortJUIDescAllowed - ) - .addClass( columns[ colIdx ] == 'asc' ? - classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ? - classes.sSortJUIDesc : - column.sSortingClassJUI - ); - } ); - } - } - } ); - - /* - * Public helper functions. These aren't used internally by DataTables, or - * called by any of the options passed into DataTables, but they can be used - * externally by developers working with DataTables. They are helper functions - * to make working with DataTables a little bit easier. - */ - - var __htmlEscapeEntities = function ( d ) { - return typeof d === 'string' ? - d.replace(//g, '>').replace(/"/g, '"') : - d; - }; - - /** - * Helpers for `columns.render`. - * - * The options defined here can be used with the `columns.render` initialisation - * option to provide a display renderer. The following functions are defined: - * - * * `number` - Will format numeric data (defined by `columns.data`) for - * display, retaining the original unformatted data for sorting and filtering. - * It takes 5 parameters: - * * `string` - Thousands grouping separator - * * `string` - Decimal point indicator - * * `integer` - Number of decimal points to show - * * `string` (optional) - Prefix. - * * `string` (optional) - Postfix (/suffix). - * * `text` - Escape HTML to help prevent XSS attacks. It has no optional - * parameters. - * - * @example - * // Column definition using the number renderer - * { - * data: "salary", - * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) - * } - * - * @namespace - */ - DataTable.render = { - number: function ( thousands, decimal, precision, prefix, postfix ) { - return { - display: function ( d ) { - if ( typeof d !== 'number' && typeof d !== 'string' ) { - return d; - } - - var negative = d < 0 ? '-' : ''; - var flo = parseFloat( d ); - - // If NaN then there isn't much formatting that we can do - just - // return immediately, escaping any HTML (this was supposed to - // be a number after all) - if ( isNaN( flo ) ) { - return __htmlEscapeEntities( d ); - } - - d = Math.abs( flo ); - - var intPart = parseInt( d, 10 ); - var floatPart = precision ? - decimal+(d - intPart).toFixed( precision ).substring( 2 ): - ''; - - return negative + (prefix||'') + - intPart.toString().replace( - /\B(?=(\d{3})+(?!\d))/g, thousands - ) + - floatPart + - (postfix||''); - } - }; - }, - - text: function () { - return { - display: __htmlEscapeEntities - }; - } - }; - - - /* - * This is really a good bit rubbish this method of exposing the internal methods - * publicly... - To be fixed in 2.0 using methods on the prototype - */ - - - /** - * Create a wrapper function for exporting an internal functions to an external API. - * @param {string} fn API function name - * @returns {function} wrapped function - * @memberof DataTable#internal - */ - function _fnExternApiFunc (fn) - { - return function() { - var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat( - Array.prototype.slice.call(arguments) - ); - return DataTable.ext.internal[fn].apply( this, args ); - }; - } - - - /** - * Reference to internal functions for use by plug-in developers. Note that - * these methods are references to internal functions and are considered to be - * private. If you use these methods, be aware that they are liable to change - * between versions. - * @namespace - */ - $.extend( DataTable.ext.internal, { - _fnExternApiFunc: _fnExternApiFunc, - _fnBuildAjax: _fnBuildAjax, - _fnAjaxUpdate: _fnAjaxUpdate, - _fnAjaxParameters: _fnAjaxParameters, - _fnAjaxUpdateDraw: _fnAjaxUpdateDraw, - _fnAjaxDataSrc: _fnAjaxDataSrc, - _fnAddColumn: _fnAddColumn, - _fnColumnOptions: _fnColumnOptions, - _fnAdjustColumnSizing: _fnAdjustColumnSizing, - _fnVisibleToColumnIndex: _fnVisibleToColumnIndex, - _fnColumnIndexToVisible: _fnColumnIndexToVisible, - _fnVisbleColumns: _fnVisbleColumns, - _fnGetColumns: _fnGetColumns, - _fnColumnTypes: _fnColumnTypes, - _fnApplyColumnDefs: _fnApplyColumnDefs, - _fnHungarianMap: _fnHungarianMap, - _fnCamelToHungarian: _fnCamelToHungarian, - _fnLanguageCompat: _fnLanguageCompat, - _fnBrowserDetect: _fnBrowserDetect, - _fnAddData: _fnAddData, - _fnAddTr: _fnAddTr, - _fnNodeToDataIndex: _fnNodeToDataIndex, - _fnNodeToColumnIndex: _fnNodeToColumnIndex, - _fnGetCellData: _fnGetCellData, - _fnSetCellData: _fnSetCellData, - _fnSplitObjNotation: _fnSplitObjNotation, - _fnGetObjectDataFn: _fnGetObjectDataFn, - _fnSetObjectDataFn: _fnSetObjectDataFn, - _fnGetDataMaster: _fnGetDataMaster, - _fnClearTable: _fnClearTable, - _fnDeleteIndex: _fnDeleteIndex, - _fnInvalidate: _fnInvalidate, - _fnGetRowElements: _fnGetRowElements, - _fnCreateTr: _fnCreateTr, - _fnBuildHead: _fnBuildHead, - _fnDrawHead: _fnDrawHead, - _fnDraw: _fnDraw, - _fnReDraw: _fnReDraw, - _fnAddOptionsHtml: _fnAddOptionsHtml, - _fnDetectHeader: _fnDetectHeader, - _fnGetUniqueThs: _fnGetUniqueThs, - _fnFeatureHtmlFilter: _fnFeatureHtmlFilter, - _fnFilterComplete: _fnFilterComplete, - _fnFilterCustom: _fnFilterCustom, - _fnFilterColumn: _fnFilterColumn, - _fnFilter: _fnFilter, - _fnFilterCreateSearch: _fnFilterCreateSearch, - _fnEscapeRegex: _fnEscapeRegex, - _fnFilterData: _fnFilterData, - _fnFeatureHtmlInfo: _fnFeatureHtmlInfo, - _fnUpdateInfo: _fnUpdateInfo, - _fnInfoMacros: _fnInfoMacros, - _fnInitialise: _fnInitialise, - _fnInitComplete: _fnInitComplete, - _fnLengthChange: _fnLengthChange, - _fnFeatureHtmlLength: _fnFeatureHtmlLength, - _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate, - _fnPageChange: _fnPageChange, - _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing, - _fnProcessingDisplay: _fnProcessingDisplay, - _fnFeatureHtmlTable: _fnFeatureHtmlTable, - _fnScrollDraw: _fnScrollDraw, - _fnApplyToChildren: _fnApplyToChildren, - _fnCalculateColumnWidths: _fnCalculateColumnWidths, - _fnThrottle: _fnThrottle, - _fnConvertToWidth: _fnConvertToWidth, - _fnGetWidestNode: _fnGetWidestNode, - _fnGetMaxLenString: _fnGetMaxLenString, - _fnStringToCss: _fnStringToCss, - _fnSortFlatten: _fnSortFlatten, - _fnSort: _fnSort, - _fnSortAria: _fnSortAria, - _fnSortListener: _fnSortListener, - _fnSortAttachListener: _fnSortAttachListener, - _fnSortingClasses: _fnSortingClasses, - _fnSortData: _fnSortData, - _fnSaveState: _fnSaveState, - _fnLoadState: _fnLoadState, - _fnSettingsFromNode: _fnSettingsFromNode, - _fnLog: _fnLog, - _fnMap: _fnMap, - _fnBindAction: _fnBindAction, - _fnCallbackReg: _fnCallbackReg, - _fnCallbackFire: _fnCallbackFire, - _fnLengthOverflow: _fnLengthOverflow, - _fnRenderer: _fnRenderer, - _fnDataSource: _fnDataSource, - _fnRowAttributes: _fnRowAttributes, - _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant - // in 1.10, so this dead-end function is - // added to prevent errors - } ); - - - // jQuery access - $.fn.dataTable = DataTable; - - // Provide access to the host jQuery object (circular reference) - DataTable.$ = $; - - // Legacy aliases - $.fn.dataTableSettings = DataTable.settings; - $.fn.dataTableExt = DataTable.ext; - - // With a capital `D` we return a DataTables API instance rather than a - // jQuery object - $.fn.DataTable = function ( opts ) { - return $(this).dataTable( opts ).api(); - }; - - // All properties that are available to $.fn.dataTable should also be - // available on $.fn.DataTable - $.each( DataTable, function ( prop, val ) { - $.fn.DataTable[ prop ] = val; - } ); - - - // Information about events fired by DataTables - for documentation. - /** - * Draw event, fired whenever the table is redrawn on the page, at the same - * point as fnDrawCallback. This may be useful for binding events or - * performing calculations when the table is altered at all. - * @name DataTable#draw.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - */ - - /** - * Search event, fired when the searching applied to the table (using the - * built-in global search, or column filters) is altered. - * @name DataTable#search.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - */ - - /** - * Page change event, fired when the paging of the table is altered. - * @name DataTable#page.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - */ - - /** - * Order event, fired when the ordering applied to the table is altered. - * @name DataTable#order.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - */ - - /** - * DataTables initialisation complete event, fired when the table is fully - * drawn, including Ajax data loaded, if Ajax data is required. - * @name DataTable#init.dt - * @event - * @param {event} e jQuery event object - * @param {object} oSettings DataTables settings object - * @param {object} json The JSON object request from the server - only - * present if client-side Ajax sourced data is used - */ - - /** - * State save event, fired when the table has changed state a new state save - * is required. This event allows modification of the state saving object - * prior to actually doing the save, including addition or other state - * properties (for plug-ins) or modification of a DataTables core property. - * @name DataTable#stateSaveParams.dt - * @event - * @param {event} e jQuery event object - * @param {object} oSettings DataTables settings object - * @param {object} json The state information to be saved - */ - - /** - * State load event, fired when the table is loading state from the stored - * data, but prior to the settings object being modified by the saved state - * - allowing modification of the saved state is required or loading of - * state for a plug-in. - * @name DataTable#stateLoadParams.dt - * @event - * @param {event} e jQuery event object - * @param {object} oSettings DataTables settings object - * @param {object} json The saved state information - */ - - /** - * State loaded event, fired when state has been loaded from stored data and - * the settings object has been modified by the loaded data. - * @name DataTable#stateLoaded.dt - * @event - * @param {event} e jQuery event object - * @param {object} oSettings DataTables settings object - * @param {object} json The saved state information - */ - - /** - * Processing event, fired when DataTables is doing some kind of processing - * (be it, order, searcg or anything else). It can be used to indicate to - * the end user that there is something happening, or that something has - * finished. - * @name DataTable#processing.dt - * @event - * @param {event} e jQuery event object - * @param {object} oSettings DataTables settings object - * @param {boolean} bShow Flag for if DataTables is doing processing or not - */ - - /** - * Ajax (XHR) event, fired whenever an Ajax request is completed from a - * request to made to the server for new data. This event is called before - * DataTables processed the returned data, so it can also be used to pre- - * process the data returned from the server, if needed. - * - * Note that this trigger is called in `fnServerData`, if you override - * `fnServerData` and which to use this event, you need to trigger it in you - * success function. - * @name DataTable#xhr.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - * @param {object} json JSON returned from the server - * - * @example - * // Use a custom property returned from the server in another DOM element - * $('#table').dataTable().on('xhr.dt', function (e, settings, json) { - * $('#status').html( json.status ); - * } ); - * - * @example - * // Pre-process the data returned from the server - * $('#table').dataTable().on('xhr.dt', function (e, settings, json) { - * for ( var i=0, ien=json.aaData.length ; i").css({position:"fixed",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("
    ").css({position:"absolute",top:1,left:1, -width:100,overflow:"scroll"}).append(h("
    ").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function hb(a,b,c,d,e,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;d!==e;)a.hasOwnProperty(d)&& -(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Ea(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:I.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);ja(a,d,h(b).data())}function ja(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f= -(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(fb(c),K(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),E(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),E(b,c,"aDataSort"));var g=b.mData,j=Q(g),i=b.mRender?Q(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&& -(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=j(a,b,k,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return R(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed): -!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Y(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Fa(a);for(var c=0,d=b.length;cq[f])d(l.length+q[f],n);else if("string"===typeof q[f]){j=0;for(i=l.length;jb&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ca(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild); -c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ia(a,e,d,d===k?k:e._aData).data;else{var j=e.anCells;if(j)if(d!==k)g(j[d],d);else{c=0;for(f=j.length;c").appendTo(g));b=0;for(c=l.length;btr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(j).find(">tr>th, >tr>td").addClass(n.sFooterTH); -if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart= --1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!lb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:n;for(j=j?0:g;j",{"class":e?d[0]:""}).append(h("
    ").appendTo(this));o.nTBody=j[0];j=q.children("tfoot");if(0===j.length&&0").appendTo(this);0===j.length||0===j.children().length?q.addClass(i.sNoFooter):0/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,cc=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Xa=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,M=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Ya[b]||(Ya[b]=RegExp(Qa(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g, -"").replace(Ya[b],"."):a},Za=function(a,b,c){var d="string"===typeof a;if(M(a))return!0;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Xa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return M(a)?!0:!(M(a)||"string"===typeof a)?null:Za(a.replace(Aa,""),b,c)?!0:null},G=function(a,b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e")[0],Zb=ua.textContent!==k,$b=/<.*?>/g,Oa=m.util.throttle,Tb=[],w=Array.prototype,dc=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]: -null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};r=function(a,b){if(!(this instanceof r))return new r(a,b);var c=[],d=function(a){(a=dc(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;ea?new r(b[a],this[a]):null},filter:function(a){var b=[];if(w.filter)b=w.filter.call(this,a,this);else for(var c=0,d=this.length;c").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=aa(d),e.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this}); -p(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});p(["row().child.remove()","row().child().remove()"],function(){cb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var ec=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d=0?b:g.length+b];if(typeof a==="function"){var e=Ba(c,f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,e),i[f])?f:null})}var k=typeof a==="string"?a.match(ec):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1], -10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[Z(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null});default:return[]}if(a.nodeName&&a._DT_CellIndex)return[a._DT_CellIndex.column];b=h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray();if(b.length||!a.nodeName)return b;b=h(a).closest("*[data-dt-column]");return b.length?[b.data("dt-column")]:[]},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});s("columns().header()", -"column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});s("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});s("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});s("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});s("columns().cache()","column().cache()", -function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ha(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});s("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ha(a.aoData,e,"anCells",b)},1)});s("columns().visible()","column().visible()",function(a,b){var c=this.iterator("column",function(b,c){if(a===k)return b.aoColumns[c].bVisible;var f=b.aoColumns,g=f[c],j=b.aoData,i,n,l;if(a!==k&&g.bVisible!==a){if(a){var m= -h.inArray(!0,G(f,"bVisible"),c+1);i=0;for(n=j.length;id;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]: -null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});return b?new r(c):c};m.camelToHungarian=K;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)|| -(a[0]+=".dt");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",function(a){na(a)})});p("settings()",function(){return new r(this.context,this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return G(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode, -d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),p;b.bDestroying=!0;u(b,"aoDestroyCallback","destroy",[b]);a||(new r(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(D).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&e!=j.parentNode&&(i.children("tfoot").detach(),i.append(j));b.aaSorting=[];b.aaSortingFixed=[];va(b);h(l).removeClass(b.asStripeClasses.join(" ")); -h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";i[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),i.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a% -p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){p(b+"s().every()",function(a){var d=this.selector.opts,e=this;return this.iterator(b,function(f,g,h,i,n){a.call(e[b](g,"cell"===b?h:d,"cell"===b?d:k),g,h,i,n)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=Q(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.12";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0, -sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null, -sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null, -fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1=== -a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries", -sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET", -renderer:null,rowId:"DT_RowId"};X(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};X(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null, -bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[], -aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k, -fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a= -this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=v={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{}, -header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd", -sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead", -sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ca="",Ca="",H=Ca+"ui-state-default",ia=Ca+"css_right ui-icon ui-icon-",Xb=Ca+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses, -m.ext.classes,{sPageButton:"fg-button ui-button "+H,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:H+" sorting_asc",sSortDesc:H+" sorting_desc",sSortable:H+" sorting",sSortableAsc:H+" sorting_asc_disabled",sSortableDesc:H+" sorting_desc_disabled",sSortableNone:H+" sorting_disabled",sSortJUIAsc:ia+"triangle-1-n",sSortJUIDesc:ia+"triangle-1-s",sSortJUI:ia+"carat-2-n-s", -sSortJUIAscAllowed:ia+"carat-1-n",sSortJUIDescAllowed:ia+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+H,sScrollFoot:"dataTables_scrollFoot "+H,sHeaderTH:H,sFooterTH:H,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ya(a, -b)]},simple_numbers:function(a,b){return["previous",ya(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ya(a,b),"next","last"]},_numbers:ya,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i=a.oLanguage.oAria.paginate||{},k,l,m=0,p=function(b,d){var o,r,u,s,v=function(b){Ta(a,b.data.action,true)};o=0;for(r=d.length;o").appendTo(b);p(u,s)}else{k=null; -l="";switch(s){case "ellipsis":b.append('');break;case "first":k=j.sFirst;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":k=j.sPrevious;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":k=j.sNext;l=s+(e",{"class":g.sPageButton+" "+l,"aria-controls":a.sTableId,"aria-label":i[s], -"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(k).appendTo(b);Wa(u,{action:s},v);m++}}}},r;try{r=h(b).find(I.activeElement).data("dt-idx")}catch(o){}p(h(b).empty(),d);r&&h(b).find("[data-dt-idx="+r+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||M(a)?"date": -null},function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,!0)?"html-num-fmt"+c:null},function(a){return M(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Aa,""):""},string:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob, -" "):a}});var za=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(v.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return M(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return M(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a, -b){return ab?-1:0}});db("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("
    ").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e, -f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});var Yb=function(a){return"string"===typeof a?a.replace(//g,">").replace(/"/g,"""):a};m.render={number:function(a, -b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return Yb(f);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:Yb}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:lb,_fnAjaxParameters:ub,_fnAjaxUpdateDraw:vb,_fnAjaxDataSrc:sa,_fnAddColumn:Ea,_fnColumnOptions:ja, -_fnAdjustColumnSizing:Y,_fnVisibleToColumnIndex:Z,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:la,_fnColumnTypes:Ga,_fnApplyColumnDefs:ib,_fnHungarianMap:X,_fnCamelToHungarian:K,_fnLanguageCompat:Da,_fnBrowserDetect:gb,_fnAddData:N,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:B,_fnSetCellData:jb,_fnSplitObjNotation:Ja,_fnGetObjectDataFn:Q,_fnSetObjectDataFn:R, -_fnGetDataMaster:Ka,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ca,_fnGetRowElements:Ia,_fnCreateTr:Ha,_fnBuildHead:kb,_fnDrawHead:ea,_fnDraw:O,_fnReDraw:T,_fnAddOptionsHtml:nb,_fnDetectHeader:da,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:pb,_fnFilterComplete:fa,_fnFilterCustom:yb,_fnFilterColumn:xb,_fnFilter:wb,_fnFilterCreateSearch:Pa,_fnEscapeRegex:Qa,_fnFilterData:zb,_fnFeatureHtmlInfo:sb,_fnUpdateInfo:Cb,_fnInfoMacros:Db,_fnInitialise:ga,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:ob, -_fnFeatureHtmlPaginate:tb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:qb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:rb,_fnScrollDraw:ka,_fnApplyToChildren:J,_fnCalculateColumnWidths:Fa,_fnThrottle:Oa,_fnConvertToWidth:Fb,_fnGetWidestNode:Gb,_fnGetMaxLenString:Hb,_fnStringToCss:x,_fnSortFlatten:V,_fnSort:mb,_fnSortAria:Jb,_fnSortListener:Va,_fnSortAttachListener:Ma,_fnSortingClasses:va,_fnSortData:Ib,_fnSaveState:wa,_fnLoadState:Kb,_fnSettingsFromNode:xa,_fnLog:L,_fnMap:E,_fnBindAction:Wa,_fnCallbackReg:z, -_fnCallbackFire:u,_fnLengthOverflow:Sa,_fnRenderer:Na,_fnDataSource:y,_fnRowAttributes:La,_fnCalculateEnd:function(){}});h.fn.dataTable=m;m.$=h;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable}); diff --git a/wqflask/wqflask/static/new/packages/DataTables/js/jquery.js b/wqflask/wqflask/static/new/packages/DataTables/js/jquery.js deleted file mode 100644 index 71b96edb..00000000 --- a/wqflask/wqflask/static/new/packages/DataTables/js/jquery.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! jQuery v1.12.0 | (c) jQuery Foundation | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; -return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
    ",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];u(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ka(a),g,n,i]);u(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ka(a),g,n,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function T(a,b){var c=a.oFeatures,d=c.bFilter; -c.bSort&&mb(a);d?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;O(a);a._drawHold=!1}function nb(a){var b=a.oClasses,c=h(a.nTable),c=h("
    ").insertBefore(c),d=a.oFeatures,e=h("
    ",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,n,l,q,t=0;t")[0]; -n=f[t+1];if("'"==n||'"'==n){l="";for(q=2;f[t+q]!=n;)l+=f[t+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),i.id=n[0].substr(1,n[0].length-1),i.className=n[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;t+=q}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==j&&d.bPaginate&&d.bLengthChange)g=ob(a);else if("f"==j&&d.bFilter)g=pb(a);else if("r"==j&&d.bProcessing)g=qb(a);else if("t"==j)g=rb(a);else if("i"==j&&d.bInfo)g=sb(a);else if("p"== -j&&d.bPaginate)g=tb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(n=i.length;q',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("
    ",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("
    ").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());O(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function tb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){O(a)},b=h("
    ").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures; -d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),k,l=0;for(k=f.p.length;lf&&(d=0)):"first"==b?d=0: -"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");u(a,null,"processing", -[a,b])}function rb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");l.length||(l=null);i=h("
    ",{"class":f.sScrollWrapper}).append(h("
    ",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:x(d):"100%"}).append(h("
    ",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box", -width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("
    ",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:x(d)}).append(b));l&&i.append(h("
    ",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:x(d):"100%"}).append(h("
    ",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===j?g:null).append(b.children("tfoot"))))); -var b=i.children(),k=b[0],f=b[1],t=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(t.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=t;a.aoDrawCallback.push({fn:ka,sName:"scrolling"});return i[0]}function ka(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,j=f.children("div"),i=j[0].style,n=j.children("table"),j=a.nScrollBody,l=h(j),q=j.style,t=h(a.nScrollFoot).children("div"), -m=t.children("table"),o=h(a.nTHead),F=h(a.nTable),p=F[0],r=p.style,u=a.nTFoot?h(a.nTFoot):null,Eb=a.oBrowser,Ua=Eb.bScrollOversize,s=G(a.aoColumns,"nTh"),P,v,w,y,z=[],A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};v=j.scrollHeight>j.clientHeight;if(a.scrollBarVis!==v&&a.scrollBarVis!==k)a.scrollBarVis=v,Y(a);else{a.scrollBarVis=v;F.children("thead, tfoot").remove();u&&(w=u.clone().prependTo(F),P=u.find("tr"),w= -w.find("tr"));y=o.clone().prependTo(F);o=o.find("tr");v=y.find("tr");y.find("th, td").removeAttr("tabindex");c||(q.width="100%",f[0].style.width="100%");h.each(qa(a,y),function(b,c){D=Z(a,b);c.style.width=a.aoColumns[D].sWidth});u&&J(function(a){a.style.width=""},w);f=F.outerWidth();if(""===c){r.width="100%";if(Ua&&(F.find("tbody").height()>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(F.outerWidth()-b);f=F.outerWidth()}else""!==d&&(r.width=x(d),f=F.outerWidth());J(E,v);J(function(a){B.push(a.innerHTML); -z.push(x(h(a).css("width")))},v);J(function(a,b){if(h.inArray(a,s)!==-1)a.style.width=z[b]},o);h(v).height(0);u&&(J(E,w),J(function(a){C.push(a.innerHTML);A.push(x(h(a).css("width")))},w),J(function(a,b){a.style.width=A[b]},P),h(w).height(0));J(function(a,b){a.innerHTML='
    '+B[b]+"
    ";a.style.width=z[b]},v);u&&J(function(a,b){a.innerHTML='
    '+C[b]+"
    ";a.style.width= -A[b]},w);if(F.outerWidth()j.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(Ua&&(j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(P-b);(""===c||""!==d)&&L(a,1,"Possible column misalignment",6)}else P="100%";q.width=x(P);g.width=x(P);u&&(a.nScrollFoot.style.width=x(P));!e&&Ua&&(q.height=x(p.offsetHeight+b));c=F.outerWidth();n[0].style.width=x(c);i.width=x(c);d=F.height()>j.clientHeight||"scroll"==l.css("overflow-y");e="padding"+(Eb.bScrollbarLeft?"Left": -"Right");i[e]=d?b+"px":"0px";u&&(m[0].style.width=x(c),t[0].style.width=x(c),t[0].style[e]=d?b+"px":"0px");F.children("colgroup").insertBefore(F.children("thead"));l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}}function J(a,b,c){for(var d=0,e=0,f=b.length,g,j;e").appendTo(j.find("tbody")); -j.find("thead, tfoot").remove();j.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());j.find("tfoot th, tfoot td").css("width","");n=qa(a,j.find("thead")[0]);for(m=0;m").css({width:o.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(m=0;m").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(j).appendTo(k);f&&g?j.width(g):f?(j.css("width","auto"),j.removeAttr("width"),j.width()").css("width",x(a)).appendTo(b||I.body),d=c[0].offsetWidth;c.remove();return d}function Gb(a,b){var c=Hb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("
    ").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Hb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;fd&&(d=c.length,e=f);return e}function x(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function V(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;ae?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return ce?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(j=0;jg?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,d=a.aoColumns,e=V(a),a=a.oLanguage.oAria,f=0,g=d.length;f/g, -"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0e?e+1:3));e=0;for(f=d.length;ee?e+1:3))}a.aLastSort=d}function Ib(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,$(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j= -d.length?[0,c[1]]:c)}));e.search!==k&&h.extend(a.oPreviousSearch,Bb(e.search));b=0;for(c=e.columns.length;b=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Na(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function ya(a,b){var c=[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=W(0,b):a<=d?(c=W(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=W(b-(c-2),b):(c=W(a-d+2,a+d-1),c.push("ellipsis"), -c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function db(a){h.each({num:function(b){return za(b,a)},"num-fmt":function(b){return za(b,a,Xa)},"html-num":function(b){return za(b,a,Aa)},"html-num-fmt":function(b){return za(b,a,Aa,Xa)}},function(b,c){v.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(v.type.search[b+a]=v.type.search.html)})}function Nb(a){return function(){var b=[xa(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this, -b)}}var m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new r(xa(this[v.iApiIndex])):new r(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1): -(""!==d.sX||""!==d.sY)&&ka(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a, -c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(), -[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return xa(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener= -function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=v.fnVersionCheck;var b=this,c=a===k,d=this.length;c&&(a={});this.oApi=this.internal=v.internal;for(var e in m.ext.internal)e&&(this[e]=Nb(e));this.each(function(){var e={},e=1t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&!o.renderer.header&&(o.renderer.header="jqueryui"): -o.renderer="jqueryui":h.extend(i,m.ext.classes,e.oClasses);q.addClass(i.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=e.iDisplayStart,o._iDisplayStart=e.iDisplayStart);null!==e.iDeferLoading&&(o.bDeferLoading=!0,g=h.isArray(e.iDeferLoading),o._iRecordsDisplay=g?e.iDeferLoading[0]:e.iDeferLoading,o._iRecordsTotal=g?e.iDeferLoading[1]:e.iDeferLoading);var r=o.oLanguage;h.extend(!0,r,e.oLanguage);""!==r.sUrl&&(h.ajax({dataType:"json",url:r.sUrl,success:function(a){Da(a);K(l.oLanguage,a);h.extend(true, -r,a);ga(o)},error:function(){ga(o)}}),n=!0);null===e.asStripeClasses&&(o.asStripeClasses=[i.sStripeOdd,i.sStripeEven]);var g=o.asStripeClasses,v=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return v.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),o.asDestroyStripes=g.slice());t=[];g=this.getElementsByTagName("thead");0!==g.length&&(da(o.aoHeader,g[0]),t=qa(o));if(null===e.aoColumns){p=[];g=0;for(j=t.length;g").appendTo(this));o.nTHead=j[0];j=q.children("tbody");0===j.length&&(j=h("
    a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:l.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.rnamespace||a.rnamespace.test(g.namespace))&&(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n(" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Webinar #02 - Mapping Addiction and Behavioral Traits and Getting at Causal Gene Variants with GeneNetwork

    +

    Friday, May 22nd. 2020 + 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT +

    +

    Goals of this webinar (QTL to gene variant):

    +
      +
    • Demonstrate mapping a quantitative trait using GeneNetwork (GN)
    • +
    • Explore GN tools to identify genes and genetics variants related to a QTL
    • +
    +

    Presented by:
    +Dr. Rob Williams
    +Professor and Chair
    +Department of Genetics, Genomics, and Informatics
    +University of Tennessee Health Science Center +

    Link to course material +

    Webinar #03 - Introduction to expression (e)QTL and their role in connecting QTL to genes and molecular networks

    +

    Friday, June 12, 2020 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT + +

    Goals of this webinar (QTL to gene/molecular networks):

    +
      +
    • Define eQTL
    • +
    • Examine the role of eQTL in the relationship of genes and molecular networks with phenotypic QTL
    • +
    • eQTL for co-expression networks
    • +
    +

    Presented by:
    +Dr. Laura Saba
    +Associate Professor
    +Department of Pharmaceutical Sciences
    +University of Colorado Anschutz Medical Campus +

    +Webinar flyer (pdf)
    +Link to course material
    +

    Webinar #04 - From Candidate Genes to Causal Variants—Strategies for and Examples of Identifying Genes and Sequence Variants in Rodent Populations

    +

    Friday, June 26, 2020 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT + +

    Goals of this webinar (candidate genes to causal variants):

    +
      +
    • To understand when it is practical or (just as often) not practical to try to "clone" the gene or nucleotide variant modulating trait variants +
    • +
    • To understand that defining the crucial causal nucleotide variant is usually a bonus and often not for the translational or even mechanistic utility of discoveries. +
    • +
    • To review new sequence-based methods to identify common and rare variants—the reduced complexity cross and epoch-effects in reference populations +
    • +
    +

    Presented by:
    +Dr. Rob Williams
    +Professor and Chair
    +Department of Genetics, Genomics, and Informatics
    +University of Tennessee Health Science Center +

    + +

    +Link to course material
    +Link to course material in powerpoint pptx: [P30_Webinar_on_QTGenes_26Jun2020v3.pptx]
    +

    Webinar #05 - Identifying genes from QTL using RNA expression and the PhenoGen website (http://phenogen.org)

    +

    Friday, August 28, 2020 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    +1-hour presentation followed by 30 minutes of discussion
    + +

    Goals of this webinar (candidate genes to causal variants): +

    Demonstrate how to use the PhenoGen website to identify transcripts:

    +
      +
    • Physically located within a QTL
    • +
    • Physically located within a QTL and expressed in brain
    • +
    • With a brain cis eQTL within the QTL
    • +
    • With any brain eQTL within the QTL
    • +
    • Within a co-expression network controlled from the same region as the QTL
    • +
    +

    Presented by:
    +Dr. Laura Saba
    +Associate Professor
    +Department of Pharmaceutical Sciences
    +University of Colorado Anschutz Medical Campus +

    +

    +Link to course material
    +

    Webinar #06 - Sex as a Biological Covariate in QTL Studies

    +

    Friday, September 11th, 2020 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    +1-hour presentation followed by 30 minutes of discussion
    + +

    Goals of this webinar (trait variance to QTL): + +

      +
    • Review QTL mapping
    • +
    • Understand the role of sex in QTL study design
    • +
    • Use sex as a covariate in QTL analysis
    • +
    • Understand X chromosome segregation in crosses
    • +
    • Make adjustments for X chromosome in QTL analysis
    • +
    +

    Presented by:
    +Dr. Saunak Sen
    +Professor and Chief of Biostatistics
    +Department of Preventative Medicine
    +University of Tennessee Health Science Center +

    Link to course material +

    Webinar #07 - Introduction to Weighted Gene Co-expression Network Analysis

    +

    Friday, September 25th at 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    +1-hour presentation followed by 30 minutes of discussion
    + +

    Goals of this webinar (molecular networks): +

      +
    • Introduction and motivation for co-expression network analysis
    • +
    • Basics of weighted gene co-expression network analysis
    • +
    • Step-by-step guide to WGCNA using the wgcna package in R.
    • +
    +

    Background reading available at: http://bit.ly/osga_wgcna

    +

    Presented by:
    +Dr. Laura Saba
    +Associate Professor
    +Department of Pharmaceutical Sciences
    +University of Colorado Anschutz Medical Campus +

    Link to course material +

    Webinar #08 - Using genetic and non-genetic covariates in QTL studies

    +

    Friday, October 9th at 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    +1-hour presentation followed by 30 minutes of discussion
    + +

    Goals of this webinar (quantitative trait to genetic loci): +

      +
    • Identify covariates and mediators in QTL studies
    • +
    • Adjust for covariates in QTL scans
    • +
    • Review genetic relatedness in segregating populations
    • +
    • Adjust for genetic relatedness using linear mixed models
    • +
    + +

    Presented by:
    +Dr. Saunak Sen
    +Professor and Chief of Biostatistics
    +Department of Preventative Medicine
    +University of Tennessee Health Science Center +

    Link to course material

    Webinar #09 - Introduction to GeneWeaver: Integrating and analyzing heterogeneous functional genomics data

    +

    Friday, October 23th at 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    +1-hour presentation followed by 30 minutes of discussion
    + +

    Goals of this webinar: +

      +
    • Compare a user's gene list with multiple functional genomics data sets
    • +
    • Compare and contrast gene lists with data currently available and integrated in GeneWeaver
    • +
    • Explore functional relationships among genes and disease across species
    • +
    + +

    Presented by:
    +Dr. Elissa Chesler
    +Professor The Jackson Laboratory +

    +

    +Dr. Erich Baker
    +Professor and Chair
    +Department of Computer Science
    +Baylor University +

    Link to course material

    +

    + + +

    Webinar #10 - Sketching alternate realities: An introduction to causal inference in genetic studies

    +

    Friday, November 20th at 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    + 1-hour presentation followed by 30 minutes of discussion
    + +

    Goals of this webinar: +

    Determination of cause is an important goal of biological studies, and genetic studies provide unique opportunities. In this introductory lecture we will frame causal inference as a missing data problem to clarify challenges, assumptions, and strategies necessary for assigning cause. We will survey the use of directed acyclic graphs (DAGs) to express causal information and to guide analytic strategies. +

      +
    • Express causal inference as a missing data problem (counterfactual framework)
    • +
    • Outline assumptions needed for causal inference
    • +
    • Express causal information as (directed acyclic) graphs
    • +
    • Outline how to use graphs to guide analytic strategy
    • +
    + +

    Presented by:
    +Dr. Saunak Sen
    +Professor and Chief of Biostatistics
    +Department of Preventative Medicine
    +University of Tennessee Health Science Center +

    Link to course material +

    + +
    +

    Webinar #11 - Beginner's guide to bulk RNA-Seq analysis

    +

    Friday, February 12th, 2021 at 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    + 1-hour presentation followed by 30 minutes of discussion
    + +

    Goals of this webinar: +

    The use of high throughput short read RNA sequencing has become common place in many scientific laboratories. The analysis tools for quantitating a transcriptome have matured becoming relatively simple to use. The goals of this webinar are: +

      +
    • To give a general overview of the popular Illumina technology for sequencing RNA.
    • +
    • To outline several of the key aspects to consider when designing an RNA-Seq study
    • +
    • To provide guidance on methods and tools for transforming reads to quantitative expression measurements.
    • +
    • To describe statistical models that are typically used for differential expression and why these specialized models are needed.
    • +
    + +

    Presented by:
    +Dr. Laura Saba
    +Associate Professor
    +Department of Pharmaceutical Sciences
    +University of Colorado Anschutz Medical Campus +

    Link to course material +

    +

    Webinar flyer (pdf)

    + +

    This webinar series is sponsored by the NIDA Center of Excellence in Omics, Systems Genetics, and the Addictome (P30 DA044223) and the NIAAA-funded PhenoGen Website (R24 AA013162).

    +

    Webinar #12 - From GWAS to gene: What are the essential analyses and how do we bring them together using heterogeneous stock rats?

    +

    Friday, February 26th at 10am PST/ 11am MST/ 12pm CST/ 1pm EST
    + 1-hour presentation followed by 30 minutes of discussion
    + +

    Goals of this webinar: +

    Heterogeneous stock (HS) rats are an outbred population that was created in 1984 by intercrossing 8 inbred strains. The Center for GWAS in Outbred Rats (www.ratgenes.org) has developed a suite of analysis tools for analyzing genome wide association studies (GWAS) in HS rats +

      +
    • Explain the HS rat population and their history
    • +
    • Describe the automated pipeline that performs GWAS in HS rats
    • +
    • Explore the fine mapping of associated regions and explain the various secondary analyses that we use to prioritize genes within associated intervals
    • +
    + +

    Presented by:
    +Abraham A. Palmer, Ph.D.
    +Professor & Vice Chair for Basic Research
    +Department of Psychiatry
    +University of California San Diego +

    +

    Webinar flyer (pdf)

    +

    Link to course material +

    Link to course material in pptx: Palmer_talk_2-26-21.pptx

    + +

    This webinar series is sponsored by the NIDA Center of Excellence in Omics, Systems Genetics, and the Addictome (P30 DA044223) and the NIDA-funded Center for GWAS in Outbred Rats (P50 DA037844).

    +
    +

    Webinar #13 - Become a UseR: A brief tour of R

    +

    Friday, March 12th at 10am PST/ 11am MST/ 12pm CST/ 1pm EST
    + 1-hour presentation followed by 30 minutes of discussion
    +

    We will introduce R programming language and outline the benefits of learning R. We will give a brief tour of basic concepts and tasks: variables, objects, functions, basic statistics, visualization, and data import/export. We will showcase a practical example demonstrating statistical analysis. + +

    Goals of this webinar: +

      +
    • Why should one use/learn R?
    • +
    • How to install R/Rstudio
    • +
    • Learn about R basics: variables, programming, functions
    • +
    • Learn about the R package ecosystem that extends its capabilities
    • +
    • See a basic statistical analysis example
    • +
    • Learn about additional resources
    • +
    + +

    Presented by:
    +Gregory Farage, PhD and Saunak Sen, PhD
    +Department of Preventive Medicine
    +University of Tennessee Health Science Center +

    Link to course material +

    +

    Webinar flyer (pdf)

    + + +
    +

    Webinar #14 - Landing on Jupyter: A guided tour of interactive notebooks

    +

    Friday, March 26th at 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    + 1-hour presentation followed by 30 minutes of discussion
    +

    Jupyter is an interactive interface to data science and scientific computing across a variety of programming languages. We will present the Jupyter notebook, and explain some key concepts (e.g., kernel, cells). We will show how to create a new notebook; modify an existing notebook; save, export, and publish a notebook. We will discuss several possible use cases: developing code, writing reports, taking notes, and teaching/presenting. + +

    Goals of this webinar: +

      +
    • Learn what Jupyter notebooks are
    • +
    • Learn how to install, configure, and use Jupyter notebooks
    • +
    • Learn how to use Jupyter notebooks for research, teaching, or code + development
    • +
    + +

    Presented by:
    +Gregory Farage, PhD and Saunak Sen, PhD
    +Department of Preventive Medicine
    +University of Tennessee Health Science Center +

    Link to course material +

    +

    Webinar flyer (pdf)

    + + +
    +

    Webinar #15 – Introduction to Metabolomics Platforms and Data Analysis

    +

    Friday, April 9th at 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    + 1-hour presentation followed by 30 minutes of discussion
    +

    Goals of this webinar: +

    The use of metabolomics to profile small molecules is now widespread in biomedical research. The goals of this webinar are: +

      +
    • To describe research questions that can be addressed using metabolomics
    • +
    • To give a general overview of metabolomics technologies
    • +
    • To outline steps in a metabolomics data analysis pipeline
    • +
    • To provide information on common resources and databases
    • +
    + +

    Presented by:
    +Katerina Kechris, PhD
    +Professor
    +Department of Biostatistics and Informatics
    +Colorado School of Public Health
    +University of Colorado Anschutz Medical Campus +

    Link to course material +

    +

    Webinar flyer (pdf)

    + +

    Webinar #16 – Introduction to the Hybrid Rat Diversity Panel: A renewable rat panel for genetic studies of addiction-related traits

    +

    Friday, April 23rd at 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    + 1-hour presentation followed by 30 minutes of discussion
    +

    Goals of this webinar: +

    The Hybrid Rat Diversity Panel (HRDP) is an inbred panel of rats that included two recombinant inbred panels and a panel of classic inbred strains. +

      +
    • To describe hybrid diversity panels, in particular the HRDP, including advantages and disadvantages when studying the role of genetics is substance use disorders, e.g., renewable genomes and the accumulation of behavioral and physiological phenotypes and high throughput omics data.
    • +
    • To outline current resources and resources that are being generated.
    • +
    • To demonstrate the utility of a renewable genetically diverse rodent population when exploring the interaction between genetics, drug exposure, and behavior.
    • +
    + +

    Presented by:
    + Hao Chen, PhD
    +Associate Professor
    +Department of Pharmacology, Addiction Science, and Toxicology
    +University of Tennessee Health Science Center +

    +Dr. Laura Saba
    +Associate Professor
    +Department of Pharmaceutical Sciences
    +University of Colorado Anschutz Medical Campus +

    Link to course material +

    +

    Webinar flyer (pdf)

    + +

    Webinar #17 – Identifying sample mix-ups in eQTL data

    +

    Friday, June 11th at 10am PDT/ 11am MDT/ 12pm CDT/ 1pm EDT
    + 1-hour presentation followed by 30 minutes of discussion
    +

    Goals of this webinar: +

    Sample mix-ups interfere with our ability to detect genotype-phenotype associations. However, the presence of numerous eQTL with strong effects provides the opportunity to not just identify sample mix-ups, but also to correct them. +

      +
    • To illustrate methods for identifying sample duplicates and errors in sex annotations.
    • +
    • To illustrate methods for identifying sample mix-ups in DNA and RNA samples from experimental cross data.
    • +
    + +

    Presented by:
    + Karl Broman, PhD
    +Professor
    +Department of Biostatistics & Medical Informatics
    +University of Wisconsin-Madison +

    + +

    Webinar flyer (pdf)

    +

    Link to course material:kbroman.org/Talk_OSGA2021

    + + +

    Bonus 1 - Data structure, disease risk, GXE, and causal modeling

    +

    Friday, November 20th at 9am PDT/ 11pm CDT/ 12pm EDT
    + 1-hour presentation followed by 30 minutes of discussion
    + +

    Human disease is mainly due to complex interactions between genetic and environmental factors (GXE). We need to acquire the right "smart" data types—coherent and multiplicative data—required to make accurate predictions about risk and outcome for n = 1 individuals—a daunting task. We have developed large families of fully sequenced mice that mirror the genetic complexity of humans. We are using these Reference Populations to generate multiplicatively useful data and to build and test causal quantitative models of disease mechanisms with a special focus on diseases of aging, addiction, and neurological and psychiatric disease. + +

    Speaker Bio: Robert (Rob) W. Williams received a BA in neuroscience from UC Santa Cruz (1975) and a Ph.D. in system physiology at UC Davis with Leo M. Chalupa (1983). He did postdoctoral work in developmental neurobiology at Yale School of Medicine with Pasko Rakic where he developed novel stereological methods to estimate cell populations in brain. In 2013 Williams established the Department of Genetics, Genomics and Informatics at UTHSC. He holds the UT Oak Ridge National Laboratory Governor’s Chair in Computational Genomics. Williams is director of the Complex Trait Community (www.complextrait.org) and editor-in-chief of Frontiers in Neurogenomics. One of Williams’ more notable contributions is in the field of systems neurogenetics and experimental precision medicine. He and his research collaborators have built GeneNetwork (www.genenetwork.org), an online resource of data and analysis code that is used as a platform for experimental precision medicine.

    + +

    Presented by:
    +Dr. Rob Williams
    +Professor and Chair
    +Department of Genetics, Genomics, and Informatics
    +University of Tennessee Health Science Center +

    + + +
    +
    +
    + + + + + + + + + + + {% endblock %} -- cgit v1.2.3 From 43af3d7bde2b34ecc8bec02b14b320610c399b61 Mon Sep 17 00:00:00 2001 From: Arthur Centeno Date: Tue, 15 Jun 2021 16:15:15 +0000 Subject: Webinars have been added to tutorials.html --- wqflask/wqflask/templates/tutorials.html | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/wqflask/wqflask/templates/tutorials.html b/wqflask/wqflask/templates/tutorials.html index 18f8d675..89143809 100644 --- a/wqflask/wqflask/templates/tutorials.html +++ b/wqflask/wqflask/templates/tutorials.html @@ -528,6 +528,30 @@ Registration: https://bit.ly/osga_2020- + + + + +

    Bonus 2 - Introduction to Gene Network

    +

    Please note that this tutorial is based on GeneNetwork v1 + +

    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. GeneNetwork combines more than 25 years of legacy data generated by hundreds of scientists together with sequence data (SNPs) and massive transcriptome data sets (expression genetic or eQTL data sets). The quantitative trait locus (QTL) mapping module that is built into GN is optimized for fast on-line analysis of traits that are controlled by combinations of gene variants and environmental factors. GeneNetwork can be used to study humans, mice (BXD, AXB, LXS, etc.), rats (HXB), Drosophila, and plant species (barley and Arabidopsis). Most of these population data sets are linked with dense genetic maps (genotypes) that can be used to locate the genetic modifiers that cause differences in expression and phenotypes, including disease susceptibility. + +

    Users are welcome to enter their own private data directly into GeneNetwork to exploit the full range of analytic tools and to map modulators in a powerful environment. This combination of data and fast analytic functions enable users to study relations between sequence variants, molecular networks, and function.

    + +

    Presented by:
    +Dr. Rob Williams
    +Professor and Chair
    +Department of Genetics, Genomics, and Informatics
    +University of Tennessee Health Science Center +

    + + + + + + @@ -562,5 +586,6 @@ $('#myTable').DataTable(); + {% endblock %} -- cgit v1.2.3 From f4ad7bc3ebfa9be6ff98c9f935a9e8576974ddd6 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 15 Jun 2021 17:51:15 +0000 Subject: Fix issue where column values were used as str instead of bytes; pretty sure this was introduced by Python3 --- wqflask/utility/redis_tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py index 96a4be12..ff125bd2 100644 --- a/wqflask/utility/redis_tools.py +++ b/wqflask/utility/redis_tools.py @@ -26,6 +26,8 @@ def is_redis_available(): def load_json_from_redis(item_list, column_value): + if type(column_value) == str: + column_value = str.encode(column_value) return json.loads(item_list[column_value]) -- cgit v1.2.3 From d31f3f763471b19559ca74e73b52b3cb5e7153ce Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 15 Jun 2021 18:00:54 +0000 Subject: Commented out rpy2 code and import from show_corr_results.py --- wqflask/wqflask/correlation/show_corr_results.py | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/wqflask/wqflask/correlation/show_corr_results.py b/wqflask/wqflask/correlation/show_corr_results.py index 10e0d626..2f3df67a 100644 --- a/wqflask/wqflask/correlation/show_corr_results.py +++ b/wqflask/wqflask/correlation/show_corr_results.py @@ -22,7 +22,7 @@ import collections import json import scipy import numpy -import rpy2.robjects as ro # R Objects +# import rpy2.robjects as ro # R Objects import utility.logger import utility.webqtlUtil @@ -459,10 +459,10 @@ class CorrelationResults: if num_overlap > 5: # ZS: 2015 could add biweight correlation, see http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3465711/ - if self.corr_method == 'bicor': - sample_r, sample_p = do_bicor( - self.this_trait_vals, target_vals) - elif self.corr_method == 'pearson': + # if self.corr_method == 'bicor': + # sample_r, sample_p = do_bicor( + # self.this_trait_vals, target_vals) + if self.corr_method == 'pearson': sample_r, sample_p = scipy.stats.pearsonr( self.this_trait_vals, target_vals) else: @@ -487,22 +487,22 @@ class CorrelationResults: self.sample_data[str(sample)] = float(value) -def do_bicor(this_trait_vals, target_trait_vals): - r_library = ro.r["library"] # Map the library function - r_options = ro.r["options"] # Map the options function +# def do_bicor(this_trait_vals, target_trait_vals): +# r_library = ro.r["library"] # Map the library function +# r_options = ro.r["options"] # Map the options function - r_library("WGCNA") - r_bicor = ro.r["bicorAndPvalue"] # Map the bicorAndPvalue function +# r_library("WGCNA") +# r_bicor = ro.r["bicorAndPvalue"] # Map the bicorAndPvalue function - r_options(stringsAsFactors=False) +# r_options(stringsAsFactors=False) - this_vals = ro.Vector(this_trait_vals) - target_vals = ro.Vector(target_trait_vals) +# this_vals = ro.Vector(this_trait_vals) +# target_vals = ro.Vector(target_trait_vals) - the_r, the_p, _fisher_transform, _the_t, _n_obs = [ - numpy.asarray(x) for x in r_bicor(x=this_vals, y=target_vals)] +# the_r, the_p, _fisher_transform, _the_t, _n_obs = [ +# numpy.asarray(x) for x in r_bicor(x=this_vals, y=target_vals)] - return the_r, the_p +# return the_r, the_p def generate_corr_json(corr_results, this_trait, dataset, target_dataset, for_api=False): -- cgit v1.2.3 From c458bf0ad731e5e5fd9cbd0686936b3a441bae63 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 15 Jun 2021 18:01:41 +0000 Subject: Commented out WGCNA and CTL Analysis buttons from tool_buttons.html since they use rpy2 --- wqflask/wqflask/templates/tool_buttons.html | 70 ++++++++++++++--------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/wqflask/wqflask/templates/tool_buttons.html b/wqflask/wqflask/templates/tool_buttons.html index 5b2dbf3d..3f9d8211 100644 --- a/wqflask/wqflask/templates/tool_buttons.html +++ b/wqflask/wqflask/templates/tool_buttons.html @@ -1,35 +1,35 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + -- cgit v1.2.3 From afee4d625248565857df98d3510f680ae6204864 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 15 Jun 2021 18:02:18 +0000 Subject: Commented out WGCNA/CTL imports and endpoints since they import rpy2 --- wqflask/wqflask/views.py | 52 ++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 0714bd20..f96e5034 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -69,8 +69,8 @@ from wqflask.correlation import show_corr_results from wqflask.correlation.correlation_gn3_api import compute_correlation from wqflask.correlation_matrix import show_corr_matrix from wqflask.correlation import corr_scatter_plot -from wqflask.wgcna import wgcna_analysis -from wqflask.ctl import ctl_analysis +# from wqflask.wgcna import wgcna_analysis +# from wqflask.ctl import ctl_analysis from wqflask.snp_browser import snp_browser from wqflask.search_results import SearchResultPage from wqflask.export_traits import export_search_results_csv @@ -357,18 +357,18 @@ def wcgna_setup(): return render_template("wgcna_setup.html", **request.form) -@app.route("/wgcna_results", methods=('POST',)) -def wcgna_results(): - logger.info("In wgcna, request.form is:", request.form) - logger.info(request.url) - # Start R, load the package and pointers and create the analysis - wgcna = wgcna_analysis.WGCNA() - # Start the analysis, a wgcnaA object should be a separate long running thread - wgcnaA = wgcna.run_analysis(request.form) - # After the analysis is finished store the result - result = wgcna.process_results(wgcnaA) - # Display them using the template - return render_template("wgcna_results.html", **result) +# @app.route("/wgcna_results", methods=('POST',)) +# def wcgna_results(): +# logger.info("In wgcna, request.form is:", request.form) +# logger.info(request.url) +# # Start R, load the package and pointers and create the analysis +# wgcna = wgcna_analysis.WGCNA() +# # Start the analysis, a wgcnaA object should be a separate long running thread +# wgcnaA = wgcna.run_analysis(request.form) +# # After the analysis is finished store the result +# result = wgcna.process_results(wgcnaA) +# # Display them using the template +# return render_template("wgcna_results.html", **result) @app.route("/ctl_setup", methods=('POST',)) @@ -380,18 +380,18 @@ def ctl_setup(): return render_template("ctl_setup.html", **request.form) -@app.route("/ctl_results", methods=('POST',)) -def ctl_results(): - logger.info("In ctl, request.form is:", request.form) - logger.info(request.url) - # Start R, load the package and pointers and create the analysis - ctl = ctl_analysis.CTL() - # Start the analysis, a ctlA object should be a separate long running thread - ctlA = ctl.run_analysis(request.form) - # After the analysis is finished store the result - result = ctl.process_results(ctlA) - # Display them using the template - return render_template("ctl_results.html", **result) +# @app.route("/ctl_results", methods=('POST',)) +# def ctl_results(): +# logger.info("In ctl, request.form is:", request.form) +# logger.info(request.url) +# # Start R, load the package and pointers and create the analysis +# ctl = ctl_analysis.CTL() +# # Start the analysis, a ctlA object should be a separate long running thread +# ctlA = ctl.run_analysis(request.form) +# # After the analysis is finished store the result +# result = ctl.process_results(ctlA) +# # Display them using the template +# return render_template("ctl_results.html", **result) @app.route("/news") -- cgit v1.2.3 From 1baf5f7611909c651483208184c5fbf7d4a7a088 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 15 Jun 2021 18:04:28 +0000 Subject: Commented out Biweight Midcorrelation option on trait page, since it uses rpy2 and is currently disabled --- wqflask/wqflask/templates/show_trait_calculate_correlations.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/templates/show_trait_calculate_correlations.html b/wqflask/wqflask/templates/show_trait_calculate_correlations.html index ef784c84..e623a968 100644 --- a/wqflask/wqflask/templates/show_trait_calculate_correlations.html +++ b/wqflask/wqflask/templates/show_trait_calculate_correlations.html @@ -70,7 +70,7 @@
    @@ -157,4 +157,4 @@
    - \ No newline at end of file + -- cgit v1.2.3 From 04a869d708bafe805441d70ff3c588e1e244d439 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 15 Jun 2021 20:49:39 +0000 Subject: Use markdown from Github for News --- wqflask/wqflask/__init__.py | 3 ++- wqflask/wqflask/markdown_routes.py | 8 ++++++++ wqflask/wqflask/templates/news.html | 33 ++++++++++++++++++--------------- wqflask/wqflask/views.py | 6 ------ 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/wqflask/wqflask/__init__.py b/wqflask/wqflask/__init__.py index 2e94dd01..8b2055cd 100644 --- a/wqflask/wqflask/__init__.py +++ b/wqflask/wqflask/__init__.py @@ -15,6 +15,7 @@ from wqflask.markdown_routes import policies_blueprint from wqflask.markdown_routes import environments_blueprint from wqflask.markdown_routes import facilities_blueprint from wqflask.markdown_routes import blogs_blueprint +from wqflask.markdown_routes import news_blueprint app = Flask(__name__) @@ -52,6 +53,7 @@ app.register_blueprint(policies_blueprint, url_prefix="/policies") app.register_blueprint(environments_blueprint, url_prefix="/environments") app.register_blueprint(facilities_blueprint, url_prefix="/facilities") app.register_blueprint(blogs_blueprint, url_prefix="/blogs") +app.register_blueprint(news_blueprint, url_prefix="/news") @app.before_request @@ -68,7 +70,6 @@ from wqflask import export_traits from wqflask import gsearch from wqflask import update_search_results from wqflask import docs -from wqflask import news from wqflask import db_info from wqflask import user_login from wqflask import user_session diff --git a/wqflask/wqflask/markdown_routes.py b/wqflask/wqflask/markdown_routes.py index b81bfb55..580b9ac0 100644 --- a/wqflask/wqflask/markdown_routes.py +++ b/wqflask/wqflask/markdown_routes.py @@ -23,6 +23,7 @@ environments_blueprint = Blueprint("environments_blueprint", __name__) links_blueprint = Blueprint("links_blueprint", __name__) policies_blueprint = Blueprint("policies_blueprint", __name__) facilities_blueprint = Blueprint("facilities_blueprint", __name__) +news_blueprint = Blueprint("news_blueprint", __name__) blogs_blueprint = Blueprint("blogs_blueprint", __name__) @@ -109,6 +110,13 @@ def references(): rendered_markdown=render_markdown("general/references/references.md")), 200 +@news_blueprint.route('/') +def news(): + return render_template( + "news.html", + rendered_markdown=render_markdown("general/news/news.md")), 200 + + @environments_blueprint.route("/") def environments(): diff --git a/wqflask/wqflask/templates/news.html b/wqflask/wqflask/templates/news.html index 1ec302e6..a615564b 100644 --- a/wqflask/wqflask/templates/news.html +++ b/wqflask/wqflask/templates/news.html @@ -1,21 +1,24 @@ {% extends "base.html" %} -{% block title %}{{title}}{% endblock %} +{% block title %}News{% endblock %} + +{% block css %} + +{% endblock %} {% block content %} -
    -

    {{title}}

    - - - {% for newsitem in newslist %} - - - - - {% endfor %} - -
    - {{newsitem.date}} - {{newsitem.details|safe}}
    + +
    +
    + {{ rendered_markdown|safe }} + +
    + {% endblock %} diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index f96e5034..b9181368 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -394,12 +394,6 @@ def ctl_setup(): # return render_template("ctl_results.html", **result) -@app.route("/news") -def news(): - doc = Docs("news", request.args) - return render_template("docs.html", **doc.__dict__) - - @app.route("/intro") def intro(): doc = Docs("intro", request.args) -- cgit v1.2.3 From d723eb260bfd9c707f6e282037c4e42670bdf48e Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 16 Jun 2021 21:15:27 +0000 Subject: Changed views.py to call the correlation code using the GN3 API + a function in show_corr_results for setting other template vars (might change this later) --- wqflask/wqflask/views.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index b9181368..96f228af 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -65,7 +65,7 @@ from wqflask.comparison_bar_chart import comparison_bar_chart from wqflask.marker_regression import run_mapping from wqflask.marker_regression import display_mapping_results from wqflask.network_graph import network_graph -from wqflask.correlation import show_corr_results +from wqflask.correlation.show_corr_results import set_other_template_vars from wqflask.correlation.correlation_gn3_api import compute_correlation from wqflask.correlation_matrix import show_corr_matrix from wqflask.correlation import corr_scatter_plot @@ -1082,15 +1082,9 @@ def network_graph_page(): @app.route("/corr_compute", methods=('POST',)) def corr_compute_page(): - logger.info("In corr_compute, request.form is:", pf(request.form)) - logger.info(request.url) - template_vars = show_corr_results.CorrelationResults(request.form) - return render_template("correlation_page.html", **template_vars.__dict__) - - # to test/disable the new correlation api uncomment these lines - - # correlation_results = compute_correlation(request.form) - # return render_template("test_correlation_page.html", correlation_results=correlation_results) + correlation_results = compute_correlation(request.form, compute_all=True) + correlation_results = set_other_template_vars(request.form, correlation_results) + return render_template("correlation_page.html", **correlation_results) @app.route("/test_corr_compute", methods=["POST"]) -- cgit v1.2.3 From c327905af1ef1a0c66a453127ee8f5ba4a824573 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 16 Jun 2021 21:16:09 +0000 Subject: Changed correlation page template to account for differences in the output returned from the GN3 API correlation code --- wqflask/wqflask/templates/correlation_page.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/wqflask/wqflask/templates/correlation_page.html b/wqflask/wqflask/templates/correlation_page.html index 4cad2749..f66eb4bd 100644 --- a/wqflask/wqflask/templates/correlation_page.html +++ b/wqflask/wqflask/templates/correlation_page.html @@ -17,9 +17,9 @@
    -

    Values of record {{ this_trait.name }} in the {{ dataset.fullname }} +

    Values of record {{ this_trait.name }} in the {{ this_dataset.fullname }} dataset were compared to all records in the {{ target_dataset.fullname }} - dataset. The top {{ return_number }} correlations ranked by the {{ formatted_corr_type }} are displayed. + dataset. The top {{ return_results }} correlations ranked by the {{ formatted_corr_type }} are displayed. You can resort this list by clicking the headers. Select the Record ID to open the trait data and analysis page.

    @@ -30,7 +30,7 @@ {% include 'tool_buttons.html' %} @@ -43,7 +43,7 @@ - + @@ -146,7 +146,7 @@ - + + -- cgit v1.2.3 From 2d9220c3d88f5f818ae81e3cafdc69288c9cfbd8 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 4 May 2021 19:24:15 +0000 Subject: Changed recheck_rows to include the DataTable as input in preparation for moving it into a separate JS file that can be imported by all table templates --- wqflask/wqflask/templates/search_result_page.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index eac3ec61..279a1886 100644 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -391,9 +391,9 @@ }{% endif %} ]; - recheck_rows = function(checked_rows){ + recheck_rows = function(the_table, checked_rows){ //ZS: This is meant to recheck checkboxes after columns are resized - check_cells = trait_table.column(0).nodes().to$(); + check_cells = the_table.column(0).nodes().to$(); for (let i = 0; i < check_cells.length; i++) { if (checked_rows.includes(i)){ check_cells[i].childNodes[0].checked = true; @@ -523,7 +523,7 @@ let checked_rows = get_checked_rows(); trait_table = $('#' + tableId).DataTable(table_settings); if (checked_rows.length > 0){ - recheck_rows(checked_rows); + recheck_rows(trait_table, checked_rows); } if (first_run){ -- cgit v1.2.3 From 304128b1231df425709fbe3b128bd0c5eda4139b Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 4 May 2021 19:29:01 +0000 Subject: Include table ID as input for get_checked_rows --- wqflask/wqflask/templates/search_result_page.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index 279a1886..adc8c264 100644 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -408,9 +408,9 @@ } } - get_checked_rows = function(trait_table){ + get_checked_rows = function(table_id){ let checked_rows = [] - $("#trait_table input").each(function(index){ + $("#" + table_id + " input").each(function(index){ if ($(this).prop("checked") == true){ checked_rows.push(index); } @@ -520,7 +520,7 @@ $('#table_container').css("width", String($('#trait_table').width() + width_change {% if trait_list|length > 20 %}+ 17{% endif %}) + "px"); //ZS : Change the container width by the change in width of the adjusted column, so the overall table size adjusts properly } - let checked_rows = get_checked_rows(); + let checked_rows = get_checked_rows(tableId); trait_table = $('#' + tableId).DataTable(table_settings); if (checked_rows.length > 0){ recheck_rows(trait_table, checked_rows); -- cgit v1.2.3 From 16338316a7529265aaa94d541698c8ed8cc97aba Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 4 May 2021 19:35:31 +0000 Subject: Include table/table_id as parameters in setUseerColumnsDefWidths and saveColumnSettings --- wqflask/wqflask/templates/search_result_page.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index adc8c264..a351256b 100644 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -424,7 +424,7 @@ function loadDataTable(first_run=false){ if (!first_run){ - setUserColumnsDefWidths(); + setUserColumnsDefWidths(tableId); } //ZS: Need to make sort by symbol, also need to make sure blank symbol fields at the bottom and symbols starting with numbers below letters @@ -508,7 +508,7 @@ width_change = ui.size.width - ui.originalSize.width; }, stop: function () { - saveColumnSettings(); + saveColumnSettings(tableId, trait_table); loadDataTable(); } }); @@ -541,11 +541,11 @@ trait_table.columns.adjust(); }); - function setUserColumnsDefWidths() { + function setUserColumnsDefWidths(table_id) { var userColumnDef; // Get the settings for this table from localStorage - var userColumnDefs = JSON.parse(localStorage.getItem(tableId)) || []; + var userColumnDefs = JSON.parse(localStorage.getItem(table_id)) || []; if (userColumnDefs.length === 0 ) return; @@ -575,8 +575,8 @@ }); } - function saveColumnSettings() { - var userColumnDefs = JSON.parse(localStorage.getItem(tableId)) || []; + function saveColumnSettings(table_id, trait_table) { + var userColumnDefs = JSON.parse(localStorage.getItem(table_id)) || []; var width, header, existingSetting; -- cgit v1.2.3 From fe224fb35f926373ff75c9a19217a4602ca3ea99 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 4 May 2021 19:40:09 +0000 Subject: Moved several table functions out of search_results.html and into new file table_functions.js --- .../static/new/javascript/table_functions.js | 93 +++++++++++++++++++++ wqflask/wqflask/templates/search_result_page.html | 96 +--------------------- 2 files changed, 94 insertions(+), 95 deletions(-) create mode 100644 wqflask/wqflask/static/new/javascript/table_functions.js diff --git a/wqflask/wqflask/static/new/javascript/table_functions.js b/wqflask/wqflask/static/new/javascript/table_functions.js new file mode 100644 index 00000000..e24b3e03 --- /dev/null +++ b/wqflask/wqflask/static/new/javascript/table_functions.js @@ -0,0 +1,93 @@ +recheck_rows = function(the_table, checked_rows){ + //ZS: This is meant to recheck checkboxes after columns are resized + check_cells = the_table.column(0).nodes().to$(); + for (let i = 0; i < check_cells.length; i++) { + if (checked_rows.includes(i)){ + check_cells[i].childNodes[0].checked = true; + } + } + + check_rows = trait_table.rows().nodes(); + for (let i =0; i < check_rows.length; i++) { + if (checked_rows.includes(i)){ + check_rows[i].classList.add("selected") + } + } + } + +get_checked_rows = function(table_id){ + let checked_rows = [] + $("#" + table_id + " input").each(function(index){ + if ($(this).prop("checked") == true){ + checked_rows.push(index); + } + }); + + return checked_rows +} + +function setUserColumnsDefWidths(table_id) { + var userColumnDef; + + // Get the settings for this table from localStorage + var userColumnDefs = JSON.parse(localStorage.getItem(table_id)) || []; + + if (userColumnDefs.length === 0 ) return; + + columnDefs.forEach( function(columnDef) { + + // Check if there is a width specified for this column + userColumnDef = userColumnDefs.find( function(column) { + return column.targets === columnDef.targets; + }); + + // If there is, set the width of this columnDef in px + if ( userColumnDef ) { + + columnDef.sWidth = userColumnDef.width + 'px'; + columnDef.width = userColumnDef.width + 'px'; + + $('.toggle-vis').each(function(){ + if ($(this).attr('data-column') == columnDef.targets){ + if ($(this).hasClass("active")){ + columnDef.bVisible = false + } else { + columnDef.bVisible = true + } + } + }) + } + }); +} + +function saveColumnSettings(table_id, trait_table) { +var userColumnDefs = JSON.parse(localStorage.getItem(table_id)) || []; + +var width, header, existingSetting; + +trait_table.columns().every( function ( targets ) { + + // Check if there is a setting for this column in localStorage + existingSetting = userColumnDefs.findIndex( function(column) { return column.targets === targets;}); + + // Get the width of this column + header = this.header(); + width = $(header).width(); + + if ( existingSetting !== -1 ) { + // Update the width + userColumnDefs[existingSetting].width = width; + } else { + + // Add the width for this column + userColumnDefs.push({ + targets: targets, + width: width, + }); + + } +}); + +// Save (or update) the settings in localStorage +localStorage.setItem(table_id, JSON.stringify(userColumnDefs)); +} \ No newline at end of file diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index a351256b..436eb639 100644 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -154,6 +154,7 @@ + - + - + + + @@ -409,13 +411,12 @@ "info": "Showing from _START_ to _END_ of " + js_data.total_markers + " records", }, "order": [[1, "asc" ]], - "sDom": "iRZtir", - "iDisplayLength": -1, - "autoWidth": false, - "deferRender": true, + "sDom": "itir", + "autoWidth": true, "bSortClasses": false, - "scrollCollapse": false, - "paging": false + "scrollY": "100vh", + "scroller": true, + "scrollCollapse": true } ); {% elif selectedChr != -1 and plotScale =="physic" and (dataset.group.species == 'mouse' or dataset.group.species == 'rat') %} $('#trait_table').dataTable( { -- cgit v1.2.3 From 116f911561dc81565dc0f77c12e901c0d53de4e5 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 5 Jan 2021 16:10:45 -0600 Subject: Changed limit from 2000 markers to 10000 markers for the number to show in mapping results table --- wqflask/wqflask/marker_regression/run_mapping.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index c5b980a7..f601201b 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -673,9 +673,9 @@ def trim_markers_for_table(markers): sorted_markers = sorted( markers, key=lambda k: k['lrs_value'], reverse=True) - # ZS: So we end up with a list of just 2000 markers - if len(sorted_markers) >= 2000: - trimmed_sorted_markers = sorted_markers[:2000] + #ZS: So we end up with a list of just 2000 markers + if len(sorted_markers) >= 10000: + trimmed_sorted_markers = sorted_markers[:10000] return trimmed_sorted_markers else: return sorted_markers -- cgit v1.2.3 From 53ee6cd16a3791fd1cb32f758f6799d87c10f4c0 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 21 Jun 2021 20:27:44 +0000 Subject: Fixed some indentation of JS --- wqflask/wqflask/templates/search_result_page.html | 170 +++++++++++----------- 1 file changed, 84 insertions(+), 86 deletions(-) diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index b1a19f40..b2690747 100644 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -5,9 +5,9 @@ + - {% endblock %} {% block content %} @@ -144,7 +144,6 @@ {% block js %} - @@ -402,90 +401,89 @@ //ZS: Need to make sort by symbol, also need to make sure blank symbol fields at the bottom and symbols starting with numbers below letters table_settings = { - 'drawCallback': function( settings ) { - $('#' + tableId + ' tr').off().on("click", function(event) { - if (event.target.type !== 'checkbox' && event.target.tagName.toLowerCase() !== 'a') { - var obj =$(this).find('input'); - obj.prop('checked', !obj.is(':checked')); - } - if ($(this).hasClass("selected") && event.target.tagName.toLowerCase() !== 'a'){ - $(this).removeClass("selected") - } else if (event.target.tagName.toLowerCase() !== 'a') { - $(this).addClass("selected") - } - change_buttons() - }); - }, - 'createdRow': function ( row, data, index ) { - $('td', row).eq(0).attr("style", "text-align: center; padding: 0px 10px 2px 10px;"); - $('td', row).eq(1).attr("align", "right"); - $('td', row).eq(1).attr('data-export', index+1); - $('td', row).eq(2).attr('data-export', $('td', row).eq(2).text()); - {% if dataset.type == 'ProbeSet' %} - $('td', row).eq(3).attr('title', $('td', row).eq(3).text()); - $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); - if ($('td', row).eq(3).text().length > 20) { - $('td', row).eq(3).text($('td', row).eq(3).text().substring(0, 20)); - $('td', row).eq(3).text($('td', row).eq(3).text() + '...') - } - $('td', row).eq(4).attr('title', $('td', row).eq(4).text()); - $('td', row).eq(4).attr('data-export', $('td', row).eq(4).text()); - $('td', row).slice(5,10).attr("align", "right"); - $('td', row).eq(5).attr('data-export', $('td', row).eq(5).text()); - $('td', row).eq(6).attr('data-export', $('td', row).eq(6).text()); - $('td', row).eq(7).attr('data-export', $('td', row).eq(7).text()); - $('td', row).eq(8).attr('data-export', $('td', row).eq(8).text()); - $('td', row).eq(9).attr('data-export', $('td', row).eq(9).text()); - {% elif dataset.type == 'Publish' %} - $('td', row).eq(3).attr('title', $('td', row).eq(3).text()); - $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); - $('td', row).eq(4).attr('title', $('td', row).eq(4).text()); - $('td', row).eq(4).attr('data-export', $('td', row).eq(4).text()); - $('td', row).eq(4).attr('align', 'right'); - $('td', row).slice(6,10).attr("align", "right"); - $('td', row).eq(5).attr('data-export', $('td', row).eq(5).text()); - $('td', row).eq(6).attr('data-export', $('td', row).eq(6).text()); - $('td', row).eq(7).attr('data-export', $('td', row).eq(7).text()); - $('td', row).eq(8).attr('data-export', $('td', row).eq(8).text()); - $('td', row).eq(9).attr('data-export', $('td', row).eq(8).text()); - {% elif dataset.type == 'Geno' %} - $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); - {% endif %} - }, - "data": trait_list, - "columns": columnDefs, - "order": [[1, "asc" ]], - "sDom": "iti", - "destroy": true, - {% if wide_columns_exist != true %} - "autoWidth": true, - {% else %} - "autoWidth": false, - {% endif %} - "deferRender": true, - "bSortClasses": false, - "scrollX": true, - {% if trait_list|length > 20 %} - "scrollY": "500px", - "scroller": true, - "scrollCollapse": true, - {% else %} - "iDisplayLength": -1, - {% endif %} - "initComplete": function (settings) { - //Add JQueryUI resizable functionality to each th in the ScrollHead table - $('#' + tableId + '_wrapper .dataTables_scrollHead thead th').resizable({ - handles: "e", - alsoResize: '#' + tableId + '_wrapper .dataTables_scrollHead table', //Not essential but makes the resizing smoother - resize: function( event, ui ) { - width_change = ui.size.width - ui.originalSize.width; - }, - stop: function () { - saveColumnSettings(tableId, trait_table); - loadDataTable(); - } - }); - }, + 'drawCallback': function( settings ) { + $('#' + tableId + ' tr').off().on("click", function(event) { + if (event.target.type !== 'checkbox' && event.target.tagName.toLowerCase() !== 'a') { + var obj =$(this).find('input'); + obj.prop('checked', !obj.is(':checked')); + } + if ($(this).hasClass("selected") && event.target.tagName.toLowerCase() !== 'a'){ + $(this).removeClass("selected") + } else if (event.target.tagName.toLowerCase() !== 'a') { + $(this).addClass("selected") + } + change_buttons() + }); + }, + 'createdRow': function ( row, data, index ) { + $('td', row).eq(0).attr("style", "text-align: center; padding: 0px 10px 2px 10px;"); + $('td', row).eq(1).attr("align", "right"); + $('td', row).eq(1).attr('data-export', index+1); + $('td', row).eq(2).attr('data-export', $('td', row).eq(2).text()); + {% if dataset.type == 'ProbeSet' %} + $('td', row).eq(3).attr('title', $('td', row).eq(3).text()); + $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); + if ($('td', row).eq(3).text().length > 20) { + $('td', row).eq(3).text($('td', row).eq(3).text().substring(0, 20)); + $('td', row).eq(3).text($('td', row).eq(3).text() + '...') + } + $('td', row).eq(4).attr('title', $('td', row).eq(4).text()); + $('td', row).eq(4).attr('data-export', $('td', row).eq(4).text()); + $('td', row).slice(5,10).attr("align", "right"); + $('td', row).eq(5).attr('data-export', $('td', row).eq(5).text()); + $('td', row).eq(6).attr('data-export', $('td', row).eq(6).text()); + $('td', row).eq(7).attr('data-export', $('td', row).eq(7).text()); + $('td', row).eq(8).attr('data-export', $('td', row).eq(8).text()); + $('td', row).eq(9).attr('data-export', $('td', row).eq(9).text()); + {% elif dataset.type == 'Publish' %} + $('td', row).eq(3).attr('title', $('td', row).eq(3).text()); + $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); + $('td', row).eq(4).attr('title', $('td', row).eq(4).text()); + $('td', row).eq(4).attr('data-export', $('td', row).eq(4).text()); + $('td', row).eq(4).attr('align', 'right'); + $('td', row).slice(6,10).attr("align", "right"); + $('td', row).eq(5).attr('data-export', $('td', row).eq(5).text()); + $('td', row).eq(6).attr('data-export', $('td', row).eq(6).text()); + $('td', row).eq(7).attr('data-export', $('td', row).eq(7).text()); + $('td', row).eq(8).attr('data-export', $('td', row).eq(8).text()); + $('td', row).eq(9).attr('data-export', $('td', row).eq(8).text()); + {% elif dataset.type == 'Geno' %} + $('td', row).eq(3).attr('data-export', $('td', row).eq(3).text()); + {% endif %} + }, + "data": trait_list, + "columns": columnDefs, + "order": [[1, "asc" ]], + "sDom": "iti", + "destroy": true, + {% if wide_columns_exist != true %} + "autoWidth": true, + {% else %} + "autoWidth": false, + {% endif %} + "deferRender": true, + "bSortClasses": false, + {% if trait_list|length > 20 %} + "scrollY": "500px", + "scroller": true, + "scrollCollapse": true, + {% else %} + "iDisplayLength": -1, + {% endif %} + "initComplete": function (settings) { + //Add JQueryUI resizable functionality to each th in the ScrollHead table + $('#' + tableId + '_wrapper .dataTables_scrollHead thead th').resizable({ + handles: "e", + alsoResize: '#' + tableId + '_wrapper .dataTables_scrollHead table', //Not essential but makes the resizing smoother + resize: function( event, ui ) { + width_change = ui.size.width - ui.originalSize.width; + }, + stop: function () { + saveColumnSettings(tableId, trait_table); + loadDataTable(); + } + }); + } } if (!first_run){ -- cgit v1.2.3 From ef49a5bf5b1b2b010c71f77394c5b178c00b8942 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 21 Jun 2021 20:29:26 +0000 Subject: Fixed indentation in table_functions.js --- .../static/new/javascript/table_functions.js | 181 ++++++++++----------- 1 file changed, 88 insertions(+), 93 deletions(-) diff --git a/wqflask/wqflask/static/new/javascript/table_functions.js b/wqflask/wqflask/static/new/javascript/table_functions.js index e24b3e03..745563c2 100644 --- a/wqflask/wqflask/static/new/javascript/table_functions.js +++ b/wqflask/wqflask/static/new/javascript/table_functions.js @@ -1,93 +1,88 @@ -recheck_rows = function(the_table, checked_rows){ - //ZS: This is meant to recheck checkboxes after columns are resized - check_cells = the_table.column(0).nodes().to$(); - for (let i = 0; i < check_cells.length; i++) { - if (checked_rows.includes(i)){ - check_cells[i].childNodes[0].checked = true; - } - } - - check_rows = trait_table.rows().nodes(); - for (let i =0; i < check_rows.length; i++) { - if (checked_rows.includes(i)){ - check_rows[i].classList.add("selected") - } - } - } - -get_checked_rows = function(table_id){ - let checked_rows = [] - $("#" + table_id + " input").each(function(index){ - if ($(this).prop("checked") == true){ - checked_rows.push(index); - } - }); - - return checked_rows -} - -function setUserColumnsDefWidths(table_id) { - var userColumnDef; - - // Get the settings for this table from localStorage - var userColumnDefs = JSON.parse(localStorage.getItem(table_id)) || []; - - if (userColumnDefs.length === 0 ) return; - - columnDefs.forEach( function(columnDef) { - - // Check if there is a width specified for this column - userColumnDef = userColumnDefs.find( function(column) { - return column.targets === columnDef.targets; - }); - - // If there is, set the width of this columnDef in px - if ( userColumnDef ) { - - columnDef.sWidth = userColumnDef.width + 'px'; - columnDef.width = userColumnDef.width + 'px'; - - $('.toggle-vis').each(function(){ - if ($(this).attr('data-column') == columnDef.targets){ - if ($(this).hasClass("active")){ - columnDef.bVisible = false - } else { - columnDef.bVisible = true - } - } - }) - } - }); -} - -function saveColumnSettings(table_id, trait_table) { -var userColumnDefs = JSON.parse(localStorage.getItem(table_id)) || []; - -var width, header, existingSetting; - -trait_table.columns().every( function ( targets ) { - - // Check if there is a setting for this column in localStorage - existingSetting = userColumnDefs.findIndex( function(column) { return column.targets === targets;}); - - // Get the width of this column - header = this.header(); - width = $(header).width(); - - if ( existingSetting !== -1 ) { - // Update the width - userColumnDefs[existingSetting].width = width; - } else { - - // Add the width for this column - userColumnDefs.push({ - targets: targets, - width: width, - }); - - } -}); - -// Save (or update) the settings in localStorage -localStorage.setItem(table_id, JSON.stringify(userColumnDefs)); -} \ No newline at end of file +recheck_rows = function(the_table, checked_rows){ + //ZS: This is meant to recheck checkboxes after columns are resized + check_cells = the_table.column(0).nodes().to$(); + for (let i = 0; i < check_cells.length; i++) { + if (checked_rows.includes(i)){ + check_cells[i].childNodes[0].checked = true; + } + } + + check_rows = trait_table.rows().nodes(); + for (let i =0; i < check_rows.length; i++) { + if (checked_rows.includes(i)){ + check_rows[i].classList.add("selected") + } + } +} + +get_checked_rows = function(table_id){ + let checked_rows = [] + $("#" + table_id + " input").each(function(index){ + if ($(this).prop("checked") == true){ + checked_rows.push(index); + } + }); + + return checked_rows +} + +function setUserColumnsDefWidths(table_id) { + var userColumnDef; + + // Get the settings for this table from localStorage + var userColumnDefs = JSON.parse(localStorage.getItem(table_id)) || []; + + if (userColumnDefs.length === 0 ) return; + + columnDefs.forEach( function(columnDef) { + // Check if there is a width specified for this column + userColumnDef = userColumnDefs.find( function(column) { + return column.targets === columnDef.targets; + }); + + // If there is, set the width of this columnDef in px + if ( userColumnDef ) { + + columnDef.sWidth = userColumnDef.width + 'px'; + columnDef.width = userColumnDef.width + 'px'; + + $('.toggle-vis').each(function(){ + if ($(this).attr('data-column') == columnDef.targets){ + if ($(this).hasClass("active")){ + columnDef.bVisible = false + } else { + columnDef.bVisible = true + } + } + }) + } + }); +} + +function saveColumnSettings(table_id, trait_table) { + var userColumnDefs = JSON.parse(localStorage.getItem(table_id)) || []; + var width, header, existingSetting; + + trait_table.columns().every( function ( targets ) { + // Check if there is a setting for this column in localStorage + existingSetting = userColumnDefs.findIndex( function(column) { return column.targets === targets;}); + + // Get the width of this column + header = this.header(); + width = $(header).width(); + + if ( existingSetting !== -1 ) { + // Update the width + userColumnDefs[existingSetting].width = width; + } else { + // Add the width for this column + userColumnDefs.push({ + targets: targets, + width: width, + }); + } + }); + + // Save (or update) the settings in localStorage + localStorage.setItem(table_id, JSON.stringify(userColumnDefs)); +} -- cgit v1.2.3 From 6f621612f1f74694b9895c7252c1d72e7b2f7314 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 21 Jun 2021 20:33:11 +0000 Subject: Implemented resizeable columns for gene global search + fixed the way change_buttons is called and row highlighting works in the DataTables callback --- wqflask/wqflask/templates/gsearch_gene.html | 347 +++++++++++++++------------- 1 file changed, 183 insertions(+), 164 deletions(-) diff --git a/wqflask/wqflask/templates/gsearch_gene.html b/wqflask/wqflask/templates/gsearch_gene.html index 5549ac8a..3b8f9ff7 100644 --- a/wqflask/wqflask/templates/gsearch_gene.html +++ b/wqflask/wqflask/templates/gsearch_gene.html @@ -2,6 +2,7 @@ {% block title %}Search Results{% endblock %} {% block css %} + {% endblock %} {% block content %} @@ -31,7 +32,7 @@

    -
    +
    @@ -54,6 +55,7 @@ + - {% endblock %} -- cgit v1.2.3 From 48154b81c705f6eb2af16b0394e1e2e5bcca83b6 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 21 Jun 2021 20:35:07 +0000 Subject: Added table_functions.js to gsearch_pheno.html --- wqflask/wqflask/templates/gsearch_pheno.html | 1 + 1 file changed, 1 insertion(+) diff --git a/wqflask/wqflask/templates/gsearch_pheno.html b/wqflask/wqflask/templates/gsearch_pheno.html index 89316cbc..a5e30c8b 100644 --- a/wqflask/wqflask/templates/gsearch_pheno.html +++ b/wqflask/wqflask/templates/gsearch_pheno.html @@ -54,6 +54,7 @@ + - {% endblock %} -- cgit v1.2.3 From d99776e16fc991f75803836f6e3292cea9f6d621 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 24 Jun 2021 21:11:59 +0000 Subject: Gave initial widths for dataset and description columns in gene global search --- wqflask/wqflask/templates/gsearch_gene.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/templates/gsearch_gene.html b/wqflask/wqflask/templates/gsearch_gene.html index 3b8f9ff7..0a3ec027 100644 --- a/wqflask/wqflask/templates/gsearch_gene.html +++ b/wqflask/wqflask/templates/gsearch_gene.html @@ -132,6 +132,7 @@ 'title': "Dataset", 'type': "natural", 'targets': 6, + 'width': "320px", 'data': "dataset_fullname" }, { @@ -145,6 +146,7 @@ 'title': "Description", 'type': "natural", 'data': null, + 'width': "120px", 'targets': 8, 'render': function(data, type, row, meta) { try { @@ -260,7 +262,6 @@ "order": [[1, "asc" ]], 'sDom': "iti", "destroy": true, - "autoWidth": true, "deferRender": true, "bSortClasses": false, {% if trait_count > 20 %} @@ -301,9 +302,8 @@ {% if trait_list|length > 20 %} $('#table_container').css("width", String($('#trait_table').width() + 17) + "px"); {% endif %} + trait_table.draw(); } - - trait_table.draw(); } $('#redraw').click(function() { -- cgit v1.2.3 From d6937e74b85a4fc44153530520774836eed60fe6 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 28 Jun 2021 18:31:29 +0000 Subject: Added doc for creating guix profile, written by Bonface --- doc/guix_profile_setup.org | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 doc/guix_profile_setup.org diff --git a/doc/guix_profile_setup.org b/doc/guix_profile_setup.org new file mode 100644 index 00000000..c397377c --- /dev/null +++ b/doc/guix_profile_setup.org @@ -0,0 +1,39 @@ +* Setting up GUIX profile for GN + +First create a guix profile with the latest packages: + +: ~/opt/guix/bin/guix pull + +This will create a profile with the latest packages under`~/.config/guix/current` + +Now you have the latest guix. Check: `$HOME/.config/guix/current/bin/guix --version` + +At this point, it's worth mentioning that installing +python3-genenetwork using `$HOME/.config/guix/current/bin/guix` should +work; but let's use the dev version(since that may come handy in +time), and it's a nice thing to know. + +Next, we ensure that the appropriate GUILEPATHS are set: + +: export GUILE_LOAD_PATH=$HOME/.config/guix/current/share/guile/site/3.0/ +: export GUILE_LOAD_COMPILED_PATH=$HOME/.config/guix/current/lib/guile/3.0/site-ccache/ + +Get into the container: + +: $HOME/.config/guix/current/bin/guix environment -C guix --ad-hoc bash gcc-toolchain +: ./bootstrap +: ./configure --localstatedir=/var --sysconfdir=/etc + +Check that everything works: + +: make check + +Clean up and build: + +: make clean-go +: make -j 4 +: exit + +Install Python3 (substitute paths when necessary): + +: env GUIX_PACKAGE_PATH='/home/zas1024/guix-bioinformatics:/home/zas1024/guix-past/modules' $HOME/.config/guix/current/bin/guix install python3-genenetwork2 -p ~/opt/python3-genenetwork2 --substitute-urls="http://guix.genenetwork.org https://berlin.guixsd.org https://ci.guix.gnu.org https://mirror.hydra.gnu.org" -- cgit v1.2.3 From a2919c924f647887f6383d1e2efebc914ade0da3 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 30 Jun 2021 20:24:52 +0000 Subject: Removed CSS that truncates cell content and adds an ellipses when the cell is too small to contain content; it now just makes the content wrap --- wqflask/wqflask/static/new/css/trait_list.css | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/wqflask/wqflask/static/new/css/trait_list.css b/wqflask/wqflask/static/new/css/trait_list.css index 6d49f009..ce3075d4 100644 --- a/wqflask/wqflask/static/new/css/trait_list.css +++ b/wqflask/wqflask/static/new/css/trait_list.css @@ -51,22 +51,3 @@ div.dts div.dataTables_scrollBody table { div.dts div.dataTables_paginate,div.dts div.dataTables_length{ display:none } - -/* This is the important CSS */ -table.dataTable { - table-layout: fixed; - width: 100%; - white-space: nowrap; - overflow: hidden; -} - -table.dataTable th { - white-space: nowrap; - overflow: hidden; -} - -table.dataTable td { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} \ No newline at end of file -- cgit v1.2.3 From c3302c1f44af146ab296110c11e68ab25ddae4df Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 1 Jul 2021 17:50:31 +0000 Subject: Changed initialize_show_trait_tables.js to include resizeable columns and deal with both primary/other tables in a single function --- .../new/javascript/initialize_show_trait_tables.js | 214 +++++++++++++-------- 1 file changed, 136 insertions(+), 78 deletions(-) diff --git a/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js b/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js index 6ca92fb6..d8a1b006 100644 --- a/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js +++ b/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js @@ -15,6 +15,7 @@ build_columns = function() { 'data': null, 'orderDataType': "dom-checkbox", 'searchable' : false, + 'targets': 0, 'render': function(data, type, row, meta) { return '' } @@ -23,12 +24,14 @@ build_columns = function() { 'title': "ID", 'type': "natural", 'searchable' : false, + 'targets': 1, 'data': "this_id" }, { 'title': "Sample", 'type': "natural", 'data': null, + 'targets': 2, 'render': function(data, type, row, meta) { return '' + data.name + '' } @@ -38,6 +41,7 @@ build_columns = function() { 'orderDataType': "dom-input", 'type': "cust-txt", 'data': null, + 'targets': 3, 'render': function(data, type, row, meta) { if (data.value == null) { return '' @@ -48,12 +52,15 @@ build_columns = function() { } ]; + attr_start = 4 if (js_data.se_exists) { + attr_start += 2 column_list.push( { 'bSortable': false, 'type': "natural", 'data': null, + 'targets': 4, 'searchable' : false, 'render': function(data, type, row, meta) { return '±' @@ -64,6 +71,7 @@ build_columns = function() { 'orderDataType': "dom-input", 'type': "cust-txt", 'data': null, + 'targets': 5, 'render': function(data, type, row, meta) { if (data.variance == null) { return '' @@ -73,24 +81,47 @@ build_columns = function() { } } ); - } - if (js_data.has_num_cases === true) { - column_list.push( - { - 'title': "
    N
    ", - 'orderDataType': "dom-input", - 'type': "cust-txt", - 'data': null, - 'render': function(data, type, row, meta) { - if (data.num_cases == null || data.num_cases == undefined) { - return '' - } else { - return '' + if (js_data.has_num_cases === true) { + attr_start += 1 + column_list.push( + { + 'title': "
    N
    ", + 'orderDataType': "dom-input", + 'type': "cust-txt", + 'data': null, + 'targets': 6, + 'render': function(data, type, row, meta) { + if (data.num_cases == null || data.num_cases == undefined) { + return '' + } else { + return '' + } } } - } - ); + ); + } + } + else { + if (js_data.has_num_cases === true) { + attr_start += 1 + column_list.push( + { + 'title': "
    N
    ", + 'orderDataType': "dom-input", + 'type': "cust-txt", + 'data': null, + 'targets': 4, + 'render': function(data, type, row, meta) { + if (data.num_cases == null || data.num_cases == undefined) { + return '' + } else { + return '' + } + } + } + ); + } } attr_keys = Object.keys(js_data.attributes).sort((a, b) => (js_data.attributes[a].name.toLowerCase() > js_data.attributes[b].name.toLowerCase()) ? 1 : -1) @@ -100,6 +131,7 @@ build_columns = function() { 'title': "
    " + js_data.attributes[attr_keys[i]].name + "
    ", 'type': "natural", 'data': null, + 'targets': attr_start + i, 'render': function(data, type, row, meta) { attr_name = Object.keys(data.extra_attributes).sort()[meta.col - data.first_attr_col] @@ -119,14 +151,35 @@ build_columns = function() { return column_list } -var primary_table = $('#samples_primary').DataTable( { +columnDefs = build_columns() + +loadDataTable(first_run=true, table_id="samples_primary", table_data=js_data['sample_lists'][0]) +if (js_data.sample_lists.length > 1){ + loadDataTable(first_run=true, table_id="samples_other", table_data=js_data['sample_lists'][1]) +} + +function loadDataTable(first_run=false, table_id, table_data){ + + console.log("COL DEFS:", columnDefs) + + if (!first_run){ + setUserColumnsDefWidths(table_id); + } + + if (table_id == "samples_primary"){ + table_type = "Primary" + } else { + table_type = "Other" + } + + table_settings = { 'initComplete': function(settings, json) { $('.edit_sample_value').change(function() { edit_data_change(); }); }, 'createdRow': function ( row, data, index ) { - $(row).attr('id', "Primary_" + data.this_id) + $(row).attr('id', table_type + "_" + data.this_id) $(row).addClass("value_se"); if (data.outlier) { $(row).addClass("outlier"); @@ -154,75 +207,80 @@ var primary_table = $('#samples_primary').DataTable( { $('td', row).eq(attribute_start_pos + i + 1).attr("style", "text-align: " + js_data.attributes[attr_keys[i]].alignment + "; padding-top: 2px; padding-bottom: 0px;") } }, - 'data': js_data['sample_lists'][0], - 'columns': build_columns(), - 'order': [[1, "asc"]], - 'sDom': "Ztr", - 'autoWidth': true, - 'orderClasses': true, + 'data': table_data, + 'columns': columnDefs, + "order": [[1, "asc" ]], + "sDom": "iti", + "destroy": true, + "autoWidth": first_run, + "deferRender": true, + "bSortClasses": false, "scrollY": "100vh", - 'scroller': true, - 'scrollCollapse': true -} ); + "scrollCollapse": true, + "scroller": true, + "iDisplayLength": -1, + "initComplete": function (settings) { + //Add JQueryUI resizable functionality to each th in the ScrollHead table + $('#' + table_id + '_wrapper .dataTables_scrollHead thead th').resizable({ + handles: "e", + alsoResize: '#' + table_id + '_wrapper .dataTables_scrollHead table', //Not essential but makes the resizing smoother + resize: function( event, ui ) { + width_change = ui.size.width - ui.originalSize.width; + }, + stop: function () { + saveColumnSettings(table_id, the_table); + loadDataTable(first_run=false, table_id, table_data); + } + }); + } + } -primary_table.draw(); //ZS: This makes the table adjust its height properly on initial load + var the_table = $('#' + table_id).DataTable(table_settings); -primary_table.on( 'order.dt search.dt draw.dt', function () { - primary_table.column(1, {search:'applied', order:'applied'}).nodes().each( function (cell, i) { - cell.innerHTML = i+1; - } ); -} ).draw(); + the_table.draw(); //ZS: This makes the table adjust its height properly on initial load -$('#primary_searchbox').on( 'keyup', function () { - primary_table.search($(this).val()).draw(); -} ); + the_table.on( 'order.dt search.dt draw.dt', function () { + the_table.column(1, {search:'applied', order:'applied'}).nodes().each( function (cell, i) { + cell.innerHTML = i+1; + } ); + } ).draw(); -if (js_data.sample_lists.length > 1){ - var other_table = $('#samples_other').DataTable( { - 'initComplete': function(settings, json) { - $('.edit_sample_value').change(function() { - edit_data_change(); - }); - }, - 'createdRow': function ( row, data, index ) { - $(row).attr('id', "Primary_" + data.this_id) - $(row).addClass("value_se"); - if (data.outlier) { - $(row).addClass("outlier"); - } - $('td', row).eq(1).addClass("column_name-Index") - $('td', row).eq(2).addClass("column_name-Sample") - $('td', row).eq(3).addClass("column_name-Value") - if (js_data.se_exists) { - $('td', row).eq(5).addClass("column_name-SE") - if (js_data.has_num_cases === true) { - $('td', row).eq(6).addClass("column_name-num_cases") - } else { - if (js_data.has_num_cases === true) { - $('td', row).eq(4).addClass("column_name-num_cases") - } - } + if (!first_run){ + $('#' + table_type.toLowerCase() + '_container').css("width", String($('#' + table_id).width() + width_change + 17) + "px"); //ZS : Change the container width by the change in width of the adjusted column, so the overall table size adjusts properly + + let checked_rows = get_checked_rows(table_id); + if (checked_rows.length > 0){ + recheck_rows(the_table, checked_rows); + } + } + + if (first_run){ + $('#' + table_type.toLowerCase() + '_container').css("width", String($('#' + table_id).width() + 17) + "px"); + } + + $('#' + table_type.toLowerCase() + '_searchbox').on( 'keyup', function () { + the_table.search($(this).val()).draw(); + } ); + + $('.toggle-vis').on('click', function (e) { + e.preventDefault(); + + function toggle_column(column) { + //ZS: Toggle column visibility + column.visible( ! column.visible() ); + if (column.visible()){ + $(this).removeClass("active"); } else { - if (js_data.has_num_cases === true) { - $('td', row).eq(4).addClass("column_name-num_cases") - } + $(this).addClass("active"); } + } - for (i=0; i < attr_keys.length; i++) { - $('td', row).eq(attribute_start_pos + i + 1).addClass("column_name-" + js_data.attributes[attr_keys[i]].name) - $('td', row).eq(attribute_start_pos + i + 1).attr("style", "text-align: " + js_data.attributes[attr_keys[i]].alignment + "; padding-top: 2px; padding-bottom: 0px;") - } - }, - 'data': js_data['sample_lists'][1], - 'columns': build_columns(), - 'order': [[1, "asc"]], - 'sDom': "Ztr", - 'autoWidth': true, - 'orderClasses': true, - "scrollY": "100vh", - 'scroller': true, - 'scrollCollapse': true + // Get the column API object + var target_cols = $(this).attr('data-column').split(",") + for (let i = 0; i < target_cols.length; i++){ + var column = the_table.column( target_cols[i] ); + toggle_column(column); + } } ); - other_table.draw(); //ZS: This makes the table adjust its height properly on initial load } -- cgit v1.2.3 From be51132f68ea92c3917c16f1d2962772e8debe04 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 1 Jul 2021 17:51:32 +0000 Subject: Removed width from sample_group div and change table div ID to be distinct when there are both primary and other samplee tables --- wqflask/wqflask/templates/show_trait_edit_data.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/templates/show_trait_edit_data.html b/wqflask/wqflask/templates/show_trait_edit_data.html index 5939c953..c5944d3f 100644 --- a/wqflask/wqflask/templates/show_trait_edit_data.html +++ b/wqflask/wqflask/templates/show_trait_edit_data.html @@ -53,11 +53,11 @@ {% set outer_loop = loop %} -
    +

    {{ sample_type.header }}

    -
    +

    Loading...
    -- cgit v1.2.3 From bbda9eb2eb542b7d90abdfcd1a8f34c35d38491c Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 1 Jul 2021 17:53:05 +0000 Subject: Added imports necessary for resizeable columns and removed unused code --- wqflask/wqflask/templates/show_trait.html | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/wqflask/wqflask/templates/show_trait.html b/wqflask/wqflask/templates/show_trait.html index 3dbf5f57..7074e21e 100644 --- a/wqflask/wqflask/templates/show_trait.html +++ b/wqflask/wqflask/templates/show_trait.html @@ -5,9 +5,10 @@ - + + @@ -155,6 +156,7 @@ + +{% endblock %} diff --git a/wqflask/wqflask/templates/edit_trait.html b/wqflask/wqflask/templates/edit_trait.html deleted file mode 100644 index 7d4c65f8..00000000 --- a/wqflask/wqflask/templates/edit_trait.html +++ /dev/null @@ -1,234 +0,0 @@ -{% extends "base.html" %} -{% block title %}Trait Submission{% endblock %} -{% block content %} - -Edit Trait for Published Database -Submit Trait | Reset - -{% if diff %} - -
    -
    - -

    Update History

    -
    -

    Loading...
    - - - - - - - - {% set ns = namespace(display_cell=True) %} - - {% for timestamp, group in diff %} - {% set ns.display_cell = True %} - {% for i in group %} - - {% if ns.display_cell and i.timestamp == timestamp %} - - {% set author = i.author %} - {% set timestamp_ = i.timestamp %} - - {% else %} - - {% set author = "" %} - {% set timestamp_ = "" %} - - {% endif %} - - - - - {% set ns.display_cell = False %} - - {% endfor %} - {% endfor %} - -
    TimestampEditorFieldDiff
    {{ timestamp_ }}{{ author }}{{ i.diff.field }}
    {{ i.diff.diff }}
    - - -
    - -{% endif %} - -
    -

    Trait Information:

    -
    - - -
    - - -
    -
    -
    - -
    - - -
    - -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - -
    - - -
    -
    -
    - - - - - - -
    -
    - -{%endblock%} - -{% block js %} - -{% endblock %} diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index da427bfe..595a0de7 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -428,9 +428,9 @@ def submit_trait_form(): version=GN_VERSION) -@app.route("/trait//edit/") +@app.route("/trait//edit/inbredset-id/") @admin_login_required -def edit_trait(name, inbred_set_id): +def edit_phenotype(name, inbred_set_id): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), passwd=current_app.config.get("DB_PASS"), @@ -476,7 +476,7 @@ def edit_trait(name, inbred_set_id): if len(diff_data) > 0: diff_data_ = groupby(diff_data, lambda x: x.timestamp) return render_template( - "edit_trait.html", + "edit_phenotype.html", diff=diff_data_, publish_xref=publish_xref, phenotype=phenotype_, @@ -486,7 +486,7 @@ def edit_trait(name, inbred_set_id): @app.route("/trait/update", methods=["POST"]) -def update_trait(): +def update_phenotype(): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), passwd=current_app.config.get("DB_PASS"), @@ -544,7 +544,8 @@ def update_trait(): data=MetadataAudit(dataset_id=data_.get("dataset-name"), editor=author.decode("utf-8"), json_data=json.dumps(diff_data))) - return redirect("/trait/10007/edit/1") + return redirect(f"/trait/{data_.get('dataset-name')}" + f"/edit/inbredset-id/{data_.get('inbred-set-id')}") @app.route("/create_temp_trait", methods=('POST',)) -- cgit v1.2.3 From ffa34cc70ad70ab39682e8bdda6b6eb0d46a577d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 9 Jul 2021 13:42:22 +0300 Subject: wqflask: views: Add extra route showing probeset editing form * wqflask/wqflask/views.py (edit_probeset): New function/ route. --- wqflask/wqflask/views.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 595a0de7..e1f6dd71 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -34,8 +34,10 @@ from gn3.db import insert from gn3.db import update from gn3.db.metadata_audit import MetadataAudit from gn3.db.phenotypes import Phenotype +from gn3.db.phenotypes import Probeset from gn3.db.phenotypes import Publication from gn3.db.phenotypes import PublishXRef +from gn3.db.phenotypes import probeset_mapping from flask import current_app @@ -485,6 +487,23 @@ def edit_phenotype(name, inbred_set_id): ) +@app.route("/trait/edit/probeset-name/") +@admin_login_required +def edit_probeset(dataset_name): + conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), + user=current_app.config.get("DB_USER"), + passwd=current_app.config.get("DB_PASS"), + host=current_app.config.get("DB_HOST")) + probeset_ = fetchone(conn=conn, + table="ProbeSet", + columns=list(probeset_mapping.values()), + where=Probeset(name=dataset_name)) + return render_template( + "edit_probeset.html", + probeset=probeset_ + ) + + @app.route("/trait/update", methods=["POST"]) def update_phenotype(): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), -- cgit v1.2.3 From 503ff72502d7bc440450c1c91ac2d5051ca446b9 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 9 Jul 2021 15:23:10 +0300 Subject: templates: edit_probeset: Add template for editing probeset data * wqflask/wqflask/templates/edit_probeset.html: New file(template). --- wqflask/wqflask/templates/edit_probeset.html | 239 +++++++++++++++++++++++++++ wqflask/wqflask/views.py | 31 +++- 2 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 wqflask/wqflask/templates/edit_probeset.html diff --git a/wqflask/wqflask/templates/edit_probeset.html b/wqflask/wqflask/templates/edit_probeset.html new file mode 100644 index 00000000..85d49561 --- /dev/null +++ b/wqflask/wqflask/templates/edit_probeset.html @@ -0,0 +1,239 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} +{% block content %} + +Edit Trait for Probeset +Submit Trait | Reset + +{% if diff %} + +
    +
    + +

    Update History

    +
    + + + + + + + + + {% set ns = namespace(display_cell=True) %} + + {% for timestamp, group in diff %} + {% set ns.display_cell = True %} + {% for i in group %} + + {% if ns.display_cell and i.timestamp == timestamp %} + + {% set author = i.author %} + {% set timestamp_ = i.timestamp %} + + {% else %} + + {% set author = "" %} + {% set timestamp_ = "" %} + + {% endif %} + + + + + {% set ns.display_cell = False %} + + {% endfor %} + {% endfor %} + +
    TimestampEditorFieldDiff
    {{ timestamp_ }}{{ author }}{{ i.diff.field }}
    {{ i.diff.diff }}
    +
    + +
    + +{% endif %} + +
    +

    Probeset Information:

    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + + + + + +
    +
    + +{%endblock%} + +{% block js %} + +{% endblock %} diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index e1f6dd71..741861e6 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -498,13 +498,40 @@ def edit_probeset(dataset_name): table="ProbeSet", columns=list(probeset_mapping.values()), where=Probeset(name=dataset_name)) + json_data = fetchall( + conn, + "metadata_audit", + where=MetadataAudit(dataset_id=probeset_.id_)) + Edit = namedtuple("Edit", ["field", "old", "new", "diff"]) + Diff = namedtuple("Diff", ["author", "diff", "timestamp"]) + diff_data = [] + for data in json_data: + json_ = json.loads(data.json_data) + timestamp = json_.get("timestamp") + author = json_.get("author") + for key, value in json_.items(): + if isinstance(value, dict): + for field, data_ in value.items(): + diff_data.append( + Diff(author=author, + diff=Edit(field, + data_.get("old"), + data_.get("new"), + "\n".join(difflib.ndiff( + [data_.get("old")], + [data_.get("new")]))), + timestamp=timestamp)) + diff_data_ = None + if len(diff_data) > 0: + diff_data_ = groupby(diff_data, lambda x: x.timestamp) return render_template( "edit_probeset.html", - probeset=probeset_ - ) + diff=diff_data_, + probeset=probeset_) @app.route("/trait/update", methods=["POST"]) +@admin_login_required def update_phenotype(): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), -- cgit v1.2.3 From 76b4cb3cfa945c0016fa083bb672e42816707572 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 9 Jul 2021 15:27:24 +0300 Subject: wqflask: views: Add route for updating the probeset --- wqflask/wqflask/views.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 741861e6..731ca291 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -488,7 +488,7 @@ def edit_phenotype(name, inbred_set_id): @app.route("/trait/edit/probeset-name/") -@admin_login_required +# @admin_login_required def edit_probeset(dataset_name): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), @@ -594,6 +594,63 @@ def update_phenotype(): f"/edit/inbredset-id/{data_.get('inbred-set-id')}") +@app.route("/probeset/update", methods=["POST"]) +@admin_login_required +def update_probeset(): + conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), + user=current_app.config.get("DB_USER"), + passwd=current_app.config.get("DB_PASS"), + host=current_app.config.get("DB_HOST")) + data_ = request.form.to_dict() + probeset_ = { + "id_": data_.get("id"), + "symbol": data_.get("symbol"), + "description": data_.get("description"), + "probe_target_description": data_.get("probe_target_description"), + "chr_": data_.get("chr"), + "mb": data_.get("mb"), + "alias": data_.get("alias"), + "geneid": data_.get("geneid"), + "homologeneid": data_.get("homologeneid"), + "unigeneid": data_.get("unigeneid"), + "omim": data_.get("OMIM"), + "refseq_transcriptid": data_.get("refseq_transcriptid"), + "blatseq": data_.get("blatseq"), + "targetseq": data_.get("targetseq"), + "strand_probe": data_.get("Strand_Probe"), + "probe_set_target_region": data_.get("probe_set_target_region"), + "probe_set_specificity": data_.get("probe_set_specificity"), + "probe_set_blat_score": data_.get("probe_set_blat_score"), + "probe_set_blat_mb_start": data_.get("probe_set_blat_mb_start"), + "probe_set_blat_mb_end": data_.get("probe_set_blat_mb_end"), + "probe_set_strand": data_.get("probe_set_strand"), + "probe_set_note_by_rw": data_.get("probe_set_note_by_rw"), + "flag": data_.get("flag") + } + updated_probeset = update( + conn, "ProbeSet", + data=Probeset(**probeset_), + where=Probeset(id_=data_.get("id"))) + + diff_data = {} + author = g.user_session.record.get(b'user_name') + if updated_probeset: + diff_data.update({"Probeset": diff_from_dict(old={ + k: data_.get(f"old_{k}") for k, v in probeset_.items() + if v is not None}, new=probeset_)}) + if diff_data: + diff_data.update({"probeset_name": data_.get("probeset_name")}) + diff_data.update({"author": author.decode('utf-8')}) + diff_data.update({"timestamp": datetime.datetime.now().strftime( + "%Y-%m-%d %H:%M:%S")}) + insert(conn, + table="metadata_audit", + data=MetadataAudit(dataset_id=data_.get("id"), + editor=author.decode("utf-8"), + json_data=json.dumps(diff_data))) + return redirect(f"/trait/edit/probeset-name/{data_.get('probeset_name')}") + + @app.route("/create_temp_trait", methods=('POST',)) def create_temp_trait(): logger.info(request.url) -- cgit v1.2.3 From d01b1aadcd3604e4f592f64ce7d9eb010d4c7230 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sat, 10 Jul 2021 09:55:12 +0300 Subject: templates: show_trait_details: Show edit buttons conditionally * wqflask/wqflask/templates/show_trait_details.html: Check the data type, and load different edit buttons. --- wqflask/wqflask/templates/show_trait_details.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html index 83f7b0ac..bb30c54c 100644 --- a/wqflask/wqflask/templates/show_trait_details.html +++ b/wqflask/wqflask/templates/show_trait_details.html @@ -235,8 +235,14 @@ {% endif %} {% if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access" %} + {% if this_trait.dataset.type == 'Publish' %} {% endif %} + + {% if this_trait.dataset.type == 'ProbeSet' %} + + {% endif %} + {% endif %}
    -- cgit v1.2.3 From 344d6a86d2e468249ec7b583e8c0751f99cd6a5b Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 12 Jul 2021 11:40:40 +0300 Subject: tests: test_markdown_routes: Remove stubbed out tests --- wqflask/tests/integration/test_markdown_routes.py | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 wqflask/tests/integration/test_markdown_routes.py diff --git a/wqflask/tests/integration/test_markdown_routes.py b/wqflask/tests/integration/test_markdown_routes.py deleted file mode 100644 index 5e3e5045..00000000 --- a/wqflask/tests/integration/test_markdown_routes.py +++ /dev/null @@ -1,21 +0,0 @@ -"Integration tests for markdown routes" -import unittest - -from bs4 import BeautifulSoup - -from wqflask import app - - -class TestGenMenu(unittest.TestCase): - """Tests for glossary""" - - def setUp(self): - self.app = app.test_client() - - def tearDown(self): - pass - - def test_glossary_page(self): - """Test that the glossary page is rendered properly""" - response = self.app.get('/glossary', follow_redirects=True) - pass -- cgit v1.2.3 From 48ac21fda375b8255bec86e83496d730bb57ffff Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 12 Jul 2021 17:49:09 +0000 Subject: Encode user_id as bytestring if not already bytestring to account for some user_ids being stored as different encoding --- wqflask/utility/authentication_tools.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index 57dbf8ba..6802d689 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -11,7 +11,6 @@ from utility.redis_tools import (get_redis_conn, add_resource) Redis = get_redis_conn() - def check_resource_availability(dataset, trait_id=None): # At least for now assume temporary entered traits are accessible if type(dataset) == str or dataset.type == "Temp": @@ -133,12 +132,17 @@ def check_owner_or_admin(dataset=None, trait_id=None, resource_id=None): else: resource_id = get_resource_id(dataset, trait_id) - if g.user_session.user_id in Redis.smembers("super_users"): + try: + user_id = g.user_session.user_id.encode('utf-8') + except: + user_id = g.user_session.user_id + + if user_id in Redis.smembers("super_users"): return "owner" resource_info = get_resource_info(resource_id) if resource_info: - if g.user_session.user_id == resource_info['owner_id']: + if user_id == resource_info['owner_id']: return "owner" else: return check_admin(resource_id) -- cgit v1.2.3 From 427071fc797d6208f9833401833f17d929f11343 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 13 Jul 2021 17:08:53 +0000 Subject: Fixed corr_coeffient to corr_coefficient --- wqflask/wqflask/correlation/show_corr_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/correlation/show_corr_results.py b/wqflask/wqflask/correlation/show_corr_results.py index e936f28c..e2daa991 100644 --- a/wqflask/wqflask/correlation/show_corr_results.py +++ b/wqflask/wqflask/correlation/show_corr_results.py @@ -94,7 +94,7 @@ def correlation_json_for_table(correlation_data, this_trait, this_dataset, targe results_dict['dataset'] = target_dataset['name'] results_dict['hmac'] = hmac.data_hmac( '{}:{}'.format(target_trait['name'], target_dataset['name'])) - results_dict['sample_r'] = f"{float(trait['corr_coeffient']):.3f}" + results_dict['sample_r'] = f"{float(trait['corr_coefficient']):.3f}" results_dict['num_overlap'] = trait['num_overlap'] results_dict['sample_p'] = f"{float(trait['p_value']):.3e}" if target_dataset['type'] == "ProbeSet": -- cgit v1.2.3 From 2fbfdc0e57ad9d2b27b99b32f3d48af4fae96dd9 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Fri, 16 Jul 2021 22:47:21 +0300 Subject: handle missing tissue input data --- wqflask/wqflask/correlation/correlation_gn3_api.py | 31 ++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/wqflask/wqflask/correlation/correlation_gn3_api.py b/wqflask/wqflask/correlation/correlation_gn3_api.py index aea91220..c96da8ee 100644 --- a/wqflask/wqflask/correlation/correlation_gn3_api.py +++ b/wqflask/wqflask/correlation/correlation_gn3_api.py @@ -149,7 +149,6 @@ def fetch_sample_data(start_vars, this_trait, this_dataset, target_dataset): sample_data = test_process_data(this_trait, this_dataset, start_vars) - if target_dataset.type == "ProbeSet": target_dataset.get_probeset_data(list(sample_data.keys())) else: @@ -202,17 +201,22 @@ def compute_correlation(start_vars, method="pearson", compute_all=False): if tissue_input is not None: (primary_tissue_data, target_tissue_data) = tissue_input - corr_input_data = { - "primary_tissue": primary_tissue_data, - "target_tissues_dict": target_tissue_data - } - correlation_results = compute_tissue_correlation( - primary_tissue_dict=corr_input_data["primary_tissue"], - target_tissues_data=corr_input_data[ - "target_tissues_dict"], - corr_method=method - - ) + corr_input_data = { + "primary_tissue": primary_tissue_data, + "target_tissues_dict": target_tissue_data + } + correlation_results = compute_tissue_correlation( + primary_tissue_dict=corr_input_data["primary_tissue"], + target_tissues_data=corr_input_data[ + "target_tissues_dict"], + corr_method=method + + ) + else: + return {"correlation_results": [], + "this_trait": this_trait.name, + "target_dataset": start_vars['corr_dataset'], + "return_results": corr_return_results} elif corr_type == "lit": (this_trait_geneid, geneid_dict, species) = do_lit_correlation( @@ -302,5 +306,4 @@ def get_tissue_correlation_input(this_trait, trait_symbol_dict): "trait_symbol_dict": trait_symbol_dict, "symbol_tissue_vals_dict": corr_result_tissue_vals_dict } - return (primary_tissue_data, target_tissue_data) - return None + return (primary_tissue_data, target_tissue_data) \ No newline at end of file -- cgit v1.2.3 From be3dbbf8d7349a076be28c9eba8dc33e18adb477 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Fri, 16 Jul 2021 23:17:34 +0300 Subject: set compute_all for test_compute to True --- wqflask/wqflask/views.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 731ca291..1bd11c4e 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -302,6 +302,7 @@ def gsearchact(): elif type == "phenotype": return render_template("gsearch_pheno.html", **result) + @app.route("/gsearch_table", methods=('GET',)) def gsearchtable(): logger.info(request.url) @@ -316,6 +317,7 @@ def gsearchtable(): return flask.jsonify(current_page) + @app.route("/gsearch_updating", methods=('POST',)) def gsearch_updating(): logger.info("REQUEST ARGS:", request.values) @@ -1193,9 +1195,10 @@ def corr_compute_page(): @app.route("/test_corr_compute", methods=["POST"]) def test_corr_compute_page(): - correlation_data = compute_correlation(request.form) + correlation_data = compute_correlation(request.form, compute_all=True) return render_template("test_correlation_page.html", **correlation_data) - + + @app.route("/corr_matrix", methods=('POST',)) def corr_matrix_page(): logger.info("In corr_matrix, request.form is:", pf(request.form)) -- cgit v1.2.3 From 78f0e216c165eb7879ab07755a2d74deec3a11d5 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Fri, 16 Jul 2021 23:20:14 +0300 Subject: minor fixes for test_correlation_page table columns --- wqflask/wqflask/templates/test_correlation_page.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/test_correlation_page.html b/wqflask/wqflask/templates/test_correlation_page.html index 0809b65e..991773a2 100644 --- a/wqflask/wqflask/templates/test_correlation_page.html +++ b/wqflask/wqflask/templates/test_correlation_page.html @@ -113,7 +113,7 @@ console.log(correlationResults) {"data":corr_type=="sample"?null:"fd","width":"25px"}, { "data": "index","width":"120px","title":"Index" }, { "data": "trait_name","title":"TraitName"}, - { "data": "corr_coeffient","defaultContent": "--"}, + { "data": "corr_coefficient","defaultContent": "--"}, { "data": "p_value","defaultContent":"--"}, { "data": "num_overlap","defaultContent":"--"}, {"data":"tissue_corr","defaultContent":"--","title":"Tissue r"}, -- cgit v1.2.3 From b0514de0f73a9a6d756d7d5daa2a0641c5456ff2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 16 Jul 2021 13:56:10 +0300 Subject: wqflask: views: Add endpoint for getting the csv data --- wqflask/wqflask/views.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 1bd11c4e..b97f15a4 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -38,6 +38,7 @@ from gn3.db.phenotypes import Probeset from gn3.db.phenotypes import Publication from gn3.db.phenotypes import PublishXRef from gn3.db.phenotypes import probeset_mapping +from gn3.db.traits import get_trait_csv_sample_data from flask import current_app @@ -1313,3 +1314,19 @@ def json_default_handler(obj): else: raise TypeError('Object of type %s with value of %s is not JSON serializable' % ( type(obj), repr(obj))) + + +@app.route("/trait//sampledata/") +def get_sample_data_as_csv(trait_name: int, phenotype_id: int): + conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), + user=current_app.config.get("DB_USER"), + passwd=current_app.config.get("DB_PASS"), + host=current_app.config.get("DB_HOST")) + csv_data = get_trait_csv_sample_data(conn, str(trait_name), + str(phenotype_id)) + return Response( + csv_data, + mimetype="text/csv", + headers={"Content-disposition": + "attachment; filename=myplot.csv"} + ) -- cgit v1.2.3 From 47f6f23c976cd72f3181d003f64740902341f51d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 22 Jul 2021 22:07:21 +0300 Subject: wqflask: views: Edit end-point to use the phenotype-id squash! wqflask: views: Edit end-point to use the phenotype-id --- wqflask/wqflask/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index b97f15a4..7fd52ac9 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -433,9 +433,9 @@ def submit_trait_form(): version=GN_VERSION) -@app.route("/trait//edit/inbredset-id/") +@app.route("/trait//edit/phenotype-id/") @admin_login_required -def edit_phenotype(name, inbred_set_id): +def edit_phenotype(name, phenotype_id): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), passwd=current_app.config.get("DB_PASS"), @@ -444,7 +444,7 @@ def edit_phenotype(name, inbred_set_id): conn=conn, table="PublishXRef", where=PublishXRef(id_=name, - inbred_set_id=inbred_set_id)) + phenotype_id=phenotype_id)) phenotype_ = fetchone( conn=conn, table="Phenotype", @@ -594,7 +594,7 @@ def update_phenotype(): editor=author.decode("utf-8"), json_data=json.dumps(diff_data))) return redirect(f"/trait/{data_.get('dataset-name')}" - f"/edit/inbredset-id/{data_.get('inbred-set-id')}") + f"/edit/phenotype-id/{data_.get('phenotype-id')}") @app.route("/probeset/update", methods=["POST"]) -- cgit v1.2.3 From 81f491e0be1ebd23bfc44bc8766b7f1aee998abb Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 26 Jul 2021 12:50:02 +0300 Subject: wqflask: views: Remove un-necessary comment "#" sequence --- wqflask/wqflask/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 7fd52ac9..6e8d1e78 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1297,8 +1297,6 @@ def browser_inputs(): return flask.jsonify(file_contents) -########################################################################## - def json_default_handler(obj): """Based on http://stackoverflow.com/a/2680060/1175849""" -- cgit v1.2.3 From 749807d0bd676ad76e427a8151339bba2780a659 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 26 Jul 2021 12:52:10 +0300 Subject: wqflask: views: Process file_csv if provided and get the diff * wqflask/wqflask/views.py (update_phenotype): If a file is provided in the request, get the diff and store it in a data escrow. --- wqflask/wqflask/views.py | 55 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 6e8d1e78..73829bb2 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -27,6 +27,7 @@ from zipfile import ZIP_DEFLATED from wqflask import app +from gn3.commands import run_cmd from gn3.db import diff_from_dict from gn3.db import fetchall from gn3.db import fetchone @@ -43,6 +44,7 @@ from gn3.db.traits import get_trait_csv_sample_data from flask import current_app from flask import g +from flask import flash from flask import Response from flask import request from flask import make_response @@ -541,6 +543,52 @@ def update_phenotype(): passwd=current_app.config.get("DB_PASS"), host=current_app.config.get("DB_HOST")) data_ = request.form.to_dict() + author = g.user_session.record.get(b'user_name') + if 'file' not in request.files: + flash("No sample data has been uploaded") + else: + file_ = request.files['file'] + trait_name = str(data_.get('dataset-name')) + phenotype_id = str(data_.get('phenotype-id', 35)) + SAMPLE_DATADIR = "/tmp/sample-data/" + if not os.path.exists(SAMPLE_DATADIR): + os.makedirs(SAMPLE_DATADIR) + if not os.path.exists(os.path.join(SAMPLE_DATADIR, + "diffs")): + os.makedirs(os.path.join(SAMPLE_DATADIR, + "diffs")) + if not os.path.exists(os.path.join(SAMPLE_DATADIR, + "updated")): + os.makedirs(os.path.join(SAMPLE_DATADIR, + "updated")) + current_time = str(datetime.datetime.now().isoformat()) + new_file_name = ("/tmp/sample-data/updated/" + f"{author.decode('utf-8')}." + f"{trait_name}.{phenotype_id}." + f"{current_time}.csv") + uploaded_file_name = ("/tmp/sample-data/updated/" + f"updated.{author.decode('utf-8')}." + f"{trait_name}.{phenotype_id}." + f"{current_time}.csv") + file_.save(new_file_name) + csv_ = get_trait_csv_sample_data(conn=conn, + trait_name=str(trait_name), + phenotype_id=str(phenotype_id)) + with open(uploaded_file_name, "w") as f_: + f_.write(csv_) + r = run_cmd(cmd=("/home/bonface/opt/genenetwork3/bin/csvdiff " + f"'{uploaded_file_name}' '{new_file_name}' " + "--format json")) + diff_output = ("/tmp/sample-data/diffs/" + f"{trait_name}." + f"{phenotype_id}.{current_time}.json") + with open(diff_output, "w") as f: + print(r.get("output")) + dict_ = json.loads(r.get("output")) + dict_.update({"author": author.decode('utf-8')}) + dict_.update({"timestamp": datetime.datetime.now().strftime( + "%Y-%m-%d %H:%M:%S")}) + f.write(json.dumps(dict_)) # Run updates: phenotype_ = { "pre_pub_description": data_.get("pre-pub-desc"), @@ -582,7 +630,6 @@ def update_phenotype(): diff_data.update({"Publication": diff_from_dict(old={ k: data_.get(f"old_{k}") for k, v in publication_.items() if v is not None}, new=publication_)}) - author = g.user_session.record.get(b'user_name') if diff_data: diff_data.update({"dataset_id": data_.get("dataset-name")}) diff_data.update({"author": author.decode('utf-8')}) @@ -1320,10 +1367,10 @@ def get_sample_data_as_csv(trait_name: int, phenotype_id: int): user=current_app.config.get("DB_USER"), passwd=current_app.config.get("DB_PASS"), host=current_app.config.get("DB_HOST")) - csv_data = get_trait_csv_sample_data(conn, str(trait_name), - str(phenotype_id)) + csv_ = get_trait_csv_sample_data(conn, str(trait_name), + str(phenotype_id)) return Response( - csv_data, + csv_, mimetype="text/csv", headers={"Content-disposition": "attachment; filename=myplot.csv"} -- cgit v1.2.3 From b355b91f67305c4cb3ebbad994cc0ff2906eb701 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 26 Jul 2021 13:17:14 +0300 Subject: wqflask: views: Show diff files for the admin * wqflask/wqflask/views.py (display_diffs_admin): New function. * wqflask/wqflask/templates/display_files.html: New template to show the diffs. --- wqflask/wqflask/templates/display_files.html | 21 +++++++++++++++++++++ wqflask/wqflask/views.py | 12 ++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 wqflask/wqflask/templates/display_files.html diff --git a/wqflask/wqflask/templates/display_files.html b/wqflask/wqflask/templates/display_files.html new file mode 100644 index 00000000..e7210c1b --- /dev/null +++ b/wqflask/wqflask/templates/display_files.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} +{% block content %} + +Show files for approval + +
    +
      + {% for file in files %} +
    • {{ file }}
    • + {% endfor %} +
    +
    +{%endblock%} + +{% block js %} + +{% endblock %} diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 73829bb2..cf7b5552 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1375,3 +1375,15 @@ def get_sample_data_as_csv(trait_name: int, phenotype_id: int): headers={"Content-disposition": "attachment; filename=myplot.csv"} ) + + +@app.route("/data/approve/") +def display_diffs_admin(): + DIFF_DIR = "/tmp/sample-data/diffs" + files = [] + if os.path.exists(DIFF_DIR): + files = filter(lambda x: not(x.endswith((".approved", ".rejected"))), + os.listdir(DIFF_DIR)) + return render_template("display_files.html", + files=files) + -- cgit v1.2.3 From 5e61c115a2c2be351c16fd9d1a764faecd8591fa Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 26 Jul 2021 13:21:38 +0300 Subject: wqflask: views: Approve data when the "approve" button is clicked * wqflask/wqflask/views.py (approve_data): New function. --- wqflask/wqflask/views.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index cf7b5552..1d50bdff 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -33,6 +33,7 @@ from gn3.db import fetchall from gn3.db import fetchone from gn3.db import insert from gn3.db import update +from gn3.db import update_raw from gn3.db.metadata_audit import MetadataAudit from gn3.db.phenotypes import Phenotype from gn3.db.phenotypes import Probeset @@ -1387,3 +1388,41 @@ def display_diffs_admin(): return render_template("display_files.html", files=files) + +@app.route("/data-samples/approve/") +def approve_data(name): + sample_data = {} + conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), + user=current_app.config.get("DB_USER"), + passwd=current_app.config.get("DB_PASS"), + host=current_app.config.get("DB_HOST")) + with open(os.path.join("/tmp/sample-data/diffs", name), 'r') as myfile: + sample_data = json.load(myfile) + modifications = [d for d in sample_data.get("Modifications")] + for modification in modifications: + if modifications.get("current"): + (strain_id, publish_id, + strain_name, + value, se, count) = modification.get("Current").split(",") + row_counts = update_sample_data( + conn=conn, + strain_name=strain_name, + strain_id=int(strain_id), + publish_data_id=int(PUBLISH_ID), + value=value, + error=se, + count=int(count) + ) + insert(conn, + table="metadata_audit", + data=MetadataAudit( + dataset_id=sample_data.get("publishdata_id"), + editor=sample_data.get("author"), + json_data=json.dumps(sample_data))) + + # Once data is approved, rename it! + os.rename(os.path.join("/tmp/sample-data/diffs", name), + os.path.join("/tmp/sample-data/diffs", + f"{name}.approved")) + return redirect("/data/approve/") + -- cgit v1.2.3 From f5b0a59a41b1cf3b29557aa918b217b0749879b8 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 26 Jul 2021 13:22:38 +0300 Subject: wqflask: views: Return the json data for viewing * wqflask/wqflask/views.py (display_file): New function. --- wqflask/wqflask/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 1d50bdff..12b47ce9 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1426,3 +1426,9 @@ def approve_data(name): f"{name}.approved")) return redirect("/data/approve/") + +@app.route("/display-file/") +def display_file(name): + with open(os.path.join("/tmp/sample-data/diffs", name), 'r') as myfile: + content = myfile.read() + return Response(content, mimetype='text/json') -- cgit v1.2.3 From 9b68883041b581fbd01e5568d36d82581dec7772 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 26 Jul 2021 14:20:44 +0300 Subject: templates: edit_phenotype: Add enctype to form cause of file uploads --- wqflask/wqflask/templates/edit_phenotype.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html index 7d4c65f8..5edddada 100644 --- a/wqflask/wqflask/templates/edit_phenotype.html +++ b/wqflask/wqflask/templates/edit_phenotype.html @@ -53,7 +53,7 @@ Submit Trait | Reset {% endif %} -
    +

    Trait Information:

    -- cgit v1.2.3 From 6eab9f07e5eaf75eaa4f406b1ed3a3cfec9575f8 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 26 Jul 2021 14:22:32 +0300 Subject: templates: edit_phenotype: Add file upload and "approval" buttons --- wqflask/wqflask/templates/edit_phenotype.html | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html index 5edddada..691ad27b 100644 --- a/wqflask/wqflask/templates/edit_phenotype.html +++ b/wqflask/wqflask/templates/edit_phenotype.html @@ -206,11 +206,31 @@ Submit Trait | Reset
    + +
    + +
    + + +
    + +
    + + + +
    - + -- cgit v1.2.3 From 4482a1c5dc61dc4813911f1439721d0407286e44 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 29 Jul 2021 22:43:16 +0300 Subject: edit_phenotype: Remove duplicate html --- wqflask/wqflask/templates/edit_phenotype.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html index 691ad27b..9530fad0 100644 --- a/wqflask/wqflask/templates/edit_phenotype.html +++ b/wqflask/wqflask/templates/edit_phenotype.html @@ -208,11 +208,6 @@ Submit Trait | Reset
    - -
    - - -
    -- cgit v1.2.3 From 097f4168901741d7913e79da09c1f3907c3d1312 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 29 Jul 2021 22:47:29 +0300 Subject: templates: edit_phenotype: add some form of padding upload button --- wqflask/wqflask/templates/edit_phenotype.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html index 9530fad0..7961c188 100644 --- a/wqflask/wqflask/templates/edit_phenotype.html +++ b/wqflask/wqflask/templates/edit_phenotype.html @@ -208,7 +208,7 @@ Submit Trait | Reset
    - +
    -- cgit v1.2.3 From 0793a41cc8534b5084bbf35b437d956681a8c19d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 29 Jul 2021 23:08:50 +0300 Subject: templates: edit_phenotype: Use proper page header for this page --- wqflask/wqflask/templates/edit_phenotype.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html index 7961c188..0e7c4f63 100644 --- a/wqflask/wqflask/templates/edit_phenotype.html +++ b/wqflask/wqflask/templates/edit_phenotype.html @@ -2,8 +2,9 @@ {% block title %}Trait Submission{% endblock %} {% block content %} -Edit Trait for Published Database -Submit Trait | Reset + {% if diff %} -- cgit v1.2.3 From 399d388b6704d39d778617cedebec31341428b75 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 29 Jul 2021 23:09:34 +0300 Subject: templates: edit_phenotype: Properly centre form controls --- wqflask/wqflask/templates/edit_phenotype.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html index 0e7c4f63..9810b7ab 100644 --- a/wqflask/wqflask/templates/edit_phenotype.html +++ b/wqflask/wqflask/templates/edit_phenotype.html @@ -223,7 +223,7 @@ Download csv of sample data for editing
    -
    +
    -- cgit v1.2.3 From f5677ef162fec18603fce6d903fcdfd31b3541cc Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 29 Jul 2021 23:10:07 +0300 Subject: templates: edit_phenotype: Add some margin to links for csv download --- wqflask/wqflask/templates/edit_phenotype.html | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html index 9810b7ab..83daba58 100644 --- a/wqflask/wqflask/templates/edit_phenotype.html +++ b/wqflask/wqflask/templates/edit_phenotype.html @@ -212,15 +212,12 @@
    -
    - + - -
    -- cgit v1.2.3 From cf278e6c119871d08f68c13e07b190b8c14e0414 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 29 Jul 2021 23:12:14 +0300 Subject: wqflask: views: Replace `update_raw` with `update_sample_data` --- wqflask/wqflask/views.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 12b47ce9..4746fcc2 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -33,7 +33,6 @@ from gn3.db import fetchall from gn3.db import fetchone from gn3.db import insert from gn3.db import update -from gn3.db import update_raw from gn3.db.metadata_audit import MetadataAudit from gn3.db.phenotypes import Phenotype from gn3.db.phenotypes import Probeset @@ -41,6 +40,7 @@ from gn3.db.phenotypes import Publication from gn3.db.phenotypes import PublishXRef from gn3.db.phenotypes import probeset_mapping from gn3.db.traits import get_trait_csv_sample_data +from gn3.db.traits import update_sample_data from flask import current_app @@ -1398,10 +1398,11 @@ def approve_data(name): host=current_app.config.get("DB_HOST")) with open(os.path.join("/tmp/sample-data/diffs", name), 'r') as myfile: sample_data = json.load(myfile) + PUBLISH_ID = sample_data.get("publishdata_id") modifications = [d for d in sample_data.get("Modifications")] for modification in modifications: - if modifications.get("current"): - (strain_id, publish_id, + if modification.get("Current"): + (strain_id, strain_name, value, se, count) = modification.get("Current").split(",") row_counts = update_sample_data( @@ -1419,11 +1420,10 @@ def approve_data(name): dataset_id=sample_data.get("publishdata_id"), editor=sample_data.get("author"), json_data=json.dumps(sample_data))) - - # Once data is approved, rename it! - os.rename(os.path.join("/tmp/sample-data/diffs", name), - os.path.join("/tmp/sample-data/diffs", - f"{name}.approved")) + # Once data is approved, rename it! + os.rename(os.path.join("/tmp/sample-data/diffs", name), + os.path.join("/tmp/sample-data/diffs", + f"{name}.approved")) return redirect("/data/approve/") -- cgit v1.2.3 From 52d53eb90aa0a4fbaab713704f4894e652cb91f3 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 29 Jul 2021 23:12:51 +0300 Subject: wqflask: views: Filter out approved/ rejected files during display * wqflask/wqflask/views.py (approve_data): Only show files that have not been approved/ rejected. --- wqflask/wqflask/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 4746fcc2..9a961f26 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1383,8 +1383,9 @@ def display_diffs_admin(): DIFF_DIR = "/tmp/sample-data/diffs" files = [] if os.path.exists(DIFF_DIR): + files = os.listdir(DIFF_DIR) files = filter(lambda x: not(x.endswith((".approved", ".rejected"))), - os.listdir(DIFF_DIR)) + files) return render_template("display_files.html", files=files) -- cgit v1.2.3 From 3804d64d6eed6c9b5fbb95758a84014ff026705d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 29 Jul 2021 23:14:44 +0300 Subject: wqflask: views: Get the publishdata_id from the file --- wqflask/wqflask/views.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 9a961f26..0f1df123 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -572,11 +572,19 @@ def update_phenotype(): f"{trait_name}.{phenotype_id}." f"{current_time}.csv") file_.save(new_file_name) + publishdata_id = "" + lines = [] + with open(new_file_name, "r") as f: + lines = f.read() + first_line = lines.split('\n', 1)[0] + publishdata_id = first_line.split("Id:")[-1].strip() + with open(new_file_name, "w") as f: + f.write(lines.split("\n\n")[-1]) csv_ = get_trait_csv_sample_data(conn=conn, trait_name=str(trait_name), phenotype_id=str(phenotype_id)) with open(uploaded_file_name, "w") as f_: - f_.write(csv_) + f_.write(csv_.split("\n\n")[-1]) r = run_cmd(cmd=("/home/bonface/opt/genenetwork3/bin/csvdiff " f"'{uploaded_file_name}' '{new_file_name}' " "--format json")) @@ -584,9 +592,9 @@ def update_phenotype(): f"{trait_name}." f"{phenotype_id}.{current_time}.json") with open(diff_output, "w") as f: - print(r.get("output")) dict_ = json.loads(r.get("output")) dict_.update({"author": author.decode('utf-8')}) + dict_.update({"publishdata_id": publishdata_id}) dict_.update({"timestamp": datetime.datetime.now().strftime( "%Y-%m-%d %H:%M:%S")}) f.write(json.dumps(dict_)) -- cgit v1.2.3 From 305a2fc37bc233c9842bb12346c7e476c1d81be7 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 30 Jul 2021 16:38:43 +0300 Subject: wqflask: views: Flash messages during edits --- wqflask/wqflask/templates/edit_phenotype.html | 9 +++++++++ wqflask/wqflask/views.py | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html index 83daba58..0d7071f7 100644 --- a/wqflask/wqflask/templates/edit_phenotype.html +++ b/wqflask/wqflask/templates/edit_phenotype.html @@ -2,6 +2,15 @@ {% block title %}Trait Submission{% endblock %} {% block content %} +{% with messages = get_flashed_messages(with_categories=true) %} +{% if messages %} +{% for category, message in messages %} +
    +

    {{ message }}

    +
    +{% endfor %} +{% endif %} +{% endwith %} diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 0f1df123..18d3e99f 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -546,7 +546,7 @@ def update_phenotype(): data_ = request.form.to_dict() author = g.user_session.record.get(b'user_name') if 'file' not in request.files: - flash("No sample data has been uploaded") + flash("No sample-data has been uploaded", "warning") else: file_ = request.files['file'] trait_name = str(data_.get('dataset-name')) @@ -598,6 +598,7 @@ def update_phenotype(): dict_.update({"timestamp": datetime.datetime.now().strftime( "%Y-%m-%d %H:%M:%S")}) f.write(json.dumps(dict_)) + flash("Sample-data has been successfully uploaded", "success") # Run updates: phenotype_ = { "pre_pub_description": data_.get("pre-pub-desc"), @@ -649,6 +650,7 @@ def update_phenotype(): data=MetadataAudit(dataset_id=data_.get("dataset-name"), editor=author.decode("utf-8"), json_data=json.dumps(diff_data))) + flash(f"Diff-data: \n{diff_data}\nhas been uploaded", "success") return redirect(f"/trait/{data_.get('dataset-name')}" f"/edit/phenotype-id/{data_.get('phenotype-id')}") -- cgit v1.2.3 From d9e6c4d67aa5cb18cf199f5e27fbc1ac73906c89 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 17:49:44 +0300 Subject: templates: edit_phenotype: Remove link to the approve page --- wqflask/wqflask/templates/edit_phenotype.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html index 0d7071f7..a7d6f1b0 100644 --- a/wqflask/wqflask/templates/edit_phenotype.html +++ b/wqflask/wqflask/templates/edit_phenotype.html @@ -222,9 +222,6 @@
    - - Data Approval - Sample Data(CSV Download) -- cgit v1.2.3 From b5fc6068a1dbe493a3d7b240df236ada36984570 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 17:51:35 +0300 Subject: templates: edit_phenotype: Put csv "oownload" button above "Browse" --- wqflask/wqflask/templates/edit_phenotype.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/templates/edit_phenotype.html b/wqflask/wqflask/templates/edit_phenotype.html index a7d6f1b0..7a841793 100644 --- a/wqflask/wqflask/templates/edit_phenotype.html +++ b/wqflask/wqflask/templates/edit_phenotype.html @@ -216,16 +216,15 @@
    -
    -
    -
    - +
    + +
    -- cgit v1.2.3 From 299847f03a84c5a74c51d8cb6bea6763431773c2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 21:08:31 +0300 Subject: templates: display_files: Add a a reject button --- wqflask/wqflask/templates/display_files.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/display_files.html b/wqflask/wqflask/templates/display_files.html index e7210c1b..30974300 100644 --- a/wqflask/wqflask/templates/display_files.html +++ b/wqflask/wqflask/templates/display_files.html @@ -7,7 +7,8 @@ Show files for approval
    -- cgit v1.2.3 From f175c8806b4fbf372cbe65790ce7816b98e32695 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 21:24:10 +0300 Subject: wqflask: views: Add author name to generated diff's filename --- wqflask/wqflask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 18d3e99f..ed5398a2 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -589,7 +589,7 @@ def update_phenotype(): f"'{uploaded_file_name}' '{new_file_name}' " "--format json")) diff_output = ("/tmp/sample-data/diffs/" - f"{trait_name}." + f"{trait_name}.{author.decode('utf-8')}." f"{phenotype_id}.{current_time}.json") with open(diff_output, "w") as f: dict_ = json.loads(r.get("output")) -- cgit v1.2.3 From 79d6dc5ac4610ea50e61bca80598b87d42911490 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 21:24:39 +0300 Subject: wqflask: views: Add dataset_id to diff json that'll be stored --- wqflask/wqflask/views.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index ed5398a2..11e1bfaa 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -593,10 +593,13 @@ def update_phenotype(): f"{phenotype_id}.{current_time}.json") with open(diff_output, "w") as f: dict_ = json.loads(r.get("output")) - dict_.update({"author": author.decode('utf-8')}) - dict_.update({"publishdata_id": publishdata_id}) - dict_.update({"timestamp": datetime.datetime.now().strftime( - "%Y-%m-%d %H:%M:%S")}) + dict_.update({ + "author": author.decode('utf-8'), + "publishdata_id": publishdata_id, + "dataset_id": data_.get("dataset-name"), + "timestamp": datetime.datetime.now().strftime( + "%Y-%m-%d %H:%M:%S") + }) f.write(json.dumps(dict_)) flash("Sample-data has been successfully uploaded", "success") # Run updates: -- cgit v1.2.3 From 2c0b0d7132ff67709de13d986a92293b68e275e2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 21:25:30 +0300 Subject: wqflask: views: use dataset_id when inserting into metadata_audit --- wqflask/wqflask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 11e1bfaa..760d5947 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1431,7 +1431,7 @@ def approve_data(name): insert(conn, table="metadata_audit", data=MetadataAudit( - dataset_id=sample_data.get("publishdata_id"), + dataset_id=name.split(".")[0], # use the dataset name editor=sample_data.get("author"), json_data=json.dumps(sample_data))) # Once data is approved, rename it! -- cgit v1.2.3 From e200245b2a1666b7a16f4484891c7c2b143fefe9 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 21:44:30 +0300 Subject: wqflask: views: Rename endpoint to "/admin/data-sample/diffs/" --- wqflask/wqflask/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 760d5947..1a8536ac 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1391,7 +1391,7 @@ def get_sample_data_as_csv(trait_name: int, phenotype_id: int): ) -@app.route("/data/approve/") +@app.route("/admin/data-sample/diffs/") def display_diffs_admin(): DIFF_DIR = "/tmp/sample-data/diffs" files = [] @@ -1438,7 +1438,7 @@ def approve_data(name): os.rename(os.path.join("/tmp/sample-data/diffs", name), os.path.join("/tmp/sample-data/diffs", f"{name}.approved")) - return redirect("/data/approve/") + return redirect("/admin/data-sample/diffs/") @app.route("/display-file/") -- cgit v1.2.3 From 4594743baf6e6fd4b50f68665d009084f605bc78 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 21:45:17 +0300 Subject: wqflask: views: After approval, rename the file at the right point --- wqflask/wqflask/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 1a8536ac..c7582782 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1434,10 +1434,11 @@ def approve_data(name): dataset_id=name.split(".")[0], # use the dataset name editor=sample_data.get("author"), json_data=json.dumps(sample_data))) - # Once data is approved, rename it! - os.rename(os.path.join("/tmp/sample-data/diffs", name), - os.path.join("/tmp/sample-data/diffs", - f"{name}.approved")) + if modifications: + # Once data is approved, rename it! + os.rename(os.path.join("/tmp/sample-data/diffs", name), + os.path.join("/tmp/sample-data/diffs", + f"{name}.approved")) return redirect("/admin/data-sample/diffs/") -- cgit v1.2.3 From 366c9c433a0b18db25f5cb28d7c7dc1ed17b3c67 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 21:46:07 +0300 Subject: Display a flash message after a file(and data) approval squash! Display a flash message after a file(and data) approval --- wqflask/wqflask/templates/display_files.html | 9 +++++++++ wqflask/wqflask/views.py | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/display_files.html b/wqflask/wqflask/templates/display_files.html index 30974300..f8bc188f 100644 --- a/wqflask/wqflask/templates/display_files.html +++ b/wqflask/wqflask/templates/display_files.html @@ -2,6 +2,15 @@ {% block title %}Trait Submission{% endblock %} {% block content %} +{% with messages = get_flashed_messages(with_categories=true) %} +{% if messages %} +{% for category, message in messages %} +
    +

    {{ message }}

    +
    +{% endfor %} +{% endif %} +{% endwith %} Show files for approval
    diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index c7582782..11c8b347 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1414,12 +1414,13 @@ def approve_data(name): sample_data = json.load(myfile) PUBLISH_ID = sample_data.get("publishdata_id") modifications = [d for d in sample_data.get("Modifications")] + row_counts = len(modifications) for modification in modifications: if modification.get("Current"): (strain_id, strain_name, value, se, count) = modification.get("Current").split(",") - row_counts = update_sample_data( + update_sample_data( conn=conn, strain_name=strain_name, strain_id=int(strain_id), @@ -1439,6 +1440,9 @@ def approve_data(name): os.rename(os.path.join("/tmp/sample-data/diffs", name), os.path.join("/tmp/sample-data/diffs", f"{name}.approved")) + flash((f"Just updated data from: {name}; {row_counts} " + "row(s) modified!"), + "success") return redirect("/admin/data-sample/diffs/") -- cgit v1.2.3 From 3241306917277278a218270a6691d103df6fee4e Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 21:57:41 +0300 Subject: Reject diff data on click --- wqflask/wqflask/templates/display_files.html | 3 ++- wqflask/wqflask/views.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/display_files.html b/wqflask/wqflask/templates/display_files.html index f8bc188f..4b4babc4 100644 --- a/wqflask/wqflask/templates/display_files.html +++ b/wqflask/wqflask/templates/display_files.html @@ -17,7 +17,8 @@ Show files for approval
    diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 11c8b347..69b432f5 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1446,6 +1446,15 @@ def approve_data(name): return redirect("/admin/data-sample/diffs/") +@app.route("/data-samples/reject/") +def reject_data(name): + os.rename(os.path.join("/tmp/sample-data/diffs", name), + os.path.join("/tmp/sample-data/diffs", + f"{name}.rejected")) + flash(f"{name} has been rejected!", "success") + return redirect("/admin/data-sample/diffs/") + + @app.route("/display-file/") def display_file(name): with open(os.path.join("/tmp/sample-data/diffs", name), 'r') as myfile: -- cgit v1.2.3 From fbe52a11f4471d928e398f09edc2e243bbbab58a Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 22:07:06 +0300 Subject: Rename display_files.html -> display_files_admin.html --- wqflask/wqflask/templates/display_files.html | 32 ---------------------- wqflask/wqflask/templates/display_files_admin.html | 32 ++++++++++++++++++++++ wqflask/wqflask/views.py | 3 +- 3 files changed, 34 insertions(+), 33 deletions(-) delete mode 100644 wqflask/wqflask/templates/display_files.html create mode 100644 wqflask/wqflask/templates/display_files_admin.html diff --git a/wqflask/wqflask/templates/display_files.html b/wqflask/wqflask/templates/display_files.html deleted file mode 100644 index 4b4babc4..00000000 --- a/wqflask/wqflask/templates/display_files.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "base.html" %} -{% block title %}Trait Submission{% endblock %} -{% block content %} - -{% with messages = get_flashed_messages(with_categories=true) %} -{% if messages %} -{% for category, message in messages %} -
    -

    {{ message }}

    -
    -{% endfor %} -{% endif %} -{% endwith %} -Show files for approval - -
    - -
    -{%endblock%} - -{% block js %} - -{% endblock %} diff --git a/wqflask/wqflask/templates/display_files_admin.html b/wqflask/wqflask/templates/display_files_admin.html new file mode 100644 index 00000000..4b4babc4 --- /dev/null +++ b/wqflask/wqflask/templates/display_files_admin.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} +{% block content %} + +{% with messages = get_flashed_messages(with_categories=true) %} +{% if messages %} +{% for category, message in messages %} +
    +

    {{ message }}

    +
    +{% endfor %} +{% endif %} +{% endwith %} +Show files for approval + +
    + +
    +{%endblock%} + +{% block js %} + +{% endblock %} diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 69b432f5..56f9a5eb 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1399,7 +1399,8 @@ def display_diffs_admin(): files = os.listdir(DIFF_DIR) files = filter(lambda x: not(x.endswith((".approved", ".rejected"))), files) - return render_template("display_files.html", + return render_template("display_files_admin.html", + files=files) files=files) -- cgit v1.2.3 From 72bdbc512ee250ffaa1fa0c1ba91b2a065a485d0 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 22:08:25 +0300 Subject: wqflask: views: Only show user's diff --- wqflask/wqflask/templates/display_files_user.html | 31 +++++++++++++++++++++++ wqflask/wqflask/views.py | 14 ++++++++++ 2 files changed, 45 insertions(+) create mode 100644 wqflask/wqflask/templates/display_files_user.html diff --git a/wqflask/wqflask/templates/display_files_user.html b/wqflask/wqflask/templates/display_files_user.html new file mode 100644 index 00000000..b6bab709 --- /dev/null +++ b/wqflask/wqflask/templates/display_files_user.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} +{% block title %}Trait Submission{% endblock %} +{% block content %} + +{% with messages = get_flashed_messages(with_categories=true) %} +{% if messages %} +{% for category, message in messages %} +
    +

    {{ message }}

    +
    +{% endfor %} +{% endif %} +{% endwith %} +Show files for approval + +
    + +
    +{%endblock%} + +{% block js %} + +{% endblock %} diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 56f9a5eb..78da024f 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1392,6 +1392,7 @@ def get_sample_data_as_csv(trait_name: int, phenotype_id: int): @app.route("/admin/data-sample/diffs/") +@admin_login_required def display_diffs_admin(): DIFF_DIR = "/tmp/sample-data/diffs" files = [] @@ -1401,6 +1402,19 @@ def display_diffs_admin(): files) return render_template("display_files_admin.html", files=files) + + +@app.route("/user/data-sample/diffs/") +def display_diffs_users(): + DIFF_DIR = "/tmp/sample-data/diffs" + files = [] + author = g.user_session.record.get(b'user_name').decode("utf-8") + if os.path.exists(DIFF_DIR): + files = os.listdir(DIFF_DIR) + files = filter(lambda x: not(x.endswith((".approved", ".rejected"))) \ + and author in x, + files) + return render_template("display_files_user.html", files=files) -- cgit v1.2.3 From cbe5d6edacafe7f1afba49e20c644a34fa9aaf7d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 22:19:57 +0300 Subject: Replace TMPDIR hard-coded val with one set from flask --- wqflask/wqflask/views.py | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 78da024f..2f704ac9 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -544,6 +544,7 @@ def update_phenotype(): passwd=current_app.config.get("DB_PASS"), host=current_app.config.get("DB_HOST")) data_ = request.form.to_dict() + TMPDIR = current_app.config.get("TMPDIR") author = g.user_session.record.get(b'user_name') if 'file' not in request.files: flash("No sample-data has been uploaded", "warning") @@ -551,7 +552,7 @@ def update_phenotype(): file_ = request.files['file'] trait_name = str(data_.get('dataset-name')) phenotype_id = str(data_.get('phenotype-id', 35)) - SAMPLE_DATADIR = "/tmp/sample-data/" + SAMPLE_DATADIR = os.path.join(TMPDIR, "sample-data") if not os.path.exists(SAMPLE_DATADIR): os.makedirs(SAMPLE_DATADIR) if not os.path.exists(os.path.join(SAMPLE_DATADIR, @@ -563,14 +564,17 @@ def update_phenotype(): os.makedirs(os.path.join(SAMPLE_DATADIR, "updated")) current_time = str(datetime.datetime.now().isoformat()) - new_file_name = ("/tmp/sample-data/updated/" - f"{author.decode('utf-8')}." - f"{trait_name}.{phenotype_id}." - f"{current_time}.csv") - uploaded_file_name = ("/tmp/sample-data/updated/" - f"updated.{author.decode('utf-8')}." - f"{trait_name}.{phenotype_id}." - f"{current_time}.csv") + new_file_name = (os.path.join(TMPDIR, + "sample-data/updated/", + (f"{author.decode('utf-8')}." + f"{trait_name}.{phenotype_id}." + f"{current_time}.csv"))) + uploaded_file_name = (os.path.join( + TMPDIR, + "sample-data/updated/", + (f"updated.{author.decode('utf-8')}." + f"{trait_name}.{phenotype_id}." + f"{current_time}.csv"))) file_.save(new_file_name) publishdata_id = "" lines = [] @@ -1425,7 +1429,9 @@ def approve_data(name): user=current_app.config.get("DB_USER"), passwd=current_app.config.get("DB_PASS"), host=current_app.config.get("DB_HOST")) - with open(os.path.join("/tmp/sample-data/diffs", name), 'r') as myfile: + TMPDIR = current_app.config.get("TMPDIR") + with open(os.path.join(f"{TMPDIR}/sample-data/diffs", + name), 'r') as myfile: sample_data = json.load(myfile) PUBLISH_ID = sample_data.get("publishdata_id") modifications = [d for d in sample_data.get("Modifications")] @@ -1452,8 +1458,8 @@ def approve_data(name): json_data=json.dumps(sample_data))) if modifications: # Once data is approved, rename it! - os.rename(os.path.join("/tmp/sample-data/diffs", name), - os.path.join("/tmp/sample-data/diffs", + os.rename(os.path.join(f"{TMPDIR}/sample-data/diffs", name), + os.path.join(f"{TMPDIR}/sample-data/diffs", f"{name}.approved")) flash((f"Just updated data from: {name}; {row_counts} " "row(s) modified!"), @@ -1463,8 +1469,9 @@ def approve_data(name): @app.route("/data-samples/reject/") def reject_data(name): - os.rename(os.path.join("/tmp/sample-data/diffs", name), - os.path.join("/tmp/sample-data/diffs", + TMPDIR = current_app.config.get("TMPDIR") + os.rename(os.path.join(f"{TMPDIR}/sample-data/diffs", name), + os.path.join(f"{TMPDIR}/sample-data/diffs", f"{name}.rejected")) flash(f"{name} has been rejected!", "success") return redirect("/admin/data-sample/diffs/") @@ -1472,6 +1479,8 @@ def reject_data(name): @app.route("/display-file/") def display_file(name): - with open(os.path.join("/tmp/sample-data/diffs", name), 'r') as myfile: + TMPDIR = current_app.config.get("TMPDIR") + with open(os.path.join(f"{TMPDIR}/sample-data/diffs", + name), 'r') as myfile: content = myfile.read() return Response(content, mimetype='text/json') -- cgit v1.2.3 From 759b04823818cd09f5e8e78299f270b3f7b73042 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 2 Aug 2021 22:28:00 +0300 Subject: Remove hard coded PATH for csvdiff binary --- wqflask/wqflask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 2f704ac9..a40e0d44 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -589,7 +589,7 @@ def update_phenotype(): phenotype_id=str(phenotype_id)) with open(uploaded_file_name, "w") as f_: f_.write(csv_.split("\n\n")[-1]) - r = run_cmd(cmd=("/home/bonface/opt/genenetwork3/bin/csvdiff " + r = run_cmd(cmd=("csvdiff " f"'{uploaded_file_name}' '{new_file_name}' " "--format json")) diff_output = ("/tmp/sample-data/diffs/" -- cgit v1.2.3 From a24ea03594d6879adf92cd4db2b52108ddb352bd Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 4 Aug 2021 14:51:20 +0300 Subject: wqflask: views: Replace hard-coded "/tmp" dir --- wqflask/wqflask/views.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index a40e0d44..fe81bb6c 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -592,7 +592,7 @@ def update_phenotype(): r = run_cmd(cmd=("csvdiff " f"'{uploaded_file_name}' '{new_file_name}' " "--format json")) - diff_output = ("/tmp/sample-data/diffs/" + diff_output = (f"{TMPDIR}/sample-data/diffs/" f"{trait_name}.{author.decode('utf-8')}." f"{phenotype_id}.{current_time}.json") with open(diff_output, "w") as f: @@ -1398,7 +1398,8 @@ def get_sample_data_as_csv(trait_name: int, phenotype_id: int): @app.route("/admin/data-sample/diffs/") @admin_login_required def display_diffs_admin(): - DIFF_DIR = "/tmp/sample-data/diffs" + TMPDIR = current_app.config.get("TMPDIR") + DIFF_DIR = f"{TMPDIR}/sample-data/diffs" files = [] if os.path.exists(DIFF_DIR): files = os.listdir(DIFF_DIR) @@ -1410,7 +1411,8 @@ def display_diffs_admin(): @app.route("/user/data-sample/diffs/") def display_diffs_users(): - DIFF_DIR = "/tmp/sample-data/diffs" + TMPDIR = current_app.config.get("TMPDIR") + DIFF_DIR = f"{TMPDIR}/sample-data/diffs" files = [] author = g.user_session.record.get(b'user_name').decode("utf-8") if os.path.exists(DIFF_DIR): -- cgit v1.2.3 From 457296f004e576a632d2e77c656d41d9ceed0c47 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 4 Aug 2021 16:28:34 +0300 Subject: wqflask: views: Remove unnecessary casting for "count" Sometimes, the "count" value can be an "x", as opposed to an int. With the cast, an un-handled error could be thrown. --- wqflask/wqflask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index fe81bb6c..76fe029f 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1450,7 +1450,7 @@ def approve_data(name): publish_data_id=int(PUBLISH_ID), value=value, error=se, - count=int(count) + count=count ) insert(conn, table="metadata_audit", -- cgit v1.2.3 From b8c88668dbf1bc96c2faf14c483e3af35babc3e5 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 5 Aug 2021 20:51:46 +0000 Subject: Generate phenotype and covariate filenames for GEMMA from a hash of values and dataset name - previously the covariate filename was static and the phenotype filename was just random --- wqflask/wqflask/marker_regression/gemma_mapping.py | 35 ++++++++++++---------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/wqflask/wqflask/marker_regression/gemma_mapping.py b/wqflask/wqflask/marker_regression/gemma_mapping.py index f88c5ac8..efd8bba8 100644 --- a/wqflask/wqflask/marker_regression/gemma_mapping.py +++ b/wqflask/wqflask/marker_regression/gemma_mapping.py @@ -11,6 +11,7 @@ from utility.tools import flat_files from utility.tools import GEMMA_WRAPPER_COMMAND from utility.tools import TEMPDIR from utility.tools import WEBSERVER_MODE +from gn3.computations.gemma import generate_hash_of_string import utility.logger logger = utility.logger.getLogger(__name__) @@ -34,10 +35,7 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco, genofile_name = this_dataset.group.name if first_run: - trait_filename = (f"{str(this_dataset.group.name)}_" - f"{str(this_trait.name)}_" - f"{generate_random_n_string(6)}") - gen_pheno_txt_file(this_dataset, genofile_name, vals, trait_filename) + pheno_filename = gen_pheno_txt_file(this_dataset, genofile_name, vals) if not os.path.isfile(f"{webqtlConfig.GENERATED_IMAGE_DIR}" f"{genofile_name}_output.assoc.txt"): @@ -56,13 +54,13 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco, chr_list_string = ",".join(this_chromosomes_name) if covariates != "": - gen_covariates_file(this_dataset, covariates, samples) + covar_filename = gen_covariates_file(this_dataset, covariates, samples) if use_loco == "True": generate_k_command = (f"{GEMMA_WRAPPER_COMMAND} --json --loco " f"{chr_list_string} -- {GEMMAOPTS} " f"-g {flat_files('genotype/bimbam')}/" f"{genofile_name}_geno.txt -p " - f"{TEMPDIR}/gn2/{trait_filename}.txt -a " + f"{TEMPDIR}/gn2/{pheno_filename}.txt -a " f"{flat_files('genotype/bimbam')}/" f"{genofile_name}_snps.txt -gk > " f"{TEMPDIR}/gn2/{k_output_filename}.json") @@ -73,10 +71,10 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco, f"-- {GEMMAOPTS} " f"-g {flat_files('genotype/bimbam')}/" f"{genofile_name}_geno.txt " - f"-p {TEMPDIR}/gn2/{trait_filename}.txt ") + f"-p {TEMPDIR}/gn2/{pheno_filename}.txt ") if covariates != "": gemma_command += (f"-c {flat_files('mapping')}/" - f"{this_dataset.group.name}_covariates.txt " + f"{covar_filename}.txt " f"-a {flat_files('genotype/bimbam')}/" f"{genofile_name}_snps.txt " f"-lmm 9 -maf {maf} > {TEMPDIR}/gn2/" @@ -92,7 +90,7 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco, f"{GEMMAOPTS} " f" -g {flat_files('genotype/bimbam')}/" f"{genofile_name}_geno.txt -p " - f"{TEMPDIR}/gn2/{trait_filename}.txt -a " + f"{TEMPDIR}/gn2/{pheno_filename}.txt -a " f"{flat_files('genotype/bimbam')}/" f"{genofile_name}_snps.txt -gk > " f"{TEMPDIR}/gn2/{k_output_filename}.json") @@ -106,12 +104,11 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco, f"{genofile_name}_snps.txt " f"-lmm 9 -g {flat_files('genotype/bimbam')}/" f"{genofile_name}_geno.txt -p " - f"{TEMPDIR}/gn2/{trait_filename}.txt ") + f"{TEMPDIR}/gn2/{pheno_filename}.txt ") if covariates != "": gemma_command += (f" -c {flat_files('mapping')}/" - f"{this_dataset.group.name}" - f"_covariates.txt > " + f"{covar_filename}.txt > " f"{TEMPDIR}/gn2/{gwa_output_filename}.json") else: gemma_command += f" > {TEMPDIR}/gn2/{gwa_output_filename}.json" @@ -129,16 +126,20 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco, return marker_obs, gwa_output_filename -def gen_pheno_txt_file(this_dataset, genofile_name, vals, trait_filename): +def gen_pheno_txt_file(this_dataset, genofile_name, vals): """Generates phenotype file for GEMMA""" - with open(f"{TEMPDIR}/gn2/{trait_filename}.txt", "w") as outfile: + filename = "PHENO_" + generate_hash_of_string(this_dataset.name + str(vals)) + + with open(f"{TEMPDIR}/gn2/{filename}.txt", "w") as outfile: for value in vals: if value == "x": outfile.write("NA\n") else: outfile.write(value + "\n") + return filename + def gen_covariates_file(this_dataset, covariates, samples): covariate_list = covariates.split(",") @@ -168,14 +169,18 @@ def gen_covariates_file(this_dataset, covariates, samples): this_covariate_data.append("-9") covariate_data_object.append(this_covariate_data) + filename = "COVAR_" + generate_hash_of_string(this_dataset.name + str(covariate_data_object)) + with open((f"{flat_files('mapping')}/" - f"{this_dataset.group.name}_covariates.txt"), + f"{filename}.txt"), "w") as outfile: for i in range(len(covariate_data_object[0])): for this_covariate in covariate_data_object: outfile.write(str(this_covariate[i]) + "\t") outfile.write("\n") + return filename + def parse_loco_output(this_dataset, gwa_output_filename, loco="True"): -- cgit v1.2.3 From d29dfb72ceb005ab045203a36b1fc1552544b1e2 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Fri, 6 Aug 2021 12:56:07 +0200 Subject: gunicorn production settings: increase process refresh rate to clean up RAM. Also use the jitter switch. --- bin/genenetwork2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/genenetwork2 b/bin/genenetwork2 index 5f4e0f9a..2b94b2a2 100755 --- a/bin/genenetwork2 +++ b/bin/genenetwork2 @@ -209,7 +209,7 @@ if [ "$1" = '-gunicorn-prod' ] ; then echo PYTHONPATH=$PYTHONPATH if [ -z $SERVER_PORT ]; then echo "ERROR: Provide a SERVER_PORT" ; exit 1 ; fi PID=$TMPDIR/gunicorn.$USER.pid - cmd="--bind 0.0.0.0:$SERVER_PORT --pid $PID --workers 20 --keep-alive 6000 --max-requests 1000 --timeout 1200 wsgi" + cmd="--bind 0.0.0.0:$SERVER_PORT --pid $PID --workers 20 --keep-alive 6000 --max-requests 100 --max-requests-jitter 30 --timeout 1200 wsgi" echo RUNNING gunicorn $cmd gunicorn $cmd exit $? -- cgit v1.2.3 From db0bdf52156b4f75402a7a92584d162aa5d9b151 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 9 Aug 2021 18:34:31 +0000 Subject: Removed some paths that have been throwing errors in the logs constantly --- wqflask/wqflask/static/new/css/bootstrap-custom.css | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wqflask/wqflask/static/new/css/bootstrap-custom.css b/wqflask/wqflask/static/new/css/bootstrap-custom.css index 7c8549e1..a0d3ff6a 100644 --- a/wqflask/wqflask/static/new/css/bootstrap-custom.css +++ b/wqflask/wqflask/static/new/css/bootstrap-custom.css @@ -327,7 +327,7 @@ th { font-family: 'Glyphicons Halflings'; src: url('../fonts/glyphicons-halflings-regular.eot'); - src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); } .glyphicon { @@ -7554,5 +7554,3 @@ button.close { display: none !important; } } - -/*# sourceMappingURL=bootstrap.css.map */ \ No newline at end of file -- cgit v1.2.3 From 162851e8f92ed6d01c25d4f6034bcac4986e8094 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 10 Aug 2021 19:01:47 +0000 Subject: Replaced forward slashes with underscores in the hashed filenames used by GEMMA, since the forward slashes make the paths not work properly --- wqflask/wqflask/marker_regression/gemma_mapping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/marker_regression/gemma_mapping.py b/wqflask/wqflask/marker_regression/gemma_mapping.py index efd8bba8..623ab87f 100644 --- a/wqflask/wqflask/marker_regression/gemma_mapping.py +++ b/wqflask/wqflask/marker_regression/gemma_mapping.py @@ -129,7 +129,7 @@ def run_gemma(this_trait, this_dataset, samples, vals, covariates, use_loco, def gen_pheno_txt_file(this_dataset, genofile_name, vals): """Generates phenotype file for GEMMA""" - filename = "PHENO_" + generate_hash_of_string(this_dataset.name + str(vals)) + filename = "PHENO_" + generate_hash_of_string(this_dataset.name + str(vals)).replace("/", "_") with open(f"{TEMPDIR}/gn2/{filename}.txt", "w") as outfile: for value in vals: @@ -169,7 +169,7 @@ def gen_covariates_file(this_dataset, covariates, samples): this_covariate_data.append("-9") covariate_data_object.append(this_covariate_data) - filename = "COVAR_" + generate_hash_of_string(this_dataset.name + str(covariate_data_object)) + filename = "COVAR_" + generate_hash_of_string(this_dataset.name + str(covariate_data_object)).replace("/", "_") with open((f"{flat_files('mapping')}/" f"{filename}.txt"), -- cgit v1.2.3 From 10ef0e532324e838c7be5a0f7f451dcaec9c2ce8 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 10 Aug 2021 20:19:27 +0000 Subject: Fixed issue that caused correlations to not work with temp traits after switching to using the GN3 code --- wqflask/wqflask/correlation/correlation_gn3_api.py | 7 +++++-- wqflask/wqflask/correlation/show_corr_results.py | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/correlation/correlation_gn3_api.py b/wqflask/wqflask/correlation/correlation_gn3_api.py index c96da8ee..f7c06a46 100644 --- a/wqflask/wqflask/correlation/correlation_gn3_api.py +++ b/wqflask/wqflask/correlation/correlation_gn3_api.py @@ -18,7 +18,10 @@ from gn3.db_utils import database_connector def create_target_this_trait(start_vars): """this function creates the required trait and target dataset for correlation""" - this_dataset = data_set.create_dataset(dataset_name=start_vars['dataset']) + if start_vars['dataset'] == "Temp": + this_dataset = data_set.create_dataset(dataset_name="Temp", dataset_type="Temp", group_name=start_vars['group']) + else: + this_dataset = data_set.create_dataset(dataset_name=start_vars['dataset']) target_dataset = data_set.create_dataset( dataset_name=start_vars['corr_dataset']) this_trait = create_trait(dataset=this_dataset, @@ -306,4 +309,4 @@ def get_tissue_correlation_input(this_trait, trait_symbol_dict): "trait_symbol_dict": trait_symbol_dict, "symbol_tissue_vals_dict": corr_result_tissue_vals_dict } - return (primary_tissue_data, target_tissue_data) \ No newline at end of file + return (primary_tissue_data, target_tissue_data) diff --git a/wqflask/wqflask/correlation/show_corr_results.py b/wqflask/wqflask/correlation/show_corr_results.py index e2daa991..d73965da 100644 --- a/wqflask/wqflask/correlation/show_corr_results.py +++ b/wqflask/wqflask/correlation/show_corr_results.py @@ -30,7 +30,10 @@ def set_template_vars(start_vars, correlation_data): corr_type = start_vars['corr_type'] corr_method = start_vars['corr_sample_method'] - this_dataset_ob = create_dataset(dataset_name=start_vars['dataset']) + if start_vars['dataset'] == "Temp": + this_dataset_ob = create_dataset(dataset_name="Temp", dataset_type="Temp", group_name=start_vars['group']) + else: + this_dataset_ob = create_dataset(dataset_name=start_vars['dataset']) this_trait = create_trait(dataset=this_dataset_ob, name=start_vars['trait_id']) -- cgit v1.2.3 From 2dce5e20b5dcfa032a32a486bb4e4a4390c5f2b7 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 10 Aug 2021 20:44:13 +0000 Subject: Fixed the way n_samples is calculated for the loading page and mapping figure; previously it would includes parents/f1s --- wqflask/wqflask/views.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 76fe029f..44560427 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1033,7 +1033,7 @@ def loading_page(): start_vars['dataset'], group_name=start_vars['group']) else: dataset = create_dataset(start_vars['dataset']) - samples = start_vars['primary_samples'].split(",") + samples = dataset.group.samplelist if 'genofile' in start_vars: if start_vars['genofile'] != "": genofile_string = start_vars['genofile'] @@ -1178,9 +1178,8 @@ def mapping_results_page(): gn1_template_vars = display_mapping_results.DisplayMappingResults( result).__dict__ - with Bench("Rendering template"): - rendered_template = render_template( - "mapping_results.html", **gn1_template_vars) + rendered_template = render_template( + "mapping_results.html", **gn1_template_vars) return rendered_template -- cgit v1.2.3 From 9a1e7270bc9c2130a4cb9405f2de24053dea6582 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 11 Aug 2021 20:26:45 +0000 Subject: Sort by attribute ID instead of name in initialize_show_trait_tables.js when defining attribute columns --- wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js b/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js index 6ca92fb6..49311f87 100644 --- a/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js +++ b/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js @@ -93,7 +93,7 @@ build_columns = function() { ); } - attr_keys = Object.keys(js_data.attributes).sort((a, b) => (js_data.attributes[a].name.toLowerCase() > js_data.attributes[b].name.toLowerCase()) ? 1 : -1) + attr_keys = Object.keys(js_data.attributes).sort((a, b) => (js_data.attributes[a].id > js_data.attributes[b].id) ? 1 : -1) for (i = 0; i < attr_keys.length; i++){ column_list.push( { @@ -101,7 +101,7 @@ build_columns = function() { 'type': "natural", 'data': null, 'render': function(data, type, row, meta) { - attr_name = Object.keys(data.extra_attributes).sort()[meta.col - data.first_attr_col] + attr_name = Object.keys(data.extra_attributes).sort((a, b) => (parseInt(a) > parseInt(b)) ? 1 : -1)[meta.col - data.first_attr_col] if (attr_name != null && attr_name != undefined){ if (Array.isArray(data.extra_attributes[attr_name])){ -- cgit v1.2.3 From 67bab7f9cd12ceb7ea0b0ac4b0b98c24935f3ab5 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 11 Aug 2021 20:28:35 +0000 Subject: Fixed issue that caused case attribute columns to become unaligned if some strains didn't have values for all case attributes + changed ordering to use attribute ID instead of name --- wqflask/wqflask/show_trait/SampleList.py | 44 ++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/wqflask/wqflask/show_trait/SampleList.py b/wqflask/wqflask/show_trait/SampleList.py index 92cea550..e4bbe5b9 100644 --- a/wqflask/wqflask/show_trait/SampleList.py +++ b/wqflask/wqflask/show_trait/SampleList.py @@ -32,7 +32,7 @@ class SampleList: for counter, sample_name in enumerate(sample_names, 1): sample_name = sample_name.replace("_2nd_", "") - # ZS: self.this_trait will be a list if it is a Temp trait + # self.this_trait will be a list if it is a Temp trait if isinstance(self.this_trait, list): sample = webqtlCaseData.webqtlCaseData(name=sample_name) if counter <= len(self.this_trait): @@ -47,7 +47,7 @@ class SampleList: name=sample_name, value=float(self.this_trait[counter - 1])) else: - # ZS - If there's no value for the sample/strain, + # If there's no value for the sample/strain, # create the sample object (so samples with no value # are still displayed in the table) try: @@ -63,29 +63,29 @@ class SampleList: sample.this_id = str(counter) - # ZS: For extra attribute columns; currently only used by + # For extra attribute columns; currently only used by # several datasets if self.sample_attribute_values: sample.extra_attributes = self.sample_attribute_values.get( sample_name, {}) - # ZS: Add a url so RRID case attributes can be displayed as links - if 'rrid' in sample.extra_attributes: + # Add a url so RRID case attributes can be displayed as links + if '36' in sample.extra_attributes: if self.dataset.group.species == "mouse": - if len(sample.extra_attributes['rrid'].split(":")) > 1: - the_rrid = sample.extra_attributes['rrid'].split(":")[ + if len(sample.extra_attributes['36'].split(":")) > 1: + the_rrid = sample.extra_attributes['36'].split(":")[ 1] - sample.extra_attributes['rrid'] = [ - sample.extra_attributes['rrid']] - sample.extra_attributes['rrid'].append( + sample.extra_attributes['36'] = [ + sample.extra_attributes['36']] + sample.extra_attributes['36'].append( webqtlConfig.RRID_MOUSE_URL % the_rrid) elif self.dataset.group.species == "rat": - if len(str(sample.extra_attributes['rrid'])): - the_rrid = sample.extra_attributes['rrid'].split("_")[ + if len(str(sample.extra_attributes['36'])): + the_rrid = sample.extra_attributes['36'].split("_")[ 1] - sample.extra_attributes['rrid'] = [ - sample.extra_attributes['rrid']] - sample.extra_attributes['rrid'].append( + sample.extra_attributes['36'] = [ + sample.extra_attributes['36']] + sample.extra_attributes['36'].append( webqtlConfig.RRID_RAT_URL % the_rrid) self.sample_list.append(sample) @@ -128,12 +128,13 @@ class SampleList: FROM CaseAttribute, CaseAttributeXRefNew WHERE CaseAttributeXRefNew.CaseAttributeId = CaseAttribute.Id AND CaseAttributeXRefNew.InbredSetId = %s - ORDER BY lower(CaseAttribute.Name)''', (str(self.dataset.group.id),)) + ORDER BY CaseAttribute.Id''', (str(self.dataset.group.id),)) self.attributes = {} for attr, values in itertools.groupby(results.fetchall(), lambda row: (row.Id, row.Name)): key, name = attr self.attributes[key] = Bunch() + self.attributes[key].id = key self.attributes[key].name = name self.attributes[key].distinct_values = [ item.Value for item in values] @@ -168,10 +169,13 @@ class SampleList: for sample_name, items in itertools.groupby(results.fetchall(), lambda row: row.SampleName): attribute_values = {} + # Make a list of attr IDs without values (that have values for other samples) + valueless_attr_ids = [self.attributes[key].id for key in self.attributes.keys()] for item in items: + valueless_attr_ids.remove(item.Id) attribute_value = item.Value - # ZS: If it's an int, turn it into one for sorting + # If it's an int, turn it into one for sorting # (for example, 101 would be lower than 80 if # they're strings instead of ints) try: @@ -179,8 +183,10 @@ class SampleList: except ValueError: pass - attribute_values[self.attributes[item.Id].name.lower( - )] = attribute_value + attribute_values[str(item.Id)] = attribute_value + for attr_id in valueless_attr_ids: + attribute_values[str(attr_id)] = "" + self.sample_attribute_values[sample_name] = attribute_values def get_first_attr_col(self): -- cgit v1.2.3 From eecaad2dac6efa885540287a7ef5a273c6d18be2 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 12 Aug 2021 21:55:26 +0000 Subject: Get case attribute descriptions from DB and display them as mouseover titles in the sample table headers for the trait page --- wqflask/wqflask/show_trait/SampleList.py | 7 ++++--- .../wqflask/static/new/javascript/initialize_show_trait_tables.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/show_trait/SampleList.py b/wqflask/wqflask/show_trait/SampleList.py index e4bbe5b9..ae30aa59 100644 --- a/wqflask/wqflask/show_trait/SampleList.py +++ b/wqflask/wqflask/show_trait/SampleList.py @@ -124,18 +124,19 @@ class SampleList: # Get attribute names and distinct values for each attribute results = g.db.execute(''' - SELECT DISTINCT CaseAttribute.Id, CaseAttribute.Name, CaseAttributeXRefNew.Value + SELECT DISTINCT CaseAttribute.Id, CaseAttribute.Name, CaseAttribute.Description, CaseAttributeXRefNew.Value FROM CaseAttribute, CaseAttributeXRefNew WHERE CaseAttributeXRefNew.CaseAttributeId = CaseAttribute.Id AND CaseAttributeXRefNew.InbredSetId = %s ORDER BY CaseAttribute.Id''', (str(self.dataset.group.id),)) self.attributes = {} - for attr, values in itertools.groupby(results.fetchall(), lambda row: (row.Id, row.Name)): - key, name = attr + for attr, values in itertools.groupby(results.fetchall(), lambda row: (row.Id, row.Name, row.Description)): + key, name, description = attr self.attributes[key] = Bunch() self.attributes[key].id = key self.attributes[key].name = name + self.attributes[key].description = description self.attributes[key].distinct_values = [ item.Value for item in values] self.attributes[key].distinct_values = natural_sort( diff --git a/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js b/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js index 49311f87..4de1b0ac 100644 --- a/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js +++ b/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js @@ -97,7 +97,7 @@ build_columns = function() { for (i = 0; i < attr_keys.length; i++){ column_list.push( { - 'title': "
    " + js_data.attributes[attr_keys[i]].name + "
    ", + 'title': "
    " + js_data.attributes[attr_keys[i]].name + "
    ", 'type': "natural", 'data': null, 'render': function(data, type, row, meta) { -- cgit v1.2.3 From f4fbb6d53419a19c6ee67977d18605cdcbb09c0e Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 12 Aug 2021 22:35:53 +0000 Subject: add function for reading in JSON file that lists sample lists unique to each study within a group (in this case only BXD Longevity for now) --- wqflask/base/data_set.py | 9 +++++++++ wqflask/wqflask/show_trait/show_trait.py | 1 + 2 files changed, 10 insertions(+) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 4cb82665..b8f2f9fb 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -398,6 +398,15 @@ class DatasetGroup: if maternal and paternal: self.parlist = [maternal, paternal] + def get_study_samplelists(self): + study_sample_file = "%s/study_sample_lists/%s.json" % (webqtlConfig.GENODIR, self.name) + try: + f = open(study_sample_file) + except: + return None + study_samples = json.load(f) + return study_samples + def get_genofiles(self): jsonfile = "%s/%s.json" % (webqtlConfig.GENODIR, self.name) try: diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index c07430dd..d3356bc3 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -192,6 +192,7 @@ class ShowTrait: [self.dataset.species.chromosomes.chromosomes[this_chr].name, i]) self.genofiles = self.dataset.group.get_genofiles() + self.study_samplelists = self.dataset.group.get_study_samplelists() # ZS: No need to grab scales from .geno file unless it's using # a mapping method that reads .geno files -- cgit v1.2.3 From 308860f05b87e5fc3899230b7312be9543e6c299 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 12 Aug 2021 23:16:02 +0000 Subject: Add option to filter samples by study to template --- .../templates/show_trait_transform_and_filter.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/wqflask/wqflask/templates/show_trait_transform_and_filter.html b/wqflask/wqflask/templates/show_trait_transform_and_filter.html index 20f78b48..56c3c30e 100644 --- a/wqflask/wqflask/templates/show_trait_transform_and_filter.html +++ b/wqflask/wqflask/templates/show_trait_transform_and_filter.html @@ -45,6 +45,25 @@
    {% endif %} + {% if study_samplelists|length > 0 %} +
    + + + + +
    + {% endif %}
    {% if (numerical_var_list|length > 0) or js_data.se_exists %} -- cgit v1.2.3 From 98683bd5cc809aa03e0bd58a67733498b4f56a9d Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 12 Aug 2021 23:17:04 +0000 Subject: Fix the way the study_sample_lists path is set and checked --- wqflask/base/data_set.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index b8f2f9fb..1042e1bd 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -399,7 +399,7 @@ class DatasetGroup: self.parlist = [maternal, paternal] def get_study_samplelists(self): - study_sample_file = "%s/study_sample_lists/%s.json" % (webqtlConfig.GENODIR, self.name) + study_sample_file = locate_ignore_error(self.name + ".json", 'study_sample_lists') try: f = open(study_sample_file) except: -- cgit v1.2.3 From 810b2ace0a9cb2511cf0ef6f0c01f70a0ce11915 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 12 Aug 2021 23:19:38 +0000 Subject: Return empty list instead of None in get_study_samplelists --- wqflask/base/data_set.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 1042e1bd..0ea61faa 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -403,7 +403,7 @@ class DatasetGroup: try: f = open(study_sample_file) except: - return None + return [] study_samples = json.load(f) return study_samples -- cgit v1.2.3 From 11d5e9ffbf48c6a2fa7a59f10a9d7fe44cee0c23 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 12 Aug 2021 23:20:28 +0000 Subject: Get list of study titles to use for the dropdown menu and store the full list of study samplelists as a hddn input (don't like this, but no easy alternative right now) --- wqflask/wqflask/show_trait/show_trait.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index d3356bc3..bc97d417 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -192,7 +192,8 @@ class ShowTrait: [self.dataset.species.chromosomes.chromosomes[this_chr].name, i]) self.genofiles = self.dataset.group.get_genofiles() - self.study_samplelists = self.dataset.group.get_study_samplelists() + study_samplelist_json = self.dataset.group.get_study_samplelists() + self.study_samplelists = [study["title"] for study in study_samplelist_json] # ZS: No need to grab scales from .geno file unless it's using # a mapping method that reads .geno files @@ -281,6 +282,7 @@ class ShowTrait: hddn['selected_chr'] = -1 hddn['mapping_display_all'] = True hddn['suggestive'] = 0 + hddn['study_samplelists'] = json.dumps(study_samplelist_json) hddn['num_perm'] = 0 hddn['categorical_vars'] = "" if categorical_var_list: -- cgit v1.2.3 From 88c9f4f9bd2adca23052fc3f5c5b965accf6722d Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 12 Aug 2021 23:20:52 +0000 Subject: Add JS for filtering sample table rows by study samplelist --- wqflask/wqflask/static/new/javascript/show_trait.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/static/new/javascript/show_trait.js b/wqflask/wqflask/static/new/javascript/show_trait.js index 77ef1720..3957175b 100644 --- a/wqflask/wqflask/static/new/javascript/show_trait.js +++ b/wqflask/wqflask/static/new/javascript/show_trait.js @@ -713,10 +713,24 @@ block_by_index = function() { for (_k = 0, _len1 = index_list.length; _k < _len1; _k++) { index = index_list[_k]; val_nodes[index - 1].childNodes[0].value = "x"; - } }; +filter_by_study = function() { + let this_study = $('#filter_study').val(); + let block_group = $('#filter_study_group').val(); + let study_sample_data = JSON.parse($('input[name=study_samplelists]').val()) + let filter_samples = study_sample_data[parseInt(this_study)]['samples'] + let sample_nodes = table_api.column(2).nodes().to$(); + let val_nodes = table_api.column(3).nodes().to$(); + for (i = 0; i < sample_nodes.length; i++) { + this_sample = sample_nodes[i].childNodes[0].innerText; + if (!filter_samples.includes(this_sample)){ + val_nodes[i].childNodes[0].value = "x"; + } + } +} + filter_by_value = function() { let filter_logic = $('#filter_logic').val(); let filter_column = $('#filter_column').val(); @@ -1690,6 +1704,11 @@ $('#block_by_index').click(function(){ edit_data_change(); }); +$('#filter_by_study').click(function(){ + filter_by_study(); + edit_data_change(); +}) + $('#filter_by_value').click(function(){ filter_by_value(); edit_data_change(); -- cgit v1.2.3 From 9ae4c8d0370137c3a698d57566305d815929d4ce Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 12 Aug 2021 23:28:08 +0000 Subject: Fixed issue where 1 was added to the loop.index instead of subtracted + only show the sample group selection if there are more than one sample groups --- wqflask/wqflask/templates/show_trait_transform_and_filter.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/show_trait_transform_and_filter.html b/wqflask/wqflask/templates/show_trait_transform_and_filter.html index 56c3c30e..064fd3e5 100644 --- a/wqflask/wqflask/templates/show_trait_transform_and_filter.html +++ b/wqflask/wqflask/templates/show_trait_transform_and_filter.html @@ -50,9 +50,10 @@ + {% if sample_groups|length != 1 %} + {% endif %}
    {% endif %} -- cgit v1.2.3 From 6acafff1b22089916b8323afb872e2ebed37943a Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 12 Aug 2021 23:28:49 +0000 Subject: Account for user selecting one of the two sample groups if more than one exist --- wqflask/wqflask/static/new/javascript/show_trait.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/static/new/javascript/show_trait.js b/wqflask/wqflask/static/new/javascript/show_trait.js index 3957175b..845ed907 100644 --- a/wqflask/wqflask/static/new/javascript/show_trait.js +++ b/wqflask/wqflask/static/new/javascript/show_trait.js @@ -718,9 +718,19 @@ block_by_index = function() { filter_by_study = function() { let this_study = $('#filter_study').val(); - let block_group = $('#filter_study_group').val(); + let study_sample_data = JSON.parse($('input[name=study_samplelists]').val()) let filter_samples = study_sample_data[parseInt(this_study)]['samples'] + + if ($('#filter_study_group').length){ + let block_group = $('#filter_study_group').val(); + if (block_group === "other") { + table_api = $('#samples_other').DataTable(); + } else { + table_api = $('#samples_primary').DataTable(); + } + } + let sample_nodes = table_api.column(2).nodes().to$(); let val_nodes = table_api.column(3).nodes().to$(); for (i = 0; i < sample_nodes.length; i++) { -- cgit v1.2.3 From e2ee1c489f0eb59c3efbf81d42f43a1f74cd1adb Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 6 Aug 2021 15:31:30 +0300 Subject: wqflask: show_trait:: Remove debug comment --- wqflask/wqflask/show_trait/show_trait.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index c07430dd..b640ec27 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -520,6 +520,9 @@ class ShowTrait: sample_group_type='primary', header="%s Only" % (self.dataset.group.name)) self.sample_groups = (primary_samples,) + print("\nttttttttttttttttttttttttttttttttttttttttttttt\n") + print(self.sample_groups) + print("\nttttttttttttttttttttttttttttttttttttttttttttt\n") self.primary_sample_names = primary_sample_names self.dataset.group.allsamples = all_samples_ordered -- cgit v1.2.3 From 579ae94b08f0b1cef00350b54dadc12869ad70de Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 6 Aug 2021 16:15:17 +0300 Subject: base: data_set: Remove unnecessary comments and logging statements --- wqflask/base/data_set.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 4cb82665..edc22540 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -277,7 +277,6 @@ class Markers: filtered_markers = [] for marker in self.markers: if marker['name'] in p_values: - # logger.debug("marker {} IS in p_values".format(i)) marker['p_value'] = p_values[marker['name']] if math.isnan(marker['p_value']) or (marker['p_value'] <= 0): marker['lod_score'] = 0 @@ -298,7 +297,6 @@ class HumanMarkers(Markers): self.markers = [] for line in marker_data_fh: splat = line.strip().split() - # logger.debug("splat:", splat) if len(specified_markers) > 0: if splat[1] in specified_markers: marker = {} @@ -737,7 +735,6 @@ class DataSet: and Strain.SpeciesId=Species.Id and Species.name = '{}' """.format(create_in_clause(self.samplelist), *mescape(self.group.species)) - logger.sql(query) results = dict(g.db.execute(query).fetchall()) sample_ids = [results[item] for item in self.samplelist] @@ -908,7 +905,6 @@ class PhenotypeDataSet(DataSet): Geno.Name = '%s' and Geno.SpeciesId = Species.Id """ % (species, this_trait.locus) - logger.sql(query) result = g.db.execute(query).fetchone() if result: @@ -938,7 +934,6 @@ class PhenotypeDataSet(DataSet): Order BY Strain.Name """ - logger.sql(query) results = g.db.execute(query, (trait, self.id)).fetchall() return results @@ -1005,7 +1000,6 @@ class GenotypeDataSet(DataSet): Order BY Strain.Name """ - logger.sql(query) results = g.db.execute(query, (webqtlDatabaseFunction.retrieve_species_id(self.group.name), trait, self.name)).fetchall() @@ -1126,8 +1120,6 @@ class MrnaAssayDataSet(DataSet): ProbeSet.Name = '%s' """ % (escape(str(this_trait.dataset.id)), escape(this_trait.name))) - - logger.sql(query) result = g.db.execute(query).fetchone() mean = result[0] if result else 0 @@ -1147,7 +1139,6 @@ class MrnaAssayDataSet(DataSet): Geno.Name = '{}' and Geno.SpeciesId = Species.Id """.format(species, this_trait.locus) - logger.sql(query) result = g.db.execute(query).fetchone() if result: @@ -1179,7 +1170,6 @@ class MrnaAssayDataSet(DataSet): Order BY Strain.Name """ % (escape(trait), escape(self.name)) - logger.sql(query) results = g.db.execute(query).fetchall() return results @@ -1190,7 +1180,6 @@ class MrnaAssayDataSet(DataSet): where ProbeSetXRef.ProbeSetFreezeId = %s and ProbeSetXRef.ProbeSetId=ProbeSet.Id; """ % (column_name, escape(str(self.id))) - logger.sql(query) results = g.db.execute(query).fetchall() return dict(results) @@ -1224,7 +1213,6 @@ def geno_mrna_confidentiality(ob): query = '''SELECT Id, Name, FullName, confidentiality, AuthorisedUsers FROM %s WHERE Name = "%s"''' % (dataset_table, ob.name) - logger.sql(query) result = g.db.execute(query) (dataset_id, -- cgit v1.2.3 From 7f2a1f9d1b91b9d0d24da56afcfe26197a434af3 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 16 Aug 2021 11:13:40 +0300 Subject: Add "Description" column to CaseAttribute See: eecaad2d --- scripts/add_missing_columns.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/add_missing_columns.sh b/scripts/add_missing_columns.sh index 70d5fdeb..611e2dd6 100644 --- a/scripts/add_missing_columns.sh +++ b/scripts/add_missing_columns.sh @@ -13,6 +13,9 @@ ALTER TABLE PublishXRef ADD mean double AFTER DataId; + ALTER TABLE CaseAttribute + ADD Description varchar(255) AFTER Name; + -- This takes some time ALTER TABLE ProbeSet ADD UniProtID varchar(20) AFTER ProteinName; -- cgit v1.2.3 From 2071107566d89a12fe27fdcc94dd16b7b8680f83 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 16 Aug 2021 11:38:05 +0300 Subject: wqflask: views: Remove commented out code blocks --- wqflask/wqflask/views.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 44560427..08e895cb 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -365,20 +365,6 @@ def wcgna_setup(): return render_template("wgcna_setup.html", **request.form) -# @app.route("/wgcna_results", methods=('POST',)) -# def wcgna_results(): -# logger.info("In wgcna, request.form is:", request.form) -# logger.info(request.url) -# # Start R, load the package and pointers and create the analysis -# wgcna = wgcna_analysis.WGCNA() -# # Start the analysis, a wgcnaA object should be a separate long running thread -# wgcnaA = wgcna.run_analysis(request.form) -# # After the analysis is finished store the result -# result = wgcna.process_results(wgcnaA) -# # Display them using the template -# return render_template("wgcna_results.html", **result) - - @app.route("/ctl_setup", methods=('POST',)) def ctl_setup(): # We are going to get additional user input for the analysis @@ -388,20 +374,6 @@ def ctl_setup(): return render_template("ctl_setup.html", **request.form) -# @app.route("/ctl_results", methods=('POST',)) -# def ctl_results(): -# logger.info("In ctl, request.form is:", request.form) -# logger.info(request.url) -# # Start R, load the package and pointers and create the analysis -# ctl = ctl_analysis.CTL() -# # Start the analysis, a ctlA object should be a separate long running thread -# ctlA = ctl.run_analysis(request.form) -# # After the analysis is finished store the result -# result = ctl.process_results(ctlA) -# # Display them using the template -# return render_template("ctl_results.html", **result) - - @app.route("/intro") def intro(): doc = Docs("intro", request.args) -- cgit v1.2.3 From 923d849d640e3048422e3cdfa6ce24716cfa7904 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 16 Aug 2021 18:54:16 +0300 Subject: wqflask: views: Use "inbredset_id" in the "Edit Trait" URL --- wqflask/wqflask/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 08e895cb..3f0ab93f 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -408,9 +408,9 @@ def submit_trait_form(): version=GN_VERSION) -@app.route("/trait//edit/phenotype-id/") +@app.route("/trait//edit/inbredset-id/") @admin_login_required -def edit_phenotype(name, phenotype_id): +def edit_phenotype(name, inbredset_id): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), passwd=current_app.config.get("DB_PASS"), @@ -419,7 +419,7 @@ def edit_phenotype(name, phenotype_id): conn=conn, table="PublishXRef", where=PublishXRef(id_=name, - phenotype_id=phenotype_id)) + inbred_set_id=inbredset_id)) phenotype_ = fetchone( conn=conn, table="Phenotype", -- cgit v1.2.3 From 82a7ccf6d2e5e18dcbd83f8070fd88ddc34ad419 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 16 Aug 2021 19:00:34 +0300 Subject: Update URL for editing published trait --- wqflask/wqflask/templates/show_trait_details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html index bb30c54c..53e16aa0 100644 --- a/wqflask/wqflask/templates/show_trait_details.html +++ b/wqflask/wqflask/templates/show_trait_details.html @@ -236,7 +236,7 @@ {% if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access" %} {% if this_trait.dataset.type == 'Publish' %} - + {% endif %} {% if this_trait.dataset.type == 'ProbeSet' %} -- cgit v1.2.3 From 83af88a4bbf9d8a67f4d11c3546ba64f09efb6d2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 17 Aug 2021 09:15:04 +0300 Subject: wqflask: wsgi: Remove it * wqflask/wsgi.py: Delete file. Not used anywhere. --- wqflask/wsgi.py | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 wqflask/wsgi.py diff --git a/wqflask/wsgi.py b/wqflask/wsgi.py deleted file mode 100644 index 755da333..00000000 --- a/wqflask/wsgi.py +++ /dev/null @@ -1,4 +0,0 @@ -from run_gunicorn import app as application # expect application as a name - -if __name__ == "__main__": - application.run() -- cgit v1.2.3 From b0610024e7cb5dec73742fd461cd662881869214 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 17 Aug 2021 09:15:47 +0300 Subject: wqflask: views: Re-enable admin access in edit probeset --- wqflask/wqflask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 3f0ab93f..7095c392 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -466,7 +466,7 @@ def edit_phenotype(name, inbredset_id): @app.route("/trait/edit/probeset-name/") -# @admin_login_required +@admin_login_required def edit_probeset(dataset_name): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), -- cgit v1.2.3 From d89a13ef9f3f4da47432eb3555df3ada7bbfbb8b Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 17 Aug 2021 12:41:24 +0300 Subject: wqflask: runserver: Add DebugToolbar when running in DEBUG mode --- wqflask/runserver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wqflask/runserver.py b/wqflask/runserver.py index df957bd9..8198b921 100644 --- a/wqflask/runserver.py +++ b/wqflask/runserver.py @@ -23,6 +23,9 @@ app_config() werkzeug_logger = logging.getLogger('werkzeug') if WEBSERVER_MODE == 'DEBUG': + from flask_debugtoolbar import DebugToolbarExtension + app.debug = True + toolbar = DebugToolbarExtension(app) app.run(host='0.0.0.0', port=SERVER_PORT, debug=True, -- cgit v1.2.3 From b70bf1250bc93a47c3e3e32d4d2f4a116121d11f Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 17 Aug 2021 12:43:50 +0300 Subject: wqflask:.DS_Store: Delete it --- wqflask/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 wqflask/.DS_Store diff --git a/wqflask/.DS_Store b/wqflask/.DS_Store deleted file mode 100644 index d992942f..00000000 Binary files a/wqflask/.DS_Store and /dev/null differ -- cgit v1.2.3 From 94f278b68817fb22d363fdfd1b416f9c674c3226 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 18 Aug 2021 09:42:48 +0300 Subject: Revert "wqflask: wsgi: Remove it" This reverts commit 83af88a4bbf9d8a67f4d11c3546ba64f09efb6d2. --- wqflask/wsgi.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 wqflask/wsgi.py diff --git a/wqflask/wsgi.py b/wqflask/wsgi.py new file mode 100644 index 00000000..755da333 --- /dev/null +++ b/wqflask/wsgi.py @@ -0,0 +1,4 @@ +from run_gunicorn import app as application # expect application as a name + +if __name__ == "__main__": + application.run() -- cgit v1.2.3 From f5f1b3e64db18dbdde39860f1f86a4bf408e3dd6 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 16 Aug 2021 19:50:24 +0000 Subject: Replace textarea with multiple select element for displaying mapping covariates --- wqflask/wqflask/templates/show_trait_mapping_tools.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html index 3dd44c85..3f0557aa 100755 --- a/wqflask/wqflask/templates/show_trait_mapping_tools.html +++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html @@ -77,7 +77,9 @@
    - + {% endif %} @@ -320,7 +322,9 @@ - + {% endif %} -- cgit v1.2.3 From eb7863b540bda56f3e0a769440ebc16c675d4adb Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 16 Aug 2021 19:51:07 +0000 Subject: Update get_covariates_from_collection.js to properly interact with the select element instead of the previous textarea --- .../javascript/get_covariates_from_collection.js | 55 +++++++++++++++------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js b/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js index 3e414034..62590aaf 100644 --- a/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js +++ b/wqflask/wqflask/static/new/javascript/get_covariates_from_collection.js @@ -65,10 +65,8 @@ if ( ! $.fn.DataTable.isDataTable( '#collection_table' ) ) { collection_click = function() { var this_collection_url; - console.log("Clicking on:", $(this)); this_collection_url = $(this).find('.collection_name').prop("href"); this_collection_url += "&json"; - console.log("this_collection_url", this_collection_url); collection_list = $("#collections_holder").html(); return $.ajax({ dataType: "json", @@ -79,32 +77,57 @@ collection_click = function() { submit_click = function() { var covariates_string = ""; - var covariates_display_string = ""; + var covariates_as_set = new Set(); + $(".selected-covariates:first option").each(function() { + if ($(this).val() != ""){ + covariates_as_set.add($(this).val() + "," + $(this).text()); + } + }); $('#collections_holder').find('input[type=checkbox]:checked').each(function() { var this_dataset, this_trait; this_trait = $(this).parents('tr').find('.trait').text(); this_trait_display = $(this).parents('tr').find('.trait').data("display_name"); this_description = $(this).parents('tr').find('.description').text(); - console.log("this_trait is:", this_trait_display); this_dataset = $(this).parents('tr').find('.dataset').data("dataset"); - console.log("this_dataset is:", this_dataset); - covariates_string += this_trait + ":" + this_dataset + "," - //this_covariate_display_string = this_trait + ": " + this_description this_covariate_display_string = this_trait_display if (this_covariate_display_string.length > 50) { this_covariate_display_string = this_covariate_display_string.substring(0, 45) + "..." } - covariates_display_string += this_covariate_display_string + "\n" + covariates_as_set.add(this_trait + ":" + this_dataset + "," + this_covariate_display_string) + }); + + covariates_as_list = Array.from(covariates_as_set) + + // Removed the starting "No covariates selected" option before adding options for each covariate + if (covariates_as_list.length > 0){ + $(".selected-covariates option[value='']").each(function() { + $(this).remove(); + }); + } + + $(".selected-covariates option").each(function() { + $(this).remove(); }); - // Trim the last newline from display_string - covariates_display_string = covariates_display_string.replace(/\n$/, "") - // Trim the last comma - covariates_string = covariates_string.substring(0, covariates_string.length - 1) - //covariates_display_string = covariates_display_string.substring(0, covariates_display_string.length - 2) + covariate_list_for_form = [] + $.each(covariates_as_list, function (index, value) { + option_value = value.split(",")[0] + option_text = value.split(",")[1] + $(".selected-covariates").append($(" -- cgit v1.2.3 From e98ef60db6d0986360e6c8aa920241bbb268af8e Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 17 Aug 2021 03:07:18 +0000 Subject: Add some CSS changing the way the colorbox pop-up for adding covariates looks on the trait page --- wqflask/wqflask/static/new/css/show_trait.css | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/wqflask/wqflask/static/new/css/show_trait.css b/wqflask/wqflask/static/new/css/show_trait.css index 782dabc2..b0514e01 100644 --- a/wqflask/wqflask/static/new/css/show_trait.css +++ b/wqflask/wqflask/static/new/css/show_trait.css @@ -260,3 +260,33 @@ input.trait-value-input { div.inline-div { display: inline; } + +/* div.colorbox_border { + border: 1px solid grey; +} */ +div#cboxContent { + /* box-shadow: + 0 2.8px 2.2px rgba(0, 0, 0, 0.034), + 0 6.7px 5.3px rgba(0, 0, 0, 0.048), + 0 12.5px 10px rgba(0, 0, 0, 0.06), + 0 22.3px 17.9px rgba(0, 0, 0, 0.072), + 0 41.8px 33.4px rgba(0, 0, 0, 0.086), + 0 100px 80px rgba(0, 0, 0, 0.12) */ + + padding: 10px 10px 5px 10px; + + -moz-box-shadow: 3px 3px 5px #535353; + -webkit-box-shadow: 3px 3px 5px #535353; + box-shadow: 3px 3px 5px #535353; + + -moz-border-radius: 6px 6px 6px 6px; + -webkit-border-radius: 6px; + border-radius: 6px 6px 6px 6px; + + /* border: 2px solid grey; */ +} + +#cboxClose { + margin-right: 5px; + margin-bottom: 2px; +} -- cgit v1.2.3 From 5c879337593366005d07f390b8e0da901ec23881 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 17 Aug 2021 18:34:50 +0000 Subject: Changed Remove All button to Clear and changed Select and Clear button colors --- wqflask/wqflask/templates/show_trait_mapping_tools.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html index cc472914..e63d0195 100755 --- a/wqflask/wqflask/templates/show_trait_mapping_tools.html +++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html @@ -74,9 +74,9 @@ No collections available. Please add traits to a collection to use them as covariates. {% else %}
    - + - +

    - + {% endif %} -- cgit v1.2.3 From fc15b67ca15a8f08833a8b1e479c152bc4a2b723 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 17 Aug 2021 20:35:50 +0000 Subject: Added option to make collection default in the colorbox pop-up --- wqflask/wqflask/templates/collections/add.html | 29 +++++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/wqflask/wqflask/templates/collections/add.html b/wqflask/wqflask/templates/collections/add.html index cb4172ac..8640fdb8 100644 --- a/wqflask/wqflask/templates/collections/add.html +++ b/wqflask/wqflask/templates/collections/add.html @@ -5,7 +5,7 @@ or add to an existing collection.

    @@ -79,4 +104,15 @@ $("#loading_form").attr("action", "{{ start_vars.form_url }}"); setTimeout(function(){ $("#loading_form").submit()}, 350); + +$('#show_full_diff').click(function() { + if ($('#diff_table_container').is(':visible')){ + $('#diff_table_container').hide(); + } else { + $('#diff_table_container').show(); + } +}) + + + -- cgit v1.2.3 From 26958694322855f4ee91beb3b01e73afe75066cf Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 17 Aug 2021 20:50:31 +0000 Subject: Added function for getting the diff of sample values before and after user changes to show_trait.py --- wqflask/wqflask/show_trait/show_trait.py | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index 4120c801..96f6901a 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -1,3 +1,5 @@ +from typing import Dict + import string import datetime import uuid @@ -805,3 +807,41 @@ def get_scales_from_genofile(file_location): return [["physic", "Mb"], ["morgan", "cM"]] else: return [["physic", "Mb"]] + + + +def get_diff_of_vals(new_vals: Dict, trait_id: str) -> Dict: + """ Get the diff between current sample values and the values in the DB + + Given a dict of the changed values and the trait/dataset ID, return a Dict + with keys corresponding to each sample with a changed value and a value + that is a dict with keys for the old_value and new_value + + """ + + trait_name = trait_id.split(":")[0] + dataset_name = trait_id.split(":")[1] + trait_ob = create_trait(name=trait_name, dataset_name=dataset_name) + + old_vals = {sample : trait_ob.data[sample].value for sample in trait_ob.data} + + shared_samples = set.union(set(new_vals.keys()), set(old_vals.keys())) + + diff_dict = {} + for sample in shared_samples: + try: + new_val = float(new_vals[sample]) + except: + new_val = "x" + try: + old_val = float(old_vals[sample]) + except: + old_val = "x" + + if new_val != old_val: + diff_dict[sample] = { + "new_val": new_val, + "old_val": old_val + } + + return diff_dict -- cgit v1.2.3 From 71e6c7483c8675e96376295927133c370478bbba Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 17 Aug 2021 20:51:26 +0000 Subject: Removed some unnecessary empty lines from loading.html JS --- wqflask/wqflask/templates/loading.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wqflask/wqflask/templates/loading.html b/wqflask/wqflask/templates/loading.html index b02605ee..1edde31e 100644 --- a/wqflask/wqflask/templates/loading.html +++ b/wqflask/wqflask/templates/loading.html @@ -101,7 +101,6 @@ -- cgit v1.2.3 From ab3b838b99b58693f3f187093e78f492e7cf392b Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 18 Aug 2021 00:13:55 +0000 Subject: Exclude attributes with only one distinct value as well --- wqflask/wqflask/templates/show_trait_transform_and_filter.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/show_trait_transform_and_filter.html b/wqflask/wqflask/templates/show_trait_transform_and_filter.html index 064fd3e5..8d21b0b0 100644 --- a/wqflask/wqflask/templates/show_trait_transform_and_filter.html +++ b/wqflask/wqflask/templates/show_trait_transform_and_filter.html @@ -25,7 +25,7 @@ {% endif %} -- cgit v1.2.3 From 041354b4c278c4784119da60ace2c3d4ad981354 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 19 Aug 2021 18:28:31 +0000 Subject: Fixed issue in JS that caused filter_by_value to not work for case attributes --- wqflask/wqflask/static/new/javascript/show_trait.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/static/new/javascript/show_trait.js b/wqflask/wqflask/static/new/javascript/show_trait.js index be9e2089..f050d4ae 100644 --- a/wqflask/wqflask/static/new/javascript/show_trait.js +++ b/wqflask/wqflask/static/new/javascript/show_trait.js @@ -818,7 +818,7 @@ filter_by_value = function() { var this_col_value = filter_val_nodes[i].childNodes[0].value; } else { if (filter_val_nodes[i].childNodes[0] !== undefined){ - var this_col_value = filter_val_nodes[i].childNodes[0].data; + var this_col_value = filter_val_nodes[i].innerText; } else { continue } -- cgit v1.2.3 From 0e0f25a71d2fab7395af007643b908af51bd7ca2 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 19 Aug 2021 18:29:45 +0000 Subject: Fixed R/qtl 'select covariates' buttons to be the same as the changed ones for GEMMA --- wqflask/wqflask/templates/show_trait_mapping_tools.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html index ddcc3edf..4c0efecb 100755 --- a/wqflask/wqflask/templates/show_trait_mapping_tools.html +++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html @@ -320,9 +320,9 @@ No collections available. Please add traits to a collection to use them as covariates. {% else %}
    - + - +
    + {% if categorical_vars is defined %} + + {% endif %} -- cgit v1.2.3 From 0bbe9072566397942a6db034cb11203bcb43b78f Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 23 Aug 2021 19:03:10 +0000 Subject: Fix the way categorical_var_list is set to account for case attribute Ids being keys now instead of names --- wqflask/wqflask/show_trait/show_trait.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index 96f6901a..36e69fa3 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -701,7 +701,7 @@ def get_categorical_variables(this_trait, sample_list) -> list: if len(sample_list.attributes) > 0: for attribute in sample_list.attributes: if len(sample_list.attributes[attribute].distinct_values) < 10: - categorical_var_list.append(sample_list.attributes[attribute].name) + categorical_var_list.append(str(sample_list.attributes[attribute].id)) return categorical_var_list -- cgit v1.2.3 From 654b35c2050e89f9ba533a3713d40465cf6fcd47 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 23 Aug 2021 19:05:10 +0000 Subject: Fix get_perm_strata to usethe Ids for case attributes instead of names + allow perm_strata to be passed to the template --- wqflask/wqflask/marker_regression/run_mapping.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index f601201b..ebad7d36 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -220,7 +220,7 @@ class RunMapping: elif self.mapping_method == "rqtl_plink": results = self.run_rqtl_plink() elif self.mapping_method == "rqtl_geno": - perm_strata = [] + self.perm_strata = [] if "perm_strata" in start_vars and "categorical_vars" in start_vars: self.categorical_vars = start_vars["categorical_vars"].split( ",") @@ -229,7 +229,7 @@ class RunMapping: sample_names=self.samples, this_trait=self.this_trait) - perm_strata = get_perm_strata( + self.perm_strata = get_perm_strata( self.this_trait, primary_samples, self.categorical_vars, self.samples) self.score_type = "LOD" self.control_marker = start_vars['control_marker'] @@ -243,10 +243,10 @@ class RunMapping: # self.pair_scan = True if self.permCheck and self.num_perm > 0: self.perm_output, self.suggestive, self.significant, results = rqtl_mapping.run_rqtl( - self.this_trait.name, self.vals, self.samples, self.dataset, self.mapping_scale, self.model, self.method, self.num_perm, perm_strata, self.do_control, self.control_marker, self.manhattan_plot, self.covariates) + self.this_trait.name, self.vals, self.samples, self.dataset, self.mapping_scale, self.model, self.method, self.num_perm, self.perm_strata, self.do_control, self.control_marker, self.manhattan_plot, self.covariates) else: results = rqtl_mapping.run_rqtl(self.this_trait.name, self.vals, self.samples, self.dataset, self.mapping_scale, self.model, self.method, - self.num_perm, perm_strata, self.do_control, self.control_marker, self.manhattan_plot, self.covariates) + self.num_perm, self.perm_strata, self.do_control, self.control_marker, self.manhattan_plot, self.covariates) elif self.mapping_method == "reaper": if "startMb" in start_vars: # ZS: Check if first time page loaded, so it can default to ON if "additiveCheck" in start_vars: @@ -765,9 +765,9 @@ def get_perm_strata(this_trait, sample_list, categorical_vars, used_samples): if sample in list(sample_list.sample_attribute_values.keys()): combined_string = "" for var in categorical_vars: - if var.lower() in sample_list.sample_attribute_values[sample]: + if var in sample_list.sample_attribute_values[sample]: combined_string += str( - sample_list.sample_attribute_values[sample][var.lower()]) + sample_list.sample_attribute_values[sample][var]) else: combined_string += "NA" else: -- cgit v1.2.3 From fff90d1a1b194fe5e65e99506bc4fe6fffc9a255 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 23 Aug 2021 19:06:01 +0000 Subject: Account for situations where the minimum permutation value is also above webqtlConfig.MAXLRS; previously threw an error --- wqflask/utility/Plot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/utility/Plot.py b/wqflask/utility/Plot.py index 9b2c6735..d4256a46 100644 --- a/wqflask/utility/Plot.py +++ b/wqflask/utility/Plot.py @@ -139,7 +139,7 @@ def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLab max_D = max(data) min_D = min(data) # add by NL 06-20-2011: fix the error: when max_D is infinite, log function in detScale will go wrong - if max_D == float('inf') or max_D > webqtlConfig.MAXLRS: + if (max_D == float('inf') or max_D > webqtlConfig.MAXLRS) and min_D < webqtlConfig.MAXLRS: max_D = webqtlConfig.MAXLRS # maximum LRS value xLow, xTop, stepX = detScale(min_D, max_D) @@ -156,7 +156,7 @@ def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLab j += step for i, item in enumerate(data): - if item == float('inf') or item > webqtlConfig.MAXLRS: + if (item == float('inf') or item > webqtlConfig.MAXLRS) and min_D < webqtlConfig.MAXLRS: item = webqtlConfig.MAXLRS # maximum LRS value j = int((item - xLow) / step) Count[j] += 1 -- cgit v1.2.3 From 7546da17cc5b7a2f97b490e1593b41609b281a43 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 23 Aug 2021 19:29:38 +0000 Subject: Fixed issue with the way categorical_vars and perm_strata were being passed to the template --- wqflask/wqflask/templates/mapping_results.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html index 7baac000..81eb1ba1 100644 --- a/wqflask/wqflask/templates/mapping_results.html +++ b/wqflask/wqflask/templates/mapping_results.html @@ -45,8 +45,10 @@ {% if categorical_vars is defined %} - - + + {% endif %} + {% if perm_strata is defined %} + {% endif %} -- cgit v1.2.3 From 629aab0ef51c2c6f7d4f479e51a7168d9aa14d41 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 23 Aug 2021 19:31:18 +0000 Subject: Removed unused variable from list of 'wanted' variables for mapping --- wqflask/wqflask/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 957c34be..11a9380c 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1067,7 +1067,6 @@ def mapping_results_page(): 'num_perm', 'permCheck', 'perm_strata', - 'strat_var', 'categorical_vars', 'perm_output', 'num_bootstrap', -- cgit v1.2.3 From fe345c9f2e99be748511f6889420d6560a553a37 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Tue, 24 Aug 2021 09:55:05 +0200 Subject: README: install with guix profile --- doc/README.org | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/doc/README.org b/doc/README.org index 43c92e3c..fb1781aa 100644 --- a/doc/README.org +++ b/doc/README.org @@ -2,7 +2,8 @@ * Table of Contents :TOC: - [[#introduction][Introduction]] - - [[#install][Install]] + - [[#installing-guix-packages][Installing Guix packages]] + - [[#creating-a-gnu-guix-profile][Creating a GNU Guix profile]] - [[#running-gn2][Running GN2]] - [[#run-gn-proxy][Run gn-proxy]] - [[#run-redis][Run Redis]] @@ -37,27 +38,79 @@ tree. Current supported versions can be found as the SHA values of For a full view of runtime dependencies as defined by GNU Guix, see an example of the [[#gn2-dependency-graph][GN2 Dependency Graph]]. -* Install +* Installing Guix packages Make sure to install GNU Guix using the binary download instructions on the main website. Follow the instructions on [[GUIX-Reproducible-from-source.org]] to download pre-built binaries. Note -the download amounts to several GBs of data. +the download amounts to several GBs of data. Debian-derived distros +may support + +: apt-get install guix + +* Creating a GNU Guix profile + +We run a GNU Guix channel with packages at [[https://git.genenetwork.org/guix-bioinformatics/guix-bioinformatics][guix-bioinformatics]]. The +README has instructions for hosting a channel, but typically we use +the GUIX_PACKAGE_PATH instead. First upgrade to a recent guix with + +: mkdir ~/opt +: guix pull -p ~/opt/guix-pull + +It should upgrade (ignore the locales warnings). You can optionally +specify the specific git checkout of guix with + +: guix pull -p ~/opt/guix-pull --commit=f04883d + +which is useful when you ned to roll back to an earlier version +(sometimes our channel goes out of sync). Next, we install +GeneNetwork2 with + +: source ~/opt/guix-pull/etc/profile +: git clone https://git.genenetwork.org/guix-bioinformatics/guix-bioinformatics.git ~/guix-bioinformatics +: cd ~/guix-bioinformatics +: git pull +: env GUIX_PACKAGE_PATH=$HOME/guix-bioinformatics guix package -i genenetwork2 -p ~/opt/genenetwork2 + +you probably also need guix-past (the upstream channel for older packages): + +: git clone https://gitlab.inria.fr/guix-hpc/guix-past.git ~/guix-past +: cd ~/guix-past +: git pull +: env GUIX_PACKAGE_PATH=$HOME/guix-bioinformatics:$HOME/guix-past/modules ~/opt/guix-pull/bin/guix package -i genenetwork2 -p ~/opt/genenetwork2 + +ignore the warnings. Guix should install the software without trying +to build everything. If you system insists on building all packages, +try the `--dry-run` switch and fix the [[https://guix.gnu.org/manual/en/html_node/Substitute-Server-Authorization.html][substitutes]]. You may add the +`--substitute-urls="http://guix.genenetwork.org https://ci.guix.gnu.org https://mirror.hydra.gnu.org"` switch. + +The guix.genenetwork.org has most of our packages pre-built(!). To use +it on your own machine the public key is + +#+begin_src scheme +(public-key + (ecc + (curve Ed25519) + (q #E50F005E6DA2F85749B9AA62C8E86BB551CE2B541DC578C4DBE613B39EC9E750#))) +#+end_src + +Once we have a GNU Guix profile, a running database (see below) and the file storage, +we should be ready to fire up GeneNetwork: * Running GN2 -Default settings for GN2 are listed in a file called -[[../etc/default_settings.py][default_settings.py]]. You can copy this file and pass it as a new -parameter to the genenetwork2 command, e.g. +Check out the source with git: -: genenetwork2 mysettings.py +: git clone git@github.com:genenetwork/genenetwork2.git +: cd genenetwork2 -or you can set environment variables to override individual parameters, e.g. +Run GN2 with above Guix profile -: env SERVER_PORT=5004 SQL_URI=mysql://user:pwd@dbhostname/db_webqtl genenetwork2 +: export GN2_PROFILE=$HOME/opt/genenetwork2 +: env TMPDIR=$HOME/tmp WEBSERVER_MODE=DEBUG LOG_LEVEL=DEBUG SERVER_PORT=5002 GENENETWORK_FILES=/export/data/genenetwork/genotype_files SQL_URI=mysql://webqtlout:webqtlout@localhost/db_webqtl ./bin/genenetwork2 -gunicorn-dev the debug and logging switches can be particularly useful when -developing GN2. +developing GN2. Location and files are the current ones for Penguin2. * Run gn-proxy -- cgit v1.2.3 From ba1dd2c354863536f2c12a25b5e59aed56a683b6 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Tue, 24 Aug 2021 10:22:43 +0200 Subject: README: minor edits --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bfb16ddb..003cfd7b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ [![DOI](https://zenodo.org/badge/5591/genenetwork/genenetwork2.svg)](https://zenodo.org/badge/latestdoi/5591/genenetwork/genenetwork2) [![JOSS](http://joss.theoj.org/papers/10.21105/joss.00025/status.svg)](http://joss.theoj.org/papers/10.21105/joss.00025) [![Actions Status](https://github.com/genenetwork/genenetwork2/workflows/tests/badge.svg)](https://github.com/genenetwork/genenetwork2/actions) + # GeneNetwork This repository contains the current source code for GeneNetwork (GN) (https://www.genenetwork.org/ (version 2). GN2 is a Web -2.0-style framework that includes data and computational tools for online genetics and genomic analysis of -many different populations and many types of molecular, cellular, and physiological data. +2.0-style framework that includes data and computational tools for online genetics and genomic analysis of +many different populations and many types of molecular, cellular, and physiological data. The system is used by scientists and clinians in the field of precision health care and systems genetics. GN and its predecessors have been in operation since Jan 1994, making it one of the longest-lived web services in biomedical research (https://en.wikipedia.org/wiki/GeneNetwork, and see a partial list of publications using GN and its predecessor, WebQTL (https://genenetwork.org/references/). @@ -26,7 +27,7 @@ interface genenetwork2 ``` -(default is http://localhost:5003/). A quick example is +A quick example is ```sh env GN2_PROFILE=~/opt/gn-latest SERVER_PORT=5300 GENENETWORK_FILES=~/data/gn2_data/ ./bin/genenetwork2 ./etc/default_settings.py -gunicorn-dev -- cgit v1.2.3 From 8a8dcba497e8f4210329f42cd4b4e766d95a36fc Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Tue, 24 Aug 2021 10:54:01 +0200 Subject: README: on installing GN2 --- doc/README.org | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/doc/README.org b/doc/README.org index fb1781aa..1236016e 100644 --- a/doc/README.org +++ b/doc/README.org @@ -2,6 +2,7 @@ * Table of Contents :TOC: - [[#introduction][Introduction]] + - [[#check-list][Check list]] - [[#installing-guix-packages][Installing Guix packages]] - [[#creating-a-gnu-guix-profile][Creating a GNU Guix profile]] - [[#running-gn2][Running GN2]] @@ -38,6 +39,17 @@ tree. Current supported versions can be found as the SHA values of For a full view of runtime dependencies as defined by GNU Guix, see an example of the [[#gn2-dependency-graph][GN2 Dependency Graph]]. +* Check list + +To run GeneNetwork the following services need to function: + +1. [ ] GNU Guix with a guix profile for genenetwork2 +1. [ ] A path to the (static) genotype files +1. [ ] Gn-proxy for authentication +1. [ ] The genenetwork3 service +1. [ ] Redis +1. [ ] Mariadb + * Installing Guix packages Make sure to install GNU Guix using the binary download instructions @@ -107,15 +119,26 @@ Check out the source with git: Run GN2 with above Guix profile : export GN2_PROFILE=$HOME/opt/genenetwork2 -: env TMPDIR=$HOME/tmp WEBSERVER_MODE=DEBUG LOG_LEVEL=DEBUG SERVER_PORT=5002 GENENETWORK_FILES=/export/data/genenetwork/genotype_files SQL_URI=mysql://webqtlout:webqtlout@localhost/db_webqtl ./bin/genenetwork2 -gunicorn-dev +: env TMPDIR=$HOME/tmp WEBSERVER_MODE=DEBUG LOG_LEVEL=DEBUG SERVER_PORT=5012 GENENETWORK_FILES=/export/data/genenetwork/genotype_files SQL_URI=mysql://webqtlout:webqtlout@localhost/db_webqtl ./bin/genenetwork2 etc/default_settings.py -gunicorn-dev the debug and logging switches can be particularly useful when developing GN2. Location and files are the current ones for Penguin2. +It may be useful to tunnel the web server to your local browser with +an ssh tunnel: + +If you want to test a service running on the server on a certain +port (say 8202) use + + ssh -L 8202:127.0.0.1:8202 -f -N myname@penguin2.genenetwork.org + +And browse on your local machine to http://localhost:8202/ + * Run gn-proxy GeneNetwork requires a separate gn-proxy server which handles -authorisation and access control. For instructions see the [[https://github.com/genenetwork/gn-proxy][README]]. +authorisation and access control. For instructions see the +[[https://github.com/genenetwork/gn-proxy][README]]. Note it may already be running on our servers! * Run Redis -- cgit v1.2.3 From 3fd1d766fcf38f5a147c01b1c397469139c9080d Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 25 Aug 2021 17:39:02 +0000 Subject: Fix issue that could cause mapping_scale to not be set correctly in show_trait.py --- wqflask/wqflask/show_trait/show_trait.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index 36e69fa3..80f5d117 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -300,7 +300,7 @@ class ShowTrait: hddn['compare_traits'] = [] hddn['export_data'] = "" hddn['export_format'] = "excel" - if len(self.scales_in_geno) < 2: + if len(self.scales_in_geno) < 2 and bool(self.scales_in_geno): hddn['mapping_scale'] = self.scales_in_geno[list( self.scales_in_geno.keys())[0]][0][0] -- cgit v1.2.3 From ae634a36891ddba9748e0f75ff1cf51e95e4a8a7 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 25 Aug 2021 17:39:16 +0000 Subject: Fix issue that could cause an error when doing cM mapping --- wqflask/wqflask/marker_regression/display_mapping_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index f941267e..3986c441 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -2113,7 +2113,7 @@ class DisplayMappingResults: thisChr.append( [_locus.name, _locus.cM - Locus0CM]) else: - for j in (0, nLoci / 4, nLoci / 2, nLoci * 3 / 4, -1): + for j in (0, round(nLoci / 4), round(nLoci / 2), round(nLoci * 3 / 4), -1): while _chr[j].name == ' - ': j += 1 if _chr[j].cM != preLpos: -- cgit v1.2.3 From 3327c9dc95d2c4d295dc8dbf4839c01294b53ada Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 25 Aug 2021 17:46:02 +0000 Subject: Add map scale option that was missing for groups with only a single genotype file --- wqflask/wqflask/templates/show_trait_mapping_tools.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html index 4c0efecb..5365140d 100755 --- a/wqflask/wqflask/templates/show_trait_mapping_tools.html +++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html @@ -229,6 +229,17 @@ + {% else %} +
    + +
    + +
    +
    {% endif %}
    -- cgit v1.2.3 From 582893c0cc87f87e5dfc7fe0209b2e4a3f85d4a4 Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 25 Aug 2021 18:02:31 +0000 Subject: Fixed issue in get_diff_of_vals that caused the diff to be calculated wrong (due to one set of values being rounded to 3 digits and the other not --- wqflask/wqflask/show_trait/show_trait.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index 80f5d117..3f93bae0 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -830,11 +830,11 @@ def get_diff_of_vals(new_vals: Dict, trait_id: str) -> Dict: diff_dict = {} for sample in shared_samples: try: - new_val = float(new_vals[sample]) + new_val = round(float(new_vals[sample]), 3) except: new_val = "x" try: - old_val = float(old_vals[sample]) + old_val = round(float(old_vals[sample]), 3) except: old_val = "x" -- cgit v1.2.3 From cc7c392198f73ac8e8419be1367ce566d85a873c Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 25 Aug 2021 19:05:07 +0000 Subject: Fix issue where correlation results weren't included parents/f1s --- wqflask/wqflask/correlation/correlation_gn3_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/correlation/correlation_gn3_api.py b/wqflask/wqflask/correlation/correlation_gn3_api.py index a78cb0b9..d0d4bcba 100644 --- a/wqflask/wqflask/correlation/correlation_gn3_api.py +++ b/wqflask/wqflask/correlation/correlation_gn3_api.py @@ -148,7 +148,7 @@ def lit_for_trait_list(corr_results, this_dataset, this_trait): def fetch_sample_data(start_vars, this_trait, this_dataset, target_dataset): sample_data = process_samples( - start_vars, this_dataset.group.samplelist) + start_vars, this_dataset.group.all_samples_ordered()) if target_dataset.type == "ProbeSet": target_dataset.get_probeset_data(list(sample_data.keys())) -- cgit v1.2.3 From 5ebe36d5ff685e6b663b14c130606aa60b0123c2 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 3 Sep 2021 14:58:05 +0000 Subject: Fix issue where values written to phenotype file for R/qtl sometimes had trailing decimal values by grounding to 3 places past the decimal --- wqflask/wqflask/marker_regression/rqtl_mapping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/marker_regression/rqtl_mapping.py b/wqflask/wqflask/marker_regression/rqtl_mapping.py index 09afb8d1..cd578870 100644 --- a/wqflask/wqflask/marker_regression/rqtl_mapping.py +++ b/wqflask/wqflask/marker_regression/rqtl_mapping.py @@ -89,7 +89,7 @@ def write_phenotype_file(trait_name: str, for i, sample in enumerate(samples): this_row = [sample] if vals[i] != "x": - this_row.append(vals[i]) + this_row.append(str(round(float(vals[i]), 3))) else: this_row.append("NA") for cofactor in cofactor_data: @@ -126,7 +126,7 @@ def cofactors_to_dict(cofactors: str, dataset_ob, samples) -> Dict: sample_data = trait_ob.data for index, sample in enumerate(samples): if sample in sample_data: - sample_value = sample_data[sample].value + sample_value = str(round(float(sample_data[sample].value), 3)) cofactor_dict[cofactor_name].append(sample_value) else: cofactor_dict[cofactor_name].append("NA") -- cgit v1.2.3 From 8af212bc7eac5b39e8e6838df446a4003633a55e Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 3 Sep 2021 14:59:14 +0000 Subject: Fix issue that caused javascript to not work on the R/qtl mapping result page when permutations weren't used (because it wrongly expected the permutation histogram to always exist) --- wqflask/wqflask/templates/mapping_results.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html index 81eb1ba1..d446745d 100644 --- a/wqflask/wqflask/templates/mapping_results.html +++ b/wqflask/wqflask/templates/mapping_results.html @@ -529,7 +529,7 @@ }); {% endif %} - {% if mapping_method != "gemma" and mapping_method != "plink" %} + {% if mapping_method != "gemma" and mapping_method != "plink" and nperm > 0 and permChecked == "ON" %} $('#download_perm').click(function(){ perm_info_dict = { perm_data: js_data.perm_results, -- cgit v1.2.3 From f4297bc9e4c49f58e0759d02d3e7aea392ba5615 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 3 Sep 2021 15:01:34 +0000 Subject: Allow categorical_var_list to be passed as a template variable --- wqflask/wqflask/show_trait/show_trait.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index 3f93bae0..c4d1ae1c 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -178,11 +178,11 @@ class ShowTrait: self.sample_group_types['samples_primary'] = self.dataset.group.name sample_lists = [group.sample_list for group in self.sample_groups] - categorical_var_list = [] + self.categorical_var_list = [] self.numerical_var_list = [] if not self.temp_trait: # ZS: Only using first samplelist, since I think mapping only uses those samples - categorical_var_list = get_categorical_variables( + self.categorical_var_list = get_categorical_variables( self.this_trait, self.sample_groups[0]) self.numerical_var_list = get_numerical_variables( self.this_trait, self.sample_groups[0]) @@ -287,8 +287,8 @@ class ShowTrait: hddn['study_samplelists'] = json.dumps(study_samplelist_json) hddn['num_perm'] = 0 hddn['categorical_vars'] = "" - if categorical_var_list: - hddn['categorical_vars'] = ",".join(categorical_var_list) + if self.categorical_var_list: + hddn['categorical_vars'] = ",".join(self.categorical_var_list) hddn['manhattan_plot'] = "" hddn['control_marker'] = "" if not self.temp_trait: @@ -323,7 +323,7 @@ class ShowTrait: has_num_cases=self.has_num_cases, attributes=self.sample_groups[0].attributes, categorical_attr_exists=self.categorical_attr_exists, - categorical_vars=",".join(categorical_var_list), + categorical_vars=",".join(self.categorical_var_list), num_values=self.num_values, qnorm_values=self.qnorm_vals, zscore_values=self.z_scores, -- cgit v1.2.3 From f81a5629ab6ea7e39893af00e59c3ac6d79d7892 Mon Sep 17 00:00:00 2001 From: zsloan Date: Sun, 5 Sep 2021 17:03:51 +0000 Subject: Fixed issue that caused sample data to not be fetched correctly; there's something wrong with the 'get_probeset_data' function (not sure why this function exists) --- wqflask/wqflask/correlation/correlation_gn3_api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wqflask/wqflask/correlation/correlation_gn3_api.py b/wqflask/wqflask/correlation/correlation_gn3_api.py index d0d4bcba..a18bceaf 100644 --- a/wqflask/wqflask/correlation/correlation_gn3_api.py +++ b/wqflask/wqflask/correlation/correlation_gn3_api.py @@ -150,10 +150,7 @@ def fetch_sample_data(start_vars, this_trait, this_dataset, target_dataset): sample_data = process_samples( start_vars, this_dataset.group.all_samples_ordered()) - if target_dataset.type == "ProbeSet": - target_dataset.get_probeset_data(list(sample_data.keys())) - else: - target_dataset.get_trait_data(list(sample_data.keys())) + target_dataset.get_trait_data(list(sample_data.keys())) this_trait = retrieve_sample_data(this_trait, this_dataset) this_trait_data = { "trait_sample_data": sample_data, -- cgit v1.2.3 From 25a5fe8027a00a64513855630a4365480cf567d7 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 7 Sep 2021 18:21:34 +0000 Subject: Add timer to loading page to track how long the process has been running --- wqflask/wqflask/templates/loading.html | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/templates/loading.html b/wqflask/wqflask/templates/loading.html index 1edde31e..ccf810b0 100644 --- a/wqflask/wqflask/templates/loading.html +++ b/wqflask/wqflask/templates/loading.html @@ -12,6 +12,8 @@ {% if start_vars.tool_used == "Mapping" %}

    Computing the Maps


    + Time Elapsed: +
    Trait Metadata
    species = {{ start_vars.species[0] | upper }}{{ start_vars.species[1:] }} @@ -101,9 +103,6 @@ -- cgit v1.2.3 From d243e3a69b26d60709fe10ab0b70a0e1d53ba50d Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 7 Sep 2021 18:22:44 +0000 Subject: Add trait hash and datetime to mapping figure --- .../marker_regression/display_mapping_results.py | 68 ++++++++++++++-------- wqflask/wqflask/marker_regression/run_mapping.py | 1 + .../new/javascript/show_trait_mapping_tools.js | 10 ++-- wqflask/wqflask/templates/mapping_results.html | 1 + wqflask/wqflask/views.py | 1 + 5 files changed, 51 insertions(+), 30 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 3986c441..5f5fe6a3 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -24,6 +24,7 @@ # # Last updated by Zach 12/14/2010 +import datetime import string from math import * from PIL import Image @@ -271,6 +272,7 @@ class DisplayMappingResults: # Needing for form submission when doing single chr # mapping or remapping after changing options self.sample_vals = start_vars['sample_vals'] + self.vals_hash= start_vars['vals_hash'] self.sample_vals_dict = json.loads(self.sample_vals) self.transform = start_vars['transform'] @@ -651,7 +653,7 @@ class DisplayMappingResults: btminfo.append( 'Mapping using genotype data as a trait will result in infinity LRS at one locus. In order to display the result properly, all LRSs higher than 100 are capped at 100.') - def plotIntMapping(self, canvas, offset=(80, 120, 90, 100), zoom=1, startMb=None, endMb=None, showLocusForm=""): + def plotIntMapping(self, canvas, offset=(80, 120, 110, 100), zoom=1, startMb=None, endMb=None, showLocusForm=""): im_drawer = ImageDraw.Draw(canvas) # calculating margins xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset @@ -661,7 +663,7 @@ class DisplayMappingResults: if self.legendChecked: yTopOffset += 10 if self.covariates != "" and self.controlLocus and self.doControl != "false": - yTopOffset += 20 + yTopOffset += 25 if len(self.transform) > 0: yTopOffset += 5 else: @@ -1195,43 +1197,47 @@ class DisplayMappingResults: dataset_label = "%s - %s" % (self.dataset.group.name, self.dataset.fullname) - string1 = 'Dataset: %s' % (dataset_label) + + current_datetime = datetime.datetime.now() + string1 = 'UTC Timestamp: %s' % (current_datetime.strftime("%b %d %Y %H:%M:%S")) + string2 = 'Dataset: %s' % (dataset_label) + string3 = 'Trait Hash: %s' % (self.vals_hash) if self.genofile_string == "": - string2 = 'Genotype File: %s.geno' % self.dataset.group.name + string4 = 'Genotype File: %s.geno' % self.dataset.group.name else: - string2 = 'Genotype File: %s' % self.genofile_string + string4 = 'Genotype File: %s' % self.genofile_string - string4 = '' + string6 = '' if self.mapping_method == "gemma" or self.mapping_method == "gemma_bimbam": if self.use_loco == "True": - string3 = 'Using GEMMA mapping method with LOCO and ' + string5 = 'Using GEMMA mapping method with LOCO and ' else: - string3 = 'Using GEMMA mapping method with ' + string5 = 'Using GEMMA mapping method with ' if self.covariates != "": - string3 += 'the cofactors below:' + string5 += 'the cofactors below:' cofactor_names = ", ".join( [covar.split(":")[0] for covar in self.covariates.split(",")]) - string4 = cofactor_names + string6 = cofactor_names else: - string3 += 'no cofactors' + string5 += 'no cofactors' elif self.mapping_method == "rqtl_plink" or self.mapping_method == "rqtl_geno": - string3 = 'Using R/qtl mapping method with ' + string5 = 'Using R/qtl mapping method with ' if self.covariates != "": - string3 += 'the cofactors below:' + string5 += 'the cofactors below:' cofactor_names = ", ".join( [covar.split(":")[0] for covar in self.covariates.split(",")]) - string4 = cofactor_names + string6 = cofactor_names elif self.controlLocus and self.doControl != "false": - string3 += '%s as control' % self.controlLocus + string5 += '%s as control' % self.controlLocus else: - string3 += 'no cofactors' + string5 += 'no cofactors' else: - string3 = 'Using Haldane mapping function with ' + string5 = 'Using Haldane mapping function with ' if self.controlLocus and self.doControl != "false": - string3 += '%s as control' % self.controlLocus + string5 += '%s as control' % self.controlLocus else: - string3 += 'no control for other QTLs' + string5 += 'no control for other QTLs' y_constant = 10 if self.this_trait.name: @@ -1260,7 +1266,9 @@ class DisplayMappingResults: d = 4 + max( im_drawer.textsize(identification, font=labelFont)[0], im_drawer.textsize(string1, font=labelFont)[0], - im_drawer.textsize(string2, font=labelFont)[0]) + im_drawer.textsize(string2, font=labelFont)[0], + im_drawer.textsize(string3, font=labelFont)[0], + im_drawer.textsize(string4, font=labelFont)[0]) im_drawer.text( text=identification, xy=(xLeftOffset, y_constant * fontZoom), font=labelFont, @@ -1269,7 +1277,9 @@ class DisplayMappingResults: else: d = 4 + max( im_drawer.textsize(string1, font=labelFont)[0], - im_drawer.textsize(string2, font=labelFont)[0]) + im_drawer.textsize(string2, font=labelFont)[0], + im_drawer.textsize(string3, font=labelFont)[0], + im_drawer.textsize(string4, font=labelFont)[0]) if len(self.transform) > 0: transform_text = "Transform - " @@ -1296,14 +1306,22 @@ class DisplayMappingResults: text=string2, xy=(xLeftOffset, y_constant * fontZoom), font=labelFont, fill=labelColor) y_constant += 15 - if string3 != '': + im_drawer.text( + text=string3, xy=(xLeftOffset, y_constant * fontZoom), + font=labelFont, fill=labelColor) + y_constant += 15 + im_drawer.text( + text=string4, xy=(xLeftOffset, y_constant * fontZoom), + font=labelFont, fill=labelColor) + y_constant += 15 + if string4 != '': im_drawer.text( - text=string3, xy=(xLeftOffset, y_constant * fontZoom), + text=string5, xy=(xLeftOffset, y_constant * fontZoom), font=labelFont, fill=labelColor) y_constant += 15 - if string4 != '': + if string5 != '': im_drawer.text( - text=string4, xy=(xLeftOffset, y_constant * fontZoom), + text=string6, xy=(xLeftOffset, y_constant * fontZoom), font=labelFont, fill=labelColor) def drawGeneBand(self, canvas, gifmap, plotXScale, offset=(40, 120, 80, 10), zoom=1, startMb=None, endMb=None): diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index ebad7d36..2f90b475 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -75,6 +75,7 @@ class RunMapping: self.vals = [] self.samples = [] self.sample_vals = start_vars['sample_vals'] + self.vals_hash = start_vars['vals_hash'] sample_val_dict = json.loads(self.sample_vals) samples = sample_val_dict.keys() if (len(genofile_samplelist) != 0): diff --git a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js index 09e9d024..b75d658e 100644 --- a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js +++ b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js @@ -141,11 +141,11 @@ $('input[name=display_all]').change((function(_this) { })(this)); //ZS: This is a list of inputs to be passed to the loading page, since not all inputs on the trait page are relevant to mapping -var mapping_input_list = ['temp_uuid', 'trait_id', 'dataset', 'tool_used', 'form_url', 'method', 'transform', 'trimmed_markers', 'selected_chr', 'chromosomes', 'mapping_scale', 'sample_vals', - 'score_type', 'suggestive', 'significant', 'num_perm', 'permCheck', 'perm_output', 'perm_strata', 'categorical_vars', 'num_bootstrap', 'bootCheck', 'bootstrap_results', - 'LRSCheck', 'covariates', 'maf', 'use_loco', 'manhattan_plot', 'control_marker', 'do_control', 'genofile', - 'pair_scan', 'startMb', 'endMb', 'graphWidth', 'lrsMax', 'additiveCheck', 'showSNP', 'showGenes', 'viewLegend', 'haplotypeAnalystCheck', - 'mapmethod_rqtl_geno', 'mapmodel_rqtl_geno', 'temp_trait', 'group', 'species', 'reaper_version', 'primary_samples'] +var mapping_input_list = ['temp_uuid', 'trait_id', 'dataset', 'tool_used', 'form_url', 'method', 'transform', 'trimmed_markers', 'selected_chr', 'chromosomes', 'mapping_scale', + 'sample_vals', 'vals_hash', 'score_type', 'suggestive', 'significant', 'num_perm', 'permCheck', 'perm_output', 'perm_strata', 'categorical_vars', + 'num_bootstrap', 'bootCheck', 'bootstrap_results', 'LRSCheck', 'covariates', 'maf', 'use_loco', 'manhattan_plot', 'control_marker', + 'do_control', 'genofile', 'pair_scan', 'startMb', 'endMb', 'graphWidth', 'lrsMax', 'additiveCheck', 'showSNP', 'showGenes', 'viewLegend', + 'haplotypeAnalystCheck', 'mapmethod_rqtl_geno', 'mapmodel_rqtl_geno', 'temp_trait', 'group', 'species', 'reaper_version', 'primary_samples'] $(".rqtl-geno-tab, #rqtl_geno_compute").on("click", (function(_this) { return function() { diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html index d446745d..162ae810 100644 --- a/wqflask/wqflask/templates/mapping_results.html +++ b/wqflask/wqflask/templates/mapping_results.html @@ -34,6 +34,7 @@ + diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 11a9380c..707b18e1 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1051,6 +1051,7 @@ def mapping_results_page(): 'samples', 'vals', 'sample_vals', + 'vals_hash', 'first_run', 'output_files', 'geno_db_exists', -- cgit v1.2.3 From 1f2e32a91727abab77ecdf501fcc5040b17dfece Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 9 Sep 2021 17:07:06 +0000 Subject: Replaced trait name with trait display name in display_mapping_results so the group codes will be includes in phenotype IDs --- wqflask/wqflask/marker_regression/display_mapping_results.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index 5f5fe6a3..e9ba7dff 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -1249,18 +1249,18 @@ class DisplayMappingResults: if self.this_trait.symbol: identification += "Trait: %s - %s" % ( - self.this_trait.name, self.this_trait.symbol) + self.this_trait.display_name, self.this_trait.symbol) elif self.dataset.type == "Publish": if self.this_trait.post_publication_abbreviation: identification += "Trait: %s - %s" % ( - self.this_trait.name, self.this_trait.post_publication_abbreviation) + self.this_trait.display_name, self.this_trait.post_publication_abbreviation) elif self.this_trait.pre_publication_abbreviation: identification += "Trait: %s - %s" % ( - self.this_trait.name, self.this_trait.pre_publication_abbreviation) + self.this_trait.display_name, self.this_trait.pre_publication_abbreviation) else: - identification += "Trait: %s" % (self.this_trait.name) + identification += "Trait: %s" % (self.this_trait.display_name) else: - identification += "Trait: %s" % (self.this_trait.name) + identification += "Trait: %s" % (self.this_trait.display_name) identification += " with %s samples" % (self.n_samples) d = 4 + max( -- cgit v1.2.3 From 5020158b3ab7cf14a5809af65d2c616d32714a22 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 9 Sep 2021 17:07:47 +0000 Subject: Change trait name to display name in the metadata at the top of the page (so group codes will be includes in phenotype IDs --- wqflask/wqflask/templates/mapping_results.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html index 162ae810..7f865056 100644 --- a/wqflask/wqflask/templates/mapping_results.html +++ b/wqflask/wqflask/templates/mapping_results.html @@ -68,7 +68,7 @@

    Map Viewer: Whole Genome


    Population: {{ dataset.group.species|capitalize }} {{ dataset.group.name }}
    Database: {{ dataset.fullname }}
    - {% if dataset.type == "ProbeSet" %}Trait ID:{% else %}Record ID:{% endif %} {{ this_trait.name }}
    + {% if dataset.type == "ProbeSet" %}Trait ID:{% else %}Record ID:{% endif %} {{ this_trait.display_name }}
    {% if dataset.type == "ProbeSet" %} Gene Symbol: {{ this_trait.symbol }}
    Location: Chr {{ this_trait.chr }} @ {{ this_trait.mb }} Mb
    -- cgit v1.2.3 From f51569c80a4e00ad7dcd99ed9bf3ed6d9aaf4051 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 10 Sep 2021 18:54:44 +0000 Subject: Removed encoding, since it's apparently not needed since the Python 3 switchover (and was causing there to be no matches between user IDs and groups) --- wqflask/utility/redis_tools.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py index ff125bd2..99295c67 100644 --- a/wqflask/utility/redis_tools.py +++ b/wqflask/utility/redis_tools.py @@ -135,10 +135,8 @@ def get_user_groups(user_id): for key in groups_list: try: group_ob = json.loads(groups_list[key]) - group_admins = set([this_admin.encode( - 'utf-8') if this_admin else None for this_admin in group_ob['admins']]) - group_members = set([this_member.encode( - 'utf-8') if this_member else None for this_member in group_ob['members']]) + group_admins = set([this_admin if this_admin else None for this_admin in group_ob['admins']]) + group_members = set([this_member if this_member else None for this_member in group_ob['members']]) if user_id in group_admins: admin_group_ids.append(group_ob['id']) elif user_id in group_members: -- cgit v1.2.3 From d76e1ec30195074cf8dfd0f8396285d31faa8984 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 10 Sep 2021 19:02:10 +0000 Subject: Fix issue with the way the template was checking if genofile_string was set; it was receiving an empty string, so the previous logic made it think there was a genofile string when there wasn't one --- wqflask/wqflask/templates/mapping_results.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html index 7f865056..7a222d0c 100644 --- a/wqflask/wqflask/templates/mapping_results.html +++ b/wqflask/wqflask/templates/mapping_results.html @@ -73,7 +73,7 @@ Gene Symbol: {{ this_trait.symbol }}
    Location: Chr {{ this_trait.chr }} @ {{ this_trait.mb }} Mb
    {% endif %} - {% if genofile_string is defined %} + {% if genofile_string != "" %} Genotypes: {{ genofile_string.split(":")[1] }} {% endif %}
    -- cgit v1.2.3 From f414cd4109229546bfb8c56fea95b6729121f0b3 Mon Sep 17 00:00:00 2001 From: jgart Date: Thu, 9 Sep 2021 22:28:02 -0500 Subject: Add Jupyter Notebook Launcher to Tools dropdown menu --- wqflask/wqflask/templates/base.html | 1 + 1 file changed, 1 insertion(+) diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html index 049ebe6d..14e6bc88 100644 --- a/wqflask/wqflask/templates/base.html +++ b/wqflask/wqflask/templates/base.html @@ -87,6 +87,7 @@
  • Systems Genetics PheWAS
  • Genome Browser
  • BXD Power Calculator
  • +
  • Jupyter Notebook Launcher
  • Interplanetary File System
  • -- cgit v1.2.3 From 38a8544553506651a263b0134ab5372c6a62c629 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 13 Sep 2021 18:58:10 +0000 Subject: Temporarily point the View in GN1 button to gn1-lily.genenetwork.org since some features aren't working on gn1.genenetwork.org yet --- wqflask/wqflask/templates/show_trait_details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html index 53e16aa0..36d3c15a 100644 --- a/wqflask/wqflask/templates/show_trait_details.html +++ b/wqflask/wqflask/templates/show_trait_details.html @@ -233,7 +233,7 @@ {% endif %} {% endif %} - + {% if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access" %} {% if this_trait.dataset.type == 'Publish' %} -- cgit v1.2.3 From 6a94f7a3a4893f087af53c2d39685d9dcb4b733d Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 13 Sep 2021 19:41:19 +0000 Subject: Change Genotype File text to only show the meaningful genotype deescription --- wqflask/wqflask/marker_regression/display_mapping_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index e9ba7dff..a9ebeb64 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -1206,7 +1206,7 @@ class DisplayMappingResults: if self.genofile_string == "": string4 = 'Genotype File: %s.geno' % self.dataset.group.name else: - string4 = 'Genotype File: %s' % self.genofile_string + string4 = 'Genotype File: %s' % self.genofile_string.split(":")[1] string6 = '' if self.mapping_method == "gemma" or self.mapping_method == "gemma_bimbam": -- cgit v1.2.3 From 75132a5382d32d9332f743aca0e406ae6137d7e5 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 13 Sep 2021 19:49:05 +0000 Subject: Include some extra metadata in the mapping page details (in addition to the figure itself) --- wqflask/wqflask/marker_regression/display_mapping_results.py | 4 ++-- wqflask/wqflask/templates/mapping_results.html | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/marker_regression/display_mapping_results.py b/wqflask/wqflask/marker_regression/display_mapping_results.py index a9ebeb64..77d6e2db 100644 --- a/wqflask/wqflask/marker_regression/display_mapping_results.py +++ b/wqflask/wqflask/marker_regression/display_mapping_results.py @@ -1198,8 +1198,8 @@ class DisplayMappingResults: self.dataset.fullname) - current_datetime = datetime.datetime.now() - string1 = 'UTC Timestamp: %s' % (current_datetime.strftime("%b %d %Y %H:%M:%S")) + self.current_datetime = datetime.datetime.now().strftime("%b %d %Y %H:%M:%S") + string1 = 'UTC Timestamp: %s' % (self.current_datetime) string2 = 'Dataset: %s' % (dataset_label) string3 = 'Trait Hash: %s' % (self.vals_hash) diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html index 7a222d0c..f2d11e89 100644 --- a/wqflask/wqflask/templates/mapping_results.html +++ b/wqflask/wqflask/templates/mapping_results.html @@ -69,14 +69,15 @@ Population: {{ dataset.group.species|capitalize }} {{ dataset.group.name }}
    Database: {{ dataset.fullname }}
    {% if dataset.type == "ProbeSet" %}Trait ID:{% else %}Record ID:{% endif %} {{ this_trait.display_name }}
    + Trait Hash: {{ vals_hash }}
    {% if dataset.type == "ProbeSet" %} Gene Symbol: {{ this_trait.symbol }}
    Location: Chr {{ this_trait.chr }} @ {{ this_trait.mb }} Mb
    {% endif %} {% if genofile_string != "" %} - Genotypes: {{ genofile_string.split(":")[1] }} + Genotypes: {{ genofile_string.split(":")[1] }}
    {% endif %} -
    + Current Date/Time: {{ current_datetime }}

    Download Full Results
    -- cgit v1.2.3 From 2ef7cad06cc1dcde61c91d2eafef002d25cb194e Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 13 Sep 2021 19:58:43 +0000 Subject: Add trait name and trait hash to exported mapping results --- wqflask/wqflask/marker_regression/run_mapping.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index 2f90b475..b101b948 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -422,8 +422,9 @@ class RunMapping: total_markers = len(self.qtl_results) with Bench("Exporting Results"): - export_mapping_results(self.dataset, self.this_trait, self.qtl_results, self.mapping_results_path, - self.mapping_scale, self.score_type, self.transform, self.covariates, self.n_samples) + export_mapping_results(self.dataset, self.this_trait, self.qtl_results, + self.mapping_results_path, self.mapping_scale, self.score_type, + self.transform, self.covariates, self.n_samples, self.vals_hash) with Bench("Trimming Markers for Figure"): if len(self.qtl_results) > 30000: @@ -541,13 +542,15 @@ class RunMapping: return trimmed_genotype_data -def export_mapping_results(dataset, trait, markers, results_path, mapping_scale, score_type, transform, covariates, n_samples): +def export_mapping_results(dataset, trait, markers, results_path, mapping_scale, score_type, transform, covariates, n_samples, vals_hash): with open(results_path, "w+") as output_file: output_file.write( "Time/Date: " + datetime.datetime.now().strftime("%x / %X") + "\n") output_file.write( "Population: " + dataset.group.species.title() + " " + dataset.group.name + "\n") output_file.write("Data Set: " + dataset.fullname + "\n") + output_file.write("Trait: " + trait.display_name + "\n") + output_file.write("Trait Hash: " + vals_hash + "\n") output_file.write("N Samples: " + str(n_samples) + "\n") if len(transform) > 0: transform_text = "Transform - " -- cgit v1.2.3 From 91d04e5b573107aead1da8482dd9184d9869b82a Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 13 Sep 2021 20:03:45 +0000 Subject: Change mapping export filename to use the trait hash --- wqflask/wqflask/marker_regression/run_mapping.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index b101b948..361c413b 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -104,9 +104,7 @@ class RunMapping: if "results_path" in start_vars: self.mapping_results_path = start_vars['results_path'] else: - mapping_results_filename = self.dataset.group.name + "_" + \ - ''.join(random.choice(string.ascii_uppercase + string.digits) - for _ in range(6)) + mapping_results_filename = "_".join([self.dataset.group.name, self.vals_hash]) self.mapping_results_path = "{}{}.csv".format( webqtlConfig.GENERATED_IMAGE_DIR, mapping_results_filename) -- cgit v1.2.3 From 24e0de14969edb220a03e22d1c9b7e6ed33f6b82 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 13 Sep 2021 20:04:30 +0000 Subject: Change mapping export filename to use the path passed from run_mapping, instead of the generic mapping_results.csv --- wqflask/wqflask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 707b18e1..54314a20 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -1170,7 +1170,7 @@ def export_mapping_results(): results_csv = open(file_path, "r").read() response = Response(results_csv, mimetype='text/csv', - headers={"Content-Disposition": "attachment;filename=mapping_results.csv"}) + headers={"Content-Disposition": "attachment;filename=" + os.path.basename(file_path)}) return response -- cgit v1.2.3 From c0fbbb42a79baa823b517ee8f537cec1edc057c8 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 14 Sep 2021 18:09:36 +0000 Subject: Replace / with _ in the file hashes in rqtl_mapping.py, since they get translated to directories --- wqflask/wqflask/marker_regression/rqtl_mapping.py | 1 + 1 file changed, 1 insertion(+) diff --git a/wqflask/wqflask/marker_regression/rqtl_mapping.py b/wqflask/wqflask/marker_regression/rqtl_mapping.py index cd578870..a148b49c 100644 --- a/wqflask/wqflask/marker_regression/rqtl_mapping.py +++ b/wqflask/wqflask/marker_regression/rqtl_mapping.py @@ -61,6 +61,7 @@ def get_hash_of_textio(the_file: TextIO) -> str: the_file.seek(0) hash_of_file = hashlib.md5(the_file.read().encode()).hexdigest() + hash_of_file = hash_of_file.replace("/", "_") # Replace / with _ to prevent issue with filenames being translated to directories return hash_of_file -- cgit v1.2.3 From 8d54d1871e1319f7db46132d3eed61a817d5dc4c Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 14 Sep 2021 18:14:33 +0000 Subject: Replace / with _ in the mapping results filename, due to / causing issues with being translated to directories --- wqflask/wqflask/marker_regression/run_mapping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index 361c413b..1df53fef 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -104,7 +104,7 @@ class RunMapping: if "results_path" in start_vars: self.mapping_results_path = start_vars['results_path'] else: - mapping_results_filename = "_".join([self.dataset.group.name, self.vals_hash]) + mapping_results_filename = "_".join([self.dataset.group.name, self.vals_hash]).replace("/", "_") self.mapping_results_path = "{}{}.csv".format( webqtlConfig.GENERATED_IMAGE_DIR, mapping_results_filename) -- cgit v1.2.3 From b6c0885007d516b257d1027b97df66c11b8672dd Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 16 Sep 2021 13:26:53 +0300 Subject: wqflask: views: Redirect to the correct URL after phenotype update --- wqflask/wqflask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 54314a20..85aa6b17 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -633,7 +633,7 @@ def update_phenotype(): json_data=json.dumps(diff_data))) flash(f"Diff-data: \n{diff_data}\nhas been uploaded", "success") return redirect(f"/trait/{data_.get('dataset-name')}" - f"/edit/phenotype-id/{data_.get('phenotype-id')}") + f"/edit/inbredset-id/{data_.get('inbred-set-id')}") @app.route("/probeset/update", methods=["POST"]) -- cgit v1.2.3 From 03a91557c119fc2c4cdfc36015260034ead0ba98 Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 20 Sep 2021 06:54:04 +0300 Subject: Provide UI elements for clustered heatmap generation Issue: https://github.com/genenetwork/gn-gemtext-threads/blob/main/topics/gn1-migration-to-gn2/clustering.gmi * wqflask/wqflask/collect.py: provide hard-coded URL for testing * wqflask/wqflask/templates/collections/view.html: provide button Provide a button to trigger the heatmap generation. As a test, we also provide a hard-coded URL for the API endpoint to get the heatmap data. --- wqflask/wqflask/collect.py | 3 ++- wqflask/wqflask/templates/collections/view.html | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/collect.py b/wqflask/wqflask/collect.py index 01274ba9..41ff47ae 100644 --- a/wqflask/wqflask/collect.py +++ b/wqflask/wqflask/collect.py @@ -219,7 +219,8 @@ def view_collection(): json_version.append(jsonable(trait_ob)) collection_info = dict(trait_obs=trait_obs, - uc=uc) + uc=uc, + heatmap_data_url="http://localhost:8080/api/heatmaps/clustered") if "json" in params: return json.dumps(json_version) diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index 9ec98ab1..09f9dd21 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -50,6 +50,12 @@ +
    -- cgit v1.2.3 From 99bfeeef777bdaa804e4f91cae486a34fdf1e1c2 Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 20 Sep 2021 08:55:01 +0300 Subject: Implement proof-of-concept code to submit data Issue: https://github.com/genenetwork/gn-gemtext-threads/blob/main/topics/gn1-migration-to-gn2/clustering.gmi * Implement some javascript to activate the "Clustered Heatmap" button. This commit provides a proof-of-concept implementation to help with identifying the requirements for sending and receiving of the heatmaps data. --- wqflask/wqflask/templates/collections/view.html | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index 09f9dd21..783458fc 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -253,6 +253,27 @@ $("#make_default").on("click", function(){ make_default(); }); + + $("#clustered-heatmaps").on("click", function() { + heatmap_url = $(this).attr("data-url") + console.log("heatmap url:", heatmap_url) + traits = $(".trait_checkbox:checked").map(function() { + return this.value + }).get(); + console.log("SELECTED TRAITS", traits); + $.ajax({ + type: "POST", + url: heatmap_url, + contentType: "application/json", + data: JSON.stringify({ + "traits_names": traits + }), + dataType: "JSON", + success: function(res) { + console.log("results:", res) + } + }); + }); }); -- cgit v1.2.3 From 614f641624582754e29b84d632e311ed5f186c1e Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 20 Sep 2021 09:06:28 +0300 Subject: Move "Clustered Heatmap" button to separate form Issue: https://github.com/genenetwork/gn-gemtext-threads/blob/main/topics/gn1-migration-to-gn2/clustering.gmi * Move the button out of the "export_form" into a new "heatmaps_form" to avoid some weird JS interaction that showed up. --- wqflask/wqflask/templates/collections/view.html | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index 783458fc..06fd80f4 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -50,13 +50,16 @@ - + +
    + +
    Show/Hide Columns: -- cgit v1.2.3 From cfe0de277021c41eedeb65ec7f1560ac6d67ad0a Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 20 Sep 2021 09:15:10 +0300 Subject: Fix id used. --- wqflask/wqflask/templates/collections/view.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index 06fd80f4..0578460d 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -257,7 +257,7 @@ make_default(); }); - $("#clustered-heatmaps").on("click", function() { + $("#clustered-heatmap").on("click", function() { heatmap_url = $(this).attr("data-url") console.log("heatmap url:", heatmap_url) traits = $(".trait_checkbox:checked").map(function() { -- cgit v1.2.3 From f1876d4d8da5c973375fc398fedaa12825a0b780 Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 20 Sep 2021 09:20:44 +0300 Subject: Prevent the default submit action Issue: https://github.com/genenetwork/gn-gemtext-threads/blob/main/topics/gn1-migration-to-gn2/clustering.gmi * Prevent the default submit action. --- wqflask/wqflask/templates/collections/view.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index 0578460d..51b96e10 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -257,6 +257,10 @@ make_default(); }); + $("#heatmaps_form").submit(function(e) { + e.preventDefault(); + }); + $("#clustered-heatmap").on("click", function() { heatmap_url = $(this).attr("data-url") console.log("heatmap url:", heatmap_url) -- cgit v1.2.3 From 719b256996facb1f4bee2a0288bce1f18168f99b Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 16 Sep 2021 17:59:49 +0000 Subject: Added back the option to edit privileges on trait pages, since the metadata edit button had replaced it --- wqflask/wqflask/templates/show_trait_details.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html index 36d3c15a..6a541c8c 100644 --- a/wqflask/wqflask/templates/show_trait_details.html +++ b/wqflask/wqflask/templates/show_trait_details.html @@ -242,6 +242,9 @@ {% if this_trait.dataset.type == 'ProbeSet' %} {% endif %} + {% if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access" %} + + {% endif %} {% endif %}
    -- cgit v1.2.3 From a9509a01191883e61cd5013453d18e89db022df2 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 20 Sep 2021 17:34:26 +0000 Subject: Removed the reaper_version selection option, since original qtlreaper is no longer supported --- wqflask/wqflask/templates/show_trait_mapping_tools.html | 9 --------- 1 file changed, 9 deletions(-) diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html index 5365140d..c6b6c0e1 100755 --- a/wqflask/wqflask/templates/show_trait_mapping_tools.html +++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html @@ -95,15 +95,6 @@ {% elif mapping_method == "QTLReaper" %}
    -
    - -
    - -
    -
    -- cgit v1.2.3 From 2ad5b87f1c9bc5ce5bd3f54f7d83f18123ce3c3e Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 20 Sep 2021 17:37:18 +0000 Subject: Returned the GN1 url to gn1.genenetwork.org since it's apparently working well enough again now that some issues have been fixed --- wqflask/wqflask/templates/show_trait_details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html index 6a541c8c..2a21dd24 100644 --- a/wqflask/wqflask/templates/show_trait_details.html +++ b/wqflask/wqflask/templates/show_trait_details.html @@ -233,7 +233,7 @@ {% endif %} {% endif %} - + {% if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access" %} {% if this_trait.dataset.type == 'Publish' %} -- cgit v1.2.3 From 98f9027b8ce8f33dda7f0b1b5495b22b4a450349 Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Wed, 22 Sep 2021 06:30:42 +0300 Subject: Test heatmap creation from serialized figure Issue: https://github.com/genenetwork/gn-gemtext-threads/blob/main/topics/gn1-migration-to-gn2/clustering.gmi * Attempt using the figure, serialized as JSON, to display the clustered heatmap. --- wqflask/wqflask/templates/collections/view.html | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index 51b96e10..bca629a9 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -128,6 +128,9 @@ +
    +

    @@ -148,6 +151,8 @@ + + +{% endblock %} + -- cgit v1.2.3 From 167ec0df8d8d487832e6a0acaee3eac8963d9804 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Fri, 1 Oct 2021 05:34:20 +0300 Subject: add xterm web terminal --- wqflask/wqflask/templates/test_wgcna_results.html | 225 +++++++++++----------- 1 file changed, 116 insertions(+), 109 deletions(-) diff --git a/wqflask/wqflask/templates/test_wgcna_results.html b/wqflask/wqflask/templates/test_wgcna_results.html index 37ea2aa0..f95766ad 100644 --- a/wqflask/wqflask/templates/test_wgcna_results.html +++ b/wqflask/wqflask/templates/test_wgcna_results.html @@ -1,146 +1,153 @@ {% extends "base.html" %} {% block title %}WCGNA results{% endblock %} +{% block content %} + -{% block content %} - -
    +.control_net_colors { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + text-align: center; +} -
    -
    - {% for key, value in results["data"]["output"]["soft_threshold"].items()%} -
    -

    {{key}}

    - {% for val in value %} -

    {{val|round(3)}}

    - - {% endfor %} - -
    - {% endfor %} +.control_mod_eigens { + display: grid; + grid-template-columns: repeat(2, 200px); +} -
    +#terminal { -
    -

    Net colors

    -
    - {% for key,value in results["data"]["output"]["net_colors"].items() %} -
    -

    {{key}}

    -

    {{value}}

    -
    - - {% endfor %} + max-width: 768px; -
    - + margin: 10px; -
    - - -
    -

    Module eigen genes

    - -
    - {% for strain in results["data"]["input"]["sample_names"]%} - {{strain}} - {% endfor %} - {% for mod,values in results["data"]["output"]["ModEigens"].items() %} - {{mod}} {{values}} - - {% endfor %} - -
    - -
    - - - dsffsdf - +} + +
    +
    +
    +
    +
    +
    +
    + {% for key, value in results["data"]["output"]["soft_threshold"].items()%} +
    +

    {{key}}

    + {% for val in value %} +

    {{val|round(3)}}

    + {% endfor %} +
    + {% endfor %} +
    +
    +

    Net colors

    +
    + {% for key,value in results["data"]["output"]["net_colors"].items() %} +
    +

    {{key}}

    +

    {{value}}

    +
    + {% endfor %} +
    +
    +
    +

    Module eigen genes

    +
    + {% for strain in results["data"]["input"]["sample_names"]%} + {{strain}} + {% endfor %} + {% for mod,values in results["data"]["output"]["ModEigens"].items() %} + {{mod}} {{values}} + {% endfor %} +
    + + +
    +
    +
    + + -{% endblock %} + writeToTerminal({ cursorBlink: true, lineHeight: 1.2 }, "terminal")(terminal_output) + +{% endblock %} \ No newline at end of file -- cgit v1.2.3 From 266d4c4a425ca0a215c8d789e2978d213d5ff37d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Sep 2021 21:25:13 +0300 Subject: Rename "admin_login_required" to "edit_access_required" --- wqflask/wqflask/decorators.py | 2 +- wqflask/wqflask/views.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py index f0978fd3..f6e3eb8a 100644 --- a/wqflask/wqflask/decorators.py +++ b/wqflask/wqflask/decorators.py @@ -3,7 +3,7 @@ from flask import g from functools import wraps -def admin_login_required(f): +def edit_access_required(f): """Use this for endpoints where admins are required""" @wraps(f) def wrap(*args, **kwargs): diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index b297da08..5067ca0e 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -85,7 +85,7 @@ from wqflask.export_traits import export_search_results_csv from wqflask.gsearch import GSearch from wqflask.update_search_results import GSearch as UpdateGSearch from wqflask.docs import Docs, update_text -from wqflask.decorators import admin_login_required +from wqflask.decorators import edit_access_required from wqflask.db_info import InfoPage from utility import temp_data @@ -420,7 +420,7 @@ def submit_trait_form(): @app.route("/trait//edit/inbredset-id/") -@admin_login_required +@edit_access_required def edit_phenotype(name, inbredset_id): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), @@ -477,7 +477,7 @@ def edit_phenotype(name, inbredset_id): @app.route("/trait/edit/probeset-name/") -@admin_login_required +@edit_access_required def edit_probeset(dataset_name): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), @@ -520,7 +520,7 @@ def edit_probeset(dataset_name): @app.route("/trait/update", methods=["POST"]) -@admin_login_required +@edit_access_required def update_phenotype(): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), @@ -646,7 +646,7 @@ def update_phenotype(): @app.route("/probeset/update", methods=["POST"]) -@admin_login_required +@edit_access_required def update_probeset(): conn = MySQLdb.Connect(db=current_app.config.get("DB_NAME"), user=current_app.config.get("DB_USER"), @@ -1381,7 +1381,7 @@ def get_sample_data_as_csv(trait_name: int, phenotype_id: int): @app.route("/admin/data-sample/diffs/") -@admin_login_required +@edit_access_required def display_diffs_admin(): TMPDIR = current_app.config.get("TMPDIR") DIFF_DIR = f"{TMPDIR}/sample-data/diffs" -- cgit v1.2.3 From 7f317126d7d422b073cb4e4a8698757fe1e763f3 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 21 Sep 2021 21:36:32 +0300 Subject: Replace hard-coded e-mails with gn-proxy queries * wqflask/wqflask/decorators.py (edit_access_required.wrap): Query the proxy to see the access rights of a given user. --- wqflask/wqflask/decorators.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py index f6e3eb8a..54aa6795 100644 --- a/wqflask/wqflask/decorators.py +++ b/wqflask/wqflask/decorators.py @@ -1,14 +1,36 @@ """This module contains gn2 decorators""" from flask import g +from typing import Dict from functools import wraps +from utility.hmac import hmac_creation + +import json +import requests def edit_access_required(f): """Use this for endpoints where admins are required""" @wraps(f) def wrap(*args, **kwargs): - if g.user_session.record.get(b"user_email_address") not in [ - b"labwilliams@gmail.com"]: + resource_id: str = "" + if kwargs.get("inbredset_id"): # data type: dataset-publish + resource_id = hmac_creation("dataset-publish:" + f"{kwargs.get('inbredset_id')}:" + f"{kwargs.get('name')}") + if kwargs.get("dataset_name"): # data type: dataset-probe + resource_id = hmac_creation("dataset-probeset:" + f"{kwargs.get('dataset_name')}") + response: Dict = {} + try: + _user_id = g.user_session.record.get(b"user_id", + "").decode("utf-8") + response = json.loads( + requests.get("http://localhost:8080/" + "available?resource=" + f"{resource_id}&user={_user_id}").content) + except: + response = {} + if "edit" not in response.get("data", []): return "You need to be admin", 401 return f(*args, **kwargs) return wrap -- cgit v1.2.3 From ac14326be6695f185f843d29bf3ff016f5eb3016 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Sep 2021 14:30:09 +0300 Subject: new_security: login_user.html: Delete commented out block --- .../wqflask/templates/new_security/login_user.html | 26 ---------------------- 1 file changed, 26 deletions(-) diff --git a/wqflask/wqflask/templates/new_security/login_user.html b/wqflask/wqflask/templates/new_security/login_user.html index 095036f0..88eab6bc 100644 --- a/wqflask/wqflask/templates/new_security/login_user.html +++ b/wqflask/wqflask/templates/new_security/login_user.html @@ -114,31 +114,5 @@ label.error,div.error{ {% endblock %} {% block js %} - - - {% include "new_security/_scripts.html" %} {% endblock %} -- cgit v1.2.3 From 84a0cce8a341b8b45b3b0037379818c32d5614b2 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 22 Sep 2021 14:32:03 +0300 Subject: Remove "_scripts.html" and all it's references --- wqflask/wqflask/templates/new_security/_scripts.html | 1 - wqflask/wqflask/templates/new_security/forgot_password.html | 1 - wqflask/wqflask/templates/new_security/forgot_password_step2.html | 1 - wqflask/wqflask/templates/new_security/password_reset.html | 1 - wqflask/wqflask/templates/new_security/register_user.html | 1 - wqflask/wqflask/templates/new_security/registered.html | 1 - wqflask/wqflask/templates/new_security/thank_you.html | 1 - wqflask/wqflask/templates/new_security/verification_still_needed.html | 1 - 8 files changed, 8 deletions(-) delete mode 100644 wqflask/wqflask/templates/new_security/_scripts.html diff --git a/wqflask/wqflask/templates/new_security/_scripts.html b/wqflask/wqflask/templates/new_security/_scripts.html deleted file mode 100644 index 5fefe305..00000000 --- a/wqflask/wqflask/templates/new_security/_scripts.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/wqflask/wqflask/templates/new_security/forgot_password.html b/wqflask/wqflask/templates/new_security/forgot_password.html index e5c42a45..60a221da 100644 --- a/wqflask/wqflask/templates/new_security/forgot_password.html +++ b/wqflask/wqflask/templates/new_security/forgot_password.html @@ -48,6 +48,5 @@ {% endblock %} {% block js %} - {% include "new_security/_scripts.html" %} {% endblock %} diff --git a/wqflask/wqflask/templates/new_security/forgot_password_step2.html b/wqflask/wqflask/templates/new_security/forgot_password_step2.html index b4bf41c7..1835fd4c 100644 --- a/wqflask/wqflask/templates/new_security/forgot_password_step2.html +++ b/wqflask/wqflask/templates/new_security/forgot_password_step2.html @@ -20,7 +20,6 @@ {% endblock %} {% block js %} - {% include "new_security/_scripts.html" %} {% endblock %} diff --git a/wqflask/wqflask/templates/new_security/password_reset.html b/wqflask/wqflask/templates/new_security/password_reset.html index 684c12b1..e21f075c 100644 --- a/wqflask/wqflask/templates/new_security/password_reset.html +++ b/wqflask/wqflask/templates/new_security/password_reset.html @@ -73,7 +73,6 @@ {% block js %} - {% include "new_security/_scripts.html" %} {% endblock %} diff --git a/wqflask/wqflask/templates/new_security/register_user.html b/wqflask/wqflask/templates/new_security/register_user.html index 3ae4488b..c2895517 100644 --- a/wqflask/wqflask/templates/new_security/register_user.html +++ b/wqflask/wqflask/templates/new_security/register_user.html @@ -100,7 +100,6 @@ {% block js %} - {% include "new_security/_scripts.html" %} {% endblock %} diff --git a/wqflask/wqflask/templates/new_security/registered.html b/wqflask/wqflask/templates/new_security/registered.html index f2f58ec1..29889a97 100644 --- a/wqflask/wqflask/templates/new_security/registered.html +++ b/wqflask/wqflask/templates/new_security/registered.html @@ -19,7 +19,6 @@ {% block js %} - {% include "new_security/_scripts.html" %} {% endblock %} diff --git a/wqflask/wqflask/templates/new_security/thank_you.html b/wqflask/wqflask/templates/new_security/thank_you.html index 0ff7ee8d..d4f5e574 100644 --- a/wqflask/wqflask/templates/new_security/thank_you.html +++ b/wqflask/wqflask/templates/new_security/thank_you.html @@ -18,7 +18,6 @@ {% endblock %} {% block js %} - {% include "new_security/_scripts.html" %} {% endblock %} diff --git a/wqflask/wqflask/templates/new_security/verification_still_needed.html b/wqflask/wqflask/templates/new_security/verification_still_needed.html index dc0f9e68..1f91fd8d 100644 --- a/wqflask/wqflask/templates/new_security/verification_still_needed.html +++ b/wqflask/wqflask/templates/new_security/verification_still_needed.html @@ -21,7 +21,6 @@ {% endblock %} {% block js %} - {% include "new_security/_scripts.html" %} {% endblock %} -- cgit v1.2.3 From 6e0ea75ca427721aed0a5f394b501b2cde9bf769 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 23 Sep 2021 13:17:55 +0300 Subject: Add script for creating/ updating groups during authorisation --- scripts/authentication/group.py | 130 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 scripts/authentication/group.py diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py new file mode 100644 index 00000000..b89bc3ec --- /dev/null +++ b/scripts/authentication/group.py @@ -0,0 +1,130 @@ +"""A script for adding users to a specific group. + +Example: + +Assuming there are no groups and 'test@bonfacemunyoki.com' does not +exist in Redis: + +.. code-block:: bash + python group.py -g "editors" -m "test@bonfacemunyoki.com" + +results in:: + + Successfully created the group: 'editors' + Data: '{"admins": [], "members": []}' + +If 'me@bonfacemunyoki.com' exists in 'users' in Redis and we run: + +.. code-block:: bash + python group.py -g "editors" -m "me@bonfacemunyoki.com" + +now results in:: + + No new group was created. + Updated Data: {'admins': [], 'members': ['me@bonfacemunyoki.com']} + +""" + +import argparse +import redis +import json + +from typing import Dict, List, Optional, Set +from glom import glom # type: ignore + + +def create_group_data(users: Dict, target_group: str, + members: Optional[str] = None, + admins: Optional[str] = None) -> Dict: + """Return a dictionary that contains the following keys: "key", + "field", and "value" that can be used in a redis hash as follows: + HSET key field value + + Parameters: + + - `users`: a list of users for example: + + {'8ad942fe-490d-453e-bd37-56f252e41603': + '{"email_address": "me@test.com", + "full_name": "John Doe", + "organization": "Genenetwork", + "password": {"algorithm": "pbkdf2", + "hashfunc": "sha256", + "salt": "gJrd1HnPSSCmzB5veMPaVk2ozzDlS1Z7Ggcyl1+pciA=", + "iterations": 100000, "keylength": 32, + "created_timestamp": "2021-09-22T11:32:44.971912", + "password": "edcdaa60e84526c6"}, + "user_id": "8ad942fe", "confirmed": 1, + "registration_info": { + "timestamp": "2021-09-22T11:32:45.028833", + "ip_address": "127.0.0.1", + "user_agent": "Mozilla/5.0"}}'} + + - `target_group`: the group name that will be stored inside the + "groups" hash in Redis. + + - `members`: a comma-separated list of values that contain members + of the `target_group` e.g. "me@test1.com, me@test2.com, + me@test3.com" + + - `admins`: a comma-separated list of values that contain + administrators of the `target_group` e.g. "me@test1.com, + me@test2.com, me@test3.com" + + """ + _members = "".join(members.split()).split(",") if members else [] + _admins: List = "".join(admins.split()).split(",") if admins else [] + + user_emails: Set = {glom(json.loads(user_details), "email_address") + for _, user_details in users.items()} + + return {"key": "groups", + "field": target_group, + "value": json.dumps({ + "admins": [admin for admin in _admins + if admin in user_emails], + "members": [member for member in _members + if member in user_emails] + })} + + +if __name__ == "__main__": + # Initialising the parser CLI arguments + parser = argparse.ArgumentParser() + parser.add_argument("-g", "--group-name", + help="This is the name of the GROUP mask") + parser.add_argument("-m", "--members", + help="Members of the GROUP mask") + parser.add_argument("-a", "--admins", + help="Admins of the GROUP mask") + args = parser.parse_args() + + if not args.group_name: + exit("\nExiting. Please specify a group name to use!\n") + + members = args.members if args.members else None + admins = args.admins if args.admins else None + REDIS_CONN = redis.Redis() + USERS = {key.decode(): val.decode() + for key, val in REDIS_CONN.hgetall("users").items()} + + if not any([members, admins]): + exit("\nExiting. Please provide a value for " + "MEMBERS(-m) or ADMINS(-a)!\n") + + data = create_group_data( + users=USERS, + target_group=args.group_name, + members=members, + admins=admins) + created_p = REDIS_CONN.hset(data.get("key", ""), + data.get("field", ""), + data.get("value", "")) + + groups = json.loads(REDIS_CONN.hget("groups", + args.group_name)) # type: ignore + if created_p: + exit(f"\nSuccessfully created the group: '{args.group_name}'\n" + f"Data: {groups}\n") + exit("\nNo new group was created.\n" + f"Updated Data: {groups}\n") -- cgit v1.2.3 From c585945c5516092b362efecc16325ad9ecc54291 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Thu, 23 Sep 2021 15:39:32 +0300 Subject: Add script that adds "editors" group to all resources in Redis --- scripts/authentication/resource.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 scripts/authentication/resource.py diff --git a/scripts/authentication/resource.py b/scripts/authentication/resource.py new file mode 100644 index 00000000..75ef9e93 --- /dev/null +++ b/scripts/authentication/resource.py @@ -0,0 +1,25 @@ +"""A script that adds the group: 'editors' to every +resource. 'editors' should have the right to edit both metadata and +data. + +To use this script, simply run: + +.. code-block:: python + python resource.py + +""" +import json +import redis + + +if __name__ == "__main__": + REDIS_CONN = redis.Redis() + resources = REDIS_CONN.hgetall("resources_clone") + for resource_id, resource in resources.items(): + deserialized_resource = json.loads(resource) + deserialized_resource["group_masks"] = { + "editors": {"metadata": "edit", + "data": "edit"}} + REDIS_CONN.hset("resources_clone", + resource_id, + json.dumps(deserialized_resource)) -- cgit v1.2.3 From edb6fe1b9dd98d84deb0925a2d83726e739d8677 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 27 Sep 2021 16:28:12 +0300 Subject: wqflask: resource_manager: Remove logger --- wqflask/wqflask/resource_manager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index b28c1b04..c54dd0b3 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -8,8 +8,6 @@ from wqflask import app from utility.authentication_tools import check_owner_or_admin from utility.redis_tools import get_resource_info, get_group_info, get_groups_like_unique_column, get_user_id, get_user_by_unique_column, get_users_like_unique_column, add_access_mask, add_resource, change_resource_owner -from utility.logger import getLogger -logger = getLogger(__name__) @app.route("/resources/manage", methods=('GET', 'POST')) -- cgit v1.2.3 From df487791c91a5aa1a9a3b4e1a6c9ce17a58eafe6 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 4 Oct 2021 12:21:16 +0300 Subject: Modify resource editing script to enable data backups & restoration --- scripts/authentication/resource.py | 101 ++++++++++++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 13 deletions(-) diff --git a/scripts/authentication/resource.py b/scripts/authentication/resource.py index 75ef9e93..8fcf09d7 100644 --- a/scripts/authentication/resource.py +++ b/scripts/authentication/resource.py @@ -1,25 +1,100 @@ -"""A script that adds the group: 'editors' to every -resource. 'editors' should have the right to edit both metadata and -data. +"""A script that: -To use this script, simply run: +- Optionally restores data from a json file. + +- By default, without any args provided, adds the group: 'editors' to +every resource. 'editors' should have the right to edit both metadata +and data. + +- Optionally creates a back-up every time you edit a resource. + + +To restore a back-up: + +.. code-block:: python + python resource.py --restore + +To add editors to every resource without creating a back-up: .. code-block:: python python resource.py +To add editors to every resource while creating a back-up before any +destructive edits: + +.. code-block:: python + python resource.py --enable-backup + """ +import argparse import json import redis +import os + +from datetime import datetime + + +def recover_hash(name: str, file_path: str, set_function) -> bool: + """Recover back-ups using the `set_function` + + Parameters: + + - `name`: Redis hash where `file_path` will be restored + + - `file_path`: File path where redis hash is sourced from + + - `set_function`: Function used to do the Redis backup for + example: HSET + + """ + try: + with open(file_path, "r") as f: + resources = json.load(f) + for resource_id, resource in resources.items(): + set_function(name=name, + key=resource_id, + value=resource) + return True + except Exception as e: + print(e) + return False if __name__ == "__main__": - REDIS_CONN = redis.Redis() - resources = REDIS_CONN.hgetall("resources_clone") - for resource_id, resource in resources.items(): - deserialized_resource = json.loads(resource) - deserialized_resource["group_masks"] = { - "editors": {"metadata": "edit", - "data": "edit"}} - REDIS_CONN.hset("resources_clone", + # Initialising the parser CLI arguments + parser = argparse.ArgumentParser() + parser.add_argument("--restore", + help="Restore from a given backup") + parser.add_argument("--enable-backup", action="store_true", + help="Create a back up before edits") + args = parser.parse_args() + + if args.restore: + if recover_hash(name="resources", + file_path=args.back_up, + set_function=redis.Redis(decode_responses=True).hset): + exit(f"\n Done restoring {args.back_up}!\n") + else: + exit(f"\n There was an error restoring {args.back_up}!\n") + + REDIS_CONN = redis.Redis(decode_responses=True) + RESOURCES = REDIS_CONN.hgetall("resources") + BACKUP_DIR = os.path.join(os.getenv("HOME"), "redis") + if args.enable_backup: + FILENAME = ("resources-" + f"{datetime.now().strftime('%Y-%m-%d-%I:%M:%S-%p')}" + ".json") + if not os.path.exists(BACKUP_DIR): + os.mkdir(BACKUP_DIR) + with open(os.path.join(BACKUP_DIR, FILENAME), "w") as f: + json.dump(RESOURCES, f, indent=4) + print(f"\nDone backing upto {FILENAME}") + + for resource_id, resource in RESOURCES.items(): + _resource = json.loads(resource) # str -> dict conversion + _resource["group_masks"] = {"editors": {"metadata": "edit", + "data": "edit"}} + REDIS_CONN.hset("resources", resource_id, - json.dumps(deserialized_resource)) + json.dumps(_resource)) + exit("Done updating `resources`\n") -- cgit v1.2.3 From c5215d1ed224480a274476933beded9d2ba7f7dc Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 4 Oct 2021 13:40:06 +0300 Subject: Decode redis response by default --- scripts/authentication/group.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index b89bc3ec..265e8664 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -104,9 +104,8 @@ if __name__ == "__main__": members = args.members if args.members else None admins = args.admins if args.admins else None - REDIS_CONN = redis.Redis() - USERS = {key.decode(): val.decode() - for key, val in REDIS_CONN.hgetall("users").items()} + REDIS_CONN = redis.Redis(decode_responses=True) + USERS = REDIS_CONN.hgetall("users") if not any([members, admins]): exit("\nExiting. Please provide a value for " -- cgit v1.2.3 From 8fae92c83d49042da68638319385df02061df44b Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 4 Oct 2021 13:45:59 +0300 Subject: scripts: group.py: Remove "glom" dependency --- scripts/authentication/group.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index 265e8664..02f782b3 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -30,7 +30,6 @@ import redis import json from typing import Dict, List, Optional, Set -from glom import glom # type: ignore def create_group_data(users: Dict, target_group: str, @@ -74,10 +73,12 @@ def create_group_data(users: Dict, target_group: str, """ _members = "".join(members.split()).split(",") if members else [] _admins: List = "".join(admins.split()).split(",") if admins else [] - - user_emails: Set = {glom(json.loads(user_details), "email_address") - for _, user_details in users.items()} - + user_emails: Set = set() + for _, user_details in users.items(): + _details = json.loads(user_details) + if _details.get("email_address"): + user_emails.add(_details.get("email_address")) + print(user_emails) return {"key": "groups", "field": target_group, "value": json.dumps({ -- cgit v1.2.3 From 7c1dd1211f96ca1021debc27a80d1700e70b9c6b Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 4 Oct 2021 13:49:12 +0300 Subject: scripts: group.py: Modify exit message when displaying updated data --- scripts/authentication/group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index 02f782b3..cc0b037e 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -125,6 +125,6 @@ if __name__ == "__main__": args.group_name)) # type: ignore if created_p: exit(f"\nSuccessfully created the group: '{args.group_name}'\n" - f"Data: {groups}\n") + f"`HGETALL groups {args.group_name}`: {groups}\n") exit("\nNo new group was created.\n" - f"Updated Data: {groups}\n") + f"`HGETALL groups {args.group_name}`: {groups}\n") -- cgit v1.2.3 From 609865fc42b7436d8c34cdcefd159c3352c5d91c Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 4 Oct 2021 20:58:14 +0000 Subject: Add group link for user member groups --- wqflask/wqflask/templates/admin/group_manager.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/admin/group_manager.html b/wqflask/wqflask/templates/admin/group_manager.html index c0b99e75..692a7abc 100644 --- a/wqflask/wqflask/templates/admin/group_manager.html +++ b/wqflask/wqflask/templates/admin/group_manager.html @@ -81,7 +81,7 @@ {{ loop.index }} - {{ group.name }} + {{ group.name }} {{ group.admins|length + group.members|length }} {{ group.created_timestamp }} {{ group.changed_timestamp }} -- cgit v1.2.3 From 5b116de4aaf796be138ee0ad06c2242b3f3c33c7 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 4 Oct 2021 21:02:37 +0000 Subject: Changed get_user_groups to pull both the ID and details in the for loop from group_list by using group_list.items() --- wqflask/utility/redis_tools.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py index 99295c67..de9dde46 100644 --- a/wqflask/utility/redis_tools.py +++ b/wqflask/utility/redis_tools.py @@ -127,20 +127,20 @@ def check_verification_code(code): def get_user_groups(user_id): - # ZS: Get the groups where a user is an admin or a member and + # Get the groups where a user is an admin or a member and # return lists corresponding to those two sets of groups - admin_group_ids = [] # ZS: Group IDs where user is an admin - user_group_ids = [] # ZS: Group IDs where user is a regular user + admin_group_ids = [] # Group IDs where user is an admin + user_group_ids = [] # Group IDs where user is a regular user groups_list = Redis.hgetall("groups") - for key in groups_list: + for group_id, group_details in groups_list.items(): try: - group_ob = json.loads(groups_list[key]) - group_admins = set([this_admin if this_admin else None for this_admin in group_ob['admins']]) - group_members = set([this_member if this_member else None for this_member in group_ob['members']]) + _details = json.loads(group_details) + group_admins = set([this_admin if this_admin else None for this_admin in _details['admins']]) + group_members = set([this_member if this_member else None for this_member in _details['members']]) if user_id in group_admins: - admin_group_ids.append(group_ob['id']) + admin_group_ids.append(group_id) elif user_id in group_members: - user_group_ids.append(group_ob['id']) + user_group_ids.append(group_id) else: continue except: -- cgit v1.2.3 From 35a970adba5ee1d60769a81b446122a60eac9494 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 4 Oct 2021 21:03:52 +0000 Subject: Changed the group.py script to replace user e-mails with IDs and to include id, name, changed_timestamp, and created_timestamp in group details --- scripts/authentication/group.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index cc0b037e..a5f75aad 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -26,11 +26,13 @@ now results in:: """ import argparse +import datetime import redis import json from typing import Dict, List, Optional, Set +REDIS_CONN = redis.Redis(decode_responses=True) def create_group_data(users: Dict, target_group: str, members: Optional[str] = None, @@ -71,21 +73,26 @@ def create_group_data(users: Dict, target_group: str, me@test2.com, me@test3.com" """ - _members = "".join(members.split()).split(",") if members else [] + + _members: List = "".join(members.split()).split(",") if members else [] _admins: List = "".join(admins.split()).split(",") if admins else [] - user_emails: Set = set() - for _, user_details in users.items(): + + user_ids: Dict = dict() + for user_id, user_details in users.items(): _details = json.loads(user_details) if _details.get("email_address"): - user_emails.add(_details.get("email_address")) - print(user_emails) + user_ids[_details.get("email_address")] = user_id + print(user_ids) return {"key": "groups", "field": target_group, "value": json.dumps({ - "admins": [admin for admin in _admins - if admin in user_emails], - "members": [member for member in _members - if member in user_emails] + "id": target_group, + "name": target_group, + "admins": [user_ids[admin] for admin in _admins + if admin in user_ids], + "members": [user_ids[member] for member in _members + if member in user_ids], + "changed_timestamp": datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') })} @@ -105,7 +112,6 @@ if __name__ == "__main__": members = args.members if args.members else None admins = args.admins if args.admins else None - REDIS_CONN = redis.Redis(decode_responses=True) USERS = REDIS_CONN.hgetall("users") if not any([members, admins]): @@ -117,6 +123,12 @@ if __name__ == "__main__": target_group=args.group_name, members=members, admins=admins) + + if not REDIS_CONN.hget("groups", data.get("field", "")): + updated_data = json.loads(data["value"]) + updated_data["created_timestamp"] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') + data["value"] = json.dumps(updated_data) + created_p = REDIS_CONN.hset(data.get("key", ""), data.get("field", ""), data.get("value", "")) -- cgit v1.2.3 From 4c6a7e46dd7afe311c0bed38c4a69ddadf3cb416 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 4 Oct 2021 21:09:15 +0000 Subject: Moved REDIS_CONN back into if __name__ == '__main__' since it doesn't need to be globally accessed anymore (I think I intiially moved it because I was calling it in create_group_data, but that ended up being unnecessary --- scripts/authentication/group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index a5f75aad..76c7fb4f 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -32,8 +32,6 @@ import json from typing import Dict, List, Optional, Set -REDIS_CONN = redis.Redis(decode_responses=True) - def create_group_data(users: Dict, target_group: str, members: Optional[str] = None, admins: Optional[str] = None) -> Dict: @@ -112,6 +110,8 @@ if __name__ == "__main__": members = args.members if args.members else None admins = args.admins if args.admins else None + + REDIS_CONN = redis.Redis(decode_responses=True) USERS = REDIS_CONN.hgetall("users") if not any([members, admins]): -- cgit v1.2.3 From 18b50f56d614021d5727d546f3d6e360575e4468 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Tue, 5 Oct 2021 19:07:24 +0300 Subject: work on wgcna_setup form --- wqflask/wqflask/templates/wgcna_setup.html | 54 ++++++++++++++++-------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/wqflask/wqflask/templates/wgcna_setup.html b/wqflask/wqflask/templates/wgcna_setup.html index c5461497..44932389 100644 --- a/wqflask/wqflask/templates/wgcna_setup.html +++ b/wqflask/wqflask/templates/wgcna_setup.html @@ -2,8 +2,10 @@ {% block title %}WCGNA analysis{% endblock %} {% block content %} -

    WGCNA analysis parameters

    +
    +
    +

    WGCNA analysis parameters

    {% if request.form['trait_list'].split(",")|length < 4 %} {% else %} -
    - -
    - -
    - -
    + + +
    + +
    +
    -
    - -
    - -
    +
    +
    + +
    +
    -
    - -
    - -
    +
    + +
    + +
    +
    -
    - -
    - -
    +
    +
    + +
    +
    +
    -
    +
    - + {% endif %}
    +
    {% endblock %} -- cgit v1.2.3 From 31071d78f2ffb0ae4c7e0b99e74c7729a1a36e9c Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Tue, 5 Oct 2021 19:49:50 +0300 Subject: move xterm code to setup --- wqflask/wqflask/templates/wgcna_setup.html | 140 +++++++++++++++++++---------- 1 file changed, 94 insertions(+), 46 deletions(-) diff --git a/wqflask/wqflask/templates/wgcna_setup.html b/wqflask/wqflask/templates/wgcna_setup.html index 44932389..4b13e54e 100644 --- a/wqflask/wqflask/templates/wgcna_setup.html +++ b/wqflask/wqflask/templates/wgcna_setup.html @@ -1,53 +1,101 @@ {% extends "base.html" %} {% block title %}WCGNA analysis{% endblock %} - -{% block content %} - +{% block content %} + + + + +
    -
    -

    WGCNA analysis parameters

    - {% if request.form['trait_list'].split(",")|length < 4 %} - - {% else %} -
    - -
    - -
    - -
    -
    -
    - -
    - -
    -
    - -
    - -
    - -
    -
    -
    - -
    - +
    +

    WGCNA analysis parameters

    + {% if request.form['trait_list'].split(",")|length < 4 %} -
    -
    -
    - -
    + {% else %} + + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    + + {% endif %} +
    +
    +
    +
    +
    - - {% endif %}
    -{% endblock %} + +{% endblock %} \ No newline at end of file -- cgit v1.2.3 From 6f739fccbb7cff9f05b53ff4775ecee0761c293b Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Tue, 5 Oct 2021 19:50:37 +0300 Subject: js formatting --- wqflask/wqflask/templates/wgcna_setup.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/templates/wgcna_setup.html b/wqflask/wqflask/templates/wgcna_setup.html index 4b13e54e..34690f29 100644 --- a/wqflask/wqflask/templates/wgcna_setup.html +++ b/wqflask/wqflask/templates/wgcna_setup.html @@ -84,11 +84,11 @@ document.addEventListener('DOMContentLoaded', function() { // open socket connection - const socket = io(`${GN_SERVER_URL}`) + const socket = io(`${GN_SERVER_URL}`) // add namespace - socket.on("output",({data})=>{ + socket.on("output", ({ data }) => { - term.writeln(data) + term.writeln(data) }) } else { -- cgit v1.2.3 From 37c37996424e826187ac3eca9ed4cd11b9715736 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Tue, 5 Oct 2021 19:52:51 +0300 Subject: code format for wgcna results file --- wqflask/wqflask/templates/test_wgcna_results.html | 61 +++++++++-------------- 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/wqflask/wqflask/templates/test_wgcna_results.html b/wqflask/wqflask/templates/test_wgcna_results.html index f95766ad..9484595f 100644 --- a/wqflask/wqflask/templates/test_wgcna_results.html +++ b/wqflask/wqflask/templates/test_wgcna_results.html @@ -2,13 +2,17 @@ {% block title %}WCGNA results{% endblock %} {% block content %} + + +
    @@ -122,32 +118,21 @@ let terminal_output = results.output let { output } = results.data let sft = output.soft_threshold - - + -{% endblock %} \ No newline at end of file +{% endblock %} +** \ No newline at end of file -- cgit v1.2.3 From 6b4b7a5774f42ff6be1fb5fde6ba1fef632cd1a4 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Wed, 6 Oct 2021 11:48:50 +0300 Subject: add datatable for eigengenes --- wqflask/wqflask/templates/test_wgcna_results.html | 80 ++++++++++++++++------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/wqflask/wqflask/templates/test_wgcna_results.html b/wqflask/wqflask/templates/test_wgcna_results.html index 9484595f..ac82647d 100644 --- a/wqflask/wqflask/templates/test_wgcna_results.html +++ b/wqflask/wqflask/templates/test_wgcna_results.html @@ -2,8 +2,17 @@ {% block title %}WCGNA results{% endblock %} {% block content %} + - + + + + + + + + +
    @@ -104,11 +113,12 @@
    -
    - - CLuster dendogram - + {% if image["image_generated"] %} +
    +
    + + {% endif %}
    @@ -161,12 +171,5 @@ $(document).ready(function(){ } ); }) - - - - - - - {% endblock %} \ No newline at end of file -- cgit v1.2.3 From 949789a00d8e6e901cc18b939737cd42e14c0236 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 6 Oct 2021 16:12:22 +0300 Subject: scripts: group: Use a unique key to identify a group --- scripts/authentication/group.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index 76c7fb4f..eea13efe 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -29,6 +29,7 @@ import argparse import datetime import redis import json +import uuid from typing import Dict, List, Optional, Set @@ -71,26 +72,31 @@ def create_group_data(users: Dict, target_group: str, me@test2.com, me@test3.com" """ + # Emails + _members: Set = set("".join(members.split()).split(",") + if members else []) + _admins: Set = set("".join(admins.split()).split(",") + if admins else []) - _members: List = "".join(members.split()).split(",") if members else [] - _admins: List = "".join(admins.split()).split(",") if admins else [] + # Unique IDs + member_ids: Set = set() + admin_ids: Set = set() - user_ids: Dict = dict() for user_id, user_details in users.items(): _details = json.loads(user_details) - if _details.get("email_address"): - user_ids[_details.get("email_address")] = user_id - print(user_ids) + if _details.get("email_address") in _members: + member_ids.add(user_id) + if _details.get("email_address") in _admins: + admin_ids.add(user_id) + + timestamp: str = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') return {"key": "groups", - "field": target_group, + "field": str(uuid.uuid4()), "value": json.dumps({ - "id": target_group, "name": target_group, - "admins": [user_ids[admin] for admin in _admins - if admin in user_ids], - "members": [user_ids[member] for member in _members - if member in user_ids], - "changed_timestamp": datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') + "admins": list(admin_ids), + "members": list(member_ids), + "changed_timestamp": timestamp, })} -- cgit v1.2.3 From 931c7eb07cc995118ba808df760fd74de036853f Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 6 Oct 2021 16:15:31 +0300 Subject: scripts: group: Remove unused import --- scripts/authentication/group.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index eea13efe..d3f9a1e4 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -31,7 +31,8 @@ import redis import json import uuid -from typing import Dict, List, Optional, Set +from typing import Dict, Optional, Set + def create_group_data(users: Dict, target_group: str, members: Optional[str] = None, -- cgit v1.2.3 From 870edaf2cf8ce8588ee7c58d08fc1f307f7198ec Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 6 Oct 2021 16:19:04 +0300 Subject: scripts: group: Remove empty `""` value for data.get data.get("field") will default to None if there is no value; and None is falsy. --- scripts/authentication/group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index d3f9a1e4..1919d9db 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -131,10 +131,10 @@ if __name__ == "__main__": members=members, admins=admins) - if not REDIS_CONN.hget("groups", data.get("field", "")): updated_data = json.loads(data["value"]) updated_data["created_timestamp"] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') data["value"] = json.dumps(updated_data) + if not REDIS_CONN.hget("groups", data.get("field")): created_p = REDIS_CONN.hset(data.get("key", ""), data.get("field", ""), -- cgit v1.2.3 From c1b23a1b01071c252ddae6dbea14500e4c248d84 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 6 Oct 2021 21:01:54 +0300 Subject: workflows: main.yml: Disable link checking --- .github/workflows/main.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f279a7e5..8e2c7966 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,11 +55,11 @@ jobs: GENENETWORK_FILES=/genotype_files/ bin/genenetwork2 \ etc/default_settings.py -c -m unittest discover -v - - name: Test for Broken Links - run: | - env GN2_PROFILE=/gn2-profile \ - TMPDIR=/tmp\ - WEBSERVER_MODE=DEBUG LOG_LEVEL=DEBUG \ - GENENETWORK_FILES=/genotype_files/ bin/genenetwork2 \ - etc/default_settings.py -c \ - $PWD/test/requests/links_scraper/genelinks.py + # - name: Test for Broken Links + # run: | + # env GN2_PROFILE=/gn2-profile \ + # TMPDIR=/tmp\ + # WEBSERVER_MODE=DEBUG LOG_LEVEL=DEBUG \ + # GENENETWORK_FILES=/genotype_files/ bin/genenetwork2 \ + # etc/default_settings.py -c \ + # $PWD/test/requests/links_scraper/genelinks.py -- cgit v1.2.3 From 40dddc1a78a7808b480d26594ced689cdcc08c24 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 6 Oct 2021 21:23:08 +0300 Subject: scripts: group: Fix indentation --- scripts/authentication/group.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index 1919d9db..7e73be15 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -131,10 +131,10 @@ if __name__ == "__main__": members=members, admins=admins) - updated_data = json.loads(data["value"]) - updated_data["created_timestamp"] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') - data["value"] = json.dumps(updated_data) if not REDIS_CONN.hget("groups", data.get("field")): + updated_data = json.loads(data["value"]) + updated_data["created_timestamp"] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') + data["value"] = json.dumps(updated_data) created_p = REDIS_CONN.hset(data.get("key", ""), data.get("field", ""), -- cgit v1.2.3 From 67222a6cb11995eb5a4af58f63cc9385ccfb9226 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 6 Oct 2021 21:24:16 +0300 Subject: scripts: group: Break up long line --- scripts/authentication/group.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index 7e73be15..ed17f260 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -133,7 +133,8 @@ if __name__ == "__main__": if not REDIS_CONN.hget("groups", data.get("field")): updated_data = json.loads(data["value"]) - updated_data["created_timestamp"] = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') + timestamp = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') + updated_data["created_timestamp"] = timestamp data["value"] = json.dumps(updated_data) created_p = REDIS_CONN.hset(data.get("key", ""), -- cgit v1.2.3 From dc378d26c003a8f0503ad69235d1685d66e4d611 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 6 Oct 2021 21:26:03 +0300 Subject: scripts: group: Update docstrings for "create_group_data" --- scripts/authentication/group.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index ed17f260..08a4a2bc 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -41,6 +41,9 @@ def create_group_data(users: Dict, target_group: str, "field", and "value" that can be used in a redis hash as follows: HSET key field value + The "field" return value is a unique-id that is used to + distinguish the groups. + Parameters: - `users`: a list of users for example: -- cgit v1.2.3 From d5f6670836cbed804a00e02ec0258d0c87564006 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 6 Oct 2021 21:40:35 +0300 Subject: scripts: group: Replace args.group_name with data["field"] --- scripts/authentication/group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index 08a4a2bc..c8c2caad 100644 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -145,7 +145,7 @@ if __name__ == "__main__": data.get("value", "")) groups = json.loads(REDIS_CONN.hget("groups", - args.group_name)) # type: ignore + data.get("field"))) # type: ignore if created_p: exit(f"\nSuccessfully created the group: '{args.group_name}'\n" f"`HGETALL groups {args.group_name}`: {groups}\n") -- cgit v1.2.3 From 70f8ed53f85cfb42ca81ed6c3b4c9cf1060940e5 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 6 Oct 2021 21:44:51 +0300 Subject: scripts: resource: Add option for specifying a groups uuid --- scripts/authentication/resource.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/authentication/resource.py b/scripts/authentication/resource.py index 8fcf09d7..4996f34c 100644 --- a/scripts/authentication/resource.py +++ b/scripts/authentication/resource.py @@ -63,12 +63,16 @@ def recover_hash(name: str, file_path: str, set_function) -> bool: if __name__ == "__main__": # Initialising the parser CLI arguments parser = argparse.ArgumentParser() + parser.add_argument("--group-id", + help="Add the group id to all resources") parser.add_argument("--restore", help="Restore from a given backup") parser.add_argument("--enable-backup", action="store_true", help="Create a back up before edits") args = parser.parse_args() + if not args.group_id: + exit("Please specify the group-id!\n") if args.restore: if recover_hash(name="resources", file_path=args.back_up, @@ -92,8 +96,8 @@ if __name__ == "__main__": for resource_id, resource in RESOURCES.items(): _resource = json.loads(resource) # str -> dict conversion - _resource["group_masks"] = {"editors": {"metadata": "edit", - "data": "edit"}} + _resource["group_masks"] = {args.group_id: {"metadata": "edit", + "data": "edit"}} REDIS_CONN.hset("resources", resource_id, json.dumps(_resource)) -- cgit v1.2.3 From 7805a48172ada364d3783db043dbcf637445a7fe Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 8 Oct 2021 22:07:43 +0000 Subject: Adding convert_dol_genotypes.py to scripts; everything is hard-coded in it since I was only writing it to generate a specific file and it probably won't be re-used --- scripts/convert_dol_genotypes.py | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 scripts/convert_dol_genotypes.py diff --git a/scripts/convert_dol_genotypes.py b/scripts/convert_dol_genotypes.py new file mode 100644 index 00000000..353f1b53 --- /dev/null +++ b/scripts/convert_dol_genotypes.py @@ -0,0 +1,68 @@ +# This is just to convert the Rqtl2 format genotype files for DOL into a .geno file +# Everything is hard-coded since I doubt this will be re-used and I just wanted to generate the file quickly + +import os + +geno_dir = "/home/zas1024/gn2-zach/DO_genotypes/" +markers_file = "/home/zas1024/gn2-zach/DO_genotypes/SNP_Map.txt" +gn_geno_path = "/home/zas1024/gn2-zach/DO_genotypes/DOL.geno" + +marker_data = {} +with open(markers_file, "r") as markers_fh: + for i, line in enumerate(markers_fh): + if i == 0: + continue + else: + line_items = line.split("\t") + this_marker = {} + this_marker['chr'] = line_items[2] if line_items[2] != "0" else "M" + this_marker['pos'] = f'{float(line_items[3])/1000000:.6f}' + marker_data[line_items[1]] = this_marker + +sample_names = [] +for filename in os.listdir(geno_dir): + if "gm4qtl2_geno" in filename: + with open(geno_dir + "/" + filename, "r") as rqtl_geno_fh: + for i, line in enumerate(rqtl_geno_fh): + line_items = line.split(",") + if i < 3: + continue + elif not len(sample_names) and i == 3: + sample_names = [item.replace("TLB", "TB") for item in line_items[1:]] + elif i > 3: + marker_data[line_items[0]]['genotypes'] = ["X" if item.strip() == "-" else item.strip() for item in line_items[1:]] + +def sort_func(e): + try: + return int(e['chr']) + except: + if e['chr'] == "X": + return 20 + elif e['chr'] == "Y": + return 21 + elif e['chr'] == "M": + return 22 + +marker_list = [] +for key, value in marker_data.items(): + if 'genotypes' in value: + this_marker = { + 'chr': value['chr'], + 'locus': key, + 'pos': value['pos'], + 'genotypes': value['genotypes'] + } + marker_list.append(this_marker) + +marker_list.sort(key=sort_func) + +with open(gn_geno_path, "w") as gn_geno_fh: + gn_geno_fh.write("\t".join((["Chr", "Locus", "cM", "Mb"] + sample_names))) + for marker in marker_list: + row_contents = [ + marker['chr'], + marker['locus'], + marker['pos'], + marker['pos'] + ] + marker['genotypes'] + gn_geno_fh.write("\t".join(row_contents) + "\n") -- cgit v1.2.3 From b37a9c6c495d142852d0cee54d83f5c9e815e37b Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 8 Oct 2021 22:19:07 +0000 Subject: Fixed the sort to account for both chr and pos in a kind of hack-y way + added some comments + changed EOL to LF because the file suddenly started including EOL characters --- scripts/convert_dol_genotypes.py | 142 ++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 68 deletions(-) diff --git a/scripts/convert_dol_genotypes.py b/scripts/convert_dol_genotypes.py index 353f1b53..81b3bd6d 100644 --- a/scripts/convert_dol_genotypes.py +++ b/scripts/convert_dol_genotypes.py @@ -1,68 +1,74 @@ -# This is just to convert the Rqtl2 format genotype files for DOL into a .geno file -# Everything is hard-coded since I doubt this will be re-used and I just wanted to generate the file quickly - -import os - -geno_dir = "/home/zas1024/gn2-zach/DO_genotypes/" -markers_file = "/home/zas1024/gn2-zach/DO_genotypes/SNP_Map.txt" -gn_geno_path = "/home/zas1024/gn2-zach/DO_genotypes/DOL.geno" - -marker_data = {} -with open(markers_file, "r") as markers_fh: - for i, line in enumerate(markers_fh): - if i == 0: - continue - else: - line_items = line.split("\t") - this_marker = {} - this_marker['chr'] = line_items[2] if line_items[2] != "0" else "M" - this_marker['pos'] = f'{float(line_items[3])/1000000:.6f}' - marker_data[line_items[1]] = this_marker - -sample_names = [] -for filename in os.listdir(geno_dir): - if "gm4qtl2_geno" in filename: - with open(geno_dir + "/" + filename, "r") as rqtl_geno_fh: - for i, line in enumerate(rqtl_geno_fh): - line_items = line.split(",") - if i < 3: - continue - elif not len(sample_names) and i == 3: - sample_names = [item.replace("TLB", "TB") for item in line_items[1:]] - elif i > 3: - marker_data[line_items[0]]['genotypes'] = ["X" if item.strip() == "-" else item.strip() for item in line_items[1:]] - -def sort_func(e): - try: - return int(e['chr']) - except: - if e['chr'] == "X": - return 20 - elif e['chr'] == "Y": - return 21 - elif e['chr'] == "M": - return 22 - -marker_list = [] -for key, value in marker_data.items(): - if 'genotypes' in value: - this_marker = { - 'chr': value['chr'], - 'locus': key, - 'pos': value['pos'], - 'genotypes': value['genotypes'] - } - marker_list.append(this_marker) - -marker_list.sort(key=sort_func) - -with open(gn_geno_path, "w") as gn_geno_fh: - gn_geno_fh.write("\t".join((["Chr", "Locus", "cM", "Mb"] + sample_names))) - for marker in marker_list: - row_contents = [ - marker['chr'], - marker['locus'], - marker['pos'], - marker['pos'] - ] + marker['genotypes'] - gn_geno_fh.write("\t".join(row_contents) + "\n") +# This is just to convert the Rqtl2 format genotype files for DOL into a .geno file +# Everything is hard-coded since I doubt this will be re-used and I just wanted to generate the file quickly + +import os + +geno_dir = "/home/zas1024/gn2-zach/DO_genotypes/" +markers_file = "/home/zas1024/gn2-zach/DO_genotypes/SNP_Map.txt" +gn_geno_path = "/home/zas1024/gn2-zach/DO_genotypes/DOL.geno" + +# Iterate through the SNP_Map.txt file to get marker positions +marker_data = {} +with open(markers_file, "r") as markers_fh: + for i, line in enumerate(markers_fh): + if i == 0: + continue + else: + line_items = line.split("\t") + this_marker = {} + this_marker['chr'] = line_items[2] if line_items[2] != "0" else "M" + this_marker['pos'] = f'{float(line_items[3])/1000000:.6f}' + marker_data[line_items[1]] = this_marker + +# Iterate through R/qtl2 format genotype files and pull out the samplelist and genotypes for each marker +sample_names = [] +for filename in os.listdir(geno_dir): + if "gm4qtl2_geno" in filename: + with open(geno_dir + "/" + filename, "r") as rqtl_geno_fh: + for i, line in enumerate(rqtl_geno_fh): + line_items = line.split(",") + if i < 3: + continue + elif not len(sample_names) and i == 3: + sample_names = [item.replace("TLB", "TB") for item in line_items[1:]] + elif i > 3: + marker_data[line_items[0]]['genotypes'] = ["X" if item.strip() == "-" else item.strip() for item in line_items[1:]] + +# Generate list of marker obs to iterate through when writing to .geno file +marker_list = [] +for key, value in marker_data.items(): + if 'genotypes' in value: + this_marker = { + 'chr': value['chr'], + 'locus': key, + 'pos': value['pos'], + 'genotypes': value['genotypes'] + } + marker_list.append(this_marker) + +def sort_func(e): + """For ensuring that X/Y chromosomes/mitochondria are sorted to the end correctly""" + try: + return float((e['chr']))*1000 + float(e['pos']) + except: + if e['chr'] == "X": + return 20000 + float(e['pos']) + elif e['chr'] == "Y": + return 21000 + float(e['pos']) + elif e['chr'] == "M": + return 22000 + float(e['pos']) + +# Sort markers by chromosome +marker_list.sort(key=sort_func) + +# Write lines to .geno file +with open(gn_geno_path, "w") as gn_geno_fh: + gn_geno_fh.write("\t".join((["Chr", "Locus", "cM", "Mb"] + sample_names))) + for marker in marker_list: + row_contents = [ + marker['chr'], + marker['locus'], + marker['pos'], + marker['pos'] + ] + marker['genotypes'] + gn_geno_fh.write("\t".join(row_contents) + "\n") -- cgit v1.2.3 From e473a210491620477898ba69f33f69f14fdf5893 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 8 Oct 2021 22:49:20 +0000 Subject: Fix issue where outliers weren't being highlighted for rows drawn by Scroller (so all rows beyond a certain point) --- wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js | 2 ++ wqflask/wqflask/templates/show_trait.html | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js b/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js index 4de1b0ac..0a060cdc 100644 --- a/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js +++ b/wqflask/wqflask/static/new/javascript/initialize_show_trait_tables.js @@ -130,6 +130,7 @@ var primary_table = $('#samples_primary').DataTable( { $(row).addClass("value_se"); if (data.outlier) { $(row).addClass("outlier"); + $(row).attr("style", "background-color: orange;"); } $('td', row).eq(1).addClass("column_name-Index") $('td', row).eq(2).addClass("column_name-Sample") @@ -189,6 +190,7 @@ if (js_data.sample_lists.length > 1){ $(row).addClass("value_se"); if (data.outlier) { $(row).addClass("outlier"); + $(row).attr("style", "background-color: orange;"); } $('td', row).eq(1).addClass("column_name-Index") $('td', row).eq(2).addClass("column_name-Sample") diff --git a/wqflask/wqflask/templates/show_trait.html b/wqflask/wqflask/templates/show_trait.html index 3dbf5f57..f3fa1332 100644 --- a/wqflask/wqflask/templates/show_trait.html +++ b/wqflask/wqflask/templates/show_trait.html @@ -254,8 +254,6 @@ } ); {% endif %} - $('#samples_primary, #samples_other').find("tr.outlier").css('background-color', 'orange') - $('.edit_sample_checkbox:checkbox').change(function() { if ($(this).is(":checked")) { if (!$(this).closest('tr').hasClass('selected')) { -- cgit v1.2.3 From 9b28d111ad156f3862286e88bc220e02d5e1312b Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 8 Oct 2021 23:31:19 +0000 Subject: Fixed some issues with scale and score_type in mapping export + include mapping method in export filename --- wqflask/wqflask/marker_regression/run_mapping.py | 25 ++++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index 290c4a14..80094057 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -104,7 +104,7 @@ class RunMapping: if "results_path" in start_vars: self.mapping_results_path = start_vars['results_path'] else: - mapping_results_filename = "_".join([self.dataset.group.name, self.vals_hash]).replace("/", "_") + mapping_results_filename = "_".join([self.dataset.group.name, self.mapping_method, self.vals_hash]).replace("/", "_") self.mapping_results_path = "{}{}.csv".format( webqtlConfig.GENERATED_IMAGE_DIR, mapping_results_filename) @@ -405,8 +405,8 @@ class RunMapping: total_markers = len(self.qtl_results) with Bench("Exporting Results"): - export_mapping_results(self.dataset, self.this_trait, self.qtl_results, - self.mapping_results_path, self.mapping_scale, self.score_type, + export_mapping_results(self.dataset, self.this_trait, self.qtl_results, self.mapping_results_path, + self.mapping_method, self.mapping_scale, self.score_type, self.transform, self.covariates, self.n_samples, self.vals_hash) with Bench("Trimming Markers for Figure"): @@ -525,7 +525,11 @@ class RunMapping: return trimmed_genotype_data -def export_mapping_results(dataset, trait, markers, results_path, mapping_scale, score_type, transform, covariates, n_samples, vals_hash): +def export_mapping_results(dataset, trait, markers, results_path, mapping_method, mapping_scale, score_type, transform, covariates, n_samples, vals_hash): + if mapping_scale == "physic": + scale_string = "Mb" + else: + scale_string = "cM" with open(results_path, "w+") as output_file: output_file.write( "Time/Date: " + datetime.datetime.now().strftime("%x / %X") + "\n") @@ -535,6 +539,7 @@ def export_mapping_results(dataset, trait, markers, results_path, mapping_scale, output_file.write("Trait: " + trait.display_name + "\n") output_file.write("Trait Hash: " + vals_hash + "\n") output_file.write("N Samples: " + str(n_samples) + "\n") + output_file.write("Mapping Tool: " + str(mapping_method) + "\n") if len(transform) > 0: transform_text = "Transform - " if transform == "qnorm": @@ -564,10 +569,7 @@ def export_mapping_results(dataset, trait, markers, results_path, mapping_scale, output_file.write("Name,Chr,") if score_type.lower() == "-logP": score_type = "-logP" - if 'Mb' in markers[0]: - output_file.write("Mb," + score_type) - if 'cM' in markers[0]: - output_file.write("Cm," + score_type) + output_file.write(scale_string + "," + score_type) if "additive" in list(markers[0].keys()): output_file.write(",Additive") if "dominance" in list(markers[0].keys()): @@ -575,11 +577,8 @@ def export_mapping_results(dataset, trait, markers, results_path, mapping_scale, output_file.write("\n") for i, marker in enumerate(markers): output_file.write(marker['name'] + "," + str(marker['chr']) + ",") - if 'Mb' in marker: - output_file.write(str(marker['Mb']) + ",") - if 'cM' in marker: - output_file.write(str(marker['cM']) + ",") - if "lod_score" in marker.keys(): + output_file.write(str(marker[scale_string]) + ",") + if score_type == "-logP": output_file.write(str(marker['lod_score'])) else: output_file.write(str(marker['lrs_value'])) -- cgit v1.2.3 From 6578f6fa44dfa54bee29a16347bf3265ec6d76ad Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sat, 9 Oct 2021 21:55:08 +0300 Subject: add function to process gn3 wgcna output --- wqflask/wqflask/wgcna/gn3_wgcna.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 wqflask/wqflask/wgcna/gn3_wgcna.py diff --git a/wqflask/wqflask/wgcna/gn3_wgcna.py b/wqflask/wqflask/wgcna/gn3_wgcna.py new file mode 100644 index 00000000..2657a099 --- /dev/null +++ b/wqflask/wqflask/wgcna/gn3_wgcna.py @@ -0,0 +1,27 @@ +"""module contains code to consume gn3-wgcna api +and process data to be rendered by datatables +""" + + + +def process_wgcna_data(response): + """function for processing modeigene genes + for create row data for datataba""" + mod_eigens = response["output"]["ModEigens"] + + sample_names = response["input"]["sample_names"] + + mod_dataset = [[sample] for sample in sample_names] + + for _, mod_values in mod_eigens.items(): + for (index, _sample) in enumerate(sample_names): + mod_dataset[index].append(round(mod_values[index], 3)) + + return { + "col_names": ["sample_names", *mod_eigens.keys()], + "mod_dataset": mod_dataset + } + + +def process_image(): + pass \ No newline at end of file -- cgit v1.2.3 From ef80c72194dd8a0b8868ece15589e0a3cf04516f Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sat, 9 Oct 2021 21:55:39 +0300 Subject: unittest for processing wgcna output --- wqflask/tests/unit/wqflask/wgcna/__init__.py | 0 wqflask/tests/unit/wqflask/wgcna/test_wgcna.py | 50 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 wqflask/tests/unit/wqflask/wgcna/__init__.py create mode 100644 wqflask/tests/unit/wqflask/wgcna/test_wgcna.py diff --git a/wqflask/tests/unit/wqflask/wgcna/__init__.py b/wqflask/tests/unit/wqflask/wgcna/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wqflask/tests/unit/wqflask/wgcna/test_wgcna.py b/wqflask/tests/unit/wqflask/wgcna/test_wgcna.py new file mode 100644 index 00000000..8e947e2f --- /dev/null +++ b/wqflask/tests/unit/wqflask/wgcna/test_wgcna.py @@ -0,0 +1,50 @@ + +"""module contains for processing gn3 wgcna data""" +from unittest import TestCase + +from wqflask.wgcna.gn3_wgcna import process_wgcna_data + + +class DataProcessingTests(TestCase): + """class contains data processing tests""" + + def test_data_processing(self): + """test for parsing data for datatable""" + output = { + "input": { + "sample_names": ["BXD1", "BXD2", "BXD3", "BXD4", "BXD5", "BXD6"], + + }, + "output": { + "ModEigens": { + "MEturquoise": [ + 0.0646677768085351, + 0.137200224277058, + 0.63451113720732, + -0.544002665501479, + -0.489487590361863, + 0.197111117570427 + ], + "MEgrey": [ + 0.213, + 0.214, + 0.3141, + -0.545, + -0.423, + 0.156, + ] + }}} + + row_data = [['BXD1', 0.065, 0.213], + ['BXD2', 0.137, 0.214], + ['BXD3', 0.635, 0.314], + ['BXD4', -0.544, -0.545], + ['BXD5', -0.489, -0.423], + ['BXD6', 0.197, 0.156]] + + expected_results = { + "col_names": ["sample_names", "MEturquoise", "MEgrey"], + "mod_dataset": row_data + } + + self.assertEqual(process_wgcna_data(output), expected_results) -- cgit v1.2.3 From 599ac567990a3881dc3821ad226a18ce538d1a17 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sat, 9 Oct 2021 21:58:36 +0300 Subject: add function to process image data --- wqflask/wqflask/wgcna/gn3_wgcna.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/wgcna/gn3_wgcna.py b/wqflask/wqflask/wgcna/gn3_wgcna.py index 2657a099..225bef22 100644 --- a/wqflask/wqflask/wgcna/gn3_wgcna.py +++ b/wqflask/wqflask/wgcna/gn3_wgcna.py @@ -3,7 +3,6 @@ and process data to be rendered by datatables """ - def process_wgcna_data(response): """function for processing modeigene genes for create row data for datataba""" @@ -23,5 +22,12 @@ def process_wgcna_data(response): } -def process_image(): - pass \ No newline at end of file +def process_image(response): + """function to process image check if byte string is empty""" + image_data = response["output"]["image_data"] + return ({ + "image_generated": True, + "image_data": image_data + } if image_data else { + "image_generated": False + }) -- cgit v1.2.3 From 1b77d2417ba71ad7e2274429dd20c9272cb0f582 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sat, 9 Oct 2021 22:00:26 +0300 Subject: function to fetch trait data --- wqflask/wqflask/wgcna/gn3_wgcna.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wqflask/wqflask/wgcna/gn3_wgcna.py b/wqflask/wqflask/wgcna/gn3_wgcna.py index 225bef22..f7ed4cef 100644 --- a/wqflask/wqflask/wgcna/gn3_wgcna.py +++ b/wqflask/wqflask/wgcna/gn3_wgcna.py @@ -3,6 +3,16 @@ and process data to be rendered by datatables """ +def fetch_trait_data(requestform): + """fetch trait data""" + db_obj = SimpleNamespace() + get_trait_db_obs(db_obj, + [trait.strip() + for trait in requestform['trait_list'].split(',')]) + + return process_dataset(db_obj.trait_list) + + def process_wgcna_data(response): """function for processing modeigene genes for create row data for datataba""" -- cgit v1.2.3 From c9ae69a30a972f47232f8457e9e1b8cd514f9832 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sat, 9 Oct 2021 22:04:17 +0300 Subject: add function to process trait sample data --- wqflask/wqflask/wgcna/gn3_wgcna.py | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/wqflask/wqflask/wgcna/gn3_wgcna.py b/wqflask/wqflask/wgcna/gn3_wgcna.py index f7ed4cef..9ab6b3e0 100644 --- a/wqflask/wqflask/wgcna/gn3_wgcna.py +++ b/wqflask/wqflask/wgcna/gn3_wgcna.py @@ -1,6 +1,7 @@ """module contains code to consume gn3-wgcna api and process data to be rendered by datatables """ +from utility.helper_functions import get_trait_db_obs def fetch_trait_data(requestform): @@ -13,6 +14,49 @@ def fetch_trait_data(requestform): return process_dataset(db_obj.trait_list) +def process_dataset(trait_list): + """process datasets and strains""" + + input_data = {} + traits = [] + strains = [] + + # xtodo unique traits and strains + + for trait in trait_list: + traits.append(trait[0].name) + + input_data[trait[0].name] = {} + for strain in trait[0].data: + strains.append(strain) + input_data[trait[0].name][strain] = trait[0].data[strain].value + + return { + "wgcna_input": input_data + } + + def process_dataset(trait_list): + """process datasets and strains""" + + input_data = {} + traits = [] + strains = [] + + # xtodo unique traits and strains + + for trait in trait_list: + traits.append(trait[0].name) + + input_data[trait[0].name] = {} + for strain in trait[0].data: + strains.append(strain) + input_data[trait[0].name][strain] = trait[0].data[strain].value + + return { + "wgcna_input": input_data + } + + def process_wgcna_data(response): """function for processing modeigene genes for create row data for datataba""" -- cgit v1.2.3 From 585cd45c56ae4bc444336815cbde791b0c0d2e7b Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sat, 9 Oct 2021 22:11:50 +0300 Subject: add function to call wgcna api --- wqflask/wqflask/wgcna/gn3_wgcna.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/wqflask/wqflask/wgcna/gn3_wgcna.py b/wqflask/wqflask/wgcna/gn3_wgcna.py index 9ab6b3e0..520f3d04 100644 --- a/wqflask/wqflask/wgcna/gn3_wgcna.py +++ b/wqflask/wqflask/wgcna/gn3_wgcna.py @@ -1,6 +1,7 @@ """module contains code to consume gn3-wgcna api and process data to be rendered by datatables """ +from typing import SimpleNamespace from utility.helper_functions import get_trait_db_obs @@ -85,3 +86,33 @@ def process_image(response): } if image_data else { "image_generated": False }) + + +def run_wgcna(form_data): + """function to run wgcna""" + + wgcna_api = f"{GN3_URL}/api/wgcna/run_wgcna" + + # parse form data + + trait_dataset = fetch_trait_data(form_data) + + response = requests.post(wgcna_api, { + "socket_id": form_data.get("socket_id"), # streaming disabled + "sample_names": list(set(strains)), + "trait_names": form_traits, + "trait_sample_data": form_strains, + "TOMtype": form_data["TOMtype"], + "minModuleSize": int(form_data["MinModuleSize"]), + "corType": form_data["corType"] + + } + ).json() + + if response.status_code == 200: + return { + {"results": response, + "data": process_wgcna_data(response), + "image": process_image(response) + } + } -- cgit v1.2.3 From 9a17787cab82fe1c89dc68521eca9e6c8bb1dbb6 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sun, 10 Oct 2021 01:44:45 +0300 Subject: pep8 formatting fix for parsing response data --- wqflask/wqflask/wgcna/gn3_wgcna.py | 66 ++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/wqflask/wqflask/wgcna/gn3_wgcna.py b/wqflask/wqflask/wgcna/gn3_wgcna.py index 520f3d04..96510223 100644 --- a/wqflask/wqflask/wgcna/gn3_wgcna.py +++ b/wqflask/wqflask/wgcna/gn3_wgcna.py @@ -1,10 +1,18 @@ """module contains code to consume gn3-wgcna api and process data to be rendered by datatables """ -from typing import SimpleNamespace + +import requests +from types import SimpleNamespace from utility.helper_functions import get_trait_db_obs +def validate_form(requestform): + return { + "" + } + + def fetch_trait_data(requestform): """fetch trait data""" db_obj = SimpleNamespace() @@ -31,30 +39,14 @@ def process_dataset(trait_list): for strain in trait[0].data: strains.append(strain) input_data[trait[0].name][strain] = trait[0].data[strain].value + # "sample_names": list(set(strains)), + # "trait_names": form_traits, + # "trait_sample_data": form_strains, return { - "wgcna_input": input_data - } - - def process_dataset(trait_list): - """process datasets and strains""" - - input_data = {} - traits = [] - strains = [] - - # xtodo unique traits and strains - - for trait in trait_list: - traits.append(trait[0].name) - - input_data[trait[0].name] = {} - for strain in trait[0].data: - strains.append(strain) - input_data[trait[0].name][strain] = trait[0].data[strain].value - - return { - "wgcna_input": input_data + "input": input_data, + "trait_names": traits, + "sample_names": strains } @@ -91,28 +83,26 @@ def process_image(response): def run_wgcna(form_data): """function to run wgcna""" + GN3_URL = "http://127.0.0.1:8081" + wgcna_api = f"{GN3_URL}/api/wgcna/run_wgcna" # parse form data trait_dataset = fetch_trait_data(form_data) + form_data["minModuleSize"] = int(form_data["MinModuleSize"]) - response = requests.post(wgcna_api, { - "socket_id": form_data.get("socket_id"), # streaming disabled - "sample_names": list(set(strains)), - "trait_names": form_traits, - "trait_sample_data": form_strains, - "TOMtype": form_data["TOMtype"], - "minModuleSize": int(form_data["MinModuleSize"]), - "corType": form_data["corType"] + response = requests.post(wgcna_api, json={ + "sample_names": list(set(trait_dataset["sample_names"])), + "trait_names": trait_dataset["trait_names"], + "trait_sample_data": list(trait_dataset["input"].values()), + **form_data } ).json() - if response.status_code == 200: - return { - {"results": response, - "data": process_wgcna_data(response), - "image": process_image(response) - } - } + return { + "results": response, + "data": process_wgcna_data(response["data"]), + "image": process_image(response["data"]) + } -- cgit v1.2.3 From b8a2b58cbf5bb96d86d59da7e72a9cb5f874fc41 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sun, 10 Oct 2021 01:47:50 +0300 Subject: call run_wgcna in views && render test template --- wqflask/wqflask/views.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 44560427..a462b31a 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -39,8 +39,8 @@ from gn3.db.phenotypes import Probeset from gn3.db.phenotypes import Publication from gn3.db.phenotypes import PublishXRef from gn3.db.phenotypes import probeset_mapping -from gn3.db.traits import get_trait_csv_sample_data -from gn3.db.traits import update_sample_data +# from gn3.db.traits import get_trait_csv_sample_data +# from gn3.db.traits import update_sample_data from flask import current_app @@ -77,6 +77,7 @@ from wqflask.correlation_matrix import show_corr_matrix from wqflask.correlation import corr_scatter_plot # from wqflask.wgcna import wgcna_analysis # from wqflask.ctl import ctl_analysis +from wqflask.wgcna.gn3_wgcna import run_wgcna from wqflask.snp_browser import snp_browser from wqflask.search_results import SearchResultPage from wqflask.export_traits import export_search_results_csv @@ -365,18 +366,11 @@ def wcgna_setup(): return render_template("wgcna_setup.html", **request.form) -# @app.route("/wgcna_results", methods=('POST',)) -# def wcgna_results(): -# logger.info("In wgcna, request.form is:", request.form) -# logger.info(request.url) -# # Start R, load the package and pointers and create the analysis -# wgcna = wgcna_analysis.WGCNA() -# # Start the analysis, a wgcnaA object should be a separate long running thread -# wgcnaA = wgcna.run_analysis(request.form) -# # After the analysis is finished store the result -# result = wgcna.process_results(wgcnaA) -# # Display them using the template -# return render_template("wgcna_results.html", **result) +@app.route("/wgcna_results", methods=('POST',)) +def wcgna_results(): + """call the gn3 api to get wgcna response data""" + results = run_wgcna(dict(request.form)) + return render_template("test_wgcna_results.html", **results) @app.route("/ctl_setup", methods=('POST',)) -- cgit v1.2.3 From f07d5014a3d6756199bd206b4251f6d3b48bf165 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sun, 10 Oct 2021 01:49:14 +0300 Subject: pep8 formatting && remove unused functions --- wqflask/wqflask/wgcna/gn3_wgcna.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/wqflask/wqflask/wgcna/gn3_wgcna.py b/wqflask/wqflask/wgcna/gn3_wgcna.py index 96510223..c4cc2e7f 100644 --- a/wqflask/wqflask/wgcna/gn3_wgcna.py +++ b/wqflask/wqflask/wgcna/gn3_wgcna.py @@ -7,12 +7,6 @@ from types import SimpleNamespace from utility.helper_functions import get_trait_db_obs -def validate_form(requestform): - return { - "" - } - - def fetch_trait_data(requestform): """fetch trait data""" db_obj = SimpleNamespace() -- cgit v1.2.3 From 63a161e2a6d3863720aa6814f1060bed22c22a39 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sun, 10 Oct 2021 01:54:51 +0300 Subject: enable wgcna in gn2 toolbar --- wqflask/wqflask/templates/tool_buttons.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/templates/tool_buttons.html b/wqflask/wqflask/templates/tool_buttons.html index 3f9d8211..3ee5be19 100644 --- a/wqflask/wqflask/templates/tool_buttons.html +++ b/wqflask/wqflask/templates/tool_buttons.html @@ -18,13 +18,13 @@ BNW - +
    - +
    - - + {% if image["image_generated"] %}
    @@ -140,13 +129,8 @@ let results = {{results|safe}} let phenoModules = results["data"]["output"]["net_colors"] -let phenotypes = Array.from(Object.keys(phenoModules)); +let phenotypes = Object.keys(phenoModules) let phenoMods = Object.values(phenoModules) -console.log(phenotypes) - - -console.log(typeof phenotypes); - let {col_names,mod_dataset} = {{data|safe}} $('#eigens').DataTable( { @@ -159,7 +143,6 @@ let {col_names,mod_dataset} = {{data|safe}} } ); $('#phenos').DataTable( { data:phenotypes.map((phenoName,idx)=>{ - console.log(phenoName) return [phenoName,phenoMods[idx]] }), columns: [{ -- cgit v1.2.3 From 1642c6bf0b8dee3bd3d4c32636a2c11ab97025a0 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sun, 10 Oct 2021 12:02:01 +0300 Subject: replace form input with select options --- wqflask/wqflask/templates/wgcna_setup.html | 35 ++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/wqflask/wqflask/templates/wgcna_setup.html b/wqflask/wqflask/templates/wgcna_setup.html index 9d4bbfc7..86d9fa10 100644 --- a/wqflask/wqflask/templates/wgcna_setup.html +++ b/wqflask/wqflask/templates/wgcna_setup.html @@ -35,29 +35,42 @@
    -
    - -
    - -
    + +
    + +
    + +
    +
    - +
    -
    - -
    - -
    + +
    + +
    + +
    +
    + + + {% endif %}
    -- cgit v1.2.3 From a212ad123f902b6a9c74bcac1d98bc274cebbdda Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 12 Oct 2021 17:36:02 +0000 Subject: Fixed export_mapping_results test in test_run_mapping.py --- .../wqflask/marker_regression/test_run_mapping.py | 33 +++++++++++----------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/wqflask/tests/unit/wqflask/marker_regression/test_run_mapping.py b/wqflask/tests/unit/wqflask/marker_regression/test_run_mapping.py index 3747aeb8..868b0b0b 100644 --- a/wqflask/tests/unit/wqflask/marker_regression/test_run_mapping.py +++ b/wqflask/tests/unit/wqflask/marker_regression/test_run_mapping.py @@ -43,7 +43,7 @@ class TestRunMapping(unittest.TestCase): }) } self.dataset = AttributeSetter( - {"fullname": "dataser_1", "group": self.group, "type": "ProbeSet"}) + {"fullname": "dataset_1", "group": self.group, "type": "ProbeSet"}) self.chromosomes = AttributeSetter({"chromosomes": chromosomes}) self.trait = AttributeSetter( @@ -180,37 +180,36 @@ class TestRunMapping(unittest.TestCase): with mock.patch("wqflask.marker_regression.run_mapping.datetime.datetime", new=datetime_mock): export_mapping_results(dataset=self.dataset, trait=self.trait, markers=markers, - results_path="~/results", mapping_scale="physic", score_type="-log(p)", - transform="qnorm", covariates="Dataset1:Trait1,Dataset2:Trait2", n_samples="100", - vals_hash="") + results_path="~/results", mapping_method="gemma", mapping_scale="physic", + score_type="-logP", transform="qnorm", + covariates="Dataset1:Trait1,Dataset2:Trait2", + n_samples="100", vals_hash="") write_calls = [ mock.call('Time/Date: 09/01/19 / 10:12:12\n'), mock.call('Population: Human GP1_\n'), mock.call( - 'Data Set: dataser_1\n'), + 'Data Set: dataset_1\n'), mock.call('Trait: Test Name\n'), mock.call('Trait Hash: \n'), - mock.call('N Samples: 100\n'), mock.call( - 'Transform - Quantile Normalized\n'), + mock.call('N Samples: 100\n'), + mock.call('Mapping Tool: gemma\n'), + mock.call('Transform - Quantile Normalized\n'), mock.call('Gene Symbol: IGFI\n'), mock.call( 'Location: X1 @ 123313 Mb\n'), mock.call('Cofactors (dataset - trait):\n'), mock.call('Trait1 - Dataset1\n'), mock.call('Trait2 - Dataset2\n'), mock.call('\n'), mock.call('Name,Chr,'), - mock.call('Mb,-log(p)'), mock.call('Cm,-log(p)'), + mock.call('Mb,-logP'), mock.call(',Additive'), mock.call(',Dominance'), mock.call('\n'), mock.call('MK1,C1,'), - mock.call('12000,'), mock.call('1,'), - mock.call('3'), mock.call(',VA'), - mock.call(',TT'), mock.call('\n'), - mock.call('MK2,C2,'), mock.call('10000,'), - mock.call('15,'), mock.call('7'), + mock.call('12000,'), mock.call('3'), + mock.call(',VA'), mock.call(',TT'), + mock.call('\n'), mock.call('MK2,C2,'), + mock.call('10000,'), mock.call('7'), mock.call('\n'), mock.call('MK1,C3,'), - mock.call('1,'), mock.call('45,'), - mock.call('7'), mock.call(',VE'), - mock.call(',Tt') - + mock.call('1,'), mock.call('7'), + mock.call(',VE'), mock.call(',Tt') ] mock_open.assert_called_once_with("~/results", "w+") filehandler = mock_open() -- cgit v1.2.3 From db9225caf0a78b13af1892d47c69463e00262d03 Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Wed, 13 Oct 2021 08:22:51 +0300 Subject: disable heatmap image --- wqflask/wqflask/templates/test_wgcna_results.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/templates/test_wgcna_results.html b/wqflask/wqflask/templates/test_wgcna_results.html index 0d253564..1dddd393 100644 --- a/wqflask/wqflask/templates/test_wgcna_results.html +++ b/wqflask/wqflask/templates/test_wgcna_results.html @@ -94,9 +94,9 @@
    {% endif %} -
    +
    -- cgit v1.2.3 From 2b5b6a119e22d0bbe29c08681c34a50f9769f38e Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Wed, 13 Oct 2021 09:06:57 +0300 Subject: Update the action button text Issue: https://github.com/genenetwork/gn-gemtext-threads/blob/main/topics/gn1-migration-to-gn2/non-clustered-heatmaps-and-flipping.gmi * Update the action button text to more closely correspond to the action that the button triggers. --- wqflask/wqflask/templates/collections/view.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index d347a060..0ded66a6 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -54,7 +54,7 @@ class="btn btn-primary" data-url="{{heatmap_data_url}}" title="Generate heatmap from this collection"> - Clustered Heatmap + Generate Heatmap -- cgit v1.2.3 From ba98ef026544d4437e65a7bd248ff9591296b48e Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Wed, 13 Oct 2021 09:37:19 +0300 Subject: Add some documentation for generating heatmaps Issue: https://github.com/genenetwork/gn-gemtext-threads/blob/main/topics/gn1-migration-to-gn2/non-clustered-heatmaps-and-flipping.gmi * Add some documentation on generating the heatmaps, that would be useful for the end user. --- doc/heatmap-generation.org | 34 ++++++++++++++++++++++++++++++++ doc/images/gn2_header_collections.png | Bin 0 -> 7890 bytes doc/images/heatmap_form.png | Bin 0 -> 9363 bytes doc/images/heatmap_with_hover_tools.png | Bin 0 -> 42578 bytes 4 files changed, 34 insertions(+) create mode 100644 doc/heatmap-generation.org create mode 100644 doc/images/gn2_header_collections.png create mode 100644 doc/images/heatmap_form.png create mode 100644 doc/images/heatmap_with_hover_tools.png diff --git a/doc/heatmap-generation.org b/doc/heatmap-generation.org new file mode 100644 index 00000000..a697c70b --- /dev/null +++ b/doc/heatmap-generation.org @@ -0,0 +1,34 @@ +#+STARTUP: inlineimages +#+TITLE: Heatmap Generation +#+AUTHOR: Muriithi Frederick Muriuki + +* Generating Heatmaps + +Like a lot of other features, the heatmap generation requires an existing collection. If none exists, see [[][Creating a new collection]] for how to create a new collection. + +Once you have a collection, you can navigate to the collections page by clicking on the "Collections" link in the header + + +[[./images/gn2_header_collections.png]] + +From that page, pick the collection that you want to work with by clicking on its name on the collections table. + +That takes you to that collection's page, where you can select the data that you want to use to generate the heatmap. + +** Selecting Orientation + +Once you have selected the data, select the orientation of the heatmap you want generated. You do this by selecting either *"vertical"* or *"horizontal"* in the heatmaps form: + +[[./images/heatmap_form.png]] + +Once you have selected the orientation, click on the "Generate Heatmap" button as in the image above. + +The heatmap generation might take a while, but once it is done, an image shows up above the data table. + +** Downloading the PNG copy of the Heatmap + +Once the heatmap image is shown, hovering over it, displays some tools to interact with the image. + +To download, hover over the heatmap image, and click on the "Download plot as png" icon as shown. + +[[./images/heatmap_with_hover_tools.png]] diff --git a/doc/images/gn2_header_collections.png b/doc/images/gn2_header_collections.png new file mode 100644 index 00000000..ac23f9c1 Binary files /dev/null and b/doc/images/gn2_header_collections.png differ diff --git a/doc/images/heatmap_form.png b/doc/images/heatmap_form.png new file mode 100644 index 00000000..163fbb60 Binary files /dev/null and b/doc/images/heatmap_form.png differ diff --git a/doc/images/heatmap_with_hover_tools.png b/doc/images/heatmap_with_hover_tools.png new file mode 100644 index 00000000..4ab79f99 Binary files /dev/null and b/doc/images/heatmap_with_hover_tools.png differ -- cgit v1.2.3 From 25cafac773edf3a053819b53ef860321a678941a Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 13 Oct 2021 17:46:18 +0000 Subject: Fix issue where score_type being set wrong caused an error when exporting mapping results (a while back it was changed from LOD to -logP) --- wqflask/wqflask/marker_regression/run_mapping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/marker_regression/run_mapping.py b/wqflask/wqflask/marker_regression/run_mapping.py index 80094057..769b9240 100644 --- a/wqflask/wqflask/marker_regression/run_mapping.py +++ b/wqflask/wqflask/marker_regression/run_mapping.py @@ -230,7 +230,7 @@ class RunMapping: self.perm_strata = get_perm_strata( self.this_trait, primary_samples, self.categorical_vars, self.samples) - self.score_type = "LOD" + self.score_type = "-logP" self.control_marker = start_vars['control_marker'] self.do_control = start_vars['do_control'] if 'mapmethod_rqtl_geno' in start_vars: -- cgit v1.2.3 From bc1b297acdd85f9bc04cd402f646ce123401b907 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 18 Oct 2021 17:55:09 +0000 Subject: Replace this_trait.dataset.type with just self.dataset.type --- wqflask/wqflask/search_results.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index bc0c08a1..5db469c1 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -200,13 +200,14 @@ class SearchResultPage: self.max_widths[key] = max(len(str(trait[key])), self.max_widths[key]) if key in self.max_widths else len(str(trait[key])) self.wide_columns_exist = False - if this_trait.dataset.type == "Publish": + if self.dataset.type == "Publish": if (self.max_widths['display_name'] > 25 or self.max_widths['description'] > 100 or self.max_widths['authors']> 80): self.wide_columns_exist = True - if this_trait.dataset.type == "ProbeSet": + if self.dataset.type == "ProbeSet": if (self.max_widths['display_name'] > 25 or self.max_widths['symbol'] > 25 or self.max_widths['description'] > 100): self.wide_columns_exist = True + self.trait_list = trait_list def search(self): -- cgit v1.2.3 From 789f91513a551dfe65133a2ae6191e6d98cbfcf2 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 18 Oct 2021 18:29:52 +0000 Subject: Changed table width to 100% for non-Geno datasets, since it can always be manually resized --- wqflask/wqflask/templates/search_result_page.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index 72a4b560..5922ac75 100644 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -126,7 +126,7 @@ {% endif %}
    {% endif %} -
    +
    -- cgit v1.2.3 From 7f241d1205eefe5bec871ef2d5b99168e0e136d7 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 18 Oct 2021 18:30:55 +0000 Subject: Fixed the way group code was used when setting phenotype trait display name + prevent encoding error with try/except --- wqflask/wqflask/search_results.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index 5db469c1..61970e7e 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -122,8 +122,8 @@ class SearchResultPage: trait_dict['display_name'] = result[0] if self.dataset.type == "Publish": - if self.dataset.group.code: - trait_dict['display_name'] = self.dataset.group.code + "_" + str(result[0]) + if result[10]: + trait_dict['display_name'] = str(result[10]) + "_" + str(result[0]) trait_dict['dataset'] = self.dataset.name trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(trait_dict['name'], trait_dict['dataset'])) @@ -186,7 +186,11 @@ class SearchResultPage: # Convert any bytes in dict to a normal utf-8 string for key in trait_dict.keys(): if isinstance(trait_dict[key], bytes): - trait_dict[key] = trait_dict[key].decode('utf-8') + try: + trait_dict[key] = trait_dict[key].decode('utf-8') + except UnicodeDecodeError: + trait_dict[key] = trait_dict[key].decode('latin-1') + trait_list.append(trait_dict) if self.results: -- cgit v1.2.3 From 4b1ce6c7ee290f2a11f9323085e7e559c7c7f322 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 19 Oct 2021 05:00:30 +0000 Subject: Fixed heatmap URL in view_collection --- wqflask/wqflask/collect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/collect.py b/wqflask/wqflask/collect.py index 3475ae5d..76ef5ca4 100644 --- a/wqflask/wqflask/collect.py +++ b/wqflask/wqflask/collect.py @@ -222,7 +222,7 @@ def view_collection(): collection_info = dict( trait_obs=trait_obs, uc=uc, - heatmap_data_url=f"{GN_SERVER_URL}api/heatmaps/clustered") + heatmap_data_url=f"{GN_SERVER_URL}heatmaps/clustered") if "json" in params: return json.dumps(json_version) -- cgit v1.2.3 From 12b72d28b28ede3e891afa2e679570a27110a094 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 19 Oct 2021 05:15:07 +0000 Subject: Fixed bug where the record ID for ProbeSet traits wasn't being pulled from the correct position in the query results --- wqflask/wqflask/search_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index 61970e7e..f8ecd178 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -112,7 +112,7 @@ class SearchResultPage: trait_dict = {} trait_dict['index'] = index + 1 - trait_dict['name'] = result[0] + trait_dict['name'] = result[2] #ZS: Check permissions on a trait-by-trait basis for phenotype traits if self.dataset.type == "Publish": -- cgit v1.2.3 From ebfeee414acc0f59090fc86eaf2d631b7a3f0790 Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 19 Oct 2021 05:29:46 +0000 Subject: Fixed some errors/logic for search results related to how the name/display name was being set --- wqflask/wqflask/search_results.py | 26 +++++++++++------------ wqflask/wqflask/templates/search_result_page.html | 12 ++++++++++- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index f8ecd178..dcc8021a 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -112,22 +112,11 @@ class SearchResultPage: trait_dict = {} trait_dict['index'] = index + 1 - trait_dict['name'] = result[2] - - #ZS: Check permissions on a trait-by-trait basis for phenotype traits - if self.dataset.type == "Publish": - permissions = check_resource_availability(self.dataset, trait_dict['name']) - if "view" not in permissions['data']: - continue - - trait_dict['display_name'] = result[0] - if self.dataset.type == "Publish": - if result[10]: - trait_dict['display_name'] = str(result[10]) + "_" + str(result[0]) trait_dict['dataset'] = self.dataset.name - trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(trait_dict['name'], trait_dict['dataset'])) if self.dataset.type == "ProbeSet": + trait_dict['display_name'] = result[2] + trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(trait_dict['display_name'], trait_dict['dataset'])) trait_dict['symbol'] = "N/A" if result[3] is None else result[3].strip() description_text = "N/A" if result[4] is None or str(result[4]) == "" else trait_dict['symbol'] @@ -144,10 +133,21 @@ class SearchResultPage: trait_dict['lod_score'] = "N/A" if result[9] is None or result[9] == "" else f"{float(result[9]) / 4.61:.1f}" trait_dict['lrs_location'] = "N/A" if result[13] is None or result[13] == "" or result[14] is None else f"Chr{result[13]}: {float(result[14]):.6f}" elif self.dataset.type == "Geno": + trait_dict['display_name'] = str(result[0]) + trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(trait_dict['display_name'], trait_dict['dataset'])) trait_dict['location'] = "N/A" if (result[4] != "NULL" and result[4] != "") and (result[5] != 0): trait_dict['location'] = f"Chr{result[4]}: {float(result[5]):.6f}" elif self.dataset.type == "Publish": + # Check permissions on a trait-by-trait basis for phenotype traits + trait_dict['name'] = trait_dict['display_name'] = str(result[0]) + trait_dict['hmac'] = hmac.data_hmac('{}:{}'.format(trait_dict['name'], trait_dict['dataset'])) + permissions = check_resource_availability(self.dataset, trait_dict['display_name']) + if "view" not in permissions['data']: + continue + + if result[10]: + trait_dict['display_name'] = str(result[10]) + "_" + str(result[0]) trait_dict['description'] = "N/A" trait_dict['pubmed_id'] = "N/A" trait_dict['pubmed_link'] = "N/A" diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index 5922ac75..95842316 100644 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -206,7 +206,7 @@ 'width': "{{ max_widths.display_name * 8 }}px", 'targets': 2, 'render': function(data, type, row, meta) { - return '' + data.display_name + '' + return '' + data.display_name + '' } }, { @@ -361,6 +361,16 @@ 'targets': 9, 'orderSequence': [ "desc", "asc"] }{% elif dataset.type == 'Geno' %}, + { + 'title': "Record", + 'type': "natural-minus-na", + 'width': "{{ max_widths.display_name * 9 }}px", + 'data': null, + 'targets': 2, + 'render': function(data, type, row, meta) { + return '' + data.display_name + '' + } + }, { 'title': "
    Location
    ", 'type': "natural-minus-na", -- cgit v1.2.3 From c16e361c402028c02421644c652c42c641931ddf Mon Sep 17 00:00:00 2001 From: zsloan Date: Tue, 19 Oct 2021 15:48:33 +0000 Subject: Fixes issue with the bytes encoding being included in the string for ProbeSet trait descriptions on the search result page --- wqflask/wqflask/search_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index dcc8021a..5ca1f9ca 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -120,7 +120,7 @@ class SearchResultPage: trait_dict['symbol'] = "N/A" if result[3] is None else result[3].strip() description_text = "N/A" if result[4] is None or str(result[4]) == "" else trait_dict['symbol'] - target_string = result[5] + target_string = result[5].decode('utf-8') if result[5] else "" description_display = description_text if target_string is None or str(target_string) == "" else description_text + "; " + str(target_string).strip() trait_dict['description'] = description_display -- cgit v1.2.3 From 2d0c4fa72757674fa5e526696c66c5b7b6b6f983 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 22 Oct 2021 02:12:54 +0000 Subject: Possible fix for the weird inconsistent correlation key error for strain BXD073bxBXD077F1 --- wqflask/wqflask/correlation/correlation_gn3_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/correlation/correlation_gn3_api.py b/wqflask/wqflask/correlation/correlation_gn3_api.py index a18bceaf..1e3a40f2 100644 --- a/wqflask/wqflask/correlation/correlation_gn3_api.py +++ b/wqflask/wqflask/correlation/correlation_gn3_api.py @@ -65,7 +65,7 @@ def process_samples(start_vars, sample_names, excluded_samples=None): excluded_samples = () sample_vals_dict = json.loads(start_vars["sample_vals"]) for sample in sample_names: - if sample not in excluded_samples: + if sample not in excluded_samples and sample in sample_vals_dict: val = sample_vals_dict[sample] if not val.strip().lower() == "x": sample_data[str(sample)] = float(val) -- cgit v1.2.3 From 5798e6a2161384cb6cf6b6e1e28eccf3a2ee8da3 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Fri, 22 Oct 2021 11:11:10 +0200 Subject: Updated funding and credits --- wqflask/wqflask/export_traits.py | 2 +- wqflask/wqflask/templates/base.html | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/wqflask/wqflask/export_traits.py b/wqflask/wqflask/export_traits.py index 5459dc31..f4e04a4b 100644 --- a/wqflask/wqflask/export_traits.py +++ b/wqflask/wqflask/export_traits.py @@ -48,7 +48,7 @@ def export_search_results_csv(targs): if targs['filter_term'] != "None": metadata.append(["Search Filter Terms: " + targs['filter_term']]) metadata.append(["Exported Row Number: " + str(len(table_rows))]) - metadata.append(["Funding for The GeneNetwork: NIGMS (R01 GM123489, 2017-2021), NIDA (P30 DA044223, 2017-2022), NIA (R01AG043930, 2013-2018), NIAAA (U01 AA016662, U01 AA013499, U24 AA013513, U01 AA014425, 2006-2017), NIDA/NIMH/NIAAA (P20-DA 21131, 2001-2012), NCI MMHCC (U01CA105417), NCRR/BIRN (U24 RR021760)"]) + metadata.append(["Funding for The GeneNetwork: NIGMS (R01 GM123489, 2017-2026), NIDA (P30 DA044223, 2017-2022), NIA (R01AG043930, 2013-2018), NIAAA (U01 AA016662, U01 AA013499, U24 AA013513, U01 AA014425, 2006-2017), NIDA/NIMH/NIAAA (P20-DA 21131, 2001-2012), NCI MMHCC (U01CA105417), NCRR/BIRN (U24 RR021760)"]) metadata.append([]) trait_list = [] diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html index d30c575a..a878efa9 100644 --- a/wqflask/wqflask/templates/base.html +++ b/wqflask/wqflask/templates/base.html @@ -159,17 +159,16 @@ ; June 15, 2001 as WebQTL; and Jan 5, 2005 as GeneNetwork.

    - This site is currently operated by + This site is currently operated by Rob Williams, - Pjotr Prins, + Pjotr Prins, Saunak Sen, Zachary Sloan, Arthur Centeno, - Christian Fischer and Bonface Munyoki.

    -

    Design and code by Pjotr Prins, Zach Sloan, Arthur Centeno, Christan Fischer, Bonface Munyoki, Danny Arends, Sam Ockman, Lei Yan, Xiaodong Zhou, Christian Fernandez, - Ning Liu, Rudi Alberts, Elissa Chesler, Sujoy Roy, Evan G. Williams, Alexander G. Williams, Kenneth Manly, Jintao Wang, Robert W. Williams, and +

    Design and code by Pjotr Prins, Zach Sloan, Arthur Centeno, Christan Fischer, Bonface Munyoki, Danny Arends, Arun Isaac, Alex Mwangi, Fred Muriithi, Sam Ockman, Lei Yan, Xiaodong Zhou, Christian Fernandez, + Ning Liu, Rudi Alberts, Elissa Chesler, Sujoy Roy, Evan G. Williams, Alexander G. Williams, Kenneth Manly, Jintao Wang, Robert W. Williams, and colleagues.


    @@ -182,7 +181,7 @@
  • NIGMS - Systems Genetics and Precision Medicine Project (R01 GM123489, 2017-2021) + Systems Genetics and Precision Medicine Project (R01 GM123489, 2017-2026)
  • NIDA -- cgit v1.2.3 From 29d0abcefb87cab9308522d30e5c2691108d83e9 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Fri, 22 Oct 2021 12:06:20 +0200 Subject: Add NSF award --- wqflask/wqflask/templates/base.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html index a878efa9..e62f571d 100644 --- a/wqflask/wqflask/templates/base.html +++ b/wqflask/wqflask/templates/base.html @@ -183,6 +183,11 @@ NIGMS Systems Genetics and Precision Medicine Project (R01 GM123489, 2017-2026)
  • +
  • + NSF + Panorama: Integrated Rack-Scale Acceleration for Computational Pangenomics + (PPoSS 2118709, 2021-2016) +
  • NIDA NIDA Core Center of Excellence in Transcriptomics, Systems Genetics, and the Addictome (P30 DA044223, 2017-2022) -- cgit v1.2.3 From cc32cc4f1472ddaf63a5b9428e8a67b8ba139282 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 22 Oct 2021 18:36:45 +0000 Subject: Added proxy and local GN3 URLs in tools.py (which should be set in the settings file) --- wqflask/utility/tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py index e28abb48..1219993a 100644 --- a/wqflask/utility/tools.py +++ b/wqflask/utility/tools.py @@ -266,6 +266,8 @@ WEBSERVER_MODE = get_setting('WEBSERVER_MODE') GN2_BASE_URL = get_setting('GN2_BASE_URL') GN2_BRANCH_URL = get_setting('GN2_BRANCH_URL') GN_SERVER_URL = get_setting('GN_SERVER_URL') +GN_PROXY_URL = get_setting('GN_PROXY_URL') +GN3_LOCAL_URL = get_setting('GN_LOCAL_URL') SERVER_PORT = get_setting_int('SERVER_PORT') SQL_URI = get_setting('SQL_URI') LOG_LEVEL = get_setting('LOG_LEVEL') -- cgit v1.2.3 From f2cdd50ebd5b927d46c8fbf7f32c9ca4ea61686f Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 22 Oct 2021 18:42:06 +0000 Subject: Replace hardcoded GN3 RQTL URL --- wqflask/wqflask/marker_regression/rqtl_mapping.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/wqflask/wqflask/marker_regression/rqtl_mapping.py b/wqflask/wqflask/marker_regression/rqtl_mapping.py index 1dca1b1b..34b10fc5 100644 --- a/wqflask/wqflask/marker_regression/rqtl_mapping.py +++ b/wqflask/wqflask/marker_regression/rqtl_mapping.py @@ -12,14 +12,11 @@ import numpy as np from base.webqtlConfig import TMPDIR from base.trait import create_trait -from utility.tools import locate +from utility.tools import locate, GN3_LOCAL_URL import utility.logger logger = utility.logger.getLogger(__name__) -GN3_RQTL_URL = "http://localhost:8086/api/rqtl/compute" -GN3_TMP_PATH = "/export/local/home/zas1024/genenetwork3/tmp" - def run_rqtl(trait_name, vals, samples, dataset, mapping_scale, model, method, num_perm, perm_strata_list, do_control, control_marker, manhattan_plot, cofactors): """Run R/qtl by making a request to the GN3 endpoint and reading in the output file(s)""" @@ -49,7 +46,7 @@ def run_rqtl(trait_name, vals, samples, dataset, mapping_scale, model, method, n if perm_strata_list: post_data["pstrata"] = True - rqtl_output = requests.post(GN3_RQTL_URL, data=post_data).json() + rqtl_output = requests.post(GN3_LOCAL_URL + "api/rqtl/compute", data=post_data).json() if num_perm > 0: return rqtl_output['perm_results'], rqtl_output['suggestive'], rqtl_output['significant'], rqtl_output['results'] else: -- cgit v1.2.3 From a05d0e4eb9412d9495a0f96980df27acb1526a03 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 22 Oct 2021 18:45:10 +0000 Subject: Replace hardcoded GN proxy URLs with one pulled from settings --- wqflask/utility/authentication_tools.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index 6802d689..afea69e1 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -4,11 +4,12 @@ import requests from flask import g from base import webqtlConfig - from utility.redis_tools import (get_redis_conn, get_resource_info, get_resource_id, add_resource) +from utility.tools import GN_PROXY_URL + Redis = get_redis_conn() def check_resource_availability(dataset, trait_id=None): @@ -24,19 +25,19 @@ def check_resource_availability(dataset, trait_id=None): if resource_id: resource_info = get_resource_info(resource_id) - # ZS: If resource isn't already in redis, add it with default + # If resource isn't already in redis, add it with default # privileges if not resource_info: resource_info = add_new_resource(dataset, trait_id) - # ZS: Check if super-user - we should probably come up with some + # Check if super-user - we should probably come up with some # way to integrate this into the proxy if g.user_session.user_id in Redis.smembers("super_users"): return webqtlConfig.SUPER_PRIVILEGES response = None - the_url = "http://localhost:8080/available?resource={}&user={}".format( + the_url = GN_PROXY_URL + "available?resource={}&user={}".format( resource_id, g.user_session.user_id) try: @@ -93,7 +94,7 @@ def get_group_code(dataset): def check_admin(resource_id=None): - the_url = "http://localhost:8080/available?resource={}&user={}".format( + the_url = GN_PROXY_URL + "available?resource={}&user={}".format( resource_id, g.user_session.user_id) try: response = json.loads(requests.get(the_url).content)['admin'] -- cgit v1.2.3 From a9ca9b0da80feaa5318343dbb7b814c78b5f41f7 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 22 Oct 2021 18:48:02 +0000 Subject: Replace hardcoded GN proxy URL in decorators.py with one pulled from settings --- wqflask/wqflask/decorators.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py index 54aa6795..c19e1aef 100644 --- a/wqflask/wqflask/decorators.py +++ b/wqflask/wqflask/decorators.py @@ -3,6 +3,7 @@ from flask import g from typing import Dict from functools import wraps from utility.hmac import hmac_creation +from utility.tools import GN_PROXY_URL import json import requests @@ -25,11 +26,11 @@ def edit_access_required(f): _user_id = g.user_session.record.get(b"user_id", "").decode("utf-8") response = json.loads( - requests.get("http://localhost:8080/" - "available?resource=" - f"{resource_id}&user={_user_id}").content) + requests.get(GN_PROXY_URL + "available?resource=" + f"{resource_id}&user={_user_id}").content) except: response = {} + if "edit" not in response.get("data", []): return "You need to be admin", 401 return f(*args, **kwargs) -- cgit v1.2.3 From 6bdd48e506e7c05b89ab7669ff81040fdd862ebb Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 22 Oct 2021 18:51:23 +0000 Subject: Replace hardcoded GN proxy URLs in trait.py with one pulled from settings --- wqflask/base/trait.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index 96a09302..f0749858 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -7,7 +7,7 @@ from base.webqtlCaseData import webqtlCaseData from base.data_set import create_dataset from utility import hmac from utility.authentication_tools import check_resource_availability -from utility.tools import GN2_BASE_URL +from utility.tools import GN2_BASE_URL, GN_PROXY_URL from utility.redis_tools import get_redis_conn, get_resource_id from utility.db_tools import escape @@ -361,10 +361,10 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): resource_id = get_resource_id(dataset, trait.name) if dataset.type == 'Publish': - the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view".format( + the_url = GN_PROXY_URL + "run-action?resource={}&user={}&branch=data&action=view".format( resource_id, g.user_session.user_id) else: - the_url = "http://localhost:8080/run-action?resource={}&user={}&branch=data&action=view&trait={}".format( + the_url = GN_PROXY_URL + "run-action?resource={}&user={}&branch=data&action=view&trait={}".format( resource_id, g.user_session.user_id, trait.name) try: -- cgit v1.2.3 From b8f2df1f08c423923aeb72a1b2c2316e6da232dc Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 22 Oct 2021 18:51:50 +0000 Subject: Fix line pulling GN3_LOCAL_URL from settings --- wqflask/utility/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py index 1219993a..0efe8ca9 100644 --- a/wqflask/utility/tools.py +++ b/wqflask/utility/tools.py @@ -267,7 +267,7 @@ GN2_BASE_URL = get_setting('GN2_BASE_URL') GN2_BRANCH_URL = get_setting('GN2_BRANCH_URL') GN_SERVER_URL = get_setting('GN_SERVER_URL') GN_PROXY_URL = get_setting('GN_PROXY_URL') -GN3_LOCAL_URL = get_setting('GN_LOCAL_URL') +GN3_LOCAL_URL = get_setting('GN3_LOCAL_URL') SERVER_PORT = get_setting_int('SERVER_PORT') SQL_URI = get_setting('SQL_URI') LOG_LEVEL = get_setting('LOG_LEVEL') -- cgit v1.2.3 From e8b6b4e99f7a4ea649dab0c5a7bf7695531e97d2 Mon Sep 17 00:00:00 2001 From: zsloan Date: Fri, 22 Oct 2021 18:52:39 +0000 Subject: Include the admin privilege for the 'editors' group, since it wasn't being set before (which caused some problems) --- scripts/authentication/resource.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/authentication/resource.py b/scripts/authentication/resource.py index 4996f34c..1a2bcd8a 100644 --- a/scripts/authentication/resource.py +++ b/scripts/authentication/resource.py @@ -97,7 +97,8 @@ if __name__ == "__main__": for resource_id, resource in RESOURCES.items(): _resource = json.loads(resource) # str -> dict conversion _resource["group_masks"] = {args.group_id: {"metadata": "edit", - "data": "edit"}} + "data": "edit", + "admin": "edit-admins"}} REDIS_CONN.hset("resources", resource_id, json.dumps(_resource)) -- cgit v1.2.3 From 205b5e7b1796898a5dd72482c6905f05bbaa44c7 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sat, 9 Oct 2021 09:32:54 +0300 Subject: api: correlation: Delete unused logger --- wqflask/wqflask/api/correlation.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/wqflask/wqflask/api/correlation.py b/wqflask/wqflask/api/correlation.py index 870f3275..ff125d96 100644 --- a/wqflask/wqflask/api/correlation.py +++ b/wqflask/wqflask/api/correlation.py @@ -15,9 +15,6 @@ from wqflask.correlation import correlation_functions from utility import webqtlUtil, helper_functions, corr_result_helpers from utility.benchmark import Bench -import utility.logger -logger = utility.logger.getLogger(__name__) - def do_correlation(start_vars): assert('db' in start_vars) -- cgit v1.2.3 From a22ffd365e669b0d8d577a82ea6bcd9c5c188b2b Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sat, 9 Oct 2021 09:34:09 +0300 Subject: api: correlation: Delete unused imports --- wqflask/wqflask/api/correlation.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/wqflask/wqflask/api/correlation.py b/wqflask/wqflask/api/correlation.py index ff125d96..a135ecc8 100644 --- a/wqflask/wqflask/api/correlation.py +++ b/wqflask/wqflask/api/correlation.py @@ -1,19 +1,13 @@ import collections - import scipy -from utility.db_tools import escape - -from flask import g - from base import data_set from base.trait import create_trait, retrieve_sample_data - -from wqflask.correlation.show_corr_results import generate_corr_json +from flask import g +from utility import corr_result_helpers +from utility.db_tools import escape from wqflask.correlation import correlation_functions -from utility import webqtlUtil, helper_functions, corr_result_helpers -from utility.benchmark import Bench def do_correlation(start_vars): -- cgit v1.2.3 From 0c6ad845e6f461e880b423bd8f79156754658f8a Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sat, 9 Oct 2021 09:43:40 +0300 Subject: Move markdown_routes to api/markdown All new API definitions should be migrated to "wqflask/ api" --- .../tests/unit/wqflask/api/test_markdown_routes.py | 54 ++++++ wqflask/tests/unit/wqflask/test_markdown_routes.py | 54 ------ wqflask/wqflask/__init__.py | 16 +- wqflask/wqflask/api/correlation.py | 6 - wqflask/wqflask/api/markdown.py | 186 +++++++++++++++++++++ wqflask/wqflask/markdown_routes.py | 186 --------------------- 6 files changed, 248 insertions(+), 254 deletions(-) create mode 100644 wqflask/tests/unit/wqflask/api/test_markdown_routes.py delete mode 100644 wqflask/tests/unit/wqflask/test_markdown_routes.py create mode 100644 wqflask/wqflask/api/markdown.py delete mode 100644 wqflask/wqflask/markdown_routes.py diff --git a/wqflask/tests/unit/wqflask/api/test_markdown_routes.py b/wqflask/tests/unit/wqflask/api/test_markdown_routes.py new file mode 100644 index 00000000..1c513ac5 --- /dev/null +++ b/wqflask/tests/unit/wqflask/api/test_markdown_routes.py @@ -0,0 +1,54 @@ +"""Test functions for wqflask/api/markdown.py""" + +import unittest +from unittest import mock + +from dataclasses import dataclass +from wqflask.api.markdown import render_markdown + + +@dataclass +class MockRequests404: + status_code: int = 404 + + +@dataclass +class MockRequests200: + status_code: int = 200 + content: str = b""" +# Glossary +This is some content + +## Sub-heading +This is another sub-heading""" + + +class TestMarkdownRoutesFunctions(unittest.TestCase): + """Test cases for functions in markdown""" + + @mock.patch('wqflask.api.markdown.requests.get') + def test_render_markdown_when_fetching_locally(self, requests_mock): + requests_mock.return_value = MockRequests404() + markdown_content = render_markdown("general/glossary/glossary.md") + requests_mock.assert_called_with( + "https://raw.githubusercontent.com" + "/genenetwork/gn-docs/" + "master/general/" + "glossary/glossary.md") + self.assertRegexpMatches(markdown_content, + "Content for general/glossary/glossary.md not available.") + + @mock.patch('wqflask.api.markdown.requests.get') + def test_render_markdown_when_fetching_remotely(self, requests_mock): + requests_mock.return_value = MockRequests200() + markdown_content = render_markdown("general/glossary/glossary.md") + requests_mock.assert_called_with( + "https://raw.githubusercontent.com" + "/genenetwork/gn-docs/" + "master/general/" + "glossary/glossary.md") + self.assertEqual("""

    Glossary

    +

    This is some content

    +

    Sub-heading

    +

    This is another sub-heading

    """, + markdown_content) diff --git a/wqflask/tests/unit/wqflask/test_markdown_routes.py b/wqflask/tests/unit/wqflask/test_markdown_routes.py deleted file mode 100644 index 90e0f17c..00000000 --- a/wqflask/tests/unit/wqflask/test_markdown_routes.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Test functions in markdown utils""" - -import unittest -from unittest import mock - -from dataclasses import dataclass -from wqflask.markdown_routes import render_markdown - - -@dataclass -class MockRequests404: - status_code: int = 404 - - -@dataclass -class MockRequests200: - status_code: int = 200 - content: str = b""" -# Glossary -This is some content - -## Sub-heading -This is another sub-heading""" - - -class TestMarkdownRoutesFunctions(unittest.TestCase): - """Test cases for functions in markdown_routes""" - - @mock.patch('wqflask.markdown_routes.requests.get') - def test_render_markdown_when_fetching_locally(self, requests_mock): - requests_mock.return_value = MockRequests404() - markdown_content = render_markdown("general/glossary/glossary.md") - requests_mock.assert_called_with( - "https://raw.githubusercontent.com" - "/genenetwork/gn-docs/" - "master/general/" - "glossary/glossary.md") - self.assertRegexpMatches(markdown_content, - "Content for general/glossary/glossary.md not available.") - - @mock.patch('wqflask.markdown_routes.requests.get') - def test_render_markdown_when_fetching_remotely(self, requests_mock): - requests_mock.return_value = MockRequests200() - markdown_content = render_markdown("general/glossary/glossary.md") - requests_mock.assert_called_with( - "https://raw.githubusercontent.com" - "/genenetwork/gn-docs/" - "master/general/" - "glossary/glossary.md") - self.assertEqual("""

    Glossary

    -

    This is some content

    -

    Sub-heading

    -

    This is another sub-heading

    """, - markdown_content) diff --git a/wqflask/wqflask/__init__.py b/wqflask/wqflask/__init__.py index 8b2055cd..5758b13e 100644 --- a/wqflask/wqflask/__init__.py +++ b/wqflask/wqflask/__init__.py @@ -8,14 +8,14 @@ from flask import Flask from typing import Tuple from urllib.parse import urlparse from utility import formatting -from wqflask.markdown_routes import glossary_blueprint -from wqflask.markdown_routes import references_blueprint -from wqflask.markdown_routes import links_blueprint -from wqflask.markdown_routes import policies_blueprint -from wqflask.markdown_routes import environments_blueprint -from wqflask.markdown_routes import facilities_blueprint -from wqflask.markdown_routes import blogs_blueprint -from wqflask.markdown_routes import news_blueprint +from wqflask.api.markdown import glossary_blueprint +from wqflask.api.markdown import references_blueprint +from wqflask.api.markdown import links_blueprint +from wqflask.api.markdown import policies_blueprint +from wqflask.api.markdown import environments_blueprint +from wqflask.api.markdown import facilities_blueprint +from wqflask.api.markdown import blogs_blueprint +from wqflask.api.markdown import news_blueprint app = Flask(__name__) diff --git a/wqflask/wqflask/api/correlation.py b/wqflask/wqflask/api/correlation.py index a135ecc8..9b875c99 100644 --- a/wqflask/wqflask/api/correlation.py +++ b/wqflask/wqflask/api/correlation.py @@ -9,7 +9,6 @@ from utility.db_tools import escape from wqflask.correlation import correlation_functions - def do_correlation(start_vars): assert('db' in start_vars) assert('target_db' in start_vars) @@ -26,7 +25,6 @@ def do_correlation(start_vars): corr_results = calculate_results( this_trait, this_dataset, target_dataset, corr_params) - #corr_results = collections.OrderedDict(sorted(corr_results.items(), key=lambda t: -abs(t[1][0]))) final_results = [] for _trait_counter, trait in enumerate(list(corr_results.keys())[:corr_params['return_count']]): @@ -54,11 +52,7 @@ def do_correlation(start_vars): "#_strains": num_overlap, "p_value": sample_p } - final_results.append(result_dict) - - # json_corr_results = generate_corr_json(final_corr_results, this_trait, this_dataset, target_dataset, for_api = True) - return final_results diff --git a/wqflask/wqflask/api/markdown.py b/wqflask/wqflask/api/markdown.py new file mode 100644 index 00000000..580b9ac0 --- /dev/null +++ b/wqflask/wqflask/api/markdown.py @@ -0,0 +1,186 @@ +"""Markdown routes + +Render pages from github, or if they are unavailable, look for it else where +""" + +import requests +import markdown +import os +import sys + +from bs4 import BeautifulSoup # type: ignore + +from flask import send_from_directory +from flask import Blueprint +from flask import render_template + +from typing import Dict +from typing import List + +glossary_blueprint = Blueprint('glossary_blueprint', __name__) +references_blueprint = Blueprint("references_blueprint", __name__) +environments_blueprint = Blueprint("environments_blueprint", __name__) +links_blueprint = Blueprint("links_blueprint", __name__) +policies_blueprint = Blueprint("policies_blueprint", __name__) +facilities_blueprint = Blueprint("facilities_blueprint", __name__) +news_blueprint = Blueprint("news_blueprint", __name__) + +blogs_blueprint = Blueprint("blogs_blueprint", __name__) + + +def render_markdown(file_name, is_remote_file=True): + """Try to fetch the file name from Github and if that fails, try to +look for it inside the file system """ + github_url = ("https://raw.githubusercontent.com/" + "genenetwork/gn-docs/master/") + + if not is_remote_file: + text = "" + with open(file_name, "r", encoding="utf-8") as input_file: + text = input_file.read() + return markdown.markdown(text, + extensions=['tables']) + + md_content = requests.get(f"{github_url}{file_name}") + + if md_content.status_code == 200: + return markdown.markdown(md_content.content.decode("utf-8"), + extensions=['tables']) + + return (f"\nContent for {file_name} not available. " + "Please check " + "(here to see where content exists)" + "[https://github.com/genenetwork/gn-docs]. " + "Please reach out to the gn2 team to have a look at this") + + +def get_file_from_python_search_path(pathname_suffix): + cands = [os.path.join(d, pathname_suffix) for d in sys.path] + try: + return list(filter(os.path.exists, cands))[0] + except IndexError: + return None + + +def get_blogs(user: str = "genenetwork", + repo_name: str = "gn-docs") -> dict: + + blogs: Dict[int, List] = {} + github_url = f"https://api.github.com/repos/{user}/{repo_name}/git/trees/master?recursive=1" + + repo_tree = requests.get(github_url).json()["tree"] + + for data in repo_tree: + path_name = data["path"] + if path_name.startswith("blog") and path_name.endswith(".md"): + split_path = path_name.split("/")[1:] + try: + year, title, file_name = split_path + except Exception as e: + year, file_name = split_path + title = "" + + subtitle = os.path.splitext(file_name)[0] + + blog = { + "title": title, + "subtitle": subtitle, + "full_path": path_name + } + + if year in blogs: + blogs[int(year)].append(blog) + else: + blogs[int(year)] = [blog] + + return dict(sorted(blogs.items(), key=lambda x: x[0], reverse=True)) + + +@glossary_blueprint.route('/') +def glossary(): + return render_template( + "glossary.html", + rendered_markdown=render_markdown("general/glossary/glossary.md")), 200 + + +@references_blueprint.route('/') +def references(): + return render_template( + "references.html", + rendered_markdown=render_markdown("general/references/references.md")), 200 + + +@news_blueprint.route('/') +def news(): + return render_template( + "news.html", + rendered_markdown=render_markdown("general/news/news.md")), 200 + + +@environments_blueprint.route("/") +def environments(): + + md_file = get_file_from_python_search_path("wqflask/DEPENDENCIES.md") + svg_file = get_file_from_python_search_path( + "wqflask/dependency-graph.html") + svg_data = None + if svg_file: + with open(svg_file, 'r') as f: + svg_data = "".join( + BeautifulSoup(f.read(), + 'lxml').body.script.contents) + + if md_file is not None: + return ( + render_template("environment.html", + svg_data=svg_data, + rendered_markdown=render_markdown( + md_file, + is_remote_file=False)), + 200 + ) + # Fallback: Fetch file from server + return (render_template( + "environment.html", + svg_data=None, + rendered_markdown=render_markdown( + "general/environments/environments.md")), + 200) + + +@environments_blueprint.route('/svg-dependency-graph') +def svg_graph(): + directory, file_name, _ = get_file_from_python_search_path( + "wqflask/dependency-graph.svg").partition("dependency-graph.svg") + return send_from_directory(directory, file_name) + + +@links_blueprint.route("/") +def links(): + return render_template( + "links.html", + rendered_markdown=render_markdown("general/links/links.md")), 200 + + +@policies_blueprint.route("/") +def policies(): + return render_template( + "policies.html", + rendered_markdown=render_markdown("general/policies/policies.md")), 200 + + +@facilities_blueprint.route("/") +def facilities(): + return render_template("facilities.html", rendered_markdown=render_markdown("general/help/facilities.md")), 200 + + +@blogs_blueprint.route("/") +def display_blog(blog_path): + return render_template("blogs.html", rendered_markdown=render_markdown(blog_path)) + + +@blogs_blueprint.route("/") +def blogs_list(): + blogs = get_blogs() + + return render_template("blogs_list.html", blogs=blogs) diff --git a/wqflask/wqflask/markdown_routes.py b/wqflask/wqflask/markdown_routes.py deleted file mode 100644 index 580b9ac0..00000000 --- a/wqflask/wqflask/markdown_routes.py +++ /dev/null @@ -1,186 +0,0 @@ -"""Markdown routes - -Render pages from github, or if they are unavailable, look for it else where -""" - -import requests -import markdown -import os -import sys - -from bs4 import BeautifulSoup # type: ignore - -from flask import send_from_directory -from flask import Blueprint -from flask import render_template - -from typing import Dict -from typing import List - -glossary_blueprint = Blueprint('glossary_blueprint', __name__) -references_blueprint = Blueprint("references_blueprint", __name__) -environments_blueprint = Blueprint("environments_blueprint", __name__) -links_blueprint = Blueprint("links_blueprint", __name__) -policies_blueprint = Blueprint("policies_blueprint", __name__) -facilities_blueprint = Blueprint("facilities_blueprint", __name__) -news_blueprint = Blueprint("news_blueprint", __name__) - -blogs_blueprint = Blueprint("blogs_blueprint", __name__) - - -def render_markdown(file_name, is_remote_file=True): - """Try to fetch the file name from Github and if that fails, try to -look for it inside the file system """ - github_url = ("https://raw.githubusercontent.com/" - "genenetwork/gn-docs/master/") - - if not is_remote_file: - text = "" - with open(file_name, "r", encoding="utf-8") as input_file: - text = input_file.read() - return markdown.markdown(text, - extensions=['tables']) - - md_content = requests.get(f"{github_url}{file_name}") - - if md_content.status_code == 200: - return markdown.markdown(md_content.content.decode("utf-8"), - extensions=['tables']) - - return (f"\nContent for {file_name} not available. " - "Please check " - "(here to see where content exists)" - "[https://github.com/genenetwork/gn-docs]. " - "Please reach out to the gn2 team to have a look at this") - - -def get_file_from_python_search_path(pathname_suffix): - cands = [os.path.join(d, pathname_suffix) for d in sys.path] - try: - return list(filter(os.path.exists, cands))[0] - except IndexError: - return None - - -def get_blogs(user: str = "genenetwork", - repo_name: str = "gn-docs") -> dict: - - blogs: Dict[int, List] = {} - github_url = f"https://api.github.com/repos/{user}/{repo_name}/git/trees/master?recursive=1" - - repo_tree = requests.get(github_url).json()["tree"] - - for data in repo_tree: - path_name = data["path"] - if path_name.startswith("blog") and path_name.endswith(".md"): - split_path = path_name.split("/")[1:] - try: - year, title, file_name = split_path - except Exception as e: - year, file_name = split_path - title = "" - - subtitle = os.path.splitext(file_name)[0] - - blog = { - "title": title, - "subtitle": subtitle, - "full_path": path_name - } - - if year in blogs: - blogs[int(year)].append(blog) - else: - blogs[int(year)] = [blog] - - return dict(sorted(blogs.items(), key=lambda x: x[0], reverse=True)) - - -@glossary_blueprint.route('/') -def glossary(): - return render_template( - "glossary.html", - rendered_markdown=render_markdown("general/glossary/glossary.md")), 200 - - -@references_blueprint.route('/') -def references(): - return render_template( - "references.html", - rendered_markdown=render_markdown("general/references/references.md")), 200 - - -@news_blueprint.route('/') -def news(): - return render_template( - "news.html", - rendered_markdown=render_markdown("general/news/news.md")), 200 - - -@environments_blueprint.route("/") -def environments(): - - md_file = get_file_from_python_search_path("wqflask/DEPENDENCIES.md") - svg_file = get_file_from_python_search_path( - "wqflask/dependency-graph.html") - svg_data = None - if svg_file: - with open(svg_file, 'r') as f: - svg_data = "".join( - BeautifulSoup(f.read(), - 'lxml').body.script.contents) - - if md_file is not None: - return ( - render_template("environment.html", - svg_data=svg_data, - rendered_markdown=render_markdown( - md_file, - is_remote_file=False)), - 200 - ) - # Fallback: Fetch file from server - return (render_template( - "environment.html", - svg_data=None, - rendered_markdown=render_markdown( - "general/environments/environments.md")), - 200) - - -@environments_blueprint.route('/svg-dependency-graph') -def svg_graph(): - directory, file_name, _ = get_file_from_python_search_path( - "wqflask/dependency-graph.svg").partition("dependency-graph.svg") - return send_from_directory(directory, file_name) - - -@links_blueprint.route("/") -def links(): - return render_template( - "links.html", - rendered_markdown=render_markdown("general/links/links.md")), 200 - - -@policies_blueprint.route("/") -def policies(): - return render_template( - "policies.html", - rendered_markdown=render_markdown("general/policies/policies.md")), 200 - - -@facilities_blueprint.route("/") -def facilities(): - return render_template("facilities.html", rendered_markdown=render_markdown("general/help/facilities.md")), 200 - - -@blogs_blueprint.route("/") -def display_blog(blog_path): - return render_template("blogs.html", rendered_markdown=render_markdown(blog_path)) - - -@blogs_blueprint.route("/") -def blogs_list(): - blogs = get_blogs() - - return render_template("blogs_list.html", blogs=blogs) -- cgit v1.2.3 From 2f26b77c7be370dad03e9b8e2ce7f6040ccce528 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 12 Oct 2021 14:19:03 +0300 Subject: Add default setting for REDIS_URL --- etc/default_settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/default_settings.py b/etc/default_settings.py index 023aa53b..651cc55e 100644 --- a/etc/default_settings.py +++ b/etc/default_settings.py @@ -26,6 +26,9 @@ import sys GN_VERSION = open("../etc/VERSION", "r").read() +# Redis +REDIS_URL = "redis://:@localhost:6379/0" + # ---- MySQL SQL_URI = "mysql://gn2:mysql_password@localhost/db_webqtl_s" -- cgit v1.2.3 From eb2e43434c137a070db5943db21d3b92afdd9a91 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 12 Oct 2021 14:19:26 +0300 Subject: Mark `get_resource_info` as deprecated --- wqflask/utility/redis_tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py index de9dde46..e68397ab 100644 --- a/wqflask/utility/redis_tools.py +++ b/wqflask/utility/redis_tools.py @@ -4,6 +4,7 @@ import datetime import redis # used for collections +from deprecated import deprecated from utility.hmac import hmac_creation from utility.logger import getLogger logger = getLogger(__name__) @@ -321,6 +322,7 @@ def get_resource_id(dataset, trait_id=None): return resource_id +@deprecated def get_resource_info(resource_id): resource_info = Redis.hget("resources", resource_id) if resource_info: -- cgit v1.2.3 From 212d19c29a42bd6966965b166cdbb4dd642e5eb4 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 13 Oct 2021 13:05:26 +0300 Subject: Add test-cases for `get_user_membership` --- .../tests/unit/wqflask/test_resource_manager.py | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 wqflask/tests/unit/wqflask/test_resource_manager.py diff --git a/wqflask/tests/unit/wqflask/test_resource_manager.py b/wqflask/tests/unit/wqflask/test_resource_manager.py new file mode 100644 index 00000000..a27f40e1 --- /dev/null +++ b/wqflask/tests/unit/wqflask/test_resource_manager.py @@ -0,0 +1,51 @@ +"""Test cases for wqflask/resource_manager.py""" +import unittest + +from unittest import mock +from wqflask.resource_manager import get_user_membership + + +class TestGetUserMembership(unittest.TestCase): + """Test cases for `get_user_membership`""" + + def setUp(self): + conn = mock.MagicMock() + conn.hgetall.return_value = { + '7fa95d07-0e2d-4bc5-b47c-448fdc1260b2': ( + '{"name": "editors", ' + '"admins": ["8ad942fe-490d-453e-bd37-56f252e41604", "rand"], ' + '"members": ["8ad942fe-490d-453e-bd37-56f252e41603", ' + '"rand"], ' + '"changed_timestamp": "Oct 06 2021 06:39PM", ' + '"created_timestamp": "Oct 06 2021 06:39PM"}')} + self.conn = conn + + def test_user_is_group_member_only(self): + """Test that a user is only a group member""" + self.assertEqual( + get_user_membership( + conn=self.conn, + user_id="8ad942fe-490d-453e-bd37-56f252e41603", + group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), + {"member": True, + "admin": False}) + + def test_user_is_group_admin_only(self): + """Test that a user is a group admin only""" + self.assertEqual( + get_user_membership( + conn=self.conn, + user_id="8ad942fe-490d-453e-bd37-56f252e41604", + group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), + {"member": False, + "admin": True}) + + def test_user_is_both_group_member_and_admin(self): + """Test that a user is both an admin and member of a group""" + self.assertEqual( + get_user_membership( + conn=self.conn, + user_id="rand", + group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), + {"member": True, + "admin": True}) -- cgit v1.2.3 From f42d0d5b04f2974e6d93155ef7b936653a9d6248 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 13 Oct 2021 13:07:48 +0300 Subject: resource_manager: Delete every route & helper method in this module This module is coupled to `utility.redis_tools` which needs to be deprecated. Also, the routes are created by importing `app` instead of creating a new blueprint. --- wqflask/wqflask/resource_manager.py | 130 ------------------------------------ 1 file changed, 130 deletions(-) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index c54dd0b3..0b1805d8 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -1,144 +1,14 @@ import json -from flask import (Flask, g, render_template, url_for, request, make_response, - redirect, flash) -from wqflask import app -from utility.authentication_tools import check_owner_or_admin -from utility.redis_tools import get_resource_info, get_group_info, get_groups_like_unique_column, get_user_id, get_user_by_unique_column, get_users_like_unique_column, add_access_mask, add_resource, change_resource_owner -@app.route("/resources/manage", methods=('GET', 'POST')) -def manage_resource(): - params = request.form if request.form else request.args - if 'resource_id' in request.args: - resource_id = request.args['resource_id'] - admin_status = check_owner_or_admin(resource_id=resource_id) - resource_info = get_resource_info(resource_id) - group_masks = resource_info['group_masks'] - group_masks_with_names = get_group_names(group_masks) - default_mask = resource_info['default_mask']['data'] - owner_id = resource_info['owner_id'] - owner_display_name = None - if owner_id != "none": - try: # ZS: User IDs are sometimes stored in Redis as bytes and sometimes as strings, so this is just to avoid any errors for the time being - owner_id = str.encode(owner_id) - except: - pass - owner_info = get_user_by_unique_column("user_id", owner_id) - if 'name' in owner_info: - owner_display_name = owner_info['full_name'] - elif 'user_name' in owner_info: - owner_display_name = owner_info['user_name'] - elif 'email_address' in owner_info: - owner_display_name = owner_info['email_address'] - return render_template("admin/manage_resource.html", owner_name=owner_display_name, resource_id=resource_id, resource_info=resource_info, default_mask=default_mask, group_masks=group_masks_with_names, admin_status=admin_status) -@app.route("/search_for_users", methods=('POST',)) -def search_for_user(): - params = request.form - user_list = [] - user_list += get_users_like_unique_column("full_name", params['user_name']) - user_list += get_users_like_unique_column( - "email_address", params['user_email']) - return json.dumps(user_list) - - -@app.route("/search_for_groups", methods=('POST',)) -def search_for_groups(): - params = request.form - group_list = [] - group_list += get_groups_like_unique_column("id", params['group_id']) - group_list += get_groups_like_unique_column("name", params['group_name']) - - user_list = [] - user_list += get_users_like_unique_column("full_name", params['user_name']) - user_list += get_users_like_unique_column( - "email_address", params['user_email']) - for user in user_list: - group_list += get_groups_like_unique_column("admins", user['user_id']) - group_list += get_groups_like_unique_column("members", user['user_id']) - - return json.dumps(group_list) - - -@app.route("/resources/change_owner", methods=('POST',)) -def change_owner(): - resource_id = request.form['resource_id'] - if 'new_owner' in request.form: - admin_status = check_owner_or_admin(resource_id=resource_id) - if admin_status == "owner": - new_owner_id = request.form['new_owner'] - change_resource_owner(resource_id, new_owner_id) - flash("The resource's owner has beeen changed.", "alert-info") - return redirect(url_for("manage_resource", resource_id=resource_id)) - else: - flash("You lack the permissions to make this change.", "error") - return redirect(url_for("manage_resource", resource_id=resource_id)) - else: - return render_template("admin/change_resource_owner.html", resource_id=resource_id) - - -@app.route("/resources/change_default_privileges", methods=('POST',)) -def change_default_privileges(): - resource_id = request.form['resource_id'] - admin_status = check_owner_or_admin(resource_id=resource_id) - if admin_status == "owner" or admin_status == "edit-admins": - resource_info = get_resource_info(resource_id) - default_mask = resource_info['default_mask'] - if request.form['open_to_public'] == "True": - default_mask['data'] = 'view' - else: - default_mask['data'] = 'no-access' - resource_info['default_mask'] = default_mask - add_resource(resource_info) - flash("Your changes have been saved.", "alert-info") - return redirect(url_for("manage_resource", resource_id=resource_id)) - else: - return redirect(url_for("no_access_page")) - - -@app.route("/resources/add_group", methods=('POST',)) -def add_group_to_resource(): - resource_id = request.form['resource_id'] admin_status = check_owner_or_admin(resource_id=resource_id) - if admin_status == "owner" or admin_status == "edit-admins" or admin_status == "edit-access": - if 'selected_group' in request.form: - group_id = request.form['selected_group'] - resource_info = get_resource_info(resource_id) - default_privileges = resource_info['default_mask'] - return render_template("admin/set_group_privileges.html", resource_id=resource_id, group_id=group_id, default_privileges=default_privileges) - elif all(key in request.form for key in ('data_privilege', 'metadata_privilege', 'admin_privilege')): - group_id = request.form['group_id'] - group_name = get_group_info(group_id)['name'] - access_mask = { - 'data': request.form['data_privilege'], - 'metadata': request.form['metadata_privilege'], - 'admin': request.form['admin_privilege'] - } - add_access_mask(resource_id, group_id, access_mask) - flash("Privileges have been added for group {}.".format( - group_name), "alert-info") - return redirect(url_for("manage_resource", resource_id=resource_id)) - else: - return render_template("admin/search_for_groups.html", resource_id=resource_id) - else: - return redirect(url_for("no_access_page")) - - -def get_group_names(group_masks): - group_masks_with_names = {} - for group_id, group_mask in list(group_masks.items()): - this_mask = group_mask - group_name = get_group_info(group_id)['name'] - this_mask['name'] = group_name - group_masks_with_names[group_id] = this_mask - - return group_masks_with_names -- cgit v1.2.3 From 026a0f4e46a1909b16433bf1620f1b778fa6b913 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 13 Oct 2021 13:10:40 +0300 Subject: resource_manager: Add `get_user_membership` method --- wqflask/wqflask/resource_manager.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 0b1805d8..dfd853b2 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -1,8 +1,10 @@ +import redis import json +from typing import Dict @@ -11,4 +13,31 @@ import json +def get_user_membership(conn: redis.Redis, user_id: str, + group_id: str) -> Dict: + """Return a dictionary that indicates whether the `user_id` is a + member or admin of `group_id`. + + Args: + - conn: a Redis Connection with the responses decoded. + - user_id: a user's unique id + e.g. '8ad942fe-490d-453e-bd37-56f252e41603' + - group_id: a group's unique id + e.g. '7fa95d07-0e2d-4bc5-b47c-448fdc1260b2' + + Returns: + A dict indicating whether the user is an admin or a member of + the group: {"member": True, "admin": False} + + """ + results = {"member": False, "admin": False} + for key, value in conn.hgetall('groups').items(): + if key == group_id: + group_info = json.loads(value) + if user_id in group_info.get("admins"): + results["admin"] = True + if user_id in group_info.get("members"): + results["member"] = True + break + return results admin_status = check_owner_or_admin(resource_id=resource_id) -- cgit v1.2.3 From 6195ad475cddce2007b125b856829cbb41defd30 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 15 Oct 2021 08:49:23 +0300 Subject: resource_manager: Add OrderedEnum Borrowed from: --- wqflask/wqflask/resource_manager.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index dfd853b2..330600ed 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -1,13 +1,26 @@ import redis import json +import functools +from enum import Enum from typing import Dict +@functools.total_ordering +class OrderedEnum(Enum): + @classmethod + @functools.lru_cache(None) + def _member_list(cls): + return list(cls) + def __lt__(self, other): + if self.__class__ is other.__class__: + member_list = self.__class__._member_list() + return member_list.index(self) < member_list.index(other) + return NotImplemented -- cgit v1.2.3 From 3a1db0b113a6ab32926cf6070e7cc89459f83041 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 15 Oct 2021 08:50:26 +0300 Subject: Add immutable data structures for admin and data access roles --- wqflask/wqflask/resource_manager.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 330600ed..7f4718f0 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -2,7 +2,7 @@ import redis import json import functools -from enum import Enum +from enum import Enum, unique @@ -23,8 +23,18 @@ class OrderedEnum(Enum): return NotImplemented +@unique +class DataRole(OrderedEnum): + NO_ACCESS = "no-access" + VIEW = "view" + EDIT = "edit" +@unique +class AdminRole(OrderedEnum): + NOT_ADMIN = "not-admin" + EDIT_ACCESS = "edit-access" + EDIT_ADMINS = "edit-admins" def get_user_membership(conn: redis.Redis, user_id: str, group_id: str) -> Dict: -- cgit v1.2.3 From 610335cb3c3030cf39e91ad3232d468b388fc340 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 15 Oct 2021 08:51:46 +0300 Subject: Get a users access mask If a user has several access roles, select the highest role. --- .../tests/unit/wqflask/test_resource_manager.py | 59 ++++++++++++++++++++++ wqflask/wqflask/resource_manager.py | 48 ++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/wqflask/tests/unit/wqflask/test_resource_manager.py b/wqflask/tests/unit/wqflask/test_resource_manager.py index a27f40e1..b0b7e6a3 100644 --- a/wqflask/tests/unit/wqflask/test_resource_manager.py +++ b/wqflask/tests/unit/wqflask/test_resource_manager.py @@ -3,6 +3,9 @@ import unittest from unittest import mock from wqflask.resource_manager import get_user_membership +from wqflask.resource_manager import get_user_access_roles +from wqflask.resource_manager import DataRole +from wqflask.resource_manager import AdminRole class TestGetUserMembership(unittest.TestCase): @@ -49,3 +52,59 @@ class TestGetUserMembership(unittest.TestCase): group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), {"member": True, "admin": True}) + + +class TestCheckUserAccessRole(unittest.TestCase): + """Test cases for `get_user_access_roles`""" + + def setUp(self): + conn = mock.MagicMock() + conn.hget.return_value = ( + '{"owner_id": "8ad942fe-490d-453e-bd37", ' + '"default_mask": {"data": "no-access", ' + '"metadata": "no-access", ' + '"admin": "not-admin"}, ' + '"group_masks": ' + '{"7fa95d07-0e2d-4bc5-b47c-448fdc1260b2": ' + '{"metadata": "edit", "data": "edit"}}, ' + '"name": "_14329", "' + 'data": {"dataset": 1, "trait": 14329}, ' + '"type": "dataset-publish"}') + + conn.hgetall.return_value = { + '7fa95d07-0e2d-4bc5-b47c-448fdc1260b2': ( + '{"name": "editors", ' + '"admins": ["8ad942fe-490d-453e-bd37-56f252e41604", "rand"], ' + '"members": ["8ad942fe-490d-453e-bd37-56f252e41603", ' + '"rand"], ' + '"changed_timestamp": "Oct 06 2021 06:39PM", ' + '"created_timestamp": "Oct 06 2021 06:39PM"}')} + self.conn = conn + + def test_get_user_access_when_owner(self): + """Test that the right access roles are set""" + self.assertEqual(get_user_access_roles( + conn=self.conn, + resource_id="", # Can be anything + user_id="8ad942fe-490d-453e-bd37"), + {"data": DataRole.EDIT, + "metadata": DataRole.EDIT, + "admin": AdminRole.EDIT_ACCESS}) + + def test_get_user_access_default_mask(self): + self.assertEqual(get_user_access_roles( + conn=self.conn, + resource_id="", # Can be anything + user_id=""), + {"data": DataRole.NO_ACCESS, + "metadata": DataRole.NO_ACCESS, + "admin": AdminRole.NOT_ADMIN}) + + def test_get_user_access_group_mask(self): + self.assertEqual(get_user_access_roles( + conn=self.conn, + resource_id="", # Can be anything + user_id="8ad942fe-490d-453e-bd37-56f252e41603"), + {"data": DataRole.EDIT, + "metadata": DataRole.EDIT, + "admin": AdminRole.NOT_ADMIN}) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 7f4718f0..a3a94f9e 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -63,4 +63,52 @@ def get_user_membership(conn: redis.Redis, user_id: str, results["member"] = True break return results + + +def get_user_access_roles(conn: redis.Redis, + resource_id: str, + user_id: str) -> Dict: + """Get the highest access roles for a given user + + Args: + - conn: A redis connection with `decoded_responses == True`. + - resource_id: The unique id of a given resource. + + Returns: + A dict indicating the highest access role the user has. + """ + # This is the default access role + access_role = { + "data": [DataRole.NO_ACCESS], + "metadata": [DataRole.NO_ACCESS], + "admin": [AdminRole.NOT_ADMIN], + } + resource_info = json.loads(conn.hget('resources', resource_id)) + + # Check the resource's default mask + if default_mask := resource_info.get("default_mask"): + access_role["data"].append(DataRole(default_mask.get("data"))) + access_role["metadata"].append(DataRole(default_mask.get("metadata"))) + access_role["admin"].append(AdminRole(default_mask.get("admin"))) + + # Then check if the user is the owner Check with Zach and Rob if + # the owner, be default should, as the lowest access_roles, edit + # access + if resource_info.get("owner_id") == user_id: + access_role["data"].append(DataRole.EDIT) + access_role["metadata"].append(DataRole.EDIT) + access_role["admin"].append(AdminRole.EDIT_ACCESS) + + # Check the group mask. If the user is in that group mask, use the + # access roles for that group + if group_masks := resource_info.get("group_masks"): + for group_id, roles in group_masks.items(): + user_membership = get_user_membership(conn=conn, + user_id=user_id, + group_id=group_id) + if any(user_membership.values()): + access_role["data"].append(DataRole(roles.get("data"))) + access_role["metadata"].append( + DataRole(roles.get("metadata"))) + return {k: max(v) for k, v in access_role.items()} admin_status = check_owner_or_admin(resource_id=resource_id) -- cgit v1.2.3 From da86bc79798c05ec469d76f375741f306213e4d0 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 15 Oct 2021 13:01:38 +0300 Subject: Replace "resource_id" with "resource_info" dict This avoids calling Redis twice when fetching metadata about the resource. --- .../tests/unit/wqflask/test_resource_manager.py | 36 +++++++++++++--------- wqflask/wqflask/resource_manager.py | 10 +++--- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/wqflask/tests/unit/wqflask/test_resource_manager.py b/wqflask/tests/unit/wqflask/test_resource_manager.py index b0b7e6a3..9d5aaf0b 100644 --- a/wqflask/tests/unit/wqflask/test_resource_manager.py +++ b/wqflask/tests/unit/wqflask/test_resource_manager.py @@ -58,18 +58,26 @@ class TestCheckUserAccessRole(unittest.TestCase): """Test cases for `get_user_access_roles`""" def setUp(self): + self.resource_info = { + "owner_id": "8ad942fe-490d-453e-bd37", + "default_mask": { + "data": "no-access", + "metadata": "no-access", + "admin": "not-admin", + }, + "group_masks": { + "7fa95d07-0e2d-4bc5-b47c-448fdc1260b2": { + "metadata": "edit", + "data": "edit", + }}, + "name": "_14329", + "data": { + "dataset": 1, + "trait": 14329, + }, + "type": "dataset-publish", + } conn = mock.MagicMock() - conn.hget.return_value = ( - '{"owner_id": "8ad942fe-490d-453e-bd37", ' - '"default_mask": {"data": "no-access", ' - '"metadata": "no-access", ' - '"admin": "not-admin"}, ' - '"group_masks": ' - '{"7fa95d07-0e2d-4bc5-b47c-448fdc1260b2": ' - '{"metadata": "edit", "data": "edit"}}, ' - '"name": "_14329", "' - 'data": {"dataset": 1, "trait": 14329}, ' - '"type": "dataset-publish"}') conn.hgetall.return_value = { '7fa95d07-0e2d-4bc5-b47c-448fdc1260b2': ( @@ -85,7 +93,7 @@ class TestCheckUserAccessRole(unittest.TestCase): """Test that the right access roles are set""" self.assertEqual(get_user_access_roles( conn=self.conn, - resource_id="", # Can be anything + resource_info=self.resource_info, user_id="8ad942fe-490d-453e-bd37"), {"data": DataRole.EDIT, "metadata": DataRole.EDIT, @@ -94,7 +102,7 @@ class TestCheckUserAccessRole(unittest.TestCase): def test_get_user_access_default_mask(self): self.assertEqual(get_user_access_roles( conn=self.conn, - resource_id="", # Can be anything + resource_info=self.resource_info, user_id=""), {"data": DataRole.NO_ACCESS, "metadata": DataRole.NO_ACCESS, @@ -103,7 +111,7 @@ class TestCheckUserAccessRole(unittest.TestCase): def test_get_user_access_group_mask(self): self.assertEqual(get_user_access_roles( conn=self.conn, - resource_id="", # Can be anything + resource_info=self.resource_info, user_id="8ad942fe-490d-453e-bd37-56f252e41603"), {"data": DataRole.EDIT, "metadata": DataRole.EDIT, diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index a3a94f9e..57b99296 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -66,16 +66,19 @@ def get_user_membership(conn: redis.Redis, user_id: str, def get_user_access_roles(conn: redis.Redis, - resource_id: str, + resource_info: Dict, user_id: str) -> Dict: """Get the highest access roles for a given user Args: - - conn: A redis connection with `decoded_responses == True`. - - resource_id: The unique id of a given resource. + - conn: A redis connection with the responses decoded. + - resource_info: A dict containing details(metadata) about a + given resource. + - user_id: The unique id of a given user. Returns: A dict indicating the highest access role the user has. + """ # This is the default access role access_role = { @@ -83,7 +86,6 @@ def get_user_access_roles(conn: redis.Redis, "metadata": [DataRole.NO_ACCESS], "admin": [AdminRole.NOT_ADMIN], } - resource_info = json.loads(conn.hget('resources', resource_id)) # Check the resource's default mask if default_mask := resource_info.get("default_mask"): -- cgit v1.2.3 From 8dd3457b20b5ce96cf7e0f5029e3541d57ca116d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 15 Oct 2021 15:47:21 +0300 Subject: Remove "utility.hmac.hmac_creation" which causes circular imports Hacky but re-implement `hmac_creation` as `create_hmac` --- wqflask/wqflask/decorators.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py index c19e1aef..5930e7ec 100644 --- a/wqflask/wqflask/decorators.py +++ b/wqflask/wqflask/decorators.py @@ -1,26 +1,34 @@ """This module contains gn2 decorators""" -from flask import g +import hashlib +import hmac +from flask import current_app, g from typing import Dict from functools import wraps -from utility.hmac import hmac_creation -from utility.tools import GN_PROXY_URL import json import requests +def create_hmac(data: str, secret: str) -> str: + return hmac.new(bytearray(secret, "latin-1"), + bytearray(data, "utf-8"), + hashlib.sha1).hexdigest[:20] def edit_access_required(f): """Use this for endpoints where admins are required""" @wraps(f) def wrap(*args, **kwargs): resource_id: str = "" if kwargs.get("inbredset_id"): # data type: dataset-publish - resource_id = hmac_creation("dataset-publish:" - f"{kwargs.get('inbredset_id')}:" - f"{kwargs.get('name')}") + resource_id = create_hmac( + data=("dataset-publish:" + f"{kwargs.get('inbredset_id')}:" + f"{kwargs.get('name')}"), + secret=current_app.config.get("SECRET_HMAC_CODE")) if kwargs.get("dataset_name"): # data type: dataset-probe - resource_id = hmac_creation("dataset-probeset:" - f"{kwargs.get('dataset_name')}") + resource_id = create_hmac( + data=("dataset-probeset:" + f"{kwargs.get('dataset_name')}"), + secret=current_app.config.get("SECRET_HMAC_CODE")) response: Dict = {} try: _user_id = g.user_session.record.get(b"user_id", -- cgit v1.2.3 From 712318f7ab5e676b229ba0d479be09e9f92b9568 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 15 Oct 2021 15:48:46 +0300 Subject: decorators: Add `@login_required` decorator --- wqflask/wqflask/decorators.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py index 5930e7ec..cc69902e 100644 --- a/wqflask/wqflask/decorators.py +++ b/wqflask/wqflask/decorators.py @@ -1,6 +1,8 @@ """This module contains gn2 decorators""" import hashlib import hmac +import redis + from flask import current_app, g from typing import Dict from functools import wraps @@ -13,6 +15,23 @@ def create_hmac(data: str, secret: str) -> str: return hmac.new(bytearray(secret, "latin-1"), bytearray(data, "utf-8"), hashlib.sha1).hexdigest[:20] + + +def login_required(f): + """Use this for endpoints where login is required""" + @wraps(f) + def wrap(*args, **kwargs): + user_id = (g.user_session.record.get(b"user_id", + b"").decode("utf-8") or + g.user_session.record.get("user_id", "")) + redis_conn = redis.from_url(current_app.config["REDIS_URL"], + decode_responses=True) + if not redis_conn.hget("users", user_id): + return "You need to be logged in!", 401 + return f(*args, **kwargs) + return wrap + + def edit_access_required(f): """Use this for endpoints where admins are required""" @wraps(f) -- cgit v1.2.3 From 217fc72eee688aed4409120d964cb2140ce11961 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 15 Oct 2021 15:53:07 +0300 Subject: utility.hmac: Label "hmac_creation" as deprecated This function is coupled to "wqflask.app", therefore requiring it's import at the module level. This may lead circular importation issues when working with blueprints. --- wqflask/utility/hmac.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wqflask/utility/hmac.py b/wqflask/utility/hmac.py index 29891677..d6e515ed 100644 --- a/wqflask/utility/hmac.py +++ b/wqflask/utility/hmac.py @@ -1,11 +1,14 @@ import hmac import hashlib +from deprecated import deprecated from flask import url_for from wqflask import app +@deprecated("This function leads to circular imports. " + "If possible use wqflask.decorators.create_hmac instead.") def hmac_creation(stringy): """Helper function to create the actual hmac""" -- cgit v1.2.3 From 13c5727b90d2b5e20e69a334c1cb78fd258cf3e8 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 18 Oct 2021 09:45:49 +0300 Subject: manage_resource.html: Remove "flash" --- wqflask/wqflask/templates/admin/manage_resource.html | 1 - 1 file changed, 1 deletion(-) diff --git a/wqflask/wqflask/templates/admin/manage_resource.html b/wqflask/wqflask/templates/admin/manage_resource.html index 33a37594..f69e6799 100644 --- a/wqflask/wqflask/templates/admin/manage_resource.html +++ b/wqflask/wqflask/templates/admin/manage_resource.html @@ -7,7 +7,6 @@ {% block content %}
    - {{ flash_me() }}

  • Loading...
    + @@ -85,9 +86,10 @@ - {% for key, value in group_masks.items() %} + {% for key, value in resource_info.get('group_masks').items() %} + -- cgit v1.2.3 From 997fb64394a7fa49d154820bbfdafcdfcc05f32e Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 18 Oct 2021 11:52:30 +0300 Subject: Remove dead line --- wqflask/wqflask/resource_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 57b99296..03b45822 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -113,4 +113,3 @@ def get_user_access_roles(conn: redis.Redis, access_role["metadata"].append( DataRole(roles.get("metadata"))) return {k: max(v) for k, v in access_role.items()} - admin_status = check_owner_or_admin(resource_id=resource_id) -- cgit v1.2.3 From 8eedaab544d5bd1ab667bdd88d055b671bbee1d0 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 18 Oct 2021 11:52:55 +0300 Subject: wqflask: resource_manager: Add `add_extra_resource_metadata` method --- wqflask/wqflask/resource_manager.py | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 03b45822..16bab250 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -113,3 +113,42 @@ def get_user_access_roles(conn: redis.Redis, access_role["metadata"].append( DataRole(roles.get("metadata"))) return {k: max(v) for k, v in access_role.items()} + + +def add_extra_resource_metadata(conn: redis.Redis, resource: Dict) -> Dict: + """If resource['owner_id'] exists, add metadata about that user. Also, +if the resource contains group masks, add the group name into the +resource dict. Note that resource['owner_id'] and the group masks are +unique identifiers so they aren't human readable names. + + Args: + - conn: A redis connection with the responses decoded. + - resource: A dict containing details(metadata) about a + given resource. + + Returns: + An embellished dictionary with the human readable names of the + group masks and the owner id if it was set. + + """ + # Embellish the resource information with owner details if the + # owner is set + if (owner_id := resource.get("owner_id", "none").lower()) == "none": + resource["owner_id"] = None + resource["owner_details"] = None + else: + user_details = json.loads(conn.hget("users", owner_id)) + resource["owner_details"] = { + "email_address": user_details.get("email_address"), + "full_name": user_details.get("full_name"), + "organization": user_details.get("organization"), + } + + # Embellish the resources information with the group name if the + # group masks are present + if groups := resource.get('group_masks', {}): + for group_id in groups.keys(): + resource['group_masks'][group_id]["group_name"] = ( + json.loads(conn.hget("groups", group_id)).get('name')) + return resource + -- cgit v1.2.3 From ff2813b8c0522d780ffc8bc44692a749de9f45e6 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 18 Oct 2021 11:54:37 +0300 Subject: Add "GET /resource-management/resources/" --- wqflask/wqflask/__init__.py | 5 +++++ wqflask/wqflask/resource_manager.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/__init__.py b/wqflask/wqflask/__init__.py index 5758b13e..5b2d05d1 100644 --- a/wqflask/wqflask/__init__.py +++ b/wqflask/wqflask/__init__.py @@ -8,6 +8,9 @@ from flask import Flask from typing import Tuple from urllib.parse import urlparse from utility import formatting + +from wqflask.resource_manager import resource_management + from wqflask.api.markdown import glossary_blueprint from wqflask.api.markdown import references_blueprint from wqflask.api.markdown import links_blueprint @@ -55,6 +58,8 @@ app.register_blueprint(facilities_blueprint, url_prefix="/facilities") app.register_blueprint(blogs_blueprint, url_prefix="/blogs") app.register_blueprint(news_blueprint, url_prefix="/news") +app.register_blueprint(resource_management, url_prefix="/resource-management") + @app.before_request def before_request(): diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 16bab250..9665ebb0 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -4,10 +4,15 @@ import functools from enum import Enum, unique - +from flask import Blueprint +from flask import current_app +from flask import g +from flask import render_template from typing import Dict +from wqflask.decorators import login_required + @functools.total_ordering class OrderedEnum(Enum): @@ -36,6 +41,10 @@ class AdminRole(OrderedEnum): EDIT_ACCESS = "edit-access" EDIT_ADMINS = "edit-admins" + +resource_management = Blueprint('resource_management', __name__) + + def get_user_membership(conn: redis.Redis, user_id: str, group_id: str) -> Dict: """Return a dictionary that indicates whether the `user_id` is a @@ -152,3 +161,28 @@ unique identifiers so they aren't human readable names. json.loads(conn.hget("groups", group_id)).get('name')) return resource + +@resource_management.route("/resources/") +@login_required +def manage_resource(resource_id: str): + user_id = (g.user_session.record.get(b"user_id", + b"").decode("utf-8") or + g.user_session.record.get("user_id", "")) + redis_conn = redis.from_url( + current_app.config["REDIS_URL"], + decode_responses=True) + + # Abort early if the resource can't be found + if not (resource := redis_conn.hget("resources", resource_id)): + return f"Resource: {resource_id} Not Found!", 401 + + return render_template( + "admin/manage_resource.html", + resource_info=(embellished_resource:=add_extra_resource_metadata( + conn=redis_conn, + resource=json.loads(resource))), + access_role=get_user_access_roles( + conn=redis_conn, + resource_info=embellished_resource, + user_id=user_id), + DataRole=DataRole, AdminRole=AdminRole) -- cgit v1.2.3 From 8ba81937fec81c7e9790db422a52d99f4234a194 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 18 Oct 2021 12:02:51 +0300 Subject: authentication_tools: Mark `check_owner_or_admin` as deprecated Use the new auth proxy tools instead. --- wqflask/utility/authentication_tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wqflask/utility/authentication_tools.py b/wqflask/utility/authentication_tools.py index afea69e1..c4801c8c 100644 --- a/wqflask/utility/authentication_tools.py +++ b/wqflask/utility/authentication_tools.py @@ -1,6 +1,7 @@ import json import requests +from deprecated import deprecated from flask import g from base import webqtlConfig @@ -126,6 +127,7 @@ def check_owner(dataset=None, trait_id=None, resource_id=None): return False +@deprecated def check_owner_or_admin(dataset=None, trait_id=None, resource_id=None): if not resource_id: if dataset.type == "Temp": -- cgit v1.2.3 From 9f43e0231698f8fcd472ad736b4276c6ad6eeb6d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 18 Oct 2021 12:08:45 +0300 Subject: manage_resource.html: Remove unused stylesheets --- wqflask/wqflask/templates/admin/manage_resource.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wqflask/wqflask/templates/admin/manage_resource.html b/wqflask/wqflask/templates/admin/manage_resource.html index 91e23e56..f4bcc163 100644 --- a/wqflask/wqflask/templates/admin/manage_resource.html +++ b/wqflask/wqflask/templates/admin/manage_resource.html @@ -1,9 +1,5 @@ {% extends "base.html" %} {% block title %}Resource Manager{% endblock %} -{% block css %} - - -{% endblock %} {% block content %}
    -- cgit v1.2.3 From 948a0b7e3572c39aefd1b18083afc174dcf4a164 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 18 Oct 2021 12:09:05 +0300 Subject: manage_resource: Add missing bootstrap "table" class --- wqflask/wqflask/templates/admin/manage_resource.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/admin/manage_resource.html b/wqflask/wqflask/templates/admin/manage_resource.html index f4bcc163..a970679d 100644 --- a/wqflask/wqflask/templates/admin/manage_resource.html +++ b/wqflask/wqflask/templates/admin/manage_resource.html @@ -71,7 +71,7 @@ {% if resource_info.get('group_masks', [])|length > 0 %}

    Current Group Permissions


    -
    Id Name Data Metadata
    {{ value.name }}{{ value.group_name}} {{ value.data }} {{ value.metadata }} {{ value.admin }}
    +
    -- cgit v1.2.3 From f3c17102d2d8fc6be35be237c299a22f0e16a920 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 18 Oct 2021 12:09:32 +0300 Subject: manage_resource: Remove column "Admin" from group permissions table A group does not have "admin" privileges. Instead, a user can have admin privileges over a particular group. --- wqflask/wqflask/templates/admin/manage_resource.html | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/wqflask/wqflask/templates/admin/manage_resource.html b/wqflask/wqflask/templates/admin/manage_resource.html index a970679d..a2ed3538 100644 --- a/wqflask/wqflask/templates/admin/manage_resource.html +++ b/wqflask/wqflask/templates/admin/manage_resource.html @@ -78,17 +78,15 @@ - {% for key, value in resource_info.get('group_masks').items() %} - + - {% endfor %} @@ -101,12 +99,9 @@ - - {% endblock %} - {% block js %} -- cgit v1.2.3 From 198add36d970c005320700a0ed01e8632e8f6922 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 10:33:03 +0300 Subject: manage_resources.html: Clean up html --- .../wqflask/templates/admin/manage_resource.html | 33 +++++++++------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/wqflask/wqflask/templates/admin/manage_resource.html b/wqflask/wqflask/templates/admin/manage_resource.html index a2ed3538..80dfc8ee 100644 --- a/wqflask/wqflask/templates/admin/manage_resource.html +++ b/wqflask/wqflask/templates/admin/manage_resource.html @@ -6,29 +6,24 @@ {% set DATA_ACCESS = access_role.get('data') %} {% set METADATA_ACCESS = access_role.get('metadata') %} {% set ADMIN_STATUS = access_role.get('admin') %} - - - + {% if user_details.get('organization') %} +

    + Organization: {{ user_details.get('organization')}} +

    + {% endif %} + {% if DATA_ACCESS > DataRole.VIEW and ADMIN_STATUS > AdminRole.NOT_ADMIN %} + + {% endif %} + {% endif %}
    -- cgit v1.2.3 From 150a527924f071a06bafc835e03b12eaa12b5768 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 11:05:17 +0300 Subject: wqflask: decorators: Fix type when calling hexdigest() --- wqflask/wqflask/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py index cc69902e..b1141fb7 100644 --- a/wqflask/wqflask/decorators.py +++ b/wqflask/wqflask/decorators.py @@ -14,7 +14,7 @@ import requests def create_hmac(data: str, secret: str) -> str: return hmac.new(bytearray(secret, "latin-1"), bytearray(data, "utf-8"), - hashlib.sha1).hexdigest[:20] + hashlib.sha1).hexdigest()[:20] def login_required(f): -- cgit v1.2.3 From 6bdf173ccb79e2e21cfc4e0d96377dae3bd1e6c1 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 14:55:03 +0300 Subject: scripts: group.py: Add shebang --- scripts/authentication/group.py | 1 + 1 file changed, 1 insertion(+) mode change 100644 => 100755 scripts/authentication/group.py diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py old mode 100644 new mode 100755 index c8c2caad..460a6329 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """A script for adding users to a specific group. Example: -- cgit v1.2.3 From 467723891d7c153c365322ea5812a714186a478d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 14:57:11 +0300 Subject: scripts: group.py: Add extra optional arg to specify uid --- scripts/authentication/group.py | 118 ++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 47 deletions(-) diff --git a/scripts/authentication/group.py b/scripts/authentication/group.py index 460a6329..08652454 100755 --- a/scripts/authentication/group.py +++ b/scripts/authentication/group.py @@ -7,22 +7,40 @@ Assuming there are no groups and 'test@bonfacemunyoki.com' does not exist in Redis: .. code-block:: bash - python group.py -g "editors" -m "test@bonfacemunyoki.com" + python group.py -n "editors" \ + -m "me@bonfacemunyoki.com" results in:: - Successfully created the group: 'editors' - Data: '{"admins": [], "members": []}' + Successfully created the group: 'editors' + `HGET groups 0360449f-5a96-4940-8522-b22d62085da9`: {'name': 'editors', 'admins': [], 'members': ['8ad942fe-490d-453e-bd37-56f252e41603'], 'changed_timestamp': 'Oct 19 2021 09:34AM', 'created_timestamp': 'Oct 19 2021 09:34AM'} + -If 'me@bonfacemunyoki.com' exists in 'users' in Redis and we run: +Assuming we have a group's unique id: .. code-block:: bash - python group.py -g "editors" -m "me@bonfacemunyoki.com" + python group.py -n "editors" \ + -m "me@bonfacemunyoki.com" \ + -g "8ad942fe-490d-453e-bd37-56f252e41603" now results in:: - No new group was created. - Updated Data: {'admins': [], 'members': ['me@bonfacemunyoki.com']} + Successfully created the group: 'editors' + `HGET groups 8ad942fe-490d-453e-bd37-56f252e41603`: {'name': 'editors', 'admins': [], 'members': ['8ad942fe-490d-453e-bd37-56f252e41603'], 'changed_timestamp': 'Oct 19 2021 09:38AM', 'created_timestamp': 'Oct 19 2021 09:38AM'} + +If 'me@bonfacemunyoki.com' exists in 'users' in Redis for the above +command and we run: + +.. code-block:: bash + python group.py -n "editors" \ + -m "me@bonfacemunyoki.com" \ + -g "8ad942fe-490d-453e-bd37-56f252e41603" + +now results in:: + + No new group was created. + `HGET groups 8ad942fe-490d-453e-bd37-56f252e41603`: {'name': 'editors', 'admins': [], 'members': ['8ad942fe-490d-453e-bd37-56f252e41603'], 'changed_timestamp': 'Oct 19 2021 09:40AM'} + """ @@ -37,44 +55,48 @@ from typing import Dict, Optional, Set def create_group_data(users: Dict, target_group: str, members: Optional[str] = None, - admins: Optional[str] = None) -> Dict: - """Return a dictionary that contains the following keys: "key", - "field", and "value" that can be used in a redis hash as follows: - HSET key field value + admins: Optional[str] = None, + group_id: Optional[str] = None) -> Dict: + """Create group data which is isomorphic to a redis HSET i.e.: KEY, + FIELD and VALUE. If the group_id is not specified, a unique hash + will be generated. The "field" return value is a unique-id that is used to distinguish the groups. - Parameters: - - - `users`: a list of users for example: - - {'8ad942fe-490d-453e-bd37-56f252e41603': - '{"email_address": "me@test.com", - "full_name": "John Doe", - "organization": "Genenetwork", - "password": {"algorithm": "pbkdf2", - "hashfunc": "sha256", - "salt": "gJrd1HnPSSCmzB5veMPaVk2ozzDlS1Z7Ggcyl1+pciA=", - "iterations": 100000, "keylength": 32, - "created_timestamp": "2021-09-22T11:32:44.971912", - "password": "edcdaa60e84526c6"}, - "user_id": "8ad942fe", "confirmed": 1, - "registration_info": { - "timestamp": "2021-09-22T11:32:45.028833", - "ip_address": "127.0.0.1", - "user_agent": "Mozilla/5.0"}}'} - - - `target_group`: the group name that will be stored inside the - "groups" hash in Redis. - - - `members`: a comma-separated list of values that contain members - of the `target_group` e.g. "me@test1.com, me@test2.com, - me@test3.com" - - - `admins`: a comma-separated list of values that contain - administrators of the `target_group` e.g. "me@test1.com, - me@test2.com, me@test3.com" + Args: + - users: a list of users for example: + {'8ad942fe-490d-453e-bd37-56f252e41603': + '{"email_address": "me@test.com", + "full_name": "John Doe", + "organization": "Genenetwork", + "password": {"algorithm": "pbkdf2", + "hashfunc": "sha256", + "salt": "gJrd1HnPSSCmzB5veMPaVk2ozzDlS1Z7Ggcyl1+pciA=", + "iterations": 100000, "keylength": 32, + "created_timestamp": "2021-09-22T11:32:44.971912", + "password": "edcdaa60e84526c6"}, + "user_id": "8ad942fe", "confirmed": 1, + "registration_info": { + "timestamp": "2021-09-22T11:32:45.028833", + "ip_address": "127.0.0.1", + "user_agent": "Mozilla/5.0"}}'} + + - target_group: the group name that will be stored inside the + "groups" hash in Redis. + - members: an optional comma-separated list of values that + contain members of the `target_group` e.g. "me@test1.com, + me@test2.com, me@test3.com" + - admins: an optional comma-separated list of values that + contain administrators of the `target_group` + e.g. "me@test1.com, me@test2.com, me@test3.com" + - group_id: an optional unique identifier for a group. If not + set, a unique value will be auto-generated. + + Returns: + A dictionary that contains the following keys: "key", "field", + and "value" that can be used in a redis hash as follows: HSET key + field value """ # Emails @@ -96,7 +118,7 @@ def create_group_data(users: Dict, target_group: str, timestamp: str = datetime.datetime.utcnow().strftime('%b %d %Y %I:%M%p') return {"key": "groups", - "field": str(uuid.uuid4()), + "field": (group_id or str(uuid.uuid4())), "value": json.dumps({ "name": target_group, "admins": list(admin_ids), @@ -108,8 +130,10 @@ def create_group_data(users: Dict, target_group: str, if __name__ == "__main__": # Initialising the parser CLI arguments parser = argparse.ArgumentParser() - parser.add_argument("-g", "--group-name", + parser.add_argument("-n", "--group-name", help="This is the name of the GROUP mask") + parser.add_argument("-g", "--group-id", + help="[Optional] This is the name of the GROUP mask") parser.add_argument("-m", "--members", help="Members of the GROUP mask") parser.add_argument("-a", "--admins", @@ -133,7 +157,8 @@ if __name__ == "__main__": users=USERS, target_group=args.group_name, members=members, - admins=admins) + admins=admins, + group_id=args.group_id) if not REDIS_CONN.hget("groups", data.get("field")): updated_data = json.loads(data["value"]) @@ -144,11 +169,10 @@ if __name__ == "__main__": created_p = REDIS_CONN.hset(data.get("key", ""), data.get("field", ""), data.get("value", "")) - groups = json.loads(REDIS_CONN.hget("groups", data.get("field"))) # type: ignore if created_p: exit(f"\nSuccessfully created the group: '{args.group_name}'\n" - f"`HGETALL groups {args.group_name}`: {groups}\n") + f"`HGET groups {data.get('field')}`: {groups}\n") exit("\nNo new group was created.\n" - f"`HGETALL groups {args.group_name}`: {groups}\n") + f"`HGET groups {data.get('field')}`: {groups}\n") -- cgit v1.2.3 From 5d6559cdc0f9251a90b351bf440b930a000f8354 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 16:17:32 +0300 Subject: scripts: resource.py: Add shebang --- scripts/authentication/resource.py | 1 + 1 file changed, 1 insertion(+) mode change 100644 => 100755 scripts/authentication/resource.py diff --git a/scripts/authentication/resource.py b/scripts/authentication/resource.py old mode 100644 new mode 100755 index 1a2bcd8a..1f5f31d8 --- a/scripts/authentication/resource.py +++ b/scripts/authentication/resource.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """A script that: - Optionally restores data from a json file. -- cgit v1.2.3 From 3c074638ca0b5e175e2dbb93aaf3bf21e8af8151 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 16:39:32 +0300 Subject: Move access_roles enums to it's own module --- wqflask/wqflask/access_roles.py | 30 ++++++++++++++++++++++++++++++ wqflask/wqflask/resource_manager.py | 32 ++------------------------------ 2 files changed, 32 insertions(+), 30 deletions(-) create mode 100644 wqflask/wqflask/access_roles.py diff --git a/wqflask/wqflask/access_roles.py b/wqflask/wqflask/access_roles.py new file mode 100644 index 00000000..6cffbc81 --- /dev/null +++ b/wqflask/wqflask/access_roles.py @@ -0,0 +1,30 @@ +import functools +from enum import Enum, unique + + +@functools.total_ordering +class OrderedEnum(Enum): + @classmethod + @functools.lru_cache(None) + def _member_list(cls): + return list(cls) + + def __lt__(self, other): + if self.__class__ is other.__class__: + member_list = self.__class__._member_list() + return member_list.index(self) < member_list.index(other) + return NotImplemented + + +@unique +class DataRole(OrderedEnum): + NO_ACCESS = "no-access" + VIEW = "view" + EDIT = "edit" + + +@unique +class AdminRole(OrderedEnum): + NOT_ADMIN = "not-admin" + EDIT_ACCESS = "edit-access" + EDIT_ADMINS = "edit-admins" diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 9665ebb0..4e160bb8 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -1,8 +1,6 @@ import redis import json -import functools -from enum import Enum, unique from flask import Blueprint from flask import current_app @@ -12,34 +10,8 @@ from flask import render_template from typing import Dict from wqflask.decorators import login_required - - -@functools.total_ordering -class OrderedEnum(Enum): - @classmethod - @functools.lru_cache(None) - def _member_list(cls): - return list(cls) - - def __lt__(self, other): - if self.__class__ is other.__class__: - member_list = self.__class__._member_list() - return member_list.index(self) < member_list.index(other) - return NotImplemented - - -@unique -class DataRole(OrderedEnum): - NO_ACCESS = "no-access" - VIEW = "view" - EDIT = "edit" - - -@unique -class AdminRole(OrderedEnum): - NOT_ADMIN = "not-admin" - EDIT_ACCESS = "edit-access" - EDIT_ADMINS = "edit-admins" +from wqflask.access_roles import AdminRole +from wqflask.access_roles import DataRole resource_management = Blueprint('resource_management', __name__) -- cgit v1.2.3 From c7899f6d55a3063a99e19e402ae753030a2ecff8 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 16:40:57 +0300 Subject: wqflask: decorators: Make `edit_access_required` more generic This now supports passing the `resource_id` directly. --- wqflask/wqflask/decorators.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py index b1141fb7..13867cda 100644 --- a/wqflask/wqflask/decorators.py +++ b/wqflask/wqflask/decorators.py @@ -6,6 +6,7 @@ import redis from flask import current_app, g from typing import Dict from functools import wraps +from wqflask.access_roles import DataRole import json import requests @@ -33,7 +34,7 @@ def login_required(f): def edit_access_required(f): - """Use this for endpoints where admins are required""" + """Use this for endpoints where people with admin or edit privileges are required""" @wraps(f) def wrap(*args, **kwargs): resource_id: str = "" @@ -48,6 +49,8 @@ def edit_access_required(f): data=("dataset-probeset:" f"{kwargs.get('dataset_name')}"), secret=current_app.config.get("SECRET_HMAC_CODE")) + if kwargs.get("resource_id"): # The resource_id is already provided + resource_id = kwargs.get("resource_id") response: Dict = {} try: _user_id = g.user_session.record.get(b"user_id", @@ -57,8 +60,8 @@ def edit_access_required(f): f"{resource_id}&user={_user_id}").content) except: response = {} - - if "edit" not in response.get("data", []): - return "You need to be admin", 401 + if max([DataRole(role) for role in response.get( + "data", ["no-access"])]) < DataRole.EDIT: + return "You need to have edit access", 401 return f(*args, **kwargs) return wrap -- cgit v1.2.3 From 400649f6bb8d0b11e51aa1fec00d82972a785723 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 16:42:59 +0300 Subject: manage_resource.html: Toggle the "is open to public" radio buttons --- wqflask/wqflask/templates/admin/manage_resource.html | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/wqflask/wqflask/templates/admin/manage_resource.html b/wqflask/wqflask/templates/admin/manage_resource.html index 80dfc8ee..1002ac45 100644 --- a/wqflask/wqflask/templates/admin/manage_resource.html +++ b/wqflask/wqflask/templates/admin/manage_resource.html @@ -24,8 +24,12 @@ {% endif %} {% endif %} - -
    + + +
    + + +
    @@ -35,15 +39,16 @@
    {% if DATA_ACCESS > DataRole.VIEW and ADMIN_STATUS > AdminRole.NOT_ADMIN %} + {% set is_open_to_public = DataRole(resource_info.get('default_mask').get('data')) > DataRole.NO_ACCESS %}
    @@ -51,7 +56,7 @@
    - +
    {% endif %} @@ -92,7 +97,7 @@
    {% endif %} -
    +
    -- cgit v1.2.3 From 803f4c47b070ac541e9d12c76101c8c1ac267640 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 16:43:46 +0300 Subject: Rename manage_resource -> view_resource --- wqflask/wqflask/resource_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 4e160bb8..e0d2105f 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -136,7 +136,7 @@ unique identifiers so they aren't human readable names. @resource_management.route("/resources/") @login_required -def manage_resource(resource_id: str): +def view_resource(resource_id: str): user_id = (g.user_session.record.get(b"user_id", b"").decode("utf-8") or g.user_session.record.get("user_id", "")) -- cgit v1.2.3 From a5d24f3885239c4fe15fd6d5dd070ec28403473d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 16:44:35 +0300 Subject: resource_manager: Use := to store embellished resource dict --- wqflask/wqflask/resource_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index e0d2105f..94c9ba4f 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -150,8 +150,9 @@ def view_resource(resource_id: str): return render_template( "admin/manage_resource.html", - resource_info=(embellished_resource:=add_extra_resource_metadata( + resource_info=(embellished_resource := add_extra_resource_metadata( conn=redis_conn, + resource_id=resource_id, resource=json.loads(resource))), access_role=get_user_access_roles( conn=redis_conn, -- cgit v1.2.3 From 6fed3eec407f9457260fb586067d76a35318445c Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 16:45:26 +0300 Subject: wqflask: resource_manager: Add "resource_id" to resource dict --- wqflask/wqflask/resource_manager.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 94c9ba4f..94c351a4 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -96,7 +96,9 @@ def get_user_access_roles(conn: redis.Redis, return {k: max(v) for k, v in access_role.items()} -def add_extra_resource_metadata(conn: redis.Redis, resource: Dict) -> Dict: +def add_extra_resource_metadata(conn: redis.Redis, + resource_id: str, + resource: Dict) -> Dict: """If resource['owner_id'] exists, add metadata about that user. Also, if the resource contains group masks, add the group name into the resource dict. Note that resource['owner_id'] and the group masks are @@ -104,14 +106,17 @@ unique identifiers so they aren't human readable names. Args: - conn: A redis connection with the responses decoded. + - resource_id: The unique identifier of the resource. - resource: A dict containing details(metadata) about a given resource. Returns: - An embellished dictionary with the human readable names of the - group masks and the owner id if it was set. + An embellished dictionary with its resource id; the human + readable names of the group masks; and the owner id if it was set. """ + resource["resource_id"] = resource_id + # Embellish the resource information with owner details if the # owner is set if (owner_id := resource.get("owner_id", "none").lower()) == "none": -- cgit v1.2.3 From 666c0df4c6536b831a2c08ea61c87de8f37a696d Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 16:46:09 +0300 Subject: Add `POST resource-management/resources//make-public` This endpoint either makes resources public or non-public by tweaking the access-masks. --- wqflask/wqflask/resource_manager.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 94c351a4..fe25902e 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -6,9 +6,13 @@ from flask import Blueprint from flask import current_app from flask import g from flask import render_template +from flask import redirect +from flask import request +from flask import url_for from typing import Dict +from wqflask.decorators import edit_access_required from wqflask.decorators import login_required from wqflask.access_roles import AdminRole from wqflask.access_roles import DataRole @@ -164,3 +168,33 @@ def view_resource(resource_id: str): resource_info=embellished_resource, user_id=user_id), DataRole=DataRole, AdminRole=AdminRole) + + +@resource_management.route("/resources//make-public", + methods=('POST',)) +@edit_access_required +@login_required +def update_resource_publicity(resource_id: str): + redis_conn = redis.from_url( + current_app.config["REDIS_URL"], + decode_responses=True) + resource_info = json.loads(redis_conn.hget("resources", resource_id)) + + if (is_open_to_public := request + .form + .to_dict() + .get("open_to_public")) == "True": + resource_info['default_mask'] = { + 'data': DataRole.VIEW.value, + 'admin': AdminRole.NOT_ADMIN.value, + 'metadata': DataRole.VIEW.value, + } + elif is_open_to_public == "False": + resource_info['default_mask'] = { + 'data': DataRole.NO_ACCESS.value, + 'admin': AdminRole.NOT_ADMIN.value, + 'metadata': DataRole.NO_ACCESS.value, + } + redis_conn.hset("resources", resource_id, json.dumps(resource_info)) + return redirect(url_for("resource_management.view_resource", + resource_id=resource_id)) -- cgit v1.2.3 From 182327254fe2964c3dd41aabe49ab99748800a64 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Tue, 19 Oct 2021 16:47:38 +0300 Subject: scripts: resource: Add value for "admin" when updating resources --- scripts/authentication/resource.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/authentication/resource.py b/scripts/authentication/resource.py index 1f5f31d8..4b8d801a 100755 --- a/scripts/authentication/resource.py +++ b/scripts/authentication/resource.py @@ -38,14 +38,14 @@ from datetime import datetime def recover_hash(name: str, file_path: str, set_function) -> bool: """Recover back-ups using the `set_function` - Parameters: + Args: + - name: Redis hash where `file_path` will be restored + - file_path: File path where redis hash is sourced from + - set_function: Function used to do the Redis backup for + example: HSET - - `name`: Redis hash where `file_path` will be restored - - - `file_path`: File path where redis hash is sourced from - - - `set_function`: Function used to do the Redis backup for - example: HSET + Returns: + A boolean indicating whether the function ran successfully. """ try: @@ -99,7 +99,7 @@ if __name__ == "__main__": _resource = json.loads(resource) # str -> dict conversion _resource["group_masks"] = {args.group_id: {"metadata": "edit", "data": "edit", - "admin": "edit-admins"}} + "admin": "not-admin"}} REDIS_CONN.hset("resources", resource_id, json.dumps(_resource)) -- cgit v1.2.3 From cc3ae4707d2418712d13261d4bf9d5a509169c7e Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 20 Oct 2021 12:28:44 +0300 Subject: Add "GN2_PROXY" as a configurable option --- etc/default_settings.py | 3 +++ wqflask/wqflask/decorators.py | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/etc/default_settings.py b/etc/default_settings.py index 651cc55e..8636f4db 100644 --- a/etc/default_settings.py +++ b/etc/default_settings.py @@ -29,6 +29,9 @@ GN_VERSION = open("../etc/VERSION", "r").read() # Redis REDIS_URL = "redis://:@localhost:6379/0" +# gn2-proxy +GN2_PROXY = "http://localhost:8080" + # ---- MySQL SQL_URI = "mysql://gn2:mysql_password@localhost/db_webqtl_s" diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py index 13867cda..edbea90f 100644 --- a/wqflask/wqflask/decorators.py +++ b/wqflask/wqflask/decorators.py @@ -5,6 +5,7 @@ import redis from flask import current_app, g from typing import Dict +from urllib.parse import urljoin from functools import wraps from wqflask.access_roles import DataRole @@ -56,8 +57,10 @@ def edit_access_required(f): _user_id = g.user_session.record.get(b"user_id", "").decode("utf-8") response = json.loads( - requests.get(GN_PROXY_URL + "available?resource=" - f"{resource_id}&user={_user_id}").content) + requests.get(urljoin( + current_app.config.get("GN2_PROXY"), + ("available?resource=" + f"{resource_id}&user={_user_id}"))).content) except: response = {} if max([DataRole(role) for role in response.get( -- cgit v1.2.3 From 050391c297f35fa4073d3360de47b889a39f0829 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 20 Oct 2021 12:29:08 +0300 Subject: Add `edit_admins_required` decorator --- wqflask/wqflask/decorators.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py index edbea90f..cd06aee7 100644 --- a/wqflask/wqflask/decorators.py +++ b/wqflask/wqflask/decorators.py @@ -7,6 +7,7 @@ from flask import current_app, g from typing import Dict from urllib.parse import urljoin from functools import wraps +from wqflask.access_roles import AdminRole from wqflask.access_roles import DataRole import json @@ -68,3 +69,27 @@ def edit_access_required(f): return "You need to have edit access", 401 return f(*args, **kwargs) return wrap + + +def edit_admins_access_required(f): + """Use this for endpoints where ownership of a resource is required""" + @wraps(f) + def wrap(*args, **kwargs): + resource_id: str = kwargs.get("resource_id", "") + response: Dict = {} + try: + _user_id = g.user_session.record.get(b"user_id", + "").decode("utf-8") + response = json.loads( + requests.get(urljoin( + current_app.config.get("GN2_PROXY"), + ("available?resource=" + f"{resource_id}&user={_user_id}"))).content) + except: + response = {} + if max([AdminRole(role) for role in response.get( + "data", ["not-admin"])]) >= AdminRole.EDIT_ADMINS: + return "You need to have edit-admins access", 401 + return f(*args, **kwargs) + return wrap + -- cgit v1.2.3 From c76971e2eaa556a97481974c73f964c2b341e723 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 20 Oct 2021 13:43:05 +0300 Subject: Use gn-proxy to fetch access-roles instead of looking at redis --- wqflask/wqflask/resource_manager.py | 71 +++++++++++++------------------------ 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index fe25902e..74941341 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -1,6 +1,6 @@ import redis import json - +import requests from flask import Blueprint from flask import current_app @@ -10,6 +10,7 @@ from flask import redirect from flask import request from flask import url_for +from urllib.parse import urljoin from typing import Dict from wqflask.decorators import edit_access_required @@ -50,54 +51,33 @@ def get_user_membership(conn: redis.Redis, user_id: str, return results -def get_user_access_roles(conn: redis.Redis, - resource_info: Dict, - user_id: str) -> Dict: +def get_user_access_roles( + resource_id: str, + user_id: str, + gn_proxy_url: str="http://localhost:8080") -> Dict: """Get the highest access roles for a given user Args: - - conn: A redis connection with the responses decoded. - - resource_info: A dict containing details(metadata) about a - given resource. + - resource_id: The unique id of a given resource. - user_id: The unique id of a given user. + - gn_proxy_url: The URL where gn-proxy is running. Returns: A dict indicating the highest access role the user has. """ - # This is the default access role - access_role = { - "data": [DataRole.NO_ACCESS], - "metadata": [DataRole.NO_ACCESS], - "admin": [AdminRole.NOT_ADMIN], - } - - # Check the resource's default mask - if default_mask := resource_info.get("default_mask"): - access_role["data"].append(DataRole(default_mask.get("data"))) - access_role["metadata"].append(DataRole(default_mask.get("metadata"))) - access_role["admin"].append(AdminRole(default_mask.get("admin"))) - - # Then check if the user is the owner Check with Zach and Rob if - # the owner, be default should, as the lowest access_roles, edit - # access - if resource_info.get("owner_id") == user_id: - access_role["data"].append(DataRole.EDIT) - access_role["metadata"].append(DataRole.EDIT) - access_role["admin"].append(AdminRole.EDIT_ACCESS) - - # Check the group mask. If the user is in that group mask, use the - # access roles for that group - if group_masks := resource_info.get("group_masks"): - for group_id, roles in group_masks.items(): - user_membership = get_user_membership(conn=conn, - user_id=user_id, - group_id=group_id) - if any(user_membership.values()): - access_role["data"].append(DataRole(roles.get("data"))) - access_role["metadata"].append( - DataRole(roles.get("metadata"))) - return {k: max(v) for k, v in access_role.items()} + role_mapping = {} + for x, y in zip(DataRole, AdminRole): + role_mapping.update({x.value: x, }) + role_mapping.update({y.value: y, }) + access_role = {} + for key, value in json.loads( + requests.get(urljoin( + gn_proxy_url, + ("available?resource=" + f"{resource_id}&user={user_id}"))).content).items(): + access_role[key] = max(map(lambda x: role_mapping[x], value)) + return access_role def add_extra_resource_metadata(conn: redis.Redis, @@ -152,21 +132,20 @@ def view_resource(resource_id: str): redis_conn = redis.from_url( current_app.config["REDIS_URL"], decode_responses=True) - # Abort early if the resource can't be found if not (resource := redis_conn.hget("resources", resource_id)): return f"Resource: {resource_id} Not Found!", 401 return render_template( "admin/manage_resource.html", - resource_info=(embellished_resource := add_extra_resource_metadata( + resource_info=(add_extra_resource_metadata( conn=redis_conn, resource_id=resource_id, resource=json.loads(resource))), access_role=get_user_access_roles( - conn=redis_conn, - resource_info=embellished_resource, - user_id=user_id), + resource_id=resource_id, + user_id=user_id, + gn_proxy_url=current_app.config.get("GN2_PROXY")), DataRole=DataRole, AdminRole=AdminRole) @@ -183,7 +162,7 @@ def update_resource_publicity(resource_id: str): if (is_open_to_public := request .form .to_dict() - .get("open_to_public")) == "True": + .get("open_to_public")) == "True": resource_info['default_mask'] = { 'data': DataRole.VIEW.value, 'admin': AdminRole.NOT_ADMIN.value, -- cgit v1.2.3 From fbffb2e4f7538cc34332f37064d4b67cd52588fb Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 20 Oct 2021 13:43:57 +0300 Subject: Update failing tests when testing `get_user_access_roles` --- .../tests/unit/wqflask/test_resource_manager.py | 82 ++++++++-------------- 1 file changed, 29 insertions(+), 53 deletions(-) diff --git a/wqflask/tests/unit/wqflask/test_resource_manager.py b/wqflask/tests/unit/wqflask/test_resource_manager.py index 9d5aaf0b..01fb2021 100644 --- a/wqflask/tests/unit/wqflask/test_resource_manager.py +++ b/wqflask/tests/unit/wqflask/test_resource_manager.py @@ -1,4 +1,5 @@ """Test cases for wqflask/resource_manager.py""" +import json import unittest from unittest import mock @@ -57,62 +58,37 @@ class TestGetUserMembership(unittest.TestCase): class TestCheckUserAccessRole(unittest.TestCase): """Test cases for `get_user_access_roles`""" - def setUp(self): - self.resource_info = { - "owner_id": "8ad942fe-490d-453e-bd37", - "default_mask": { - "data": "no-access", - "metadata": "no-access", - "admin": "not-admin", - }, - "group_masks": { - "7fa95d07-0e2d-4bc5-b47c-448fdc1260b2": { - "metadata": "edit", - "data": "edit", - }}, - "name": "_14329", - "data": { - "dataset": 1, - "trait": 14329, - }, - "type": "dataset-publish", - } - conn = mock.MagicMock() - - conn.hgetall.return_value = { - '7fa95d07-0e2d-4bc5-b47c-448fdc1260b2': ( - '{"name": "editors", ' - '"admins": ["8ad942fe-490d-453e-bd37-56f252e41604", "rand"], ' - '"members": ["8ad942fe-490d-453e-bd37-56f252e41603", ' - '"rand"], ' - '"changed_timestamp": "Oct 06 2021 06:39PM", ' - '"created_timestamp": "Oct 06 2021 06:39PM"}')} - self.conn = conn - - def test_get_user_access_when_owner(self): + @mock.patch("wqflask.resource_manager.requests.get") + def test_edit_access(self, requests_mock): """Test that the right access roles are set""" + response = mock.PropertyMock(return_value=json.dumps( + { + 'data': ['no-access', 'view', 'edit', ], + 'metadata': ['no-access', 'view', 'edit', ], + 'admin': ['not-admin', 'edit-access', ], + } + )) + type(requests_mock.return_value).content = response self.assertEqual(get_user_access_roles( - conn=self.conn, - resource_info=self.resource_info, + resource_id="0196d92e1665091f202f", user_id="8ad942fe-490d-453e-bd37"), - {"data": DataRole.EDIT, - "metadata": DataRole.EDIT, - "admin": AdminRole.EDIT_ACCESS}) + {"data": DataRole.EDIT, + "metadata": DataRole.EDIT, + "admin": AdminRole.EDIT_ACCESS}) - def test_get_user_access_default_mask(self): + @mock.patch("wqflask.resource_manager.requests.get") + def test_no_access(self, requests_mock): + response = mock.PropertyMock(return_value=json.dumps( + { + 'data': ['no-access', ], + 'metadata': ['no-access', ], + 'admin': ['not-admin', ], + } + )) + type(requests_mock.return_value).content = response self.assertEqual(get_user_access_roles( - conn=self.conn, - resource_info=self.resource_info, + resource_id="0196d92e1665091f202f", user_id=""), - {"data": DataRole.NO_ACCESS, - "metadata": DataRole.NO_ACCESS, - "admin": AdminRole.NOT_ADMIN}) - - def test_get_user_access_group_mask(self): - self.assertEqual(get_user_access_roles( - conn=self.conn, - resource_info=self.resource_info, - user_id="8ad942fe-490d-453e-bd37-56f252e41603"), - {"data": DataRole.EDIT, - "metadata": DataRole.EDIT, - "admin": AdminRole.NOT_ADMIN}) + {"data": DataRole.NO_ACCESS, + "metadata": DataRole.NO_ACCESS, + "admin": AdminRole.NOT_ADMIN}) -- cgit v1.2.3 From d32f0e0f56827c653902456b50ed71911a795a3c Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 20 Oct 2021 14:11:16 +0300 Subject: decorators: Use the correct logic to check for edit_admins_access --- wqflask/wqflask/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py index cd06aee7..843539ee 100644 --- a/wqflask/wqflask/decorators.py +++ b/wqflask/wqflask/decorators.py @@ -88,7 +88,7 @@ def edit_admins_access_required(f): except: response = {} if max([AdminRole(role) for role in response.get( - "data", ["not-admin"])]) >= AdminRole.EDIT_ADMINS: + "admin", ["not-admin"])]) < AdminRole.EDIT_ADMINS: return "You need to have edit-admins access", 401 return f(*args, **kwargs) return wrap -- cgit v1.2.3 From af1d704504d8f03135b805d5b4be100c5c0ad6f8 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 20 Oct 2021 14:12:53 +0300 Subject: manage_resource.html: Update the URL for updating the owner --- wqflask/wqflask/templates/admin/manage_resource.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/admin/manage_resource.html b/wqflask/wqflask/templates/admin/manage_resource.html index 1002ac45..af4d4908 100644 --- a/wqflask/wqflask/templates/admin/manage_resource.html +++ b/wqflask/wqflask/templates/admin/manage_resource.html @@ -19,7 +19,7 @@ {% endif %} {% if DATA_ACCESS > DataRole.VIEW and ADMIN_STATUS > AdminRole.NOT_ADMIN %} {% endif %} -- cgit v1.2.3 From 935270b1cc1e265b785958cf5805bf155d8ae859 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 20 Oct 2021 14:38:11 +0300 Subject: utility: redis_tools: Remove dead functions --- wqflask/utility/redis_tools.py | 78 ------------------------------------------ 1 file changed, 78 deletions(-) diff --git a/wqflask/utility/redis_tools.py b/wqflask/utility/redis_tools.py index e68397ab..c2a3b057 100644 --- a/wqflask/utility/redis_tools.py +++ b/wqflask/utility/redis_tools.py @@ -58,30 +58,6 @@ def get_user_by_unique_column(column_name, column_value): return item_details -def get_users_like_unique_column(column_name, column_value): - """Like previous function, but this only checks if the input is a - subset of a field and can return multiple results - - """ - matched_users = [] - - if column_value != "": - user_list = Redis.hgetall("users") - if column_name != "user_id": - for key in user_list: - user_ob = json.loads(user_list[key]) - if "user_id" not in user_ob: - set_user_attribute(key, "user_id", key) - user_ob["user_id"] = key - if column_name in user_ob: - if column_value in user_ob[column_name]: - matched_users.append(user_ob) - else: - matched_users.append(load_json_from_redis(user_list, column_value)) - - return matched_users - - def set_user_attribute(user_id, column_name, column_value): user_info = json.loads(Redis.hget("users", user_id)) user_info[column_name] = column_value @@ -166,52 +142,6 @@ def get_group_info(group_id): return group_info -def get_group_by_unique_column(column_name, column_value): - """ Get group by column; not sure if there's a faster way to do this """ - - matched_groups = [] - - all_group_list = Redis.hgetall("groups") - for key in all_group_list: - group_info = json.loads(all_group_list[key]) - # ZS: Since these fields are lists, search in the list - if column_name == "admins" or column_name == "members": - if column_value in group_info[column_name]: - matched_groups.append(group_info) - else: - if group_info[column_name] == column_value: - matched_groups.append(group_info) - - return matched_groups - - -def get_groups_like_unique_column(column_name, column_value): - """Like previous function, but this only checks if the input is a - subset of a field and can return multiple results - - """ - matched_groups = [] - - if column_value != "": - group_list = Redis.hgetall("groups") - if column_name != "group_id": - for key in group_list: - group_info = json.loads(group_list[key]) - # ZS: Since these fields are lists, search in the list - if column_name == "admins" or column_name == "members": - if column_value in group_info[column_name]: - matched_groups.append(group_info) - else: - if column_name in group_info: - if column_value in group_info[column_name]: - matched_groups.append(group_info) - else: - matched_groups.append( - load_json_from_redis(group_list, column_value)) - - return matched_groups - - def create_group(admin_user_ids, member_user_ids=[], group_name="Default Group Name"): group_id = str(uuid.uuid4()) @@ -354,11 +284,3 @@ def add_access_mask(resource_id, group_id, access_mask): Redis.hset("resources", resource_id, json.dumps(the_resource)) return the_resource - - -def change_resource_owner(resource_id, new_owner_id): - the_resource = get_resource_info(resource_id) - the_resource['owner_id'] = new_owner_id - - Redis.delete("resource") - Redis.hset("resources", resource_id, json.dumps(the_resource)) -- cgit v1.2.3 From be0e73c1db12806580fa5c541eceec2a54c10ad3 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 25 Oct 2021 12:05:57 +0300 Subject: Add "GET /resources//change-owner" endpoint --- wqflask/wqflask/resource_manager.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 74941341..9487bab5 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -14,6 +14,7 @@ from urllib.parse import urljoin from typing import Dict from wqflask.decorators import edit_access_required +from wqflask.decorators import edit_admins_access_required from wqflask.decorators import login_required from wqflask.access_roles import AdminRole from wqflask.access_roles import DataRole @@ -177,3 +178,12 @@ def update_resource_publicity(resource_id: str): redis_conn.hset("resources", resource_id, json.dumps(resource_info)) return redirect(url_for("resource_management.view_resource", resource_id=resource_id)) + + +@resource_management.route("/resources//change-owner") +@edit_admins_access_required +@login_required +def view_resource_owner(resource_id: str): + return render_template( + "admin/change_resource_owner.html", + resource_id=resource_id) -- cgit v1.2.3 From 696a6c6dc758e16b582e20419819acd5b17a75f1 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 25 Oct 2021 12:07:05 +0300 Subject: Add "POST /resource-management//users/search" --- wqflask/wqflask/resource_manager.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 9487bab5..58c0b5cb 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -11,7 +11,7 @@ from flask import request from flask import url_for from urllib.parse import urljoin -from typing import Dict +from typing import Dict, Tuple from wqflask.decorators import edit_access_required from wqflask.decorators import edit_admins_access_required @@ -187,3 +187,21 @@ def view_resource_owner(resource_id: str): return render_template( "admin/change_resource_owner.html", resource_id=resource_id) + + +@resource_management.route("/users/search", methods=('POST',)) +@edit_admins_access_required +@login_required +def search_user(resource_id: str): + results = {} + for user in (users := redis.from_url( + current_app.config["REDIS_URL"], + decode_responses=True).hgetall("users")): + user = json.loads(users[user]) + for q in (request.form.get("user_name"), + request.form.get("user_email")): + if q and (q in user.get("email_address") or + q in user.get("full_name")): + results[user.get("user_id", "")] = user + return json.dumps(tuple(results.values())) + -- cgit v1.2.3 From 6296b5ff43b0a6209443679350bf15b399406699 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 25 Oct 2021 12:07:56 +0300 Subject: change_resource_owner.html: Update endpoint for user-search This link has the {{ resource_id }} set because it's a required argument for the "@edit_admins_access_required" decorator --- wqflask/wqflask/templates/admin/change_resource_owner.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/admin/change_resource_owner.html b/wqflask/wqflask/templates/admin/change_resource_owner.html index ae9409b0..b88d680d 100644 --- a/wqflask/wqflask/templates/admin/change_resource_owner.html +++ b/wqflask/wqflask/templates/admin/change_resource_owner.html @@ -57,7 +57,7 @@ $('#find_users').click(function() { $.ajax({ method: "POST", - url: "/search_for_users", + url: "/resource-management/{{ resource_id }}/users/search", data: { user_name: $('input[name=user_name]').val(), user_email: $('input[name=user_email]').val() -- cgit v1.2.3 From 57cd071f98fddf1bbd23a0ea1b42005698fc4a81 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 25 Oct 2021 12:10:20 +0300 Subject: change_resource_owner.html: Replace "var" with "let" "let" is lexically scoped. --- wqflask/wqflask/templates/admin/change_resource_owner.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/templates/admin/change_resource_owner.html b/wqflask/wqflask/templates/admin/change_resource_owner.html index b88d680d..951df207 100644 --- a/wqflask/wqflask/templates/admin/change_resource_owner.html +++ b/wqflask/wqflask/templates/admin/change_resource_owner.html @@ -67,9 +67,8 @@ }) populate_users = function(json_user_list){ - var user_list = JSON.parse(json_user_list) - - var the_html = "" + let user_list = JSON.parse(json_user_list) + let the_html = "" if (user_list.length > 0){ the_html += "
    IdName Data MetadataAdmin
    {{ value.name }}{{ key }} {{ value.group_name}} {{ value.data }} {{ value.metadata }}{{ value.admin }}
    "; the_html += ""; -- cgit v1.2.3 From 7337cc6b141933f7b85f6e4928c6a88b73c3cbba Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 25 Oct 2021 12:12:11 +0300 Subject: change_resource_owner.html: Replace "the_html" with searchResultHtml --- .../templates/admin/change_resource_owner.html | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wqflask/wqflask/templates/admin/change_resource_owner.html b/wqflask/wqflask/templates/admin/change_resource_owner.html index 951df207..74ee1e45 100644 --- a/wqflask/wqflask/templates/admin/change_resource_owner.html +++ b/wqflask/wqflask/templates/admin/change_resource_owner.html @@ -68,40 +68,40 @@ populate_users = function(json_user_list){ let user_list = JSON.parse(json_user_list) - let the_html = "" + let searchResultsHtml = "" if (user_list.length > 0){ - the_html += "
    IndexNameE-mail AddressOrganization
    "; - the_html += ""; - the_html += ""; + searchResultsHtml += "
    IndexNameE-mail AddressOrganization
    "; + searchResultsHtml += ""; + searchResultsHtml += ""; for (_i = 0, _len = user_list.length; _i < _len; _i++) { this_user = user_list[_i] - the_html += ""; - the_html += ""; - the_html += "" + searchResultsHtml += ""; + searchResultsHtml += ""; + searchResultsHtml += "" if ("full_name" in this_user) { - the_html += ""; + searchResultsHtml += ""; } else { - the_html += "" + searchResultsHtml += "" } if ("email_address" in this_user) { - the_html += ""; + searchResultsHtml += ""; } else { - the_html += "" + searchResultsHtml += "" } if ("organization" in this_user) { - the_html += ""; + searchResultsHtml += ""; } else { - the_html += "" + searchResultsHtml += "" } - the_html += "" + searchResultsHtml += "" } - the_html += ""; - the_html += "
    IndexNameE-mail AddressOrganization
    " + (_i + 1).toString() + "
    " + (_i + 1).toString() + "" + this_user.full_name + "" + this_user.full_name + "N/AN/A" + this_user.email_address + "" + this_user.email_address + "N/AN/A" + this_user.organization + "" + this_user.organization + "N/AN/A
    "; + searchResultsHtml += ""; + searchResultsHtml += ""; } else { - the_html = "No users were found matching the entered criteria." + searchResultsHtml = "No users were found matching the entered criteria." } - $('#user_results').html(the_html) + $('#user_results').html(searchResultsHtml) if (user_list.length > 0){ $('#users_table').dataTable({ 'order': [[1, "asc" ]], -- cgit v1.2.3 From 18e57bad1b44fece0f7b1f98bd73e5556691a3cd Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 25 Oct 2021 12:42:11 +0300 Subject: Add "POST /resources//change-owner" endpoint --- wqflask/wqflask/resource_manager.py | 31 +++++++++++++++++----- .../templates/admin/change_resource_owner.html | 3 +-- .../wqflask/templates/admin/manage_resource.html | 3 ++- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 58c0b5cb..3371e59d 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -1,23 +1,24 @@ -import redis import json +import redis import requests from flask import Blueprint from flask import current_app +from flask import flash from flask import g -from flask import render_template from flask import redirect +from flask import render_template from flask import request from flask import url_for -from urllib.parse import urljoin from typing import Dict, Tuple +from urllib.parse import urljoin +from wqflask.access_roles import AdminRole +from wqflask.access_roles import DataRole from wqflask.decorators import edit_access_required from wqflask.decorators import edit_admins_access_required from wqflask.decorators import login_required -from wqflask.access_roles import AdminRole -from wqflask.access_roles import DataRole resource_management = Blueprint('resource_management', __name__) @@ -55,7 +56,7 @@ def get_user_membership(conn: redis.Redis, user_id: str, def get_user_access_roles( resource_id: str, user_id: str, - gn_proxy_url: str="http://localhost:8080") -> Dict: + gn_proxy_url: str = "http://localhost:8080") -> Dict: """Get the highest access roles for a given user Args: @@ -189,6 +190,23 @@ def view_resource_owner(resource_id: str): resource_id=resource_id) +@resource_management.route("/resources//change-owner", + methods=('POST',)) +@edit_admins_access_required +@login_required +def change_owner(resource_id: str): + if user_id := request.form.get("new_owner"): + redis_conn = redis.from_url( + current_app.config["REDIS_URL"], + decode_responses=True) + resource = json.loads(redis_conn.hget("resources", resource_id)) + resource["owner_id"] = user_id + redis_conn.hset("resources", resource_id, json.dumps(resource)) + flash("The resource's owner has been changed.", "alert-info") + return redirect(url_for("resource_management.view_resource", + resource_id=resource_id)) + + @resource_management.route("/users/search", methods=('POST',)) @edit_admins_access_required @login_required @@ -204,4 +222,3 @@ def search_user(resource_id: str): q in user.get("full_name")): results[user.get("user_id", "")] = user return json.dumps(tuple(results.values())) - diff --git a/wqflask/wqflask/templates/admin/change_resource_owner.html b/wqflask/wqflask/templates/admin/change_resource_owner.html index 74ee1e45..7fd84387 100644 --- a/wqflask/wqflask/templates/admin/change_resource_owner.html +++ b/wqflask/wqflask/templates/admin/change_resource_owner.html @@ -10,8 +10,7 @@ -
    - +
    diff --git a/wqflask/wqflask/templates/admin/manage_resource.html b/wqflask/wqflask/templates/admin/manage_resource.html index af4d4908..a84bd77f 100644 --- a/wqflask/wqflask/templates/admin/manage_resource.html +++ b/wqflask/wqflask/templates/admin/manage_resource.html @@ -2,7 +2,8 @@ {% block title %}Resource Manager{% endblock %} {% block content %} -
    +
    + {{ flash_me() }} {% set DATA_ACCESS = access_role.get('data') %} {% set METADATA_ACCESS = access_role.get('metadata') %} {% set ADMIN_STATUS = access_role.get('admin') %} -- cgit v1.2.3 From 700802303e5e8221a9d591ba985d6607aa61e1ce Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 25 Oct 2021 12:43:01 +0300 Subject: manage_resource.html: Replace button with link --- wqflask/wqflask/templates/admin/manage_resource.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/templates/admin/manage_resource.html b/wqflask/wqflask/templates/admin/manage_resource.html index a84bd77f..613aa70e 100644 --- a/wqflask/wqflask/templates/admin/manage_resource.html +++ b/wqflask/wqflask/templates/admin/manage_resource.html @@ -19,10 +19,10 @@ {% endif %} {% if DATA_ACCESS > DataRole.VIEW and ADMIN_STATUS > AdminRole.NOT_ADMIN %} - + {% endif %} {% endif %} -- cgit v1.2.3