Модели Django - как фильтровать количество объектов ForeignKey
У меня есть модели A
и B
, которые выглядят следующим образом:
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)
Теперь у меня есть некоторые объекты A
и B
, и я бы хотел получить запрос, который выбирает все объекты A
, у которых меньше 2 B
, указывая на них.
A - это что-то вроде пула, а пользователи (B) объединяют пул. если только 1 или 0 соединены, пул не должен отображаться вообще.
Возможно ли такое моделирование? Или мне нужно немного изменить это?
Ответы
Ответ 1
Звучит как работа для extra
.
A.objects.extra(
select={
'b_count': 'SELECT COUNT(*) FROM yourapp_b WHERE yourapp_b.a_id = yourapp_a.id',
},
where=['b_count < 2']
)
Если счетчик B - это то, что вам часто требуется в качестве критерия фильтрации или заказа, или его нужно отображать в представлениях списка, вы можете рассмотреть денормализацию, добавив поле b_count к вашей модели A и используя сигналы для обновления, когда B добавляется или удаляется:
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)
Другим решением было бы управлять флагом состояния объекта A, когда вы добавляете или удаляете связанный B.
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)
Ответ 2
Вопрос и выбранный ответ - с 2008 года, и с тех пор эта функциональность была интегрирована в структуру django. Поскольку это верхний хит google для "django filter foreign key count", я хотел бы добавить более легкое решение с недавней версией django, используя Aggregation.
from django.db.models import Count
cats = A.objects.annotate(num_b=Count('b')).filter(num_b__lt=2)
В моем случае мне пришлось принять эту концепцию еще дальше. Мой объект "B" имел логическое поле, называемое is_available, и я хотел только вернуть объекты A, у которых было более 0 B объектов с is_available, равным True.
A.objects.filter(B__is_available=True).annotate(num_b=Count('b')).filter(num_b__gt=0).order_by('-num_items')
Ответ 3
Я бы рекомендовал изменить ваш дизайн, чтобы включить в него некоторое поле статуса.
Вопрос один из "почему?" Почему A имеет < 2 B и почему A имеет >= 2 B. Это потому, что пользователь ничего не вводил? Или потому, что они пытались, и у их ввода были ошибки. Или это потому, что < 2 в этом случае не применяется.
Использование присутствия или отсутствия внешнего ключа ограничивает значение - хорошо - настоящее или отсутствующее. У вас нет возможности представить "почему?"
Кроме того, у вас есть следующий вариант
[ a for a in A.objects.all() if a.b_set.count() < 2 ]
Это может быть дорого, потому что он извлекает все A, а не заставляет базу данных выполнять работу.
Изменить: из комментария "потребовалось бы, чтобы я смотрел, как пользователь присоединяется/пользователь выходит из событий пула".
Вы ничего не "смотрите" - вы предоставляете API, который делает то, что вам нужно. Это центральное преимущество модели Django. Здесь один путь, с эксплицитными методами в классе 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
Вы также можете определить для этого специальный Manager
и заменить менеджер по умолчанию b_set
менеджером, который подсчитывает ссылки и обновления A
.
Ответ 4
Я предполагаю, что объединение или выход из пула может происходить не так часто, как перечисление (показ) пулов. Я также считаю, что было бы более эффективным для пользователей присоединиться/оставить действия для обновления статуса отображения пула. Таким образом, перечисление и отображение пулов потребует меньше времени, так как вы просто запускаете один запрос для SHOW_STATUS объектов пула.
Ответ 5
Как это сделать?
queryset = A.objects.filter(b__gt=1).distinct()