One page websites with Django CMS

Using tools the way they were not really meant to be used

Thorgate
Thorgate
5 min readMay 31, 2016

--

by Alan Kesselmann, Team lead at Thorgate

In April, 2016 DjangoCon was hosted in Budapest. Among the many participants were both our CTO Rivo Laks and Team lead Taavi Teska.

One thing that piqued Taavi‘s curiosity was Django CMS because although we use Django extensively in our projects at Thorgate, we typically resort to either flatpages or Mezzanine to provide content that user can modify, such as sales pages.

I had previously used older versions of Django CMS, but had always ran into one or more annoying issues that could only be overcome by extending something, adding extra functionality, or upgrading the requirements. All in all, my opinion about Django CMS was somewhat reserved, however, Taavi was impressed and chose Django CMS to create a webpage for Hansasoft OÜ.

This website was completed both rapidly and professionally, yet what interested me was Taavi’s use of Django CMS to implement one page navigation in the “products” page. His approach made me think :

Is it possible to create a one page website using Django CMS that is fully editable by the user?

I have always liked to explore the tools we use so that we can use them in the best possible way, however, this does not always mean we use them in the way the authors had intended. My tweaks, presented below, were written with the same creative intent as the guys who implemented the game tetris using Excel.

The Content Management Systems are almost always created in a way that ties a page with its pre-defined content areas using some kind of template. First, one must define page templates, create HTML and CSS styles for them, write the template names into some kind of configuration file and then use an admin interface to create the pages. Pages made using this approach can only use the content provided by their template. That is how it usually works.

At this point I asked:

Can Django CMS be extended to provide all content in a single page?

My initial guess was that I would need to create a template that renders all other templates. Django CMS uses CurrentPageMiddleware which sets the current page for each request, which, in turn, is rendered by the template. After looking into what Django CMS does internally, the “fix” was pretty easy:

base.html:{% load cms_tags ... myproject_tags %}
<div class="container">
{% for page in request.current_page.get_children %}
{% render_page page %}
{% endfor %}
</div>

The render_page template tag you see here is a simple template tag that takes child page as input and returns rendered HTML much like Django CMS does itself. One could create a templatetags folder within the main app of your project and add myproject_tags.py into it, which, in turn, would include this (do not forget to restart your runserver after creating the tag):

from copy import copy
from django import template
from django.template import loader, Context

register = template.Library()

@register.simple_tag(takes_context=True)
def render_page(context, page):
tpl = page.get_template()
request = context['request']
t = loader.get_template(tpl)
c = copy(context)
r = copy(request)
c['request'] = r
setattr(r, 'current_page', page)

return t.render(c)

Until now we have fixed the main part, but there are still issues: Menu for example. By default the CMS menu renders everything, links everything. We, on the other hand, want to keep user on the same page at all times. We also do not want to render the “main” page but only the children of that page. So let’s “fix” the menu too.

In the previously referenced base.html we change the location of menu to this:

{% show_menu_below_id "landing" 0 1 100 100 "cms/menu.html" %}

So the more complete example would be like this:

base.html:{% load cms_tags ... myproject_tags %}...
{% show_menu_below_id "landing" 0 1 100 100 "cms/menu.html" %}
...
<div class="container">
{% for page in request.current_page.get_children %}
{% render_page page %}
{% endfor %}
</div>

The id “landing” refers to the id of the main page. Unfortunately, If you do not set it in the admin, all the other pieces fall apart. The menu template itself could look something like this:

{% load i18n menu_tags cache %}

{% for child in children %}
<li class="nav-link">
<a href="#{{ child.title }}"><span>{{ child.get_menu_title }}</span></a>
</li>
{% endfor %}

Let’s review what we have done so far:

  • Menu, that does not render all the pages, but the pages below “main” page with id “landing”
  • Base template base.html which instead of rendering it’s own content renders all the child pages using template tag.
  • Template tag that we mentioned before

And what is still missing:

  • the child page templates

These templates can be pretty simple. Since we render those sub-pages within <body> tag of single page, they do not need any <head> or <body> elements themselves. They can be something like:

{% load cms_tags i18n myproject_tags %}
<div class="block" id="page_{{ page.get_title }}">
{% placeholder "Content" %}
{% placeholder "Gallery" %}
</div>

As you can see we use the base functionality of Django CMS here. Yet, solving the rendering of subpages in this way does create one serious issue. Lets say you create five subpages using the same content template. If you are in the “create” mode of the site, it becomes very difficult to distinguish which page’s “Content” or “Gallery” block you are currently editing or adding to. To solve this you need to extend the main template within Django CMS.

This template is named dragbar.html and it should be placed in the “cms/toolbar” folder under your templates folder. This is quite a large template, however, it turns out that you only need to change a small part of it:

<div class="cms-dragbar-title">
{{ placeholder.page }}&nbsp;|&nbsp;{{ placeholder.get_label }}
{% if placeholder.is_static %}<span class="cms-hover-tooltip cms-hover-tooltip-right" tabindex="-1" data-cms-tooltip="{% trans 'This is a static placeholder' %}"><span class="cms-icon cms-icon-pin cms-dragarea-static-icon"></span></span>{% endif %}
<span class="cms-dragbar-toggler">
<a href="#" class="cms-dragbar-expand-all">{% trans "Expand all" %}</a>
<a href="#" class="cms-dragbar-collapse-all">{% trans "Collapse all" %}</a>
</span>
</div>

As you can see we write {{ placeholder.page }} before placeholder label. That gives us a way to distinguish different placeholder blocks with same name by their parent page. The output after this change will be Features | Content for the page named “Features”.

In the beginning I was prepared for a much tougher challenge and was ready to twist Django CMS into submission if need be. However, my intended result came surprisingly easy and made me want to give a big thumbs up to the guys at Divio who created this excellent CMS. High fives all around.

If you wish to see the complete example, then check out this repository. It contains simple django-cms project based on our own django project template.

PS. Did you know the biggest Python event in Northern Europe, PyCon Estonia 2019, is just around the corner?
Check it out here: http://bit.ly/pyconestonia

--

--

Thorgate
Thorgate

We are THE digital product company. We think people, not products and that is how we develop world-changing ideas, with passion and for impact.