Дополнительные поля Serializer в Django REST Framework 3
Ситуация
Я создаю простую конечную точку, которая позволяет создавать пользователя. Мне нужно поле, которое отсутствует в моей пользовательской модели (т.е. confirm_password
). Я проведу проверку, которая сравнивает это поле и другое поле, которое находится в моей модели, и никогда больше не используйте дополнительное поле в сериализаторе.
Проблема
Версия 3 DRF изменила процесс выполнения этого, и я не совсем понимаю, что предлагает документация. См. здесь для документации.
Попытка решения
Я создал UserSerializer
, который выглядит так:
from django.contrib.auth import get_user_model
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
confirm_password = serializers.CharField(allow_blank=False)
def validate(self, data):
"""
Checks to be sure that the received password and confirm_password
fields are exactly the same
"""
if data['password'] != data.pop('confirm_password'):
raise serializers.ValidationError("Passwords do not match")
return data
def create(self, validated_data):
"""
Creates the user if validation succeeds
"""
password = validated_data.pop('password', None)
user = self.Meta.model(**validated_data)
user.set_password(password)
user.save()
return user
class Meta:
# returns the proper auth model
model = get_user_model()
# fields that will be deserialized
fields = ['password', 'confirm_password',
'username', 'first_name', 'last_name', 'email']
# fields that will be serialized only
read_only_fields = ['is_staff', 'is_superuser']
# fields that will be deserialized only
write_only_fields = ['password' 'confirm_password']
Я надеялся, что popping confirm_password
в validate
позаботится о моих проблемах, но я просто получаю следующее:
Получите KeyError
при попытке получить значение для поля confirm_password
в serializer UserSerializer
. Поле сериализатора может быть названо неправильно и не соответствует никакому атрибуту или ключу в экземпляре OrderedDict
Ответы
Ответ 1
Вы ищете поле только для записи, так как я предполагаю, что вы не захотите отображать подтверждение пароля в API. Django REST Framework ввел параметр write_only
в шкалу времени 2.3.x в дополнение к параметру read_only
, поэтому выполняется только проверка времени, когда выполняется обновление. Мета-свойство write_only_fields
было добавлено примерно в одно и то же время, но важно понять, как обе эти функции работают вместе.
Свойство meta write_only_fields
автоматически добавит свойство write_only
к полю, когда оно будет автоматически создано, например, для поля password
в модели User
. Это не будет сделано для любых полей, которые не относятся к модели, или полей, которые явно указаны в сериализаторе. В вашем случае вы явно указываете поле confirm_password
вашего сериализатора, поэтому он не работает.
Получите KeyError
при попытке получить значение для поля confirm_password
в serializer UserSerializer
. Поле сериализатора может быть названо неправильно и не соответствует атрибуту или ключу в экземпляре OrderedDict
Это возникает во время повторной сериализации созданного пользователя, когда он пытается сериализовать поле confirm_password
. Поскольку он не может найти поле в модели User
, он вызывает эту ошибку, которая пытается объяснить проблему. К сожалению, потому что это у нового пользователя, он говорит вам смутно взглянуть на экземпляр OrderedDict
вместо экземпляра User
.
class UserSerializer(serializers.ModelSerializer):
confirm_password = serializers.CharField(allow_blank=False, write_only=True)
Если вы явно указываете write_only
в поле сериализатора и удаляете поле из write_only_fields
, тогда вы должны увидеть поведение, которое вы ожидаете.
Вы можете найти документацию об этом на этой ссылке
Ответ 2
Также полезно для реализации вложенных сериализаторов, представляющих модели, когда корневая модель не имеет прямого доступа к полям, которые вы хотите использовать.
Спасибо @vyscond;)
Fyi вот мой случай:
models.py
class Company(models.Model):
permission_classes = (
IsCompanyMember,
)
name = models.CharField(
unique=True,
max_length=100,
verbose_name='company name',
null=False
)
class Profile(models.Model):
company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)
user = models.OneToOneField(User, on_delete=models.CASCADE)
is_company_admin = models.BooleanField(default=False, null=False)
is_email_validated = models.BooleanField(default=False, null=False)
is_approved_by_company_admin = models.BooleanField(default=False, null=False)
serializer.py
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('name',)
class CustomUserRegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(style={'input_type': 'password'},
write_only=True,
validators=settings.get('PASSWORD_VALIDATORS'))
company = CompanySerializer(allow_null=False, required=True,write_only=True)
class Meta:
model = User
fields = ('username',
'email',
'password',
'company',
'first_name',
'last_name')