DRF получает ошибку AttributeError при попытке разместить данные
Я создаю API регистрации пользователей в DRF, используя модель Profile с отношением OneToOne с моделью User по умолчанию Django. Я создал сериализатор с моделью User и определил поля Profile как поля сериализатора.
Проблема в том, что когда я публикую данные, хотя пользователь и профиль сохранены в базе данных, я получаю эту ошибку
error AttributeError at /auth/registration Got AttributeError when attempting to get a value for field address on serializer UserRegistrationSerializer. The serializer field might be named incorrectly and not match any attribute or key on the User instance. Original exception text was: 'User' object has no attribute 'address'.
Использование write_only с address
, кажется, решает проблему, но я хочу понять, почему это происходит и как это представление обращается к этому полю адреса, почему оно игнорирует phone_number
и date_of_birth
и не дает мне ошибок на них. Любой ответ будет полезен. Спасибо!
Вот мой код.
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
date_of_birth = models.DateField(null=True)
phone_number = models.CharField(null=True, max_length=30)
address = models.CharField(max_length=250)
def __str__(self):
return f'Profile of {self.user.username}'
Serializer.py
class UserRegistrationSerializer(serializers.ModelSerializer):
phone_number = serializers.CharField(required=False, max_length=30)
date_of_birth = serializers.DateField(required=False)
address = serializers.CharField(max_length=250)
password = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True, label = 'Confirm Password')
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'email', 'phone_number', 'date_of_birth', 'address', 'password', 'password2']
def create(self, validated_data):
phone_number = validated_data.pop('phone_number', None)
date_of_birth = validated_data.pop('date_of_birth', None)
address = validated_data.pop('address', None)
password = validated_data.pop('password', None)
first_name = validated_data.pop('first_name', None)
last_name = validated_data.pop('last_name', None)
user = User (
username = validated_data['username'],
email = validated_data['email'],
first_name = first_name,
last_name = last_name
)
user.set_password(password)
user.save()
Profile.objects.create(
user=user,
date_of_birth=date_of_birth,
address=address,
phone_number=phone_number
)
return user
view.py
class UserRegistrationView(generics.CreateAPIView):
serializer_class = UserRegistrationSerializer
queryset = User.objects.all()
Эта ошибка возникает потому, что нет телефонного_номера, даты_рождения и адреса, принадлежащих пользователю, поэтому DRF не может получить к ним доступ.
Но когда вы ставите write_only=True
, DRF больше не пытается получить к ним доступ, поэтому кажется, что write_only устраняет проблему.
Вы можете создать нестер-сериализатор ProfileSerializer
и использовать его в своих UserRegistrationSerializer
:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['date_of_birth','phone_number','address']
class UserRegistrationSerializer(serializers.ModelSerializer):
profile = ProfileSerializer(required=True)
password = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True, label='Confirm Password')
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'email', 'password', 'password2', 'profile']
def create(self, validated_data):
profile_data = validated_data.pop('profile')
password = validated_data.pop('password')
user = User.objects.create(**validated_data)
user.set_password(password)
user.save()
Profile.objects.create(
user=user,
date_of_birth=profile_data.get('date_of_birth'),
phone_number=profile_data.get('phone_number'),
address=profile_data.get('address')
)
return user
Также вы можете использовать source
attr в поле сериализатора:
class UserRegistrationSerializer(serializers.ModelSerializer):
phone_number = serializers.CharField(source='profile.phone_number', required=False, max_length=30)
date_of_birth = serializers.DateField(source='profile.date_of_birth', required=False)
address = serializers.CharField(source='profile.address', required=False, max_length=250)
password = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True, label='Confirm Password')
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'email', 'phone_number', 'date_of_birth', 'address', 'password', 'password2']
def create(self, validated_data):
profile_data = validated_data.pop('profile', {})
password = validated_data.pop('password')
user = User.objects.create(**validated_data)
user.set_password(password)
user.save()
Profile.objects.create(
user=user,
phone_number=profile_data.get('phone_number'),
date_of_birth=profile_data.get('date_of_birth'),
address=profile_data.get('address')
)
return user
Эта проблема связана с тем, как Django Rest Framework обрабатывает поля в сериализаторах и моделях.
Коренная причина
Вы используете модель ModelSerializer
, основанную на модели User
, но добавили поля phone_number
, date_of_birth
и address
, которые принадлежат модели Profile
, а не модели User
.
Когда DRF сериализует или десериализует объект User
, он ожидает, что все поля в сериализаторе соответствуют атрибутам экземпляра User
. Поскольку address
, phone_number
и date_of_birth
являются полями модели Profile
, а не модели User
, DRF не знает, как к ним обращаться.
Напротив, когда вы устанавливаете write_only=True
для поля address
, это позволяет сериализатору принимать ввод для этого поля, не пытаясь прочитать его из объекта User
, поэтому ошибка исчезает. Но это лишь частичное решение проблемы.
Почему только address
вызывает ошибку
Когда у вас есть required=False
, DRF не жалуется на отсутствие атрибутов в модели User
, потому что эти поля не являются обязательными. Вот почему вы получаете ошибку только для address
. Если вы установите значение True, вы получите ту же ошибку.
Решение
Вам необходимо разделить заботы о сериализации моделей User
и Profile
. Лучший способ сделать это - использовать вложенный сериализатор для полей Profile
.
Вот обновленный код:
serializers.py
Создайте отдельный ProfileSerialzier
и вложите его в UserRegisterationSerializer
:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['date_of_birth', 'phone_number', 'address']
class UserRegistrationSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
password = serializers.CharField(write_only=True)
password2 = serializers.CharField(write_only=True, label='Confirm Password')
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'email', 'password', 'password2', 'profile']
def create(self, validated_data):
profile_data = validated_data.pop('profile', {})
password = validated_data.pop('password', None)
first_name = validated_data.pop('first_name', None)
last_name = validated_data.pop('last_name', None)
user = User(
username=validated_data['username'],
email=validated_data['email'],
first_name=first_name,
last_name=last_name
)
user.set_password(password)
user.save()
Profile.objects.create(
user=user,
date_of_birth=profile_data.get('date_of_birth', None),
phone_number=profile_data.get('phone_number', None),
address=profile_data.get('address', None),
)
return user
Данные запроса образца
{
"username": "testuser",
"first_name": "Test",
"last_name": "User",
"email": "testuser@example.com",
"password": "password123",
"password2": "password123",
"profile": {
"date_of_birth": "1990-01-01",
"phone_number": "1234567890",
"address": "Test Street"
}
}
Надеюсь, это немного поможет вам.