Share This
Связаться со мной
Крути в низ
Categories
//Эффективное использование DRF-сериализаторов в Django

Эффективное использование DRF-сериализаторов в Django

29.10.2020Category : Python

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

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

Мы обсудим следующие вопросы:

  • несколько способов использовать аргумент source сериализатора;
  • как и когда использовать метод SerializerMethodField;
  • как и когда использовать метод to_presentation;

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

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

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

from rest_framework import serializers  from django.contrib.auth.models import User   class UserSerializer(serializers.ModelSerializer):      class Meta:         model = User         fields = ('username', 'email', 'first_name', 'last_name') 

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

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': 'akshar@agiliq.com', 'first_name': '', 'last_name': ''}

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

Чтобы добиться этого, мы можем добавить в код метод CharField с ключевым аргументом source:

class UserSerializer(serializers.ModelSerializer):      user_name = serializers.CharField(source='username')      class Meta:         model = User         fields = ('user_name', 'email', 'first_name', 'last_name')

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

In [6]: serializer = UserSerializer(user) In [7]: serializer.data Out[7]: {'user_name': 'akshar', 'email': 'akshar@agiliq.com', '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 c User.get_full_name.

class UserSerializer(serializers.ModelSerializer):      user_name = serializers.CharField(source='username')     full_name = serializers.CharField(source='get_full_name')      class Meta:         model = User         fields = ('user_name', 'email', 'first_name', 'last_name', 'full_name') 

Теперь перезапустим оболочку и получим сериализованное представление класса User:

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

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

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

Представим, у нас есть модель Profile , которая имеет отношения один-к-одному (OneToOneField) с классом User.

Эта модель имеет следующий вид:

class Profile(models.Model):      user = models.OneToOneField(User, on_delete=models.CASCADE)     street = models.CharField(max_length=100)     city = models.CharField(max_length=100)      def get_full_address(self):         return "%s, %s" % (self.street, self.city) 

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

class UserSerializer(serializers.ModelSerializer):      street = serializers.CharField(source='profile.street')     city = serializers.CharField(source='profile.city')      class Meta:         model = User         fields = ('email', 'first_name', 'last_name', 'street', 'city') 

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

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

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

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

В данном случае мы можем просто передать в аргумент source метод profile.get_full_address.

class UserSerializer(serializers.ModelSerializer):      full_address = serializers.CharField(source='profile.get_full_address')      class Meta:         model = User         fields = ('email', 'first_name', 'last_name', 'full_address') 

Опять сериализуем класс User и получим:

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

Теперь давайте посмотрим, как легко работает аргумент source с отношением многие-ко-многим (ManyToManyField).

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

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, который будет сериализовать экземпляр каждой группы.

Выглядеть он будет следующим образом:

from django.contrib.auth.models import Group  class GroupSerializer(serializers.ModelSerializer):  class Meta:     model = Group     fields = ('id', 'name') 

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

А способ, отвечающий духу DRF, состоит в том, чтобы добавить поле all_groups в сериализатор и установить его значение при помощи класса GroupSerializer.

class GroupSerializer(serializers.ModelSerializer):      class Meta:         model = Group         fields = ('id', 'name')   class UserSerializer(serializers.ModelSerializer):      all_groups = GroupSerializer(source='groups', many=True)      class Meta:         model = User         fields = ('email', 'first_name', 'last_name', 'all_groups') 

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

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': 'akshar@agiliq.com', '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 и она имеет отношение один-ко-многим (ForeignKey) к классу User. Тогда мы можем добавлять статьи (articles) пользователя (User) в сериализованное представление следующим образом:

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

Как и когда использовать метод SerializerMethodField

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

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

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

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

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

class UserSerializer(serializers.ModelSerializer):      first_name = serializers.SerializerMethodField()      def get_first_name(self, obj):         return obj.first_name.title()      class Meta:         model = User         fields = ('email', 'first_name', 'last_name') 

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

Перезапустим оболочку и произведем сериализацию:

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

Обратите внимание, что имя (значение поля first_name) теперь пишется с заглавной буквы.

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

class UserSerializer(serializers.ModelSerializer):      full_name = serializers.SerializerMethodField()      def get_full_name(self, obj):         return obj.get_full_name().upper()      class Meta:         model = User         fields = ('email', 'first_name', 'last_name', 'full_name') 

Опять произведем сериализацию и увидим:

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

Если же мы захотим установить значение groups в None вместо пустого списка, наш сериализатор примет следующий вид:

class UserSerializer(serializers.ModelSerializer):      groups = serializers.SerializerMethodField()      def get_groups(self, obj):         groups = obj.groups.all()         if not groups:             return None         return GroupSerializer(groups, many=True).data      class Meta:         model = User         fields = ('email', 'first_name', 'last_name', 'groups')

Как и когда использовать метод to_representation

Сериализаторы в DRF имеют полезный метод под названием to_representation.

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

Тогда наш сериализатор будет иметь следующий вид:

class UserSerializer(serializers.ModelSerializer):      class Meta:         model = User         fields = ('email', 'first_name', 'last_name')      def to_representation(self, instance):         representation = super().to_representation(instance)         if instance.is_superuser:             representation['admin'] = True         return representation 

Аргумент instance является экземпляром класса User.

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

In [2]: user = User.objects.latest('pk') In [5]: serializer = UserSerializer(user) In [6]: serializer.data Out[6]: {'email': 'akshar@agiliq.com', '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': 'akshar@agiliq.com', 'first_name': 'akshar', 'last_name': 'raaj'}

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

Выводы

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

    Проходите тест по Python и поймите, готовы ли вы идти на курсы
  • 8 views
  • 0 Comment

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Связаться со мной
Close