Skip to content

Instantly share code, notes, and snippets.

@asplake
Created December 21, 2009 11:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save asplake/260907 to your computer and use it in GitHub Desktop.
Save asplake/260907 to your computer and use it in GitHub Desktop.
Routes enhancements
"Fixes and enhancements to Routes 1.11 - see http://positiveincline.com/?p=561"
# NB: superceded by http://bitbucket.org/asplake/routes/
import routes.mapper
COLLECTION_ACTIONS = ['index', 'create', 'new']
MEMBER_ACTIONS = ['show', 'update', 'delete', 'edit']
class SubMapperParent(object):
def submapper(self, **kwargs):
return SubMapper(self, **kwargs)
def collection(
self,
collection_name,
resource_name,
path_prefix=None,
member_prefix='/{id}',
controller=None,
collection_actions=COLLECTION_ACTIONS,
member_actions = MEMBER_ACTIONS,
member_options=None,
**kwargs):
if not controller:
controller =resource_name or collection_name
if not path_prefix:
path_prefix = '/' + collection_name
collection = SubMapper(
self,
collection_name=collection_name,
resource_name=resource_name,
path_prefix = path_prefix,
controller=controller,
actions=collection_actions,
**kwargs)
collection.member = SubMapper(
collection,
path_prefix = member_prefix,
actions=member_actions,
**(member_options or {}))
return collection
# Define a new Mapper that knows about our enhanced SubMapper
class Mapper(SubMapperParent, routes.Mapper):
# Pretty string representation
def __str__(self):
def format_methods(r):
if r.conditions:
method = r.conditions.get('method', '')
return method if type(method) is str else ', '.join(method)
else:
return ''
table = [('Route name', 'Methods', 'Path')] + [
(
r.name or '',
format_methods(r),
r.routepath or ''
)
for r in self.matchlist]
widths = [
max(len(row[col]) for row in table)
for col in range(len(table[0]))]
return '\n'.join(
' '.join(row[col].ljust(widths[col]) for col in range(len(widths)))
for row in table)
# Add some new tricks to SubMapper
class SubMapper(SubMapperParent, routes.mapper.SubMapper):
# Save a resource name on initialisation, defaulting it to the controller name
def __init__(self, obj, resource_name=None, collection_name=None, actions=None, **kwargs):
routes.mapper.SubMapper.__init__(self, obj, **kwargs)
self.collection_name = collection_name
self.member = None
self.resource_name = resource_name \
or getattr(obj, 'resource_name', None) \
or kwargs.get('controller', None) \
or getattr(obj, controller, None)
self.add_actions(actions or [])
# Fix known bug, support merging of dict-valued options (needed for nested collections)
def connect(self, *args, **kwargs):
newkargs = {}
newargs = args
for key, value in self.kwargs.items():
if key == 'path_prefix':
if len(args) > 1: # was len(args > 1)
newargs = (args[0], value + args[1])
else:
newargs = (value + args[0],)
elif key in kwargs:
if isinstance(value, dict):
newkargs[key] = dict(value, **kwargs[key]) # merge dicts
else:
newkargs[key] = value + kwargs[key]
else:
newkargs[key] = self.kwargs[key]
for key in kwargs:
if key not in self.kwargs:
newkargs[key] = kwargs[key]
return self.obj.connect(*newargs, **newkargs)
# Generate a subresource linked by "rel", e.g.
#
# with mapper.submapper(controller='thing', path_prefix='/things') as c:
# c.link('new')
# with c.submapper(path_prefix='/{id}')) as m:
# m.link('edit')
#
# generates
#
# mapper.connect(
# 'new_thing', '/things/edit',
# controller='thing', action='new',
# conditions={'method': 'GET'})
# mapper.connect(
# 'edit_thing', '/things/{id}/edit',
# controller='thing', action='edit',
# conditions={'method': 'GET'})
#
# Overridable defaults:
# name: {rel}_{self.resource_name}
# action: rel
# rel: name
# method: 'GET'
#
# At least one of rel and name (the route name) must be supplied. It would
# be unusual not to supply rel.
#
def link(self, rel=None, name=None, action=None, method='GET', **kwargs):
return self.connect(
name or (rel + '_' + self.resource_name),
'/' + (rel or name),
action=action or rel or name,
**_kwargs_with_conditions(kwargs, method))
def new(self, **kwargs):
return self.link(rel='new', **kwargs)
def edit(self, **kwargs):
return self.link(rel='edit', **kwargs)
# Generate an action (typically with the POST method) on a resource that
# supports other methods (typically GET).
#
# with mapper.submapper(controller='thing', path_prefix='/things') as m:
# with m.submapper(path_prefix='/{id}')) as o:
# o.action('show', name='thing')
# o.action('update', method='PUT')
#
# generates
#
# mapper.connect(
# 'thing', '/things/{id}',
# controller='thing', action='show',
# conditions={'method': 'GET'})
# mapper.connect(
# 'save_thing', '/things/{id}',
# controller='thing', action='update',
# conditions={'method': 'PUT'})
#
# Overridable defaults:
# name: {action}_{self.resource_name}
# action: name
# method: GET
#
# At least one of name (the route name) and action must be supplied.
#
def action(self, name=None, action=None, method='GET', **kwargs):
return self.connect(
name or (action + '_' + self.resource_name),
'',
action=action or name,
**_kwargs_with_conditions(kwargs, method))
def index(self, name=None, **kwargs):
return self.action(
name=name or self.collection_name,
action='index', method='GET', **kwargs)
def show(self, name = None, **kwargs):
return self.action(
name=name or self.resource_name,
action='show', method='GET', **kwargs)
def create(self, **kwargs):
return self.action(action='create', method='POST', **kwargs)
def update(self, **kwargs):
return self.action(action='update', method='PUT', **kwargs)
def delete(self, **kwargs):
return self.action(action='delete', method='DELETE', **kwargs)
def add_actions(self, actions):
[getattr(self, action)() for action in actions]
# Create kwargs with a 'conditions' member generated for the given method
def _kwargs_with_conditions(kwargs, method):
if method and 'conditions' not in kwargs:
newkwargs = kwargs.copy()
newkwargs['conditions'] = {'method': method}
return newkwargs
else:
return kwargs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment