Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Count method in Django template not working as expected

I'm building a news app that allows members to post comments on articles. I want to display the articles in my template and also display the number of comments that have been made on each article. I tried using the count method but, it retrieved the total number of comments in my comments table instead of the number of comments that a particular article has.

#models.py
class Article(models.Model):
    #auto-generate indices for our options
    ENTRY_STATUS = enumerate(('no', 'yes'))
    #this will be a foreign key once account app is built
    author = models.CharField(default=1, max_length=1)
    category = models.ForeignKey(Category)
    title = models.CharField(max_length=50)
    entry = models.TextField()
    dateposted = models.DateTimeField(default=timezone.now, auto_now_add=True)
    draft = models.IntegerField(choices=ENTRY_STATUS, default=0)
    lastupdated = models.DateTimeField(default=timezone.now, auto_now=True)

    #prevents the generic labeling of our class as 'Classname object'
    def __unicode__(self):
        return self.title


class Comment(models.Model):
    #this will be a foreign key once account app is built
    author = models.CharField(default=1, max_length=1)
    article = models.ForeignKey(Article)
    dateposted = models.DateTimeField(auto_now_add=True)
    comment = models.TextField()

    def __unicode__(self):
        #returns the dateposted as a unicode string
        return unicode(self.dateposted)

#templates/list_articles.html
{% for comment in comments %}
    {% if comment.article_id == article.id %}
        {% if comments.count < 2 %}
            #this is returning all comments in comment table
            <b>{{ comments.count }} comment</b>
        {% else %}
            <b>{{ comments.count }} comments</b>
        {% endif %}
    {% endif %}
{% endfor %}

All the examples I've seen so far manually provide a value to filter by(e.g. Comment.objects.filter(article_id=x).count() ) In my case I only have access via the template.

#views.py
class ArticlesListView(ListView):
    context_object_name = 'articles'
    # only display published pieces (limit 5)
    queryset = Article.objects.select_related().order_by('-dateposted').filter(draft=0)[:5]
    template_name = 'news/list_articles.html'

    # overide this to pass additional context to templates
    def get_context_data(self, **kwargs):
        context = super(ArticlesListView, self).get_context_data(**kwargs)
        #get all comments
        context['comments'] = Comment.objects.order_by('-dateposted')
        #get all article photos
        context['photos'] = Photo.objects.all()
        #context['total_comments'] = Comment.objects.filter(article_id=Article)
        return context

My intended result is to have a listing of all articles and a roll-up of comments made on that article below each article(e.g. Article 1: 4 comments, Article 5: 1 comment, etc...) Right now I'm getting: Article 1: 4 comments, Article 5: 4 comments(even though Article 5 only has 1 comment)

Any help is appreciated. I've spent 5 hours reading through the documentation but every example manually provides a value to filter by.

like image 652
Staccato Avatar asked Sep 04 '25 17:09

Staccato


1 Answers

I'm not sure why you find this unexpected. comments is all the comments, so of course comments.count is a count of all the comments. How could it be otherwise? You don't filter them anywhere.

This is however a really really horrible way to do things. There is absolutely no reason to pass all comments to the template and then iterate through them to check if they're the right article. You have a foreign key from Comment to Article, so you should use the reverse relationship to get the relevant commments.

Leave out the Comment query altogether from your view, and in your template just do this (replacing that whole block of nested fors and ifs):

{{ article.comment_set.count }}

This however does one count query per article. A better solution is to use annotations, so you can do it all in one single query. Change your queryset to add the annotated count of related comments:

from django.db.models import Count

class ArticlesListView(ListView):
    queryset = Article.objects.select_related().annotate(comment_count=Count('comments')).order_by('-dateposted').filter(draft=0)

and now you can just do

{{ article.comment_count }}
like image 98
Daniel Roseman Avatar answered Sep 07 '25 16:09

Daniel Roseman