Cat

Django 41. Комментарии к постам

В этом посте мы добавим на сайт комментарии к постам.

Все статьи

Icon Link

Дополнительные материалы

Icon Link

Реклама

Icon Link
Сайт на Django proDream 03 Апрель 2024 Просмотров: 1520

В нашем проекте не хватает одного важного элемента – блока комментариев на странице поста. Давайте исправим это.

Что мы сделаем:

  1. Добавим на страницу поста отображение комментариев.
  2. Добавим возможность авторизованным пользователям оставлять комментарии.
  3. Также у автора комментария или администратора должна быть возможность удалить или отредактировать комментарий.

 

Модель комментария.

Для хранения комментариев нам нужна модель.

Откроем файл models.py в директории приложения blog.

Создадим класс CommentModel, унаследованный от models.Model, и пропишем четыре поля:

  • user – Внешний ключ на модель пользователя.
  • post – Внешний ключ на модель поста. Для доступа к модели комментария из объекта модели поста указываем аргумент related_name="comments".
  • comment – Текстовое поле для комментария.
  • created_at – Поле с датой написания комментария.

Также пропишем внутренний класс Meta с названием модели и dunder-метод __str__ с текстовым представлением объекта модели.

 

Код модели:

class CommentModel(models.Model):  
    user = models.ForeignKey(  
        User, on_delete=models.CASCADE, verbose_name="Пользователь"  
    )  
    post = models.ForeignKey(  
        PostModel,  
        on_delete=models.CASCADE,  
        verbose_name="Пост",  
        related_name="comments",  
    )  
    comment = models.TextField(verbose_name="Комментарий")  
    created_at = models.DateTimeField(auto_now_add=True)  

    class Meta:  
        verbose_name = "Комментарий"  
        verbose_name_plural = "Комментарии"  

    def __str__(self):  
        return f"Комментарий от {self.user} к посту {self.post}"

 

Регистрация в панели администратора.

Откроем файл admin.py.

Создадим класс CommentAdmin, унаследованный от admin.ModelAdmin. Обернём его в декоратор @admin.register(), передав в качестве аргумента модель комментария.

Внутри класса поля можно прописать по желанию, например:

  • list_display - отображаемые столбцы.
  • list_filter - выбор по каким полям проводить фильтрацию.

Дополнительно добавим столбец с названием поста при клике на которую будет открываться страница поста.

Для этого создадим метод post_link, в аргументах он принимает self и объект модели obj.
Внутри метода, используя функцию mark_safe, возвращаем строку со ссылкой.

Затем ниже вызываем метод allow_tags и устанавливаем его в True. Это позволит выводить HTML на странице панели администратора.

Не забываем прописать новое поле в list_display.

 

Код класса:

@admin.register(models.CommentModel)  
class CommentAdmin(admin.ModelAdmin):  
    list_display = ("user", "post_link", "created_at", "comment")  
    list_filter = ("user", "post")  

    def post_link(self, obj):  
        return mark_safe(  
            f'<a href="{obj.post.get_absolute_url()}">{obj.post.title}</a>'  
        )  

    post_link.allow_tags = True

 

Форма добавления комментария.

Для того чтобы можно было добавлять новый комментарий, необходима форма модели.

Откроем файл forms.py и создадим класс CommentForm, унаследованный от forms.ModelForm.

Форма будет весьма классическая:

  • Определяем поле comment.
  • Прописываем класс Meta, указав модель и всего одно поле - comment.

 

Код формы:

class CommentForm(forms.ModelForm):  
    comment = forms.CharField(  
        widget=forms.Textarea(  
            attrs={  
                "class": "form-control",  
                "placeholder": "Введите текст комментария",  
                "rows": 5  
            }  
        ),  
    )  

    class Meta:  
        model = CommentModel  
        fields = ("comment",)

 

Представления.

Теперь займёмся представлениями. Нам для комментариев нужно три представления:

  • Добавление комментария.
  • Редактирование.
  • Удаление.

Также необходимо изменить представление страницы поста для передачи в него формы комментария.

 

Представление страницы поста.

Откроем файл views.py. Найдём класс PostPageView и добавим метод get_context_data.
Он тоже весьма стандартный. Внутри метода в контекст добавим новый ключ comment_form, в который определим объект формы CommentForm.

 

Код метода:

def get_context_data(self, **kwargs):  
    context = super().get_context_data(**kwargs)  
    context["comment_form"] = CommentForm()  
    return context

 

Базовое представление.

Нам понадобится три представления, и по сути они идентичны. Различия только в наследуемых классах, поэтому, чтобы избежать дублирования кода, создадим базовый класс BaseCommentView.

В классе пропишем поле model, в котором определим класс модели комментария.

И переопределим метод get_success_url, принимающий только self.
В нём мы вернём путь до страницы поста.

 

Код класса:

class BaseCommentView:  
    model = CommentModel  

    def get_success_url(self):  
        post = models.PostModel.objects.get(pk=self.object.post.pk)  
        return reverse(  
            "blog:post_page",  
            kwargs={"category_slug": post.category.slug, "slug": post.slug},  
        )

 

Представление добавления комментария.

Создадим класс AddCommentView, унаследованный от BaseCommentView и CreateView, который будет отвечать за добавление комментария.

В нём мы добавим поле form_class, определив в нём класс формы.

И переопределим метод form_valid, принимающий self и объект формы form.
Поскольку в модель класса требуется передавать пользователя и связанный с комментарием пост, а в форме мы не можем этого сделать, определим их тут.
Внутри метода определяем поля user и post, затем вызываем super-метод для сохранения объекта.

 

Код представления:

class AddCommentView(BaseCommentView, CreateView):  
    form_class = CommentForm  

    def form_valid(self, form):  
        form.instance.user = self.request.user  
        form.instance.post = models.PostModel.objects.get(pk=self.kwargs.get("pk"))  
        return super().form_valid(form)

 

Представления редактирования и удаления комментария.

Создадим два класса:

  • EditCommentView, унаследованный от BaseCommentView и UpdateView.
  • DeleteCommentView, унаследованный от BaseCommentView и DeleteView.

В обоих классах пропишем поле template_name, в котором пропишем шаблон для страницы редактирования и страницы удаления комментария.

В классе EditCommentView дополнительно пропишем поле form_class, как в классе добавления комментария.

 

Код классов:

class EditCommentView(BaseCommentView, UpdateView):  
    form_class = CommentForm  
    template_name = "blog/comment_edit.html"  


class DeleteCommentView(BaseCommentView, DeleteView):  
    template_name = "blog/comment_delete.html"

 

Маршруты представлений.

Для всех трёх представлений осталось создать маршруты. Для этого откроем файл urls.py и в список urlpatterns добавим следующие строки:

path("comment/add/<int:pk>/", views.AddCommentView.as_view(), name="add_comment"),  
path(  
    "comment/edit/<int:pk>/", views.EditCommentView.as_view(), name="edit_comment"  
),  
path(  
    "comment/delete/<int:pk>/",  
    views.DeleteCommentView.as_view(),  
    name="delete_comment",  
),

 

Шаблоны комментариев.

Начнём с основного шаблона вывода комментариев.

 

Шаблон добавления и вывода комментариев.

В директории с шаблонами создадим новый файл comments.html.

В нём пропишем шаблон для отображения комментариев:

<div class="row">
    <h2 class="mt-3 mb-3">Добавить комментарий:</h2>
    {% if user.is_authenticated %}
        <form method="post" enctype="multipart/form-data" action="{% url "blog:add_comment" pk=post.pk %}">
            {% csrf_token %}
            {{ comment_form.comment }}
            <button class="btn btn-outline-warning mt-3" type="submit">Отправить</button>
        </form>
    {% else %}
        <p><a href="{% url 'user_app:login' %}">Войдите</a> чтобы добавить комментарий.</p>
    {% endif %}
</div>

<div class="row">
    <h2 class="mt-3 mb-3">Комментарии пользователей:</h2>
    {% if comments %}
        {% for comment in comments %}
            <p><a href="{% url 'user_app:user_profile' username=post.author %}">{{ comment.user }}</a>
                | {{ comment.created_at }} | {% if comment.user  user or user.is_superuser %}
                    <a href="{% url 'blog:edit_comment' comment.pk %}">Редактировать</a> |
                    <a href="{% url 'blog:delete_comment' comment.pk %}">Удалить</a>
                {% endif %}</p>
            <p>{{ comment.comment }}</p>
            <hr>
        {% endfor %}
    {% else %}
        <p>Нет комментариев</p>
    {% endif %}
</div>

 

В первом блоке выводим форму для добавления комментария. Обратите внимание, что в форме передаём аргумент action с маршрутом на представление добавления комментария.

Во втором блоке в цикле выводим комментарии. Также проверяем, что автор комментария – это текущий пользователь или администратор, чтобы отобразить кнопки для редактирования и удаления комментария.

Далее откроем шаблон страницы поста и в удобное место подключим файл шаблона комментариев, передав в него объект поста и список комментариев:

{% include "blog/modules/comments.html" with post=post comments=post.comments.all%}

 

Шаблон редактирования комментария.

Создадим файл comment_edit.html и пропишем следующий код:

{% extends 'blog/base.html' %}
{% block title %}Редактирование комментария{% endblock %}

{% block content %}
    <div class="form-section container mt-3">
        <h2>Редактирование комментария</h2>
        <form method="post">
            {% csrf_token %}
            <p>{{ form.comment }}</p>
            <button class="btn btn-primary" type="submit">Обновить</button>
            <a href="{% url 'blog:post_page' slug=object.post.slug category_slug=object.post.category.slug %}"
               class="btn btn-secondary">Отмена</a>
        </form>
    </div>
{% endblock %}

 

Шаблон удаления комментария.

Создадим файл comment_delete.html и пропишем следующий код:

{% extends 'blog/base.html' %}
{% block title %}Удаление комментария{% endblock %}

{% block content %}
    <div class="form-section container mt-3">
        <h2>Удаление комментария</h2>
        <p>Вы действительно хотите удалить этот комментарий?</p>
        <pre>{{ object.comment }}</pre>
        <form method="post">
            {% csrf_token %}
            <button class="btn btn-danger me-2" type="submit">Да, удалить</button>
            <a href="{% url 'blog:post_page' slug=object.post.slug category_slug=object.post.category.slug %}"
               class="btn btn-secondary">Отмена</a>
        </form>
    </div>
{% endblock %}

 

Заключение.

Добавить комментарии несложно. Куда сложнее будет далее за ними следить. Хоть у нас и есть "защита" в виде ограничения на зарегистрированных пользователей, спамеры "не дремлют", и, возможно, в будущем придётся добавлять защиту в виде капчи или что-то другое.

Автор