2. Custom Plugins

    This means that your published web content, whatever it is, is kept up-to-date at all times.

    It’s like magic, but quicker.

    Unless you’re lucky enough to discover that your needs can be met by the built-in plugins, or by the many available 3rd-party plugins, you’ll have to write your own custom CMS Plugin. Don’t worry though - writing a CMS Plugin is rather simple.

    A plugin is the most convenient way to integrate content from another Django app into a django CMS page.

    For example, suppose you’re developing a site for a record company in django CMS. You might like to have a “Latest releases” box on your site’s home page.

    Of course, you could every so often edit that page and update the information. However, a sensible record company will manage its catalogue in Django too, which means Django already knows what this week’s new releases are.

    This is an excellent opportunity to make use of that information to make your life easier - all you need to do is create a django CMS plugin that you can insert into your home page, and leave it to do the work of publishing information about the latest releases for you.

    Plugins are reusable. Perhaps your record company is producing a series of reissues of seminal Swiss punk records; on your site’s page about the series, you could insert the same plugin, configured a little differently, that will publish information about recent new releases in that series.

    A django CMS plugin is fundamentally composed of three things.

    • a plugin editor, to configure a plugin each time it is deployed
    • a plugin publisher, to do the automated work of deciding what to publish
    • a plugin template, to render the information into a web page

    These correspond to the familiar Model-View-Template scheme:

    • the plugin model to store its configuration
    • the plugin view that works out what needs to be displayed
    • the plugin template to render the information

    And so to build your plugin, you’ll make it from:

    • a subclass of cms.models.pluginmodel.CMSPlugin to store the configuration for your plugin instances
    • a subclass of cms.plugin_base.CMSPluginBase that defines the operating logic of your plugin
    • a template that renders your plugin

    is actually a subclass of django.contrib.admin.options.ModelAdmin.

    It is its render() method that is the plugin’s view function.

    2.2.2. An aside on models and configuration

    The plugin model, the subclass of cms.models.pluginmodel.CMSPlugin, is actually optional.

    You could have a plugin that doesn’t need to be configured, because it only ever does one thing.

    For example, you could have a plugin that only publishes information about the top-selling record of the past seven days. Obviously, this wouldn’t be very flexible - you wouldn’t be able to use the same plugin for the best-selling release of the last month instead.

    Usually, you find that it is useful to be able to configure your plugin, and this will require a model.

    You may use python manage.py startapp to set up the basic layout for you plugin app. Alternatively, just add a file called cms_plugins.py to an existing Django application.

    In there, you place your plugins. For our example, include the following code:

    Now we’re almost done. All that’s left is to add the template. Add the following into the root template directory in a file called hello_plugin.html:

    1. <h1>Hello {% if request.user.is_authenticated %}{{ request.user.first_name }} {{ request.user.last_name}}{% else %}Guest{% endif %}</h1>

    Now let’s take a closer look at what we did there. The cms_plugins.py files are where you should define your subclasses of , these classes define the different plugins.

    There are three required attributes on those classes:

    • model: The model you wish to use for storing information about this plugin. If you do not require any special information, for example configuration, to be stored for your plugins, you can simply use cms.models.pluginmodel.CMSPlugin (We’ll look at that model more closely in a bit).
    • name: The name of your plugin as displayed in the admin. It is generally good practice to mark this string as translatable using django.utils.translation.ugettext_lazy(), however this is optional.
    • render_template: The template to render this plugin with.

    In addition to those three attributes, you must also define a render() method on your subclasses. It is specifically this render method that is the view for your plugin.

    The render method takes three arguments:

    • context: The context with which the page is rendered.
    • instance: The instance of your plugin that is rendered.
    • placeholder: The name of the placeholder that is rendered.

    This method must return a dictionary or an instance of , which will be used as context to render the plugin template.

    In many cases, you want to store configuration for your plugin instances. For example, if you have a plugin that shows the latest blog posts, you might want to be able to choose the amount of entries shown. Another example would be a gallery plugin where you want to choose the pictures to show for the plugin.

    To do so, you create a Django model by subclassing cms.models.pluginmodel.CMSPlugin in the models.py of an installed application.

    Let’s improve our HelloPlugin from above by making its fallback name for non-authenticated users configurable.

    In our models.py we add the following:

    1. from cms.models.pluginmodel import CMSPlugin
    2. class Hello(CMSPlugin):
    3. guest_name = models.CharField(max_length=50, default='Guest')

    If you followed the Django tutorial, this shouldn’t look too new to you. The only difference to normal models is that you subclass cms.models.pluginmodel.CMSPlugin rather than django.db.models.base.Model.

    Now we need to change our plugin definition to use this model, so our new cms_plugins.py looks like this:

    1. from cms.plugin_base import CMSPluginBase
    2. from cms.plugin_pool import plugin_pool
    3. from django.utils.translation import ugettext_lazy as _
    4. from models import Hello
    5. class HelloPlugin(CMSPluginBase):
    6. name = _("Hello Plugin")
    7. render_template = "hello_plugin.html"
    8. def render(self, context, instance, placeholder):
    9. context['instance'] = instance
    10. return context
    11. plugin_pool.register_plugin(HelloPlugin)

    We changed the model attribute to point to our newly created Hello model and pass the model instance to the context.

    As a last step, we have to update our template to make use of this new configuration:

    The only thing we changed there is that we use the template variable {{ instance.guest_name }} instead of the hardcoded Guest string in the else clause.

    Warning

    cms.models.pluginmodel.CMSPlugin subclasses cannot be further subclassed at the moment. In order to make your plugin models reusable, please use abstract base models.

    Warning

    You cannot name your model fields the same as any installed plugins lower-cased model name, due to the implicit one-to-one relation Django uses for subclassed models. If you use all core plugins, this includes: file, flash, googlemap, link, picture, snippetptr, teaser, twittersearch, twitterrecententries and video.

    Additionally, it is recommended that you avoid using page as a model field, as it is declared as a property of cms.models.pluginmodel.CMSPlugin, and your plugin will not work as intended in the administration without further work.

    If your custom plugin has foreign key or many-to-many relations you are responsible for copying those if necessary whenever the CMS copies the plugin.

    Let’s assume this is your plugin:

    1. class ArticlePluginModel(CMSPlugin):
    2. title = models.CharField(max_length=50)
    3. sections = models.ManyToManyField(Section)
    4. def __unicode__(self):
    5. return self.title

    Now when the plugin gets copied, you want to make sure the sections stay:

    1. def copy_relations(self, oldinstance):
    2. self.sections = oldinstance.sections.all()

    Your full model now:

    1. class ArticlePluginModel(CMSPlugin):
    2. title = models.CharField(max_length=50)
    3. sections = models.ManyToManyField(Section)
    4. def __unicode__(self):
    5. def copy_relations(self, oldinstance):
    6. self.sections = oldinstance.sections.all()

    2.5.1. Plugin form

    Since extends django.contrib.admin.options.ModelAdmin, you can customize the form for your plugins just as you would customize your admin interfaces.

    Note

    If you want to overwrite the form be sure to extend from admin/cms/page/plugin_change_form.html to have a unified look across the plugins and to have the preview functionality automatically installed.

    If your plugin depends on certain media files, javascript or stylesheets, you can include them from your plugin template using django-sekizai. Your CMS templates are always enforced to have the css and js sekizai namespaces, therefore those should be used to include the respective files. For more information about django-sekizai, please refer to the .

    2.5.2.1. Sekizai style

    To fully harness the power of django-sekizai, it is helpful to have a consistent style on how to use it. Here is a set of conventions that should be followed (but don’t necessarily need to be):

    • One bit per addtoblock. Always include one external CSS or JS file per addtoblock or one snippet per addtoblock. This is needed so django-sekizai properly detects duplicate files.
    • External files should be on one line, with no spaces or newlines between the addtoblock tag and the HTML tags.
    • When using embedded javascript or CSS, the HTML tags should be on a newline.

    A good example:

    A bad example:

    1. {% load sekizai_tags %}
    2. {% addtoblock "js" %}<script type="text/javascript" src="{{ MEDIA_URL }}myplugin/js/myjsfile.js"></script>
    3. <script type="text/javascript" src="{{ MEDIA_URL }}myplugin/js/myotherfile.js"></script>{% endaddtoblock %}
    4. {% addtoblock "css" %}
    5. <link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}myplugin/css/astylesheet.css"></script>
    6. {% endaddtoblock %}
    7. {% addtoblock "js" %}<script type="text/javascript">
    8. $(document).ready(function(){
    9. doSomething();
    10. });
    11. </script>{% endaddtoblock %}

    2.5.3. Plugin Context Processors

    Plugin context processors are callables that modify all plugins’ context before rendering. They are enabled using the CMS_PLUGIN_CONTEXT_PROCESSORS setting.

    A plugin context processor takes 2 arguments:

    • instance: The instance of the plugin model
    • placeholder: The instance of the placeholder this plugin appears in.

    The return value should be a dictionary containing any variables to be added to the context.

    Example:

    1. def add_verbose_name(instance, placeholder):
    2. '''
    3. This plugin context processor adds the plugin model's verbose_name to context.
    4. '''
    5. return {'verbose_name': instance._meta.verbose_name}

    Plugin processors are callables that modify all plugins’ output after rendering. They are enabled using the setting.

    A plugin processor takes 4 arguments:

    • instance: The instance of the plugin model
    • placeholder: The instance of the placeholder this plugin appears in.
    • rendered_content: A string containing the rendered content of the plugin.
    • original_context: The original context for the template used to render the plugin.

    Note

    Plugin processors are also applied to plugins embedded in Text plugins (and any custom plugin allowing nested plugins). Depending on what your processor does, this might break the output. For example, if your processor wraps the output in a div tag, you might end up having div tags inside of p tags, which is invalid. You can prevent such cases by returning rendered_content unchanged if instance._render_meta.text_enabled is True, which is the case when rendering an embedded plugin.

    2.5.4.1. Example

    Suppose you want to wrap each plugin in the main placeholder in a colored box but it would be too complicated to edit each individual plugin’s template:

    In your settings.py:

    1. CMS_PLUGIN_PROCESSORS = (
    2. 'yourapp.cms_plugin_processors.wrap_in_colored_box',
    3. )