Comment composer dynamiquement un filtre de requête OR dans Django?

104

À partir d'un exemple, vous pouvez voir un filtre de requête OR multiple:

Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))

Par exemple, cela se traduit par:

[<Article: Hello>, <Article: Goodbye>, <Article: Hello and goodbye>]

Cependant, je souhaite créer ce filtre de requête à partir d'une liste. Comment faire ça?

par exemple [1, 2, 3] -> Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))

Jack Ha
la source
1
Vous semblez avoir posé cette question deux fois: stackoverflow.com/questions/852404
Dominic Rodger
Pour ce cas d'utilisation spécifique, vous utiliseriez probablement Article.objects.filter(pk__in=[1, 2, 3])dans django moderne, mais la question est toujours pertinente si vous voulez faire quelque chose d'un peu plus avancé en OR'ing Q objets ensemble.
beruic

Réponses:

162

Vous pouvez enchaîner vos requêtes comme suit:

values = [1,2,3]

# Turn list of values into list of Q objects
queries = [Q(pk=value) for value in values]

# Take one Q object from the list
query = queries.pop()

# Or the Q object with the ones remaining in the list
for item in queries:
    query |= item

# Query the model
Article.objects.filter(query)
Dave Webb
la source
3
Merci! C'était ce que je cherchais :) Je ne savais pas que tu pouvais faire | =
Jack Ha
23
Vous pouvez également initialiser la requête en utilisant: query = Q ()
chachan
5
vous pouvez créer des champs dynamiques en utilisant ** {'fieldname': value}: queries = [Q (** {'fieldname': value}) pour la valeur en valeurs]
rechie le
1
Comment pouvez-vous composer des requêtes brutes avec Django si vous souhaitez ajouter des conditions optionnelles comme ci-dessus?
utilisateur
Cela n'a pas fonctionné pour moi, je ne sais pas pourquoi. les requêtes ne renvoient aucun résultat pour moi
Mehran Nouri
83

Pour créer des requêtes plus complexes, il existe également la possibilité d'utiliser les constantes Q.OR et Q.AND de l'objet Q () intégré avec la méthode add () comme ceci:

list = [1, 2, 3]
# it gets a bit more complicated if we want to dynamically build
# OR queries with dynamic/unknown db field keys, let's say with a list
# of db fields that can change like the following
# list_with_strings = ['dbfield1', 'dbfield2', 'dbfield3']

# init our q objects variable to use .add() on it
q_objects = Q(id__in=[])

# loop trough the list and create an OR condition for each item
for item in list:
    q_objects.add(Q(pk=item), Q.OR)
    # for our list_with_strings we can do the following
    # q_objects.add(Q(**{item: 1}), Q.OR)

queryset = Article.objects.filter(q_objects)

# sometimes the following is helpful for debugging (returns the SQL statement)
# print queryset.query
à l'extérieur
la source
12
Pour les nouveaux venus sur ce fil, comme moi, je pense que cette réponse devrait être considérée comme la meilleure réponse. C'est plus djangoesque que la réponse acceptée. Je vous remercie!
theresaanna
5
Je dirais qu'il est plus pythonique d'utiliser les opérateurs OR et AND intégrés (| et &). q_objects |= Q(pk=item)
Bobort
Parfait! Je vous remercie!
RL Shyam
1
Il convient de noter que si listvous êtes vide, vous retournerez l'équivalent de Article.objects.all(). Facile à atténuer en revenant Article.objects.none()pour ce test cependant.
Wil
2
@Wil vous pouvez également initialiser q_objectsavec Q(id__in=[]). Il échouera toujours à moins que OU avec quelque chose et l'optimiseur de requête le gérera bien.
Jonathan Richards le
44

Une façon plus courte d'écrire la réponse de Dave Webb en utilisant la fonction de réduction de python :

# For Python 3 only
from functools import reduce

values = [1,2,3]

# Turn list of values into one big Q objects  
query = reduce(lambda q,value: q|Q(pk=value), values, Q())  

# Query the model  
Article.objects.filter(query)  
Tom Viner
la source
On dirait que la réduction "intégrée" a été supprimée et remplacée par functools.reduce. source
lsowen
Merci @lsowen, corrigé.
Tom Viner
Et il est possible d'utiliser à la operator.or_place du lambda.
eigenein le
38
from functools import reduce
from operator import or_
from django.db.models import Q

values = [1, 2, 3]
query = reduce(or_, (Q(pk=x) for x in values))
Ignacio Vazquez-Abrams
la source
Ok, mais d'où vient le operator?
mpiskore
1
@mpiskore: même endroit que tous les autres modules Python: vous l'importez.
Ignacio Vazquez-Abrams
1
drôle. c'était vraiment ma question: dans quel module / bibliothèque puis-je le trouver? google n'a pas beaucoup aidé.
mpiskore
oh, je pensais que c'était une sorte d'opérateur Django ORM. Comme c'est idiot de ma part, merci!
mpiskore
20

Il est peut-être préférable d'utiliser l'instruction SQL IN.

Article.objects.filter(id__in=[1, 2, 3])

Voir la référence de l'API queryset .

Si vous avez vraiment besoin de faire des requêtes avec une logique dynamique, vous pouvez faire quelque chose comme ça (moche + non testé):

query = Q(field=1)
for cond in (2, 3):
    query = query | Q(field=cond)
Article.objects.filter(query)
alex vasi
la source
1
Vous pouvez également utiliserquery |= Q(field=cond)
Bobort
8

Voir la documentation :

>>> Blog.objects.in_bulk([1])
{1: <Blog: Beatles Blog>}
>>> Blog.objects.in_bulk([1, 2])
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>}
>>> Blog.objects.in_bulk([])
{}

Notez que cette méthode ne fonctionne que pour les recherches de clé primaire, mais cela semble être ce que vous essayez de faire.

Donc ce que vous voulez c'est:

Article.objects.in_bulk([1, 2, 3])
Dominic Rodger
la source
6

Si nous voulons définir par programme le champ de base de données que nous voulons interroger:

import operator
questions = [('question__contains', 'test'), ('question__gt', 23 )]
q_list = [Q(x) for x in questions]
Poll.objects.filter(reduce(operator.or_, q_list))
zzart
la source
6

Solution qui utilise reduceet or_opérateurs pour filtrer par multi-champs.

from functools import reduce
from operator import or_
from django.db.models import Q

filters = {'field1': [1, 2], 'field2': ['value', 'other_value']}

qs = Article.objects.filter(
   reduce(or_, (Q(**{f'{k}__in': v}) for k, v in filters.items()))
)

ps fest un nouveau format littéral de chaînes. Il a été introduit dans python 3.6

Ivan Semochkin
la source
4

Vous pouvez utiliser l'opérateur | = pour mettre à jour par programme une requête à l'aide d'objets Q.

Jeff Ober
la source
2
Est-ce documenté quelque part? Je cherche depuis 15 minutes et c'est la seule chose que je peux trouver.
wobbily_col
Comme tant de choses dans notre industrie, il est documenté sur StackOverflow!
Chris
2

Celui-ci est pour la liste pk dynamique:

pk_list = qs.values_list('pk', flat=True)  # i.e [] or [1, 2, 3]

if len(pk_list) == 0:
    Article.objects.none()

else:
    q = None
    for pk in pk_list:
        if q is None:
            q = Q(pk=pk)
        else:
            q = q | Q(pk=pk)

    Article.objects.filter(q)
Velodée
la source
Vous pouvez utiliser à la q = Q()place de q = None, puis supprimer la if q is Noneclause - légèrement moins efficace mais peut supprimer trois lignes de code. (Le Q vide est ensuite fusionné lorsque la requête est exécutée.)
Chris
1

Une autre option que je ne le savais pas jusqu'à récemment - QuerySetremplace également les &, |, ~, etc, opérateurs. Les autres réponses que les objets OR Q sont une meilleure solution à cette question, mais par souci d'intérêt / argument, vous pouvez faire:

id_list = [1, 2, 3]
q = Article.objects.filter(pk=id_list[0])
for i in id_list[1:]:
    q |= Article.objects.filter(pk=i)

str(q.query)renverra une requête avec tous les filtres de la WHEREclause.

Chris
la source
1

Pour la boucle:

values = [1, 2, 3]
q = Q(pk__in=[]) # generic "always false" value
for val in values:
    q |= Q(pk=val)
Article.objects.filter(q)

Réduire:

from functools import reduce
from operator import or_

values = [1, 2, 3]
q_objects = [Q(pk=val) for val in values]
q = reduce(or_, q_objects, Q(pk__in=[]))
Article.objects.filter(q)

Les deux sont équivalents à Article.objects.filter(pk__in=values)

Il est important de considérer ce que vous voulez quand il valuesest vide. De nombreuses réponses avec Q()comme valeur de départ renverront tout . Q(pk__in=[])est une meilleure valeur de départ. C'est un objet Q toujours en échec qui est bien géré par l'optimiseur (même pour les équations complexes).

Article.objects.filter(Q(pk__in=[]))  # doesn't hit DB
Article.objects.filter(Q(pk=None))    # hits DB and returns nothing
Article.objects.none()                # doesn't hit DB
Article.objects.filter(Q())           # returns everything

Si vous voulez tout renvoyer quand il valuesest vide, vous devez ET avec ~Q(pk__in=[])pour vous assurer que le comportement:

values = []
q = Q()
for val in values:
    q |= Q(pk=val)
Article.objects.filter(q)                     # everything
Article.objects.filter(q | author="Tolkien")  # only Tolkien

q &= ~Q(pk__in=[])
Article.objects.filter(q)                     # everything
Article.objects.filter(q | author="Tolkien")  # everything

Il est important de se rappeler que ce Q()n'est rien , pas un objet Q qui se succède toujours. Toute opération l'impliquant la laissera tomber complètement.

Jonathan Richards
la source
0

facile ..
de django.db.models importez Q importez vous modélisez args = (Q (visibilité = 1) | (Q (visibilité = 0) & Q (utilisateur = self.user))) #Tuple parameters = {} #dic order = limite 'create_at' = 10

Models.objects.filter(*args,**parameters).order_by(order)[:limit]
Alfonsoolavarria
la source