Spec-Zone.ru › Wagtail 3

Ваш первый сайт Wagtail

Примечание

Этот учебник охватывает настройку нового проекта Wagtail. Если вы хотите добавить Wagtail в существующий проект Django, см. Интеграция Wagtail в проект Django.

Установка и запуск Wagtail

Установка зависимостей

Wagtail поддерживает Python 3.7, 3.8, 3.9 и 3.10.

Чтобы проверить, какая у вас версия Python 3:

$ python3 --version

Если это не возвращает номер версии или возвращает версию ниже 3.7, вам потребуется установить Python 3.

Важно

Перед установкой Wagtail необходимо установить библиотеки libjpeg и zlib, которые обеспечивают поддержку работы с изображениями JPEG, PNG и GIF (через библиотеку Python Pillow). Способ установки зависит от платформы — см. инструкции по установке для вашей платформы.

Создание и активация виртуальной среды

Мы рекомендуем использовать виртуальную среду, которая изолирует установленные зависимости от других проектов. В этом учебнике используется venv, которая входит в комплект Python 3.

В Windows (cmd.exe):

> python3 -m venv mysite\env
> mysite\env\Scripts\activate.bat

В GNU/Linux или MacOS (bash):

$ python3 -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. Посмотрите в этом файле, как команда start перечислила их там.

Установка зависимостей проекта

$ cd mysite
$ pip install -r requirements.txt

Это гарантирует, что у вас есть соответствующие версии Wagtail, Django и других зависимостей для только что созданного проекта.

Создание базы данных

Если вы не обновили настройки проекта, это будет файл базы данных 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/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', classname="full"),
    ]

body определено как RichTextField, специальное поле Wagtail. Когда blank=True, это означает, что это поле необязательно и может быть пустым. Вы можете использовать любые из полей Django core. 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.

Теги шаблонов Wagtail

Помимо тегов и фильтров Django тегов и фильтров, Wagtail предоставляет ряд собственных тегов и фильтров шаблонов, которые можно загрузить, включив {% load wagtailcore_tags %} в верхней части файла шаблона.

В этом руководстве мы используем фильтр richtext для экранирования и вывода содержимого RichTextField:

{% load wagtailcore_tags %}
{{ page.body|richtext }}

Результат:

<p>
    <b>Welcome</b> to our new site!
</p>

Примечание: Вам нужно будет включить {% load wagtailcore_tags %} в каждом шаблоне, использующем теги Wagtail. Django выдаст TemplateSyntaxError если теги не загружены.

Основной блог

Теперь мы готовы создать блог. Для этого запустите 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', classname="full")
    ]

Запустите python manage.py makemigrations и python manage.py migrate.

Поскольку модель называется BlogIndexPage, имя шаблона по умолчанию (если мы его не переопределим) будет blog/templates/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, который похож на тег Django url, но принимает объект Wagtail Page в качестве аргумента.

В административной панели Wagtail создайте BlogIndexPage как дочерний элемент главной страницы, убедитесь, что у него есть псевдоним «blog» на вкладке «Опубликовать», и опубликуйте его. Теперь вы должны иметь возможность получить доступ к URL /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', classname="full"),
    ]

В модели выше мы импортируем index, так как это делает модель поисковой. Затем вы можете перечислить поля, которые вы хотите, чтобы пользователь мог искать.

Запустите python manage.py makemigrations и python manage.py migrate.

Создайте шаблон в blog/templates/blog/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. Хотя поле «заголовок» присутствует в базовой модели Page, «введение» присутствует только в модели 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, вы обнаружите целый ряд модификаторов QuerySet, которые помогут вам перемещаться по иерархии.

# 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()

Для получения дополнительной информации см.: Справочник по QuerySet для страниц

Переопределение контекста

У нашей страницы индекса блога есть несколько проблем:

  1. Блоги обычно отображают контент в обратном хронологическом порядке
  2. Мы хотим убедиться, что отображаем только опубликованный контент.

Для этого нам нужно сделать больше, чем просто получить дочерние элементы страницы индекса в шаблоне. Вместо этого мы хотим изменить QuerySet в определении модели. 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

Всё, что мы сделали, это извлекли исходный контекст, создали пользовательский QuerySet, добавили его в полученный контекст и вернули изменённый контекст обратно в представление. Вам также потребуется немного изменить ваш шаблон blog_index_page.html. Измените:

{% for post in page.get_children %} на {% for post in blogpages %}

Теперь попробуйте убрать одну из ваших статей – она должна исчезнуть со страницы индекса блога. Остальные статьи теперь должны быть отсортированы так, чтобы самые недавно опубликованные были первыми.

Изображения

Добавим возможность прикреплять галерею изображений к нашим статьям блога. Хотя можно просто вставить изображения в поле форматированного текста body, есть несколько преимуществ при настройке изображений галереи в качестве нового отдельного типа объекта в базе данных – таким образом, вы получаете полный контроль над макетом и стилем изображений в шаблоне, а не должны выстраивать их определённым образом в поле форматированного текста. Это также позволяет использовать изображения в других местах, независимо от текста блога – например, отображать миниатюры на странице индекса блога.

Добавьте новую модель 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', classname="full"),
        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', classname="full"),
        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-адрес в админпанели и манипулировать ее содержимым, возвращая QuerySet из метода 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, а также не имеем модели профиля для авторов — мы оставим это в качестве упражнения для читателя.

При нажатии на кнопку тега внизу страницы BlogPost теперь должна отображаться страница примерно такого вида:

Категории

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

Сначала определим модель 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 — так как сниппеты обычно не нуждаются в таких полях, как слаг или дата публикации, интерфейс редактирования для них не разделен на отдельные вкладки «содержимое» / «опубликовать» / «настройки» по умолчанию, поэтому нет необходимости различать «панели контента» и «панели публикации».

Примените эти изменения и создайте несколько категорий в области «Сниппеты», которая теперь отображается в меню админпанели.

Теперь мы можем добавить категории в модель BlogPage в качестве поля «многие ко многим». Для этого мы используем тип поля ParentalManyToManyField — это разновидность стандартного Django ManyToManyField, гарантирующая, что выбранные объекты правильно сохраняются в записях страниц в истории ревизий, так же, как ParentalKey заменяет ForeignKey для отношений «один ко многим».

# 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/v3.0.3/getting_started/tutorial.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API