Django Rest Frameworkでネストされたシリアライザーをどのようにフィルタリングしますか?


84

Django Rest Frameworkで、シリアライザーが別のシリアライザーにネストされている場合、どのようにフィルタリングしますか?

私のフィルターはDRFビューセットに適用されますが、別のシリアライザーの内部からシリアライザーを呼び出すと、ネストされたシリアライザーのビューセットが呼び出されないため、ネストされた結果はフィルター処理されていないように見えます。

元のビューセットにフィルターを追加しようとしましたが、ネストされた結果が個別の事前にフェッチされたクエリとして呼び出されるため、ネストされた結果をフィルターに掛けていないようです。(ネストされたシリアライザーは逆ルックアップです。)

ネストされたシリアライザー自体にget_queryset()オーバーライドを追加して(ビューセットから移動して)、そこにフィルターを追加することは可能ですか?私もそれを試しましたが、運がありませんでした。

これは私が試したものですが、呼ばれることさえないようです:

class QuestionnaireSerializer(serializers.ModelSerializer):
    edition = EditionSerializer(read_only=True)
    company = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = Questionnaire

    def get_queryset(self):
        query = super(QuestionnaireSerializer, self).get_queryset(instance)
        if not self.request.user.is_staff:
            query = query.filter(user=self.request.user, edition__hide=False)
        return query

6
get_querysetは、ModelViewSetSerializerではなく、のクラスです。そのため、呼び出されません
NotSimon 2015

回答:


99

ListSerializerをサブクラス化して、メソッドを上書きできますto_representation

デフォルトでは、to_representationメソッドはdata.all()ネストされたクエリセットを呼び出します。したがってdata = data.filter(**your_filters)、メソッドが呼び出される前に効果的に作成する必要があります。次に、ネストされたシリアライザーのメタで、サブクラス化されたListSerializerをlist_serializer_classとして追加する必要があります。

  1. サブクラスListSerializer、上書きto_representationしてからスーパーを呼び出す
  2. サブクラス化されたListSerializerをlist_serializer_classネストされたSerializerのメタとして追加します

サンプルに関連するコードは次のとおりです。

class FilteredListSerializer(serializers.ListSerializer):

    def to_representation(self, data):
        data = data.filter(user=self.context['request'].user, edition__hide=False)
        return super(FilteredListSerializer, self).to_representation(data)


class EditionSerializer(serializers.ModelSerializer):

    class Meta:
        list_serializer_class = FilteredListSerializer
        model = Edition


class QuestionnaireSerializer(serializers.ModelSerializer):
    edition = EditionSerializer(read_only=True)
    company = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = Questionnaire

1
それがトリックでした!結局、シリアライザーが複雑になりすぎていると判断し、それらすべてをリファクタリングして、クライアントにさらにいくつかのapi呼び出しを実行させましたが、アプリは大幅に簡素化されました。
ジョン

3
同様の問題の解決策の基礎としてこれを使用しようとしています。それが本当にそれ自身の質問に値するかどうかはわかりません。QuestionnaireSerializer変数をListSerializerに渡すにはどうすればよいですか?概算するには、エディションのIDとアンケートのIDでフィルタリングする必要があります。
ブレンダン

3
これは、DRFのドキュメントに記載されているはずです。超便利ありがとうございます!
ダニエルヴァンフライメン2015

7
私の実装では、'FilteredListSerializer' object has no attribute 'request'他の誰かが同じようになっていますか?
Dominooch 2015

11
@Dominoochに回答するには、self.requestの代わりにself.context ['request']を使用する必要があります
rojoca

25

SOおよび他の場所からの多くのソリューションをテストしました。

Django 2.0 + DRF3.7.7の実用的なソリューションが1つだけ見つかりました。

ネストされたクラスを持つモデルでメソッドを定義します。ニーズに合ったフィルターを作成します。

class Channel(models.Model):
    name = models.CharField(max_length=40)
    number = models.IntegerField(unique=True)
    active = models.BooleanField(default=True)

    def current_epg(self):
        return Epg.objects.filter(channel=self, end__gt=datetime.now()).order_by("end")[:6]


class Epg(models.Model):
    start = models.DateTimeField()
    end = models.DateTimeField(db_index=True)
    title = models.CharField(max_length=300)
    description = models.CharField(max_length=800)
    channel = models.ForeignKey(Channel, related_name='onair', on_delete=models.CASCADE)

class EpgSerializer(serializers.ModelSerializer):
    class Meta:
        model = Epg
        fields = ('channel', 'start', 'end', 'title', 'description',)


class ChannelSerializer(serializers.ModelSerializer):
    onair = EpgSerializer(many=True, read_only=True, source="current_epg")

    class Meta:
        model = Channel
        fields = ('number', 'name', 'onair',)

注意を払うsource="current_epg"と、ポイントが得られます。


はい!このコメントは、モデルで定義する関数であるソースの機能を活用しており、そこでフィルタリングを利用できます。涼しい!
possumkeys 2018年

クラスの下の関数に文字列を渡すことは可能ですか?
AlexW

多対多関連分野の注文だけが必要でした。また、多対多の異なる解決策を試しました(しゃれを意図した)。しかし、これが私のために働いた唯一の解決策でした!ありがとう!
gabn 8820年

これは、受け入れられた答えよりもdjangoのコード哲学の観点からより正しい解決策のようです。DjangoはActiveModel(「ファットモデル」)アプローチを提案しているため、フィルタリングはモデルレベル(またはビューセットレベル)で行う必要があり、シリアル化はビジネスロジックについて何も知らないはずです。
oxfn

14

上記のすべての答えは機能しますが、DjangoのPrefetchオブジェクトを使用するのが最も簡単な方法だと思います。

言うRestaurantobjが、多くの持ってMenuItemいるそのうちのいくつかは、Sをis_remove == True、そしてあなただけ削除されていないものが欲しいです。

RestaurantViewSet、次のようなことを行います

from django.db.models import Prefetch

queryset = Restaurant.objects.prefetch_related(
    Prefetch('menu_items', queryset=MenuItem.objects.filter(is_removed=False), to_attr='filtered_menu_items')
)

RestaurantSerializer、次のようなことを行います

class RestaurantSerializer(serializers.ModelSerializer):
    menu_items = MenuItemSerializer(source='filtered_menu_items', many=True, read_only=True)


2
素晴らしい解決策、これがそれを解決するための最良の方法であることに同意します。
ヨルダン

これは一番上にあるはずです。現在の最上位のソリューションは、データベースから既にプルされた後、to_representationを使用してデータをフィルタリングします。このソリューションは、クエリのデータをフィルタリングし、一括リクエストでフェッチします。ほとんどの場合、はるかに優れています。
アレックス

これは大いに役立ちました、ありがとう!
うん、

7

シリアライザーがインスタンス化され、many = Trueが渡されると、ListSerializerインスタンスが作成されます。その後、シリアライザークラスは親ListSerializerの子になります

このメソッドは、フィールドのターゲットを値引数として受け取り、ターゲットをシリアル化するために使用する必要がある表現を返す必要があります。value引数は通常、モデルインスタンスになります。

以下はネストされたシリアライザーの例です

class UserSerializer(serializers.ModelSerializer):
    """ Here many=True is passed, So a ListSerializer instance will be 
     created"""
    system = SystemSerializer(many=True, read_only=True)

    class Meta:
        model = UserProfile
        fields = ('system', 'name')

class FilteredListSerializer(serializers.ListSerializer):
    
    """Serializer to filter the active system, which is a boolen field in 
       System Model. The value argument to to_representation() method is 
      the model instance"""
    
    def to_representation(self, data):
        data = data.filter(system_active=True)
        return super(FilteredListSerializer, self).to_representation(data)

class SystemSerializer(serializers.ModelSerializer):
    mac_id = serializers.CharField(source='id')
    system_name = serializers.CharField(source='name')
    serial_number = serializers.CharField(source='serial')

    class Meta:
        model = System
        list_serializer_class = FilteredListSerializer
        fields = (
            'mac_id', 'serial_number', 'system_name', 'system_active', 
        )

ビューで:

class SystemView(viewsets.GenericViewSet, viewsets.ViewSet):
    def retrieve(self, request, email=None):
        data = get_object_or_404(UserProfile.objects.all(), email=email)
        serializer = UserSerializer(data)
        return Response(serializer.data)

5

SerializerMethodFieldフィルタリングするシリアライザーフィールドでを使用する方が簡単で、より簡単だと思います。

だからあなたはこのようなことをするでしょう。

class CarTypesSerializer(serializers.ModelSerializer):

    class Meta:
        model = CarType
        fields = '__all__'


class CarSerializer(serializers.ModelSerializer):

    car_types = serializers.SerializerMethodField()

    class Meta:
        model = Car
        fields = '__all__'

    def get_car_types(self, instance):
        # Filter using the Car model instance and the CarType's related_name
        # (which in this case defaults to car_types_set)
        car_types_instances = instance.car_types_set.filter(brand="Toyota")
        return CarTypesSerializer(car_types_instances, many=True).data

これにより、シリアライザーserializers.ListSerializerごとに異なるフィルタリング基準が必要な場合に、のオーバーライドを多数作成する必要がなくなります。

また、サブクラス定義に飛び込む代わりに、シリアライザー内でフィルターが何をするかを正確に確認できるという追加の利点もあります。

もちろん、欠点は、ネストされたオブジェクトが多数あるシリアライザーがあり、それらすべてを何らかの方法でフィルター処理する必要がある場合です。シリアライザーコードが大幅に増加する可能性があります。どのようにフィルタリングするかはあなた次第です。

お役に立てれば!

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