Upgrading from Wagtail 1.0 to Wagtail 1.11

There are plenty of reasons to upgrade your Wagtail site. Before we look at how to do it, let’s take a look at a few of those reasons.

Why upgrade your Wagtail site?

  • Wagtail is now compatible with Django 1.11 and Python 3.6, so you can use the latest versions (at the time of this blog post) of all three together.
  • Page Revision Management was released in Wagtail 1.4, allowing users to preview and rollback revisions.
Page revision management in Wagtail

Image from http://docs.wagtail.io/

The Wagtail user bar

The new Wagtail Userbar shown with the top-left configuration, note it does not conflict with Django Debug Toolbar.

  • Streamfield was already really nice, but the addition of TableBlock looks useful for easily editing tabular data.
  • Page-level permissions for logged-in users belonging to specific groups are now possible via the new Page Privacy Options
  • Wagtail now supports many-to-many relations on the Page model.
  • If you’re using PostgreSQL, you can use the built-in PostgreSQL search engine rather than ElasticSearch.
  • Finally, with the June 2017 release of Wagtail 1.11, the Wagtail team updated the Wagtail Explorer with the new admin API and React components. The explorer is now faster to use, includes all of the pages in your site (not just parent pages), and lets you edit a page in fewer steps.

How I ported a Wagtail 1.0 site to Wagtail 1.11

Now that we’ve had a look at the features gained from updating, let’s see how to update.

I decided to port a Wagtail 1.0 project to Wagtail 1.11. I was able to upgrade from 1.0 to 1.11 directly, rather than upgrading version by version (which is a slower process), with a few changes along the way.

To start, I went ahead and created a brand new local virtual environment on my laptop. I pip installed all the current requirements for my Wagtail 1.0 project, and then updated Wagtail.

$(newwagtailenv) pip install -r requirements/dev.txt
$(newwagtailenv) pip install wagtail==1.11

Because we’re tracking versions of our requirements in a file, I updated the versions that required updates from the Wagtail update. This included updates to django-taggit and django-modelcluster among some other new requirements.

I assumed that data migrations would be required for this Wagtail upgrade. When I ran migrate, I encountered an issue right away.

$(newwagtailenv) python manage.py migrate
$(newwagtailenv) ... in bind_to_model
related = getattr(model, self.relation_name).related
TypeError: getattr(): attribute name must be string

I found this post to help me solve the issue. Going forward, I noticed, the Wagtail core team recommends using Stack Overflow to research Wagtail questions.

The error was caused because I was using an older style of the InlinePanel definitions with the page model as the first parameter. Because that style was deprecated in Wagtail 1.2, I needed to make a few code changes like this one:

Change:

InlinePanel(CaseStudyPage, 'countries', label="Countries"),

To:

InlinePanel('countries', label="Countries"),

The next error I saw when I tried to migrate had to do with tuples and lists.

$(newwagtailenv) python manage.py migrate
$(newwagtailenv) index.SearchField('intro'),
$(newwagtailenv) TypeError: can only concatenate list (not "tuple") to list

For the 1.5 release of Wagtail, the search_fields attribute on the Page models (and other searchable models) changed from a tuple to a list.

This was another pretty simple fix.

Change:

class MyPage(Page):
    ...
    search_fields = Page.search_fields + (
        index.SearchField('intro'),
    )

To:

class MyPage(Page):
    ...
    search_fields = Page.search_fields + [
        index.SearchField('intro'),
    ]

At this point, I was able to successfully run python manage.py migrate. I gave my test suite a try and it ran successfully, so I tested the site out locally as well. It worked beautifully.

That’s all I had to do! But I decided to do one last thing anyway.

I was excited about the fact that Wagtail solved the issue of not having many-to-many fields on the Page model in version 1.9. I read up on the new ParentalManyToManyField and made a plan to use it because having one fewer model meant that there would be less code to maintain long-term. Paying down some technical debt now meant that future developers who were maintaining this Wagtail site wouldn’t have to spend time researching an older work-around in order to get up to speed, and is generally considered best practice.

When we originally built this Wagtail site, I used the “through model” workaround described in this issue by defining three separate models for each many-to-many relationship. For instance, I had a CaseStudyPage, based off the Page model, the Country model, and a through model called CountryCaseStudy that created the many-to-many relationship between CaseStudyPage and Country.

Here’s how to move from the “through model” method to the new ParentalManyToManyField, including how to port the data:

Moving from the through model to the newly available many-to-many relationship via the ParentalManyToManyField

I wanted to move from the through model to the newly available many-to-many relationship via the ParentalManyToManyField.

  • Create a new field to replace the through model implementation on the Page model (CountryCaseStudy in this case) called countries_new
countries_new = ParentalManyToManyField('portal_pages.CaseStudyPage', blank=True)
  • Make a new migration file for this new field so it retains the old data before ripping out the old models.
$ python manage.py makemigrations
  • Create a new data migration file to copy data from the through model to countries_new.
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


# Loop through all Case Study pages and save the countries
# to the new ParentalManyToManyField

def no_op(apps, schema_editor):
    # Do nothing on reversal to data
    pass

def save_countries_to_new_parental_m2m(apps, schema_editor):
    # Need to import the actual model here
    # versus the "fake" model
    # so that the Clusterable model logic works and we can
    # successfully save the ParentalManyToManyField
    from portal_pages.models import CaseStudyPage

    for csp in CaseStudyPage.objects.all():
        csp.countries_new = [country.country for country in csp.countries.all()]
        csp.save()


class Migration(migrations.Migration):

    dependencies = [
        ('portal_pages', '0055_casestudypage_countries_new'),
        ('wagtailcore', '0038_make_first_published_at_editable'),
    ]

    operations = [
        migrations.RunPython(save_countries_to_new_parental_m2m, no_op),
    ]
  • Update existing code that used the through model to use the new field instead
  • Delete the through model now that it’s no longer needed
  • Run makemigrations and migrate again

The trickiest part for me was moving data to the newly implemented ParentalManyToManyField. Because the Page model is a Clusterable model, I needed to import the current model class rather than use the historical model state. I spent a little time figuring that out and have to thank Matthew Westcott, who guided me in the right direction from the Wagtail Slack channel.

You can see the updates I made on GitHub on the RapidPro Community Portal wagtail-updates branch. There is still more work to be done and we hope to complete it soon.

Conclusion

The Wagtail CMS has really come into its own as a beautiful and easy-to-use content management system. I highly recommend keeping your Wagtail site up to date to take advantage of all the newest features. To read more about Caktus and Wagtail, check out this post about adding pages outside of the CMS or this one about our participation in Wagtail Sprints.

New Call-to-action
blog comments powered by Disqus
Times
Check

Success!

Times

You're already subscribed

Times