[PYTHON] Wagtail Recommendations (2) Let's define, edit, and display a page

Introduction

From this time, I will divide it into several times and try to actually build a simple CMS with Wagtail. As a goal, I imagined a site that hosts pages for multiple research projects and research groups.

Let's name the top page of each research project or research group TopPage. Below that, pages such as project outline explanations and member introductions will be placed, but the pages used for them will be called Plain Page. In addition, let's prepare a blog post-like page PostPage and a pageListPage that displays a list of related blog posts together.

For the time being, leave the HomePage provided by default as it is, so that multiple TopPages can be hung under it. Then, under TopPage, PlainPage or ListPage can be hung, and under ListPage, another ListPage or PostPage can be hung.

This time, let's take a look at the general flow from actually defining a page, editing it, and displaying it, using TopPage as the subject.

Page definition

The HomePage, TopPage, PlainPage, ListPage, PostPage, etc. introduced above represent the types of pages managed by the CMS. Wagtail defines these pages as subclasses that inherit from the Page class.

The definition of HomePage prepared by default is described in home/models.py as follows.

from wagtail.core.models import Page

class HomePage(Page):
    pass

That is, HomePage has nothing added to the Page class by default.

When you log in to the Wagtail management site as a superuser, you should see only one page with the title "Welcome to your new Wagtail site!" Registered. This corresponds to an instance of the HomePage class. When you open the edit screen of this page, there are three tabs, CONTENT, PROMOTE, and SETTINGS, and you can add some information from each tab. These pieces of information are examples of information that the Page class can hold.

When defining your own page, by inheriting the Page class, you will add information (fields and methods) other than those originally provided in the Page class. There are two things to consider at this time.

――What information should the editor enter from the management site for that page? --What information should be passed to the template to display (render) the page?

The answers to these items do not always match. This is because some of the latter items may be automatically acquired by another method even if the editor does not input them. Here, first, after adding the information corresponding to the former item as a field to the Page class, let's expand the edit screen so that they can be input from the management site.

The definition of the self-made page is described in the model module in the cms application added last time, that is, in cms/models.py. Here, the TopPage class is defined as shown below.

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.images.edit_handlers import ImageChooserPanel

class TopPage(Page):
    cover_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    intro = models.CharField(max_length=255)
    main_body = RichTextField(blank=True)
    side_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    side_title = models.CharField(blank=True, max_length=255)
    side_body = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        ImageChooserPanel('cover_image'),
        FieldPanel('intro'),
        FieldPanel('main_body', classname="full"),
        ImageChooserPanel('side_image'),
        FieldPanel('side_title'),
        FieldPanel('side_body', classname="full"),
    ]

You can see that intro (information in the introductory text of the page) and side_title (sidebar title) have been added as Django's models.CharField. In addition, main_body (page body) and side_body (sidebar body) have been added using Wagtail's unique field RichTextFIeld. RichTextFIeld is for passing a document input from the edit screen in rich text format to a template with an appropriate html tag, and can be said to be an indispensable field for CMS.

You can also see how to add image information by looking at cover_image and side_image. Specifically, you can use Django's models.ForeignKey to associate it with the wagtailimages.Image class.

Finally, you can see that the panel information about the newly added field has been added to the list of content_panels. This specifies the format of the input form for inputting information about those fields from the CONTENT tab of the page edit screen of the management site. ImageChooserPanel is specified for images, and FieldPanel is specified for others (for the time being, if you specify FieldPanel for other than images, an appropriate input form will be prepared according to the field type. You should think about it).

Edit page

Next, let's edit the page defined above on the page edit screen of the management site. By doing so, you can also see how the specification added to content_panels is reflected on the edit screen (for example, check how the edit screen changes if you remove the specification ofclass name = "full". You should try it).

Specifically, select "Welcome to your new Wagtail site!" From the page list, click ADD CHILD PAGE instead of EDIT, and select TopPage to edit the newTopPage instance. Opens. The editing itself is intuitive, so no explanation is necessary. Let's freely add information and create a sample instance.

The Slug on the PROMOTE tab contains an automatically generated character string, but if you change this, you can change the URL (at the end) when this page is published.

View page

Now you have defined your own TopPage class and created an instance of it. Next, let's display this on a browser. The URL of the page is determined based on the Slag string above.

In a normal Django application, when you access a given address, the view function associated with it is called in urls.py. Then, the general flow is that the view function generates an appropriate context and incorporates it into the template to render the page. Therefore, in order to be able to display the page, it was necessary to add the routing specification to urls.py and prepare the view function and template.

On the other hand, in Wagtail, routing is done automatically, so there is no need to edit urls.py, and in standard usage, it is not necessary to prepare something like a view function. If only the template is prepared, the page will be rendered automatically based on the instance information.

So, let's prepare a template first. Templates used by pages of the TopPage class defined in cms/models.py are named templates/cms/top_page.html by default (note that the Pascal case in the class name is the snake case in the template). Try). Normally, you can just use the default name.

Since it is difficult to create templates/cms/top_page.html from scratch, we will inherit from templates/base.html provided by Wagtail by default. Templates/base.html contains blocks called extra_css for adding style files, content for adding content, and extra_js for adding javaScript, so use those blocks.

In order to improve the perspective of template inheritance, we first created templates/cms/base.html as follows.

{% extends 'base.html' %}
{% load static %}

{% block extra_css %}
    <link
    rel="stylesheet"
    href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
    integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
    crossorigin="anonymous"
    />
    <link rel="stylesheet" type="text/css" href="{% static 'css/cms/base.css' %}">
{% endblock %}

{% block content %}
    <div id="nav_bar">{% block nav_bar %}{% endblock %}</div>
    <div id="header">{% block header %}{% endblock %}</div>
    <div id="main">{% block main %}{% endblock %}</div>
    <div id="footer">{% block footer %}{% endblock %}</div>
{% endblock %}

{% block extra_js %}
    <script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
    <script
    src="https://unpkg.com/react-bootstrap@next/dist/react-bootstrap.min.js"
    crossorigin></script>
{% endblock %}

In the extra_css block, css for Bootstarap and self-made css/cms/base.css are read. In the content block, the contents of the <body> tag are further subdivided into four <div> with ids nav_bar, header, main, and footer. Also, in the extra_js block, the scripts for React and React Bootstrap are loaded (I use React Bootstrap because I want to use React in other places. If I just use Bootstrap, jQuery is normal. Should be taken in).

The description of my own css was kept to a minimum. The contents of css/cms/base.css are as follows.

body {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  padding: 50px 0 50px 0;
}

.jumbotron {
  height: 400px;
  color: #ffffff;
  background-size: cover; 
  background-position: center center;
  background-color: rgba(0, 0, 0, 0.5); 
  background-blend-mode: multiply;
}

#main > .container {
  max-width: 992px;
}

#footer {
  font-size: 90%;
}

/*The following description is a specification to make the embedded media responsive.*/

.rich-text img {
    max-width: 100%;
    height: auto;
}

.responsive-object {
    position: relative;
}

.responsive-object iframe,
.responsive-object object,
.responsive-object embed {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

The second half is a specification for making images and other embedded media responsive (see here for details). In addition, add the following description to config/settings/base.py.

WAGTAILEMBEDS_RESPONSIVE_HTML = True

Then, based on these, templates/cms/top_page.html was created as shown below.

{% extends 'cms/base.html' %}
{% load static wagtailcore_tags wagtailimages_tags %}

{% block nav_bar %}
    <nav class="navbar fixed-top navbar-dark bg-dark mb-0">
        <span class="navbar-text mx-auto">
            Say something at the begining.
        </span>
    </nav>
{% endblock %}

{% block header %}
    {% image page.cover_image fill-1000x400 as my_image %}
    <div class="jumbotron jumbotron-fluid" style="background-image: url('{{ my_image.url }}');">
        <div class="container">
            <h1 class="display-4">{{ page.title }}</h1>
            <p class="lead">{{ page.intro }}</p>
        </div>
    </div>
{% endblock %}

{% block main %}
    <div class="container">
        <div class="row">
            <div class="col-md-8">
                {% block main_body %}
                    <div class="rich-text my-5">
                        {{ page.main_body|richtext }}
                    </div>
                {% endblock %}
            </div>
            <div class="col-md-4">
                {% block side_bar %}
                    <div class="card my-4">
                        <h4 class="card-header">{{ page.side_title}}</h4>
                        <div class="card-body">
                            {% image page.side_image fill-200x200 class="img-fluid rounded-circle d-block mx-auto" alt="" %}
                        </div>
                        <div class="card-body">
                            <div class="rich-text">
                                {{ page.side_body|richtext }}
                            </div>
                        </div>
                    </div>
                {% endblock %}
            </div>
        </div>
    </div>
{% endblock %}

{% block footer %}
    <nav class="navbar navbar-dark bg-dark fixed-bottom">
        <span class="navbar-text mx-auto py-0">
            Say something at the end.
        </span>
    </nav>
{% endblock %}

{% block extra_js %}
    {{ block.super }}
{% endblock %}

By loading wagtailcore_tags and wagtailimages_tags, you can use the template tags provided by Wagtail (image tag and richtext filter in the above example). Also, it can be seen that the field information of the page instance can be accessed with page.field name. Since the html tag is escaped in the document stored in the RichText field, it is restored through the richtext filter. Also, by enclosing that part in <div class =" rich-text ">, the following specifications described in css/cms/base.css are effective (for details, here). See).

.rich-text img {
    max-width: 100%;
    height: auto;
}

Here, we will not go into how to handle images using the image tag (but see here for details). The nav_bar block and footer block are dummies. Jumbotron with the image specified by cover_image as the background is displayed in the header block. Bootstrap card is used for the sidebar.

in conclusion

This time, I briefly introduced the flow of page definition, editing, and display in Wagtail. I think it's a lot easier than creating a similar page in plain Django. Personally, I also like the ease of use of the management site. Adjusting the appearance can be a bit tedious, but you can also use the publicly available Bootstrap template (see This Tutorial for details).

From the next time onward, I would like to touch on detailed topics little by little.

Link

-Wagtail Recommendation (1) A story about choosing Wagtail by comparing and examining Django-based CMS -Wagtail Recommendation (3) Understand and use the tree structure of the page -Wagtail Recommendation (4) Let's pass context to template -Wagtail Recommendation (5) Let's add your own block to StreamField -Wagtail Recommendation (6) Add Categories and Tags

Recommended Posts

Wagtail Recommendations (2) Let's define, edit, and display a page
Get a Python web page, character encode it, and display it
Wagtail Recommendations (6) Add categories and tags
Django-TodoList② ~ Let's display the details page ~
Let's write a Python program and run it
[Wagtail] Add a login page to the Wagtail project