|
1 """associate url's path to view identifier / rql queries |
|
2 |
|
3 It currently handle url's path with the forms |
|
4 |
|
5 * <publishing_method> |
|
6 |
|
7 * minimal REST publishing: |
|
8 * <eid> |
|
9 * <etype>[/<attribute name>/<attribute value>]* |
|
10 |
|
11 * folder navigation |
|
12 |
|
13 |
|
14 You can actually control URL (more exactly path) resolution using URL path |
|
15 evaluator. |
|
16 |
|
17 XXX actionpath and folderpath execute a query whose results is lost |
|
18 because of redirecting instead of direct traversal |
|
19 |
|
20 :organization: Logilab |
|
21 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
22 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
23 """ |
|
24 |
|
25 __docformat__ = "restructuredtext en" |
|
26 |
|
27 from rql import TypeResolverException |
|
28 |
|
29 from cubicweb import RegistryException, typed_eid |
|
30 from cubicweb.web import NotFound, Redirect |
|
31 from cubicweb.web.component import SingletonComponent, Component |
|
32 |
|
33 |
|
34 class PathDontMatch(Exception): |
|
35 """exception used by url evaluators to notify they can't evaluate |
|
36 a path |
|
37 """ |
|
38 |
|
39 class URLPublisherComponent(SingletonComponent): |
|
40 """associate url's path to view identifier / rql queries, |
|
41 by applying a chain of urlpathevaluator components. |
|
42 |
|
43 An evaluator is a URLPathEvaluator subclass with a .evaluate_path |
|
44 method taking the request object and the path to publish as |
|
45 argument. It will either returns a publishing method identifier |
|
46 and a rql query on success or raises a `PathDontMatch` exception |
|
47 on failure. URL evaluators are called according to their `priority` |
|
48 attribute, with 0 as the greatest priority and greater values as |
|
49 lower priority. The first evaluator returning a result or raising |
|
50 something else than `PathDontMatch` will stop the handlers chain. |
|
51 """ |
|
52 id = 'urlpublisher' |
|
53 |
|
54 def __init__(self, default_method='view'): |
|
55 super(URLPublisherComponent, self).__init__() |
|
56 self.default_method = default_method |
|
57 evaluators = [] |
|
58 for evaluatorcls in self.vreg.registry_objects('components', |
|
59 'urlpathevaluator'): |
|
60 # instantiation needed |
|
61 evaluator = evaluatorcls(self) |
|
62 evaluators.append(evaluator) |
|
63 self.evaluators = sorted(evaluators, key=lambda x: x.priority) |
|
64 |
|
65 def process(self, req, path): |
|
66 """given an url (essentialy caracterized by a path on the server, |
|
67 but additional information may be found in the request object), return |
|
68 a publishing method identifier (eg controller) and an optional result |
|
69 set |
|
70 |
|
71 :type req: `cubicweb.web.Request` |
|
72 :param req: the request object |
|
73 |
|
74 :type path: str |
|
75 :param path: the path of the resource to publish |
|
76 |
|
77 :rtype: tuple(str, `cubicweb.common.utils.ResultSet` or None) |
|
78 :return: the publishing method identifier and an optional result set |
|
79 |
|
80 :raise NotFound: if no handler is able to decode the given path |
|
81 """ |
|
82 parts = [part for part in path.split('/') |
|
83 if part != ''] or (self.default_method,) |
|
84 if req.form.get('rql'): |
|
85 if parts[0] in self.vreg.registry('controllers'): |
|
86 return parts[0], None |
|
87 return 'view', None |
|
88 for evaluator in self.evaluators: |
|
89 try: |
|
90 pmid, rset = evaluator.evaluate_path(req, parts[:]) |
|
91 break |
|
92 except PathDontMatch: |
|
93 continue |
|
94 else: |
|
95 raise NotFound(path) |
|
96 if pmid is None: |
|
97 pmid = self.default_method |
|
98 return pmid, rset |
|
99 |
|
100 |
|
101 class URLPathEvaluator(Component): |
|
102 __abstract__ = True |
|
103 id = 'urlpathevaluator' |
|
104 |
|
105 def __init__(self, urlpublisher): |
|
106 self.urlpublisher = urlpublisher |
|
107 |
|
108 |
|
109 class RawPathEvaluator(URLPathEvaluator): |
|
110 """handle path of the form:: |
|
111 |
|
112 <publishing_method>?parameters... |
|
113 """ |
|
114 priority = 0 |
|
115 def evaluate_path(self, req, parts): |
|
116 if len(parts) == 1 and parts[0] in self.vreg.registry('controllers'): |
|
117 return parts[0], None |
|
118 raise PathDontMatch() |
|
119 |
|
120 |
|
121 class EidPathEvaluator(URLPathEvaluator): |
|
122 """handle path with the form:: |
|
123 |
|
124 <eid> |
|
125 """ |
|
126 priority = 1 |
|
127 def evaluate_path(self, req, parts): |
|
128 if len(parts) != 1: |
|
129 raise PathDontMatch() |
|
130 try: |
|
131 rset = req.execute('Any X WHERE X eid %(x)s', |
|
132 {'x': typed_eid(parts[0])}, 'x') |
|
133 except ValueError: |
|
134 raise PathDontMatch() |
|
135 if rset.rowcount == 0: |
|
136 raise NotFound() |
|
137 return None, rset |
|
138 |
|
139 |
|
140 class RestPathEvaluator(URLPathEvaluator): |
|
141 """handle path with the form:: |
|
142 |
|
143 <etype>[[/<attribute name>]/<attribute value>]* |
|
144 """ |
|
145 priority = 2 |
|
146 def __init__(self, urlpublisher): |
|
147 super(RestPathEvaluator, self).__init__(urlpublisher) |
|
148 self.etype_map = {} |
|
149 for etype in self.schema.entities(): |
|
150 etype = str(etype) |
|
151 self.etype_map[etype.lower()] = etype |
|
152 |
|
153 def evaluate_path(self, req, parts): |
|
154 if not (0 < len(parts) < 4): |
|
155 raise PathDontMatch() |
|
156 try: |
|
157 etype = self.etype_map[parts.pop(0).lower()] |
|
158 except KeyError: |
|
159 raise PathDontMatch() |
|
160 cls = self.vreg.etype_class(etype) |
|
161 if parts: |
|
162 if len(parts) == 2: |
|
163 attrname = parts.pop(0).lower() |
|
164 try: |
|
165 cls.e_schema.subject_relation(attrname) |
|
166 except KeyError: |
|
167 raise PathDontMatch() |
|
168 else: |
|
169 attrname = cls._rest_attr_info()[0] |
|
170 value = req.url_unquote(parts.pop(0)) |
|
171 rset = self.attr_rset(req, etype, attrname, value) |
|
172 else: |
|
173 rset = self.cls_rset(req, cls) |
|
174 if rset.rowcount == 0: |
|
175 raise NotFound() |
|
176 return None, rset |
|
177 |
|
178 def cls_rset(self, req, cls): |
|
179 return req.execute(cls.fetch_rql(req.user)) |
|
180 |
|
181 def attr_rset(self, req, etype, attrname, value): |
|
182 rql = u'Any X WHERE X is %s, X %s %%(x)s' % (etype, attrname) |
|
183 if attrname == 'eid': |
|
184 try: |
|
185 rset = req.execute(rql, {'x': typed_eid(value)}, 'x') |
|
186 except (ValueError, TypeResolverException): |
|
187 # conflicting eid/type |
|
188 raise PathDontMatch() |
|
189 else: |
|
190 rset = req.execute(rql, {'x': value}) |
|
191 return rset |
|
192 |
|
193 |
|
194 class URLRewriteEvaluator(URLPathEvaluator): |
|
195 """tries to find a rewrite rule to apply |
|
196 |
|
197 URL rewrite rule definitions are stored in URLRewriter objects |
|
198 """ |
|
199 priority = 3 |
|
200 def evaluate_path(self, req, parts): |
|
201 # uri <=> req._twreq.path or req._twreq.uri |
|
202 uri = req.url_unquote('/' + '/'.join(parts)) |
|
203 vobjects = sorted(self.vreg.registry_objects('urlrewriting'), |
|
204 key=lambda x: x.priority, |
|
205 reverse=True) |
|
206 for rewritercls in vobjects: |
|
207 rewriter = rewritercls() |
|
208 try: |
|
209 # XXX we might want to chain url rewrites |
|
210 return rewriter.rewrite(req, uri) |
|
211 except KeyError: |
|
212 continue |
|
213 raise PathDontMatch() |
|
214 |
|
215 |
|
216 class ActionPathEvaluator(URLPathEvaluator): |
|
217 """handle path with the form:: |
|
218 |
|
219 <any evaluator path>/<action> |
|
220 """ |
|
221 priority = 4 |
|
222 def evaluate_path(self, req, parts): |
|
223 if len(parts) < 2: |
|
224 raise PathDontMatch() |
|
225 # remove last part and see if this is something like an actions |
|
226 # if so, call |
|
227 try: |
|
228 requested = parts.pop(-1) |
|
229 actions = self.vreg.registry_objects('actions', requested) |
|
230 except RegistryException: |
|
231 raise PathDontMatch() |
|
232 for evaluator in self.urlpublisher.evaluators: |
|
233 if evaluator is self or evaluator.priority == 0: |
|
234 continue |
|
235 try: |
|
236 pmid, rset = evaluator.evaluate_path(req, parts[:]) |
|
237 except PathDontMatch: |
|
238 continue |
|
239 else: |
|
240 try: |
|
241 action = self.vreg.select(actions, req, rset) |
|
242 except RegistryException: |
|
243 raise PathDontMatch() |
|
244 else: |
|
245 # XXX avoid redirect |
|
246 raise Redirect(action.url()) |
|
247 raise PathDontMatch() |