LJ Archive

Zotonic: the Erlang Content Management System

Michael Connors

Issue #204, April 2011

It's more than just a CMS. Create complicated Web sites quickly with Zotonic.

Described by its authors as a pragmatic and modern CMS, Zotonic is that and much more. When I started using Zotonic, it was because of its efficiency and the fact that I could pack several client CMS sites onto a machine with only humble resources. I soon discovered, however, that Zotonic is not only a CMS, but also a Web framework, which allows me to create very complicated Web sites in a fraction of the time it would have taken me using more traditional languages and frameworks. Zotonic won't fall over if it encounters an error, and it does not need to be poked with a stick and awakened every time a request comes in.

Zotonic is written in Erlang, a functional language that was designed for programming telephone switches. The logic behind using Erlang for Web development is that modern Web sites, with their plethora of connections from users and robots, are starting to look more and more like telephone exchanges. “I have never programmed in a functional language, and Erlang looks like Dutch to me!”, I hear you say. Well, the authors of Zotonic are fluent in Erlang (and Dutch, incidentally), and they have done a good job of creating a piece of software that is useful out of the box, regardless of whether you know Erlang, and Zotonic could be just the killer app you need to dive in and learn Erlang.

Another attractive feature of Zotonic is its PostgreSQL database (see sidebar). As someone who has toyed with learning Erlang for a while, probably one of the big barriers was that on top of learning a completely new programming paradigm, I also would have to learn a new database in the form of mnesia. Zotonic's use of PostgreSQL means one less new thing to learn and at least allows me to feel in familiar territory when I am designing my data.

Dependencies

I am running the latest version of Ubuntu, which has Erlang preinstalled. You can test whether you have Erlang by typing erl at the command line. If you get the Erlang shell, you are good to go. Press Ctrl-c, followed by the letter a and carriage return to exit Erlang. If you don't have Erlang on your system, you can download it from the Erlang Web site or install it with your distribution's package manager.

Another dependency is ImageMagick; to check whether it's installed, run:

convert -version

You, of course, need to have PostgreSQL installed, and you need Mercurial installed to fetch the latest version of Zotonic from the Google code site.

Installing and Configuring Zotonic

Fetch the Zotonic source and build it:

hg clone https://zotonic.googlecode.com/hg/ zotonic
cd zotonic
make

Now, create a database for Zotonic:

CREATE USER zotonic WITH PASSWORD 'yourdbpassword';
CREATE DATABASE zotonic 
    WITH OWNER = zotonic ENCODING = 'UTF8';
GRANT ALL ON DATABASE zotonic TO zotonic;
\c zotonic
CREATE LANGUAGE "plpgsql";

The Default Site

Zotonic comes complete with an example site, which implements a simple blog. You can find the code for this default site in priv/sites/default/, and you can get this default site running by creating a config file and starting Zotonic.

Find the sample config file in priv/sites/default/config.in, and rename it or create a copy with no extension:

cp priv/sites/default/config.in priv/sites/default/config

Open config in your favourite text editor, and modify it to use the database you just created:

% Hostname for virtual host support
{hostname, "127.0.0.1:8000"},
{hostalias, "localhost:8000"},
% PostgreSQL database connection
{dbhost, "127.0.0.1"},
{dbport, 5432},
{dbuser, "zotonic"},
{dbpassword, "yourdbpassword"},
{dbdatabase, "zotonic"},

Now, start Zotonic in debug mode using start.sh:

./start.sh

You should see text fly by on the console that suggests some tables are being created. Point your browser at 127.0.0.1:8000, and you should see your new blog.

Using Zotonic for Content Management

Zotonic is first and foremost a content management system—that is what it says on the tin. Now that you have a running version of Zotonic, you can try out the content management features.

Point your browser at http://127.0.0.1:8000/admin. You will see a login screen, and as this is your first login, you need to set the password.

Use the login name “admin”, leave the password field empty, and click Log On. You will see the create password form.

Once you have set your password, you are presented with the Zotonic admin dashboard. Down on the left-hand side is the admin menu. Much of it is straightforward, but one of the more interesting items here is modules.

Figure 1. Zotonic Dashboard and Broadcast Dialog

In the Modules menu item, there's a list of available modules, some of them activated, others not. Activate the comments module to see the comments form appear at the bottom of your blog posts.

You can create a new blog post by creating a new page of category “article”.

In the list of pages, find the home page. You also can find it by searching for “home” using the search box on the top right of the admin page.

Open this page for editing, and scroll down it until you see advanced options. Expand the advanced options section, and notice that the home page has a unique name of page_home set. This is useful for referring to this page later in your code.

Customizing the Front End

Zotonic uses a modified version of Erlydtl for templating. Erlydtl is an Erlang implementation of the Django templating language.

Look in the templates folder of the default Zotonic site (priv/sites/default/templates). Here you will find a collection of .tpl files, which are templates that define the site. Templates that start with an underscore are not intended to be rendered alone, but rather can be included in another template.

Most of the templates in this directory inherit from base.tpl, which includes the site's header, menu and footer. This site uses article.tpl for displaying pages of the category “article” and uses home.tpl to display the home page.

Dispatch rules map URIs to resources. Look in the file priv/sites/default/dispatch/dispatch. The following two dispatch rules are defined:

{
     home,  [],
     resource_page, 
     [ {template, "home.tpl"}, {id, page_home} ]
 },
{
     article, ["article", id, slug],
     resource_page,
     [ {template, "article.tpl"}, {cat, article} ]
},

The first rule states that you render the page with the unique name of page_home using the template home.tpl.

The second rule says that you want to render all pages of category article to article.tpl. You also define the structure of the URL in this rule. Each article will have an address of this form: /article/id/page-name.

In both of these examples, you use resource_page to do the actual rendering. This renders a resource as an HTML page and gives you access to the ID and category of the page from the template.

Other text pages you create will be rendered to page.tpl by default.

An about page, with the unique name page_about already exists in the default Zotonic site; it is currently rendered to page.tpl. Let's try making our own template to display the about page.

Create a template in the templates directory called about.tpl, and put the following code in it:


{% extends "base.tpl" %}
    
{% block title %}
    {{ m.rsc[id].title }}
{% endblock %}
    
{% block content %}
    
    <h1>{{ m.rsc[id].title }}
             -- {{ m.rsc[id].summary }}</h1>
   <h2>Hello, this is my about page!</h2>
    {{ m.rsc[id].body|show_media }}
    
{% endblock %}

Add the following to your dispatch rules:

{about,      ["about"],
                  resource_page,
                  [ 
                      {template, "about.tpl"}, 
                      {id, page_about}
                  ]
}

Stop Zotonic, and run make.

Start Zotonic again. Now, if you go to http://127.0.0.1:8000/about in your browser, you will see the text from the default about page rendered using the new template you created.

Having access to the ID of the about resource from the template, you can make calls to the database to retrieve other information to display. As you can see from the template above, I used the title (m.rsc[id].title), the summary (m.rsc[id].summary) and the body (m.rsc[id].body). I also used a “filter” called show_media to convert image markers in the body text into actual image tags for display.

Summary of Some Other Front-End Tools

You already have seen the show_media filter above, and many other filters exist to transform data for output. Other than filters, front-end development in Zotonic is aided by tags and scomps.

In the example above, I used the block tag to replace the content area in the template that I'm extending. Other tags that I use often are if, for and lib:


{% if id == 1 %}
    <p>The ID is 1</p>
{% endif %}
    
{% for color in ["bleu", "blanc", "rouge"] %}
    <p>{{ color }}</p>
{% endfor %}

The lib tag can be used to import an aggregate of stylesheets or scripts to reduce the number of requests to the server:

{% lib 
        "css/zp-menu.css"
        "css/zp-project.css"
    
%}

Scomps, or screen components, are used when tags are not powerful enough and more logic is needed. The scomps that I use most frequently are menu and validate.

Menu is used to insert the standard Zotonic menu into your site:

{% menu id=id %}

Validate is used to validate a form field at both the front end and the back end:


<input 
    type="password" 
    id="password" 
    name="password" value="" />
<input 
    type="password" 
    id="password2" 
    name="password2" value="" />
{% validate id="password" 
    type={confirmation match="password2"} %}

Extending the Back End

If you are willing to write some Erlang code, Zotonic can become much more than just a content management system. You can extend Zotonic with modules. The modules can be stored in the modules subdirectory of your site.

To make a module, create a modules directory within your site if it does not already exist:

mdkir priv/sites/default/modules

Let's create a simple module that implements a Web site guestbook. Users will be able to see the existing guestbook posts and add a new post.

Create a directory called mod_guestbook within the modules directory:

mkdir priv/sites/default/modules/mod_guestbook

Using your favorite text editor, create a file in this directory called mod_guestbook.erl, and in this file, put the following code:


%% @author Michael Connors
%% @doc A guestbook module. 
-module(mod_guestbook).
-author("Michael Connors <michael@bring42.net>").
-mod_title("Guestbook").
-mod_description("A simple guestbook module.").
-mod_prio(500).
    
%% interface functions
-export([
    init/1,
    datamodel/0
]).
    
-include_lib("zotonic.hrl").
    
%% @doc Initiates the server.
init(Context) ->
    %% Manage our data model
    z_datamodel:manage(?MODULE, 
                       datamodel(), 
                       Context).
datamodel() ->
    [{categories,
      [
       {gp,
        text,
        [{title, <<"Guestbook Post">>}]}
      ]
     }
    ].

Stop Zotonic and run make again. This will build your new module. Now, restart Zotonic, and log in to the admin. Go to the modules page, and observe that there now is a new module called Guestbook.

You can see here the values defined in the code for author, mod_title, mod_description and mod_prio. The Prio value indicates the importance of the module—the highest being 1 and the default being 500. Modules with a higher priority are checked first for templates and scomps.

A percentage symbol in Erlang indicates a comment, so any of the lines in this code preceded by percentage symbols are ignored by the compiler. The first two lines, although comments, also contain special notation, which is used to document the program.

Here, I exported init/1. This is because it must be called by external modules; init has an arity of 1, meaning it takes 1 argument:

-export([
    init/1
]).

If I had a function that took two arguments, I would export it like this:

-export([
    itsname/2
]).

You do not need to export datamodel, because it is used only by the init function in this module.

Init will be called whenever your module is loaded, and the first time it is called, it will create a new category in Zotonic called guestbook_post. This will be a subcategory of “text” and will have the display name Guestbook Post.

For each guestbook post, you should have a title and summary—luckily, all pages in Zotonic already have a title and summary, so without doing anything else, you can add posts to your guestbook by creating pages of the category Guestbook Post. Create a few Guestbook Posts now, ensuring that you fill in the titles and summaries. Also, don't forget to tick Published; otherwise, they won't be visible to users who are not logged in. You can use these to test your guestbook's display, which I discuss next.

Templates

Create a new subdirectory in mod_guestbook called templates:

mkdir priv/sites/default/mod_guestbook/templates

Using your favorite text editor, create the following file named guestbook.tpl:


{% extends "base.tpl" %}
    
{% block content %}
<h1>Guestbook</h1>
<ul id="guestbook-posts" class="guestbook-posts">
{% with 
    m.search[
        {query cat='gp' sort='-publication_start'}
    ] as posts %}
    {% for post in posts %}
        {% include "_guestbook_post.tpl" %}
    {% endfor %}
{% endwith %}
</ul>
{% include "_guestbook_form.tpl" %}
{% endblock %}

This template fetches the pages of category guestbook_post in order of publication date; it extends the base template of the site in which it is used and overrides the “content block” of that base template.

I also am including two other templates, _guestbook_post.tpl and _guestbook_form.tpl. I'll create these templates later.

Next, you need a dispatch rule. Create a new subdirectory of mod_guestbook named dispatch:

mkdir priv/sites/default/mod_guestbook/dispatch

Using a text-editor, create a file called dispatch (with no extension) in the dispatch folder. It should contain the following dispatch rule:

[
    {guestbook, ["guestbook"],    
                resource_template,   
                [ {template, "guestbook.tpl"}]}
].

The first parameter above is the name of the rule. This is followed by a list containing the URI scheme; in this case, it's simply /guestbook. Let's use a premade Zotonic resource called resource_template to do the rendering, and the template that you actually will be rendering is called guestbook.tpl.

Save everything, run make and then restart Zotonic. When you navigate to 127.0.0.1:8000/guestbook, you will see a page that simply contains the heading Guestbook.

In the template above, I included another template called _guestbook_post.tpl, which I did not create yet. This template will contain the details of each guestbook post and be rendered once for every guestbook post. Let's create it now in the templates subdirectory of mod_guestbook:


    <li class="guestbook-post">
        <p>{{ post.title }}-{{post.summary}}</p>
    </li>

Run make and reload Zotonic. You now should see the guestbook posts you created earlier in the admin.

The next step is to allow users to sign the guestbook by creating the _guestbook_form.tpl template:


{% wire id="guestbook-form" 
            type="submit" 
            postback={np} 
            delegate="mod_guestbook" %}
<form id="guestbook-form" 
            method="post" action="postback">
  <div>
    <div class="form-item">
      <label for="title">Title</label>
        <input type="text" name="title" id="title" />
        {% validate id="title" type={presence} %}
    </div>
    <div class="form-item">
      <label for="summary">Summary</label>
      <input type="text" name="summary" id="summary" />
      {% validate id="summary" type={presence} %}
    </div>
    <div class="form-item button-wrapper">
      <button type="submit">{_ Post _}</button>
    </div>
  </div>
</form>

You use the “wire” scomp to specify that the form with the id="guestbook-form" will be handled by the event function of mod_guestbook. You also use the “validate” scomp to check for the presence of the required fields. If you wanted the summary field to be optional, you could leave out the validate scomp for the summary field. Here, you just use the presence validator, but there are others, such as numericality, length, confirmation (for making sure two fields match) and the very useful format validator, which takes a regular expression.

Now, you need to implement the event function of mod_guestbook to handle this post:

%% @doc Handle the submit event of guestbook
event({submit, {np, _}, _TriggerId, _TargetId}, C) ->
    T = z_context:get_q_validated("title", C),
    S = z_context:get_q_validated("summary", C),
    CatId = 
    m_category:name_to_id_check(gp, C),
    AC = z_acl:sudo(C),
    Props = [
         {category_id, CatId},
         {title, T},
         {summary, S},
         {is_published, true}],
     {ok, RscID} = m_rsc:insert(Props, AC),
     Post = m_rsc:get(RscId, C),
     TemplateProps = [
         {post, Post}
     ],
     Html = z_template:render("_guestbook_post.tpl",
                                  TemplateProps, AC),
     z_render:insert_top("guestbook-posts", 
                                  Html, AC).

Don't forget to export event/2. Now you are writing Erlang code and making use of some support functions that come with Zotonic.

If you are new to Erlang, the first thing to note is that once a variable has been bound to a value, it cannot be changed. This may seem strange, but the idea is to avoid side effects, so that you can write distributed applications. A nice side effect (I know) is that it makes Erlang easier to debug.

In Erlang, you use lists and tuples for storing aggregates of data. A list is enclosed in square brackets and a tuple in curly brackets. Variables in Erlang start with a capital letter (Props), and you also use atoms, which are lowercase. Atoms do not have any value associated with themselves; in essence, they are the value.

Functions that relate to access control are found in z_acl, and in this case, you use z_acl:sudo to gain superuser rights. z_context:get_q_validated allows you to get the contents of a validated field from the post; z_template:render returns a rendered template, and z_render:insert_top inserts some text at the top of an HTML element with a given ID. More support functions can be found in src/support.

The code for accessing the database is found in src/models. Here, I accessed the database to check the ID of a category (m_category:name_to_id_check) and also to insert a new resource (m_rsc:insert).

The guestbook is not completely finished. You still need to add the name of the person that signed it. This is easy, however, and you don't need to go near the database to do it. Simply add a new field to your form template, modify your event function to handle that field, and your guestbook will be complete.

Michael Connors is a freelance software developer from Ireland, but he currently lives in Normandy, France. These days, he mostly develops software in Erlang.

LJ Archive