Для чтения данной статьи потребуются базовые знания по Django REST фреймворку.
В этой статье мы расскажем, как можно эффективно использовать сериализаторы во время операций чтения, а также рассмотрим три мощные функции, которые помогут нам достичь желаемых результатов с меньшим количеством кода.
Мы обсудим следующие вопросы:
- несколько способов использовать аргумент
source
сериализатора; - как и когда использовать метод
SerializerMethodField
; - как и когда использовать метод
to_representation
;
Как использовать ключевой аргумент 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 при чтении. Если вы хотите разобраться, как эффективно использовать сериализаторы при операция записи, ждите продолжение.