2009-12-11 4 views
9

У меня есть функция, которая выглядит следующим образом:Фильтрация агрегата в Django ORM

def post_count(self): 
     return self.thread_set.aggregate(num_posts=Count('post'))['num_posts'] 

Я только хочу, чтобы рассчитывать сообщения, которые имеют свой статус помечена как «активный». Есть ли простой способ добавить фильтр перед функцией Count?

Модель определения:

class Category(models.Model): 
    name = models.CharField(max_length=100) 
    slug = models.SlugField(max_length=100, blank=True, primary_key=True) 
    ordering = models.IntegerField(max_length=3, default=0) 

    @property 
    def thread_count(self): 
     return self.thread_set.all().count() 

    @property 
    def post_count(self): 
     return self.thread_set.aggregate(num_posts=Count('post'))['num_posts'] 

class Thread(models.Model): 
    user = models.ForeignKey(User) 
    category = models.ForeignKey(Category) 
    title = models.CharField(max_length=100) 
    slug = models.SlugField(max_length=100) 
    content = models.TextField() 
    created = models.DateTimeField(auto_now_add=True) 
    latest_activity = models.DateTimeField(auto_now_add=True) 

class Post(models.Model): 
    thread = models.ForeignKey(Thread) 
    parent = models.ForeignKey('Post', null=True, blank=True) 
    display_name = models.CharField(max_length=100) 
    email = models.EmailField(db_index=True) 
    ip_address = models.IPAddressField(null=True, blank=True) 
    content = models.TextField() 
    status = models.CharField(choices=STATUS_CHOICES, max_length=25, db_index=True, default='approved') 
    created = models.DateTimeField() 
+2

Как выглядят ваши модели? – Mayuresh

ответ

9

ОК, теперь, когда вопрос включает в себя определение модели, я представляю вам, что это должно работать , если ваша версия Django не поддерживает некоторые функции я использую здесь (в этом случае, пожалуйста, дайте мне знать!):

Post.objects.filter(thread__in=thread_set, status='active').aggregate(num_posts=Count('id')) 

Джанго позволяет __in фильтры принять QuerySet решить, что пункт IN должен выглядеть как в SQL, поэтому, если вы пройдете thread__in=thread_set, Django будет фильтровать сообщения, чтобы только те, чье поле thread указывает на один из id s потоков в вашем thread_set. Остается для вызова aggregate.

Это должно фильтровать сообщения с только один дб запрос с чем-то вроде WHERE thread_id IN ... внутри, а не с одного запроса в потоке, который действительно был бы ужасно. Если что-то еще произошло, это было бы ошибкой в ​​Django ...

Результат должен быть в большинстве двух запросов о создании Category «s postcount - один, чтобы получить thread_set и еще один на самом деле сосчитать сообщения , Альтернативой является фильтрация потока/пост-соединения на основе поля Threadcategory и Poststatus, которое я бы не ожидал, что это будет намного быстрее. (я говорю «самое большее», потому что я думаю, что они могут быть сплавлены автоматически ...Хотя я не думаю, что это произойдет с нынешним Django. Невозможно проверить банкомат, извините)

EDIT:.Django's QuerySet API reference говорит, что это на __in фильтры:


В

В данном списке.

Пример:

Entry.objects.filter(id__in=[1, 3, 4]) 

SQL эквивалент:

SELECT ... WHERE id IN (1, 3, 4); 

Вы также можете использовать QuerySet динамически оценивать список значений вместо предоставления списка буквальных значений:

inner_qs = Blog.objects.filter(name__contains='Cheddar') 
entries = Entry.objects.filter(blog__in=inner_qs) 

Этот запрос будет оцениваться как инструкция подзапроса:

SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%') 

Приведенный выше код фрагмент также может быть записана следующим образом:

inner_q = Blog.objects.filter(name__contains='Cheddar').values('pk').query 
entries = Entry.objects.filter(blog__in=inner_q) 

Изменено в Django 1.1: В Django 1.0, только последний фрагмент кода является действительным.

Эта вторая форма является менее читаемой и неестественной для записи, поскольку она обращается к внутреннему атрибуту запроса и требует значения ValuesQuerySet. Если ваш код не требует совместимости с Django 1.0, используйте первую форму, непосредственно передавая запрос.


Таким образом, я предполагаю, что Django это способна пропускать один запрос к БД в случае здесь речь. Если анализатор запросов db выполняет хорошую работу, эффект может быть почти оптимальным. :-)

+0

Это лучшее решение. Это может быть и лучшее, что вы можете сделать с ордой джанго. Я просто надеялся, что есть простой способ добавить фильтр к оригиналу self.thread_set.aggregate (num_posts = Count ('post')) ['num_posts']. –

+0

Ну, я наконец-то обернулся, чтобы посмотреть документы на фильтрах __in с внутренними QuerySets ... Похоже, они должны хорошо работать, нет? :-) Жаль, что я не могу найти способ напрямую подключить фильтр к вашему коду, так как я вижу, как это может быть более естественным образом подойти к конкретному способу мышления о том, что говорит код ... (Хотя, с другой стороны, вы считаете посты, поэтому переходить через «Почта» мне кажется хорошо.) Надеюсь, что это сработает для вас, во всяком случае, с точки зрения производительности. Всего найлучшего! –

0

Да. Просто сделай это. Это должно работать, как и ожидалось:

self.thread_set.filter(active_status=1).aggregate(num_posts=Count('post'))['num_posts'] 

Любой исходный запрос возвращает QuerySet, так any available methods that return QuerySets может быть может быть в значительной степени неопределенно долго прикован вместе для сложных критериев совпадения. Начиная с aggregate()does not return a QuerySet, вы хотите убедиться, что он последний в цепочке.

+0

Я уверен, что фильтр будет фильтровать, а не сообщения в каждом потоке. –

+0

Ой, вы правы. Вам нужно будет получить полный список сообщений для потока, затем отфильтровать активные сообщения, а затем объединить их. Но другой вопрос о том, что 'aggregate()' last все еще стоит. Я буду ткнуть, а затем отредактировать свой ответ. – jathanism

+0

У меня пока нет примера кода, но похоже, что вы можете его искать: http://docs.djangoproject.com/en/dev/topics/db/aggregation/#joins-and-aggregates (Соединения и агрегаты) – jathanism

0

Я искал нечто похожее и не нашел отличного решения. Я использую что-то вроде этого:

def post_count(self): 
     return len(Post.objects.filter(someModel = self).filter(active_status = 1)) 

Это не здорово, но я не думаю, что Django позволяет фильтровать на основе вторичных модели агрегатов и аннотации. Я буду проверять, есть ли у кого-нибудь лучшее решение.

+1

Почему не Post.objects.filter (someModel = self, active_status = 1) .count()? –

+0

Также я использую агрегат здесь, потому что я хочу больше, чем просто счетчик сообщений в конкретном потоке (представленный someModel в вашем примере), мне нужен счетчик сообщений для всех потоков в категории, поэтому я использую thread_set. –

+0

Да, пример, который я дал, довольно неряшлив. Ваш код определенно чище. Точка, которую я пыталась сделать, заключается в том, что я уверен, что вам нужно работать через модель Post, но не модель Thread. – Zach

0

Вы можете посмотреть на написание пользовательского объекта менеджер:

http://docs.djangoproject.com/en/1.1/topics/db/managers/

Я не использовал aggregate(), но это может позволить вам написать собственный менеджер, чтобы обеспечить отфильтрованный active_thread_set, а затем сделать self.active_thread_set.aggregate(...) , Если нет, то это позволит вам сделать пользовательский SQL и добавить num_posts свойства на Thread объектов (см PollManager.with_counts() примера.)

0

Было бы хорошо немного изменить ситуацию?

Как показано ниже, вы можете добавить свойство post_count в класс Thread, в котором учитываются активные сообщения в потоке.

Этот post_count может быть использован для расчета активных сообщений в категории путем сложения всех активных сообщений во всех потоках в категории.

class Category(models.Model): 
    name = models.CharField(max_length=100) 
    slug = models.SlugField(max_length=100, blank=True, primary_key=True) 
    ordering = models.IntegerField(max_length=3, default=0) 

    @property 
    def thread_count(self): 
     return self.thread_set.all().count() 

    @property 
    def post_count(self): # <-- Changed 
     return reduce(lambda x,y: x + y, [x.post_count for x in self.thread_set.all()]) 

class Thread(models.Model): 
    user = models.ForeignKey(User) 
    category = models.ForeignKey(Category) 
    title = models.CharField(max_length=100) 
    slug = models.SlugField(max_length=100) 
    content = models.TextField() 
    created = models.DateTimeField(auto_now_add=True) 
    latest_activity = models.DateTimeField(auto_now_add=True) 

    @property 
    def post_count(self): # <---- Newly added 
     return self.post_set.filter(status = 'ACTIVE').count() 

class Post(models.Model): 
    thread = models.ForeignKey(Thread) 
    parent = models.ForeignKey('Post', null=True, blank=True) 
    display_name = models.CharField(max_length=100) 
    email = models.EmailField(db_index=True) 
    ip_address = models.IPAddressField(null=True, blank=True) 
    content = models.TextField() 
    status = models.CharField(choices=STATUS_CHOICES, max_length=25, db_index=True, default='approved') 
    created = models.DateTimeField() 
Смежные вопросы