Django : utiliser un QueryDict

a marqué ce sujet comme résolu.

Bonjour,

Mon application Django repose sur une fonction de recherche. J’ai prévu pour celle-là (la recherche) d’utiliser des paramètres URL sur une méthode get. Seulement, je voulais isoler la fonction de recherche pour la rendre déjà testable, et réutilisable (notamment en API).

J’ai donc défini une fonction avec deux paramètres obligatoires, le point de recherche de base et la distance de recherche, et un dictionnaire de filtres supplémentaires optionnels. Voilà ce que cela donne :

def ecoliste_research(search_location: Point, distance: int, filters: dict = None) -> list[Address]:
    addresses = Address.objects.filter(
        geolocation__distance_lte=(search_location, D(km=distance))
    )
    if filters:
        if "materials" in filters.keys():
            addresses = addresses.filter(
                enterprise__products__type_id__in=filters["materials"]
            )
        # D’autres sur le même motif, que j’enlève pour raccourcir

    return addresses

Et voilà le test sur lequel je suis, qui actuellement fonctionne :

    def test_search_materialtype(self) -> None:
        filters = {"materials": [1,]}
        addresses = ecoliste_research(self.search_location, 1000, filters=filters)
        self.assertIn(self.enterprise1_address, addresses)
        self.assertNotIn(self.enterprise_multi_address1, addresses)

Seulement, Django ne retourne pas un dictionnaire, mais un QueryDict. Et comme on le voit sur la doc, les QueryDict ne retourne pas une liste de valeurs lorsque appelé sur une clef, mais uniquement la dernière. Il y a bien une fonction qui permet de retourner une liste, QueryDict.getlist(key, default=None). Par contre, le cast en dict utilise le même système de ne rendre que la dernière valeur.

J’ai donc deux solutions :

  • Modifier ma fonction pour qu’elle prenne un QueryDict en paramètre. Ce QueryDict comprendrait également les deux paramètres de base pour la recherche, le point et la distance de recherche. J’ai l’impression que je perds en souplesse sur ma fonction avec ça ?
  • Transformer le QueryDict en dict, mais Django n’a pas implémenté ça, et j’ai l’impression que ce serait un peu sale à faire, non ?

Merci

+0 -0

Peut-être avec une fonction intermédiaire qui découplerait tes deux fonctions ?

def searchFromWeb(query : QueryDict) -> list[Address]:
  queryDict = {key: listValues for key, listValues in query.lists()}

  return ecolist_search(queryDict)
+0 -0

C’est la seule solution, effectivement, d’itérer sur le QueryDict. Mais je trouve ça pas très propre, non ? Est-ce que tu aurais un avis sur si c’est une une bonne idée ?

En fait, je me dis que si c’était une bonne idée, Django l’aurait intégré par défaut, non ?

+0 -0

Je suppose qu’il existe une RFC quelque-part qui dit que pour une requête de type a=1&a=2&a=3, on ne devrait prendre en compte que la dernière valeur de a, et que pour la plupart des cas c’est le comportement voulu?

Comment se comporte le QueryDict pour a[]=1&a[]=2&a[]=3? C’est la syntaxe utilisée en PHP pour avoir un tableau d’arguments GET/POST.

+0 -0

Ben je t’avoue que je sais pas trop.

Je me dis que souvent c’est une erreur d’avoir plusieurs fois la même clé et que ne garder que la dernière prend sens. D’ailleurs as-tu vraiment besoin d’avoir plusieurs fois la même clé ?

Je vois bien {"materials": [1,]}, mais je pose la question, car elle doit quand même être posée, on doit savoir pourquoi on fait ça. Pas juste pour passer un test.

Mais ton cas semble légitime, même si ce n’est pas le cas le plus courant. Donc ça ne me semble pas immonde de faire la conversion. Ta fonction ne peut pas attendre un QueryDict ça serait le plus sale à mon sens si je comprends bien ce qu’elle fait, mais comment gérer le cas qu’une clé ait plusieurs valeurs ? Car dans l’exemple que j’ai donné, même une valeur unique 'b=12' est indexée sous forme de liste, ça ne me semble pas déconnant comme comportement.

+1 -0

Comment se comporte le QueryDict pour a[]=1&a[]=2&a[]=3? C’est la syntaxe utilisée en PHP pour avoir un tableau d’arguments GET/POST.

amael

Honnêtement aucune idée, mais à la lecture de la doc j’ai peur que ça fasse juste {"a[]": [1, 2, 3]} et que ça ne renvoie quand même que la dernière valeur.

Ben je t’avoue que je sais pas trop.

Je me dis que souvent c’est une erreur d’avoir plusieurs fois la même clé et que ne garder que la dernière prend sens. D’ailleurs as-tu vraiment besoin d’avoir plusieurs fois la même clé ?

Je ne sais pas, c’est Django qui anticipe ce problème, mais je sais que j’ai besoin du <select multiple>, tout mon formulaire repose dessus. Après, ce n’est pas moi qui choisit comment les formulaires html gèrent ça.

Je vois bien {"materials": [1,]}, mais je pose la question, car elle doit quand même être posée, on doit savoir pourquoi on fait ça. Pas juste pour passer un test.

Mais ton cas semble légitime, même si ce n’est pas le cas le plus courant. Donc ça ne me semble pas immonde de faire la conversion. Ta fonction ne peut pas attendre un QueryDict ça serait le plus sale à mon sens si je comprends bien ce qu’elle fait, mais comment gérer le cas qu’une clé ait plusieurs valeurs ? Car dans l’exemple que j’ai donné, même une valeur unique 'b=12' est indexée sous forme de liste, ça ne me semble pas déconnant comme comportement.

ache

Je te confirme, j’ai besoin de la liste. Je vais avoir plusieurs listes d’options à cocher dans le formulaire.

J’ai checké un peu la doc du MDN à propos de l’élément, mais je ne vois pas de mention des clefs multiples.

Ceci dit, je viens de voir que les éléments de formulaire envoient la clé même si vide, du coup il faudra que je modifie la fonction. C’est un autre sujet.

+0 -0

J’ai utilisé ta proposition @ache parce que je sens que c’est de toute façon un problème insoluble. Mais là aussi j’ai le choix :

def ecoliste_research_querydict(
    search_location: Point, search_distance: int, querydict: QueryDict
) -> list[Address]:
    # filters = {key: value for key, value in querydict.lists()}
    filters = {}
    for key, value in querydict.lists():
        if not value == "":
            filters[key] = value
    return ecoliste_research(search_location, search_distance, filters=filters)

J’ai dû utiliser la boucle for pour m’assure de n’avoir pas de valeur du tout plutôt qu’une clé associée à une valeur d’une liste comprenant une string vide [""]. Sinon, l’option serait de changer mes conditions dans ma fonction de recherche, aussi :

    addresses = Address.objects.filter(
        geolocation__distance_lte=(search_location, D(km=distance))
    )
    if filters:
        if "materials" in filters.keys():
            if not filters["materials"] == [""]:
                addresses = addresses.filter(
                    enterprise__products__type_id__in=filters["materials"]
                )

Et ce pour chaque condition. Je ne sais pas laquelle est la plus propre. Ou sinon, je peux aussi nettoyer le dico en entrée de fonction.

Edit : Je n’arrive pas à savoir si je m’embête pour rien, parce que je ne trouve pas de doc détaillée sur comment les forms se transcrivent dans les paramètres GET.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte