2008-11-22 8 views
121

classe donné une:Dans Django, comment filtrer un QuerySet avec des recherches de champs dynamiques?

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 que des filtres basés sur des 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`. 

Répondre

222

expansion de l'argument de Python peut être utilisé 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 langage Python très commun et utile.

+5

Juste un petit tour de tête: assurez-vous que les chaînes dans les kwargs sont de type str non unicode, sinon filter() grognera. –

+0

Le sera-t-il? Ne force-t-il pas à ce stade? – jMyles

+0

Merci Daniel! Ça m'a aidé. Comment s'appelle-t-il en Python? Argument d'expansion? Je ne l'ai pas trouvé dans les docs. – santiagobasulto

-1

Une forme de recherche très complexe indique généralement qu'un modèle plus simple essaie de s'en sortir.

Comment, exactement, pensez-vous obtenir les valeurs pour le nom et le fonctionnement de la colonne? Où obtenez-vous les valeurs 'name' et 'startswith'?

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

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

    • Quelques-uns? Une liste de sélection complexe n'a pas de sens. Quelques champs et quelques if-statements ont du sens.
    • Un grand nombre? Votre modèle ne sonne pas bien. Il semble que le "champ" est en fait une clé d'une ligne dans une autre table, pas une colonne.
  2. Boutons de filtre spécifiques. Attendez ... C'est ainsi que fonctionne l'administrateur Django. Les 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 violation de la première forme normale.

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

+0

Merci pour la réponse. Le modèle de données donné à titre d'exemple n'est que cela, pour une illustration facile. Je préfère ne pas imaginer que quelqu'un met quelque chose d'aussi odieux dans un vrai projet. ;) Je veux découpler les relations génériques et factoriser une logique réutilisable. –

+0

Django déjà * est * générique. Écrire des choses plus génériques sur Django, c'est un peu trop de généricité. Ma recommandation est de simplement implémenter votre application, en évitant de trop généraliser un framework déjà générique. –

+8

En toute déférence, il est présomptueux de faire des recommandations sans rien savoir de la conception. Pour "implémenter simplement" cette application, il faudrait 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. :) –

6

Un exemple simplifié:

Dans une application de l'enquête Django, je voulais une liste de sélection HTML montrant les utilisateurs enregistrés. Mais parce que nous avons 5000 utilisateurs enregistrés, j'avais besoin d'un moyen de filtrer cette liste en fonction des critères de la requête (tels que les personnes qui ont terminé un certain atelier). Pour que l'élément d'enquête soit réutilisable, il était nécessaire que la personne qui a créé la question d'enquête puisse joindre ces critères à cette question (ne pas vouloir coder la requête dans l'application).

La solution que j'ai trouvée n'est pas 100% conviviale (nécessite l'aide d'une personne technique pour créer la requête) mais elle résout le problème. Lors de la création de la question, l'éditeur peut entrer dans 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 comme un dictionnaire.Nous allons revenir dans un réel dictionnaire avec eval(), puis farcir dans le queryset avec ** kwargs:

kwargs = eval(self.question.custom_query) 
user_list = User.objects.filter(**kwargs).order_by("last_name") 
+0

Je me demande ce qu'il faudrait pour créer un ModelField/FormField/WidgetField personnalisé qui a implémenté le comportement pour permettre à l'utilisateur, du côté de l'interface graphique, de "construire" une requête, ne voyant jamais le texte, mais utilisant une interface faire cela. On dirait un joli projet ... –

+1

T. Stone - J'imagine que ce serait facile de construire un tel outil de façon simpliste si les modèles qui ont besoin d'être interrogés étaient simples, mais très difficiles à faire d'une manière approfondie qui expose toutes les options possibles, surtout si les modèles étaient complexes. – shacker

+2

-1 L'appel 'eval()' lors de l'importation de l'utilisateur est une mauvaise idée, même si vous faites complètement confiance à vos utilisateurs. Un champ JSON serait une meilleure idée ici. –

6

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

+5

Pourriez-vous (ou quelqu'un) fournir un exemple d'utilisation des objets Q dans l'utilisation de noms de champs dynamiques? – jackdbernier

+3

C'est la même chose que dans [Daniel Naab's answer] (http://stackoverflow.com/a/310785/4021086) La seule différence étant que vous passez les arguments dans le constructeur de l'objet Q. 'Q (** filters)', si vous voulez construire dynamiquement des objets Q, vous pouvez les placer dans une liste et utiliser '.filter (* q_objects)', ou utiliser les opérateurs au niveau du bit pour combiner les objets Q. –