Django फिल्टर क्वेरी __ में * हर * सूची में आइटम के लिए


102

मान लीजिए कि मेरे पास निम्नलिखित मॉडल हैं

class Photo(models.Model):
    tags = models.ManyToManyField(Tag)

class Tag(models.Model):
    name = models.CharField(max_length=50)

एक दृश्य में मेरे पास एक सूची है जिसमें सक्रिय फिल्टर हैं जिन्हें श्रेणियां कहा जाता है । मैं उन फोटो ऑब्जेक्ट्स को फ़िल्टर करना चाहता हूं जिनमें सभी टैग श्रेणियों में मौजूद हैं ।

मैंने कोशिश की:

Photo.objects.filter(tags__name__in=categories)

लेकिन यह श्रेणियों में किसी भी आइटम से मेल खाता है , सभी आइटम नहीं ।

इसलिए यदि श्रेणियां ['हॉलिडे', 'समर' होंगी] मैं चाहता हूं कि फोटो हॉलिडे और समर टैग दोनों के साथ हो।

क्या इसे हासिल किया जा सकता है?


7
हो सकता है: qs = Photo.objects.all (); श्रेणियों में श्रेणी के लिए: qs = qs.filter (tags__name = श्रेणी)
jpic

2
jpic सही है, Photo.objects.filter(tags__name='holiday').filter(tags__name='summer')जाने का रास्ता है। (यह jpic के उदाहरण के समान है)। प्रत्येक को क्वेरी में filterऔर अधिक जोड़ना चाहिए JOIN, ताकि आप एनोटेशन दृष्टिकोण ले सकें यदि वे बहुत अधिक हैं।
लूसिक डेस

1
यहाँ डॉक्स में संदर्भ है: docs.djangoproject.com/en/dev/topics/db/queries/…
sgallen

आपको उम्मीद है कि इसके लिए Django
Vincent

जवाबों:


124

सारांश:

एक विकल्प है, जैसा कि टिप्पणियों में jpic और sgallen द्वारा सुझाया गया है, .filter()प्रत्येक श्रेणी के लिए जोड़ना है । प्रत्येक अतिरिक्त filterअधिक जोड़ जोड़ता है, जो श्रेणियों के छोटे सेट के लिए कोई समस्या नहीं होनी चाहिए।

नहीं है एकत्रीकरण दृष्टिकोण । यह क्वेरी श्रेणियों के एक बड़े सेट के लिए छोटी और शायद तेज़ होगी।

आपके पास कस्टम क्वेरी का उपयोग करने का विकल्प भी है ।


कुछ उदाहरण

परीक्षण व्यवस्था:

class Photo(models.Model):
    tags = models.ManyToManyField('Tag')

class Tag(models.Model):
    name = models.CharField(max_length=50)

    def __unicode__(self):
        return self.name

In [2]: t1 = Tag.objects.create(name='holiday')
In [3]: t2 = Tag.objects.create(name='summer')
In [4]: p = Photo.objects.create()
In [5]: p.tags.add(t1)
In [6]: p.tags.add(t2)
In [7]: p.tags.all()
Out[7]: [<Tag: holiday>, <Tag: summer>]

जंजीर फ़िल्टर दृष्टिकोण का उपयोग करना :

In [8]: Photo.objects.filter(tags=t1).filter(tags=t2)
Out[8]: [<Photo: Photo object>]

परिणामी क्वेरी:

In [17]: print Photo.objects.filter(tags=t1).filter(tags=t2).query
SELECT "test_photo"."id"
FROM "test_photo"
INNER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
INNER JOIN "test_photo_tags" T4 ON ("test_photo"."id" = T4."photo_id")
WHERE ("test_photo_tags"."tag_id" = 3  AND T4."tag_id" = 4 )

ध्यान दें कि प्रत्येक क्वेरी में filterअधिक जोड़ता है JOINS

एनोटेशन दृष्टिकोण का उपयोग करना :

In [29]: from django.db.models import Count
In [30]: Photo.objects.filter(tags__in=[t1, t2]).annotate(num_tags=Count('tags')).filter(num_tags=2)
Out[30]: [<Photo: Photo object>]

परिणामी क्वेरी:

In [32]: print Photo.objects.filter(tags__in=[t1, t2]).annotate(num_tags=Count('tags')).filter(num_tags=2).query
SELECT "test_photo"."id", COUNT("test_photo_tags"."tag_id") AS "num_tags"
FROM "test_photo"
LEFT OUTER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
WHERE ("test_photo_tags"."tag_id" IN (3, 4))
GROUP BY "test_photo"."id", "test_photo"."id"
HAVING COUNT("test_photo_tags"."tag_id") = 2

ANDएड Qऑब्जेक्ट्स काम नहीं करेंगे:

In [9]: from django.db.models import Q
In [10]: Photo.objects.filter(Q(tags__name='holiday') & Q(tags__name='summer'))
Out[10]: []
In [11]: from operator import and_
In [12]: Photo.objects.filter(reduce(and_, [Q(tags__name='holiday'), Q(tags__name='summer')]))
Out[12]: []

परिणामी क्वेरी:

In [25]: print Photo.objects.filter(Q(tags__name='holiday') & Q(tags__name='summer')).query
SELECT "test_photo"."id"
FROM "test_photo"
INNER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
INNER JOIN "test_tag" ON ("test_photo_tags"."tag_id" = "test_tag"."id")
WHERE ("test_tag"."name" = holiday  AND "test_tag"."name" = summer )

6
क्या कस्टम लुकअप के साथ कोई समाधान है? docs.djangoproject.com/en/1.10/howto/custom-lookups यह "__in" को "__all" स्विच करने के लिए ठंडा होगा और इसे सही sql क्वेरी बनाएगा।
t1m0

1
यह एनोटेशन समाधान गलत लगता है। क्या होगा अगर तीन टैग संभव हैं (एक अतिरिक्त कॉल करने की सुविधा देता है t3, और एक फोटो में टैग हैं t2और t3फिर यह फोटो अभी भी दिए गए क्वेरी को देगा।
beruic

@beruic मुझे लगता है कि विचार यह है कि आप num_tags = 2 को num_tags = len (टैग) से बदल देंगे; मुझे उम्मीद है कि हार्ड-कोडेड 2 सिर्फ उदाहरण के लिए था।
tbm

3
@tbm यह अभी भी काम नहीं करेगा। Photo.objects.filter(tags__in=tags)उन फ़ोटो से मेल खाता है, जिनमें से कोई भी टैग हो, न केवल वे जो सभी के पास हैं। उनमें से कुछ जिनके पास केवल वांछित टैग में से एक है, आपके द्वारा खोजे जा रहे टैग की मात्रा बिल्कुल हो सकती है, और उनमें से कुछ जिनमें सभी वांछित टैग हैं, अतिरिक्त टैग भी हो सकते हैं।
बेरिके

1
@beruic एनोटेशन केवल क्वेरी द्वारा लौटाए गए टैग को गिनता है, इसलिए यदि (क्वेरी द्वारा दिए गए संख्यात्मक टैग) == (संख्या के लिए खोज किए गए टैग) तो पंक्ति शामिल है; "अतिरिक्त" टैग की खोज नहीं की जाती है, इसलिए इसे गिना नहीं जाएगा। मैंने इसे अपने ऐप के भीतर सत्यापित किया है।
tbm

8

एक और तरीका जो काम करता है, हालाँकि केवल PostgreSQL का उपयोग कर रहा है django.contrib.postgres.fields.ArrayField:

डॉक्स से कॉपी किया गया उदाहरण :

>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])

>>> Post.objects.filter(tags__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__contains=['django'])
<QuerySet [<Post: First post>, <Post: Third post>]>

>>> Post.objects.filter(tags__contains=['django', 'thoughts'])
<QuerySet [<Post: First post>]>

ArrayFieldओवरलैप और इंडेक्स ट्रांसफ़ॉर्म जैसी कुछ और शक्तिशाली विशेषताएं हैं ।


3

यह भी Django ORM और कुछ पायथन जादू का उपयोग करके गतिशील क्वेरी पीढ़ी द्वारा किया जा सकता है :)

from operator import and_
from django.db.models import Q

categories = ['holiday', 'summer']
res = Photo.filter(reduce(and_, [Q(tags__name=c) for c in categories]))

यह विचार है कि प्रत्येक श्रेणी के लिए उपयुक्त क्यू ऑब्जेक्ट्स उत्पन्न करें और फिर उन्हें एक क्वेरी में ऑपरेटर और ऑपरेटर का उपयोग करके संयोजित करें। उदाहरण के लिए यह आपके बराबर होगा

res = Photo.filter(Q(tags__name='holiday') & Q(tags__name='summer'))

3
यह काम नहीं करेगा। प्रश्न में मॉडल के लिए आपके क्वेरी उदाहरण कुछ भी नहीं लौटाएंगे।
१avor:

सुधार के लिए धन्यवाद। मैंने सोचा कि चाइनिंग एक फिल्टर में क्यू ऑब्जेक्ट्स के लिए filterउपयोग करने के समान होगा and... मेरी गलती।
demalexx

कोई चिंता नहीं, मेरा पहला विचार भी क्यू वस्तुओं।
लूसिक डेस

1
यदि आप तुलना करने के लिए बड़े तालिकाओं और बड़े डेटा के साथ काम करते हैं तो हम इसे धीमा कर देंगे। (जैसे 1 मिलियन प्रत्येक)
gies0r

यदि आप किसी नकारात्मक ऑपरेटर से स्विच करते हैं और उसका उपयोग करते हैं तो यह दृष्टिकोण काम करना चाहिए । जैसे: filterexcluderes = Photo.exclude(~reduce(and_, [Q(tags__name=c) for c in categories]))
बेन

1

मैं एक छोटे से फ़ंक्शन का उपयोग करता हूं जो किसी दिए गए ऑपरेटर के लिए एक स्तंभ नाम की सूची पर फ़िल्टर को पुन: प्रसारित करता है:

def exclusive_in (cls,column,operator,value_list):         
    myfilter = column + '__' + operator
    query = cls.objects
    for value in value_list:
        query=query.filter(**{myfilter:value})
    return query  

और इस समारोह को इस तरह कहा जा सकता है:

exclusive_in(Photo,'tags__name','iexact',['holiday','summer'])

यह सूची में किसी भी वर्ग और अधिक टैग के साथ भी काम करता है; ऑपरेटर किसी को भी 'जैसे', 'में', 'ने', ... हो सकता है।



-1

यदि हम इसे गतिशील रूप से करना चाहते हैं, उदाहरण के बाद:

tag_ids = [t1.id, t2.id]
qs = Photo.objects.all()

for tag_id in tag_ids:
    qs = qs.filter(tag__id=tag_id)    

print qs

दूसरे पुनरावृत्ति जैसे ही काम नहीं कर सकते, क्वेरी खाली होगी
लैपिन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.