web-dev-qa-db-ja.com

Django Rest FrameworkはOnetoOneの関係船を1つのモデルのように感じさせます

Userを2つの異なるモデルUserProfileUserに保存しました。 APIの観点からは、これら2つが異なることを誰も気にしません。

だからここに私は持っています:

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

そして

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

したがって、UserPSerializerのフィールドuserは、そのリソースへのリンクにすぎません。しかし、ユーザーの観点から見ると、彼がUserについて知っている理由はまったくありません。

それらを一緒にマッシュしてユーザーに1つのモデルとして提示できるいくつかのトリックはありますか、それとも手動でこれを何らかの方法で行う必要がありますか?.

39
nickik

POSTおよびPUT @ kahloのアプローチ でPUTすることもできますが、シリアライザのcreateおよびupdateメソッドもオーバーライドする場合に限ります。

次のようなプロファイルモデルがあるとします。

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

以下は、追加のプロファイルフィールドの読み取りと書き込みの両方を行うユーザーシリアライザーです。

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)

結果のAPIは、必要に応じてフラットなユーザーリソースを提供します。

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

また、POSTとPUTリクエストの両方にプロファイルのavatar_urlフィールドを含めることができます(ユーザーリソースのDELETEを実行すると、Djangoの通常の削除カスケードであるにもかかわらず、プロファイルモデルも削除されます。 )

ここでのロジックは、alwaysが欠落している場合(更新時に)、ユーザーのプロファイルモデルを作成します。ユーザーとプロファイルでは、おそらくそれが必要です。他の関係の場合はそうではない可能性があり、更新または作成ロジックを変更する必要があります。 (これがDRF ネストされた関係を介して自動的に書き込まれない理由です を使用します。)

33
medmunds

私はこれに出くわしました。特にUserおよびUserProfileモデルに書き戻すための良い解決策はまだ見つけていません。現在、SerializerMethodFieldを使用して手動でシリアライザをフラット化していますが、これは非常に苛立たしいことですが、機能します。

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

これは恐ろしく手動ですが、最終的には次のようになります。

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

それはあなたが探しているものだと思います。

12
bbengfort

フィールドが大きくならないので、UserPSerializerに変更を実装します。

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')
6
kahlo