diff --git a/breccia_mapper/settings.py b/breccia_mapper/settings.py
index 795ae33..16d6569 100644
--- a/breccia_mapper/settings.py
+++ b/breccia_mapper/settings.py
@@ -77,6 +77,7 @@ THIRD_PARTY_APPS = [
FIRST_PARTY_APPS = [
'people',
'activities',
+ 'export',
]
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + FIRST_PARTY_APPS
diff --git a/breccia_mapper/templates/base.html b/breccia_mapper/templates/base.html
index ef6c828..3d2ddc0 100644
--- a/breccia_mapper/templates/base.html
+++ b/breccia_mapper/templates/base.html
@@ -79,7 +79,7 @@
{% if request.user.is_superuser %}
- Export
+ Export
diff --git a/breccia_mapper/urls.py b/breccia_mapper/urls.py
index b8ed941..2806827 100644
--- a/breccia_mapper/urls.py
+++ b/breccia_mapper/urls.py
@@ -28,9 +28,8 @@ urlpatterns = [
views.IndexView.as_view(),
name='index'),
- path('export',
- views.ExportListView.as_view(),
- name='export'),
+ path('',
+ include('export.urls')),
path('',
include('people.urls')),
diff --git a/breccia_mapper/views.py b/breccia_mapper/views.py
index 86480e6..272f7c9 100644
--- a/breccia_mapper/views.py
+++ b/breccia_mapper/views.py
@@ -4,13 +4,8 @@ Views belonging to the core of the project.
These views don't represent any of the models in the apps.
"""
-from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
class IndexView(TemplateView):
template_name = 'index.html'
-
-
-class ExportListView(LoginRequiredMixin, TemplateView):
- template_name = 'export.html'
diff --git a/export/__init__.py b/export/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/export/apps.py b/export/apps.py
new file mode 100644
index 0000000..15c9d19
--- /dev/null
+++ b/export/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class ExportConfig(AppConfig):
+ name = 'export'
diff --git a/export/serializers/__init__.py b/export/serializers/__init__.py
new file mode 100644
index 0000000..5f4be95
--- /dev/null
+++ b/export/serializers/__init__.py
@@ -0,0 +1,3 @@
+from . import (
+ people
+)
diff --git a/export/serializers/base.py b/export/serializers/base.py
new file mode 100644
index 0000000..0df4a45
--- /dev/null
+++ b/export/serializers/base.py
@@ -0,0 +1,65 @@
+"""
+Serialize models to and deserialize from JSON.
+"""
+
+from collections import OrderedDict
+import typing
+
+from rest_framework import serializers
+
+
+class FlattenedModelSerializer(serializers.ModelSerializer):
+ @classmethod
+ def flatten_data(cls, data,
+ sub_type: typing.Type = dict,
+ sub_value_accessor: typing.Callable = lambda x: x.items()) -> typing.OrderedDict:
+ """
+ Flatten a dictionary so that subdictionaries become a series of `key[.subkey[.subsubkey ...]]` entries
+ in the top level dictionary.
+
+ Works for other data structures (e.g. DRF Serializers) by providing suitable values for the
+ `sub_type` and `sub_value_accessor` parameters.
+
+ :param data: Dictionary or other data structure to flatten
+ :param sub_type: Type to recursively flatten
+ :param sub_value_accessor: Function to access keys and values contained within sub_type.
+ """
+ data_out = OrderedDict()
+
+ for key, value in sub_value_accessor(data):
+ if isinstance(value, sub_type):
+ # Recursively flatten nested structures of type `sub_type`
+ sub_flattened = cls.flatten_data(value,
+ sub_type=sub_type,
+ sub_value_accessor=sub_value_accessor).items()
+
+ # Enter recursively flattened values into result dictionary
+ for sub_key, sub_value in sub_flattened:
+ # Keys in result dictionary are of format `key[.subkey[.subsubkey ...]]`
+ data_out[f'{key}.{sub_key}'] = sub_value
+
+ else:
+ data_out[key] = value
+
+ return data_out
+
+ @property
+ def column_headers(self) -> typing.List[str]:
+ """
+ Get all column headers that will be output by this serializer.
+ """
+ fields = self.flatten_data(self.fields,
+ sub_type=serializers.BaseSerializer,
+ sub_value_accessor=lambda x: x.fields.items())
+
+ return list(fields)
+
+ def to_representation(self, instance) -> typing.OrderedDict:
+ """
+
+ """
+ rep = super().to_representation(instance)
+
+ rep = self.flatten_data(rep)
+
+ return rep
diff --git a/export/serializers/people.py b/export/serializers/people.py
new file mode 100644
index 0000000..4ded304
--- /dev/null
+++ b/export/serializers/people.py
@@ -0,0 +1,66 @@
+import typing
+
+from rest_framework import serializers
+
+from people import models
+
+from . import base
+
+
+class PersonSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.Person
+ fields = [
+ 'pk',
+ 'name',
+ ]
+
+
+class PersonExportSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = models.Person
+ fields = [
+ 'pk',
+ 'name',
+ 'core_member',
+ 'gender',
+ 'age_group',
+ 'nationality',
+ 'country_of_residence',
+ ]
+
+
+class RelationshipSerializer(base.FlattenedModelSerializer):
+ source = PersonSerializer()
+ target = PersonSerializer()
+
+ class Meta:
+ model = models.Relationship
+ fields = [
+ 'pk',
+ 'source',
+ 'target',
+ ]
+
+ @property
+ def column_headers(self) -> typing.List[str]:
+ headers = super().column_headers
+
+ # Add relationship questions to columns
+ for question in models.RelationshipQuestion.objects.all():
+ headers.append(question.slug)
+
+ return headers
+
+ def to_representation(self, instance):
+ rep = super().to_representation(instance)
+
+ try:
+ # Add relationship question answers to data
+ for answer in instance.current_answers.question_answers.all():
+ rep[answer.question.slug] = answer.slug
+
+ except AttributeError:
+ pass
+
+ return rep
diff --git a/breccia_mapper/templates/export.html b/export/templates/export/export.html
similarity index 84%
rename from breccia_mapper/templates/export.html
rename to export/templates/export/export.html
index e0aca40..d8741e9 100644
--- a/breccia_mapper/templates/export.html
+++ b/export/templates/export/export.html
@@ -24,7 +24,7 @@
|
Export
+ href="{% url 'export:person' %}">Export
|
@@ -33,7 +33,7 @@
|
Export
+ href="{% url 'export:relationship' %}">Export
|
diff --git a/export/urls.py b/export/urls.py
new file mode 100644
index 0000000..c991724
--- /dev/null
+++ b/export/urls.py
@@ -0,0 +1,20 @@
+from django.urls import path
+
+from . import views
+
+
+app_name = 'export'
+
+urlpatterns = [
+ path('export',
+ views.ExportListView.as_view(),
+ name='index'),
+
+ path('export/people',
+ views.people.PersonExportView.as_view(),
+ name='person'),
+
+ path('export/relationships',
+ views.people.RelationshipExportView.as_view(),
+ name='relationship'),
+]
diff --git a/export/views/__init__.py b/export/views/__init__.py
new file mode 100644
index 0000000..5efd3f7
--- /dev/null
+++ b/export/views/__init__.py
@@ -0,0 +1,5 @@
+from .base import ExportListView
+
+from . import (
+ people
+)
diff --git a/people/views/export.py b/export/views/base.py
similarity index 74%
rename from people/views/export.py
rename to export/views/base.py
index e57ba2b..8416e47 100644
--- a/people/views/export.py
+++ b/export/views/base.py
@@ -3,10 +3,9 @@ import typing
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse
+from django.views.generic import TemplateView
from django.views.generic.list import BaseListView
-from .. import models, serializers
-
class CsvExportView(LoginRequiredMixin, BaseListView):
model = None
@@ -26,11 +25,5 @@ class CsvExportView(LoginRequiredMixin, BaseListView):
return response
-class PersonExportView(CsvExportView):
- model = models.Person
- serializer_class = serializers.PersonExportSerializer
-
-
-class RelationshipExportView(CsvExportView):
- model = models.Relationship
- serializer_class = serializers.RelationshipSerializer
+class ExportListView(LoginRequiredMixin, TemplateView):
+ template_name = 'export/export.html'
diff --git a/export/views/people.py b/export/views/people.py
new file mode 100644
index 0000000..77d6999
--- /dev/null
+++ b/export/views/people.py
@@ -0,0 +1,14 @@
+from . import base
+from .. import serializers
+
+from people import models
+
+
+class PersonExportView(base.CsvExportView):
+ model = models.person.Person
+ serializer_class = serializers.people.PersonExportSerializer
+
+
+class RelationshipExportView(base.CsvExportView):
+ model = models.relationship.Relationship
+ serializer_class = serializers.people.RelationshipSerializer
diff --git a/people/serializers.py b/people/serializers.py
index 8d820f7..f11004e 100644
--- a/people/serializers.py
+++ b/people/serializers.py
@@ -2,71 +2,11 @@
Serialize models to and deserialize from JSON.
"""
-from collections import OrderedDict
-import typing
-
from rest_framework import serializers
from . import models
-class FlattenedModelSerializer(serializers.ModelSerializer):
- @classmethod
- def flatten_data(cls, data,
- sub_type: typing.Type = dict,
- sub_value_accessor: typing.Callable = lambda x: x.items()) -> typing.OrderedDict:
- """
- Flatten a dictionary so that subdictionaries become a series of `key[.subkey[.subsubkey ...]]` entries
- in the top level dictionary.
-
- Works for other data structures (e.g. DRF Serializers) by providing suitable values for the
- `sub_type` and `sub_value_accessor` parameters.
-
- :param data: Dictionary or other data structure to flatten
- :param sub_type: Type to recursively flatten
- :param sub_value_accessor: Function to access keys and values contained within sub_type.
- """
- data_out = OrderedDict()
-
- for key, value in sub_value_accessor(data):
- if isinstance(value, sub_type):
- # Recursively flatten nested structures of type `sub_type`
- sub_flattened = cls.flatten_data(value,
- sub_type=sub_type,
- sub_value_accessor=sub_value_accessor).items()
-
- # Enter recursively flattened values into result dictionary
- for sub_key, sub_value in sub_flattened:
- # Keys in result dictionary are of format `key[.subkey[.subsubkey ...]]`
- data_out[f'{key}.{sub_key}'] = sub_value
-
- else:
- data_out[key] = value
-
- return data_out
-
- @property
- def column_headers(self) -> typing.List[str]:
- """
- Get all column headers that will be output by this serializer.
- """
- fields = self.flatten_data(self.fields,
- sub_type=serializers.BaseSerializer,
- sub_value_accessor=lambda x: x.fields.items())
-
- return list(fields)
-
- def to_representation(self, instance) -> typing.OrderedDict:
- """
-
- """
- rep = super().to_representation(instance)
-
- rep = self.flatten_data(rep)
-
- return rep
-
-
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = models.Person
@@ -75,22 +15,8 @@ class PersonSerializer(serializers.ModelSerializer):
'name',
]
-
-class PersonExportSerializer(serializers.ModelSerializer):
- class Meta:
- model = models.Person
- fields = [
- 'pk',
- 'name',
- 'core_member',
- 'gender',
- 'age_group',
- 'nationality',
- 'country_of_residence',
- ]
-
-class RelationshipSerializer(FlattenedModelSerializer):
+class RelationshipSerializer(serializers.ModelSerializer):
source = PersonSerializer()
target = PersonSerializer()
@@ -101,26 +27,3 @@ class RelationshipSerializer(FlattenedModelSerializer):
'source',
'target',
]
-
- @property
- def column_headers(self) -> typing.List[str]:
- headers = super().column_headers
-
- # Add relationship questions to columns
- for question in models.RelationshipQuestion.objects.all():
- headers.append(question.slug)
-
- return headers
-
- def to_representation(self, instance):
- rep = super().to_representation(instance)
-
- try:
- # Add relationship question answers to data
- for answer in instance.current_answers.question_answers.all():
- rep[answer.question.slug] = answer.slug
-
- except AttributeError:
- pass
-
- return rep
diff --git a/people/urls.py b/people/urls.py
index 784522c..eb73fe1 100644
--- a/people/urls.py
+++ b/people/urls.py
@@ -38,14 +38,6 @@ urlpatterns = [
views.relationship.RelationshipUpdateView.as_view(),
name='relationship.update'),
- path('people/export',
- views.export.PersonExportView.as_view(),
- name='person.export'),
-
- path('relationships/export',
- views.export.RelationshipExportView.as_view(),
- name='relationship.export'),
-
path('network',
views.network.NetworkView.as_view(),
name='network'),
diff --git a/people/views/__init__.py b/people/views/__init__.py
index 5dd2c46..39dd5c9 100644
--- a/people/views/__init__.py
+++ b/people/views/__init__.py
@@ -3,7 +3,6 @@ Views for displaying or manipulating models within the `people` app.
"""
from . import (
- export,
network,
person,
relationship