Official original text link this series of articles github address reprint please indicate the source

Serializer relationship

Relationship fields are used to represent model relationships. They can be applied to ForeignKey, ManyToManyField and OneToOneField relationships, reverse relationships, and custom relationships such as GenericForeignKey.

Note: Relational fields are declared in relations.py, but by convention you should import them from the serializers module using from rest_framework import serializers, And reference fields like serializers.

.

Check relationships.

When using the ModelSerializer class, serialization fields and relationships are automatically generated for you. Examine these automatically generated fields to learn how to customize the relationship style.

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

>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print repr(serializer)  # Or `print(repr(serializer))` in Python 3.x.
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

API reference

To explain the various types of relational fields, we will use a few simple models for our example. Our model will use music albums, along with the tracks listed in each album.

class Album(models.Model):
    album_name = models.CharField(max_length=100)
    artist = models.CharField(max_length=100)

class Track(models.Model):
    album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
    order = models.IntegerField()
    title = models.CharField(max_length=100)
    duration = models.IntegerField()

    class Meta:
        unique_together = ('album'.'order')
        ordering = ['order']

    def __unicode__(self):
        return '%d: %s' % (self.order, self.title)
Copy the code

StringRelatedField

The StringRelatedField is used to represent relationships using the __unicode__ method.

For example, the following serialized class.

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.StringRelatedField(many=True)

    class Meta:
        model = Album
        fields = ('album_name'.'artist'.'tracks')
Copy the code

Serialize to the following form.

{
    'album_name': 'Things We Lost In The Fire',
    'artist': 'Low',
    'tracks': [
        '1: Sunflower',
        '2: Whitetail',
        '3: Dinosaur Act',
        ...
    ]
}
Copy the code

This field is read-only.

Parameters:

  • many– If the relationship is one-to-many, set this parameter toTrue.

PrimaryKeyRelatedField

PrimaryKeyRelatedField is used to represent relationships using its primary key.

For example, the following serialization class:

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = Album
        fields = ('album_name'.'artist'.'tracks')
Copy the code

Serialize to this representation:

{
    'album_name': 'Undun',
    'artist': 'The Roots',
    'tracks': [
        89,
        90,
        91,
        ...
    ]
}
Copy the code

By default, this field is read and write, but you can change this behavior using the read_only flag.

Parameters:

  • queryset– The query set used for model instance queries when validating field input. The query set must be explicitly set, or setread_only=True.
  • many– This parameter should be set to if applied to one-to-many relationshipsTrue.
  • allow_null– If set toTrue, then the field will acceptNoneValue or an empty string for a nullable relationship. The default isFalse.
  • pk_field– Set to a field to control serialization/deserialization of primary key values. For example,pk_field=UUIDField(format='hex')Serializes the UUID primary key into its compact hexadecimal representation.

HyperlinkedRelatedField

HyperlinkedRelatedField is used to represent relationships using hyperlinks.

For example, the following serialization class:

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.HyperlinkedRelatedField(
        many=True,
        read_only=True,
        view_name='track-detail'
    )

    class Meta:
        model = Album
        fields = ('album_name'.'artist'.'tracks')
Copy the code

Serialize to this representation:

{
    'album_name': 'Graceland',
    'artist': 'Paul Simon',
    'tracks': [
        'http://www.example.com/api/tracks/45/',
        'http://www.example.com/api/tracks/46/',
        'http://www.example.com/api/tracks/47/',
        ...
    ]
}
Copy the code

By default, this field is read and write, but you can change this behavior using the read_only flag.


Note: This field is designed for objects that map to urls that accept a single URL keyword argument, such as objects set with lookup_field and lookup_URl_kwarg parameters.

This applies to urls that contain a single primary key or slug parameter as part of the URL.

If you need a more complex hyperlink representation, you’ll need to customize this field, as explained later.


Parameters:

  • view_name– Used as the view name of the relational target. If you are using the standard router class, this will be a format for<modelname>-detailString of.mandatory.
  • queryset– The query set used for model instance queries when validating field input. The query set must be explicitly set, or setread_only=True.
  • many– This parameter should be set to if applied to one-to-many relationshipsTrue.
  • allow_null– If set toTrue, then the field will acceptNoneValue or an empty string for a nullable relationship. The default isFalse.
  • lookup_field– The target field for lookup. Corresponds to the URL keyword parameter on the reference view. The default is'pk'.
  • lookup_url_kwarg– The name of the keyword parameter defined in the URL conf corresponding to the lookup field. Default use withlookup_fieldSame value.
  • format– If a format suffix is used, the hyperlink field will use the same format suffix as the target, unless usedformatParameter is overridden.

SlugRelatedField

SlugRelatedField is used to represent relationships using fields on the target.

For example, the following serialization class:

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.SlugRelatedField(
        many=True,
        read_only=True,
        slug_field='title'
     )

    class Meta:
        model = Album
        fields = ('album_name'.'artist'.'tracks')
Copy the code

Serialize to this representation:

{
    'album_name': 'Dear John'.'artist': 'Loney Dear'.'tracks': [
        'Airport Surroundings'.'Everything Turns to You'.'I Was Only Going Out'. ] }Copy the code

By default, this field is read and write, but you can change this behavior using the read_only flag.

When using SlugRelatedField as a read and write field, it is often necessary to ensure that the SLug field corresponds to the model field with Unique =True.

Parameters:

  • slug_field– The field used to represent the target. This should be the field that uniquely identifies a given instance. For example,username.mandatory
  • queryset– The query set used for model instance queries when validating field input. The query set must be explicitly set, or setread_only=True.
  • many– This parameter should be set to if applied to one-to-many relationshipsTrue.
  • allow_null– If set toTrue, then the field will acceptNoneValue or an empty string for a nullable relationship. The default isFalse.

HyperlinkedIdentityField

This field can be used as an identity relationship, such as HyperlinkedModelSerializer ‘url’ field. It can also be used for properties of objects. For example, the following serialization class:

class AlbumSerializer(serializers.HyperlinkedModelSerializer):
    track_listing = serializers.HyperlinkedIdentityField(view_name='track-list')

    class Meta:
        model = Album
        fields = ('album_name'.'artist'.'track_listing')
Copy the code

Serialize to this representation:

{
    'album_name': 'The Eraser'.'artist': 'Thom Yorke'.'track_listing': 'http://www.example.com/api/track_list/12/',}Copy the code

This field is always read-only.

Parameters:

  • view_name– Used as the view name of the relational target. If you are using the standard router class, this will be a format for<modelname>-detailString of.mandatory.
  • lookup_field– The target field for lookup. Corresponds to the URL keyword parameter on the reference view. The default is'pk'.
  • lookup_url_kwarg– The name of the keyword parameter defined in the URL conf corresponding to the lookup field. Default use withlookup_fieldSame value.
  • format– If a format suffix is used, the hyperlink field will use the same format suffix as the target, unless usedformatParameter is overridden.

Nested relations

Nested relationships can be expressed by using serialized classes as fields.

If this field is used to represent a one-to-many relationship, the many=True flag should be added to the serialized field.

Take a chestnut

For example, the following serialization class:

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = ('order'.'title'.'duration')

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True, read_only=True)

    class Meta:
        model = Album
        fields = ('album_name'.'artist'.'tracks')
Copy the code

Serialize to a nested representation like this:

>>> album = Album.objects.create(album_name="The Grey Album", artist='Danger Mouse')
>>> Track.objects.create(album=album, order=1, title='Public Service Announcement', duration=245)
<Track: Track object>
>>> Track.objects.create(album=album, order=2, title='What More Can I Say', duration=264)
<Track: Track object>
>>> Track.objects.create(album=album, order=3, title='Encore', duration=159)
<Track: Track object>
>>> serializer = AlbumSerializer(instance=album)
>>> serializer.data
{
    'album_name': 'The Grey Album'.'artist': 'Danger Mouse'.'tracks': [{'order': 1.'title': 'Public Service Announcement'.'duration': 245},
        {'order': 2.'title': 'What More Can I Say'.'duration': 264},
        {'order': 3.'title': 'Encore'.'duration': 159},... ] ,}Copy the code

Writable nested serialized classes

By default, nested serialized classes are read-only. If you want to support writes to nested serialized fields, you need to create create() and/or update() methods to explicitly specify how child relationships should be saved.

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = ('order'.'title'.'duration')

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True)

    class Meta:
        model = Album
        fields = ('album_name'.'artist'.'tracks')

    def create(self, validated_data):
        tracks_data = validated_data.pop('tracks')
        album = Album.objects.create(**validated_data)
        for track_data in tracks_data:
            Track.objects.create(album=album, **track_data)
        return album

>>> data = {
    'album_name': 'The Grey Album'.'artist': 'Danger Mouse'.'tracks': [{'order': 1.'title': 'Public Service Announcement'.'duration': 245},
        {'order': 2.'title': 'What More Can I Say'.'duration': 264},
        {'order': 3.'title': 'Encore'.'duration': 159},],}>>> serializer = AlbumSerializer(data=data)
>>> serializer.is_valid()
True
>>> serializer.save()
<Album: Album object>
Copy the code

Custom relational fields

In those rare cases where none of the existing relationship types fits the presentation you need, you can implement a completely custom relationship field that describes exactly how the output representation should be generated from the model instance.

To implement custom relational fields, you should override RelatedField and implement the.to_representation(self, value) method. This method takes the target of the field as a value parameter and returns a representation applied to the serialization target. The value parameter is usually a model instance.

If you want to implement read/write relational fields, you must also implement the.to_internal_value(self, data) method.

To provide a dynamic set of context-based queries, you can also override.get_querySet (self) instead of specifying.querySet or initializing the field on the class.

Take a chestnut

For example, we can define a relational field that serializes a track into a custom string representation using its order, title, and duration.

import time

class TrackListingField(serializers.RelatedField):
    def to_representation(self, value):
        duration = time.strftime('%M:%S', time.gmtime(value.duration))
        return 'Track %d: %s (%s)' % (value.order, value.name, duration)

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackListingField(many=True)

    class Meta:
        model = Album
        fields = ('album_name'.'artist'.'tracks')
Copy the code

Serialize to this representation:

{
    'album_name': 'Sometimes I Wish We Were an Eagle'.'artist': 'Bill Callahan'.'tracks': [
        'Track 1: Jim Cain (04:39)'.'Track 2: Eid Ma Clack Shaw (04:19)'.'Track 3: The Wind and the Dove (04:34)'. ] }Copy the code

Custom hyperlink fields

In some cases, you may need to customize the behavior of hyperlink fields to represent urls that require multiple query fields.

You can do this by inheriting HyperlinkedRelatedField. There are two methods that can be overridden:

get_url(self, obj, view_name, request, format)

The get_URL method is used to map an object instance to its URL representation.

If the view_name and lookup_field properties are not configured to properly match the URL conf, NoReverseMatch may be raised.

get_object(self, queryset, view_name, view_args, view_kwargs)

If you want to support writable hyperlink fields, you’ll also need to rewrite get_object to map incoming urls back to the objects they represent. There is no need to override this method for read-only hyperlink fields.

The return value of this method should be the object corresponding to the matching URL conf parameter.

An ObjectDoesNotExist exception may be raised.

Take a chestnut

Suppose we have a URL for a Customer object with two keyword arguments, as follows:

/api/<organization_slug>/customers/<customer_pk>/
Copy the code

This cannot be represented by a default implementation that accepts only a single lookup field.

In this case, we need to inherit the HyperlinkedRelatedField and rewrite the methods in it to get the behavior we want:

from rest_framework import serializers
from rest_framework.reverse import reverse

class CustomerHyperlink(serializers.HyperlinkedRelatedField):
    # We define these as class attributes, so we don't need to pass them as arguments.
    view_name = 'customer-detail'
    queryset = Customer.objects.all()

    def get_url(self, obj, view_name, request, format):
        url_kwargs = {
            'organization_slug': obj.organization.slug,
            'customer_pk': obj.pk
        }
        return reverse(view_name, kwargs=url_kwargs, request=request, format=format)

    def get_object(self, view_name, view_args, view_kwargs):
        lookup_kwargs = {
           'organization__slug': view_kwargs['organization_slug'].'pk': view_kwargs['customer_pk']}return self.get_queryset().get(**lookup_kwargs)
Copy the code

Note that if you want to use this style with generic views, you also need to override.get_object on the view to get the correct lookup behavior.

In general, we recommend using flat style with API presentation whenever possible, but the nested URL style is reasonable when used in moderation.


Further explanation

querysetparameter

The QuerySet parameter is required only for writable relational fields, in which case it is used to perform a model instance lookup that maps from the base user input to the model instance.

In version 2.x, if you are using the ModelSerializer class, the querySet parameter is sometimes automatically determined by the serializer class.

This behavior is now replaced with always using an explicit Queryset parameter for writable relational fields.

This reduces the number of hidden “magic tricks” provided by The ModelSerializer, makes the behavior of the fields clearer, and ensures that when using the ModelSerializer shortcut (highly encapsulated, Using simple) or using fully explicit Serializer classes is trivial.

Custom HTML display

The model’s built-in __str__ method is used to generate a string representation of the object used to populate the Choices property. These choices are used to populate the HTML input of the selection in the browsable API.

To provide a custom representation for these inputs, override the display_value() method of the RelatedField subclass. This method will take a model object and should return a string appropriate to represent it. Such as:

class TrackPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
    def display_value(self, instance):
        return 'Track: %s' % (instance.title)
Copy the code

Select field cutoffs

When rendering browsable API relational fields, a maximum of 1000 optional items are displayed by default. If there are More items, “More than 1000 items… “is displayed. The disabled option of.

This behavior is intended to prevent the template from being rendered in an acceptable time frame due to the large number of relationships displayed.

There are two keyword arguments that can be used to control this behavior:

  • html_cutoff– Sets the maximum number of options displayed in the HTML SELECT drop-down menu. Set toNoneYou can disable any restrictions. The default is1000.
  • html_cutoff_text– Sets a text string to be displayed when the HTML SELECT drop-down menu exceeds the maximum number of displays. The default is"Wining {count} items..."

You can also control these Settings globally in Settings with HTML_SELECT_CUTOFF and HTML_SELECT_CUTOFF_TEXT.

In case you force a cutoff, you may want to use a simple input field in your HTML form instead. You can do this using the style keyword argument. Such as:

assigned_to = serializers.SlugRelatedField(
   queryset=User.objects.all(),
   slug_field='username',
   style={'base_template': 'input.html'})Copy the code

Inverse relationship

Please note that the reverse relationship will not automatically included in the ModelSerializer and HyperlinkedModelSerializer class. To include reverse relationships, you must explicitly add them to the list of fields. Such as:

class AlbumSerializer(serializers.ModelSerializer):
    class Meta:
        fields = ('tracks',...).Copy the code

It is usually necessary to ensure that the appropriate relATED_NAME parameter is set on the relationship and can be used as the field name. Such as:

class Track(models.Model):
    album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
    ...
Copy the code

If you have not already set the associated name for the reverse relationship, you need to use the automatically generated associated name in the fields parameter. Such as:

class AlbumSerializer(serializers.ModelSerializer):
    class Meta:
        fields = ('track_set',...).Copy the code

General relationship

If you serialize a generic foreign key, you need to customize the field to explicitly determine how to serialize the relationship.

For example, given a tag for the following model, that tag has a generic relationship with any other model:

class TaggedItem(models.Model):
    """ Tags arbitrary model instances using a generic relation. See: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/ """
    tag_name = models.SlugField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    tagged_object = GenericForeignKey('content_type'.'object_id')

    def __unicode__(self):
        return self.tag_name
Copy the code

The following two modes can be associated with tags:

class Bookmark(models.Model):
    """ A bookmark consists of a URL, and 0 or more descriptive tags. """
    url = models.URLField()
    tags = GenericRelation(TaggedItem)


class Note(models.Model):
    """ A note consists of some text, and 0 or more descriptive tags. """
    text = models.CharField(max_length=1000)
    tags = GenericRelation(TaggedItem)
Copy the code

We can define a custom field that can be used to serialize a tag instance and use the type of each instance to determine how it should be serialized.

class TaggedObjectRelatedField(serializers.RelatedField):
    """ A custom field to use for the `tagged_object` generic relationship. """

    def to_representation(self, value):
        """ Serialize tagged objects to a simple textual representation. """
        if isinstance(value, Bookmark):
            return 'Bookmark: ' + value.url
        elif isinstance(value, Note):
            return 'Note: ' + value.text
        raise Exception('Unexpected type of tagged object')
Copy the code

If you need a nested representation of the relationship, you can use the desired serialization class in the.to_representation() method:

    def to_representation(self, value):
        """ Serialize bookmark instances using a bookmark serializer, and note instances using a note serializer. """
        if isinstance(value, Bookmark):
            serializer = BookmarkSerializer(value)
        elif isinstance(value, Note):
            serializer = NoteSerializer(value)
        else:
            raise Exception('Unexpected type of tagged object')

        return serializer.data
Copy the code

Note that a reverse generic key represented using the GenericRelation field can be serialized using the regular relational field type, because the type of the target in the relationship is always known.

ManyToManyFields with Through models

By default, the relational fields specifying manytomanyfields with the Through model are set to read-only.

If you explicitly specify a relational field pointing to a ManyToManyField with the Through model, make sure read_only is set to True.