aboutsummaryrefslogtreecommitdiffstats
path: root/docs/api-guide/serializers.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/api-guide/serializers.md')
-rw-r--r--docs/api-guide/serializers.md241
1 files changed, 241 insertions, 0 deletions
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
new file mode 100644
index 00000000..377b0c10
--- /dev/null
+++ b/docs/api-guide/serializers.md
@@ -0,0 +1,241 @@
+# 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_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