On the Wagtail management site, a new page instance is created by ADD CHILD PAGE on an existing page. For example, remember that last time we created one instance of the TopPage
class as a child element of the HomePage
class instance ("Welcome to your new Wagtail site!" Page) that is provided by default.
As a result, every page instance (other than the default HomePage
instance) has only one parent element. Also, each page instance can have multiple child elements. In this way, it can be said that all the page instances in Wagtail are connected to each other by the parent-child relationship defined by the tree structure.
The URL of each page is also automatically assigned according to this tree structure (so it is not necessary to specify the routing in urls.py). For example, the default HomePage
instance address is the default
https: // host name /
If it remains, the address of a TopPage
instance created as its child element is
https: // hostname/hello /
(Hello is the character string specified in Slug on this page). Furthermore, if the Slug string of another page generated as its child element is world, the address of that page is
https: // hostname/hello/world /
That is to say.
This time, after adding the PlainPage
class, increase the number of page instances on the management site, experience this tree structure, and try a little technique using it.
First, in addition to the previous TopPage
, add the definition of PlainPage
. Specifically, cms/models.py was extended as shown below.
from django.db import models
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, PageChooserPanel
from wagtail.images.edit_handlers import ImageChooserPanel
class TopPage(Page):
...
class PlainPage(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)
content_panels = Page.content_panels + [
ImageChooserPanel('cover_image'),
FieldPanel('intro'),
FieldPanel('main_body', classname="full"),
]
parent_page_types = ['cms.TopPage']
subpage_types = []
def get_top_page(self):
pages = self.get_ancestors().type(TopPage)
return pages[0]
Since cover_image
, intro
, and main_body
are also fields in TopPage
, there is no need to explain up to content_panels
. parent_page_types
and subpage_types
are descriptions to limit the page classes that can be associated with this image class in a parent-child relationship. Specifically, it specifies that the parent element of PlainPage
must be TopPage
and that it cannot have child elements.
get_top_page ()
is a self-made method that returns the TopPage
instance that is its parent element. In this self-made method, I use a method called get_ancestors ()
, which is one of the methods to get other pages by using the parent-child relationship between page instances provided by Wagtail. Is. There are other similar methods available, so check here.
Now that you have defined the PlainPage
class, log in to your admin site and create somePlainPage
instances as child elements of the previously created TopPage
instance (you'll use them later). ..
Now that we've defined a new page class and created some instances of it, we'll want to display them in the browser next, but before that, let's add a little extension to the TopPage
class as well. This is a preparation for implementing the nav_bar block and footer block, which were left as dummies last time. Specifically, the code was extended as follows.
class TopPage(Page):
...
footer_text = models.CharField(blank=True, max_length=255)
content_panels = Page.content_panels + [
...,
FieldPanel('footer_text'),
InlinePanel('nav_items', label="Nav items"),
]
def get_top_page(self):
return self
class PlainPage(Page):
...
class NavItems(Orderable):
top_page = ParentalKey(TopPage, related_name='nav_items')
label = models.CharField(max_length=255)
page = models.ForeignKey(
Page,
on_delete=models.CASCADE,
related_name='+'
)
panels = [
FieldPanel('label'),
PageChooserPanel('page'),
]
The footer_text
added to the TopPage
class is for storing the text to be displayed in the footer. Looking at content_panels
, in addition to this panel for footer_text
, InlinePanel
for nav_items
is added. What is this?
To understand this, we need to refer to the definition of the NavItems
class below. First, we can see that the NavItems
class is defined as a subclass that inherits the Orderable
class. The Orderable
class is a class provided by Wagtail so that the parts of a page can be defined independently of the page and can be linked to the page in a many-to-one manner. Here, this is used to manage the items (link destination page page
and its label label
) installed in the navigation bar.
The landing page page
of each navigation item can be linked to the Page
class with models.ForeignKey
. And use PageChooserPanel
to edit it. Note that in the Orderable
class, the edit panel is specified by panels
instead of content_panels
.
This NavItems
class is associated with the TopPage
class as a part, and for this, the ParentalKey
of the django-model cluster is used as described in the top_page
field. Then, on the edit screen of the TopPage
class, the instance of the NavItems
class can be edited using the InlinePanel
added to the contant_panels
.
Also note that the get_top_page ()
method has been added to the TopPage
class as well. This is a method that returns itself in the case of the TopPage
class. The reason for adding such a method is clarified below (so please be patient and read on).
At this stage, log in to the management site again, open the edit screen of the TopPage
instance created last time, and add some instances of the NavItems
class along with the character string of footer_text
.
Now that you are ready, proceed to view the page. First, let's display the newly created Plain Page
. The template templates/cms/plain_page.html for this class is defined as below.
{% extends 'cms/base.html' %}
{% load static wagtailcore_tags wagtailimages_tags %}
{% block nav_bar %}{% endblock %}
{% block header %}
{% if page.cover_image %}
{% image page.cover_image fill-1000x400 as my_image %}
{% else %}
{% image page.get_top_page.specific.cover_image fill-1000x400 as my_image %}
{% endif %}
<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.get_top_page.specific.side_title}}
</h4>
<div class="card-body">
{% image page.get_top_page.specific.side_image fill-200x200 class="img-fluid rounded-circle d-block mx-auto" alt="" %}
</div>
<div class="card-body">
<div class="rich-text">
{{ page.get_top_page.specific.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">
{{ page.get_top_page.specific.footer_text }}
</span>
</nav>
{% endblock %}
{% block extra_js %}
{{ block.super }}
<script type="text/javascript" src="{% static 'js/navbar.min.js' %}"></script>
<script>
const nav_props = {
color: "dark",
variant: "dark",
brandlabel: "{{ page.get_top_page.specific.title }}",
brandhref: "{% pageurl page.get_top_page %}",
navs: [
{% for nav_item in page.get_top_page.specific.nav_items.all %}
{
label: "{{ nav_item.label }}",
href: "{% pageurl nav_item.page %}"
},
{% endfor %}
],
};
ReactDOM.render(
React.createElement(MyNavbar, nav_props),
document.getElementById('nav_bar')
);
</script>
{% endblock %}
Let's start with the header block. It is basically the same as the description in the previous templates/cms/top_page.html, but as the background image my_image
, if cover_image
is not registered in this PlainPage
instance itself,get_top_page () It can be seen that it is devised to use the
cover_image of the
TopPageinstance of the parent element acquired by the
method.
Also, at that time, you may have noticed that it is page.get_top_page.specific.cover_image
instead of page.get_top_page.cover_image
. Looking at the entire code, there are some other places where .specific
is added. What is this .specific
?
In fact, if you get another page instance with the get_top_page ()
method (such as the get_ancestors ()
method inside), by default the Page
class (in this case, not TopPage
, but that) It seems that an instance of (superclass) is referenced. Therefore, as it is, the attributes added in the subclass cannot be accessed, but the trick is to add .specific
to make it possible.
Next, if you look at the code of the side_bar block and footer block in the main block, you can see that the necessary information is obtained from the corresponding TopPage
instances and rendered.
Finally, let's move on to the nav_bar block. The contents of this block are empty because we let React and React Bootstrap render this <div id =" nav_bar ">
. The specific script is described in the extra_js block. You can also confirm that the nav_items
prepared above is used and that the URL of the linked page can be obtained with the pageurl tag of Wagtail.
Note that js/navbar.min.js read at the beginning of this block is a compilation/compression of the following React code created using JSX.
var Navbar = ReactBootstrap.Navbar;
var Nav = ReactBootstrap.Nav;
function MyNavbar (props) {
return (
<Navbar bg={props.color} variant={props.variant} fixed="top" expand="sm">
<Navbar.Brand href={props.brandhref}>{props.brandlabel}</Navbar.Brand>
<Navbar.Toggle/>
<Navbar.Collapse>
<Nav className="ml-auto">
{props.navs.map((nav) => (
<Nav.Link href={nav.href}>{nav.label}</Nav.Link>
))}
</Nav>
</Navbar.Collapse>
</Navbar>
);
};
Now you can safely display the pages of the PlainPage
class.
Finally, since the TopPage
class has been extended above, I would like to modify the template templates/cms/top_page.html there as well. In fact, you don't have to add a lot of code to this file for that. Rather, all you have to do is greatly simplify it as shown below.
{% extends 'cms/plain_page.html' %}
That is, the template for the PlainPage
class can be used as it is for the page of the TopPage
class. Yes, that's why we added the seemingly useless get_top_page ()
method to the TopPage
class.
This time, after understanding that the pages under the control of Wagtail are organized in a tree structure, I touched on a part of the technique to use it. I also tried to define page parts using the Orderable
class.
Next time, I'd like to add the ListPage
class and touch on some techniques related to it.
-Wagtail Recommendations (1) A story about choosing Wagtail by comparing and examining Django-based CMS -Wagtail Recommendation (2) Let's define, edit, and display 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) Let's add categories and tags
Recommended Posts