MEZZaTHEMing (creating Mezzanine themes) Part 1: base.html

This is the first part of a series of blog posts that describes the process that I use to create themes for Mezzanine. In this case you will be following along as I create the theme Lucid, which you can purchase on MEZZaTHEME. Lucid is based on the responsive Twitter Bootstrap template that you can find here. Keep in mind that there are many approaches that could be used to create a theme for Mezzanine and this is just one of them. If you see something that I could do better, notice a bug, or have anything else to say, sound off in the comments below!

This post will focus on how I start out: modifying base.html. Since every Mezzanine template inherits from base.html, the base.html design and layout dictate much of the design and layout of your site. For this reason I always start working with base.html first.

Getting Started

  1. Create a new Mezzanine project and a Django app that will hold static assets and ultimately be the theme:

    $ mezzanine-project lucid
    $ cd lucid
    $ python manage.py startapp theme
    $ mkdir theme/static
    $ python manage.py createdb --noinput
    
  2. Next copy the static files (css, js, images) from the downloaded template to the theme's static directory.

  3. Add "theme", to the INSTALLED_APPS setting in settings.py. It should go above any Mezzanine apps so that theme's templates override Mezzanine's defaults.

Run the server and check your site in a browser to make sure everything is working correctly:

$ python manage.py runserver

Base.html

  1. Copy Mezzanine's base.html template to your theme directory:

    $ python manage.py collecttemplates -t base.html
    $ mv templates/ theme/
    
  2. Open the newly copied base.html and the index.html file from the template we are working with. Copy the css, javascript and any other additional items (for example Google fonts in this case) from the head of index.html to the head of the theme's base.html. (In this case the javascripts are in the footer but I will be moving them to the head).

Be careful to leave any template code intact. For example deleting {% block meta_title %}{% endblock %} would prevent the <title> tag from being set properly. I also usually rename the bootstrap files to include their version. So in this case I renamed bootstrap.css and bootstrap-responsive.css to bootstrap.2.2.1.css and bootstrap-responsive.2.2.1.css. This prevents a the wrong version of bootstrap from being loaded (from mezzanine or any other app you may have installed).

At this point the head section of my base.html looks like this:

<head>

    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width">
    <meta name="keywords" content="{% block meta_keywords %}{% endblock %}">
    <meta name="description" content="{% block meta_description %}{% endblock %}">
    <title>{% block meta_title %}{% endblock %}{% if settings.SITE_TITLE %} | {{ settings.SITE_TITLE }}{% endif %}</title>
    <link rel="shortcut icon" href="{% static "img/favicon.ico" %}">

    {% ifinstalled mezzanine.blog %}
    <link rel="alternate" type="application/rss+xml" title="RSS" href="{% url "blog_post_feed" "rss" %}">
    <link rel="alternate" type="application/atom+xml" title="Atom" href="{% url "blog_post_feed" "atom" %}">
    {% endifinstalled %}

    <link href='//fonts.googleapis.com/css?family=Open+Sans:400,600,700,800' rel='stylesheet' type='text/css'>

    {% compress css %}
    <link href="css/prettyPhoto.css" rel="stylesheet" type="text/css" />
    <link rel="stylesheet" id="camera-css"  href="css/camera.css" type="text/css" media="all">
    <link href="css/bootstrap.2.2.1.css" rel="stylesheet">
    <link href="css/theme.css" rel="stylesheet">
    <link rel="stylesheet" type="text/css" href="css/skins/tango/skin.css" />
    <link href="css/bootstrap-responsive.2.2.1.css" rel="stylesheet">
    {% block extra_css %}{% endblock %}
    {% endcompress %}

    <!--[if lt IE 9]>
        <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->

    {% compress js %}
    <script src="{% static "mezzanine/js/"|add:settings.JQUERY_FILENAME %}"></script>
    <script type="text/javascript" src="js/jquery.easing.1.3.js"></script>
    <script type="text/javascript" src="js/jquery.mobile.customized.min.js"></script>
    <script type="text/javascript" src="js/camera.js"></script>
    <script src="js/bootstrap.js"></script>
    <script src="js/superfish.js"></script>
    <script type="text/javascript" src="js/jquery.prettyPhoto.js"></script>
    <script type="text/javascript" src="js/jquery.jcarousel.js"></script>
    <script type="text/javascript" src="js/jquery.tweet.js"></script>
    <script type="text/javascript" src="js/myscript.js"></script>
    <script type="text/javascript">
        $(document).ready(function(){   
            //Slider
            $('#camera_wrap_1').camera();

            //Featured works & latest posts
            $('#mycarousel, #mycarousel2, #newscarousel').jcarousel();                                                  
        });     
    </script>
    {% block extra_js %}{% endblock %}
    {% endcompress %}

    {% block extra_head %}{% endblock %}

</head>

Updating static asset locations

The only outstanding problem with our static assets is that the css and javascript refer to locations that don't exist. To remedy this I use find and replace to add in the static template tag. For example I find href="css and mass replace with href="{% static "css; then I find .css" and replace it with .css" %}". I do the same thing with javascript and now all the css and javascript should be properly linked. Any resources that refer to an outside domain may need to be fixed. For example, if you end up with:

<!--[if lt IE 9]>
    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js" %}"></script>
<![endif]-->

Get rid of the %}"

Setting up the <body>

Finally copy everything from the <body> of the template's index.html and paste it to the body of your theme's base.html replacing everything except {% include "includes/footer_scripts.html" %} at the end. Using some find and replace magic again update the src of all images to properly use the static template tag. Run the dev server again and check the site in a browser: it should look exactly the same as opening the template's index.html in browser. If it doesn't, go back and double check everything. I always start with making sure that all the css files are loading properly. For example, as I was creating this tutorial, I had <link href="{% static "css/bootstrap.2.2.1.css" %}" rel="stylesheet"> in the head of base.html but hadn't actually renamed the corresponding file; this caused the page to not load.

If you tried to go to any other page you would see the exact same thing since we blew out all the blocks when we copied everything over to the body of base.html. This is to be expected and we will add back block_tags later. Now that we have a skeleton of the site up it's time to begin getting content to be driven by Mezzanine.

The Header

The page header (including the logo, menu and social media icons) will be present across most if not all of you Mezzanine project. Starting with the opening body tag I begin working my way down through my base.html.

First up, I change the logo to point to "/" instead of "index.html". This would also be a good time to update the logo. I replaced the image with {{ settings.SITE_TITLE }}, increased the font size and gave it the same line height as the row it is in--but you can do whatever you want.

Moving down I see a ul that contains links to social networks. To make this easy to manage I add a defaults.py to the theme app and corresponding settings; these settings will then be editable in the admin interface of the site, e.g.:

register_setting(
    name="SOCIAL_LINK_FACEBOOK",
    label=_("Facebook link"),
    description=_("If present a Facebook icon linking here will be in the "
        "header."),
    editable=True,
    default="https://facebook.com/mezzatheme",
)

I add each of the setting to TEMPLATE_ACCESSIBLE_SETTINGS and then I update each of the list elements to look like this:

{% if settings.SOCIAL_LINK_FACEBOOK %}<li>
    <a target="_blank" href="{{ settings.SOCIAL_LINK_FACEBOOK }}" class="facebook">Facebook</a>
</li>{% endif %}

Next I come to the dropdown menu. First I copy the pages/menus/dropdown.html to my theme:

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

After making a few minor changes to dropdown.html I replaced the whole menu ul in base.html with:

{% page_menu "pages/menus/dropdown.html" %}

The menu is now driven by Mezzanine. I will leave the actual changes that were made as an exercise for the reader but it was a very small amount of changes (less than I can count on my fingers; don't worry, I don't have eleven fingers or anything like that).

After setting up the dropdown menu the header is finished. All of the content after the end of the header and before the footer is specific to the home page so I skip over it for now and move onto the footer.

The Footer

Along with the header, the footer will also be present across most--if not all--of your Mezzanine project. I use the same approach as I used with the header, starting at the top and working my way down.

The first item I come across is what would have been a javascript Twitter widget. Since Twitter got rid of their open API that was easy to access with javscript this widget will not work. I'll replace it with one of Twitter's widgets which can be created here. I also remove tweet.js from the javscripts in the head and open myscript.js and remove any code that was related to the old Twitter widget.

Continuing on there is a contact form and a testimonials box. To simplify things a bit I will replace both of these with a box that contains the content of settings.SITE_TAGLINE. To accommodate the change in the number of items in the footer I update each of the footer boxes to be span4s instead of span3s.

The last item in the footer widgets is a Flickr widget that pulls in the most recent photos from a Flickr account. Currently the id of this account is hard coded into myscript.js. To make this dynamic I create a setting in the earlier created defaults.py to be the Flickr id that photos are retrieved from. I then pull the Flickr code out of myscript.js and add it to a jquery document ready function directly in the head of base.html. I replace the id with {{ settings.FLICKR_ID }} and the flickr widget is now dynamically controlled.

Next is more standard footer items (logo, copyright notice, another menu, etc..,). I replace the logo with the SITE_TITLE again (also updating the link location) and style it as necessary. I update the copyright notice to display the current year and the correct site name. I update the search box's action to point to {% url 'search' %}, make its method get and change the text input's name to q (since this is what Mezzanine expects).

Next are more social media icons which I handle the same way as I did in the header.

Last of all is another menu, I create a menu template to handle it and insert a call to page_menu into base.html in place of it. Now that all of the menus have been created, I open up the project's settings.py and update PAGE_MENU_TEMPLATES to reflect the menus that are present. It ends up looking like this:

PAGE_MENU_TEMPLATES = (
    (1, "Top navigation bar", "pages/menus/dropdown.html"),
    (2, "Footer", "pages/menus/flat_footer.html"),
)

Final Thoughts

That's it for base.html. Your Mezzanine project has a header and footer that will be present throughout your site.The skeleton of your site is coming together and you are ready to move on! Stay tuned for the next blog post in this series.

Comments