DRF getting AttributeError while trying to post data

I'm creating a user registration API in DRF using a Profile model with a OneToOne relationship with Django's default User Model. I have created a serializer with User model and defined the fields of Profile as serializer fields.

The problem is when I post the data, though the user and profile are saved in the database I'm getting this error


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'.

using write_only with address seems to solve the problem but I want to understand why is this happening and how this view accessing this address field why it ignores the phone_number and date_of_birth and doesn't give me errors on those. Any input will be helpful. Thanks!

Here is my code.

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()

This error occurs because there is no phone_number, date_of_birth, and address belong to User so DRF couldnt access to them. But when you putting write_only=True, DRF doesn't try to access them anymore, that's why it seems like write_only fixes the problem.

You could create nester serialiser ProfileSerializer and use it in your 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

Also you could use source attr at serialiser field:

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

This issue is due to how Django Rest Framework handles fields in serializers and models.

The Root Cause

You're using a ModelSerializer based on the User model, but you've added fields phone_number, date_of_birth, and address that belong to the Profile model, not the User model.

When DRF serializes or deserializes the User object, it expects that all fields in the serializer correspond to attributes on the User instance. Since address, phone_number and date_of_birth are fields on the Profile model, not the User model, DRF doesn't know how to access them.

In contrast, when you set write_only=True for the address field, it only allows the serializer to accept input for that field without rying to read it from the User object, wihch is why the error goes away. But this is only a partial solution.

Why only address causes an error

When you have required=False, DRF doesn't complain about missing attributes on theUser model because those fields are not required. This is why you get an error for only address. If you set it True, you'll get the same error.

Solution

You need to separate the concerns of serializing the User and Profile models. The best way to do this is to use a nested serializer for the Profile fields.

Here's the updated code:

serializers.py

Create a separate ProfileSerialzier and nest it inside your 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

Sample request data

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

I hope this will help you a little.

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