3. App Integration

It is pretty easy to integrate your own Django applications with django CMS. You have 5 ways of integrating your app:

  1. Menus

    Statically extend the menu entries

  2. Attach Menus

    Attach your menu to a page.

  3. App-Hooks

    Attach whole apps with optional menu to a page.

  4. Navigation Modifiers

    Modify the whole menu tree

  5. Custom Plugins

    Display your models / content in cms pages

  6. Toolbar

    Add Entries to the toolbar to add/edit your models

3.2. Attach Menus

Classes that extend from menus.base.Menu always get attached to the root. But if you want the menu to be attached to a CMS Page you can do that as well.

Instead of extending from Menu you need to extend from cms.menu_bases.CMSAttachMenu and you need to define a name. We will do that with the example from above:

from menus.base import NavigationNode
from menus.menu_pool import menu_pool
from django.utils.translation import ugettext_lazy as _
from cms.menu_bases import CMSAttachMenu

class TestMenu(CMSAttachMenu):

    name = _("test menu")

    def get_nodes(self, request):
        nodes = []
        n = NavigationNode(_('sample root page'), "/", 1)
        n2 = NavigationNode(_('sample settings page'), "/bye/", 2)
        n3 = NavigationNode(_('sample account page'), "/hello/", 3)
        n4 = NavigationNode(_('sample my profile page'), "/hello/world/", 4, 3)
        nodes.append(n)
        nodes.append(n2)
        nodes.append(n3)
        nodes.append(n4)
        return nodes

menu_pool.register_menu(TestMenu)

Now you can link this Menu to a page in the ‘Advanced’ tab of the page settings under attached menu.

Each must have a get_menu_title() method, a get_absolute_url() method, and a childrens list with all of its children inside (the ‘s’ at the end of childrens is done on purpose because children is already taken by django-mptt).

Be sure that get_menu_title() and get_absolute_url() don’t trigger any queries when called in a template or you may have some serious performance and database problems with a lot of queries.

It may be wise to cache the output of get_nodes(). For this you may need to write a wrapper class because of dynamic content that the pickle module can’t handle.

If you want to display some static pages in the navigation (“login”, for example) you can write your own “dummy” class that adheres to the conventions described above.

A base class for this purpose can be found in cms/utils/navigation.py

3.3. App-Hooks

With App-Hooks you can attach whole Django applications to pages. For example you have a news app and you want it attached to your news page.

To create an apphook create a cms_app.py in your application. And in it write the following:

from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from django.utils.translation import ugettext_lazy as _

class MyApphook(CMSApp):
    name = _("My Apphook")
    urls = ["myapp.urls"]

apphook_pool.register(MyApphook)

Replace myapp.urls with the path to your applications urls.py.

Now edit a page and open the advanced settings tab. Select your new apphook under “Application”. Save the page.

Warning

If you are on a multi-threaded server (mostly all webservers, except the dev-server): Restart the server because the URLs are cached by Django and in a multi-threaded environment we don’t know which caches are cleared yet.

Note

If at some point you want to remove this apphook after deleting the cms_app.py there is a cms management command called uninstall apphooks that removes the specified apphook(s) from all pages by name. eg. manage.py cms uninstall apphooks MyApphook. To find all names for uninstallable apphooks there is a command for this as well manage.py cms list apphooks.

If you attached the app to a page with the url /hello/world/ and the app has a urls.py that looks like this:

from django.conf.urls.defaults import *

urlpatterns = patterns('sampleapp.views',
    url(r'^$', 'main_view', name='app_main'),
    url(r'^sublevel/$', 'sample_view', name='app_sublevel'),
)

The main_view should now be available at /hello/world/ and the sample_view has the url /hello/world/sublevel/.

Note

All views that are attached like this must return a RequestContext instance instead of the default Context instance.

3.3.1. Apphook Menus

If you want to add a menu to that page as well that may represent some views in your app add it to your apphook like this:

from myapp.menu import MyAppMenu

class MyApphook(CMSApp):
    name = _("My Apphook")
    urls = ["myapp.urls"]
    menus = [MyAppMenu]

apphook_pool.register(MyApphook)

For an example if your app has a Category model and you want this category model to be displayed in the menu when you attach the app to a page. We assume the following model:

from django.db import models
from django.core.urlresolvers import reverse
import mptt

class Category(models.Model):
    parent = models.ForeignKey('self', blank=True, null=True)
    name = models.CharField(max_length=20)

    def __unicode__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('category_view', args=[self.pk])

try:
    mptt.register(Category)
except mptt.AlreadyRegistered:
    pass

We would now create a menu out of these categories:

from menus.base import NavigationNode
from menus.menu_pool import menu_pool
from django.utils.translation import ugettext_lazy as _
from cms.menu_bases import CMSAttachMenu
from myapp.models import Category

class CategoryMenu(CMSAttachMenu):

    name = _("test menu")

    def get_nodes(self, request):
        nodes = []
        for category in Category.objects.all().order_by("tree_id", "lft"):
            node = NavigationNode(
                category.name,
                category.get_absolute_url(),
                category.pk,
                category.parent_id
            )
            nodes.append(node)
        return nodes

menu_pool.register_menu(CategoryMenu)

If you add this menu now to your app-hook:

from myapp.menus import CategoryMenu

class MyApphook(CMSApp):
    name = _("My Apphook")
    urls = ["myapp.urls"]
    menus = [MyAppMenu, CategoryMenu]

You get the static entries of MyAppMenu and the dynamic entries of CategoryMenu both attached to the same page.

3.3.2. Application and instance namespaces

If you’d like to use application namespaces to reverse the URLs related to your app, you can assign a value to the app_name attribute of your app hook like this:

class MyNamespacedApphook(CMSApp):
    name = _("My Namespaced Apphook")
    urls = ["myapp.urls"]
    app_name = "myapp_namespace"

apphook_pool.register(MyNamespacedApphook)

As seen for Language Namespaces, you can reverse namespaced apps similarly:

{% url myapp_namespace:app_main %}

If you want to access the same url but in a different language use the language templatetag:

{% load i18n %}
{% language "de" %}
    {% url myapp_namespace:app_main %}
{% endlanguage %}

What makes namespaced app hooks really interesting is the fact that you can hook them up to more than one page and reverse their URLs by using their instance namespace. Django CMS takes the value of the reverse_id field assigned to a page and uses it as instance namespace for the app hook.

To reverse the URLs you now have two different ways: explicitly by defining the instance namespace, or implicitely by specifiyng the application namespace and letting the url templatetag resolving the correct application instance by looking at the currently set current_app value.

Note

The official Django documentation has more details about application and instance namespaces, the current_app scope and the reversing of such URLs. You can look it up at https://docs.djangoproject.com/en/dev/topics/http/urls/#url-namespaces

When using the reverse function, the current_app has to be explicitly passed as an argument. You can do so by looking up the current_app attribute of the request instance:

def myviews(request):
    ...
    reversed_url = reverse('myapp_namespace:app_main',
            current_app=request.current_app)
    ...

Or, if you are rendering a plugin, of the context instance:

class MyPlugin(CMSPluginBase):
    def render(self, context, instance, placeholder):
        ...
        reversed_url = reverse('myapp_namespace:app_main',
                current_app=context.current_app)
        ...

3.5. Custom Plugins

If you want to display content of your apps on other pages custom plugins are a great way to accomplish that. For example, if you have a news app and you want to display the top 10 news entries on your homepage, a custom plugin is the way to go.

For a detailed explanation on how to write custom plugins please head over to the Custom Plugins section.

3.6. Toolbar

To add a toolbar items for you app create a cms_toolbar.py in your app.

Example:

from cms.toolbar.items import List, Item
from cms.toolbar_base import CMSToolbar
from cms.toolbar_pool import toolbar_pool
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _


class CategoryToolbar(CMSToolbar):
    def insert_items(self, items, toolbar, request, is_app):
        if request.user.is_staff:
            items[0].items = [Item(reverse('admin:sampleapp_category_changelist'), _('Categories'),
                                   load_side_frame=True)] + items[0].items
            if is_app:
                cat_list = List(reverse('admin:sampleapp_category_changelist'), _('Category'))
                cat_list.items.append(Item(reverse('admin:sampleapp_category_add'), _("Add Category")))
                items.append(cat_list)


toolbar_pool.register(CategoryToolbar)

You extend CMSToolbar and register it with the toolbar_pool. In the insert_items() function you receive some parameters:

  • items

    The current toolbar items. Add new items to this list that extend cms.toolbar.items.BaseItem. You can find most used item types in cms.toolbar.items.

  • toolbar

    The instance of the toolbar.

  • request

    The current request

  • is_app

    Is the current url mapped to this app? True if yes.

If you want to add items based on the current view you can do this in the view itself.

Example:

def category_view(request, id):
    cat = Category.objects.get(pk=id)
    if request.user.is_staff:
        request.toolbar.items[1].items.append(
            Item(reverse('admin:sampleapp_category_change', args=[cat.pk]), "Change Category"))
    return render_to_response('sampleapp/category_view.html',
                              RequestContext(request, {'category': cat}))