Django Slug Tutorial
In this tutorial, we will add slugs to a Django website. As noted in the official docs for slugfield: «slug» is a newspaper term, a short label for something containing only letters, numbers, underscores, or hyphens. Slugs are generally used in URLs.»
To give a concrete example, assume you had a Newspaper website (such as we’ll build in this tutorial). For a story titled «Hello World,» the URL would be example.com/hello-world assuming the site was called example.com .
Despite their ubiquity, slugs can be challenging to implement the first time. Therefore, we will implement everything from scratch so you can see how the pieces all fit together. If you are already comfortable implementing ListView and DetailView, you can jump to the Slug section.
Set Up
To start, let’s navigate into a directory for our code, which can be hosted anywhere on your computer, but an easy-to-find location is the Desktop in a directory called newspaper .
# Windows $ cd onedrive\desktop\code $ mkdir newspaper $ cd newspaper # macOS $ cd ~/desktop/code $ mkdir newspaper $ cd newspaper
To pay homage to Django’s origin in a newspaper, we’ll create a basic Newspaper website with Articles. If you need help installing Python, Django, and all the rest (see here for in-depth instructions).
Create and activate a virtual environment on your command line, install Django, create a new project called django_project , set up the initial database via migrate , and then start the local web server with runserver .
# Windows $ python -m venv .venv $ .venv\Scripts\Activate.ps1 (.venv) $ python -m pip install django~=4.2.0 (.venv) $ django-admin startproject django_project . (.venv) $ python manage.py migrate (.venv) $ python manage.py runserver # macOS $ python3 -m venv .venv $ source .venv/bin/activate (.venv) $ python3 -m pip install django~=4.2.0 (.venv) $ django-admin startproject django_project . (.venv) $ python3 manage.py migrate (.venv) $ python3 manage.py runserver
Don’t forget to include that period . at the end of the startproject command! It is an optional step that avoids Django creating an additional directory otherwise.
Navigate to http://127.0.0.1:8000/ in your web browser to see the Django welcome page, which confirms everything is configured correctly.
Articles app
Since this tutorial focuses on slugs, I will give the commands and code to wire up this Articles app. Full explanations can be found in my book Django for Beginners!
Let’s start by creating an app called articles . Stop the local server with Control+c and use the startapp command to create this new app.
(.venv) $ python manage.py startapp articles
Then update INSTALLED_APPS within our django_project/settings.py file to notify Django about the app.
# django_project/settings.py INSTALLED_APPS = [ . "articles", # new ]
Article Model
Create the database model with a title and body . We’ll also set __str__ and a get_absolute_url , which are Django best practices.
# 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)])
Now create a migrations file for this change, then add it to our database via migrate .
(.venv) $ python manage.py makemigrations articles (.venv) $ python manage.py migrate
Django Admin
The Django admin is a convenient way to play around with models, so we’ll use it. But first, create a superuser account.
(.venv) $ python manage.py createsuperuser
Then, update articles/admin.py to display our app within the admin.
# 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)
Start the server again with python manage.py runserver and navigate to the admin at http://127.0.0.1:8000/admin. Log in with your superuser account.
Click on the «+ Add» next to the Articles section and add an entry.
URLs
In addition to a model, we’ll eventually need a URL, view, and template to display an Article page. I like to move to URLs next after models, although the order doesn’t matter: we need all four before we can display a single page. The first step is to add the articles app to our project-level django_project/urls.py file.
# django_project/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 ]
Next, in your text editor, create the app-level articles/urls.py file. We’ll have a ListView to list all articles and a DetailView for individual articles.
Note that we’re referencing two views that have yet to be created: ArticleListView and ArticleDetailView . We’ll add them in the next section.
# articles/urls.py from django.urls import path from .views import ArticleListView, ArticleDetailView urlpatterns = [ path("", ArticleDetailView.as_view(), name="article_detail"), path("", ArticleListView.as_view(), name="article_list"), ]
Views
For each view, we specify the related model and appropriate not-yet-created template.
# 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"
Templates
Finally, we come to the last step: templates. By default, Django will look for a templates directory within each app. That structure, in our case, would be articles/templates/template.html .
Type Control+c on the command line and create the new templates directory.
(.venv) $ mkdir articles/templates
Then in your text editor, add the two new templates: articles/templates/article_list.html and articles/templates/article_detail.html .
We loop over object_list for our articles list page, provided by ListView . And we add an ‘a href by using the get_absolute_url` method added to the model.
h1>Articlesh1> ul> li>a href=«>»>>a>li> ul>
The detail view outputs our two fields— title and body —using the object default provided by DetailView. You can, and probably should, rename both object_list in the ListView and object in the DetailView to be more descriptive.
div> h2>h2> p>>p> div>
Make sure the server is running— python manage.py runserver —and check out both our pages in your web browser.
The list of all articles is available at http://127.0.0.1:8000/ .
And the detail view for our single article is at http://127.0.0.1:8000/1 .
Slugs
Finally, we come to slugs. Ultimately, we want our article title to be reflected in the URL. In other words, «A Day in the Life» should have the URL of http://127.0.0.1:8000/a-day-in-the-life .
Only two steps are required: updating our articles/models.py file and articles/urls.py . Ready? Let’s go.
In our model, we can add Django’s built-in SlugField. But we must also—and this is the part that typically trips people up—update get_absolute_url as well. That’s where we pass in the value used in our URL. Currently, it passes in the id for the article an args , so 1 for our first article. We need to change that over to a keyword argument, kwargs , for our 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
Let’s add a migration file since we’ve updated our model.
(.venv) > 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
Ack! What is this?! We already have data in our database, our single article, so we can’t just willy-nilly add a new field on. Django is helpfully telling us that we either need a one-off default of null or add it ourselves. Hmmm.
Therefore, it is generally good advice to always add new fields with either null=True or a default value.
Let’s take the straightforward approach of setting null=True for now. So type 2 on the command line. Then, add this to our slug field.
# 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>)
Try to create a migrations file again, and it will work.
(.venv) > python manage.py makemigrations articles
Go ahead and migrate the database to apply the change.
(.venv) > python manage.py migrate
But if you think about it, we’ve created a null value for our slug . We have to go into the admin to set it properly. Start up the local server, python manage.py runserver , and go to the Article page in the admin. The Slug field is empty.
Manually add our desired value, a-day-in-the-life and click «Save.»
Okay, the last step is to update articles/urls.py so that we display the slug keyword argument in the URL itself. Luckily, that just means swapping out for .
# articles/urls.py from django.urls import path from .views import ArticleListView, ArticleDetailView urlpatterns = [ path("", ArticleDetailView.as_view(), name="article_detail"), # new path("", ArticleListView.as_view(), name="article_list"), ]
And we’re done! Go to the list view page at http://127.0.0.1:8000/ and click on the link for our article.
And there it is, with our slug as the URL! Beautiful.
Unique and Null
Moving forward, do we want to allow a null value for a slug? Probably not, as it will break our site! Another consideration is: what happens if there are identical slugs? How will that resolve itself? The short answer is: not well.
Therefore, let’s change our slug field so that null is not allowed and unique values are required.
# 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>)
Then run makemigrations .
(.venv) @ 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:
Select 2 since we can manually handle the existing row ourselves and already have. Then migrate the database.
(.venv) $ python manage.py migrate
PrePopulated Fields
Manually adding a slug field each time quickly becomes tedious. So we can use a prepopulated_field in the admin to automate the process for us.
Update articles/admin.py as follows:
# 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)
Now head over to the admin and add a new article. You’ll note that as you type in the Title field, the Slug field is automatically populated. Pretty neat!
Signals, Lifecycle Hooks, Save, and Forms/Serializers
In the real world, it’s unlikely to simply provide admin access to a user. You could, but at scale, it’s definitely not a good idea. And even on a small scale, most non-technical users will find a web interface more appealing.
How do you auto-populate the slug field when creating a new Article? It turns out Django has a built-in tool for this called slugify!
But how to use slugify ? In practice, it’s common to see this done with a Signal. But I would argue—as would Django Fellow Carlton Gibson—that this is not a good use of signals because we know both the sender and receiver here. There’s no mystery. We discuss the topic’s proper use of signals at length in our Django Chat Podcast episode.
An alternative to signals is to use a lifecycle hook via something like the django-lifecycle package. Lifecycle hooks are an alternative to Signals that provide similar functionality with less indirection.
Another common way to implement this is by overriding the Article model’s save method. This approach also «works» but is not the best solution. Here is one way to do that.
# 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)
The best solution is to create the slug in the form itself by overriding the form’s clean method so that cleaned_data has the slug, or JavaScript can be used to auto-populate the field as is done in the Django admin itself. If you’re using an API, the same approach can be applied to the serializer.
This solution does not rely on a custom signal and handles the slug before it touches the database. In the words of Carlton Gibson, who suggested this approach, it is a win-win-win.
Words of Caution
A quick word of caution on using slugs in a large website. Despite requiring an Article to be unique , naming conflicts using slugs is almost inevitable. However, slugs do seem to have good SEO properties. A good compromise is to combine a slug with a UUID or a username. The ultimate URL would therefore be >/> or >/> . If you look at Github, for example, they use the pattern of username + slug for each repo, which is why my Django for Beginners source code is located at https://github.com/wsvincent/djangoforbeginners . While you could also use an id + a slug, using IDs is often a security concern and should be avoided for production websites.
© LearnDjango | Django is a registered trademark of the Django Software Foundation.
Руководство по слагам 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('', 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 , добавленного к модели.
Представление деталей выводит наши два поля — title и body — используя object по умолчанию, предоставляемое DetailView. Вы можете и, вероятно, должны переименовать оба поля object_list в ListView и object в DetailView, чтобы они были более описательными.
Убедитесь, что сервер работает— 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=) # 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=)
Попробуйте создать файл миграций снова, и он будет работать.
(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. К счастью, это означает лишь замену на .
# articles/urls.py from django.urls import path from .views import ArticleListView, ArticleDetailView urlpatterns = [ path('', 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=)
Сделайте миграцию/переход. Нужна пустота для прошлой записи.
(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 = # 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=) 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 будет >/> или >/> . Если вы посмотрите на Github, например, исходный код этого руководства находится по адресу https://github.com/wsvincent/django-slug-tutorial, что является шаблоном имя пользователя + slug. Хотя вы могли бы также использовать id + slug, использование идентификаторов часто является проблемой безопасности и, по моему мнению, его следует избегать для производственных веб-сайтов.
Следующие шаги
Если вы хотите сравнить свой код с официальным исходным кодом, его можно найти на Github.
Как в Django генерировать slug url
Есть задача генерировать slug (при создании поста с помощью формы на сайте) в зависимости от title поста. К примеру:
title = "собака"
url = "127.0.0.1:8000/sobaka"
Пытался использовать функцию slugify : models.py:
def gen_slug(string): finally_slug = slugify(string, allow_unicode=True) return finally_slug + '-' + str(int(time())) class Post(models.Model): post_title = models.CharField(max_length=250, verbose_name='Заголовок') post_slug = models.SlugField(max_length=250, unique=True, verbose_name='URL', null=True, blank=True) def save(self, *args, **kwargs): self.post_slug = gen_slug(self.post_title) super().save(*args, **kwargs) def get_absolute_url(self): return reverse('show_post', kwargs=)
path('post//', ShowPost, name='show_post'),
def homepage(request): random_content_one = Post.objects.order_by("?")[:30] context = < 'random_content_one': random_content_one, >return render(request, 'sitelogic/base.html', context=context) def ShowPost(request, post_title_slug): post = get_object_or_404(Post, post_slug=post_title_slug) context = < 'post': post, 'title': post.post_title, >return render(request, 'sitelogic/showpost.html', context=context)
Работает только если title был заполнен английскими буквами. Если русскими то возникает ошибка. К примеру если title = «собака» , то ошибка будет такая:
NoReverseMatch at Reverse for 'show_post' with keyword arguments '' not found. 1 pattern(s) tried: ['post/(?P[-a-zA-Z0-9_]+)/\\Z']
P.S: С уникальностью slug’ов проблем нет, т.к дополнительно используется модуль time при генерации url’ов
Добавляем слаги (slug) к URL-адресам
На этом занятии мы сделаем отображение отдельных статей по их слагу (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) context = { 'post': post, 'menu': menu, 'title': post.title, 'cat_selected': 1, } return render(request, 'women/post.html', context=context)
Здесь функция get_object_or_404 выбирает одну запись из таблицы Women, которая имеет идентификатор, равный post_id, либо генерирует исключение 404, если запись не была найдена. Это довольно частая операция, когда нужно найти какую-либо отдельную запись, а в противном случае, перенаправить пользователя на заготовленную страницу 404. Поэтому в Django для таких случаев заготовлена специальная функция.
Далее, формируется словарь из параметров шаблона и отображается страница на основе шаблона post.html. У нас пока нет такого файла, добавим его со следующим содержимым:
{% extends 'women/base.html' %} {% block content %} h1>{{post.title}}/h1> {% if post.photo %} p>img class="img-article-left" src=">">/p> {% endif %} {post.content} {% endblock %}
Здесь все достаточно очевидно. Вначале отображаем заголовок h1, затем, фотографию статьи, если она есть, ну и потом уже содержимое самой статьи.
Если теперь перейти по ссылке, то увидим полноценную статью. Если же указать неверный адрес, то получим исключение 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, указал уникальным, индексируемым и имя URL, отображаемое в админке. Однако, если сейчас попытаться создать миграцию для внесения этих изменений в структуру таблицы women:
python manage.py makemigrations
то увидим предупреждение, что поле не может быть пустым (так как у нас есть записи в таблице). Чтобы таблицы были сформированы как надо, я решил создать БД заново. Поэтому сразу добавил такое же поле в модели Category:
class Category(models.Model): name = models.CharField(max_length=100, db_index=True, verbose_name="Категория") slug = models.SlugField(max_length=255, unique=True, db_index=True, verbose_name="URL") .
Удалим все файлы миграций, прежний файл БД и выполним команду
python manage.py makemigrations
для создания первой мигации. Затем, с помощью команды:
python manage.py migrate
сформируем таблицы с новыми структурами. Этот пример хорошо показывает, как важно заранее продумывать структуры таблиц для сайта. Осталось восстановить записи в БД. Для этого я заново создам суперпользователя для админки:
python manage.py createsuperuser
с именем root, почтой root@coolsite.ru и паролем 1234. Запускаем веб-сервер и заходим в админ-панель.
Для начала добавим категории. Здесь нам предлагается ввести ее название и слаг (URL). Конечно, можно заполнить оба поля вручную, например, «Актрисы» и «actrisi». Но, так как слаг, обычно, повторяет заголовок, только записанный латиницей, то фреймворк Django позволяет этот процесс автоматизировать. Давайте откроем файл women/admin.py и для модели Category в классе CategoryAdmin добавим атрибут:
prepopulated_fields = {"slug": ("name", )}
Это специальное свойство, которое указывает фреймворку автоматически заполнять поле slug по данным поля name.
Возвращаемся в админку, обновляем страницу и, смотрите, при вводе строки в поле name, автоматически формируется поле slug. Это очень здорово и значительно облегчает нашу работу. Теперь можно совершенно спокойно добавить две рубрики «Актрисы» и «Певицы».
Далее, прежде чем добавлять статьи, сделаем такую же связку по слагу для модели Women в классе WomenAdmin:
prepopulated_fields = {"slug": ("title",)}
только здесь мы указываем поле title. Возвращаемся в админ-панель и на вкладке добавления женщин введем информацию по актрисам:
Анджелина Джоли, Дженнифер Лоуренс, Джулия Робертс, Марго Робби, Ума Турман
А также по певицам:
Ариана Гранде, Бейонсе, Кэтти Перри, Рианна, Шакира
Отлично, база данных готова и теперь можно сделать отображение статей по слагу. Для этого откроем файл women/urls.py и в списке urlpatterns изменим маршрут для постов на следующий:
path('post//', show_post, name='post'),
Затем, в файле women/views.py немного поменяем функцию представления show_post:
def show_post(request, post_slug): post = get_object_or_404(Women, slug=post_slug) .
И в модели Women (в файле women/models.py) будем формировать URL-адрес по параметру slug:
class Women(models.Model): . def get_absolute_url(self): return reverse('post', kwargs={'post_slug': self.slug}) .
Все, обновляем главную страницу сайта и видим, что теперь посты доступны по слагу, а не идентификатору. Этот пример показывает как в Django легко и просто можно менять URL-адреса и вместо id использовать другие поля, в частности, слаг. При этом, мы не производили совершенно никаких изменений в шаблонах, благодаря использованию метода get_absolute_url() в модели Women. Кроме того, Django автоматически защищает такие адреса от SQL-инъекций, когда злоумышленник пытается выполнить SQL-запрос, прописывая его в адресной строке браузера. Благодаря всем этим мелочам, которые берет на себя фреймворк, даже начинающий веб-мастер может конструировать вполне безопасные сайты с богатым функционалом.
Аналогичную операцию использования слагов можно сделать и для отображения рубрик. Предлагаю вам выполнить это самостоятельно для закрепления материала.
Видео по теме
#1. Django — что это такое, порядок установки
#2. Модель MTV. Маршрутизация. Функции представления
#3. Маршрутизация, обработка исключений запросов, перенаправления
#4. Определение моделей. Миграции: создание и выполнение
#5. CRUD — основы ORM по работе с моделями
#6. Шаблоны (templates). Начало
#7. Подключение статических файлов. Фильтры шаблонов
#8. Формирование URL-адресов в шаблонах
#9. Создание связей между моделями через класс ForeignKey
#10. Начинаем работу с админ-панелью
#11. Пользовательские теги шаблонов
#12. Добавляем слаги (slug) к URL-адресам
#13. Использование форм, не связанных с моделями
#14. Формы, связанные с моделями. Пользовательские валидаторы
#15. Классы представлений: ListView, DetailView, CreateView
#16. Основы ORM Django за час
#17. Mixins — убираем дублирование кода
#18. Постраничная навигация (пагинация)
#19. Регистрация пользователей на сайте
#20. Делаем авторизацию пользователей на сайте
#21. Оптимизация сайта с Django Debug Toolbar
#22. Включаем кэширование данных
#23. Использование капчи captcha
#24. Тонкая настройка админ панели
#25. Начинаем развертывание Django-сайта на хостинге
#26. Завершаем развертывание Django-сайта на хостинге
© 2023 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта