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

    Static extend the menu entries

  2. AttachMenus

    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

3.1. Menus

Create a menu.py in your application and write the following inside:

  1. from menus.base import Menu, NavigationNode
  2. from menus.menu_pool import menu_pool
  3. from django.utils.translation import ugettext_lazy as _
  4. class TestMenu(Menu):
  5. def get_nodes(self, request):
  6. nodes = []
  7. n = NavigationNode(_('sample root page'), "/", 1)
  8. n2 = NavigationNode(_('sample settings page'), "/bye/", 2)
  9. n3 = NavigationNode(_('sample account page'), "/hello/", 3)
  10. n4 = NavigationNode(_('sample my profile page'), "/hello/world/", 4, 3)
  11. nodes.append(n)
  12. nodes.append(n2)
  13. nodes.append(n3)
  14. nodes.append(n4)
  15. return nodes
  16. menu_pool.register_menu(TestMenu)

If you refresh a page you should now see the menu entries from above. The get_nodes function should return a list of NavigationNode instances. A NavigationNode takes the following arguments:

  • title

    What should the menu entry read?

  • url,

    Link if menu entry is clicked.

  • id

    A unique id for this menu.

  • parent_id=None

    If this is a child of an other node give here the id of the parent.

  • parent_namespace=None

    If the parent node is not from this menu you can give it the parent namespace. The namespace is the name of the class. In the above example that would be: “TestMenu”

    • attr=None

    A dictionary of additional attributes you may want to use in a modifier or in the template.

3.2. Attach Menus

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

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

  1. from menus.base import NavigationNode
  2. from menus.menu_pool import menu_pool
  3. from django.utils.translation import ugettext_lazy as _
  4. from cms.menu_bases import CMSAttachMenu
  5. class TestMenu(CMSAttachMenu):
  6. name = _("test menu")
  7. def get_nodes(self, request):
  8. nodes = []
  9. n = NavigationNode(_('sample root page'), "/", 1)
  10. n2 = NavigationNode(_('sample settings page'), "/bye/", 2)
  11. n3 = NavigationNode(_('sample account page'), "/hello/", 3)
  12. n4 = NavigationNode(_('sample my profile page'), "/hello/world/", 4, 3)
  13. nodes.append(n)
  14. nodes.append(n2)
  15. nodes.append(n3)
  16. nodes.append(n4)
  17. return nodes
  18. 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.

It is encouraged to use django-mptt (a suitable version is included in the mptt directory) for the tree structure because of performance considerations. The objects provided must adhere to the following structure:

Each must have a get_menu_title function, a get_absolute_url function, and a childrens array with all of its children inside (the ‘s’ at the end of childrens is done on purpose because children is already taken by 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 DB 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 there write the following:

  1. from cms.app_base import CMSApp
  2. from cms.apphook_pool import apphook_pool
  3. from django.utils.translation import ugettext_lazy as _
  4. class MyApphook(CMSApp):
  5. name = _("My Apphook")
  6. urls = ["myapp.urls"]
  7. 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.

** ATTENTION ** 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.

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

  1. from django.conf.urls.defaults import *
  2. urlpatterns = patterns('sampleapp.views',
  3. url(r'^$', 'main_view', name='app_main'),
  4. url(r'^sublevel/$', 'sample_view', name='app_sublevel'),
  5. )

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.

Language Namespaces

An additional feature of apphooks is that if you use the MultilingualURLMiddleware all apphook urls are language namespaced.

What this means:

To reverse the first url from above you would use something like this in your template:

  1. {% url app_main %}

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

  1. {% url de:app_main %}
  2. {% url en:app_main %}
  3. {% url fr:app_main %}

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:

  1. from myapp.menu import MyAppMenu
  2. class MyApphook(CMSApp):
  3. name = _("My Apphook")
  4. urls = ["myapp.urls"]
  5. menus = [MyAppMenu]
  6. 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:

  1. from django.db import models
  2. from django.core.urlresolvers import reverse
  3. import mptt
  4. class Category(models.Model):
  5. parent = models.ForeignKey('self', blank=True, null=True)
  6. name = models.CharField(max_length=20)
  7. def __unicode__(self):
  8. return self.name
  9. def get_absolute_url(self):
  10. return reverse('category_view', args=[self.pk])
  11. try:
  12. mptt.register(Category)
  13. except mptt.AlreadyRegistered:
  14. pass

It is encouraged to use django-mptt (a suitable version is included in the mptt directory) if you have data that is organized in a tree.

We would now create a menu out of these categories:

  1. from menus.base import NavigationNode
  2. from menus.menu_pool import menu_pool
  3. from django.utils.translation import ugettext_lazy as _
  4. from cms.menu_bases import CMSAttachMenu
  5. from myapp.models import Category
  6. class CategoryMenu(CMSAttachMenu):
  7. name = _("test menu")
  8. def get_nodes(self, request):
  9. nodes = []
  10. for category in Category.objects.all().order_by("tree_id", "lft"):
  11. nodes.append(NavigationNode(category.name, category.pk, category.parent_id))
  12. return nodes
  13. menu_pool.register_menu(CategoryMenu)

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

  1. from myapp.menus import CategoryMenu
  2. class MyApphook(CMSApp):
  3. name = _("My Apphook")
  4. urls = ["myapp.urls"]
  5. menus = [MyAppMenu, CategoryMenu]

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

3.4. Navigation Modifiers

Navigation Modifiers can add or change properties of NavigationNodes, they even can rearrange whole menus. You normally want to create them in your apps menu.py.

A simple modifier looks something like this:

  1. from menus.base import Modifier
  2. from menus.menu_pool import menu_pool
  3. class MyMode(Modifier):
  4. """
  5. """
  6. def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
  7. if post_cut:
  8. return nodes
  9. count = 0
  10. for node in nodes:
  11. node.counter = count
  12. count += 1
  13. return nodes

It has a function modify that should return a list of NavigationNodes. Modify should take the following arguments:

  • request

    A Django request instance. Maybe you want to modify based on sessions, or user or permissions?

  • nodes

    All the nodes. Normally you want to return them again.

  • namespace

    A Menu Namespace. Only given if somebody requested a menu with only nodes from this namespace.

  • root_id

    Was a menu request based on an ID?

  • post_cut

    Every modifier is called 2 times. First on the whole tree. After that the tree gets cut. To only show the nodes that are shown in the current menu. After the cut the modifiers are called again with the final tree. If this is the case post_cut is True.

  • breadcrumb

    Is this not a menu call but a breadcrumb call?

Here is an example of a build in modifier that marks all nodes level:

  1. class Level(Modifier):
  2. """
  3. marks all node levels
  4. """
  5. post_cut = True
  6. def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
  7. if breadcrumb:
  8. return nodes
  9. for node in nodes:
  10. if not node.parent:
  11. if post_cut:
  12. node.menu_level = 0
  13. else:
  14. node.level = 0
  15. self.mark_levels(node, post_cut)
  16. return nodes
  17. def mark_levels(self, node, post_cut):
  18. for child in node.children:
  19. if post_cut:
  20. child.menu_level = node.menu_level + 1
  21. else:
  22. child.level = node.level + 1
  23. self.mark_levels(child, post_cut)

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.