Bộ lọc Django queryset __in cho * mọi * mục trong danh sách


101

Giả sử tôi có các mô hình sau

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

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

Trong một dạng xem, tôi có một danh sách với các bộ lọc đang hoạt động được gọi là danh mục . Tôi muốn lọc các đối tượng Ảnh có tất cả các thẻ trong danh mục .

Tôi đã thử:

Photo.objects.filter(tags__name__in=categories)

Nhưng điều này phù hợp với bất kỳ mục nào trong các danh mục, không phải tất cả các mục.

Vì vậy, nếu danh mục sẽ là ['kỳ nghỉ', 'mùa hè'] thì tôi muốn Ảnh có cả thẻ kỳ nghỉ và mùa hè.

Điều này có thể đạt được không?


6
Có thể: qs = Photo.objects.all (); cho hạng mục trong các chuyên mục: qs = qs.filter (tags__name = loại)
JPIC

2
jpic là đúng, Photo.objects.filter(tags__name='holiday').filter(tags__name='summer')là con đường để đi. (Điều này giống với ví dụ của jpic). Mỗi thứ filternên thêm nhiều JOINs vào truy vấn, vì vậy bạn có thể thực hiện phương pháp chú thích nếu chúng quá nhiều.
Davor Lucic

1
Đây là thông tin tham khảo trong tài liệu: docs.djangoproject.com/en/dev/topics/db/queries/…
sgallen

Bạn mong chờ có được một xây dựng chức năng cho điều này bằng Django
Vincent

Câu trả lời:


124

Tóm lược:

Một tùy chọn, như được đề xuất bởi jpic và sgallen trong các nhận xét, để thêm .filter()cho mỗi danh mục. Mỗi phần bổ sung filterthêm nhiều liên kết hơn, điều này không thành vấn đề đối với một nhóm danh mục nhỏ.

cách tiếp cận tổng hợp . Truy vấn này sẽ ngắn hơn và có lẽ nhanh hơn đối với một tập hợp lớn các danh mục.

Bạn cũng có tùy chọn sử dụng truy vấn tùy chỉnh .


Vài ví dụ

Thiết lập thử nghiệm:

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>]

Sử dụng phương pháp tiếp cận bộ lọc chuỗi :

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

Truy vấn kết quả:

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 )

Lưu ý rằng mỗi câu filterbổ sung thêm JOINSvào truy vấn.

Sử dụng phương pháp chú thích :

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>]

Truy vấn kết quả:

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

ANDQcác đối tượng ed sẽ không hoạt động:

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]: []

Truy vấn kết quả:

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
Có giải pháp nào với tra cứu tùy chỉnh không? docs.djangoproject.com/en/1.10/howto/custom-lookups Sẽ rất tuyệt nếu chuyển "__in" thành "__all" và yêu cầu nó tạo truy vấn sql chính xác.
t1m0

1
Giải pháp chú thích này có vẻ sai. Có gì nếu có ba thẻ càng tốt (cho phép gọi một trong những bổ sung cho t3, và một bức ảnh có các thẻ t2t3Sau đó, bức ảnh này vẫn sẽ phù hợp với truy vấn cụ thể..
beruic

@beruic Tôi nghĩ ý tưởng là bạn sẽ thay thế num_tags = 2 bằng num_tags = len (tags); Tôi mong đợi phần 2 được mã hóa cứng chỉ là ví dụ.
tbm

3
@tbm Nó vẫn sẽ không hoạt động. Photo.objects.filter(tags__in=tags)khớp với ảnh có bất kỳ thẻ nào, không chỉ những ảnh có tất cả. Một số trong số đó chỉ có một trong các thẻ mong muốn, có thể có chính xác số lượng thẻ mà bạn đang tìm kiếm và một số trong số đó có tất cả các thẻ mong muốn, cũng có thể có các thẻ bổ sung.
beruic

1
@beruic chú thích chỉ đếm các thẻ được truy vấn trả về, vì vậy nếu (thẻ num được truy vấn trả về) == (thẻ num được tìm kiếm) thì hàng được bao gồm; thẻ "bổ sung" không được tìm kiếm, vì vậy sẽ không được tính. Tôi đã xác minh điều này trong ứng dụng của riêng mình.
tbm

8

Một cách tiếp cận khác hoạt động, mặc dù chỉ PostgreSQL, đang sử dụng django.contrib.postgres.fields.ArrayField:

Ví dụ được sao chép từ tài liệu :

>>> 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>]>

ArrayFieldcó một số tính năng mạnh mẽ hơn như chồng chéobiến đổi chỉ mục .


3

Điều này cũng có thể được thực hiện bằng cách tạo truy vấn động sử dụng Django ORM và một số phép thuật Python :)

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]))

Ý tưởng là tạo các đối tượng Q thích hợp cho từng danh mục và sau đó kết hợp chúng bằng cách sử dụng toán tử AND thành một QuerySet. Ví dụ: ví dụ của bạn, nó sẽ bằng

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

3
Điều này sẽ không hoạt động. Các ví dụ truy vấn của bạn sẽ không trả lại bất kỳ điều gì cho các mô hình được đề cập.
Davor Lucic

Cảm ơn đã sửa chữa. Tôi nghĩ rằng chuỗi filtersẽ giống như sử dụng andcho các đối tượng Q trong một bộ lọc ... Sai lầm của tôi.
demalexx 27/12/11

Không phải lo lắng, suy nghĩ đầu tiên của tôi cũng là đối tượng Q.
Davor Lucic

1
Điều này chúng tôi sẽ chậm hơn nếu bạn làm việc với các bảng lớn và dữ liệu lớn để so sánh với. (thích 1 Triệu mỗi cái)
gies0r

Cách tiếp cận này sẽ hoạt động nếu bạn chuyển từ filtersang excludevà sử dụng toán tử phủ định. Như vậy: res = Photo.exclude(~reduce(and_, [Q(tags__name=c) for c in categories]))
Ben

1

Tôi sử dụng một hàm nhỏ lặp lại các bộ lọc trên danh sách cho một toán tử nhất định một tên cột:

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  

và hàm này có thể được gọi như thế:

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

nó cũng hoạt động với bất kỳ lớp nào và nhiều thẻ khác trong danh sách; toán tử có thể là bất kỳ ai như 'iexact', 'in', 'chứa', 'ne', ...



-1

Nếu chúng ta muốn thực hiện động, hãy làm theo ví dụ:

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

Không thể hoạt động ngay sau lần lặp thứ hai, bộ truy vấn sẽ trống
lapin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.