Evgenij LegotskojSept. 30, 2016, 10:42 p.m.

Django - Tutorial 011. Adding comments to the site based on Django

When I worked with the implementation of comments on the site for Django, I was surprised to find that Django does not provide any modules for implementation comments. Rather it gave him before, it was a unit django.contrib.comments, but in version 1.7 it was announced as deprecated and offered either to cut yourself, or you can use something like Disqus. Well, it sort of code also supports syntax highlighting, but ... in the articles one light, the other in the comments - it will be ugly.

Therefore I decided to write my own comments on the site.

To implement the necessary comments:

  • Add a new model, call it Comment ;
  • Add an idea that will handle the addition of a comment;
  • Add a form to add a comment;
  • Use Materialized Path to organize tree structure;

Comment model

Model comments will contain the following fields:

  • path - It will contain an array of integers, which will contain the full path to the root. As mentioned in the article on Materialized Path, is the ID of the parent element;
  • article_id - a foreign key to an article in which there is a comment;
  • author_id - a foreign key to the author's comment;
  • content - message;
  • pub_date - date and time of the publication of the comment;

In addition, given get_offset() methods, which will define the comment shift the level along the length of the path, and get_col() , which will determine the number of columns in the grid, which will take a comment and overridden method __str__ , which will be responsible for the display of the contents of a comment in the admin area.

The shift and the number of columns will organize a tree display of comments on the page, but the shift is not more than 6 columns, because at the moment the grid is divided into 12 columns.

class Comment(models.Model):
    class Meta:
        db\_table = "comments"

    path = ArrayField(models.IntegerField())
    article\_id = models.ForeignKey(Article)
    author\_id = models.ForeignKey(User)
    content = models.TextField('Comment')
    pub\_date = models.DateTimeField('Date of comment', default=timezone.now)

    def \_\_str\_\_(self):
        return self.content[0:200]

    def get\_offset(self):
        level = len(self.path) - 1
        if level > 5:
            level = 5
        return level

    def get\_col(self):
        level = len(self.path) - 1
        if level > 5:
            level = 5
        return 12 - level


Comments sent to the site by using the POST request on a particular address, which must be described in the file urls.py .

from django.conf.urls import url

from . import views

app\_name = 'post'
urlpatterns = [
    url(r'^(?P<article\_id>[0-9]+)/$', views.EArticleView.as\_view(), name='article'),
    url(r'^comment/(?P<article\_id>[0-9]+)/$', views.add\_comment, name='add\_comment'),

As I have said in many articles, I work in the module knowledge with articles and sections, but this time I brought the article into a separate module to unify the URL of the article, so they are not dependent on the sections and is not lost indexation, if Article It will be moved to another section. Therefore, the work is now talking with the post module. There also will be sent to and comment. Post comments in the end will be the path of post/comment/12 , if the user says the article with ID = 12.

Comment form

The comment form will be placed in a separate file forms.py .

Обработка формы и сохранение комментария будет происходить в представлении, поэтому метода save здесь нет. Данная форма служит лишь для ввода комментария и отсылки его на сервер.

from django import forms

from .models import Comment

class CommentForm(forms.Form):

    parent\_comment = forms.IntegerField(

    comment\_area = forms.CharField(

parent_comment field is very important, as it will contain the ID of the parent comment, which will be automatically supplied when responding to one of the comments under the article. The user will not fill it and it will be hidden. In addition, it is not mandatory to fill, as a comment may refer directly to the article.

But the attitude to a particular comment made by JavaScript scripts.


To add a comment, I was limited only by, without any representations, especially since decorator @login_required may implemented easly to the method than the presentation.

It is also responsible for the representation of articles mapping has been modified since it was necessary to include a comment in the form of given article page context, unless the user is authorized.

from django.views import View
from django.shortcuts import render\_to\_response, get\_object\_or\_404, redirect
from django.contrib import auth
from django.http import Http404
from django.contrib.auth.decorators import login\_required
from django.views.decorators.http import require\_http\_methods
from django.core.exceptions import ObjectDoesNotExist
from django.template.context\_processors import csrf

from knowledge.models import Article, Comment
from knowledge.forms import CommentForm

class EArticleView(View):
    template\_name = 'post/article.html'
    comment\_form = CommentForm

    def get(self, request, *args, **kwargs):
        article = get\_object\_or\_404(Article, id=self.kwargs['article\_id'])
        context = {}
        user = auth.get\_user(request)
        # Put in the context of all the comments that are relevant to the article 
        # simultaneously sorting them along the way, the auto-increment ID, 
        # so the problems with the hierarchy should not have any comments yet
        context['comments'] = article.comment\_set.all().order\_by('path')
        context['next'] = article.get\_absolute\_url()
        # We add form only if the user is authenticated
        if user.is\_authenticated:
            context['form'] = self.comment\_form

        return render\_to\_response(template\_name=self.template\_name, context=context)

# Decorators in which only authorized user 
# able to send comment via POST request
def add\_comment(request, article\_id):

    form = CommentForm(request.POST)
    article = get\_object\_or\_404(Article, id=article\_id)

    if form.is\_valid():
        comment = Comment()
        comment.path = []
        comment.article\_id = article
        comment.author\_id = auth.get\_user(request)
        comment.content = form.cleaned\_data['comment\_area']

        # Django does not allow to see the comments on the ID, we do not save it,
        # Although PostgreSQL has the tools in its arsenal, but it is not going to
        # work with raw SQL queries, so form the path after the first save
        # And resave a comment
        except ObjectDoesNotExist:


    return redirect(article.get\_absolute\_url())

Article Template comment

Did I mention that I use django-bootstrap3 module for page layout? So do not be surprised as this template are laid out.

Comments are ordinary lines in the Grid Bootstrap system, and the tree is achieved due to the shift of columns.

Very important here is the fact that each row id = {{comment.id}} - This is exactly the same value that will be entered in a hidden form field if the user does not comment on the article, and some of the comments.

On the same ID with JavaScript will move the comment form on a page. A form will be placed via show_comments_form() function. This function is placed in a handler reference "Reply" at every comment, as well as links to the handler, just to write a comment. This feature uses the jQuery library. So do not forget to connect it to your base template. I connected the version that is used with Bootstrap .

{% extends 'home/base.html' %}
{% load bootstrap3 %}
{% block page %}
        <h1>{{ article.article\_title }}</h1>
        {{ article.article\_content|safe }}
    {% for comment in comments %}
        <a name="comment-{{ comment.id }}"></a>
        <div class="row" id="{{ comment.id }}">
            <div class="col-md-{{ comment.get\_col }} col-md-offset-{{ comment.get\_offset }}">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <strong>{{ comment.author\_id.get\_full\_name|default:comment.author\_id.username }}</strong>&nbsp;&nbsp;
                        {{ comment.pub\_date }}
                        <a href="#comment-{{ comment.id }}">#</a>
                    <div class="panel-body">
                        <div>{{ comment.content|safe }}</div>
                        {% if form %}<a class="btn btn-default btn-xs pull-right"
                                        onclick="return show\_comments\_form({{ comment.id }})">
                            {% bootstrap\_icon "share-alt" %}&nbsp;&nbsp;Ответить</a>
                        {% endif %}
    {% endfor %}
    {% if form %}
        <h3 id="write\_comment"><a onclick="return show\_comments\_form('write\_comment')">Write a comment.</a></h3>
        <form id="comment\_form" action="{% url 'post:add\_comment' article.id %}" method="post">
        {% csrf\_token %}
        {% bootstrap\_form form %}
        {% buttons %}
            <button type="submit" class="btn btn-primary">{% bootstrap\_icon "comment" %}&nbsp;&nbsp;Комментировать</button>
        {% endbuttons %}
    {% else %}
        <div class="panel panel-warning">
            <div class="panel-heading">
                <h3 class="panel-title">Комментарии</h3>
            <div class="panel-body">
                Только авторизованные пользователи могут оставлять комментарии.<br />
    {% endif %}
{% endblock %}

JavaScript to move the comments page

Well, everything is simple to outrageous. If id = write_comment , it means that a comment is the first level and a hidden field will be empty, and the form is moved by the words "Write a comment." Otherwise fill in a hidden field and put it under the commentary, under which we give the answer.

function show\_comments\_form(parent\_comment\_id)
    if (parent\_comment\_id == 'write\_comment')
    $("#comment\_form").insertAfter("#" + parent\_comment\_id);

For Django I recommend VDS-server of Timeweb hoster .

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.
Support the author Donate

не совсем понятно как это реализовать куда импортировать view и urls подскажите !

views и urls должны присутсвовать в вашем приложении, например сайт в Django состоит из нескольких приложений, которые создаются через команду startapp, по умолчанию там всегда есть директории urls и views .

В моём случае это приложение post.
app_name = 'post'

Если говорить про то, куда в итоге подключить urls , то они должны быть подключены в главный файл urls, он должен располагаться там же, где и файл settings.py

сделал немного по другому

class EArticleView(View):
    template_name = 'knowledge/article.html'
    comment_form = CommentForm

    def get(self, request,  *args, **kwargs):
        article = get_object_or_404(Article, id=self.kwargs['article_id'])
        context = {}
        user = auth.get_user(request)
        context['article'] = article
        # Помещаем в контекст все комментарии, которые относятся к статье
        # попутно сортируя их по пути, ID автоинкрементируемые, поэтому
        # проблем с иерархией комментариев не должно возникать
        context['comments'] = article.comment_set.all().order_by('path')
        context['next'] = article.get_absolute_url()
        # Будем добавлять форму только в том случае, если пользователь авторизован
        if user.is_authenticated:
            context['form'] = self.comment_form

        return render(request, template_name=self.template_name, context=context)

    # Декораторы по которым, только авторизованный пользователь
    # может отправить комментарий и только с помощью POST запроса
    def post(self, request, *args, **kwargs):
        if request.method == 'POST':

            form = CommentForm(request.POST)
            article = get_object_or_404(Article, id=self.kwargs['article_id'])
            if form.is_valid():
                comment = Comment(

                # Django не позволяет увидеть ID комментария по мы не сохраним его,
                # хотя PostgreSQL имеет такие средства в своём арсенале, но пока не будем
                # работать с сырыми SQL запросами, поэтому сформируем path после первого сохранения
                # и пересохраним комментарий
                except ObjectDoesNotExist:
                    print('не получилось')
            return redirect(article.get_absolute_url())
как думаете так тоже хорошо ?

Да, так будет даже лучше, я на сайте уже обновил до такого вида код

Вот это уже не нужно
if request.method == 'POST':
Поскольку Вы и так используете метод post, то есть эта проверка избыточна.

Что касается древовидных комментариев, то я от них как видите отказался. Я просто добавляю ID комментария, на который был дан ответ. Древовидные комментарии в итоге оказались не очень удобны для сайта с вставками программного кода.

есть визуальный пример ?


Визуальный пример чего? комментариев?
При ответе на конкретный комментарий рядом с ником отвечающего будет стрелочка и указание ник другого пользователя. Который будет ссылкой на комментарий, на который был дан ответ.


Доброго времени суток Евгений. У меня ка то так получилось:

Не подскажете с чем может быть связано?

Добрый день.

Скорее всего поля неправильно были получены. Что-то не так с запросом. То есть в полученном объекте значения values не были корректно забраны. Либо просто неправильно написали названия полей в шаблоне

Доброго времени суток Евгений. Не подскажете что я делаю не так? Получаю ошибку такого характера:

Reverse for 'add_comment' with arguments '('',)' not found. 1 pattern(s) tried: ['comment\\/(?P<article_id>[0-9]+)\\/$']

Говорит что ошибка в views в этой строке:

return render(request, template_name=self.template_name, context=context) 

Добрый день!

шаблон не находит, или шаблон неправильно прописали, или тег шаблона неправильно написан, иных выводов сделать не могу, из того, что вы написали. трейсбек нужно смотреть. Создайте тему на форуме и выложить трейсбек с ошибкой.

А какие меры можно принять, чтобы обеспечить защиту от спама?
И еще как можно реализовать, чтобы по комменты выводились в админке и их можно было отклонять/одобрять?

Самая первая мера - это комментарии только для зарегистрированных пользователей.
Пока ресурс маленький, то и спамеров почти нет, которые готовы зарегистрироваться на сайте ради спам комментария.
Ну а для регистрации такие ограничения как подтверждение email и recaptcha.
У меня так спамеры активно начали появляться только когда посещаемость перевалила за 1500 в день.

Что касается одобрения и отклонения, то у меня просто есть поле moderation в каждой модели и action для админки, которые помечают контент как спам или модерированный контент. Но если учесть, что обычно спаммер регистрируется и только спамит. То я просто удаляю весь акканут со всем контентом спамера, а ег email заношу в чёрный список, чтобы с этого email регистрация больше не проходила. Для меня это вопрос двух действий и 10 секунд, а для спамера, который вручную этим занимается - это очень накладно.

Ну и чтобы голову не греть лишний раз, то у меня просто есть прокси модели, которые сразу фильтруют в админке только немодерированный контент.

поле moderation в каждой модели и action для админки, которые помечают контент как спам или модерированный контент.

А action - это функция одобрения/отклонения?

У меня эта часть кода выведена в open source, смотрите здесь

path = ArrayField(models.IntegerField())

ArrayField подходит только для postresql, а для mysql - выдает ошибку. Как можно переписать эту строку, чтобы на mysql работало?

Я бы вам посоветовал выкинуть mysql на помойку, но вы меня наверное не послушаете.
Скорее или конвертировать последовательность Integer значений в строку и сохранять в обычный CharField или извращаться с ManyToManyField.
Даже не знаю, что из этих двух вариантов будет хуже. Один порожадает лишний оверхед с преобразованиями, а второй оверхед с запросами.
Ни то ни другое не адекватное, но при шлифовке наверное будет работать.

а в чем явное преимущество postgresql над mysql?)

Он более функциональный и его функционал объективно лучше поддерживается Django.

Из первого, что приходит на ум:

  • Это наличие полей типа Array
  • Поддержка полей для JSON
  • Хорошая поддержка GIS функционала, также есть и батарейки соответствующие

Честно, я так сразу не вспомню, но когда сам задавался вопросом, то столкнулся с тем, что рекомендуют при разработке на Джанго использовать Postgres.

Вообще самая рекомендуемая связка стека - это Django/PostgreSQL/Nginx.

Как бы можно и MySQL, но вы скорее увидите на StackOverflow вопрос о том как сделать в MySQL так, как это делается в PostgreSQL, а не наоборот.
А для меня это уже повод задуматься.


Извините, проверка функциональности, визуально здесь не увидел древовидной иерархии в комментах (вроде я тоже не дерево:))
зы спасибо за интересные статьи!

В рамках развития сайта выпилил полностью. Ибо неудобно было поддерживать. Да и всё-таки не нравятся они мне из-за избыточной траты полезного пространства. Не говоря уже о том, что на мобильных платформах это выглядит отвратно.

Меня интересует как экранировать или преобразовывать теги которые нежелательны при добавлении в бд. Ибо текст комментария выводится

{{ comment.text|safe }}

то есть как html. А без safe это просто набор текста и всё

Плохо я однако пользовался поиском по сайту. Спасибо

Поиск не совсем хорошо работает, так что норм


Only authorized users can post comments.
Please, Log in or Sign up

Let me recommend you a great European Fornex hosting.

Fornex has proven itself to be a stable host over the years.

For Django projects I recommend VPS hosting

Following the link you will receive a 5% discount on shared hosting services, dedicated servers, VPS and VPN

View Hosting

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:50points,
  • Rating points-4

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:20points,
  • Rating points-10
  • storm
  • Jan. 20, 2023, 10:30 p.m.

C++ - Тест 003. Условия и циклы

  • Result:0points,
  • Rating points-10
Popular publications in the last 90 Days
Last comments

Qt WinAPI - Lesson 004. QtIFW - Automation WinDeployQt and build installers with Qt Installer Framework

Hello Evgenij, regarding the online installer, I've tried many times to use web host for the created repo after repogen step. I tried using github but I found people talking it is not …
  • juvf
  • Jan. 17, 2023, 9:18 a.m.

Qt/C++ - Lesson 051. QMediaPlayer – simple audio player

PS. Почти дописал плеер на QML. Уперся в ограничения QML. Переписываю плеер на с++/qt, а графика останится в qml. Нашел то, что мне надо, а именно индикатор звука. Qt может перехватывать аудиопо…

Qt/C++ - Lesson 039. How to paint stroke in QSqlTableModel by value in the column?

В этом случае вижу только какой-нибудь костыль в стиле перебора по всем индексам в заголовке с помощью методу headerData . То есть пройтись в for цикле пока не будет совпадения н…
  • avt
  • Dec. 12, 2022, 8:06 p.m.

Qt/C++ - Lesson 039. How to paint stroke in QSqlTableModel by value in the column?

Спасибо за ответ. Нет, дело не в читаемости кода, в разных таблицах у меня есть столбцы с одинаковым именем, но с разными индексами. Хотел сделать решение по имени столбца для всех таблиц сразу.…
  • juvf
  • Dec. 12, 2022, 3:06 p.m.

Qt/C++ - Lesson 051. QMediaPlayer – simple audio player

Now discuss on the forum

Как создать уникальное значение поля на основе существующих значений

В принципе это можно сделать так: def unique_field(self): return '{0}_{1}'.format(self.title, self.price)class Tovar(models.Model): title=models.CharField('Наименование',max_length=…
  • Wayne
  • Jan. 27, 2023, 12:47 p.m.

Здравствуйте помогите с qml

как сделать так, чтобы зеленая фигура при движения за пределы круга пропадала на qml

Sorting the added QML elements in the ListModel

I am writing an alarm clock in QML, I am required to sort the alarms in ascending order (depending on the date or time (if there are several alarms on the same day). I've done the sorting …

QSqlRelatipnalTabelModel Qt 4.8.1 как получить id внешней связи?

Наконец-то готовы представить полноценное развитие Qt QSqlTableModel и QTableView. Посмотреть можно у нас на сайте здесь На github здесь здесь Радостная новос…
  • Pisych
  • Jan. 25, 2023, 10:01 p.m.

Ввод бухгалтерского документа в одной форме

вопрос снят. спасибо за ответы. сообразил, как сделать:)
© EVILEG 2015-2022
Recommend hosting TIMEWEB