On SingletonAdmins and SitewideContent (editing sitewide content in Mezzanine's admin)

The problem

Recently while working on developing a Mezzanine powered site I was asked to make it possible to edit the footer in the Mezzanine admin. In this case the footer contained a block of text that was contact info and two blocks that were links, some within the site, some external. In the past I've created a ContentBlock model that is just a title and RichTextField. I then have created an accompanying templatetag that looks content blocks up by title and displays the associated content field. That approach works but it's brittle because if someone changes the title of a content block it will no longer be displayed. It also results in extra overhead because displaying three sections would require three database calls. This time around I thought about the problem some more, and came up with a superior solution which makes use of Mezzanine's SingletonAdmin class.

For the purpose of this article assume that you are in a similar situation and have a footer that contains three content blocks which you would like editable via the admin. The techniques described could easily be extended to accommodate whatever site-wide content you would like to be editable in Mezzanine's admin.

The models

class SitewideContent(SiteRelated):
    box_one_title = models.CharField(max_length=200, default="Contact us")
    box_one_content = RichTextField()
    box_two_title = models.CharField(max_length=200, default="Sites we like")
    box_two_content = RichTextField()
    box_three_title = models.CharField(max_length=200, default="Highlighted pages")
    box_three_content = RichTextField()

    class Meta:
        verbose_name = _('Sitewide Content')
        verbose_name_plural = _('Sitewide Content')

You may find it odd that I include a title field associated with each content field. While admin's could easily add a heading at the beginning of each content field I find it useful to force the title, constraining admin users slightly and enforcing consistency. In any case it's a subjective choice and you can do whatever suits your situation.

The admin

Next I get the SitewideContent model to show in the admin. There should only be one instance of SitewideContent on a site so I make use of Mezzanine's SingletonAdmin. Mezzanine's SingletonAdmin makes it so that you are never shown the add object screen for a model and are instead redirected to the add form or if an object already exists, redirected to it's change form. This means that there will only ever be a single instance of the model in the databse. The admin.py looks like this:

from django.contrib import admin

from mezzanine.core.admin import SingletonAdmin

from models import SitewideContent

admin.site.register(SitewideContent, SingletonAdmin)

I also modify ADMIN_MENU_ORDER in settings.py to get SitewideContent to show in the Content dropdown in the admin, it looks like this (you will need to replace some_app_name with the name of the app that has the SitewideContent model):

    ("Content", ("pages.Page", "blog.BlogPost", "some_app_name.SitewideContent",
       "generic.ThreadedComment", ("Media Library", "fb_browse"),)),
    ("Site", ("sites.Site", "redirects.Redirect", "conf.Setting")),
    ("Users", ("auth.User", "auth.Group",)),


The final piece of this puzzle is getting this content to show in the footer (or wherever). There are various approaches that could be used (middleware maybe, a context processor, etc...); I ended up creating a tempalettag like so:

from mezzanine import template
from mezzanine.utils.sites import current_site_id

from some_app_name.models import SitewideContent

register = template.Library()

def get_sitewide_content():
    Adds the `SitewideContent` to the context
    return SitewideContent.objects.get_or_create(site_id=current_site_id())[0]

and then finally in base.html I use the templatetag:

{% get_sitewide_content as sitewide %}
{% if sitewide %}
<footer id="footer">
    <div class="container">
        <div class="row-fluid">
            <div class="span4">
                {% editable sitewide.box_one_title sitewide.box_one_content %}
                <h4>{{ sitewide.box_one_title }}</h4>
                {{ sitewide.box_one_content|richtext_filters|safe }}
                {% endeditable %}

            <div class="span4">
                {% editable sitewide.box_two_title sitewide.box_two_content %}
                <h4>{{ sitewide.box_two_title }}</h4>
                {{ sitewide.box_two_content|richtext_filters|safe }}
                {% endeditable %}

            <div class="span4">
                {% editable sitewide.box_three_title sitewide.box_three_content %}
                <h4>{{ sitewide.box_three_title }}</h4>
                {{ sitewide.box_three_content|richtext_filters|safe }}
                {% endeditable %}
        </div><!-- row-fluid -->
{% endif %}

After following these steps there will be a new Sitewide Content link under the Content dropdown in the Mezzanine admin. Clicking it will allow you to edit the footer (or whatever you have set up) that shows up across your entire site.

See anything I did wrong, or want to let me know of any idea of your own, use the comments below!