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"
    }
}

Надеюсь, это немного поможет вам.

Вернуться на верх