4.0.0 release notes#

Note

Version 4.1 is the first community release of django CMS 4. It includes all of the changes mentioned in this section and those mentioned in 4.1.0 release notes. django CMS 3 users seeking to upgrade should immediately go to version 4.1.

Version 4.0 has never been released on pypi but is available on github.

This release of django CMS is a complete rewrite of the core, hugely simplifying what django-cms does out of the box. The main reasons for the changes:

  • Limitations with publishing, where only 2 versions can ever exist

  • Too many “opinions” of how parts of the CMS should work baked in

Warning

Upgrading from previous versions

4.0.0 introduces some changes that require action if you are upgrading from a previous version. Please read below for a step-by-step guide to the process of upgrading from 3.5+ to 4.0.0

The core principles of django-cms 4.0+

  • The CMS core such be simplified

  • The core should not force any opinions

  • The app registry, plugin and wizard pools should be simplified and allow easy registration

  • Allow third parties to define how publishing should work, if anyone needs the feature to work differently they can. This is why publishing was moved to djangocms-versioning.

How to upgrade to 4.0.0#

It is currently recommended to start new projects on django-cms 4.0.0. The changes from django-cms 3.x to 4.x are so different that only 3rd party utilities can assist with the migration such as djangocms-4-migration https://pypi.org/project/djangocms-4-migration/.

Please refer to the guidance within the aforementioned package to perform a migration between projects.

What’s new in 4.0.0#

New features at a glance

  • A revised model structure, delivering huge performance improvements

  • Powerful versioning functionality

  • A new app configuration facility that allows other apps to customise / control other apps by enhancing features.

  • Dedicated Edit, Preview and Structure endpoints. Allows the editing interface to be used by custom models and not just pages.

  • New and improved plugin architecture

  • New Alias Placeholders that are versioned and provide more control (replaces Static placeholders)

Improvements and new features#

Do we comment on changes that have already happened in previous v3 versions, such as Github actions etc??

Bug Fixes#

Removal of deprecated functionality#

  • Removed Page.get_draft()

  • Removed Page.get_published()

  • Removed StaticPlaceholders

Main differences to django CMS 3.x#

The main differences to note in the core CMS which is now extremely simplified are:

  • No concept of publishing, removed because it was limited to just draft and live. An opinionated implementation is now accomplished through djangocms_versioning. Many new concepts exist in this application. The reason that the publishing is external is due to the fact that it is an opinionated implementation. If it is agreed as the way forward by the community it could potentially be brought in as an internal app that compliments the core codebase, similar to how Django is organised internally.

  • CMS app config, allows other apps to customise / control other apps by enabling or disabling features.

  • Dedicated Edit, Preview and Structure endpoints, this allows any applications using Placeholders inside or outside of the CMS (djangocms_alias) to use the same editing experience.

  • New plugin architecture, simplified and no reliance on treebeard which was problematic in the past.

  • Static placeholders are being replaced by djangocms_alias because static placeholders cannot be versioned or allow moderation.

Model changes#

Page, Title (now PageContent) and Placeholder refactor#

There are various changes to the model structure for the Page and PageContents (formerly Title). The most notable is the fact that plugins from different Title instances were all saved in the same Placeholder instance. This has now changed in DjangoCMS 4, a PageContent (formerly Title) instance now contains a dedicated set of Placeholder instances.

The model structure was changed to allow flexibility in the core of the cms, this allowed a package such as djangocms-versioning to create infinite PageContent models.

Data model of CMS < 4#
  • Page (x1 for Draft and x1 for Live)
    • Title Language: “EN”

    • Title Language: “DE”

    • Placeholder Slot: “header”

    • Placeholder Slot: “contents”
      • Plugin 1 Language “EN”

      • Plugin 2 Language “DE”

Data model of CMS >= 4#
  • Page
    • PageContents Language: “EN”
      • Placeholder Slot: “header”

      • Placeholder Slot: “contents”
        • Plugin 1 Language “EN”

    • PageContents Language: “DE”
      • Placeholder Slot: “header”

      • Placeholder Slot: “contents”
        • Plugin 2 Language “DE”

Page, PageContents (Title) and Placeholder relation refactor: https://github.com/django-cms/django-cms/commit /37082d074a4e37a9d2114c4236d526529daa1219

Moving Title to PageContent#

The model structure was changed to allow the core of the cms to be flexible and un-opinionated.

To handle the fact that the Title model is renamed in the CMS you will need to import the PageContent model.

For a djangocms 4.0 only project:

from cms.models import PageContent

For a djangocms 3.x and 4.0 compatible project:

# To handle the fact that the Title model is renamed in the CMS you will need to import the PageContent model.
try:
    from cms.models import PageContent
# django CMS 3.x
except ImportError:
    from cms.models import Title as PageContent

For a djangocms 4.x+ only project:

from cms.models import PageContent

Settings#

New or changed settings added.

CMS_TOOLBAR_ANONYMOUS_ON#

default

False

This setting controls if anonymous users can see the CMS toolbar with a login button when ?toolbat_on is appended to a URL. The default behaviour is to not show the toolbar to anonymous users.

CMS_TOOLBAR_URL__ENABLE#

default

toolbar_on

This setting is used to force the toolbar to show on a page.

CMS_TOOLBAR_URL__DISABLE#

default

toolbar_off

This setting is used to force the toolbar to be hidden on a page.

App registration#

https://github.com/django-cms/django-cms/pull/6421 app registration docs in the description of the PR

  • Add-ons now make use of a new config system; this is to be migrated to all pools. Add-ons can now define whether they support other addons (such as versioning) as well as provide configuration. This is useful in telling features like versioning how to version an add-on.

  • Previously all add-ons would manage their own pool, now it is moving to an app registry based system that will allow centralised control. Although all new add-ons should implement this system the new system will not be depreciated at this time.

  • CMSApp is an existing term from v2.5, it is how apphooks are declared in the newer versions of the cms.

  • CMSAPPConfig is a class, which defines the configuration for a specific add-on, this is then passed to CMSAppExtension. It provides a way of telling the core that an app wants to access something from another app config (the centralized way of handling app config). For example: Alias wants to tell versioning to version it. This requires two components, versioning must define CMSAppExtension, all it needs to do is implement one method, called configure_app, which takes an instance of the CMSAppConfig. In order for an alias app to be linked to it set app_name_enabled=True. When the extension is configured like this the cms will take all the config settings and pass them to the relevant extension, specify models that need to be versioned and which apps need to access this config. CMSAppExtension is the way to register the add-ons and in the future plugins (or plugin_pools) with have their configs defined in CMSAPPConfig.

App configuration example#

An application that defines an app extension can be used by other apps by registering as “enabled” in the CMSAppConfig by adding: “package_with_extension_enabled”:

# A package that defines an app extension for other apps to register with
# myapp/cms_config.py
class MyappCMSExtension(CMSAppExtension):
    def __init__(self):
        self.mylist = []

    def configure_app(self, cms_config):
        if hasattr(cms_config, "myapp_attribute"):
            self.mylist.append(cms_config.myapp_attribute)


# A package that defines a value to add to the extension
# someotherapp/cms_config.py
class SomeotherappCMSConfig(CMSAppConfig):
    # By enabling the someotherapp with myapp, the extension will be used for the someotherapp
    myapp_enabled = True
    # Supply a value to `myapp_attribute` to be added to the myapp cms_config.mylist attribute.
    myapp_attribute = "A string value"

App configuration usage examples in djangocms-url-manager and djangocms-alias#

It is configurable in v4 so you can have another Content Type that you want to work with url manager. here is an example of how url does this for the cms Page, shows you the power of the cms config: https://github.com/django-cms/djangocms-url-manager/blob/acffbeedd3950b9d91f971e7a190b2789d2fe9d9/djangocms_url_manager/cms_config.py#L14

If you had a new Content Type and a new application , you can add the config entry in your third party application and url manager would start to use your model.

Here is an example of djangocms-alias configuring itself for versioning: https://github.com/django-cms/djangocms-alias/blob/7d90b7763278ff74ebe49f70420ecb9f0e2dc4c6/djangocms_alias/cms_config.py#L26 versioning knows nothing about Alias, Alias tells versioning how to use it. No more other apps embedding logic. Obviously Page is configured in url manager by default because it depends on django-cms.

Publishing has been moved to djangocms-versioning#

  • There is no longer the concept of publishing baked into the core of the CMS. By default any content changes are instantly live with no option to unpublish other than to remove altogether.

  • To enable publishing the package djangocms-versioning or other similar package that is Django CMS 4.0+ compatible should be installed.

  • The reason that publishing was removed from the core is because the solution baked in made a lot of assumptions that enforced various limitations on developers. By not providing a publishing method it allows developers to provide their own solutions to the publishing paradigm.

  • Goal is to migrate the monkey patching of versioning into the core to allow a “simple” mode in djangocms-versioning that replaces the 3.x draft/live mode when installing (default option).

See here for the djangocms-versioning documentation.

djangocms-versioning overrides queries from PageContent#

To get all versions regardless of versioning state you can use the “_base_manager”: PageContent._base_manager.all()::

# Get only published PageContents PageContent.objects.all()

# Get all PageContents regardless of the versioning status, be careful with this as it can return archived, draft and published versions! PageContent._base_manager.all()

# Get only draft PageContents from djangcms-versioning.constants import DRAFT PageContent._base_manager.filter(versions__state=DRAFT)

Disabling the admin sideframe#

  • The CMS sideframe in the Django admin caused many issues when navigating through different plugins admin views, the experience it offered left the user confused at the page they were currently on after making various changes, it was also buggy at times. Disable the sideframe by adding the following setting in the settings.py file, it is enabled by default. CMS_SIDEFRAME_ENABLED = False

Plugin refactor#

  • Plugins used to utilise Treebeard. The Treebeard implementation was not coping with this, it was prone to breakage and tree corruption. The refactor simplifies and avoids this by utilising a parent child relationship with plugins. The main issue when replacing the Treebeard implementation was performance, here the standard Django ORM could not provide the query complexity and performance required, individual implementations for the different SQL dialects was implemented to aid performance of plugin queries.

  • Initial plugin refactor: https://github.com/django-cms/django-cms/commit/83d38dbb2e51b4cb65aff5726a1c415de7a1c376

  • Support for other SQL dialects for the plugin tree structure: https://github.com/django-cms/django-cms/commit /4dfaa1c360c2a15f6572b89fc994a254be9e961d

Signals#

Page signals have been merged into pre_obj and post_obj signals for operations on Page. Publishing signals have been removed as of DjangoCMS 4.0 but are available in djangocms-versioning: https://github.com/django-cms/django-cms/commit/03941533670ee9f8c5c078bda8e5cfdd9a639f53

Log Operations#

Previously the logs created were inconsistent and were not created for all page and placeholder operations. Now all page and placeholder operations are logged in the Django Admin model LogEntry. The logs can also be triggered by external apps via using the signals provided in the CMS. https://github.com/django-cms/django-cms/commit/03941533670ee9f8c5c078bda8e5cfdd9a639f53

Placeholder Admin#

The placeholder is now responsible for the edit, structure and preview endpoints. This was previously taken care of by appending ?edit, ?structure and ?preview, This change was made to allow objects that weren’t pages to be viewed and edited in their own way (Alias is an example of this).

  • The views to render the endpoints: render_object_structure, render_object_edit, render_object_preview located at: https://github.com/django-cms/django-cms/blob/release/4.0.x/cms/views.py#L195 The endpoint is determined by using a reverse look up to the registered admin instance using the toolbar utils: (get_object_preview_url, get_object_structure_url, get_object_edit_url) https://github.com/django-cms/django-cms/blob/release/4.0.x/cms/toolbar/utils.py#L122 This is due to the addition of versioning. Previously every add-on was responsible for their edit end points which made it impossible for versioning to bring the correct end point for a specific version. You need to specify cms_toolbar_enabled_models attribute, which is a list of tuples in the following format: (model, render function). model - model you want to be editable

  • render function - a function that takes django.http.HttpRequest object and an object of the model specified above, and returns a django.http.HttpResponse (or any subclass, like TemplateResponse) object based on provided data. Please note that the preview/edit endpoint has changed. Appending ?edit no longer works. There’s a separate endpoint for editing (that the toolbar is aware of and links to when clicking Edit button). One also needs to include cms_enabled = True in the cms config, otherwise that cms_toolbar_enabled_models config won’t be passed to the cms.

  • PlaceholderAdminMixin is deprecated and has a deprecation notice that it will be removed in the next major release: CMS 5.0. https://github.com/django-cms/django-cms/blob/release/4.0.x/cms/admin/placeholderadmin.py#L178

Placeholder relations#

The PlaceholderField has been replaced by the PlaceholderRelationField, the built-in migrations will automatically take care of the replacement, but it can’t however replace the code.

You need to replace your fields such as:

class Post(models.Model):
    ...
    media = PlaceholderField("media", related_name="media")

with:

class Post(models.Model):
    ...
    placeholders = PlaceholderRelationField()

The above you may think is very strange, and you are completely correct. This is because the placeholder relationship is now a GenericForeignKey relationship, so it can handle many different placeholders at once.

To be able to use media again, we can create a property like the below example:

class Post(models.Model):
    ...
    def _get_placeholder_from_slotname(self, slotname):
        try:
            return self.placeholders.get(slot=slotname)
        except Placeholder.DoesNotExist:
            from cms.utils.placeholder import rescan_placeholders_for_obj
            rescan_placeholders_for_obj(self)
            return self.placeholders.get(slot=slotname)

    @cached_property
    def media(self):
        return self._get_placeholder_from_slotname("media")

Placeholder endpoints#

The Placeholder endpoints are designed in a way that allows other third party packages to reuse the edit and preview modes. The major benefit of the reuse is that a third party package can use the views to manage plugins.

Preview end-point#

The preview endpoint replaces what was the ?preview feature in django-cms 3.x

To generate a preview url you can reuse the following snippet, replacing my_page_content_instance with an instance of PageContent:

from cms.toolbar.utils import get_object_preview_url

edit_url = get_object_preview_url(my_page_content_instance)

Edit end-point#

The edit endpoint replaces what was the ?edit feature in django-cms 3.x

To generate an edit url you can reuse the following snippet, replacing my_page_content_instance with an instance of PageContent:

from cms.toolbar.utils import get_object_edit_url

edit_url = get_object_edit_url(my_page_content_instance)

Structure end-point#

The structure endpoint is a endpoint used by the plugin sidebar used when viewing the edit endpoint. It’s where the plugins are rendered and can be dragged & dropped, added and removed.

Configuring you application to use Placeholder endpoint#

We can use djangocms-alias as an example here because this is a very good example of a package that “reuses” the django-cms placeholder endpoints.

Your app should have a placeholder field, djangocms-alias adds this manually. The core CMS has a more advanced technique of adding placeholders by the templates, for django-cms alias we only need one placeholder. Please refer to how the core django-cms package implements this for PageContent if you need more advanced control of Placeholder creation.

It is important that your app uses the concept for djangocms-versioning of a grouper and content model:

# models.py

class AliasContent(models.Model):
    ...
    placeholders = PlaceholderRelationField()
    placeholder_slotname = 'content'

Within your packages cms_config add the following entry:

# cms_config.py

class AliasCMSConfig(CMSAppConfig):
    cms_enabled = True
    cms_toolbar_enabled_models = [(AliasContent, render_alias_content)]

Static Placeholders#

Static Placeholders have been superseded by djangocms-alias, because they cannot be versioned.