Wie filtert man einen verschachtelten Serializer in Django Rest Framework?

81

Wie filtern Sie in Django Rest Framework einen Serializer, wenn er in einem anderen Serializer verschachtelt ist?

Meine Filter werden in den DRF-Ansichtssätzen festgelegt. Wenn Sie jedoch einen Serializer aus einem anderen Serializer heraus aufrufen, wird das Viewset des verschachtelten Serializers nie aufgerufen, sodass die verschachtelten Ergebnisse ungefiltert angezeigt werden.

Ich habe versucht, einen Filter für das ursprüngliche Ansichtsset hinzuzufügen, aber es scheint die verschachtelten Ergebnisse nicht zu filtern, da die verschachtelten Ergebnisse als separate vorab abgerufene Abfrage aufgerufen werden. (Der verschachtelte Serializer ist eine umgekehrte Suche.)

Ist es möglich, eine get_queryset () - Überschreibung im verschachtelten Serializer selbst hinzuzufügen (aus dem Viewset zu verschieben), um den Filter dort hinzuzufügen? Ich habe das auch ohne Glück versucht.

Dies ist, was ich versucht habe, aber es scheint nicht einmal genannt zu werden:

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
John
quelle
6
get_querysetist eine Klasse auf ModelViewSet, nicht auf dem Serializer, weshalb es nicht aufgerufen wird
NotSimon

Antworten:

97

Sie können den ListSerializer in Unterklassen unterteilen und die to_representationMethode überschreiben .

Standardmäßig to_representationruft die Methode data.all()das verschachtelte Abfrageset auf. Sie müssen also effektiv machen, data = data.filter(**your_filters)bevor die Methode aufgerufen wird. Anschließend müssen Sie Ihren untergeordneten ListSerializer als list_serializer_class zum Meta des verschachtelten Serializers hinzufügen.

  1. Unterklasse ListSerializer, überschreibt to_representationund ruft dann super auf
  2. Fügen Sie den untergeordneten ListSerializer als Meta list_serializer_classzum verschachtelten Serializer hinzu

Hier ist der relevante Code für Ihr Beispiel.

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
in Perspektive
quelle
1
Das hat funktioniert! Am Ende entschied ich jedoch, dass meine Serializer zu komplex wurden, und überarbeitete sie alle. Der Client musste ein paar weitere API-Aufrufe ausführen, vereinfachte jedoch meine App erheblich.
John
3
Der Versuch, dies als Grundlage für eine Lösung eines ähnlichen Problems zu verwenden; Ich bin mir nicht sicher, ob es wirklich eine eigene Frage verdient. Wie würde ich eine QuestionnaireSerializerVariable von an den ListSerializer übergeben? Zur Annäherung muss ich sowohl nach der ID der Edition als auch nach der ID des Fragebogens filtern.
Brendan
3
Dies sollte in der DRF-Dokumentation enthalten sein. Super nützlich, danke!
Daniel van Flymen
7
In meiner Implementierung bekomme ich 'FilteredListSerializer' object has no attribute 'request'jemand anderes das gleiche?
Dominooch
11
Um @Dominooch zu beantworten, müssen Sie self.context ['request'] anstelle von self.request
rojoca
25

Testete viele Lösungen von SO und anderen Orten.

Es wurde nur eine funktionierende Lösung für Django 2.0 + DRF 3.7.7 gefunden.

Definieren Sie eine Methode in einem Modell mit verschachtelter Klasse. Stellen Sie einen Filter her, der Ihren Anforderungen entspricht.

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',)

Achten Sie darauf source="current_epg"und Sie werden den Punkt bekommen.

Duddits
quelle
Ja! Dieser Kommentar nutzt die Fähigkeit der Quelle, eine Funktion zu sein, die Sie im Modell definieren. Dann können Sie die Filterung dort nutzen! Cool!
Opossumkeys
Ist es möglich, einen String an die Funktion unter der Klasse zu übergeben?
AlexW
Ich brauchte nur die Bestellung eines vielen verwandten Bereichs. Auch versucht viele zu viele verschiedene Lösungen (Wortspiel beabsichtigt). Aber dies war die einzige Lösung, die für mich funktioniert hat! Vielen Dank!
gabn88
Sieht so aus, als wäre dies eine korrektere Lösung in Bezug auf die Code-Philosophie von Django als eine akzeptierte Antwort. Django schlägt einen ActiveModel-Ansatz ("Fat Models") vor, daher sollte die Filterung auf Modellebene (oder Viewset-Ebene) erfolgen und die Serialisierung sollte nichts über Geschäftslogik wissen.
Oxfn
12

Während alle oben genannten Antworten funktionieren, finde ich die Verwendung von Djangos PrefetchObjekt der einfachste Weg von allen.

RestaurantAngenommen, ein Objekt hat viele MenuItems, von denen einige sind is_remove == True, und Sie möchten nur diejenigen, die nicht entfernt werden.

In RestaurantViewSet, so etwas wie

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

In RestaurantSerializer, so etwas wie

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

Dennis Lau
quelle
2
Tolle Lösung, ich stimme zu, dies ist der beste Weg, um es zu lösen.
Jordanien
Dies sollte oben sein. Die aktuelle Top-Lösung filtert die Daten mit to_representation, nachdem sie bereits aus der Datenbank abgerufen wurden. Diese Lösung filtert die Daten in der Abfrage und ruft sie in einer Massenanforderung ab. In den meisten Fällen viel besser.
Alex
7

Wenn ein Serializer instanziiert wird und many = True übergeben wird, wird eine ListSerializer-Instanz erstellt. Die Serializer-Klasse wird dann zu einem untergeordneten Element des übergeordneten ListSerializer

Diese Methode verwendet das Ziel des Felds als Wertargument und sollte die Darstellung zurückgeben, die zum Serialisieren des Ziels verwendet werden soll. Das Wertargument ist normalerweise eine Modellinstanz.

Unten sehen Sie das Beispiel des verschachtelten Serializers

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', 
        )

Im Hinblick auf:

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)
Vinay Kumar
quelle
5

Ich finde es einfacher und unkomplizierter, ein SerializerMethodFieldFeld für den Serializer zu verwenden, das Sie filtern möchten.

Sie würden also so etwas tun.

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

Dies erspart Ihnen das Erstellen vieler Überschreibungen von, serializers.ListSerializerwenn Sie unterschiedliche Filterkriterien für verschiedene Serialisierer benötigen.

Es hat auch den zusätzlichen Vorteil, genau zu sehen, was der Filter im Serializer tut, anstatt in eine Unterklassendefinition einzutauchen.

Der Nachteil ist natürlich, wenn Sie einen Serializer mit vielen verschachtelten Objekten haben, die alle auf irgendeine Weise gefiltert werden müssen. Dies kann dazu führen, dass der Serializer-Code stark zunimmt. Es liegt an Ihnen, wie Sie filtern möchten.

Hoffe das hilft!

Rob B.
quelle