Changing Contexts in Zope
Created by .
Last modified on 2003/08/05.
Untitled
DTML and context
DTML can script everything available in a given Zope context. DTML is
(effectively) always written inside a DTML object (either a DTML
method
or a DTML Document). The place in the Acquisition hierarchy
determines what
its context is (for more information see
Acquisition). Perhaps this would
seem a bit formal description,
but it is crucial to understanding Zope.
A DTML object and its context *always* consist of the following
parts.
namespace
|
contents |
Global
Zope
|
Zope internals (as far as they are exposed): string, math,
random modules, len, range etc |
acquistion parents |
Folders and folderish objects and their context. Acquisition
presents you with a namespace stack, consisting of
PARENTS. It is a (python) list, consisting of
PARENTS[0,1...n].
The last item in the PARENTS list is always the root folder.
PARENTS[n] may also be written as PARENTS[-1] |
REQUEST
| all variables concerned with the incoming (web) request,
especially the webserver and cgi variables, and cookies
|
RESPONSE
| all variables concerning the outgoing (web)request, including
cookies, content-types and redirection
|
object itself |
including object properties, both own and
acquired |
*NOTE* One caveat is that dtml objects do not always
behave the same: a DTML Document is an object in itself (with its own
properties), while a DTML Method is just a method of a folderish object (see
http://www.zope.org/Documentation/How-To/AdvancedDTML ) . Mixing the
two may lead to unwanted behaviour so choose your
objects carefully.
Most of the scriptable parts of a DTML object in the table are
treated
extensively elsewhere. To get a bit of feeling of how the
object and the
changing context work together in Zope, let's take a
look at an example.
Please note that this example is neither meant to
explain a specific tag nor
any of the more subtle parts of
acquisition. It is just to show the
capabilities of DTML and the need
to understand that understanding context
is crucial to understanding
Zope.
As acquisition is such a central phenonomenon in Zope lets throw in a
little acquisition example:
Suppose we have a (folder/document) hierarchy as follows:
Folder1 {properties: property_1:'this is a folder'}
DTMLDocument1
{no properties set}
DTMLDocument2 {properties: property_1:'this is a
document'}
DTMLMethod1 {has no properties}
Folder2 {no properties set}
DTMLDocument21 {no
properties}
DTMLDocument22 {properties: property_1:'this
is a DOCUMENT'}
DTMLMethod21 {has no properties}
The dtml source code of *all* DTML documents and methods is as
follows:
<dtml-var standard_html_header>
<h2><dtml-var
property_1></h2>
<dtml-var standard_html_footer>
In the different document this renders as follows:
DTMLDocument1:
This is a folder
DTMLDocument2:
This is a document
DTMLMethod1:
This is a folder
DTMLDocument21
This is a folder
DTMLDocument22
This is a DOCUMENT
DTMLMethod21
This is a folder
These examples are just meant to show how a *very simple* form
of
acquisition works, and how powerful it is. The use may not be readily
apparent. However, remember you can do the same with dtml methods. So
if
you change the DTML method standard_html_header, it will have
effect on
*all* DTML object in the acquisition stack below it. In
this way you can
easily change or update the look of (parts of) your
site by replacing
only one (1
) object.
3. A
closer look at acquisition
There is much more to be said about acquisition, and so there is in
a
separate
chapter, so we do not pretend to give an exhaustive treatment
here.
Nonetheless, acquisition is crucial to changing contexts in Zope,
so
we shall go into some more examples here. A few simple examples to
begin
with - we shall relate it to more general design questions.
3.1 Example 1
One of the elements in the pages we described above was a
hierarchy_links,
displaying the links of the pages all the way up to the
root.
The DTML Method hierarchy_links was listed as follows [*if you are not
at
all familiar with DTML, it is probably necessary to study at least
_some_ of
the basics at first. See [link]]
1 <dtml-in PARENTS
reverse>
2 <dtml-if
sequence-start>
3 <a
href="<dtml-var absolute_url>"><dtml-var
id></a>
4
<dtml-else>
5 | <a
href="<dtml-var absolute_url>"><dtml-var
id></a>
6
</dtml-if>
7 </dtml-in>
Analysis:
Line 1: loop the PARENTS list in reverse order. [* For each
object called,
acquisition builds a stack of objects that are higher in the
hierarchy
(a namespace stack). These are the parents of the current object,
for n
parenst the list is n items long, ranging from 0 to n. See
acquisition
chapter]
line 2-3: if it is the first item in the loop, show a
link to the item,
else
line 4-5: show a link with a separator.
Now, this shows some of Zope's strength: is a very simple program, but
it
works everywhere in your site and follows the changing contexts
without
needing to be changed itself.
3.2 Example 2:
It is a in line with the changing colors example we started
out with.
Suppose we have a folder called documents, with a document
called
boring.html in it. The boring document has content that may be usable
in
many places (like address information of your firm). Acquisition
makes
it available as
http://acquisition.com/documents/boring.html
But it can also be available as
http://acquisition.com/themes/christmas/documents/boring.html
In /themes/christmas you find a new standard_html_header which puts
a
gaudy red and green theme on the text, and contains an /images folder
with
christmas-oriented versions of the page decorations. There might
also be
a /themes/escher (in which case the url would be
http://acquisition.com/themes/escher/documents/boring.html).
Discussion:
The downside of acquisition is that URL's are not always in
the same,
consistent place on your side: acquisition makes hierarchy less
absolute.
For example, you have a link to a page called
AddressInformation
from both your site's homepage and from a page called
Documentation (and
from who knows what other pages).
Without changing contexts, this will result in the same
AddressInformation
page having many different urls. This is not exactly
consistent. Even more
than in conventional site design, acquisition
makes it possible to design you
navigation in a sloppy way ('no need to
move that folder - acquisition will
take care of it anyway').
Granted, the different links to a page are no disaster. Still, when
you
have a chain of links, url's may become innecessary long, to the
point
that they have an adverse effect on Zope's performance. For
example if you
have a hierarchy like this
root
documents
AddressInformation
GeneralInformation
products
ProductInformation
it is very well conceivable that you end up with a url like this:
http://acquisition.com/products/ProductInformation/documents/
GeneralInformation/products/ProductInformation/AddressInformation
(this URL did not fit on one line)
[Today I read something about this having been patched in Zope 2.1 -
is
that right? If so, this section must be thrown out]
By the way, Zope _does_ give you a way to address this issue, by
linking
not through a relative link (<a
href="desired_page">), but by linking
absolutely (<a
href="<dtml-var "desired_page.absolute_url">).
3.3 Common misconceptions about acquisition
The very dynamics of acquisition in Zope can easily confuse you.
Moreover,
once you start pushing acquisition to its limits, it is
important to know how
it works and how it does not.
Many people who start working with Zope have a misconception
about
acquisition. Technically put, acquisition builds a namespace
stack
from the root to the object called. In looking through the stacks
it
first looks for the required object in the current namespace, then
goes
up one step in the namespace stack and goes looking in that object
and
its context.
Next is an illustration of one of the misconceptions about
acquisition is
from an actual question to the Zope list. Incidentally,
the Zope list, its
sister lists (at the time of writing Zope-dev and
ZDP), and their archives
are a great resource for examples [URL] and
explanations about Zope.
Question:
Take I the following folder structure:
/
index_html
show_images
/images
bob <image
object>
henry <image
object>
/subfolderhenry
index_html
/images
henry <image object>
The /index_html uses show_images to display /images/bob and /images/henry
I want my /subfolderhenry/index_html to use show_images to
show
/images/bob and /subfolderhenry/images/henry.
When /subfolderhenry/index_html is rendered, the acquisition method
goes
looking for <dtml-var images/bob>. What seems to be
happening is that
it looks in /subfolderhenry/images and doesn't find
bob. The problem is
that it returns an error instead of moving up the
tree to look in
/images (where it would find bob).
The person in question wanted to know why this did not work: whether
he
mis-understood the way acquisition works, or whether this was a bug
or
Zope problem.
Exercise: Can you figure out the answer for yourself?
Analysis
There was a misunderstanding of acquisition, of course.
In the hierarchy
presented, if Zope is asked for
subfolderhenry/images/henry, it goes looking
for an images folder in
subfolderhenry and finds one. It does not find the
image (object) henry,
because it's not there. However, as it already found an
object called
images, it does _not_ go on, but stops and reports a not found
error.
The misconceptions about acquisitio, is the way that Zope goes looking
for
an object. Some starters with Zope is able to 'step back' when it
does
acquisition. For example, in the question above the suppose Zope to
go
looking inside an object looking for a folder object called images.
If it has
one, it goes looking for an actual image object (the object
called - in this
case henry). If it does not find one, Zope steps back,
and once more resumes
its climb up the acquisition hierarchy to go
looking for other images
objects.
That is _not_ the case, and for many good reasons. If it did (even if
it
were technically conceivable), it would produce inpredictable results
and
make acquisition even more complicated than it is now.
Solution
Now that the question is answered, it still needs a proper solution.
The
author of the question wrote: as a work-around I can drop the
images
directories, and host the images in the actual directories. Not
a great
solution though - I agree
Another exercise: find a solution.
In the post and its answer the following alternatives were presented:
A. Ask yourself if you really need an images folder in each folder,
or
whether you could do with one images folder somewhere high in
the
hierarchy. Then a reference to images from below that point is always
be
to the same images folder.
B. you can refer to the images folder in the root explicitly:
<dtml-var "PARENTS[-1].images.henry"> (note: PARENTS[-1]
always is the root
folder)
Of course, this is equivalent to the normal HTML
<IMG SRC="/images/henry">
but then you would have to specify the image attributes by hand, where
in
the first case you can have Zope represent them, as follows:
<dtml-var "PARENTS[-1].images.henry,tag(border=0,
align='right')">
C. you could name you root images folder 'images' and the images
folder in
the henry folder 'Images', as Zope is case sensitive, it
will distinguish
them from each other. The only problem is that _you_
are likely to mix them
up.
==============
part 3 When acquisition gets
complicated
===============
3.4 Complications in acquisition
It should be clear that acquisition adds some incredible flexibility
to
your site. There are already many excellent examples of it in the
Zope
community. Many of them are too complicated to explain here (if only
if
I do not completely understand them myself), but they are worthwile
to
study. Remember that Zope is Open Source and most of the Zope
Products
are also, and reading the sources is often a great way of learning
more -
it does require knowledge of the Python language [links to
Chameleon
Folder and SiteAccess and who knows what]
Example: multilingual site
There has been expressed interest in internationalizing Zope. Now this
is
an issue with many sides, and also concerned with Zope internals and
browser
issues, but part of it can be handled by site design. Only the
Zope site
design part will concern us here, as it is an example per
excellence of a
changing context. In many non-US sites it is very common
to have a
multilingal site, and that is what the example is about.
Multilingual sites
The problem:
The goal is: the same DTML document should be able to create pages
in
multiple languages and with the same application logic for
each
language. That is, without copying. This means we aim at a site with
a
single tree, not two trees for two languages.
Some things to take into
account: a site may have content in several
languages, but not all pages may
be or should be available in all
languages. Therefore, it should also have
content in a default language.
This seems like an ideal problem for Zope's acquisition. The
different
solutions were (once again) taken from an actual discussion on one
of
the Zope related lists. There were several solutions proposed to
the
problem. To be sure, none of them was completely 'right' in the sense
of
solving the whole problem. But one approach was definitely better
than
the other (sadly the solution that did not work was mine). The
solutions
proposed show different approaches, both of them trying to
use
acquisition as an important tool. The basic analysis both
solutions
agreed upon was that The minimum difference between a site in Dutch
and
the same site in English is the textual contents. I'm hoping
layout
issues will at least with European languages be relatively minor, so
the
layout can stay the same.
First I present the solutions, then we go on to an analysis.
Solution 1
ROOT
contents_en
contents_nl
index_html
English
Dutch
Foo
Bar
....
English and Dutch are folders and each contains a property
called
'language', set to "English" and "Dutch"
respectively. They are empty
folders.
Foo is a folder with an index_html containing layout info, and whenever
it
tries to get some textual contents, it'll dtml-var it in from the
folder
dependent on language.
<dtml-if "language=='Dutch'">
<dtml-var
contents_nl>
<dtml-else>
<dtml-var
contents_en>
</dtml-if>
You call the Dutch page with:
Dutch/foo
You call the English page with:
English/foo
You'd call the 'default' language with:
foo (precise implementation for default is left as an exercise for
the
reader)
The same for bar and any other subfolders.
Solution 2
ROOT
en
---
---
---
de
---
---
---
sitehome
---
---
---
ch
---
---
---
nl
---
---
---
If your en, de, ch, nl all contain a standard_html_header method and
other
usable methods. And if you make one language the main language of the
site
and place this in the folder I called 'ROOT'. Acquisition makes it
possible
to
call urls like http://root/en/sitehome/page.html
or
http://root/nl/sitehome/page.html
or
http://whatever/dutch|english|antarctic/site/content
The
DTML-source of the page.html looks like this:
<dtml-var standard_html_header>
all sorts of fancy html
layout
<dtml-var tekst1>
more layout
<dtml-var
tekst2>
even more layout
<dtml-var standard_html_footer>
It will give you your language specific pages, unless there aren't any,
in
which case you get the text in the standard language.
Exercise: does this solution work? Why not?
Analysis and some remarks:
(see the tree above)
Calling the page in sibling folders works as proposed
in the solution:
It _is_ possible to place a contents in the directories at
the same level as
foo and then call them as
http://whatever/dutch|english|antarctic/site/content
[also refer to http://www.zope.org/pipermail/zope/1999-June/005141.html]
The default part does _not_ work as described.
If sitehome is on the same level as the language specific folders, in
a
URL like http://ROOT/en/sitehome/page
(in which page refers to text
methods in en) acquisition only returns the
page with the method from en, if
there is no method with the same name
anywhere else in the hierarchy: not
above and not below. If there is, _that_
method will be returned.
Anyway, if you want to return language specific
content, unless there is
none available, in which case you would return
default content, you
should construct something like:
<dtml-var standard_html_header>
<all sorts of fancy html
layout>
<dtml-if tekst1>
<dtml-var
tekst1>
<dtml-else>
<dtml-var
default-text1>
</dtml-if>
<more layout>
<dtml-if tekst2>
<dtml-var
tekst2>
<dtml-else>
<dtml-var
default-text2>
</dtml-if>
bla bla
<dtml-var
standard_html_footer>
Note 1: This means that the sitehome folder would have to have a
different
naming convention than the other directories. This is due to a
feature
in acquisition.
Note 2:
Now in either solution you would have to be careful with
hyperlinks. They
should not be absolute because in that case you run the risk
of 'jumping
out of the environment'. [This last point could of course be
solved by
taking the variable from cookies or even http header variables,
but
these are much more inflexible in changing context.]
[As a solution I propose to either use relative url's, the Zope
BASE/URL
tag in some way if you have to refer to other parts. I do not have
time
to sort that out right now]
This is complicated matter, so we'll present another example, once
again
taken from 'real life', that is, the Zope lists. This time it
will
provide a quite detailed explanation of how it works, so it
inevitable
involves some Python, as that's what Zope is (mostly) written
in.
Example: A flexible homepage
The problem:
In many cases it is desirable to make a flexible homepage,
with a
frames/no-frames/flash version etcetera.
Once again Zope's flexibility will make this much easier than with
a
filesytem-based site. Once again, a flexible homepage will mean that
you
have content stored once, and the environment in sibling folders.
Take the following proposal [/ means ROOT]:
/
index_html (checks a cookie,
gives
a frameset or a
redirect
to /noframes/home
(frameset
loads /home))
home
(content)
standard_html_header (together define my
page
standard_html_footer layout)
frames/ <-
Framesets
standard_html_header (define a frameset
that
standard_html_footer loads the same URL
minus
index_html the
/frames prefix)
nav
(navigational frame)
noframes/ <- No
framesets
standard_html_header (define a layout
with
standard_html_footer navigation in a
table)
index_html
All objects are either Folders or DTML Methods. So far so good, this
works
like charm. With acquisition, the home document can be viewed with
and
without a frameset.
But then I make another Folder, lets say Personal, and define a
index_html
in that Folder:
Personal/
index_html
Calling http://examplesite.com/Personal/
works fine, but when I call
http://examplesite/frames/Personal/
the same layout appears as the
previous URL. De standard_html_header and
-_footer from the root are being
used, not the ones in the frames/ Folder!
The same goes for noframes/.
[For draft readers: I do not have time for it now, but all this is
going
to be in an example Zope site, to be provided with this chapter]
Analysis:
Jim Fulton, creator of much of Zope' core, and certainly of
acquisition
explained at the mail list why it works as it works (this
involves some
python):
When you acquire an object, the acquired object gets the context of
the
aquirer *and* the context it was acquired from, with the source
context
taking precedence over the destination context. In the
example above,
'Personal' acquires from the top-level folder first,
and then from the
'frames' folder.
Lets walk through why this is so. Suppose in Python, we have
a
variable 'app' that is the top-level folder.
The expression:
app.Personal
Gets 'Personal' in the context of 'app'.
app.frames
Gets 'frames' in the context if 'app'. So far so good.
Now consider:
app.frames.Personal
'Personal' is acquired from 'app'. We have to get
'app.Personal',
and then use it in the context of 'app.frames'. This
results in:
(Personal of app) of (frames of app)
When searching for an attribute, we always search the
innermost contexts
first. In this example, we search
'Personal of app' before we search
'frames of app'.
This is explained much better
in his Acquisition algebra. See:
<
P>
<
EM>
http://www.zope.org/Members/jim/Info/IPC8/AcquisitionAlgebra/index.html
The solution:
There may be several workarounds and solutions to the problem. Let's
look
at one
[Note: Often a solution to some of these problems may be is
simply
changing the order of objects also changes the order of
acquisition.
In this particular case that did not work, as there was an
index_html
method in /frames. And the right index_html had to be
acquired.]
/frames
/framedcontent
/noframes
Were the three URL modifiers to be implemented.
A. Framed
/frames in front of the URL results in a frameset with navigation in
one
frame and the same URL in the other, but with /framedcontent in front
of
it instead of /frames. In framedcontent we present the content
without
further navigating. With javascript we check whether we are inside a
frame.
For example:
http://examplesite.com/framed/content
results in:
<frameset cols="150, 1*" border=0 frameborder=0
framespacing=0>
<frame name=nav src="/frames/nav"
scrolling=no noresize framesborder=0>
<frame name=con
src="/framedcontent/content" frameborder=0>
etcetera
and the javascript in /framedcontent/content looks like:
<script language=javascript><!--
if (window.name !=
'con')
top.document.location =
'http://examplesite.com/frames/content';
//--></script>
B.Not framed
/noframes presents the same as /framedcontent, but now we put
navigation
within the same document (using a table)
For example: http://examplesite.com/noframes/content
results in:
<table width=100% border=0>
<tr valign=top>
<td
width=150>
navigation
<a
href="/home">Home</a><BR>
<a
href="/destination1/">destination1</a><BR>
<a
href="/destination2/">destination2</a><BR>
</td>
<td>Al sorts of interesting content
</td>
</tr>
</table>
The tricks for this are in standard_html_header and
standard_html_footer:
s_h_header:
<dtml-if st_html_header>
<dtml-var
st_html_header>
<dtml-else>
<dtml-if
"REQUEST.cookies.has_key('st_frames') and st_frames ==
'no'">
<dtml-call
"RESPONSE.redirect(_.string.replace(URL,
BASE0,
BASE0 + '/noframes'))">
<dtml-else>
<dtml-call "RESPONSE.redirect(_.string.replace(URL, BASE0, BASE0
+
'/framedcontent'))">
</dtml-if>
</dtml-if>
In (other) words:
If we find st_html_header in the acquisition path, use
it. Otherwise
redirect to the frames or noframes version, depending on a
cookie.
s_h_footer:
<dtml-if st_html_footer><dtml-var
st_html_footer></dtml-if>
Only show a st_html_footer if it is in the acquisition path.
st_html_header en st_footer are only defined in /frames,
/framedcontent
and in /noframes. So someone who uses the url
http://examplesite.com/content
will
be redirected to the frames or noframes version, in this case
frames/content
or noframes/comtent. And frames/content is a frameset of
which one frame
shows the URL /framedcontent/home
Resuming:
Trying to override standard_html_header and standard_html_footer
is not a
good idea.
If you use acquisition on sibling folders as described
in the examples
above, you will
have to define you own conditional
options. In the case of the last example
standard_html_header and
standard_html_footer made use of these
optional
replacements.