DjangoCon Europe 2019: Building plugin ecosystems with Django

Writeup of the DjangoCon Europe 2019 talk »Building plugin ecosystems with Django« by Raphael Michel

Raphael Michel: A software developer working with the web for over ten years and with Django for over five years. He runs a small software company and maintain multiple open source projects. The most notable Django-based one is pretix, a full-featured and free conference ticketing software.

Let's talk WordPress – many, many people use it. It's about a third of the web (says w3techs.com). One of the reason is that it's very easy to install, but mostly it's due to the plugins – ranging from free to commercial, from simple to vastly complex shop systems. WordPress is an application platform, not just a content management system.

Raphael's pretix is a ticket sales platform, which is completely open source. A key design goal was to be extensible and to offer a plugin interface to avoid diverging forks or feature bloat.

Django provides us with useful concepts for this, although we'll have to add some things on our own. One useful concept is apps: The term application describes a Python package that provides some set of features. Applications my be reused in various projects. But reusable doesn't mean pluggable! Adding an app to an existing project is tricky and to be done by developers, not users or even administrators. So apps are great for functionality that co-exists, but is only loosely integrated.

Django also provides signals. Signal are events that are connected to receiver methods, that get called when the receiver is fired. Signals are considered bad and evil by many people. They can make your software hard to debug and read, and shouldn't be overused, but here they are very useful.

Let's bring this closer together.

Installation

Let's look at an __init__.py

class PluginApp(AppConfig):
    name = 'xyz'

    class PretixPluginMeta:
        name = 'xyz plugin'

    def ready(self):
        from . import signals

And use a fancy setup.py to your installable plugin:

setup(
    name='pretix-xyz',
    
    entry_points={
    'pretix.plugin': [
        'pretix_xyz=pretix_xyz:PretixPluginMeta',
    ]
    }
     )

And in our settings, we'll just find all plugins and append to our INSTALLED_APPS.

pip install pretix-xyz
python -m pretix migrate

This makes installation very easy, but how do we integrate functionality?

Routing

In your urls.py you now can look at all the apps, find the plugins, and import their urls module, if it's there – and you'll even get automatic namespacing, freeing your plugin authors from running into URL name conflicts.

You can even wrap all those views included in URLs in a decorator to isolate the plugins, or add checks to make plugins more secure!

Signal sending

Signals can be annoying to send, so you can add a custom template tag, which sends out the named signal, iterates over the response, and returns them (supposedly either helpful data, or HTML directly), to be rendered in your template.

Use cases

  • Execute actions after certain actions
  • Validate data before certain actions
  • Display additiona views
  • Use signals for discovery of complex class-based interfaces (which then provide structured data and methods)

Documentation!

Documentation is important – provide API docs and example plugin implementations! Provide a cookiecutter to get people started, too.

Per-client plugins in multi tenant applications

Allowing customers to activate or deactivate plugins is really useful. Plugin signals will only be called if the plugin is active for the customer calling it. This can be done by using a custom subclass of Signal which knows about the current customer, and storing a list of active plugins per customer.

What's missing?

pretix has about 50 plugins, and other projects like pretalx copied the system, too. It doesn't support installation via the web interface, since that's a security hazard.