scout-browser 4.97.0__py3-none-any.whl → 4.99.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scout/adapter/mongo/institute.py +42 -55
- scout/adapter/mongo/variant.py +19 -15
- scout/adapter/mongo/variant_loader.py +11 -11
- scout/build/individual.py +2 -0
- scout/build/variant/variant.py +8 -0
- scout/commands/download/ensembl.py +18 -2
- scout/commands/update/individual.py +2 -0
- scout/commands/update/panelapp.py +15 -2
- scout/constants/__init__.py +6 -7
- scout/constants/clnsig.py +2 -0
- scout/constants/file_types.py +12 -0
- scout/constants/igv_tracks.py +8 -6
- scout/constants/panels.py +3 -0
- scout/constants/variant_tags.py +6 -6
- scout/demo/643594.config.yaml +1 -0
- scout/load/panelapp.py +11 -5
- scout/models/case/case_loading_models.py +4 -0
- scout/parse/variant/clnsig.py +38 -0
- scout/parse/variant/genotype.py +4 -10
- scout/parse/variant/models.py +5 -11
- scout/parse/variant/rank_score.py +5 -13
- scout/parse/variant/variant.py +90 -111
- scout/server/app.py +33 -22
- scout/server/blueprints/alignviewers/controllers.py +29 -10
- scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +41 -11
- scout/server/blueprints/cases/templates/cases/case.html +1 -1
- scout/server/blueprints/cases/templates/cases/utils.html +6 -6
- scout/server/blueprints/clinvar/controllers.py +29 -14
- scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +13 -4
- scout/server/blueprints/clinvar/views.py +14 -2
- scout/server/blueprints/institutes/controllers.py +10 -2
- scout/server/blueprints/institutes/templates/overview/filters.html +14 -1
- scout/server/blueprints/login/controllers.py +112 -12
- scout/server/blueprints/login/views.py +38 -60
- scout/server/blueprints/public/templates/public/index.html +5 -1
- scout/server/blueprints/variant/controllers.py +1 -1
- scout/server/blueprints/variant/templates/variant/acmg.html +6 -2
- scout/server/blueprints/variant/templates/variant/components.html +19 -0
- scout/server/blueprints/variant/templates/variant/utils.html +3 -3
- scout/server/blueprints/variants/controllers.py +10 -1
- scout/server/blueprints/variants/templates/variants/components.html +28 -0
- scout/server/blueprints/variants/templates/variants/mei-variants.html +8 -6
- scout/server/blueprints/variants/templates/variants/sv-variants.html +9 -7
- scout/server/blueprints/variants/templates/variants/utils.html +8 -12
- scout/server/blueprints/variants/templates/variants/variants.html +4 -25
- scout/server/config.py +8 -0
- scout/server/utils.py +22 -5
- scout/utils/acmg.py +25 -26
- scout/utils/ensembl_biomart_clients.py +1 -1
- scout/utils/ensembl_rest_clients.py +25 -32
- scout/utils/hgvs.py +1 -1
- {scout_browser-4.97.0.dist-info → scout_browser-4.99.0.dist-info}/METADATA +10 -14
- {scout_browser-4.97.0.dist-info → scout_browser-4.99.0.dist-info}/RECORD +56 -56
- {scout_browser-4.97.0.dist-info → scout_browser-4.99.0.dist-info}/WHEEL +0 -0
- {scout_browser-4.97.0.dist-info → scout_browser-4.99.0.dist-info}/entry_points.txt +0 -0
- {scout_browser-4.97.0.dist-info → scout_browser-4.99.0.dist-info}/licenses/LICENSE +0 -0
@@ -325,21 +325,21 @@
|
|
325
325
|
|
326
326
|
{% if variant.category in ("mei", "str", "snv", "cancer") %}
|
327
327
|
{% if variant.category == "cancer" %}
|
328
|
-
<a href=
|
328
|
+
<a href='{{ url_for('variant.cancer_variant',
|
329
329
|
institute_id=variant.institute,
|
330
330
|
case_name=case.display_name,
|
331
|
-
variant_id=variant._id) }}
|
331
|
+
variant_id=variant._id) }}' target='_blank'>
|
332
332
|
{% else %}
|
333
|
-
<a href=
|
333
|
+
<a href='{{ url_for('variant.variant',
|
334
334
|
institute_id=variant.institute,
|
335
335
|
case_name=case.display_name,
|
336
|
-
variant_id=variant._id) }}
|
336
|
+
variant_id=variant._id) }}' target='_blank'>
|
337
337
|
{% endif %}
|
338
338
|
{% else %} <!-- structural variants -->
|
339
|
-
<a href=
|
339
|
+
<a href='{{ url_for('variant.sv_variant',
|
340
340
|
institute_id=variant.institute,
|
341
341
|
case_name=case.display_name,
|
342
|
-
variant_id=variant._id) }}
|
342
|
+
variant_id=variant._id) }}' target='_blank'>
|
343
343
|
{% endif %}
|
344
344
|
{{ pretty_variant(variant) }}
|
345
345
|
</a>
|
@@ -28,30 +28,45 @@ LOG = logging.getLogger(__name__)
|
|
28
28
|
|
29
29
|
|
30
30
|
def _get_var_tx_hgvs(case_obj: dict, variant_obj: dict) -> List[Tuple[str, str]]:
|
31
|
-
"""Retrieve all transcripts /
|
31
|
+
"""Retrieve all transcripts / HGVS for a given variant."""
|
32
32
|
|
33
33
|
build = str(case_obj.get("genome_build", "37"))
|
34
34
|
tx_hgvs_list = [("", "Do not specify")]
|
35
|
+
case_has_build_37 = "37" in case_obj.get("genome_build", "37")
|
36
|
+
|
35
37
|
add_gene_info(store, variant_obj, genome_build=build)
|
38
|
+
|
36
39
|
for gene in variant_obj.get("genes", []):
|
37
|
-
|
40
|
+
transcripts = gene.get("transcripts", [])
|
41
|
+
|
42
|
+
for tx in transcripts:
|
43
|
+
|
44
|
+
refseq_id = tx.get("refseq_id")
|
45
|
+
coding_seq_name = tx.get("coding_sequence_name")
|
46
|
+
if not (refseq_id and coding_seq_name):
|
47
|
+
continue # Skip transcripts missing required fields
|
48
|
+
|
38
49
|
mane_select = tx.get("mane_select_transcript")
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
50
|
+
mane_plus_clinical = tx.get("mane_plus_clinical_transcript")
|
51
|
+
|
52
|
+
for refseq in tx.get("refseq_identifiers", []):
|
53
|
+
refseq_version = fetch_refseq_version(refseq) # Adds version to a RefSeq ID
|
54
|
+
hgvs_simple = f"{refseq_version}:{coding_seq_name}"
|
55
|
+
|
56
|
+
refseq_is_mane_select = mane_select == refseq_version
|
57
|
+
refseq_is_mane_plus_clinical = mane_plus_clinical == refseq_version
|
43
58
|
|
44
|
-
|
45
|
-
|
59
|
+
# Transcript is validate only when conditions are met
|
60
|
+
validated = (
|
61
|
+
validate_hgvs(build, hgvs_simple)
|
62
|
+
if (case_has_build_37 or refseq_is_mane_select or refseq_is_mane_plus_clinical)
|
63
|
+
else ""
|
64
|
+
)
|
46
65
|
|
47
|
-
|
48
|
-
if validated:
|
49
|
-
label += "_validated_"
|
66
|
+
label = f"{hgvs_simple}{'_validated_' if validated else ''}{'_mane-select_' if refseq_is_mane_select else ''}{'_mane-plus-clinical_' if refseq_is_mane_plus_clinical else ''}"
|
50
67
|
|
51
|
-
|
52
|
-
label += "_mane-select_"
|
68
|
+
tx_hgvs_list.append((hgvs_simple, label))
|
53
69
|
|
54
|
-
tx_hgvs_list.append((hgvs_simple, label))
|
55
70
|
return tx_hgvs_list
|
56
71
|
|
57
72
|
|
@@ -92,7 +92,7 @@
|
|
92
92
|
{% if var_category == 'snv' %}
|
93
93
|
<br><br>
|
94
94
|
<!-- Transcripts & HGVS:(optional) -->
|
95
|
-
{{variant_data.var_form.tx_hgvs.label(class="fw-bold, text-dark")}}
|
95
|
+
{{ variant_data.var_form.tx_hgvs.label(class="fw-bold, text-dark") }}
|
96
96
|
|
97
97
|
<span class="text-danger" data-bs-toggle='tooltip' title="If you do not provide any HGVS expression, chromosome coordinates will be used to describe this variant instead (automatic). HGVS expressions were validated using VariantValidator"><strong>?</strong></span>
|
98
98
|
<span class="badge bg-primary float-end"><a class="text-white" href="https://variantvalidator.org/service/validate/" target="_blank" rel="noopener">VariantValidator</a></span>
|
@@ -101,7 +101,7 @@
|
|
101
101
|
{% with messages = get_flashed_messages() %}
|
102
102
|
{% if messages %}
|
103
103
|
{% for message in messages %}
|
104
|
-
<span class="ml-3"
|
104
|
+
<span class="ml-3">{{ message }}</span><br>
|
105
105
|
{% endfor %}
|
106
106
|
<br><br>
|
107
107
|
{% endif %}
|
@@ -113,14 +113,17 @@
|
|
113
113
|
{% for item_row in variant_data.var_form.tx_hgvs | batch(3) %}
|
114
114
|
<tr>
|
115
115
|
{% for item in item_row %}
|
116
|
-
{% set ns = namespace(label='', validated=false, mane_select=false) %}
|
117
|
-
|
116
|
+
{% set ns = namespace(label='', validated=false, mane_select=false, mane_plus_clinical=false) %}
|
118
117
|
{% if "_validated_" in item.label.text %}
|
119
118
|
{% set ns.validated = true %}
|
120
119
|
{% endif %}
|
121
120
|
{% if "_mane-select_" in item.label.text %}
|
122
121
|
{% set ns.mane_select = true %}
|
123
122
|
{% endif %}
|
123
|
+
{% if "_mane-plus-clinical_" in item.label.text %}
|
124
|
+
{% set ns.mane_plus_clinical = true %}
|
125
|
+
{% endif %}
|
126
|
+
|
124
127
|
{% set ns.label = item.label.text | replace("_validated_", "") | replace("_mane-select_", "") %}
|
125
128
|
|
126
129
|
<td style="width: 3%; text-align: right; vertical-align: baseline;">
|
@@ -138,6 +141,9 @@
|
|
138
141
|
{% if ns.mane_select %}
|
139
142
|
<span class='badge bg-dark'>MANE SELECT</span>
|
140
143
|
{% endif %}
|
144
|
+
{% if ns.mane_plus_clinical %}
|
145
|
+
<span class='badge bg-dark'>MANE PLUS CLINICAL</span>
|
146
|
+
{% endif %}
|
141
147
|
{% if ns.validated %}
|
142
148
|
<em class="fa fa-check text-success" aria-hidden="true" data-bs-toggle="tooltip" title="Verified by VariantValidator"></em>
|
143
149
|
{% endif %}
|
@@ -147,6 +153,9 @@
|
|
147
153
|
</tr>
|
148
154
|
{% endfor %}
|
149
155
|
</table>
|
156
|
+
|
157
|
+
<span class="text-danger">HGVS validation is performed only for variants in build GRCh37 or MANE Select and MANE Plus Clinical transcripts. For any other available HGVS present in this list, manual validation can be performed using the VariantValidator link above.</span>
|
158
|
+
|
150
159
|
<br><br>
|
151
160
|
<!-- dbSNP identifier -->
|
152
161
|
{{variant_data.var_form.variations_ids.label(class="fw-bold, text-dark")}}
|
@@ -3,10 +3,22 @@ from json import dumps
|
|
3
3
|
from tempfile import NamedTemporaryFile
|
4
4
|
from typing import List, Tuple
|
5
5
|
|
6
|
-
from flask import
|
6
|
+
from flask import (
|
7
|
+
Blueprint,
|
8
|
+
flash,
|
9
|
+
redirect,
|
10
|
+
render_template,
|
11
|
+
request,
|
12
|
+
send_file,
|
13
|
+
url_for,
|
14
|
+
)
|
7
15
|
from flask_login import current_user
|
8
16
|
|
9
|
-
from scout.constants.clinvar import
|
17
|
+
from scout.constants.clinvar import (
|
18
|
+
CASEDATA_HEADER,
|
19
|
+
CLINVAR_HEADER,
|
20
|
+
GERMLINE_CLASSIF_TERMS,
|
21
|
+
)
|
10
22
|
from scout.server.extensions import clinvar_api, store
|
11
23
|
from scout.server.utils import institute_and_case
|
12
24
|
|
@@ -391,8 +391,16 @@ def update_institute_settings(store: MongoAdapter, institute_obj: Dict, form: Mu
|
|
391
391
|
updated_institute = store.update_institute(
|
392
392
|
internal_id=institute_obj["_id"],
|
393
393
|
sanger_recipients=get_sanger_recipients(form),
|
394
|
-
coverage_cutoff=
|
395
|
-
|
394
|
+
coverage_cutoff=(
|
395
|
+
int(form["coverage_cutoff"])
|
396
|
+
if form.get("coverage_cutoff")
|
397
|
+
else form.get("coverage_cutoff")
|
398
|
+
),
|
399
|
+
frequency_cutoff=(
|
400
|
+
float(form["frequency_cutoff"])
|
401
|
+
if form.get("frequency_cutoff")
|
402
|
+
else form.get("frequency_cutoff")
|
403
|
+
),
|
396
404
|
show_all_cases_status=form.getlist("show_all_cases_status"),
|
397
405
|
display_name=form.get("display_name"),
|
398
406
|
phenotype_groups=phenotype_groups,
|
@@ -34,8 +34,21 @@
|
|
34
34
|
<div class="row" id="body-row"> <!--sidebar and main container are on the same row-->
|
35
35
|
{{ institute_actionbar(institute) }} <!-- This is the sidebar -->
|
36
36
|
<div class="col">
|
37
|
+
|
37
38
|
<div class="card">
|
38
|
-
<div class="card-header"><b>
|
39
|
+
<div class="card-header"><b>Soft filters created by an admin on the institute's settings page</b></div>
|
40
|
+
<div class="card-body">
|
41
|
+
{% for filter_tag in institute.soft_filters %}
|
42
|
+
<span class="badge bg-secondary">{{filter_tag}}</span>
|
43
|
+
{% else%}
|
44
|
+
No soft filters available
|
45
|
+
{% endfor %}
|
46
|
+
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
|
50
|
+
<div class="card mt-3">
|
51
|
+
<div class="card-header"><b>Custom filters created for institute on variants pages</b></div>
|
39
52
|
<div class="card-body">
|
40
53
|
<ul class="list-group">
|
41
54
|
<li class="list-group-item list-group-item-heading">
|
@@ -1,22 +1,20 @@
|
|
1
1
|
import logging
|
2
|
+
from datetime import datetime
|
3
|
+
from typing import Optional
|
2
4
|
|
3
|
-
|
5
|
+
import requests
|
6
|
+
from flask import Response, current_app, flash, redirect, session, url_for
|
7
|
+
from flask_login import login_user
|
4
8
|
|
5
|
-
from scout.server.extensions import ldap_manager
|
6
|
-
|
7
|
-
LOG = logging.getLogger(__name__)
|
9
|
+
from scout.server.extensions import ldap_manager, oauth_client, store
|
8
10
|
|
11
|
+
from .models import LoginUser
|
9
12
|
|
10
|
-
|
11
|
-
"""Log in a LDAP user
|
13
|
+
LOG = logging.getLogger(__name__)
|
12
14
|
|
13
|
-
Args:
|
14
|
-
userid(str): user id provided in ldap login form (usually and email)
|
15
|
-
password(str): user passord provided in ldap loding form
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
"""
|
16
|
+
def ldap_authorized(userid: str, password: str) -> bool:
|
17
|
+
"""Log in an LDAP user."""
|
20
18
|
authorized = False
|
21
19
|
try:
|
22
20
|
authorized = ldap_manager.authenticate(
|
@@ -63,3 +61,105 @@ def users(store):
|
|
63
61
|
return dict(
|
64
62
|
users=sorted(user_objs, key=lambda user: -user["events"]),
|
65
63
|
)
|
64
|
+
|
65
|
+
|
66
|
+
def user_has_consented(user_consent: Optional[str]) -> bool:
|
67
|
+
"""Check if user has given consent for activity logging."""
|
68
|
+
if not current_app.config.get("USERS_ACTIVITY_LOG_PATH"):
|
69
|
+
return True
|
70
|
+
|
71
|
+
if user_consent is None and "consent_given" not in session:
|
72
|
+
flash(
|
73
|
+
"Logging user activity is a requirement for using this site and accessing your account. Without consent to activity logging, you will not be able to log in into Scout.",
|
74
|
+
"warning",
|
75
|
+
)
|
76
|
+
return False
|
77
|
+
return True
|
78
|
+
|
79
|
+
|
80
|
+
def ldap_login(ldap_user: Optional[str], ldap_password: Optional[str]) -> Optional[str]:
|
81
|
+
"""Authenticate user via LDAP and return user ID if authorized."""
|
82
|
+
|
83
|
+
if not ldap_user or not ldap_password:
|
84
|
+
return None
|
85
|
+
|
86
|
+
authorized: bool = ldap_authorized(ldap_user, ldap_password)
|
87
|
+
|
88
|
+
if authorized:
|
89
|
+
return ldap_user
|
90
|
+
|
91
|
+
flash("User not authorized by LDAP server", "warning")
|
92
|
+
|
93
|
+
|
94
|
+
def google_login() -> Optional[Response]:
|
95
|
+
"""Authenticate user via Google OIDC and redirect to the redirect URI. The name of this endpoint should be present on the Google login settings."""
|
96
|
+
if "email" in session:
|
97
|
+
return redirect(url_for("public.login")) # Redirect to the login route with session info
|
98
|
+
|
99
|
+
redirect_uri: str = url_for("login.authorized", _external=True)
|
100
|
+
try:
|
101
|
+
return oauth_client.google.authorize_redirect(redirect_uri)
|
102
|
+
except Exception:
|
103
|
+
flash("An error has occurred while logging in user using Google OAuth", "warning")
|
104
|
+
return None
|
105
|
+
|
106
|
+
|
107
|
+
def keycloak_login() -> Optional[Response]:
|
108
|
+
"""Authenticate user via Keycloak OIDC and redirect to the redirect URI. The name of this endpoint should be present on the Keycloak login settings."""
|
109
|
+
|
110
|
+
redirect_uri: str = url_for("login.authorized", _external=True)
|
111
|
+
try:
|
112
|
+
return oauth_client.keycloak.authorize_redirect(redirect_uri)
|
113
|
+
except Exception:
|
114
|
+
flash("An error has occurred while logging in user using Keycloak", "warning")
|
115
|
+
return None
|
116
|
+
|
117
|
+
|
118
|
+
def database_login(user_mail: Optional[str]) -> Optional[str]:
|
119
|
+
"""Authenticate user against the Scout database and return email if successful."""
|
120
|
+
return user_mail
|
121
|
+
|
122
|
+
|
123
|
+
def validate_and_login_user(user_mail: Optional[str], user_id: Optional[str]) -> Response:
|
124
|
+
"""Validate user in Scout database and log them in."""
|
125
|
+
user_obj: Optional[dict] = store.user(email=user_mail, user_id=user_id)
|
126
|
+
|
127
|
+
if user_obj is None:
|
128
|
+
flash("User not found in Scout database", "warning")
|
129
|
+
session.pop("email", None)
|
130
|
+
return redirect(url_for("public.index"))
|
131
|
+
|
132
|
+
user_obj["accessed_at"] = datetime.now()
|
133
|
+
|
134
|
+
if session.get("name"): # Set name & locale if available (Google Auth)
|
135
|
+
user_obj["name"] = session.get("name")
|
136
|
+
user_obj["locale"] = session.get("locale")
|
137
|
+
|
138
|
+
store.update_user(user_obj)
|
139
|
+
return perform_flask_login(LoginUser(user_obj))
|
140
|
+
|
141
|
+
|
142
|
+
def perform_flask_login(user_dict: "LoginUser") -> Response:
|
143
|
+
"""Login user using Flask-Login."""
|
144
|
+
if login_user(user_dict, remember=True):
|
145
|
+
flash(f"Welcome {user_dict.name}", "success")
|
146
|
+
return redirect(url_for("cases.index"))
|
147
|
+
|
148
|
+
flash("Sorry, you could not log in", "warning")
|
149
|
+
return redirect(url_for("public.index"))
|
150
|
+
|
151
|
+
|
152
|
+
def logout_oidc_user(session, provider: str):
|
153
|
+
"""Log out a user from an OIDC login provider-"""
|
154
|
+
logout_url = current_app.config[provider].get("logout_url")
|
155
|
+
if not logout_url or not session.get("token_response"):
|
156
|
+
return
|
157
|
+
refresh_token = session["token_response"]["refresh_token"]
|
158
|
+
requests.post(
|
159
|
+
logout_url,
|
160
|
+
data={
|
161
|
+
"client_id": current_app.config[provider]["client_id"],
|
162
|
+
"client_secret": current_app.config[provider]["client_secret"],
|
163
|
+
"refresh_token": refresh_token,
|
164
|
+
},
|
165
|
+
)
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
import logging
|
3
|
-
from
|
3
|
+
from typing import Optional
|
4
4
|
|
5
5
|
from flask import (
|
6
6
|
Blueprint,
|
7
|
+
Response,
|
7
8
|
current_app,
|
8
9
|
flash,
|
9
10
|
redirect,
|
@@ -45,82 +46,67 @@ def load_user(user_id):
|
|
45
46
|
|
46
47
|
@login_bp.route("/login", methods=["GET", "POST"])
|
47
48
|
@public_endpoint
|
48
|
-
def login():
|
49
|
+
def login() -> Response:
|
49
50
|
"""Login a user if they have access."""
|
50
51
|
|
51
|
-
if
|
52
|
-
|
53
|
-
flash(
|
54
|
-
"Logging user activity is a requirement for using this site and accessing your account. Without consent to activity logging, you will not be able to log in into Scout.",
|
55
|
-
"warning",
|
56
|
-
)
|
57
|
-
return redirect(url_for("public.index"))
|
58
|
-
session["consent_given"] = True
|
52
|
+
if controllers.user_has_consented(user_consent=request.form.get("consent_checkbox")) is False:
|
53
|
+
return redirect(url_for("public.index"))
|
59
54
|
|
60
|
-
user_id = None
|
61
|
-
user_mail = None
|
55
|
+
user_id: Optional[str] = None
|
56
|
+
user_mail: Optional[str] = None
|
62
57
|
|
63
58
|
if current_app.config.get("LDAP_HOST", current_app.config.get("LDAP_SERVER")):
|
64
|
-
|
65
|
-
request.form.get("ldap_user"), request.form.get("ldap_password")
|
59
|
+
user_id = controllers.ldap_login(
|
60
|
+
ldap_user=request.form.get("ldap_user"), ldap_password=request.form.get("ldap_password")
|
66
61
|
)
|
67
|
-
if
|
68
|
-
user_id = request.form.get("ldap_user")
|
69
|
-
else:
|
70
|
-
flash("User not authorized by LDAP server", "warning")
|
62
|
+
if user_id is None:
|
71
63
|
return redirect(url_for("public.index"))
|
72
64
|
|
73
65
|
elif current_app.config.get("GOOGLE"):
|
74
66
|
if session.get("email"):
|
75
67
|
user_mail = session["email"]
|
76
|
-
session.pop("email", None)
|
77
68
|
else:
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
LOG.info("Validating user %s email %s against Scout database", user_id, user_mail)
|
87
|
-
|
88
|
-
user_obj = store.user(email=user_mail, user_id=user_id)
|
89
|
-
if user_obj is None:
|
90
|
-
flash("User not found in Scout database", "warning")
|
91
|
-
return redirect(url_for("public.index"))
|
69
|
+
# Redirect to Google OAuth if not completed
|
70
|
+
return controllers.google_login()
|
71
|
+
|
72
|
+
elif current_app.config.get("KEYCLOAK"):
|
73
|
+
if session.get("email"):
|
74
|
+
user_mail = session["email"]
|
75
|
+
else:
|
76
|
+
return controllers.keycloak_login()
|
92
77
|
|
93
|
-
|
94
|
-
|
95
|
-
user_obj["name"] = session.get("name")
|
96
|
-
user_obj["locale"] = session.get("locale")
|
97
|
-
store.update_user(user_obj)
|
78
|
+
elif request.form.get("email"):
|
79
|
+
user_mail = controllers.database_login(user_mail=request.form.get("email"))
|
98
80
|
|
99
|
-
|
100
|
-
return perform_login(user_dict)
|
81
|
+
return controllers.validate_and_login_user(user_mail=user_mail, user_id=user_id)
|
101
82
|
|
102
83
|
|
103
84
|
@login_bp.route("/authorized")
|
104
85
|
@public_endpoint
|
105
86
|
def authorized():
|
106
|
-
"""
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
87
|
+
"""OIDC callback function."""
|
88
|
+
if current_app.config.get("GOOGLE"):
|
89
|
+
client = oauth_client.google
|
90
|
+
if current_app.config.get("KEYCLOAK"):
|
91
|
+
client = oauth_client.keycloak
|
92
|
+
token = client.authorize_access_token()
|
93
|
+
user = client.parse_id_token(token, None)
|
94
|
+
|
95
|
+
session["email"] = user.get("email").lower()
|
96
|
+
session["name"] = user.get("name")
|
97
|
+
session["locale"] = user.get("locale")
|
98
|
+
session["token_response"] = token
|
113
99
|
|
114
100
|
return redirect(url_for(".login"))
|
115
101
|
|
116
102
|
|
117
103
|
@login_bp.route("/logout")
|
118
104
|
def logout():
|
119
|
-
logout_user()
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
session.
|
105
|
+
logout_user() # logs out user from scout
|
106
|
+
for provider in ["GOOGLE", "KEYCLOAK"]:
|
107
|
+
if current_app.config.get(provider):
|
108
|
+
controllers.logout_oidc_user(session, provider)
|
109
|
+
session.clear()
|
124
110
|
flash("you logged out", "success")
|
125
111
|
return redirect(url_for("public.index"))
|
126
112
|
|
@@ -130,11 +116,3 @@ def users():
|
|
130
116
|
"""Show all users in the system."""
|
131
117
|
data = controllers.users(store)
|
132
118
|
return render_template("login/users.html", **data)
|
133
|
-
|
134
|
-
|
135
|
-
def perform_login(user_dict):
|
136
|
-
if login_user(user_dict, remember=True):
|
137
|
-
flash("you logged in as: {}".format(user_dict.name), "success")
|
138
|
-
return redirect(url_for("cases.index"))
|
139
|
-
flash("sorry, you could not log in", "warning")
|
140
|
-
return redirect(url_for("public.index"))
|
@@ -21,7 +21,11 @@
|
|
21
21
|
</p>
|
22
22
|
{% else %}
|
23
23
|
<form class="row" method="POST" action="{{ url_for('login.login') }}">
|
24
|
-
{% if config.
|
24
|
+
{% if config.KEYCLOAK %}
|
25
|
+
<button name="googleLogin" class="btn btn-primary btn-lg text-white" href="{{ url_for('login.login') }}" type="submit">
|
26
|
+
Login with Keycloak
|
27
|
+
</button>
|
28
|
+
{% elif config.GOOGLE %}
|
25
29
|
<button name="googleLogin" class="btn btn-primary btn-lg text-white" href="{{ url_for('login.login') }}" type="submit">
|
26
30
|
Login with Google
|
27
31
|
</button>
|
@@ -369,7 +369,7 @@ def variant(
|
|
369
369
|
|
370
370
|
overlapping_vars = []
|
371
371
|
if get_overlapping:
|
372
|
-
for var in store.
|
372
|
+
for var in store.hgnc_overlapping(variant_obj):
|
373
373
|
var.update(predictions(var.get("genes", [])))
|
374
374
|
overlapping_vars.append(var)
|
375
375
|
variant_obj["end_chrom"] = variant_obj.get("end_chrom", variant_obj["chromosome"])
|
@@ -79,7 +79,11 @@
|
|
79
79
|
<div class="row">
|
80
80
|
<select {{ 'disabled' if evaluation and edit is false}} id="modifier-{{ criterion_code }}" name="modifier-{{ criterion_code }}" class="form-control form-select">
|
81
81
|
<option value="" {% if not modifier %}selected{% endif %}>Strength modifier...</option>
|
82
|
-
{%
|
82
|
+
{% set sa_level = "Stand-alone" %}
|
83
|
+
{% if category == "pathogenicity" %}
|
84
|
+
{% set sa_level = "Very Strong" %}
|
85
|
+
{% endif %}
|
86
|
+
{% for level in sa_level, "Strong", "Moderate", "Supporting" %}
|
83
87
|
{% if(level != evidence) %}
|
84
88
|
<option id="{{ criterion_code }}-{{ level }}" value="{{ level }}" {% if modifier == level %}selected{% endif %}>{{ level }}</option>
|
85
89
|
{% endif %}
|
@@ -148,7 +152,7 @@
|
|
148
152
|
|
149
153
|
function update_classification() {
|
150
154
|
var criteria = $(':checked').map(function(idx, elem) {
|
151
|
-
const modifiers = ["Strong", "Moderate", "Supporting"];
|
155
|
+
const modifiers = ["Stand-alone", "Very Strong", "Strong", "Moderate", "Supporting"];
|
152
156
|
for (possible_modifier of modifiers) {
|
153
157
|
var modifier_option;
|
154
158
|
if(elem.value !== null && elem.value !== '') {
|
@@ -32,6 +32,25 @@
|
|
32
32
|
</tbody>
|
33
33
|
</table>
|
34
34
|
{% endif %}
|
35
|
+
|
36
|
+
{% if variant.clnsig_onc %}
|
37
|
+
<table id='clinsig_table' class="table table-bordered table-sm">
|
38
|
+
<thead>
|
39
|
+
<th>Oncogenicity</th>
|
40
|
+
<th>Disease name</th>
|
41
|
+
<th>Revstat</th>
|
42
|
+
</thead>
|
43
|
+
<tbody>
|
44
|
+
{% for classif in variant.clnsig_onc %}
|
45
|
+
<tr>
|
46
|
+
<td>{{ classif.value|capitalize }}</td>
|
47
|
+
<td>{{ classif.dn|replace("_", " ")|replace(",", ", ") }}</td>
|
48
|
+
<td>{{ classif.revstat }}</td>
|
49
|
+
</tr>
|
50
|
+
{% endfor %}
|
51
|
+
</tbody>
|
52
|
+
</table>
|
53
|
+
{% endif %}
|
35
54
|
{% endmacro %}
|
36
55
|
|
37
56
|
{% macro acmg_classification_item(variant, data) %}
|
@@ -118,10 +118,10 @@
|
|
118
118
|
|
119
119
|
{% macro overlapping_panel(variant, overlapping_vars, case, institute) %}
|
120
120
|
<div class="card panel-default">
|
121
|
-
{% if variant.category
|
122
|
-
<div class="panel-heading">
|
121
|
+
{% if variant.category != "snv" %}
|
122
|
+
<div class="panel-heading">Gene overlapping variants</div>
|
123
123
|
{% else %}
|
124
|
-
<div class="panel-heading">
|
124
|
+
<div class="panel-heading">Gene overlapping non-SNVs</div>
|
125
125
|
{% endif %}
|
126
126
|
<div class="card-body">
|
127
127
|
<div class="table-responsive">
|
@@ -137,7 +137,7 @@ def variants(
|
|
137
137
|
|
138
138
|
variants = []
|
139
139
|
for variant_obj in variant_res:
|
140
|
-
overlapping_svs = list(store.
|
140
|
+
overlapping_svs = list(store.hgnc_overlapping(variant_obj))
|
141
141
|
variant_obj["overlapping"] = overlapping_svs or None
|
142
142
|
|
143
143
|
evaluations = []
|
@@ -233,6 +233,9 @@ def sv_variants(store, institute_obj, case_obj, variants_query, variant_count, p
|
|
233
233
|
case_dismissed_vars = store.case_dismissed_variants(institute_obj, case_obj)
|
234
234
|
|
235
235
|
for variant_obj in variants_query.skip(skip_count).limit(per_page):
|
236
|
+
overlapping_svs = list(store.hgnc_overlapping(variant_obj))
|
237
|
+
variant_obj["overlapping"] = overlapping_svs or None
|
238
|
+
|
236
239
|
# show previous classifications for research variants
|
237
240
|
clinical_var_obj = variant_obj
|
238
241
|
if variant_obj["variant_type"] == "research":
|
@@ -279,6 +282,9 @@ def mei_variants(
|
|
279
282
|
case_dismissed_vars = store.case_dismissed_variants(institute_obj, case_obj)
|
280
283
|
|
281
284
|
for variant_obj in variants_query.skip(skip_count).limit(per_page):
|
285
|
+
overlapping = list(store.hgnc_overlapping(variant_obj))
|
286
|
+
variant_obj["overlapping"] = overlapping or None
|
287
|
+
|
282
288
|
# show previous classifications for research variants
|
283
289
|
clinical_var_obj = variant_obj
|
284
290
|
if variant_obj["variant_type"] == "research":
|
@@ -1448,6 +1454,9 @@ def cancer_variants(store, institute_id, case_name, variants_query, variant_coun
|
|
1448
1454
|
variant_obj["second_rep_gene"] = secondary_gene
|
1449
1455
|
variant_obj["clinical_assessments"] = get_manual_assessments(variant_obj)
|
1450
1456
|
|
1457
|
+
overlapping = list(store.hgnc_overlapping(variant_obj))
|
1458
|
+
variant_obj["overlapping"] = overlapping or None
|
1459
|
+
|
1451
1460
|
evaluations = []
|
1452
1461
|
# Get previous ClinGen-CGC-VIGG evaluations of the variant from other cases
|
1453
1462
|
for evaluation_obj in store.get_ccv_evaluations(variant_obj):
|