Skip to content

Commit fdc424f

Browse files
committed
Fixed Signals import
1 parent 4b154aa commit fdc424f

File tree

4 files changed

+270
-8
lines changed

4 files changed

+270
-8
lines changed

src/signals/admin.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from django.contrib import admin
44
from import_export.admin import ImportExportModelAdmin
55

6-
from signals.resources import SignalResource, SignalBaseResource
6+
from signals.resources import SignalResource, SignalBaseResource, OtherEndpointSignalResource
77

88

99
from signals.models import (
@@ -14,6 +14,7 @@
1414
Pathogen,
1515
SeverityPyramidRung,
1616
Signal,
17+
OtherEndointSignal,
1718
SignalType,
1819
SignalGeography,
1920
GeographyUnit,
@@ -150,6 +151,35 @@ class SignalAdmin(ImportExportModelAdmin):
150151
resource_classes: list[type[SignalResource]] = [SignalResource, SignalBaseResource]
151152

152153

154+
@admin.register(OtherEndointSignal)
155+
class OtherEndpointsSignalAdmin(ImportExportModelAdmin):
156+
"""
157+
Admin interface for managing signal objects.
158+
"""
159+
160+
list_display: tuple[
161+
Literal["name"],
162+
Literal["signal_type"],
163+
Literal["format_type"],
164+
Literal["category"],
165+
Literal["geographic_scope"],
166+
] = ("name", "signal_type", "format_type", "category", "geographic_scope")
167+
search_fields: tuple[
168+
Literal["name"],
169+
Literal["signal_type__name"],
170+
Literal["format_type__name"],
171+
Literal["category__name"],
172+
Literal["geographic_scope__name"],
173+
] = (
174+
"name",
175+
"signal_type__name",
176+
"format_type__name",
177+
"category__name",
178+
"geographic_scope__name",
179+
)
180+
resource_classes: list[type[SignalResource]] = [OtherEndpointSignalResource]
181+
182+
153183
@admin.register(SignalGeography)
154184
class SignalGeographyAdmin(admin.ModelAdmin):
155185
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Generated by Django 5.0.7 on 2025-03-27 18:21
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('signals', '0017_severitypyramidrung_display_order_number'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='OtherEndointSignal',
15+
fields=[
16+
],
17+
options={
18+
'verbose_name': 'Other Endpoint Signal',
19+
'verbose_name_plural': 'Other Endpoint Signals',
20+
'proxy': True,
21+
'indexes': [],
22+
'constraints': [],
23+
},
24+
bases=('signals.signal',),
25+
),
26+
]

src/signals/models.py

+7
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,13 @@ def get_display_name(self):
575575
return self.name
576576

577577

578+
class OtherEndointSignal(Signal):
579+
class Meta:
580+
proxy = True
581+
verbose_name = "Other Endpoint Signal"
582+
verbose_name_plural = "Other Endpoint Signals"
583+
584+
578585
class SignalsDbView(models.Model):
579586
id = models.BigIntegerField(primary_key=True)
580587
name = models.CharField(max_length=255)

src/signals/resources.py

+206-7
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,14 @@ def process_available_geographies(row) -> None:
155155
"display_order_number": max_display_order_number + 1,
156156
},
157157
)
158-
signal = Signal.objects.get(
159-
name=row["Indicator"], source=row["Source Subdivision"]
160-
)
158+
try:
159+
signal = Signal.objects.get(
160+
name=row["Indicator"], source=row["Source Subdivision"]
161+
)
162+
except KeyError:
163+
signal = Signal.objects.get(
164+
name=row["Signal"], source=row["Source Subdivision"]
165+
)
161166
signal_geography, _ = SignalGeography.objects.get_or_create(
162167
geography=geography_instance, signal=signal
163168
)
@@ -167,20 +172,20 @@ def process_available_geographies(row) -> None:
167172

168173

169174
def process_base(row) -> None:
170-
if row["Indicator BaseName"]:
175+
if row["Signal BaseName"]:
171176
source: SourceSubdivision = SourceSubdivision.objects.get(
172177
name=row["Source Subdivision"]
173178
)
174179
base_signal: Signal = Signal.objects.get(
175-
name=row["Indicator BaseName"], source=source
180+
name=row["Signal BaseName"], source=source
176181
)
177182
row["base"] = base_signal.id
178183

179184

180185
class ModelResource(resources.ModelResource):
181186
def get_field_names(self):
182187
names = []
183-
for field in self.get_fields():
188+
for field in list(self.fields.values()):
184189
names.append(self.get_field_name(field))
185190
return names
186191

@@ -213,7 +218,7 @@ class SignalBaseResource(ModelResource):
213218
Resource class for importing Signals base.
214219
"""
215220

216-
name = Field(attribute="name", column_name="Indicator")
221+
name = Field(attribute="name", column_name="Signal")
217222
display_name = Field(attribute="display_name", column_name="Name")
218223
base = Field(
219224
attribute="base",
@@ -241,6 +246,200 @@ class SignalResource(ModelResource):
241246
Resource class for importing and exporting Signal models
242247
"""
243248

249+
name = Field(attribute="name", column_name="Signal")
250+
display_name = Field(attribute="display_name", column_name="Name")
251+
member_name = Field(attribute="member_name", column_name="Member API Name")
252+
member_short_name = Field(
253+
attribute="member_short_name", column_name="Member Short Name"
254+
)
255+
member_description = Field(
256+
attribute="member_description", column_name="Member Description"
257+
)
258+
pathogen = Field(
259+
attribute="pathogen",
260+
column_name="Pathogen/\nDisease Area",
261+
widget=widgets.ManyToManyWidget(Pathogen, field="name", separator=","),
262+
)
263+
signal_type = Field(
264+
attribute="signal_type",
265+
column_name="Indicator Type",
266+
widget=widgets.ForeignKeyWidget(SignalType, field="name"),
267+
)
268+
active = Field(attribute="active", column_name="Active")
269+
description = Field(attribute="description", column_name="Description")
270+
short_description = Field(
271+
attribute="short_description", column_name="Short Description"
272+
)
273+
format_type = Field(
274+
attribute="format_type",
275+
column_name="Format",
276+
widget=widgets.ForeignKeyWidget(FormatType, field="name"),
277+
)
278+
time_type = Field(attribute="time_type", column_name="Time Type")
279+
time_label = Field(attribute="time_label", column_name="Time Label")
280+
reporting_cadence = Field(
281+
attribute="reporting_cadence", column_name="Reporting Cadence"
282+
)
283+
typical_reporting_lag = Field(
284+
attribute="typical_reporting_lag", column_name="Typical Reporting Lag"
285+
)
286+
typical_revision_cadence = Field(
287+
attribute="typical_revision_cadence", column_name="Typical Revision Cadence"
288+
)
289+
demographic_scope = Field(
290+
attribute="demographic_scope", column_name="Population"
291+
)
292+
severity_pyramid_rung = Field(
293+
attribute="severity_pyramid_rung",
294+
column_name="Surveillance Categories",
295+
widget=widgets.ForeignKeyWidget(SeverityPyramidRung),
296+
)
297+
category = Field(
298+
attribute="category",
299+
column_name="Category",
300+
widget=widgets.ForeignKeyWidget(Category, "name"),
301+
)
302+
geographic_scope = Field(
303+
attribute="geographic_scope",
304+
column_name="Geographic Coverage",
305+
widget=widgets.ForeignKeyWidget(GeographicScope),
306+
)
307+
available_geographies = Field(
308+
attribute="available_geography",
309+
column_name="Geographic Levels",
310+
widget=widgets.ManyToManyWidget(Geography, field="name", separator=","),
311+
)
312+
temporal_scope_start = Field(
313+
attribute="temporal_scope_start", column_name="Temporal Scope Start"
314+
)
315+
temporal_scope_start_note = Field(
316+
attribute="temporal_scope_start_note", column_name="Temporal Scope Start Note"
317+
)
318+
temporal_scope_end = Field(
319+
attribute="temporal_scope_end", column_name="Temporal Scope End"
320+
)
321+
temporal_scope_end_note = Field(
322+
attribute="temporal_scope_end_note", column_name="Temporal Scope End Note"
323+
)
324+
is_smoothed = Field(attribute="is_smoothed", column_name="Is Smoothed")
325+
is_weighted = Field(attribute="is_weighted", column_name="Is Weighted")
326+
is_cumulative = Field(attribute="is_cumulative", column_name="Is Cumulative")
327+
has_stderr = Field(attribute="has_stderr", column_name="Has StdErr")
328+
has_sample_size = Field(attribute="has_sample_size", column_name="Has Sample Size")
329+
high_values_are = Field(attribute="high_values_are", column_name="High Values Are")
330+
source = Field(
331+
attribute="source",
332+
column_name="Source Subdivision",
333+
widget=widgets.ForeignKeyWidget(SourceSubdivision, field="name"),
334+
)
335+
data_censoring = Field(attribute="data_censoring", column_name="Data Censoring")
336+
missingness = Field(attribute="missingness", column_name="Missingness")
337+
organization_access_list = Field(
338+
attribute="organization_access_list", column_name="Who may access this indicator?"
339+
)
340+
organization_sharing_list = Field(
341+
attribute="organization_sharing_list",
342+
column_name="Who may be told about this indicator?",
343+
)
344+
license = Field(attribute="license", column_name="Data Use Terms")
345+
restrictions = Field(attribute="restrictions", column_name="Use Restrictions")
346+
signal_set = Field(
347+
attribute="signal_set",
348+
column_name="Indicator Set",
349+
widget=widgets.ForeignKeyWidget(SignalSet, field="name"),
350+
)
351+
352+
class Meta:
353+
model = Signal
354+
fields: list[str] = [
355+
"name",
356+
"display_name",
357+
"member_name",
358+
"member_short_name",
359+
"member_description",
360+
"pathogen",
361+
"signal_type",
362+
"active",
363+
"description",
364+
"short_description",
365+
"time_label",
366+
"reporting_cadence",
367+
"typical_reporting_lag",
368+
"typical_revision_cadence",
369+
"demographic_scope",
370+
"category",
371+
"geographic_scope",
372+
"available_geographies",
373+
"temporal_scope_start",
374+
"temporal_scope_start_note",
375+
"temporal_scope_end",
376+
"temporal_scope_end_note",
377+
"is_smoothed",
378+
"is_weighted",
379+
"is_cumulative",
380+
"has_stderr",
381+
"has_sample_size",
382+
"high_values_are",
383+
"source",
384+
"data_censoring",
385+
"missingness",
386+
"organization_access_list",
387+
"organization_sharing_list",
388+
"license",
389+
"restrictions",
390+
"time_type",
391+
"signal_set",
392+
"format_type",
393+
"severity_pyramid_rung",
394+
]
395+
import_id_fields: list[str] = ["name", "source"]
396+
store_instance = True
397+
skip_unchanged = True
398+
399+
def before_import_row(self, row, **kwargs) -> None:
400+
fix_boolean_fields(row)
401+
process_pathogen(row)
402+
process_signal_type(row)
403+
process_format_type(row)
404+
process_severity_pyramid_rungs(row)
405+
process_category(row)
406+
process_geographic_scope(row)
407+
process_source(row)
408+
process_links(row, dua_column_name="Link to DUA", link_column_name="Link")
409+
if not row.get("Indicator Set"):
410+
row["Indicator Set"] = None
411+
if not row.get("Source Subdivision"):
412+
row["Source Subdivision"] = None
413+
414+
def skip_row(self, instance, original, row, import_validation_errors=None):
415+
if not row["Include in indicator app"]:
416+
try:
417+
signal = Signal.objects.get(
418+
name=row["Signal"], source=row["Source Subdivision"]
419+
)
420+
signal.delete()
421+
except Signal.DoesNotExist:
422+
pass
423+
return True
424+
425+
def after_import_row(self, row, row_result, **kwargs):
426+
try:
427+
signal_obj = Signal.objects.get(id=row_result.object_id)
428+
for link in row["Links"]:
429+
signal_obj.related_links.add(link)
430+
process_available_geographies(row)
431+
signal_obj.severity_pyramid_rung = SeverityPyramidRung.objects.get(id=row["Surveillance Categories"])
432+
signal_obj.format_type = row["Format"]
433+
signal_obj.save()
434+
except Signal.DoesNotExist as e:
435+
print(f"Signal.DoesNotExist: {e}")
436+
437+
438+
class OtherEndpointSignalResource(ModelResource):
439+
"""
440+
Resource class for importing and exporting Signal models
441+
"""
442+
244443
name = Field(attribute="name", column_name="Indicator")
245444
display_name = Field(attribute="display_name", column_name="Name")
246445
member_name = Field(attribute="member_name", column_name="Member API Name")

0 commit comments

Comments
 (0)