GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Python 3
  • Скачать Python
  • IDE
  • Скачать PyCharm
  • Django
  • Tkinter
  • Книги

This view

  • Original page
  • New address

GeezerVison is showing python-scripts.com — Создание сайта на 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 (это самая актуальная версия на момент написания статьи, советуем всегда выбирает самые свежие версии).

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

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

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

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

Shell
1
2
$ apt update
$ apt upgrade

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

Shell
1
$ apt install python3

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

Shell
1
2
3
$ 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:

Shell
1
$ apt install python3-pip

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

Shell
1
2
3
cd /home
mkdir web
cd web

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

Shell
1
2
python3 -m venv venv
source venv/bin/activate

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

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

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

Shell
1
$ pip install wagtail

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

Shell
1
2
$ wagtail start mysite
$ cd mysite

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

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

Shell
1
$ pip install -r requirements.txt

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

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

Python
1
$ python manage.py migrate

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

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

Shell
1
$ python manage.py createsuperuser

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

Shell
1
$ python manage.py runserver

Если все сработало, то 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 в модель:

Python
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:

XHTML
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 (CMS на Django)

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

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

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

XHTML
1
2
{% load wagtailcore_tags %}
{{ page.body|richtext }}

Генерирует:

XHTML
1
2
3
4
5
<div class="rich-text">
    <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
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. Создайте этот файл со следующим содержимым:

XHTML
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 %}
    <h1>{{ page.title }}</h1>
 
    <div class="intro">{{ page.intro|richtext }}</div>
 
    {% for post in page.get_children %}
        <h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
        {{ post.specific.intro }}
        {{ post.specific.body|richtext }}
    {% endfor %}
 
{% endblock %}

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

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

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

Python
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:

XHTML
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 %}
    <h1>{{ page.title }}</h1>
    <p class="meta">{{ page.date }}</p>
 
    <div class="intro">{{ page.intro }}</div>
 
    {{ page.body|richtext }}
 
    <p><a href="{{ page.get_parent.url }}">Return to blog</a></p>
 
{% endblock %}

Обратите внимание на то, что мы использовали метод 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 изнутри:

XHTML
1
2
3
4
5
{% for post in page.get_children %}
    <h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
    {{ post.specific.intro }}
    {{ post.specific.body|richtext }}
{% endfor %}

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

Python
1
class BlogPage(Page):

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

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

Python
1
2
3
4
5
6
7
{% for post in page.get_children %}
    {% with post=post.specific %}
        <h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
        <p>{{ post.intro }}</p>
        {{ post.body|richtext }}
    {% endwith %}
{% endfor %}

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

Python
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
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
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.

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

XHTML
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 %}
    <h1>{{ page.title }}</h1>
    <p class="meta">{{ page.date }}</p>
 
    <div class="intro">{{ page.intro }}</div>
 
    {{ page.body|richtext }}
 
    {% for item in page.gallery_images.all %}
        <div style="float: left; margin: 10px">
            {% image item.image fill-320x240 %}
            <p>{{ item.caption }}</p>
        </div>
    {% endfor %}
 
    <p><a href="{{ page.get_parent.url }}">Return to blog</a></p>
 
{% endblock %}

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

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

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

Python
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 для внесения главного изображения в анонс рядом с каждым изображением:

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

Метки постов

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

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

Python
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, и вы сможете отмечать посты:

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

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

XHTML
1
2
3
4
5
6
7
8
{% if page.tags.all.count %}
    <div class="tags">
        <h3>Tags</h3>
        {% for tag in page.tags.all %}
            <a href="{% slugurl 'tags' %}?tag={{ tag }}"><button type="button">{{ tag }}</button></a>
        {% endfor %}
    </div>
{% endif %}

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

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

Python
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:

XHTML
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 %}
        <h4>Showing pages tagged "{{ request.GET.tag }}"</h4>
    {% endif %}
 
    {% for blogpage in blogpages %}
 
          <p>
              <strong><a href="{% pageurl blogpage %}">{{ blogpage.title }}</a></strong><br />
              <small>Revised: {{ blogpage.latest_revision_created_at }}</small><br />
              {% if blogpage.author %}
                <p>By {{ blogpage.author.profile }}</p>
              {% endif %}
          </p>
 
    {% empty %}
        No pages found with that tag.
    {% endfor %}
 
{% endblock %}

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

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

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

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

Категории

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

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

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

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

XHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<h1>{{ page.title }}</h1>
<p class="meta">{{ page.date }}</p>
 
{% with categories=page.categories.all %}
    {% if categories %}
        <h3>Posted in:</h3>
        <ul>
            {% for category in categories %}
                <li style="display: inline">
                    {% image category.icon fill-32x32 style="vertical-align: middle" %}
                    {{ category.name }}
                </li>
            {% endfor %}
        </ul>
    {% endif %}
{% endwith %}

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

Vasile Buldumac

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

E-mail: [email protected]

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

  • 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.