Создание сайта на Wagtail (CMS на Django)

В мире Python концепт CMS, не похож с тем что вы возможно встречали в PHP (WordPress). Разобраться в PHP с готовым CMS гораздо легче чем в Python. На данный момент существуют несколько CMS которые используют фреймворк Django, самые популярные из них это django-cms и Wagtail. В данной статье мы будем выполнять первые шаги к собственному сайту на Python используя Wagtail.

Настройка сервера (VPS)

Обычный хостинг для такого проекта будет недостаточно. Если для блога на PHP достаточно заказать обычный хостинг, то для веб проекта на Python мы рекомендуем полноценный VPS на операционной системе Linux от Fornex.com Мы долгое время пользуемся их услугами для нескольких наших проектов и можем рекомендовать их.

Заказ VPS

Ссылка на хостинг: Fornex

После регистрации и входа в ваш личный кабинет, у вас появится возможность заказать VPS и указать необходимые настройки. В моем случае это SSD CLOUD 1GB на операционной системе Ubuntu 18.04 LTS (это самая актуальная версия на момент написания статьи, советуем всегда выбирает самые свежие версии).

Создание сайта на Wagtail (CMS на Django)

Если вы профи в Linux, то можете выбрать настройку «Без панели» при выборе панели управления, но если вы хотите иметь некий интерфейс настроек, то советуем выбрать панель управления «Vesta CP«.

Создание сайта на Wagtail (CMS на Django)

Подключаемся по SSH и выполняем необходимые команды в консоли.

На VPS у меня уже был установлен Python 3.6.7 но если по каким либо причинам его у вас нет, то устанавливаем его таким образом:

Далее, устанавливаем необходимые библиотеки:

Не забываем про pip:

Все манипуляции и установки через pip выполняем исключительно в виртуальном окружении, чтобы не засорять ненужными библиотеками главное окружение. В папке /home создаем новую папку /home/web

Находясь в папке /home/web мы создадим виртуальное окружение для нашего приложения:

Вводная строка терминала изменилась на что-то подобное:

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

Запустите сайт:

Wagtail предоставляет команду start, аналогичную django-admin.py startproject. Запуск wagtail start mysite в вашем проекте создаст новую папку mysite со специальными дополнениями Wagtail, включая необходимые настройки проекта, приложение “home” с пустой домашней страницей, основными шаблонами и примером приложения “search”.

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

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

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

Если вы не обновили настройки проекта, то у вас будет файл базы данных SQLite в папке проекта.

Создание пользователя admin:

Запуск сервера:

Если все сработало, то http://127.0.0.1:8000 покажет страницу приветствия:

Создание сайта на Wagtail (CMS на Django)Вы можете перейти к административному разделу в http://127.0.0.1:8000/admin

Создание сайта на Wagtail (CMS на Django)

Расширяем модель домашней страницы

Изначально, приложение “home” определяет пустую модель домашней страницы в models.py, наряду с миграцией, которая создает домашнюю страницу и указывает Wagtail использовать ее.

Измените home/models.py как указано внизу, чтобы внести поле body в модель:

body определено как RichTextField, специальное поле Wagtail. Вы можете использовать любые поля Django. content_panels определяет возможности и макет интерфейса редактирования.

Запустите manage.py makemigrations, затем python manage.py migrate, чтобы обновить базу данных с изменениями вашей модели. Вам следует вводить указанные команды каждый раз, когда вы вносите изменения в модели.

Теперь вы можете редактировать домашнюю страницу внутри раздела администратора Wagtail (переходить на Pages, Homepage и Edit), чтобы увидеть новое поле body. Введите какой-нибудь текст в поле body, и опубликуйте страницу.

Шаблон страницы теперь ждет обновления для отображения изменений, внесенных в модель. Wagtail использует обычные шаблоны Django для рендера каждого типа страницы. По умолчанию, он будет искать название шаблона, состоящего из названий приложения и модели, с разделенными нижним подчеркиванием названиями (например, HomePage внутри приложения “home” называется home/home_page.html). Этот файл шаблона может существовать в любой распознаваемой правилами шаблонов Django локации. Условно, он помещает под папкой с шаблонами внутри приложения.

Измените home/templates/home/home_page.html:

Создание сайта на Wagtail (CMS на Django)

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

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

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

Генерирует:

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

Пример: Простой блог

С этого момента мы можем приступить к созданию блога. Чтобы сделать это, запустите python manage.py startapp blog, чтобы создать новое приложение в вашем сайте Wagtail.

Внесите новое приложение blog в INSTALLED_APPS в mysite/settings/base.py.

Главная страница блога и посты

Давайте начнем с простой страницы индекса для нашего блога. В blog/models.py:

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

Так как модель называется BlogIndexPage, название шаблона по умолчанию (если мы не меняли его) будет blog/templates/blog/blog_index_page.html. Создайте этот файл со следующим содержимым:

Большая часть содержимого должна быть вам знакома, но мы объясним что делает get_children немного позже. Обратите внимание на тег pageurl, который аналогичен тегу url в Django, но принимает объект страницы Wagtail в качестве аргумента.

В админке Wagtail создайте BlogIndexPage в качестве дочернего элемента Homepage, убедитесь, что у него есть слаг “blog” во вкладке Promote и опубликуйте его. Теперь у вас должен появиться доступ к url /blog на вашем сайте (обратите внимание на то, как слаг из панели Promote определяет URL страницы).

Теперь нам нужна модель и шаблон для наших постов в блоге. В blog/models.py:

Запускаем в терминале python manage.py makemigrations и python manage.py migrate.

Создаем шаблон в blog/templates/blog/blog_page.html:

Обратите внимание на то, что мы использовали метод get_parent() для получения URL блога, частью которого является этот пост.

Теперь создаем несколько постов в блоге в качестве дочерних элементов BlogIndexPage. Убедитесь в том, что выбрали “Blog Page” при создании ваших постов.

Создание сайта на Wagtail (CMS на Django)

Создание сайта на Wagtail (CMS на Django)

Wagtail дает вам полный контроль над тем, какой тип содержимого может быть создан под различными родительскими типами контента. По умолчанию, любая страница может быть дочерней любого типа страницы.

Создание сайта на Wagtail (CMS на Django)

Теперь у вас в распоряжении базовый рабочий блог. Перейдите по URL /blog и увидите что-нибудь вроде следующего:

Создание сайта на Wagtail (CMS на Django)

Заголовки должны переводить к страницам постов, а ссылка на главную страницу должна появиться в футере каждой страницы поста.

Дочерние и родительские элементы

Большая часть работы, выполняемая в Wagtail вертится вокруг концепции иерархии структуры “дерева”, состоящего из ветвей и листьев. В данном случае, BlogIndexPage — ветвь, а экземпляры страниц блога являются листьями.

Рассмотрим blog_index_page.html изнутри:

Каждая “страница” в Wagtail может вызывать дочернюю или родительскую страницу со своей позиции в иерархии. Но почему мы должны определять post.specific.intro вместо post.intro? Это напрямую связано с тем, как мы определили нашу модель:

Метод get_children() выдает нам список экземпляров основного класса Page. Когда нам нужно сослаться на свойства экземпляров, которые наследуются от базового класса, Wagtail предоставляет особый метод, который возвращает фактическую запись BlogPage. В то время как поле “title” присутствует в базовой модели Page, “intro” присутствует только в модели BlogPage, так что нам нужен .specific для получения доступа.

Чтобы сжать код шаблона, мы можем использовать тег Django под названием with:

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

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

Есть небольшие проблемы с главной страницей нашего блога:

  1. Контент в блогах (как правило) показывается в обратном хронологическом порядке;
  2. Нам нужно быть уверенными в том, что мы показываем только опубликованный контент (без черновиков).

Чтобы достичь этих целей, нам нужно сделать нечто большее, чем просто взять дочерние элементы страниц индекса в шаблоне.

Вместо этого, нам нужно будет обновить QuerySet в определении модели. Wagtail позволяет сделать это просто, при помощи переопределяемого метода get_context(). Изменение модели BlogIndexPage проходит следующим образом:

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

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

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

Изображения

Давайте добавим возможность внесения галереи изображений в наши посты. Хотя мы можем просто вставлять картинки в тело текста, все же есть несколько преимуществ в настройке галереи изображений как нового выделенного типа объекта внутри базы данных.

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

Добавим новую модель BlogPageGalleryImage в models.py:

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

Здесь есть несколько новых концепций, давайте рассмотрим их все сразу:

Наследование от Orderable добавляет поле sort_order в модель, чтобы отслеживать порядок изображений в галерее.

ParentalKey от BlogPage отвечает за прикрепление изображений галереи в определенную страницу. ParentalKey работает аналогично ForeignKey, но также рассматривается как фундаментальная часть страницы в таких операциях, как отправка на модерацию и отслеживание истории изменений.

image является ForeignKey во встроенной модели Wagtail под названием Image, в которой хранятся изображения. Это включает выделенный тип панели ImageChooserPanel, который предоставляет всплывающий интерфейс для выбора существующего изображения или выбора нового. Таким образом, мы позволяем изображению существовать в нескольких галереях — фактически, мы создали мульти-отношение между страницами и изображениями.

Указание on_delete=models.CASCADE для внешнего ключа означает, что если изображение было удалено из системы, оно также будет удалено из галереи. (В других ситуациях, было бы разумным оставлять изображение в галерее, например если речь идет о странице “наши сотрудники”, где размещены снимки сотрудников, и одна из фотографий удалена по той или иной причине — было бы неплохо оставить это изображение в базе. В данном случае, мы меняем внешний ключ на blank=True, null=True, on_delete=models.SET_NULL.)

Наконец, внесение InlinePanel в BlogPage.content_panels делает изображения галереи доступными для интерфейса редактирования в BlogPage.

Настройте шаблон страницы блога, чтобы включить изображения:

Здесь мы используем тег {% image %} (который существует в библиотеке wagtailimages_tags, импортированной вверху шаблона) для внесения элемента с параметром  fill-320x240 чтобы отметить, что изображение должно попадать под прямоугольник размером 320х240.

Создание сайта на Wagtail (CMS на Django)

Так как изображения нашей галереи являются объектами базы данных сами по себе, мы можем запрашивать и повторно использовать их, вне зависимости от тела поста в блоге. Давайте определим метод main_image, который возвращает изображение из первого элемента галереи (или None, если в галерее нет элементов):

Этот метод теперь доступен в наших шаблонах. Обновите blog_index_page.html для внесения главного изображения в анонс рядом с каждым изображением:

Метки постов

Скажем, нам нужно дать возможность редакторам “отмечать” их посты, чтобы читатель мог просматривать тематический контент. Для этого, нам нужно вызвать систему тегов, предоставляемую в комплекте с Wagtail, прикрепить ее к модели BlogPage и панелям контента, отобразить связанные теги в шаблоне поста. Разумеется, нам понадобится рабочий вид URL для конкретных тегов.

Во-первых, поменяем models.py еще раз:

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

Обратите внимание на новые импорты modelcluster и taggit, внесение новой модели BlogPageTag и внесение поля тегов в BlogPage. Мы также воспользовались возможность использовать MultiFieldPanel в content_panels для группировки данных и полей тегов вместе для читаемости.

Поменяйте один из экземпляров ваших BlogPage, и вы сможете отмечать посты:

Создание сайта на Wagtail (CMS на Django)

Для отображение тегов в BlogPage, добавьте следующее в blog_page.html:

Обратите внимание на то, что здесь мы ссылаемся на страницы при помощи встроенного тега slugurl, вместо pageurl, которым мы пользовались ранее. Разница в том, что slugurl использует слаг Page (из панели Promote) в качестве аргумента. В то же время, pageurl чаще используется, так как он прямолинеен и избегает дополнительных поисков в базе данных. Но в случае с данным циклом, объект Page не является доступным, так что нам понадобится менее предпочитаемый тег slugurl.

Переход к тегам в постах блога теперь должно показывать набор связанных кнопок внизу — по одной на каждый тег. Однако, нажатие на кнопку выведет ошибку 404, так как мы еще не определили вид тегов. Нужно добавить следующее в models.py:

Обратите внимание на то, что эта базируемая на Page модель по умолчанию не определяет поля. Даже без полей, создание подкласса Page делает его частью экосистемы Wagtail, так что вы можете дать ему заголовок и URL в админке, а также управлять его содержимым, возвращая QuerySet из метода get_context().

Проведите миграцию, затем создайте BlogTagIndexPage в админке. Вам возможно понадобится создать новую страницу или вид в качестве дочернего элемента домашней страницы, параллельно с главной страницей вашего блога (/blog). Назначьте ему слаг “tags” в панели Promote.

Перейдите к /tags и Django скажет вам то, что вы скорее всего уже знаете: вам нужно создать шаблон blog/blog_tag_index_page.html:

Мы вызываем встроенное поле latest_revision_created_at в модели Page — приятно знать, что оно всегда в доступе.

Мы еще не добавили поле “author” в нашу модель BlogPage , как и модель профиля для авторов — оставим это как практическое задание для читателя.

Нажатие на кнопку тега внизу поста теперь должно открывать страницу следующим образом:

Создание сайта на Wagtail (CMS на Django)

Категории

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

Сначала, мы определим модель BlogCategory. Категория не является страницей сама по себе, так что мы определим ее как стандартную models.Model в Django, вместо наследования из Page. Wagtail предоставляет концепт “сниппетов” для используемых повторно частей контента, которыми нужно управлять из админки, но не существует как часть дерева сайта.

Модель может быть зарегистрирована путем внесения декоратора @register_snippet. Все типы полей, которые мы использовали на данный момент на странице могут быть использованы в сниппетах — здесь мы дадим иконку каждой категории, а также название. Внесем в blog/models.py следующее:

Обратите внимание: Мы используем panels вместо content_panels в данном коде, так как сниппеты в целом не нуждаются в полях так, как слаги или дата публикации, интерфейс редактирования для них не делится на отдельные панели “контент” / “настройки” / “продвижение” в качестве стандартных, и здесь нет необходимости проводить различие между “информационными панелями” и “рекламными панелями”.

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

Теперь мы можем добавлять категории в модель BlogPage в качестве мульти-поля. Тип поля, который мы используем для этого — ParentalManyToManyField. Это вариант стандартного поля ManyToManyField в Django, которое проверяет, правильно ли хранятся выбранные объекты в записях истории изменений. Во многом это тот же способ, которым ParentalKey заменяет ForeignKey для отношений “один ко многим”.

Здесь мы используем аргумент widget в определении FieldPanel для спецификации виджета, основанного на чекбоксе, вместо стандартного бокса множественного выбора. Такой подход можно назвать наиболее удобным для пользователя.

Наконец, мы можем обновить шаблон blog_page.html для отображения категорий:

Создание сайта на Wagtail (CMS на Django)