Повестка дня
Этот пост предполагает базовое знакомство с 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() менеджера.
Заключение
В этом посте описывается поведение сериализатора при записи. Если вы хотите понять, как эффективно использовать сериализаторы во время операций чтения, см. Это.
Свяжитесь со мной в Твиттере, я чирикаю информативные и ценные статьи и советы по программированию.