Wagtail list of Tips and Tricks!

Tips and tricks in Wagtail that will help you tweak the appearance and functionality.

Date and Time of last update Sun 29 Mar 2020
  


In this post we are going to share some functionality and appearance tweaks that you can have in your Wagtail installation. These tweaks are in effect in erev0s.com and have helped us adjust the content we have, the way we want.

We are going to see the following tweaks:

  1. Anchor Links in Streamfield rich text editor.
  2. External site links to open in a new Window
  3. Add a code button in the rich text editor
  4. Add a custom width/height image in the rich text editor

If you are interested in SEO related tweaks for your Wagtail website go ahead and take a look at this post!

This post will be updated whenever something new comes up and it is useful to be added!


Anchor Links in Streamfield rich text editor

Our goal with this tweak is to be able to have links within a page similar to the ones presented just above, where it is possible for a guest to click a link and automatically scroll down to a predefined location.

How to achieve this? The solution comes from this stackoverflow post where the idea is to simply wrap the RichText html serialization. Lets now see the code first and comment it later.

import re
from django import template
from django.utils.text import slugify
from wagtail.core.rich_text import RichText

# We'll be wrapping the original RichText.__html__(), so make
# sure we have a reference to it that we can call.
__original__html__ = RichText.__html__

# This matches an h1/.../h6, using a regexp that is only
# guaranteed to work because we know that the source of
# the HTML code we'll be working with generates nice
# and predictable HTML code.
heading_re = r"<h(\d)[^>]*>([^<]*)</h\1>"


def add_id_attribute(match):
    """
    This is a regexp replacement function that takes
    in the above regex match results, and then turns:
        <h1>some text</h1>
    Into:
        <h1><a id="some-text"></a><a href="#some-text">some text</a></h1>
    where the id attribute value is generated by running
    the heading text through Django's slugify() function.
    """
    n = match.group(1)
    text_content = match.group(2)
    id = slugify(text_content)
    return f'<h{n}><a id="{id}""></a><a href="#{id}">{text_content}</a></h{n}>'


def with_heading_ids(self):
    """
    We don't actually change how RichText.__html__ works, we just replace
    it with a function that does "whatever it already did", plus a
    substitution pass that adds fragment ids and their associated link
    elements to any headings that might be in the rich text content.
    """
    html = __original__html__(self)
    return re.sub(heading_re, add_id_attribute, html)


# Rebind the RichText's html serialization function such that
# the output is still entirely functional as far as wagtail
# can tell, except with headings enriched with fragment ids.
RichText.__html__ = with_heading_ids



Now upon submission of the post it searches for H<#> headers within it using the regex shown in line 14. Then the headers found are being replaced on line 41 using re.sub(). Lets see in a bit more detail how the matching happens. The whole trick is in lines 27-30, where the heading_re regex has matched the headers. If you notice the regex you will see it has two capturing groups, the first one returns the # of the header while the second one the actual content of it. Now in lines 27 and 28 we assign these values to variables and also do pay attention to line 29 where slugify converts the string of the header to an actual slug so it can be used as an anchor tag. The actual substitution happens as we mentioned in line 41, while in line 47 we simply rebind with the html serialization function.


External site links to open in a new Window

It is nice to have external links opening in a new tab in the browser in Wagtail as the guest can open interesting things you may be writing about without having to press the back button. In order to achieve this we are going to make use of the Rewrite Handlers available in Wagtail. See the following snippet:

from django.utils.html import escape
from wagtail.core import hooks
from wagtail.core.rich_text import LinkHandler

class NewWindowExternalLinkHandler(LinkHandler):
    # This specifies to do this override for external links only.
    identifier = 'external'

    @classmethod
    def expand_db_attributes(cls, attrs):
        href = attrs["href"]
        # Let's add the target attr, and also rel="noopener" + noreferrer fallback.
        # See https://github.com/whatwg/html/issues/4078.
        return '<a href="%s" target="_blank" rel="noopener noreferrer">' % escape(href)


@hooks.register('register_rich_text_features')
def register_external_link(features):
    features.register_link_type(NewWindowExternalLinkHandler)


Things are straight forward here, we have the identifier to be external which automatically separates the external links for us and then in line 11 we return the modified href value with added the _blank attribute along with the noopener and noreferrer for SEO and security reasons. Now all external links added to the content will be having the specified attributes while all internal links will remain intact.


Add a code button in the rich text editor

We are going to define extensions to Wagtail’s rich text handling taking advantage of the feature registry provided by Wagtail. Our goal is to add a code button to do exactly this, meaning to add <code> tags to the text we specify. Wagtail makes it a relatively easy task to accomplish. Here is how:

from wagtail.core import hooks

@hooks.register("register_rich_text_features")
def register_code_styling(features):
    """Add the <code> to the richtext editor and page."""

    # Step 1
    feature_name = "code"
    type_ = "CODE"
    tag = "code"

    # Step 2
    control = {
        "type": type_,
        "label": "</>",
        "description": "Code"
    }

    # Step 3
    features.register_editor_plugin(
        "draftail", feature_name, draftail_features.InlineStyleFeature(control)
    )

    # Step 4
    db_conversion = {
        "from_database_format": {tag: InlineStyleElementHandler(type_)},
        "to_database_format": {"style_map": {type_: {"element": tag}}}
    }

    # Step 5
    features.register_converter_rule("contentstate", feature_name, db_conversion)

    # Step 6. This is optional
    # This will register this feature with all richtext editors by default
    features.default_features.append(feature_name)

Here is a screenshot of the outcome of the snippet above from erev0s.com while writing this post.

codeTag.png


Add a custom width/height image in the rich text editor

This is something particularly useful in the content appearance and at the same time super easy to do! By default Wagtail has three options when you add an image in the rich text editor, the left and right align and the full width. The issue is that even for the full width it is not actually full width, meaning it does not cover the entire width of the screen, column etc.

Wagtail developers made it this way so the developer of the website can define the appearance of the images and not the authors creating the content. This makes absolute sense as you would want consistency across your website when different authors are creating content. Wagtail once again makes our task super easy:

from wagtail.images.formats import Format, register_image_format

register_image_format(Format('mainWide', 'MainWide', 'richtext-image mainWide', 'max-1200x500'))

The first parameter is the unique key we specify to identify this format, the second is what will actually be seen in the front end. The third parameter includes the class attributes that will be used on the <img> tag while the fourth parameter defines the size of the image using the amazing image tags provided by Wagtail!

Do keep in mind that you can also unregister some format that is already registered like the left or right align.


Conclusion

In this article we see different ways that allow us to tweak appearance and functionality of our Wagtail website. The content might get updated as soon as we have something new to add, so feel free to contribute ideas if you believe you have something interesting to share!