Merengue Plugins

Overview

Merengue plugins are installable components that allow you to add new functionality of your site. With plugins you can add:

  • New content types.
  • New blocks
  • New actions for specific content or the entire site.
  • New permissions
  • New views (listings, content detail views, etc.).

Django Apps vs. Merengue Plugins

There are two main differences between Django apps and Merengue plugins:

  • Merengue plugins can be installed from the Merengue admin site, without any programming. Django apps usually require (at a minimum) editing the settings file, copying any media files to the Django media file path, editing the URL namespace to add additional URLs, etc.
  • Merengue plugins are highly integrated with the Merengue CMS application because of the CMS-specific concepts and conventions used in Merengue including blocks, actions, viewlets, template layouts, etc. Portable Django apps are normally designed for broader use with a variety of Django-based web applications.

Installing Third Party Plugins

Only three steps are required to install a new plugin:

  1. Place the decompressed plugin into the plugins directory (normally located inside your Merengue project).
  2. Registering into Merengue. This can be done:
  • manually, if you click on the detect new plugins link in the plugins admin view.
  • automatically, if you have set the DETECT_BROKEN_PLUGINS setting to True (the default), and if you have access to the plugins admin page.
  1. Activate it from plugins admin page. See the plugins documentation in the Merengue user guide.

Media Files in Plugins

(( To be completed. Talk about media conventions taken in Merengue, merengue.views.static.serve view and sync_plugins_media command. ))

Broken Plugins

Merengue detects broken plugins found in the plugins directory. Broken plugins are shown in the plugins admin view:

../../_images/broken_plugins.png

Merengue detects broken plugins to avoid “breaking” the entire system (or site) when you install a third party plugin. The plugins admin view does not allow you to install broken plugins. However, if you have a broken plugin which is activated, you will get an exception – your site is “broken”.

Plugins can “break” for the following reasons:

  • Deleted from the file system: at some point the plugin was located in the file system, and registered by Merengue. However, later it was deleted.
  • Model problems: The plugin’s models do not complete validation.
  • Syntax error(s): The plugin’s code has one or more syntax errors. Merengue tried to register the plugin and a SyntaxError exception was raised.

To detect broken plugins, execute the mark_broken_plugins command, or set DETECT_BROKEN_PLUGINS setting to True (the default) and visit the plugins admin view.

To debug broken plugins, you need to access the broken plugin’s admin edit change form. In this view, Merengue will show the error traceback:

../../_images/broken_plugins_debug.png

Plugin Customization

Some plugins have customization parameters which appear in the plugin admin view. See the registry configuration params topic for more details.

Plugin Development

Writing a Custom Plugin

To write a custom plugin for Merengue, you need to create a Django application which will:

  • have a specfic directory layout.
  • have a configuration file.
  • extend some components.
  • extend some base models.

Plugin Tree

All plugins will be placed in the plugins directory located in your project directory.

A typical Merengue plugin directory structure:

/plugins/
    |-- fooplugin/
    |   |-- __init__.py
    |   |-- actions.py
    |   |-- blocks.py
    |   |-- config.py
    |   |-- models.py
    |   |-- views.py
    |   |-- viewlets.py
    |   |-- urls.py
    |   `-- templates/
    |       `-- fooplugin/
    |   `-- media/
    |       `-- fooplugin/
    |
    ...

Note

Only the __init__.py and config.py files are mandatory.

Let's explain piece of the directory tree listed above:

  • __init__.py is a required file (usually empty) which designates the plugin

    as a python module.

  • models.py is a normal Django application model file. See the base models documentation to get more information on the Merengue base models which you can extend in your own plugin(s).

  • config.py is the configuration file for the plugin. See the plugin configuration reference documentation.

  • blocks.py is a Merengue blocks file which contains the code for the new, visually oriented blocks defined in your plugin.

  • actions.py is a Merengue actions file.

  • views.py is normal Django views file.

  • urls.py is normal Django urls file. You can use it to control a URL namespace (i.e. all /fooplugin/.* urls).

  • templates/fooplugin/ is a directory for fooplugin template resources.

  • media/fooplugin/ is a directory for media resources (e.g. icons, css, etc.)

Plugin Configuration

A plugin configuration is defined in the PluginConfig class located in the config.py module inside your plugin directory. This class is read by Merengue when the plugin is loaded initially.

A code fragment from a config.py example:

from merengue.pluggable import Plugin

class PluginConfig(Plugin):

    name = "Foo plugin"

    url_prefixes = (
        ('fooplugin', 'plugins.fooplugin.urls'),
    )

    def get_actions(self):
        return [...]

    def get_blocks(self):
        return [...]

    def section_models(self):
        return [...]

    # ... etcetera

Configuration Parameters Reference

Plugin Metadata

The name parameter is used to give the plugin a name (visible in the Merengue plugin control center). It's a required parameter and usually consists of a single word (or two).

The description parameter adds additional information for the plugin. It can be a full sentence, describing the plugin, giving the Merengue administrator an idea of the plugin's purpose and its functionality.

Finally, the version parameter is a string with version information for the plugin. This parameter is not required, however its usage is highly recommended.

An example:

class PluginConfig(Plugin):

    name = "Foo"
    description = "Demo plugin to explain how to create Merengue plugins"
    version = "1.0.0"

Plugin URLs

The url_prefixes parameter contains the URL prefix for all of your plugin URLs.

Example:

class PluginConfig(Plugin):
    url_prefixes = (
        ('fooplugin', 'plugins.fooplugin.urls'),
        ...
    )

In this example, if the plugin was activated, all URLs below fooplugin will be handled by the plugins.fooplugin.urls module. Internally, the Merengue plugin system will perform an operation like this:

urlpatterns += patterns('',
  (r'^fooplugin/', include('plugins.fooplugin.urls')),
)

You can also internationalize your plugin's URLs. See this example:

class PluginConfig(Plugin):
    # ... stuff
    url_prefixes = (
        ({'en': 'event', 'es': 'eventos'},
         'plugins.event.urls'),
    )

The final URL prefix used will depend on the URL_DEFAULT_LANG setting (by default it will be set the same as LANGUAGE_CODE).

Note

Your site will have only one prefix for every url prefixes you have internazionalized. So, if your URL_DEFAULT_LANG is "es", only /eventos will be handled for the plugin and /event will raise a 404 error.

Plugin Possibilities

A developer can do many things when developing a new plugin:

  • Define new blocks: Implement the get_blocks(self) method in the PluginConfig class. See the blocks reference for more information.

    An example:

from plugins.fooplugin.blocks import FooBlock

class PluginConfig(Plugin):
    # stuff ...

    def get_blocks(self):
        return [FooBlock, ]
  • Define new actions: Implement the get_actions(self) method in the PluginConfig class. See the actions reference for more information.

    An example:

from plugins.fooplugin.actions import FooAction

class PluginConfig(Plugin):
    # stuff ...

    def get_actions(self):
        return [FooAction, ]
  • Create new models: Implement new models in the plugin models.py file.

    An example:

from merengue.base.models import BaseContent

class FooContent(BaseContent):
    new_field = models.CharField(max_length=100)
    # stuff ...
  • Define a plugin's custom admin site: Implement the get_model_admins(self) method in the PluginConfig class.

    An example:

from plugins.fooplugin.admin import FooContentAdmin, FooCategoryAdmin
from plugins.fooplugin.models import FooContent, FooCategory

class PluginConfig(Plugin):
    # stuff ...

    def get_model_admins(self):
        return [(FooContent, FooContentAdmin),
                (FooCategory, FooCategoryAdmin)]
  • Define new permissions: Implement the get_perms(self) method in the PluginConfig class. See the permissions reference for more information.

    An example:

from plugins.forum.models import FooContent

class PluginConfig(Plugin):
    # stuff ...

    def get_perms(self):
        return [('Vote foo', 'vote_foo', [FooContent]), ), ]
  • Define new viewlets: Implement the get_viewlets(self) method in the PluginConfig class. See the viewlets reference for more information.

    An example:

from plugins.fooplugin.viewlets import FooViewlet1, FooViewlet2

class PluginConfig(Plugin):
    # stuff ...

    def get_viewlets(self):
        return [FooViewlet1, FooViewlet2]
  • Define new middleware: Implement the get_middlewares(self) method in the PluginConfig class. See the Django middleware reference for more information.

    An example:

class PluginConfig(Plugin):
    # stuff ...

    def get_middlewares(self):
        return ['plugins.fooplugin.middleware.FooMiddleware', ]
  • Execute code after plugin activation: Implementing the hook_post_register(self) method in the PluginConfig class.

    An example:

from django.core.mail import send_mail

class PluginConfig(Plugin):
    # stuff ...

    def hook_post_register(self):
        send_mail('Foo plugin enabled', 'The foo plugin has been enabled.', 'from@example.com',
                  'admin@example.com', fail_silently=True)

Plugin Examples

Check out the Merengue plugins directory to see several plugin implementations (e.g. the news plugin).

Plugin Dependencies in a Project

Sometimes your project depends on a plugin (or multiple plugins) to be activated by default and you want to prevent the user from deactivating it (or them). For example, your project logic may rely on the existence of certains models defined in a specific plugin.

You can define which plugins your project depends on by including the following setting in your project settings:

REQUIRED_PLUGINS = ('core', 'fooplugin', )

Default: ('core', ) (the core plugin)

About the core plugin:

The core plugin must be included in the REQUIRED_PLUGINS setting for all of the standard Merengue features to be present in your site.

After changing the REQUIRED_PLUGINS setting, you will need to register the plugin with the following command to get new plugins activated (and also to get their models created if any exist):

python manage.py migrate

South Migrations in Plugins

If your plugin's models change, and you want a clean model migration for your site(s), you may need to implement South migrations for it.

In order to create South migrations, you have to use the schemamigration and datamigration commands (see the South docs for more information). You only have to remember to temporarily add the plugin name to the INSTALLED_APPS setting before executing thesecommands, because South is not able to find Merengue plugins on its own.

When a plugin with a South migration setup is installed, Merengue will automatically execute the plugin's South migrations.

To execute an existing (i.e. installed) plugin's South-based migration, you must uninstall and re-install the plugin using the Merengue admin site.

Also, you can use the very helpful migrate_plugins command which migrates all installed, enabled plugins in your site. The following lines illustrate how to use the migrate-plugins command:

$ python manage.py migrate_plugins  # migrate all enabled plugins
$ python manage.py migrate_plugins --list  # show migration list of enabled plugins
$ python manage.py migrate_plugins forum  # migrate only forum plugin, if enabled
$ python manage.py migrate_plugins forum 0002  # migrate only 0002* step for forum plugin
$ python manage.py migrate_plugins forum 0001_initial  # fakes 0001_initial step for forum plugin