This is the second post in a series of posts on how I create Mezzanine themes. These posts are a walkthrough of how I take an html template (the one I am using is freely available here, and turn it into a fully functional Mezzanine site. I would recommend starting with the first post, if you haven’t yet read it. In that post I went over how I took the downloaded html template and created a base.html file for Mezzanine. These posts assume some level of familiarity with Mezzanine and Django. It is my belief that struggling, failing, trying again and sticking with something is a great way to learn. I'm posting a lot of code but some pieces may be left up to the individual reader to figure out or do for themselves. If you are having a lot of trouble, or get stuck, sound off in the comments below! The final product, Lucid, is available on MEZZaTHEME.
Today's post will be focusing on the Mezzanine Page model, and specifically a HomePage subclass that we will create. Pages are one of the most powerful features of Mezzanine. They provide the foundation for much of the content of a Mezzanine site and when used in conjunction with page processors they can do almost anything a traditional Django view can. (I wanted to just say anything, but added almost in case anyone was feeling nitpicky and figured out something you can do with a view that you can't do with a page processor.)
Page Templates
Since these posts are about developing Mezzanine themes I am focusing a lot on templates. With that in mind a brief discussion of how Mezzanine uses templates with Page types follows. If you are already familiar with how Mezzanine handles templates you can probably safely skip over this.
Page subclass templates
Mezzanine performs a series of checks when choosing what template to render a page with. From the docs:
By default the template pages/page.html is used, but if a custom template exists it will be used instead. The check for a custom template will first check for a template with the same name as the Page instance’s slug, and if not then a template with a name derived from the subclass model’s name is checked for. So given the above example the templates pages/dr-seuss.html and pages/author.html would be checked for respectively.
In the example referenced dr-seuss is the slug of a page and Author is the custom page model being used.
In general you need to create one template per custom page type. Later, when I go over how I create the HomePage Page type I will create a corresponding homepage.html template.
page.html
Although not strictly necessary, all of the built in page type templates (gallery.html, richtextpage.html, etc...) inherit from page.html. This means that anything you put in page.html will be present across all page types (unless it's overridden in the inheriting template). By default page.html sets the title tag, meta descriptions and keywords and other behind the scenes items. While I rarely make modifications to page.html, I have occasionally: for example, when I added a featured image field to the Page model, I wanted to provide a way to display it in the header of any page type. If you want to read more about that check out the section entitled Field Injection in this blog post. Page.html is rarely involved in the design/layout of pages and mainly serves as a way of setting meta data that is common to all Page subclasses. Anyways, enough background: onward!
HomePage: your first Page subclass
Lately I've been making a HomePage Page type for all of the Mezzanine sites I develop. I find that clients really like the ease of updating. Just today, a long-time client told me, "I love how you build the homepage in the backend; it’s super easy." Mezzanine does not require the creation of a HomePage Page type and it does take a little more work, but I find that it takes the most advantage of Mezzanine's built in features and provides the best UX for end users.
The models
To get started, let's fire up the development server and look at our site in a browser. Currently (as of the end of the last post in this series) going to any page on your development site should show you the home page. We will fix that a bit later but for now simply being able to look at the home page is what we want. Ignoring the header and footer and scanning the page I see:
- A slider (this one is just images, sometimes there will be text, links, etc...)
- Four “icon/title/text blurb” sections
- A heading with two lines of text
- A “featured works” slider
- An area of free form content
- A “latest news” slider
- A “latest posts” slider
After reviewing I decide that I will eliminate the news slider because for my purposes it is redundant with the posts slider. I will make the free form content area full width to use up the extra space. To allow all this content to be modified in the backend I will make four models: HomePage, Slide, IconBlurb and Portfolio. The models end up looking like this:
from django.db import models from django.utils.translation import ugettext_lazy as _ from mezzanine.core.fields import FileField, RichTextField from mezzanine.core.models import RichText, Orderable, Slugged from mezzanine.pages.models import Page from mezzanine.utils.models import upload_to class HomePage(Page, RichText): ''' A page representing the format of the home page ''' heading = models.CharField(max_length=200, help_text="The heading under the icon blurbs") subheading = models.CharField(max_length=200, help_text="The subheading just below the heading") featured_works_heading = models.CharField(max_length=200, default="Featured Works") featured_portfolio = models.ForeignKey("Portfolio", blank=True, null=True, help_text="If selected items from this portfolio will be featured " "on the home page.") content_heading = models.CharField(max_length=200, default="About us!") latest_posts_heading = models.CharField(max_length=200, default="Latest Posts") class Meta: verbose_name = _("Home page") verbose_name_plural = _("Home pages") class Slide(Orderable): ''' A slide in a slider connected to a HomePage ''' homepage = models.ForeignKey(HomePage, related_name="slides") image = FileField(verbose_name=_("Image"), upload_to=upload_to("theme.Slide.image", "slider"), format="Image", max_length=255, null=True, blank=True) class IconBlurb(Orderable): ''' An icon box on a HomePage ''' homepage = models.ForeignKey(HomePage, related_name="blurbs") icon = FileField(verbose_name=_("Image"), upload_to=upload_to("theme.IconBlurb.icon", "icons"), format="Image", max_length=255) title = models.CharField(max_length=200) content = models.TextField() link = models.CharField(max_length=2000, blank=True, help_text="Optional, if provided clicking the blurb will go here.") class Portfolio(Page): ''' A collection of individual portfolio items ''' class Meta: verbose_name = _("Portfolio") verbose_name_plural = _("Portfolios")
We will flesh out the Portfolio class later, but for now the stub allows us to set up the ForeignKey on HomePage that will allow us to display items from a particular portfolio on a HomePage. Now that we have our models, let's create some migrations:
$ python manage.py schemamigration theme --initial $ python manage.py migrate theme
Next, we need to create an admin.py file in theme and create the admin classes for our models. These are simple enough--a TabularDynamicInlineAdmin for each of Slide and IconBlurb and a subclass of PageAdmin for HomePage that includes the two inlines. Finally we register HomePage with its custom admin and Portfolio with PageAdmin. Fire up the server again and go to the Page section of the admin. If you click the add dropdown you should now have two additional options, Home page and Portfolio.
Next we need to update our project's urls.py so that it looks for a page for the home page, rather than directly rendering index.html. Open your projects urls.py. Comment out or delete this line:
url("^$", direct_to_template, {"template": "index.html"}, name="home"),
and uncomment this line:
url("^$", "mezzanine.pages.views.page", {"slug": "/"}, name="home"),
Fire up the dev server again and try to visit your home page. You will get a page not found error. To remedy this go back to the page admin, and add a home page. Fill out some content for the home page, for now we can leave the slides and icon blurbs empty. Last of all, before saving click the Meta data section and enter /
for the url.
Run the dev server and go to the home page; you should still see the home page, but it's not being driven by the admin yet.
pages/index.html
Create index.html in theme/templates/pages and make it extend pages/page.html (pages/index.html is the template that Mezzanine looks for when the homepage is a Page). You should also load static and mezzanine_tags as we will make use of some of them later. I generally also create homepage.html and have it to extend the newly created pages/index.html. (This is so that a user could create as many Home pages as he/she likes.)
Now open up your base.html and cut any content between the header and footer and replace it with a new block like so:
<!--//header--> {% block all_content %}{% endblock %} <!--footer-->
In your newly created index.html add the all_content block and paste all of the content you cut out of base.html. In a future post we will create some of the more traditional Mezzanine blocks inside of all_content, like title, main and right_panel, but I almost always make an all_content block for use by the home page since the home page's design is usually fairly unique and doesn't include things like a title, breadcrumb menu, right panel, etc…. Your homepage still isn't being driven by Mezzanine and should look exactly the same, but we are getting closer.
It is finally time to start hooking up your index.html template to Mezzanine. Go through and replace the hard coded content with Django template language code. For example my slider code ends up looking like this:
{% if page.homepage.slides.all %} <!--slider--> <div id="main_slider"> <div class="camera_wrap" id="camera_wrap_1"> {% for slide in page.homepage.slides.all %} <div data-src="{{ MEDIA_URL }}{% thumbnail slide.image 1920 690 %}"></div> {% endfor %} </div><!-- #camera_wrap_1 --> <div class="clear"></div> </div> <!--//slider--> {% endif %}
I update the heading/subheading to this:
{% editable page.homepage.heading page.homepage.subheading %} {{ page.homepage.heading }} <span>{{ page.homepage.subheading }}</span> {% endeditable %}
Make sure to use the editable template tag when possible to make editing via the front end possible: it’s a great feature of Mezzanine and easy to implement. After you go through and hook up everything (you can drive the recent posts slider with the blog_recent_posts template tag and we will go over hooking up the featured works in a future post) you will have a fully functional home page that is editable via the Mezzanine admin. Mission accomplished!
This concludes the second post on how I make Mezzanine themes. At this point you should have a base.html that creates a consistent header and footer across the site and a homepage that is fully editable via the admin interface. If you go to any page other than the home page you should see just the header and footer with no other content (since we removed all the other content from base.html). Stay tuned for the next post which will continue this tutorial and get you farther towards creating a complete Mezzanine theme.
Comments