Contents
Introduction: Greetings Mezzaniners,
When I brought the first version of this site up a I wrote an, ah...difficult to read article on how the site was built using Mezzanine. Oh sure, you could copy/paste diff files and generate the changes for each step in the process. But as a tutorial, almost unreadable and hardly a worthy
guardian of learning.
To go with this new and slightly improved revision of the site is a new and (hopefully) much improved
Mezzanine tutorial.
Part 1: Install Through Pagedown Setup
Preliminaries: Get Ready for Dev
If you're already setup for Django dev you can probably skip some or all of this setup section. If you're new to Python or Django development, or have a freshly minted Ubuntu site and need to get your bearings to work with Mezaanine, this section can help.
I use
virtualenv and
virtualenvwrapper. The former is essential for python dev, the latter recommended. I also install the python
fabric package in system dirs, as it's used by Mezzanine and many other apps for deploy. Note that these can also be installed from apt-get, but those packages won't track as quickly as the pypi repo.
sudo easy_install pip # no pip in ubuntu < 10, make sure
sudo pip install --upgrade virtualenv virtualenvwrapper
sudo pip install --upgrade 'fabric>=1.0'
Compiler and python dev packages are needed. On Ubuntu that's
sudo apt-get --yes install build-essential python-setuptools python-dev \
python-software-properties
Before the Mezzanine install, make sure dev packages are installed to give us jpeg, png and other formats in the
PIL module. Actually, Mezzanine uses
Pillow, which is a "easy on the installee" fork of PIL. Check the lates Pillow docs for requirements. Currently on Ubuntu they are:
sudo apt-get install --yes libtiff4-dev libjpeg8-dev zlib1g-dev libfreetype6-dev \
liblcms1-dev libwebp-dev
Get Mezzanine and Start the Project
I do almost all my python in a virtualenv. With virtualenvwrapper it's
mkvirtualenv rodmtech_net # use your project name as you like
With the Mezzanine install, include
south and
django-compressor. Mezzanine uses django-compressor automatically in its templates, and sets up for south, which can be used, or not.
pip install mezzanine south django-compressor
My current practice is to build the site from the top virtualenv dir, under project
directory. That conforms to how the Mezzanine fabric script deploys. Themezzanine-project
script installs the initial files for the site in that directory.
cdvirtualenv # a virtualenvwrapper function
mezzanine-project project
cd project
pwd >../.project # virtualenvwrapper has a cdproject function that uses this path
OPTIONAL: If you're starting a "real" project, now is a good time to initialize and make a first commit.
# optional scm
hg init .
hg add .
hg commit -m 'initial project setup from mezzanine-project'
Initialize the DB and Test
Let's see what mezzanine-project command produced:
./deploy/ # templates used by fabfile.py
./fabfile.py # python fabric commands to setup and deploy a server
./.gitignore # initial ignore file for git...
./.hgignore # and mercurial
./__init__.py
./local_settings.py # local overrides to settings.py, put install specific stuff here
./manage.py # a *modified* django command wrapper
./requirements/project.txt # pip requirements to install the site
./settings.py # django settings
./urls.py # top level url routing
./wsgi.py # wsgi wrapper for deploy to wsgi servers
At this point, we can populate a dev database and run a vanilla Mezzanine site. The initial mezzanine produced DATABASES var in local_settings.py will use a sqlite database, dev.db. I'm not covering postgres or mysql setups, they're well documented. and standard django stuff.
Mezzanine adds a number of commands to manage.py, including "createdb", a Mezzanine specific alternative to django's "syndb". Createdb runs Django's syncdb and South's migrate command. Createdb also installs sample data, unless you tell it not to with --nodata.
Create the dev db with sample data. With the --noinput option createdb will create one django superuser account, admin/default.
cdproject
python manage.py createdb --noinput
python manage.py runserver
You can browse to http://localhost:8000/ login as admin (password: default) create some pages, blog entries, add photos to the gallery, etc.
Now, let's add pagedown, my preferred markdown integration for Mezzanine. Install
Pygments also, for spiffy code syntax highlighting. While you at it, add those two packages to your pip requirements file.
pip install mezzanine-pagedown Pygments
pip freeze | grep mezzanine-pagedown >>requirements/project.txt
pip freeze | grep Pygments >>requirements/project.txt
I generaly only add the top level projects I directly install to requirements, and let those pacakges call out their dependencies. However, for production, I like to have exactly what I'm developing with. I'll run the following whenever I touch the packages, for use in deploys.
pip freeze >requirements/production.txt
See the pip docs for more info:
Note: if you want to stick with the Tinymce markup editor, you can skip the rest of this section about configurin pagedown.
The pagedown setup is per
these instructions. Summarizing, in settings.py, add
"mezzanine_pagedown"
to the settings.py INSTALLED_APPS list...
INSTALLED_APPS = (
"mezzanine_pagedown",
"django.contrib.admin",
"django.contrib.auth",
and the following somewhere in settings.py (I put it just above DEPLOY SETTINGS). [Why both RICHTEXT_FILTER and RICHTEXT_FILTERS? At the time of this writing,
this ticket was not fixed, but I expect it is now.)
#####################
# PAGEDOWN SETTINGS #
#####################
RICHTEXT_WIDGET_CLASS = 'mezzanine_pagedown.widgets.PageDownWidget'
RICHTEXT_FILTER = 'mezzanine_pagedown.filters.custom'
RICHTEXT_FILTERS = (RICHTEXT_FILTER,)
PAGEDOWN_MARKDOWN_EXTENSIONS = ('extra','codehilite','toc')
RICHTEXT_FILTER_LEVEL = 3
PAGEDOWN_SERVER_SIDE_PREVIEW = True
Server side previews are selected, so near the top of urls.py add
import mezzanine_pagedown.urls
Also in urls.py, the pagedown uri MUST go above the mezzanine "catch all" (which is near the bottom anyway). I'll put it near the top, just under "urlpatterns =".
urlpatterns = patterns("",
("^pagedown/", include(mezzanine_pagedown.urls)),
It's a good time for a sanity check: check a page with markdown syntax.
python manage.py runserver
## hi
:::python
def foo():
return "foo"
1. one
1. two
While you're entering the page content in the admin, you should see the processed markdown displayed below. Save and view the page on the site.
Optional: If you get, and want to get rid of, the following deprecation warning:You haven't defined the ALLOWED_HOSTS settings, which Django 1.5 requires. Will fall back to the domains configured as sites.
add
ALLOWED_HOSTS = ('localhost', '.local')
to your settings.py. That'll do for dev, but you'll want to deal with this properly in production. See the
Django docs on ALLOWED_HOSTS for more information on this important production setting. Btw, Django won't complain about this in debug mode...it's Mezzanine with a
failsafe warning.
Part 2: Add Pygments Styles, a Custom Font and Background
Generate Pygments css
Ok, let's start customizing using a Pygments style for the markdown codehilter extension. Pygments and mezzanine-pagedown were installed and in the previous section, and there is a new manage.py command, pygments_styles
.
python manage.py pygments_styles
which should give you a list like
Usage: ./manage.py pygments_styles <scheme_name>
Available color schemes:
monokai
manni
rrt
perldoc
borland
colorful
...and so on. The same utility will generate customized css for codehilite
python manage.py pygments_styles colorful
Great, so what's the Mezzanine-esque place to put it? Well, there's no one answer. One place you do not want to put it is in the top level static dir. The manage.py collectstatic feature (very handy) dumps all static known to the static-files app there. Then, a capable static delivery system, e.g., Apache, Nginx or a CDN, serve static outside of Django. (The Django development server has been handling that so far, but very inefficient for production.)
Two ways to go here, Django options, not specific to Mezzanine:
add a STATICFILES_DIRS list to settings, with a path to the top level of user supplied static files, or
add an app to the django project via INSTALLED_APPS with a subdir named 'static'.
I prefer the latter because it avoids littering the top level with subdirs. Plus, there's already that top level ./static that will be used by collectstatic, and seperate top level dir for static files I don't like. IMHO, using an app (python module dir, really) is more in tune with current Django practice: Django 1.5, changed from 1.3, creates a subdir of the same name as the top level project, and puts settings and urls there.
Note: mezzanine-project generates and "old" 1.3 layout. It's not difficult to massage that into the current Django scheme. Mezzanine will likely catch up in due time.
It's worth mentioning that in the current
Django docs on static files, in a sidebar called "Static file namespacing" somewhat discourages serving static files from app subdirs. I'm not sure why this is, perhaps it's legacy documentation, or maybe there's reasoning behind it. In any case, I'll create a "main" app, rodmtech, at the top of the INSTALLED_APPS list, insuring it's first in line amongst all the apps.
mkdir -p rodmtech/static/css # replacing rodmtech as needed
touch rodmtech/__init__.py
python manage.py pygments_styles colorful >rodmtech/static/css/codehilite.css
Add the new rodmtech module to settings.INSTALLED_APPS, and take due notice of the
Mezzanine notes on app ordering. Put the app in the list before any other mezzanine apps......it's first seen, first served for statics and templates.
INSTALLED_APPS = (
"rodmtech",
"mezzanine_pagedown",
"django.contrib.admin",
In case you're curious, Django looking in app static dirs isn't entirely automatic either. This feature is controlled by the
STATICFILES_FINDERS setting. It's a good read along with
Managing static files.
Add the new CSS Links to the Base Template
Great, so we've got a codehilite.css, but there's nothing automatic about having a new stylesheet pulled down. Delivered page content is all being produced by templates buried in Mezzanine apps. How do we get at them and start customizating?
What we need to do is
- make copies of the Mezaanine templates,
- customize then, and
- make sure Django finds our copies before the default versions.
You can do that by fishing them out from under site-packages manually, or the preferred way, use the Mezzanine provided collecttemplates
function.
The collecttemplates function lets you be selective, but I'm going to pull them all. (Certainly for figuring out Mezzanine, grabbing all the templates is helpful.) Your choice here really. Alternately, you could just delete templates you don't touch and fall back to those found in the Mezzanine apps.
One argument for keeping most most of the templates in your space: in case a Mezzanine software upgrade changes Mezzanine templates that would break your customizations. The Mezz maintainers are pretty careful about not breaking things, but it can happen depending on how you customize.
python manage.py collecttemplates
That puts all of Mezzanine's templates in the templates dir under you project root dir. Be sure to read about how
Mezzanine finds templates. In fact, read
that whole page carefully if you plan on going deep with Mezzanine.
Django is finding those templates via settings.TEMPLATE_DIRS
. We could also have put them in a templates dir under the rodmrodmtechtech app and the app_directories template loader would find them there. As is, collecttemplates insists on putting them at the top level templates dir. Btw, mezzanine-project setup that TEMPLATE_DIRS variable in settings.py for us. So be it.
Finally, finally, finally: add codehilite.css to the header section in the base template. While doing that, also add a custom.css to override bootstrap, mezzanine and pygments defaults. On top of all that, let's also add a custom font, from the google maintained collection.
In templates/base.html
, find the stylesheet link tags, which should be just under a {% compress css %}
line. Add the custom.css file below the bootstrap and mezzanine entries. The font callout must be above/outside the compress tag; what's inside the "compress css" will get concatanted into a single download file for production, i.e., with DEBUG turned off. A link with an href will not work inside that compress block.
The result should look like
<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Ubuntu">
{% compress css %}
<link rel="stylesheet" href="{% static "css/bootstrap.css" %}">
<link rel="stylesheet" href="{% static "css/mezzanine.css" %}">
<link rel="stylesheet" href="{% static "css/bootstrap-responsive.css" %}">
<link rel="stylesheet" href="{% static "css/codehilite.css" %}">
<link rel="stylesheet" href="{% static "css/custom.css" %}">
Then create rodmtech/static/css/custom.css and fill with
body {
background-color: #ddd;
font-family : 'Ubuntu', sans-serif;
}
to match the font you imported.
Make sure your dev server is running, and create or refresh a page with markdown code rendered. You should get colored code highlighting, a grey background and a new font.
Part 3: Template Layout Customization
Design Plan
Let's get further into Mezzanine customization and really tweak some templates, i.e., specialize the homepage and blog listing page. Let's also get rid of bottom and left menus.
Modify Search
A small change first though. So the user doesn't have to choose what they want to search for, remove the choice of Blog Posts, Pages, and Everything; search will always be Everything. Mezzanine has the [SEARCH_MODEL_CHOICES][30]
setting to control this feature.
SEARCH_MODEL_CHOICES = []
Remove Left and Bottom Menus
Next, remove bottom and left menus. In settings.py, add
PAGE_MENU_TEMPLATES = ( (1, "Top navigation bar", "pages/menus/dropdown.html"), )
underneath the SEARCH_MODEL_CHOICES
just added. However, doing this change only removes menus from admin, not templates. Before we move on to those templates, it's worth taking a look a the discussion near the commented PAGE_MENU_TEMPLATES
, approximately line 44 of settings.py in the current release.
To remove from layout go to templates/base.html and remove
{% page_menu "pages/menus/tree.html" %}
and from down in the footer section remove
{% page_menu "pages/menus/footer.html" %}
With no left menu I like to give wider main content space. The middle of my templates/body.html
change
to
and
to
Make Home Page CMS Editable
Now for the home page. In urls.py look for HOMEPAGE AS AN EDITABLE PAGE IN THE PAGE TREE
. Uncomment the line under the lengthy comment
url("^$", "mezzanine.pages.views.page", {"slug": "/"}, name="home"),
Then, under the HOMEPAGE AS STATIC TEMPLATE
docu-comment, # out the url line.
#url("^$", direct_to_template, {"template": "index.html"}, name="home"),
This change hands the home page over to the mezzanine.pages package views.py file, to the page
function. (You can look it up if you're interested,site-packages/mezzanine/pages/views.py
line 42, atm.) The key point is the home page will now come from the CMS, i.e., the Pages table in the database, specifically, the record with a /
as its slug value.
As soon as we save the urls.py change out,
/
won't load, as that page is not yet in the Pages table. Go to admin, add a page and give it a
/
url (in the Meta section), save it, and then try loading
http://localhost:8000
While we're mucking with pages in the admin, clean out pages you don't want. For my site, I delete the Team, History, Blog, Gallery and Legals pages from Pages, assuming you started with the Mezzanine sample data. Remember, you can also start without sample data using createdb --nodata
for a full custom build.
After you create that new home page, go to the page admin again to reorder, so the new home page is first in the list. Drag that page to first position in the tree of pages...the menu templates will use this order. (Click on the little triangles and drag...took me a minute to figure that out.)
While you're in admin, you might bring up the Site->Settings page and set some things, e.g., Tagline, Site Title, Twitter tokens and query, etc.
If you do setup twitter creds in the admin Settings section, run
python manage.py poll_twitter
to populate the tweet table. You'll want that in a cronjob in production; see deploy/crontab
for an example.
Customize Base and Page Templates
On to that home page customization. What I want on the home page is the CMS content followed by a listing of recent blog posts. Also, I want the twitter feed on this page, but not every page on the site. Note also that the blog listing page has a right hand panel that will stay.
Mezzanine's CMS template rules will use
templates/pages/index.html
for the
/
page from the Pages table. This template is already in our tree from
collecttemplates
.
From here on, the work is standard Django template hacking, not much Mezzanine specific about it, except that Mezzanine gave us the starting templates. Presently, in templates/base.html
there's a
that contains an optional login button and the twitter div. I'm going to move the contents of that div block to templates/pages/index.html
and in `templates/base.html replace it with a block tag that will make the right panel optional.
In base.html, replace
with
class="{% block right_panel_span %}span1{% endblock %} right">
{% block right_panel %}{% endblock %}
Then, cut the remaining interior contents of that div, specifically
{% nevercache %}
{% include "includes/user_panel.html" %}
{% endnevercache %}
class="panel">
{% block right_panel %}
{% ifinstalled mezzanine.twitter %}
{% include "twitter/tweets.html" %}
{% endifinstalled %}
{% endblock %}
from base.html and paste it into the bottom of templates/pages/index.html
.
Above the just pasted div add
{% block right_panel_span %}span3{% endblock %}
Inside that same div find
and its matching
and pull them outside the former div contents. The bottom of templates/pages/index.html
should look like
{% block right_panel_span %}span3{% endblock %}
{% block right_panel %}
{% nevercache %}
{% include "includes/user_panel.html" %}
{% endnevercache %}
class="panel">
{% ifinstalled mezzanine.twitter %}
{% include "twitter/tweets.html" %}
{% endifinstalled %}
{% endblock %}
The
right_panel_span
in base.html lets an extending template specify the width of it's right panel in the base template. Similarly,
right_panel
allows the template to specify contents. If you're not familiar with html templates and that all sounds mysterious, time to read up on
Django template blocks and inheritance.
The net of all this is, if there's no right panel
block, base.html will make that an empty span1 div, for spacing. If the extending template does have a right panel, it can adjust the width via right_panel_span and specify content with right_panel, as previously mentioned.
One more thing: there are template tags new to templates/pages/index.html. At the top, under the "extends", add
{% load mezzanine_tags blog_tags i18n future %}
If you browse the site now, only the home page should have a right panel. Other content pages should not. But one of the reasons I wanted the right panel gone on non-home pages is more room for content. The base template still has a span8 div (was span7 before we took out the left menu) around the main block.
Make that center div configurable with a span10 by default, similar to right_panel_span. In base.html,
changes to
class="{% block main_span %}span10{% endblock %} middle">
and in templates/pages/index.html, just above {% block main %} add
{% block main_span %}span8{% endblock %}
One thing left to do: if you try the top level blog page, it's likely wacked. It's right panel needs fixing. In templates/blog/blog_post_list.html
add
{% block main_span %}span8{% endblock %}
{% block right_panel_span %}span3{% endblock %}
above {% block main %}
Good time to save, check visuals in a browser, and commit, if you're using scm.
Finally, let's add a recent post listing to the home page. In templates/pages/index.html
right underneath the block.super line
{% block blog_recent_posts %}
{% blog_recent_posts 5 as recent_posts %}
{% if recent_posts %}
{% trans "Recent Posts" %}
Be sure to create a blog post to test.
Next is one of the most powerful ways to customize Mezzanine: a new content page type. The new page will be for my documentation section, which is a tree of articles and how-tos on various subjects. I plan to have a top level "Docs" page, with a few subject area pages underneath it, say "Mezzanine", "Django", "Python", etc. Underneath these pages, I'll have "leaf node" articles of content. I want the Docs and subject pages to have a clickable table of contents, after the page content.
Note: I got the hang of extending page schemas by looking at the Mezzanine Gallery model and interface. Good reading.
Start by creating a new model for the page. I'm going to use the rodmtech app created previously. (You could also create and use a new app, withpython manage.py createapp newapp
; if you do, be sure to add it to INSTALLED_APPS
in settings.py.)
As soon as this file is saved, the Django ORM will be out of sync with our database. The site should work, but the admin won't, at least not the Pages section of the admin, until we get things back in sync. Would could use python manage.py syncdb
, but instead get used to using South.
Now the admin doesn't know about our new DocToc page type. Create rodmtech/admin.py and fill with
For a good example of extending the admin for a richer page type, see the galleries app in Mezzanine, i.e.,site-packages/mezzanine/galleries/admin.py
.
After adding a new admin.py, you'll need to exit the dev server, ^C out of manage.py runserver, and python manage.py runserver
again. (Now why does the new rodmtech/admin.py require a restart? Afaict, Django looks for app/admin.py files at startup, to build it's admin site dynamically. If you change one that was already there, ok, "runserver" sees that and reloads. But a whole new admin.py file? Django doesn't catch that.)
Go back to the Pages section of the admin, refresh it if you're already there, and you should see "Doc Page" in the Add dropdown.
Now, the only thing missing are some DocPage pages. In the admin, create a new top level DocPage with the "Add TOC" checkbox checked. Add some additional pages underneath, and test your work.
Thanks for following along. Questions, comments, complaints are welcome.
No comments:
Post a Comment