getting started with web.py

09.03.2009

web.py is a popular simple, minimalist web framework in python. in this post, i'll talk about a creating a simple web application in web.py using mako templates.

first, we need to download, and install web.py which is easy, just download the file, extract it and type python setup.py install

before beginning, make sure web.py is installed correctly, by;
  $ python
  >>> import web

you should not see any importerror message.

in a web.py document, websites urls are written in urls list, and map them to the correspond classes. here's a simple hello world application in web.py
  import web
  
  urls = (
    '/', 'index',
  )
  
  class index(object):
    def GET(self):
      return 'hello world'
  
  app = web.application(urls, globals())
  
  if __name__ == '__main__':
      app.run()

ok, i think you get the structure of a web.py document. we can continue on building our little website. let's start with the basic library imports.
as i said above, my preference of template engine is mako, so we need to make some configuration.
  import web
  from web.contrib.template import render_mako
  
  render = render_mako(
    directories = ['templates'],
    module_directory = ['modules'],
    input_encoding = 'utf-8',
    output_encoding = 'utf-8',
    default_filters = ['decode.utf8'],
  )


and urls of our website;
  urls = (
    '^/(.*)/', 'redirect',
    
    '/posts/(\d-)', 'posts',
    
    '/', 'index',
    
  )
you may wonder the purpose of '^/(.*)/', 'redirect'. that's my preference again. when a user tries to go http://example/posts/34535/ address, he will be redirected to http://example/posts/34535.

let's continue with index class.
  class index(object):
    def GET(self):
      web.header('Content-type', 'text/html; charset=utf-8')
      posts = db.get_posts()
      
      if not posts:
        raise web.notfound()
      return render.index(posts = posts)
first, we define some header, like content-type of the document, which is html. secondly; we get some posts from database which we have not defined yet. don't worry about it for now, and thirdly, we render index.html file.

and our posts class, which accepts commenting from users.
  class posts(object):
    def GET(self, entry_id):
      web.header('Content-type', 'text/html; charset=utf-8')
      
      if not entry_id:
        raise web.notfound()
      post = db.get_posts(entry_id)

      if not post:
        raise web.notfound()
      form = comment_form()
      return render.post(post = post, form = form)
like db.get_posts, we have not yet defined comment_form. db.py file is the file that we handle database processes.
our db.py file as follows;
  import web
  
  USER = 'db_username'
  PASS = 'db_passwd'
  DB = 'db_test'
  
  conn = web.database(db='mysql', user = USER, pw = PASS, charset='utf-8')
  
  def get_posts():
    res = conn.select('db_table',
      what = 'id, title, entry',
      order = 'created DESC',
      limit = 30
    )
    return res
    
  def get_post(post_id):
    res = conn.select('db_table',
      what = 'id, title, entry',
      where = 'id = $id',
      limit = 1,
      vars = {'id' = post_id}
    )
    if not res:
      return None
    return res[0]
in what we write the columns we want to retrieve, where is the filter options. when you retrieve the page you can see the query that is executed in the console.

web.py also comes with handy forms class, which you can create forms automatically, and validation is very ease with the help of the lambda.

our forms.py class as follows;
  from web import form
  
  class MyForm(form.Form):
    def render(self):
      out = ''
      out += self.rendernote(self.note)
      out += '\n'
      for i in self.inputs:
        out += '<p><label for="%s">%s<span id="note_%s">%s</span></label>' % (i.id, i. description, i.id, self.rendernote(i.note))
        out += i.pre + i,render() + i.post + '</p>'
      return out
      
    def rendernote(self, note):
      if note:
        err = '%s' % (note)
        return err
      else: return ''
  
  comment_form = MyForm(
    form.Textbox('username',
      form.Validator('please type your name', lambda x : 255 > len(unicode(x)) > 2),
      description = 'your name:',
      id = 'id_names',
      class _= 'css_class',
    ),
    form.Textarea('message',
      form.Validator('please write your message', lambda x : len(unicode(x)) > 10),
      description = 'your message',
      rows = 10,
      cols = 50,
      class _= 'css_class',
    ),
  )
we defined our own form class which is MyForm, because web.py's default form render class uses tables which is not a valid tag in xhtml strict. comment_form is our comment form renderer.

since we have to POST comment form, we need to edit posts class as follows;
  class posts(object):
    def GET(self, entry_id):
      web.header('Content-type', 'text/html; charset=utf-8')
      
      if not entry_id:
        raise web.notfound()
      post = db.get_posts(entry_id)

      if not post:
        raise web.notfound()
      form = comment_form()
      return render.post(post = post, form = form)
      
    def POST(self, entry_id):
      web.header('Content-type', 'text/html; charset=utf-8')
      
      if not entry_id:
        raise web.notfound()
      post = db.get_posts(entry_id)
      
      if not post:
        raise web.notfound()
      
      form = comment_form()
      if form.validates():
        res = db.set_comment(form)
        if res:
          raise web.seeother('/index?done=true')
      return render.post(post = post, form = form)
      
it's pretty ease. we have finished our simple site with commenting enabled options. and now we can continue to templates. first, we'll create a base.html file, and our index.html, and post.html files will inherit our base.html file.
  <?xml version="1.0" encoding="utf-8"?>
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
  <title>My Site</title>
  <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
  <meta http-equiv="Content-Style-Type" content="text/css" />
  <link rel="stylesheet" type="text/css" media="screen,projection" href="/static/css/style.css" />
  </head>
  <body>
    <h1>Welcome to my site!</h1>
    ${self.content()}
  </body>
  </html>
this is our base.html file. in index.html we'll define a new def which will be named content.

here's our index.html file;
  <%inherit file="base.html" />
  <%def name="content()">
    <ul>
      %for post in posts:
        <li>${post.title}</li>
      %endfor
    </ul>
  </%def>


and post.html file as follows;
  <%inherit file="base.html" />
  <%def name="content()">
    <div>
        <h1>${post.title}</h1>
        <p>${post.entry}</p>
      %endfor
    </div>
    <div>
      <form action="/posts/${post.id}" method="post">
        ${form.render()}
      </form>
    </div>
  </%def>


i didn't cover the topics like login, and some more advanced stuff, but if you get the idea of this simple web application you'll notice doing such thing is very ease in web.py. my python web framework choice is web.py, 'cos of its simplicity, and let's you code in python.
blog comments powered by Disqus