- How do I implement a commenting system on my own website?
- What are the options and possible solutions?
- Is there any ready-to-use user solutions( Django app)? With easy integration into my own website.
Commenting system for a website, using the Django framework
Introduction
Hi. If you, like me, are asking the same questions, like:
If so, you are in the right URL address.
In this article, I will describe my way of solving this kind of
problem, I mean commenting and replying on the website. And this system will allow
commenting, for guests, and for registered users.
Also, I want to make a note that my goal is not to make such a commenting system,
that no one has ever seen in their lives. The goal was to make commenting right fit
for my website, that's it.
And if this works for you too, great. If not, then I am sure that you always can
make some notes and learn something new for yourself.
Types and variations
How comments can be implemented? And what ways and variants to choose when implementing your own commenting system ?This is the question I asked myself when for the first time I needed to add commenting to the site.
-
I chose 2 main types of system commenting, based on who can leave comments, the rest are just variations and tips.
- Anonymous commenting (Any rogue will be able to leave a comment on your site)
- Authenticated commenting (Commenting for registered users only)
Anonymous commenting
This is the easiest to implement, but at the same time the most dangerous. Why dangerous?
Here, perhaps, I didn’t answer quite correctly, but my dangerous point was that absolutely any person or bot should leave comments. In absolutely any quantity and absolutely any quality.
You yourself understand that anonymity gives people the freedom to do and write whatever they want. And from my own experience I know
If a person has the opportunity to give a shit and even with impunity, those who wish to do so must be polite.
Authenticated commenting
Only registered users comment.
If you are creating an economic network, forum or online store, this is the comment form for you. Important note: Registration must provide some benefits to those who register.
I repeat again, if you have a blog, news portal or information portal, I do not recommend it.
What did I choose? And how it works for me.
I have a mixed one. Both anonymous and registered users can leave comments under posts.
Let's imagine you visited my website. You have read the article you are interested in and decided to leave a comment.
Scroll to the end, write what you think, and press the “Leave a comment” button.
A new form pops up when you click a button and asks you to enter your name. You entered it.
The message form has changed and your name has appeared.
You press, “Leave a comment” again. It appears on your page.
Now let's move on to the part where I explain step by step how to implement this commenting system.
Create a Comment model
Let's start with the Comment model. She will store all our comments.
-
What fields do we need?
- type ➜ field that indicates which article it relates to
- user ➜ what user left the comment (registered one)
- anonymous_user_name ➜ The name you chose for yourself
- anonymous_user_id ➜ Required to be able to create an anonymous comment from a non-anonymous one
- content ➜ actual content of comment
- timeCreated ➜ when it was written
Prepare comment and comments templates
-
In general, we need 3 templates, at least
- one for rendering one comment (will be used when you need to add a new comment)
- one for rendering a group of comments (will be used when they need to be loaded)
- and another one where these comments will be
Here is a template for one comment
{{com.content}}
{% if user.slug == com.user.slug and request.session.is_auth %}
{% endif %}
Let me clarify a couple of unclear points in this template.
Checking if a comment was written by an anonymous user
{% if not com.anonymous_user_id|length > 0%}
Checking whether the user who posted this comment has an avatar
{% if com.user.avatar|length > 0 %}
Now this piece of the template
{% if user.slug == com.user.slug and request.session.is_auth %}
{% endif %}
This is a pretty important piece, so to speak. In short, it says that only registered user
will be able to delete their comments.
In the comment template everything is absolutely the same as
for one comment. Except that it is needed to render multiple comments.
That is, it uses a for loop
Prepare a template where these comments will appear
Now let's consider the place where these comments will be displayed.
This is the code I use in base_post.html. That is, this is the template from which
I inherit all other posts (articles, cases, news). In short,
provides basic functionality for my articles. Below in the chapter "Completed application and other resources"
In truth, I use comments for more than just posts. Each registered
The user has his own account where he can view all the comments he left. And delete
if he wants.
Let's look at the part of base_post.html that is responsible for comments.
First comes the comment form itself.
...
{% csrf_token %}
Next there is a button to leave a comment. For authenticated users, she will simply leave a comment.
But for anonymous users, another button will be displayed (the button is still the same, it just has different functionality)
It will only ask for the anonymous name. After which, it will be replaced with a regular button for sending a comment.
For anonymous
{% if not request.session.is_auth %}
For registered
{% else %}
{% endif %}
Write an ajax request to leave a comment
Templates are of course good, but now we need to connect the server part with the front end. Let's write an ajax script
Don't forget to include it in base_post.html
{% block scripts %}
{% csrf_token %}
{% block scripts_post %}
{% endblock %}
{% endblock %}
A little clarification. First we include the jQuery library.
Then we save the csrf token and what post the user is viewing now
{% csrf_token %}
Next, we connect our script to make comments work (our ajax requests will be there)
And since templates for articles, news and cases are inherited from this template, it can be
separate scripts (or styles) are required.
That's why it's here.
{% block scripts_post %}
{% endblock %}
When the Leave a comment button is clicked (whether from an authorized or anonymous user)
This function is running.
function sendComment(path){
var post = post_slug
var about = $("#about").val()
var username = $("#comments_el__user_name_commentAdd").text()
$.ajax({
type: "POST",
url: "/" + language_code + "/" + path + "/",
data: {
'post': post,
'about': about,
'username': username
},
headers: {'X-CSRFToken': csrftoken},
mode: 'same-origin', // Do not send CSRF token to another domain.
success: function(result){
$(result).insertAfter("#comment_add")
$(".comment_remove__button").off()
$(".comment_remove__button").one('click', function() {
removeComment(this)
})
},
})
}
Where we are sending the name of the current post, message, and username to the server
var post = post_slug
var about = $("#about").val()
var username = $("#comments_el__user_name_commentAdd").text()
If successful, we render comment.html and paste the resulting result above all comments.
And don’t forget to add an event to be able to delete this comment.
$(result).insertAfter("#comment_add")
$(".comment_remove__button").off()
$(".comment_remove__button").one('click', function() {
removeComment(this)
})
Write an ajax request to load comments
Uploading comments to the server is great, but other users should also see
comments. Here is a function to display comments for a single post.
function loadComments(){
var post = post_slug
$.ajax({
type: "POST",
url: "/" + language_code + "/load_comments/",
data: {
'post': post,
'number': number,
'offset': offset,
},
headers: {'X-CSRFToken': csrftoken},
mode: 'same-origin', // Do not send CSRF token to another domain.
success: function(result){
$(result).insertBefore("#scroll-sentinel")
$(".comment_remove__button").off()
$(".comment_remove__button").one('click', function() {
removeComment(this)
})
offset = offset + number
},
})
}
It is important to note that this function is triggered when the scroll bar has reached the end.
For this purpose, a special observer is used, who monitors this.
$(document).ready(function(){
default_avatar_path = $("#comments_el__user_avatar_commentAdd").attr('src')
const observer = new IntersectionObserver((entries, observer) => {
// Loop through the entries
for (const entry of entries) {
// Check if the entry is intersecting the viewport
if (entry.isIntersecting) {
// Load more content
loadComments()
}
}
});
const scrollSentinel = document.querySelector("#scroll-sentinel");
observer.observe(scrollSentinel);
...
Write an ajax request to delete a comment
And a small bonus. How to delete comments.
From the previous sections, you saw how and where events are assigned to delete a comment.
Now let's see how it is removed.
function removeComment(toRemove){
var post = post_slug
var comment_id = $(toRemove).attr("commentid")
$.ajax({
type: "POST",
url: "/" + language_code + "/remove_comment/",
data: {
'post': post,
'comment_id': comment_id,
},
headers: {'X-CSRFToken': csrftoken},
mode: 'same-origin', // Do not send CSRF token to another domain.
success: function(result){
// Removes only client side part
$(toRemove).parent().remove()
},
})
}
As you noticed, to delete a comment you need its id. This is a custom attribute that we
we fill in when we render the comment or comments.
Here he is
{% if user.slug == com.user.slug and request.session.is_auth %}
Setting up views and paths
Almost everything is ready. All that remains is to figure out the routes and the performances themselves.
Comment/urls.py looks like this:
from django.urls import path
from .views import *
urlpatterns = [
path('send_comment_guesting/', send_comment_guesting),
path('send_comment_authorized/', send_comment_authorized),
path('remove_comment/', remove_comment),
path('prepare_user/', prepare_user),
path('load_comments/', load_comments),
path('load_comments_by_user/', load_comments_by_user),
]
Let's look at each presentation separately. Except, of course, load_comments_by_user.
You can watch this function yourself if you want. We are reviewing comments
using the example of posts.
def send_comment_guesting(request):
media_root = settings.MEDIA_URL
if request.method == 'POST':
type = Post.objects.filter(slug=request.POST['post']).get()
content = request.POST['about']
username = request.POST['username']
user_id = request.session.session_key
comment = Comment(
anonymous_user_id=user_id,
anonymous_user_name=username,
type=type,
content=content
)
comment.save()
context = {
'com': comment,
'media_root': media_root,
}
return render(request, "Comment/comment.html", context=context)
We save the comment as anonymous пользователь. Where:
- type ➜ post to which the comment belongs
- content ➜ content of comment
- username ➜ anonymous user name
- user_id ➜ current session id
Next, a record is created in the database and the page with the comment is rendered.
def send_comment_authorized(request):
media_root = settings.MEDIA_URL
if request.method == 'POST':
user = User.objects.filter(name=request.session.get('username')).get()
type = Post.objects.filter(slug=request.POST['post']).get()
content = request.POST['about']
comment = Comment(user=user, type=type, content=content)
comment.save()
context = {
'com': comment,
'user': user,
'media_root': media_root,
}
return render(request, "Comment/comment.html", context=context)
Save the comment as authorized. Almost everything is the same, only here
the user object who added the comment is added.
def prepare_user(request):
data = {
'isValid': False,
'username': None,
}
if request.method == "GET":
username = request.GET['username']
data['isValid'] = True
data['username'] = username
return JsonResponse(data)
You can consider this function a service function, because the only task of this function is
insert the username into the form to create a comment.
def remove_comment(request):
if request.method == 'POST':
comment_id = request.POST['comment_id']
comment = Comment.objects.filter(id=comment_id).get()
comment.delete()
status = 200
return JsonResponse({}, status=status)
Delete a comment by id.
def load_comments(request):
try:
user = User.objects.filter(name=request.session.get('username')).get()
except:
user = None
media_root = settings.MEDIA_URL
if request.method == 'POST':
number = request.POST.get('number', 5)
offset = request.POST.get('offset', 0)
type = Post.objects.filter(slug=request.POST['post']).get()
comments = Comment.objects.filter(type=type).order_by('-timeCreated')[(int(offset)):(int(number)) + (int(offset))]
context = {
'comments': comments,
'user': user,
'media_root': media_root,
}
return render(request, "Comment/comments.html", context=context)
Loading comments.
First, we check whether an authorized user is asking for a download or not.
try:
user = User.objects.filter(name=request.session.get('username')).get()
except:
user = None
Next, we determine how many, from which comment and from which article to load comments.
number = request.POST.get('number', 5)
offset = request.POST.get('offset', 0)
type = Post.objects.filter(slug=request.POST['post']).get()
comments = Comment.objects.filter(type=type).order_by('-timeCreated')[(int(offset)):(int(number)) + (int(offset))]
Then we render the found comments and return them to the user. It returns here:
function loadComments():
...
success: function(result){
$(result).insertBefore("#scroll-sentinel")
$(".comment_remove__button").off()
$(".comment_remove__button").one('click', function() {
removeComment(this)
})
offset = offset + number
},
Where we insert the received comments into the page, increase the counter and assign an event for deletion
comment.
Completed application and other resources
Post Scriptum. Conclusion
So, now that the article is over, I would like to apologize for the fact that the article did not turn out the way it should have.
go out. I'll explain now.
Initially, I planned to talk about 3 possible options for writing comments.
Such as:
- Anonymous
- Via social networks
- Only for registered users
But alas, as soon as I realized how much time it would take me, I decided to cool my ardor a little. And write an article
specifically about my journey of writing a commenting system. That is, a combined version of anonymous/registered
commenting.
I also wanted to insert comment forms for each type. That is, start with the simplest, then move on to
commenting through social networks, and end it all with the fact that only registered users will be able to comment.
Which, by the way, I don’t have yet.
Maybe someday I will write this article in the form I wanted, but for now we have what we have.
5
Used termins
- Django views ⟶ In Django, views are a fundamental component that handle the logic of your web application. They process incoming web requests, interact with models (to retrieve or modify data), and return an HTTP response. In essence, views determine what data gets displayed and how it is presented by associating URLs with Python functions or classes.
- Django framework ⟶ Is a high-level, open-source web framework for building web applications using the Python programming language. It follows the model-view-template (MVT) architectural pattern and is designed to facilitate rapid development while promoting clean, pragmatic design.
- Website ⟶ Is defined as a collection of related web pages that are typically identified by a common domain name and published on at least one web server. Websites can serve various purposes and can include anything from personal blogs to business sites, e-commerce platforms, or informational resources.
- Django template ⟶ This is a text document marked up with a special syntax for inserting new code.
- Django model ⟶ Is a database manager used in the Django framework. Implemented via classes and inheritance in python.
Related questions
- I can’t stand Django template language. Do I have to use it? I think this template engine is the best thing ever, but I know that choosing a template language runs close to religion. There’s nothing about Django that requires using the template language, so if you’re attached to Jinja2, Mako, or whatever it's ok.
- How can I see the raw SQL queries Django is running? Make sure your Django DEBUG setting is set to True. Then import connection from django.db. connection.queries is only available if DEBUG is True. It’s a list of dictionaries in order of query execution. Each dictionary has the sql and time property.
- If I make changes to a model, how do I update the database? Take a look at Django’s support for schema migrations. If you don’t mind clearing data, your project’s manage.py utility has a flush option to reset the database to the state it was in immediately after migrate was executed.