Lesson 7: Layers, Skins and Macros

  1. Files:

Teacher: Paul Carduner

Students: Brittney, Will, Preetam, and Linda

Paul:

Today we are going to learn how to develop custom skins for our application. So far we have been using the ZMI to control our application, even though the ZMI provides much more functionality than we want. So for the sake of our end users, we want to create our own simplified version of the ZMI that only has the functionality we required.

Skins can be a somewhat complicated topic because the skinning architecture has several different parts that interfact with each other to provide flexible skinning capabilities. The first and somewhat all-encompassing part of skinning is a layer.

I like to think of layers as boxes that can contain a bunch of stuff, including other boxes. Primarily, the boxes/layers hold things we want to display on our web page. These things or what we might call resources, can be anything from images to stylesheets to files to special views and macros. Although we have not used it yet, the configuration tags used to create pages and resources take an optional layer attribute which can be used to specify which layer they are in (or which box they are in).

Having multiple layers is especially useful when we have things like mutliple skins. Imagine having a web application that was used by a bunch of different schools. Each school is going to want their own logo to appear in the application. Since we don't want to worry about all the different logos when writing the application, we simply create new layers for each of the schools. Then we can put a different logo image or stylesheet or anything into each of the schools' layer. Then when the school logs in, zope will look up the logo image in the layer for that school.

Will:

Does this mean you can have mutiple layers?

Paul:

The really interesting part about layers is that they can inherit from other layers (like having boxes inside boxes). So this answers your question, Will, about the layers. Then if zope doesn't find a logo image in a certain layer, it will look in the parent layers too. This way we could have a default logo if the school did not provide their own.

Since we aren't planning on deploying Z Contact with a bunch of different looks, we only need to create one layer.

Linda:

So is it like a Java inheritance where it overwrites new values with old?

Paul:

Like python inheritance? Yes.

The other part of skinning with Zope3 is an actual skin itself. In Zope terminology, the skin is what figures out which layer to use. Remember how each school would see a different layer? We have to be able to tell Zope which layer to use and that is done with a skin. The skin is in fact just a marker that gets attached to the request object (the users HTTP request from their web browser). Then when zope inspects the request object, it sees this special marker and looks for resources or views in one of the layers specified by the skin. The special marker is actually just an interface. This sounds somewhat complicated, and it is, but you will see how all this fits together next.

In the simplest case (which is our case), we are only going to have one layer and one skin. The way that skins specify which layers should be looked in for a given resource is by inheritance. That is, if our skin wants to tell zope to look in layers A, B, and C, then the interface defining our skin would inherit layers A, B, and C (just like normal python inheritance). This means that skins are actually layers too. You might think of them as a grouping of other layers.

So enough talk, let's get to creating our skin. As I said earlier, skins are just marker interfaces. So open up the interfaces.py file and create an interface called IZContactSkin. Now, the only layer we want to inherit from is the default layer. The default layer is the layer that things get registered for when you don't specify a specific layer in the configuration tag. At the moment, all our pages, and all of zope's views as well, are registered on this default layer. And even though we are writing our own custom skin, we will still want to be able to use a lot of the views zope provides for us (which live in the default layer). The default layer is just another interfaces located at zope.publisher.interfaces.browser.IDefaultBrowserLayer, so make the IZContactSkin inherit from this interface. My skin definition looks like this:

class IZContactSkin(zope.publisher.interfaces.browser.IDefaultBrowserLayer):
    """The ZContact skin."""
That's it. That is our skin. So any request object that provides the IZContactSkin interface will use our skin. So how does a request object go about providing this interface? Well, zope has this handy thing called a traversal adapter.

An example and a statement: The traverser traverses the url. This is what the url would look like: http://localhost/++skin++ZContact/path/to/whatever. When zope traverses this url, that is, when zope looks up the url, it will see the ++skin++ part and know that what follows is the name of the skin to use. It will then lookup the interface for that name, and make the request object provide that interface. Then zope machinery handles the rest.

To give our skin a name so it can be accessed through the URL, we have to register it in our configure.zcml file. Open up configure.zcml and add the following tag:

  <interface
      interface="zcontact.interfaces.IZContactSkin"
      type="zope.publisher.interfaces.browser.IBrowserSkinType"
      name="ZContact"
      />
This tells zope that the skin we made is in fact a skin (that is the IBrowserSkinType part) and that the skin can be looked up using the name ZContact. With that written, we can access our skin through a url. I would recommend restarting our zope server now and checking it out. Go to any place in the ZMI, then add ++skin++ZContact to the beginning of the url (just after the server:port) and see what the page looks like using our skin.

What you should see is something pretty ugly:

Of course our skin looks this way because we are no longer using zope's built in macros. This naturally brings us to the last part of skinning: Macros.

Macros are what people might normally think of when you say skin because it defines how and what things get layed out on the screen. It is the visual part of a skin. Macros are defined in the same way that we create page templates. In fact, you might think of them as meta templates - templates for templates. Remember in lesson 3 when we had the line <html metal:use-macro="context/@@standard_macros/view">? Most views in Zope use the view macro, which is specified in the standard_macros view. So if we want our skin to work with existing zope components, we will make our own standard_macros view, with our own view macro.

Notice that @@standard_macros is just a view lookup. Remember that a view is a lot like a page with a class and template. The first thing we need to do to create macros is add a standard_macros page to the new layer we created, IZContactSkin. So go back to your configure.zcml file and add the following lines:

  <browser:page
      for="*"
      name="standard_macros"
      template="zcontactmacros.pt"
      permission="zope.View"
      layer="zcontact.interfaces.IZContactSkin"
      />
Let's go through this tag line by line. We specify for="*" to say that this view should be available no matter what type of object we are looking at. The name="standard_macros" is just the name of the view (what comes after @@ in the view lookup. The template="zcontactmacros.pt" line specifies the template where our macros will be defined. We want to use the zope.View permission because if you can view a page you should be able to view the macro for it (otherwise it won't display correctly). Finally, the layer="zcontact.interfaces.IZContactSkin" line puts this view in the new layer we created (don't forget that the skin is a layer itself). If we didn't include this line, the this zcml registration would conflict with the standard_macros view registration from inside zope, because you can't have to views with the same name in the same layer.

Now we need to write the actual page template. As we specified in zcml, we need to create a new file called zcontactmacros.pt. The first macro we want to create should be called view. Start by writing the following:

<html metal:define-macro="view">
Hello World For Macros!
</html>

Note: Be sure to look at a management screen (which uses the view macro). If you look at a non-management screen it will try to look up the page macro and fail. You could rename this macro to page to have it display there.

Notice that for anything involving macros, we use the metal namespace. Now restart your server and open up any page using this skin. You should get an empty screen that just says Hello World For Macros!

If you look at the other page templates we have created, like viewcontact.pt, you will see other metal attributes, like metal:fill-slot. In our macro we can define slots where content will go. Page templates which use the macro can then fill those slots using the fill-slot attribute. To create a slot we use the define-slot attribute in our macro. So, we might want to add a default header to our macro, then present the information from the body slot. Edit your viewcontact.pt file to look like this:

<html metal:define-macro="view">
  <head><title>ZContact</title></head>
  <body>
    <h1>Z Contact</h1>
    <h4>The Hottest Contact Manager on the Planet</h4>
    <hr />
    <div metal:define-slot="body">
      Hello World For Macros!
    </div>
  </body>
</html>
Since our macro is just a page template, we don't need to restart the zope server to see our changes. So check out your page again. Now you should see what we put in the macro as well as the contents defined by the template for the page we are looking at.

Since this still looks exceedingly ugly, we are going to want to use some cascading style sheets. Casecading style sheets present an excellent usecase for resources. In zope, a resource is just a file that lives on the server's file system, and not in Zope's object database. These might include CSS files, images, javascript libraries, and more. Since I generally use quite a number of resources in my web applications, I like to have a folder where I keep all the resources. So go ahead and create a new directory within zcontact called resources. Then we need to register this directory as containing a bunch of resources. To do this, open up configure.zcml and add the following tag:

  <browser:resourceDirectory
      directory="resources"
      name="zcontact_resources"
      layer="zcontact.interfaces.IZContactSkin"
      />
To access a file in the resources directory from the web, we use the path http:/localhost/@@/zcontact_resources/. Now lets add a link to the css file in our html code:
<html metal:define-macro="view">
  <head>
    
<link rel="stylesheet" type="text/css" href="/@@/zcontact_resources/style.css" tal:attributes="href context/++resource++zcontact_resources/style.css"/>
<title>ZContact</title> </head> <body> <h1>Z Contact</h1> <h4>The Hottest Contact Manager on the Planet</h4> <hr /> <div metal:define-slot="body"> Hello World For Macros! </div> </body> </html>
Now you should restart your zope server (so the resourceDirectory zcml tag gets registered), and take a look at the page again.

Files:

When you click on the links below you will be presented with text versions of these files. If you choose to save these files from your web browser, be sure to put the files in the proper directory structure.

  1. zcontact/interfaces.py
  2. zcontact/contact.py
  3. zcontact/zcontact-configure.zcml
  4. zcontact/configure.zcml
  5. zcontact/__init__.py
  6. zcontact/viewcontact.pt
  7. zcontact/tests.py
  8. zcontact/ftests.py
  9. zcontact/README.txt
  10. zcontact/browser.txt
  11. zcontact/contactcontainer.pt
  12. zcontact/zcontactmacros.pt
  13. zcontact/resources/style.css

A tarball of all these files is located here: lesson07.tgz

Previous Lesson

Front Page