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

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

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

Leave a Comment