Archive for the ‘Web Development’ Category

PyCon 2010 Poster Session

Saturday, February 27th, 2010

I finally presented something at a Python conference. Many thanks to Vern Ceder for organizing the PyCon 2010 poster session!

Here’s a copy of the poster, which describes a way to run QUnit-based JavaScript unit tests using Python’s unittest facilities:

PyCon Poster.png
PyCon Poster.pdf

The code described by the poster is available from bitbucket.org, under the BSD license.

Running TileCache within a Django Application

Thursday, September 24th, 2009

[Cross-posted from Bottled Text, as usual.]

Punchline

Here is how to serve TileCache tile images from within a Django application.

from TileCache.Service import Service
_service = Service(...)
def get_tile(request):
    global _service
    format, image = _service.dispatchRequest(
        request.GET, request.path, request.method,
        request.get_host())
    result = HttpResponse(str(image), mimetype=format)
    return result

Scenario

You’re building a low-traffic Django-based GIS application, and you need to serve your own map layers. You’re using TileCache to improve your application’s performance. But installation and configuration are hassles.

  • All of your servers must run with the right user and group IDs, so the Django app can expire the tile cache when necessary.
  • Your Django app needs to understand the structure of the tile cache, so it can remove the correct tile images when the underlying data changes.
  • Etc.
standalone_tilecache.png

This would all be much easier if you could serve TileCache requests from within your Django application. They’re both Python-based; why not?

django_plus_tilecache.png

The TileCache code base includes sample code that shows how to run TileCache as a CGI or a FastCGI service. I couldn’t find any sample code for running TileCache within a Django application, but it was easy to convert the cgiHandler code for use with Django’s HttpRequest objects.

Installation Prerequisites

In order for TileCache to generate its own tiles, instead of delegating to a separate mapserver instance, you must already have compiled and installed mapserver’s Python mapscript bindings. For instructions on compiling the bindings see the mapscript/python/README file in the mapserver source distribution.

Configuring TileCache

import os
thisdir = os.path.abspath(os.path.dirname(__file__))
def relpath(p):
    return os.path.abspath(os.path.join(thisdir, p))
from TileCache.Service import Service
import TileCache.Layers.MapServer as MS
# Create the service 'singleton'.
_mapfile = relpath("../mapserv/data/mapfile.map")
_service = Service(
  _cache,  # See "Cache Invalidation", below
  {
    "basic": MS.MapServer(
        "basic", _mapfile, layers="basic", debug=False),
  }
)

Handling Tile Requests

This is the sweet part. It’s derived from the cgiHandler() example in the TileCache source code, but Django’s HttpRequest class makes the implementation very simple:

def get_tile(request):
    global _service
    format, image = _service.dispatchRequest(
        request.GET, request.path, request.method,
        request.get_host())
    result = HttpResponse(str(image), mimetype=format)
    return result

What About Feature Info Requests?

I don’t know much about the required web API of a WMS server, but it appears as if the same URL must serve both tiles and feature info requests; the type of request is determined by the Request querystring parameter.

Django’s dispatch system is based on URL pathnames; I’m not aware of any way to dispatch based on query string parameters. So you’ll need to either configure your web server (e.g. Apache) to rewrite WMS requests to distinct URLs provided by your Django app, or you’ll need to do some dispatch within your Django app.

Suppose you opt for the latter. Then your urls.py might look something like this:

    ...
    url(r'^wms/$', 'world.views.wms', name='wms'),
    ...

and in world/views.py you might have this:

def wms(request):
    if request.GET.get("request") == "GetFeatureInfo":
        return get_feature_info(request)
    return get_tile(request)

Cache Invalidation

For my web app, several of the tile layers are derived from a Django model which is updated via the admin interface. Whenever the model changes, the tile cache for the corresponding layer(s) needs to be invalidated, so the images can be regenerated.

The TileCache Cache interface doesn’t provide for invalidation. Since I’m using a filesystem-based cache, I subclassed TileCache.Caches.Disk to create a Disk cache which does support invalidation.

import shutil
from TileCache.Caches.Disk import Disk
class InvalidatingDisk(Disk):
    """A Disk cache which can invalidate its contents,
       layer by layer."""
    def invalidate(self, layerName=None):
        if self.basedir:
            pathname = self.basedir
            if layerName is not None:
                pathname = os.path.join(self.basedir,
                                        layerName)
            shutil.rmtree(pathname, ignore_errors=True)

Ted Leung on web app design patterns

Sunday, September 13th, 2009

DjangoCon 2009:

“Avi Bryant’s keynote took its root in his experiences building Trendly. As one might expect, Avi started building Trendly using Seaside. But by the time he finished, he noticed that very little of Seaside was actually being used. He attributed this to the fact that Trendly’s architecture involves loading a single HTML, with a ton of Javascript. That Javascript then manages all of the interaction with the server, which consists of snippets of JSON data. This range true to me because we used a similar architecture for Chandler Hub, the web based version of Chandler (our interaction with the server was based on atom and atompub, not JSON), and it’s the kind of architecture that GMail is based on.”

The same has been true for us (Mesa Analytics) in most of our web applications. Sometimes we do have “multi-page” web apps, but each of the individual pages typically follows the pattern above: load, then let JavaScript drive a bunch of task-specific server interactions.

Between Ted’s summary of DjangoCon 2009 and the chatter about Tornado, it has been an interesting week for web application developers.

ActionScript: TileList, deleting, scrolling backwards

Wednesday, August 19th, 2009

[Another day, another blog cross-post...]

I have a Flex app which shows users a TiledList of chemical structure depictions. Since it can be a large list, it’s lazy-loaded from the server as the user scrolls through the list.

Users can delete items from the list, with undo. In order to do this with reasonable performance, once the app has received confirmation from the server that an item has been deleted, it clears out the single deleted item from its local lazy-list.

To undo the deletion locally, the Flex app fills the correct lazy-list entry with an ItemPendingError; that error gets thrown as soon as the TileList tries to retrieve the item.

All of this works okay when the row containing the undeleted item is already visible. On the other hand, if the user has scrolled away from the row where the undeleted item will reappear, then when (s)he scrolls back the TileList simply empties out that item and all of the successive items in the row. Ugly!

ugly_repaint.png

Workaround

When the item is undeleted, immediately try to retrieve it via getItemAt(itemIndex). Catch the resulting ItemPendingError and register an ItemResponder. When the ItemResponder’s result or fault method is called, tell the TileList to invalidateList(). If the undeleted item actually contains a value, the TileList will repaint correctly — no more unsightly gaps.

import mx.collections.errors.ItemPendingError;
import mx.collections.ItemResponder;
[...]
try {
    structures.getItemAt(offset);
} catch (e:ItemPendingError) {
    e.addResponder(
    new ItemResponder(
        function(result:Object, token:Object = null):void {
            tilelist.invalidateList();
        },
        function(error:Object, token:Object = null):void {
            tilelist.invalidateList();
        }));
}

Firefox 3.1b2, Safari 4 Beta and HTML 5 canvas text

Wednesday, March 4th, 2009

This will be useful for depicting chemical structures using HTML5 canvas: both Safari 4 beta and Firefox 3.1b2 support the HTML5 canvas text rendering APIs.

There are still rough edges, e.g. it’s hard to measure text dimensions and therefore hard to position text properly. Still, it’s great to see!

$(document).ready(function() {
    function redraw() {
        var canv = $('#canv');
        var w = canv.width();
        var h = canv.height();
        // Let canvas know how big CSS wants it to be.
        canv.attr("width", w);
        canv.attr("height", h);
        var ctx = canv[0].getContext("2d");

        ctx.clearRect(0, 0, w, h);

        var msg = "Hello, Safari text.";
        var fontSpec = "24pt Verdana";

        // Find out the dimensions of the rendered msg.
        var e = $('<span style="visibility:hidden;font:' + fontSpec + '">' +
                  msg + '</span>');
        $('body').append(e);
        var tw = e.width();
        var th = e.height();
        e.remove();

        ctx.save();
        ctx.translate(w/2, h/2);
            // Indented to highlight transformation state.
            ctx.save();
            ctx.rotate(-45 * Math.PI / 180.0);
            ctx.translate(-tw/2, th/2);
            ctx.font = fontSpec;
            ctx.fillText(msg, 0, 0);
            ctx.restore();
        ctx.restore();
    };
    redraw();
});

Mozilla Webdev » Blog Archive » Native JSON in Firefox 3.1

Monday, February 16th, 2009

Mozilla Webdev » Blog Archive » Native JSON in Firefox 3.1:

“Pretty easy huh? And here’s how to get a JSON string from an object:

var personString = JSON.stringify(person);”

As a long-time fan of Python’s pickle and shelve, I hope JSON.stringify properly handles JavaScript object graphs. And I hope jQuery exposes a similar API.

I think jQuery provides only .param, which is designed as a helper for forms serialization and which does not handle nested objects. For example, in jQuery 1.3.1 this:

var graph = {
    'item': {
        'name': 'bob',
        value:42
    },
    'index': 1};
$('#msg').html("Graph: " + $.param(graph));

produces this:
Graph: item=%5Bobject+Object%5D&index=1

Django snippets: DebugFooter middleware with textmate links

Tuesday, January 20th, 2009

Django snippets: DebugFooter middleware with textmate links:

“This version adds TextMate links : if you are working on your local machine and using TextMate you can click on the template paths and they will be opened in TextMate. This speeds up development time considerably!

also, this works with django 1.0″

Very useful.

Release:jQuery 1.3 – jQuery JavaScript Library

Wednesday, January 14th, 2009

Release:jQuery 1.3 – jQuery JavaScript Library:

“Live Events

jQuery now supports ‘live events’ – events that can be bound to all current – and future – elements. Using event delegation, and a seamless jQuery-style API, the result is both easy to use and very fast.”

If I read this correctly, now you can reload parts of your page and have any replaced controls wired up automatically. No more need for post-processing callbacks on $.load() or $.getJSON()?

Outstanding!

Can’t debug after updating to Flex Builder 3.0.2

Tuesday, November 18th, 2008

I’m evaluating FlexBuilder 3, and have just installed the Flex Builder 3.0.2 update. All of a sudden I can’t debug my AIR project.

The error dialog displays a lengthy message which ends with:

error while loading initial content 

Thanks to Google and Adobe’s public bug database, the fix is straightforward: edit the project’s *-app.xml file and change the namespace (2nd line) to 1.5:


<application xmlns="http://ns.adobe.com/air/application/1.5">

One-line web server in Python

Wednesday, November 5th, 2008

From Ed Taekema’s weblog, a reminder[1] about how to create a web server in one line of Python:

python -c 'import SimpleHTTPServer;SimpleHTTPServer.test()'

This starts a server listening on port 8000, serving files from its launch directory. If you just hit the server (http://localhost:8000/) it will by default serve index.html; if index.html doesn’t exist, it will give you a directory listing. Also, the server will provide only those files residing in or below the current working directory; e.g. http://localhost:8000/../ resolves to the default document.

Wish I’d seen Ed’s note this morning…

This is handy when you want to experiment with things like interactions between Flex applications and JavaScript, or other situations in which you can’t get by with simple file:/// URLs. This would be handy as a jqUnit test, as well.


[1] Reminder? It’s been a long time since I read “Internet Programming with Python” :-)