Spec-Zone.ru › Django REST Framework

Связи сериализаторов

Структуры данных, а не алгоритмы, являются центральными в программировании.

— Роб Пайк

Полевые отношения используются для представления отношений моделей. Они могут применяться к ForeignKey, ManyToManyField и OneToOneField отношениям, а также к обратным отношениям и настраиваемым отношениям, таким как GenericForeignKey.

Примечание: Поля отношений объявляются в relations.py, но по соглашению вы должны импортировать их из модуля serializers, используя from rest_framework import serializers, и ссылаться на поля как serializers.<FieldName>.

Примечание: REST Framework не пытается автоматически оптимизировать наборы запросов, переданные сериализаторам, с точки зрения select_related и prefetch_related, так как это было бы слишком много магии. Сериализатор с полем, охватывающим отношение orm через атрибут источника, может потребовать дополнительного обращения к базе данных для извлечения связанных объектов из базы данных. Программист несет ответственность за оптимизацию запросов, чтобы избежать дополнительных обращений к базе данных, которые могут произойти при использовании такого сериализатора.

Например, следующий сериализатор приведет к обращению к базе данных каждый раз при оценке поля tracks, если оно не предварительно обработано:

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

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

# For each album object, tracks should be fetched from database
qs = Album.objects.all()
print(AlbumSerializer(qs, many=True).data)

Если AlbumSerializer используется для сериализации достаточно большого набора запросов с many=True, это может быть серьезной проблемой производительности. Оптимизация набора запросов, переданного AlbumSerializer, с:

qs = Album.objects.prefetch_related('tracks')
# No additional database hits required
print(AlbumSerializer(qs, many=True).data)

разрешит проблему.

Просмотр отношений.

При использовании класса ModelSerializer поля и отношения сериализатора будут автоматически сгенерированы для вас. Просмотр этих автоматически сгенерированных полей может быть полезным инструментом для определения того, как настроить стиль отношения.

Для этого откройте оболочку Django, используя python manage.py shell, затем импортируйте класс сериализатора, инициализируйте его и распечатайте представление объекта…

>>> 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())

Справочник API

Чтобы объяснить различные типы полей отношений, мы будем использовать несколько простых моделей для наших примеров. Нашими моделями будут музыкальные альбомы и треки, перечисленные в каждом альбоме.

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 __str__(self):
        return '%d: %s' % (self.order, self.title)

StringRelatedField

StringRelatedField может быть использован для представления целевого объекта отношения с помощью метода __str__.

Например, следующий сериализатор:

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

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

Будет сериализован в следующем представлении:

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

Это поле только для чтения.

Аргументы:

  • many - Если применяется к отношению «многие ко многим», вы должны установить этот аргумент в True.

PrimaryKeyRelatedField

PrimaryKeyRelatedField может быть использован для представления целевого объекта отношения с использованием его первичного ключа.

Например, следующий сериализатор:

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

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

Будет сериализован в представлении, подобном этому:

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

По умолчанию это поле доступно для чтения и записи, хотя вы можете изменить это поведение, используя флаг read_only.

Аргументы:

  • queryset - Набор запросов, используемый для поиска экземпляров моделей при валидации входных данных поля. Отношения должны либо явно установить набор запросов, либо установить read_only=True.
  • many - Если применяется к отношению «многие ко многим», вы должны установить этот аргумент в True.
  • allow_null - Если установлено в True, поле будет принимать значения None или пустую строку для отношений с возможностью NULL. По умолчанию False.
  • pk_field - Установите поле для управления сериализацией/десериализацией значения первичного ключа. Например, pk_field=UUIDField(format='hex') сериализует UUID первичный ключ в его компактное шестнадцатеричное представление.

HyperlinkedRelatedField

HyperlinkedRelatedField может быть использован для представления целевого объекта отношения с помощью гиперссылки.

Например, следующий сериализатор:

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']

Будет сериализован в представлении, подобном этому:

{
    '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/',
        ...
    ]
}

По умолчанию это поле доступно для чтения и записи, хотя вы можете изменить это поведение, используя флаг read_only.

Примечание: Это поле предназначено для объектов, которые отображаются на URL, принимающем единственный аргумент URL-ключа, как установлено с помощью аргументов lookup_field и lookup_url_kwarg.

Это подходит для URL-адресов, содержащих один первичный ключ или аргумент slug в качестве части URL.

Если вам необходимо более сложное гиперссылочное представление, вам нужно будет настроить поле, как описано в разделе «настраиваемые гиперссылочные поля» ниже.

Аргументы:

  • view_name - Имя представления, которое должно использоваться в качестве цели отношения. Если вы используете стандартные классы маршрутизаторов, это будет строка с форматом <modelname>-detail. Обязательно.
  • queryset - Набор запросов, используемый для поиска экземпляров моделей при валидации входных данных поля. Отношения должны либо явно установить набор запросов, либо установить read_only=True.
  • many - Если применяется к отношению «многие ко многим», вы должны установить этот аргумент в True.
  • allow_null - Если установлено в True, поле будет принимать значения None или пустую строку для отношений с возможностью NULL. По умолчанию False.
  • lookup_field - Поле в целевом объекте, которое должно использоваться для поиска. Должно соответствовать аргументу URL-ключа в целевом представлении. По умолчанию 'pk'.
  • lookup_url_kwarg - Имя аргумента ключевого слова, определенного в URL-конфигурации, соответствующее полю поиска. По умолчанию используется то же значение, что и lookup_field.
  • format - При использовании суффиксов форматов гиперссылочные поля будут использовать тот же суффикс формата для целевого объекта, если не переопределено с помощью аргумента format.

SlugRelatedField

SlugRelatedField может быть использован для представления целевого объекта отношения с использованием поля в целевом объекте.

Например, следующий сериализатор:

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

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

Будет сериализован в представлении, подобном этому:

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

По умолчанию это поле доступно для чтения и записи, хотя вы можете изменить это поведение, используя флаг read_only.

При использовании SlugRelatedField в качестве поля чтения и записи, вы обычно захотите убедиться, что поле slug соответствует полю модели с unique=True.

Аргументы:

  • slug_field - Поле в целевом объекте, которое должно использоваться для его представления. Это поле должно однозначно идентифицировать любой данный экземпляр. Например, username. Обязательно
  • queryset - Набор запросов, используемый для поиска экземпляров моделей при валидации входных данных поля. Отношения должны либо явно установить набор запросов, либо установить read_only=True.
  • many - Если применяется к отношению «многие ко многим», вы должны установить этот аргумент в True.
  • allow_null - Если установлено в True, поле будет принимать значения None или пустую строку для отношений с возможностью NULL. По умолчанию False.

HyperlinkedIdentityField

Это поле может быть применено в качестве идентификационного отношения, например, поле 'url' в HyperlinkedModelSerializer. Оно также может использоваться для атрибута объекта. Например, следующий сериализатор:

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

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'track_listing']

Будет сериализован в представлении, подобном этому:

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

Это поле всегда только для чтения.

Аргументы:

  • view_name - Имя представления, которое должно использоваться в качестве цели отношения. Если вы используете стандартные классы маршрутизаторов, это будет строка с форматом <model_name>-detail. Обязательно.
  • lookup_field - Поле в целевом объекте, которое должно использоваться для поиска. Должно соответствовать аргументу URL-ключа в целевом представлении. По умолчанию 'pk'.
  • lookup_url_kwarg - Имя аргумента ключевого слова, определенного в URL-конфигурации, соответствующее полю поиска. По умолчанию используется то же значение, что и lookup_field.
  • format - При использовании суффиксов форматов гиперссылочные поля будут использовать тот же суффикс формата для целевого объекта, если не переопределено с помощью аргумента format.

Вложенные отношения

В отличие от ранее обсуждаемых ссылок на другой объект, целевой объект может быть вложен в представление объекта, который на него ссылается. Такие вложенные отношения могут быть выражены с помощью сериализаторов в качестве полей.

Если поле используется для представления отношения «многие ко многим», вы должны добавить флаг many=True к полю сериализатора.

Пример

Например, следующий сериализатор:

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']

Будет сериализован во вложенное представление, подобное этому:

>>> 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},
        ...
    ],
}

Изменяемые вложенные сериализаторы

По умолчанию вложенные сериализаторы только для чтения. Если вы хотите поддерживать операции записи для вложенного поля сериализатора, вам нужно создать методы create() и/или update() для явного указания того, как должны сохраняться дочерние отношения:

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>

Настраиваемые поля отношений

В редких случаях, когда ни один из существующих стилей отношений не подходит для требуемого вами представления, вы можете реализовать полностью настраиваемое поле отношения, которое точно описывает, как должно быть сгенерировано выходное представление из экземпляра модели.

Для реализации настраиваемого поля отношения вы должны переопределить RelatedField, и реализовать метод .to_representation(self, value). Этот метод принимает целевой объект поля в качестве аргумента value, и должен возвращать представление, которое должно использоваться для сериализации целевого объекта. Аргумент value обычно будет экземпляром модели.

Если вы хотите реализовать поле отношения для чтения и записи, вы также должны реализовать метод .to_internal_value(self, data).

Чтобы предоставить динамический набор запросов на основе context, вы также можете переопределить .get_queryset(self) вместо указания .queryset в классе или при инициализации поля.

Пример

Например, мы можем определить поле отношения для сериализации трека в пользовательское строковое представление, используя его порядок, название и продолжительность:

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']

Это пользовательское поле затем сериализуется в следующем представлении:

{
    '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)',
        ...
    ]
}

Пользовательские гиперссылочные поля

В некоторых случаях вам может потребоваться настроить поведение гиперссылочного поля, чтобы представлять URL-адреса, которые требуют более одного поля поиска.

Вы можете добиться этого, переопределив HyperlinkedRelatedField. Существует два метода, которые можно переопределить:

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

Метод get_url используется для сопоставления экземпляра объекта с его представлением URL.

Может вызвать NoReverseMatch, если атрибуты view_name и lookup_field не настроены для правильного соответствия конфигурации URL.

get_object(self, view_name, view_args, view_kwargs)

Если вы хотите поддерживать записываемое гиперссылочное поле, то вам также нужно переопределить get_object, чтобы сопоставить входящие URL-адреса с представляемыми ими объектами. Для чтения гиперссылочных полей нет необходимости переопределять этот метод.

Возвращаемое значение этого метода должно быть объектом, соответствующим аргументам сопоставленной конфигурации URL.

Может вызвать исключение ObjectDoesNotExist.

Пример

Предположим, у нас есть URL для объекта клиента, который принимает два ключевых аргумента, например:

/api/<organization_slug>/customers/<customer_pk>/

Это невозможно представить с помощью реализации по умолчанию, которая принимает только одно поле поиска.

В этом случае нам нужно переопределить HyperlinkedRelatedField для достижения желаемого поведения:

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)

Обратите внимание, что если вы хотите использовать этот стиль вместе с общими представлениями, то вам также необходимо переопределить .get_object в представлении, чтобы получить правильное поведение поиска.

В целом, мы рекомендуем использовать плоский стиль для представлений API, где это возможно, но в меру допустим и вложенный стиль URL.

Дополнительные замечания

Аргумент queryset

Аргумент queryset требуется только для записываемых полей отношений, в этом случае он используется для поиска экземпляра модели, который сопоставляет исходные данные пользователя с экземпляром модели.

В версии 2.x класс сериализатора иногда мог автоматически определить аргумент queryset если использовался класс ModelSerializer.

Это поведение теперь заменено на всегда использование явного аргумента queryset для записываемых полей отношений.

Это уменьшает количество скрытой «магии», которую предоставляет ModelSerializer, делает поведение поля более ясным и гарантирует, что легко переключаться между использованием сокращения ModelSerializer или использованием полных классов Serializer.

Настройка отображения HTML

Встроенный метод __str__ модели будет использоваться для генерации строковых представлений объектов, используемых для заполнения свойства choices. Эти варианты используются для заполнения HTML-входных элементов выбора в обозреваемом API.

Чтобы предоставить настраиваемые представления для таких входных данных, переопределите display_value() подкласса RelatedField. Этот метод получит объект модели и должен вернуть строку, подходящую для его представления. Например:

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

Ограничения полей выбора

При отображении в обозреваемом API поля отношений по умолчанию отображают не более 1000 элементов выбора. Если элементов больше, отображается отключенный параметр с надписью «Более 1000 элементов…».

Это поведение призвано предотвратить невозможность рендеринга шаблона за приемлемое время из-за очень большого количества отображаемых отношений.

Есть два ключевых аргумента, которые вы можете использовать для управления этим поведением:

  • html_cutoff - Если задано, это будет максимальное количество вариантов, которое будет отображено выпадающим списком HTML. Установите значение None, чтобы отключить любое ограничение. По умолчанию 1000.
  • html_cutoff_text - Если задано, это отобразит текстовый индикатор, если максимальное количество элементов было обрезано в выпадающем списке HTML. По умолчанию "More than {count} items…"

Вы также можете управлять ими глобально, используя параметры HTML_SELECT_CUTOFF и HTML_SELECT_CUTOFF_TEXT.

В случаях, когда ограничение применяется, вы можете вместо этого использовать обычное текстовое поле в форме HTML. Вы можете сделать это, используя ключевой аргумент style. Например:

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

Обратные связи

Обратите внимание, что обратные связи не включаются автоматически классами ModelSerializer и HyperlinkedModelSerializer. Чтобы включить обратную связь, необходимо явно добавить ее в список полей. Например:

class AlbumSerializer(serializers.ModelSerializer):
    class Meta:
        fields = ['tracks', ...]

Обычно вы хотите убедиться, что вы установили соответствующий аргумент related_name для связи, который можно использовать в качестве имени поля. Например:

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

Если вы не задали имя связанного объекта для обратной связи, вам необходимо использовать автоматически сгенерированное имя связанного объекта в аргументе fields. Например:

class AlbumSerializer(serializers.ModelSerializer):
    class Meta:
        fields = ['track_set', ...]

См. документацию Django по обратным связям для получения дополнительной информации.

Общедоступные связи

Если вы хотите сериализовать общий внешний ключ, вам необходимо определить пользовательское поле, чтобы явно определить, как вы хотите сериализовать целевые объекты связи.

Например, данная модель тега, которая имеет общие отношения с другими произвольными моделями:

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 __str__(self):
        return self.tag_name

И следующие две модели, которые могут иметь связанные теги:

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)

Мы могли бы определить пользовательское поле, которое можно использовать для сериализации помеченных экземпляров, используя тип каждого экземпляра, чтобы определить, как он должен быть сериализован:

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')

Если вам нужно, чтобы целевой объект связи имел вложенное представление, вы можете использовать необходимые сериализаторы внутри метода .to_representation():

    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

Обратите внимание, что обратные общие ключи, выраженные с помощью поля GenericRelation, могут быть сериализованы с помощью обычных типов полей отношений, поскольку тип целевого объекта в связи всегда известен.

Для получения дополнительной информации см. документацию Django по общим связям.

ManyToManyFields с моделью Through

По умолчанию поля отношений, которые нацелены на ManyToManyField с указанной моделью through, устанавливаются в режим только для чтения.

Если вы явно указываете поле связи, указывающее на ManyToManyField с моделью «через», убедитесь, что read_only установлено в True.

Если вы хотите представить дополнительные поля в модели «через», то вы можете сериализовать модель «через» как вложенный объект.

Пакеты сторонних разработчиков

Доступны также следующие пакеты сторонних разработчиков.

DRF вложенные маршрутизаторы

Пакет drf-nested-routers предоставляет маршрутизаторы и поля отношений для работы со вложенными ресурсами.

Rest Framework общие связи

Библиотека rest-framework-generic-relations предоставляет сериализацию для чтения/записи общих внешних ключей.

relations.py

Copyright © 2011–present Encode OSS Ltd.
Licensed under the BSD License.
https://www.django-rest-framework.org/api-guide/relations/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API