Ваш первый сайт Wagtail
Примечание
Этот учебник охватывает настройку нового проекта Wagtail. Если вы хотите добавить Wagtail в существующий проект Django, см. Интеграция Wagtail в проект Django.
Установка и запуск Wagtail
Установка зависимостей
Wagtail поддерживает Python 3.7, 3.8, 3.9, 3.10 и 3.11.
Чтобы проверить, есть ли у вас подходящая версия Python 3:
python --version
В Windows (cmd.exe, с загрузчиком Python для Windows):
py --version
Если это не вернёт номер версии или вернёт версию ниже 3.7, вам необходимо установить Python 3.
Примечание
Перед установкой Wagtail необходимо установить библиотеки libjpeg и zlib, которые обеспечивают поддержку работы с изображениями JPEG, PNG и GIF (через библиотеку Python Pillow). Способ выполнения этого действия зависит от платформы — см. инструкции по установке, специфичные для платформы Pillow.
Создание и активация виртуальной среды
Мы рекомендуем использовать виртуальную среду, которая изолирует установленные зависимости от других проектов. В этом руководстве используется venv, которая входит в комплект Python 3.
В Windows (cmd.exe):
py -m venv mysite\env mysite\env\Scripts\activate.bat # or: mysite\env\Scripts\activate
В GNU/Linux или MacOS (bash):
python -m venv mysite/env source mysite/env/bin/activate
Для других оболочек см. venv документацию.
Примечание
Если вы используете систему контроля версий (например, git), mysite будет каталогом вашего проекта. Каталог env внутри него должен быть исключён из контроля версий.
Установка Wagtail
Используйте pip, который входит в состав Python, для установки Wagtail и его зависимостей:
pip install wagtail
Генерация вашего сайта
Wagtail предоставляет команду start аналогичную django-admin startproject. Выполнение wagtail start mysite в вашем проекте сгенерирует новую папку mysite с несколькими дополнительными элементами, специфичными для Wagtail, включая необходимые настройки проекта, приложение «home» с пустой моделью HomePage и базовыми шаблонами, а также пример приложения «search».
Поскольку папка mysite уже была создана venv, выполните wagtail start с дополнительным аргументом для указания целевого каталога:
wagtail start mysite mysite
Примечание
В Wagtail, как правило, каждый тип страницы или тип контента представлен отдельным приложением. Однако разные приложения могут быть осведомлены друг о друге и получать доступ к данным друг друга. Все приложения должны быть зарегистрированы в разделе INSTALLED_APPS файла settings.py. Посмотрите в этот файл, чтобы увидеть, как команда start перечислила их там.
Установка зависимостей проекта
cd mysite pip install -r requirements.txt
Это гарантирует, что у вас есть соответствующие версии Wagtail, Django и других зависимостей для проекта, который вы только что создали. Файл requirements.txt содержит все зависимости, необходимые для запуска проекта.
Создание базы данных
Если вы не обновили настройки проекта, это будет файл базы данных SQLite в каталоге проекта.
python manage.py migrate
Эта команда гарантирует, что таблицы в вашей базе данных соответствуют моделям в вашем проекте. Каждый раз, когда вы изменяете модель (например, добавляете поле в модель), вам необходимо выполнить эту команду для обновления базы данных.
Создание пользователя администратора
python manage.py createsuperuser
После входа в админ-панель суперпользователь имеет полные права и может просматривать/создавать/управлять базой данных.
Запуск сервера
python manage.py runserver
Если всё прошло успешно, http://127.0.0.1:8000 покажет вам страницу приветствия:
Примечание
В этом руководстве мы будем использовать http://127.0.0.1:8000, но в зависимости от вашей конфигурации это может быть http://localhost:8000/ или другой IP-адрес или порт. Пожалуйста, прочитайте вывод консоли manage.py runserver для определения правильного URL вашего локального сайта.
Теперь вы можете получить доступ к административной области по адресу http://127.0.0.1:8000/admin
Расширение модели HomePage
По умолчанию приложение «home» определяет пустую модель HomePage в models.py, а также миграцию, которая создаёт главную страницу и настраивает Wagtail для её использования.
Отредактируйте home/models.py следующим образом, чтобы добавить поле body в модель:
from django.db import models
from wagtail.models import Page
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel
class HomePage(Page):
body = RichTextField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('body'),
]
body определяется как RichTextField, специальное поле Wagtail. Когда blank=True, это означает, что это поле необязательно и может быть пустым. Вы можете использовать любые из полей ядра Django. content_panels определяют возможности и макет интерфейса редактирования. При добавлении полей в content_panels, они становятся доступными для редактирования в интерфейсе Wagtail. Дополнительная информация о создании моделей страниц.
Выполните python manage.py makemigrations (это создаст файл миграций), а затем python manage.py migrate (это выполнит миграции и обновит базу данных с изменениями модели). Вы должны выполнять указанные выше команды каждый раз, когда вносите изменения в определение модели.
Теперь вы можете отредактировать главную страницу в административной области Wagtail (перейдите в Разделы, Главная страница, затем Редактировать), чтобы увидеть новое поле body. Введите текст в поле body и опубликуйте страницу, выбрав Опубликовать в нижней части редактора страницы, а не Сохранить черновик.
Теперь шаблон страницы должен быть обновлён для отражения внесённых изменений в модель. Wagtail использует обычные шаблоны Django для отображения каждого типа страницы. По умолчанию он будет искать файл шаблона, сформированный из имени приложения и модели, разделяя заглавные буквы нижним подчёркиванием (например, HomePage в приложении «home» становится home/home_page.html). Этот файл шаблона может находиться в любом месте, распознаваемом правилами шаблонов Django; как правило, он размещается в папке templates внутри приложения.
Отредактируйте home/templates/home/home_page.html следующим образом:
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block body_class %}template-homepage{% endblock %}
{% block content %}
{{ page.body|richtext }}
{% endblock %}
base.html относится к родительскому шаблону и всегда должен быть первой меткой шаблона, используемой в шаблоне. Расширение от этого шаблона позволяет избежать переписывания кода и позволяет страницам в вашем приложении совместно использовать аналогичную структуру (используя теги блоков в дочернем шаблоне, вы можете переопределять определённое содержимое внутри родительского шаблона).
wagtailcore_tags также должен быть загружен в начале шаблона и предоставить дополнительные теги по сравнению с теми, которые предоставляет Django.
Основные принципы блога
Теперь мы готовы создать блог. Для этого выполните python manage.py startapp blog , чтобы создать новое приложение в вашем сайте Wagtail.
Добавьте новое приложение blog в INSTALLED_APPS в mysite/settings/base.py.
Индекс блога и публикации
Начнем с простой страницы индекса для нашего блога. В blog/models.py:
from wagtail.models import Page
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel
class BlogIndexPage(Page):
intro = RichTextField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('intro')
]
Выполните python manage.py makemigrations и python manage.py migrate.
Поскольку модель называется BlogIndexPage, имя шаблона по умолчанию (если мы его не переопределим) будет blog_index_page.html. Django будет искать шаблон, имя которого соответствует имени вашей модели страницы внутри каталога шаблонов в папке вашего приложения блога. Это поведение по умолчанию можно переопределить при необходимости.
Чтобы создать шаблон для модели BlogIndexPage, создайте файл по адресу blog/templates/blog/blog_index_page.html.
Примечание
Возможно, вам потребуется создать папки templates/blog внутри папки вашего приложения blog
В файле blog_index_page.html введите следующее содержимое:
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block body_class %}template-blogindexpage{% endblock %}
{% block content %}
<h1>{{ page.title }}</h1>
<div class="intro">{{ page.intro|richtext }}</div>
{% for post in page.get_children %}
<h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
{{ post.specific.intro }}
{{ post.specific.body|richtext }}
{% endfor %}
{% endblock %}
Большая часть этого должна быть знакома, но мы объясним get_children немного позже. Обратите внимание на тег pageurl, который аналогичен тегу url Django, но принимает объект страницы Wagtail в качестве аргумента.
В админ-панели Wagtail перейдите к страницам, а затем к Домашней. Добавьте дочернюю страницу к домашней странице, нажав на значок «Действия» и выбрав опцию «Добавить дочернюю страницу». Выберите «Индекс страницы блога» из списка типов страниц. Используйте «Наш блог» в качестве заголовка страницы, убедитесь, что в закладке «Опубликовать» указан псевдоним «blog», и опубликуйте её. Теперь вы должны иметь возможность получить доступ к URL http://127.0.0.1:8000/blog на вашем сайте (обратите внимание, как псевдоним из вкладки «Опубликовать» определяет URL страницы).
Теперь нам нужна модель и шаблон для наших публикаций в блоге. В blog/models.py:
from django.db import models
from wagtail.models import Page
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel
from wagtail.search import index
# Keep the definition of BlogIndexPage, and add:
class BlogPage(Page):
date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = RichTextField(blank=True)
search_fields = Page.search_fields + [
index.SearchField('intro'),
index.SearchField('body'),
]
content_panels = Page.content_panels + [
FieldPanel('date'),
FieldPanel('intro'),
FieldPanel('body'),
]
В модели выше мы импортируем index, так как это делает модель поисковой. Затем вы можете перечислить поля, которые вы хотите, чтобы пользователь мог искать.
Выполните python manage.py makemigrations и python manage.py migrate.
Создайте новый файл шаблона по адресу blog/templates/blog/blog_page.html. Теперь добавьте следующее содержимое в ваш недавно созданный файл blog_page.html:
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block body_class %}template-blogpage{% endblock %}
{% block content %}
<h1>{{ page.title }}</h1>
<p class="meta">{{ page.date }}</p>
<div class="intro">{{ page.intro }}</div>
{{ page.body|richtext }}
<p><a href="{{ page.get_parent.url }}">Return to blog</a></p>
{% endblock %}
Обратите внимание на использование встроенного метода Wagtail get_parent() для получения URL блога, частью которого является эта запись.
Теперь создайте несколько публикаций в блоге как дочерние страницы BlogIndexPage. Обязательно выберите тип «Страница блога» при создании своих записей.
Wagtail предоставляет вам полный контроль над тем, какие типы контента могут быть созданы под различными типами родительского контента. По умолчанию любой тип страницы может быть дочерним элементом любого другого типа страницы.
Опубликуйте каждую запись в блоге, когда закончите редактирование.
Теперь у вас должно быть начало работающего блога. Перейдите к URL /blog , и вы увидите что-то подобное:
Заголовки должны ссылаться на страницы записей, а ссылка обратно на главную страницу блога должна отображаться в нижнем колонтитуле каждой страницы записи.
Родители и дочерние элементы
Большая часть вашей работы в Wagtail будет связана с концепцией иерархических структур «дерева», состоящих из узлов и листьев (см. Теория). В этом случае BlogIndexPage является «узлом», а отдельные экземпляры BlogPage — «листьями».
Еще раз взгляните на внутренности blog_index_page.html:
{% for post in page.get_children %}
<h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
{{ post.specific.intro }}
{{ post.specific.body|richtext }}
{% endfor %}
Каждая «страница» в Wagtail может обращаться к своему родителю или дочерним элементам из своего собственного положения в иерархии. Но почему мы должны указать post.specific.intro , а не post.intro? Это связано со способом определения нашей модели:
class BlogPage(Page):
Метод get_children() возвращает список экземпляров базового класса Page. Когда мы хотим обратиться к свойствам экземпляров, которые наследуются от базового класса, Wagtail предоставляет метод specific, который извлекает фактическую запись BlogPage. Хотя поле «title» присутствует в базовой модели Page , «intro» присутствует только в модели BlogPage, поэтому нам нужен .specific , чтобы получить к нему доступ.
Для уточнения кода шаблона можно использовать тег Django with:
{% for post in page.get_children %}
{% with post=post.specific %}
<h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
<p>{{ post.intro }}</p>
{{ post.body|richtext }}
{% endwith %}
{% endfor %}
Когда вы начинаете писать более настраиваемый код Wagtail, вы обнаружите целый набор модификаторов набора запросов, которые помогут вам ориентироваться в иерархии.
# Given a page object 'somepage': MyModel.objects.descendant_of(somepage) child_of(page) / not_child_of(somepage) ancestor_of(somepage) / not_ancestor_of(somepage) parent_of(somepage) / not_parent_of(somepage) sibling_of(somepage) / not_sibling_of(somepage) # ... and ... somepage.get_children() somepage.get_ancestors() somepage.get_descendants() somepage.get_siblings()
Для получения дополнительной информации см.: Ссылка на набор запросов Page
Переопределение контекста
У наших представлений индекса блога есть несколько проблем:
- Блоги обычно отображают контент в обратном хронологическом порядке
- Мы хотим убедиться, что отображаем только опубликованный контент.
Для достижения этих целей нам нужно сделать больше, чем просто получить дочерние элементы страницы индекса в шаблоне. Вместо этого мы хотим изменить набор запросов в определении модели. Wagtail делает это возможным через переопределяемый метод get_context(). Измените модель BlogIndexPage следующим образом:
class BlogIndexPage(Page):
intro = RichTextField(blank=True)
def get_context(self, request):
# Update context to include only published posts, ordered by reverse-chron
context = super().get_context(request)
blogpages = self.get_children().live().order_by('-first_published_at')
context['blogpages'] = blogpages
return context
Все, что мы сделали здесь, это получили исходный контекст, создали настраиваемый набор запросов, добавили его в полученный контекст и вернули измененный контекст в представление. Вам также потребуется немного изменить шаблон blog_index_page.html. Изменить:
{% for post in page.get_children %} на {% for post in blogpages %}
Теперь попробуйте снять публикацию с публикации — она должна исчезнуть со страницы индекса блога. Остальные сообщения теперь должны быть отсортированы так, чтобы самые последние опубликованные были первыми.
Изображения
Добавим возможность прикреплять галерею изображений к нашим записям в блоге. Хотя можно просто вставить изображения в поле rich text body, есть несколько преимуществ в настройке изображений галереи как нового специального типа объектов в базе данных — таким образом вы имеете полный контроль над макетом и стилем изображений в шаблоне, а не над их расположением в поле rich text. Это также позволяет использовать изображения в других местах независимо от текста блога — например, для отображения миниатюры на странице индекса блога.
Добавьте новую модель BlogPageGalleryImage в models.py:
from django.db import models
# New imports added for ParentalKey, Orderable, InlinePanel
from modelcluster.fields import ParentalKey
from wagtail.models import Page, Orderable
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel, InlinePanel
from wagtail.search import index
# ... (Keep the definition of BlogIndexPage, and update BlogPage:)
class BlogPage(Page):
date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = RichTextField(blank=True)
search_fields = Page.search_fields + [
index.SearchField('intro'),
index.SearchField('body'),
]
content_panels = Page.content_panels + [
FieldPanel('date'),
FieldPanel('intro'),
FieldPanel('body'),
InlinePanel('gallery_images', label="Gallery images"),
]
class BlogPageGalleryImage(Orderable):
page = ParentalKey(BlogPage, on_delete=models.CASCADE, related_name='gallery_images')
image = models.ForeignKey(
'wagtailimages.Image', on_delete=models.CASCADE, related_name='+'
)
caption = models.CharField(blank=True, max_length=250)
panels = [
FieldPanel('image'),
FieldPanel('caption'),
]
Выполните python manage.py makemigrations и python manage.py migrate.
Здесь несколько новых концепций, давайте разберем их по очереди:
Наследование от Orderable добавляет поле sort_order в модель, чтобы отслеживать порядок изображений в галерее.
Отношение ParentalKey к BlogPage — это то, что прикрепляет изображения галереи к определенной странице. ParentalKey работает аналогично ForeignKey , но также определяет BlogPageGalleryImage как «дочерний элемент» модели BlogPage , так что он рассматривается как неотъемлемая часть страницы в операциях, таких как отправка на модерацию и отслеживание истории изменений.
image — это ForeignKey к встроенной модели Wagtail Image , где хранятся сами изображения. Это отображается в редакторе страницы как всплывающее окно для выбора существующего изображения или загрузки нового. Таким образом, мы позволяем изображению существовать в нескольких галереях — по сути, мы создали много-ко-многие отношения между страницами и изображениями.
Указание on_delete=models.CASCADE на внешнем ключе означает, что если изображение удалено из системы, запись в галерее также удаляется. (В других ситуациях может быть целесообразно оставить запись на месте — например, если на странице «наш персонал» был список людей с фото, и одно из этих фото было удалено, мы бы предпочли оставить человека на странице без фото. В этом случае мы установили внешний ключ на blank=True, null=True, on_delete=models.SET_NULL.)
Наконец, добавление InlinePanel к BlogPage.content_panels делает изображения галереи доступными в интерфейсе редактирования для BlogPage.
Измените шаблон страницы блога, чтобы добавить изображения:
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags %}
{% block body_class %}template-blogpage{% endblock %}
{% block content %}
<h1>{{ page.title }}</h1>
<p class="meta">{{ page.date }}</p>
<div class="intro">{{ page.intro }}</div>
{{ page.body|richtext }}
{% for item in page.gallery_images.all %}
<div style="float: left; margin: 10px">
{% image item.image fill-320x240 %}
<p>{{ item.caption }}</p>
</div>
{% endfor %}
<p><a href="{{ page.get_parent.url }}">Return to blog</a></p>
{% endblock %}
Здесь мы используем тег {% image %} (который существует в библиотеке wagtailimages_tags , импортированной вверху шаблона), чтобы вставить элемент <img> с параметром fill-320x240 , указывающим, что изображение должно быть изменено размера и обрезано, чтобы заполнить прямоугольник 320x240. Вы можете узнать больше о работе с изображениями в шаблонах в документации.
Поскольку изображения нашей галереи — это объекты базы данных сами по себе, мы теперь можем их запрашивать и повторно использовать независимо от тела публикации в блоге. Давайте определим метод main_image , который возвращает изображение из первого элемента галереи (или None , если элементов галереи нет):
class BlogPage(Page):
date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = RichTextField(blank=True)
def main_image(self):
gallery_item = self.gallery_images.first()
if gallery_item:
return gallery_item.image
else:
return None
search_fields = Page.search_fields + [
index.SearchField('intro'),
index.SearchField('body'),
]
content_panels = Page.content_panels + [
FieldPanel('date'),
FieldPanel('intro'),
FieldPanel('body'),
InlinePanel('gallery_images', label="Gallery images"),
]
Этот метод теперь доступен из наших шаблонов. Обновите blog_index_page.html , чтобы отобразить основное изображение в качестве миниатюры рядом с каждой записью:
{% load wagtailcore_tags wagtailimages_tags %}
...
{% for post in blogpages %}
{% with post=post.specific %}
<h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
{% with post.main_image as main_image %}
{% if main_image %}{% image main_image fill-160x100 %}{% endif %}
{% endwith %}
<p>{{ post.intro }}</p>
{{ post.body|richtext }}
{% endwith %}
{% endfor %}
Тегирование записей
Предположим, мы хотим позволить редакторам «тегировать» свои записи, чтобы читатели, например, могли просматривать вместе весь контент, связанный с велосипедами. Для этого нам нужно вызвать систему тегов, входящую в состав Wagtail, прикрепить ее к модели BlogPage и панелям содержимого, а также отобразить связанные теги на шаблоне записи блога. Конечно, нам также понадобится рабочая страница просмотра URL-адресов, специфичных для тегов.
Сначала измените models.py еще раз:
from django.db import models
# New imports added for ClusterTaggableManager, TaggedItemBase, MultiFieldPanel
from modelcluster.fields import ParentalKey
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase
from wagtail.models import Page, Orderable
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel
from wagtail.search import index
# ... (Keep the definition of BlogIndexPage)
class BlogPageTag(TaggedItemBase):
content_object = ParentalKey(
'BlogPage',
related_name='tagged_items',
on_delete=models.CASCADE
)
class BlogPage(Page):
date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = RichTextField(blank=True)
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
# ... (Keep the main_image method and search_fields definition)
content_panels = Page.content_panels + [
MultiFieldPanel([
FieldPanel('date'),
FieldPanel('tags'),
], heading="Blog information"),
FieldPanel('intro'),
FieldPanel('body'),
InlinePanel('gallery_images', label="Gallery images"),
]
Запустите python manage.py makemigrations и python manage.py migrate.
Обратите внимание на новые импорты modelcluster и taggit, добавление новой модели BlogPageTag и добавление поля tags в BlogPage. Мы также воспользовались возможностью использовать MultiFieldPanel в content_panels для группировки полей даты и тегов вместе для лучшей читабельности.
Отредактируйте один из ваших BlogPage экземпляров, и вы теперь сможете тегировать записи:
Чтобы отобразить теги на BlogPage, добавьте это в blog_page.html:
{% if page.tags.all.count %}
<div class="tags">
<h3>Tags</h3>
{% for tag in page.tags.all %}
<a href="{% slugurl 'tags' %}?tag={{ tag }}"><button type="button">{{ tag }}</button></a>
{% endfor %}
</div>
{% endif %}
Обратите внимание, что здесь мы ссылаемся на страницы с встроенным тегом slugurl вместо pageurl, который мы использовали ранее. Разница в том, что slugurl принимает в качестве аргумента псевдоним страницы (из вкладки «Продвижение»). pageurl используется чаще, так как он недвусмыслен и избегает дополнительных обращений к базе данных. Но в случае этого цикла объект страницы не доступен непосредственно, поэтому мы используем менее предпочтительный тег slugurl.
Посещение записи блога с тегами должно отображать набор связанных кнопок внизу — по одной для каждого тега. Однако при нажатии кнопки вы получите ошибку 404, так как мы пока не определили представление «теги». Добавьте в models.py:
class BlogTagIndexPage(Page):
def get_context(self, request):
# Filter by tag
tag = request.GET.get('tag')
blogpages = BlogPage.objects.filter(tags__name=tag)
# Update template context
context = super().get_context(request)
context['blogpages'] = blogpages
return context
Обратите внимание, что эта модель на основе страницы не определяет собственных полей. Даже без полей наследование от Page делает ее частью экосистемы Wagtail, так что вы можете задать для нее заголовок и URL-адрес в админке и можете управлять ее содержимым, возвращая набор запросов из метода get_context().
Импортируйте это, затем создайте новую BlogTagIndexPage в админке. Вероятно, вы захотите создать новую страницу/представление как дочернее по отношению к домашней странице, параллельно вашему индексу блога. Присвойте ей псевдоним «теги» на вкладке «Продвижение».
Обратитесь к /tags и Django сообщит вам то, что вы, вероятно, уже знаете: вам нужно создать шаблон blog/blog_tag_index_page.html:
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block content %}
{% if request.GET.tag %}
<h4>Showing pages tagged "{{ request.GET.tag }}"</h4>
{% endif %}
{% for blogpage in blogpages %}
<p>
<strong><a href="{% pageurl blogpage %}">{{ blogpage.title }}</a></strong><br />
<small>Revised: {{ blogpage.latest_revision_created_at }}</small><br />
{% if blogpage.author %}
<p>By {{ blogpage.author.profile }}</p>
{% endif %}
</p>
{% empty %}
No pages found with that tag.
{% endfor %}
{% endblock %}
Мы вызываем встроенное поле latest_revision_created_at в модели Page — полезно знать, что оно всегда доступно.
Мы пока не добавили поле «автор» в нашу модель BlogPage и у нас нет модели профиля для авторов — мы оставим это в качестве упражнения для читателя.
Нажатие на кнопку тега внизу записи блога должно теперь отображать страницу примерно такого вида:
Категории
Давайте добавим систему категорий в наш блог. В отличие от тегов, где автор страницы может создать тег, просто используя его на странице, наши категории будут фиксированным списком, управляемым владельцем сайта через отдельный раздел интерфейса администрирования.
Сначала мы определим модель BlogCategory. Категория — это не страница сама по себе, поэтому мы определим ее как стандартную Django models.Model модель, а не наследуя от Page. Wagtail вводит концепцию «снэппетов» для повторно используемых фрагментов содержимого, которые необходимо управлять через интерфейс администрирования, но они не существуют в иерархии страниц; модель может быть зарегистрирована как снэппет путем добавления декоратора @register_snippet. Все типы полей, которые мы использовали до сих пор на страницах, также можно использовать в снэппетах — здесь мы добавим каждому категориям значок изображения и имя. Добавьте в blog/models.py:
from wagtail.snippets.models import register_snippet
@register_snippet
class BlogCategory(models.Model):
name = models.CharField(max_length=255)
icon = models.ForeignKey(
'wagtailimages.Image', null=True, blank=True,
on_delete=models.SET_NULL, related_name='+'
)
panels = [
FieldPanel('name'),
FieldPanel('icon'),
]
def __str__(self):
return self.name
class Meta:
verbose_name_plural = 'blog categories'
Примечание
Обратите внимание, что мы используем panels вместо content_panels здесь — поскольку снэппеты обычно не нуждаются в таких полях, как псевдоним или дата публикации, интерфейс редактирования для них не разделен на отдельные вкладки «содержимое» / «продвижение» / «настройки», как по умолчанию, и поэтому нет необходимости различать «панели содержимого» и «панели продвижения».
Примените это изменение, запустив python manage.py makemigrations и python manage.py migrate. Создайте несколько категорий через раздел «Снэппеты», который теперь появился в меню администрирования.
Теперь мы можем добавить категории к модели BlogPage в качестве поля «многие ко многим». Тип поля, который мы используем для этого, — ParentalManyToManyField — это вариант стандартного Django ManyToManyField, который гарантирует, что выбранные объекты правильно хранятся в записи страницы в истории версий, аналогично тому, как ParentalKey заменяет ForeignKey для отношений «один ко многим». Чтобы добавить категории в BlogPage, измените models.py в папке вашего приложения блога:
# New imports added for forms and ParentalManyToManyField
from django import forms
from django.db import models
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase
# ...
class BlogPage(Page):
date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = RichTextField(blank=True)
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
categories = ParentalManyToManyField('blog.BlogCategory', blank=True)
# ... (Keep the main_image method and search_fields definition)
content_panels = Page.content_panels + [
MultiFieldPanel([
FieldPanel('date'),
FieldPanel('tags'),
FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
], heading="Blog information"),
FieldPanel('intro'),
FieldPanel('body'),
InlinePanel('gallery_images', label="Gallery images"),
]
Здесь мы используем ключевое слово widget в определении FieldPanel для указания виджета на основе флажка вместо поля выбора по умолчанию, так как это часто считается более удобным для пользователя.
Наконец, мы можем обновить шаблон blog_page.html для отображения категорий:
<h1>{{ page.title }}</h1>
<p class="meta">{{ page.date }}</p>
{% with categories=page.categories.all %}
{% if categories %}
<h3>Posted in:</h3>
<ul>
{% for category in categories %}
<li style="display: inline">
{% image category.icon fill-32x32 style="vertical-align: middle" %}
{{ category.name }}
</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
Куда дальше
- Прочитайте документацию Wagtail по разделам и справочнику
- Узнайте, как реализовать StreamField для содержимого страницы произвольной формы
- Просмотрите раздел расширенных тем и прочитайте руководства сторонних разработчиков
© 2014-present Torchbox Ltd and individual contributors.
All rights are reserved.
Licensed under the BSD License.
https://docs.wagtail.org/en/stable/getting_started/tutorial.html