Формирование URL-адресов в шаблонах

Курс по Django: https://stepik.org/a/183363

Архив проекта: lesson-8-coolsite.zip

На данный момент мы с вами научились создавать шаблоны, прописывать в них различные теги {% имя_тега %}, переменные {{ имя_переменной }} и фильтры {{value|имя_фильтра}}. Я не приводил подробное описание работы с этими элементами, так как ранее уже создавал курс по этой теме – шаблонизатору Jinja и, при необходимости, каждый из вас может посмотреть этот плейлист:

https://www.youtube.com/watch?v=cFJqMXxVNsI&list=PLA0M1Bcd0w8wfmtElObQrBbZjY6XeA06U

Следующий важный шаг – научиться правильно указывать URL-адреса в наших шаблонах. На предыдущих занятиях почти у всех ссылок я ставил заглушки – символ шарп:

<a href= "#">…</a>

Пришло время сформировать полноценные ссылки на страницы. Для этого в Django используется специальный тег:

{% url '<URL-адрес или имя маргрута>' [параметры ссылки] %}

Давайте для начала пропишем с помощью этого тега маршрут к главной странице в шаблоне base.html. В нем имеется вот такая строчка:

<li class="logo"><a href="/"><div class="logo"></div></a></li>

Здесь косая черта – это как раз ссылка на главную страницу. Но это не лучший подход, так как URL-адрес главной не обязательно должен совпадать с доменным именем (в нашем случае: http://127.0.0.1:8000/). Мы можем определить ее и как-то иначе, например:

http://127.0.0.1:8000/home/

И тогда везде в шаблонах придется вносить эти изменения. Гораздо практичнее и, чаще всего, так и делают, используют имена маршрутов. Если мы откроем файл women/urls.py, то увидим, что главная страница имеет имя home. Им мы и воспользуемся, указав в теге url:

<li class="logo"><a href="{% url 'home' %}"><div class="logo"></div></a></li>

Видите, как это удобно. Теперь, если изменить адрес главной страницы:

urlpatterns = [
    path('home/', index, name='home'),
    path('about/', about, name='about'),
]

И перейти по адресу:

http://127.0.0.1:8000/home/

то ссылка на главную автоматически изменится. В этом удобство именованных ссылок.

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

menu = [{'title': "О сайте", 'url_name': 'about'},
        {'title': "Добавить статью", 'url_name': 'add_page'},
        {'title': "Обратная связь", 'url_name': 'contact'},
        {'title': "Войти", 'url_name': 'login'}
]

Затем, в файле women/urls.py сформируем маршруты к этим страницам:

urlpatterns = [
    path('', index, name='home'),
    path('about/', about, name='about'),
    path('addpage/', addpage, name='add_page'),
    path('contact/', contact, name='contact'),
    path('login/', login, name='login'),
]

И в файле women/views.py пропишем указанные функции представлений:

def about(request):
    return render(request, 'women/about.html', {'menu': menu, 'title': 'О сайте'})
 
def addpage(request):
    return HttpResponse("Добавление статьи")
 
def contact(request):
    return HttpResponse("Обратная связь")
 
def login(request):
    return HttpResponse("Авторизация")

Пока это просто функции-заглушки. А вот функцию index перепишем в таком виде:

def index(request):
    posts = Women.objects.all()
    context = {
        'posts': posts,
        'menu': menu,
        'title': 'Главная страница'
    }
 
    return render(request, 'women/index.html', context=context)

Смотрите, мы здесь определили отдельный словарь context, в котором указали все те параметры, что собираемся передавать шаблону index.html, а затем, в функции render передаем этот словарь с помощью именованного параметра context. Это будет эквивалентно предыдущей записи, но так текст программы читается гораздо лучше.

Осталось в самом шаблоне правильно обработать коллекцию menu. В файле шаблона base.html пропишем следующие строчки:

{% for m in menu %}
         {% if not forloop.last %}
                            <li><a href="{% url m.url_name %}">{{m.title}}</a></li>
         {% else %}
                            <li class="last"><a href="{% url m.url_name %}">{{m.title}}</a></li>
         {% endif %}
{% endfor %}

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

Формирование динамических URL-адресов

Осталось прописать ссылки у списка наших статей. Во-первых, определим шаблон маршрута, по которому они будут доступны. Для этого в файле women/urls.py добавим строчку в urlpatterns:

path('post/<int:post_id>/', show_post, name='post'),

то есть, маршрут постов будет выглядеть так:

http://127.0.0.1:8000/post/<идентификатор_поста>/

Конечно, в реальных проектах вместо post_id, как правило, используется строка (слаг), записанная латиницей и отражающая суть статьи. Такие ссылки лучше ранжируются поисковыми системами и понятнее пользователям сайта. Позже мы тоже будем использовать слаг, но на данном этапе нам важно разобраться, как создавать динамические URL-ссылки на уровне шаблонов.

Итак, маршрут определен, имя маршрута задано как post, осталось прописать функцию представления show_post. Мы ее пока определим вот такой заглушкой:

def show_post(request, post_id):
    return HttpResponse(f"Отображение статьи с id = {post_id}")

Осталось все это определить в шаблоне index.html, в котором происходит отображение списка статей. Фактически, все что нам нужно – это переписать следующую строчку:

<p class="link-read-post"><a href="{% url 'post' p.pk %}">Читать пост</a></p>

Смотрите, здесь в параметре href используется уже знакомый нам тег шаблона url, далее указываем имя ссылки post и через пробел ее параметр post_id в виде p.pk. Вспоминаем, что мы перебираем здесь коллекцию posts, которая хранит ссылки на объекты класса модели Women. И у этих объектов имеется параметр pk, равный идентификатору записи. Почему мы используем pk, а не id? В принципе, можно указать е его, но согласно конвенции Django, лучше использовать атрибут pk.

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

http://127.0.0.1:8000/post/5/

и увидим строчку «Отображение статьи с id = 5» от функции заглушки show_post. Вот так довольно просто можно формировать различные динамические URL-адреса на уровне шаблонов.

Функция get_absolute_url()

Как я уже не раз отмечал, фреймворк Django работает согласно паттерну MTV (Models, Templates, Views), то есть, ему постоянно приходится связывать модели с шаблонами и видами, а значит, формировать URL-ссылки для выбранных записей из таблиц БД. И мы только что это делали – формировали URL-ссылки для записей модели Women. При разработке сайтов – это типовая операция. Поэтому, разработчики Django озаботились упрощением и автоматизацией этой процедуры. Что они нам предлагают? Смотрите. В классах моделей можно определять специальный метод под названием:

get_absolute_url()

который бы возвращал полный URL-адрес для каждой конкретной записи (ассоциированной с текущим объектом). Например, в классе модели Women мы можем определить этот метод следующим образом:

    def get_absolute_url(self):
        return reverse('post', kwargs={'post_id': self.pk})

Здесь используется функция reverse, которая строит текущий URL-адрес записи на основе имени маршрута post и словаря параметров kwargs. В данном случае указан один параметр post_id со значением идентификатора объекта self.pk. Разумеется, вначале нам нужно импортировать эту функцию:

from django.urls import reverse

Все, теперь при обращении к методу get_absolute_url объекта класса модели Women, мы будем получать ее URL-адрес.

Где здесь упрощение и автоматизация? Смотрите, в шаблоне index.html, при формировании ссылок статей, мы теперь можем вместо тега шаблона url указать метод get_absolute_url:

<p class="link-read-post"><a href="{{ p.get_absolute_url }}">Читать пост</a></p>

Вспоминаем, что p как раз ссылается на объекты класса Women, следовательно, у нее появился атрибут get_absolute_url, который мы и прописываем. И, обратите внимание, при указании этого метода, мы не пишем в конце круглые скобки, т.к. он здесь самостоятельно не вызывается. Вызов сделает функция render при обработке этого шаблона.

Почему это лучше тега url? Представьте, что в будущем мы изменили шаблон этой ссылки и стали выводить посты не по id, а по слагу. Тогда, при использовании тега url, нам пришлось бы менять эти ссылки в каждом шаблоне, заменяя self.pk на self.slug, например. В этом как раз неудобство и источник потенциальных ошибок. Теперь, с методом get_absolute_url() нам достаточно изменить маршрут в нем и это автоматически скажется на всех шаблонах, где она используется.

Второй важный момент функции get_absolute_url() заключается в том, что согласно конвенции, модули Django используют этот метод в своей работе (если он определен в модели). Например, стандартная админ-панель обращается к этому методу для построения ссылок на каждую запись наших моделей. И в дальнейшем мы увидим как это работает.

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

Курс по Django: https://stepik.org/a/183363

Видео по теме