18. Extending Cascade¶
All Cascade plugins are derived from the same base class
cmsplugin_cascade.plugin_base.CascadeModelBase
, which stores all its model fields inside a
dictionary, serialized as JSON string in the database. This makes it much easier to extend the
Cascade eco-system, since no database migration [1] is required when adding a new, or
extending plugins from this project.
The database model CascadeModelBase
stores all the plugin settings in a single JSON field named
glossary
. This in practice behaves like a Django context, but in order to avoid confusion with
the latter, it has been named “glossary”.
Note
Custom Cascade plugins should set the app_label
attribute (see
below). This is important so migrations for the proxy models generated by
Cascade are created in the correct app.
If this attribute is not set, Cascade will default to the left-most
part of the plugin’s module path. So if your plugin lives in
myapp.cascadeplugins
, Cascade will use myapp
as the app label.
We recommend that you always set app_label
explicitly.
18.1. Simple Example¶
This plugin is very simple and just renders static content which has been declared in the template.
from cms.plugin_pool import plugin_pool
from cmsplugin_cascade.plugin_base import CascadePluginBase
class StylishPlugin(CascadePluginBase):
name = 'Stylish Element'
render_template = 'myapp/cascade/stylish-element.html'
plugin_pool.register_plugin(StylishPlugin)
If the editor form pops up for this plugin, a dumb message appears: “There are no further settings for this plugin”. This is because no editable fields have been added to that plugin yet.
18.2. Customize the Plugin Editor¶
In order to make the plugin remember its settings and other optional data, we must specify a Django form to be used by the plugin. Since its payload data is stored in a JSON field, we use django-entangled, to map the form fields.
Each of those form fields handles a special field value, or in some cases, a list of field values. They all require one or more Django form fields, which are rendered by the plugins popup editor.
Let’s add a simple selector to choose between a red and a green color. Do this by adding form
to the plugin class.
from django.forms import ChoiceField
from entangled.forms import EntangledModelFormMixin
from cmsplugin_cascade.plugin_base import CascadePluginBase
class StylishFormMixin(EntangledModelFormMixin):
color = ChoiceField(
choices=[('red', 'Red'), ('green', 'Green')],
label="Element's Color",
initial='red',
help_text="Specify the color of the DOM element."
)
class Meta:
entangled_fields = {'glossary': ['color']}
class StylishPlugin(CascadePluginBase):
…
form = StylishFormMixin
In the plugin’s editor, the form now pops up with a single select box, where the user can choose between a red and a green element.
The form StylishFormMixin
inherits from EntangledModelFormMixin
available through the
separate Django app django-entangled. This app allows to edit JSON-Model fields using a standard
Django form. Since djangocms-cascade may extend this form with additional fields, here we use
a special mixin class, rather than a Django ModelForm
. Remember to add class Meta
to this
form, in order to specify the mapping of form fields inside the JSON field named glossary
.
18.3. Special Form Field for Plugin Editors¶
For single text fields or select boxes, Django’s built-in fields, such as CharField
or
ChoiceField
can be used. Sometimes these simple fields are not enough, therefore
djangocms-cascade additionally provides special form fields, which makes it easier to
create editors specialized for styling CSS. These special fields are all part of the module
cmsplugin_cascade.fields
.
SizeField: | When entering measurements, such as margins, paddings, widths, heights, etc, one may choose
between different units, such as px , em , rem or % . This fields validates its
input by checking if a unit is specified. |
---|---|
MultiSizeField: | Use this field to group a list of size input fields together. This for instance is used, to encapsulate all margins into one list inside the JSON object. |
ColorField: | Use this field when the user shall enter a color value. Since the color picker widget built
into the browser often is inconvenient, the special picker a-color-picker can be used instead.
This external library even supports alpha channels. Simply install it into the directory of
the Django project and add node_modules to the list of STATICFILES_DIRS . Since we can
not leave the color field empty, this field adds a checkbox to inform the plugin editor, if no
color is desired. The latter means, that the color is inherited by an upper DOM element. |
BorderChoiceField: | |
Use this field to style borders. It adds three input fields, one to set the border width, one for the border style and one for the border color. The latter uses the special picker a-color-picker, if installed. Otherwise it falls back to the built-in color widget. |
18.4. Overriding the Model¶
Since all djangocms-cascade plugins store their data in a JSON-serializable field, there rarely
is a need to add another database field to the common models CascadeElement
and/or
SharableCascadeElement
and thus no need for database migrations.
However, quite often there is a need to add or override the methods for these models. Therefore each
Cascade plugin creates its own proxy model on the fly. These models inherit from
CascadeElement
and/or SharableCascadeElement
and named like the plugin class, with the
suffix Model
. By default, their behavior is the same as for their parent model classes.
To extend this behavior, the author of a plugin may declare a tuple of mixin classes, which are injected during the creation of the proxy model. Example:
class MySpecialPropertyMixin(object):
def processed_value(self):
value = self.glossary.get('field_name')
# process value
return value
class MySpecialPlugin(LinkPluginBase):
module = 'My Module'
name = 'My special Plugin'
model_mixins = (MySpecialPropertyMixin,)
render_template = 'my_module/my_special_plugin.html'
...
The proxy model created for this plugin class, now contains the extra method processed_value()
,
which for instance may be accessed during template rendering.
templates/my_module/my_special_plugin.html
:
<div>{{ instance.processed_value }}</div>
Needless to say, that you can’t add any extra database fields to the class named
MySpecialPropertyMixin
, since the corresponding model class is marked as proxy.
18.4.1. JavaScript¶
In case your customized plugin requires some Javascript code to improve the editor’s experience, please refer to the section client-side.
18.4.2. Adding extra fields to the model¶
In rare situations, we might want to add extra fields to the model, which inherit from
django.db.models.fields.Field
rather than using django-entangled to emulate this
behavior, by mapping Django form fields to a JSON model field (glossary
).
In other words: We want a real database field.
This can be achieved by creating a Django model inheriting from
cmsplugin_cascade.models_base.CascadeModelBase
and referring to it, such as:
class MyPluginModel(CascadeModelBase):
class Meta:
db_table = 'shop_cart_cascadeelement'
verbose_name = _("Cart Element")
byte_val = models.PositiveSmallIntegerField("Byte Value")
class MySpecialPlugin(LinkPluginBase):
module = 'My Module'
name = 'My special Plugin'
model = MyModel
18.5. Transparent Plugins¶
Some of the plugins in Cascade’s ecosystem are considered as transparent. This means that they logically don’t fit into the given grid-system, but should rather be considered as wrappers of other HTML elements.
For example, the Bootstrap Panel can be added as child of a Column. However, it may contain
exactly the same plugins, as the Column does. Now, instead of adding the PanelPlugin
as
a possible parent to all of our existing Bootstrap plugins, we simply declare the Panel as
“transparent”. It then behaves as it’s own parent, allowing all plugins as children, which
themselves are permitted to be added to that column.
Transparent plugins can be stacked. For example, the Bootstrap Accordion consists of one or more Accordion Panels. Both of them are considered as transparent, which means that we can add all plugins to an Accordion Panels, which we also could add to a Column.
18.6. Plugin Attribute Reference¶
CascadePluginBase
is derived from CMSPluginBase
, so all CMSPluginBase attributes can
also be overridden by plugins derived from CascadePluginBase
. Please refer to their
documentation for details.
Additionally BootstrapPluginBase
allows the following attributes:
name: | This name is shown in the pull down menu in structure view. There is not default value. |
---|---|
app_label: | The app_label to use on generated proxy models. This should usually be the same as the app_label of the app that defines the plugin. |
tag_type: | A HTML element into which this plugin is wrapped. Generic templates can render their
content into any |
require_parent: | Default: Is it required that this plugin is a child of another plugin? Otherwise the plugin can be added to any placeholder. |
parent_classes: | Default: None. A list of Plugin Class Names. If this is set, the plugin may only be added to plugins listed here. |
allow_children: | Default: Can this plugin have child plugins? Or can other plugins be placed inside this plugin? |
child_classes: | Default: A list of plugins, which are allowed as children of this plugin. This differs from
Do not override this attribute. DjangoCMS-Cascade automatically generates a list of allowed
children plugins, by evaluating the list Plugins, which are part of the plugin pool, but which do not specify their parents using the
list |
generic_child_classes: | |
Default: None. A list of plugins which shall be added as children to a plugin, but which themselves do not
declare this plugin in their |
|
default_css_class: | |
Default: None. A CSS class which is always added to the wrapping DOM element. |
|
default_inline_styles: | |
Default: None. A dictionary of inline styles, which is always added to the wrapping DOM element. |
|
get_identifier: | This is a classmethod, which can be added to a plugin to give it a meaningful name. Its signature is: @classmethod
def get_identifier(cls, obj):
return 'A plugin name'
This method shall be used to name the plugin in structured view. |
form: | Override the form used by the plugin editor. This must be a class inheriting from
|
model_mixins: | Tuple of mixin classes, with additional methods to be added the auto-generated proxy model for the given plugin class. Check section “Overriding the Model” for a detailed explanation. |
18.7. Plugin Permissions¶
To register (or unregister) a plugin, simply invoke ./manage.py migrate cmsplugin_cascade
. This
will add (or remove) the content type and the model permissions. We therefore can control in a very
fine grained manner, which user or group is allowed to edit which types of plugins.
Footnotes
[1] | After having created a customized plugin, it must be registered in Django’s permission system, otherwise only administrators, but no staff users, are allowed to add, change or delete them. |