Custom Fields
DRF covers most data with its built-in fields, but you will eventually need output that no model field produces. There are two ways to get it: SerializerMethodField for read-only computed values, and a Field subclass when the data has to convert in both directions.
SerializerMethodField
SerializerMethodField is a read-only field whose value comes from a method on the serializer. It is the simplest way to expose a model property, a computed value, or anything else that is not a concrete field.
By default it calls get_<field_name>(), passing the object being serialized. Pass method_name if you want to point it at a differently named method.
class ArticleSerializer(serializers.ModelSerializer):
word_count = serializers.SerializerMethodField()
author_name = serializers.SerializerMethodField()
class Meta:
model = Article
fields = ['id', 'title', 'body', 'word_count', 'author_name']
def get_word_count(self, obj):
return len(obj.body.split())
def get_author_name(self, obj):
return obj.author.get_full_name(){
"id": 1,
"title": "Working with Django Signals",
"body": "Signals allow decoupled applications to ...",
"word_count": 412,
"author_name": "Ada Lovelace"
}This is also how you expose the values fields = '__all__' never picks up, such as model properties, methods and GenericForeignKey.
WARNING
A SerializerMethodField is always read-only and is ignored on write. It also runs once per object, which makes N+1 queries easy to introduce: the get_author_name() method above hits the database for every article unless the view's queryset uses select_related('author').
Subclassing Field
When a field has to be written as well as read, subclass Field and override .to_representation() and .to_internal_value().
to_representation()
The .to_representation() method is called to convert the initial datatype into a primitive, serializable datatype.
to_internal_value()
The .to_internal_value() method is called to restore a primitive datatype into its internal python representation. This method should raise a serializers.ValidationError if the data is invalid.
Example
A custom field that serializes a class representing an RGB color value:
import re
from rest_framework import serializers
class Color:
"""
A simple color class used for demonstration purposes.
"""
def __init__(self, red, green, blue):
self.red = red
self.green = green
self.blue = blue
class ColorField(serializers.Field):
"""
Color objects are serialized into 'rgb(#, #, #)' notation.
"""
default_error_messages = {
'invalid_type': 'Incorrect type. Expected a string.',
'incorrect_format': 'Incorrect format. Expected `rgb(#, #, #)`',
}
def to_representation(self, value):
return f"rgb({value.red}, {value.green}, {value.blue})"
def to_internal_value(self, data):
if not isinstance(data, str):
self.fail('invalid_type')
match = re.match(r'^rgb\((?P<red>\d+),\s*(?P<green>\d+),\s*(?P<blue>\d+)\)$', data)
if match is None:
self.fail('incorrect_format')
return Color(
int(match.group('red')),
int(match.group('green')),
int(match.group('blue'))
)Usage in a serializer:
class ItemSerializer(serializers.Serializer):
name = serializers.CharField(max_length=100)
# The 'source' argument maps the field to a differently-named attribute on the object being serialized
color = ColorField(source='color_obj')NOTE
Idiomatic custom fields define default_error_messages on the class and use self.fail('error_key') to raise validation errors. This allows developers to easily override your error messages when they instantiate the field.
