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.
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}'
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
class UserRegistrationView(generics.CreateAPIView):
serializer_class = UserRegistrationSerializer
queryset = User.objects.all()
This issue is due to how Django Rest Framework handles fields in serializers and models.
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.
address
causes an errorWhen 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.
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
{
"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.