.. _topics-plugins-index:

================
Merengue Plugins
================

.. highlightlang:: html+django

.. _topics-plugins-overview:


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.).

.. admonition:: 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. 

.. _topics-plugins-installation:


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:

  * automatically, if you have set the :setting:`DETECT_BROKEN_PLUGINS` setting to ``True``
    (the default), and if you have access to the plugins admin page.
  * manually, if you execute the ``mark_broken_plugins`` command.

3. Activate it from plugins admin page. See the :ref:`plugins documentation <topics-userguide-plugins>`
   in the Merengue user guide.

.. _topics-plugins-media-files:

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:

.. image:: _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 :setting:`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:

.. image:: _images/broken_plugins_debug.png

.. _topics-plugins-customization:

Plugin Customization
====================

Some plugins have customization parameters which appear in the plugin admin view.
See the :ref:`registry configuration params <topics-registry-configuration-params>`
topic for more details.

.. _topics-plugins-development:


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:

.. code-block:: python

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

.. admonition:: 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
  :ref:`base models <topics-models-base>` 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
  :ref:`plugin configuration reference <topics-plugins-configuration>` 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.)

.. _`Django views`: http://docs.djangoproject.com/en/dev/topics/http/views/#topics-http-views
.. _`Django URLs`: http://docs.djangoproject.com/en/dev/topics/http/urls/#topics-http-urls

.. _topics-plugins-configuration:

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:

.. code-block:: python

    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:

.. code-block:: python

    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:

.. code-block:: python

    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:

.. code-block:: python

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

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

.. code-block:: python

    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 :ref:`blocks reference <topics-blocks>` for more information.
  
  An example:

.. code-block:: python

    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 :ref:`actions reference <topics-actions>` for
  more information.
  
  An example:

.. code-block:: python

    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:

.. code-block:: python

    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:

.. code-block:: python

    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 :ref:`permissions reference <topics-permissions>` for
  more information.
  
  An example:

.. code-block:: python

    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 :ref:`viewlets reference <topics-viewlets>` for
  more information.
  
  An example:

.. code-block:: python

    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:

.. code-block:: python

    class PluginConfig(Plugin):
        # stuff ...

        def get_middlewares(self):
            return ['plugins.fooplugin.middleware.FooMiddleware', ]

.. _`Django middleware reference`: http://docs.djangoproject.com/en/1.1/topics/http/middleware/

* **Execute code after plugin activation**: Implementing the ``hook_post_register(self)``
  method in the ``PluginConfig`` class.
  
  An example:

.. code-block:: python

    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`_).

.. _`plugins directory`: http://tracpub.yaco.es/merengue/browser/trunk/merengueproj/plugins/
.. _`news plugin`: http://tracpub.yaco.es/merengue/browser/trunk/merengueproj/plugins/news/

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:

.. code-block:: python

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

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

.. admonition:: 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:

.. code-block:: bash

    $ 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

.. _`South`: http://south.aeracode.org/
.. _`South docs`: http://south.aeracode.org/docs/
