4.1 Albatross Application Model

In the Albatross world view explained in the previous section, all web applications follow the same processing sequence to handle browser requests. When processing a browser request to generate a response the processing (in most cases) flows according to figure 4.1.

Figure 4.1: Request Processing Dataflow
\includegraphics[]{dataflow}

The processing steps are:

  1. Capture the browser request in a Request object.

  2. Pass the Request object to the run() method of the application object.

  3. Application locates the Python code for processing the browser request.

  4. Page processing code runs one or more Albatross templates.

  5. Templates contain either standard Albatross tags or application defined extension tags.

  6. As tags are converted to HTML a stream of content fragments is sent to the execution context.

  7. When the execution context content is flushed all of the fragments are joined together.

  8. Joined flushed content is sent to the Request object write_content() method.

  9. Application response is returned to the browser.

In the Albatross code the processing is driven by the run() method of the Application class in the app module.

It is instructive to look at the exact code from Albatross that implements the processing sequence.

    ctx = self.create_context()
    ctx.set_request(req)
    self.load_session(ctx)
    self.load_page(ctx)
    if self.validate_request(ctx):
        self.merge_request(ctx)
        self.process_request(ctx)
    self.display_response(ctx)
    self.save_session(ctx)
    ctx.flush_content()

The code is contained within a try/except block to allow the application to trap and handle exceptions.

The Application class assumes very little about the implementation of each of these steps. The detail of the processing stages is defined by a collection of mixin classes. A combination of the Application class and a selection of the mixin classes is used to construct your application class and execution context classes. There are a number of prefabricated applications and execution contexts, see chapter 8.

This mix and match approach to building the application and execution context classes provides a great deal of flexibility. Albatross is an application toolkit, not a deployment platform. You are encouraged to look at the code and develop your own mixin classes to suit your deployment requirements. One of the primary goals of Albatross is to keep the toolkit line count low. This reduces the amount of time you need to spend before you can make your own custom extensions to the toolkit.

In the previous chapter we talked about the importance of separating the presentation layer of the application from the implementation as shown in figure 4.2. Albatross HTML templates provide a fairly powerful tool for achieving that separation.

Figure 4.2: Separation of Presentation/Implementation
\includegraphics[]{twolayer}

The presentation layer consists of a collection of template files that contain the logic required to display data from the objects contained in the implementation layer. In Albatross, the line between the two layers is the execution context.

To make objects available to the presentation layer the application places references to those objects into the local or global namespace of the execution context. The local namespace is populated in application code like this:

    ctx.locals.mbox = Mbox(ctx.locals.username, ctx.locals.passwd)
    ctx.locals.msg = ctx.locals.mbox[int(ctx.locals.msgnum) - 1]

To execute Python expressions inside the template files, the execution context uses the following Python code from the NamespaceMixin class:

    def eval_expr(self, expr):
        self.locals.__ctx__ = self
        try:
            return eval(expr, self.__globals, self.locals.__dict__)
        finally:
            del self.locals.__ctx__

Whenever application code calls the run_template() or run_template_once() methods of the NamespaceMixin class Albatross sets the global namespace (via set_globals()) for expression evaluation (in self.__globals) to the globals of the function that called run_template() or run_template_once().

Not many applications are output only, most accept browser input. The Albatross application object merges the browser request into the execution context in the merge_request() method. Referring back to the application processing sequence also note that the application object displays the result of processing the browser request via the execution context.

With this in mind figure 4.2 becomes figure 4.3.

Figure 4.3: Presentation/Implementation and Execution Context
\includegraphics[]{twolayerctx}

The only thing missing is the application glue that processes the browser requests, places application objects into the execution context, and directs the execution of template files.

The application model built into Albatross is intended to facilitate the use of a model-view-controller like approach (see figure 4.4) to constructing your application. There are many excellent descriptions of the model-view-controller design pattern which can be found by searching for ``model view controller'' on http://www.google.com/.

Figure 4.4: Albatross model-view-controller
\includegraphics[]{mvc}

The user invokes application functions via the controller through the view. The controller contains logic to direct the application functionality contained within the model. All of the real application functionality is in the model, not the controller. Changes to the application model are then propagated to the view via the controller.

In Albatross terms, the implementation layer is the model and the presentation layer is the view. The application glue plays the role of the controller. By divorcing all application logic from the view and controller you are able to construct unit test suites for your application functionality using the Python unittest module.

Albatross uses an approach inspired by the traditional model-view-controller design pattern. So now we can draw the final version of the diagram which shows how Albatross applications process browser requests in figure 4.5.

Figure 4.5: Albatross Application Model
\includegraphics[]{albmvc}

As you can see the execution context is central to all of the Albatross processing. It is worth revisiting the application processing sequence set out on page at the start of this section to see how the application draws all of the elements together.

During step four (page processing) the Albatross application object will call on your application code to process the browser request.

There are a number of different ways in that your application code can be ``attached'' to the Albatross application object. The PageObjectMixin application mixin class requires that you implement application functionality in ``page objects'' and define methods that can be called by the toolkit. As an example, here is the page object for the 'login' page of the popview sample application.

class LoginPage:
    name = 'login'

    def page_process(self, ctx):
        if ctx.req_equals('login'):
            if ctx.locals.username and ctx.locals.passwd:
                try:
                    ctx.open_mbox()
                    ctx.add_session_vars('username', 'passwd')
                except poplib.error_proto:
                    return
                ctx.set_page('list')

    def page_display(self, ctx):
        ctx.run_template('login.html')

When the toolkit needs to process the browser request it calls the page_process() method of the current page object. As you can see, the code determines which request was made by the browser, instantiates the application objects required to service the browser request, then directs the context to move to a new page via the set_page() method.

When Albatross is ready to display the browser response it calls the page_display() method of the current page object. In the code above, set_page() is only called if the mailbox is opened successfully. This means that a failed login will result in the login page being displayed again.

Note that when you change pages the object that generates the HTML will be a different object to that that processed the browser request.

To let you get a foothold on the toolkit application functionality we will work through another variant of the form application.