Rendering a dictionary
HTML, XML, and JSON
Consider the following action:
def count():
session.counter = (session.counter or 0) + 1
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:
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:
http://127.0.0.1:8000/app/default/count.html
http://127.0.0.1:8000/app/default/count.xml
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:
<document>
<counter>3</counter>
<now>2009-08-01 13:00:00</now>
</document>
Here is the JSON output:
{ '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
response.generic_patterns = ['*'] if request.is_local else []
- to allow all generic views
response.generic_patterns = ['*']
- to allow only .json
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.
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”
{{extend 'layout.html'}}
{{=BEAUTIFY(response._vars)}}
<button onclick="document.location='{{=URL("admin", "default", "design",
args=request.application)}}'">admin</button>
<button onclick="jQuery('#request').slideToggle()">request</button>
<div class="hidden" id="request"><h2>request</h2>{{=BEAUTIFY(request)}}</div>
<button onclick="jQuery('#session').slideToggle()">session</button>
<div class="hidden" id="session"><h2>session</h2>{{=BEAUTIFY(session)}}</div>
<button onclick="jQuery('#response').slideToggle()">response</button>
<div class="hidden" id="response"><h2>response</h2>{{=BEAUTIFY(response)}}</div>
<script>jQuery('.hidden').hide();</script>
Here is the code for “generic.xml”
{{
try:
from gluon.serializers import xml
response.write(xml(response._vars), escape=False)
response.headers['Content-Type']='text/xml'
except:
raise HTTP(405, 'no xml')
}}
And here is the code for “generic.json”
{{
try:
from gluon.serializers import json
response.write(json(response._vars), escape=False)
response.headers['Content-Type'] = 'application/json'
except:
raise HTTP(405, 'no json')
}}
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:
db.define_table('person', Field('name'))
The following action can be rendered in HTML, but not in XML or JSON:
def everybody():
people = db().select(db.person.ALL)
return dict(people=people)
while the following action can rendered in XML and JSON:
def everybody():
people = db().select(db.person.ALL).as_list()
return dict(people=people)
Custom formats
If, for example, you want to render an action as a Python pickle:
http://127.0.0.1:8000/app/default/count.pickle
you just need to create a new view file “default/count.pickle” that contains:
{{
import cPickle
response.headers['Content-Type'] = 'application/python.pickle'
response.write(cPickle.dumps(response._vars), escape=False)
}}
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:
{'title': '',
'link': '',
'description': '',
'created_on': '',
'entries': []}
and each entry in entries must have the same similar structure:
{'title': '',
'link': '',
'description': '',
'created_on': ''}
For example the following action can be rendered as an RSS feed:
def feed():
return dict(title="my feed",
link="http://feed.example.com",
description="my first feed",
entries=[dict(title="my feed",
link="http://feed.example.com",
description="my first feed")
])
by simply visiting the URL:
http://127.0.0.1:8000/app/default/feed.rss
Alternatively, assuming the following model:
db.define_table('rss_entry',
Field('title'),
Field('link'),
Field('created_on', 'datetime'),
Field('description'))
the following action can also be rendered as an RSS feed:
def feed():
return dict(title="my feed",
link="http://feed.example.com",
description="my first feed",
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:
{{
try:
from gluon.serializers import rss
response.write(rss(response._vars), escape=False)
response.headers['Content-Type'] = 'application/rss+xml'
except:
raise HTTP(405, 'no rss')
}}
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.
def aggregator():
import gluon.contrib.feedparser as feedparser
d = feedparser.parse("http://rss.slashdot.org/Slashdot/slashdot/to")
return dict(title=d.channel.title,
link=d.channel.link,
description=d.channel.description,
created_on=request.now,
entries=[dict(title=entry.title,
link=entry.link,
description=entry.description,
created_on=request.now) for entry in d.entries])
It can be accessed at:
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:
db.define_table('animal',
Field('species'),
Field('genus'),
Field('family'))
and the following action:
def animals():
animals = db().select(db.animal.ALL)
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:
{{
import cStringIO
stream = cStringIO.StringIO()
animals.export_to_csv_file(stream)
response.headers['Content-Type'] = 'application/vnd.ms-excel'
response.write(stream.getvalue(), escape=False)
}}
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.