2.4 release notes#

What’s new in 2.4#


Upgrading from previous versions

2.4 introduces some changes that require action if you are upgrading from a previous version.

You will need to read the sections Migrations overhaul and Added a check command below.

Introducing Django 1.5 support, dropped support for Django 1.3 and Python 2.5#

Django CMS 2.4 introduces Django 1.5 support.

In django CMS 2.4 we dropped support for Django 1.3 and Python 2.5. Django 1.4 and Python 2.6 are now the minimum required versions.

Migrations overhaul#

In version 2.4, migrations have been completely rewritten to address issues with newer South releases.

To ease the upgrading process, all the migrations for the cms application have been consolidated into a single migration file, 0001_initial.py.

  • migration 0001 is a real migration, that gets you to the same point migrations 0001-0036 used to

  • the migrations 0002 to 0036 inclusive still exist, but are now all dummy migrations

  • migrations 0037 and later are new migrations

How this affects you#

If you’re starting with a new installation, you don’t need to worry about this. Don’t even bother reading this section; it’s for upgraders.

If you’re using version 2.3.2 or newer, you don’t need to worry about this either.

If you’re using version 2.3.1 or older, you will need to run a two-step process.

First, you’ll need to upgrade to 2.3.3, to bring your migration history up-to-date with the new scheme. Then you’ll need to perform the migrations for 2.4.

For the two-step upgrade process do the following in your project main directory:

pip install django-cms==2.3.3
python manage.py syncdb
python manage.py migrate
pip install django-cms==2.4
python manage.py migrate

Added delete orphaned plugins command#

Added a management command for deleting orphaned plugins from the database.

The command can be run with:

manage.py cms delete_orphaned_plugins

Please read cms delete-orphaned-plugins before using.

Added a check command#

Added a management command to check your configuration and environment.

To use this command, simply run:

manage.py cms check

This replaces the old at-runtime checks.


Has been removed since it is no longer in use. From 2.4 onward, all pages exist in a public and draft version. Users with the publish_page permission can publish changes to the public site.

Management command required

To bring a previous version of your site’s database up-to-date, you’ll need to run manage.py cms moderator on. Never run this command without first checking for orphaned plugins, using the cms list plugins command. If it reports problems, run manage.py cms delete_orphaned_plugins. Running cms moderator with orphaned plugins will fail and leave bad data in your database. See cms list and cms delete-orphaned-plugins.

Also, check that all your plugins define a copy_relations() method if required. You can do this by running manage.py cms check and read the Presence of “copy_relations” section. See Handling Relations for guidance on this topic.

Added Fix MPTT Management command#

Added a management command for fixing MPTT tree data.

The command can be run with:

manage.py cms fix-mptt

Removed the MultilingualMiddleware#

We removed the MultilingualMiddleware. This removed rather some unattractive monkey-patching of the reverse() function as well. As a benefit we now support localisation of URLs and apphook URLs with standard Django helpers.

For django 1.4 more information can be found here:

If you are still running django 1.3 you are able to achieve the same functionality with django-i18nurl. It is a backport of the new functionality in django 1.4 and can be found here:

What you need to do:

  • Remove cms.middleware.multilingual.MultilingualURLMiddleware from your settings.

  • Be sure django.middleware.locale.LocaleMiddleware is in your settings, and that it comes after the SessionMiddleware.

  • Be sure that the cms.urls is included in a i18n_patterns:

    from django.conf.urls.i18n import i18n_patterns
    from django.contrib import admin
    from django.conf import settings
    from django.urls import *
    urlpatterns = i18n_patterns('',
        re_path(r'^admin/', include(admin.site.urls)),
        re_path(r'^', include('cms.urls')),
    if settings.DEBUG:
        urlpatterns = patterns('',
        re_path(r'^media/(?P<path>.*)$', 'django.views.static.serve',
                {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
        re_path(r'', include('django.contrib.staticfiles.urls')),
    ) + urlpatterns
  • Change your url and reverse calls to language namespaces. We now support the django way of calling other language urls either via {% language %} template tag or via activate("de") function call in views.


    {% url "de:myview" %}


    {% load i18n %}{% language "de" %}
    {% url "myview_name" %}
    {% endlanguage %}
  • reverse urls now return the language prefix as well. So maybe there is some code that adds language prefixes. Remove this code.

Added LanguageCookieMiddleware#

To fix the behaviour of django to determine the language every time from new, when you visit / on a page, this middleware saves the current language in a cookie with every response.

To enable this middleware add the following to your MIDDLEWARE_CLASSES setting:



CMS_LANGUAGES has be overhauled. It is no longer a list of tuples like the LANGUAGES settings.

An example explains more than thousand words:

        1: [
                'code': 'en',
                'name': gettext('English'),
                'fallbacks': ['de', 'fr'],
                'public': True,
                'hide_untranslated': True,
                'code': 'de',
                'name': gettext('Deutsch'),
                'fallbacks': ['en', 'fr'],
                'public': True,
                'code': 'fr',
                'name': gettext('French'),
                'public': False,
        2: [
                'code': 'nl',
                'name': gettext('Dutch'),
                'public': True,
                'fallbacks': ['en'],
        'default': {
            'fallbacks': ['en', 'de', 'fr'],
            'public': False,
            'hide_untranslated': False,

For more details on what all the parameters mean please refer to the CMS_LANGUAGES docs.

The following settings are not needed any more and have been removed:






Please remove them from your settings.py.


Was marked deprecated in 2.3 and has now been removed.

Plugins in Plugins#

We added the ability to have plugins in plugins. Until now only the TextPlugin supported this. For demonstration purposes we created a MultiColumn Plugin. The possibilities for this are endless. Imagine: StylePlugin, TablePlugin, GalleryPlugin etc.

The column plugin can be found here:

At the moment the limitation is that plugins in plugins is only editable in the frontend.

Here is the MultiColumn Plugin as an example:

class MultiColumnPlugin(CMSPluginBase):
    model = MultiColumns
    name = _("Multi Columns")
    render_template = "cms/plugins/multi_column.html"
    allow_children = True
    child_classes = ["ColumnPlugin"]

There are 2 new properties for plugins:


Boolean If set to True it allows adding Plugins.


List A List of Plugin Classes that can be added to this plugin. If not provided you can add all plugins that are available in this placeholder.

How to render your child plugins in the template#

We introduce a new template tag in the cms_tags called {% render_plugin %} Here is an example of how the MultiColumn plugin uses it:

{% load cms_tags %}
<div class="multicolumn">
{% for plugin in instance.child_plugins %}
    {% render_plugin plugin %}
{% endfor %}

As you can see the children are accessible via the plugins children attribute.

New way to handle django CMS settings#

If you have code that needs to access django CMS settings (settings prefixed with CMS_ or PLACEHOLDER_) you would have used for example from django.conf import settings; settings.CMS_TEMPLATES. This will no longer guarantee to return sane values, instead you should use cms.utils.conf.get_cms_setting which takes the name of the setting without the CMS_ prefix as argument and returns the setting.

Example of old, now deprecated style:

from django.conf import settings


Should be replaced with the new API:

from cms.utils.conf import get_cms_setting


Added cms.constants module#

This release adds the cms.constants module which will hold generic django CMS constant values. Currently it only contains TEMPLATE_INHERITANCE_MAGIC which used to live in cms.conf.global_settings but was moved to the new cms.constants module in the settings overhaul mentioned above.

django-reversion integration changes#

django-reversion integration has changed. Because of huge databases after some time we introduce some changes to the way revisions are handled for pages.

  1. Only publish revisions are saved. All other revisions are deleted when you publish a page.

  2. By default only the latest 25 publish revisions are kept. You can change this behaviour with the new CMS_MAX_PAGE_PUBLISH_REVERSIONS setting.

Changes to the show_sub_menu template tag#

The show_sub_menu has received two new parameters. The first stays the same and is still: how many levels of menu should be displayed.

The second: root_level (default=None), specifies at what level, if any, the menu should root at. For example, if root_level is 0 the menu will start at that level regardless of what level the current page is on.

The third argument: nephews (default=100), specifies how many levels of nephews (children of siblings) are shown.

PlaceholderAdmin support i18n#

If you use placeholders in other apps or models we now support more than one language out of the box. If you just use PlaceholderAdmin it will display language tabs like the cms. If you use django-hvad it uses the hvad language tabs.

If you want to disable this behaviour you can set render_placeholder_language_tabs = False on your Admin class that extends PlaceholderAdmin. If you use a custom change_form_template be sure to have a look at cms/templates/admin/placeholders/placeholder/change_form.html for how to incorporate language tabs.


If you have a lot of users (500+) you can set this setting to a number after which admin User fields are displayed in a raw Id field. This improves performance a lot in the admin as it has not to load all the users into the html.

Backwards incompatible changes#

New minimum requirements for dependencies#

  • Django 1.3 and Python 2.5 are no longer supported.

Pending deprecations#

  • simple_language_changer will be removed in version 3.0. A bug-fix makes this redundant as every non-managed URL will behave like this.