Курс по Django: https://stepik.org/a/183363
Архив проекта: 22_sitewomen.zip
На этом занятии
мы сделаем отображение отдельных статей по их слагу (slug). Если кто не
знает, то slug – это
уникальный фрагмент URL-адреса, ассоциированный с конкретной записью и,
обычно, состоит из набора маленьких латинских букв, цифр, символов подчеркивания
и дефиса. Например, статья «Основные методы строк» на сайте https://proproprogs.ru
доступна по следующему адресу:
Здесь slug – это последние
символы, по которым и выбирается данная страница из БД. Использование слагов –
рекомендуемая практика в веб-программировании. Такие страницы лучше ранжируются
поисковыми системами и понятнее конечному пользователю.
Давайте вначале
сделаем отображение статей по их идентификатору, а затем, заменим адрес на
слаг. У нас уже есть функция-заглушка show_post() в файле women/views.py. Мы ее
перепишем, следующим образом:
def show_post(request, post_id):
post = get_object_or_404(Women, pk=post_id)
data = {
'title': post.title,
'menu': menu,
'post': post,
'cat_selected': 1,
}
return render(request, 'women/post.html', context=data)
Здесь функция get_object_or_404 выбирает одну
запись из таблицы Women, которая имеет идентификатор, равный post_id, либо генерирует
исключение 404, если запись не была найдена. Это аналог следующего кода:
try:
post = Women.objects.get(pk=post_id)
except Women.DoesNotExist:
raise Http404("Page Not Found")
Но прописывать
постоянно такие строчки не очень удобно, поэтому в Django для таких
случаев заготовлена специальная функция get_object_or_404().
Далее,
формируется словарь из параметров шаблона и отображается страница на основе
шаблона post.html. У нас пока нет
такого файла, добавим его со следующим содержимым:
{% extends 'base.html' %}
{% block content %}
<h1>{{post.title}}</h1>
{% if post.photo %}
<p ><img class="img-article-left" src="{{post.photo.url}}"></p>
{% endif %}
{{post.content|linebreaks}}
{% endblock %}
Здесь все
достаточно очевидно. Вначале отображаем заголовок h1, затем,
фотографию статьи, если она есть, ну и потом уже содержимое самой статьи.
Если теперь
перейти по ссылке, то увидим статью, взятую по указанному индексу:
http://127.0.0.1:8000/post/1/
Если же указать
неверный адрес, то получим исключение 404. Повторю еще раз, исключения в таком
развернутом виде отображаются только в режиме отладки сайта. При эксплуатации с
константой DEBUG = False вместо
исключения отображается заготовленная страница 404.
Добавление слага
Следующим шагом
сделаем отображение статей по их слагу. Но откуда нам его взять? Для этого в
модели Women необходимо
прописать еще одно поле, которое так и назовем – slug:
class Women(models.Model):
title = models.CharField(max_length=255, verbose_name="Заголовок")
slug = models.SlugField(max_length=255, unique=True, db_index=True, verbose_name="URL")
...
Я его определил
после поля title, указав
уникальным и индексируемым. Однако если сейчас попытаться создать миграцию для
внесения этих изменений в структуру таблицы women:
python
manage.py makemigrations
то увидим
предупреждение, что поле не может быть пустым (так как у нас есть записи в
таблице). Чтобы таблица была сформирована, временно пропишу еще два параметра blank=True
и default='', а unique=True уберу:
slug = models.SlugField(max_length=255, db_index=True, blank=True, default='')
Снова выполним
команду:
python manage.py makemigrations
и видим, что
теперь никаких ошибок нет и был создан еще один файл миграций со значением 2.
Применим эти
миграции для добавления нового поля в таблицу women:
python
manage.py migrate
Видим, что в
таблице было успешно создано новое поле slug и размещено в
самом конце.
Давайте теперь
его заполним уникальными значениями. Для этого я перейду в терминал:
python
manage.py shell_plus
(модели здесь импортируются
автоматически) и выполню следующие команды:
for w in Women.objects.all():
w.slug = 'slug-'+str(w.pk)
w.save()
Теперь у каждой
записи свой слаг в виде строки «slug-<идентификатор>».
Вернемся к
определению модели. Изменим поле slug следующим образом:
slug = models.SlugField(max_length=255, db_index=True, unique=True)
Создадим
миграцию для внесения изменений в таблицу:
python
manage.py makemigrations
и применим ее:
python
manage.py migrate
Теперь поле slug у нас должно
быть уникальным, причем это условие уровня базы данных, то есть СУБД
дополнительно будет проверять на уникальность поля slug каждой
добавляемой или изменяемой записи.
Отлично, база
данных готова и теперь можно сделать отображение статей по слагу. Для этого
откроем файл women/urls.py и в списке urlpatterns
изменим маршрут для постов на следующий:
path('post/<slug:post_slug>/', views.show_post, name='post'),
Затем, в файле women/views.py немного
поменяем функцию представления show_post:
def show_post(request, post_slug):
post = get_object_or_404(Women, slug=post_slug)
...
Теперь, если
перейти по адресу:
http://127.0.0.1:8000/post/slug-1/
то увидим
отображение первой статьи по слагу, а не идентификатору статьи, что несколько
понятнее для человека. Конечно, на данный момент у нас слаг не очень понятный,
поэтому я перейду в таблицу women и у всех статей вручную поменяю слаги
на более удобочитаемые:
- andzhelina-dzholi
- margo-robbi
- dzhuliya-roberts
- ekaterina-guseva
Сохраняем
изменения, переходим по адресу:
http://127.0.0.1:8000/post/andzhelina-dzholi/
и видим пост по
Анджелине Джоли.
Изменение ссылок в шаблоне. Метод get_absolute_url()
Однако если мы
сейчас перейдем на главную страницу сайта, то ссылки кнопки «Читать пост» у нас
по-прежнему отображаются с идентификатором. Давайте тоже это поправим и сделаем
ссылки со слагами. Для этого в модели Women (в файле women/models.py) будем формировать
нужный нам URL-адрес по
параметру slug с помощью
метода get_absolute_url() следующим
образом:
class Women(models.Model):
...
def get_absolute_url(self):
return reverse('post', kwargs={'post_slug': self.slug})
...
В шаблоне index.html вызовем этот
метод для каждого объекта класса Women:
{% extends 'base.html' %}
{% block content %}
<ul class="list-articles">
{% for p in posts %}
{% if p.is_published %}
<li><h2>{{p.title}}</h2>
{% autoescape off %}
{{p.content|linebreaks|truncatewords:50}}
{% endautoescape %}
<div class="clear"></div>
<p class="link-read-post"><a href="{{ p.get_absolute_url }}">Читать пост</a></p>
</li>
{% endif %}
{% endfor %}
</ul>
{% endblock %}
Вспоминаем, что p в цикле for как раз
ссылается на объекты класса Women, следовательно, у которого теперь есть
атрибут get_absolute_url. И, обратите
внимание, при указании этого метода, мы не пишем в конце круглые скобки, т.к.
он здесь самостоятельно не вызывается. Вызов сделает функция render при обработке
этого шаблона.
Почему мы
заменили тег url методом get_absolute_url? Представьте,
что в будущем шаблон этой ссылки снова изменился для вывода постов по id. Тогда, при
использовании тега url, нам пришлось бы менять эти ссылки в каждом шаблоне,
заменяя self.slug на self.pk. В этом как раз
неудобство и источник потенциальных ошибок. А благодаря определению нового
метода get_absolute_url() нам
достаточно изменить маршрут только в нем и это автоматически скажется на всех
шаблонах, где используется его вызов.
Второй важный
момент функции get_absolute_url() заключается в
том, что согласно конвенции, модули Django используют этот
метод в своей работе (если он определен в модели). Например, стандартная
админ-панель обращается к этому методу для построения ссылок на каждую запись
наших моделей. И в дальнейшем мы увидим как это работает.
В свою очередь
тег {% url %} имеет смысл
применять для построения ссылок не связанных с моделями или, для ссылок без
параметров, используя только имена маршрутов.
Но вернемся
непосредственно к нашему проекту и в функции представления index() укажем читать
все посты из БД, у которых флаг is_published равен 1, и
передавать их шаблон:
def index(request):
posts = Women.objects.filter(is_published=1)
data = {
'title': 'Главная страница',
'menu': menu,
'posts': posts,
}
return render(request, 'women/index.html', context=data)
Обновляем
главную страницу сайта и видим, что теперь посты доступны по слагу, а не
идентификатору. Этот пример показывает как в Django легко и просто
можно менять URL-адреса и вместо
id использовать
другие поля, в частности, слаг. При этом в шаблоне мы использовали метод get_absolute_url() модели Women для
формирования корректного URL-адреса. Кроме того, Django автоматически
защищает такие адреса от SQL-инъекций, когда злоумышленник пытается
выполнить SQL-запрос,
прописывая его в адресной строке браузера. Благодаря всем этим мелочам, которые
берет на себя фреймворк, даже начинающий веб-мастер может конструировать вполне
безопасные сайты с богатым функционалом.
Курс по Django: https://stepik.org/a/183363