Django Serialize ForeignKey, ManyToMany, Instance, Property Method
Jun 13, 2019 · 5 Min Read · 9 Likes · 8 CommentsDjango’s serialization framework provides a mechanism for “translating” Django models into other formats. Usually they are in json output, yaml output, XML output, GeoJSON output etc text based formats.
Here, we are going to supercharge these serializers to do more things, even try to render properties method and instance method.
This article will be useful to you if you are:
- Interested to output some data in serialized formats(given above) and don’t want to integrate any third party libraries for that.
- Want to Develop an API without any help of third party libraries.
- Using Django serializer and need to output extra values from property/instance methods.
- Not really a big fan of third party libraries.
This article is divided into three parts, the first one is how to use serializers to show ManyToMany and ForignKey Fields. In second part, we are going to show how to override the serializers to display property and instance methods. In third section, there are some small examples using DRF(a third party library), if you are in real hurry.
Proper way: the natural implementation
For example purpose, let’s create three models:
from django.db import models
class User(models.Model):
name = models.CharField(max_length=255)
class Tag(models.Model):
name = models.CharField(max_length=255)
class Blog(models.Model):
author = models.ForeignKey(
User, on_delete=models.CASCADE
)
tags = models.ManyToManyField(Tag)
text = models.TextField()
@property
def a_property_method(self):
return "I am a Property Method"
def an_instance_method(self):
return "I am a Instance Method"
To use django’s implementation of rendering ForeignKey
(we will address it as FK) and ManyToMany
(we will address it as M2M) fields, we need to add natural_key
method inside models:
class User(models.Model):
name = models.CharField(max_length=255)
def natural_key(self):
return (self.name)
class Tag(models.Model):
name = models.CharField(max_length=255)
def natural_key(self):
return (self.name)
Then, if we try to render FK and M2M field like this:
In [1]:from django.core import serializers
In [2]:serialized_data = serializers.serialize('json', Blog.objects.all(),
use_natural_foreign_keys=True, fields=['author', 'tags'], indent=4
)
In [3]:print(serialized_data)
Out [4]:
[
{
"model": "experiment.blog",
"pk": 1,
"fields": {
"author": "User",
"tags": [
"Tag 1",
"Tag 2"
]
}
}
]
FYI, django serializer by default will not render @property
or instance
methods.
Our way: overriding serializer classes
This is our way by overriding the serializer class.
Render property or instance method(for ‘JSON’, ‘Python’, ‘GeoJSON’, ‘YAML’ serializer)
To render @property
or instance methods, we have to override the Serializer classes. We are going to override the def end_object(self, obj):
method of any serializer.
from django.core.serializers.json import Serializer
from django.db.models import Manager
# FYI: It can be any of the following as well:
# from django.core.serializers.pyyaml import Serializer
# from django.core.serializers.python import Serializer
# from django.contrib.gis.serializers.geojson import Serializer
class CustomSerializer(Serializer):
def end_object(self, obj):
for field in self.selected_fields:
if field == 'pk':
continue
elif field in self._current.keys():
continue
else:
try:
self._current[field] = getattr(obj, field)() # for model methods
continue
except TypeError:
pass
try:
self._current[field] = getattr(obj, field) # for property methods
continue
except AttributeError:
pass
super(CustomSerializer, self).end_object(obj)
Usage of property or instance method
serializers = CustomSerializer()
queryset = Blog.objects.all()
data = serializers.serialize(queryset, fields=('a_property_method', 'an_instance_method'))
Only render specific fields using __field_name
in ‘ForeignKey’(For ‘JSON’, ‘Python’, ‘GeoJSON’, ‘YAML’ serializer)
In this approach, we are going to take pass name of the fields inside FK which we want to display in our response as forignkey__field_name
. Passing these parameters going to look like this: fields=['fk__name1', 'fk__name2']
etc. So, let’s override the Serializer
from django.contrib.gis.serializers.geojson import Serializer
from django.db.models import Manager
# FYI: It can be any of the following as well:
# from django.core.serializers.pyyaml import Serializer
# from django.core.serializers.python import Serializer
# from django.core.serializers.json import Serializer
JSON_ALLOWED_OBJECTS = (dict,list,tuple,str,int,bool)
class CustomSerializer(Serializer):
def end_object(self, obj):
for field in self.selected_fields:
if field == 'pk':
continue
elif field in self._current.keys():
continue
else:
try:
if '__' in field:
fields = field.split('__')
value = obj
for f in fields:
value = getattr(value, f)
if value != obj and isinstance(value, JSON_ALLOWED_OBJECTS) or value == None:
self._current[field] = value
except AttributeError:
pass
super(CustomSerializer, self).end_object(obj)
Usage for specific fields in ForeignKey
Usage will look like this:
serializers = CustomSerializer()
queryset = Blog.objects.all()
data = serializers.serialize(queryset, fields=('author__name', 'text'))
Output
[
{
"author__name": "user1",
"text": "Some Text"
},
{
"author__name": "user2",
"text": "Some Text"
}
]
Show property and instance method(for ‘XML’ serializer)
Same as JSON, Python, GeoJSON, YAML serializer, here we will override the end_object
method. But in a slightly different way:
from django.core.serializers.xml_serializer import Serializer
class CustomSerializer(Serializer):
def _init_options(self):
self.non_model_fields = self.options.get('non_model_fields', [])
def start_serialization(self):
self._init_options()
super(CustomSerializer, self).start_serialization()
def end_object(self, obj):
for field in self.non_model_fields:
value = None
try:
value = getattr(obj, field)() # object method
continue
except TypeError:
pass
try:
value = getattr(obj, field) # property method
continue
except AttributeError:
pass
self.indent(2)
self.xml.startElement('field', {
'name': field,
'type': value.__class__.__name__,
})
if value:
self.xml.characters(str(value))
else:
self.xml.addQuickElement("None")
self.xml.endElement("field")
return super(CustomSerializer, self).end_object(obj)
Usage of ‘XML’ serializer
serializers = CustomSerializer()
queryset = Blog.objects.all()
data = serializers.serialize(queryset, non_model_fields=('a_property_method', 'an_instance_method'))
Third party: quicker way
We will be using Django Rest Framework and their serializer
in this section.
Show ForeignKey fields
Show ForeignKey values using depth
.
class BlogSerializer(models.Model):
class Meta:
model = Blog
fields = '__all__'
depth = 1
Show M2M fields
We can define a serializer for Nested class to be shown in M2M. Like this:
from rest_framework import serializers
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class BlogSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True)
class Meta:
model = Blog
fields = '__all__'
depth = 1
Show property and instance methods
We can do that by specifying an extra field:
class BlogSerializer(serializers.ModelSerializer):
a_property_method = serializers.CharField(source='a_property_method', read_only=True)
an_instance_method = serializers.CharField(source='an_instance_method', read_only=True)
class Meta:
model = Blog
fields = '__all__'
Thats all for today, thank you for reading. You can checkout my answers on this topic on StackOverflow:
If you have any question, please ask in the comments section below, Cheers!!
Last updated: Jul 13, 2024
I won't spam you. Unsubscribe at any time.