Эффективное использование DRF-сериализаторов в Django
Для чтения данной статьи потребуются базовые знания по 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 и поймите, готовы ли вы идти на курсы
- 7 views
- 0 Comment