mirror of
https://github.com/Southampton-RSG/breccia-mapper.git
synced 2026-03-03 11:27:09 +00:00
refactor: Move flattening to serializer base class
This commit is contained in:
@@ -2,11 +2,69 @@
|
|||||||
Serialize models to and deserialize from JSON.
|
Serialize models to and deserialize from JSON.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
import typing
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from . import models
|
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.Collection:
|
||||||
|
"""
|
||||||
|
Get all column headers that will be output by this serializer.
|
||||||
|
"""
|
||||||
|
return self.flatten_data(self.fields,
|
||||||
|
sub_type=serializers.BaseSerializer,
|
||||||
|
sub_value_accessor=lambda x: x.fields.items())
|
||||||
|
|
||||||
|
def to_representation(self, instance) -> typing.OrderedDict:
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
rep = super().to_representation(instance)
|
||||||
|
|
||||||
|
rep = self.flatten_data(rep)
|
||||||
|
|
||||||
|
return rep
|
||||||
|
|
||||||
|
|
||||||
class PersonSerializer(serializers.ModelSerializer):
|
class PersonSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Person
|
model = models.Person
|
||||||
@@ -30,7 +88,7 @@ class PersonExportSerializer(serializers.ModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class RelationshipSerializer(serializers.ModelSerializer):
|
class RelationshipSerializer(FlattenedModelSerializer):
|
||||||
source = PersonSerializer()
|
source = PersonSerializer()
|
||||||
target = PersonSerializer()
|
target = PersonSerializer()
|
||||||
|
|
||||||
@@ -41,3 +99,15 @@ class RelationshipSerializer(serializers.ModelSerializer):
|
|||||||
'source',
|
'source',
|
||||||
'target',
|
'target',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
rep = super().to_representation(instance)
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# for answer in instance.current_answers.question_answers.all():
|
||||||
|
# rep[answer.question.text] = answer.text
|
||||||
|
#
|
||||||
|
# except AttributeError:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
return rep
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
|||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.views.generic.list import BaseListView
|
from django.views.generic.list import BaseListView
|
||||||
|
|
||||||
from rest_framework.serializers import BaseSerializer
|
|
||||||
|
|
||||||
from .. import models, serializers
|
from .. import models, serializers
|
||||||
|
|
||||||
|
|
||||||
@@ -14,54 +12,15 @@ class CsvExportView(LoginRequiredMixin, BaseListView):
|
|||||||
model = None
|
model = None
|
||||||
serializer_class = None
|
serializer_class = None
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def flatten_data(cls, data,
|
|
||||||
sub_type: typing.Type = dict,
|
|
||||||
sub_value_accessor: typing.Callable = lambda x: x.items()) -> typing.Dict:
|
|
||||||
"""
|
|
||||||
Flatten a dictionary so that subdictionaryies 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 = {}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def render_to_response(self, context: typing.Dict) -> HttpResponse:
|
def render_to_response(self, context: typing.Dict) -> HttpResponse:
|
||||||
response = HttpResponse(content_type='text/csv')
|
response = HttpResponse(content_type='text/csv')
|
||||||
response['Content-Disposition'] = f'attachment; filename="{self.get_context_object_name(self.object_list)}.csv"'
|
response['Content-Disposition'] = f'attachment; filename="{self.get_context_object_name(self.object_list)}.csv"'
|
||||||
|
|
||||||
serializer = self.serializer_class(self.get_queryset(), many=True)
|
serializer = self.serializer_class(self.get_queryset(), many=True)
|
||||||
columns = self.flatten_data(serializer.child.fields,
|
|
||||||
sub_type=BaseSerializer,
|
|
||||||
sub_value_accessor=lambda x: x.fields.items())
|
|
||||||
|
|
||||||
writer = csv.DictWriter(response, fieldnames=columns)
|
writer = csv.DictWriter(response, fieldnames=serializer.child.column_headers)
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
|
writer.writerows(serializer.data)
|
||||||
for row in serializer.data:
|
|
||||||
writer.writerow(self.flatten_data(row))
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user