Django-filter: Impossible de filtrer la clé étrangère inversée

Créé le 22 juin 2020  ·  10Commentaires  ·  Source: carltongibson/django-filter

introduction

Salut! Merci beaucoup pour cette bibliothèque ! Ce fut un tel plaisir de le tester avec mon API actuelle !

Objectif actuel

J'ai deux modèles, un School et un SchoolCourse . SchoolCourse a une relation inverse plusieurs à un avec School .

Je veux pouvoir filtrer l'objet School en fonction du name du SchoolCourse

De plus, en tant qu'objectif étendu, j'aimerais pouvoir filtrer par SchoolLesson éventuellement aussi.

Modèle de données

Modèle d'école

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

Cours scolaire

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

Qui - si vous jouez avec ces modèles dans la vue de débogage, vous verrez ceci

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'

Configuration du filtre

Le filtre que j'ai fait semble assez simple.

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"
        ]

Problème

Cependant, cela ne fonctionne pas. Si je fais cette demande :
{{API_URL}}/api/v1/schools/?course_name=Nihongo

Je reçois ceci comme réponse

{
    "schools": []
}

Des questions

Qu'est-ce que je fais de mal ici ?

Est-ce que j'interprète mal les filtres Django ? Est-ce que j'interprète mal comment connecter les modèles de données ?

Toute orientation est appréciée!

Commentaire le plus utile

Je suppose que cela est lié au décalage donné. Il n'y a qu'un seul objet dans le jeu de requêtes filtré, mais la demande indique au paginateur d'ignorer les 49 premiers éléments. Si vous deviez vérifier

print(filtered_school_models[49:])

Je suppose que vous obtiendriez <QuerySet []> .

Fermeture, car il semble que ce soit un problème de pagination.

Tous les 10 commentaires

Salut @loganknecht. Pouvez-vous vérifier que votre URL est correcte ? Dans votre exemple d'URL, la chaîne de requête commence par un / au lieu d'un ? .

Ah @rpkilby - excuses, c'était un oubli de ma part en copiant et collant de Postman . L'url est en fait {{API_URL}}/api/v1/schools/?offset=49&course_name=Nihongo mais j'enlevais le offset pour être moins déroutant et j'ai réussi à faire le contraire 😂

Votre exemple ne me pose aucun problème évident. Ma meilleure hypothèse est qu'il y a un problème avec la vue API. Avez-vous ajouté le DjangoFilterBackend aux $# filter_backends 1$#$ de vos paramètres/vue ? Et avez-vous défini filterset_class = SchoolFilter (notez que c'était auparavant filter_class ) ?

Salut @rpkilby

Voici le code de vue que j'ai

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

Le pdb.set_trace() est l'endroit où j'ai posté l'interrogation ci-dessus.

Je n'ai pas défini le DjangoFilterBackend car je supposais qu'il s'agissait d'une configuration globale et je ne teste cela que sur un seul point de terminaison.

De plus, je n'ai pas défini cela pour la classe non plus pour la même raison.

Il est important de savoir que je PEUX utiliser ce filtre sur le nom du SchoolModel mais lorsque je filtre sur la clé étrangère inversée school_course cela ne fonctionne pas.

Gotcha - vous créez donc directement le jeu de filtres dans la vue. Dans ce cas, oui, vous n'auriez pas besoin de définir la classe filter backend/filterset.

Vous pouvez vérifier si les données semblent correctes, s'il y a eu des erreurs ou si la requête SQL est correctement formée. Essayer:

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 Voici ce que je vois

<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 Cela ressemble à un problème avec mon implémentation de paginateur qui ressemble à ceci

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)

Si vous voyez une solution, merci de m'en faire part. Restez à l'écoute pendant que j'essaie de résoudre ce problème.

Je suppose que cela est lié au décalage donné. Il n'y a qu'un seul objet dans le jeu de requêtes filtré, mais la demande indique au paginateur d'ignorer les 49 premiers éléments. Si vous deviez vérifier

print(filtered_school_models[49:])

Je suppose que vous obtiendriez <QuerySet []> .

Fermeture, car il semble que ce soit un problème de pagination.

Salut @rpkilby

Je veux juste dire merci beaucoup de m'avoir parlé à travers ça. Ce n'était même pas un problème django-filter . J'étais juste un dingus. Votre esquive en caoutchouc est super super appréciée.

Vous êtes incroyable, et encore une fois, merci pour cette incroyable bibliothèque !

Pas de soucis. Heureux d'aider!

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

lalzada picture lalzada  ·  3Commentaires

sassanh picture sassanh  ·  4Commentaires

GuillaumeCisco picture GuillaumeCisco  ·  3Commentaires

jwineinger picture jwineinger  ·  3Commentaires

gsvr picture gsvr  ·  3Commentaires