Dans Django, comment filtrer un QuerySet avec des recherches de champs dynamiques?

161

Étant donné une classe:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=20)

Est-il possible, et si oui comment, d'avoir un QuerySet qui filtre en fonction d'arguments dynamiques? Par exemple:

 # Instead of:
 Person.objects.filter(name__startswith='B')
 # ... and:
 Person.objects.filter(name__endswith='B')

 # ... is there some way, given:
 filter_by = '{0}__{1}'.format('name', 'startswith')
 filter_value = 'B'

 # ... that you can run the equivalent of this?
 Person.objects.filter(filter_by=filter_value)
 # ... which will throw an exception, since `filter_by` is not
 # an attribute of `Person`.
Brian M. Hunt
la source

Réponses:

311

L'expansion des arguments de Python peut être utilisée pour résoudre ce problème:

kwargs = {
    '{0}__{1}'.format('name', 'startswith'): 'A',
    '{0}__{1}'.format('name', 'endswith'): 'Z'
}

Person.objects.filter(**kwargs)

C'est un idiome Python très courant et utile.

Daniel Naab
la source
6
Juste un petit avertissement rapide: assurez-vous que les chaînes dans les kwargs sont de type str et non unicode, sinon filter () grognera.
Steve Jalim le
1
@santiagobasulto Il fait également référence à un paramètre d'emballage / déballage, et ses variations.
Daniel Naab
7
gentil, gentil et gentil !
Oscar Mederos
5
@DanielNaab mais cela ne fonctionnera que sur les kwargs travaillant sur le filtrage de condition AND, toute alternative pour la condition OR.
Prateek099
3
@prateek, vous pouvez toujours utiliser des objets Q: stackoverflow.com/questions/13076822/…
deecodameeko
6

Un exemple simplifié:

Dans une application de sondage Django, je voulais une liste de sélection HTML montrant les utilisateurs enregistrés. Mais comme nous avons 5000 utilisateurs enregistrés, j'avais besoin d'un moyen de filtrer cette liste en fonction de critères de requête (comme uniquement les personnes ayant terminé un certain atelier). Pour que l'élément d'enquête soit réutilisable, il fallait que la personne qui crée la question d'enquête puisse associer ces critères à cette question (ne souhaitant pas coder en dur la requête dans l'application).

La solution que j'ai trouvée n'est pas 100% conviviale (nécessite l'aide d'un technicien pour créer la requête) mais elle résout le problème. Lors de la création de la question, l'éditeur peut saisir un dictionnaire dans un champ personnalisé, par exemple:

{'is_staff':True,'last_name__startswith':'A',}

Cette chaîne est stockée dans la base de données. Dans le code de vue, il revient en tant que self.question.custom_query. La valeur de cela est une chaîne qui ressemble à un dictionnaire. Nous le transformons en un vrai dictionnaire avec eval (), puis le remplissons dans le jeu de requêtes avec ** kwargs:

kwargs = eval(self.question.custom_query)
user_list = User.objects.filter(**kwargs).order_by("last_name")   
shacker
la source
Je me demande ce qu'il faudrait pour créer un ModelField / FormField / WidgetField personnalisé qui implémente le comportement pour permettre à l'utilisateur, du côté de l'interface graphique, de "construire" une requête, sans jamais voir le texte réel, mais en utilisant une interface pour faites-le. Sons comme un joli projet ...
T. pierre
1
T. Stone - J'imagine qu'il serait facile de créer un tel outil de manière simpliste si les modèles nécessitant des requêtes étaient simples, mais très difficiles à faire d'une manière approfondie qui exposait toutes les options possibles, en particulier si les modèles étaient complexe.
shacker
5
-1 appeler eval()l'importation d'utilisateurs est une mauvaise idée, même si vous faites entièrement confiance à vos utilisateurs. Un champ JSON serait une meilleure idée ici.
John Carter
5

Django.db.models.Q est exactement ce que vous voulez d'une manière Django.

Brent81
la source
7
Pourriez-vous (ou quelqu'un) fournir un exemple de la façon d'utiliser les objets Q en utilisant des noms de champs dynamiques?
jackdbernier
3
C'est la même chose que dans la réponse de Daniel Naab. La seule différence est que vous passez les arguments dans le constructeur d'objet Q. Q(**filters), si vous souhaitez créer dynamiquement des objets Q, vous pouvez les mettre dans une liste et utiliser .filter(*q_objects), ou utiliser les opérateurs au niveau du bit pour combiner les objets Q.
Will S
5
Cette réponse devrait vraiment inclure un exemple d'utilisation de Q pour résoudre le problème d'OP.
pdoherty926
-2

Un formulaire de recherche très complexe indique généralement qu'un modèle plus simple essaie de creuser son chemin.

Comment, exactement, prévoyez-vous obtenir les valeurs pour le nom de la colonne et l'opération? D'où tirez-vous les valeurs d' 'name'un 'startswith'?

 filter_by = '%s__%s' % ('name', 'startswith')
  1. Un formulaire de «recherche»? Vous allez - quoi? - choisir le nom dans une liste de noms? Choisissez l'opération dans une liste d'opérations? Bien que ouvert, la plupart des gens trouvent cela déroutant et difficile à utiliser.

    Combien de colonnes ont de tels filtres? 6? 12? 18?

    • Quelques? Une liste de sélection complexe n'a pas de sens. Quelques champs et quelques instructions if ont du sens.
    • Un grand nombre? Votre modèle ne sonne pas correctement. On dirait que le "champ" est en fait une clé vers une ligne dans une autre table, pas une colonne.
  2. Boutons de filtre spécifiques. Attendez ... C'est ainsi que fonctionne l'administrateur Django. Des filtres spécifiques sont transformés en boutons. Et la même analyse que ci-dessus s'applique. Quelques filtres ont du sens. Un grand nombre de filtres signifie généralement une sorte de première violation de forme normale.

Un grand nombre de champs similaires signifie souvent qu'il aurait dû y avoir plus de lignes et moins de champs.

S.Lott
la source
9
Avec égards, il est présomptueux de faire des recommandations sans rien savoir de la conception. Pour "implémenter simplement" cette application engendrerait des fonctions astronomiques (> 200 applications ^ 21 foos) pour répondre aux exigences. Vous lisez le but et l'intention dans l'exemple; tu ne devrais pas. :)
Brian M. Hunt
2
Je rencontre beaucoup de gens qui pensent que leur problème serait trivial à résoudre si seulement les choses étaient (a) plus génériques et (b) fonctionnaient comme elles l'imaginaient. De cette façon, il y a une frustration sans fin parce que les choses ne sont pas comme elles l'imaginaient. J'ai vu trop d'échecs provenir de la «réparation du cadre».
S.Lott
2
Les choses fonctionnent comme prévu et souhaité par la réponse de Daniel. Ma question portait sur la syntaxe, pas sur le design. Si j'avais eu le temps de rédiger le design, je l'aurais fait. Je suis sûr que votre contribution serait utile, mais ce n'est tout simplement pas une option pratique.
Brian M. Hunt
8
S.Lott, votre réponse ne répond même pas à distance à cette question. Si vous ne connaissez pas de réponse, veuillez laisser la question seule. Ne répondez pas avec des conseils de conception non sollicités lorsque vous n'avez absolument aucune connaissance de la conception!
slypete
2
@slypete: si une modification de la conception supprime le problème, le problème est résolu. Poursuivre le chemin sur la base d'une mauvaise conception est plus coûteux et complexe que nécessaire. Il est préférable de résoudre les problèmes à l'origine des problèmes que de résoudre d'autres problèmes résultant de mauvaises décisions de conception. Je suis désolé que vous n'aimiez pas l'analyse des causes profondes. Mais quand quelque chose est vraiment difficile, cela signifie généralement que vous essayez la mauvaise chose pour commencer.
S.Lott