Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 295 lines (231 sloc) 12.304 kb
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
1 from pyramid.config import ConfigurationError
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
2 import inspect
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
3
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
4 __all__ = ['includeme', 'add_resource', 'action']
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
5
6 def includeme(config):
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
7 config.add_directive('add_resource', add_resource)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
8
9 def strip_slashes(name):
10 """Remove slashes from the beginning and end of a part/URL."""
11 if name.startswith('/'):
12 name = name[1:]
13 if name.endswith('/'):
14 name = name[:-1]
15 return name
16
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
17 class action(object):
18 """Decorate a method for registration by
19 :func:`~pyramid_routehelper.add_resource`.
20
21 Keyword arguments are identical to :class:`~pyramid.view.view_config`, with
22 the exception to how the ``name`` argument is used.
23
24 ``alt_for``
25 Designate a method as another view for the specified action if
26 the decorated method is not the desired action name instead of registering
27 the method with an action of the same name.
28
29 ``format``
30 Specify a format for the view that this decorator describes.
31 """
32 def __init__(self, **kw):
33 self.kw = kw
34
35 def __call__(self, wrapped):
36 if hasattr(wrapped, '__exposed__'):
37 wrapped.__exposed__.append(self.kw)
38 else:
39 wrapped.__exposed__ = [self.kw]
40 return wrapped
41
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
42 # map.resource port
43 def add_resource(self, handler, member_name, collection_name, **kwargs):
44 """ Add some RESTful routes for a resource handler.
45
46 This function should never be called directly; instead the
47 ``pyramid_routehelper.includeme`` function should be used to include this
48 function into an application; the function will thereafter be available
49 as a method of the resulting configurator.
50
51 The concept of a web resource maps somewhat directly to 'CRUD'
52 operations. The overlying things to keep in mind is that
53 adding a resource handler is about handling creating, viewing, and
54 editing that resource.
55
56 ``handler`` is a dotted name of (or direct reference to) a
57 Python handler class,
58 e.g. ``'my.package.handlers.MyHandler'``.
59
60 ``member_name`` should be the appropriate singular version of the resource
61 given your locale and used with members of the collection.
62
63 ``collection_name`` will be used to refer to the resource collection methods
64 and should be a plural version of the member_name argument.
65
66 All keyword arguments are optional.
67
68 ``collection``
69 Additional action mappings used to manipulate/view the
70 entire set of resources provided by the handler.
71
72 Example::
73
74 config.add_resource('myproject.handlers:MessageHandler', 'message', 'messages', collection={'rss':'GET'})
75 # GET /messages/rss (maps to the rss action)
76 # also adds named route "rss_message"
77
78 ``member``
79 Additional action mappings used to access an individual
80 'member' of this handler's resources.
81
82 Example::
83
84 config.add_resource('myproject.handlers:MessageHandler', 'message', 'messages', member={'mark':'POST'})
85 # POST /messages/1/mark (maps to the mark action)
86 # also adds named route "mark_message"
87
88 ``new``
89 Action mappings that involve dealing with a new member in
90 the controller resources.
91
92 Example::
93
94 config.add_resource('myproject.handlers:MessageHandler', 'message', 'messages', new={'preview':'POST'})
95 # POST /messages/new/preview (maps to the preview action)
96 # also adds a url named "preview_new_message"
97
98 ``path_prefix``
99 Prepends the URL path for the Route with the path_prefix
100 given. This is most useful for cases where you want to mix
101 resources or relations between resources.
102
103 ``name_prefix``
104 Perpends the route names that are generated with the
105 name_prefix given. Combined with the path_prefix option,
106 it's easy to generate route names and paths that represent
107 resources that are in relations.
108
109 Example::
110
111 config.add_resource('myproject.handlers:CategoryHandler', 'message', 'messages',
112 path_prefix='/category/:category_id',
113 name_prefix="category_")
114 # GET /category/7/messages/1
115 # has named route "category_message"
116
117 ``parent_resource``
118 A ``dict`` containing information about the parent
119 resource, for creating a nested resource. It should contain
120 the ``member_name`` and ``collection_name`` of the parent
121 resource.
122
123 If ``parent_resource`` is supplied and ``path_prefix``
124 isn't, ``path_prefix`` will be generated from
125 ``parent_resource`` as
126 "<parent collection name>/:<parent member name>_id".
127
128 If ``parent_resource`` is supplied and ``name_prefix``
129 isn't, ``name_prefix`` will be generated from
130 ``parent_resource`` as "<parent member name>_".
131
132 Example::
133
6e19e81 corrected example documentation
Yeeland Chen authored
134 >>> from pyramid.url import route_path
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
135 >>> config.add_resource('myproject.handlers:LocationHandler', 'location', 'locations',
136 ... parent_resource=dict(member_name='region',
137 ... collection_name='regions'))
138 >>> # path_prefix is "regions/:region_id"
139 >>> # name prefix is "region_"
6e19e81 corrected example documentation
Yeeland Chen authored
140 >>> route_path('region_locations', region_id=13)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
141 '/regions/13/locations'
6e19e81 corrected example documentation
Yeeland Chen authored
142 >>> route_path('region_new_location', region_id=13)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
143 '/regions/13/locations/new'
6e19e81 corrected example documentation
Yeeland Chen authored
144 >>> route_path('region_location', region_id=13, id=60)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
145 '/regions/13/locations/60'
6e19e81 corrected example documentation
Yeeland Chen authored
146 >>> route_path('region_edit_location', region_id=13, id=60)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
147 '/regions/13/locations/60/edit'
148
149 Overriding generated ``path_prefix``::
150
151 >>> config.add_resource('myproject.handlers:LocationHandler', 'location', 'locations',
152 ... parent_resource=dict(member_name='region',
153 ... collection_name='regions'),
154 ... path_prefix='areas/:area_id')
155 >>> # name prefix is "region_"
6e19e81 corrected example documentation
Yeeland Chen authored
156 >>> route_path('region_locations', area_id=51)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
157 '/areas/51/locations'
158
159 Overriding generated ``name_prefix``::
160
6e19e81 corrected example documentation
Yeeland Chen authored
161 >>> config.add_resource('myproject.handlers:LocationHandler', 'location', 'locations',
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
162 ... parent_resource=dict(member_name='region',
163 ... collection_name='regions'),
164 ... name_prefix='')
165 >>> # path_prefix is "regions/:region_id"
6e19e81 corrected example documentation
Yeeland Chen authored
166 >>> route_path('locations', region_id=51)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
167 '/regions/51/locations'
168 """
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
169 handler = self.maybe_dotted(handler)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
170
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
171 action_kwargs = {}
172 for name,meth in inspect.getmembers(handler, inspect.ismethod):
173 if hasattr(meth, '__exposed__'):
174 for settings in meth.__exposed__:
175 config_settings = settings.copy()
176 action_name = config_settings.pop('alt_for', name)
177
178 # If format is not set, use the route that doesn't specify a format
179 if 'format' not in config_settings:
180 if 'default' in action_kwargs.get(action_name,{}):
181 raise ConfigurationError("Two methods have been decorated without specifying a format.")
182 else:
183 action_kwargs.setdefault(action_name, {})['default'] = config_settings
184 # Otherwise, append to the list of view config settings for formatted views
185 else:
186 config_settings['attr'] = name
187 action_kwargs.setdefault(action_name, {}).setdefault('formatted',[]).append(config_settings)
188
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
189 collection = kwargs.pop('collection', {})
190 member = kwargs.pop('member', {})
191 new = kwargs.pop('new', {})
192 path_prefix = kwargs.pop('path_prefix', None)
193 name_prefix = kwargs.pop('name_prefix', None)
194 parent_resource = kwargs.pop('parent_resource', None)
195
196 if parent_resource is not None:
197 if path_prefix is None:
198 path_prefix = '%s/:%s_id' % (parent_resource['collection_name'], parent_resource['member_name'])
199 if name_prefix is None:
200 name_prefix = '%s_' % parent_resource['member_name']
201 else:
202 if path_prefix is None: path_prefix = ''
203 if name_prefix is None: name_prefix = ''
204
205 member['edit'] = 'GET'
206 new['new'] = 'GET'
207
208 def swap(dct, newdct):
209 map(lambda (key,value): newdct.setdefault(value.upper(), []).append(key), dct.items())
210 return newdct
211
212 collection_methods = swap(collection, {})
213 member_methods = swap(member, {})
214 new_methods = swap(new, {})
215
216 collection_methods.setdefault('POST', []).insert(0, 'create')
217 member_methods.setdefault('PUT', []).insert(0, 'update')
218 member_methods.setdefault('DELETE', []).insert(0, 'delete')
219
220 # Continue porting code
221 controller = strip_slashes(collection_name)
222 path_prefix = strip_slashes(path_prefix)
223 path_prefix = '/' + path_prefix
224 if path_prefix and path_prefix != '/':
225 path = path_prefix + '/' + controller
226 else:
227 path = '/' + controller
228 collection_path = path
229 new_path = path + '/new'
230 member_path = path + '/:id'
3acef5e track added routes to avoid duplicates (e.g. 'message' for show, upda…
Yeeland Chen authored
231
9c6f331 changed to be more DRY
Yeeland Chen authored
232 added_route_names = {}
233
234 def add_route_if_new(self, route_name, path, **kwargs):
235 if route_name not in added_route_names:
236 self.add_route(route_name, path, **kwargs)
237 added_route_names[route_name] = path
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
238
239 def add_route_and_view(self, action, route_name, path, request_method='any'):
240 if request_method != 'any':
241 request_method = request_method.upper()
242 else:
243 request_method = None
244
9c6f331 changed to be more DRY
Yeeland Chen authored
245 add_route_if_new(self, route_name, path, **kwargs)
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
246 self.add_view(view=handler, attr=action, route_name=route_name, request_method=request_method, **action_kwargs.get(action, {}).get('default', {}))
247
248 for format_kwargs in action_kwargs.get(action, {}).get('formatted', []):
249 format = format_kwargs.pop('format')
3acef5e track added routes to avoid duplicates (e.g. 'message' for show, upda…
Yeeland Chen authored
250 formatted_route_name = "%s_formatted_%s" % (format, route_name)
251
9c6f331 changed to be more DRY
Yeeland Chen authored
252 add_route_if_new(self, formatted_route_name, "%s.%s" % (path, format), **kwargs)
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
253 self.add_view(view=handler, attr=format_kwargs.pop('attr'), request_method=request_method,
254 route_name = "%s_formatted_%s" % (format, route_name), **format_kwargs)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
255
256 for method, lst in collection_methods.iteritems():
257 primary = (method != 'GET' and lst.pop(0)) or None
258 for action in lst:
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
259 add_route_and_view(self, action, "%s%s_%s" % (name_prefix, action, collection_name), "%s/%s" % (collection_path,action))
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
260
261 if primary:
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
262 add_route_and_view(self, primary, name_prefix + collection_name, collection_path, method)
263
264 # Add route and view for collection
265 add_route_and_view(self, 'index', name_prefix + collection_name, collection_path, 'GET')
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
266
267 for method, lst in new_methods.iteritems():
268 for action in lst:
269 path = (action == 'new' and new_path) or "%s/%s" % (new_path, action)
270 name = "new_" + member_name
271 if action != 'new':
272 name = action + "_" + name
273 formatted_path = (action == 'new' and new_path + '.:format') or "%s/%s.:format" % (new_path, action)
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
274 add_route_and_view(self, action, name_prefix + name, path, method)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
275
276 for method, lst in member_methods.iteritems():
277 if method not in ['POST', 'GET', 'any']:
278 primary = lst.pop(0)
279 else:
280 primary = None
281 for action in lst:
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
282 add_route_and_view(self, action, '%s%s_%s' % (name_prefix, action, member_name), '%s/%s' % (member_path, action))
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
283
284 if primary:
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
285 add_route_and_view(self, primary, name_prefix + member_name, member_path, method)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
286
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
287 add_route_and_view(self, 'show', name_prefix + member_name, member_path, method)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
288
289 # Submapper support
290
291
292 # Sub_domain option
293
294
295 # Converters??
Something went wrong with that request. Please try again.