1. Add Customized HTML Snippets¶
When working in Structure Mode as provided by django-CMS, while editing the DOM tree inside a placeholder, we might want to add a HTML snippet which is not part of the Cascade ecosystem. Instead of creating an additional Django template, it often is much easier to just add a customized plugin. This plugin then is available when editing a placeholder in Structure Mode.
1.1. Customized Cascade plugin¶
Creating a customized plugin for the merchant’s implementaion of that e-commerce project is very easy. Just add this small Python module:
from cms.plugin_pool import plugin_pool
from shop.cascade.plugin_base import ShopPluginBase
class MySnippetPlugin(ShopPluginBase):
name = "My Snippet"
render_template = 'myshop/cascade/my-snippet.html'
plugin_pool.register_plugin(MySnippetPlugin)
then, in the project’s settings.py
register that plugin together with all other Cascade
plugins:
CMSPLUGIN_CASCADE_PLUGINS = (
'cmsplugin_cascade.segmentation',
'cmsplugin_cascade.generic',
'cmsplugin_cascade.link',
'shop.cascade',
'cmsplugin_cascade.bootstrap3',
'myshop.cascade',
...
)
The template itself myshop/cascade/my-snippet.html
can contain all templatetags as configured
within the Django project.
Often we want to associate customized styles and/or scripts to work with our new template. Since we honor the principle of encapsulation, we somehow must refer to these files in a generic way. This is where django-sekizai helps us:
{% load static sekizai_tags %}
{% addtoblock "css" %}<link href="{% static 'myshop/css/my-snippet.css' %}" rel="stylesheet" type="text/css" />{% endaddtoblock %}
{% addtoblock "js" %}<script src="{% static 'myshop/js/my-snippet.js' %}" type="text/javascript"></script>{% endaddtoblock %}
<div>
my snippet code goes here...
</div>
Note
The main rendering template requires a block such as {% render_block "css" %}
and
{% render_block "js" %}
which then displays the stylesheets and scripts inside the
appropriate HTML elements.
1.1.1. Further customizing the plugin¶
Sometimes we require additional parameters which shall be customizable by the merchant, while editing the plugin. For Cascade this can be achieved very easily. First think about what kind of data to store, and which form widgets are appropriate for that kind of editor. Say we want to add a text field holding the snippets title, then change the change the plugin code from above to:
class MySnippetPlugin(ShopPluginBase):
...
title = GlossaryField(widgets.TextInput(), label=_("Title"))
Inside the rendering template for that plugin, the newly added title can be accessed as:
<h1>{{ instance.glossary.title }}</h1>
<div>...
Cascade offers many more options than just these. For details please check its reference guide.
1.2. Creating a customized Form snippet¶
Sometimes we might need a dialog form, to store arbitrary information queried from the customer
using a customized form. Say we need to know, when to deliver the goods. This information will be
stored inside the dictionary Cart.extra
and thus transferred automatically to Order.extra
whenever the cart object is converted into an order object.
Our form plugin now must inherit from shop.cascade.plugin_base.DialogFormPluginBase
instead
of our ordinary shop plugin class:
from cms.plugin_pool import plugin_pool
from shop.models.cart import CartModel
from shop.cascade.plugin_base import DialogFormPluginBase
class DeliveryDatePlugin(DialogFormPluginBase):
name = "Delivery Date"
form_class = 'myshop.forms.DeliveryDateForm'
render_template = 'myshop/checkout/delivery-date.html'
def get_form_data(self, context, instance, placeholder):
cart = CartModel.objects.get_from_request(context['request'])
initial = {'delivery_date': getattr(cart, 'extra', {}).get('delivery_date', '')}
return {'initial': initial}
DialogFormPluginBase.register_plugin(DeliveryDatePlugin)
here additionally we have to specify a form_class
. This form class can inherit from
shop.forms.base.DialogForm
or shop.forms.base.DialogModelForm
. Its behavior is
almost identical to its Django’s counterparts:
class DeliveryDateForm(DialogForm):
scope_prefix = 'data.delivery_date'
date = fields.DateField(label="Delivery date")
@classmethod
def form_factory(cls, request, data, cart):
delivery_date_form = cls(data=data)
if delivery_date_form.is_valid():
cart.extra.update(delivery_date_form.cleaned_data)
return delivery_date_form
The scope_prefix
marks the JavaScript object below our AngularJS $scope
. This must be an
identifier which is unique across all dialog forms building up our ecosystem of Cascade plugins.
The classmethod form_factory
must, as its name implies, create a form object of the class it
belongs to. As in our example from above, we use this to update the cart’s extra
dictionary,
whenever the customer submitted a valid delivery date.
The last piece is to put everything together using a form template such as:
{% extends "shop/checkout/dialog-base.html" %}
{% block dialog_form %}
<form name="{{ delivery_date_form.form_name }}" novalidate>
{{ delivery_date_form.as_div }}
</form>
{% endblock %}