Tutorial¶
This tutorial guides you through the steps to use every feature of django-comments-xtd together with the Django Comments Framework. The Django project used throughout the tutorial is available to download. Following the tutorial will take about an hour, and it is highly recommended to get a comprehensive understanding of django-comments-xtd.
Introduction¶
Through the following sections the tutorial will cover the creation of a simple blog with stories to which we will add comments, exercising each and every feature provided by both, django-comments and django-comments-xtd, from comment post verification by mail to comment moderation and nested comments.
Preparation¶
Before we install any package we will set up a virtualenv:
mkdir ~/django-comments-xtd-tutorial
cd ~/django-comments-xtd-tutorial
python3.12 -m venv venv
source venv/bin/activate
And we will install everything we need in it:
pip install django-comments-xtd
wget https://github.com/danirus/django-comments-xtd/raw/master/example/tutorial.tar.gz
tar -xvzf tutorial.tar.gz
cd tutorial
By installing django-comments-xtd we install all its dependencies, Django, django-contrib-comments and django-rest-framework among them. So we are ready to work on the project.
Take a look at the content of the tutorial directory, it contains:
A blog app with a Post model. It uses two generic class-based views to list the posts and show a post in detail.
The templates directory, with a base.html and home.html, and the templates for the blog app: blog/post_list.html and blog/post_detail.html.
The static directory with a css/bootstrap.min.css file (this file is a static asset available, when the app is installed, under the path django_comments_xtd/css/bootstrap.min.css).
The tutorial directory containing the settings and urls modules.
And a fixtures directory with data files to create the admin superuser (with admin password), the default site and some blog posts.
Let’s finish the initial setup, load the fixtures and run the development server:
python manage.py migrate
python manage.py loaddata fixtures/*.json
python manage.py runserver
Head to http://localhost:8000 and visit the tutorial site.
Important
Remember to implement the get_absolute_url
method in the model of those
objects that will receive comments, like the class Post
in this
tutorial. It is so because the permanent URL of each comment uses the
shortcut
view of django.contrib.contenttypes
which in turn uses
the get_absolute_url
method.
Configuration¶
Now that the project is running we are ready to add comments. Edit the settings
module, tutorial/settings.py
, and add django_comments_xtd
and django_comments
to INSTALLED_APPS
:
INSTALLED_APPS = [
...
'django_comments_xtd',
'django_comments',
'blog',
]
In addition to that insert the following settings to tell django_comments
that the application handling comments will actually be django_comments_xtd
. We also want to configure some email related settings:
# Tell django.contrib.comments that the application
# handling the comments is django-comments-xtd:
COMMENTS_APP = 'django_comments_xtd'
# Either enable sending mail messages to the console:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Or set up the EMAIL_* settings so that Django can send emails:
EMAIL_HOST = "smtp.example.com"
EMAIL_PORT = "587"
EMAIL_HOST_USER = "alias@example.com"
EMAIL_HOST_PASSWORD = "yourpassword"
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = "Helpdesk <helpdesk@example.com>"
Edit the urls module of the project, tutorial/tutorial/urls.py
and mount
the URL patterns of django_comments_xtd in the path /comments/
. The urls
installed with django_comments_xtd include django_comments’ urls too:
from django.urls import include, path
urlpatterns = [
...
path(r'comments/', include('django_comments_xtd.urls')),
...
]
Now let Django create the tables for the two new applications:
python manage.py migrate
Log in the admin site,
localhost:8000/admin/
(user: admin
, password: admin
), and be sure that the domain field of
the Site
instance points to the correct domain, which for the development
server is expected to be localhost:8000
.
The value is used to create comment verifications, follow-up cancellations,
etc. Update the value if you were using a different a domain or port other than localhost:8000
.
Moderation¶
One of the differences between django-comments-xtd and other commenting applications is the fact that by default it requires comment confirmation by email when users are not logged in, a very effective feature to discard unwanted comments. However there might be cases in which you would prefer a different approach. The Django Comments Framework comes with moderation capabilities included, on top of which you can build your own comment filtering.
Comment moderation is often established to fight spam, but can be used for other purposes, like triggering actions based on comment content, or rejecting comments based on how old is the subject being commented.
In this section we want to set up comment moderation for our blog application,
so that comments sent to a blog post older than a year will be automatically
flagged for moderation. Also we want Django to send an email to registered
MANAGERS
of the project when the comment is flagged.
Let’s start adding our email address to the MANAGERS
in the
tutorial/settings.py
module:
MANAGERS = (
('Joe Bloggs', 'joe.bloggs@example.com'),
)
Now we will create a new Moderator
class that inherits from Django Comments
Framework’s CommentModerator
. This class enables moderation by defining a
number of class attributes. Read more about it in moderation options, in the official documentation of the Django Comments
Framework.
We will also register our Moderator
class with the django-comments-xtd’s
moderator
object. We use django-comments-xtd’s object instead of
django-contrib-comments’ because we still want to have confirmation by email
for non-registered users, nested comments, follow-up notifications, etc.
Let’s add those changes to the blog/model.py
file:
...
# Append these imports below the current ones.
from django_comments.moderation import CommentModerator
from django_comments_xtd.moderation import moderator
...
# Add this code at the end of the file.
class PostCommentModerator(CommentModerator):
email_notification = True
auto_moderate_field = 'publish'
moderate_after = 365
moderator.register(Post, PostCommentModerator)
That makes it, moderation is ready. Visit any of the blog posts with a
publish
datetime older than a year and try to send a comment. After
confirming the comment you will see the django_comments_xtd/moderated.html
template, and your comment will be put on hold for approval.
If on the other hand you send a comment to a blog post created within the last year (login in the admin interface and update the publish field of the post) your comment will not be put in moderation. Give it a try as a logged in user and as an anonymous user.
When sending a comment as a logged-in user the comment won’t have to be confirmed and will be put in moderation immediately. However, when you send it as an anonymous user the comment will have to be confirmed by clicking on the confirmation link, immediately after that the comment will be put on hold pending for approval.
In both cases, due to the attribute email_notification = True
above, all
mail addresses listed in the MANAGERS
setting will receive a
notification about the reception of a new comment. If you did not received
such message, you might need to review your email settings, or the console
output. Read about the mail settings above in the Configuration section.
The mail message received is based on the
comments/comment_notification_email.txt
template provided with
django-comments-xtd.
A last note on comment moderation: comments pending for moderation have to be reviewed and eventually approved. Don’t forget to visit the comments-xtd app in the admin interface. Filter comments by is public: No and is removed: No. Tick the box of those you want to approve, choose Approve selected comments in the action dropdown, at the top left of the comment list, and click on the Go button.
Disallow black listed domains¶
In case you wanted to disable comment confirmation by mail you might want to set up some sort of control to reject spam.
This section goes through the steps to disable comment confirmation while enabling a comment filtering solution based on Joe Wein’s blacklist of spamming domains. We will also add a moderation function that will put in moderation comments containing badwords.
Let us first disable comment confirmation. Edit the tutorial/settings.py
file and add:
COMMENTS_XTD_CONFIRM_EMAIL = False
django-comments-xtd comes with a Moderator class that inherits from
CommentModerator
and implements a method allow
that will do the
filtering for us. We just have to change blog/models.py
and replace
CommentModerator
with SpamModerator
, as follows:
# Remove the CommentModerator imports and leave only this:
from django_comments_xtd.moderation import moderator, SpamModerator
# Our class Post PostCommentModerator now inherits from SpamModerator
class PostCommentModerator(SpamModerator):
...
moderator.register(Post, PostCommentModerator)
Now we can add a domain to the BlackListed
model in the admin interface.
Or we could download a blacklist from Joe Wein’s website and load the table
with actual spamming domains.
Once we have a BlackListed
domain, try to send a new comment and use an
email address with such a domain. Be sure to log out before trying, otherwise
django-comments-xtd will use the logged in user credentials and ignore the
email given in the comment form.
Sending a comment with an email address of the blacklisted domain triggers a
Comment post not allowed response, which would have been a HTTP 400 Bad
Request response with DEBUG = False
in production.
Moderate on bad words¶
Let’s now create our own Moderator class by subclassing SpamModerator
. The
goal is to provide a moderate
method that looks in the content of the
comment and returns False
whenever it finds a bad word in the message. The
effect of returning False
is that comments’ is_public
attribute will be
put to False
and therefore the comment will be in moderation.
The blog application comes with a bad word list in the
file blog/badwords.py
.
We assume we already have a list of BlackListed
domains and we don’t need
further spam control. So we will disable comment confirmation by email. Edit
the settings.py
file:
COMMENTS_XTD_CONFIRM_EMAIL = False
Now edit blog/models.py
and add the code corresponding to our new
PostCommentModerator
:
# Below the other imports:
from django_comments_xtd.moderation import moderator, SpamModerator
from blog.badwords import badwords
...
class PostCommentModerator(SpamModerator):
email_notification = True
def moderate(self, comment, content_object, request):
# Make a dictionary where the keys are the words of the message
# and the values are their relative position in the message.
def clean(word):
ret = word
if word.startswith('.') or word.startswith(','):
ret = word[1:]
if word.endswith('.') or word.endswith(','):
ret = word[:-1]
return ret
lowcase_comment = comment.comment.lower()
msg = dict([
(clean(w), i)
for i, w in enumerate(lowcase_comment.split())
])
for badword in badwords:
if isinstance(badword, str):
if lowcase_comment.find(badword) > -1:
return True
else:
lastindex = -1
for subword in badword:
if subword in msg:
if lastindex > -1:
if msg[subword] == (lastindex + 1):
lastindex = msg[subword]
else:
lastindex = msg[subword]
else:
break
if msg.get(badword[-1]) and msg[badword[-1]] == lastindex:
return True
return super(PostCommentModerator, self).moderate(
comment, content_object, request
)
moderator.register(Post, PostCommentModerator)
Now we can try to send a comment with any of the bad words listed in badwords.
After sending the comment we will see the content of the
django_comments_xtd/moderated.html
template and the comment will be put in
moderation.
If you enable comment confirmation by email, the comment will be put on hold after the user clicks on the confirmation link in the email.
Threads¶
Up until this point in the tutorial django-comments-xtd has been configured to
disallow nested comments. Every comment is at thread level 0. It is so because
by default the setting COMMENTS_XTD_MAX_THREAD_LEVEL
is set to 0.
When the COMMENTS_XTD_MAX_THREAD_LEVEL
is greater than 0, comments
below the maximum thread level may receive replies that will nest inside each
other up to the maximum thread level. A comment in a the thread level below
the COMMENTS_XTD_MAX_THREAD_LEVEL
can show a Reply link that
allows users to send nested comments.
In this section we will enable nested comments by modifying
COMMENTS_XTD_MAX_THREAD_LEVEL
and apply some changes to
our blog_detail.html
template.
We can make use of two template tags, render_xtdcomment_tree
and
get_xtdcomment_tree
. The former renders a template with the comments
while the latter put the comments in a nested data structure in the context of
the template.
We will also introduce the setting COMMENTS_XTD_LIST_ORDER
, that
allows altering the default order in which the comments are sorted in the list.
By default comments are sorted by thread and their position inside the thread,
which turns out to be in ascending datetime of arrival. In this example we will
list newer comments first.
Let’s start by editing tutorial/settings.py
to set up the maximum thread
level to 1 and a comment ordering such that newer comments are retrieve first:
# Change comment threading.
COMMENTS_XTD_MAX_THREAD_LEVEL = 1 # default is 0
# Change comment order, by default is ('thread_id', 'order').
COMMENTS_XTD_LIST_ORDER = ('-thread_id', 'order')
Now we have to modify the blog post detail template to load the comments_xtd
templatetag and make use of render_xtdcomment_tree
. We also want to move
the comment form from the bottom of the page to a more visible position right
below the blog post, followed by the list of comments.
Edit blog/post_detail.html
to make it look like follows:
{% extends "base.html" %}
{% load comments %}
{% load comments_xtd %}
{% block title %}{{ object.title }}{% endblock %}
{% block content %}
<div class="pb-3">
<h1 class="page-header text-center">{{ object.title }}</h1>
<p class="small text-center">{{ object.publish|date:"l, j F Y" }}</p>
</div>
<div>
{{ object.body|linebreaks }}
</div>
{% get_comment_count for object as comment_count %}
<div class="py-4 text-center">
<a href="{% url 'blog:post-list' %}">Back to the post list</a>
⋅
{{ comment_count }} comment{{ comment_count|pluralize }}
ha{{ comment_count|pluralize:"s,ve"}} been posted.
</div>
{% if object.allow_comments %}
<div class="comment mt-3 mb-5">
<h4 class="text-center mb-4">Your comment</h4>
<div class="card pt-4">
{% render_comment_form for object %}
</div>
</div>
{% endif %}
{% if comment_count %}
<ul class="media-list">
{% render_xtdcomment_tree for object %}
</ul>
{% endif %}
{% endblock %}
The tag render_xtdcomment_tree
renders the template django_comments_xtd/comment_tree.html
.
Now visit any of the blog posts to which you have already sent comments and see that a new Reply link shows up below each comment. Click on the link and post a new comment. It will appear nested inside the parent comment.
The new comment will not show a Reply link because COMMENTS_XTD_MAX_THREAD_LEVEL
has been set to 1. Raise it to 2 and reload the page to offer the chance to nest comments inside one level deeper.
Different max thread levels¶
There might be cases in which nested comments have a lot of sense and others in which we would prefer a plain comment sequence. We can handle both scenarios under the same Django project.
We just have to use both settings, COMMENTS_XTD_MAX_THREAD_LEVEL
and COMMENTS_XTD_MAX_THREAD_LEVEL_BY_APP_MODEL
. The former
establishes the default maximum thread level site wide, while the latter
sets the maximum thread level on app.model basis.
If we wanted to disable nested comments site wide, and enable nested comments
up to level one for blog posts, we would set it up as follows in our
settings.py
module:
COMMENTS_XTD_MAX_THREAD_LEVEL = 0 # site wide default
COMMENTS_XTD_MAX_THREAD_LEVEL_BY_APP_MODEL = {
# Objects of the app blog, model post, can be nested
# up to thread level 1.
'blog.post': 1,
}
The nested_count
field¶
When threaded comments are enabled the field nested_count
of every XtdComment instance keeps track of how many nested comments it contains.
Flags¶
The Django Comments Framework supports comment flagging, so comments can be flagged for:
Removal suggestion, when a registered user suggests the removal of a comment.
Moderator deletion, when a comment moderator marks the comment as deleted.
Moderator approval, when a comment moderator sets the comment as approved.
django-comments-xtd expands flagging with two more flags:
Liked it, when a registered user likes the comment.
Disliked it, when a registered user dislikes the comment.
In this section we will see how to enable a user with the capacity to flag a comment for removal with the Removal suggestion flag, how to express likeability, conformity, acceptance or acknowledgement with the Liked it flag and the opposite with the Disliked it flag.
One important requirement to mark comments is that the user flagging must be authenticated. In other words, comments can not be flagged by anonymous users.
Commenting options¶
As of version 2.0 django-comments-xtd has a new setting
COMMENTS_XTD_APP_MODEL_OPTIONS
that must be used to allow comment
flagging. The purpose of it is to give an additional level of control about what
actions users can perform on comments: flag them as inappropriate, like/dislike them, retrieve the list of users who liked/disliked them, and whether visitors can post comments or only registered users can do it.
It defaults to:
COMMENTS_XTD_APP_MODEL_OPTIONS = {
'default': {
'allow_flagging': False,
'allow_feedback': False,
'show_feedback': False,
'who_can_post': 'all' # Valid values: 'all', users'
}
}
We will go through the first three options in the following sections. As for the last option, who_can_post, I recommend you to read the special use case Only signed in users can comment, that explains the topic in depth.
Removal suggestion¶
Enabling the comment removal flag is about including the allow_flagging
argument in the render_xtdcomment_tree
template tag. Edit the
blog/post_detail.html
template and append the argument:
...
<ul class="media-list">
{% render_xtdcomment_tree for object allow_flagging %}
</ul>
The allow_flagging argument makes the templatetag populate a variable
allow_flagging = True
in the context in which
django_comments_xtd/comment_tree.html
is rendered. Edit now the settings
module and enable the allow_flagging
option for the blog.post
:
COMMENTS_XTD_APP_MODEL_OPTIONS = {
'blog.post': {
'allow_flagging': True,
'allow_feedback': False,
'show_feedback': False,
}
}
Now let’s suggest a removal. First we need to login in the admin interface so that we are not an anonymous user. Then we can visit any of the blog posts we sent comments to. There is a flag at the right side of every comment’s header. Clicking on it takes the user to a page in which she is requested to confirm the removal suggestion. Finally, clicking on the red Flag button confirms the request.
Users with the django_comments.can_moderate
permission will see a red
labelled counter near the flag button in each flagged comment, representing
how many times comments have been flagged. Also notice that when a user flags
a comment for removal the icon turns red for that user.
Administrators/moderators can find flagged comment entries in the admin interface, under the Comment flags model, within the Django Comments application.
Getting notifications¶
A user might want to flag a comment on the basis of a violation of the site’s terms of use, hate speech, racism or the like. To prevent a comment from staying published long after it has been flagged we might want to receive notifications on flagging events.
For such purpose django-comments-xtd provides the class XtdCommentModerator, which extends django-contrib-comments’ CommentModerator.
In addition to all the options of its parent class,
XtdCommentModerator offers the removal_suggestion_notification
attribute, that when set to True
makes Django send an email to all the
MANAGERS
on every Removal suggestion flag created.
To see an example let’s edit blog/models.py
. If you are already using the
class SpamModerator, which inherits from XtdCommentModerator, just add
removal_suggestion_notification = True
to your PostCommentModeration
class. Otherwise add the following code:
from django_comments_xtd.moderation import moderator, XtdCommentModerator
...
class PostCommentModerator(XtdCommentModerator):
removal_suggestion_notification = True
moderator.register(Post, PostCommentModerator)
Be sure that PostCommentModerator
is the only moderation class registered
for the Post
model, and be sure as well that the MANAGERS
setting contains a valid email address. The message sent is based on the
django_comments_xtd/removal_notification_email.txt
template, already
provided within django-comments-xtd. After these changes flagging a comment
with a Removal suggestion will trigger a notification by mail.
Liked it, Disliked it¶
Django-comments-xtd adds two new flags: the Liked it and the Disliked it flags.
Unlike the Removal suggestion flag, the Liked it and Disliked it flags are mutually exclusive. A user can not like and dislike a comment at the same time. Users can like/dislike at any time but only the last action will prevail.
In this section we make changes to give our users the capacity to like or
dislike comments. Following the same pattern as with the removal flag, enabling
like/dislike buttons is about adding an argument to the
render_xtdcomment_tree
, the argument allow_feedback
.
Edit the blog/post_detail.html
template and add the new argument:
<ul class="media-list">
{% render_xtdcomment_tree for object allow_flagging allow_feedback %}
</ul>
The allow_feedback argument makes the templatetag populate a variable
allow_feedback = True
in the context in which
django_comments_xtd/comment_tree.html
is rendered. Edit the settings
module and enable the allow_feedback
option for the blog.post
app.label pair:
COMMENTS_XTD_APP_MODEL_OPTIONS = {
'blog.post': {
'allow_flagging': True,
'allow_feedback': True,
'show_feedback': False,
}
}
The blog post detail template is ready to show the like/dislike buttons, refresh your browser.
Having the new like/dislike links in place, if we click on any of them we will
end up in either the django_comments_xtd/like.html
or the
django_comments_xtd/dislike.html
templates, which are meant to request
the user a confirmation for the operation.
Show the list of users¶
With the like/dislike buttons enabled we might as well consider to display the
users who actually liked/disliked comments. Again addind an argument to the
render_xtdcomment_tree
will enable the feature. Change the
blog/post_detail.html
and add the argument show_feedback
to the template tag:
[...]
<ul class="media-list">
{% render_xtdcomment_tree for object allow_flagging allow_feedback show_feedback %}
</ul>
{% endif %}
{% endblock %}
{% block extra-js %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
<script>
window.addEventListener('DOMContentLoaded', (_) => {
const tooltipQs = '[data-bs-toggle="tooltip"]';
const tooltipList = document.querySelectorAll(tooltipQs);
[...tooltipList].map(el => new bootstrap.Tooltip(el, {html: true}));
});
</script>
{% endblock %}
Also change the settings and enable the show_feedback
option for
blog.post
:
COMMENTS_XTD_APP_MODEL_OPTIONS = {
'blog.post': {
'allow_flagging': True,
'allow_feedback': True,
'show_feedback': True,
}
}
We load twitter-bootstrap libraries from their respective default CDNs as the code above uses bootstrap’s tooltip functionality to show the list of users when the mouse hovers the numbers near the buttons, as the following image shows:
Put the mouse over the counters near the like/dislike buttons to display the list of users.
Markdown¶
In versions prior to 2.0 django-comments-xtd required the installation of
django-markup as a dependency. There was also a specific template filter
called render_markup_comment
to help rendering comment’s content in
the markup language of choice.
As of version 2.0 the backend side of the application does not require the
installation of any additional package to parser comments’ content, and
therefore does not provide the render_markup_comment
filter anymore.
However, in the client side the JavaScript plugin uses Markdown by default
to render comments’ content.
As for the backend side, comment’s content is presented by default in plain
text, but it is easily customizable by overriding the template
includes/django_comments_xtd/render_comment.html
.
In this section we will send a Markdown formatted comment, and once published we will install support for Markdown, with django-markdown2. We’ll then override the template mentioned above so that comments are interpreted as Markdown.
Send a comment formatted in Markdown:
Sed id [pharetra](https://www.example.com) lorem. **Pellentesque** ornare
tincidunt dapibus. Aenean ac odio libero.
It should look like this:
Now we will install django-markdown2, and create the template directory and the template file:
(venv)$ pip install django-markdown2
(venv)$ mkdir -p templates/includes/django_comments_xtd/
(venv)$ touch templates/includes/django_comments_xtd/comment_content.html
We have to add django_markdown2
to our INSTALLED_APPS
, and add
the following template code to the file comment_content.html
we just created:
{% load md2 %}
{{ content|markdown:"safe, code-friendly, code-color" }}
Now our project is ready to show comments posted in Markdown. After reloading, the comment’s page will look like this:
JavaScript plugin¶
Up until now we have used django-comments-xtd as a backend application. As of version 2.0 it includes a JavaScript plugin that helps moving part of the logic to the browser. By making use of the JavaScript plugin users don’t have to leave the blog post page to preview, submit or reply comments, or to like/dislike them. But it comes at the cost of using:
ReactJS
jQuery (to handle Ajax calls).
Twitter-Bootstrap (for the UI).
Remarkable (for Markdown support).
To know more about the client side of the application and the build process read the specific page on the JavaScript plugin.
In this section of the tutorial we go through the steps to make use of the JavaScript plugin.
Enable Web API¶
The JavaScript plugin uses the Web API provided within the app. In order to enable it install the django-rest-framework:
pip install djangorestframework
Once installed, add it to our tutorial INSTALLED_APPS
setting:
INSTALLED_APPS = [
...
'rest_framework',
...
]
To know more about the Web API provided by django-comments-xtd read on the Web API page.
Enable app.model options¶
Be sure COMMENTS_XTD_APP_MODEL_OPTIONS
includes the options we want
to enable for comments sent to Blog posts. In this case we will allow users to
flag comments for removal (allow_flagging option), to like/dislike comments
(allow_feedback), and we want users to see the list of people who
liked/disliked comments:
COMMENTS_XTD_APP_MODEL_OPTIONS = {
'blog.post': {
'allow_flagging': True,
'allow_feedback': True,
'show_feedback': True,
}
}
The i18n JavaScript Catalog¶
Internationalization support (see Internationalization) has been included within the
plugin by making use of the Django’s JavaScript i18n catalog. If your project doesn’t need
i18n you can easily remove every mention to these functions (namespaced
under the django object) from the source and change the
webpack.config.js
file to build the plugin without it.
Our tutorial doesn’t have i18n enabled (the comp example project
has it), but we will not remove its support from the plugin, we will simply
enable the JavaScript Catalog URL, so that the plugin can access its functions.
Edit tutorial/urls.py
and add the following url:
from django.views.i18n import JavaScriptCatalog
urlpatterns = [
...
path(r'jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
]
In the next section we will use the new URL to load the i18n JavaScript catalog.
Configure CORS¶
We are going to load Bootstrap JS library, ReactJS and Remarkable from a CDN. Web browsers will not trust those file by default, we need to send CORS headers to let browsers know that our Django backend trusts those origins.
The easiest way to do so is by using the django-cors-headers app:
pip install django-cors-headers
Edit the tutorial/settings.py
file to add corsheaders
to the INSTALLED_APPS
. We also have to insert middleware and create the CORS_ALLOWED_ORIGINS
setting to indicate what are the origins we trust:
INSTALLED_APPS = [
...
'corsheaders',
...
]
MIDDLEWARE = [
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
# We are loading scripts from these CDNs.
CORS_ALLOWED_ORIGINS = [
"https://cdnjs.cloudflare.com",
"https://cdn.jsdelivr.net",
]
Load the plugin¶
Now let’s edit blog/post_detail.html
. We are going to remove the blocks related with the comments and leave only a <div id=”comments”/> that is used as the hook to load the JavaScript ReactJS plugin.
Be sure your blog/post_detail.html
looks like the following:
{% extends "base.html" %}
{% load static %}
{% load comments %}
{% load comments_xtd %}
{% block title %}{{ object.title }}{% endblock %}
{% block content %}
<div class="pb-3">
<h1 class="text-center">{{ object.title }}</h1>
<p class="small text-center">{{ object.publish|date:"l, j F Y" }}</p>
</div>
<div>
{{ object.body|linebreaks }}
</div>
{% get_comment_count for object as comment_count %}
<div class="py-4 text-center">
<a href="{% url 'blog:post-list' %}">Back to the post list</a>
</div>
<div id="comments"></div>
{% endblock %}
{% block extra-js %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js" integrity="sha512-QVs8Lo43F9lSuBykadDb0oSXDL/BbZ588urWVCRwSIoewQv/Ewg1f84mK3U790bZ0FfhFa1YSQUmIhG+pIRKeg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js" integrity="sha512-6a1107rTlA4gYpgHAqbwLAtxmWipBdJFcq8y5S/aTge3Bp+VAklABm2LO+Kg51vOWR9JMZq1Ovjl5tpluNpTeQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/remarkable/2.0.1/remarkable.min.js" integrity="sha512-skYYbQHAuOTpeJTthhUH3flZohep8blA+qjZOY0VqmfXMDiYcWxu29F5UbxU4LxaIpGkRBk+3Qf8qaXfd9jngg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
window.comments_props = {% get_commentbox_props for object %};
window.comments_props_override = {
allow_comments: {%if object.allow_comments%}true{%else%}false{%endif%},
allow_feedback: true,
show_feedback: true,
allow_flagging: true,
polling_interval: 5000 // In milliseconds.
};
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
<script
type="text/javascript"
src="{% url 'javascript-catalog' %}"></script>
<script src="{% static 'django_comments_xtd/js/django-comments-xtd-2.10.0.js' %}"></script>
<script>
window.addEventListener('DOMContentLoaded', (_) => {
const tooltipQs = '[data-bs-toggle="tooltip"]';
const tooltipList = document.querySelectorAll(tooltipQs);
[...tooltipList].map(el => new bootstrap.Tooltip(el, {html: true}));
});
</script>
{% endblock %}
The blog post page is now ready to handle comments through the JavaScript plugin, including the following features:
Post comments.
Preview comments, with instant preview update while typing.
Reply comment in the same page, with instant preview while typing.
Notifications of new incoming comments using active polling (override polling_interval parameter, see the content of first <script> tag in the code above).
- Button to reload the tree of comments, highlighting new comments (see
image below).
Immediate like/dislike actions.
Final notes¶
We have reached the end of the tutorial. I hope you got enough to start using django-comments-xtd in your own project.
The following page introduces the Demo projects. The simple demo is a straightforward backend handled project that uses comment confirmation by mail, with follow-up notifications and mute links. The custom demo is an example about how to extend django-comments-xtd Comment model with new attributes. The comp demo shows a project using the complete set of features provided by both django-contrib-comments and django-comments-xtd.
Checkout the Control Logic page to understand how django-comments-xtd works along with django-contrib-comments. The Web API page details the API provided. The JavaScript Plugin covers every aspect regarding the frontend code. Read on Filters and Template Tags to see in detail the list of template tags and filters offered. The page on Customizing django-comments-xtd goes through the steps to extend the app with a quick example and little prose. Read the Settings page and the Templates page to get to know how you can customize the default behaviour and default look and feel.
If you want to help, please, report any bug or enhancement directly to the github page of the project. Your contributions are welcome.
Comment confirmation¶
Before we go any further we need to set up the
COMMENTS_XTD_SALT
setting. This setting plays an important role during the comment confirmation by mail. It helps obfuscating the comment before the user approves its publication.django-comments-xtd does not store comments in the server until they have been confirmed. This way there is little to none possible comment spam flooding in the database. Comments are encoded in URLs and sent for confirmation by mail. Only when the user clicks the confirmation URL the comment lands in the database.
This behaviour is disabled for authenticated users, and can be disabled for anonymous users too by simply setting
COMMENTS_XTD_CONFIRM_EMAIL
toFalse
.Now let’s append the following entries to the tutorial settings module: