Primary image for django CMS is a great toolbox

django CMS is a great toolbox

Here at Lincoln Loop, for the past 15 years, we have built countless Digital experiences (DXP) that help to move our customers’ digital transformation forward.

The ultimate result of what is often a long-term engagement is often a code base that is composed of tens of thousands of lines of code that assemble and extend some key Python / Django components:

The goal here is not to give you a long list of vetted dependencies but to tell you why these two components have been at the core of the solutions we have delivered multiple times.

First, over the years, we learned how to scale this stack efficiently to millions of pageviews a day without worrying about going down every time content gets popular. Eight years ago, I wrote a book about it, High-performance django, with Peter Baumgartner. The secret is to do as little work as possible on every pageview and cache as many of the results as possible. However, this topic differs from the point I want to develop in this article.

Why django CMS is a great toolbox

Extend your models with a PlaceholderField

One outstanding feature of django CMS is that it lends itself to be used out of the box to create a Page driven approach, but you can also open the box and extend your own Model with the django CMS plugin infrastructure, this is done using a PlaceholderField and documented

from django.db import models
from cms.models.fields import PlaceholderField

class Article(models.Model):
    # your fields
    category = models.ForeignKey(
        Category, 
        related_name="primary_articles", 
        on_delete=models.CASCADE)
    body = PlaceholderField('article_body')
    # your methods

I will not paraphrase the documentation on how to hook this into your project entirely, but I want to bring your attention the new world of capability you gain with this feature. You can edit your model with PlaceholderFields in Django’s admin. However, any PlaceholderFields in it will only be available through the frontend editing.

To develop your imagination, I will demonstrate how you could build an online magazine starting with the Article model above.

You can use django CMS Pages to build the top-level sections: news, science, history, … That will display a reverse chronological list of Article related to this current category. This can be done again using django CMS plugin infrastructure where you create an ArticleStream model:

class ArticleStream(CMSPlugin):
    ....
    TEMPLATE_CHOICES = (
        (TEMPLATE_GRID, "Article Grid"),
        (TEMPLATE_ONE_COLUMN, "Article List"),
    )

    category = models.ForeignKey(
        Category, blank=True, null=True, on_delete=models.CASCADE
    )
    template = models.CharField(
        max_length=50, choices=TEMPLATE_CHOICES, default=TEMPLATE_ONE_COLUMN
    )
    title_override = models.CharField(max_length=255, blank=True)
    number = models.PositiveSmallIntegerField(default=3)
    offset = models.PositiveSmallIntegerField(default=0)
 
    def __str__(self):
        return "{0} articles for {1}".format(self.number, self.category)

In addition of the ArticleStream model you will need to build its admin incarnation as subclass of a CMSPluginBase

class ArticleStreamPlugin(CMSPluginBase):
    admin_preview = False
    filter_horizontal = ("category",)
    autocomplete_fields = ("category", "tag", "special")
    model = ArticleStream
    name = "Articles"
    render_template = "plugins/article_stream/article_stream.html"

    def render(self, context, instance: ArticleStream, placeholder):
        current = context.get("object")
        if not isinstance(current, Article):
            current = None

        if instance.category:
            articles = Article.objects.published().filter( category=instance.category)
        else:
            articles = Article.objects.published()

        if instance.offset:
            articles = articles[instance.offset : instance.number + instance.offset]
        else:
            articles = articles[: instance.number]

        context.update(
            {
                "instance": instance,
                "placeholder": placeholder,
                "category": instance.category,
                "title": instance.title_override,
                "articles": articles,
            }
        )
        return context

As you scale up to an unusual amount of pageviews or quantity of objects, you might find yourself in a position where you need to make a different compromise than the one done for the default out-of-the-box experiences by django CMS. This can happen for example with the components like the menus from the page tree or how the Pages or Articles are cached.

It is interesting to note that as you scale up you might move from the django CMS Pages to Category detail page that would also use a Placeholderfield that reuse the ArticleStream model above. The fact that you can reuse the same plugin will ease the transition. You might decide to create the content of the Category.article_stream automatically or manually.

django-filer manages your assets

django-filer manages files and images used by your organization. As with django CMS you can extend its base models to add your attributes. For example, we can add a date_taken, an author, and a credit_link. The order field is used by the plugin that can build a slideshow based on a selected folder.

from filer.models import BaseImage
class Image(BaseImage):
    date_taken = models.DateTimeField(
        _("date taken"), null=True, blank=True, editable=False
    )
    author = models.CharField(_("credit"), max_length=255, null=True, blank=True)
    order = models.PositiveIntegerField(default=0, verbose_name="Slideshow Order")
    credit_link = models.URLField(null=True, blank=True)
    class Meta(BaseImage.Meta):
        app_label = "filer_override"

Once this step is done, you will be left with a bit of work to finalize the admin integration.

django CMS page tree

django CMS ships with an app that lets you build a Page structure. You can think of it as django.contrib.flatpages on steroids. With this component, your team can create and manage a nested page structure supported by an elegant front-end editing approach. It is interesting to note that you can develop plugins that these pages will use. For example, some top-level pages can use a plugin to list articles based on predefined criteria. It is also common to have a plugin that pulls data from an API.

In a nutshell why django CMS is a great toolbox

django CMS does not take over Django’s admin interface but lets you extend it with a plugin infrastructure that supports frontend editing directly from the detailed view of your models. This approach is documented and supported by the framework. You can cherry-pick the components (Pages, Menus, Internationalization, …) you are interested in and easily extend them to meet your business requirements. There are a lot of applications that have been built to integrate well with django CMS, django-filer covers the asset management aspect of your build. PYPI list 303 packages that are somehow related that can at minimum serve as a source of inspiration on what you can do.

Yann Malet

About the author

Yann Malet

Yann builds and architects performant digital platforms for publishers. In 2015, Yann co-authored High-Performance Django with Peter Baumgartner. Prior to his involvement with Lincoln Loop, Yann focused on Product Lifecycle Management systems (PLM) for several large …