Создание системы комментариев
План
- Модель
Comment. - Регистрация в админке (модерация).
- Форма
CommentForm. - View для обработки комментария.
- Вывод комментариев и формы в шаблоне поста.
Модель
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
name = models.CharField("Имя", max_length=80)
email = models.EmailField()
body = models.TextField("Комментарий")
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField("Активен", default=True)
class Meta:
ordering = ["created"]
indexes = [models.Index(fields=["created"])]
verbose_name = "комментарий"
verbose_name_plural = "комментарии"
def __str__(self):
return f"Комментарий {self.name} к «{self.post}»"
related_name="comments"→post.comments.all().activeпозволит скрывать неуместные комментарии, не удаляя их.
python manage.py makemigrations blog
python manage.py migrate
Админка
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ["name", "email", "post", "created", "active"]
list_filter = ["active", "created", "updated"]
search_fields = ["name", "email", "body"]
list_editable = ["active"]
Форма
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ["name", "email", "body"]
Поля post, active, даты в форму не включаем — пользователь не должен их задавать.
View для отправки комментария
from django.views.decorators.http import require_POST
from .forms import CommentForm, EmailPostForm
@require_POST
def post_comment(request, post_id):
post = get_object_or_404(Post, id=post_id, status=Post.Status.PUBLISHED)
comment = None
form = CommentForm(data=request.POST)
if form.is_valid():
comment = form.save(commit=False) # объект создан, но не сохранён
comment.post = post # дописываем то, чего нет в форме
comment.save()
return render(request, "blog/post/comment.html", {"post": post, "form": form, "comment": comment})
save(commit=False) — ключевой приём: получить объект модели из формы, дополнить его и только потом сохранить.
Маршрут:
path("<int:post_id>/comment/", views.post_comment, name="post_comment"),
Обновляем post_detail
def post_detail(request, year, month, day, post):
post = get_object_or_404(Post, status=Post.Status.PUBLISHED, slug=post,
publish__year=year, publish__month=month, publish__day=day)
comments = post.comments.filter(active=True)
form = CommentForm()
return render(request, "blog/post/detail.html", {"post": post, "comments": comments, "form": form})
Шаблоны
Фрагмент формы blog/post/includes/comment_form.html:
<h2>Добавить комментарий</h2>
<form action="{% url 'blog:post_comment' post.id %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<p><input type="submit" value="Отправить"></p>
</form>
В detail.html после текста поста:
{% with comments.count as total_comments %}
<h2>{{ total_comments }} comment{{ total_comments|pluralize }}</h2>
{% endwith %}
{% for comment in comments %}
<div class="comment">
<p class="info">
Комментарий {{ forloop.counter }} от {{ comment.name }}, {{ comment.created|date:"d.m.Y H:i" }}
</p>
{{ comment.body|linebreaks }}
</div>
{% empty %}
<p>Комментариев пока нет.</p>
{% endfor %}
{% include "blog/post/includes/comment_form.html" %}
Страница результата blog/post/comment.html:
{% extends "blog/base.html" %}
{% block title %}Комментарий{% endblock %}
{% block content %}
{% if comment %}
<h2>Ваш комментарий добавлен.</h2>
<p><a href="{{ post.get_absolute_url }}">Вернуться к посту</a></p>
{% else %}
{% include "blog/post/includes/comment_form.html" %}
{% endif %}
{% endblock %}
Если форма невалидна, на этой странице снова показывается форма с ошибками и введёнными данными.
Безопасность
Текст комментария выводится через {{ comment.body|linebreaks }} — Django экранирует HTML, поэтому вставленный <script> не выполнится. Никогда не применяйте |safe к пользовательскому вводу.
Улучшения
- Перенаправлять на пост после комментария (
redirect(post.get_absolute_url() + "#comments")) вместо отдельной страницы. - Хранить автора-пользователя вместо имени и email, когда появится авторизация.
- Модерация:
active=Falseпо умолчанию и публикация из админки. - Древовидные ответы на комментарии и AJAX — в проекте «Сайт статей».
Итог
Commentсвязан сPostчерезForeignKeyсrelated_name="comments".ModelForm+save(commit=False)для заполнения скрытых полей.@require_POSTдля view, принимающей только отправку формы.post.comments.filter(active=True)— только видимые комментарии.
Практика решено 0 из 6
Опишите модель Comment: связь post с Post (каскад, related_name="comments"),
name (CharField 80), email (EmailField), body (TextField), created (дата создания, автоматически),
active (BooleanField, по умолчанию True).
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
name = models.CharField(max_length=80)
email = models.EmailField()
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=True)
Создайте CommentForm (ModelForm для Comment) с полями name, email, body.
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ["name", "email", "body"]
Допишите обработку формы в post_comment: если форма валидна, создайте комментарий
без сохранения (commit=False), привяжите его к посту и сохраните.
@require_POST
def post_comment(request, post_id):
post = get_object_or_404(Post, id=post_id, status=Post.Status.PUBLISHED)
comment = None
form = CommentForm(data=request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return render(request, "blog/post/comment.html", {"post": post, "form": form, "comment": comment})
Как в post_detail получить только активные комментарии поста?
Переменная total — число комментариев. Выведите подпись по-английски: 1 comment, но 3 comments.
Окончание s добавьте фильтром pluralize.
{{ total }} comment{{ total|pluralize }}
Зачем view post_comment обёрнута в @require_POST?
Войдите, чтобы отмечать пройденные уроки.