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

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

6.8 Реализация конкретно-прикладных шаблонных тегов и фильтров
3 из 3 шагов пройдено

Создание шаблонного тега включения

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

Отредактируйте файл templatetags/blog_tags.py, добавив следующий ниже исходный код:

@register.inclusion_tag('blog/post/latest_posts.html')
def show_latest_posts(count=5):
    latest_posts = Post.published.order_by('-publish')[:count]
    return {'latest_posts': latest_posts}

 В приведенном выше исходном коде мы зарегистрировали шаблонный тег, применяя декоратор @register.inclusion_tag.
Используя blog/post/latest_posts.html, был указан шаблон, который будет прорисовываться возвращенными значениями.

Шаблонный тег будет принимать опциональный параметр count, который по умолчанию равен 5.
Этот параметр позволит задавать число отображаемых постов. Данная переменная используется для того, чтобы ограничивать результаты запроса Post.published.order_by('-publish')[:count].

Обратите внимание, что приведенная выше функция возвращает не простое значение, а словарь переменных. Теги включения должны возвращать словарь значений, который используется в качестве контекста для прорисовки заданного шаблона. Только созданный шаблонный тег позволяет задавать опциональное число отображаемых постов как {% show_latest_posts 3 %}.

Теперь создайте новый файл шаблона в разделе blog/post/ и назовите его latest_posts.html.

Отредактируйте новый шаблон blog/post/latest_posts.html, добавив следующий ниже исходный код:

<ul>
    {% for post in latest_posts %}
        <li>
            <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
        </li>
    {% endfor %}
</ul>

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

Теперь отредактируйте шаблон blog/base.html, добавив в самый конец наш новый шаблонный тег, чтобы отображать последние три поста, как показано ниже:

.....

    <h3>Latest posts</h3>
    {% show_latest_posts 3 %}
  </div>
</body>
</html>

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

 

Создание шаблонного тега, возвращающего набор запросов

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

Отредактируйте файл templatetags/blog_tags.py, добавив следующую ниже инструкцию импорта и шаблонный тег:

from django.db.models import Count

@register.simple_tag
def get_most_commented_posts(count=5):
    return Post.published.annotate(
        total_comments=Count('comments')
    ).exclude(total_comments=0).order_by('-total_comments')[:count]

В приведенном выше шаблонном теге с помощью функции annotate() формируется набор запросов QuerySet, чтобы агрегировать общее число комментариев к каждому посту.

Функция агрегирования Count используется для сохранения количества комментариев в вычисляемом поле total_comments по каждому объекту Post.

Набор запросов QuerySet упорядочивается по вычисляемому полю в убывающем порядке. Также предоставляется опциональная переменная count, чтобы ограничивать общее число возвращаемых объектов.

В дополнение к Count Django предлагает функции агрегирования Avg, Max, Min и Sum.

Подробнее о функциях агрегирования можно почитать на странице https://docs.djangoproject.com/en/5.0/topics/db/aggregation/.

Далее отредактируйте шаблон blog/base.html, добавив следующий ниже исходный код:

.......

      <h3>Latest posts</h3>
      {% show_latest_posts 3 %}
      <h3>Most commented posts</h3>
      {% get_most_commented_posts as most_commented_posts %}
      <ul>
         {% for post in most_commented_posts %}
         <li>
            <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
         </li>
         {% endfor %}
      </ul>
   </div>
</body>
</html>

В приведенном выше исходном коде результат сохраняется в конкретно прикладной переменной, используя аргумент as, за которым следует имя переменной.

В качестве шаблонного тега используется {% get_most_commented_posts as most_commented_posts %}, чтобы сохранить результат шаблонного тега в новой переменной с именем most_commented_posts.

Затем возвращенные посты отображаются, используя HTML-элемент в виде неупорядоченного списка.

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

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

Подробнее о них можно почитать на странице https://docs.djangoproject.com/en/5.0/howto/custom-template-tags/.


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

Наверное необходимо убрать из подсчета скрытые комменты

Post.published.annotate(
    total_comments=Count('comments', filter=Q(comments__active=True))
).exclude(total_comments=0).order_by('-total_comments')[:count]

а как настроить шалбон list.html, что бы символ запятой между тэгами стоял не отделенный двумя пробелами?

так: jazz, music

вместо: jazz , music

Изменен Виктор Русинович

@Виктор_Русинович, Для этого нужно выводить в шаблоне в одну строку:

            {% for tag in post.tags.all %}
                <a href="{% url "blog:post_list_by_tag" tag.slug %}">{{ tag.name }}</a>{% if not forloop.last %}, {% endif %}
            {% endfor %}

@Илья_Перминов, получилось, спасибо большое за ответ!

я исправил баг когда выводились посты без комментраиев:

@register.simple_tag
def get_most_commented_posts(count=0):
    total_comments_by_post  = Post.published.annotate(total_comments=Count('comments')).order_by('-total_comments')[:5]
    for i in range(len(total_comments_by_post)):
        if total_comments_by_post[i].total_comments > 0:
            count += 1
    return Post.published.annotate(total_comments=Count('comments')).order_by('-total_comments')[:count]

А в какой момент у вас выводились посты без комментариев?

@Максим_Евланов,  Все, понял о чем вы. Только можно сделать проще:

@register.simple_tag
def get_most_commented_posts(count=5):
    return Post.published.annotate(total_comments=Count('comments')).exclude(total_comments=0).order_by(
        '-total_comments')[:count]

Просто уберем из запроса все посты, у которых комментариев нет, через exclude(total_comments=0)

Добавили это в лекцию.

@Андрей_Костин, Нет, это не наш курс. Видимо там использовалась книга Меле, более ранняя ее версия по Django 2, в качестве основы, у нас на основе её сделаны 5-6й модули и это указано в списке используемой литературы.

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

почему в итоге в blog_tags.py данная строка '@register.simple_tag' будет дважды указана? Разве одного раза недостаточно? 

@Yernur_Satybaldiyev, Это декоратор регистрации шаблонного тега. Декораторы же указываются для конкретных функций.