rest
Для чтения данной статьи потребуются базовые знания по Django REST фреймворку.
В этой статье мы расскажем, как можно эффективно использовать сериализаторы во время операций чтения, а также рассмотрим три мощные функции, которые помогут нам достичь желаемых результатов с меньшим количеством кода.
Мы обсудим следующие вопросы:
source сериализатора;SerializerMethodField;to_representation;sourceDRF-сериализатор имеет ключевой аргумент под названием 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)
Бывают случаи, когда вам нужно запустить какой-то собственный код во время сериализации определенного поля.
Вот несколько примеров:
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') Сериализаторы в 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, на Talanto.work сейчас есть варианты для junior, middle, senior…
Рынок Python-вакансий большой, но удобным его назвать сложно. Формально вакансий много, однако в реальном поиске…
Срезы в Python — это больше, чем a[2:7]. В статье — быстрый разбор полезных приёмов:…
В этой статье мы рассмотрим возведение в степень в Python с помощью различных методов, от…
Дубликаты в списке Python — частая проблема. В статье показано, как быстро убрать повторы, в…
Услуга виртуального хостинга на SpaceWeb позволяет арендовать часть сервера (shared-сервера) и разместить на нём свой…