refactor: Move flattening to serializer base class

This commit is contained in:
James Graham
2020-04-01 09:57:28 +01:00
parent 04ae8cb4f6
commit 76ae447cc6
2 changed files with 74 additions and 45 deletions

View File

@@ -2,11 +2,69 @@
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.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 Meta:
model = models.Person
@@ -30,7 +88,7 @@ class PersonExportSerializer(serializers.ModelSerializer):
]
class RelationshipSerializer(serializers.ModelSerializer):
class RelationshipSerializer(FlattenedModelSerializer):
source = PersonSerializer()
target = PersonSerializer()
@@ -41,3 +99,15 @@ class RelationshipSerializer(serializers.ModelSerializer):
'source',
'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

View File

@@ -5,8 +5,6 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse
from django.views.generic.list import BaseListView
from rest_framework.serializers import BaseSerializer
from .. import models, serializers
@@ -14,54 +12,15 @@ class CsvExportView(LoginRequiredMixin, BaseListView):
model = 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:
response = HttpResponse(content_type='text/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)
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()
for row in serializer.data:
writer.writerow(self.flatten_data(row))
writer.writerows(serializer.data)
return response