Evgenii Legotckoi
Evgenii Legotckoi30. September 2016 12:42

Django - Tutorial 011. Hinzufügen von Kommentaren zur Site basierend auf Django

Als ich anfing, Kommentare auf der Site für Django zu implementieren, war ich überrascht, dass Django keine Module zum Implementieren von Kommentaren bereitstellt. Vielmehr stellte er es vorher zur Verfügung, es war das Modul django.contrib.comments , aber in Version 1.7 wurde es als veraltet deklariert und angeboten, es entweder selbst zu schneiden oder so etwas wie ** Disqus. * zu verwenden. * Gut , es scheint auch das Hervorheben der Codesyntax zu unterstützen, aber ... es gibt ein Highlight in den Artikeln, ein anderes in den Kommentaren - es wird hässlich.

Deshalb werden wir unser eigenes Fahrrad vorstellen und unsere Käfer fangen.

Um Kommentare zu implementieren, müssen Sie:

  • Fügen Sie ein neues Modell hinzu, nennen wir es Kommentar;
  • Fügen Sie eine Ansicht hinzu, die das Hinzufügen eines Kommentars behandelt;
  • Fügen Sie ein Formular hinzu, um einen Kommentar einzugeben;
  • Verwenden Sie den [Materialized Path]-Ansatz, um die Baumstruktur zu organisieren (https://evileg.com/ru/post/12/);

Kommentarmodell

Das Kommentarmodell enthält die folgenden Felder:

  • path - enthält ein Array von ganzzahligen Werten, die den vollständigen Pfad zum Stammverzeichnis enthalten. Wie im Artikel Materialisierter Pfad beschrieben, sind dies die IDs aller übergeordneten Elemente;
  • article \ _id - Fremdschlüssel für den Artikel, der den Kommentar enthält;
  • author \ _id - Fremdschlüssel zum Autor des Kommentars;
  • Inhalt - der Kommentar selbst;
  • pub \ _date - Datum und Uhrzeit der Veröffentlichung des Kommentars;

Darüber hinaus gibt es Methoden get \ _offset () , die die Höhe des Kommentar-Offsets entlang der Pfadlänge bestimmen, und get \ _col () , die die Anzahl der Spalten in der Raster, das der Kommentar belegt, sowie die überschriebene Methode \ _ \ _ str \ _ \ _, die für die Anzeige eines Teils des Kommentarinhalts im Admin-Panel verantwortlich ist.

Die Verschiebung und die Anzahl der Spalten organisieren die baumartige Anzeige der Kommentare auf der Seite, aber die Verschiebung beträgt nicht mehr als 6 Spalten, da das Raster derzeit in 12 Spalten unterteilt ist.

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('Комментарий')
    pub_date = models.DateTimeField('Дата комментария', 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

URLs.py-Datei

Der Kommentar wird mit einer POST-Anfrage an eine bestimmte Adresse an die Site gesendet, die in der Datei urls.py beschrieben werden muss

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'),
]

Wie ich in vielen Artikeln sagte, arbeite ich im Wissensmodul mit Artikeln und Abschnitten, aber diesmal habe ich die Artikel in ein separates Modul verschoben, um die URL der Artikel zu vereinheitlichen, damit sie nicht von der abhängig sind Abschnitte und Indexierung gehen nicht verloren, falls der Artikel in einen anderen Abschnitt verschoben wird. Daher geht jetzt die Arbeit mit dem Post -Modul. Dort wird auch ein Kommentar verschickt. Als Ergebnis wird ein Kommentar entlang des Pfads post / comment / 12 gesendet, wenn der Benutzer Artikel mit der ID = 12 kommentiert.

Kommentarformular

Das Kommentarformular wird in einer separaten Datei abgelegt forms.py.

Die Verarbeitung des Formulars und das Speichern des Kommentars erfolgt in der Ansicht, daher gibt es hier keine Speichermethode. Dieses Formular dient lediglich dazu, einen Kommentar einzugeben und an den Server zu senden.

from django import forms

from .models import Comment


class CommentForm(forms.Form):

    parent_comment = forms.IntegerField(
        widget=forms.HiddenInput,
        required=False
    )

    comment_area = forms.CharField(
        label="",
        widget=forms.Textarea
    )

Das Feld parent \ _comment ist sehr wichtig, da es die ID des übergeordneten Kommentars enthält, die automatisch ersetzt wird, wenn auf einen der Kommentare unter dem Artikel geantwortet wird. Der Benutzer wird es nicht ausfüllen und es wird ausgeblendet. Außerdem ist das Ausfüllen optional, da sich der Kommentar direkt auf den Artikel beziehen kann.

Nun, die Beziehung zu einem bestimmten Kommentar erfolgt über ein JavaScript-Skript.

Darstellung

Um einen Kommentar hinzuzufügen, beschränkte ich mich auf eine Methode ohne Präsentation, zumal der Dekorator @ login \ _required einfacher an eine Methode als an eine Ansicht angehängt werden kann.

Außerdem wurde die für die Anzeige des Artikels zuständige Ansicht geändert, da das Kommentarformular in den Kontext der jeweiligen Artikelseite eingebunden werden musste, wenn der Benutzer natürlich eingeloggt ist.

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 = {}
        context.update(csrf(request))
        user = auth.get_user(request)
        # Помещаем в контекст все комментарии, которые относятся к статье
        # попутно сортируя их по пути, 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_to_response(template_name=self.template_name, context=context)

# Декораторы по которым, только авторизованный пользователь 
# может отправить комментарий и только с помощью POST запроса
@login_required
@require_http_methods(["POST"])
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']
        comment.save()

        # Django не позволяет увидеть ID комментария по мы не сохраним его, 
        # хотя PostgreSQL имеет такие средства в своём арсенале, но пока не будем
        # работать с сырыми SQL запросами, поэтому сформируем path после первого сохранения
        # и пересохраним комментарий 
        try:
            comment.path.extend(Comment.objects.get(id=form.cleaned_data['parent_comment']).path)
            comment.path.append(comment.id)
        except ObjectDoesNotExist:
            comment.path.append(comment.id)

        comment.save()

    return redirect(article.get_absolute_url())

Artikelvorlage mit Kommentar

Habe ich erwähnt, dass ich das Modul django-bootstrap3 für das Seitenlayout verwende? Seien Sie also nicht überrascht, wie diese Vorlage aufgebaut ist.

Kommentare sind normale Zeilen im Bootstrap Grid-System, und die Baumstruktur wird durch Verschieben der Spalten erreicht.

Es ist hier sehr wichtig, dass jede Zeile id = {{comment.id}} hat - Dies ist genau der Wert, der in das versteckte Formularfeld eingefügt wird, wenn der Benutzer nicht den Artikel kommentiert, sondern einige von die Kommentare.

Das Kommentarformular auf der Seite wird mit derselben ID mit JavaScript navigiert. Und das Formular wird mit der Funktion show \ _comments \ _form() platziert. Diese Funktion wird im Link-Handler "Antworten" für jeden Kommentar sowie im Link-Handler platziert, um nur einen Kommentar zu schreiben . Diese Funktion verwendet die jQuery-Bibliothek. Vergessen Sie also nicht, sie in Ihr Basis-Template aufzunehmen. Ich verbinde die verwendete Version mit Bootstrap.

{% extends 'home/base.html' %}
{% load bootstrap3 %}
{% block page %}
    <article>
        <h1>{{ article.article_title }}</h1>
        {{ article.article_content|safe }}
    </article>
    <h2>Комментарии</h2>
    {% 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>
                    <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 %}
                    </div>
                </div>
            </div>
        </div>
    {% endfor %}
    {% if form %}
        <h3 id="write_comment"><a onclick="return show_comments_form('write_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 %}
        </form>
    {% else %}
        <div class="panel panel-warning">
            <div class="panel-heading">
                <h3 class="panel-title">Комментарии</h3>
            </div>
            <div class="panel-body">
                Только авторизованные пользователи могут оставлять комментарии.<br />
            </div>
        </div>
    {% endif %}
{% endblock %}

JavaScript zum Verschieben von Kommentaren auf der Seite


Nun, alles ist einfach zu blamieren. Wenn id = write \ _comment bedeutet dies, dass der Kommentar die erste Ebene hat und das versteckte Feld leer ist und das Formular unter die Aufschrift "Kommentar schreiben" verschoben wird. Andernfalls füllen wir in das versteckte Feld und platzieren Sie es unter dem Kommentar , unter dem wir die Antwort geben.

function show_comments_form(parent_comment_id)
{
    if (parent_comment_id == 'write_comment')
    {
        $("#id_parent_comment").val('')
    }
    else
    {
        $("#id_parent_comment").val(parent_comment_id);
    }
    $("#comment_form").insertAfter("#" + parent_comment_id);
}

Für Django empfehle ich Timeweb VDS-Server .

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

bernar92
  • 6. Dezember 2017 00:39

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

Evgenii Legotckoi
  • 6. Dezember 2017 03:18

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

В моём случае это приложение post.
app_name = 'post'
Evgenii Legotckoi
  • 6. Dezember 2017 03:22

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

bernar92
  • 6. Dezember 2017 06:19

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

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 = {}
        context.update(csrf(request))
        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 запроса
    @method_decorator(login_required)
    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(
                    path=[],
                    article_id=article,
                    author_id=request.user,
                    content=form.cleaned_data['comment_area']
                )
                comment.save()

                # Django не позволяет увидеть ID комментария по мы не сохраним его,
                # хотя PostgreSQL имеет такие средства в своём арсенале, но пока не будем
                # работать с сырыми SQL запросами, поэтому сформируем path после первого сохранения
                # и пересохраним комментарий
                try:
                    comment.path.extend(Comment.objects.get(id=form.cleaned_data['parent_comment']).path)
                    comment.path.append(comment.id)
                    print('получилось')
                except ObjectDoesNotExist:
                    comment.path.append(comment.id)
                    print('не получилось')
                comment.save()
            return redirect(article.get_absolute_url())
как думаете так тоже хорошо ?

Evgenii Legotckoi
  • 6. Dezember 2017 06:30
Да, так будет даже лучше, я на сайте уже обновил до такого вида код

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

Что касается древовидных комментариев, то я от них как видите отказался. Я просто добавляю ID комментария, на который был дан ответ. Древовидные комментарии в итоге оказались не очень удобны для сайта с вставками программного кода.
bernar92
  • 7. Dezember 2017 04:24

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


bernar92
  • 7. Dezember 2017 04:30

так

Evgenii Legotckoi
  • 7. Dezember 2017 04:47

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

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

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

Добрый день.

Скорее всего поля неправильно были получены. Что-то не так с запросом. То есть в полученном объекте значения values не были корректно забраны. Либо просто неправильно написали названия полей в шаблоне
ИМ
  • 15. November 2018 16:35

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


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) 







Добрый день!

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


progammist
  • 20. Mai 2020 07:15

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

Evgenii Legotckoi
  • 20. Mai 2020 16:07

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

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

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

progammist
  • 21. Mai 2020 04:39
  • (bearbeitet)

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

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

Evgenii Legotckoi
  • 22. Mai 2020 02:43

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

progammist
  • 28. Mai 2020 13:56

path = ArrayField(models.IntegerField())

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

Evgenii Legotckoi
  • 28. Mai 2020 15:31

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

progammist
  • 28. Mai 2020 15:42
  • (bearbeitet)

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

Evgenii Legotckoi
  • 28. Mai 2020 15:49

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

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

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

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

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

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

b
  • 21. März 2022 05:29

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

Evgenii Legotckoi
  • 17. April 2022 17:36

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

NSProject
  • 15. Juni 2022 06:03

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

{{ comment.text|safe }}

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

Evgenii Legotckoi
  • 15. Juni 2022 06:50
NSProject
  • 15. Juni 2022 07:27

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

Evgenii Legotckoi
  • 15. Juni 2022 07:28

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

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
A
ALO1ZE19. Oktober 2024 08:19
Fb3-Dateileser auf Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken