MEZZaTHEMing Part 3: Pages, extra DRY

This is the third post in my series on creating themes for Mezzanine. Part 1 went over setting up base.html to provide a consistent header and footer across the whole site. Part 2 covered adding your home page to Mezzanine's CMS.

In this post I will describe the process that I use to style the built in page types. I begin with a bit of the philosophy I try to adhere to. After I wax poetic I'll get back to the nuts and bolts, so hang in there!

The DRY Approach

A good goal in programming is to make your code as DRY (don't repeat yourself) as possible. As I develop Mezzanine themes I always try to keep that in mind. Djagno's template inheritance encourages this and if you read the first post in this series you've already seen this in action. The header and footer are defined in base.html and nowhere else. This is great because if you wanted to change something about either of those you would just need to modify base.html and the changes would show up everywhere. The drought (see what I did there?) shouldn't stop with base.html, it should reach as far into your templates as possible.

The default templates that come with Mezzanine are great example of very DRY templates. Most if not all of the built in page types have templates that define no additional markup and just make use of the Django blocks defined in base.html. Whenever I am creating a Mezzanine theme I try to do the same. The more individuated and complex the design for a particular page becomes the more difficult this can become. An extreme example of this is the {% block all_content %} I wrap all of base.html's content blocks with and described in my last post. I use the all_content block as sparingly as is realistically possible (the homepage in the last post is an example of its use), trying to maintain the DRYness of my templates.

Finding water in the desert

Making templates DRY sounds great, but sometimes it is a major pain and it seems much simpler to not worry about it. That may be true on occasion, but usually the short term time savings will not be worth the headaches that can arise if your templates aren't DRY.

The following are some tips, tricks and common idioms that help me make my templates DRYer. Once I remove stuff from the panels to make it shorter, Mezzanine's default base.html looks essentially like this (note that the blocks and main markup are intact):

<div class="container">
    <div class="row">
        <div class="span2 left">
            {% block left_panel %}
            {% endblock %}
        </div>
        <div class="span7 middle">
            {% block main %}{% endblock %}
        </div>
        <div class="span3 right">
            {% block right_panel %}
            {% endblock %}
        </div>
    </div>
</div>

The first thing I would do is add an all_content block, depending on the situation and how I was planning on using it, this would either go outside the container or immediately after the row. The next thing I might do is wrap each of the span divs in another block, i.e.:

{% block left_panel_wrapper %}
<div class="span2 left">
    {% block left_panel %}
    {% endblock %}
</div>
{% endblock %}

Then if I don't want the left panel to show up in a particular template I can simply add this line to it:

{% block left_panel_wrapper %}{% endblock %}

Another trick is to add a span size block to some or all of the main content blocks blocks, i.e.:

<div class="span{% block main_span_size %}7{% endblock %} middle">
    {% block main %}
    {% endblock %}
</div>

After using the first trick to remove the left panel I could add the following to the same template to make the main content fill the extra space:

{% block main_span_size %}9{% endblock %}

Taken together these two tricks can help you avoid writing the same markup in templates with slightly different span configurations.

Often times the markup for the content of two html pages (a blog and gallery for example) will be almost identical save for a different css class. A simple, but shortsited approach would be to use the all_content block and then rewrite the exact same markup changing only a css class. A smarter approach is to add a class block to each of the content blocks (aside: depending on a number of factors including how many shared css classes there are it may or may not make sense to use this trick in combination with the previous span size block trick). These class blocks could look like this:

<div class="span7 {% block main_class %}middle{% endblock %}">
    {% block main %}
    {% endblock %}
</div>

“middle” would be the default class but in a template you could add:

{% block main_class %}gallery{% endblock %}

or

{% block main_class %}blog{% endblock %}

to create the correct styling for your blog or gallery pages.

Using all of these idioms together the content sections of your base.html might end up looking like:

<div class="container">
    <div class="row">

        {% block left_wrapper %}
        <div class="span{% block left_span_size %}2{% endblock %} {% block left_class %}left{% endblock %}">
            {% block left_panel %}
            {% endblock %}
        </div>
        {% endblock %}

        {% block main_wrapper %}
        <div class="span{% block main_span_size %}7{% endblock %} {% block main_class %}middle{% endblock %}">
            {% block main %}{% endblock %}
        </div>
        {% endblock %}

        {% block right_wrapper %}
        <div class="span{% block right_span_size %}3{% endblock %} {% block right_class %}right{% endblock %}">
            {% block right_panel %}
            {% endblock %}
        </div>
        {% endblock %}

    </div>
</div>

Don't get me wrong, I'm not advocating that you use every single one of these tricks in every project; use them when they help you make your code DRYer.

Desert[sic] was first, back to the main course

Now that I have the background and reasoning out of the way I'll show you how to apply those principles to the theme that we are making, Lucid.

Pages

As things stand, if you run your dev server you can visit the home page, but any other page will just show the header and footer. This is because the template blocks that Mezzanine makes use of to display content have not yet been created.

As discussed in detail above, we want to keep our templates DRY: let's open up a few of the html pages from the downloaded template that we are basing our theme on and see how similar they are structurally. I opened up about.html and blog.html, I scrolled down to about line 80 in each of them and compared their <!--page_container--> sections. We are in luck, they are very similar.

One thing I don't like about the downloaded template is that the about page (which I am basing a lot of this on) has a content section that is basically full width with a lot of random span sizes. Rather than have the content pages have a full width content section, I'll have the content be a span8 and show the blog filter panel on pages. Something else to notice about the template is that titles are only shown in the breadcrumb menu. To handle this I'll make a custom breadcrumb menu template that doesn't show the current page and then separately show it with a title block. All in all my base.html (excluding the head, header and footer) now looks like this:

{% block all_content %}
<!--page_container-->
<div class="page_container">
    <div class="breadcrumb">
        <div class="wrap">
            <div class="container">
                <ul class="breadcrumb">
                {% spaceless %}
                {% block breadcrumb_menu %}{% page_menu "pages/menus/breadcrumb.html" %}{% endblock %}
                {% endspaceless %}
                <li class="active">{% block title %}{% endblock %}</li>
                </ul>
            </div>
        </div>
    </div>
    <div class="wrap">
        <div class="container">
            <div class="row">
                <div class="span8">
                    {% block main %}{% endblock %}
                </div>
                <div class="span4">
                    {% block right_panel %}{% include "blog/includes/filter_panel.html" %}{% endblock %}
                </div>
            </div>
        </div>
    </div>
</div>
<!--//page_container-->
{% endblock %}

At this point you could decide you are finished. We had a problem: the header and footer showed on all pages except the home page; this problem has been eliminated through using the blocks that Mezzanine expects; your site will now have a consistent look and feel. If you are satisfied feel free to stop reading, but I am not and will continue writing! The template we are converting to a Mezzanine theme still has a great blog design that we haven't implemented: there is a design for the contact page, and we can create Portfolio content types and then reuse those Portfolio pages for Mezzanine's galleries. I'll go over styling the galleries right now, a later post will cover the blog and portfolio and once you've finished going through all these posts you will be an expert and more than capable of styling the contact page on your own.

Galleries

The portfolio page of the template we are working with is essentially a gallery, it just has some extras that don't fit the requirements of Mezzanine's galleries (categories, links, etc...). With that in mind it should be easy to update the design of Mezzanine Galleries to match.

Let's start by getting the default gallery template:

$ python manage.py collecttemplates -t pages/gallery.html
$ mv templates/pages/gallery.html theme/templates/pages/

Open gallery.html and delete the extra_js block, it's specific to Mezzanine's implementation and we don't need it.

Next open one of the portfolio templates that we downloaded (I'll work with the three column style). In the downloaded template scroll down to about line 80 where it says <!--page_container-->. The breadcrumb menu is the same as before, but there is the extra options div (which we won't use since it doesn't apply to galleries) and rather than a span8 and a span4 the portfolio is just a bunch of span4s, each one a portfolio item (in our case gallery pictures). Its markup and classes are a bit different than the other pages we looked at and used to set up base.html, but we can easily use some of the tips and tricks I discussed before to make it work.

The first difference I notice is that the outer <div class="container"> also has the class inner_content. I contemplate creating a container_class block to insert it, but before doing that I search through all the downloaded template files. The inner_content class is actually never used so it can safely be deleted. I'm tempted to also remove the <div class="projects"> but a search reveals that it is used as a selector in some javascript so it should be included. I end up deciding to create main_wrapper and right_wrapper blocks and simply override those sections. To actually set up the gallery page I copy and paste this into my gallery.html:

<div class="projects">                                  
    <div class="span4 element category01" data-category="category01">
        <div class="hover_img">
            <img src="img/portfolio/1.jpg" alt="" />
            <span class="portfolio_zoom"><a href="img/portfolio/1.jpg" rel="prettyPhoto[portfolio1]"></a></span>
            <span class="portfolio_link"><a href="single_portfolio.html">View item</a></span>
        </div>
        <div class="item_description">
            <h6><a href="single_portfolio.html">Lorem ipsum dolor</a></h6>
            <div class="descr">Et dicta essent vis, sed vitae dictas vulputate ea, ex zril quaeque mentitum quo.</div>
        </div>                                    
    </div>

I then go through and cut and paste relevant parts of the existing gallery code into the code we inserted into the Gallery template. The first thing I do is wrap the span4 div in the forloop that iterates through gallery images. I delete those elements that don't apply and hook up those that do. Finally, I delete the remaining, old gallery markup. In the end, gallery.html looks like this:

{% extends "pages/page.html" %}

{% load mezzanine_tags staticfiles %}

{% block main_wrapper %}
{{ block.super }}

<div class="span12">
{% editable page.gallery.content %}
{{ page.gallery.content|richtext_filters|safe }}
{% endeditable %}
</div>

<div class="projects">
{% with page.gallery.images.all as images %}
{% for image in images %}                                 
    <div class="span4 element">
        <div class="hover_img">
            <img src="{{ MEDIA_URL }}{% thumbnail image.file 370 185 %}" alt="" />
            <span class="portfolio_zoom"><a href="{{ MEDIA_URL }}{% thumbnail image.file 1200 0 %}" rel="prettyPhoto[portfolio1]"></a></span>
        </div>
        <div class="item_description">
            <h6>{{ image.description }}</h6>
        </div>                                    
    </div>
{% endfor %}
{% endwith %}
</div>
{% endblock %}

{% block right_wrapper %}{% endblock %}

and I've updated the base.html content blocks to this:

{% block main_wrapper %}
<div class="span8">
    {% block main %}{% endblock %}
</div>
{% endblock %}
{% block right_wrapper %}
<div class="span4">
    {% block right_panel %}{% include "blog/includes/filter_panel.html" %}{% endblock %}
</div>
{% endblock %}\

Going to a gallery should now show you a page that looks very similar to the downloaded templates portfolio. Hovering over the image provides a link, to show a larger version, but wait...the link is off center. The reason for this is that we deleted another link that was next to it. To fix this open theme/static/css/theme.css and around line 729, in the portfolio section update the last bit of the margin from -33px to -16.5px. The reasoning for this is that the link image is 33px and we want to pull it half of its width to the left so that it is exactly dead center.

If you've made it this far, congratulations. We have more styling to do, but at this point you really have created a full Mezzanine theme. Stay tuned for the next post and ask your questions and let me know what you think so far in the comments below.

Comments