What’s It Like For A Developer Moving To Wagtail From Drupal?

Kevin howbrook
14 min readSep 25, 2018

--

Since writing my last post 6 things I learned whilst moving a Drupal 8 site to my first Wagtail site, I’ve got my feet wet, well soaked actually, with Wagtail and now is a good time to pause and expand on why I thought switching to Wagtail after 5 years as a Drupal developer was a good idea.

Drupal 8 and Wagtail logos

As a developer, I’m always trying out new frameworks and technology but I usually have a solid foundation to fall back on. For a long time, Drupal was my nucleus of development and I’ve been working with it for over 5 years, from front end to back end. Sure I would utilise new technology like AngularJS or Vue.js, but I never considered a totally new framework. I’ve been using Drupal for a long time so why would I change a solid foundation like that? Well, turns out it’s not that solid…

I’ve been working for Torchbox, creators of Wagtail, for over a year on Drupal 7 and 8 projects. Upcoming changes to core for Drupal 8 included moving towards decoupling, utilising existing PHP libraries like Symfony, a smoother publishing workflow, and better code management. I hate to say it but, for me, the delivery of these changes fell somewhat short of the mark. Whilst the community of Drupal developers did get big changes, the cost was massive. The changes, specifically bringing Symfony in, were a good decision but the result was that in a matter of months skilled Drupal developers were faced with learning a new way of working with Drupal. And they had to learn it fast. This left developers, clients, and specifically ‘Drupal site builders’ scratching their heads more than usual. In the limbo of changing from Drupal 7 to Drupal 8, my curiosity for alternatives increased. Was it time to learn something new? Is there something I can adopt which does more than build websites?

Compared to Drupal, Wagtail is young-but it’s grown up fast. If you consider Drupal 8 as a new CMS (as I do) they’ve been around a similar amount of time, but the Django framework that Wagtail itself is built on is nearly as old as Drupal. This gives us some good signs… It’s here to stay, it has a core team of contributing developers, and it has documentation. So, after moving a Drupal site to Wagtail and seeing the immediate benefits, why have I decided to stick with it?

  • Wagtail has a community you can easily get involved in.
  • For developers, Wagtail is a great tool to add to your toolbox for creating web applications but also, learning Python is a whole new toolbox because It’s used across a range of industries, not just web development. This is really positive for your career as a developer
  • The list of organisations worldwide who have independently selected Wagtail for major projects is pretty impressive, and it’s quickly growing. Google, Nasa, and Oxfam to name a few.

PHP to Python 🐍

The first thing to come to terms with is actually writing Python and just how much code you will be writing. You won’t be ‘building’ things in the UI anymore. This opens you up to many new aspects of development and will be different depending on your skill level, but for me, I was never really exposed to traditional development concepts because Drupal handles a lot of this for you and you never really see it. After learning the basics of the language I found some great tools to help me write better code.

Wagtail asks that all contributions adhere to the PEP8 style guide. So, a good habit to get into is writing code that meets this standard. Realistically, I’m not about to read and memorize a new coding standard fresh off the PHP boat with semi-colons for shoes. A colleague of mine (Rich) suggested setting up a pre-commit git hook which uses Flake8 to check the code against PEP8 standards, it catches things like line length errors (E501 if you’re interested) and unused imports. I can’t stress how good this is, mostly because without knowing it you start a little game of trying to beat Flake8 which leads to better code. In VSCode you can also configure PEP8 to run when saving a file, and errors are shown in the output. Personally I prefer the git hook, because you won’t be able to commit until it’s fixed, and while at pressured times this is annoying, it’s like having a better you tapping you on the shoulder saying “Now now, I know you’re busy but let’s get this right”.

Wagtail (Django) Apps are like Drupal Modules

You probably know this already, but when compared with Drupal 8, this is easier to understand. Drupal 8 uses Symfony, Wagtail uses Django. More specifically, Wagtail is built on the Django web framework. So when working with Wagtail, you will be working with Django. I like to think of community apps for Wagtail and Django as Drupal modules. They are community-built solutions for use on your project but when your Wagtail project starts getting more custom and complex, you will more than likely be utilising the Django part of your project. There are lots of Django apps out there you can use but there are also Wagtail apps too… just check this awesome list out from Springload.

A Good Editor 📝

With Drupal, I used the IntelliJ PHP Storm IDE. It’s awesome, and with Drupal 8 development, Xdebug was a must. Being a fan of IntelliJ products I tried PyCharm, but you can only debug VMs with the full (paid) version. That’s because the debugging in IntelliJ is amazing. I asked what other Wagtail developers were using and the majority was Sublime, VSCode, and Vim. VSCode seemed to be more like a full IDE so I tried that and I’m pretty happy with it. Mostly because it’s lighter than PyCharm, but also because you can modify it nicely to suit your style. This post is a great tutorial on how to slim down the UI.

There are other nice things too… the integrated terminal is great, subtle things like splitting terminal windows and configuring keys are really useful as I’ve found myself working in the terminal a lot more with Wagtail. It’s also pretty easy to set up things like trimming white space when you save a file and believe me, you’re going to want to set that up!

I highly recommend spending the time to configure your editor and setting up the post commit hook. It’s a great help and makes something new and unfamiliar seem a little less daunting.

Starting a site

For Drupal, there are many ways to start your project locally. In the beginning, I actually set up PHP and MySQL on my local machine and used that, then MAMP, but eventually you’ll switch to a virtualised environment using Vagrant or Docker. Drupal VM, in my opinion, is leading the way with local Drupal development, offering a full a Vagrant VM or Docker approach and Drupal lists the many ways you can set up your project in the docs https://www.drupal.org/docs/develop/local-server-setup. As with Drupal, Wagtail has Vagrant and Docker solutions posted all over to help you (here is a good example). You can find most of them on Github or via a web search. I think there is a key difference between Wagtail and Drupal here though.

In Wagtail, the starting point is largely left open to you as a developer (something I also used to love Laravel for). This is great because it means what you want, you specifically develop. You aren’t manipulating out of the box tools and bending things so they ‘sort of work’. This keeps the framework nice and light.

Wagtail.io has a great 10-minute guide to get you going, but coming from Drupal and using virtualisation I don’t want to install all the dependencies on my local machine… because what if I want to work on an older project or a different Django version? You can still use services like Docker or Vagrant but Python offers its own solution for this, way faster for getting started. You can use something like Pipenv. There is also the much more established Virtualenv for Python but I like Pipenv. So here is a modified version of starting your Wagtail site with Pipenv. This will create a virtual environment, and install wagtail, with all its requirements. So now, any packages we add while working on our project are bound to this virtual environment.

As a bonus, you can set which Python interpreter you are using in Vscode too. More about that on the VScode docs here.

Working on a project 📝

So now I have a good editor setup, I’ve got an isolated environment, I’ve stopped writing semi-colons at the end of every line of code (well almost) and after running that last command python manage.py runserver I can see my bare bones wagtail site. So what does actually working with Wagtail look like compared to Drupal?

Structurally Awesome

Obviously, the file structure is going to be different, but to me it was very different. Luckily it’s all documented well in the Wagtail and Django docs, but when it comes to Django itself, there are different ways you can structure your project to make more sense to you and others. I highly recommend Two scoops of Django to learn more about this. Cookiecutter is a great tool for generating projects if you want to automate your own structure or reuse community solutions like pydannys.

The nicest thing about the structure is the way the apps and templates sit together. A template for an app sits in the same directory as the code defining it. I’ve opened many a Drupal project before and seen up to a hundred template files in directories that could make better sense. Also, the actual names of Wagtail’s templates just make more sense, but you can also call them whatever you want. I’ve never really found this to be an issue in Drupal, but I remember how long it took me to understand the template naming convention in Drupal because Drupal’s templating system works by overriding templates already provided by core. This is good for someone wanting to just get something going ‘out of the box’, but I always had to google ‘override template X’ because my brain refused to store that information. As a newcomer to Wagtail even template naming seems to me like it’s more intuitive. For example:

node--standard-page.html.twig  # Drupal
standard_page.html # Wagtail

There are all sorts of benefits to having a templating system like Jinja2 or Twig, and although Drupal was pretty late to this template party, at least it showed up in the end. The default approach to templating in Wagtail means you aren’t searching for templates to override, you are writing templates when they are needed. This is a nicer and more exposed way to work, but it also means Front End development and planning can be done almost completely separate to the back end.

Stop Building Content Types And Start Developing Apps

In Drupal, content types are created through the UI, and now in Drupal 8, configuration for these content types can be exported into code and imported on your production site, I don’t know how developers manage to do that without config split. In Wagtail, you define a ‘content type’ as an app, all in code… so say goodbye to your UI clicking.

Let’s look at an example. In Drupal, I would create the Blog content type, add some fields and if I’m using display suite I might configure the fields display accordingly. Here is what that looks like:

Adding a content type with one field

This is just defining the content type and a field. We haven’t exported anything into code for shipping to our production site yet. After this, we would create a view page to show blog posts. Usually with the path /blog…

Drupal Blog listing view

So what is this like in Wagtail? The Wagtail docs explain a basic blog better than I can, so be sure to check that out. But, here’s the basic outline:

  1. Run python manage.py startapp blog
  2. Add the new blog app to INSTALLED_APPS in mysite/settings/base.py.
  3. Write your model with fields for date, intro and body:

4. Run python manage.py makemigrations and python manage.py migrate.

By comparison with Drupal, these 37 lines of code give us:

  • class BlogPage(Page) The blog post content type including fields that should be indexed for search.
  • class BlogIndexPage(Page) The Blog listing view page

The def get_contextmethod allows us to specify that a blog page added under a blog index will be sent to the template. There is a more complete example of this on the Wagtail under the Blog Index and Posts tutorial.

What’s really nice about this is that step 4 generates code to create database tables needed, and although this is what the new config management does in Drupal 8, instead of a massive folder of yaml files, we generate ‘migration’ code for the specific app/content type. If you have exclusively used Drupal up to this point then consider the word migration here. Wagtail follows the more traditional approach to ‘data migrations’ in development. If you mention migration to any experienced Drupal developer, then you’ll probably find yourself conversing about content migration, which is much more specific.

Now it’s totally portable as a single app. What’s even nicer is that from now on… if I need to add a field, I just edit my model, add it into my template run makemigrations and migrate. This is a much better experience for developers.

Publishers Publish, Developers Develop.

One of Wagtail’s many features is that it’s geared towards publishing. A CMS with a confusing admin UI containing hundreds of links to configurable elements is definitely not, especially when so many aren’t needed by users publishing content. In Wagtail, even as a superuser of the site (a user with admin privileges), there are some configurable items but most of these will have been deliberately included for a specific purpose. So you don’t see anything useless.

The publishing experience is great: the content hinges on the main menu structure, so you can move pages around to be parents or children easily. This promotes focus on content and I very much support freezing the configuration of a site as much as possible to maintain the integrity of the website’s design

Here is a great module to help build menus in Wagtail: https://github.com/rkhleics/wagtailmenus

Debugging 🐛

One of the most important tools for a developer is knowing how to debug. It doesn’t just help you solve a problem, it helps you learn more about the framework, and even the code itself. In Drupal, we had Devel, Kint, and Xdebug. Most of the time I found that a var_dump provided most of what I needed, but Xdebug opened up a better understanding of how Drupal 8 worked.

I knew nothing about debugging in Python and most of the time I was just printing out variables still. There are some great tools though, at the moment I’m getting used to using Pudb. It’s similar Xdebug in that it works to an inspection/execution point in your app, it will pop open an interactive terminal when the point is hit and you can step through and inspect. Just insert the following where you want to inspect

from pudb import set_trace; set_trace()
Pudb debugging

Remember, we are using Django, so we can also use the Django Debug Toolbar too:

Django Debug Toolbar

EntityQuery vs QuerySet API 🔎

We can retrieve content in Drupal using something like EntityQuery. This is the preferred way but you can also use direct queries, here is an example just retrieving nodes:

$query = \Drupal::entityQuery('node');  
$query->condition('status', 1);
$query->condition('type', 'blog');
$entity_ids = $query->execute();
// Do something with $entity IDs, like load multiple...

We would have to extend this for field information, but with Wagtail it’s a lot simpler, we can use the Django QuerySet API

blog_posts = BlogPage.objects.live().order_by('title')

This isn’t just nicer to write and understand, it’s actually returning a list (yes it’s ordered) of objects. Whereas our Drupal query just got us IDs.

For more complex queries like retrieving content from form inputs and date ranges for events, we can use Django Q objects:

Q object encapsulates a SQL expression in a Python object that can be used in database-related operations. Using Q objects we can make complex queries with less and simple code.

from django.db.models import Q...
events = EventPage.objects.live().order_by('title')
events = events.filter(Q(event_date__range([start_date, end_date])) | Q(event_is_daily=True))

Read more about the QuerySet API in the Django Documentation, also be sure to check out Making Queries and Q objects

Translation t() vs _ 🌐

Drupal offers us t() for string translation, and in Drupal 8 this still works (sort of). It’s now a pluggable service, in short you should use dependency injection to obtain the translation service. For someone new to Drupal or even new to writing code, that’s not simple. I’ve no shame in admitting the effort it took me to understand dependency injection.

For Wagtail we can use Django’s translation:

from django.utils.translation import ugettext_lazy as _
...
label=_("Translate me!"),

Signals, Hooks, and EventSubscribers 📢

Hooks are very important to a framework, allowing developers to execute code at certain points in the code execution. I had what can only be described as a nightmare whilst working with Drupal 8 hooks. I didn’t realise just how much they were cached in Drupal 8. I’d argue that hook_node_view() is now pretty much useless becauseit’s cached. With the addition of Symfony, it’s now possible and much more preferable to use EventSubscribers in Drupal 8.

Wagtail implements Django’s signal dispatcher, and the Wagtail docs have some great examples of using Signals for things like invalidating cache:

# models.py
from django.dispatch import receiver
from django.db.models.signals import pre_delete

from wagtail.core.signals import page_published
from wagtail.contrib.frontend_cache.utils import PurgeBatch

...

def blog_page_changed(blog_page):
# Find all the live BlogIndexPages that contain this blog_page
batch = PurgeBatch()
for blog_index in BlogIndexPage.objects.live():
if blog_page in blog_index.get_blog_items().object_list:
batch.add_page(blog_index)

# Purge all the blog indexes we found in a single request
batch.purge()


@receiver(page_published, sender=BlogPage)
def blog_published_handler(instance):
blog_page_changed(instance)


@receiver(pre_delete, sender=BlogPage)
def blog_deleted_handler(instance):
blog_page_changed(instance)

There’s more, you can also register your own functions by defining them in a wagtail_hooks.py file using the @hooks.register decorator:

from wagtail.core import hooks

@hooks.register('name_of_hook')
def my_hook_function(arg1, arg2...)
# your code here

In Summary ☀️ Was Moving to Wagtail A Good Idea?

For me, this is an easy Yes, and if you are a developer too, I think moving to Wagtail will reignite the reason you were interested in development in the first place. The ‘Drupal Site Builder’ role will slowly become obsolete as it becomes best practice to create functionality in code rather than using hundreds of modules, so why wait for that? Why not switch to something that’s not only already doing that, but has been doing it for a long time?

The move to another framework will leave you with questions, it will present concepts you will need to understand that you haven’t considered before, and moving to Python might make your head spin initially… But stick with it and fear not, help is available and more people like you are making the switch every day. Join the Wagtail Slack and find out how people are getting on.

--

--