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

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

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

Создание шаблонного фильтра для поддержки синтаксиса Markdown

Мы создадим конкретно-прикладной фильтр, который позволит использовать синтаксис упрощенной разметки Markdown в постах блога, а затем в шаблонах конвертировать текст поста в HTML.

Markdown – это синтаксис форматирования обычного текста, который очень прост в использовании и предназначен для конвертирования в HTML. Посты можно писать, используя простой синтаксис Markdown, и получать автоматическую конвертацию контента в исходный код HTML. Изучить синтаксис Markdown намного проще, чем изучить HTML. Используя Markdown, можно сделать так, чтобы другие не сведущие в технологиях участники легко писали посты для вашего блога.

С основами формата Markdown можно ознакомиться на странице https://daringfireball.net/projects/markdown/basics.

Сначала установите модуль Python markdown посредством pip, используя следующую ниже команду в командной оболочке:

pip install markdown

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

from django.utils.safestring import mark_safe
import markdown


@register.filter(name='markdown')
def markdown_format(text):
    return mark_safe(markdown.markdown(text))

Шаблонные фильтры регистрируются таким же образом, как и шаблонные теги.

Во избежание конфликта имен между именем функции и модулем markdown мы дали функции имя markdown_format, а фильтру – имя markdown для использования в шаблонах, в частности {{ variable|markdown }}.

Django экранирует генерируемый фильтрами исходный код HTML; символы HTML-сущностей заменяются их кодированными в HTML символами. Например, <p> преобразовывается в &lt;p&gt;(символ <, символ p, символ >).

Мы используем предоставляемую веб-фреймворком Django функцию mark_safe, чтобы помечать результат как безопасный для прорисовки в шаблоне исходный код HTML. По умолчанию Django не будет доверять никакому исходному коду HTML и будет экранировать его перед его вставкой в результат.

Единственными исключениями являются переменные, которые помечены как безопасные, чтобы тем самым избежать экранирования. Такое поведение не дает Django выводить потенциально опасный исходный код HTML и позволяет создавать исключения, дабы возвращать безопасный исходный код HTML.

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

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

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

{% block content %}
    <h1>{{ post.title }}</h1>
    <p class="date">
        Published {{ post.publish }} by {{ post.author }}
    </p>
    {{ post.body|markdown }}
    <p>
        <a href="{% url 'blog:post_share' post.id %}">
            Share this post
        </a>
    </p>
    <h2>Similar posts</h2>
    {% for post in similar_posts %}
        <p>
            <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
        </p>
    {% empty %}
        There are no similar posts yet.
    {% endfor %}

    {% 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 %}

Фильтр linebreaks шаблонной переменной {{ post.body }} был заменен фильтром markdown.

Данный фильтр не только преобразовывает разрывы строк в теги <p>, но и конвертирует форматирование Markdown в HTML.

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

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

{% block title %}My Blog{% endblock %}

{% block content %}
    <h1>My Blog</h1>
    {% if tag %}
        <h2>Posts tagged with "{{ tag.name }}"</h2> {% endif %}
    {% for post in posts %}
        <h2>
            <a href="{{ post.get_absolute_url }}">
                {{ post.title }}
            </a>
        </h2>
        <p class="tags">
            Tags:
            {% 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 %}
        </p>
        <p class="date">
            Published {{ post.publish }} by {{ post.author }}
        </p>
        {{ post.body|markdown|truncatewords_html:30 }}
    {% endfor %}
    {% include "pagination.html" with page=posts %}
{% endblock %}

В шаблонную переменную {{ post.body }} был добавлен новый фильтр markdown.

Этот фильтр преобразовывает контент в формате Markdown в формат HTML. Поэтому мы заменили приведенный выше фильтр truncatewords фильтром truncatewords_html. Данный фильтр усекает строку после определенного числа слов, избегая незакрытых HTML-тегов.

Теперь пройдите по URL-адресу http://127.0.0.1:8000/admin/blog/post/add/ в своем браузере и создайте новый пост со следующим ниже текстом:

This is a post formatted with markdown

--------------------------------------

*This is emphasized* and **this is more emphasized**.

Here is a list:

* One

* Two

* Three

 

And a [link to the Django website](https://www.djangoproject.com/)

Форма должна выглядеть следующим образом:

Пройдите по URL-адресу http://127.0.0.1:8000/blog/ в своем браузере и посмотрите, как прорисовывается новый пост.
Вы должны увидеть следующий ниже результат:

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

Более подробная информация о конкретно-прикладных фильтрах находится по адресу https://docs.djangoproject.com/en/5.0/howto/custom-template-tags/#writing-custom-template-filters.


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

Было бы неплохо при изменении большого html файла комментарием выделять какие именно строки добавлены или исправлены

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

Изменен Евгений Габовский

@Евгений_Габовский, выполните команду в терминале:

pip freeze > requirements.txt

А затем загрузите архив проекта по ссылке https://mega.nz/filerequest/rANtUqzWHQ4 , посмотрим в чём причина.

@Евгений_Габовский, удалось решить проблему?

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

@Георгий_Тимофеев, редактор мы будем добавлять в блоге 2.0(раздел 11.5).

Markdown тоже нужен, например на нём могут быть написаны описания проектов(README.md) на гитхабе:

https://docs.github.com/ru/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/about-writing-and-formatting-on-github

https://docs.github.com/ru/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax

Я, возможно, что-то пропустил, но в какой момент был реализован этот URL маршрут, ссылка на который формируется в blog/post/list.html:

?

В предыдущем разделе всего один шаг, и он посвящен другой функции.
Без добавления маршрута приложение "падает" на главной странице.

Добавить не сложно, конечно:

urlpatterns = [
    ...,
    path('post_list/<slug:tag_slug>/', views.post_list, name='post_list_by_tag'),
]
Изменен ilya kutaev

@ilya_kutaev, 6.6.2 добавляли:

Добавьте следующий ниже дополнительный шаблон URL-адреса, чтобы отображать список постов по тегу:

path('tag/<slug:tag_slug>/',
      views.post_list, name='post_list_by_tag'),

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

И путь не угадал, но это непринципиально

Интересно, какой процент потенциальных пользователей блога захотят (смогут) использовать синтаксис разметки Markdown... Хорошо бы показать практическое применение пакета CKEditor, особенно его применение в  формах. 

@Евгений_Куликов, думаю совсем не большой, лично сталкивался с ним только на GitHub, при написании файла README.md.

Про применение CKEditor рассказывается в уроке 11.5.

@Дмитрий_Селезнев,  Обычно грызу гранит знаний постепенно, не забегая вперед. Пришлось посмотреть )))

Я не очень хорошо ознакомлена с тэгами и самим html, поэтому не совсем понимаю, почему, несмотря на то, что мы уже загрузили тэг

{% load blog_tags %}

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

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

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

Сделал все точно по шагам. Почему то markdown не валидный фильтр получаю ошибку

@Ilia_Boiarintsev, нужны еще данные, хотя бы что за ошибка?

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

я уже потом поменял местами фильтры

@Ilia_Boiarintsev, Пришлите содержимое файла templatetags/blog_tags.py

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

import markdown
from django import template
from django.db.models import Count
from django.utils.safestring import mark_safe
from ..models import Post

register = template.Library()


@register.simple_tag
def total_posts():
    return Post.published.count()

@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.simple_tag
def get_most_commented_posts(count=5):
    return Post.published.annotate(
        total_comments=Count('comments')
    ).order_by('-total_comments')[:count]

@register.filter(name='markdown')
def markdown_format(text):
    return mark_safe(markdown.markdown(text))

@Ilia_Boiarintsev, Выполните команду:

pip freeze > requirements.txt

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

@Илья_Перминов, загрузил

@Ilia_Boiarintsev, Вы прислали 1 папку проекта, мне нужны все файлы проекта с приложением blog.

Примерно как на скриншоте, только команда выше создаст еще один файл.

@Илья_Перминов, исправился

@Ilia_Boiarintsev, Первым делом у вас пропущена финальная модель постов(раздел 5.2 курс), там поле статуса добавилось. Далее пропущен раздел 5.4, мы добавляем менеджер к модели постов. Раздел 5.6, 6.4, 6.5, 6.6, 6.7, 6.8 вообще пропущены. Я даже не знаю что тут смотреть.

@Ilia_Boiarintsev, Вот как должен выглядеть проект на этом этапе - https://github.com/Permin0ff/Course_mysite_02.git

@Илья_Перминов,какжется я просто проект не сохранил

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

@Ilia_Boiarintsev, да, загружайте по прежней ссылке, я посмотрю.

@Илья_Перминов, загрузил

@Ilia_Boiarintsev, Давайте по порядку.

Во первых, проект создаем django-admin startproject blog . То есть точка в конце обязательна, чтобы проект создался в корне папки с виртуальной средой. Мы изучали это в самом начале курса.

Во вторых в шаблонах не хватает строчки {% load blog_tags %}, вверху кода. Добавьте ее в list.html и detail.html

@Илья_Перминов, Благодарю

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

@Ilia_Boiarintsev, я про вот эту лекцию, где мы разбираем как в бесплатной версии создавать проекты и приложения. Все пользуются разными IDE, мы же не будем несколько раз писать одно и тоже.

@Илья_Перминов, разобрался с иерархией папок, спасибо!