In general, most sites on the web follow a relatively prescriptive layout.
Sometimes the menu is on the right, or below the masthead. Somethimes one has more than one navigation area. Often, since screens are so wide these days and this impacts on readability, one will have a "Side Matter" area to the right, and perhaps a footer area below the content:
As you can see, we chose to implement a masthead, left side menu and footer.
To recap, Zope Interfaces are a way to declare what attributes and methods a Python object of class might implement. They do so without making use of inheritance or mixins, but rather allow classes to declare that they implement one or more interfaces through Python decorators or alternatively by using Python class advisors (eg. grok.implements(InterfaceName)
).
Interfaces are used extensively by Grok & Zope as a means to defining application components which ultimately get rendered to HTML. For example, the IBrowserView
(specific) and IView
(generic) interfaces are used to identify classes which implement a view, where views may define a template. Classes implementing interfaces which declare attributes that implement zope.schema
fields may be rendered to HTML forms.
Adaptors are classes which adapt objects having one interface, and provide objects which implement another. The Zope architecture provides functions to query adaptors and return objects of a given type. For example, one might use zope.component.queryAdaptor(my_object, IContentTemplate)
to locate a template, where my_object implements an IBrowserView
. The exact kind of template returned is immaterial since we know that IContentTemplate
defines the __call__()
method, and all templates can be treated in the same way.
This is the part that many people (especially noobs) generally miss about Grok & Zope. I missed it myself when starting with Grok, leading to endless confusion and frustration.
So listen up.
The idea of traversal is to identify a data resource by walking a tree of objects by name, mapping names from the URL. Then, depending on what is identified, we can either render a default view (index) for that resource, or another named view, or traverse further into the hierarchy. But what exactly is the type (or class) of the data item we identified by means of traversal?
Well, interfaces add a whole new amazing aspect to the idea of "duck typing". Call it "duck typing by proxy":
The above is what is called a "Marker" interface. It is declared as a ZCA Interface,
but it does not specify what must be done to implement it. Consequently, any model component may state that it implements the interface without having to do anything special.
We can also use the marker interface in grok.View
implementations. If we do this, the view will be rendered for contexts which implement the marker interface. So, for example, we may define a view called 'index' (the default view) with a context of ILayout
, and this view will apply to each and every model which implements ILayout
:
Looking at that graphically, if we have for example Model1
and Model2
which both implement an ILayout
interface, then an Index
view (or Layout
View, or whatever) defined with an ILayout
context will become available and render for both Model1
and Model2
.
In other words, we have a simple way to define a single view which applies to models we define in our application, simply by stating that the model implements (quacks like) a particular interface. This is great as far as things go, but this is far from the end of our story.
Views in Grok are often rendered by means of Zope Page Templates (zpt). Templates are closer to HTML syntax than python code, and are much easier to develop and maintain than writing all of the HTML as Python strings. Although Grok supports a variery of templating languages (for example, jinja2 integrates well), this site sticks to plain old ZPT. ZPT and Chameleon (a compiled and optimised ZPT) are similar in many respects in that they are attribute based and valid HTML in their own right.
Consider the following Zope Page Template (layout.pt):
One will notice immediately that the above is in fact an HTML document, but with a few rather strange looking attributes. These all start with the xml namespace tal:
and may be explained in the order in which the occur in the above template file:
tal:content="context/title | context/description | string:Untitled"
:context
is an automatic variable in the template namespace, as are view/viewlet
as applicable and request
. The meaning of context
should be clear from prior discussions, and the content
attribute simply tells the template to replace the content of the HTML tag with the value from context.title
. If that does not exist, then to use context/description
. If that fails too, then use the string "Untitled".tal:content='structure provider:x'
, where x
∈ [masthead, navigation, sidebar, content, footer].structure
specifier tells the template language to include the content from the provider verbatim and not to try to escape the HTML prior to inclusion.<base tal:attributes="href python:view.url(context, '')" />
includes a 'base' tag for every page, which allows relative URL's to function in an unambiguous way. The tal:attributes
attribute allows for the definition of attributes for the tag which are based upon data from the application. In this instance, the view.url()
method is called for the current context in order to produce the base URL.relative='pq/rs' | |
url='http://ab/cd/' | result='http://ab/cd/pq/rs' |
url='http://ab/cd' | result='http://ab/pq/rs' |
So what then, is a "provider"? The short answer, is that a provider is a grok.ViewletManager
. and a grok.ViewletManager
provides grok.Viewlet
's. There is a longer answer, but to discuss it would serve only to distract.
Viewlet managers can be seen as pluggable areas of your rendered view. In our case, we define a viewlet manager for each area that we want to fill differently depending on context.
You will find the following code which defines viewlet managers for each discrete region of our site :
Viewlet managers are called whenever they are asked to render their content. In the code above, we see that they will render for a context which implements the ILayout
interface. Fair enough, since the layout.pt page template implements the renderer for the Layout
view which also renders for instances of ILayout
. This is quite important though, since we could have specified an ancestor interface (eg. Interface
) from which ILayout
derives, and then the viewlet manager would have had wider scope.
Of course, viewlet managers render that which they manage. Namely, viewlets. The generic masthead viewlet is defined as follows:
As with views, viewlets generally render through means of page templates. The mastheadviewlet.pt template looks like this:
Of course, everyone should by now know exactly what it produces. (hint: look at the top of this page).
To complete the picture, take a look again at our graphic depicting two models, Model 1 and Model 2, both implementing ILayout
. That meant we could have an automatic index view for both models, and a generic masthead that renders different titles depending on which model was identified in the URL.
But what if we wanted to do completely different HTML for say, the content section? Model1 could be an index, and Model2 could be an article, and rendering content for each differs completely!
We can do this by using a specific context: grok.context(Model1)
or grok.context(Model2)
in a viewlet class will allow one to write two completely different viewlets which differentiate between Model1 and Model2. In this way, the page becomes adaptive to the kind of data which is the context at the time the page is rendered.