[PYTHON] Wagtail Recommendation (5) Let's add your own block to StreamField

Introduction

Among the fields provided by Wagtail, there is a field called StreamField, which allows you to combine multiple different blocks in any order to compose content. For example, it is an image of combining code blocks and text blocks in Google Colaboratory.

This time, let's add the PostPage class and use this StreamField in it.

Add page class with StreamField

So, first define a new page class PostPage with StreamField in cms/models.py as follows.

from django.db import models

from modelcluster.fields import ParentalKey

from wagtail.core.models import Page, Orderable
from wagtail.core.fields import RichTextField, StreamField
from wagtail.core.blocks import RawHTMLBlock, RichTextBlock

from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, PageChooserPanel, StreamFieldPanel
from wagtail.images.edit_handlers import ImageChooserPanel

class TopPage(Page):
    ...

class PlainPage(Page):
    ...

class ListPage(Page):
    ...
    subpage_types = ['cms.ListPage', 'cms.PostPage']
    ...

class PostPage(Page):
    cover_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    intro = models.CharField(max_length=250)
    main_body = StreamField([
        ('rich_text', RichTextBlock(icon='doc-full', label='Rich Text')),
        ('html', RawHTMLBlock(icon='code', label='Raw Html')),
        ('code', CodeBlock(icon='code', label='Pretty Code')),
    ])

    content_panels = Page.content_panels + [
        ImageChooserPanel('cover_image'),
        FieldPanel('intro'),
        StreamFieldPanel('main_body'),
        InlinePanel('related_pages', label="Related Pages"),
    ]

    parent_page_types = ['cms.ListPage']
    subpage_types = []

    def get_top_page(self):
        ...

    def get_breads(self):
        ...

    def get_context(self, request):
        ...

class NavItems(Orderable):
    ...

class RelatedPages(Orderable):
    ...

It can be seen that it is very similar to the TopPage, PlainPage, ListPage, etc. defined so far. As with the template, for example, based on PlainPage, you can define another page class as a subclass that inherits it, but we will not go into that here.

The main difference from the previous pages is that main_body is defined in StreamField instead of RichTextField. And if you look at content_panels, you can see that the editing panel of StreamField should be specified as StreamFieldPanel. I don't think there is any need for explanation other than those related to SteamField.

The blocks that can be used when constructing content with StreamField are listed in the list in the argument. Of them, RichTextBlock and RawHTMLBlock are prepared by Wagtail by default, and their arguments icon and label are icons and labels displayed on the edit screen of the management site. Is specified. RichTextBlock is a block for inputting a document in rich text format, and RawHTMLBlock is a block for directly inputting html code as it is.

On the other hand, CodeBlock is a uniquely defined block (inheriting RawHTMLBlock). This will be explained in a little more detail in the next section.

Add your own block

As an example of adding your own block to StreamField, let's add a block CodeBlock to display the syntax highlighted program code using Google Code Prettify. In order to include your own block, you need to prepare a class for that block and a template for displaying it.

First, let's define the CodeBlock class. Create a file called cms/blocks.py and write the following code in it.

from wagtail.core.blocks import RawHTMLBlock

class CodeBlock(RawHTMLBlock):
    class Meta:
        template = 'cms/blocks/code.html'

As you can see, CodeBlock is practically the same as the default RawHTMLBlock, but the template used for rendering has changed, as specified in Meta. ..

Next, prepare this template. Add a directory called blocks/under templates/cms /, create a file called code.html in it, and write the following code.

<pre class="prettyprint">
<code>{{ value }}</code>
</pre>

In this way, Google Code Prettify should be applied by specifying class =" prettyprint " in the <pre> tag. However, it is necessary to read the script for Google Code Prettify from the CDN. This was added to templates/cms/base.html as follows.

...
{% block extra_js %}
    ...
    <script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js"></script>
{% endblock %}

At this stage, log in to the admin site and add some PostPage pages under one of the ListPage class pages.

Post page template

Now that the preparations are complete, proceed to display the page of the PostPage class. Since PostPage is a page that imagines a blog post, let's display the posting date and time or the update date and time in Jumbotron. Therefore, first, enclose the text part in the header block of templates/cms/plain_page.html in the header_text block as shown below.

...
{% block header %}
    ...
        <div class="container">
            {% block header_text %}
                <h1 class="display-4">{{ page.title }}</h1>
                <p class="lead">{{ page.intro }}</p>
            {% endblock %}
        </div>
    </div>
{% endblock %}
...

Next, inheriting this templates/cms/plain_page.html, templates/cms/post_page.html was created as follows.

{% extends 'cms/plain_page.html' %}
{% load wagtailcore_tags %}

{% block header_text %}
    {{ block.super }}
    {% if page.last_published_at > page.first_published_at %}
        <span>Last modified on {{ page.last_published_at|date }}</span>
    {% else %}
        <span>Posted on {{ page.first_published_at|date }}</span>
    {% endif %}
{% endblock %}


{% block main_body %}
    <div class="rich-text my-5">
        {% for block in page.main_body %}
            {% include_block block %}
        {% endfor %}
    </div>

    {% for item_page in page_list %}
        <hr>
        <div class="my-4">
            <a href="{% pageurl item_page %}">
                <h4>{{ item_page.title }}</h4>
                {{ item_page.specific.intro }}
            </a>
            {% if item_page.last_published_at <= item_page.first_published_at %}
                <p>Posted on {{ item_page.first_published_at|date }}</p>
            {% else %}
                <p>Last modified on {{ item_page.last_published_at|date }}</p>
            {% endif %}
        </div>
    {% endfor %}
    <hr>
{% endblock %}

If you look inside the header_text block, you can see that the last updated date is displayed for pages that have been updated several times, and the posting date is displayed for pages that have not been updated.

Also, if you look at the first <div> in the main_body block, you can see how to handle StreamField in the template. Specifically, the for statement can be used to extract blocks in order and then include_block.

Let's actually display it on the browser and confirm that the syntax highlighting is applied to the program code.

Code highlights in RichText fields

In the above, I tried to do code highlighting by adding a unique block to StreamField, but as another method, you can incorporate the function of code highlighting into RichTextField. Here, as a bonus, I will briefly introduce the method.

As explained in here, the rich text editor Draftail used in Wagtail can be added with its own editing functions.

Here, let's add an editing function for code highlighting. Specifically, create a file called cms/wagtail_hooks.py and write the following contents. As a result, a button called Code is added to the toolbar of the rich text editor. Then, when you select a range and press this button, this editing function is applied to the text in that range. That is, the text will be enclosed in the tag <pre class =" prettyprint "> as specified in tag.

import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import BlockElementHandler
from wagtail.core import hooks

@hooks.register('register_rich_text_features')
def register_code_feature(features):
    feature_name = 'code'
    type_ = 'code'
    tag = 'pre class="prettyprint"'

    control = {
        'type': type_,
        'label': 'Code',
        'description': 'code',
        'element': 'code',
    }

    features.register_editor_plugin(
        'draftail', feature_name, draftail_features.BlockFeature(control)
    )

    features.register_converter_rule('contentstate', feature_name, {
        'from_database_format': {tag: BlockElementHandler(type_)},
        'to_database_format': {'block_map': {type_: tag}},
    })

    features.default_features.append(feature_name)

in conclusion

This time, I introduced the convenience of StreamField and also touched on how to add a simple editing function to RichTextField.

Next time, I would like to add the tag and category functions that are also covered in the tutorial Your first Wagtail site on the official website.

Link

-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 (3) Understand and use the tree structure of the page -Wagtail Recommendation (4) Let's pass context to template -Wagtail Recommendation (6) Add Categories and Tags

Recommended Posts

Wagtail Recommendation (5) Let's add your own block to StreamField
Add your own content view to mitmproxy
Wagtail Recommendation (4) Let's pass the context to the template
Bridge ROS to your own protocol
[python] How to add RDF triples to your own Fuseki server using rdflib
Add a GPIO board to your computer. (1)
Migrate your own CMS data to WordPress
To import your own module with jupyter
How to install your own (root) CA
Want to add type hints to your Python decorator?
Try to make your own AWS-SDK with bash
How to define your own target in Sage
Annotate your own data to train Mask R-CNN
[Wagtail] Add a login page to the Wagtail project
Steps to install your own library with pip