Wagtail Photography 5

Remember how in the previous post I talked about the SubProject pages but we never actually did it? Well today we will. We will be using StreamFields for those pages. As a wise man once said:

Streamfields are a great way to create a model suitable for pages where the structure may vary. If we want to tell a story effectively, we shouldn’t limit ourselves to a fixed structure: the client wanted to combine images, texts, other things, in order to create a story that flows from the moment one clicks on the Story in the Projects page to the time they get to its end.

Who was that wise man I wonder?

If you read the documentation you will see it is composed of blocks and arragenements of sub-blocks. Ok you have heard enough, you are sold on the idea of stream fields and blocks… Let’s create a new application in which we will store them. Why? Because I have used these in multiple projects so making it slightly more modular having them in a separate app.

python manage.py startapp streams

Then let’s add the streams app to our portfolio/settings/base.py file.


INSTALLED_APPS = [
    ...,

    'streams',

    ...
]

Great now let’s go inside that streams folder. Unlike before, we won’t be adding stuff to the models.py file. Instead, let’s create a blocks.py file and let’s create a few types of blocks. I will go through each one one at a time. First let’s import what we will use.

from wagtail.core import blocks
from wagtail.images.blocks import ImageChooserBlock

Then let’s create a Title and Text block. This can be used to separate various sections on a page or it might just be used once at the top of the page. The beauty of Streamfields and blocks is that you are free to build-up a page from whatever components you want (as long as you create them first).

class TitleAndTextBlock(blocks.StructBlock):
    """Title and text and nothing else."""

    title = blocks.CharBlock(required=True,
                             help_text="Title of your Sub-Project Page")
    text = blocks.TextBlock(required=False,
                            help_text="The text under the title of your Sub-Project page")
    class Meta:
        template = 'streams/title_and_text_block.html'
        icon = 'edit'
        label = "Title & Text"

Notice my TitleAndTextBlock inherits from blocks.StructBlock. What is a StructBlock? It is a type of block that has sub-blocks in it. The current block has a title sub-block (of CharBlock type) and a text sub-block (of TextBlock type). Notice that since we are dealing with blocks, we use blocks for the title and text attributes, not fields. I also specify what the template for the title and text block is, the icon I want Wagtail to display and the label for this block. The template for the current block is as follows, with a few bootstrap styling options scattered in there.

<div class="container py-3">
    <div class="row display-flex no-gutters">
        <div class="col">
            <h4 class="text-center">{{self.title}}</h4>
            <p class="text-center text-wrap">{{self.text}}</p>
        </div>
    </div>
</div>

The next block is a RichTextBlock that can be placed anywhere on a page. It’s quite simple. The code and the template for it follow:

class RichTextBlock(blocks.RichTextBlock):
    """Rich text with all the features."""

    class Meta:
        template = 'streams/rich_text_block.html'
        icon = 'edit'
        label = 'Full rich text'
<div class="container py-3 ">
    <div class="row display-flex no-gutters">
        <div class="col align-content-center">
            <p>{{self}}</p>
        </div>
    </div>
</div>

Sometimes the client might want to add some empty space on a page. How can then be achieved? Well if it was in the middle of text, they could just hit enter a few times. I let them do that by using a <br> tag repeteadly in an EmptyBlock.

class EmptyBlock(blocks.StructBlock):
    """Block that inserts a number of lines of empty space."""

    nlines = blocks.IntegerBlock(required=True)

    class Meta:
        template = "streams/empty_block.html"
        icon = 'edit'
        label = 'Empty space yall
<div class="container">
    {% for line in {{self.nlines}} %}    
    <br>
    {% endfor %}
</div>

Finally, the real reason I used blocks is to create Card Blocks with title, image and caption. It also inherits from blocks.StructBlock.

class CardBlock(blocks.StructBlock):
    """Cards with image and text. The image is optional."""

    # TODO: add color chooser option for the card of the block
    title = blocks.CharBlock(required=False,
                             help_text="Add a title to the card if you want.")

    cards = blocks.ListBlock(
        blocks.StructBlock(
            [
                ("image", ImageChooserBlock(required=False)),
                ("caption", blocks.TextBlock(required=False,
                                             max_length=500)),
            ]
        )
    )

    class Meta:
        template = "streams/card_block.html"
        icon = "placeholder"
        label = "Cards"
{% load wagtailimages_tags %}

{% for card in self.cards %}
{% image card.image height-800 as img %}
{% image card.image height-2000 as orig_img %}
            <a data-fslightbox="gallery" href="{{orig_img.url}}">
                <img src="{{img.url}}">
            </a>
{% endfor %}

Yes there’s a todo in there in case the client thinks they would want that as well. But what is happening in the CardBlock? It has a title and a cards ListBlock which currently just has another StructBlock inside with an image and a caption. The image itself is an ImageChooserBlock (which we imported at the beginning) and the caption is a TextBlock. Both currently optional while the client decides on the approach they want to use for certain pages. What the client can do is add multiple image/caption blocks inside the card block that has a title at the beginning (optional). Think of it as an “isolated” gallery that can be inserted at some point into the page. The reason “isolated” is under quotation marks is because the client still wanted there to be an overall one gallery lightbox per page but both options are possible. That’s why in the template there is a fslightbox reference. But Vlad, lightboxes? jQuery? have you not heard of React? or Gatsby JS? or Vue? or (insert whatever JS framework you might think of). Of course, and if the client needs that, I’d be happy to implement that for them with Wagtail’s Headless CMS option. Would it be overkill for this case? I’d say so. Feel free to disagree. You can reach me with your comments by writing them on a paper airplane and throwing it. Maybe it will reach me…but seriously, if you have any feedback or comments feel free to reach out to me. My social links and/or email can be found on this website.

OK those are the blocks I created for the current project. Where did I use them? I used them on the SubProject pages. Have we talked about those? We mentioned but we didn’t look at them in detail. We will do so in the next post. Let me first fix some mistakes I found while writing this post…Testing in production right??

Also why is my development blog all Wagtail? More stuff coming soon.