I am trying to extend the Django rest framework (version 3.x.x) with gender
, created_at
, updated_at
fields that are defined in a separate model called UserProfile
. When I try to update the UserProfile
model instance including the nested User
model instance, it updates only the UserProfile
instance (gender, updated_at fields
). So basically what I want to achieve is to be able to update also the email, first_name and last_name
fields from the User
model
models.py:
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key = True, related_name = 'profile')
gender = models.CharField(choices = GENDERS, default = 2, max_length = 64)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __unicode__(self):
return self.user.username
@receiver(post_save, sender = User)
def create_profile_for_user(sender, instance = None, created = False, **kwargs):
if created:
UserProfile.objects.get_or_create(user = instance)
@receiver(pre_delete, sender = User)
def delete_profile_for_user(sender, instance = None, **kwargs):
if instance:
user_profile = UserProfile.objects.get(user = instance)
user_profile.delete()
serializers.py:
class UserProfileSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source = 'pk', read_only = True)
username = serializers.CharField(source = 'user.username', read_only = True)
email = serializers.CharField(source = 'user.email')
first_name = serializers.CharField(source = 'user.first_name')
last_name = serializers.CharField(source = 'user.last_name')
class Meta:
model = UserProfile
fields = (
'id', 'username', 'email', 'first_name', 'last_name',
'created_at', 'updated_at', 'gender',
)
read_only_fields = ('created_at', 'updated_at',)
def update(self, instance, validated_data):
#user = User.objects.get(pk = instance.user.pk);
user = instance.user
user.email = validated_data.get('user.email', user.email)
user.first_name = validated_data.get('user.first_name', user.first_name)
user.last_name = validated_data.get('user.last_name', user.last_name)
user.save()
instance.gender = validated_data.get('gender', instance.gender)
instance.save()
return instance
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
profile = UserProfile.objects.create(user = user, **validated_data)
return profile
views.py:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
Open auth/urls.py and add update profile endpoint. we should send a PUT request to API for checking update profile endpoint. We must add username, first_name, last_name and email. If fields passed validations, user profile will be changed.
Django's serialization framework provides a mechanism for “translating” Django models into other formats. Usually these other formats will be text-based and used for sending Django data over a wire, but it's possible for a serializer to handle any format (text-based or not).
Django REST framework (DRF) is a powerful and flexible toolkit for building Web APIs. Its main benefit is that it makes serialization much easier. Django REST framework is based on Django's class-based views, so it's an excellent option if you're familiar with Django.
A slight modification to the above code replacing the None with {}
Because 'NoneType' object has no attribute 'items'
def update(self, instance, validated_data):
# First, update the User
user_data = validated_data.pop('user', {})
for attr, value in user_data.items():
setattr(instance.user, attr, value)
# Then, update UserProfile
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
There is another way that I am implementing which takes care of validation. I am implementing it for handling PATCH
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'first_name', 'last_name')
class ProfileSerializer(serializers.ModelSerializer):
email = serializers.CharField(source='user.email')
first_name = serializers.CharField(source='user.first_name')
last_name = serializers.CharField(source='user.last_name')
class Meta:
model = Profile
exclude = ('user',)
def update(self, instance, validated_data):
user_data = validated_data.pop('user', {})
user_serializer = UserSerializer(instance.user, data=user_data, partial=True)
user_serializer.is_valid(raise_exception=True)
user_serializer.update(instance.user, user_data)
super(ProfileSerializer, self).update(instance, validated_data)
return instance
Here's how I did it:
def update(self, instance, validated_data):
# First, update the User
user_data = validated_data.pop('user', None)
for attr, value in user_data.items():
setattr(instance.user, attr, value)
# Then, update UserProfile
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
Basically, I looked into the original code for serializers
(here, line 800) and modified it so that it would fit my needs.
Good luck.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With