CNK's Blog

Monkey Patching Wagtail

At work we run a large multitenant version of Wagtail (~500 separate websites on a single installation). To achieve this and to make some other changes to the way Wagtail behaves, we have a number of monkey patches. So we have consolidated all of them in their own Django app which we called wagtail_patches. This is loaded into our INSTALLED_APPS after most of our own apps but before any of the Wagtail apps:

    # settings.py
    INSTALLED_APPS = [
        # Multitenant apps. These are ordered with regard to template overrides.
        'core',
        'search',
        'site_creator',
        'calendar',
        'theme_v6_5',
        'theme_v7_0',
        'robots_txt',
        'wagtail_patches',  #####
        'sitemap',
        'features',
        'custom_auth',

        # Wagtail apps.
        'wagtail.embeds',
        'wagtail.sites',
        'wagtail.users',
        'wagtail.snippets',
        'wagtail.documents',
        # We use a custom replacement for wagtail.images that makes it add decoding="async" and loading="lazy" attrs.
        # 'wagtail.images',
        'wagtail_patches.apps.MultitenantImagesAppConfig',
        'wagtail.search',
        'wagtail.admin',
        'wagtail',
        'wagtail.contrib.modeladmin',
        'wagtail.contrib.settings',
        'wagtail.contrib.routable_page',

        # Wagtail dependencies, django, etc.....
    ]

And then in that app, we use the apps.py file to load everything from the patches directory:

    from django.apps import AppConfig
    from wagtail.images.apps import WagtailImagesAppConfig


    class WagtailPatchesConfig(AppConfig):
        name = 'wagtail_patches'
        verbose_name = 'Wagtail Patches'
        ready_is_done = False
        # If there are multiple AppConfigs in a single apps.py, one of them needs to be default=True.
        default = True

        def ready(self):
            """
            This function runs as soon as the app is loaded. It executes our monkey patches to various parts of Wagtail
            that change it to support our architecture of fully separated tenants.
            """
            # As suggested by the Django docs, we need to make absolutely certain that this code runs only once.
            if not self.ready_is_done:
                # The act of performing this import executes all the code in patches/__init__.py.
                from . import patches  # noqa
                self.ready_is_done = True
            else:
                print("{}.ready() executed more than once! This method's code is skipped on subsequent runs.".format(
                    self.__class__.__name__
                ))


    class MultitenantImagesAppConfig(WagtailImagesAppConfig):
        default_attrs = {"decoding": "async", "loading": "lazy"}

You will note that the first of our customizations is right in apps.py. We use this file to configure default html attributes for image tags generated by Wagtail - per the instructions in “Adding default attributes to all images”.