Руководство по слагам Django
В этом руководстве мы добавим слаги на сайт Django. Как отмечается в официальной документации : "Slug - это газетный термин. Slug - это короткое обозначение чего-либо, содержащее только буквы, цифры, подчеркивания или дефисы. Обычно они используются в URL-адресах."
Чтобы привести конкретный пример, предположим, что у вас есть сайт газеты (такой, какой мы создадим в этом учебнике). Для статьи под названием "Hello World", URL будет example.com/hello-world
при условии, что сайт называется example.com
.
Несмотря на свою повсеместную распространенность, слаги могут быть несколько сложными для реализации с первого раза, по крайней мере, по моему опыту. Поэтому мы будем реализовывать все с нуля, чтобы вы могли увидеть, как все части подходят друг другу. Если вам уже удобно реализовывать ListView и DetailView, вы можете сразу перейти к разделу Slug.
Полный исходный код можно найти на Github.
Установить
Для начала давайте перейдем в каталог для нашего кода. Она может быть размещена в любом месте вашего компьютера, но если вы работаете на Mac, то легко найти ее в каталоге Desktop
.
$ cd ~/Desktop $ mkdir newspaper && cd newspaper
Чтобы отдать дань уважения Django, который зародился в газете, мы создадим базовый Newspaper веб-сайт с помощью Articles. Если вам нужна помощь в установке Python, Django и всего остального ( смотрите здесь подробные инструкции).
В командной строке введите следующие команды для установки последней версии с помощью Pipenv
, создайте проект под названием config
, настройте начальную базу данных с помощью migrate
, а затем запустите локальный веб-сервер с помощью runserver
.
$ pipenv install django==3.0 $ pipenv shell (newspaper) $ django-admin startproject config . (newspaper) $ python manage.py migrate (newspaper) $ python manage.py runserver
Не забудьте поставить точку .
в конце команды startproject
! Это необязательный шаг, который позволит избежать создания Django дополнительной директории в противном случае.
Перейдите по адресу http://127.0.0.1:8000/ в вашем веб-браузере, чтобы увидеть страницу приветствия Django, которая подтверждает, что все настроено правильно.
Приложение для статей
Поскольку основное внимание в этом учебнике уделяется слагам, я просто дам команды и код для подключения этого приложения Articles. Полные объяснения можно найти в моей книге Django для начинающих!
Для начала создадим приложение под названием articles
. Остановите локальный сервер командой Control+c
и используйте команду startapp
для создания этого нового приложения.
(newspaper) $ python manage.py startapp articles
Затем обновите INSTALLED_APPS
внутри нашего config/settings.py
файла, чтобы уведомить Django о приложении.
# config/settings.py INSTALLED_APPS = [ ... 'articles.apps.ArticlesConfig', # new ]
Модель статьи
Создайте модель базы данных, которая будет иметь title
и body
. Мы также установим __str__
и get_absolute_url
, что является лучшей практикой Django.
# articles/models.py from django.db import models from django.urls import reverse class Article(models.Model): title = models.CharField(max_length=255) body = models.TextField() def __str__(self): return self.title def get_absolute_url(self): return reverse('article_detail', args=[str(self.id)])
Теперь создайте файл миграций для этого изменения, затем добавьте его в нашу базу данных через migrate
.
(newspaper) $ python manage.py makemigrations articles (newspaper) $ python manage.py migrate
Django Admin
Админка Django - это удобный способ играть с моделями, поэтому мы будем использовать ее. Но сначала создайте учетную запись суперпользователя.
(newspaper) $ python manage.py createsuperuser
А затем обновите articles/admin.py
для отображения нашего приложения в админке.
# articles/admin.py from django.contrib import admin from .models import Article class ArticleAdmin(admin.ModelAdmin): list_display = ('title', 'body',) admin.site.register(Article, ArticleAdmin)
Снова запустите сервер с помощью python manage.py runserver
и перейдите в админку по адресу http://127.0.0.1:8000/admin. Войдите в систему под учетной записью суперпользователя.
Нажмите на "+ Добавить" рядом с разделом Articles
и добавьте запись.
URLs
В дополнение к модели нам в конечном итоге понадобятся URL, представление и шаблон для отображения страницы статьи. Мне нравится переходить к URL следующим после моделей, хотя порядок не имеет значения: нам нужны все четыре, прежде чем мы сможем отобразить одну страницу. Первым шагом будет добавление приложения articles
в наш файл config/urls.py
на уровне проекта.
# config/urls.py from django.contrib import admin from django.urls import path, include # new urlpatterns = [ path('admin/', admin.site.urls), path('', include('articles.urls')), # new ]
Далее мы должны создать файл уровня приложения urls.py
.
$ touch articles/urls.py
У нас будет ListView для списка всех статей и DetailView для отдельных статей.
Обратите внимание, что мы ссылаемся на два представления, которые еще не созданы: ArticleListView
и ArticleDetailView
. Мы добавим их в следующем разделе.
# articles/urls.py from django.urls import path from .views import ArticleListView, ArticleDetailView urlpatterns = [ path('<int:pk>', ArticleDetailView.as_view(), name='article_detail'), path('', ArticleListView.as_view(), name='article_list'), ]
Виды
Для каждого представления мы указываем связанную модель и соответствующий еще не созданный шаблон.
# articles/views.py from django.views.generic import ListView, DetailView from .models import Article class ArticleListView(ListView): model = Article template_name = 'article_list.html' class ArticleDetailView(DetailView): model = Article template_name = 'article_detail.html'
Шаблоны
Наконец, мы подошли к последнему шагу: шаблоны. По умолчанию Django будет искать внутри каждого приложения каталог templates
. В нашем случае это будет структура articles/templates/template.html
.
Введите Control+c
в командной строке и создайте новый templates
каталог.
(newspaper) $ mkdir articles/templates
Теперь добавьте два новых шаблона.
(newspaper) $ touch articles/templates/article_list.html (newspaper) $ touch articles/templates/article_detail.html
Для нашей страницы списка мы переходим по циклу object_list
, который обеспечивается ListView
. И мы добавляем a href
с помощью метода get_absolute_url
, добавленного к модели.
<!-- article_list.html --> <h1>Articles</h1> {% for article in object_list %} <ul> <li><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></li> </ul> {% endfor %}
Представление деталей выводит наши два поля - title
и body
- используя object
по умолчанию, предоставляемое DetailView. Вы можете и, вероятно, должны переименовать оба поля object_list
в ListView и object
в DetailView, чтобы они были более описательными.
<!-- article_detail.html --> <div> <h2>{{ object.title }</h2> <p>{{ object.body }}</p> </div>
Убедитесь, что сервер работает--python manage.py runserver
-- и просмотрите обе наши страницы в своем веб-браузере.
Список всех статей доступен на сайте http://127.0.0.1:8000/
.
А детальный вид для нашей отдельной статьи находится по адресу http://127.0.0.1:8000/1
.
Слаги
Наконец, мы переходим к слагам. В конечном итоге мы хотим, чтобы название нашей статьи было отражено в URL. Другими словами, "День из жизни" должен иметь URL http://127.0.0.1:8000/a-day-in-the-life
.
Необходимо выполнить всего два шага: обновить наш файл articles/models.py
и articles/urls.py
. Готовы? Поехали...
В нашей модели мы можем добавить встроенное в Django SlugField. Но мы должны также - и это та часть, которая обычно ставит людей в тупик - обновить get_absolute_url
также. Именно здесь мы передаем значение, используемое в нашем URL. В настоящее время он передает id
для статьи args
, так что 1
для нашей первой статьи. Нам нужно изменить это на аргумент ключевого слова, kwargs
, для нашей slug
.
# articles/models.py from django.db import models from django.urls import reverse class Article(models.Model): title = models.CharField(max_length=255) body = models.TextField() slug = models.SlugField() # new def __str__(self): return self.title def get_absolute_url(self): return reverse('article_detail', kwargs={'slug': self.slug}) # new
Далее давайте добавим файл миграции, поскольку мы обновили нашу модель.
(newspaper) $ python manage.py makemigrations articles You are trying to add a non-nullable field 'slug' to article without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py
Ак! Что это?! Оказывается, у нас уже есть данные в нашей базе данных, наша единственная Статья, поэтому мы не можем просто так взять и добавить новое поле. Django услужливо подсказывает нам, что мы должны либо сделать одноразовое поле по умолчанию null
, либо добавить его самостоятельно. Хммм.
По этой самой причине, как правило, хорошим советом является всегда добавлять новые поля либо с null=True
, либо со значением default
.
Давайте пока воспользуемся простым подходом и установим null=True
. Итак, введите 2
в командной строке. Затем добавьте это в наше поле slug
.
# articles/models.py from django.db import models from django.urls import reverse class Article(models.Model): title = models.CharField(max_length=255) body = models.TextField() slug = models.SlugField(null=True) # new def __str__(self): return self.title def get_absolute_url(self): return reverse('article_detail', kwargs={'slug': self.slug})
Попробуйте создать файл миграций снова, и он будет работать.
(newspaper) $ python manage.py makemigrations articles
Продолжайте и migrate
базу данных, чтобы применить изменения.
(newspaper) $ python manage.py migrate
Но если подумать, то мы создали значение null
для нашего slug
. Нам нужно зайти в админку, чтобы установить его правильно. Запустите локальный сервер, python manage.py runserver
, и перейдите на страницу Article в админке. Поле Slug
будет пустым.
Вручную добавьте нужное значение a-day-in-the-life
и нажмите "Сохранить"
Ок, последний шаг - обновить articles/urls.py
так, чтобы отображать аргумент ключевого слова slug
в самом URL. К счастью, это означает лишь замену <int:pk>
на <slug:slug>
.
# articles/urls.py from django.urls import path from .views import ArticleListView, ArticleDetailView urlpatterns = [ path('<slug:slug>', ArticleDetailView.as_view(), name='article_detail'), # new path('', ArticleListView.as_view(), name='article_list'), ]
И мы закончили! Перейдите на страницу просмотра списка по адресу http://127.0.0.1:8000/
и нажмите на ссылку для нашей статьи.
И вот он с нашим slug в качестве URL! Прекрасно.
Unique и Null
Далее, действительно ли мы хотим разрешить значение null
для slug? Скорее всего, нет, так как это сломает наш сайт! Еще одно соображение: что произойдет, если будут одинаковые slug? Как это будет разрешено? Короткий ответ: не очень хорошо.
Поэтому давайте изменим наше поле slug
так, чтобы null
не допускалось, а уникальные значения были обязательны.
# articles/models.py from django.db import models from django.urls import reverse class Article(models.Model): title = models.CharField(max_length=255) body = models.TextField() slug = models.SlugField(null=False, unique=True) # new def __str__(self): return self.title def get_absolute_url(self): return reverse('article_detail', kwargs={'slug': self.slug})
Сделайте миграцию/переход. Нужна пустота для прошлой записи.
(newspaper) $ python manage.py makemigrations articles You are trying to change the nullable field 'slug' on article to non-nullable without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL values in a previous data migration) 3) Quit, and let me add a default in models.py Select an option:
Выбираем 2
, поскольку мы можем сами вручную обработать существующий ряд, и фактически уже сделали это. Затем migrate
в базу данных.
(newspaper) $ python manage.py migrate
Предварительно заполненные поля
Ручное добавление поля slug
каждый раз быстро становится утомительным. Поэтому мы можем использовать предзаполняемое поле в админке, чтобы автоматизировать этот процесс для нас.
Обновите articles/admin.py
следующим образом:
# articles/admin.py from django.contrib import admin from .models import Article class ArticleAdmin(admin.ModelAdmin): list_display = ('title', 'body',) prepopulated_fields = {'slug': ('title',)} # new admin.site.register(Article, ArticleAdmin)
Теперь перейдите в админку и добавьте новую статью. Вы заметите, что по мере ввода текста в поле Title
поле Slug
автоматически заполняется. Довольно аккуратно!
Сигналы, хуки жизненного цикла, сохранение и формы/сериализаторы
В реальном мире маловероятно просто предоставить пользователю доступ администратора. Можно, но в больших масштабах это определенно не лучшая идея. И даже в небольших масштабах большинство нетехнических пользователей найдут веб-интерфейс более привлекательным
Итак... как автоматически заполнить поле slug
при создании новой статьи. Оказывается, в Django для этого есть встроенный инструмент под названием slugify!
Но как использовать slugify
? На практике часто можно увидеть, как это делается с помощью Signal. Но я бы возразил - как и коллега по Django Карлтон Гибсон - что это не лучшее использование сигналов, потому что здесь мы знаем и отправителя, и получателя. Здесь нет никакой тайны. Мы подробно обсуждаем правильное использование сигналов в нашем эпизоде Django Chat Podcast, посвященном этой теме.
Альтернативой сигналам является использование перехватчиков жизненного цикла с помощью чего-то вроде пакета django-lifecycle. Lifecycle hooks - это альтернатива сигналам, которая обеспечивает схожую функциональность с меньшей непрямолинейностью.
Другим распространенным способом реализации этого является переопределение метода Article
модели save. Это тоже "работает", но не является лучшим решением. Вот один из способов сделать это
# articles/models.py from django.db import models from django.template.defaultfilters import slugify # new from django.urls import reverse class Article(models.Model): title = models.CharField(max_length=255) body = models.TextField() slug = models.SlugField(null=False, unique=True) def __str__(self): return self.title def get_absolute_url(self): return reverse('article_detail', kwargs={'slug': self.slug}) def save(self, *args, **kwargs): # new if not self.slug: self.slug = slugify(self.title) return super().save(*args, **kwargs)
Лучшим решением, на мой взгляд, является создание slug в самой форме. Это можно сделать, переопределив метод clean формы так, чтобы cleaned_data
содержал slug, или можно использовать JavaScript для автоматического заполнения поля, как это сделано в админке Django. Если вы используете API, тот же подход можно применить к сериализатору.
Это решение не полагается на пользовательский сигнал и обрабатывает slug до того, как он коснется базы данных. По словам Карлтона Гибсона, который предложил мне этот подход, win-win-win.
Слова предостережения
Небольшое предостережение по поводу использования slugs на большом сайте. Несмотря на то, что статья должна быть unique
, при использовании прорезей почти неизбежны конфликты имен. Однако слаги, похоже, обладают хорошими SEO-свойствами. Хорошим компромиссом является сочетание slug с UUID или именем пользователя. Таким образом, конечный URL будет {{ uuid }}/{{ slug }}
или {{ username }}/{{ slug }}
. Если вы посмотрите на Github, например, исходный код этого руководства находится по адресу https://github.com/wsvincent/django-slug-tutorial, что является шаблоном имя пользователя + slug. Хотя вы могли бы также использовать id
+ slug, использование идентификаторов часто является проблемой безопасности и, по моему мнению, его следует избегать для производственных веб-сайтов.
Следующие шаги
Если вы хотите сравнить свой код с официальным исходным кодом, его можно найти на Github.
Вернуться на верх