2. Custom Plugins
You have three options to extend Django CMS: Custom plugins, plugin context processors, and plugin processors.
2.1. Writing a custom plugin
You can use python manage.py startapp
to get some basefiles for your plugin, or just add a folder gallery
to your project’s root folder, add an empty __init__.py
, so that the module gets detected.
Suppose you have the following gallery model:
class Gallery(models.Model):
name = models.CharField(max_length=30)
class Picture(models.Model):
gallery = models.ForeignKey(Gallery)
image = models.ImageField(upload_to="uploads/images/")
description = models.CharField(max_length=60)
And that you want to display this gallery between two text blocks.
You can do this with a CMS plugin. To create a CMS plugin you need two components: a CMSPlugin model and a cms_plugins.py file.
2.1.1. Plugin Model
First create a model that links to the gallery via a ForeignKey field:
from cms.models import CMSPlugin
class GalleryPlugin(CMSPlugin):
gallery = models.ForeignKey(Gallery)
Be sure that your model inherits the CMSPlugin class. The plugin model can have any fields it wants. They are the fields that get displayed if you edit the plugin.
Now models.py looks like the following:
from django.db import models
from cms.models import CMSPlugin
class Gallery(models.Model):
parent = models.ForeignKey('self', blank=True, null=True)
name = models.CharField(max_length=30)
def __unicode__(self):
return self.name
def get_absolute_url(self):
return reverse('gallery_view', args=[self.pk])
class Meta:
verbose_name_plural = 'gallery'
class Picture(models.Model):
gallery = models.ForeignKey(Gallery)
image = models.ImageField(upload_to="uploads/images/")
description = models.CharField(max_length=60)
class GalleryPlugin(CMSPlugin):
gallery = models.ForeignKey(Gallery)
Warning
CMSPlugin
subclasses cannot be further subclassed, if you want to make a reusable plugin model, make an abstract base model which does not extend CMSPlugin
and subclass this abstract model as well as CMSPlugin
in your real plugin model. Further note that you cannot name your model fields the same as any plugin’s lowercased model name you use is called, due to the implicit one to one relation Django uses for subclassed models.
2.1.1.1. Handling Relations
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.
To do this you can implement a method called copy_relations
on your plugin model which get’s the old instance of the plugin as argument.
Lets assume this is your plugin:
class ArticlePluginModel(CMSPlugin):
title = models.CharField(max_length=50)
sections = models.ManyToManyField(Section)
def __unicode__(self):
return self.title
Now when the plugin gets copied, you want to make sure the sections stay:
def copy_relations(self, oldinstance):
self.sections = oldinstance.sections.all()
Your full model now:
class ArticlePluginModel(CMSPlugin):
title = models.CharField(max_length=50)
sections = models.ManyToManyField(Section)
def __unicode__(self):
return self.title
def copy_relations(self, oldinstance):
self.sections = oldinstance.sections.all()
2.1.2. cms_plugins.py
After that create in the application folder (the same one where models.py is) a cms_plugins.py file.
In there write the following:
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from models import GalleryPlugin
from django.utils.translation import ugettext as _
class CMSGalleryPlugin(CMSPluginBase):
model = GalleryPlugin
name = _("Gallery")
render_template = "gallery/gallery.html"
def render(self, context, instance, placeholder):
context.update({
'gallery':instance.gallery,
'object':instance,
'placeholder':placeholder
})
return context
plugin_pool.register_plugin(CMSGalleryPlugin)
CMSPluginBase itself inherits from ModelAdmin so you can use all the things (inlines for example) you would use in a regular admin class.
For a list of all the options you have on CMSPluginBase have a look at the plugin reference
2.1.3. Template
Now create a gallery.html template in templates/gallery/
and write the following in there:
{% for image in gallery.picture_set.all %}
<img src="{{ image.image.url }}" alt="{{ image.description }}" />
{% endfor %}
Add a file admin.py
in your plugin root-folder and insert the following:
from django.contrib import admin
from cms.admin.placeholderadmin import PlaceholderAdmin
from models import Gallery,Picture
class PictureInline(admin.StackedInline):
model = Picture
class GalleryAdmin(admin.ModelAdmin):
inlines = [PictureInline]
admin.site.register(Gallery, GalleryAdmin)
Now go into the admin create a gallery and afterwards go into a page and add a gallery plugin and some pictures should appear in your page.
2.1.4. Limiting Plugins per Placeholder
You can limit in which placeholder certain plugins can appear. Add a CMS_PLACEHOLDER_CONF
to your settings.py
.
Example:
CMS_PLACEHOLDER_CONF = {
'col_sidebar': {
'plugins': ('FilePlugin', 'FlashPlugin', 'LinkPlugin', 'PicturePlugin', 'TextPlugin', 'SnippetPlugin'),
'name': gettext("sidebar column")
},
'col_left': {
'plugins': ('FilePlugin', 'FlashPlugin', 'LinkPlugin', 'PicturePlugin', 'TextPlugin', 'SnippetPlugin','GoogleMapPlugin','CMSTextWithTitlePlugin','CMSGalleryPlugin'),
'name': gettext("left column")
},
'col_right': {
'plugins': ('FilePlugin', 'FlashPlugin', 'LinkPlugin', 'PicturePlugin', 'TextPlugin', 'SnippetPlugin','GoogleMapPlugin',),
'name': gettext("right column")
},
}
“col_left” and “col_right” are the names of two placeholders. The plugins list are filled with Plugin class names you find in the cms_plugins.py
. You can add extra context to each placeholder so plugin-templates can react to them.
You can change the displayed name in the admin with the name parameter. In combination with gettext you can translate this names according to the language of the user. Additionally you can limit the number of plugins (either total or by type) for each placeholder with the limits parameter (see Configuration
for details).
2.1.5. Advanced
CMSGalleryPlugin can be even further customized:
Because CMSPluginBase extends ModelAdmin from django.contrib.admin you can use all the things you are used to with normal admin classes. You can define inlines, the form, the form template etc.
Note: If you want to overwrite the form be sure to extend from admin/cms/page/plugin_change_form.html
to have an unified look across the plugins and to have the preview functionality automatically installed.
2.2. Plugin Context Processors
Plugin context processors are callables that modify all plugin’s 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:
# settings.py:
CMS_PLUGIN_CONTEXT_PROCESSORS = (
'yourapp.cms_plugin_context_processors.add_verbose_name',
)
# yourapp.cms_plugin_context_processors.py:
def add_verbose_name(instance, placeholder):
'''
This plugin context processor adds the plugin model's verbose_name to context.
'''
return {'verbose_name': instance._meta.verbose_name}
2.3. Plugin Processors
Plugin processors are callables that modify all plugin’s output after rendering. They are enabled using the CMS_PLUGIN_PROCESSORS
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 that plugin processors are also applied to plugins embedded in Text. 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 DIVs 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.3.1. Example
Suppose you want to put 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:
CMS_PLUGIN_PROCESSORS = (
'yourapp.cms_plugin_processors.wrap_in_colored_box',
)
In your yourapp.cms_plugin_processors.py:
def wrap_in_colored_box(instance, placeholder, rendered_content, original_context):
'''
This plugin processor wraps each plugin's output in a colored box if it is in the "main" placeholder.
'''
if placeholder.slot != 'main' \ # Plugins not in the main placeholder should remain unchanged
or (instance._render_meta.text_enabled # Plugins embedded in Text should remain unchanged in order not to break output
and instance.parent):
return rendered_content
else:
from django.template import Context, Template
# For simplicity's sake, construct the template from a string:
t = Template('<div style="border: 10px {{ border_color }} solid; background: {{ background_color }};">{{ content|safe }}</div>')
# Prepare that template's context:
c = Context({
'content': rendered_content,
# Some plugin models might allow you to customize the colors,
# for others, use default colors:
'background_color': instance.background_color if hasattr(instance, 'background_color') else 'lightyellow',
'border_color': instance.border_color if hasattr(instance, 'border_color') else 'lightblue',
})
# Finally, render the content through that template, and return the output
return t.render(c)