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

в 7:18, , рубрики: cms, django, wagtail, Разработка веб-сайтов, сайтостроение

С момента написания первой части про Wagtail CMS уже вышла версия 1.6.3 — самое время продолжить наш путь по созданию простого блога.

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

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

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

В Wagtail логика работы с изображениями довольно простая. За управление изображениями в models.py отвечает строка:

from wagtail.wagtailimages.edit_handlers import ImageChooserPanel

В админ.панели добавление изображения отображается так:

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

Добавление изображения на страницу определяется следующим блоком:


class BlogPage(Page):
     main_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
        )

     content_panels = Page.content_panels + [
        FieldPanel('date'),
        ImageChooserPanel('main_image'), #добавляет пункт выбора изображения в админ.панели страницы
        FieldPanel('intro'),
        FieldPanel('body'),
        ]

На страницу изображение выводиться благодаря коду в blog/templates/blog/blog_page.html:


{% load wagtailcore_tags wagtailimages_tags %}

{% if page.main_image %}
    {% image page.main_image width-500 %} #вывод изображения с шириной 500px
{% endif %}

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

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

Подробнее про работу с изображениями в wagtail можно прочитать в документации.

Теперь пора создать подраздел, в котором будут все наши посты blogindexpage.html. Добавим в blog/models.py следующий код:


class BlogIndexPage(Page):
    intro = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('intro', classname="full")
    ]

В папке с шаблонами создадим новый — blog/templates/blog/blog_index_page.html
и добавим в него следующий код:


{% extends "base.html" %}

{% load wagtailcore_tags %}

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

{% block content %}
    <h1>{{ page.title }}</h1>

    <div class="intro">{{ page.intro|richtext }}</div>
{% endblock %}

Выполните миграцию — python manage.py makemigrations и python manage.py migrate

Теперь в админке появился шаблон страницы blogindexpage, в котором есть только текстовый блок intro. Мы добавим страницу с этим шаблоном, как дочернюю главной страницы. Ранее созданную страницу с первым постом надо будет переместить в дочерний раздел страницы blogindexpage

Пример структуры сайта: Главная страница → подраздел блоги → страница поста

Нам необходимо вывести на страницу подраздела «блоги» все посты, но если их будет много, то страница может быть очень большой. Поэтому мы будем выводить только 9 последних постов, а для просмотра остальных постов использовать пагинацию django из модуля django.core.paginator.

Внесем изменения в models.py:


from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

class BlogIndexPage(Page):
    intro = RichTextField(blank=True)

    @property
    def blogs(self):
        # Получить список страниц блога, которые являются потомками этой страницы
        blogs = BlogPage.objects.live().descendant_of(self)

        # Сортировать по дате
        blogs = blogs.order_by('-date')

        return blogs

    def get_context(self, request):
        blogs = self.blogs
        # Пагинация
        page = request.GET.get('page')
        paginator = Paginator(blogs, 9)  # Показывать 9 постов
        try:
            blogs = paginator.page(page)
        except PageNotAnInteger:
            blogs = paginator.page(1)
        except EmptyPage:
            blogs = paginator.page(paginator.num_pages)

        # Обновить контекст шаблона
        context = super(BlogIndexPage, self).get_context(request)
        context['blogs'] = blogs
        return context

    content_panels = Page.content_panels + [
        FieldPanel('intro', classname="full")
    ]

Теперь надо создать шаблон для карточек постов. В папке blog/templates/blog создайте папку includes, в которой надо создать html шаблон blog_list_item.html — Пример пути: /blog/templates/blog/includes/blog_list_item.html

Со следующим содержанием:


{% load static wagtailcore_tags wagtailimages_tags %}
# Индивидуальная карточка поста для раздела блоги
<div class="row media-object">
    {% if blog.main_image %}
        <div class="media-object-section">
        {% image blog.main_image fill-200x200 as img %} #вывести изображение размером 200px на 200px
            <a href="{% pageurl blog %}"><img class="image_tmb" src="{{ img.url }}" /></a>
        </div>
    {% endif %}
        <div class="media-object-section">
            <p class="data"><strong>{{ blog.date }}</strong></p>
            <a href="{% pageurl blog %}"><span>{{ blog.title }}</span></a>
            <p>{{ blog.intro }}</p>
        </div>
 </div>

В шаблон blog/templates/blog/blog_index_page.html требуется добавить следующий код в блоке content:


        {% for blog in blogs %}
            {% include "blog/includes/blog_list_item.html" %}
        {% empty %}
            Новых постов нет
        {% endfor %}

Для реализации пагинации добавляем следующий блок на эту же страницу:


# Пагинация - используется django.core.paginator #
    <div class="row">
        <ul class="pagination center">
            {% if blogs.has_previous %}
                <li class="waves-effect"><a href="?page={{ blogs.previous_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&{{ key }}={{ value }}{% endifnotequal %}{% endfor %}""><i class="material-icons">chevron_left</i></a></li>
            {% endif %}
            {% if blogs.has_previous %}
                <li class="waves-effect"><a href="?page={{ blogs.previous_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&{{ key }}={{ value }}{% endifnotequal %}{% endfor %}"">{{ blogs.previous_page_number }}</a></li>
            {% endif %}

            {% if blogs.has_next %}
                <li class="active"><a href="">{{ blogs.number }}</a></li>
                <li class="waves-effect"><a href="?page={{ blogs.next_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">{{ blogs.next_page_number }}</a></li>
            {% else %}
                <li class="active"><a href="">{{ blogs.number }}</a></li>
            {% endif %}
            {% if blogs.has_next %}
                <li class="waves-effect"><a href="?page={{ blogs.next_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&{{ key }}={{ value }}{% endifnotequal %}{% endfor %}"><i class="material-icons">chevron_right</i></a></li>
            {% endif %}
        </ul>
    </div>

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

Все хорошо, осталось теперь добавить меню, для более легкого перехода между страницами нашего блога.

Меню в Wagtail работает из коробки, за него отвечает параметр «Показывать в меню:» в разделе «Продвижение», при редактировании любой страницы.
Запускаем простой блог на Wagtail CMS (Django) — часть 2 - 4
Есть подробная статья, как реализовать меню в Wagtail.

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

Добавим в base.html:


{% load home_tags %} #home_tags.py мы создадим ниже
{% block menu %}
    {% get_site_root as site_root %}
    {% top_menu parent=site_root calling_page=self %}
{% endblock %}

Создаем в папке проекта /home/ папку /templatestag/ с файлом home_tags.py, со следующим содержанием:


from django import template
from home.models import *
from blog.models import *

register = template.Library()

@register.assignment_tag(takes_context=True)
def get_site_root(context):
    return context['request'].site.root_page

def has_menu_children(page):
    return page.get_children().live().in_menu().exists()

@register.inclusion_tag('home/tags/top_menu.html', takes_context=True)
def top_menu(context, parent, calling_page=None):
    menuitems = parent.get_children().live().in_menu()
    for menuitem in menuitems:
        menuitem.show_dropdown = has_menu_children(menuitem)
        menuitem.active = (calling_page.url.startswith(menuitem.url)
                           if calling_page else False)
    return {
        'calling_page': calling_page,
        'menuitems': menuitems,
        'request': context['request'],
    }

@register.inclusion_tag('home/tags/top_menu_children.html', takes_context=True)
def top_menu_children(context, parent):
    menuitems_children = parent.get_children()
    menuitems_children = menuitems_children.live().in_menu()
    return {
        'parent': parent,
        'menuitems_children': menuitems_children,
        'request': context['request'],
    }

Чтобы как-то стилизовать меню создаем два шаблона: top_menu.html и top_menu_children.html в папке /home/templates/home/tags/

Пишем в top_menu.html:


{% load home_tags wagtailcore_tags %}
{% get_site_root as site_root %}
<li class="nav-item"><a href="{% pageurl site_root %}" class="menu_link_main" title="{{ site_root.title }}">Главная</a></li>
{% for menuitem in menuitems %}
<li class="nav-item">
    {% if menuitem.show_dropdown %}
    <a data-toggle="dropdown" class="menu_link_item" href="#">{{ menuitem.title }}</a>
    {% top_menu_children parent=menuitem %}
    {% else %}
    <a href="{% pageurl menuitem %}">{{ menuitem.title }}</a>
    {% endif %}
</li>
{% endfor %}

Пишем в top_menu_children.html:


{% load home_tags wagtailcore_tags %}
<ul class="dropdown-menu">
    <li><a href="{% pageurl parent %}">{{ parent.title }}</a></li>
    {% for child in menuitems_children %}
        <li><a href="{% pageurl child %}">{{ child.title }}</a></li>
    {% endfor %}
</ul>

Обратите внимание — в блоке {% load %} надо обязательно сделать вызов вашего home_tags, иначе ничего не заработает. И, конечно, не забудьте прописать все стили меню в css файл.

Нам осталось сделать слайдер для главной страницы, чтобы она стала веселее и добавить вывод трех последних постов из раздела блоги. Для этого нам понадобиться home_tags, созданный ранее.

За работу слайдера будет отвечать следующий код:

В папке /home/templates/home/ создайте папку /includes/, в ней создайте файл slider.html:


{% load wagtailimages_tags wagtailembeds_tags %}

{% if slider_items %}
<div class="slider" id="homeSlider">
    <ul class="slides">
        {% for slider_item in slider_items %}
      <li>
          {% image slider_item.image width-1200 as imageslider %}
            <img src="{{ imageslider.url }}" class='home_slider' alt="{{ imageslider.alt }}" />
      </li>
        {% endfor %}
    </ul>
  </div>
{% endif %}

Для wagtail можно использовать любой слайдер, например Nivo Slider, WOW или написать свой. В этом уроке я использую слайдер, который идет в комплекте с materializecss.com/media.html. Выбор слайдера для сайта дело вкуса и привычки. Чтобы слайдер работал, не забывайте добавлять все необходимые зависимости (css, js).

Далее правим home/models.py, добавляя следующий код:


from django.db import models

from wagtail.wagtailcore.models import Page, Orderable
from wagtail.wagtailcore.fields import RichTextField
from wagtail.wagtailadmin.edit_handlers import (FieldPanel,
                                                InlinePanel,
                                                MultiFieldPanel,
                                                PageChooserPanel)
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtailsearch import index

from modelcluster.fields import ParentalKey
from wagtail.wagtailsnippets.models import register_snippet


class HomePage(Page):
    body = RichTextField(blank=True)
    disc_t = RichTextField(blank=True)

    search_fields = Page.search_fields + [
        index.SearchField('body'),
        index.SearchField('disc_t'),
    ]

    content_panels = Page.content_panels + [
        InlinePanel('slider_items', label="Слайдер"), #добавляем блок управления слайдером
        FieldPanel('body', classname="full"),
        FieldPanel('disc_t', classname="full"),
    ]


class LinkFields(models.Model):
    link_external = models.URLField("URL", blank=True)
    link_page = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        related_name='+'
    )

    @property
    def link(self):
        if self.link_page:
            return self.link_page.url
        else:
            return self.link_external

    panels = [
        FieldPanel('link_external'),
        PageChooserPanel('link_page'),
    ]

    class Meta:
        abstract = True

class SliderItem(LinkFields):
    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    panels = [
        ImageChooserPanel('image')
    ]

    class Meta:
        abstract = True

class HomePageSliderItem(Orderable, SliderItem):
    page = ParentalKey('HomePage', related_name='slider_items')

Обновляемся, делаем миграции, перезапускаем сервер и открыв редактирование главной страницы увидим новый блок со слайдером.

Для отображения трех последних постов на главной странице, добавим следующий код:
В /home/templatestag/home_tags.py добавить:


@register.inclusion_tag(
    'home/tags/blog_listing_homepage.html',
    takes_context=True
)
def blog_listing_homepage(context, count=3): #count=3 выводит 3 последних поста
    blogs = BlogPage.objects.live().order_by('-date') #фильтр по дате от последнего поста
    return {
        'blogs': blogs[:count].select_related('main_image'),
        'request': context['request'],
    }

В папку /home/templates/home/tags/ добавить шаблон blog_listing_homepage.html, с содержанием:


{% if blogs %}
      {% for blog in blogs %}
        {% include "blog/includes/blog_list_item.html" %}
      {% endfor %}
{% endif %}

Чтобы вывести посты на главную страницу добавим в home_page.html после слайдера:
{% blog_listing_homepage %}

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

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

Добавим хлебные крошки, для лучшей навигации по блогу и наш проект готов для старта.
Реализуется это следующим кодом в шаблонах blog_page.html и blog_index_page.html:


         {% if self.get_ancestors|length > 1 %}
            <div class="breadcrumb_page">
                {% for page in self.get_ancestors %}
                    {% if page.is_root == False %}
                    <a class="breadcrumb" href='{% pageurl page %}'>{{ page.title }}</a>
                    {% endif %}
                {% endfor %}
            <span class="breadcrumb_active">{{ self.title }}</span>
            </div>
         {% endif %}

Оформляем блок стилями и получаем удобную навигацию в дополнение к меню сайта.

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

Надеюсь, что этот базовый урок помог кому-то начать знакомство с Wagtail CMS. В следующей части напишу про использование StreamField.

Автор: alexhouse

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js