Django REST Framework API Guide (1) : Requesting Django REST Framework API Guide (2) : Responding to Django REST Framework API Guide (3) : Views Django REST Framework API Guide (4) : Generic Views Django REST Framework API Guide (5) : View Sets Django REST Framework API Guide (6) : Routing Django REST Framework API Guide (7) : Parsing Django REST Framework API Guide (8) : Rendering Django REST Framework API Guide (9) : serialization

Link to official text

Serializers

Serializers allow you to convert complex data such as query sets and model instances to native Python data types, which can then be easily rendered as JSON, XML, or other content types. The serializer also provides deserialization, which converts parsed data back to complex types after the first validation of incoming data.

The serialization classes in the REST Framework are very similar to Djangos Form and ModelForm classes. We provide a Serializer class, which provides a powerful generic way to control the output of a response, and a ModelSerializer class, which provides an efficient shortcut for creating serialization of processing model instances and query sets.

Claim serialization class

Start by creating a simple object for the example:

from datetime import datetime

class Comment(object):
    def __init__(self, email, content, created=None):
        self.email = email
        self.content = content
        self.created = created or datetime.now()

comment = Comment(email='[email protected]', content='foo bar')
Copy the code

Declare a serialization class that is used to serialize and deserialize the data corresponding to the Comment object.

Declaring a serialized class looks very similar to declaring a form:

from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()
Copy the code

Serialized object

You can now serialize comments or comment lists using CommentSerializer. Also, using the Serializer class looks a lot like using the Form class.

serializer = CommentSerializer(comment)
serializer.data
# {'email': '[email protected]', 'content': 'foo ', 'created': '2016-01-27t15:17:10.375877 '} # {'email': '[email protected]', 'content': 'foo ', 'created':' 2016-01-27t15:17:10.375877 '}
Copy the code

At this point, the model instance has been converted to a Python native data type. To complete the serialization process, render the data as JSON.

from rest_framework.renderers import JSONRenderer

json = JSONRenderer().render(serializer.data)
json
# b "{" email" : "[email protected]", "content" : "foo bar", "created" : "the 2016-01-27 T15: closed. 375877"} '
Copy the code

Deserialize object

Deserialization is similar. First we parse a stream to a Python native data type…

from django.utils.six import BytesIO
from rest_framework.parsers import JSONParser

stream = BytesIO(json)
data = JSONParser().parse(stream)
Copy the code

. We then restore these native data types to validated data dictionaries.

serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'foo bar', 'email': '[email protected]', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
Copy the code

Save the instance

If you want to be able to return a complete object instance based on validated data, you need to implement one or both of the.create() and.update() methods. Such as:

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

    def create(self, validated_data):
        return Comment(**validated_data)

    def update(self, instance, validated_data):
        instance.email = validated_data.get('email', instance.email)
        instance.content = validated_data.get('content', instance.content)
        instance.created = validated_data.get('created', instance.created)
        return instance
Copy the code

If the object instance corresponds to the Django model, you also need to make sure that these methods save the object to the database. If Comment were a Django model, these methods might look like this:

    def create(self, validated_data):
        return Comment.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.email = validated_data.get('email', instance.email)
        instance.content = validated_data.get('content', instance.content)
        instance.created = validated_data.get('created', instance.created)
        instance.save()
        return instance
Copy the code

Now, when deserializing the data, we can call.save() to return an object instance based on the validated data.

comment = serializer.save()
Copy the code

Calling.save() creates a new instance or updates an existing instance, depending on whether the existing instance was passed when the serialized class was instantiated:

# .save() will create a new instance.
serializer = CommentSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
Copy the code

The.create() and.update() methods are optional. You can implement neither, or either or both, depending on the use case for your serialized class.

Passes additional attributes to.save()

Sometimes you want your view code to be able to inject extra data while saving the instance. This additional data may contain the current user, the current time, or any other information that is not part of the requested data.

serializer.save(owner=request.user)
Copy the code

When.create() or.update() is called, any other keyword arguments are included in the validATED_data argument.

Directly covered.save().

In some cases, the.create() and.update() method names may not make sense. For example, in the Contact form, instead of creating a new instance, we might send an email or other message.

In these cases, you can choose to override.save() directly because it is more readable and meaningful.

Here’s an example:

class ContactForm(serializers.Serializer):
    email = serializers.EmailField()
    message = serializers.CharField()

    def save(self):
        email = self.validated_data['email']
        message = self.validated_data['message']
        send_email(from=email, message=message)
Copy the code

Note that in the above case, the serializer.validATED_data property must be accessed directly.

validation

When deserializing data, you always need to call is_Valid (), or save the object instance, before attempting to access the validation data. If any validation errors occur, the.errors attribute contains a dictionary of error messages. Such as:

serializer = CommentSerializer(data={'email': 'foobar'.'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']}
Copy the code

Each key in the dictionary is a field name, and the value is an error message (list of strings) corresponding to that field. The non_field_errors key may also exist and lists any general validation errors. You can customize the name of the non_field_errors keyword using NON_FIELD_ERRORS_KEY (set in the Settings file).

When deserializing the item list, an error is returned as a dictionary list representing each deserialized item.

Data validation throws an exception

Is_valid () method with an optional raise_exception logo, if there is a validation error, will cause it causes serializers. Abnormal ValidationError.

These exceptions are handled automatically by the default exception handler provided by the REST Framework and will return an HTTP 400 Bad Request by default.

# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)
Copy the code

Field-level validation

You can specify custom field-level validation by adding the.validate_

method to the Serializer subclass. These are similar to the.clean_

methods on Django forms.

These methods take only one parameter, the value of the field to validate.

Your validate_ < field_name > method validation should return values or trigger serializers. ValidationError.

Such as:

from rest_framework import serializers

class BlogPostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField()

    def validate_title(self, value):
        """ Check that the blog post is about Django. """
        if 'django' not in value.lower():
            raise serializers.ValidationError("Blog post is not about Django")
        return value
Copy the code

Note: If the

parameter declared in your serializer is required = False, this validation step will not be performed if this field is not included.

Object-level validation

If you want additional validation for multiple fields, add a method called.validate() to your Serializer subclass. This method takes only one argument, which is a dictionary of field-values. If necessary, it should raise a ValidationError, or simply return the validated value. Such as:

from rest_framework import serializers

class EventSerializer(serializers.Serializer):
    description = serializers.CharField(max_length=100)
    start = serializers.DateTimeField()
    finish = serializers.DateTimeField()

    def validate(self, data):
        """ Check that the start is before the stop. """
        if data['start'] > data['finish'] :raise serializers.ValidationError("finish must occur after start")
        return data
Copy the code

The validator

Individual fields on the serializer can contain validators by declaring them on the field instance, for example:

def multiple_of_ten(value):
    if value % 10! =0:
        raise serializers.ValidationError('Not a multiple of ten')

class GameRecord(serializers.Serializer):
    score = IntegerField(validators=[multiple_of_ten])
    ...
Copy the code

Serialized classes can also contain reusable validators that apply to the entire set of field data. These validators are included by declaring them in an internal Meta class, as follows:

class EventSerializer(serializers.Serializer):
    name = serializers.CharField()
    room_number = serializers.IntegerField(choices=[101.102.103.201])
    date = serializers.DateField()

    class Meta:
        # Each room only has one event per day.
        validators = UniqueTogetherValidator(
            queryset=Event.objects.all(),
            fields=['room_number'.'date'])Copy the code

It’s okay if you don’t understand it, but we’ll talk more about validation in the future.

Access initial data and instances

When an initial object or query set is passed to a serialized class instance, the object is provided as.instance. If the initial object is not passed, the.instance property will be None.

When data is passed to the serialized class instance, the unmodified data is provided as.initial_data. If the data keyword argument is not passed, the.initial_data attribute does not exist.

Part of the update

By default, the serializer must pass values for all required fields or a validation error will be raised. You can use the partial parameter to allow partial updates.

# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
Copy the code

Handle nested objects

The previous examples work with objects that have only simple data types, but sometimes you need to be able to represent more complex objects where some properties of the object may not be simple data types, such as strings, dates, or integers.

The Serializer class is itself a kind of Field that can be used to represent a relationship between one object type nested within another object type.

class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField(max_length=100)

class CommentSerializer(serializers.Serializer):
    user = UserSerializer()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()
Copy the code

If the nested object can be None, the required = False flag should be passed to the nested serialized class.

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)  # May be an anonymous user.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()
Copy the code

Similarly, if the nested object is a list, the many = True flag should be passed to the nested serialized class.

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)
    edits = EditItemSerializer(many=True)  # A nested list of 'edit' items.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()
Copy the code

Writable nested representation

When dealing with nested representations that support deserialization of data, any errors in the nested object will be nested under the field name of the nested object.

serializer = CommentSerializer(data={'user': {'email': 'foobar'.'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}
Copy the code

Similarly, the.validATED_data attribute will contain nested data structures.

Write for nested representations.create()methods

If you support writable nested representations, you’ll need to write.create() or.update() methods that handle saving multiple objects.

The following example demonstrates how to create users using nested profile objects.

class UserSerializer(serializers.ModelSerializer):
    profile = ProfileSerializer()

    class Meta:
        model = User
        fields = ('username'.'email'.'profile')

    def create(self, validated_data):
        profile_data = validated_data.pop('profile')
        user = User.objects.create(**validated_data)
        Profile.objects.create(user=user, **profile_data)
        return user
Copy the code

Write for nested representations.update()methods

For updates, you need to think carefully about how you handle relationship updates. For example, if the data for the relationship is None or not provided, which of the following should happen?

  • Set the relationship in the database toNULL.
  • Delete the associated instance.
  • Ignore the data and leave it as it is.
  • Raises a validation error.

Here is an example of the.update() method from our previous UserSerializer class.

    def update(self, instance, validated_data):
        profile_data = validated_data.pop('profile')
        # Unless the application properly enforces that this field is
        # always set, the follow could raise a `DoesNotExist`, which
        # would need to be handled.
        profile = instance.profile

        instance.username = validated_data.get('username', instance.username)
        instance.email = validated_data.get('email', instance.email)
        instance.save()

        profile.is_premium_member = profile_data.get(
            'is_premium_member',
            profile.is_premium_member
        )
        profile.has_support_contract = profile_data.get(
            'has_support_contract',
            profile.has_support_contract
         )
        profile.save()

        return instance
Copy the code

Because the behavior of nested creation and updates can be ambiguous and may require complex dependencies between related models, REST Framework 3 requires that you always explicitly write these methods. The default ModelSerializer.create() and.update() methods do not include support for writable nested representations.

However, there are third-party packages available, such as DRF Writable Nested representations that support automatic Writable nesting.

Save the associated instance in the model manager class

Another way to store multiple related instances in serialized classes is to write custom model manager classes.

For example, suppose we want to ensure that a User instance and a Profile instance are always created as a pair. We might write a custom manager class like the following:

class UserManager(models.Manager):.def create(self, username, email, is_premium_member=False, has_support_contract=False):
        user = User(username=username, email=email)
        user.save()
        profile = Profile(
            user=user,
            is_premium_member=is_premium_member,
            has_support_contract=has_support_contract
        )
        profile.save()
        return user
Copy the code

This manager class now better encapsulates that user instances and profile instances are always created at the same time. You can now rewrite the.create() method on the serialized class to use the new management class method.

def create(self, validated_data):
    return User.objects.create(
        username=validated_data['username'],
        email=validated_data['email']
        is_premium_member=validated_data['profile'] ['is_premium_member']
        has_support_contract=validated_data['profile'] ['has_support_contract'])Copy the code

Working with multiple objects

The Serializer class can also handle a list of serialized or deserialized objects.

Serialize multiple objects

To serialize a query set or list of objects rather than a single object instance, the many=True flag should be passed when instantiating the serialized class. You can then pass a set of queries or a list of objects to serialize.

queryset = Book.objects.all()
serializer = BookSerializer(queryset, many=True)
serializer.data
# [
# {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'},
# {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'},
# {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'}
#]
Copy the code

Deserialize multiple objects

The default behavior for deserializing multiple objects is to support multiple object creation, but not multiple object updates.

Include additional context

In addition to the object being serialized, there are cases where you need to provide additional context for serialized classes. A common case is that if you are using a serialized class that contains hyperlink relationships, you need the serialized class to access the current request so that it can properly generate the fully qualified URL.

When instantiating a serialized object, you can provide any additional context by passing context parameters. Such as:

serializer = AccountSerializer(account, context={'request': request})
serializer.data
# {'id': 6, 'owner': u'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}
Copy the code

By accessing the self.context property, you can use context dictionaries in any serialized object field logic, such as the custom.to_representation() method.

ModelSerializer

In general, you want serialized classes to map tightly to Django model definitions.

The ModelSerializer class provides a shortcut that lets you automatically create a Serializer class whose fields correspond to the model class fields.

ModelSerializerClass and conventionalSerializerClasses are the same, except that:

  • It automatically generates a set of fields based on the model.
  • It automatically generates validators for serialized classes, such as the Unique_together validator.
  • It contains.create().update()The simple default implementation of.

Declare the ModelSerializer as follows:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id'.'account_name'.'users'.'created')
Copy the code

By default, all model class fields in the class are mapped to the corresponding serialized class fields.

Any relationships, such as foreign keys on the model, are mapped to PrimaryKeyRelatedField. Reverse relationships are not included by default unless specified in the serialization relationship document.

checkModelSerializer

The serialized class generates a representation string that lets you fully check the state of its fields. This is especially useful when working with ModelSerializer, where you need to determine which fields and validators it automatically creates for you.

To do this, use the Python manage.py shell to enter the Django shell, then import the serialized class, instantiate it, and print the object representation…

>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print(repr(serializer))
AccountSerializer():
    id = IntegerField(label='ID', read_only=True)
    name = CharField(allow_blank=True, max_length=100, required=False)
    owner = PrimaryKeyRelatedField(queryset=User.objects.all())
Copy the code

Specifies the fields to include

If you only want to use a subset of the default fields in the model serializer, you can do this using the fields or exclude option, just as you would with ModelForm. It is strongly recommended that you explicitly use the fields property for all fields serialized. This will make it less likely that you will inadvertently expose data when the model changes.

Here’s an example:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id'.'account_name'.'users'.'created')
Copy the code

You can also set the fields property to a special value ‘__all__’ to indicate that all fields in the model should be used.

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = '__all__'
Copy the code

You can set the exclude property to a list of fields to exclude from the serializer.

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        exclude = ('users'.)Copy the code

In the example above, if the Account model had three fields account_name, Users, and created, this would cause the fields Account_name and CREATED to be serialized.

The names in the fields and Exclude attributes typically map to the model fields of the model class.

Or the name in the Fields option can be mapped to an attribute or method. It does not become a parameter in the model class.

As of version 3.3.0, one of the attributes fields or Exclude must be provided.

Specifies nested serialization

The default ModelSerializer uses primary keys for association, but you can also easily generate nested representations (self-association) using the Depth option:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id'.'account_name'.'users'.'created')
        depth = 1
Copy the code

The depth option should be set to an integer value indicating the depth of the association that should be traversed before reverting to a flat representation.

If you want to customize the serialization, you need to define your own fields.

Specify fields explicitly

You can add additional fields to The ModelSerializer or override the default fields by declaring fields on the class, just as you did with the Serializer class.

class AccountSerializer(serializers.ModelSerializer):
    url = serializers.CharField(source='get_absolute_url', read_only=True)
    groups = serializers.PrimaryKeyRelatedField(many=True)

    class Meta:
        model = Account
Copy the code

Additional fields can correspond to any property or callable field on the model.

Specify a read-only field

You may want to specify multiple fields as read-only. Instead of explicitly adding a read_only = True attribute to each field, you can use the shortcut Meta option read_only_fields.

This option should be a list or tuple of field names, declared as follows:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id'.'account_name'.'users'.'created')
        read_only_fields = ('account_name'.)Copy the code

The AutoField is set to read-only by default and does not need to be added to the read_only_fields option.

Note: There is a special case where read-only fields are part of the unique_together constraint at the model level. In this case, the serialized class requires validation to constrain the field, but it also cannot be edited by the user.

The correct way to handle this is to explicitly specify fields in the serialized class, providing both read_only = True and default =… Keyword parameter.

One example is a read-only relationship with the currently authenticated User, which, along with another identifier, is unique_together. In this case, you declare the user field like this:

user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
Copy the code

We’ll talk more about validation later

Other keyword parameters

There is also a shortcut that allows you to specify any additional keyword arguments on a field using the EXTRA_kwargs option. As in the case of read_only_fields, this means that you don’t need to declare the field explicitly in the serialized class.

This option is a dictionary that maps field names to keyword parameter dictionaries. Such as:

class CreateUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('email'.'username'.'password')
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = User(
            email=validated_data['email'],
            username=validated_data['username']
        )
        user.set_password(validated_data['password'])
        user.save()
        return user
Copy the code

Relationship between the field

When serializing model instances, you can choose a number of different ways to represent relationships. The default representation of ModelSerializer is to use the primary key of the related instance.

Other representations include serialization using hyperlinks, serialization of a fully nested representation, or serialization using a custom representation.

Custom field mappings

The ModelSerializer class also exposes an API that you can override to change the fields of a serialized object when it is instantiated.

In general, if The ModelSerializer doesn’t generate the required fields by default, you should either explicitly add them to the class or use the regular Serializer class. In some cases, however, you may need to create a new base class that defines how to create the fields of a serialized object for a given model.

.serializer_field_mapping

Mapping Django model classes to REST Framework serialized classes. You can override this mapping to change the default serialized class that should be used for each model class.

.serializer_related_field

This property should be the serializer field class, which is used by default for relational fields.

For ModelSerializer, it defaults to PrimaryKeyRelatedField.

For HyperlinkedModelSerializer, it defaults to serializers. HyperlinkedRelatedField.

serializer_url_field

Serializer field class, which should be used to serialize any URL field in the class.

The default is serializers. HyperlinkedIdentityField.

serializer_choice_field

The serializer field class that should be used for any selected field in the serializer.

The default is serializers.ChoiceField.

Field_class and field_kwargs API

The following methods are called to determine the class and keyword arguments for each field that should be automatically included in the serializer. Each of these methods should return two tuples (field_class, field_kwargs).

.build_standard_field(self, field_name, model_field)

Call to generate serializer fields mapped to standard model fields.

The default implementation returns the serialized class based on the serializer_field_mapping property.

.build_relational_field(self, field_name, relation_info)

Call to generate serializer fields mapped to relational model fields.

The default implementation returns a serialized class based on the serializer_Relational_field property.

The relation_info parameter is a named tuple that contains the model_field, related_model, to_MANY, and has_through_model properties.

.build_nested_field(self, field_name, relation_info, nested_depth)

Called to generate serializer fields mapped to relational model fields when the Depth option is set.

The default implementation dynamically create a ModelSerializer based or HyperlinkedModelSerializer nested class.

Nested_depth will be the value of the depth option minus 1.

The relation_info parameter is a named tuple that contains the model_field, related_model, to_MANY, and has_through_model properties.

.build_property_field(self, field_name, model_class)

Called to generate a serializer field that maps to a property or zero-argument method on a model class.

The default implementation returns a ReadOnlyField class.

.build_url_field(self, field_name, model_class)

Called to generate a serializer field for the serializer’s own URL field.

The default implementation returns a HyperlinkedIdentityField class.

.build_unknown_field(self, field_name, model_class)

Called when the field name is not mapped to any model field or model attribute. The default implementation raises an error. However, subclasses can customize this behavior.

HyperlinkedModelSerializer

HyperlinkedModelSerializer classes and ModelSerializer similar, except that it use hyperlinks to represent the relationship rather than the primary key.

By default, the serializer will contain a URL field instead of a primary key field.

The URL field will be represented using the HyperlinkedRelatedField serializer field, and any relationships on the model will be represented using the HyperlinkedRelatedField serializer field.

You can explicitly include primary keys by adding them to the Fields option, for example:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Account
        fields = ('url'.'id'.'account_name'.'users'.'created')
Copy the code

Absolute and relative urls

In instantiation HyperlinkedModelSerializer, must in the serialization context contains the current request, such as:

serializer = AccountSerializer(queryset, context={'request': request})
Copy the code

Doing so will ensure that hyperlinks can contain appropriate host names to generate fully qualified urls, such as:

http://api.example.com/accounts/1/
Copy the code

Instead of relative urls, such as:

/accounts/1/
Copy the code

If you do want to use relative urls, you should explicitly pass {‘request’ : None} in the serialization context.

How do I determine hyperlinked views

You need to determine which views should be used to hyperlink to model instances.

By default, hyperlinks are expected to correspond to the name of the view that matches the style ‘{model_name}-detail’ and to find instances by the PK keyword argument.

You can override URL field view names and lookup fields with the view_name and lookup_field options in the EXTRA_Kwargs setting, as shown below:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Account
        fields = ('account_url'.'account_name'.'users'.'created')
        extra_kwargs = {
            'url': {'view_name': 'accounts'.'lookup_field': 'account_name'},
            'users': {'lookup_field': 'username'}}Copy the code

Alternatively, fields in a serialized class can be explicitly set. Such as:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(
        view_name='accounts',
        lookup_field='slug'
    )
    users = serializers.HyperlinkedRelatedField(
        view_name='user-detail',
        lookup_field='username',
        many=True,
        read_only=True
    )

    class Meta:
        model = Account
        fields = ('url'.'account_name'.'users'.'created')
Copy the code

Tip: Matching hyperlinks and URL conf correctly can sometimes be a bit difficult. Print HyperlinkedModelSerializer instance repr is a particularly useful method, can accurately check the relationship between expected map view the name and the lookup field.

Change the URL field name

The name of the URL field defaults to ‘URL’. You can override this setting globally using URL_FIELD_NAME (in the Settings file).

ListSerializer

The ListSerializer class provides the ability to serialize and validate the behavior of multiple objects at once. You usually don’t need to use ListSerializer directly, but should simply pass many=True when instantiating the serialized class.

When a serialized class is instantiated and many = True is passed, a ListSerializer instance will be created. The serialization class becomes a child of the parent ListSerializer

The following arguments can also be passed to the ListSerializer field or to serialized classes that pass many = True:

allow_empty

This is True by default, but can be set to False if you want to disallow empty lists as valid input.

The customListSerializerbehavior

There are several situations where you might need to customize the ListSerializer behavior. Such as:

  • You want to provide specific validation of a list, such as checking that an element does not conflict with another element in the list.
  • You want to customize the creation or update behavior of multiple objects.

In these cases, you can modify the class used when many=True is passed by using the list_Serializer_class option in the Meta class of the serialized class.

class CustomListSerializer(serializers.ListSerializer):.class CustomSerializer(serializers.Serializer):.class Meta:
        list_serializer_class = CustomListSerializer
Copy the code

Custom creation of multiple objects

The default implementation for creating multiple objects is simply to call.create() for each item in the list. To customize this behavior, you need to customize the.create() method on the ListSerializer class when many=True is passed.

class BookListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        books = [Book(**item) for item in validated_data]
        return Book.objects.bulk_create(books)

class BookSerializer(serializers.Serializer):.class Meta:
        list_serializer_class = BookListSerializer
Copy the code

Customize updates to multiple objects

By default, the ListSerializer class does not support multiple object updates. This is because the expected behavior of inserts and deletions is unclear.

To support multi-object updates, you need to override the UPDATE method. Keep the following points in mind when writing your multi-object update code:

  • How do I determine which instance should be updated for each item in the data list?
  • How should inserts be handled? Are they invalid, or are new objects created?
  • How should deletions be handled? Do they imply object deletion, or deletion of a relationship? Should they be ignored, or invalid?
  • How do I deal with sorting? Does changing the position of two items mean changing the state or being ignored?

You need to add an explicit ID field to the instance serialized class. The default implicitly generated ID field is marked as read_only. This causes it to be deleted when updated. Once you declare it explicitly, it will be available in the update method of the list serialized class.

Here is an example of how you might choose to implement multi-object updates:

class BookListSerializer(serializers.ListSerializer):
    def update(self, instance, validated_data):
        # Maps for id->instance and id->data item.
        book_mapping = {book.id: book for book in instance}
        data_mapping = {item['id']: item for item in validated_data}

        # Perform creations and updates.
        ret = []
        for book_id, data in data_mapping.items():
            book = book_mapping.get(book_id, None)
            if book is None:
                ret.append(self.child.create(data))
            else:
                ret.append(self.child.update(book, data))

        # Perform deletions.
        for book_id, book in book_mapping.items():
            if book_id not in data_mapping:
                book.delete()

        return ret

class BookSerializer(serializers.Serializer):
    # We need to identify elements in the list using their primary key,
    # so use a writable field here, rather than the default which would be read-only.
    id = serializers.IntegerField()
    ...

    class Meta:
        list_serializer_class = BookListSerializer
Copy the code

Custom ListSerializer was initialized. Procedure

When instantiating a serialized class with many=True, we need to determine which arguments and keyword arguments should be passed to the.__ init __() methods of the child Serializer class and the parent ListSerializer class.

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

Sometimes you might want to explicitly specify how to instantiate child and parent classes when passing many=True. You can do this 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)
Copy the code

BaseSerializer

The BaseSerializer class can be used to easily support other serialization and deserialization styles.

This class implements the same basic API as the Serializer class:

  • .data– Returns the outgoing original representation.
  • .is_valid()– Deserialize and validate incoming data.
  • .validated_data– Returns the incoming data for validation.
  • .errors– Returns an error during validation.
  • .save()– Saves the validated data to the object instance.

There are four methods that can be overridden, depending on what you want the serialized class to support:

  • .to_representation()– Override this operation to support serialization for read operations.
  • .to_internal_value()– Override this operation to support deserialization for write operations.
  • The create () and the update ()– Overwrite one or both of them to support saving instances.

Because this class provides the same interface as the Serializer class, you can use it in conjunction with the generic class-based view just like the existing regular Serializer or ModelSerializer.

The only difference is that the BaseSerializer class does not generate HTML forms in a browsable API. This is because the data they return does not contain all of the field information that allows each field to be rendered as a suitable HTML input.

Read-only BaseSerializer

To implement the read-only serialization class using the BaseSerializer class, we simply override the.to_representation() method. Let’s look at an example using a simple Django model:

class HighScore(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    player_name = models.CharField(max_length=10)
    score = models.IntegerField()
Copy the code

Creating a read-only serialized class for converting HighScore instances to primitive data types is simple.

class HighScoreSerializer(serializers.BaseSerializer):
    def to_representation(self, obj):
        return {
            'score': obj.score,
            'player_name': obj.player_name
        }
Copy the code

We can now use this class to serialize a single HighScore instance:

@api_view(['GET'])
def high_score(request, pk):
    instance = HighScore.objects.get(pk=pk)
    serializer = HighScoreSerializer(instance)
    return Response(serializer.data)
Copy the code

Or use it to serialize multiple instances:

@api_view(['GET'])
def all_high_scores(request):
    queryset = HighScore.objects.order_by('-score')
    serializer = HighScoreSerializer(queryset, many=True)
    return Response(serializer.data)
Copy the code

The Read – write BaseSerializer class

To create a serialized class that can be read and written, we first need to implement a.to_internal_value() method. This method returns the validation value that will be used to construct the object instance, and may raise a ValidationError if the data format provided is incorrect.

Once.to_internal_value() is implemented, the basic validation API will be available in the serializer, and you will be able to use.is_valid(),.validated_data, and.errors.

If you also want to support.save(), you need to implement one or both of the.create() and.update() methods.

Below is a complete example of our previous HighScoreSerializer, which has been updated to support read and write operations.

class HighScoreSerializer(serializers.BaseSerializer):
    def to_internal_value(self, data):
        score = data.get('score')
        player_name = data.get('player_name')

        # Perform the data validation.
        if not score:
            raise ValidationError({
                'score': 'This field is required.'
            })
        if not player_name:
            raise ValidationError({
                'player_name': 'This field is required.'
            })
        if len(player_name) > 10:
            raise ValidationError({
                'player_name': 'May not be more than 10 characters.'
            })

        # Return the validated values. This will be available as
        # the `.validated_data` property.
        return {
            'score': int(score),
            'player_name': player_name
        }

    def to_representation(self, obj):
        return {
            'score': obj.score,
            'player_name': obj.player_name
        }

    def create(self, validated_data):
        return HighScore.objects.create(**validated_data)
Copy the code

Create a new base class

The BaseSerializer class is also useful if you want to implement new generic serialization classes to handle a particular serialization style or to integrate with an optional storage back end.

The following classes are examples of generic serialized classes that can handle casting arbitrary objects to their basic representation.

class ObjectSerializer(serializers.BaseSerializer):
    """ A read-only serializer that coerces arbitrary complex objects into primitive representations. """
    def to_representation(self, obj):
        for attribute_name in dir(obj):
            attribute = getattr(obj, attribute_name)
            if attribute_name('_') :# Ignore private attributes.
                pass
            elif hasattr(attribute, '__call__') :# Ignore methods and other callables.
                pass
            elif isinstance(attribute, (str, int, bool, float, type(None))) :# Primitive types can be passed through unmodified.
                output[attribute_name] = attribute
            elif isinstance(attribute, list):
                # Recursively deal with items in lists.
                output[attribute_name] = [
                    self.to_representation(item) for item in attribute
                ]
            elif isinstance(attribute, dict):
                # Recursively deal with items in dictionaries.
                output[attribute_name] = {
                    str(key): self.to_representation(value)
                    for key, value in attribute.items()
                }
            else:
                # Force anything else to its string representation.
                output[attribute_name] = str(attribute)
Copy the code

Serializer is advanced for use

Override serialization and deserialization behavior

If you need to change the serialization or deserialization behavior of a serialized class, you can do so by overriding.to_representation() or.to_internal_value() methods.

These two methods may need to be overridden for the following reasons…

  • Add new behavior for the new serialization base class.
  • Modify the behavior of existing classes slightly.
  • Improve serialization performance of frequently accessed API endpoints to return large amounts of data.

These methods are signed as follows:

.to_representation(self, obj)

Accepts the object instance to serialize and returns a raw representation. Usually this means returning a structure with built-in Python data types. The exact types that can be handled depend on the rendering classes you configure for the API.

May be overwritten to modify the presentation style. Such as:

def to_representation(self, instance):
    """Convert `username` to lowercase."""
    ret = super().to_representation(instance)
    ret['username'] = ret['username'].lower()
    return ret
Copy the code

.to_internal_value(self, data)

Incoming unvalidated data is taken as input and the validation data that will be provided as serializer.validATED_data should be returned. If.save() is called on the serialized class, the return value is also passed to the.create() or.update() methods.

If the validation fails, the method will cause serializers. ValidationError (errors). The errors argument should be a dictionary mapped from field names (or settings.non_field_errors_key) to a list of error messages. If you do not need to change the deserialization behavior and want to provide object-level validation, it is recommended to override the.validate() method instead.

The data argument passed to this method is usually the value of Request. data, so the data type it provides will depend on the parser class you configured for the API.

Inheriting serialized classes

Like Django forms, you can extend and reuse serialized classes by inheritance. This allows you to declare a common set of fields or methods on a parent class, which can then be used in multiple serialized classes. For example,

class MyBaseSerializer(Serializer):
    my_field = serializers.CharField()

    def validate_my_field(self):.class MySerializer(MyBaseSerializer):.Copy the code

As with Django’s Model and ModelForm classes, the inner Meta class in a serialized class does not implicitly inherit from the inner Meta class of its parent. If you want a Meta class to inherit from a parent class, you must specify this explicitly. Such as:

class AccountSerializer(MyBaseSerializer):
    class Meta(MyBaseSerializer.Meta):
        model = Account
Copy the code

It is generally recommended not to use inheritance in internal Meta classes, but to declare all options explicitly.

Dynamically modifying fields

Once the serialized class has been initialized, you can use the.fields property to access the field dictionary set in the serialized class. This property can be accessed and modified to dynamically modify serialized classes.

Modifying the fields parameter directly allows you to do some interesting things, such as changing the parameters of the serialized field at run time rather than when declaring the serialized class.

Here’s an example:

For example, if you wanted to be able to set which fields a serialized class should use when initialized, you could create a serialized class like this:

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    """ A ModelSerializer that takes an additional `fields` argument that controls which fields should be displayed. """

    def __init__(self, *args, **kwargs):
        # Don't pass the 'fields' arg up to the superclass
        fields = kwargs.pop('fields'.None)

        # Instantiate the superclass normally
        super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

        if fields is not None:
            # Drop any fields that are not specified in the `fields` argument.
            allowed = set(fields)
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)
Copy the code

This will allow you to do the following:

>>> class UserSerializer(DynamicFieldsModelSerializer):
>>>     class Meta:
>>>         model = User
>>>         fields = ('id'.'username'.'email') > > >>>> print UserSerializer(user)
{'id': 2.'username': 'jonwatts'.'email': '[email protected]'} > > >>>> print UserSerializer(user, fields=('id'.'email'))
{'id': 2.'email': '[email protected]'}
Copy the code