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: Dec 29, 2024
Very very useful article. It was really useful to my project.
Thank you for the article. I think there’s 2 errors :
Thank you for noticing the issue with DRF. I fixed it. But regarding your first point, it is possible to serialize M2M field using django serializer. Please try out the code given with this article 😄 .
Thank you for the article. Very helpful!
Nevertheless I am stuck on a problem, I can’t find a solution for. Maybe you have an idea. There is a model: class Ta0200Object(models.Model): clientlfn = models.ForeignKey(Ta0100District, models.DO_NOTHING, related_name=’+’, db_column=‘ClientLFN’) districtlfn = models.ForeignKey(Ta0100District, models.DO_NOTHING, related_name=’+’, db_column=‘DistrictLFN’) objectnr = models.IntegerField(db_column=‘ObjectNR’, unique=True) objectlfn = models.AutoField(db_column=‘ObjectLFN’, unique=True, primary_key=True)
How would you serialize it if two ForeignKeys point to one model?
Thanks alot, Felix
I would suggest remove
related_name='+'
, because it is removing the backward relationship of that ForeignKey. Instead, try like this:Then you can serialize them from Parent model.
Nice article, very helpful for a beginner like me. i have followed your instructions because i need to serialize my queryset that contain PointField (from django.contrib.gis.db) and Property but it look like point field are not serialized correctly
TBH, I have not tried to serialize
PointField
, maybe you can share what errors you are getting. I will try to look into that as well. Cheers!!This is a good article. A few thoughts about it as none of these answers really were able to solve my issue. Method #2 is nice, but adds dependencies outside of a standard pip install. This would be much more useful if the author just wrote a routine to essentially do the same without the dependencies. Method #3 seems to be the way to go. However, the author’s description of how to use the Django Rest Framework isn’t very complete. Only a few code segments are given and a complete usable method is far from implemented.