Django RESTフレームワーク、同じModelViewSetで異なるシリアライザーを使用


195

2つの異なるシリアライザーを提供しながら、以下のすべての機能を利用できるようにしたいと思いますModelViewSet

  • オブジェクトのリストを表示するとき、各オブジェクトにその詳細にリダイレクトするURLを持たせ、他のすべての関係が__unicode __ターゲットモデルを使用して表示されるようにします。

例:

{
  "url": "http://127.0.0.1:8000/database/gruppi/2/",
  "nome": "universitari",
  "descrizione": "unitn!",
  "creatore": "emilio",
  "accesso": "CHI",
  "membri": [
    "emilio",
    "michele",
    "luisa",
    "ivan",
    "saverio"
  ]
}
  • オブジェクトの詳細を表示するとき、デフォルトを使用したい HyperlinkedModelSerializer

例:

{
  "url": "http://127.0.0.1:8000/database/gruppi/2/",
  "nome": "universitari",
  "descrizione": "unitn!",
  "creatore": "http://127.0.0.1:8000/database/utenti/3/",
  "accesso": "CHI",
  "membri": [
    "http://127.0.0.1:8000/database/utenti/3/",
    "http://127.0.0.1:8000/database/utenti/4/",
    "http://127.0.0.1:8000/database/utenti/5/",
    "http://127.0.0.1:8000/database/utenti/6/",
    "http://127.0.0.1:8000/database/utenti/7/"
  ]
}

私は次のようにして、このすべてをうまく機能させることができました。

serializers.py

# serializer to use when showing a list
class ListaGruppi(serializers.HyperlinkedModelSerializer):
    membri = serializers.RelatedField(many = True)
    creatore = serializers.RelatedField(many = False)

    class Meta:
        model = models.Gruppi

# serializer to use when showing the details
class DettaglioGruppi(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = models.Gruppi

views.py

class DualSerializerViewSet(viewsets.ModelViewSet):
    """
    ViewSet providing different serializers for list and detail views.

    Use list_serializer and detail_serializer to provide them
    """
    def list(self, *args, **kwargs):
        self.serializer_class = self.list_serializer
        return viewsets.ModelViewSet.list(self, *args, **kwargs)

    def retrieve(self, *args, **kwargs):
        self.serializer_class = self.detail_serializer
        return viewsets.ModelViewSet.retrieve(self, *args, **kwargs)

class GruppiViewSet(DualSerializerViewSet):
    model = models.Gruppi
    list_serializer = serializers.ListaGruppi
    detail_serializer = serializers.DettaglioGruppi

    # etc.

基本的に、ユーザーがリストビューまたは詳細ビューを要求しているときを検出し、serializer_class自分のニーズに合わせて変更します。しかし、私はこのコードに本当に満足していません。汚いハックのように見えます。最も重要なのは、2人のユーザーが同時にリストと詳細を要求した場合どうなるでしょうか。

これを使用してこれを達成するより良い方法はありますか、それともModelViewSets使用をフォールバックする必要がありGenericAPIViewますか?

編集:
カスタムベースを使用してそれを行う方法はModelViewSet次のとおりです:

class MultiSerializerViewSet(viewsets.ModelViewSet):
    serializers = { 
        'default': None,
    }

    def get_serializer_class(self):
            return self.serializers.get(self.action,
                        self.serializers['default'])

class GruppiViewSet(MultiSerializerViewSet):
    model = models.Gruppi

    serializers = {
        'list':    serializers.ListaGruppi,
        'detail':  serializers.DettaglioGruppi,
        # etc.
    }

最終的にどのように実装しましたか?user2734679によって提案された方法またはGenericAPIViewを使用していますか?
アンディラブス2014

user2734679によって提案されたように; 各アクションのシリアライザを指定するディクショナリと、指定されていない場合はデフォルトのシリアライザを追加する汎用ViewSetを作成しました
BlackBear

同様の問題(stackoverflow.com/questions/24809737/…)があり、今のところそれで終わりました(gist.github.com/andilab/a23a6370bd118bf5e858)が、あまり満足していません。
アンディラブス2014

1
このためのこの小さなパッケージを作成しました。github.com/Darwesh27/drf-custom-viewsets
Adil Malik

1
オーバーライド取得メソッドはOKです。
gzerone 2016年

回答:


287

get_serializer_classメソッドをオーバーライドします。このメソッドは、適切なSerializerクラスを取得するためにモデルミックスインで使用されます。

正しいシリアライザのインスタンスget_serializerを返すメソッドもあることに注意してください

class DualSerializerViewSet(viewsets.ModelViewSet):
    def get_serializer_class(self):
        if self.action == 'list':
            return serializers.ListaGruppi
        if self.action == 'retrieve':
            return serializers.DettaglioGruppi
        return serializers.Default # I dont' know what you want for create/destroy/update.                

1
これは素晴らしいです、ありがとう!ただし、get_serializer_classをオーバーライドしました
BlackBear 2014年

15
警告:django rest swaggerはself.actionパラメーターを配置しないため、この関数は例外をスローします。あなたはゴンツの答えを使うかもしれないし、あなたは使うかもしれないif hasattr(self, 'action') and self.action == 'list'
トム・

このための小さなpypiパッケージを作成します。github.com/Darwesh27/drf-custom-viewsets
Adil Malik

pkアクションが必要な場合、リクエストされたオブジェクトの取得方法を教えてくださいretrieve
Pranjal Mittal 2016

私のself.actionはNoneです。誰かが理由を教えてもらえますか?
カカジ2017年

86

このミックスインは便利です。get_serializer_classメソッドをオーバーライドし、アクションとシリアライザクラスまたはフォールバックを通常の動作にマップするdictを宣言できるようにします。

class MultiSerializerViewSetMixin(object):
    def get_serializer_class(self):
        """
        Look for serializer class in self.serializer_action_classes, which
        should be a dict mapping action name (key) to serializer class (value),
        i.e.:

        class MyViewSet(MultiSerializerViewSetMixin, ViewSet):
            serializer_class = MyDefaultSerializer
            serializer_action_classes = {
               'list': MyListSerializer,
               'my_action': MyActionSerializer,
            }

            @action
            def my_action:
                ...

        If there's no entry for that action then just fallback to the regular
        get_serializer_class lookup: self.serializer_class, DefaultSerializer.

        """
        try:
            return self.serializer_action_classes[self.action]
        except (KeyError, AttributeError):
            return super(MultiSerializerViewSetMixin, self).get_serializer_class()

このためのこの小さなパッケージを作成しました。github.com/Darwesh27/drf-custom-viewsets
Adil Malik

15

この答えは受け入れられた答えと同じですが、私はこのようにすることを好みます。

一般的なビュー

get_serializer_class(self):

シリアライザに使用するクラスを返します。デフォルトではserializer_class属性を返します。

オーバーライドして、読み取りおよび書き込み操作に異なるシリアライザーを使用したり、異なるタイプのユーザーに異なるシリアライザーを提供したりするなど、動的な動作を提供できます。serializer_class属性。

class DualSerializerViewSet(viewsets.ModelViewSet):
    # mapping serializer into the action
    serializer_classes = {
        'list': serializers.ListaGruppi,
        'retrieve': serializers.DettaglioGruppi,
        # ... other actions
    }
    default_serializer_class = DefaultSerializer # Your default serializer

    def get_serializer_class(self):
        return self.serializer_classes.get(self.action, self.default_serializer_class)

ビューに属性「アクション」がないことが通知されるため、使用できません。ProductIndex(generics.ListCreateAPIView)のように見えます。それはあなたが絶対に引数としてビューセットを渡す必要があることを意味しますか、それともジェネリックAPIビューを使用してそれを行う方法がありますか?
セブ

1
@Sebコメントへの返信が遅い-誰かがそれから利益を得るかもしれません:)この例ではViewsではなくViewSetsを使用しています:)
ファニー

したがって、このポストstackoverflow.com/questions/32589087/…と組み合わせると、ViewSetは、さまざまなビューをより詳細に制御し、URLを自動的に生成して一貫したAPIを取得する方法のように見えますか?当初、generics.ListeCreateAPIViewが最も効率的であると考えていましたが、基本的すぎますよね?
セブ

10

さまざまなシリアライザーを提供することに関して、なぜHTTPメソッドをチェックするアプローチを採用しないのですか?IMOがより明確になり、追加のチェックは不要です。

def get_serializer_class(self):
    if self.request.method == 'POST':
        return NewRackItemSerializer
    return RackItemSerializer

クレジット/ソース:https : //github.com/encode/django-rest-framework/issues/1563#issuecomment-42357718


12
問題のケースでは、listretrieveアクションに異なるシリアライザーを使用するため、両方がGETメソッドを使用するという問題があります。これが、django RESTフレームワークのViewSets が、対応するhttpメソッドとは似ていますが少し異なるactionの概念を使用する理由です。
派遣蓋

8

@gonzと@ user2734679の回答に基づいて、ModelViewsetの子クラスの形式でこの機能を提供するこの小さなpythonパッケージを作成しました。これがどのように機能するかです。

from drf_custom_viewsets.viewsets.CustomSerializerViewSet
from myapp.serializers import DefaltSerializer, CustomSerializer1, CustomSerializer2

class MyViewSet(CustomSerializerViewSet):
    serializer_class = DefaultSerializer
    custom_serializer_classes = {
        'create':  CustomSerializer1,
        'update': CustomSerializer2,
    }

6
汎用性の高いミックスインを使用する方が良いでしょう。
iamsk

1

複数のシリアライザを何らかの方法で事前定義することが最も明らかに文書化されている方法のようですが、FWIWには、他の文書化されたコードを利用し、インスタンス化時にシリアライザに引数を渡すことを可能にする代替アプローチがあります。ユーザー管理レベル、呼び出されるアクション、インスタンスの属性など、さまざまな要因に基づいてロジックを生成する必要がある場合は、おそらくより価値がある傾向にあると思います。

パズルの最初のピースは、インスタンス化の時点でシリアライザを動的に変更することに関するドキュメントです。そのドキュメントは、ビューセットからこのコードを呼び出す方法や、フィールドが初期化された後にフィールドの読み取り専用ステータスを変更する方法を説明していませんが、それほど難しくありません。

2番目の部分-get_serializerメソッドも文書化されています-(「他のメソッド」の下のget_serializer_classから少し下に)信頼できるので安全です(そしてソースは非常にシンプルであり、意図しない可能性が少ないことを意味します)変更による副作用)。GenericAPIViewの下のソースを確認します(ModelViewSet、およびそれと思われる他のすべての組み込みビューセットクラス)。get_serializerを定義するGenericAPIViewから継承します。

2つを組み合わせると、次のようなことができます。

シリアライザーファイル(私にとってはbase_serializers.py):

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""

def __init__(self, *args, **kwargs):
    # Don't pass the 'fields' arg up to the superclass
    fields = kwargs.pop('fields', None)

    # Adding this next line to the documented example
    read_only_fields = kwargs.pop('read_only_fields', None)

    # Instantiate the superclass normally
    super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

    if fields is not None:
        # Drop any fields that are not specified in the `fields` argument.
        allowed = set(fields)
        existing = set(self.fields)
        for field_name in existing - allowed:
            self.fields.pop(field_name)

    # another bit we're adding to documented example, to take care of readonly fields 
    if read_only_fields is not None:
        for f in read_only_fields:
            try:
                self.fields[f].read_only = True
            exceptKeyError:
                #not in fields anyway
                pass

次に、ビューセットで次のようにします。

class MyViewSet(viewsets.ModelViewSet):
    # ...permissions and all that stuff

    def get_serializer(self, *args, **kwargs):

        # the next line is taken from the source
        kwargs['context'] = self.get_serializer_context()

        # ... then whatever logic you want for this class e.g:
        if self.action == "list":
            rofs = ('field_a', 'field_b')
            fs = ('field_a', 'field_c')
        if self.action == retrieve”:
            rofs = ('field_a', 'field_c’, ‘field_d’)
            fs = ('field_a', 'field_b’)
        #  add all your further elses, elifs, drawing on info re the actions, 
        # the user, the instance, anything passed to the method to define your read only fields and fields ...
        #  and finally instantiate the specific class you want (or you could just
        # use get_serializer_class if you've defined it).  
        # Either way the class you're instantiating should inherit from your DynamicFieldsModelSerializer
        kwargs['read_only_fields'] = rofs
        kwargs['fields'] = fs
        return MyDynamicSerializer(*args, **kwargs)

そしてそれはそれであるはずです!MyViewSetを使用すると、MyDynamicSerializerを必要な引数でインスタンス化する必要があります。また、シリアライザがDynamicFieldsModelSerializerを継承していると仮定すると、何をすべきかがわかるはずです。

おそらく、シリアライザを他の方法で適応させたい場合に特別な意味があることを言及する価値があるかもしれません。たとえば、read_only_exceptionsリストを取得して、ブラックリストフィールドではなくホワイトリストに使用するなどです(私はそうする傾向があります)。また、渡されなかった場合にフィールドを空のタプルに設定し、None ...のチェックを外すと、継承するシリアライザーのフィールド定義を ' all ' に設定すると便利です。これは、シリアライザーのインスタンス化時に渡されないフィールドが誤って存続することを意味します。また、何が含まれているかを知るために、シリアライザーの呼び出しを継承シリアライザークラス定義と比較する必要はありません。たとえば、DynamicFieldsModelSerializerのinit内:

# ....
fields = kwargs.pop('fields', ())
# ...
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# ....

注意:個別のアクションにマップする2つまたは3つのクラスが必要な場合、および/または特別に動的なシリアライザの動作が必要ない場合は、ここで他の人が言及したアプローチの1つを使用することもできますが、これは代替として提示する価値があると思いました、特に他の用途を考えると。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.