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

后端 未结 3 1904
时光取名叫无心
时光取名叫无心 2020-12-07 22:58

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

相关标签:
3条回答
  • 2020-12-07 23:03

    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.

    0 讨论(0)
  • 2020-12-07 23:20

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

    0 讨论(0)
  • 2020-12-07 23:25

    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')
    
    0 讨论(0)
提交回复
热议问题