Skip to content

Commit 29f2923

Browse files
author
Dmytro Trotsko
committed
Added seprate model for the expess interface indicators
1 parent 932479f commit 29f2923

File tree

7 files changed

+209
-29
lines changed

7 files changed

+209
-29
lines changed

src/alternative_interface/admin.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
11
from django.contrib import admin
22

3-
# Register your models here.
3+
from import_export.admin import ImportExportModelAdmin
4+
5+
from alternative_interface.models import ExpressViewIndicator
6+
from alternative_interface.resources import ExpressViewIndicatorResource
7+
8+
9+
@admin.register(ExpressViewIndicator)
10+
class ExpressViewIndicatorAdmin(ImportExportModelAdmin):
11+
resource_class = ExpressViewIndicatorResource
12+
list_display = ["menu_item", "indicator", "display_name"]
13+
search_fields = ["menu_item", "indicator", "display_name"]
14+
list_filter = ["menu_item", "indicator"]
15+
ordering = ["menu_item", "indicator"]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Generated by Django 5.2.5 on 2025-11-28 18:20
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
initial = True
10+
11+
dependencies = [
12+
("indicators", "0008_alter_indicator_source_type"),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name="ExpressViewIndicator",
18+
fields=[
19+
(
20+
"id",
21+
models.BigAutoField(
22+
auto_created=True,
23+
primary_key=True,
24+
serialize=False,
25+
verbose_name="ID",
26+
),
27+
),
28+
(
29+
"menu_item",
30+
models.CharField(max_length=255, verbose_name="Menu Item"),
31+
),
32+
(
33+
"display_name",
34+
models.CharField(max_length=255, verbose_name="Display Name"),
35+
),
36+
(
37+
"indicator",
38+
models.ForeignKey(
39+
on_delete=django.db.models.deletion.CASCADE,
40+
to="indicators.indicator",
41+
verbose_name="Indicator",
42+
),
43+
),
44+
],
45+
options={
46+
"verbose_name": "Express View Indicator",
47+
"verbose_name_plural": "Express View Indicators",
48+
"ordering": ["menu_item", "indicator"],
49+
"indexes": [
50+
models.Index(
51+
fields=["menu_item", "indicator"], name="expr_view_ind_menu_idx"
52+
)
53+
],
54+
"constraints": [
55+
models.UniqueConstraint(
56+
fields=("menu_item", "indicator"),
57+
name="uniq_expr_view_menu_ind",
58+
)
59+
],
60+
},
61+
),
62+
]
Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,37 @@
11
from django.db import models
22

3-
# Create your models here.
3+
4+
class ExpressViewIndicator(models.Model):
5+
menu_item = models.CharField(
6+
verbose_name="Menu Item",
7+
max_length=255,
8+
)
9+
indicator = models.ForeignKey(
10+
"indicators.Indicator",
11+
verbose_name="Indicator",
12+
on_delete=models.PROTECT,
13+
)
14+
display_name = models.CharField(
15+
verbose_name="Display Name",
16+
max_length=255,
17+
)
18+
19+
class Meta:
20+
verbose_name = "Express View Indicator"
21+
verbose_name_plural = "Express View Indicators"
22+
ordering = ["menu_item", "indicator"]
23+
indexes = [
24+
models.Index(
25+
fields=["menu_item", "indicator"],
26+
name="expr_view_ind_menu_idx",
27+
),
28+
]
29+
constraints = [
30+
models.UniqueConstraint(
31+
fields=["menu_item", "indicator"],
32+
name="uniq_expr_view_menu_ind",
33+
),
34+
]
35+
36+
def __str__(self):
37+
return self.display_name
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from import_export import resources
2+
from import_export.fields import Field
3+
from import_export.widgets import ForeignKeyWidget
4+
from indicators.models import Indicator
5+
6+
from alternative_interface.models import ExpressViewIndicator
7+
8+
9+
def process_indicator(row):
10+
indicator_source = row.get("Indicator Source")
11+
indicator_name = row.get("Indicator Name")
12+
indicator = None
13+
if indicator_name and indicator_source:
14+
try:
15+
indicator = Indicator.objects.get(
16+
name=indicator_name, source__name=indicator_source
17+
)
18+
except Indicator.DoesNotExist:
19+
indicator = None
20+
if indicator:
21+
row["Indicator Name"] = indicator.id
22+
else:
23+
row["Indicator Name"] = None
24+
25+
26+
class ExpressViewIndicatorResource(resources.ModelResource):
27+
menu_item = Field(attribute="menu_item", column_name="Menu Item")
28+
indicator = Field(
29+
attribute="indicator",
30+
column_name="Indicator Name",
31+
widget=ForeignKeyWidget(Indicator),
32+
)
33+
display_name = Field(
34+
attribute="display_name", column_name="text for display legend"
35+
)
36+
37+
def before_import_row(self, row, **kwargs):
38+
process_indicator(row)
39+
40+
def skip_row(self, instance, original, row, import_validation_errors=None):
41+
if not row["Indicator Name"]:
42+
return True
43+
44+
class Meta:
45+
model = ExpressViewIndicator
46+
fields = (
47+
"menu_item",
48+
"indicator",
49+
"display_name",
50+
)
51+
import_id_fields = ("menu_item", "indicator")
52+
skip_unchanged = True
53+
report_skipped = True
54+
exclude = ("id",)

src/alternative_interface/views.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
from django.shortcuts import render
2-
from indicators.models import Indicator
3-
from base.models import Pathogen
2+
from django.db.models import Case, When, Value, IntegerField
3+
from alternative_interface.models import ExpressViewIndicator
44
from epiportal.settings import ALTERNATIVE_INTERFACE_VERSION
55

66
from alternative_interface.utils import get_available_geos, get_chart_data
77

88

9+
MENU_ITEMS_DISPLAY_ORDER_NUMBER = {
10+
"Influenza": 1,
11+
"COVID-19": 2,
12+
"RSV": 3,
13+
"Influenza-Like Illness (ILI)": 4,
14+
}
15+
916
HEADER_DESCRIPTION = "Discover, display and download real-time infectious disease indicators (time series) that track a variety of pathogens, diseases and syndromes in a variety of locations (primarily within the USA). Browse the list, or filter it first by locations and pathogens of interest, by surveillance categories, and more. Expand any row to expose and select from a set of related indicators, then hit 'Show Selected Indicators' at bottom to plot or export your selected indicators, or to generate code snippets to retrieve them from the Delphi Epidata API. Most indicators are served from the Delphi Epidata real-time repository, but some may be available only from third parties or may require prior approval."
1017

1118

@@ -20,41 +27,52 @@ def alternative_interface_view(request):
2027
ctx["selected_pathogen"] = pathogen_filter
2128
ctx["selected_geography"] = geography_filter
2229

23-
# Build queryset with optional filtering
24-
indicators_qs = Indicator.objects.filter(
25-
use_in_express_interface=True
26-
).prefetch_related("pathogens", "available_geographies", "indicator_set")
27-
2830
# Fetch pathogens for dropdown
29-
pathogens_qs = Pathogen.objects.filter(
30-
id__in=indicators_qs.values_list("pathogens", flat=True)
31-
).order_by("display_order_number")
32-
ctx["pathogens"] = list[Pathogen](pathogens_qs)
33-
34-
if pathogen_filter:
35-
indicators_qs = indicators_qs.filter(
36-
pathogens__id=pathogen_filter,
31+
pathogens_qs = (
32+
ExpressViewIndicator.objects.annotate(
33+
order_number=Case(
34+
*[
35+
When(menu_item=key, then=Value(value))
36+
for key, value in MENU_ITEMS_DISPLAY_ORDER_NUMBER.items()
37+
],
38+
default=Value(999),
39+
output_field=IntegerField(),
40+
)
3741
)
42+
.values("menu_item", "order_number")
43+
.distinct()
44+
.order_by("order_number")
45+
)
46+
pathogens = [item["menu_item"] for item in pathogens_qs]
47+
ctx["pathogens"] = pathogens
48+
49+
indicators_qs = ExpressViewIndicator.objects.filter(
50+
menu_item=pathogen_filter
51+
).prefetch_related("indicator")
3852

3953
# Convert to list of dictionaries
4054
ctx["indicators"] = [
4155
{
4256
"_endpoint": (
43-
indicator.indicator_set.epidata_endpoint
44-
if indicator.indicator_set
57+
indicator.indicator.indicator_set.epidata_endpoint
58+
if indicator.indicator.indicator_set
4559
else ""
4660
),
47-
"name": indicator.name,
48-
"data_source": indicator.source.name if indicator.source else "Unknown",
49-
"time_type": indicator.time_type,
61+
"name": indicator.indicator.name,
62+
"data_source": (
63+
indicator.indicator.source.name
64+
if indicator.indicator.source
65+
else "Unknown"
66+
),
67+
"time_type": indicator.indicator.time_type,
5068
"indicator_set_short_name": (
51-
indicator.indicator_set.short_name
52-
if indicator.indicator_set
69+
indicator.indicator.indicator_set.short_name
70+
if indicator.indicator.indicator_set
5371
else "Unknown"
5472
),
5573
"member_short_name": (
56-
indicator.member_short_name
57-
if indicator.member_short_name
74+
indicator.indicator.member_short_name
75+
if indicator.indicator.member_short_name
5876
else "Unknown"
5977
),
6078
}

src/epiportal/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from sentry_sdk.integrations.redis import RedisIntegration
2626

2727
APP_VERSION = "1.0.14"
28-
ALTERNATIVE_INTERFACE_VERSION = "1.0.2"
28+
ALTERNATIVE_INTERFACE_VERSION = "1.0.3"
2929

3030

3131
EPIVIS_URL = os.environ.get("EPIVIS_URL", "https://delphi.cmu.edu/epivis/")

src/templates/alternative_interface/alter_dashboard.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ <h1 class="hero-title">Respiratory Diseases Dashboard</h1>
8282
<select class="form-select filter-select" id="pathogenSelect" name="pathogen" onchange="handlePathogenChange()" style="position: relative;">
8383
<option value=""></option>
8484
{% for pathogen in pathogens %}
85-
<option value="{{ pathogen.id }}" {% if pathogen.id|stringformat:"s" == selected_pathogen %}selected{% endif %}>
86-
{{ pathogen.display_name }}
85+
<option value="{{ pathogen }}" {% if pathogen == selected_pathogen %}selected{% endif %}>
86+
{{ pathogen }}
8787
</option>
8888
{% endfor %}
8989
</select>

0 commit comments

Comments
 (0)