Replacement Features

Environment

Zope replacement should run as an Apache module.

A daemon may be required to minimize start-up time when a new page must be processed.

Objects

Each URL should refer to a file that could be treated as an object. If the URL referred to a page that was Python code, then that code would be a subclass of a Zope replacement object.
ini
URLs that point to HTML templates would be instantiated as Zope replacement objects at compile time (i.e. when the page is first surfed, much like how .py files are compiled into .pyc files when first run).

Ideally, the instance member _ _str_ _ would render the object into text that would be sent to the browser.

Departure from Zope's one-call-per-dynamic portion model

One advantage that PHP has over Zope is that a single block of code can toss values into a multitude of variables for rendering throughout the remaining HTML. This works really nicely with how a typical dynamic page works. Dynamic pages often begin with a database hit that collects data based on the user's last POST, and then these values are sprinkled throughout the HTML wherever they appeal to the web designer's aesthetic sense.

Contrast this to Zope, where a call returns a single value. This means that the designer must either call many functions or a single function that returns a multi-faceted object such as a dictionary. The latter option works far better than the former, but it does not feel like a very clean solution to such a common problem.

Personally, I would really like the template blocks to subclass off of code blocks. This would give the developer an easy way to make a wide variety of variables available in the namespace while the HTML renderer processes that block of HTML. That's not terribly clear, so let me give an example. Suppose you had the following block of HTML:

<span tal:subclass="/cgi-bin/Utils.TodaysDate">
  <!-- At this point, variables such as Month, and Year would exist in the namespace. -->
  Today is <span tal:replace="Month">November</span> <span tal:replace="DayOrdinal">5th</span>,
  and the year is <span tal:replace="Year">2006</span>.
  <p tal:subclass="/cgi-bin/Shipping.GetStatus">
    <!-- Variable set by GetStatus would overload any previously set. -->
    Your shipment was sent <span tal:replace="Month">November</span>
    <span tal:replace="DayOrdinal">5th</span>, <span tal:replace="Year">2006</span>.
  </p>
  <!-- Variables set by the GetStatus call would be out of context.
  Values overloaded in the p block would revert. -->
</span>
<!-- At this point, variables from the TodaysDate call would be out of context and undefined. -->

Template representation

As I mentioned before, I'd like HTML templates to be treated as objects. It would be nice if these objects acted like lists and could stack the same way that HTML can. For example, I'd like to see the HTML:

<body tal:subclass="/cgi-bin/Utils.TodaysDate">
  <p>Today's date: <b tal:content="ToString('%m/%d/%y')">11/6/06</b></p>
</body>

represented as:

TodaysDate \\
 
(
    Type = "body",
    Attributes =
    {
        "tal:subclass": "/cgi-bin/Utils.TodaysDate"
    },
    Content =
    [
        "\n  <p>Today's date: ",
        HTMLBlock
        (
            Type = "b",
            Attributes =
            {
                "tal:content": "ToString('%m/%d/%y')"
            },
            Content =
            [
                "11/6/06"
            ]
        ),
        "</p>\n"
    ]
)

Note how the <p> tag was not parsed as it has no tal attributes that must be processed. This should greatly simplify processing.

Code example

Here's how I would like this to work. Let's take a template like:

<p tal:subclass="script.UserInfo">
  Name: <span tal:replace="Name">Joe Smith</span><br>
  Age: <span tal:replace="Age">26</span>
</p>

And include script.zr like:

from ZopeReplacement import HTMLBlock
 
class UserInfo(HTMLBlock):
    Name = "Jimminy Cricket"
    Age = 107

Then surfing the template would cause it to render as:

<p>
  Name: Jimminy Cricket<br>
  Age: 107
</p>

Form variables

Instances could access form variables during rendering. For example:

from ZopeReplacement import HTMLBlock
 
class Income(HTMLBlock):
    def __str__(self):
        POST = self.GetEnvironment().POST
        self.Hours = POST["Hours"]
        self.BaseRate = POST["BaseRate"]
        if self.Hours > 40:
            self.OT, self.Hours = self.Hours - 40, 40
        else:
            self.OT = 0
        self.Pay = self.BaseRate * (self.Hours + self.OT * 1.5)
        return HTMLBlock.__str__(self)

Authentication

HTTP authentication could be handled during rendering:

from ZopeReplacement import HTMLBlock, UnauthorizedException
 
class Auth(HTMLBlock):
    def __str__(self):
        Auth = self.GetEnvironment().Authentication
        if (Auth.Name != "joe") or (Auth.Password != "jimbo"):
            raise UnauthorizedException
        return HTMLBlock.__str__(self)

Bouncing the web browser

Web servers can redirect the browser to a new URL by responding with a new location header. I propose we do this in a Zope replacement with an exception

from ZopeReplacement import HTMLBlock, LocationException
 
class WebPage(HTMLBlock):
    def __str__(self):
        raise LocationException("http://new.url")

Loading a new page in place

Since every page would be an object, it would be easy to replace a page's content with that of another page if needed. Here's some boilerplate authentication code that could be executed from a template's HTML tag:

from ZopeReplacement import HTMLBlock, FileOps
 
class Auth(HTMLBlock):
    def __str__(self):
        Env = self.GetEnvironment()
        POST = Env.POST
        if (POST["Name"] != "joe") or (POST["Password"] != "jimbo"):
            # Wrong user name or password!
            # Instead of serving this page, let's return the login page.
            Login = FileOps.OpenURL("/login.zr").WebPage(Env)
            return str(Login)
        return HTMLBlock.__str__(self)

Sessions

Sessioning could be extracted from the environment object:

from DateTime import DateTime
from ZopeReplacement import HTMLBlock
 
class Sessions(WebPageBase):
    def __str__(self):
        Session = self.GetSession()
        # When did we visit this page last?
        self.LastVisit = getattr(Session, "LastVisit", DateTime.now())
        # Update the session with "now"
        Session.LastVisit = DateTime.now()
        return HTMLBlock.__str__(self)

Initial values for form elements

One common thing that a web developer must do is provide initial values for form elements. For example, when the user chooses to edit his personal settings, the developer would like the current settings to appear in the controls. Another common occurrance is when a form is rejected on the server side so that some information may be fixed. Ideally, the original values should get restored so that they do not have to be re-typed.

This isn't exactly hard to do (especially for text and textarea fields), but it's signicantly more unpleasant for controls like radio buttons. Since it is such a common task, I feel that a bit of our attention could make it even easier. I'm imaginging something like:

<form action="CreateAccount" method="POST">
  <p>Please enter the following information:</p>
  Your e-mail address: <input tal:subclass="FormCtrl" name="Email"><br>
  Repeat e-mail (for verification): <input tal:subclass="FormCtrl" name="Email2"><br>
  Requested storage space: <input tal:subclass="FormCtrl" name="Space:int"><br>
  Account type:<br>
  <input tal:subclass="FormCtrl" type="radio" name="Type" value="Student"> Student<br>
  <input tal:subclass="FormCtrl" type="radio" name="Type" value="Parent"> Parent<br>
  <input tal:subclass="FormCtrl" type="radio" name="Type" value="Other"> Other<br>
  <input type="submit">
</form>

By subclassing the controls to a provided class, we can check the GetEnvironment().POST to see if values for Email, Email2, Space, and Type exist. If they do, then these can be provided, escaped for HTML. In the case of the radio button controls, the correct item can be checked.

Auto-sizing images

Every good web developer knows that to make a webpage look professional while it loads, the <img> tags must include height and width tags. Failing to do so will make the layout twist and turn as the images load and the browser discovers what sizes the images are. Additionally, alt attributes in <img> tags help make the pages more accessible.

When using static images, tools such as DreamWeaver will create the width and height attributes for you (you still have to enter in the alt text manually, of course). However, when you pull the image dynamically (from a gallery, perhaps), then the web developer is faced with a decision. Do you leave the height and width attributes out and live with the fact that the page layout will change as the file is loaded, or do you toss in a bunch of code just to prevent a visual "hiccup".

Most developers go the easy route, and I can't really blame them. Even in PHP where you have a built-in routine to access an image's height and width, you end up with an ugly chunk of code:

<img src="img/flag.jpg" alt="getimagesize() example"
<?php print getimagesize("img/flag.jpg")[3]; ?>>

It works, but there seems something wrong with sticking one tag inside another like that.

Anyhow, continuing on with the concepts from the previous section, we could simplify this by subclassing to a standard zope replacement class:

<img src="img/flag.jpg" tal:subclass="ImgCtrl">

The ImgCtrl class could load the image headers, extract the height and width, and even supply an alt tag if one was saved in the image's data fork.

Data fork

As I mentioned before, one nice thing about Zope is that you can add attributes to files. For example, in an image gallery, you could add some description text to an image so that you wouldn't have to maintain a separate database to correlate image names with the text. Such a database has its place, for sure, but it's a bit of overkill in many common situations.

The big problem with the data fork concept is where do you keep that extra data?

Zope solves this problem by keeping all files as BLOBs in a database. Since the files are no longer regular files, Zope can add on extra fields without worry of messing up the file itself. This is very clever, but as I mentioned previously, there are many downsides to this approach. In fact, reliance on a central database is one of the main things I am trying to escape with a Zope replacement.

A tempting alternative would be to add the data fork to the actual files. However, doing so would require knowledge of every file format that the developer could possibly add data to. I'm really not comfortable with the risk of destroying file data, so let's not explore this one.

Another alternative is to maintain a database of just attributes. Let the files be files, but keep the attributes elsewhere so that they do not affect the files themselves. This isn't a bad solution, but it doesn't totally escape the problems Zope has with its one database solution. Furthermore, it links the Zope replacement with yet another product, and that's something I would like to avoid. I'm all up for linking with Apache and Python, but I'd like to keep this list of dependencies small so that the "cost" of trying out a Zope replacement is minor and non-threatening.

Additionally, keeping the attributes in a database complicates the maintainence of those attributes. How will a developer clean out old attributes when the files are gone? How will he know what is there?

The possibility that I'd like to explore would be to keep each file's data fork as a pickle in a separate file (the data fork for xyz.jpg might be xyz.jpg.dfk, for instance). This is very simple to develop and maintain. If a developer wants to move a bunch of images (for example) from one place to another, he can copy the data fork files as well. Looking at the directory would show if any data fork files remained after a file was deleted.

Now what?

Let's share some proof of concept code to get the ball rolling.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-Share Alike 2.5 License.