2008-11-03 9 views
36

Tôi có một mô hình AB, đó là như thế này:mô hình Django - làm thế nào để lọc số đối tượng ForeignKey

class A(models.Model): 
    title = models.CharField(max_length=20) 
    (...) 

class B(models.Model): 
    date = models.DateTimeField(auto_now_add=True) 
    (...) 
    a = models.ForeignKey(A) 

Bây giờ tôi có một số AB đối tượng, và tôi muốn để có được một truy vấn chọn tất cả A đối tượng có ít hơn 2 B chỉ vào chúng.

A là một thứ giống như hồ bơi và người dùng (nhóm B) tham gia. nếu chỉ có 1 hoặc 0 tham gia, hồ bơi sẽ không được hiển thị.

Có thể thiết kế mô hình như vậy không? Hoặc tôi nên sửa đổi một chút?

Trả lời

6

Có vẻ như một công việc cho extra.

A.objects.extra(
    select={ 
     'b_count': 'SELECT COUNT(*) FROM yourapp_b WHERE yourapp_b.a_id = yourapp_a.id', 
    }, 
    where=['b_count < 2'] 
) 

Nếu số B là một cái gì đó bạn thường cần như một bộ lọc hoặc đặt hàng tiêu chuẩn, hoặc cần phải được hiển thị trên danh sách, bạn có thể xem xét denormalisation bằng cách thêm một lĩnh vực b_count để mô hình Một của bạn và sử dụng tín hiệu để cập nhật nó khi một B được thêm vào hoặc xóa:

from django.db import connection, transaction 
from django.db.models.signals import post_delete, post_save 

def update_b_count(instance, **kwargs): 
    """ 
    Updates the B count for the A related to the given B. 
    """ 
    if not kwargs.get('created', True) or kwargs.get('raw', False): 
     return 
    cursor = connection.cursor() 
    cursor.execute(
     'UPDATE yourapp_a SET b_count = (' 
      'SELECT COUNT(*) FROM yourapp_b ' 
      'WHERE yourapp_b.a_id = yourapp_a.id' 
     ') ' 
     'WHERE id = %s', [instance.a_id]) 
    transaction.commit_unless_managed() 

post_save.connect(update_b_count, sender=B) 
post_delete.connect(update_b_count, sender=B) 

một giải pháp khác sẽ được quản lý một lá cờ trạng thái trên một đối tượng khi bạn thêm hoặc loại bỏ một B. liên quan

B.objects.create(a=some_a) 
if some_a.hidden and some_a.b_set.count() > 1: 
    A.objects.filter(id=some_a.id).update(hidden=False) 

... 

some_a = b.a 
some_b.delete() 
if not some_a.hidden and some_a.b_set.count() < 2: 
    A.objects.filter(id=some_a.id).update(hidden=True) 
+1

Giải pháp quá phức tạp, trong khi Django có tính năng chú thích. –

3

Tôi khuyên bạn nên sửa đổi thiết kế của mình để bao gồm một số trường trạng thái trên A.

Vấn đề là một trong những lý do "tại sao?" Tại sao A có < 2 B và tại sao A có> = 2 B. Có phải vì người dùng không nhập gì đó? Hoặc là bởi vì họ đã cố gắng và đầu vào của họ có lỗi. Hoặc là vì quy tắc < 2 không áp dụng trong trường hợp này.

Sử dụng sự hiện diện hoặc vắng mặt của Khóa ngoại quốc giới hạn ý nghĩa - hiện tại hoặc vắng mặt. Bạn không có cách nào để đại diện cho "tại sao?"

Ngoài ra, bạn có tùy chọn sau

[ a for a in A.objects.all() if a.b_set.count() < 2 ] 

Điều này có thể đắt tiền vì nó lấy tất cả của hơn buộc các cơ sở dữ liệu để làm việc A.


Chỉnh sửa: Từ nhận xét "sẽ yêu cầu tôi xem người dùng tham gia/người dùng rời khỏi sự kiện hồ bơi".

Bạn không "xem" bất cứ điều gì - bạn cung cấp API làm những gì bạn cần. Đó là lợi ích trung tâm của mô hình Django. Dưới đây là một cách, với phương pháp khám phá trong lớp A.

class A(models.Model): 
    .... 
    def addB(self, b): 
     self.b_set.add(b) 
     self.changeFlags() 
    def removeB(self, b): 
     self.b_set.remove(b) 
     self.changeFlags() 
    def changeFlags(self): 
     if self.b_set.count() < 2: self.show= NotYet 
     else: self.show= ShowNow 

Bạn cũng có thể định nghĩa một Manager đặc biệt cho điều này, và thay thế Manager mặc định b_set với người quản lý của bạn mà đếm tài liệu tham khảo và cập nhật A.

+0

A là một thứ gì đó giống như hồ bơi và người dùng (nhóm B) tham gia. nếu chỉ có 1 hoặc 0 tham gia, không nên hiển thị hồ bơi ... Đó là lý do tôi muốn không bao gồm trạng thái như vậy - sẽ yêu cầu tôi xem người dùng tham gia/người dùng rời khỏi sự kiện trong hồ bơi. Nhưng, có lẽ đó là cách ... – kender

1

Tôi cho rằng việc tham gia hoặc rời khỏi hồ bơi có thể không xảy ra thường xuyên như liệt kê (hiển thị) các hồ bơi. Tôi cũng tin rằng sẽ có hiệu quả hơn cho người dùng tham gia/để lại các hành động để cập nhật trạng thái hiển thị hồ bơi.Bằng cách này, việc liệt kê & hiển thị các hồ bơi sẽ yêu cầu ít thời gian hơn vì bạn chỉ cần chạy một truy vấn cho SHOW_STATUS đối tượng trong hồ bơi.

104

Câu hỏi và câu trả lời được chọn là từ năm 2008 và kể từ đó chức năng này đã được tích hợp vào khung công tác django. Vì đây là một hit google hàng đầu cho "bộ lọc django số khóa ngoại" Tôi muốn thêm một giải pháp dễ dàng hơn với một phiên bản django gần đây sử dụng Aggregation.

from django.db.models import Count 
cats = A.objects.annotate(num_b=Count('b')).filter(num_b__lt=2) 

Trong trường hợp của tôi, tôi phải thực hiện khái niệm này thêm một bước nữa. Đối tượng "B" của tôi có một trường boolean gọi là is_available, và tôi chỉ muốn trả về một đối tượng có hơn 0 đối tượng B với is_available được đặt thành True.

A.objects.filter(B__is_available=True).annotate(num_b=Count('b')).filter(num_b__gt=0).order_by('-num_items') 
+1

Tôi đã thử làm điều này nhưng nó thực sự là chậm (như sử dụng "thêm" trong câu trả lời 1 là gần như ngay lập tức nhưng với điều này phải mất mỗi mục nhập hơn 5 giây). Có cách nào nhanh hơn mà không rơi vào SQL thô không? – daveeloo

+0

Xin lỗi, tôi chưa phải đối phó với rất nhiều dữ liệu đánh trang web của tôi vì vậy tôi chưa xử lý việc tăng tốc truy vấn này. Tôi sẽ đề nghị bộ nhớ đệm kết quả và chỉ chạy truy vấn định kỳ. – gravitron

+6

Bổ sung thêm SQL thô "phá vỡ" ý tưởng sử dụng ORM – llazzaro

0

Làm thế nào để thực hiện theo cách này?

queryset = A.objects.filter(b__gt=1).distinct()