How to extend the Toolbar¶
The django CMS toolbar provides an API that allows you to add, remove and manipulate toolbar items in your own code. It helps you to integrate django CMS’s frontend editing mode into your application, and provide your users with a streamlined editing experience.
See also
Extending the Toolbar in the tutorial
Create a cms_toolbars.py
file¶
In order to interact with the toolbar API, you need to create a
CMSToolbar
sub-class in your own code, and register it.
This class should be created in your application’s cms_toolbars.py
file, where it will be
discovered automatically when the Django runserver starts.
You can also use the CMS_TOOLBARS
to control which toolbar classes are loaded.
Define and register a CMSToolbar
sub-class¶
from cms.toolbar_base import CMSToolbar
from cms.toolbar_pool import toolbar_pool
class MyToolbarClass(CMSToolbar):
[...]
toolbar_pool.register(MyToolbarClass)
The cms.toolbar_pool.ToolbarPool.register
method can also be used as a decorator:
@toolbar_pool.register
class MyToolbarClass(CMSToolbar):
[...]
Populate the toolbar¶
Two methods are available to control what will appear in the django CMS toolbar:
populate()
, which is called before the rest of the page is renderedpost_template_populate()
, which is called after the page’s template is rendered
The latter method allows you to manage the toolbar based on the contents of the page, such as the
state of plugins or placeholders, but unless you need to do this, you should opt for the more
simple populate()
method.
class MyToolbar(CMSToolbar):
def populate(self):
# add items to the toolbar
Now you have to decide exactly what items will appear in your toolbar. These can include:
Add links and buttons to the toolbar¶
You can add links and buttons as entries to a menu instance, using the various
add_
methods.
Action |
Text link variant |
Button variant |
---|---|---|
Open link |
||
Open link in sideframe |
||
Open link in modal |
||
POST action |
The basic form for using any of these is:
def populate(self):
self.toolbar.add_link_item( # or add_button(), add_modal_item(), etc
name='A link',
url=url
)
Note that although these toolbar items may take various positional arguments in their methods, we strongly recommend using named arguments, as above. This will help ensure that your own toolbar classes and methods survive upgrades. See the reference documentation linked to in the table above for details of the signature of each method.
Opening a URL in an iframe¶
A common case is to provide a URL that opens in a sideframe or modal dialog on the same page. Administration… in the site menu, that opens the Django admin in a sideframe, is a good example of this. Both the sideframe and modal are HTML iframes.
A typical use for a sideframe is to display an admin list (similar to that used in the tutorial example):
from cms.utils.urlutils import admin_reverse
[...]
class PollToolbar(CMSToolbar):
def populate(self):
self.toolbar.add_sideframe_item(
name='Poll list',
url=admin_reverse('polls_poll_changelist')
)
A typical use for a modal item is to display the admin for a model instance:
self.toolbar.add_modal_item(name='Add new poll', url=admin_reverse('polls_poll_add'))
However, you are not restricted to these examples, and you may open any suitable resource inside the modal or sideframe. Note that protocols may need to match and the requested resource must allow it.
Adding buttons to the toolbar¶
A button is a sub-class of cms.toolbar.items.Button
Buttons can also be added in a list - a ButtonList
is a group of
visually-linked buttons.
def populate(self):
button_list = self.toolbar.add_button_list()
button_list.add_button(name='Button 1', url=url_1)
button_list.add_button(name='Button 2', url=url_2)
Finding existing toolbar items¶
find_items()
and find_first()
¶
Search for items by their type:
def populate(self):
self.toolbar.find_items(item_type=LinkItem)
will find all LinkItem
s in the toolbar (but not for example in the menus in the toolbar - it
doesn’t search other items in the toolbar for items of their own).
find_items()
returns a list of
ItemSearchResult
objects;
find_first()
returns the first object in that list. They
share similar behaviour so the examples here will use find_items()
only.
The item_type
argument is always required, but you can refine the search by using their other
attributes, for example:
self.toolbar.find_items(Menu, disabled=True))
Note that you can use these two methods to search Menu
and SubMenu
classes for items too.
Control the position of items in the toolbar¶
Methods to add menu items to the toolbar take an optional position
argument, that can be
used to control where the item will be inserted.
By default (position=None
) the item will be inserted after existing items in the same level of
the hierarchy (a new sub-menu will become the last sub-menu of the menu, a new menu will be become
the last menu in the toolbar, and so on).
A position of 0
will insert the item before all the others.
If you already have an object, you can use that as a reference too. For example:
def populate(self):
link = self.toolbar.add_link_item('Link', url=link_url)
self.toolbar.add_button('Button', url=button_url, position=link)
will add the new button before the link item.
Finally, you can use a ItemSearchResult
as a position:
def populate(self):
self.toolbar.add_link_item('Link', url=link_url)
link = self.toolbar.find_first(LinkItem)
self.toolbar.add_button('Button', url=button_url, position=link)
and since the ItemSearchResult
can be cast to an integer, you could even do:
self.toolbar.add_button(‘Button’, url=button_url, position=link+1)
Control how and when the toolbar appears¶
By default, your CMSToolbar
sub-class will be active (i.e. its
populate
methods will be called) in the toolbar on every page, when the user is_staff
.
Sometimes however a CMSToolbar
sub-class should only populate the toolbar when visiting pages
associated with a particular application.
A CMSToolbar
sub-class has a useful attribute that can help determine whether a toolbar should
be activated. is_current_app
is True
when the application containing the toolbar class
matches the application handling the request.
This allows you to activate it selectively, for example:
def populate(self):
if not self.is_current_app:
return
[...]
If your toolbar class is in another application than the one you want it to be active for, you can list any applications it should support when you create the class:
supported_apps = ['some_app']
supported_apps
is a tuple of application dotted paths (e.g: supported_apps =
('whatever.path.app', 'another.path.app')
.
The attribute app_path
will contain the name of the application handling the current request
- if app_path
is in supported_apps
, then is_current_app
will be True
.
Modifying an existing toolbar¶
If you need to modify an existing toolbar (say to change an attribute or the behaviour of a method) you can do this by creating a sub-class of it that implements the required changes, and registering that instead of the original.
The original can be unregistered using toolbar_pool.unregister()
, as in the example below.
Alternatively if you originally invoked the toolbar class using CMS_TOOLBARS
, you will
need to modify that to refer to the new one instead.
An example, in which we unregister the original and register our own:
from cms.toolbar_pool import toolbar_pool
from third_party_app.cms_toolbar import ThirdPartyToolbar
@toolbar_pool.register
class MyBarToolbar(ThirdPartyToolbar):
[...]
toolbar_pool.unregister(ThirdPartyToolbar)
Detecting URL changes to an object¶
If you want to watch for object creation or editing of models and redirect after they have been
added or changed add a watch_models
attribute to your toolbar.
Example:
class PollToolbar(CMSToolbar):
watch_models = [Poll]
def populate(self):
...
After you add this every change to an instance of Poll
via sideframe or modal window will
trigger a redirect to the URL of the poll instance that was edited, according to the toolbar
status:
in draft mode the
get_draft_url()
is returned (orget_absolute_url()
if the former does not exist)in live mode, and the method exists,
get_public_url()
is returned.
Frontend¶
If you need to interact with the toolbar, or otherwise account for it in your site’s frontend code, it provides CSS and JavaScript hooks for you to use.
It will add various classes to the page’s <html>
element:
cms-ready
, when the toolbar is readycms-toolbar-expanded
, when the toolbar is fully expandedcms-toolbar-expanding
andcms-toolbar-collapsing
during toolbar animation.
The toolbar also fires a JavaScript event called cms-ready
on the document.
You can listen to this event using jQuery:
CMS.$(document).on('cms-ready', function () { ... });