'Count number of replies on a particular post in Django

I want to count number of replies on a particular post in Django

View.py

def forum(request):
    profile = Profile.objects.all()
    if request.method=="POST":   
        user = request.user
        image = request.user.profile.image
        content = request.POST.get('content','')
        post = Post(user1=user, post_content=content, image=image)
        post.save()
        messages.success(request, f'Your Question has been posted successfully!!')
        return redirect('/forum')
    posts = Post.objects.filter().order_by('-timestamp')
    return render(request, "forum.html", {'posts':posts})

Reply code

def discussion(request, myid):
    post = Post.objects.filter(id=myid).first()
    replies = Replie.objects.filter(post=post)
    if request.method=="POST":
        user = request.user
        image = request.user.profile.image
        desc = request.POST.get('desc','')
        post_id =request.POST.get('post_id','')
        reply = Replie(user = user, reply_content = desc, post=post, image=image)
        reply.save()
        messages.success(request, f'Your Reply has been posted successfully!!')
        return redirect('/forum')
    return render(request, "discussion.html", {'post':post, 'replies':replies})    

model.py

class Post(models.Model):
    user1 = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
    post_id = models.AutoField
    post_content = models.CharField(max_length=5000)
    timestamp= models.DateTimeField(default=now)
    image = models.ImageField(upload_to="images",default="")
    def __str__(self):
       return f'{self.user1} Post'

class Replie(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
    reply_id = models.AutoField
    reply_content = models.CharField(max_length=5000) 
    post = models.ForeignKey(Post, on_delete=models.CASCADE, default='')
    timestamp= models.DateTimeField(default=now)
    image = models.ImageField(upload_to="images",default="")
    def __str__(self):
       return f'{self.user1} Post'

My Forum.html code:

{% for post in posts %}
<div class="container-fluid mt-10">
  <div class="row">
      <div class="col-md-12">
          <div class="card mb-4 forumcardcss">
              <div class="card-header forumcardheader">
                  <div class="media flex-wrap w-100 align-items-center imgcss"> <img src="/media/{{post.image}}" 
                          class="d-block ui-w-40 rounded-circle" alt="profileimage"style="width: 40px;height: 40px;">  <p class="ml-4 usernamecss">  {{post.user1}} </p>
                      <div class="media-body ml-3"> <a href="/discussion/{{post.id}}" data-abc="true"><button class="btn btn-light" style="color:blue; font-size: 13px;">Add or See reply </button>  </a>
                      </div>
                      <div class="text-muted small ml-3">
                        <div class="px-4 pt-3">Nmber of reply   &nbsp;&nbsp;  {{post.timestamp}} </div>
                      </div>
                       {% if user.is_superuser or user.is_staff %}
                        <a href="{% url 'dashboard:delete_post' post.id %}"> <button class="btn btn-danger btn-sm" onclick="window.mytest()">Delete Post</button></a>
                        <script type="text/javascript">window.mytest = function() { var isValid = confirm('If you click ok then its delete this post and related reply on it. Are you sure to delete?');if (!isValid) { event.preventDefault();  alert("It wont delete. Yay!");}}</script>
                        {% endif %}
                     </div>
                   </div>         
               <div class="card-body forumcardbody">
                  <p>{{post.post_content}}</p>
              </div>
              <div class="card-footer d-flex flex-wrap justify-content-between align-items-center px-0 pt-0 pb-3">                  
              </div>
          </div>
      </div>
  </div>
</div>
{% endfor %}

I want to do like this

this

where on the place of Number of reply, I want to display the number of replies of the particular post

Is there any way to find if Question(Post) has been answered(reply) on my post page(forum.py) I want to do it like this If the Question has been answered then it should show "Answered" else "Not answered yet"



Solution 1:[1]

@Eega suggested the right answer just some changes in the code will help you

class Post(models.Model):
    user1 = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
    post_id = models.AutoField
    post_content = models.CharField(max_length=5000)
    timestamp= models.DateTimeField(default=now)
    image = models.ImageField(upload_to="images",default="")
    
    @property
    def count_replies(self):
        return self.replie_set.count()

    def __str__(self):
       return f'{self.user1} Post'

post = Post.objects.filter(id=myid).first() to post = Post.objects.filter(id=myid).first().prefetch_related('replies_set') This will make your query optimized Also accept @Eega answer only, I have just showed you the edited code

Now I am suggesting one good method here

Post.objects.get(id=myid).annotate(post_count=Count("replie"))

Simply use this in your views without changing #models.py and access it in your template as post.post_count in for loop.

Solution 2:[2]

To archive this you can use the related name of the Post model (have a look at the documentation). Django will create a field for every foreign key that allows you to access the related model. By default, this will be named replie_set on the Post model.

This field you can then use to get the number of replies to a post by calling the count() method of the replie_set queryset. I would also add a method to the Post model that does that for you as a convenience.

To bring this together, your Post model would look like this:

class Post(models.Model):
    user1 = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
    post_id = models.AutoField
    post_content = models.CharField(max_length=5000)
    timestamp= models.DateTimeField(default=now)
    image = models.ImageField(upload_to="images",default="")
    
    @property
    def count_replies():
        return self.replies_set.count()

    def __str__(self):
       return f'{self.user1} Post'

Assuming that your forum.html template iterates over the posts like that:

{% for post in posts %}
    ...
    <p>Posts: {{ post.count_replies }}</p>
    ...
{% endfor %}

You get the number of replies by calling post.count_replies(). Of course, if you don't want to add a dedicated method to the model you can just use do post.replie_set.count() directly.


An alternative - and more efficient method - is to annotate your posts with the reply count as Abdul Aziz Barkat suggests. To do this you have to change your view like that:

from django.db.models import Count

def forum(request):
    profile = Profile.objects.all()
    if request.method=="POST":   
        user = request.user
        image = request.user.profile.image
        content = request.POST.get('content','')
        post = Post(user1=user, post_content=content, image=image)
        post.save()
        messages.success(request, f'Your Question has been posted successfully!!')
        return redirect('/forum')
    posts = Post.objects.annotate(count_replies=Count("replie")).order_by('-timestamp')
    return render(request, "forum.html", {'posts':posts})

I changed only the second to last line here:

   posts = Post.objects.annotate(count_replies=Count("replie")).order_by('-timestamp')

This adds the aggregated count of replies to each post as count_replies.

Then this value is used in the forum.html template like that:

{% for post in posts %}
<div class="container-fluid mt-10">
  <div class="row">
      <div class="col-md-12">
          <div class="card mb-4 forumcardcss">
              <div class="card-header forumcardheader">
                  <div class="media flex-wrap w-100 align-items-center imgcss"> <img src="/media/{{post.image}}" 
                          class="d-block ui-w-40 rounded-circle" alt="profileimage"style="width: 40px;height: 40px;">  <p class="ml-4 usernamecss">  {{post.user1}} </p>
                      <div class="media-body ml-3"> <a href="/discussion/{{post.id}}" data-abc="true"><button class="btn btn-light" style="color:blue; font-size: 13px;">Add or See reply </button>  </a>
                      </div>
                      <div class="text-muted small ml-3">
                        <div class="px-4 pt-3">Number of replies   &nbsp;&nbsp;  {{ post.count_replies }} </div>
                      </div>
                       {% if user.is_superuser or user.is_staff %}
                        <a href="{% url 'dashboard:delete_post' post.id %}"> <button class="btn btn-danger btn-sm" onclick="window.mytest()">Delete Post</button></a>
                        <script type="text/javascript">window.mytest = function() { var isValid = confirm('If you click ok then its delete this post and related reply on it. Are you sure to delete?');if (!isValid) { event.preventDefault();  alert("It wont delete. Yay!");}}</script>
                        {% endif %}
                     </div>
                   </div>         
               <div class="card-body forumcardbody">
                  <p>{{post.post_content}}</p>
              </div>
              <div class="card-footer d-flex flex-wrap justify-content-between align-items-center px-0 pt-0 pb-3">                  
              </div>
          </div>
      </div>
  </div>
</div>
{% endfor %}

So, only a single line changed here either:

<div class="px-4 pt-3">Number of replies   &nbsp;&nbsp;  {{ post.count_replies }} </div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2