0

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"

3
  • A few comments on the models - you don't need to explicitly create the primary key as this is done by Django automatically (see docs.djangoproject.com/en/4.0/topics/db/models/…). So post_id = models.AutoField on the Post model and reply_id = models.AutoField on the Replie model isn't necessary. Also, if you always want to automatically create the timestand on create the DateTimeField has the auto_now_add attribute that does that for you (docs.djangoproject.com/en/4.0/ref/models/fields/…). Commented May 8, 2022 at 9:01
  • I tried to fix the obvious syntax errors in the broken indentation but I obviously had to guess; please review. Perhaps the easiest fix is to replace it with your actual code. On the desktop version of this site, you can get code marked up for you by pasting your code, selecting the pasted block, and typing ctrl-K. Commented May 9, 2022 at 10:21
  • Does this answer your question? Django. How to annotate a object count from a related model Commented May 9, 2022 at 13:33

2 Answers 2

0

@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.

Sign up to request clarification or add additional context in comments.

12 Comments

after implementing both solutions I am getting AttributeError at /forum 'Post' object has no attribute 'replies_set' error
I try to do like this in my forum.html ` <div class="px-4 pt-3">{{ post.count_replies }} &nbsp;&nbsp; {{post.timestamp}} </div> `
Adding a prefetch_related will not optimize anything here, the count method is a separate query regardless. Also if you feel that you just need to edit a bit of the code you can always propose an edit...
I add code in model.py like this def count_replies(self): return self.replies_set.count()
If I am not writing self in def count_replies() function then in VS code It shows me " self " is not defined
|
0

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>

15 Comments

thank you eega! I have a doubts what about view.py I have to make changes in view.py also or not. According to your answer I have try as: I put def count_replies(): return self.replies_set.count() in POST model and post.count_replies() in forum.py but I got an error **Could not parse the remainder: '()' from 'post.count_replies()' **
@Ajay Show your template file where you are using it?
I assume you have used {{ post.count_replies() }} in your template, but it should be {{ post.count_replies }}. However, this is really reading tea leaves without you providing forum.html.
Regarding view.py- no, nothing has to change here as far as I can tell, as you are already providing the posts as context to the template.
But after implementing your solution I got an error message Could not parse the remainder: '()' from 'post.count_replies()
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.