Использование аргумента источника, SerializerMethodField и to_presentation

Этот пост предполагает базовое знакомство с фреймворком Django REST.

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

Мы обсудим:

  • Несколько способов использования аргумента сериализатора source.
  • Как и когда использовать SerializerMethodField.
  • Как и когда использовать to_representation.

Как использовать аргумент исходного ключевого слова

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

Давайте напишем сериализатор, который может создавать сериализованные представления User.

Давайте воспользуемся этим сериализатором для сериализации пользователя:

In [1]: from accounts.serializers import UserSerializer
In [2]: from django.contrib.auth.models import User
In [3]: user = User.objects.latest('pk')
In [4]: serializer = UserSerializer(user)
In [5]: serializer.data
Out[5]: {'username': 'akshar', 'email': '[email protected]', 'first_name': '', 'last_name': ''}

Предположим, что интерфейс или мобильное приложение, использующее этот API, хочет, чтобы ключ был user_name вместо username в сериализованном представлении.

Для этого мы можем добавить CharField к сериализатору с атрибутом source.

Убедитесь, что username заменено на user_name в Meta.fields. Перезапустите оболочку и создайте сериализатор.

In [6]: serializer = UserSerializer(user)
In [7]: serializer.data
Out[7]: {'user_name': 'akshar', 'email': '[email protected]', 'first_name': '', 'last_name': ''}

В последнем примере мы видели, как source работает с полем User. source также может работать с методами на User.

User имеет метод get_full_name. Установим first_name и last_name.

In [8]: user.first_name = 'akshar'
In [9]: user.last_name = 'raaj'
In [10]: user.save()
In [11]: user.get_full_name()
Out[11]: 'akshar raaj'

Давайте добавим в сериализатор поле full_name. Установите source на использование User.get_full_name.

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

In [3]: user = User.objects.latest('pk')
In [4]: serializer = UserSerializer(user)
In [5]: serializer.data
Out[5]: {'user_name': 'akshar', 'email': '[email protected]', 'first_name': 'akshar', 'last_name': 'raaj', 'full_name': 'akshar raaj'}

Обратите внимание, как full_name дает желаемый результат. Под капотом DRF использовал get_full_name для заполнения full_name.

source также может без проблем работать с отношениями, то есть с ForeignKey, OneToOneField и ManyToMany.

Предположим, что существует Profile модель, которая OneToOne связана с User.

Модель профиля выглядит так:

Допустим, мы хотим, чтобы улица и город были отправлены в серийном представлении. Мы могли бы добавить поле улицы и города в сериализатор и установить соответствующий source.

Давайте перезапустим оболочку и сериализуем пользователя.

In [4]: user = User.objects.latest('pk')
In [5]: serializer = UserSerializer(user)
In [6]: serializer.data
Out[6]: {'email': '[email protected]', 'first_name': 'akshar', 'last_name': 'raaj', 'street': 'Pennsylvania Avenue', 'city': 'Washington'}

source также легко работает с методами связанных объектов, подобно тому, как он работает с методами объекта.

Мы хотим получить полный адрес пользователей, доступный с помощью user.profile.get_full_address().

В таких случаях мы можем установить источник как profile.get_full_address.

Снова сериализуйте пользователя:

In [3]: user = User.objects.latest('pk')
In [4]: serializer = UserSerializer(user)
In [5]: serializer.data
Out[5]: {'email': '[email protected]', 'first_name': 'akshar', 'last_name': 'raaj', 'full_address': 'Pennsylvania Avenue, Washington'}

Давайте посмотрим, как легко source работает с ManyToManyField.

Мы также хотим получить связанные группы пользователей в сериализованном представлении. Давайте сначала добавим пользователю несколько групп.

In [12]: g1 = Group.objects.create(name='BBC')
In [13]: g2 = Group.objects.create(name='Sony')
In [15]: user.groups.add(*[g1, g2])

Нам нужны id и name для каждой группы, связанной с пользователем.

Нам нужно будет написать GroupSerializer, который может сериализовать экземпляр группы.

Это выглядело бы так:

Наивным способом добавления групп к сериализованным данным было бы определение SerializerMethodField и добавление user.groups.all() в метод.

Способ DRFish - добавить поле all_groups в сериализатор и установить для него значение GroupSerializer.

Давайте сериализуем пользователя и убедимся, что информация о связанной группе присутствует в сериализованных данных.

In [1]: from accounts.serializers import UserSerializer
In [2]: from django.contrib.auth.models import User
In [3]: user = User.objects.latest('pk')
In [5]: serializer = UserSerializer(user)
In [6]: serializer.data
Out[6]: {'email': '[email protected]', 'first_name': 'akshar', 'last_name': 'raaj', 'groups': [OrderedDict([('id', 2), ('name', 'BBC')]), OrderedDict([('id', 3), ('name', 'Sony')])]}

DRF достаточно умен, чтобы вызвать user.groups.all(), хотя мы просто установили source=groups. DRF делает вывод, что groups является ManyRelatedManager, и, таким образом, вызывает .all() диспетчера, чтобы получить все связанные группы.

Если мы не хотим предоставлять информацию о группе во время POST вызовов, нам придется добавить аргумент ключевого слова read_only=True в GroupSerializer.

Предположим, что существует модель с именем Article, а Article имеет значения от ForeignKey до User. Мы можем добавить статьи пользователя в сериализованное представление с помощью:

articles = ArticleSerializer(source='article_set', many=True)

Как использовать SerializerMethodField

Бывают случаи, когда вам нужно запустить какой-то собственный код во время сериализации определенного поля.

Вот несколько примеров:

  • Преобразуйте first_name в titlecase во время сериализации.
  • Преобразуйте full_name в верхний регистр.
  • Установите groups как None вместо пустого списка, если с пользователем не связаны никакие группы.

Рассмотрим первый сценарий. Мы хотим изменить first_name пользователя на регистр заголовка во время сериализации.

До сих пор мы не хотели запускать какой-либо собственный код для first_name, поэтому наличия first_name в Meta.fields было достаточно. Сейчас мы хотим запустить некоторый собственный код, поэтому нам нужно будет явно установить first_name как SerializerMethodField.

Когда поле установлено как SerializerMethodField, DRF вызывает метод с именем get_<field_name> при вычислении значения для этого поля. Здесь obj относится к экземпляру user.

Перезапустите оболочку и сериализуйте пользователя.

In [4]: user = User.objects.latest('pk')
In [5]: serializer = UserSerializer(user)
In [6]: serializer.data
    Out[6]: {'email': '[email protected]', 'first_name': 'Akshar', 'last_name': 'raaj'}

Обратите внимание, как first_name теперь сериализуется в регистр заголовка.

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

Давайте снова сериализуем пользователя:

In [3]: user = User.objects.latest('pk')
In [4]: serializer = UserSerializer(user)
In [5]: serializer.data
Out[5]: {'email': '[email protected]', 'first_name': 'akshar', 'last_name': 'raaj', 'full_name': 'AKSHAR RAAJ'}

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

Как использовать to_presentation

Сериализаторы предоставляют точку перехвата, называемую to_representation.

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

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

instance относится к сериализованному экземпляру пользователя.

Давайте сериализуем суперпользователя.

In [2]: user = User.objects.latest('pk')
In [5]: serializer = UserSerializer(user)
In [6]: serializer.data
Out[6]: {'email': '[email protected]', 'first_name': 'akshar', 'last_name': 'raaj', 'admin': True}

Отметим пользователя как не суперпользователя и снова сериализуем.

In [7]: user.is_superuser = False
In [8]: user.save()
In [9]: serializer = UserSerializer(user)
In [10]: serializer.data
Out[10]: {'email': '[email protected]', 'first_name': 'akshar', 'last_name': 'raaj'}

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

Заключение

В этом посте рассказывается о поведении сериализатора при чтении. Если вы хотите понять, как эффективно использовать сериализаторы во время операций записи, см. Эту статью.

Свяжитесь со мной в Твиттере, я чирикаю информативные и ценные статьи и советы по программированию.