Pyramid Traversal and MongoDB

March 27, 2011 at 10:06 PM | categories: codes | View Comments

This post will talk about creating a simple web application with Pyramid and MongoDB. It will also talk about using a traversal method to map a requested URL to a callable function (controller/method/view/etc) in your application code.

The App

Enter Troll, an anonymous board web application. It is a simple application that is similar to a blog app (post and comment). The features are:

  • Sorted by activity (recently commented posts come first).
  • Automatic removal for the least commented post (there is a maximum number of posts).
  • Lord Inglip summoning pedestal (read: recaptcha).

The non-features:

  • No pagination. Maybe later with webhelper. Post limit is set to 10 by default anyway.
  • No test.
  • etc

The app uses a single mongodb collection for posts. The comments are embedded to a post document.

Source code is available here.

The Stack

This app uses few technologies


NOTE : Traversal is optional in Pyramid. You can still use URL dispatch (pattern matching thing).

Traversal is a method of matching a requested URL to your application code just like a more familiar method, URL parsing and comparing it to a set of patterns. Traversal requires you to build a resource tree which is probably analogous to a file-system hierarchy. Pyramid will take a URL, and then traverse your resource tree trying to find a resource for that URL. Once a resource is found, Pyramid will try to find a function associated with that resource. The resource object found (or last traversed) is called context.

This post gave me a good idea about traversal.

For this application, two kinds of resource object are needed. The first one is the root object. This object will act as a container to post objects.

class Root(object):
    __name__ = None
    __parent__ = None

    def __init__(self, request):
        self.collection = request.db.post

    def __getitem__(self, name):
        post = Post(self.collection.find_one(dict(_id=ObjectId(name))))
        return _assign(post, name, self)

    def __len__(self):
        return self.collection.count()

    def __iter__(self):
        return ( _assign(Post(x), str(x['_id']), self) for x in self.collection.find().sort('updated', DESCENDING) )

The __getitem__ method will return post object (the child of the root object). The root object contains all posts. _assign is just a simple function to set some attributes of resource object.

def _assign(obj, name, parent):
    obj.__name__ = name
    obj.__parent__ = parent
    return obj

Next is the post object. This object is a slightly modified python dictionary (pymongo returns mongodb document as a python dict). Resource tree objects need to have a __parent__ attribute.

class Post(dict):
    def __init__(self, a_dict):
        super(Post, self).__init__(self)
        self.__name__ = None
        self.__parent__ = None

The Views

This is the function for viewing a resource object.

@view_config(renderer='single.html', context=resources.Post)
@view_config(renderer='index.html', context=resources.Root)
def view(context, request):
    form = TrollForm()
    return {'p': context, 'form': form}

Pyramid allows you to write a function once and then register it multiple times for different contexts. Coming up is the function for handling post and comment addition.

@view_config(name='add', request_method='POST', context=resources.Post)
@view_config(name='add', request_method='POST', context=resources.Root)
def add(context, request):
    author = request.params['name']
    content = request.params['content']
    _add(context, author, content)
    return HTTPFound(location=request.resource_url(context))

Here is the _add function.

def _add(context, author, content):
    if context.__parent__ is None:

Finally, doing insert/upsert to MongoDB.

def _post(collection, author, content):
    p = dict(author=author,
    #remove unpopular post if >  10
    if collection.find().count() > 10:
        collection.remove({'_id': [x for x in collection.find().sort("updated", DESCENDING)][-1]['_id']})

def _comment(collection, post_id, author, comment):
    post = collection.find_one(dict(_id=post_id))
    time = datetime.utcnow()


Post form and comment form have the same fields (author, content, and captcha) and use the same form class (from wtforms). To prevent typing the same thing in many places, I created a template macro.

<%def name="createform(c, form)">
    <% link = request.resource_url(c)%>
    <form method="POST" action="${link}@@add">
        <div> ${form.name.label}: ${form.name(size=50)}</div>
        <div> ${form.content.label}: ${form.content(rows=5, cols=50)}</div>
        <input type="submit" value="Submit!"/>

This macro takes a context and a form object, to generate an html form. URL for any resource can easily be retrieved via resource_url method on request object. The '@@' means the start of a view name. Pyramid will traverse the URL until '@@', and search for a view named 'add' for that context.

This is how to use it.

${createform(request.context, form)}

If you want to use it on another template file, import it first.

<%namespace file="base.html" import="createform" />


Pyramid is a fun framework to tinker with.

Source Code

blog comments powered by Disqus

Content is licensed under Creative Commons License
Feed: entries, comments
Contact: bitbucket, github