Skip to content

Instantly share code, notes, and snippets.

@pmg103
Created July 19, 2018 12:26
Show Gist options
  • Save pmg103/3f6f18e9fd4f425aba94e67562a5e808 to your computer and use it in GitHub Desktop.
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
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