Модели
Вот основные настройки моделей, которые у нас есть.
В списке много элементов, а элемент может быть во многих списках. Для данного элемента, если какой-либо из его списков хороший (т. е. list.bad == False
), то этот элемент является хорошим. Если элемент не отображается ни в одном из хороших списков, значит, он плохой.
У нас есть собственный QuerySet для элементов с методом возврата только хороших элементов и методом возврата только плохих элементов.
class Item(models.Model):
objects = ItemQuerySet.as_manager()
name = models.CharField(max_length=255, unique=True)
class List(models.Model):
name = models.CharField(max_length=255, unique=True)
bad = models.BooleanField(default=True)
items = models.ManyToManyField(Item, related_name='lists')
class ItemQuerySet(models.QuerySet):
def bad(self):
return self.exclude(lists__bad=False)
def good(self):
return self.filter(lists__bad=False)
Сценарий
Вот пример сценария, с которым у нас возникли проблемы: один плохой список, один хороший список и два элемента.
BadList: GoodList:
- Item1 - Item1
- Item2
Поскольку Item1 появляется по крайней мере в одном хорошем списке, он должен появиться в Item.objects.good()
, а не в Item.objects.bad()
.
Поскольку Item2 не появляется ни в одном хорошем списке, он должен появиться в Item.objects.bad()
, а не в Item.objects.good()
.
Мы можем настроить сценарий следующим образом:
# Create the two lists.
>>> goodlist = List.objects.create(name='goodlist', bad=False)
>>> badlist = List.objects.create(name='badlist', bad=True)
# Create the two items.
>>> item1 = Item.objects.create(name='item1')
>>> item2 = Item.objects.create(name='item2')
# Item1 goes in both lists
>>> goodlist.items.add(item1)
>>> badlist.items.add(item1)
# Item2 only in badlist
>>> badlist.items.add(item2)
И действительно, Item.objects.good()
и Item.objects.bad()
работают так, как мы ожидаем:
>>> Item.objects.bad() # This returns what we want! Good!
<QuerySet [<Item: item2>]>
>>> Item.objects.good() # This returns what we want! Good!
<QuerySet [<Item: item1>]>
Эта проблема
Спасибо, что терпели меня. Вот где наш пользовательский QuerySet идет не так. Если мы получим доступ к good()
и bad()
пользовательским методам QuerySet через один элемент списка, мы получим неправильные результаты.
>>> badlist.items.bad() # WRONG! We want to ONLY see item2 here!
<QuerySet [<Item: item1>, <Item: item2>]
>>> badlist.items.good() # WRONG! We want to see item1 here!
<QuerySet []>
Кажется, когда мы делаем badlist.items.bad()
, запрос только учитывает badlist
при определении того, являются ли элементы плохими, вместо того, чтобы рассматривать все списки, в которых находятся элементы. быть дело.
Я думаю, что в методе ItemQuerySet.bad
мне нужно что-то вроде self.exclude(any__lists__bad=False)
, а не просто self.exclude(lists__bad=False)
. Но, конечно, этого ключевого слова any__
на самом деле не существует, и я не уверен, как правильно выразить эту логику в Django QuerySet. Кажется, что использование объектов Q
может быть шагом вперед, но я все еще не совсем уверен, как выразить подобный запрос с объектами Q
.
В нашей фактической базе данных меньше 100 списков, но миллионы элементов. Таким образом, по соображениям производительности идеально делать это с помощью одного запроса, а не свойства или нескольких запросов.
Ваше здоровье!
badlist.items.good()
, не зная ничего лучшего. Я не хочу, чтобы люди были введены в заблуждение таким образом. Кроме того, я хотел упомянуть об этом в исходном сообщении: в нашей фактической базе данных менее 100 списков, но миллионы элементов. Таким образом, по соображениям производительности идеально делать это с помощью одного запроса, а не свойства или нескольких запросов. 13.06.2017