Django 5 для начинающих

Прогресс по курсу:  9/1004

6.5 Создание системы комментариев
4 из 8 шагов пройдено
0 из 16 баллов  получено

Добавление комментариев в шаблон детальной информации о посте

Далее необходимо отредактировать шаблон blog/post/detail.html, чтобы реализовать следующее:

  • показывать общее число комментариев к посту;
  • показывать список комментариев;
  • показывать форму для добавления пользователями новых комментариев.

Мы начнем с добавления общего числа комментариев к посту.

Отредактируйте шаблон blog/post/detail.html, внеся в него изменения, как показано ниже:

{% extends "blog/base.html" %}

{% block title %}{{ post.title }}{% endblock %}

{% block content %}
    <h1>{{ post.title }}</h1>
    <p class="date">
        Published {{ post.publish }} by {{ post.author }}
    </p>
    {{ post.body|linebreaks }}
    <p>
        <a href="{% url 'blog:post_share' post.id %}">
            Share this post
        </a>
    </p>
    {% with comments.count as total_comments %}
        <h2>
            {{ total_comments }} comment{{ total_comments|pluralize }}
        </h2>
    {% endwith %}
{% endblock %}

В указанном шаблоне мы используем Django ORM-преобразователь, применяя набор запросов comments.count().

Обратите внимание, что на языке шаблонов Django для вызова методов круглые скобки не используются. Тег {% with %} позволяет присваивать значение новой переменной, которая будет доступна в шаблоне до тех пор, пока не появится тег {% endwith %}.

 Шаблонный тег {% with %} полезен тем, что он позволяет избегать многократного обращения к базе данных или к дорогостоящим методам.

Мы используем шаблонный фильтр pluralize, чтобы отображать суффикс множественного числа для слова comment, в зависимости от значения total_comments. Шаблонные фильтры на входе принимают значение переменной, к которой они применяются, и на выходе возвращают вычисленное значение.

Подробнее о шаблонных фильтрах мы узнаем далее, в разделе 6.8.

Шаблонный фильтр pluralize возвращает строковый литерал с буквой s, если значение отличается от 1. Приведенный выше текст будет прорисовываться как 0 comments, 1 comment или N comments, в зависимости от числа активных комментариев к посту.

Теперь давайте добавим список активных комментариев в шаблон детальной информации о посте.

Отредактируйте шаблон blog/post/detail.html, внеся в него следующие ниже изменения:

{% extends "blog/base.html" %}

{% block title %}{{ post.title }}{% endblock %}

{% block content %}
    <h1>{{ post.title }}</h1>
    <p class="date">
        Published {{ post.publish }} by {{ post.author }}
    </p>
    {{ post.body|linebreaks }}
    <p>
        <a href="{% url 'blog:post_share' post.id %}">
            Share this post
        </a>
    </p>
    {% 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">
                Comment {{ forloop.counter }} by {{ comment.name }}
                {{ comment.created }}
            </p>
            {{ comment.body|linebreaks }}
        </div>
    {% empty %}
        <p>There are no comments.</p>
    {% endfor %}
{% endblock %}

Мы добавили шаблонный тег {% for %}, чтобы прокручивать комментарии к посту в цикле. Если список комментариев пуст, то выводится сообщение, информирующее пользователей о том, что комментариев к этому посту нет.

Комментарии прокручиваются в цикле посредством переменной {{ forloop.counter }}, которая обновляет счетчик цикла на каждой итерации.

По каждому посту мы показываем имя пользователя, который его опубликовал, дату и текст комментария.

Наконец, давайте добавим форму комментария в шаблон.
Отредактируйте шаблон blog/post/detail.html, вставив шаблон комментарной формы, как показано ниже:

{% extends "blog/base.html" %}

{% block title %}{{ post.title }}{% endblock %}

{% block content %}
    <h1>{{ post.title }}</h1>
    <p class="date">
        Published {{ post.publish }} by {{ post.author }}
    </p>
    {{ post.body|linebreaks }}
    <p>
        <a href="{% url 'blog:post_share' post.id %}">
            Share this post
        </a>
    </p>
    {% 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">
                Comment {{ forloop.counter }} by {{ comment.name }}
                {{ comment.created }}
            </p>
            {{ comment.body|linebreaks }}
        </div>
    {% empty %}
        <p>There are no comments.</p>
    {% endfor %}
    {% include "blog/post/includes/comment_form.html" %}
{% endblock %}

Пройдите по URL-адресу http://127.0.0.1:8000/blog/ в своем браузере и кликните по заголовку поста, чтобы взглянуть на страницу подробного описания поста:

Давайте протестируем, Заполните форму валидными данными и кликните по Add comment(Добавить комментарий). Вы должны увидеть следующую ниже страницу:

Кликните по ссылке Back to the post (Вернуться к посту). Вы должны быть перенаправлены обратно на страницу детальной информации о посте и увидеть комментарий, который вы только что добавили, как показано ниже:

Добавьте еще один комментарий в этот пост. Комментарии должны располагаться под содержимым поста в хронологическом порядке, как показано ниже:

Пройдите по URL-адресу http://127.0.0.1:8000/admin/blog/comment/ в своем браузере. Вы увидите страницу администрирования со списком созданных вами комментариев:

Кликните по заголовку одного из постов, чтобы его отредактировать. Снимите флажок Active (Активен), как показано ниже, и кликните по кнопке Save. Вы будете перенаправлены на список комментариев. В столбце Active отобразится неактивный значок комментария.

Если вернуться к представлению детальной информации о посте, то можно заметить, что неактивный комментарий больше не отображается и не учитывается при подсчете общего числа активных комментариев к посту:

Благодаря полю Active можно деактивировать неуместные комментарии и не показывать их в своих постах.


  • Комментариев
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий

Илья, еще раз спасибо за помощь с сортировкой комментариев. Тут решил еще сделать так, чтобы выводилась информация о том, сколько дней назад был оставлен комментарий. Но чтобы я ни предпринял, все упирается в то, что из одного DateFIeld, в котором будет дата сегодняшнего дня, нельзя вычесть   значение другого DateFIeld в котором хранится дата публикации комментария. Пробовал преобразовать DateFIeld в date/datetime тоже без результатов. В чем же дело?

@Агаси_Мироян, На самом деле ничего сложного, добавьте следующую функцию в модели класса Comment.

from datetime import datetime, timezone

class Comment(models.Model):

...
...
...
    def when_published(self):
        now = datetime.now(timezone.utc)
        diff = now - self.created

        # 0 day to 30 days
        if diff.days < 30:
            days = diff.days
            if days == 1:
                return str(days) + " day ago"
            else:
                return str(days) + " days ago"

        # 31 day to 365 days
        if diff.days >= 30 and diff.days < 365:
            months = diff.days // 30
            if months == 1:
                return str(months) + " month ago"
            else:
                return str(months) + " months ago"

        # 365+
        if diff.days >= 365:
            years = diff.days // 365
            if years == 1:
                return str(years) + " year ago"
            else:
                return str(years) + " years ago"

И в шаблоне detail.html в нужно месте комментариев выведите 

            <div class="comment">
                <p class="text-muted fst-italic mb-2">
                    Comment {{ forloop.counter }} by {{ comment.name }}
                    {{ comment.when_published }}
                </p>
                {{ comment.body|safe }}
            </div>
Изменен Илья Перминов

@Агаси_Мироян, я специально сделал по данному принципу, чтобы можно было немного добавить условий и сделать отсчет сколько секунд, минут и часов назад был добавлен комментарий.

@Илья_Перминов, Спасибо большое, выручили. Великолепное решение. Так, значит, это решается, через метод, интересно. 

Ага, просто нет смысла хранить в БД сегодняшнюю дату и менять её каждый день.

@Илья_Перминов, Это, чтобы лишний раз БД не трогать, так?

Конечно, просто в данном случае, это не нужное значение, которое можно и так получить. А тем более ещё нужно будет его обновлять, и придумывать эту логику. Это уже лишнее, когда дататайм умеет это все делать.

@Агаси_Мироян, случайно совсем вспомнил про встроенный фильтр у джанго, который считает время с даты создания комментария. И для этого нужно в шаблоне просто добавить фильтр.

{{ comment.created|timesince }}

Можете почитать подробнее, там еще есть несколько разных шаблонных фильтров https://docs.djangoproject.com/en/4.2/ref/templates/builtins/#timesince

@Илья_Перминов, спасибо. Сколько же у джанго возможностей разных, иногда кажется, что возможностей бесконечное множество. 

@Илья_Перминов, Переделал код один в один (спасибо за интересные решения), но переменная {{ comment.when_published }} в шаблоне  detail.html  дает ошибку module 'django.utils.timezone' has no attribute 'utc' 

в модели импорты расположены в следующем порядке:

from datetime import datetime, timezone
from django.utils import timezone

в settings.py TIME_ZONE = 'UTC' 

Никак не могу понять где засада...

@Евгений_Куликов, Можете прислать свой проект, посмотрю.

Загрузите архив по этой ссылке https://mega.nz/filerequest/rANtUqzWHQ4

@Илья_Перминов, У вас получился конфликт в импорте:

from datetime import datetime, timezone
from django.utils import timezone

Так как далее есть код:

publish = models.DateTimeField(default=timezone.now, verbose_name="Дата публикации")
......
now = datetime.now(timezone.utc)

И получается что в первой строчке нужен timezone из django.utils и его метод now. А во второй строчке нужен timezone из datetime и его метод utc.

Просто правим импорт на следующий:

from datetime import datetime
from datetime import timezone as tz
from django.utils import timezone

И далее в коде меняем на:

now = datetime.now(tz.utc)
diff = now - self.created

Тем самым убираем конфликт в импорте. Надеюсь понятно объяснил.

@Илья_Перминов, Класс! Все заработало! 

Я понимал, что проблема в схожих импортах, но не догадался до такого простого выхода. Спасибо! 

Ваш курс самый лучший!!!

А как реализовать комментарий от имени авторизованного пользователя, чтобы вместо добавления имени у нас сразу был profile.user.first_name? Добавить в модель комментария Many to Many Profile и оттуда уже извлекать?

@User_678998186, Первым делом нужно добавить связь модели комментария, с юзер моделью. Только связь один к одному, так как у одного комментария может быть только один автор.

@Илья_Перминов, понял,спасибо за наводку,дальше думаю сам разберусь. Просто сам проект делаю,за шаблон ваш взял,а комментарии хочу по другому реализовать

@User_678998186, можете посмотреть как реализовано у блога 2.0

Что то я пока не догоняю зачем поле Email при добавлении коментария ? Это защита от спама ?

@Vladislav, это минимальная контактная информация, чтобы администрация сайта или другие пользователи могли связаться с пользователем, который оставил комментарий. А от спама лучше использовать капчу, например reCAPTCHA, добавление которой мы будем рассматривать в разделе 11.4.

@Дмитрий_Селезнев

Понятно, а то я подумал, что те кто захочет написать что то плохое не захочет выдавать свой e-mail  :)

Илья, а не скажете, как реализовать систему сортировки комментариев. Чтобы на странице поста, можно было переключать сортировку комментариев, типа сначала новые/сначала старые?

@Агаси_Мироян, Мы можем изменить сортировку комментариев добавив оператор order_by, и сделав выборку по полю created.

comments = post.comments.filter(active=True).order_by('-created')

А управлять этим значением мы можем изменив следующую строку в нашем views.py

comments = post.comments.filter(active=True)

на следующий код, в котором мы проверяем запросы, и исходя из них меняем нашу выборку. 

    comment_order = 'created'
    if 'desc' in request.GET:
        comment_order = '-created'
    elif 'asc' in  request.GET:
        comment_order = 'created'
    comments = post.comments.filter(active=True).order_by(comment_order)

И теперь в нужное место шаблона detail.html можем добавить наши кнопки:

<form method="get">
    <button name="desc">По убыванию</button>
    <button name="asc">По возрастанию</button>
</form>

И все будет работать. Это я привел самый простой пример, можно сделать также на основе сессий, если у человека есть значение связанное с сортировкой, то меняем вывод на всех страницах исходя из его предпочтений.

Изменен Илья Перминов

@Илья_Перминов, Здорово, получилось! Спасибо. А можно ли сделать так, чтобы при нажатии этих кнопок, страница не перезагружалась? Или для этого JavaScript нужен?

@Агаси_Мироян, Это уже JS. Может в следующем курсе рассмотрим это.

@Илья_Перминов, хм, а для get запросов тоже нужен {% csrf_token %}? Казалось токен нужно добавлять только в post

@Антон_Глухенко, Вы правы, по привычке добавил его, токен нужен только для POST, PUT и DELETE запросов

Изменен Илья Перминов

в этой строке у вас не корректность во всей это главе 

<a href="{% url "blog:post_share" post.id %}">

вместо двойных кавычек должны быть одинарные вот тут "blog:post_share", должно быть вот так вот 'blog:post_share' 



Если кто то думает что это сложно реализовать с нуля самому, я вас огорчу это очень просто сделать, так что не пугайтесь, если сами захотите что то подобное реализовать убедитесь сами в этом!

@No_Name, спасибо, поправил.

@Илья_Перминов, Ну тогда если не сложно то и в степе 6.5.3 кавычки подправьте пожалуйста:

@Максим_Михеев, Готово, спасибо!

@Илья_Перминов, и в степе 6.6.2 есть что подправить:

@Максим_Михеев, Спасибо, поправил. Вроде пробегался по всем лекциям, но все равно упустил.

Все работает, только время стоит ​​​​​​​ хотя создавал в 18:52

@Дмитрий_Чекмасов, необходимо изменить часовой пояс в файле настроек на корректный, например:

TIME_ZONE = 'Europe/Moscow'

@Дмитрий_Селезнев, понял, спасибо