Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework make OnetoOne relation ship feel like it is one model

I have my User saved in two different models, UserProfile and User. Now from API perspective, nobody really cares that these two are different.

So here I have:

class UserSerializer(serializers.HyperlinkedModelSerializer):     class Meta:         model = User         fields = ('url', 'username', 'first_name', 'last_name', 'email') 

and

class UserPSerializer(serializers.HyperlinkedModelSerializer):     full_name = Field(source='full_name')     class Meta:         model = UserProfile         fields = ('url', 'mobile', 'user','favourite_locations') 

So in UserPSerializer the field user is just a link to that resource. But form a User perspective there is really no reason for him to know about User at all.

Is there some tricks with which I can just mash them together and present them to the user as one model or do I have to do this manually somehow.

like image 272
nickik Avatar asked Aug 29 '13 17:08

nickik


1 Answers

You can POST and PUT with @kahlo's approach if you also override the create and update methods on your serializer.

Given a profile model like this:

class Profile(models.Model):     user = models.OneToOneField(User)     avatar_url = models.URLField(default='', blank=True)  # e.g. 

Here's a user serializer that both reads and writes the additional profile field(s):

class UserSerializer(serializers.HyperlinkedModelSerializer):     # A field from the user's profile:     avatar_url = serializers.URLField(source='profile.avatar_url', allow_blank=True)      class Meta:         model = User         fields = ('url', 'username', 'avatar_url')      def create(self, validated_data):         profile_data = validated_data.pop('profile', None)         user = super(UserSerializer, self).create(validated_data)         self.update_or_create_profile(user, profile_data)         return user      def update(self, instance, validated_data):         profile_data = validated_data.pop('profile', None)         self.update_or_create_profile(instance, profile_data)         return super(UserSerializer, self).update(instance, validated_data)      def update_or_create_profile(self, user, profile_data):         # This always creates a Profile if the User is missing one;         # change the logic here if that's not right for your app         Profile.objects.update_or_create(user=user, defaults=profile_data) 

The resulting API presents a flat user resource, as desired:

GET /users/5/ {     "url": "http://localhost:9090/users/5/",      "username": "test",      "avatar_url": "http://example.com/avatar.jpg" } 

and you can include the profile's avatar_url field in both POST and PUT requests. (And DELETE on the user resource will also delete its Profile model, though that's just Django's normal delete cascade.)

The logic here will always create a Profile model for the User if it's missing (on any update). With users and profiles, that's probably what you want. For other relationships it may not be, and you'll need to change the update-or-create logic. (Which is why DRF doesn't automatically write through a nested relationship for you.)

like image 119
medmunds Avatar answered Sep 22 '22 20:09

medmunds