Skip to content

HTTPS clone URL

Subversion checkout URL

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