diff --git a/src/base/utils.py b/src/base/utils.py
new file mode 100644
index 0000000..caaa9c5
--- /dev/null
+++ b/src/base/utils.py
@@ -0,0 +1,52 @@
+from io import BytesIO, TextIOWrapper
+
+import requests
+from django.contrib import messages
+from django.shortcuts import redirect
+from django.utils.module_loading import import_string
+from django.http import FileResponse
+from import_export.results import RowResult
+
+
+def import_data(admin_instance, request, resource_class, spreadsheet_url):
+ resource = resource_class()
+ format_class = import_string("import_export.formats.base_formats.CSV")
+
+ response = requests.get(spreadsheet_url)
+ response.raise_for_status()
+
+ csvfile = TextIOWrapper(BytesIO(response.content), encoding="utf-8")
+
+ dataset = format_class().create_dataset(csvfile.read())
+
+ result = resource.import_data(dataset, dry_run=False, raise_errors=True)
+
+ if result.has_errors():
+ error_messages = ["Import errors!"]
+ for error in result.base_errors:
+ error_messages.append(repr(error.error))
+ for line, errors in result.row_errors():
+ for error in errors:
+ error_messages.append(f"Line number: {line} - {repr(error.error)}")
+ admin_instance.message_user(
+ request, "\n".join(error_messages), level=messages.ERROR
+ )
+ else:
+ success_message = (
+ "Import finished: {} new, {} updated, {} deleted and {} skipped {}."
+ ).format(
+ result.totals[RowResult.IMPORT_TYPE_NEW],
+ result.totals[RowResult.IMPORT_TYPE_UPDATE],
+ result.totals[RowResult.IMPORT_TYPE_DELETE],
+ result.totals[RowResult.IMPORT_TYPE_SKIP],
+ resource._meta.model._meta.verbose_name_plural,
+ )
+ admin_instance.message_user(request, success_message, level=messages.SUCCESS)
+ return redirect(".")
+
+
+def download_source_file(url, file_name):
+ response = requests.get(url)
+ response.raise_for_status()
+ return FileResponse(BytesIO(response.content), as_attachment=True, filename=file_name)
+
diff --git a/src/datasources/admin.py b/src/datasources/admin.py
index a4465ba..e4df1da 100644
--- a/src/datasources/admin.py
+++ b/src/datasources/admin.py
@@ -1,10 +1,14 @@
from django.contrib import admin
-
+from django.urls import path
+from django.conf import settings
from import_export.admin import ImportExportModelAdmin
-from datasources.models import SourceSubdivision
-from datasources.resources import SourceSubdivisionResource
-# Register your models here.
+from base.utils import import_data, download_source_file
+from datasources.models import OtherEndpointSourceSubdivision, SourceSubdivision
+from datasources.resources import (
+ OtherEndpointSourceSubdivisionResource,
+ SourceSubdivisionResource,
+)
@admin.register(SourceSubdivision)
@@ -26,3 +30,87 @@ class SourceSubdivisionAdmin(ImportExportModelAdmin):
ordering = ["name"]
list_filter = ["datasource_name"]
resource_classes = [SourceSubdivisionResource]
+
+ change_list_template = "admin/datasources/source_subdivision_changelist.html"
+
+ def get_urls(self):
+ urls = super().get_urls()
+ custom_urls = [
+ path(
+ "import-from-spreadsheet",
+ self.admin_site.admin_view(self.import_from_spreadsheet),
+ name="import_sourcesubdivisions",
+ ),
+ path(
+ "download-source-file",
+ self.admin_site.admin_view(self.download_source_subdivision),
+ name="download_source_subdivision",
+ ),
+ ]
+ return custom_urls + urls
+
+ def import_from_spreadsheet(self, request):
+ return import_data(
+ self,
+ request,
+ SourceSubdivisionResource,
+ settings.SPREADSHEET_URLS["source_subdivisions"],
+ )
+
+ def download_source_subdivision(self, request):
+ return download_source_file(
+ settings.SPREADSHEET_URLS["source_subdivisions"], "Source_Subdivisions.csv"
+ )
+
+
+@admin.register(OtherEndpointSourceSubdivision)
+class OtherEndpointSourceSubdivisionAdmin(ImportExportModelAdmin):
+ """
+ Admin interface for Other Endpoint Source Subdivision model.
+ """
+
+ list_display = (
+ "name",
+ "display_name",
+ "external_name",
+ "description",
+ "license",
+ "dua",
+ "datasource_name",
+ )
+ search_fields = ("name", "display_name", "external_name")
+ ordering = ["name"]
+ list_filter = ["datasource_name"]
+ resource_classes = [OtherEndpointSourceSubdivisionResource]
+
+ change_list_template = "admin/datasources/source_subdivision_changelist.html"
+
+ def get_urls(self):
+ urls = super().get_urls()
+ custom_urls = [
+ path(
+ "import-from-spreadsheet",
+ self.admin_site.admin_view(self.import_from_spreadsheet),
+ name="import_other_endpoint_sourcesubdivisions",
+ ),
+ path(
+ "download-source-file",
+ self.admin_site.admin_view(self.download_other_endpoint_sourcesubdivision),
+ name="download_other_endpoint_sourcesubdivision",
+ ),
+ ]
+ return custom_urls + urls
+
+ def import_from_spreadsheet(self, request):
+ return import_data(
+ self,
+ request,
+ OtherEndpointSourceSubdivisionResource,
+ settings.SPREADSHEET_URLS["other_endpoint_source_subdivisions"],
+ )
+
+ def download_other_endpoint_sourcesubdivision(self, request):
+ return download_source_file(
+ settings.SPREADSHEET_URLS["other_endpoint_source_subdivisions"],
+ "Other_Endpoint_Source_Subdivisions.csv",
+ )
diff --git a/src/datasources/migrations/0002_otherendpointsourcesubdivision_and_more.py b/src/datasources/migrations/0002_otherendpointsourcesubdivision_and_more.py
new file mode 100644
index 0000000..25da00b
--- /dev/null
+++ b/src/datasources/migrations/0002_otherendpointsourcesubdivision_and_more.py
@@ -0,0 +1,42 @@
+# Generated by Django 5.2.5 on 2025-08-27 14:25
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("datasources", "0001_initial"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="OtherEndpointSourceSubdivision",
+ fields=[],
+ options={
+ "verbose_name": "Other Endpoint Source Subdivision",
+ "verbose_name_plural": "Other Endpoint Source Subdivisions",
+ "proxy": True,
+ "indexes": [],
+ "constraints": [],
+ },
+ bases=("datasources.sourcesubdivision",),
+ ),
+ migrations.AddField(
+ model_name="sourcesubdivision",
+ name="source_type",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("covidcast", "Covidcast"),
+ ("other_endpoint", "Other Endpoint"),
+ ("non_delphi", "Non Delphi"),
+ ],
+ default="covidcast",
+ help_text="Type of source for the source subdivision",
+ max_length=255,
+ null=True,
+ verbose_name="Source Type",
+ ),
+ ),
+ ]
diff --git a/src/datasources/models.py b/src/datasources/models.py
index 4af68b8..5492efc 100644
--- a/src/datasources/models.py
+++ b/src/datasources/models.py
@@ -1,5 +1,7 @@
from django.db import models
+from base.models import SOURCE_TYPES
+
# Create your models here.
class SourceSubdivision(models.Model):
@@ -32,6 +34,16 @@ class SourceSubdivision(models.Model):
verbose_name="Datasource Name", max_length=255, blank=True
)
+ source_type: models.CharField = models.CharField(
+ verbose_name="Source Type",
+ max_length=255,
+ choices=SOURCE_TYPES,
+ default="covidcast",
+ help_text="Type of source for the source subdivision",
+ blank=True,
+ null=True,
+ )
+
class Meta:
ordering = ["name"]
verbose_name = "Source Subdivision"
@@ -50,3 +62,10 @@ def __str__(self):
def get_display_name(self):
return self.display_name if self.display_name else self.name
+
+
+class OtherEndpointSourceSubdivision(SourceSubdivision):
+ class Meta:
+ proxy = True
+ verbose_name = "Other Endpoint Source Subdivision"
+ verbose_name_plural = "Other Endpoint Source Subdivisions"
diff --git a/src/datasources/resources.py b/src/datasources/resources.py
index 766e432..b25dc41 100644
--- a/src/datasources/resources.py
+++ b/src/datasources/resources.py
@@ -1,7 +1,7 @@
from import_export import resources
from import_export.fields import Field
-from datasources.models import SourceSubdivision
+from datasources.models import SourceSubdivision, OtherEndpointSourceSubdivision
class SourceSubdivisionResource(resources.ModelResource):
@@ -48,3 +48,24 @@ class Meta:
"dua",
"datasource_name",
)
+
+
+class OtherEndpointSourceSubdivisionResource(SourceSubdivisionResource):
+ class Meta:
+ model = OtherEndpointSourceSubdivision
+ import_id_fields = ("name", "display_name")
+ skip_unchanged = True
+ report_skipped = False
+ fields = (
+ "name",
+ "display_name",
+ "external_name",
+ "description",
+ "license",
+ "dua",
+ "datasource_name",
+ )
+
+ def after_save_instance(self, instance, row, **kwargs):
+ instance.source_type = "other_endpoint"
+ instance.save()
diff --git a/src/epiportal/settings.py b/src/epiportal/settings.py
index ce38745..ec824ad 100644
--- a/src/epiportal/settings.py
+++ b/src/epiportal/settings.py
@@ -31,6 +31,16 @@
EPIDATA_URL = os.environ.get("EPIDATA_URL", "https://api.delphi.cmu.edu/epidata/")
EPIDATA_API_KEY = os.environ.get("EPIDATA_API_KEY", "")
+SPREADSHEET_URLS = {
+ "source_subdivisions": "https://docs.google.com/spreadsheets/d/1zb7ItJzY5oq1n-2xtvnPBiJu2L3AqmCKubrLkKJZVHs/export?format=csv&gid=0",
+ "other_endpoint_source_subdivisions": "https://docs.google.com/spreadsheets/d/1zb7ItJzY5oq1n-2xtvnPBiJu2L3AqmCKubrLkKJZVHs/export?format=csv&gid=214580132",
+ "indicator_sets": "https://docs.google.com/spreadsheets/d/1zb7ItJzY5oq1n-2xtvnPBiJu2L3AqmCKubrLkKJZVHs/export?format=csv&gid=1266808975",
+ "non_delphi_indicator_sets": "https://docs.google.com/spreadsheets/d/1zb7ItJzY5oq1n-2xtvnPBiJu2L3AqmCKubrLkKJZVHs/export?format=csv&gid=1266477926",
+ "indicators": "https://docs.google.com/spreadsheets/d/1zb7ItJzY5oq1n-2xtvnPBiJu2L3AqmCKubrLkKJZVHs/export?format=csv&gid=329338228",
+ "other_endpoint_indicators": "https://docs.google.com/spreadsheets/d/1zb7ItJzY5oq1n-2xtvnPBiJu2L3AqmCKubrLkKJZVHs/export?format=csv&gid=1364181703",
+ "non_delphi_indicators": "https://docs.google.com/spreadsheets/d/1zb7ItJzY5oq1n-2xtvnPBiJu2L3AqmCKubrLkKJZVHs/export?format=csv&gid=493612863"
+}
+
SENTRY_DSN = os.environ.get('SENTRY_DSN')
if SENTRY_DSN:
sentry_sdk.init(
diff --git a/src/fixtures/geographic_granularities.json b/src/fixtures/geographic_granularities.json
index 6c4b347..9e2cd51 100644
--- a/src/fixtures/geographic_granularities.json
+++ b/src/fixtures/geographic_granularities.json
@@ -14,7 +14,7 @@
"model": "base.Geography",
"pk": 2,
"fields": {
- "name": "hhs",
+ "name": "hhs-region",
"display_name": "U.S. HHS Region",
"display_order_number": 2,
"used_in": "indicatorsets",
diff --git a/src/indicators/admin.py b/src/indicators/admin.py
index 14fdf98..091d3f8 100644
--- a/src/indicators/admin.py
+++ b/src/indicators/admin.py
@@ -1,21 +1,15 @@
+from django.conf import settings
from django.contrib import admin
+from django.urls import path
from import_export.admin import ImportExportModelAdmin
-from indicators.models import (
- Category,
- FormatType,
- Indicator,
- IndicatorGeography,
- IndicatorType,
- OtherEndpointIndicator,
- NonDelphiIndicator
-)
-from indicators.resources import (
- IndicatorResource,
- IndicatorBaseResource,
- OtherEndpointIndicatorResource,
- NonDelphiIndicatorResource,
-)
+from base.utils import download_source_file, import_data
+from indicators.models import (Category, FormatType, Indicator,
+ IndicatorGeography, IndicatorType,
+ NonDelphiIndicator, OtherEndpointIndicator)
+from indicators.resources import (IndicatorBaseResource, IndicatorResource,
+ NonDelphiIndicatorResource,
+ OtherEndpointIndicatorResource)
@admin.register(IndicatorType)
@@ -72,6 +66,39 @@ class IndicatorAdmin(ImportExportModelAdmin):
resource_classes = [IndicatorResource, IndicatorBaseResource]
+ change_list_template = "admin/indicators/indicator_changelist.html"
+
+ def get_queryset(self, request):
+ # Exclude proxy model objects
+ qs = super().get_queryset(request)
+ return qs.filter(source_type="covidcast")
+
+ def get_urls(self):
+ urls = super().get_urls()
+ custom_urls = [
+ path(
+ "import-from-spreadsheet",
+ self.admin_site.admin_view(self.import_from_spreadsheet),
+ name="import_indicators",
+ ),
+ path(
+ "download-source-file",
+ self.admin_site.admin_view(self.download_indicator),
+ name="download_indicator",
+ ),
+ ]
+ return custom_urls + urls
+
+ def import_from_spreadsheet(self, request):
+ return import_data(
+ self, request, IndicatorResource, settings.SPREADSHEET_URLS["indicators"]
+ )
+
+ def download_indicator(self, request):
+ return download_source_file(
+ settings.SPREADSHEET_URLS["indicators"], "Indicators.csv"
+ )
+
@admin.register(OtherEndpointIndicator)
class OtherEndpointIndicatorAdmin(ImportExportModelAdmin):
@@ -93,6 +120,45 @@ class OtherEndpointIndicatorAdmin(ImportExportModelAdmin):
resource_classes = [OtherEndpointIndicatorResource]
+ change_list_template = "admin/indicators/indicator_changelist.html"
+
+ def get_queryset(self, request):
+ # Exclude proxy model objects
+ qs = super().get_queryset(request)
+ return qs.filter(source_type="other_endpoint")
+
+ def get_urls(self):
+ urls = super().get_urls()
+ custom_urls = [
+ path(
+ "import-from-spreadsheet",
+ self.admin_site.admin_view(self.import_from_spreadsheet),
+ name="import_otherendpoint_indicators",
+ ),
+ path(
+ "download-source-file",
+ self.admin_site.admin_view(
+ self.download_other_endpoint_indicator
+ ),
+ name="download_other_endpoint_indicator",
+ ),
+ ]
+ return custom_urls + urls
+
+ def import_from_spreadsheet(self, request):
+ return import_data(
+ self,
+ request,
+ OtherEndpointIndicatorResource,
+ settings.SPREADSHEET_URLS["other_endpoint_indicators"],
+ )
+
+ def download_other_endpoint_indicator(self, request):
+ return download_source_file(
+ settings.SPREADSHEET_URLS["other_endpoint_indicators"],
+ "Other_Endpoint_Indicators.csv",
+ )
+
@admin.register(NonDelphiIndicator)
class NonDelphiIndicatorAdmin(ImportExportModelAdmin):
@@ -110,3 +176,37 @@ class NonDelphiIndicatorAdmin(ImportExportModelAdmin):
list_display_links = ("name",)
resource_classes = [NonDelphiIndicatorResource]
+
+ change_list_template = "admin/indicators/indicator_changelist.html"
+
+ def get_queryset(self, request):
+ # Exclude proxy model objects
+ qs = super().get_queryset(request)
+ return qs.filter(source_type="non_delphi")
+
+ def get_urls(self):
+ urls = super().get_urls()
+ custom_urls = [
+ path(
+ "import-from-spreadsheet",
+ self.admin_site.admin_view(self.import_from_spreadsheet),
+ name="import_nondelphi_indicators",
+ ),
+ path(
+ "download-source-file",
+ self.admin_site.admin_view(self.download_nondelphi_indicator),
+ name="download_nondelphi_indicator",
+ ),
+ ]
+ return custom_urls + urls
+
+ def import_from_spreadsheet(self, request):
+ spreadsheet_url = "https://docs.google.com/spreadsheets/d/1zb7ItJzY5oq1n-2xtvnPBiJu2L3AqmCKubrLkKJZVHs/export?format=csv&gid=493612863"
+
+ return import_data(self, request, NonDelphiIndicatorResource, spreadsheet_url)
+
+ def download_nondelphi_indicator(self, request):
+ return download_source_file(
+ settings.SPREADSHEET_URLS["non_delphi_indicators"],
+ "Non_Delphi_Indicators.csv",
+ )
diff --git a/src/indicatorsets/admin.py b/src/indicatorsets/admin.py
index 27fda14..0072d02 100644
--- a/src/indicatorsets/admin.py
+++ b/src/indicatorsets/admin.py
@@ -1,11 +1,14 @@
+from django.conf import settings
from django.contrib import admin
-
+from django.urls import path
from import_export.admin import ImportExportModelAdmin
+
+from base.utils import download_source_file, import_data
from indicatorsets.models import (
+ ColumnDescription,
+ FilterDescription,
IndicatorSet,
NonDelphiIndicatorSet,
- FilterDescription,
- ColumnDescription,
)
from indicatorsets.resources import IndicatorSetResource, NonDelphiIndicatorSetResource
@@ -35,6 +38,42 @@ class IndicatorSetAdmin(ImportExportModelAdmin):
ordering = ["name"]
list_filter = ["original_data_provider"]
+ def get_queryset(self, request):
+ # Exclude proxy model objects
+ qs = super().get_queryset(request)
+ return qs.exclude(source_type="non_delphi")
+
+ change_list_template = "admin/indicatorsets/indicator_set_changelist.html"
+
+ def get_urls(self):
+ urls = super().get_urls()
+ custom_urls = [
+ path(
+ "import-from-spreadsheet",
+ self.admin_site.admin_view(self.import_from_spreadsheet),
+ name="import_indicatorsets",
+ ),
+ path(
+ "download-source-file",
+ self.admin_site.admin_view(self.download_indicator_set),
+ name="download_indicator_set",
+ ),
+ ]
+ return custom_urls + urls
+
+ def import_from_spreadsheet(self, request):
+ return import_data(
+ self,
+ request,
+ IndicatorSetResource,
+ settings.SPREADSHEET_URLS["indicator_sets"],
+ )
+
+ def download_indicator_set(self, request):
+ return download_source_file(
+ settings.SPREADSHEET_URLS["indicator_sets"], "Indicator_Sets.csv"
+ )
+
@admin.register(NonDelphiIndicatorSet)
class NonDelphiIndicatorSetAdmin(ImportExportModelAdmin):
@@ -61,6 +100,43 @@ class NonDelphiIndicatorSetAdmin(ImportExportModelAdmin):
ordering = ["name"]
list_filter = ["original_data_provider", "source_type"]
+ def get_queryset(self, request):
+ # Exclude proxy model objects
+ qs = super().get_queryset(request)
+ return qs.filter(source_type="non_delphi")
+
+ change_list_template = "admin/indicatorsets/indicator_set_changelist.html"
+
+ def get_urls(self):
+ urls = super().get_urls()
+ custom_urls = [
+ path(
+ "import-from-spreadsheet",
+ self.admin_site.admin_view(self.import_from_spreadsheet),
+ name="import_nondelphi_indicatorsets",
+ ),
+ path(
+ "download-source-file",
+ self.admin_site.admin_view(self.download_nondelphi_indicator_set),
+ name="download_nondelphi_indicator_set",
+ ),
+ ]
+ return custom_urls + urls
+
+ def import_from_spreadsheet(self, request):
+ return import_data(
+ self,
+ request,
+ NonDelphiIndicatorSetResource,
+ settings.SPREADSHEET_URLS["non_delphi_indicator_sets"],
+ )
+
+ def download_nondelphi_indicator_set(self, request):
+ return download_source_file(
+ settings.SPREADSHEET_URLS["non_delphi_indicator_sets"],
+ "Non_Delphi_Indicator_Sets.csv",
+ )
+
@admin.register(FilterDescription)
class FilterDescriptionAdmin(admin.ModelAdmin):
diff --git a/src/indicatorsets/migrations/0005_alter_indicatorset_source_type.py b/src/indicatorsets/migrations/0005_alter_indicatorset_source_type.py
new file mode 100644
index 0000000..d923c86
--- /dev/null
+++ b/src/indicatorsets/migrations/0005_alter_indicatorset_source_type.py
@@ -0,0 +1,30 @@
+# Generated by Django 5.2.5 on 2025-08-27 14:25
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("indicatorsets", "0004_columndescription_filterdescription"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="indicatorset",
+ name="source_type",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("covidcast", "Covidcast"),
+ ("other_endpoint", "Other Endpoint"),
+ ("non_delphi", "Non Delphi"),
+ ],
+ default="covidcast",
+ help_text="Type of source for the indicator set",
+ max_length=255,
+ null=True,
+ verbose_name="Source Type",
+ ),
+ ),
+ ]
diff --git a/src/indicatorsets/models.py b/src/indicatorsets/models.py
index ac00c76..a39d2a5 100644
--- a/src/indicatorsets/models.py
+++ b/src/indicatorsets/models.py
@@ -190,7 +190,7 @@ class IndicatorSet(models.Model):
max_length=255,
choices=SOURCE_TYPES,
default="covidcast",
- help_text="Type of source for the indicator",
+ help_text="Type of source for the indicator set",
blank=True,
null=True,
)
diff --git a/src/templates/admin/change_list.html b/src/templates/admin/change_list.html
new file mode 100644
index 0000000..feed979
--- /dev/null
+++ b/src/templates/admin/change_list.html
@@ -0,0 +1,101 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_urls static admin_list %}
+
+{% block title %}{% if cl.formset and cl.formset.errors %}{% translate "Error:" %} {% endif %}{{ block.super }}{% endblock %}
+{% block extrastyle %}
+ {{ block.super }}
+
+ {% if cl.formset %}
+
+ {% endif %}
+ {% if cl.formset or action_form %}
+
+ {% endif %}
+ {{ media.css }}
+ {% if not actions_on_top and not actions_on_bottom %}
+
+ {% endif %}
+{% endblock %}
+
+{% block extrahead %}
+{{ block.super }}
+{{ media.js }}
+
+{% endblock %}
+
+{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %}
+
+{% if not is_popup %}
+{% block breadcrumbs %}
+
+{% endblock %}
+{% endif %}
+
+{% block coltype %}{% endblock %}
+
+{% block content %}
+
+ {% block object-tools %}
+
+ {% endblock %}
+ {% if cl.formset and cl.formset.errors %}
+
+ {% blocktranslate count counter=cl.formset.total_error_count %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktranslate %}
+
+ {{ cl.formset.non_form_errors }}
+ {% endif %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/src/templates/admin/datasources/source_subdivision_changelist.html b/src/templates/admin/datasources/source_subdivision_changelist.html
new file mode 100644
index 0000000..ea087a4
--- /dev/null
+++ b/src/templates/admin/datasources/source_subdivision_changelist.html
@@ -0,0 +1,22 @@
+{% extends "admin/change_list.html" %}
+
+{% block object-tools-items %}
+ {{ block.super }}
+
+ {% if opts.model_name == "sourcesubdivision" %}
+
+ Import data from spreadsheet
+
+
+ Download source file
+
+ {% elif opts.model_name == "otherendpointsourcesubdivision" %}
+
+ Import data from spreadsheet
+
+
+ Download source file
+
+ {% endif %}
+
+{% endblock object-tools-items %}
\ No newline at end of file
diff --git a/src/templates/admin/indicators/indicator_changelist.html b/src/templates/admin/indicators/indicator_changelist.html
new file mode 100644
index 0000000..e3af998
--- /dev/null
+++ b/src/templates/admin/indicators/indicator_changelist.html
@@ -0,0 +1,29 @@
+{% extends "admin/change_list.html" %}
+
+{% block object-tools-items %}
+ {{ block.super }}
+
+ {% if opts.model_name == "indicator" %}
+
+ Import data from spreadsheet
+
+
+ Download source file
+
+ {% elif opts.model_name == "otherendpointindicator" %}
+
+ Import data from spreadsheet
+
+
+ Download source file
+
+ {% elif opts.model_name == "nondelphiindicator" %}
+
+ Import data from spreadsheet
+
+
+ Download source file
+
+ {% endif %}
+
+{% endblock object-tools-items %}
\ No newline at end of file
diff --git a/src/templates/admin/indicatorsets/indicator_set_changelist.html b/src/templates/admin/indicatorsets/indicator_set_changelist.html
new file mode 100644
index 0000000..c655611
--- /dev/null
+++ b/src/templates/admin/indicatorsets/indicator_set_changelist.html
@@ -0,0 +1,22 @@
+{% extends "admin/change_list.html" %}
+
+{% block object-tools-items %}
+ {{ block.super }}
+
+ {% if opts.model_name == "indicatorset" %}
+
+ Import data from spreadsheet
+
+
+ Download source file
+
+ {% elif opts.model_name == "nondelphiindicatorset" %}
+
+ Import data from spreadsheet
+
+
+ Download source file
+
+ {% endif %}
+
+{% endblock %}
\ No newline at end of file