2016-02-09

Wagtail and Streamfields

Iceland

Django was always great for developers out of the box, but creating friendly admin interfaces was always a little too much work. This post explains why I now consider using Wagtail to make this task easier.

Over the years I've tried pretty much every available Django CMS out there. I was never satisfied with any of them because Django's admin interface is just good enough out of the box. The overhead of using an additional app often wasn't worth it as the built-in admin will work for many use cases.

Sometimes you need something that is a little more user friendly and more flexible though, and in such cases I often implemented a custom admin app. That's surprisingly easy to do with Django's generic views, ModelForms, etc. Some websites are also supposed to have more or less free form pages that don't really fit into any specific model, and a CMS app can help in such situations. After a client asked for a site with some free form sections I looked into CMS solutions again and found Wagtail.

First impression: the admin interface looks very nice and is easy enough to customize. That's already good enough to consider it. After some more work with it I found something that really makes Wagtail stand out, Streamfields. Matthew Wescott, Wagtail's lead technical developer explains the motivation for creating them, and why Rich Text Fields are a poor solution for most editing problems.

More importantly, though, we're losing the semantic value of the content. If you want to insert a Google map on your page, you can get a blob of HTML from Google [...] But the actual meaningful information - "a map centred on the postcode OX7 3EW" - is buried irretrievably within that blob of HTML.

Creating huge blobs of HTML is probably something you dislike if you're using Django. To solve this problem many CMS allow you to have different "blocks" of content on one page that can use many different data models. But the user interface is usually clunky, this is something Streamfields fix, and that's the lure of using Wagtail. See the video below for an introduction, the interface is even more impressive when you start using it yourself.

This makes the admin interface look very neat and organized, adding all kinds of content is very easy, and most semantic information is preserved.

My Streamfield

My StreamField

These are the choices in my own Streamfield, the one I used to create this post. It only uses one snippet and two custom blocks and integrates them into the Streamfield: quotes, source code, and pastes. The pastes are an old model I used somewhere else on this site. The other elements like header, image and link come with Wagtail core and just need a little configuration.

A simple block

Alright, let's look at some code. The example below is how I implemented a quote block that gets rendered as blockquote. See the Wagtail documentation for more info.

from wagtail.wagtailcore.fields import StreamField
from wagtail.wagtailcore import blocks
from wagtail.wagtailcore.models import Page

class QuoteBlock(blocks.StructBlock):
    quote = blocks.TextBlock()
    source = blocks.CharBlock()
    link = blocks.URLBlock()
    author = blocks.CharBlock()

    class Meta:
        icon = 'openquote'
        template = 'wt/quote.html'

class BlogPage(Page):                                                                            
    body = StreamField([                                                                         
        ('Heading', blocks.CharBlock(classname="full title")),                  
        ('Paragraph', blocks.RichTextBlock()),                                     
        ('Quote', QuoteBlock()),                                          
    ])

This example contains the quote block, and a simple Page model that has the quote block in its Streamfield. The wt/quote.html template context receives a variable called value that can be used to access the quote properties.

A simple snippet

The example below is a quote implemented as its own model and snippet. This has the benefit of making quotes reusable, not very useful in this example, but it might be useful for all kinds of data. Even quotes if you're creating scientific content for example. Having a separate model also makes it very easy to create views that show individual quotes, or lists of them. I had built this snippet first but decided later that a block like shown above would be more appropriate for my use case.

from django.db import models
from wagtail.wagtailsnippets.models import register_snippet

from wagtail.wagtailsnippets.blocks import SnippetChooserBlock

from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore.fields import StreamField


@register_snippet
class Quote(models.Model):
    quote = models.TextField()
    source = models.CharField(max_length='255')
    link = models.URLField(blank=True)
    author = models.CharField(max_length='255')

    def __unicode__(self):
        return "%s: %s..." % (self.author, self.quote[:50])


class QuoteChooserBlock(SnippetChooserblock):
    class Meta:
        template = "myblog/quote.html"


class BlogPage(Page):
    body = StreamField([
	# [...]
        ('Quote', QuoteChooserBlock(Quote, icon='openquote')),
    ])

This QuoteChooserBlock can easily be added to any Streamfield so that any editor can use it. The template variable value is used to render an instance on the frontend. This has the downside that the context object name can conflict with existing templates, so here's how to change the template variable:

class QuoteChooserBlock(SnippetChooserBlock):
    TEMPLATE_VAR = 'quote'

    class Meta:
        template = 'myblog/quote.html'

This attribute is undocumented and I had to read the Wagtail source code to find it, but it works for now. If you want total control there's also the render method you can use like this:

class QuoteChooserBlock(SnippetChooserBlock):                                                  
    def render(self, instance):                                                                
        return render_to_string('myblog/quote.html', {'quote': instance})

Of course this example doesn't do anything useful compared to the code above, but I'm already using a similar approach for things like syntax highlighting and sections of ReStructured Text.

Conclusion

All in all I like Streamfields because of their usability. In the database they are serialized as JSON which can lead to a few minor annoyances, but Django was always very developer friendly, and Wagtail is very user friendly, so both are a good match. I probably won't be using Wagtail for each and every project, and I certainly won't make every model inherit from Wagtail's Page model. Fortunately, there's wagtailmodeladmin for other kinds of models.

4 comments

  1. avatar
    wrote this comment on
    Thanks for a nice write up! Is there any way of using streamfields within Django project without firing all that wagtail thing ie access stream fields from Django Admin?
  2. avatar
    wrote this comment on
    I don't know about that, but I seriously doubt it, Streamfields seem quite complex. That being said, nothing prevents you from using the Django and the Wagtail admin at the same time, it's what I do as well.
  3. avatar
    wrote this comment on
    Thank you for this article! I just started building on a Wagtail project but I don't quite know how to model everything. Would you suggest using Wagtail's Snippets or just plain Django models (with wagtailmodeladmin) for more complex data structures with various relationships?
  4. avatar
    wrote this comment on
    Snippets are just regular models with a little Wagtail UX sprinkled on top. I use wagtailmodeladmin for all snippets because the snippet interface is a little too limited for my taste. I can even see myself using the regular Django admin for snippets in the future, Wagtail doesn't have bulk actions for example.

Reply

Cancel reply
Markdown. Syntax highlighting with <code lang="php"><?php echo "Hello, world!"; ?></code> etc.
DjangoPythonBitcoinTuxDebianHTML5 badgeSaltStackUpset confused bugMoneyHackerUpset confused bugX.OrggitFirefoxWindowMakerBashIs it worth the time?i3 window managerWagtailContainerIrssiNginxSilenceUse a maskWorldInternet securityPianoFontGnuPGThunderbirdJenkinshome-assistant-logo