You are not logged in Log in Join
You are here: Home » Members » Rik Hoekstra » Changing Contexts in Zope

Log in
Name

Password

 

Changing Contexts in Zope

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.