Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/alternative_interface/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
from django.contrib import admin

# Register your models here.
from import_export.admin import ImportExportModelAdmin

from alternative_interface.models import ExpressViewIndicator
from alternative_interface.resources import ExpressViewIndicatorResource


@admin.register(ExpressViewIndicator)
class ExpressViewIndicatorAdmin(ImportExportModelAdmin):
resource_class = ExpressViewIndicatorResource
list_display = ["menu_item", "indicator", "display_name"]
search_fields = ["menu_item", "indicator", "display_name"]
list_filter = ["menu_item", "indicator"]
ordering = ["menu_item", "indicator"]
62 changes: 62 additions & 0 deletions src/alternative_interface/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Generated by Django 5.2.5 on 2025-11-28 18:20

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
("indicators", "0008_alter_indicator_source_type"),
]

operations = [
migrations.CreateModel(
name="ExpressViewIndicator",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"menu_item",
models.CharField(max_length=255, verbose_name="Menu Item"),
),
(
"display_name",
models.CharField(max_length=255, verbose_name="Display Name"),
),
(
"indicator",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="indicators.indicator",
verbose_name="Indicator",
),
),
],
options={
"verbose_name": "Express View Indicator",
"verbose_name_plural": "Express View Indicators",
"ordering": ["menu_item", "indicator"],
"indexes": [
models.Index(
fields=["menu_item", "indicator"], name="expr_view_ind_menu_idx"
)
],
"constraints": [
models.UniqueConstraint(
fields=("menu_item", "indicator"),
name="uniq_expr_view_menu_ind",
)
],
},
),
]
36 changes: 35 additions & 1 deletion src/alternative_interface/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
from django.db import models

# Create your models here.

class ExpressViewIndicator(models.Model):
menu_item = models.CharField(
verbose_name="Menu Item",
max_length=255,
)
indicator = models.ForeignKey(
"indicators.Indicator",
verbose_name="Indicator",
on_delete=models.PROTECT,
)
display_name = models.CharField(
verbose_name="Display Name",
max_length=255,
)

class Meta:
verbose_name = "Express View Indicator"
verbose_name_plural = "Express View Indicators"
ordering = ["menu_item", "indicator"]
indexes = [
models.Index(
fields=["menu_item", "indicator"],
name="expr_view_ind_menu_idx",
),
]
constraints = [
models.UniqueConstraint(
fields=["menu_item", "indicator"],
name="uniq_expr_view_menu_ind",
),
]

def __str__(self):
return self.display_name
54 changes: 54 additions & 0 deletions src/alternative_interface/resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from import_export import resources
from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget
from indicators.models import Indicator

from alternative_interface.models import ExpressViewIndicator


def process_indicator(row):
indicator_source = row.get("Indicator Source")
indicator_name = row.get("Indicator Name")
indicator = None
if indicator_name and indicator_source:
try:
indicator = Indicator.objects.get(
name=indicator_name, source__name=indicator_source
)
except Indicator.DoesNotExist:
indicator = None
if indicator:
row["Indicator Name"] = indicator.id
else:
row["Indicator Name"] = None


class ExpressViewIndicatorResource(resources.ModelResource):
menu_item = Field(attribute="menu_item", column_name="Menu Item")
indicator = Field(
attribute="indicator",
column_name="Indicator Name",
widget=ForeignKeyWidget(Indicator),
)
display_name = Field(
attribute="display_name", column_name="text for display legend"
)

def before_import_row(self, row, **kwargs):
process_indicator(row)

def skip_row(self, instance, original, row, import_validation_errors=None):
if not row["Indicator Name"]:
return True

class Meta:
model = ExpressViewIndicator
fields = (
"menu_item",
"indicator",
"display_name",
)
import_id_fields = ("menu_item", "indicator")
skip_unchanged = True
report_skipped = True
exclude = ("id",)
66 changes: 42 additions & 24 deletions src/alternative_interface/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
from django.shortcuts import render
from indicators.models import Indicator
from base.models import Pathogen
from django.db.models import Case, When, Value, IntegerField
from alternative_interface.models import ExpressViewIndicator
from epiportal.settings import ALTERNATIVE_INTERFACE_VERSION

from alternative_interface.utils import get_available_geos, get_chart_data


MENU_ITEMS_DISPLAY_ORDER_NUMBER = {
"Influenza": 1,
"COVID-19": 2,
"RSV": 3,
"Influenza-Like Illness (ILI)": 4,
}

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."


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

# Build queryset with optional filtering
indicators_qs = Indicator.objects.filter(
use_in_express_interface=True
).prefetch_related("pathogens", "available_geographies", "indicator_set")

# Fetch pathogens for dropdown
pathogens_qs = Pathogen.objects.filter(
id__in=indicators_qs.values_list("pathogens", flat=True)
).order_by("display_order_number")
ctx["pathogens"] = list[Pathogen](pathogens_qs)

if pathogen_filter:
indicators_qs = indicators_qs.filter(
pathogens__id=pathogen_filter,
pathogens_qs = (
ExpressViewIndicator.objects.annotate(
order_number=Case(
*[
When(menu_item=key, then=Value(value))
for key, value in MENU_ITEMS_DISPLAY_ORDER_NUMBER.items()
],
default=Value(999),
output_field=IntegerField(),
)
)
.values("menu_item", "order_number")
.distinct()
.order_by("order_number")
)
pathogens = [item["menu_item"] for item in pathogens_qs]
ctx["pathogens"] = pathogens

indicators_qs = ExpressViewIndicator.objects.filter(
menu_item=pathogen_filter
).prefetch_related("indicator")

# Convert to list of dictionaries
ctx["indicators"] = [
{
"_endpoint": (
indicator.indicator_set.epidata_endpoint
if indicator.indicator_set
indicator.indicator.indicator_set.epidata_endpoint
if indicator.indicator.indicator_set
else ""
),
"name": indicator.name,
"data_source": indicator.source.name if indicator.source else "Unknown",
"time_type": indicator.time_type,
"name": indicator.indicator.name,
"data_source": (
indicator.indicator.source.name
if indicator.indicator.source
else "Unknown"
),
"time_type": indicator.indicator.time_type,
"indicator_set_short_name": (
indicator.indicator_set.short_name
if indicator.indicator_set
indicator.indicator.indicator_set.short_name
if indicator.indicator.indicator_set
else "Unknown"
),
"member_short_name": (
indicator.member_short_name
if indicator.member_short_name
indicator.indicator.member_short_name
if indicator.indicator.member_short_name
else "Unknown"
),
}
Expand Down
2 changes: 1 addition & 1 deletion src/epiportal/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from sentry_sdk.integrations.redis import RedisIntegration

APP_VERSION = "1.0.14"
ALTERNATIVE_INTERFACE_VERSION = "1.0.1"
ALTERNATIVE_INTERFACE_VERSION = "1.0.3"


EPIVIS_URL = os.environ.get("EPIVIS_URL", "https://delphi.cmu.edu/epivis/")
Expand Down
4 changes: 2 additions & 2 deletions src/templates/alternative_interface/alter_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ <h1 class="hero-title">Respiratory Diseases Dashboard</h1>
<select class="form-select filter-select" id="pathogenSelect" name="pathogen" onchange="handlePathogenChange()" style="position: relative;">
<option value=""></option>
{% for pathogen in pathogens %}
<option value="{{ pathogen.id }}" {% if pathogen.id|stringformat:"s" == selected_pathogen %}selected{% endif %}>
{{ pathogen.display_name }}
<option value="{{ pathogen }}" {% if pathogen == selected_pathogen %}selected{% endif %}>
{{ pathogen }}
</option>
{% endfor %}
</select>
Expand Down
Loading