How To Create A Comment Section For Your Django Blog!

Add reaction Like Unicorn Exploding Head Raised Hands Fire Jump to Comments Save Boost More... Copy link Copy link Copied to Clipboard Share to X Share to LinkedIn Share to Facebook Share to Mastodon Share Post via... Report Abuse

Hi!

This mini-tutorial will focus on creating a Comment Section for adding (without update/delete) comments to each blog post. We will implement this feature while using a class-based view, namely our BlogPost DetailView, mine's looks like this:

# MainApp/models.py class BlogPost(models.Model): title = models.CharField(max_length=100) subtitle = models.CharField(max_length=200, blank=True, null=True) content = models.TextField() date_posted = models.DateTimeField(default=timezone.now) author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) image = models.ImageField(upload_to='blog_images', storage=gd_storage, null=True, blank=True) def __str__(self): return self.author.username + ', ' + self.title[:40] def get_absolute_url(self): return reverse('blogpost-detail', kwargs={'pk': self.pk}) # MainApp/views.py class BlogPostDetailView(DetailView): model = BlogPost # template_name = MainApp/BlogPost_detail.html # context_object_name = 'object' Enter fullscreen mode Exit fullscreen mode

First things first: we need to create our BlogComment model in our models.py. We can personalize our model in any way we want, I'll stick to the basics of a comment and will add the following fields: author, content/body, and a date (when the comment was posted):

# models.py from django.db import models from django.utils import timezone from django.contrib.auth.models import User class BlogComment(models.Model): blogpost_connected = models.ForeignKey( BlogPost, related_name='comments', on_delete=models.CASCADE) author = models.ForeignKey(User, on_delete=models.CASCADE) content = TextField() date_posted = models.DateTimeField(default=timezone.now) def __str__(self): return str(self.author) + ', ' + self.blogpost_connected.title[:40] Enter fullscreen mode Exit fullscreen mode
  • Every BlogComment will have an id (foreign key) of its BlogPost (a BlogPost can have multiple BlogComments), and if a BlogPost is deleted, then all the BlogComments that were linked to that BlogPost will be deleted (on_delete == CASCADE).
  • The author will also be a foreign key to the whole User object (that has its User id, username, email, etc), therefore a User can have multiple comments.
  • We will also add the magic method str() to view the comments in a more readable way (instead of viewing the object type) when we are making queries from our CLI/Admin panel).

Database Diagram for our comment

In models.py, in our BlogPost model, we can also write a function that will return the number of comments of a blog post:

# models.py class BlogPost(models.Model): title = models.CharField(max_length=100) ... @property def number_of_comments(self): return BlogComment.objects.filter(blogpost_connected=self).count() Enter fullscreen mode Exit fullscreen mode

After every change in the models.py file, we need to open our terminal and make the migrations to our database:

# CLI/Terminal >> cd C:\Projects\...\YourDjangoAppMainFolder >> python manage.py makemigrations >> python manage.py migrate Enter fullscreen mode Exit fullscreen mode

Let's create a new form class: In the same folder as our models.py, create a new file named forms.py, where we'll write the following:

# forms.py from django import forms from .models import BlogComment class NewCommentForm(forms.ModelForm): class Meta: model = BlogComment fields = ['content'] Enter fullscreen mode Exit fullscreen mode

Très bien, now we can add to our get_context_data function within our class-based view BlogPost DetailView, in views.py, the following:

# views.py from .models import BlogPost, BlogComment from .forms import NewCommentForm class BlogPostDetailView(DetailView): model = BlogPost # template_name = MainApp/BlogPost_detail.html # context_object_name = 'object' def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) comments_connected = BlogComment.objects.filter( blogpost_connected=self.get_object()).order_by('-date_posted') data['comments'] = comments_connected if self.request.user.is_authenticated: data['comment_form'] = NewCommentForm(instance=self.request.user) return data Enter fullscreen mode Exit fullscreen mode

Here we will retrieve all the comments from our current BlogPost object, store them (the query) in a local variable comments_connected, then send it further as a context to our HTML-based blogpost_detail.

However, in order to post comments directly from our class-based BlogPost DetailView, we also need to define a post method to receive the context from our form (situated in this view/html). Therefore, in the same class, we need to add:

# views.py class BlogPostDetailView(DetailView): ... def get_context_data(self, **kwargs): ... def post(self, request, *args, **kwargs): new_comment = BlogComment(content=request.POST.get('content'), author=self.request.user, blogpost_connected=self.get_object()) new_comment.save() return self.get(self, request, *args, **kwargs) Enter fullscreen mode Exit fullscreen mode

Finally, in our blogpost_detail.html let's write in the DjangoTemplateLanguage the following:

<!-- COMMENTS --> <h2>Leave your comment!</h2> <div id="comments_section"> {% if user.is_authenticated %} <form method="POST"> {% csrf_token %} <div class="form-group"> {{ comment_form }} <button class="btn btn-info" type="submit">Add comment <i class="fas fa-comments"></i></button> </div> </form> {% else %} <a class="btn btn-outline-info" href="{% url 'login' %}?next={{request.path}}">Log in to add a comment!</a><br> {% endif %} {% if comments %} <strong class="text-secondary">{{ object.number_of_comments }} Comment{{ object.number_of_comments|pluralize }}</strong> <hr> <ul> {% for comment in comments %} <li> <div> <span> <strong class="text-info">{{ comment.author }} </strong> <small class="text-muted">{{ comment.date_posted }}</small> </span> <p> {{ comment.content|safe }} </p> </div> </li> {% endfor %} </ul> {% else %} <strong class="text-secondary">No comments yet...</strong> {% endif %} </div> Enter fullscreen mode Exit fullscreen mode

Sooo, there's a lot of code there, let's go through some parts of it step by step:

  • The first thing that we do is to check if the user is authenticated: if True, then show the user a form where he can write the content of his new comment. if False, then show the user a button that redirects him to the Login page. Also, it's important that after a user logs into his account to redirect him to the earlier blog post that he wanted to post a comment, so we'll add to our redirect link "?next={{request.path}}" where request.path is the current page path (e.g. localhost/blogpost/7)
  • Then we check if our current blogpost has any comments, if not, we'll put in our HTML "No comments yet..", but if we have any comments, we will then write the number of comments and then we will loop through each of them and show its author, date, and content.

Perfect, so we are almost done! However, this comment section will look rather dull... Therefore we need to create some styling, that we can find on Google or... here (thanks to bootdey.com)! To integrate it, we just need to add the CSS code in our blogpost_detail.html and the corresponding tags in our <div> and <ul> (list) sections, following their example.

Nice, now we are done! Hope you will find this useful. 😁 You can see a live example of this comment section on my blog: codingtranquillity.herokuapp.com... where you can also find more articles like this!

Have a nice day and... Happy coding! R.B.

pic Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Submit Preview Dismiss Collapse Expand madeleineatkins profile image Madeleine Atkins Madeleine Atkins Madeleine Atkins Follow
  • Joined Mar 9, 2022
Mar 9 '22 Dropdown menu
  • Copy link
  • Hide

Thanks for sharing this wonderful post. Homepage

Collapse Expand radualexandrub profile image Radu-Alexandru B Radu-Alexandru B Radu-Alexandru B Follow Passionate about Software/Full-Stack/Application Development and Machine Learning.
  • Location Bucharest, Romania
  • Education Computer Science & Master's Degree in Computer Vision
  • Work Software Developer
  • Joined Sep 21, 2020
Mar 9 '22 Dropdown menu
  • Copy link
  • Hide

Glad that I helped you!

Welcome de DEV Community, have a wonderful week!

Collapse Expand raybesiga profile image Ray Besiga Ray Besiga Ray Besiga Follow
  • Joined Jan 11, 2018
Nov 16 '20 Dropdown menu
  • Copy link
  • Hide

Hi @Radu,

Great article but I just want to point out a few issues. The first is in your BlogPostDetailView

def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) comments_connected = BlogComment.objects.filter( blogpost_connected=self.get_object()).order_by('-date_posted') Enter fullscreen mode Exit fullscreen mode

This is bound to throw a Type Error. In this case, it will be an object not callable error.

The other issue I see arising is with the post:

def post(self, request, *args, **kwargs): new_comment = BlogComment(content=request.POST.get('content'), author=self.request.user, blogpost_connected=self.get_object()) Enter fullscreen mode Exit fullscreen mode

The blogpost_connected should be an instance of the BlogPost and not the BlogComment. I hope this is helpful. Best regards.

Collapse Expand ninanolets profile image Nina Noleto Nina Noleto Nina Noleto Follow
  • Location Berlin, Germany
  • Joined Nov 17, 2020
Nov 17 '20 Dropdown menu
  • Copy link
  • Hide

Hey, thanks for this mini tutorial! I appreciate that it's detailed but simple. Made an account here just to say thanks, it kinda saved me today :)

Collapse Expand radualexandrub profile image Radu-Alexandru B Radu-Alexandru B Radu-Alexandru B Follow Passionate about Software/Full-Stack/Application Development and Machine Learning.
  • Location Bucharest, Romania
  • Education Computer Science & Master's Degree in Computer Vision
  • Work Software Developer
  • Joined Sep 21, 2020
Nov 17 '20 Dropdown menu
  • Copy link
  • Hide

Thank you!

I like to break the whole code into smaller steps, without puzzling the viewer with extra complexity or missing code parts. However, it's hard to find the extra time to write those kinds of posts, but appreciations like these motivate me.

Welcome to DEV Community!

Collapse Expand ianrmaher profile image IanRMaher IanRMaher IanRMaher Follow
  • Joined Mar 3, 2021
Mar 7 '21 Dropdown menu
  • Copy link
  • Hide

Thanks for sharing this post with us I was searching for these tip from many days then my best friend suggests me your post now she need help regarding dissertation proofreading services and I want to help her. So if anyone knows about this please let me know.

Collapse Expand leandropaolo1 profile image leandropaolo1 leandropaolo1 leandropaolo1 Follow
  • Joined Jul 21, 2021
Jul 22 '21 Dropdown menu
  • Copy link
  • Hide

great tutorial, awesome code. Thanks man, you made my day. I also made an account today just to leave you a message. PS it never happens

Collapse Expand simfpayn profile image simfpayn simfpayn simfpayn Follow
  • Joined Apr 11, 2022
Apr 11 '22 Dropdown menu
  • Copy link
  • Hide

Students should check for certain terms while examining the best cv writing service london as some essay reviews regularly utilize keywords such as me and I, signaling that they are aiming to generate credibility and obtain confidence. Because they want to be honest, genuine customers or users prefer nouns.

Collapse Expand jiraiyasennin profile image Dostow**-> Dostow**-> Dostow**-> Follow Web developer, Sys Admin and Technical Support L2 ᕙ(`▿´)ᕗ
  • Location Ávila. Spain.
  • Work Technical Support Engineer - Backend Developer - Tehcnical Support L2
  • Joined Oct 15, 2020
Oct 15 '20 Dropdown menu
  • Copy link
  • Hide

What if i don't need the user to be logged in? I'm working in a comment system for my personal site without a registration system :-) Thanks.

Collapse Expand radualexandrub profile image Radu-Alexandru B Radu-Alexandru B Radu-Alexandru B Follow Passionate about Software/Full-Stack/Application Development and Machine Learning.
  • Location Bucharest, Romania
  • Education Computer Science & Master's Degree in Computer Vision
  • Work Software Developer
  • Joined Sep 21, 2020
Oct 15 '20 Dropdown menu
  • Copy link
  • Hide

Hmm... I didn't think about this option. But the process should be even simpler than the instructions I've written here.

Firstly, in our models.py we don't need an author based on User anymore, we will just use TextField():

# models.py from django.db import models from django.utils import timezone class BlogComment(models.Model): blogpost_connected = models.ForeignKey( BlogPost, related_name='comments', on_delete=models.CASCADE) author = TextField() content = TextField() date_posted = models.DateTimeField(default=timezone.now) def __str__(self): return self.blogpost_connected.title[:40] Enter fullscreen mode Exit fullscreen mode

[Optional] We can still return the number of comments if we want in our main BlogPost class model in models.py:

# models.py class BlogPost(models.Model): title = models.CharField(max_length=100) ... @property def number_of_comments(self): return BlogComment.objects.filter(blogpost_connected=self).count() Enter fullscreen mode Exit fullscreen mode

Make the changes to our database:

# CLI/Terminal >> cd C:\Projects\...\YourDjangoAppMainFolder >> python manage.py makemigrations >> python manage.py migrate Enter fullscreen mode Exit fullscreen mode

Create the same forms.py with an author:

# forms.py from django import forms from .models import BlogComment class NewCommentForm(forms.ModelForm): class Meta: model = BlogComment fields = ['author', 'content'] Enter fullscreen mode Exit fullscreen mode

And now in views.py we don't need to check if self.request.user.is_authenticated anymore, we will just send the form as context to our HTML-based blogpost_detail.html.

# views.py from .models import BlogPost, BlogComment from .forms import NewCommentForm class BlogPostDetailView(DetailView): model = BlogPost # template_name = MainApp/BlogPost_detail.html # context_object_name = 'object' def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) comments_connected = BlogComment.objects.filter( blogpost_connected=self.get_object()).order_by('date_posted') data['comments'] = comments_connected data['comment_form'] = NewCommentForm( ) return data def post(self, request, *args, **kwargs): new_comment = BlogComment(content=request.POST.get('content'), author=request.POST.get('author'), blogpost_connected=self.get_object()) new_comment.save() return self.get(self, request, *args, **kwargs) Enter fullscreen mode Exit fullscreen mode

And finally in our blogpost_detail.html we'll show directly the forms without checking if user.is_authenticated.

<!-- COMMENTS --> <h2>Leave your comment!</h2> <div id="comments_section"> <form method="POST"> {% csrf_token %} <div class="form-group"> {{ comment_form }} <button class="btn btn-info" type="submit">Add comment <i class="fas fa-comments"></i></button> </div> </form> {% if comments %} <strong class="text-secondary">{{ object.number_of_comments }} Comment{{ object.number_of_comments|pluralize }}</strong> <hr> <ul> {% for comment in comments %} <li> <div> <span> <strong class="text-info">{{ comment.author }} </strong> <small class="text-muted">{{ comment.date_posted }}</small> </span> <p> {{ comment.content|safe }} </p> </div> </li> {% endfor %} </ul> {% else %} <strong class="text-secondary">No comments yet...</strong> {% endif %} </div> Enter fullscreen mode Exit fullscreen mode

Hope all of these will work. Good Luck!

Collapse Expand tejkrishna profile image tejkrishna tejkrishna tejkrishna Follow
  • Joined Dec 29, 2020
Dec 29 '20 • Edited on Dec 29 • Edited Dropdown menu
  • Copy link
  • Hide

Thanks for publishing Python information. This is very understanding and useful to me

Collapse Expand jiraiyasennin profile image Dostow**-> Dostow**-> Dostow**-> Follow Web developer, Sys Admin and Technical Support L2 ᕙ(`▿´)ᕗ
  • Location Ávila. Spain.
  • Work Technical Support Engineer - Backend Developer - Tehcnical Support L2
  • Joined Oct 15, 2020
Oct 15 '20 Dropdown menu
  • Copy link
  • Hide

Amazing!! Thank you a lot!! It's more or less how i thought it should be, now i can continue with the project :-D

Collapse Expand salim1421 profile image salim1421 salim1421 salim1421 Follow
  • Joined Aug 6, 2024
Oct 17 '24 Dropdown menu
  • Copy link
  • Hide

Thanks Man

Collapse Expand trodojastine profile image Jastine Trodo Jastine Trodo Jastine Trodo Follow
  • Joined Feb 8, 2022
May 26 '22 Dropdown menu
  • Copy link
  • Hide

masterbundles.com/stock-content/ve...

Collapse Expand imbiru143 profile image Birendra Bohara Birendra Bohara Birendra Bohara Follow
  • Joined Nov 25, 2020
Nov 25 '20 Dropdown menu
  • Copy link
  • Hide

what should i do if i have to use same comment model in different place

View full discussion (17 comments) Code of Conduct Report abuse

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink.

Hide child comments as well

Confirm

For further actions, you may consider blocking this person and/or reporting abuse

Radu-Alexandru B Follow Passionate about Software/Full-Stack/Application Development and Machine Learning.
  • Location Bucharest, Romania
  • Education Computer Science & Master's Degree in Computer Vision
  • Work Software Developer
  • Joined Sep 21, 2020
How to add Like/Unlike button to your Django Blog #django #python #blog How to add sitemap.xml to your Django Blog and make it work on Heroku (SEO) #django #heroku #sitemap #seo
DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

Tag » Add Comment Section In Django