diff --git a/src/signal_sets/filters.py b/src/signal_sets/filters.py index 58267eb..bb62b44 100644 --- a/src/signal_sets/filters.py +++ b/src/signal_sets/filters.py @@ -2,10 +2,18 @@ import django_filters from django_filters.widgets import QueryArrayWidget +from django.db.models import Q -from signals.models import Pathogen, GeographicScope, Geography, SeverityPyramidRung +from signals.models import ( + Pathogen, + GeographicScope, + Geography, + SeverityPyramidRung, + Signal +) from signal_sets.models import SignalSet from datasources.models import DataSource +from signal_sets.utils import get_list_of_signals_filtered_by_geo logger = logging.getLogger(__name__) @@ -50,7 +58,9 @@ class SignalSetFilter(django_filters.FilterSet): data_source = django_filters.ModelMultipleChoiceFilter( field_name="data_source", - queryset=DataSource.objects.filter(id__in=SignalSet.objects.values_list("data_source", flat="True")), + queryset=DataSource.objects.filter( + id__in=SignalSet.objects.values_list("data_source", flat="True") + ), widget=QueryArrayWidget, ) @@ -62,7 +72,7 @@ class SignalSetFilter(django_filters.FilterSet): ("Daily", "Daily"), ("Weekly", "Weekly"), ("Hourly", "Hourly"), - ("None", "None") + ("None", "None"), ], lookup_expr="icontains", ) @@ -74,6 +84,11 @@ class SignalSetFilter(django_filters.FilterSet): ], ) + location_search = django_filters.CharFilter( + method="filter_by_geo", + widget=QueryArrayWidget, + ) + class Meta: model = SignalSet fields: list[str] = [ @@ -85,3 +100,13 @@ class Meta: "temporal_granularity", "temporal_scope_end", ] + + def filter_by_geo(self, queryset, name, value): + if not value: + return queryset + filtered_signals = get_list_of_signals_filtered_by_geo(value) + query = Q() + for item in filtered_signals["epidata"]: + query |= Q(source__name=item["source"], name=item["signal"]) + signal_sets = Signal.objects.filter(query).values_list("signal_set_id", flat=True).distinct() + return queryset.filter(id__in=signal_sets) diff --git a/src/signal_sets/forms.py b/src/signal_sets/forms.py index c70d7c0..1c1836d 100644 --- a/src/signal_sets/forms.py +++ b/src/signal_sets/forms.py @@ -66,6 +66,11 @@ class SignalSetFilterForm(forms.ModelForm): widget=forms.CheckboxSelectMultiple(), ) + location_search = forms.CharField( + label=("Location Search"), + widget=forms.TextInput(), + ) + class Meta: model = SignalSet fields: list[str] = [ diff --git a/src/signal_sets/utils.py b/src/signal_sets/utils.py new file mode 100644 index 0000000..c15e28f --- /dev/null +++ b/src/signal_sets/utils.py @@ -0,0 +1,30 @@ +import ast +import requests +from django.conf import settings + + +def list_to_dict(lst): + result = {} + for item in lst: + key, value = item.split(":") + if key in result: + if isinstance(result[key], list): + result[key].append(value) + else: + result[key] = [result[key], value] + else: + result[key] = [value] + return result + + +def dict_to_geo_string(geo_dict): + return ";".join([f"{k}:{','.join(v)}" for k, v in geo_dict.items()]) + + +def get_list_of_signals_filtered_by_geo(geos): + geos = list_to_dict(ast.literal_eval(geos)) + url = f"{settings.COVIDCAST_URL}geo_coverage" + params = {"geo": dict_to_geo_string(geos)} + response = requests.get(url, params=params) + print(response.url) + return response.json() \ No newline at end of file diff --git a/src/signal_sets/views.py b/src/signal_sets/views.py index 3ae82b5..7343379 100644 --- a/src/signal_sets/views.py +++ b/src/signal_sets/views.py @@ -32,27 +32,42 @@ def get_queryset(self) -> QuerySet[Any]: def get_url_params(self): url_params_dict = { - "pathogens": [int(el) for el in self.request.GET.getlist("pathogens")], + "pathogens": ( + [int(el) for el in self.request.GET.getlist("pathogens")] + if self.request.GET.get("pathogens") + else "" + ), "geographic_scope": ( [el for el in self.request.GET.getlist("geographic_scope")] if self.request.GET.get("geographic_scope") - else None + else "" ), "severity_pyramid_rungs": ( [el for el in self.request.GET.getlist("severity_pyramid_rungs")] if self.request.GET.get("severity_pyramid_rungs") - else None + else "" ), "data_source": [el for el in self.request.GET.getlist("data_source")], - "temporal_granularity": [ - el for el in self.request.GET.getlist("temporal_granularity") - ], + "temporal_granularity": ( + [el for el in self.request.GET.getlist("temporal_granularity")] + if self.request.GET.get("temporal_granularity") + else "" + ), "available_geographies": ( [el for el in self.request.GET.getlist("available_geographies")] if self.request.GET.get("available_geographies") - else None + else "" + ), + "temporal_scope_end": ( + self.request.GET.get("temporal_scope_end") + if self.request.GET.get("temporal_scope_end") + else "" + ), + "location_search": ( + [el for el in self.request.GET.getlist("location_search")] + if self.request.GET.get("location_search") + else "" ), - "temporal_scope_end": self.request.GET.get("temporal_scope_end"), } url_params_str = "" for param_name, param_value in url_params_dict.items(): @@ -78,7 +93,7 @@ def get_related_signals(self): "endpoint": signal_set.endpoint, "source": signal.source.name, "time_type": signal.time_type, - "description": signal.description + "description": signal.description, } ) return related_signals @@ -98,7 +113,14 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context["signal_sets"] = self.get_queryset() context["related_signals"] = json.dumps(self.get_related_signals()) context["available_geographies"] = Geography.objects.filter(used_in="signals") - context["geographic_granularities"] = [{"id": str(geo_unit.geo_id), "geoType": geo_unit.geography.name, "text": geo_unit.display_name} for geo_unit in GeographyUnit.objects.all()] + context["geographic_granularities"] = [ + { + "id": str(geo_unit.geo_id), + "geoType": geo_unit.geography.name, + "text": geo_unit.display_name, + } + for geo_unit in GeographyUnit.objects.all() + ] return context diff --git a/src/signals/migrations/0016_signal_source_view.py b/src/signals/migrations/0016_signal_source_view.py new file mode 100644 index 0000000..b2dfca2 --- /dev/null +++ b/src/signals/migrations/0016_signal_source_view.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.7 on 2025-01-28 13:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('signals', '0015_severitypyramidrung_used_in_and_more'), + ] + + operations = [ + migrations.RunSQL( + """ + CREATE OR REPLACE view signals_signal_source_view AS ( + SELECT + ss.id, + ss.name as "signal", + dd.name as "source", + ss.signal_set_id as "signal_set" + FROM signals_signal ss + LEFT JOIN datasources_datasource dd + ON ss.source_id = dd.id + ) + """ + ) + ] diff --git a/src/signals/models.py b/src/signals/models.py index cf54136..f9f7070 100644 --- a/src/signals/models.py +++ b/src/signals/models.py @@ -605,3 +605,4 @@ def get_available_geographies(self): @property def get_pathogens(self): return self.pathogens.split(",") if self.pathogens else [] + diff --git a/src/templates/signal_sets/signal_sets.html b/src/templates/signal_sets/signal_sets.html index d7c551a..b3eb73a 100644 --- a/src/templates/signal_sets/signal_sets.html +++ b/src/templates/signal_sets/signal_sets.html @@ -466,11 +466,24 @@ localStorage.setItem("covidcast_url", "{{ covidcast_url }}"); - var geoValues = {{ geographic_granularities|safe }}; + const geoValues = {{ geographic_granularities|safe }}; + var relatedSignals = JSON.parse(JSON.stringify({{ related_signals|safe }})); + var urlParams = JSON.parse(JSON.stringify({{ url_params_dict|safe }})); + $(document).ready(function () { - initSelect2('location_search', geoValues); + + const locationSearchValues = geoValues.map(item => { + return { + ...item, + "id": `${item.geoType}:${item.id}` + } + }) + initSelect2('location_search', locationSearchValues); + if (urlParams.location_search != "") { + $("#location_search").val(urlParams.location_search).trigger("change"); + } table.columns.adjust() })