@jsonify.when('isinstance(obj, User)')
def jsonify_user(obj):
result = jsonify_sqlobject( obj )
del result['password']
result["groups"] = [g.group_name for g in obj.groups]
result["permissions"] = [p.permission_name for p in obj.permissions]
return result
The first line lets the default JSONifier rules handle the object; after that, it removes the "password" field for security reasons, and then adds support for fields that the default rules can't handle (like joins). The @jsonify.when decorator handles mapping the default jsonify() function to the type-specific version, so when you want to return a User object converted to JSON, you just return "jsonify(myUser)" and you're done.
This approach can be used for other purposes. For example, in one project, I kept running across is the need to render references to objects as links to view that object. For example, say you have a app that renders the text
Last updated at 12:00 by Joe
with the template snippet:
<p>Last updated at ${thing.last_update_time} by ${thing.update_user.display_name}</p>
Easy and straightforward. But if you want to link "Joe" to Joe's user profile page, then every time you want to do this, you end up writing something like:
<p>Last updated at ${thing.last_update_time} by
<a href="${'/users/%d' % thing.update_user.id}"
title="User profile for ${thing.update_user.display_name}"
${thing.update_user.display_name}
</a>
</p>
Then hours later you kick yourself because you find one place out of 20 where you made a typo in this monstrosity (see if you can find the one in the example above!).
So I "borrowed" jsonify's approach and created linkify.py for the project:
import dispatch
import model
import types
from elementtree import ElementTree
# Linkify generic methods... modeled after jsonify
@dispatch.generic()
def linkify(obj):
raise NotImplementedError
@linkify.when('isinstance(obj, model.User)')
def linkify_user(user):
link = ElementTree.Element('a',
href='/user/%d' % user.id,
title='User Profile for "%s"' % user.display_name)
link.text = user.display_name
return link
Then, in your controllers.py, you can make this available to templates:
# Add linkify to tg namespace
import linkify
def provide_linkify(vars):
vars['linkify'] = linkify.linkify
turbogears.view.variable_providers.append(provide_linkify)
And now, in your template, you just write:
<p>Last updated at ${thing.last_update_time} by ${tg.linkify(thing.update_user)}</p>
Much, much nicer.
2 comments:
I think it would actually better to use a TG widget here. The widget would contain the template (you could use Kid instead of ET Elements), and the logic for the output.
I agree--normally I would use a widget for something like this.
class UserLinkWidget(Widget):
template = '<a href="/users/${id}">${name}</a>'
user_link_widget = UserLinkWidget()
The problem is that it becomes cumbersome when you have half a dozen different entities that you want to be able to linkify (say, a User, a Blog, a Message, etc.).
Then you either have to pass in every link widget to every template (and it would end up being a lot), or else expose them all via a variable provider.
Plus "tg.linkify(u)" looks more concise to me than "user_link_widget.display(id=u.id, name=u.display_name)", and doesn't make me remember different code for different object types.
On the other hand, you could do a mix of both methods:
@linkify.when(...)
def linkify_user(obj):
return user_link_widget.display(id=obj.id, name=obj.display_name)
Post a Comment