Adding a comment system is a common requirement in many web applications—such as blogs, news portals, forums, and e-learning platforms. While Django does not include a built-in comment framework out of the box anymore (it used to have one in older versions), it’s simple to implement a custom comments system.
This guide will walk you through building a comment system from scratch with models, views, templates, and admin integration.
What Will You Learn?
-
How to define a comment model
-
How to associate comments with posts (or any object)
-
How to create forms for submitting comments
-
How to display comments
-
How to handle nested (threaded) comments (optional)
-
Best practices and common pitfalls
Step 1: Define Models
Let's assume we already have a Post
model. We will now create a Comment
model to relate to it.
models.py
from django.db import models
from django.utils import timezone
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
created_at = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField()
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=True) # Useful for moderation
def __str__(self):
return f'Comment by {self.name} on {self.post}'
Run migrations:
python manage.py makemigrations
python manage.py migrate
Step 2: Create a Comment Form
forms.py
from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['name', 'email', 'body']
Step 3: Create Views to Handle Comments
views.py
from django.shortcuts import render, get_object_or_404, redirect
from .models import Post
from .forms import CommentForm
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id)
comments = post.comments.filter(active=True)
new_comment = None
if request.method == 'POST':
form = CommentForm(data=request.POST)
if form.is_valid():
# Create Comment object without saving to DB yet
new_comment = form.save(commit=False)
# Assign the current post to the comment
new_comment.post = post
new_comment.save()
return redirect('post_detail', post_id=post.id)
else:
form = CommentForm()
return render(request, 'blog/post_detail.html', {
'post': post,
'comments': comments,
'form': form,
'new_comment': new_comment
})
Step 4: Templates for Display and Submission
post_detail.html
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
<h3>Comments ({{ comments.count }})</h3>
{% for comment in comments %}
<div>
<p><strong>{{ comment.name }}</strong> ({{ comment.created_at|date:"F d, Y" }})</p>
<p>{{ comment.body }}</p>
</div>
{% empty %}
<p>No comments yet.</p>
{% endfor %}
<h3>Leave a comment</h3>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit Comment</button>
</form>
{% if new_comment %}
<p>Your comment has been added!</p>
{% endif %}
Add to urls.py
from django.urls import path
from . import views
urlpatterns = [
path('post/<int:post_id>/', views.post_detail, name='post_detail'),
]
Admin Configuration
To manage comments via the Django admin:
admin.py
from django.contrib import admin
from .models import Post, Comment
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'post', 'created_at', 'active')
list_filter = ('active', 'created_at')
search_fields = ('name', 'email', 'body')
admin.site.register(Post)
Optional: Nested (Threaded) Comments
If you want replies to comments, modify your Comment
model:
parent = models.ForeignKey('self', null=True, blank=True, related_name='replies', on_delete=models.CASCADE)
And in templates:
{% for comment in comments %}
<p>{{ comment.body }}</p>
{% for reply in comment.replies.all %}
<div style="margin-left: 30px;">{{ reply.body }}</div>
{% endfor %}
{% endfor %}
✅ Best Practices
Practice | Reason |
---|---|
✅ Use active field |
To allow moderation of comments |
✅ Use related_name |
Easier reverse querying like post.comments.all() |
✅ Sanitize input | Automatically handled by Django, but be cautious with rendering |
✅ Use pagination for large comment lists | For better performance |
✅ Validate email field | For anti-spam and future notifications |
❌ Common Pitfalls
Issue | Cause | Fix |
---|---|---|
Comments not saving | Form not valid | Check for validation errors using form.errors |
No comments showing | Using wrong related_name or filter |
Use post.comments.filter(active=True) |
XSS Vulnerabilities | Rendering raw input | Always use {{ comment.body }} instead of ` |
Flooding or spam | No rate limiting | Use Django middleware or captcha integration |
Summary
Django makes building a comment system easy and extensible. You can expand it with features like:
-
Replies (threaded comments)
-
Email notifications
-
Comment moderation workflows
-
User authentication integration
Complete File Structure Overview
blog/
├── models.py # Post and Comment models
├── forms.py # CommentForm
├── views.py # post_detail view
├── templates/
│ └── blog/
│ └── post_detail.html
├── admin.py # Admin customization
├── urls.py # URL routing