mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-23 18:13:57 +03:00
247 lines
11 KiB
Markdown
247 lines
11 KiB
Markdown
<a class="github" href="serializers.py"></a>
|
|
|
|
# Serializers
|
|
|
|
> Expanding the usefulness of the serializers is something that we would
|
|
like to address. However, it's not a trivial problem, and it
|
|
will take some serious design work. Any offers to help out in this
|
|
area would be gratefully accepted.
|
|
>
|
|
> — Russell Keith-Magee, [Django users group][cite]
|
|
|
|
Serializers allow complex data such as querysets and model instances to be converted to native python datatypes that can then be easily rendered into `JSON`, `XML` or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.
|
|
|
|
REST framework's serializers work very similarly to Django's `Form` and `ModelForm` classes. It provides a `Serializer` class which gives you a powerful, generic way to control the output of your responses, as well as a `ModelSerializer` class which provides a useful shortcut for creating serializers that deal with model instances and querysets.
|
|
|
|
## Declaring Serializers
|
|
|
|
Let's start by creating a simple object we can use for example purposes:
|
|
|
|
class Comment(object):
|
|
def __init__(self, email, content, created=None):
|
|
self.email = email
|
|
self.content = content
|
|
self.created = created or datetime.datetime.now()
|
|
|
|
comment = Comment(email='leila@example.com', content='foo bar')
|
|
|
|
We'll declare a serializer that we can use to serialize and deserialize `Comment` objects.
|
|
Declaring a serializer looks very similar to declaring a form:
|
|
|
|
class CommentSerializer(serializers.Serializer):
|
|
email = serializers.EmailField()
|
|
content = serializers.CharField(max_length=200)
|
|
created = serializers.DateTimeField()
|
|
|
|
def restore_object(self, attrs, instance=None):
|
|
if instance:
|
|
instance.title = attrs['title']
|
|
instance.content = attrs['content']
|
|
instance.created = attrs['created']
|
|
return instance
|
|
return Comment(**attrs)
|
|
|
|
The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data. The `restore_object` method is optional, and is only required if we want our serializer to support deserialization.
|
|
|
|
## Serializing objects
|
|
|
|
We can now use `CommentSerializer` to serialize a comment, or list of comments. Again, using the `Serializer` class looks a lot like using a `Form` class.
|
|
|
|
serializer = CommentSerializer(instance=comment)
|
|
serializer.data
|
|
# {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}
|
|
|
|
At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into `json`.
|
|
|
|
stream = JSONRenderer().render(data)
|
|
stream
|
|
# '{"email": "leila@example.com", "content": "foo bar", "created": "2012-08-22T16:20:09.822"}'
|
|
|
|
## Deserializing objects
|
|
|
|
Deserialization is similar. First we parse a stream into python native datatypes...
|
|
|
|
data = JSONParser().parse(stream)
|
|
|
|
...then we restore those native datatypes into a fully populated object instance.
|
|
|
|
serializer = CommentSerializer(data)
|
|
serializer.is_valid()
|
|
# True
|
|
serializer.object
|
|
# <Comment object at 0x10633b2d0>
|
|
>>> serializer.deserialize('json', stream)
|
|
|
|
## Validation
|
|
|
|
When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` and `.non_field_errors` properties will contain the resulting error messages.
|
|
|
|
**TODO: Describe validation in more depth**
|
|
|
|
## Dealing with nested objects
|
|
|
|
The previous example is fine for dealing with objects that only have simple datatypes, but sometimes we also need to be able to represent more complex objects,
|
|
where some of the attributes of an object might not be simple datatypes such as strings, dates or integers.
|
|
|
|
The `Serializer` class is itself a type of `Field`, and can be used to represent relationships where one object type is nested inside another.
|
|
|
|
class UserSerializer(serializers.Serializer):
|
|
email = serializers.EmailField()
|
|
username = serializers.CharField()
|
|
|
|
def restore_object(self, attrs, instance=None):
|
|
return User(**attrs)
|
|
|
|
|
|
class CommentSerializer(serializers.Serializer):
|
|
user = serializers.UserSerializer()
|
|
title = serializers.CharField()
|
|
content = serializers.CharField(max_length=200)
|
|
created = serializers.DateTimeField()
|
|
|
|
def restore_object(self, attrs, instance=None):
|
|
return Comment(**attrs)
|
|
|
|
## Creating custom fields
|
|
|
|
If you want to create a custom field, you'll probably want to override either one or both of the `.to_native()` and `.from_native()` methods. These two methods are used to convert between the intial datatype, and a primative, serializable datatype. Primative datatypes may be any of a number, string, date/time/datetime or None. They may also be any list or dictionary like object that only contains other primative objects.
|
|
|
|
The `.to_native()` method is called to convert the initial datatype into a primative, serializable datatype. The `from_native()` method is called to restore a primative datatype into it's initial representation.
|
|
|
|
Let's look at an example of serializing a class that represents an RGB color value:
|
|
|
|
class Color(object):
|
|
"""
|
|
A color represented in the RGB colorspace.
|
|
"""
|
|
|
|
def __init__(self, red, green, blue):
|
|
assert(red >= 0 and green >= 0 and blue >= 0)
|
|
assert(red < 256 and green < 256 and blue < 256)
|
|
self.red, self.green, self.blue = red, green, blue
|
|
|
|
class ColourField(Field):
|
|
"""
|
|
Color objects are serialized into "rgb(#, #, #)" notation.
|
|
"""
|
|
|
|
def to_native(self, obj):
|
|
return "rgb(%d, %d, %d)" % (obj.red, obj.green, obj.blue)
|
|
|
|
def from_native(self, data):
|
|
data = data.strip('rgb(').rstrip(')')
|
|
red, green, blue = [int(col) for col in data.split(',')]
|
|
return Color(red, green, blue)
|
|
|
|
|
|
By default field values are treated as mapping to an attribute on the object. If you need to customize how the field value is accessed and set you need to override `.field_to_native()` and/or `.field_from_native()`.
|
|
|
|
As an example, let's create a field that can be used represent the class name of the object being serialized:
|
|
|
|
class ClassNameField(Field):
|
|
def field_to_native(self, obj, field_name):
|
|
"""
|
|
Serialize the object's class name, not an attribute of the object.
|
|
"""
|
|
return obj.__class__.__name__
|
|
|
|
def field_from_native(self, data, field_name, into):
|
|
"""
|
|
We don't want to set anything when we revert this field.
|
|
"""
|
|
pass
|
|
|
|
---
|
|
|
|
# ModelSerializers
|
|
|
|
Often you'll want serializer classes that map closely to model definitions.
|
|
The `ModelSerializer` class lets you automatically create a Serializer class with fields that corrospond to the Model fields.
|
|
|
|
class AccountSerializer(ModelSerializer):
|
|
class Meta:
|
|
model = Account
|
|
|
|
**[TODO: Explain model field to serializer field mapping in more detail]**
|
|
|
|
## Specifying fields explicitly
|
|
|
|
You can add extra fields to a `ModelSerializer` or override the default fields by declaring fields on the class, just as you would for a `Serializer` class.
|
|
|
|
class AccountSerializer(ModelSerializer):
|
|
url = CharField(source='get_absolute_url', readonly=True)
|
|
group = NaturalKeyField()
|
|
|
|
class Meta:
|
|
model = Account
|
|
|
|
Extra fields can corrospond to any property or callable on the model.
|
|
|
|
## Relational fields
|
|
|
|
When serializing model instances, there are a number of different ways you might choose to represent relationships. The default representation is to use the primary keys of the related instances.
|
|
|
|
Alternative representations include serializing using natural keys, serializing complete nested representations, or serializing using a custom representation, such as a URL that uniquely identifies the model instances.
|
|
|
|
The `PrimaryKeyField` and `NaturalKeyField` fields provide alternative flat representations.
|
|
|
|
The `ModelSerializer` class can itself be used as a field, in order to serialize relationships using nested representations.
|
|
|
|
The `RelatedField` class may be subclassed to create a custom represenation of a relationship. The subclass should override `.to_native()`, and optionally `.from_native()` if deserialization is supported.
|
|
|
|
All the relational fields may be used for any relationship or reverse relationship on a model.
|
|
|
|
## Specifying which fields should be included
|
|
|
|
If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
|
|
|
|
For example:
|
|
|
|
class AccountSerializer(ModelSerializer):
|
|
class Meta:
|
|
model = Account
|
|
exclude = ('id',)
|
|
|
|
The `fields` and `exclude` options may also be set by passing them to the `serialize()` method.
|
|
|
|
**[TODO: Possibly only allow .serialize(fields=…) in FixtureSerializer for backwards compatability, but remove for ModelSerializer]**
|
|
|
|
## Specifiying nested serialization
|
|
|
|
The default `ModelSerializer` uses primary keys for relationships, but you can also easily generate nested representations using the `nested` option:
|
|
|
|
class AccountSerializer(ModelSerializer):
|
|
class Meta:
|
|
model = Account
|
|
exclude = ('id',)
|
|
nested = True
|
|
|
|
The `nested` option may be set to either `True`, `False`, or an integer value. If given an integer value it indicates the depth of relationships that should be traversed before reverting to a flat representation.
|
|
|
|
When serializing objects using a nested representation any occurances of recursion will be recognised, and will fall back to using a flat representation.
|
|
|
|
The `nested` option may also be set by passing it to the `serialize()` method.
|
|
|
|
**[TODO: Possibly only allow .serialize(nested=…) in FixtureSerializer]**
|
|
|
|
## Customising the default fields used by a ModelSerializer
|
|
|
|
class AccountSerializer(ModelSerializer):
|
|
class Meta:
|
|
model = Account
|
|
|
|
def get_pk_field(self, model_field):
|
|
return Field(readonly=True)
|
|
|
|
def get_nested_field(self, model_field):
|
|
return ModelSerializer()
|
|
|
|
def get_related_field(self, model_field):
|
|
return NaturalKeyField()
|
|
|
|
def get_field(self, model_field):
|
|
return Field()
|
|
|
|
|
|
[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
|