Повестка дня

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

Мы бы обсудили следующее:

  • Как добавить проверку настраиваемого поля
  • Как добавить кросс-полевую проверку
  • Когда и как переопределить to_internal_value ()
  • Когда и как переопределить create ()

Базовый сериализатор

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

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

In [1]: from accounts.serializers import UserSerializer
In [2]: data = {'first_name': 'john', 'last_name': 'doe', 'username': 'john', 'password': 'abc123'}
In [3]: serializer = UserSerializer(data=data)
In [4]: serializer.is_valid()
Out[4]: True
In [5]: serializer.save()
Out[5]: <User: john>

Проверка настраиваемого поля

Проверка настраиваемого поля может быть выполнена с помощью метода validate_<field_name>.

Давайте сделаем так, чтобы пароль содержал хотя бы один не буквенно-цифровой символ. Для этого нам нужно будет определить validate_password() метод.

Любой настраиваемый метод проверки должен возвращать исходное или измененное значение, если нет ValidationError. Вот почему мы вернули value.

Давайте проверим данные, в которых пароль не содержит специальных символов.

In [3]: data = {'first_name': 'john', 'last_name': 'doe', 'username': 'john', 'password': 'abc123'}
In [4]: serializer = UserSerializer(data=data)
In [5]: serializer.is_valid()
Out[5]: False
In [6]: serializer.errors
Out[6]: {'password': [ErrorDetail(string='password must have atleast one special character.', code='invalid')]}

Давайте проверим данные, в которых пароль содержит специальный символ.

In [6]: data = {'first_name': 'john', 'last_name': 'doe', 'username': 'john', 'password': 'abc123#'}
In [7]: serializer = UserSerializer(data=data)
In [8]: serializer.is_valid()
Out[8]: True

Если мы хотим запустить какую-либо проверку на first_name, имя нашего метода будет validate_first_name().

Проверка между полями

Если мы хотим добавить некоторую проверку, когда нам нужно получить доступ к нескольким полям одновременно, правильная точка подключения сериализатора для этого - метод validate().

Давайте сделаем так, чтобы first_name и last_name были разными. Для этого нам придется переопределить validate() сериализатора.

Давайте проверим данные, в которых first_name и last_name совпадают.

In [10]: data = {'first_name': 'john', 'last_name': 'john', 'username': 'john', 'password': 'abc123#'}
In [12]: serializer = UserSerializer(data=data)
In [13]: serializer.is_valid()
Out[13]: False
In [14]: serializer.errors
Out[14]: {'non_field_errors': [ErrorDetail(string="first_name and last_name shouldn't be same.", code='invalid')]}

Давайте проверим данные, в которых first_name и last_name разные.

In [1]: data = {'first_name': 'john', 'last_name': 'doe', 'username': 'john', 'password': 'abc123#'}
In [3]: serializer = UserSerializer(data=data)
In [4]: serializer.is_valid()
Out[4]: True

Когда и как переопределить to_internal_value ()

DRF имеет точку подключения под названием to_internal_value(). Его можно использовать для предварительной обработки перед выполнением кода проверки.

Предположим, ваш интерфейс или мобильное приложение отправляет информацию о пользователе, заключенную в другом словаре с ключом user. Предположим, что данные POSTed выглядят так:

data = {'user': {'first_name': 'john', 'last_name': 'doe', 'username': 'john', 'password': 'abc123#'}}

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

In [5]: data = {'user': {'first_name': 'john', 'last_name': 'doe', 'username': 'john', 'password': 'abc123#'}}
In [6]: serializer = UserSerializer(data=data)
In [7]: serializer.is_valid()
Out[7]: False

В таком случае перед проверкой полей необходимо извлечь user информацию из словаря. Мы можем добиться этого, переопределив to_internal_value().

Полный UserSerializer выглядит так:

Давайте еще раз проверим те же данные и посмотрим, что говорит сериализатор:

In [2]: data = {'user': {'first_name': 'john', 'last_name': 'doe', 'username': 'john', 'password': 'abc123#'}}
In [3]: serializer = UserSerializer(data=data)
In [4]: serializer.is_valid()
Out[4]: True

Наш сериализатор больше не жалуется.

Давайте обсудим еще один сценарий, в котором to_internal_value() было бы идеально.

DRF ожидает любые POSTed datetime / date в формате ГГГГ-ММ-ДД. Давайте убедимся, что мы не можем публиковать сообщения в каком-либо другом формате, а затем посмотрим, как мы можем исправить это, чтобы разрешить использование других форматов.

У пользователя есть поле даты и времени с именем date_joined. Давайте добавим это поле в сериализатор.

Давайте используем формат MM-DD-YYYY для date_joined и проверим данные.

In [9]: data = {'first_name': 'john', 'last_name': 'doe', 'username': 'john', 'password': 'abc123#', 'date_joined': '06/12/2019'}
In [10]: serializer = UserSerializer(data=data)
In [11]: serializer.is_valid()
Out[11]: False
In [12]: serializer.errors
Out[12]: {'date_joined': [ErrorDetail(string='Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].', code='invalid')]}

Обратите внимание на сообщение об ошибке, в котором DRF жалуется, что вы должны использовать формат YYYY-MM-DD для date_joined.

Давайте переопределим to_internal_value() и используем dateutil.parser() для анализа даты, а затем вызовем super to_internal_value() для дальнейшей проверки.

Давайте еще раз проверим те же данные.

In [2]: data = {'first_name': 'john', 'last_name': 'doe', 'username': 'john', 'password': 'abc123#', 'date_joined': '06/12/2019'}
In [3]: serializer = UserSerializer(data=data)
In [4]: serializer.is_valid()
Out[4]: True
In [5]: serializer.validated_data
Out[5]:
OrderedDict([('username', 'john'),
             ('first_name', 'john'),
             ('last_name', 'doe'),
             ('password', 'abc123#'),
             ('date_joined',
              datetime.datetime(2019, 6, 12, 0, 0, tzinfo=<UTC>))])

Когда и как переопределить create ()

В сериализаторе есть метод create(). create() выполняется при вызовеserializer.save(). По умолчанию сериализатор create() выполняет команду create диспетчера моделей.

create() следует переопределить, если мы хотим сделать что-то отличное от этого поведения по умолчанию. При создании пользовательских экземпляров мы хотели бы вызвать User.objects.create_user() вместо User.objects.create(), чтобы пароль хешировался. Это идеальный сценарий для переопределения создания.

Давайте попробуем на UserSerializer. Мы проверим, что в базе данных нет пользователей, а затем вызовем serializer.save () для создания пользователя.

In [8]: from django.contrib.auth.models import User
In [9]: User.objects.count()
Out[9]: 1

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

In [10]: data = {'first_name': 'john', 'last_name': 'doe', 'username': 'john', 'password': 'abc123#', 'date_joined': '06/12/2019'}
In [11]: serializer = UserSerializer(data=data)
In [12]: serializer.is_valid()
Out[12]: True
In [13]: serializer.save()
Out[13]: <User: john>
In [14]: User.objects.count()
Out[14]: 2

Давайте проверим, был ли пароль хеширован.

In [15]: user = User.objects.latest('pk')
In [16]: user.password
Out[16]: 'abc123#'

Пароль не был хеширован, потому что сериализатор работал по умолчанию. Вызов serializer.save(), внутренний вызов User.objects.create().

Давайте исправим это, переопределив create().

Полный сериализатор на данный момент выглядит так:

Давайте еще раз проверим и сохраним данные.

In [9]: data = {'first_name': 'john', 'last_name': 'doe', 'email': '[email protected]', 'username': 'john', 'password': 'abc123#', 'date_joined': '06/12/2019'}
In [10]: serializer = UserSerializer(data=data)
In [12]: serializer.is_valid()
Out[12]: True
In [13]: serializer.save()
Out[13]: <User: john>
In [14]: user = User.objects.latest('pk')
In [15]: user.password
Out[15]: 'pbkdf2_sha256$100000$08HZhymedEOB$2O+WXqizLBZgXVFVc7iw0r8I2Z0gAu0GulJNUY4Dj2s='   # hashed password
In [17]: user.check_password('abc123#')
Out[17]: True

create() сериализатора будет правильной точкой подключения, если мы хотим запустить get_or_create() менеджера вместо create() менеджера.

Заключение

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

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