Customizing the frontend rendering

As displayed in the Example plugin code page, a plugin is made of two classes:

  • A model class in models.py.
  • A plugin class in content_plugins.py.

The plugin class renders the model instance using:

Simply stated, a plugin provides the “view” of a “model”.

Simple rendering

To quickly create plugins with little to no effort, only the render_template needs to be specified. The template code receives the model object via the instance variable.

To switch the template depending on the model, the get_render_template() method can be overwritten instead. For example:

@plugin_pool.register
class MyPlugin(ContentPlugin):
    # ...

    def get_render_template(self, request, instance, **kwargs):
        return instance.template_name or self.render_template

To add more context data, overwrite the get_context method. The twitterfeed plugins use this for example to pass settings to the template:

@plugin_pool.register
class MyPlugin(ContentPlugin):
    # ...

    def get_context(self, request, instance, **kwargs):
        context = super(MyPlugin, self).get_context(request, instance, **kwargs)
        context.update({
            'AVATAR_SIZE': int(appsettings.FLUENT_TWITTERFEED_AVATAR_SIZE),
            'REFRESH_INTERVAL': int(appsettings.FLUENT_TWITTERFEED_REFRESH_INTERVAL),
            'TEXT_TEMPLATE': appsettings.FLUENT_TWITTERFEED_TEXT_TEMPLATE,
        })
        return context

For most scenario’s, this provides simple flexibility with a DRY approach.

Custom rendering

Instead of only providing extra context data, the whole render() method can be overwritten as well.

It should return a string with the desired output. For example, this is the render function of the text plugin:

def render(self, request, instance, **kwargs):
    return mark_safe('<div class="text">' + instance.text + '</div>\n')

The standard render() method basically does the following:

def render(self, request, instance, **kwargs):
    template = self.get_render_template(request, instance, **kwargs)
    context = self.get_context(request, instance, **kwargs)
    return self.render_to_string(request, template, context)

The output will be escaped by default, so use Django’s format_html() or mark_safe() when content should not be escaped. Hence, it’s preferred to use a template unless that makes things more complex.

Internally, the render_to_string() method wraps the rendering context in a PluginContext(). which is similar to the RequestContext that Django provides.

Form processing

An entire form with GET/POST can be handled with a plugin. This happens again by overwriting render() method.

For example, a “Call me back” plugin can be created using a custom render() function:

@plugin_pool.register
class CallMeBackPlugin(ContentPlugin):
    model = CallMeBackItem
    category = _("Contact page")
    render_template = "contentplugins/callmeback/callmeback.html"
    cache_output = False   # Important! See "Output caching" below.

    def render(self, request, instance, **kwargs):
        context = self.get_context(request, instance, **kwargs)
        context['completed'] = False

        if request.method == 'POST':
            form = CallMeBackForm(request.POST, request.FILES)
            if form.is_valid():
                instance = form.save()
                return self.redirect(reverse('thank-you-page'))
        else:
            form = CallMeBackForm()

        context['form'] = form
        return self.render_to_string(request, self.render_template, context)

Note

The cache_output attribute is False to disable the default output caching. The POST screen would return the cached output instead.

To allow plugins to perform directs, add fluent_contents.middleware.HttpRedirectRequestMiddleware to MIDDLEWARE_CLASSES.

Frontend media

Plugins can specify additional JS/CSS files which should be included. For example:

@plugin_pool.register
class MyPlugin(ContentPlugin):
    # ...

    class FrontendMedia:
        css = {
            'all': ('myplugin/all.css',)
        }
        js = (
            'myplugin/main.js',
        )

Equally, there is a frontend_media property, and get_frontend_media method.

Output caching

By default, plugin output is cached and only refreshes when the administrator saves the page. This greatly improves the performance of the web site, as very little database queries are needed, and most pages look the same for every visitor anyways.

Most plugins deliver exactly the same content for every request, hence the setting is tuned for speed by default. Further more, this lets plugin authors make a conscious decision about caching, and to avoid unexpected results in production.

When a plugin does a lot of processing at render time (e.g. requesting a web service, parsing text, sanitizing HTML, or do XSL transformations of content), consider storing the intermediate rendering results in the database using the save() method of the model. The code plugin uses this for example to store the highlighted code syntax. The render() method can just read the value.

Development tips

In DEBUG mode, changes to the render_template are detected, so this doesn’t affect the caching. Some changes however, will not be detected (e.g. include files). A quick way to clear memcache, is by using nc/ncat/netcat:

echo flush_all | nc localhost 11211

When needed, include FLUENT_CONTENTS_CACHE_OUTPUT = False in the settings file.