Serialize Django Model Class Properties
Context
Using Django’s serialization framework to serialize data into the most popular formats, i.e. xml
, json
or yaml
is quite straightforward. This can be quite useful if you’re e.g. trying to pass some json encoded data to the client-side and you don’t really want to add the entire django-rest-framework to your list of dependencies.
Concrete scenario
I’m often using select2 jQuery
component in my Django projects when I want a powerful yet simple autocomplete select widgets. If I just need to render a mostly static list of options for the select autocomplete, most of the time I find it easiest to simply pass a json
encoded QuerySet
and initialize the select2
component with the passed json
object.
Problem
Using the built-in serializers from the core
module is pretty easy:
from django.core import serializers
data = serializers.serialize('json', MyModel.objects.all())
And even though you can pass the fields
argument to only serialize a specified subset of the model fields:
from django.core import serializers
data = serializers.serialize('json', MyModel.objects.all(),
fields=('name', 'description'))
It is not possible to pass the list of model class properties:
from django.core import serializers
data = serializers.serialize('json', MyModel.objects.all(),
fields=('name', 'description'),
props=('display_name',)) # not supported, yet!
Solution
The solution is to write a custom Serializer
, inheriting from a couple of Django core serializers to make things DRY:
# serializers.py
from django.core import serializers
class PropBaseSerializer(serializers.base.Serializer):
"""
Custom serializer class which enables us to specify a subset
of model class properties (as well as fields)
"""
def serialize(self, queryset, **options):
self.selected_props = options.pop('props')
return super().serialize(queryset, **options)
def serialize_property(self, obj):
model = type(obj)
for prop in self.selected_props:
if hasattr(model, prop) and type(getattr(model, prop)) == property:
self.handle_prop(obj, prop)
def handle_prop(self, obj, prop):
self._current[prop] = getattr(obj, prop)
def end_object(self, obj):
self.serialize_property(obj)
super().end_object(obj)
class PropPythonSerializer(PropBaseSerializer, serializers.python.Serializer):
pass
class PropJsonSerializer(PropPythonSerializer, serializers.json.Serializer):
pass
The usage is now straightforward, e.g. in your views:
# views.py
from myapp.serializers import PropJsonSerializer
PropJsonSerializer().serialize(MyModel.objects.all(),
fields=('name', 'description'),
props=('display_name',))
Adding a bit shorter wrapper function
I usually add a helper function to the serializers.py
as well to have a bit more concise serialize step:
# serializers.py
def json_serialize(qs, fields=(), props=()):
return PropJsonSerializer().serialize(qs, fields=fields, props=props)
And now you can use it within your views e.g.:
# serializers.py
from apps.core.serializers import json_serialize
data = json_serialize(MyModel.objects.all(), fields=('name', 'description'),
props=('display_name',))
Or, not passing fields at all:
# serializers.py
from apps.core.serializers import json_serialize
data = json_serialize(MyModel.objects.all(), props=('display_name',))
Conclusion
To include model class properties in the serialization output:
- write a custom
Serializer
class within yourserializers.py
; - use that serializer to pass the
props
argument to specify the subset of class properties to serialize; - optionally, write a bit shorter wrapper function to save a few letters when serializing data.