Содержание

Запускаем простой блог на Wagtail CMS (Django) — часть 1 / Хабр

Являясь большим фанатом Python и фреймворка Django постоянно искал решение, как сделать разработку новых веб-проектов быстрее и удобнее.

Все, кто знаком с разработкой на Django, знают насколько неудобно строить на нем интуитивно понятную админ.панель. До мегапопулярного WordPress очень далеко, что делает порог вхождения в разработку сайтов выше, чем у PHP-фреймворков и CMS.
После долгого поиска и тестирования различных решений я нашел для себя оптимальный вариант — Wagtail CMS.

Wagtail — это полноценная CMS написанная на Django компанией Torchbox. За что им большое спасибо. Проект с открытым исходным кодом, поддерживается сообществом энтузиастов и выпускается под BSD лицензией.

Вот типичный интерфейс Wagtail:

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

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

Нам понадобится:
1. VPS минимум с 1gb памяти
2. Базовое знакомство с django
3. Навыки в удаленной настройке Ubuntu

Шаг 1:
Вы запустили VPS сервер для разработки. Используйте Putty для удаленного подключения по SSH.
Настройка проводиться на сервере с ОС Ubuntu 14.04.

— Логинимся и начинаем базовую настройку:

sudo apt-get update
sudo apt-get upgrade

Как правило, на сервере уже стоит Python 2 и 3 версии. Проверяем следующими командами:

Python -V
Python3 -V

Для проекта будем использовать Python 3, так как с ним лучше работает Pillow и некоторые библиотеки, которые нам понадобятся.
Если каким-то образом у вас на сервере не оказалось python3, то ставим его командой:

sudo apt-get install python3

Так же нам потребуется python3-setuptools и python3-dev:

sudo apt-get install python3-setuptools
sudo apt-get install python3-dev

Учитывая, что все ставиться на чистую систему поставим PIP (чтобы поставить свежую версию «8. 1.2» лучше делать это через easy_install3):

sudo easy_install3 pip

Проверить версию PIP можно командой:

pip -V

Если версия не самая свежая, то обновляем pip командой:

sudo pip install --upgrade pip

Чтобы в дальнейшем у нас не выпадало различных ошибок при установке Wagtail, сразу ставим все необходимые библиотеки для Pillow:

sudo apt-get install libtiff5-dev libjpeg8-dev zlib1g-dev \
    libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk

Теперь нам потребуется виртуальное окружение для проекта, ставим virtualenv

sudo pip3 install virtualenv

Создаем виртуалку:

virtualenv /opt/myenv

Идем в папку /opt/myenv и запускаем виртуальную среду:

source bin/activate

Вы должны увидеть строку с названием (myenv), означающую, что виртуалка запущена:

(myenv) [email protected]:/opt/myenv$

Шаг 2: Предварительная настройка сделана, переходим к установке Wagtail CMS и настройке базы данных:
1. ставим Wagtail и Gunicorn:

sudo pip3 install wagtail gunicorn

2. Создаем наш будущий блог командой

wagtail start mysite

3. Перейдем в папку блога (mysite) и установим все зависимости для проекта из файла requirements.txt

cd mysite
pip install -r requirements.txt

4. На данном этапе вы можете провести миграции и сразу запустить тестовый сервер вашего будущего блога. Но так как мы планируем сделать все правильно, то наш блог будет работать на PostgreSQL (Для django можно использовать разные базы данных, в том числе MySQL, PostgreSQL, Oracle DB и т.д). Так что проводим настройку базы данных:

sudo apt-get install libpq-dev 
sudo apt-get install postgresql postgresql-contrib 

5. На данный момент у вас уже запущен postgresql на порту 5432. Но чтобы база данных могла взаимодействовать с Wagtail нужно поставить psycopg2:

pip install psycopg2

6. Теперь надо создать саму базу данных для проекта, для этого зайдем под пользователем postgres и проведем некоторые дополнительные настройки.


Для улучшения безопасности любых веб-проектов не рекомендуется использовать доступ к БД через postgres — суперпользователя PostgreSQL. Спасибо за комментарий immaculate
Совсем просто можно создать отдельного пользователя wagtail и базу wagtail, к которой будет доступ у приложения:

# sudo su – postgres
# createdb -O wagtail wagtail

7. Так как postgresql создает пользователя wagtail не спрашивая вас про пароль, нам надо назначит ему пароль через psql:

psql

Теперь вы можете выполнить команду смены пароля:

\password wagtail

Выходим из psql и закрываем сеанс под пользователем postgres:

\q
exit

Проверьте, что вы еще в виртуально среде, если нет то опять активируйте виртуалку.

8. Теперь нам надо настроить Wagtail на взаимодействие c postgresql, для этого идем в base.py:

cd /opt/myenv/mysite/mysite/settings/
nano base. py

Открывается файл настроек, нам нужна запись DATABASES. Удаляем, то что есть и ставим туда следующее:

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2', # указываем, что база данных postgresql
'NAME': 'wagtail', # Название вашей базы данных приложения
'USER': 'wagtail', #имя пользователя, который подключается к БД
'PASSWORD': '******',
'HOST': 'localhost',
'PORT': '',
}
}

Кстати, в wagtail есть русский язык, так что можете заодно в этом же файле с настройками поправить параметр LANGUAGE_CODE.

LANGUAGE_CODE = 'ru-ru'

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

Шаг 3. Первый запуск

Перейдите в корневую папку вашего блога (cd /opt/myenv/mysite/) и начните вашу первую миграцию данных в данном проекте:

python manage.py migrate

Теперь создайте пользователя под которым вы будете заходить в админ. панель Wagtail:

python manage.py createsuperuser

Введите имя (например admin), пароль и почту.

Проверяем, что все работает:

python manage.py runserver 0.0.0.0:8000

Перейдем в браузере по ip адресу вашего VPS на 8000 порт (пример 199.199.32.32:8000) и увидим стартовую страницу wagtail.

Отлично, теперь перейдем в админ панель 199.199.32.32:8000/admin, вводим логин и пароль пользователя которые мы создали ранее и попадаем в удобный интерфейс Wagtail CMS:

Пока что наш сайт ничего не умеет, но скоро мы научим его всему необходимому для блога.

Шаг 4. Настройка стартовой страницы сайта

За главную страницу отвечает приложение «home» которое запускается из коробки. Чтобы стартовая страница была такой как вам надо внесем некоторые изменения в код.

Структура вашего сайта имеет такой вид:

mysite/
    home/
        migrations/
            __init__.py
            0001_initial. py
            0002_create_homepage.py
        templates/
            home/
                home_page.html
        __init__.py
        models.py
    search/
        templates/
            search/
                search.html
        __init__.py
        views.py
    mysite/
        settings/
            __init__.py
            base.py
            dev.py
            production.py
        static/
            css/
                mysite.css
            js/
                mysite.js
        templates/
            404.html
            500.html
            base.html
        __init__.py
        urls.py
        wsgi.py
    manage.py
    requirements.txt

Отредактируйте файл «home/models.py» следующим образом:

from __future__ import unicode_literals
from django.db import models
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore.fields import RichTextField
from wagtail.wagtailadmin.edit_handlers import FieldPanel
class HomePage(Page):
    body = RichTextField(blank=True)
    content_panels = Page.
content_panels + [ FieldPanel('body', classname="full") ]

Также отредактируйте файл home/templates/home/home_page.html:

{%  extends  "base.html"  %} 
{%  load  wagtailcore_tags  %} 
{%  block  body_class  %} template-homepage {%  endblock  %} 
{%  block  content  %} 
    {{  page.body | richtext  }} 
{%  endblock  %}

Теперь, чтобы новые изменения заработали, проведите миграции:

python manage.py makemigrations
python manage.py migrate

После этого вы можете зайти в админ.панель и отредактировать текст на вашей стартовой странице.
Сохраните изменения и откройте опять ваш сайт. Вы увидите свой текст, примерно такого вида:

Для начала неплохо, но надо добавить разметку и красоту css.
За базовый шаблон отвечает файл «base.html» расположенный в mysite/settings/base.py;
За css отвечает «mysite.css» расположенный в mysite/static/css/mysite.css;
За js отвечает «mysite.js» расположенный в mysite/static/js/mysite. js;

Я поклонник materializecss и для нашего сайта мы возьмем у них шаблон и библиотеки.
Смотреть тут http://materializecss.com/getting-started.html

Вносим следующие изменения в base.html:

{% load static wagtailuserbar %}
<!DOCTYPE html>
<!--[if lt IE 7]>      <html> <![endif]-->
<!--[if IE 7]>         <html> <![endif]-->
<!--[if IE 8]>         <html> <![endif]-->
<!--[if gt IE 8]><!--> <html> <!--<![endif]-->
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <title>{% block title %}{% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %}{% endblock %}{% block title_suffix %}{% endblock %}</title>
        <meta name="description" content="" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        {# Global stylesheets #}
        <link rel="stylesheet" type="text/css" href="{% static 'css/mysite. css' %}">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/css/materialize.min.css">
        {% block extra_css %}
            {# Override this in templates to add extra stylesheets #}
        {% endblock %}
    </head>
<body>
  <nav role="navigation">
    <div><a href="#">Logo</a>
      <ul>
        <li><a href="#">Navbar Link</a></li>
      </ul>
      <ul>
        <li><a href="#">Navbar Link</a></li>
      </ul>
      <a href="#" data-activates="nav-mobile"><i>menu</i></a>
    </div>
  </nav>
	{% wagtailuserbar %}
  <div>
		
        {% block content %}{% endblock %}
  </div>
  <footer>
    <div>
      <div>
        <div>
          <h5>Company Bio</h5>
          <p>We are a team of college students working on this project like it's our full time job.  Any amount would help support and continue development on this project and is greatly appreciated.</p>
        </div>
        <div>
          <h5>Settings</h5>
          <ul>
            <li><a href="#!">Link 1</a></li>
            <li><a href="#!">Link 2</a></li>
            <li><a href="#!">Link 3</a></li>
            <li><a href="#!">Link 4</a></li>
          </ul>
        </div>
        <div>
          <h5>Connect</h5>
          <ul>
            <li><a href="#!">Link 1</a></li>
            <li><a href="#!">Link 2</a></li>
            <li><a href="#!">Link 3</a></li>
            <li><a href="#!">Link 4</a></li>
          </ul>
        </div>
      </div>
    </div>
    <div>
      <div>
      Made by <a href="http://materializecss. com">Materialize</a>
      </div>
    </div>
  </footer>
  <!--  Scripts-->
  <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/js/materialize.min.js"></script>
        {# Global javascript #}
        <script type="text/javascript" src="{% static 'js/mysite.js' %}"></script>
        {% block extra_js %}
            {# Override this in templates to add extra javascript #}
        {% endblock %}
    </body>
</html>

Сохраняем и снова открываем наш сайт. Должно получиться, что-то похожее на эту картинку:

Уже лучше. Теперь нам надо создать блог, ради которого все и затевалось.

Шаг 5. Создание блога.

Идем опять в корень нашего проекта и выполняем следующую команду:

python manage.py startapp blog

После этого добавьте новое приложение «blog» в INSTALLED_APPS в mysite/settings/base. py.

Для каждой записи нашего блога будут стандартные поля — заголовок, дата, картинка, введение и основной текст. Чтобы все это работало внесите следующие изменения в «blog/models.py»

from django.db import models
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore.fields import RichTextField
from wagtail.wagtailadmin.edit_handlers import FieldPanel
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailsearch import index
class BlogPage(Page):
    main_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    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'),
        ImageChooserPanel('main_image'),
        FieldPanel('intro'),
        FieldPanel('body'),
    ]

Теперь создайте саму страницу, на которую все это будет выводиться в blog/templates/blog/blog_page. html:

{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags %}
{% block body_class %}template-blogpage{% endblock %}
{% block content %}
    <h2>{{ page.title }}</h2>
    <p>{{ page.date }}</p>
    {% if page.main_image %}
        {% image page.main_image width-500 %}
    {% endif %}
    <div>{{ page.intro }}</div>
    {{ page.body|richtext }}
{% endblock %}

Пока что ничего не работает, поэтому выполняем команды

python manage.py makemigrations
python manage.py migrate

Теперь можно создать первую запись в нашем блоге, через удобный интерфейс wagtail.

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

Выбираем «Blog Page» и заполняем страницу информацией.

Вот пример редактирования страницы блога в админ.панели wagtail:

Довольно удобно и быстро.

Сохраняем запись и открываем в браузере. Первая запись вашего блога готова.

Если хоть кому-то данный пост окажется полезным, то я продолжу публикацию постов на тему wagtail, в которой расскажу подробнее про различные возможности данной CMS.

Часть 2
Часть 3

Для всех заинтересовавшихся Wagtail CMS:
Официальный сайт https://wagtail.io/
Гитхаб проекта https://github.com/torchbox/wagtail/
Документация http://docs.wagtail.io/en/latest/index.html
Демосайт для изучения http://docs.wagtail.io/en/latest/getting_started/demo_site.html
Группа поддержки пользователей https://groups.google.com/forum/#!forum/wagtail

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

В мире 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 (это самая актуальная версия на момент написания статьи, советуем всегда выбирает самые свежие версии).

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

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

$ apt update $ apt upgrade

$ apt update

$ apt upgrade

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

$ apt install python3

$ apt install python3

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

$ apt install python3-setuptools python3-dev python3-venv $ apt install libtiff5-dev libjpeg8-dev zlib1g-dev $ apt install libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk

$ apt install python3-setuptools python3-dev python3-venv

$ apt install libtiff5-dev libjpeg8-dev zlib1g-dev

$ apt install libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk

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

$ apt install python3-pip

$ apt install python3-pip

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

cd /home mkdir web cd web

cd /home

mkdir web

cd web

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

python3 -m venv venv source venv/bin/activate

python3 -m venv venv

source venv/bin/activate

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

(venv) user@kvmde58-14996:/home/web#

(venv) user@kvmde58-14996:/home/web#

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

$ pip install wagtail

$ pip install wagtail

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

$ wagtail start mysite $ cd mysite

$ wagtail start mysite

$ cd mysite

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

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

$ pip install -r requirements.txt

$ pip install -r requirements.txt

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

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

Python

$ python manage.py migrate

$ python manage.py migrate

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

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

$ python manage.py createsuperuser

$ python manage. py createsuperuser

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

$ python manage.py runserver

$ python manage.py runserver

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

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

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

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

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

Python

from django.db import models from wagtail.core.models import Page from wagtail.core.fields import RichTextField from wagtail.admin.edit_handlers import FieldPanel class HomePage(Page): body = RichTextField(blank=True) content_panels = Page. content_panels + [ FieldPanel(‘body’, classname=»full»), ]

1

2

3

4

5

6

7

8

9

10

11

12

13

from django.db import models

 

from wagtail.core.models import Page

from wagtail.core.fields import RichTextField

from wagtail.admin.edit_handlers import FieldPanel

 

 

class HomePage(Page):

    body = RichTextField(blank=True)

 

    content_panels = Page.content_panels + [

        FieldPanel(‘body’, classname=»full»),

    ]

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:

{% extends «base.html» %} {% load wagtailcore_tags %} {% block body_class %}template-homepage{% endblock %} {% block content %} {{ page.body|richtext }} {% endblock %}

1

2

3

4

5

6

7

8

9

{% extends «base.html» %}

 

{% load wagtailcore_tags %}

 

{% block body_class %}template-homepage{% endblock %}

 

{% block content %}

    {{ page.body|richtext }}

{% endblock %}

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

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

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

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

{% load wagtailcore_tags %}

{{ page. body|richtext }}

Генерирует:

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

<div>

    <p>

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

    </p>

</div>

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

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

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

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

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

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

Python

from wagtail.core.models import Page from wagtail.core.fields import RichTextField from wagtail.admin.edit_handlers import FieldPanel class BlogIndexPage(Page): intro = RichTextField(blank=True) content_panels = Page.content_panels + [ FieldPanel(‘intro’, classname=»full») ]

1

2

3

4

5

6

7

8

9

10

11

from wagtail.core.models import Page

from wagtail.core.fields import RichTextField

from wagtail.admin.edit_handlers 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 %} <h2>{{ page.title }}</h2> <div>{{ page.intro|richtext }}</div> {% for post in page.get_children %} <h3><a href=»{% pageurl post %}»>{{ post.title }}</a></h3> {{ post.specific.intro }} {{ post.specific.body|richtext }} {% endfor %} {% endblock %}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

{% extends «base.html» %}

 

{% load wagtailcore_tags %}

 

{% block body_class %}template-blogindexpage{% endblock %}

 

{% block content %}

    <h2>{{ page. title }}</h2>

 

    <div>{{ page.intro|richtext }}</div>

 

    {% for post in page.get_children %}

        <h3><a href=»{% pageurl post %}»>{{ post.title }}</a></h3>

        {{ post.specific.intro }}

        {{ post.specific.body|richtext }}

    {% endfor %}

 

{% endblock %}

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

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

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

Python

from django.db import models from wagtail.core.models import Page from wagtail.core.fields import RichTextField from wagtail.admin.edit_handlers import FieldPanel from wagtail.search import index # Сохраняем определение BlogIndexPage и вносим: 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»), ]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

from django. db import models

 

from wagtail.core.models import Page

from wagtail.core.fields import RichTextField

from wagtail.admin.edit_handlers import FieldPanel

from wagtail.search import index

 

 

# Сохраняем определение BlogIndexPage и вносим:

 

 

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»),

    ]

Запускаем в терминале 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 %} <h2>{{ page.title }}</h2> <p>{{ page.date }}</p> <div>{{ page.intro }}</div> {{ page.body|richtext }} <p><a href=»{{ page.get_parent.url }}»>Return to blog</a></p> {% endblock %}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

{% extends «base.html» %}

 

{% load wagtailcore_tags %}

 

{% block body_class %}template-blogpage{% endblock %}

 

{% block content %}

    <h2>{{ page.title }}</h2>

    <p>{{ page.date }}</p>

 

    <div>{{ page.intro }}</div>

 

    {{ page.body|richtext }}

 

    <p><a href=»{{ page. get_parent.url }}»>Return to blog</a></p>

 

{% endblock %}

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

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

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

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

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

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

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

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

{% for post in page.get_children %} <h3><a href=»{% pageurl post %}»>{{ post.title }}</a></h3> {{ post.specific.intro }} {{ post.specific.body|richtext }} {% endfor %}

{% for post in page.get_children %}

    <h3><a href=»{% pageurl post %}»>{{ post.title }}</a></h3>

    {{ post.specific.intro }}

    {{ post.specific.body|richtext }}

{% endfor %}

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

Python

class BlogPage(Page):

class BlogPage(Page):

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

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

Python

{% for post in page.get_children %} {% with post=post.specific %} <h3><a href=»{% pageurl post %}»>{{ post.title }}</a></h3> <p>{{ post.intro }}</p> {{ post.body|richtext }} {% endwith %} {% endfor %}

1

2

3

4

5

6

7

{% for post in page.get_children %}

    {% with post=post. specific %}

        <h3><a href=»{% pageurl post %}»>{{ post.title }}</a></h3>

        <p>{{ post.intro }}</p>

        {{ post.body|richtext }}

    {% endwith %}

{% endfor %}

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

Python

# Задан объект страницы ‘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) # … и далее… somepage.get_children() somepage.get_ancestors() somepage.get_descendants() somepage.get_siblings()

1

2

3

4

5

6

7

8

9

10

11

12

# Задан объект страницы ‘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)

 

# … и далее…

somepage.get_children()

somepage.get_ancestors()

somepage.get_descendants()

somepage.get_siblings()

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

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

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

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

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

Python

class BlogIndexPage(Page): intro = RichTextField(blank=True) def get_context(self, request): # Обновляем контекст для внесения только опубликованных постов в обратном хронологическом порядке context = super().get_context(request) blogpages = self.get_children().live().order_by(‘-first_published_at’) context[‘blogpages’] = blogpages return context

1

2

3

4

5

6

7

8

9

class BlogIndexPage(Page):

    intro = RichTextField(blank=True)

 

    def get_context(self, request):

        # Обновляем контекст для внесения только опубликованных постов в обратном хронологическом порядке

        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 %}

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

Изображения

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

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

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

Python

from django.db import models # Новые импорты добавлены для ParentalKey, Orderable, InlinePanel, ImageChooserPanel from modelcluster.fields import ParentalKey from wagtail.core.models import Page, Orderable from wagtail.core.fields import RichTextField from wagtail.admin.edit_handlers import FieldPanel, InlinePanel from wagtail.images.edit_handlers import ImageChooserPanel from wagtail.search import index # … (Оставляем модель BlogIndexPage без изменений, и обновляем 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 = [ ImageChooserPanel(‘image’), FieldPanel(‘caption’), ]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

from django.db import models

 

# Новые импорты добавлены для ParentalKey, Orderable, InlinePanel, ImageChooserPanel

 

from modelcluster.fields import ParentalKey

 

from wagtail.core.models import Page, Orderable

from wagtail. core.fields import RichTextField

from wagtail.admin.edit_handlers import FieldPanel, InlinePanel

from wagtail.images.edit_handlers import ImageChooserPanel

from wagtail.search import index

 

 

# … (Оставляем модель BlogIndexPage без изменений, и обновляем 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 = [

        ImageChooserPanel(‘image’),

        FieldPanel(‘caption’),

    ]

Запускаем 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.

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

{% extends «base.html» %} {% load wagtailcore_tags wagtailimages_tags %} {% block body_class %}template-blogpage{% endblock %} {% block content %} <h2>{{ page.title }}</h2> <p>{{ page.date }}</p> <div>{{ page.intro }}</div> {{ page.body|richtext }} {% for item in page.gallery_images.all %} <div> {% image item.image fill-320×240 %} <p>{{ item.caption }}</p> </div> {% endfor %} <p><a href=»{{ page.get_parent.url }}»>Return to blog</a></p> {% endblock %}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

{% extends «base. html» %}

 

{% load wagtailcore_tags wagtailimages_tags %}

 

{% block body_class %}template-blogpage{% endblock %}

 

{% block content %}

    <h2>{{ page.title }}</h2>

    <p>{{ page.date }}</p>

 

    <div>{{ page.intro }}</div>

 

    {{ page.body|richtext }}

 

    {% for item in page.gallery_images.all %}

        <div>

            {% image item.image fill-320×240 %}

            <p>{{ item.caption }}</p>

        </div>

    {% endfor %}

 

    <p><a href=»{{ page.get_parent.url }}»>Return to blog</a></p>

 

{% endblock %}

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

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

Python

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»), ]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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 %} <h3><a href=»{% pageurl post %}»>{{ post.title }}</a></h3> {% with post.main_image as main_image %} {% if main_image %}{% image main_image fill-160×100 %}{% endif %} {% endwith %} <p>{{ post.intro }}</p> {{ post.body|richtext }} {% endwith %} {% endfor %}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

{% load wagtailcore_tags wagtailimages_tags %}

 

 

{% for post in blogpages %}

    {% with post=post.specific %}

        <h3><a href=»{% pageurl post %}»>{{ post.title }}</a></h3>

 

        {% with post.main_image as main_image %}

            {% if main_image %}{% image main_image fill-160×100 %}{% endif %}

        {% endwith %}

 

        <p>{{ post. intro }}</p>

        {{ post.body|richtext }}

    {% endwith %}

{% endfor %}

Метки постов

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

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

Python

from django.db import models # Добавление новых импортов для ClusterTaggableManager, TaggedItemBase, MultiFieldPanel from modelcluster.fields import ParentalKey from modelcluster.contrib.taggit import ClusterTaggableManager from taggit.models import TaggedItemBase from wagtail.core.models import Page, Orderable from wagtail.core.fields import RichTextField from wagtail. admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel from wagtail.images.edit_handlers import ImageChooserPanel from wagtail.search import index # … (Сохраняем определение 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) # … (Сохраняем определение методов main_image и search_fields) content_panels = Page.content_panels + [ MultiFieldPanel([ FieldPanel(‘date’), FieldPanel(‘tags’), ], heading=»Blog information»), FieldPanel(‘intro’), FieldPanel(‘body’), InlinePanel(‘gallery_images’, label=»Gallery images»), ]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

from django. db import models

 

# Добавление новых импортов для ClusterTaggableManager, TaggedItemBase, MultiFieldPanel

 

from modelcluster.fields import ParentalKey

from modelcluster.contrib.taggit import ClusterTaggableManager

from taggit.models import TaggedItemBase

 

from wagtail.core.models import Page, Orderable

from wagtail.core.fields import RichTextField

from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel

from wagtail.images.edit_handlers import ImageChooserPanel

from wagtail.search import index

 

 

# … (Сохраняем определение 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)

 

    # . .. (Сохраняем определение методов main_image и search_fields)

 

    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 и внесение поля тегов в BlogPage. Мы также воспользовались возможность использовать MultiFieldPanel в content_panels для группировки данных и полей тегов вместе для читаемости.

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

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

{% if page.tags.all.count %} <div> <h4>Tags</h4> {% for tag in page.tags.all %} <a href=»{% slugurl ‘tags’ %}?tag={{ tag }}»><button type=»button»>{{ tag }}</button></a> {% endfor %} </div> {% endif %}

1

2

3

4

5

6

7

8

{% if page.tags.all.count %}

    <div>

        <h4>Tags</h4>

        {% for tag in page.tags.all %}

            <a href=»{% slugurl ‘tags’ %}?tag={{ tag }}»><button type=»button»>{{ tag }}</button></a>

        {% endfor %}

    </div>

{% endif %}

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

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

Python

class BlogTagIndexPage(Page): def get_context(self, request): # Фильтр по тегам tag = request.GET.get(‘tag’) blogpages = BlogPage.objects.filter(tags__name=tag) # Обновление контекста шаблона context = super().get_context(request) context[‘blogpages’] = blogpages return context

1

2

3

4

5

6

7

8

9

10

11

12

class BlogTagIndexPage(Page):

 

    def get_context(self, request):

 

        # Фильтр по тегам

        tag = request. GET.get(‘tag’)

        blogpages = BlogPage.objects.filter(tags__name=tag)

 

        # Обновление контекста шаблона

        context = super().get_context(request)

        context[‘blogpages’] = blogpages

        return context

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

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

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

{% extends «base.html» %} {% load wagtailcore_tags %} {% block content %} {% if request.GET.tag|length %} <h5>Showing pages tagged «{{ request.GET.tag }}»</h5> {% 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 %}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

{% extends «base. html» %}

{% load wagtailcore_tags %}

 

{% block content %}

 

    {% if request.GET.tag|length %}

        <h5>Showing pages tagged «{{ request.GET.tag }}»</h5>

    {% 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 — приятно знать, что оно всегда в доступе.

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

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

Категории

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

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

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

Python

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’), ImageChooserPanel(‘icon’), ] def __str__(self): return self.name class Meta: verbose_name_plural = ‘blog categories’

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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’),

        ImageChooserPanel(‘icon’),

    ]

 

    def __str__(self):

        return self.name

 

    class Meta:

        verbose_name_plural = ‘blog categories’

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

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

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

Python

# Новые импорты вносятся для форм и 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) # … (Сохраняем определение методов main_image и search_fields) 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»), ]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

# Новые импорты вносятся для форм и 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)

 

    # … (Сохраняем определение методов main_image и search_fields)

 

    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 для отображения категорий:

<h2>{{ page.title }}</h2> <p>{{ page.date }}</p> {% with categories=page.categories.all %} {% if categories %} <h4>Posted in:</h4> <ul> {% for category in categories %} <li> {% image category.icon fill-32×32 %} {{ category.name }} </li> {% endfor %} </ul> {% endif %} {% endwith %}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<h2>{{ page.title }}</h2>

<p>{{ page.date }}</p>

 

{% with categories=page.categories.all %}

    {% if categories %}

        <h4>Posted in:</h4>

        <ul>

            {% for category in categories %}

                <li>

                    {% image category. icon fill-32×32 %}

                    {{ category.name }}

                </li>

            {% endfor %}

        </ul>

    {% endif %}

{% endwith %}

Vasile Buldumac

Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.

E-mail: [email protected]

Образование
Universitatea Tehnică a Moldovei (utm.md)

  • 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»

Запускаем простой блог на Wagtail CMS (Django)

Являясь большим фанатом Python и фреймворка Django постоянно искал решение, как сделать разработку новых веб-проектов быстрее и удобнее.

Все, кто знаком с разработкой на Django, знают насколько неудобно строить на нем интуитивно понятную админ.панель. До мегапопулярного WordPress очень далеко, что делает порог вхождения в разработку сайтов выше, чем у PHP-фреймворков и CMS.
После долгого поиска и тестирования различных решений я нашел для себя оптимальный вариант — Wagtail CMS.

Wagtail — это полноценная CMS написанная на Django компанией Torchbox. За что им большое спасибо. Проект с открытым исходным кодом, поддерживается сообществом энтузиастов и выпускается под BSD лицензией.

Вот типичный интерфейс Wagtail:

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

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

Нам понадобится:
1. VPS минимум с 1gb памяти
2. Базовое знакомство с django
3. Навыки в удаленной настройке Ubuntu

Шаг 1:
Вы запустили VPS сервер для разработки. Используйте Putty для удаленного подключения по SSH.
Настройка проводиться на сервере с ОС Ubuntu 14.04.

— Логинимся и начинаем базовую настройку:

sudo apt-get update
sudo apt-get upgrade

Как правило, на сервере уже стоит Python 2 и 3 версии. Проверяем следующими командами:

Python -V
Python3 -V

Для проекта будем использовать Python 3, так как с ним лучше работает Pillow и некоторые библиотеки, которые нам понадобятся.
Если каким-то образом у вас на сервере не оказалось python3, то ставим его командой:

sudo apt-get install python3

Так же нам потребуется python3-setuptools и python3-dev:

sudo apt-get install python3-setuptools
sudo apt-get install python3-dev

Учитывая, что все ставиться на чистую систему поставим PIP (чтобы поставить свежую версию »8. 1.2» лучше делать это через easy_install3):

sudo easy_install3 pip

Проверить версию PIP можно командой:

pip -V

Если версия не самая свежая, то обновляем pip командой:

sudo pip install --upgrade pip

Чтобы в дальнейшем у нас не выпадало различных ошибок при установке Wagtail, сразу ставим все необходимые библиотеки для Pillow:

sudo apt-get install libtiff5-dev libjpeg8-dev zlib1g-dev \
    libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk

Теперь нам потребуется виртуальное окружение для проекта, ставим virtualenv

sudo pip3 install virtualenv

Создаем виртуалку:

virtualenv /opt/myenv

Идем в папку /opt/myenv и запускаем виртуальную среду:

source bin/activate

Вы должны увидеть строку с названием (myenv), означающую, что виртуалка запущена:

(myenv) [email protected]:/opt/myenv$

Шаг 2: Предварительная настройка сделана, переходим к установке Wagtail CMS и настройке базы данных:
1. ставим Wagtail и Gunicorn:

sudo pip3 install wagtail gunicorn

2. Создаем наш будущий блог командой

wagtail start mysite

3. Перейдем в папку блога (mysite) и установим все зависимости для проекта из файла requirements.txt

cd mysite
pip install -r requirements.txt

4. На данном этапе вы можете провести миграции и сразу запустить тестовый сервер вашего будущего блога. Но так как мы планируем сделать все правильно, то наш блог будет работать на PostgreSQL (Для django можно использовать разные базы данных, в том числе MySQL, PostgreSQL, Oracle DB и т.д). Так что проводим настройку базы данных:

sudo apt-get install libpq-dev 
sudo apt-get install postgresql postgresql-contrib 

5. На данный момент у вас уже запущен postgresql на порту 5432. Но чтобы база данных могла взаимодействовать с Wagtail нужно поставить psycopg2:

pip install psycopg2

6. Теперь надо создать саму базу данных для проекта, для этого зайдем под пользователем postgres и проведем некоторые дополнительные настройки:

sudo su – postgres
createdb myblog

7. Так как postgresql создает пользователя не спрашивая вас про пароль, нам надо назначит ему пароль через psql:

psql


Теперь вы можете выполнить команду смены пароля:

\password postgres


Выходим из psql и закрываем сеанс под пользователем postgres:

\q
exit


Проверьте, что вы еще в виртуально среде, если нет то опять активируйте виртуалку.

8. Теперь нам надо настроить Wagtail на взаимодействие c postgresql, для этого идем в base.py:

cd /opt/myenv/mysite/mysite/settings/
nano base.py

Открывается файл настроек, нам нужна запись DATABASES. Удаляем, то что есть и ставим туда следующее:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2', # указываем, что база данных postgresql
'NAME': 'myblog', # Название вашей базы данных
'USER': 'postgres',
'PASSWORD': '******',
'HOST': 'localhost',
'PORT': '',
}
}

Кстати, в wagtail есть русский язык, так что можете заодно в настройках поправить параметр LANGUAGE_CODE.
LANGUAGE_CODE = 'ru-ru'

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

Шаг 3. Первый запуск

Перейдите в корневую папку вашего блога (cd /opt/myenv/mysite/) и начните вашу первую миграцию данных в данном проекте:

python manage.py migrate


Теперь создайте пользователя под которым вы будете заходить в админ.панель Wagtail:

python manage.py createsuperuser


Введите имя (например admin), пароль и почту.

Проверяем, что все работает:

python manage.py runserver 0.0.0.0:8000

Перейдем в браузере по ip адресу вашего VPS на 8000 порт (пример 199.199.32.32:8000) и увидим стартовую страницу wagtail.

Отлично, теперь перейдем в админ панель 199.199.32.32:8000/admin, вводим логин и пароль пользователя которые мы создали ранее и попадаем в удобный интерфейс Wagtail CMS:

Пока что наш сайт ничего не умеет, но скоро мы научим его всему необходимому для блога.

Шаг 4. Настройка стартовой страницы сайта

За главную страницу отвечает приложение «home» которое запускается из коробки. Чтобы стартовая страница была такой как вам надо внесем некоторые изменения в код.

Структура вашего сайта имеет такой вид:

mysite/
home/
migrations/
__init__.py
0001_initial.py
0002_create_homepage.py
templates/
home/
home_page.html
__init__.py
models.py
search/
templates/
search/
search.html
__init__.py
views.py
mysite/
settings/
__init__.py
base.py
dev.py
production.py
static/
css/
mysite.css
js/
mysite.js
templates/
404.html
500.html
base.html
__init__.py
urls.py
wsgi.py
manage.py
requirements.txt

Отредактируйте файл «home/models.py» следующим образом:

from __future__ import unicode_literals
from django.db import models
from wagtail.wagtailcore. models import Page
from wagtail.wagtailcore.fields import RichTextField
from wagtail.wagtailadmin.edit_handlers import FieldPanel
class HomePage(Page):
    body = RichTextField(blank=True)
    content_panels = Page.content_panels + [
        FieldPanel('body', classname="full")
    ]

Также отредактируйте файл home/templates/home/home_page.html:
{% extends "base.html" %}

{% load wagtailcore_tags %}

{% block body_class %} template-homepage {% endblock %}

{% block content %}
{{ page.body | richtext }}
{% endblock %}

Теперь, чтобы новые изменения заработали, проведите миграции:

python manage.py makemigrations
python manage.py migrate

После этого вы можете зайти в админ.панель и отредактировать текст на вашей стартовой странице.
Сохраните изменения и откройте опять ваш сайт. Вы увидите свой текст, примерно такого вида:

Для начала неплохо, но надо добавить разметку и красоту css.
За базовый шаблон отвечает файл «base. html» расположенный в mysite/settings/base.py;
За css отвечает «mysite.css» расположенный в mysite/static/css/mysite.css;
За js отвечает «mysite.js» расположенный в mysite/static/js/mysite.js;

Я поклонник materializecss и для нашего сайта мы возьмем у них шаблон и библиотеки.
Смотреть тут http://materializecss.com/getting-started.html

Вносим следующие изменения в base.html:

{% load static wagtailuserbar %}



  
    
        {% block title %}{% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %}{% endblock %}{% block title_suffix %}{% endblock %}
        {# Global stylesheets #}
        
        {% block extra_css %}
            {# Override this in templates to add extra stylesheets #}
        {% endblock %}
    
  
        {% wagtailuserbar %}
  

{% block content %}{% endblock %}

{# Global javascript #} {% block extra_js %} {# Override this in templates to add extra javascript #} {% endblock %}

Сохраняем и снова открываем наш сайт. Должно получиться, что-то похожее на эту картинку:

Уже лучше. Теперь нам надо создать блог, ради которого все и затевалось.

Шаг 5. Создание блога.

Идем опять в корень нашего проекта и выполняем следующую команду:

python manage.py startapp blog


После этого добавьте новое приложение «blog» в INSTALLED_APPS в mysite/settings/base.py.

Для каждой записи нашего блога будут стандартные поля — заголовок, дата, картинка, введение и основной текст. Чтобы все это работало внесите следующие изменения в «blog/models.py»

from django.db import models
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore.fields import RichTextField
from wagtail.wagtailadmin.edit_handlers import FieldPanel
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailsearch import index
class BlogPage(Page):
    main_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models. SET_NULL,
        related_name='+'
    )
    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'),
        ImageChooserPanel('main_image'),
        FieldPanel('intro'),
        FieldPanel('body'),
    ]

Теперь создайте саму страницу, на которую все это будет выводиться в blog/templates/blog/blog_page.html:

{% extends "base.html" %}

{% load wagtailcore_tags wagtailimages_tags %}

{% block body_class %}template-blogpage{% endblock %}

{% block content %}


{{ page.date }}

{% if page.main_image %}
{% image page.main_image width-500 %}
{% endif %}

{{ page.intro }}

{{ page.body|richtext }}
{% endblock %}

Пока что ничего не работает, поэтому выполняем команды

python manage. py makemigrations
python manage.py migrate

Теперь можно создать первую запись в нашем блоге, через удобный интерфейс wagtail.

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

Выбираем «Blog Page» и заполняем страницу информацией.

Вот пример редактирования страницы блога в админ.панели wagtail:

Довольно удобно и быстро.

Сохраняем запись и открываем в браузере. Первая запись вашего блога готова.

Если хоть кому-то данный пост окажется полезным, то я продолжу публикацию постов на тему wagtail, в которой расскажу подробнее про различные возможности данной CMS.

Для всех заинтересовавшихся Wagtail CMS:
Официальный сайт https://wagtail.io/
Гитхаб проекта https://github.com/torchbox/wagtail/
Документация http://docs.wagtail.io/en/latest/index.html
Демосайт для изучения http://docs.wagtail.io/en/latest/getting_started/demo_site. html
Группа поддержки пользователей https://groups.google.com/forum/#! forum/wagtail

© Habrahabr.ru

Wagtail on Azure Linux VM · Microsoft Azure

Веб-приложение на Python, virtualenv, Wagtail (Django), Gunicorn (WSGI HTTP Server), PostgreSQL (pip psycopg2). С примером веб-приложения на Wagtail можно ознакомиться по ссылке.

Для установки Wagtail CMS на Linux Debian можно воспользоваться данной инструкцией:

  • Обновить зависимости и пакеты:
sudo apt-get update
yes | sudo apt-get dist-upgrade
  • Проверить доступные версии Python и при необходимости установить Python3:
python -V    # проверка версии python
python3 -V    # проверка версии python3
yes | sudo apt-get install python3    # установка python3, если отсутствует
  • Установить дополнительные утилиты python/pip:
yes | sudo apt-get install python3-setuptools python3-dev   # установка easy_install и python3-dev, если отсутствуют
sudo easy_install pip    # установка pip, если отсутствует
pip -V    # просмотр версии pip
sudo pip install --upgrade pip    # обновление pip при необходимости
  • Установить дополнительных библиотек:
yes | sudo apt-get install libtiff5-dev libjpeg9-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8. 6-dev tk8.6-dev python-tk gcc libc-dev
  • Установка и активация виртуального окружения:
sudo pip3 install virtualenv
sudo virtualenv /opt/artenv
cd /opt/artenv
source bin/activate

Должно появится приглашение на подобие следующего:

(myenv) [email protected]:/opt/artenv$
  • Установка Wagtail и Gunicorn:
sudo pip3 install wagtail gunicorn
wagtail start artblog   # создание каркаса нашего приложение
  • Перейти в основную папку проекта /opt/artenv/artblog и установить зависимости:
sudo pip install -r requirements.txt
  • Установить и настроить базу данных проекта (PostgreSQL):
sudo apt-get install libpq-dev postgresql postgresql-contrib
ps -ef | grep postgre    # проверим работу базы данных
# стандартный порт 5432
sudo pip install psycopg2   # установим утилиту для взаимодействия нашего приложения с базой данных
sudo su - postgres    # заходим под стандартным пользователем postgresql
psql    # запускаем консольную утилиту для связи с базой данных
# вводим запрос для создания базы данных и пользователя
CREATE DATABASE <dbname> with encoding='UNICODE';
CREATE USER <username> with password '<dbpassword>';
GRANT ALL PRIVILEGES ON DATABASE <dbname> TO <username>;
# выходим из psql и завершаем сеанс пользователя postgres
\q
exit
# проверяем, что мы всё еще в виртуальной среде
# подключаем наше приложение к базе данных
sudo nano /opt/artenv/artblog/artblog/settings/base. py
# ищем запись DATABASES, и вносим указанные ранее значения
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': '<dbname>',
'USER': '<username>',
'PASSWORD': '<dbpassword>',
'HOST': 'localhost',
'PORT': '',
}
}
# при необходимости в том же файле можно установить русский язык
LANGUAGE_CODE = 'ru-ru'
# после чего закрываем редактор, сохраняя изменения: нажимаем ctrl+x, вводим y, нажимаем enter
  • Изменим первую строку в файле manage.py для того, чтобы она указывала на python в нашем виртуальном окружении и выполним миграцию:
sudo nano /opt/artenv/artblog/manage.py   # откроем в редакторе nano необходимый файл
# установим в первой строке запись, указывающую на python в виртуальном окружении, например, так #!/opt/artenv/bin/python
cd /opt/artenv/artblog    # перейдем в папку с нашим проектом
sudo python manage.py migrate   # выполним миграцию
# если появилась ошибка, что не установлен модуль django, то пробуем следующую команду, связанную с вашей версией python, например, в случае python3. 6:
sudo python3.6 manage.py migrate  # далее используем вместо python нашу версию python3.x
  • Создаем профиль администратора и запускаем наше приложение:
cd /opt/artenv/artblog    # переходим в директорию нашего проекта
sudo python manage.py createsuperuser   # создаем администратора нашего приложения, вводим логин, email, пароль
deactivate    # отключим наше виртуальное окружение
python3.x manage.py runserver 0.0.0.0:8000    # запускаем приложение на 8000 порту для проверки и отладки
# если есть запрос на измнение ALLOWED_HOSTS, то делаем следующее
# добавляем наш хост в соответствующий пункт в settings.py
sudo nano /usr/local/lib/python3.x/dist-packages/django/conf/global_settings.py  # в качестве примера
# добавляем наши хосты в ALLOWED_HOSTS = ['localhost','127.0.0.1','domain.com'] и т.п.
python3.x manage.py runserver 0.0.0.0:80    # запускаем наше приложение на 80-м порту
  • Если мы работаем на сервере, например, через ssh клиент (PuTTY или другой), то возможно нам понадобится запустить приложение через nohup с установкой процесса в качестве фонового (в конце команды &), чтобы веб-приложение продолжило свою работу после закрытия ssh-клиента:
cd /opt/artenv/artblog    # переходим в директорию нашего проекта
nohup sudo python3. x manage.py runserver 0.0.0.0:80 & # запускаем приложение в виде фонового процесса (задания)
jobs -p   # используем для просмотра наших заданий и их PID
ps -ef    # также можно просмотреть все процессы (если мы перелогинились до этого)
sudo kill PID   # для отключения нашего приложения используем данную команду  (PID находим при помощи команд, указанных выше
  • Для удобной работы в консоли Linux Debian возможно понадобятся следующие команды:
history   # история введенных нами команд (зависит от пользователя, под которым мы находимся)
!x    # x - номер команды из истории, позволяет выполнить определенную команду, введенную ранее
!!    # выполнить предыдущую команду
sudo !!   # выполнить предыдущую команду от имени администратора

Полезные ссылки:

  • Python3 | Docs
  • PostgreSQL | Docs
  • Python Package Index | pip
  • Django | Docs
  • Wagtail CMS | Docs
  • Markdown 2.6.11 | Python implementation of Markdown
  • Django-el-pagination | Django pagination tools
  • Django-taggit | Reusable Django application for simple tagging
  • GitHub Guides | Mastering Markdown

Wagtail — лучшая CMS на python в нашей галактике

Wagtail — это популярная CMS на основе Django. Django предоставляет огромные возможности — с его помощью созданы не только сайты, но и, например, популярный веб-сервис комментариев disqus, веб-сервис хранения git репозиториев bitbucket и многое другое. На django можно создать все что угодно.

Wagtail предоставляет пользователям новые, построенные с помощью Django, абстракции. Можно провести следующую аналогию: если django предоставляет вам молоток, пилу, гвозди и деревянные доски, то Wagtail сразу дает готовые столы, стулья и прочую мебель сколоченную из того, что есть в django. Для своего проекта вы конечно можете собрать необходимую мебель сами, используя только django, но во многих случаях будет гораздо проще и быстрее взять что-то готовое из Wagtail.

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

Кто стоит за Wagtail

История Wagtail начинается с 2014 года, когда британская компания Torchbox разработала сайт для Королевского колледжа искусств. Если пройти на сайт с установленным расширением Wappalyzer, можно убедиться, что заведение по сей день использует Wagtail. Изначально планировалось, что Wagtail станет open source проектом, каким он и остается по сей день. Torchbox ведет разработку своей CMS по настоящее время и предлагает платные услуги по поддержанию и созданию сайтов на Wagtail.

Скрин Wappalyzer с сайта Королевского колледжа искусств

Сейчас Wagtail используют такие организации как Mozilla Foundation, Nasa, Британский минздрав, Калтех, google в официальном туториале для своей облачной платформы устанавливает Wagtail на App Engine и многие другие. Если пройти по ссылкам из предыдущего предложения, опять же можно проверить Wappalyzer или любым другим инструментом, что сайты действительно используют Wagtail CMS.

На момент написания статьи, гитхаб репозиторий Wagtail имеет чуть больше 11 тысяч звезд и в разработке успело поучаствовать 496 человек, репозиторий очень активный. Для сравнения репозиторий Django CMS — еще одной CMS написанной с использованием Django имеет почти 8. 5 тыс звезд и 440 участников а репозиторий WordPress имеет 15.5 тыс звезд и 63 участника. Команда Wagtail регулярно выпускает новые версии по заданному расписанию, например следующая версия 2.15 ожидается 1 ноября 2021.

Основные идеи Wagtail

Разработчик подошли к созданию Wagtail с некоторыми ключевыми идеями в голове, которые они назвали «Дзен Wagtail» по аналогии с «Дзен Питона». Вот мой пересказ Дзена Wagtail:

  1. Во-первых на Wagtail нельзя создать сайт без программирования. Если вам нужно что-то простое и быстро, лучше не тратить время на Wagtail — не забивайте гвозди микроскопом, установите готовую CMS и настройте ее мышкой. Wagtail актуален для создания проектов, которые могут иметь много абстракций, сложные зависимости между ними, несколько пользователей с различными ролями и т.п.

  2. Создание и поддержание сайта это общая работа нескольких людей. В Wagtail удобно и необходимо разделять обязанности разработчика, дизайнера, администратора сайта и автора, подготавливающего материалы на сайт.

  3. Авторы материалов для сайта должны иметь удобный и быстрый способ добавлять свои идеи/мысли/новости на сайт. Авторы не должны писать код на css или html. Дизайн и контент должен быть отдельно.

  4. Лучшим инструментом для разработчика сайта является язык программирования. Wagtail предполагает, что разработчик сам напишет какие-то нужные для сайта дополнения.

Некоторые возможности Wagtail

Страницы

Одной из важных абстракций, предоставляемых Wagtail является страница — Page. Страница предоставляет кучу полезного функционала из коробки — черновик, можно сохранять все версии страницы в базе данных (по мере их редактирования), дата публикации, дата изменения и много другое. Можно создать сколько угодно отдельных типов страниц, наследуя от Page. Например, отдельные классы для страниц блога, новостей, сотрудников и т.п. В каждом из таких типов можно задать, какой контент должен быть на странице — например, просто текст, изображения, видео. .. или все вместе.

Страницы организованы в виде графа — каждая страница может быть дочерней или родительской к другой странице, что можно указать в коде. Например, можно сделать страницу «Люди» /people/, в которой будет написано какой классный коллектив работает в компании. Дочерними страницами для страницы «Люди» можно сделать индивидуальные страницы сотрудников например /people/ceo, /people/cto… Все это делается в Wagtail в пару строк кода.

Управлять содержимым страниц можно только из админ панели wagtail.

Пример редактирования страницы Wagtail

StreamField

Еще одним важным инструментом является StreamField. StreamField позволяет создать контент из блоков — также как, например, пишутся статьи на хабре, vc, vk — короче это уже стало стандартом. Ваша страница это просто набор блоков — блок изображение с подписью, блок текст, блок видео… В Wagtail можно создать собственные виды блоков. Например, можно создать блок для карусели картинок, как в vk.

Картинки

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

API

Нужна безголовая (Headless) CMS? Нет проблем! REST API в Wagtail доступно практически по умолчанию (пара строк кода, скопированных из документации). Если нужно что-то круче — graphql — есть пост от Torchbox с кодом и инструкциями какие пакеты нужно установить.

Другие возможности

Админ интерфейс Wagtail легко настраивается — можно добавить новые элементы меню, поменять логотип, поменять цвета. Админ интерфейс адаптивный — нормально выглядит на смартфоне, компьютере, планшете и т.п. Еще в Wagtail легко создавать сложные таблицы, из коробки доступен ставший стандартом elasticsearch, есть разделение доступа между пользователями а также группами пользователей и многое другое.

Освоить Wagtail

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

У Wagtail отличная документация, хотя на ответах со stackoverflow я не раз сталкивался с примерами недокументированных возможностей. Кстати на stackoverflow у Wagtail отличная поддержка. На один мой вопрос даже ответил сотрудник Torchbox Matt Westcott — один из ключевых разработчиков Wagtail.

Отличные уроки по Wagtail на английском подготовил Kalob Taulien:

Доступных материалов на русском, к сожалению, не так много. Все, что я смог найти это:

На хабре есть один старенький туториал из трех частей по запуску CMS (раз, два, три). Возможно, что-то оттуда уже устарело, но, попробовать запустить можно.

Туториал https://python-scripts.com/wagtail — надеюсь, автор написал тот пост не ради рефералки на хостинг.

Пользуясь случаем, не могу не прорекламировать блестящие уроки на youtube, отснятые моей скромной персоной:

Заключение

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

Установка и настройка Django CMS на сервере Debian 7 и Ubuntu 13