Rendering a dictionary

HTML, XML, and JSON

Consider the following action:

  1. def count():
  2. session.counter = (session.counter or 0) + 1
  3. return dict(counter=session.counter, now=request.now)

This action returns a counter that is increased by one when a visitor reloads the page, and the timestamp of the current page request.

Normally this page would be requested via:

  1. http://127.0.0.1:8000/app/default/count

and rendered in HTML. Without writing one line of code, we can ask web2py to render this page using different protocols by adding an extension to the URL:

  1. http://127.0.0.1:8000/app/default/count.html
  2. http://127.0.0.1:8000/app/default/count.xml
  3. http://127.0.0.1:8000/app/default/count.json

The dictionary returned by the action will be rendered in HTML, XML and JSON, respectively.

Here is the XML output:

  1. <document>
  2. <counter>3</counter>
  3. <now>2009-08-01 13:00:00</now>
  4. </document>

Here is the JSON output:

  1. { 'counter':3, 'now':'2009-08-01 13:00:00' }

Notice that date, time and datetime objects are rendered as strings in ISO format. This is not part of the JSON standard, but rather a web2py convention.

Generic views

When, for example, the “.xml” extension is called, web2py looks for a template file called “default/count.xml”, and if it does not find it, looks for a template called “generic.xml”. The files “generic.html”, “generic.xml”, “generic.json” are provided with the current scaffolding application. Other extensions can be easily defined by the user.

For security reasons the generic views are only allowed to be accessed on localhost. In order to enable the access from remote clients you may need to set the response.generic_patterns.

Assuming you are using a copy of scaffold app, edit the following line in models/db.py

  • restrict access only to localhost
  1. response.generic_patterns = ['*'] if request.is_local else []
  • to allow all generic views
  1. response.generic_patterns = ['*']
  • to allow only .json
  1. response.generic_patterns = ['*.json']

The generic_patterns is a glob pattern, it means you can use any patterns that matches with your app actions or pass a list of patterns.

  1. response.generic_patterns = ['*.json', '*.xml']

To use it in an older web2py app, you may need to copy the “generic.*“ files from a later scaffolding app (after version 1.60).

Here is the code for “generic.html”

  1. {{extend 'layout.html'}}
  2. {{=BEAUTIFY(response._vars)}}
  3. <button onclick="document.location='{{=URL("admin", "default", "design",
  4. args=request.application)}}'">admin</button>
  5. <button onclick="jQuery('#request').slideToggle()">request</button>
  6. <div class="hidden" id="request"><h2>request</h2>{{=BEAUTIFY(request)}}</div>
  7. <button onclick="jQuery('#session').slideToggle()">session</button>
  8. <div class="hidden" id="session"><h2>session</h2>{{=BEAUTIFY(session)}}</div>
  9. <button onclick="jQuery('#response').slideToggle()">response</button>
  10. <div class="hidden" id="response"><h2>response</h2>{{=BEAUTIFY(response)}}</div>
  11. <script>jQuery('.hidden').hide();</script>

Here is the code for “generic.xml”

  1. {{
  2. try:
  3. from gluon.serializers import xml
  4. response.write(xml(response._vars), escape=False)
  5. response.headers['Content-Type']='text/xml'
  6. except:
  7. raise HTTP(405, 'no xml')
  8. }}

And here is the code for “generic.json”

  1. {{
  2. try:
  3. from gluon.serializers import json
  4. response.write(json(response._vars), escape=False)
  5. response.headers['Content-Type'] = 'application/json'
  6. except:
  7. raise HTTP(405, 'no json')
  8. }}

Any dictionary can be rendered in HTML, XML and JSON as long as it only contains python primitive types (int, float, string, list, tuple, dictionary). response._vars contains the dictionary returned by the action.

If the dictionary contains other user-defined or web2py-specific objects, they must be rendered by a custom view.

Rendering Rows

If you need to render a set of Rows as returned by a select in XML or JSON or another format, first transform the Rows object into a list of dictionaries using the as_list() method.

Consider for example the following model:

  1. db.define_table('person', Field('name'))

The following action can be rendered in HTML, but not in XML or JSON:

  1. def everybody():
  2. people = db().select(db.person.ALL)
  3. return dict(people=people)

while the following action can rendered in XML and JSON:

  1. def everybody():
  2. people = db().select(db.person.ALL).as_list()
  3. return dict(people=people)

Custom formats

If, for example, you want to render an action as a Python pickle:

  1. http://127.0.0.1:8000/app/default/count.pickle

you just need to create a new view file “default/count.pickle” that contains:

  1. {{
  2. import cPickle
  3. response.headers['Content-Type'] = 'application/python.pickle'
  4. response.write(cPickle.dumps(response._vars), escape=False)
  5. }}

If you want to be able to render any action as a pickled file, you need only to save the above file with the name “generic.pickle”.

Not all objects are pickleable, and not all pickled objects can be un-pickled. It is safe to stick to primitive Python objects and combinations of them. Objects that do not contain references to file streams or database connections are usually pickleable, but they can only be un-pickled in an environment where the classes of all pickled objects are already defined.

RSS

web2py includes a “generic.rss” view that can render the dictionary returned by the action as an RSS feed.

Because the RSS feeds have a fixed structure (title, link, description, items, etc.) then for this to work, the dictionary returned by the action must have the proper structure:

  1. {'title': '',
  2. 'link': '',
  3. 'description': '',
  4. 'created_on': '',
  5. 'entries': []}

and each entry in entries must have the same similar structure:

  1. {'title': '',
  2. 'link': '',
  3. 'description': '',
  4. 'created_on': ''}

For example the following action can be rendered as an RSS feed:

  1. def feed():
  2. return dict(title="my feed",
  3. link="http://feed.example.com",
  4. description="my first feed",
  5. entries=[dict(title="my feed",
  6. link="http://feed.example.com",
  7. description="my first feed")
  8. ])

by simply visiting the URL:

  1. http://127.0.0.1:8000/app/default/feed.rss

Alternatively, assuming the following model:

  1. db.define_table('rss_entry',
  2. Field('title'),
  3. Field('link'),
  4. Field('created_on', 'datetime'),
  5. Field('description'))

the following action can also be rendered as an RSS feed:

  1. def feed():
  2. return dict(title="my feed",
  3. link="http://feed.example.com",
  4. description="my first feed",
  5. entries=db().select(db.rss_entry.ALL).as_list())

The as_list() method of a Rows object converts the rows into a list of dictionaries.

If additional dictionary items are found with key names not explicitly listed here, they are ignored.

Here is the “generic.rss” view provided by web2py:

  1. {{
  2. try:
  3. from gluon.serializers import rss
  4. response.write(rss(response._vars), escape=False)
  5. response.headers['Content-Type'] = 'application/rss+xml'
  6. except:
  7. raise HTTP(405, 'no rss')
  8. }}

As one more example of an RSS application, we consider an RSS aggregator that collects data from the “slashdot” feed and returns a new web2py rss feed.

  1. def aggregator():
  2. import gluon.contrib.feedparser as feedparser
  3. d = feedparser.parse("http://rss.slashdot.org/Slashdot/slashdot/to")
  4. return dict(title=d.channel.title,
  5. link=d.channel.link,
  6. description=d.channel.description,
  7. created_on=request.now,
  8. entries=[dict(title=entry.title,
  9. link=entry.link,
  10. description=entry.description,
  11. created_on=request.now) for entry in d.entries])

It can be accessed at:

  1. http://127.0.0.1:8000/app/default/aggregator.rss

CSV

The Comma Separated Values (CSV) format is a protocol to represent tabular data.

Consider the following model:

  1. db.define_table('animal',
  2. Field('species'),
  3. Field('genus'),
  4. Field('family'))

and the following action:

  1. def animals():
  2. animals = db().select(db.animal.ALL)
  3. return dict(animals=animals)

web2py does not provide a “generic.csv”; you must define a custom view “default/animals.csv” that serializes the animals into CSV. Here is a possible implementation:

  1. {{
  2. import cStringIO
  3. stream = cStringIO.StringIO()
  4. animals.export_to_csv_file(stream)
  5. response.headers['Content-Type'] = 'application/vnd.ms-excel'
  6. response.write(stream.getvalue(), escape=False)
  7. }}

Notice that one could also define a “generic.csv” file, but one would have to specify the name of the object to be serialized (“animals” in the example). This is why we do not provide a “generic.csv” file.