Peer Pressure

Stuff to look at about looking at stuff. From Chris Dent. What?

Archive

Feb
9th
Mon
permalink

TiddlyWeb Plugin Tutorial Part 3

Update 20091123 to reflect modern TiddlyWeb.

This is the third part of a multi-part tutorial for creating TiddlyWeb plugins. See parts one and two.

Personalized Custom Greeting

“Hello” isn’t always what you want to hear. Let’s change things so the message being sent it based on the incoming URL. Make the following changes (make sure you keep the lines beginning with @):

def jinx(environ, start_response):
    username = environ['tiddlyweb.usersign']['name']
    message = environ['wsgiorg.routing_args'][1]['message']
    if not message:
        message = 'Hello'
    return ['<h1>%s %s</h1>' % (message, username)]


def init(config):
    config['selector'].add('/jinx[/{message:segment}]', GET=jinx)

If (after restarting the server) you go to /jinx you’ll get the old hello message, but if you go to something like /jinx/Greetings you’ll see a different message.

Selector lets you put both required and optional named parameters in the URL. These are then accessible in environ at the wsgiorg.routing_args key. Read the selector documentation for more on how to take advantage of this.

Where’s TiddlyWeb

Okay, this is all nice, but where does TiddlyWeb fit into all of this? What about Tiddlers? The basic functionality of TiddlyWeb is the presentation and storage of Tiddlers, Bags and Recipes. The default URLs let you view what’s stored by TiddlyWeb in various ways, and store more stuff. Additional URLs allow you access to the “stuff” in different ways. Let’s extend the code we have now to list all the Bags on the system in addition to our greeting. Make the body of the /jinx method be the following:

username = environ['tiddlyweb.usersign']['name']
message = environ['wsgiorg.routing_args'][1]['message']
if not message:
    message = 'Hello'
store = environ['tiddlyweb.store']
bag_names = ', '.join([bag.name for bag in store.list_bags()])
return ['<h1>%s %s</h1>\n<h2>%s</h2>' % (message, username, bag_names)] 

Now, in addition to your greeting, you should also see “common, system”. store is a reference to the StorageInterface. It is set earlier in the WSGI stack. A store provides the interface to getting data in and out of the TiddlyWeb “database”. list_bags() lists all the bags in the system, without doing any permissions checks. It is but one of several ways to get at data in the store. Others will be explored later. See tiddlyweb.stores for more.

Our management of HTML is starting to get really annoying. This looks like a job for templates.

Templating TiddlyWeb

The current template engine of choice is jinja2. It has a straightforward syntax and doesn’t overreach. You should be able to:

sudo easy_install -U jinja2

We’re going to make a very simple template for our greeting. HTMLPresenter is still being used, so we only need to worry about the guts of our output. Create a directory called templates

mkdir templates

and in that directory create a file jinx.html with the following contents:

<h1>{{ message }} {{ name }}</h1>

<h2>We have some bags:</h2>

<ul>
    {% for bag in bags %}
    <li>{{ bag.name }}</li>
    {% endfor %}
</ul>

To the top of jinx.py add:

from jinja2 import Environment, FileSystemLoader
template_env = Environment(loader=FileSystemLoader('templates'))

and change the jinx() method body to:

username = environ['tiddlyweb.usersign']['name']
message = environ['wsgiorg.routing_args'][1]['message']
if not message:
    message = 'Hello'
store = environ['tiddlyweb.store']
bags = store.list_bags()
template = template_env.get_template('jinx.html')
return template.generate(message=message, name=username, bags=bags)

template_env provides us with the mechanism to read in a template file from something, in this case from disk. generate populates the template with the passed data and returns it in a structure suitable for WSGI responses.

End Piece

That’s an introduction to TiddlyWeb plugins. Here’s all the code:

from jinja2 import Environment, FileSystemLoader
template_env = Environment(loader=FileSystemLoader('templates'))

from tiddlywebplugins.utils import do_html, entitle, require_any_user

@do_html()
@entitle('Hello World')
@require_any_user()
def jinx(environ, start_response):
    username = environ['tiddlyweb.usersign']['name']
    message = environ['wsgiorg.routing_args'][1]['message']
    if not message:
        message = 'Hello'
    store = environ['tiddlyweb.store']
    bags = store.list_bags()
    template = template_env.get_template('jinx.html')
    return template.generate(message=message, name=username, bags=bags)


def init(config):
    config['selector'].add('/jinx[/{message:segment}]', GET=jinx)

Let’s review what we poked at:

  1. system_plugins in tiddlywebconfig.py
  2. adding functions to be called by the Selector dispatch map
  3. helpful routines, like require_any_user from the tiddlywebplugins.utils package
  4. making the HTMLPresenter frame content
  5. using arguments from the URL path
  6. getting at data in the TiddlyWeb store
  7. adding support for templates

In other postings we’ll look at other things you can do, such as:

  1. logging
  2. adding commands to twanager, the command line tool
  3. listing and sorting tiddlers
  4. using tiddlers to store arbitrary content
  5. parsing URL query strings
  6. modifying the environment then calling existing TiddlyWeb code
  7. user roles
  8. accepting uploaded content on the server
  9. overriding HTMLPresenter
  10. adding to the WSGI stack
Comments (View)
blog comments powered by Disqus