Django-filter: Umgekehrter Fremdschlüssel kann nicht gefiltert werden

Erstellt am 22. Juni 2020  ·  10Kommentare  ·  Quelle: carltongibson/django-filter

Einführung

Hallo! Vielen Dank für diese Bibliothek! Es war so ein Vergnügen, es mit meiner aktuellen API zu testen!

Aktuelles Ziel

Ich habe zwei Modelle, ein School und ein SchoolCourse . SchoolCourse hat eine umgekehrte Viele-zu-Eins-Beziehung zu School .

Ich möchte in der Lage sein, das Objekt School basierend auf dem name des SchoolCourse zu filtern

Als erweitertes Ziel würde ich außerdem gerne nach SchoolLesson filtern können.

Datenmodell

Schulmodell

class SchoolModel(Model):
    name = CharField(max_length=24, unique=True)

    REQUIRED_FIELDS = ["name"]

    class Meta:
        ordering = ("id",)

    def get_courses(self):
        school_courses = SchoolCourseModel.objects.filter(school=self)
        return school_courses

Schulkurs

class SchoolCourseModel(Model):
    description = CharField(default="", max_length=200)
    name = CharField(max_length=50)
    school = ForeignKey(SchoolModel,
                        related_name="school_course",
                        on_delete=CASCADE)

    REQUIRED_FIELDS = ["school", "name"]

    class Meta:
        ordering = ("id",)
        unique_together = ("school", "name",)

    def get_lessons(self):
        school_lessons = SchoolLessonModel.objects.filter(course=self)
        return school_lessons

Welche - wenn Sie mit diesen Modellen in der Debug-Ansicht spielen, werden Sie dies sehen

all_school_models[50].school_course
(Pdb++) <django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x10a8177d0>
all_school_models[50].school_course.all()
(Pdb++) <QuerySet [<SchoolCourseModel: SchoolCourseModel object (151)>, <SchoolCourseModel: SchoolCourseModel object (152)>, <SchoolCourseModel: SchoolCourseModel object (153)>]>
all_school_models[50].school_course.all()[0]
(Pdb++) <SchoolCourseModel: SchoolCourseModel object (151)>
all_school_models[50].school_course.all()[0].name
(Pdb++) 'Nihongo Course One'

Filterkonfiguration

Der Filter, den ich gemacht habe, scheint ziemlich einfach zu sein.

class SchoolFilter(FilterSet):
    school_name = CharFilter(field_name="name",
                             lookup_expr="icontains")
    # ---
    course_name = CharFilter(field_name="school_course__name",
                             lookup_expr="icontains")
    course_description = CharFilter(field_name="school_course__description",
                                    lookup_expr="icontains")

    class Meta:
        model = SchoolModel
        fields = [
            "school_name",
            # ---
            "course_name",
            "course_description"
        ]

Problem

Dies funktioniert jedoch nicht. Wenn ich diese Anfrage stelle:
{{API_URL}}/api/v1/schools/?course_name=Nihongo

Das bekomme ich als Antwort zurück

{
    "schools": []
}

Fragen

Was mache ich hier falsch?

Interpretiere ich Django-Filter falsch? Interpretiere ich falsch, wie man die Datenmodelle verbindet?

Jede Anleitung ist willkommen!

Hilfreichster Kommentar

Meine Vermutung ist, dass dies mit dem angegebenen Offset zusammenhängt. Es gibt nur 1 Objekt im gefilterten Abfragesatz, aber die Anfrage weist den Paginator an, die ersten 49 Elemente zu überspringen. Wenn Sie überprüfen würden

print(filtered_school_models[49:])

Ich nehme an, Sie würden <QuerySet []> bekommen.

Schließen, wie es aussieht, ist ein Paginierungsproblem.

Alle 10 Kommentare

Hallo @loganknecht. Können Sie nur als Plausibilitätsprüfung überprüfen, ob Ihre URL korrekt ist? In Ihrer Beispiel-URL beginnt die Abfragezeichenfolge mit einem / statt mit einem ? .

Ah @rpkilby - Entschuldigung, das war ein Versehen meinerseits durch Kopieren und Einfügen von Postman . Die URL ist eigentlich {{API_URL}}/api/v1/schools/?offset=49&course_name=Nihongo , aber ich habe die offset entfernt, um weniger verwirrend zu sein, und es ist mir gelungen, das Gegenteil zu tun 😂

Ihr Beispiel wirft für mich keine offensichtlichen Probleme auf. Meine beste Vermutung ist, dass es ein Problem mit der API-Ansicht gibt. Haben Sie DjangoFilterBackend zu filter_backends $ Ihrer Einstellungen/Ansicht hinzugefügt? Und haben Sie filterset_class = SchoolFilter gesetzt (beachten Sie, dass dies früher filter_class war)?

Hallo @rpkilby

Hier ist der Ansichtscode, den ich habe

class SchoolViewSet(ViewSet):
    http_method_names = ["get", "post"]

    def list(self, request):
        all_school_models = SchoolModel.objects.all()

        filtered_school_models = SchoolFilter(request.GET, queryset=all_school_models)
        # import pdb
        # pdb.set_trace()

        paginator = HeaderLinkPagination()
        current_page_results = paginator.paginate_queryset(filtered_school_models.qs,
                                                           request)

        all_schools_serializer = SchoolSerializer(current_page_results,
                                                  many=True)
        response_data = {
            "schools": all_schools_serializer.data
        }

        response_to_return = paginator.get_paginated_response(response_data)
        return response_to_return

In pdb.set_trace() habe ich das obige Verhör gepostet.

Ich habe DjangoFilterBackend nicht festgelegt, da ich davon ausgegangen bin, dass es sich um eine globale Konfiguration handelt, und ich dies nur an einem einzelnen Endpunkt teste.

Außerdem habe ich dies aus dem gleichen Grund auch nicht für die Klasse festgelegt.

Es ist wichtig zu wissen, dass ich diesen Filter für den Namen von SchoolModel verwenden KANN, aber wenn ich für den umgekehrten Fremdschlüssel school_course filtere, funktioniert es nicht.

Gotcha - Sie erstellen das Filterset also direkt in der Ansicht. In diesem Fall müssten Sie die Filter-Backend-/Filterset-Klasse nicht festlegen.

Sie können überprüfen, ob die Daten korrekt aussehen, ob Fehler aufgetreten sind oder ob die SQL-Abfrage richtig aufgebaut ist. Versuchen:

filtered_school_models = SchoolFilter(request.GET, queryset=all_school_models)
print(filtered_school_models.data)
print(filtered_school_models.errors)
print(filtered_school_models.qs)
print(str(filtered_school_models.qs.query))

@rpkilby Hier ist, was ich sehe

<QueryDict: {'offset': ['49'], 'course_name': ['Nihongo']}>

<QuerySet [<SchoolModel: SchoolModel object (51)>]>
SELECT "piano_gym_api_schoolmodel"."id", "piano_gym_api_schoolmodel"."name", "piano_gym_api_schoolmodel"."school_board_id" FROM "piano_gym_api_schoolmodel" INNER JOIN "piano_gym_api_schoolcoursemodel" ON ("piano_gym_api_schoolmodel"."id" = "piano_gym_api_schoolcoursemodel"."school_id") WHERE UPPER("piano_gym_api_schoolcoursemodel"."name"::text) LIKE UPPER(%Nihongo%) ORDER BY "piano_gym_api_schoolmodel"."id" ASC

@rpkilby Das sieht so aus, als ob es ein Problem mit meiner Paginator-Implementierung sein könnte, die so aussieht

class HeaderLinkPagination(LimitOffsetPagination):
    default_limit = settings.DEFAULT_LIMIT
    max_limit = settings.DEFAULT_MAX_LIMIT
    min_limit = settings.DEFAULT_MIN_LIMIT
    min_offset = settings.DEFAULT_MIN_OFFSET
    max_offset = settings.DEFAULT_MAX_OFFSET

    def get_paginated_response(self, data):
        next_url = self.get_next_link()
        previous_url = self.get_previous_link()

        links = []
        header_data = (
            (previous_url, "prev"),
            (next_url, "next"),
        )
        for url, label in header_data:
            if url is not None:
                links.append("<{}>; rel=\"{}\"".format(url, label))

        headers = {"Link": ", ".join(links)} if links else {}

        return Response(data, headers=headers)

    def paginate_queryset(self, queryset, request, view=None):

        limit = request.query_params.get("limit")
        offset = request.query_params.get("offset")

        if limit is None:
            limit = settings.DEFAULT_LIMIT

        if offset is None:
            offset = settings.DEFAULT_OFFSET

        limit = int(limit)
        if limit > self.max_limit:
            error_message = ("Limit should be less than or equal to {0}"
                             ).format(self.max_limit)
            errors = {"limit": [error_message]}
            raise ValidationError(errors)
        elif limit < self.min_limit:
            error_message = ("Limit should be greater than or equal to {0}"
                             ).format(self.min_limit)
            errors = {"limit": [error_message]}
            raise ValidationError(errors)

        offset = int(offset)
        if offset > self.max_offset:
            error_message = ("Offset should be less than or equal to {0}"
                             ).format(self.max_offset)
            errors = {"offset": [error_message]}
            raise ValidationError(errors)
        elif offset < self.min_offset:
            error_message = ("Offset should be greater than or equal to {0}"
                             ).format(self.min_offset)
            errors = {"offset": [error_message]}
            raise ValidationError(errors)
        import pdb
        pdb.set_trace()

        return super(self.__class__, self).paginate_queryset(queryset, request, view)

Wenn Sie eine Lösung sehen, lassen Sie es mich bitte wissen. Bleiben Sie dran, während ich versuche, das abzuschütteln.

Meine Vermutung ist, dass dies mit dem angegebenen Offset zusammenhängt. Es gibt nur 1 Objekt im gefilterten Abfragesatz, aber die Anfrage weist den Paginator an, die ersten 49 Elemente zu überspringen. Wenn Sie überprüfen würden

print(filtered_school_models[49:])

Ich nehme an, Sie würden <QuerySet []> bekommen.

Schließen, wie es aussieht, ist ein Paginierungsproblem.

Hallo @rpkilby

Ich möchte Ihnen nur so sehr dafür danken, dass Sie mich durch dieses Gespräch geführt haben. Das war nicht einmal ein django-filter Problem. Ich war nur ein Dingus. Du Rubber Ducking me ist super super geschätzt.

Sie sind fantastisch, und noch einmal vielen Dank für diese fantastische Bibliothek!

Keine Bange. Freue mich zu helfen!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen