Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 287 lines (226 sloc) 11.997 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'
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
231
232 def add_route_and_view(self, action, route_name, path, request_method='any'):
233 if request_method != 'any':
234 request_method = request_method.upper()
235 else:
236 request_method = None
237
238 self.add_route(route_name, path, **kwargs)
239 self.add_view(view=handler, attr=action, route_name=route_name, request_method=request_method, **action_kwargs.get(action, {}).get('default', {}))
240
241 for format_kwargs in action_kwargs.get(action, {}).get('formatted', []):
242 format = format_kwargs.pop('format')
243 self.add_route("%s_formatted_%s" % (format, route_name),
244 "%s.%s" % (path, format), **kwargs)
245 self.add_view(view=handler, attr=format_kwargs.pop('attr'), request_method=request_method,
246 route_name = "%s_formatted_%s" % (format, route_name), **format_kwargs)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
247
248 for method, lst in collection_methods.iteritems():
249 primary = (method != 'GET' and lst.pop(0)) or None
250 for action in lst:
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
251 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
252
253 if primary:
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
254 add_route_and_view(self, primary, name_prefix + collection_name, collection_path, method)
255
256 # Add route and view for collection
257 add_route_and_view(self, 'index', name_prefix + collection_name, collection_path, 'GET')
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
258
259 for method, lst in new_methods.iteritems():
260 for action in lst:
261 path = (action == 'new' and new_path) or "%s/%s" % (new_path, action)
262 name = "new_" + member_name
263 if action != 'new':
264 name = action + "_" + name
265 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
266 add_route_and_view(self, action, name_prefix + name, path, method)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
267
268 for method, lst in member_methods.iteritems():
269 if method not in ['POST', 'GET', 'any']:
270 primary = lst.pop(0)
271 else:
272 primary = None
273 for action in lst:
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
274 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
275
276 if primary:
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
277 add_route_and_view(self, primary, name_prefix + member_name, member_path, method)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
278
3be2383 fixed handling of various request methods. updated tests.
Yeeland Chen authored
279 add_route_and_view(self, 'show', name_prefix + member_name, member_path, method)
eab07f4 first pass at add_resource implementation
Yeeland Chen authored
280
281 # Submapper support
282
283
284 # Sub_domain option
285
286
287 # Converters??
Something went wrong with that request. Please try again.