2013-03-12 3 views
5

У меня есть следующие модели:Джанго аннотации на BooleanField

class Foo(models.Model): 
    pass 

class Bar(models.Model): 
    foo = models.ForeignKey(Foo) 
    is_successful = models.BooleanField() 

Я хотел бы получить все foo объекты с аннотациями, если все bar объектов, связанных с foo объектом имеет is_successful в True

So far мой запрос установлен:

foos = Foo.objects.all().annotate(all_successful=Min('bar__is_successful')) 

Идея для all_successful состоит в том, что если минимальное значение всех строк is_successful равно 1, то все они должны быть True (при условии, что 0 - False и 1 - True). Так, зная, что я могу использовать QuerySet так:

foo = foos[0] 

if foo.all_successful == 1: 
    print 'All bars are successful' 
else: 
    print 'Not all bars are successful' 

Это прекрасно работает в SQLite, однако он не в PostgreSQL, поскольку PostgreSQL не может выполнить MIN агрегат на булевой колонке. Я думаю, это работает в sqlite, потому что sqlite обрабатывает bools как целые числа, поэтому он может выполнять агрегат.

Вопрос: как я могу сделать эту работу в PostgreSQL без преобразования поля is_successful в IntegerField?

Thanx

ответ

10

Я знаю, что это старый вопрос, но я столкнулся с этим недавно. В Django v1.8 теперь встроена поддержка case/when, поэтому вы можете использовать ORM вместо взлома с помощью настраиваемого SQL.

https://docs.djangoproject.com/en/1.8/ref/models/conditional-expressions/#case

Foo.objects.annotate(
    all_successful=Case(
     When(bar__is_successful=False, then=False), 
     When(bar__is_successful=True, then=True), 
     default=False, 
     output_field=BooleanField() 
    )) 

Я не пробовал это, но что-то подобное работал для меня на последнем проекте.

+0

Спасибо.Это действительно возможно в Django, так что ваш вопрос был принят. – miki725

+0

это предпочтительный подход сейчас, хороший момент! спасибо за ваш вклад! – furins

+1

Ваш синтаксис легче следовать, чем фактическая документация, спасибо! Отсутствует окончательный ')' в кодовом блоке. Кроме того, добавьте окончательный kwarg 'output_field = BooleanField()' в вызов 'Case()'. – AlanSE

1

вдохновляясь https://docs.djangoproject.com/en/dev/topics/db/managers/ я предлагаю использовать пользовательский менеджер для бара класса вместо аннотирования

class BarManager(models.Manager): 
    def get_all_successful_foos_ids(self): 
     from django.db import connection 
     cursor = connection.cursor() 
     cursor.execute(""" 
      SELECT foo, COUNT(*) 
      FROM yourapp_bar 
      GROUP BY 1 
      WHERE is_successful = true""") # <-- you have to write the correct table name here 
     result_list = [] 
     for row in cursor.fetchall(): 
      if row[1] > 0: 
       result_list.append(row[0]) 
     return result_list 

class Bar(models.Model): 
    foo = models.ForeignKey(Foo) 
    is_successful = models.BooleanField() 
    objects = BarManager() # here I'm changing the default manager 

затем, в вашем коде:

foos = foo.objects.filter(id__in=Bar.objects.get_all_successful_foos_ids()) 
+0

Thanx для идеи, но это не то, что я ищу, потому что ваши предложения просто фильтруют запрос, тогда как я ищу, как комментировать мою модель foo. – miki725

+0

Я написал еще один ответ с более простым подходом, который должен дать вам ожидаемую аннотацию (у меня не было времени протестировать его, но я должен надеяться на правильное направление) – furins

+0

thanx. оно работает. кроме того, что я изменил предложение count на – miki725

3

ДЛЯ Джанго < = 1.7:, чтобы получить annotation Я думаю, вы можете просто использовать Extra

foos = Foo.objects.extra(select={'all_successful': 'CASE WHEN COUNT(b.foo) > 0 THEN 0 ELSE 1 END FROM yourapp_bar as b WHERE b.is_successful = false and b.foo = yourapp_foo.id' }) 

, если ваша система работает Django 1.8+ следуйте Dav3xor answer.

+0

Это блестяще. Я искал хороший способ использовать SQL «CASE WHEN» в Django, и это прибило его. – itnAAnti

Смежные вопросы