Django Rest Framework make OnetoOne relation ship

2019-01-16 18:22发布

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.

3条回答
我欲成王,谁敢阻挡
2楼-- · 2019-01-16 18:37

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

查看更多
男人必须洒脱
3楼-- · 2019-01-16 18:45

I just came across this; I have yet to find a good solution particularly for writing back to my User and UserProfile models. I am currently flattening my serializers manually using the SerializerMethodField, which is hugely irritating, but it works:

class UserSerializer(serializers.HyperlinkedModelSerializer):

    mobile = serializers.SerializerMethodField('get_mobile')
    favourite_locations = serializers.SerializerMethodField('get_favourite_locations')

    class Meta:
        model = User
        fields = ('url', 'username', 'first_name', 'last_name', 'email', 'mobile', 'favourite_locations')

    def get_mobile(self, obj):
        return obj.get_profile().mobile

    def get_favourite_locations(self, obj):
        return obj.get_profile().favourite_locations

This is horribly manual, but you do end up with:

{
    "url": "http://example.com/api/users/1",
    "username": "jondoe",
    "first_name": "Jon",
    "last_name": "Doe",
    "email": "jdoe@example.com",
    "mobile": "701-680-3095",
    "favourite_locations": [
        "Paris",
        "London",
        "Tokyo"
    ]
}

Which, I guess is what you're looking for.

查看更多
啃猪蹄的小仙女
4楼-- · 2019-01-16 19:01

I would implement the modifications on the UserPSerializer as the fields are not going to grow:

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

class UserPSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.CharField(source='user.url')
    username = serializers.CharField(source='user.username')
    first_name = serializers.CharField(source='user.first_name')
    last_name = serializers.CharField(source='user.last_name')
    email = serializers.CharField(source='user.email')

    class Meta:
        model = UserProfile
        fields = ('mobile', 'favourite_locations',
                  'url', 'username', 'first_name', 'last_name', 'email')
查看更多
登录 后发表回答