scout-browser 4.100.2__py3-none-any.whl → 4.102.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.
Files changed (52) hide show
  1. scout/adapter/mongo/case.py +20 -6
  2. scout/adapter/mongo/clinvar.py +7 -7
  3. scout/adapter/mongo/hgnc.py +7 -2
  4. scout/adapter/mongo/omics_variant.py +8 -0
  5. scout/adapter/mongo/variant_loader.py +6 -2
  6. scout/constants/__init__.py +1 -0
  7. scout/constants/file_types.py +1 -1
  8. scout/constants/igv_tracks.py +6 -2
  9. scout/constants/phenotype.py +1 -0
  10. scout/load/hpo.py +8 -2
  11. scout/server/blueprints/alignviewers/controllers.py +8 -6
  12. scout/server/blueprints/alignviewers/templates/alignviewers/igv_viewer.html +2 -0
  13. scout/server/blueprints/alignviewers/templates/alignviewers/utils.html +1 -1
  14. scout/server/blueprints/cases/controllers.py +56 -28
  15. scout/server/blueprints/cases/templates/cases/case_report.html +9 -88
  16. scout/server/blueprints/cases/templates/cases/matchmaker.html +1 -1
  17. scout/server/blueprints/cases/templates/cases/phenotype.html +1 -1
  18. scout/server/blueprints/cases/templates/cases/utils.html +27 -25
  19. scout/server/blueprints/cases/views.py +32 -33
  20. scout/server/blueprints/clinvar/controllers.py +18 -23
  21. scout/server/blueprints/clinvar/templates/clinvar/multistep_add_variant.html +0 -2
  22. scout/server/blueprints/diagnoses/controllers.py +4 -8
  23. scout/server/blueprints/diagnoses/templates/diagnoses/diagnoses.html +1 -1
  24. scout/server/blueprints/diagnoses/templates/diagnoses/disease_term.html +1 -1
  25. scout/server/blueprints/diagnoses/views.py +2 -2
  26. scout/server/blueprints/institutes/controllers.py +107 -73
  27. scout/server/blueprints/institutes/templates/overview/cases.html +8 -7
  28. scout/server/blueprints/login/controllers.py +2 -1
  29. scout/server/blueprints/login/views.py +5 -2
  30. scout/server/blueprints/omics_variants/views.py +2 -2
  31. scout/server/blueprints/panels/views.py +11 -2
  32. scout/server/blueprints/phenotypes/controllers.py +15 -2
  33. scout/server/blueprints/phenotypes/templates/phenotypes/hpo_terms.html +1 -1
  34. scout/server/blueprints/variant/controllers.py +10 -11
  35. scout/server/blueprints/variant/templates/variant/utils.html +1 -1
  36. scout/server/blueprints/variant/templates/variant/variant_details.html +68 -60
  37. scout/server/blueprints/variant/utils.py +25 -0
  38. scout/server/blueprints/variants/controllers.py +11 -42
  39. scout/server/blueprints/variants/templates/variants/cancer-variants.html +3 -3
  40. scout/server/blueprints/variants/templates/variants/indicators.html +18 -15
  41. scout/server/blueprints/variants/templates/variants/utils.html +1 -1
  42. scout/server/blueprints/variants/views.py +9 -8
  43. scout/server/config.py +3 -0
  44. scout/server/extensions/beacon_extension.py +7 -2
  45. scout/server/templates/bootstrap_global.html +11 -1
  46. scout/server/templates/layout.html +6 -1
  47. scout/server/utils.py +24 -3
  48. {scout_browser-4.100.2.dist-info → scout_browser-4.102.0.dist-info}/METADATA +1 -1
  49. {scout_browser-4.100.2.dist-info → scout_browser-4.102.0.dist-info}/RECORD +52 -52
  50. {scout_browser-4.100.2.dist-info → scout_browser-4.102.0.dist-info}/WHEEL +0 -0
  51. {scout_browser-4.100.2.dist-info → scout_browser-4.102.0.dist-info}/entry_points.txt +0 -0
  52. {scout_browser-4.100.2.dist-info → scout_browser-4.102.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import re
2
3
  from typing import Dict, List, Optional, Tuple
3
4
 
4
5
  from scout.adapter import MongoAdapter
@@ -683,3 +684,27 @@ def associate_variant_genes_with_case_panels(case_obj: Dict, variant_obj: Dict)
683
684
  geneid_gene[hgnc_id]["associated_gene_panels"] = matching_panels
684
685
 
685
686
  variant_obj["genes"] = list(geneid_gene.values())
687
+
688
+
689
+ def get_str_mc(variant_obj: dict) -> Optional[int]:
690
+ """Return variant Short Tandem Repeat motif count, either as given by its ALT MC value
691
+ from the variant FORMAT field, or as a number given in the ALT on the form
692
+ '<STR123>'.
693
+ """
694
+ NUM = re.compile(r"\d+")
695
+
696
+ alt_mc = None
697
+ if variant_obj["alternative"] == ".":
698
+ return alt_mc
699
+
700
+ for sample in variant_obj["samples"]:
701
+ if sample["genotype_call"] in ["./.", ".|", "0/0", "0|0"]:
702
+ continue
703
+ alt_mc = sample.get("alt_mc")
704
+ if alt_mc:
705
+ return alt_mc
706
+
707
+ alt_num = NUM.search(variant_obj["alternative"])
708
+ if alt_num:
709
+ alt_mc = int(alt_num.group())
710
+ return alt_mc
@@ -35,6 +35,7 @@ from scout.server.blueprints.variant.utils import (
35
35
  clinsig_human,
36
36
  get_callers,
37
37
  get_filters,
38
+ get_str_mc,
38
39
  predictions,
39
40
  update_representative_gene,
40
41
  update_variant_case_panels,
@@ -45,6 +46,7 @@ from scout.server.links import add_gene_links, cosmic_links, str_source_link
45
46
  from scout.server.utils import (
46
47
  case_has_alignments,
47
48
  case_has_mt_alignments,
49
+ get_case_genome_build,
48
50
  institute_and_case,
49
51
  user_institutes,
50
52
  )
@@ -57,8 +59,6 @@ from .forms import (
57
59
  )
58
60
  from .utils import update_case_panels
59
61
 
60
- NUM = re.compile(r"\d+")
61
-
62
62
  LOG = logging.getLogger(__name__)
63
63
 
64
64
 
@@ -106,7 +106,7 @@ def populate_persistent_filters_choices(
106
106
  def populate_chrom_choices(form, case_obj):
107
107
  """Populate the option of the chromosome select according to the case genome build"""
108
108
  # Populate chromosome choices
109
- chromosomes = CHROMOSOMES if "37" in str(case_obj.get("genome_build")) else CHROMOSOMES_38
109
+ chromosomes = CHROMOSOMES if get_case_genome_build(case_obj) == "37" else CHROMOSOMES_38
110
110
  form.chrom.choices = [(chrom, chrom) for chrom in chromosomes]
111
111
 
112
112
 
@@ -139,9 +139,7 @@ def variants(
139
139
  more_variants = variant_count > (skip_count + per_page)
140
140
  variant_res = variants_query.skip(skip_count).limit(per_page)
141
141
 
142
- genome_build = str(case_obj.get("genome_build", "37"))
143
- if genome_build not in ["37", "38"]:
144
- genome_build = "37"
142
+ genome_build = get_case_genome_build(case_obj)
145
143
 
146
144
  case_dismissed_vars = store.case_dismissed_variants(institute_obj, case_obj)
147
145
 
@@ -237,9 +235,7 @@ def sv_variants(store, institute_obj, case_obj, variants_query, variant_count, p
237
235
 
238
236
  more_variants = variant_count > (skip_count + per_page)
239
237
  variants = []
240
- genome_build = str(case_obj.get("genome_build", "37"))
241
- if genome_build not in ["37", "38"]:
242
- genome_build = "37"
238
+ genome_build = get_case_genome_build(case_obj)
243
239
 
244
240
  case_dismissed_vars = store.case_dismissed_variants(institute_obj, case_obj)
245
241
 
@@ -285,9 +281,7 @@ def mei_variants(
285
281
 
286
282
  more_variants = variant_count > (skip_count + per_page)
287
283
  variants = []
288
- genome_build = str(case_obj.get("genome_build", "37"))
289
- if genome_build not in ["37", "38"]:
290
- genome_build = "37"
284
+ genome_build = get_case_genome_build(case_obj)
291
285
 
292
286
  case_dismissed_vars = store.case_dismissed_variants(institute_obj, case_obj)
293
287
 
@@ -353,9 +347,7 @@ def fusion_variants(
353
347
 
354
348
  more_variants = variant_count > (skip_count + per_page)
355
349
  variants = []
356
- genome_build = str(case_obj.get("genome_build", "38"))
357
- if genome_build not in ["37", "38"]:
358
- genome_build = "38"
350
+ genome_build = get_case_genome_build(case_obj)
359
351
 
360
352
  case_dismissed_vars = store.case_dismissed_variants(institute_obj, case_obj)
361
353
 
@@ -997,28 +989,6 @@ def parse_variant(
997
989
  return variant_obj
998
990
 
999
991
 
1000
- def get_str_mc(variant_obj: dict) -> Optional[int]:
1001
- """Return variant Short Tandem Repeat motif count, either as given by its ALT MC value
1002
- from the variant FORMAT field, or as a number given in the ALT on the form
1003
- '<STR123>'.
1004
- """
1005
- alt_mc = None
1006
- if variant_obj["alternative"] == ".":
1007
- return alt_mc
1008
-
1009
- for sample in variant_obj["samples"]:
1010
- if sample["genotype_call"] in ["./.", ".|", "0/0", "0|0"]:
1011
- continue
1012
- alt_mc = sample.get("alt_mc")
1013
- if alt_mc:
1014
- return alt_mc
1015
-
1016
- alt_num = NUM.search(variant_obj["alternative"])
1017
- if alt_num:
1018
- alt_mc = int(alt_num.group())
1019
- return alt_mc
1020
-
1021
-
1022
992
  def download_str_variants(case_obj, variant_objs):
1023
993
  """Download filtered STR variants for a case to a CSV file
1024
994
 
@@ -1502,7 +1472,9 @@ def upload_panel(store, institute_id, case_name, stream):
1502
1472
  hgnc_symbols = set()
1503
1473
  for raw_symbol in raw_symbols:
1504
1474
  matching_genes = list(
1505
- store.gene_by_symbol_or_aliases(symbol=raw_symbol, build=case_obj.get("genome_build"))
1475
+ store.gene_by_symbol_or_aliases(
1476
+ symbol=raw_symbol, build=get_case_genome_build(case_obj)
1477
+ )
1506
1478
  )
1507
1479
  if not matching_genes:
1508
1480
  flash("HGNC symbol not found: {}".format(raw_symbol), "warning")
@@ -1983,10 +1955,7 @@ def update_form_hgnc_symbols(store, case_obj, form):
1983
1955
  genome_build = None
1984
1956
  case_obj = case_obj or {}
1985
1957
 
1986
- for build in ["37", "38"]:
1987
- if build in str(case_obj.get("genome_build", "")):
1988
- genome_build = build
1989
- break
1958
+ genome_build = get_case_genome_build(case_obj)
1990
1959
 
1991
1960
  # retrieve current symbols from form
1992
1961
  if form.hgnc_symbols.data:
@@ -2,7 +2,7 @@
2
2
 
3
3
  {% from "variants/components.html" import allele_cell, external_scripts, external_stylesheets, gene_cell, frequency_cell_general, observed_cell_general, variant_funct_anno_cell %}
4
4
  {% from "variants/utils.html" import cancer_filters, cell_rank, pagination_footer, pagination_hidden_div, dismiss_variants_block, filter_form_footer, filter_script_main, update_stash_filter_button_status, callers_cell %}
5
- {% from "variants/indicators.html" import pin_indicator, causative_badge, clinical_assessments_badge, comments_badge, dismissals_badge, evaluations_badge, ccv_evaluations_badge, group_assessments_badge, matching_manual_rank, other_tiered_variants, research_assessments_badge %}
5
+ {% from "variants/indicators.html" import pin_indicator, causative_badge, clinical_assessments_badge, comments_badge, dismissals_badge, evaluations_badge, group_assessments_badge, matching_manual_rank, other_tiered_variants, research_assessments_badge %}
6
6
 
7
7
  {% block title %}
8
8
  {{ variant_type|capitalize }} somatic variants
@@ -101,7 +101,7 @@
101
101
  </a>
102
102
  </td>
103
103
  <td>
104
- {{ evaluations_badge(variant) }}
104
+ {{ evaluations_badge(variant.evaluations) }}
105
105
  {{ dismissals_badge(variant) }}
106
106
  {{ matching_manual_rank(variant) }}
107
107
  {{ research_assessments_badge(variant) }}
@@ -110,7 +110,7 @@
110
110
  {{ comments_badge(case, institute, variant) }}
111
111
  {{ causative_badge(variant, case) }}
112
112
  {{ other_tiered_variants(variant) }}
113
- {{ ccv_evaluations_badge(variant) }}
113
+ {{ evaluations_badge(variant.ccv_evaluations) }}
114
114
  </td>
115
115
  <td>{{ rank_cell(variant) }}</td>
116
116
  <td>{{ cadd_cell(variant) }}</td>
@@ -61,23 +61,26 @@
61
61
  {% endif %}
62
62
  {% endmacro %}
63
63
 
64
- {% macro evaluations_badge(variant) %}
65
- {% if variant.evaluations %}
66
- {% for evaluation in (variant.evaluations or []) %}
67
- <span class="badge bg-secondary" style="margin-left:1px" data-bs-toggle="tooltip" data-bs-placement="right"
68
- title="Previously classified as {{ evaluation.classification.label }}">
69
- {{ evaluation.classification.short }}
70
- </span>
64
+ {% macro evaluations_badge(evaluations) %}
65
+ {% if evaluations %}
66
+ {% set classification_counts = {} %}
67
+ {% for evaluation in evaluations %}
68
+ {% set c = evaluation.classification if evaluation.classification is defined else evaluation.ccv_classification %}
69
+ {% if c %}
70
+ {% set short = c.short %}
71
+ {% set label = c.label %}
72
+ {% set current = classification_counts.get(short, {'count': 0, 'label': label}) %}
73
+ {% set _ = classification_counts.update({short: {
74
+ 'count': current.count + 1,
75
+ 'label': label
76
+ }}) %}
77
+ {% endif %}
71
78
  {% endfor %}
72
- {% endif %}
73
- {% endmacro %}
74
79
 
75
- {% macro ccv_evaluations_badge(variant) %}
76
- {% if variant.ccv_evaluations %}
77
- {% for evaluation in (variant.ccv_evaluations or []) %}
78
- <span class="badge bg-secondary" style="margin-left:1px" data-bs-toggle="tooltip" data-bs-placement="right"
79
- title="Previously classified as {{ evaluation.ccv_classification.label }}">
80
- {{ evaluation.ccv_classification.short }}
80
+ {% for short, data in classification_counts.items() | sort(attribute='1.count', reverse=true) %}
81
+ <span class="badge bg-secondary ms-1" data-bs-toggle="tooltip" data-bs-placement="right"
82
+ title="Previously classified as {{ data.label }} ({{ data.count }} times)">
83
+ {{ short }} ×{{ data.count }}
81
84
  </span>
82
85
  {% endfor %}
83
86
  {% endif %}
@@ -1118,7 +1118,7 @@
1118
1118
 
1119
1119
  {{ variant.variant_rank }}</a>&nbsp;
1120
1120
 
1121
- {{ evaluations_badge(variant) }}
1121
+ {{ evaluations_badge(variant.evaluations) }}
1122
1122
  {{ research_assessments_badge(variant) }}
1123
1123
  {{ clinical_assessments_badge(variant) }}
1124
1124
  {{ group_assessments_badge(variant) }}
@@ -19,7 +19,7 @@ from scout.constants import (
19
19
  SEVERE_SO_TERMS_SV,
20
20
  )
21
21
  from scout.server.extensions import store
22
- from scout.server.utils import institute_and_case, templated
22
+ from scout.server.utils import get_case_genome_build, institute_and_case, templated
23
23
 
24
24
  from . import controllers
25
25
  from .forms import (
@@ -129,7 +129,7 @@ def variants(institute_id, case_name):
129
129
 
130
130
  controllers.update_form_hgnc_symbols(store, case_obj, form)
131
131
 
132
- genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
132
+ genome_build = get_case_genome_build(case_obj)
133
133
  cytobands = store.cytoband_by_chrom(genome_build)
134
134
 
135
135
  variants_query = store.variants(
@@ -220,7 +220,7 @@ def str_variants(institute_id, case_name):
220
220
 
221
221
  controllers.activate_case(store, institute_obj, case_obj, current_user)
222
222
 
223
- genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
223
+ genome_build = get_case_genome_build(case_obj)
224
224
  cytobands = store.cytoband_by_chrom(genome_build)
225
225
 
226
226
  query = form.data
@@ -231,6 +231,7 @@ def str_variants(institute_id, case_name):
231
231
  ).sort(
232
232
  [
233
233
  ("str_repid", pymongo.ASCENDING),
234
+ ("str_trid", pymongo.ASCENDING),
234
235
  ("chromosome", pymongo.ASCENDING),
235
236
  ("position", pymongo.ASCENDING),
236
237
  ]
@@ -302,7 +303,7 @@ def sv_variants(institute_id, case_name):
302
303
  # Populate custom soft filters
303
304
  controllers.populate_institute_soft_filters(form=form, institute_obj=institute_obj)
304
305
 
305
- genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
306
+ genome_build = get_case_genome_build(case_obj)
306
307
  cytobands = store.cytoband_by_chrom(genome_build)
307
308
 
308
309
  controllers.update_form_hgnc_symbols(store, case_obj, form)
@@ -397,7 +398,7 @@ def mei_variants(institute_id, case_name):
397
398
  # populate available panel choices
398
399
  form.gene_panels.choices = controllers.gene_panel_choices(store, institute_obj, case_obj)
399
400
 
400
- genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
401
+ genome_build = get_case_genome_build(case_obj)
401
402
  cytobands = store.cytoband_by_chrom(genome_build)
402
403
 
403
404
  controllers.update_form_hgnc_symbols(store, case_obj, form)
@@ -509,7 +510,7 @@ def cancer_variants(institute_id, case_name):
509
510
 
510
511
  form.gene_panels.choices = controllers.gene_panel_choices(store, institute_obj, case_obj)
511
512
 
512
- genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
513
+ genome_build = get_case_genome_build(case_obj)
513
514
  cytobands = store.cytoband_by_chrom(genome_build)
514
515
 
515
516
  controllers.update_form_hgnc_symbols(store, case_obj, form)
@@ -587,7 +588,7 @@ def cancer_sv_variants(institute_id, case_name):
587
588
  # Populate custom soft filters
588
589
  controllers.populate_institute_soft_filters(form=form, institute_obj=institute_obj)
589
590
 
590
- genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
591
+ genome_build = get_case_genome_build(case_obj)
591
592
  cytobands = store.cytoband_by_chrom(genome_build)
592
593
 
593
594
  controllers.update_form_hgnc_symbols(store, case_obj, form)
@@ -671,7 +672,7 @@ def fusion_variants(institute_id, case_name):
671
672
  # Populate custom soft filters
672
673
  controllers.populate_institute_soft_filters(form=form, institute_obj=institute_obj)
673
674
 
674
- genome_build = "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
675
+ genome_build = get_case_genome_build(case_obj)
675
676
  cytobands = store.cytoband_by_chrom(genome_build)
676
677
 
677
678
  controllers.update_form_hgnc_symbols(store, case_obj, form)
scout/server/config.py CHANGED
@@ -1,6 +1,9 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  SECRET_KEY = "this is not secret..."
3
3
  REMEMBER_COOKIE_NAME = "scout_remember_me" # Prevent session timeout when user closes browser
4
+ # INSTANCE_NAME = "Development" # Name will be displayed on the top navigation menu
5
+ # INSTANCE_COLOR = "#800000" # Color of the top navigation menu
6
+
4
7
  # SESSION_TIMEOUT_MINUTES = 60 # Minutes of inactivity before session times out
5
8
 
6
9
  # MONGO_URI = "mongodb://127.0.0.1:27011,127.0.0.1:27012,127.0.0.1:27013/?replicaSet=rs0&readPreference=primary"
@@ -8,7 +8,12 @@ import logging
8
8
  from flask import flash, url_for
9
9
  from flask_login import current_user
10
10
 
11
- from scout.utils.scout_requests import delete_request_json, get_request_json, post_request_json
11
+ from scout.server.utils import get_case_genome_build
12
+ from scout.utils.scout_requests import (
13
+ delete_request_json,
14
+ get_request_json,
15
+ post_request_json,
16
+ )
12
17
 
13
18
  LOG = logging.getLogger(__name__)
14
19
  DATASET_BUILDS = ["GRCh37", "GRCh38"]
@@ -58,7 +63,7 @@ class Beacon:
58
63
  data(dict): a dictionary with base info to be used as json data in beacon add request (lacks path to VCF file to extract variants from)
59
64
  """
60
65
  # Initialize key/values to be sent in request:
61
- assembly = "GRCh38" if "38" in str(case_obj.get("genome_build", "37")) else "GRCh37"
66
+ assembly = "GRCh38" if get_case_genome_build(case_obj) == "38" else "GRCh37"
62
67
  dataset_id = "_".join([case_obj["owner"], case_obj.get("build", assembly)])
63
68
 
64
69
  samples = []
@@ -12,7 +12,17 @@
12
12
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/css/bootstrap-dark.min.css" integrity="sha384-pZAJcuaxKZEGkzXV5bYqUcSwBfMZPdQS/+JXdYOu9ScyZJMnGHD5Xi6HVHfZuULH" crossorigin="anonymous">
13
13
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
14
14
  <link rel="stylesheet" href="{{ url_for('static', filename='bs_styles.css') }}">
15
- {% endblock %}
15
+
16
+ {% if config.INSTANCE_COLOR %}
17
+ <style>
18
+ /* Override Scout's bg-primary with color set on app config, if available */
19
+ nav.navbar.bg-primary {
20
+ background-color: {{ config.INSTANCE_COLOR }} !important;
21
+ }
22
+ </style>
23
+ {% endif %}
24
+
25
+ {% endblock %}
16
26
 
17
27
  {% block css_style %}
18
28
  {% endblock %}
@@ -18,9 +18,14 @@
18
18
 
19
19
  {% block navbar %}
20
20
  <nav class="navbar navbar-expand-lg nav-pills navbar-dark bg-primary sticky-top" >
21
- <div class="container-fluid bg-primary rounded-bottom">
21
+ <div class="container-fluid rounded-bottom">
22
22
  <a class="navbar-brand" href="{{ url_for('cases.index') }}">
23
23
  <img src="{{ url_for('public.static', filename='scout-logo.png') }}" width="30" height="30" class="d-inline-block align-top text-white" alt="">Scout</a>
24
+ {% if config.INSTANCE_NAME %}
25
+ <span class="badge bg-light text-dark">
26
+ {{ config.INSTANCE_NAME }}
27
+ </span>
28
+ {% endif %}
24
29
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
25
30
  <span class="navbar-toggler-icon"></span>
26
31
  </button>
scout/server/utils.py CHANGED
@@ -8,11 +8,21 @@ import zipfile
8
8
  from functools import wraps
9
9
  from io import BytesIO
10
10
  from typing import Dict, Optional, Tuple
11
+ from urllib.parse import urlparse
11
12
 
12
13
  import pdfkit
13
14
  from bson.objectid import ObjectId
14
- from flask import abort, current_app, flash, render_template, request
15
+ from flask import (
16
+ Response,
17
+ abort,
18
+ current_app,
19
+ flash,
20
+ redirect,
21
+ render_template,
22
+ request,
23
+ )
15
24
  from flask_login import current_user
25
+ from werkzeug.local import LocalProxy
16
26
 
17
27
  LOG = logging.getLogger(__name__)
18
28
 
@@ -102,6 +112,17 @@ def public_endpoint(function):
102
112
  return function
103
113
 
104
114
 
115
+ def safe_redirect_back(request: LocalProxy, link: Optional[str] = None) -> Response:
116
+ """Safely redirects the user back to the referring URL, if it originates from the same host.
117
+ Otherwise, the user is redirected to a default '/'."""
118
+ referrer = request.referrer
119
+ if referrer:
120
+ parsed_referrer = urlparse(referrer)
121
+ if parsed_referrer.netloc == request.host:
122
+ return redirect(link or referrer)
123
+ return redirect("/")
124
+
125
+
105
126
  def variant_institute_and_case(
106
127
  store, variant_obj: dict, institute_id: Optional[str], case_name: Optional[str]
107
128
  ) -> Tuple[dict, dict]:
@@ -145,7 +166,7 @@ def institute_and_case(store, institute_id, case_name=None):
145
166
  return abort(404)
146
167
 
147
168
  # Make sure build is either "37" or "38"
148
- case_obj["genome_build"] = "38" if "38" in str(case_obj.get("genome_build")) else "37"
169
+ case_obj["genome_build"] = get_case_genome_build(case_obj)
149
170
 
150
171
  # validate that user has access to the institute
151
172
 
@@ -208,7 +229,7 @@ def user_institutes(store, login_user):
208
229
 
209
230
  def get_case_genome_build(case_obj: dict) -> str:
210
231
  """returns the genome build of a case, as a string."""
211
- return "38" if "38" in case_obj.get("genome_build", "37") else "37"
232
+ return "38" if "38" in str(case_obj.get("genome_build", "37")) else "37"
212
233
 
213
234
 
214
235
  def get_case_mito_chromosome(case_obj: dict) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scout-browser
3
- Version: 4.100.2
3
+ Version: 4.102.0
4
4
  Summary: Clinical DNA variant visualizer and browser
5
5
  Project-URL: Repository, https://github.com/Clinical-Genomics/scout
6
6
  Project-URL: Changelog, https://github.com/Clinical-Genomics/scout/blob/main/CHANGELOG.md