Created
July 19, 2018 12:26
-
-
Save pmg103/3f6f18e9fd4f425aba94e67562a5e808 to your computer and use it in GitHub Desktop.
Have a single source of truth used to specify both fetching and serializing of data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class OrganisationDetail(SerializationSpecMixin, generics.RetrieveAPIView): | |
queryset = Organisation.objects.all() | |
serialization_spec = [ | |
'id', | |
'name', | |
{'created_by_user': [ | |
'email', | |
'status', | |
]}, | |
{'users': [ | |
'email', | |
'full_name' | |
]} | |
] | |
class SerializationSpecMixin: | |
""" | |
Parse a serialization spec and: | |
1. optimally fetch the data required to populate this | |
2. output it | |
""" | |
def get_queryset(self): | |
queryset = self.queryset | |
queryset = queryset.only(*get_only_fields(queryset.model, self.serialization_spec)) | |
queryset = with_related(queryset, queryset.model, [], self.serialization_spec) | |
return queryset | |
def get_serializer_class(self): | |
return make_serializer_class(self.queryset.model, self.serialization_spec) | |
""" Helper functions """ | |
def with_related(queryset, model, prefixes, serialization_spec): | |
relations = model_meta.get_field_info(model).relations | |
for key, values in [list(each.items())[0] for each in get_childspecs(serialization_spec)]: | |
key_path = '__'.join(prefixes + [key]) | |
if relations[key].to_many: | |
queryset = queryset.prefetch_related(key_path) | |
else: | |
queryset = queryset.select_related(key_path) | |
queryset = with_related(queryset, model, prefixes + [key], values) | |
return queryset | |
def make_serializer_class(model, serialization_spec): | |
relations = model_meta.get_field_info(model).relations | |
return type( | |
'MySerializer', | |
(ModelSerializer,), | |
{ | |
'Meta': type( | |
'Meta', | |
(object,), | |
{'model': model, 'fields': get_fields(serialization_spec)} | |
), | |
**{ | |
key: make_serializer_class( | |
relations[key].related_model, | |
values | |
)(many=relations[key].to_many) | |
for key, values in [list(each.items())[0] for each in get_childspecs(serialization_spec)] | |
} | |
} | |
) | |
def get_fields(serialization_spec): | |
return sum( | |
[list(x.keys()) if isinstance(x, dict) else [x] for x in serialization_spec], | |
[] | |
) | |
def get_only_fields(model, serialization_spec): | |
relations = model_meta.get_field_info(model).relations | |
return [ | |
field for field in get_fields(serialization_spec) | |
if field not in relations.keys() or not relations[field].to_many | |
] | |
def get_childspecs(serialization_spec): | |
return [each for each in serialization_spec if isinstance(each, dict)] | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment