diff --git a/api-guide/fields/index.html b/api-guide/fields/index.html index 9b4ad3e30..46fa5f687 100644 --- a/api-guide/fields/index.html +++ b/api-guide/fields/index.html @@ -851,7 +851,7 @@ class UserSerializer(serializers.ModelSerializer):

Custom fields

If you want to create a custom field, you'll need to subclass Field and then override either one or both of the .to_representation() and .to_internal_value() methods. These two methods are used to convert between the initial datatype, and a primitive, serializable datatype. Primitive datatypes will typically be any of a number, string, boolean, date/time/datetime or None. They may also be any list or dictionary like object that only contains other primitive objects. Other types might be supported, depending on the renderer that you are using.

The .to_representation() method is called to convert the initial datatype into a primitive, serializable datatype.

-

The to_internal_value() method is called to restore a primitive datatype into its internal python representation.

+

The to_internal_value() method is called to restore a primitive datatype into its internal python representation. This method should raise a serializer.ValidationError if the data is invalid.

Note that the WritableField class that was present in version 2.x no longer exists. You should subclass Field and override to_internal_value() if the field supports data input.

Examples

Let's look at an example of serializing a class that represents an RGB color value:

@@ -890,6 +890,49 @@ class ColorField(serializers.Field): """ return obj.__class__.__name__ +

Raising validation errors

+

Our ColorField class above currently does not perform any data validation. +To indicate invalid data, we should raise a serializers.ValidationError, like so:

+
def to_internal_value(self, data):
+    if not isinstance(data, six.text_type):
+        msg = 'Incorrect type. Expected a string, but got %s'
+        raise ValidationError(msg % type(data).__name__)
+
+    if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data):
+        raise ValidationError('Incorrect format. Expected `rgb(#,#,#)`.')
+
+    data = data.strip('rgb(').rstrip(')')
+    red, green, blue = [int(col) for col in data.split(',')]
+
+    if any([col > 255 or col < 0 for col in (red, green, blue)]):
+        raise ValidationError('Value out of range. Must be between 0 and 255.')
+
+    return Color(red, green, blue)
+
+

The .fail() method is a shortcut for raising ValidationError that takes a message string from the error_messages dictionary. For example:

+
default_error_messages = {
+    'incorrect_type': 'Incorrect type. Expected a string, but got {input_type}',
+    'incorrect_format': 'Incorrect format. Expected `rgb(#,#,#)`.',
+    'out_of_range': 'Value out of range. Must be between 0 and 255.'
+}
+
+def to_internal_value(self, data):
+    if not isinstance(data, six.text_type):
+        msg = 'Incorrect type. Expected a string, but got %s'
+        self.fail('incorrect_type', input_type=type(data).__name__)
+
+    if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data):
+        self.fail('incorrect_format')
+
+    data = data.strip('rgb(').rstrip(')')
+    red, green, blue = [int(col) for col in data.split(',')]
+
+    if any([col > 255 or col < 0 for col in (red, green, blue)]):
+        self.fail('out_of_range')
+
+    return Color(red, green, blue)
+
+

This style keeps you error messages more cleanly separated from your code, and should be preferred.

Third party packages

The following third party packages are also available.

DRF Compound Fields

diff --git a/api-guide/serializers/index.html b/api-guide/serializers/index.html index cbb4f32f1..75c3f4541 100644 --- a/api-guide/serializers/index.html +++ b/api-guide/serializers/index.html @@ -461,6 +461,10 @@ Customizing multiple update +
  • + Customizing ListSerializer initialization +
  • + @@ -617,7 +621,7 @@ serializer.validated_data

    If your object instances correspond to Django models you'll also want to ensure that these methods save the object to the database. For example, if Comment was a Django model, the methods might look like this:

        def create(self, validated_data):
    -        return Comment.objcts.create(**validated_data)
    +        return Comment.objects.create(**validated_data)
     
         def update(self, instance, validated_data):
             instance.email = validated_data.get('email', instance.email)
    @@ -982,12 +986,12 @@ AccountSerializer():
     

    How hyperlinked views are determined

    There needs to be a way of determining which views should be used for hyperlinking to model instances.

    By default hyperlinks are expected to correspond to a view name that matches the style '{model_name}-detail', and looks up the instance by a pk keyword argument.

    -

    You can override a URL field view name and lookup field by using either, or both of, the view_name and lookup_field options in the extra_field_kwargs setting, like so:

    +

    You can override a URL field view name and lookup field by using either, or both of, the view_name and lookup_field options in the extra_kwargs setting, like so:

    class AccountSerializer(serializers.HyperlinkedModelSerializer):
         class Meta:
             model = Account
             fields = ('account_url', 'account_name', 'users', 'created')
    -        extra_field_kwargs = {
    +        extra_kwargs = {
                 'url': {'view_name': 'accounts', 'lookup_field': 'account_name'}
                 'users': {'lookup_field': 'username'}
             }
    @@ -1084,6 +1088,17 @@ class BookSerializer(serializers.Serializer):
             list_serializer_class = BookListSerializer
     

    It is possible that a third party package may be included alongside the 3.1 release that provides some automatic support for multiple update operations, similar to the allow_add_remove behavior that was present in REST framework 2.

    +

    Customizing ListSerializer initialization

    +

    When a serializer with many=True is instantiated, we need to determine which arguments and keyword arguments should be passed to the .__init__() method for both the child Serializer class, and for the parent ListSerializer class.

    +

    The default implementation is to pass all arguments to both classes, except for validators, and any custom keyword arguments, both of which are assumed to be intended for the child serializer class.

    +

    Occasionally you might need to explicitly specify how the child and parent classes should be instantiated when many=True is passed. You can do so by using the many_init class method.

    +
        @classmethod
    +    def many_init(cls, *args, **kwargs):
    +        # Instantiate the child serializer.
    +        kwargs['child'] = cls()
    +        # Instantiate the parent list serializer.
    +        return CustomListSerializer(*args, **kwargs)
    +

    BaseSerializer

    BaseSerializer class that can be used to easily support alternative serialization and deserialization styles.

    diff --git a/index.html b/index.html index 3eac76477..c6da0ba90 100644 --- a/index.html +++ b/index.html @@ -457,7 +457,7 @@ - +


    @@ -480,7 +480,7 @@

    Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs.

    Some reasons you might want to use REST framework: