comparison libervia/server/pages.py @ 1128:6414fd795df4

server, pages: multi-sites refactoring: Libervia is now handling external sites (i.e. other sites than Libervia official site). The external site are declared in sites_path_public_dict (in [DEFAULT] section) which is read by template engine, then they are linked to virtual host with vhosts_dict (linking host name to site name) in [libervia] section. Sites are only instanced once, so adding an alias is just a matter of mapping the alias host name in vhosts_dict with the same site name. menu_json and url_redirections_dict can now accept keys named after site name, which will be linked to the data for the site. Data for default site can still be keyed at first level. Libervia official pages are added to external site (if pages are not overriden), allowing to call pages of the framework and to have facilities like login handling. Deprecated url_redirections_profile option has been removed.
author Goffi <goffi@goffi.org>
date Fri, 14 Sep 2018 21:41:28 +0200
parents 9234f29053b0
children 02fc28aac2b6
comparison
equal deleted inserted replaced
1127:9234f29053b0 1128:6414fd795df4
23 from twisted.words.protocols.jabber import jid 23 from twisted.words.protocols.jabber import jid
24 from twisted.python import failure 24 from twisted.python import failure
25 25
26 from sat.core.i18n import _ 26 from sat.core.i18n import _
27 from sat.core import exceptions 27 from sat.core import exceptions
28 from sat.tools.common import uri as common_uri
29 from sat.tools.common import date_utils 28 from sat.tools.common import date_utils
30 from sat.core.log import getLogger 29 from sat.core.log import getLogger
31 30
32 log = getLogger(__name__) 31 log = getLogger(__name__)
33 from libervia.server.constants import Const as C 32 from libervia.server.constants import Const as C
34 from libervia.server import session_iface 33 from libervia.server import session_iface
35 from libervia.server.utils import quote, SubPage 34 from libervia.server.utils import quote, SubPage
36 import libervia
37 35
38 from collections import namedtuple 36 from collections import namedtuple
39 import uuid 37 import uuid
40 import os.path 38 import os.path
41 import urllib 39 import urllib
100 request.postpath = self._postpath[:] 98 request.postpath = self._postpath[:]
101 99
102 100
103 class LiberviaPage(web_resource.Resource): 101 class LiberviaPage(web_resource.Resource):
104 isLeaf = True #  we handle subpages ourself 102 isLeaf = True #  we handle subpages ourself
105 named_pages = {}
106 uri_callbacks = {}
107 signals_handlers = {} 103 signals_handlers = {}
108 pages_redirects = {}
109 cache = {} 104 cache = {}
110 cached_urls = {}
111 #  Set of tuples (service/node/sub_id) of nodes subscribed for caching 105 #  Set of tuples (service/node/sub_id) of nodes subscribed for caching
112 # sub_id can be empty string if not handled by service 106 # sub_id can be empty string if not handled by service
113 cache_pubsub_sub = set() 107 cache_pubsub_sub = set()
114 main_menu = None
115 108
116 def __init__( 109 def __init__(
117 self, 110 self, host, vhost_root, root_dir, url, name=None, redirect=None, access=None,
118 host, 111 dynamic=False, parse_url=None, prepare_render=None, render=None, template=None,
119 root_dir, 112 on_data_post=None, on_data=None, on_signal=None, url_cache=False,
120 url, 113 ):
121 name=None, 114 """Initiate LiberviaPage instance
122 redirect=None,
123 access=None,
124 dynamic=False,
125 parse_url=None,
126 prepare_render=None,
127 render=None,
128 template=None,
129 on_data_post=None,
130 on_data=None,
131 on_signal=None,
132 url_cache=False,
133 ):
134 """initiate LiberviaPages
135 115
136 LiberviaPages are the main resources of Libervia, using easy to set python files 116 LiberviaPages are the main resources of Libervia, using easy to set python files
137 The arguments are the variables found in page_meta.py 117 The non mandatory arguments are the variables found in page_meta.py
138 @param host(Libervia): the running instance of Libervia 118 @param host(Libervia): the running instance of Libervia
119 @param vhost_root(web_resource.Resource): root resource of the virtual host which
120 handle this page.
139 @param root_dir(unicode): aboslute file path of the page 121 @param root_dir(unicode): aboslute file path of the page
140 @param url(unicode): relative URL to the page 122 @param url(unicode): relative URL to the page
141 this URL may not be valid, as pages may require path arguments 123 this URL may not be valid, as pages may require path arguments
142 @param name(unicode, None): if not None, a unique name to identify the page 124 @param name(unicode, None): if not None, a unique name to identify the page
143 can then be used for e.g. redirection 125 can then be used for e.g. redirection
144 "/" is not allowed in names (as it can be used to construct URL paths) 126 "/" is not allowed in names (as it can be used to construct URL paths)
145 @param redirect(unicode, None): if not None, this page will be redirected. A redirected 127 @param redirect(unicode, None): if not None, this page will be redirected.
146 parameter is used as in self.pageRedirect. parse_url will not be skipped 128 A redirected parameter is used as in self.pageRedirect.
129 parse_url will not be skipped
147 using this redirect parameter is called "full redirection" 130 using this redirect parameter is called "full redirection"
148 using self.pageRedirect is called "partial redirection" (because some rendering method 131 using self.pageRedirect is called "partial redirection" (because some
149 can still be used, e.g. parse_url) 132 rendering method can still be used, e.g. parse_url)
150 @param access(unicode, None): permission needed to access the page 133 @param access(unicode, None): permission needed to access the page
151 None means public access. 134 None means public access.
152 Pages inherit from parent pages: e.g. if a "settings" page is restricted to admins, 135 Pages inherit from parent pages: e.g. if a "settings" page is restricted
153 and if "settings/blog" is public, it still can only be accessed by admins. 136 to admins, and if "settings/blog" is public, it still can only be accessed by
154 see C.PAGES_ACCESS_* for details 137 admins. See C.PAGES_ACCESS_* for details
155 @param dynamic(bool): if True, activate websocket for bidirectional communication 138 @param dynamic(bool): if True, activate websocket for bidirectional communication
156 @param parse_url(callable, None): if set it will be called to handle the URL path 139 @param parse_url(callable, None): if set it will be called to handle the URL path
157 after this method, the page will be rendered if noting is left in path (request.postpath) 140 after this method, the page will be rendered if noting is left in path
158 else a the request will be transmitted to a subpage 141 (request.postpath) else a the request will be transmitted to a subpage
159 @param prepare_render(callable, None): if set, will be used to prepare the rendering 142 @param prepare_render(callable, None): if set, will be used to prepare the
160 that often means gathering data using the bridge 143 rendering. That often means gathering data using the bridge
161 @param render(callable, None): if not template is set, this method will be called and 144 @param render(callable, None): if not template is set, this method will be
162 what it returns will be rendered. 145 called and what it returns will be rendered.
163 This method is mutually exclusive with template and must return a unicode string. 146 This method is mutually exclusive with template and must return a unicode
147 string.
164 @param template(unicode, None): path to the template to render. 148 @param template(unicode, None): path to the template to render.
165 This method is mutually exclusive with render 149 This method is mutually exclusive with render
166 @param on_data_post(callable, None): method to call when data is posted 150 @param on_data_post(callable, None): method to call when data is posted
167 None if not post is handled 151 None if not post is handled
168 on_data_post can return a string with following value: 152 on_data_post can return a string with following value:
169 - C.POST_NO_CONFIRM: confirm flag will not be set 153 - C.POST_NO_CONFIRM: confirm flag will not be set
170 @param on_data(callable, None): method to call when dynamic data is sent 154 @param on_data(callable, None): method to call when dynamic data is sent
171 this method is used with Libervia's websocket mechanism 155 this method is used with Libervia's websocket mechanism
172 @param on_signal(callable, None): method to call when a registered signal is received 156 @param on_signal(callable, None): method to call when a registered signal is
173 this method is used with Libervia's websocket mechanism 157 received. This method is used with Libervia's websocket mechanism
158 @param url_cache(boolean): if set, result of parse_url is cached (per profile).
159 Useful when costly calls (e.g. network) are done while parsing URL.
174 """ 160 """
175 161
176 web_resource.Resource.__init__(self) 162 web_resource.Resource.__init__(self)
177 self.host = host 163 self.host = host
164 self.vhost_root = vhost_root
178 self.root_dir = root_dir 165 self.root_dir = root_dir
179 self.url = url 166 self.url = url
180 self.name = name 167 self.name = name
181 if name is not None: 168 if name is not None:
182 if name in self.named_pages: 169 if name in self.named_pages:
183 raise exceptions.ConflictError( 170 raise exceptions.ConflictError(
184 _(u'a Libervia page named "{}" already exists'.format(name)) 171 _(u'a Libervia page named "{}" already exists'.format(name)))
185 )
186 if u"/" in name: 172 if u"/" in name:
187 raise ValueError(_(u'"/" is not allowed in page names')) 173 raise ValueError(_(u'"/" is not allowed in page names'))
188 if not name: 174 if not name:
189 raise ValueError(_(u"a page name can't be empty")) 175 raise ValueError(_(u"a page name can't be empty"))
190 self.named_pages[name] = self 176 self.named_pages[name] = self
206 if not all( 192 if not all(
207 lambda x: x is not None 193 lambda x: x is not None
208 for x in (parse_url, prepare_render, render, template) 194 for x in (parse_url, prepare_render, render, template)
209 ): 195 ):
210 raise ValueError( 196 raise ValueError(
211 _( 197 _(u"you can't use full page redirection with other rendering"
212 u"you can't use full page redirection with other rendering method," 198 u"method, check self.pageRedirect if you need to use them"))
213 u"check self.pageRedirect if you need to use them"
214 )
215 )
216 self.redirect = redirect 199 self.redirect = redirect
217 else: 200 else:
218 self.redirect = None 201 self.redirect = None
219 self.parse_url = parse_url 202 self.parse_url = parse_url
220 self.prepare_render = prepare_render 203 self.prepare_render = prepare_render
236 #  it must then contain a list of the the keys to use (without the page instance) 219 #  it must then contain a list of the the keys to use (without the page instance)
237 # e.g. [C.SERVICE_PROFILE, "pubsub", server@example.tld, pubsub_node] 220 # e.g. [C.SERVICE_PROFILE, "pubsub", server@example.tld, pubsub_node]
238 self._do_cache = None 221 self._do_cache = None
239 222
240 def __unicode__(self): 223 def __unicode__(self):
241 return u"LiberviaPage {name} at {url}".format( 224 return u"LiberviaPage {name} at {url} (vhost: {vhost_root})".format(
242 name=self.name or u"<anonymous>", url=self.url 225 name=self.name or u"<anonymous>", url=self.url, vhost_root=self.vhost_root)
243 )
244 226
245 def __str__(self): 227 def __str__(self):
246 return self.__unicode__().encode("utf-8") 228 return self.__unicode__().encode("utf-8")
247 229
230
231 @property
232 def named_pages(self):
233 return self.vhost_root.named_pages
234
235 @property
236 def uri_callbacks(self):
237 return self.vhost_root.uri_callbacks
238
239 @property
240 def pages_redirects(self):
241 return self.vhost_root.pages_redirects
242
243 @property
244 def cached_urls(self):
245 return self.vhost_root.cached_urls
246
247 @property
248 def main_menu(self):
249 return self.vhost_root.main_menu
250
248 @classmethod 251 @classmethod
249 def importPages(cls, host, parent=None, path=None): 252 def importPages(cls, host, vhost_root, root_path=None, _parent=None, _path=None,
250 """Recursively import Libervia pages""" 253 _extra_pages=False):
251 if path is None: 254 """Recursively import Libervia pages
252 path = [] 255
253 if parent is None: 256 @param host(Libervia): Libervia instance
254 root_dir = os.path.join(os.path.dirname(libervia.__file__), C.PAGES_DIR) 257 @param vhost_root(LiberviaRootResource): root of this VirtualHost
255 parent = host 258 @param root_path(unicode, None): use this root path instead of vhost_root's one
259 Used to add default site pages to external sites
260 @param _parent(Resource, None): _parent page. Do not set yourself, this is for
261 internal use only
262 @param _path(list(unicode), None): current path. Do not set yourself, this is for
263 internal use only
264 @param _extra_pages(boolean): set to True when extra pages are used (i.e.
265 root_path is set). Do not set yourself, this is for internal use only
266 """
267 if _path is None:
268 _path = []
269 if _parent is None:
270 if root_path is None:
271 root_dir = os.path.join(vhost_root.site_path, C.PAGES_DIR)
272 else:
273 root_dir = os.path.join(root_path, C.PAGES_DIR)
274 _extra_pages = True
275 _parent = vhost_root
256 else: 276 else:
257 root_dir = parent.root_dir 277 root_dir = _parent.root_dir
258 for d in os.listdir(root_dir): 278 for d in os.listdir(root_dir):
259 dir_path = os.path.join(root_dir, d) 279 dir_path = os.path.join(root_dir, d)
260 if not os.path.isdir(dir_path): 280 if not os.path.isdir(dir_path):
261 continue 281 continue
282 if _extra_pages and d in _parent.children:
283 log.debug(_(u"[{host_name}] {path} is already present, ignoring it").format(
284 host_name=vhost_root.host_name, path=u'/'.join(_path+[d])))
285 continue
262 meta_path = os.path.join(dir_path, C.PAGES_META_FILE) 286 meta_path = os.path.join(dir_path, C.PAGES_META_FILE)
263 if os.path.isfile(meta_path): 287 if os.path.isfile(meta_path):
264 page_data = {} 288 page_data = {}
265 new_path = path + [d] 289 new_path = _path + [d]
266 # we don't want to force the presence of __init__.py 290 # we don't want to force the presence of __init__.py
267 # so we use execfile instead of import. 291 # so we use execfile instead of import.
268 # TODO: when moved to Python 3, __init__.py is not mandatory anymore 292 # TODO: when moved to Python 3, __init__.py is not mandatory anymore
269 # so we can switch to import 293 # so we can switch to import
270 execfile(meta_path, page_data) 294 execfile(meta_path, page_data)
271 resource = LiberviaPage( 295 try:
272 host, 296 resource = LiberviaPage(
273 dir_path, 297 host=host,
274 u"/" + u"/".join(new_path), 298 vhost_root=vhost_root,
275 name=page_data.get("name"), 299 root_dir=dir_path,
276 redirect=page_data.get("redirect"), 300 url=u"/" + u"/".join(new_path),
277 access=page_data.get("access"), 301 name=page_data.get(u"name"),
278 dynamic=page_data.get("dynamic", False), 302 redirect=page_data.get(u"redirect"),
279 parse_url=page_data.get("parse_url"), 303 access=page_data.get(u"access"),
280 prepare_render=page_data.get("prepare_render"), 304 dynamic=page_data.get(u"dynamic", False),
281 render=page_data.get("render"), 305 parse_url=page_data.get(u"parse_url"),
282 template=page_data.get("template"), 306 prepare_render=page_data.get(u"prepare_render"),
283 on_data_post=page_data.get("on_data_post"), 307 render=page_data.get(u"render"),
284 on_data=page_data.get("on_data"), 308 template=page_data.get(u"template"),
285 on_signal=page_data.get("on_signal"), 309 on_data_post=page_data.get(u"on_data_post"),
286 url_cache=page_data.get("url_cache", False), 310 on_data=page_data.get(u"on_data"),
287 ) 311 on_signal=page_data.get(u"on_signal"),
288 parent.putChild(d, resource) 312 url_cache=page_data.get(u"url_cache", False),
289 log.info(u"Added /{path} page".format(path=u"[...]/".join(new_path))) 313 )
314 except exceptions.ConflictError as e:
315 if _extra_pages:
316 # extra pages are discarded if there is already an existing page
317 continue
318 else:
319 raise e
320 _parent.putChild(d, resource)
321 log_msg = (u"[{host_name}] Added /{path} page".format(
322 host_name=vhost_root.host_name,
323 path=u"[…]/".join(new_path)))
324 if _extra_pages:
325 log.debug(log_msg)
326 else:
327 log.info(log_msg)
290 if "uri_handlers" in page_data: 328 if "uri_handlers" in page_data:
291 if not isinstance(page_data, dict): 329 if not isinstance(page_data, dict):
292 log.error(_(u"uri_handlers must be a dict")) 330 log.error(_(u"uri_handlers must be a dict"))
293 else: 331 else:
294 for uri_tuple, cb_name in page_data["uri_handlers"].iteritems(): 332 for uri_tuple, cb_name in page_data["uri_handlers"].iteritems():
295 if len(uri_tuple) != 2 or not isinstance(cb_name, basestring): 333 if len(uri_tuple) != 2 or not isinstance(cb_name, basestring):
296 log.error(_(u"invalid uri_tuple")) 334 log.error(_(u"invalid uri_tuple"))
297 continue 335 continue
298 log.info(_(u"setting {}/{} URIs handler").format(*uri_tuple)) 336 if not _extra_pages:
337 log.info(_(u"setting {}/{} URIs handler")
338 .format(*uri_tuple))
299 try: 339 try:
300 cb = page_data[cb_name] 340 cb = page_data[cb_name]
301 except KeyError: 341 except KeyError:
302 log.error( 342 log.error(_(u"missing {name} method to handle {1}/{2}")
303 _(u"missing {name} method to handle {1}/{2}").format( 343 .format(name=cb_name, *uri_tuple))
304 name=cb_name, *uri_tuple
305 )
306 )
307 continue 344 continue
308 else: 345 else:
309 resource.registerURI(uri_tuple, cb) 346 resource.registerURI(uri_tuple, cb)
310 347
311 LiberviaPage.importPages(host, resource, new_path) 348 LiberviaPage.importPages(
312 349 host, vhost_root, _parent=resource, _path=new_path, _extra_pages=_extra_pages)
313 @classmethod
314 def setMenu(cls, menus):
315 main_menu = []
316 for menu in menus:
317 if not menu:
318 msg = _(u"menu item can't be empty")
319 log.error(msg)
320 raise ValueError(msg)
321 elif isinstance(menu, list):
322 if len(menu) != 2:
323 msg = _(
324 u"menu item as list must be in the form [page_name, absolue URL]"
325 )
326 log.error(msg)
327 raise ValueError(msg)
328 page_name, url = menu
329 else:
330 page_name = menu
331 try:
332 url = cls.getPageByName(page_name).url
333 except KeyError as e:
334 log.error(
335 _(
336 u"Can'find a named page ({msg}), please check menu_json in configuration."
337 ).format(msg=e)
338 )
339 raise e
340 main_menu.append((page_name, url))
341 cls.main_menu = main_menu
342 350
343 def registerURI(self, uri_tuple, get_uri_cb): 351 def registerURI(self, uri_tuple, get_uri_cb):
344 """register a URI handler 352 """register a URI handler
345 353
346 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler 354 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler
349 @param get_uri_cb(callable): method which take uri_data dict as only argument 357 @param get_uri_cb(callable): method which take uri_data dict as only argument
350 and return absolute path with correct arguments or None if the page 358 and return absolute path with correct arguments or None if the page
351 can't handle this URL 359 can't handle this URL
352 """ 360 """
353 if uri_tuple in self.uri_callbacks: 361 if uri_tuple in self.uri_callbacks:
354 log.info( 362 log.info(_(u"{}/{} URIs are already handled, replacing by the new handler")
355 _(u"{}/{} URIs are already handled, replacing by the new handler").format( 363 .format( *uri_tuple))
356 *uri_tuple
357 )
358 )
359 self.uri_callbacks[uri_tuple] = (self, get_uri_cb) 364 self.uri_callbacks[uri_tuple] = (self, get_uri_cb)
360 365
361 def registerSignal(self, request, signal, check_profile=True): 366 def registerSignal(self, request, signal, check_profile=True):
362 r"""register a signal handler 367 r"""register a signal handler
363 368
367 - signal name 372 - signal name
368 - signal arguments 373 - signal arguments
369 signal handler will be removed when connection with dynamic page will be lost 374 signal handler will be removed when connection with dynamic page will be lost
370 @param signal(unicode): name of the signal 375 @param signal(unicode): name of the signal
371 last arg of signal must be profile, as it will be checked to filter signals 376 last arg of signal must be profile, as it will be checked to filter signals
372 @param check_profile(bool): if True, signal profile (which MUST be last arg) will be 377 @param check_profile(bool): if True, signal profile (which MUST be last arg)
373 checked against session profile. 378 will be checked against session profile.
374 /!\ if False, profile will not be checked/filtered, be sure to know what you are doing 379 /!\ if False, profile will not be checked/filtered, be sure to know what you
375 if you unset this option /!\ 380 are doing if you unset this option /!\
376 """ 381 """
377 # FIXME: add a timeout, if socket is not opened before it, signal handler must be removed 382 # FIXME: add a timeout; if socket is not opened before it, signal handler
383 # must be removed
378 if not self.dynamic: 384 if not self.dynamic:
379 log.error(_(u"You can't register signal if page is not dynamic")) 385 log.error(_(u"You can't register signal if page is not dynamic"))
380 return 386 return
381 LiberviaPage.signals_handlers.setdefault(signal, {})[id(request)] = ( 387 LiberviaPage.signals_handlers.setdefault(signal, {})[id(request)] = (
382 self, 388 self,
383 request, 389 request,
384 check_profile, 390 check_profile,
385 ) 391 )
386 request._signals_registered.append(signal) 392 request._signals_registered.append(signal)
387 393
388 @classmethod 394 def getPageByName(self, name):
389 def getPagePathFromURI(cls, uri): 395 return self.vhost_root.getPageByName(name)
390 """Retrieve page URL from xmpp: URI 396
391 397 def getPagePathFromURI(self, uri):
392 @param uri(unicode): URI with a xmpp: scheme 398 return self.vhost.getPagePathFromURI(uri)
393 @return (unicode,None): absolute path (starting from root "/") to page handling the URI
394 None is returned if no page has been registered for this URI
395 """
396 uri_data = common_uri.parseXMPPUri(uri)
397 try:
398 page, cb = cls.uri_callbacks[uri_data["type"], uri_data["sub_type"]]
399 except KeyError:
400 url = None
401 else:
402 url = cb(page, uri_data)
403 if url is None:
404 # no handler found
405 # we try to find a more generic one
406 try:
407 page, cb = cls.uri_callbacks[uri_data["type"], None]
408 except KeyError:
409 pass
410 else:
411 url = cb(page, uri_data)
412 return url
413
414 @classmethod
415 def getPageByName(cls, name):
416 """retrieve page instance from its name
417
418 @param name(unicode): name of the page
419 @return (LiberviaPage): page instance
420 @raise KeyError: the page doesn't exist
421 """
422 return cls.named_pages[name]
423 399
424 def getPageRedirectURL(self, request, page_name=u"login", url=None): 400 def getPageRedirectURL(self, request, page_name=u"login", url=None):
425 """generate URL for a page with redirect_url parameter set 401 """generate URL for a page with redirect_url parameter set
426 402
427 mainly used for login page with redirection to current page 403 mainly used for login page with redirection to current page
548 ) 524 )
549 525
550 def getURLByNames(self, named_path): 526 def getURLByNames(self, named_path):
551 """retrieve URL from pages names and arguments 527 """retrieve URL from pages names and arguments
552 528
529 @param request(server.Request): request linked to the session
553 @param named_path(list[tuple[unicode, list[unicode]]]): path to the page as a list 530 @param named_path(list[tuple[unicode, list[unicode]]]): path to the page as a list
554 of tuples of 2 items: 531 of tuples of 2 items:
555 - first item is page name 532 - first item is page name
556 - second item is list of path arguments of this page 533 - second item is list of path arguments of this page
557 @return (unicode): URL to the requested page with given path arguments 534 @return (unicode): URL to the requested page with given path arguments
568 page_name, parent=current_page 545 page_name, parent=current_page
569 ) 546 )
570 path.append(sub_path) 547 path.append(sub_path)
571 if page_args: 548 if page_args:
572 path.extend([quote(a) for a in page_args]) 549 path.extend([quote(a) for a in page_args])
573 return self.host.checkRedirection(u"/".join(path)) 550 return self.host.checkRedirection(self.vhost_root, u"/".join(path))
574 551
575 def getURLByPath(self, *args): 552 def getURLByPath(self, *args):
576 """generate URL by path 553 """generate URL by path
577 554
578 this method as a similar effect as getURLByNames, but it is more readable 555 this method as a similar effect as getURLByNames, but it is more readable
579 by using SubPage to get pages instead of using tuples 556 by using SubPage to get pages instead of using tuples
557 @param request(server.Request): request linked to the session
580 @param *args: path element: 558 @param *args: path element:
581 - if unicode, will be used as argument 559 - if unicode, will be used as argument
582 - if util.SubPage instance, must be the name of a subpage 560 - if util.SubPage instance, must be the name of a subpage
583 @return (unicode): generated path 561 @return (unicode): generated path
584 """ 562 """
605 if not args: 583 if not args:
606 break 584 break
607 else: 585 else:
608 path, current_page = current_page.getSubPageByName(args.pop(0)) 586 path, current_page = current_page.getSubPageByName(args.pop(0))
609 arguments = [path] 587 arguments = [path]
610 return self.host.checkRedirection(u"/".join(url_elts)) 588 return self.host.checkRedirection(self.vhost_root, u"/".join(url_elts))
611 589
612 def getChildWithDefault(self, path, request): 590 def getChildWithDefault(self, path, request):
613 # we handle children ourselves 591 # we handle children ourselves
614 raise exceptions.InternalError( 592 raise exceptions.InternalError(
615 u"this method should not be used with LiberviaPage" 593 u"this method should not be used with LiberviaPage"
653 """get several path arguments at once 631 """get several path arguments at once
654 632
655 Arguments will be put in request data. 633 Arguments will be put in request data.
656 Missing arguments will have None value 634 Missing arguments will have None value
657 @param names(list[unicode]): list of arguments to get 635 @param names(list[unicode]): list of arguments to get
658 @param min_args(int): if less than min_args are found, PageError is used with C.HTTP_BAD_REQUEST 636 @param min_args(int): if less than min_args are found, PageError is used with
659 use 0 to ignore 637 C.HTTP_BAD_REQUEST
638 Use 0 to ignore
660 @param **kwargs: special value or optional callback to use for arguments 639 @param **kwargs: special value or optional callback to use for arguments
661 names of the arguments must correspond to those in names 640 names of the arguments must correspond to those in names
662 special values may be: 641 special values may be:
663 - '': use empty string instead of None when no value is specified 642 - '': use empty string instead of None when no value is specified
664 - '@': if value of argument is empty or '@', empty string will be used 643 - '@': if value of argument is empty or '@', empty string will be used
665 - 'jid': value must be converted to jid.JID if it exists, else empty string is used 644 - 'jid': value must be converted to jid.JID if it exists, else empty
666 - '@jid': if value of arguments is empty or '@', empty string will be used, else it will be converted to jid 645 string is used
646 - '@jid': if value of arguments is empty or '@', empty string will be
647 used, else it will be converted to jid
667 """ 648 """
668 data = self.getRData(request) 649 data = self.getRData(request)
669 650
670 for idx, name in enumerate(names): 651 for idx, name in enumerate(names):
671 if name[0] == u"*": 652 if name[0] == u"*":
686 idx -= 1 667 idx -= 1
687 break 668 break
688 669
689 values_count = idx + 1 670 values_count = idx + 1
690 if values_count < min_args: 671 if values_count < min_args:
691 log.warning( 672 log.warning(_(u"Missing arguments in URL (got {count}, expected at least "
692 _( 673 u"{min_args})").format(count=values_count, min_args=min_args))
693 u"Missing arguments in URL (got {count}, expected at least {min_args})"
694 ).format(count=values_count, min_args=min_args)
695 )
696 self.pageError(request, C.HTTP_BAD_REQUEST) 674 self.pageError(request, C.HTTP_BAD_REQUEST)
697 675
698 for name in names[values_count:]: 676 for name in names[values_count:]:
699 data[name] = None 677 data[name] = None
700 678
765 if not node: 743 if not node:
766 try: 744 try:
767 short = kwargs["short"] 745 short = kwargs["short"]
768 node = self.host.ns_map[short] 746 node = self.host.ns_map[short]
769 except KeyError: 747 except KeyError:
770 log.warning( 748 log.warning(_(u'Can\'t use cache for empty node without namespace '
771 _( 749 u'set, please ensure to set "short" and that it is '
772 u'Can\'t use cache for empty node without namespace set, please ensure to set "short" and that it is registered' 750 u'registered'))
773 )
774 )
775 return 751 return
776 if profile != C.SERVICE_PROFILE: 752 if profile != C.SERVICE_PROFILE:
777 #  only service profile is cache for now 753 #  only service profile is cache for now
778 return 754 return
779 try: 755 try:
780 cache = self.cache[profile][cache_type][service][node][request.uri][self] 756 cache = (self.cache[profile][cache_type][service][node]
757 [self.vhost_root][request.uri][self])
781 except KeyError: 758 except KeyError:
782 # no cache yet, let's subscribe to the pubsub node 759 # no cache yet, let's subscribe to the pubsub node
783 d1 = self.host.bridgeCall( 760 d1 = self.host.bridgeCall(
784 "psSubscribe", service.full(), node, {}, profile 761 "psSubscribe", service.full(), node, {}, profile
785 ) 762 )
786 d1.addCallback(self.checkCacheSubscribeCb, service, node) 763 d1.addCallback(self.checkCacheSubscribeCb, service, node)
787 d1.addErrback(self.checkCacheSubscribeEb, service, node) 764 d1.addErrback(self.checkCacheSubscribeEb, service, node)
788 d2 = self.host.bridgeCall("psNodeWatchAdd", service.full(), node, profile) 765 d2 = self.host.bridgeCall("psNodeWatchAdd", service.full(), node, profile)
789 d2.addErrback(self.psNodeWatchAddEb, service, node) 766 d2.addErrback(self.psNodeWatchAddEb, service, node)
790 self._do_cache = [self, profile, cache_type, service, node, request.uri] 767 self._do_cache = [self, profile, cache_type, service, node,
768 self.vhost_root, request.uri]
791 #  we don't return the Deferreds as it is not needed to wait for 769 #  we don't return the Deferreds as it is not needed to wait for
792 # the subscription to continue with page rendering 770 # the subscription to continue with page rendering
793 return 771 return
794 772
795 else: 773 else:
800 self._checkCacheHeaders(request, cache) 778 self._checkCacheHeaders(request, cache)
801 request.write(cache.rendered) 779 request.write(cache.rendered)
802 request.finish() 780 request.finish()
803 raise failure.Failure(exceptions.CancelError(u"cache is used")) 781 raise failure.Failure(exceptions.CancelError(u"cache is used"))
804 782
805 def _cacheURL(self, dummy, request, profile): 783 def _cacheURL(self, __, request, profile):
806 self.cached_urls.setdefault(profile, {})[request.uri] = CacheURL(request) 784 self.cached_urls.setdefault(profile, {})[request.uri] = CacheURL(request)
807 785
808 @classmethod 786 @classmethod
809 def onNodeEvent(cls, host, service, node, event_type, items, profile): 787 def onNodeEvent(cls, host, service, node, event_type, items, profile):
810 """Invalidate cache for all pages linked to this node""" 788 """Invalidate cache for all pages linked to this node"""
811 try: 789 try:
812 cache = cls.cache[profile][C.CACHE_PUBSUB][jid.JID(service)][node] 790 cache = cls.cache[profile][C.CACHE_PUBSUB][jid.JID(service)][node]
813 except KeyError: 791 except KeyError:
814 log.info( 792 log.info(_(
815 _( 793 u"Removing subscription for {service}/{node}: "
816 u"Removing subscription for {service}/{node}: " 794 u"the page is not cached").format(service=service, node=node))
817 u"the page is not cached"
818 ).format(service=service, node=node)
819 )
820 d1 = host.bridgeCall("psUnsubscribe", service, node, profile) 795 d1 = host.bridgeCall("psUnsubscribe", service, node, profile)
821 d1.addErrback( 796 d1.addErrback(
822 lambda failure_: log.warning( 797 lambda failure_: log.warning(
823 _(u"Can't unsubscribe from {service}/{node}: {msg}").format( 798 _(u"Can't unsubscribe from {service}/{node}: {msg}").format(
824 service=service, node=node, msg=failure_ 799 service=service, node=node, msg=failure_)))
825 )
826 )
827 )
828 d2 = host.bridgeCall("psNodeWatchAdd", service, node, profile) 800 d2 = host.bridgeCall("psNodeWatchAdd", service, node, profile)
829 # TODO: check why the page is not in cache, remove subscription? 801 # TODO: check why the page is not in cache, remove subscription?
830 d2.addErrback( 802 d2.addErrback(
831 lambda failure_: log.warning( 803 lambda failure_: log.warning(
832 _(u"Can't remove watch for {service}/{node}: {msg}").format( 804 _(u"Can't remove watch for {service}/{node}: {msg}").format(
833 service=service, node=node, msg=failure_ 805 service=service, node=node, msg=failure_)))
834 )
835 )
836 )
837 else: 806 else:
838 cache.clear() 807 cache.clear()
839 808
840 @classmethod 809 @classmethod
841 def onSignal(cls, host, signal, *args): 810 def onSignal(cls, host, signal, *args):
887 """ 856 """
888 for signal in request._signals_registered: 857 for signal in request._signals_registered:
889 try: 858 try:
890 del LiberviaPage.signals_handlers[signal][id(request)] 859 del LiberviaPage.signals_handlers[signal][id(request)]
891 except KeyError: 860 except KeyError:
892 log.error( 861 log.error(_(u"Can't find signal handler for [{signal}], this should not "
893 _( 862 u"happen").format(signal=signal))
894 u"Can't find signal handler for [{signal}], this should not happen"
895 ).format(signal=signal)
896 )
897 else: 863 else:
898 log.debug(_(u"Removed signal handler")) 864 log.debug(_(u"Removed signal handler"))
899 865
900 def delegateToResource(self, request, resource): 866 def delegateToResource(self, request, resource):
901 """continue workflow with Twisted Resource""" 867 """continue workflow with Twisted Resource"""
950 else: 916 else:
951 - first element is name as registered in name variable 917 - first element is name as registered in name variable
952 - following element are subpages path 918 - following element are subpages path
953 e.g.: "blog" redirect to page named "blog" 919 e.g.: "blog" redirect to page named "blog"
954 "blog/atom.xml" redirect to atom.xml subpage of "blog" 920 "blog/atom.xml" redirect to atom.xml subpage of "blog"
955 "/common/blog/atom.xml" redirect to the page at the fiven full path 921 "/common/blog/atom.xml" redirect to the page at the given full path
956 @param request(server.Request): current HTTP request 922 @param request(server.Request): current HTTP request
957 @param skip_parse_url(bool): if True, parse_url method on redirect page will be skipped 923 @param skip_parse_url(bool): if True, parse_url method on redirect page will be
924 skipped
958 @param path_args(list[unicode], None): path arguments to use in redirected page 925 @param path_args(list[unicode], None): path arguments to use in redirected page
959 @raise KeyError: there is no known page with this name 926 @raise KeyError: there is no known page with this name
960 """ 927 """
961 # FIXME: render non LiberviaPage resources 928 # FIXME: render non LiberviaPage resources
962 path = page_path.rstrip(u"/").split(u"/") 929 path = page_path.rstrip(u"/").split(u"/")
963 if not path[0]: 930 if not path[0]:
964 redirect_page = self.host.root 931 redirect_page = self.vhost_root
965 else: 932 else:
966 redirect_page = self.named_pages[path[0]] 933 redirect_page = self.named_pages[path[0]]
967 934
968 for subpage in path[1:]: 935 for subpage in path[1:]:
969 if redirect_page is self.host.root: 936 if redirect_page is self.vhost_root:
970 redirect_page = redirect_page.children[subpage] 937 redirect_page = redirect_page.children[subpage]
971 else: 938 else:
972 redirect_page = redirect_page.original.children[subpage] 939 redirect_page = redirect_page.original.children[subpage]
973 940
974 if path_args is not None: 941 if path_args is not None:
993 request.setResponseCode(code) 960 request.setResponseCode(code)
994 if no_body: 961 if no_body:
995 request.finish() 962 request.finish()
996 else: 963 else:
997 template = u"error/" + unicode(code) + ".html" 964 template = u"error/" + unicode(code) + ".html"
965 if self.vhost_root.site_name:
966 request.template_data[u'site'] = self.vhost_root.site_name
998 967
999 rendered = self.host.renderer.render( 968 rendered = self.host.renderer.render(
1000 template, 969 template,
1001 error_code=code, 970 error_code=code,
1002 **request.template_data 971 **request.template_data
1014 if self._do_cache is not None: 983 if self._do_cache is not None:
1015 redirected_page = self._do_cache.pop(0) 984 redirected_page = self._do_cache.pop(0)
1016 cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache) 985 cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache)
1017 page_cache = cache[redirected_page] = CachePage(data_encoded) 986 page_cache = cache[redirected_page] = CachePage(data_encoded)
1018 self._setCacheHeaders(request, page_cache) 987 self._setCacheHeaders(request, page_cache)
1019 log.debug( 988 log.debug(_(u"{page} put in cache for [{profile}]")
1020 _(u"{page} put in cache for [{profile}]").format( 989 .format( page=self, profile=self._do_cache[0]))
1021 page=self, profile=self._do_cache[0]
1022 )
1023 )
1024 self._do_cache = None 990 self._do_cache = None
1025 self._checkCacheHeaders(request, page_cache) 991 self._checkCacheHeaders(request, page_cache)
1026 992
1027 request.write(data_encoded) 993 request.write(data_encoded)
1028 request.finish() 994 request.finish()
1029 995
1030 def _subpagesHandler(self, dummy, request): 996 def _subpagesHandler(self, __, request):
1031 """render subpage if suitable 997 """render subpage if suitable
1032 998
1033 this method checks if there is still an unmanaged part of the path 999 this method checks if there is still an unmanaged part of the path
1034 and check if it corresponds to a subpage. If so, it render the subpage 1000 and check if it corresponds to a subpage. If so, it render the subpage
1035 else it render a NoResource. 1001 else it render a NoResource.
1043 self.pageError(request) 1009 self.pageError(request)
1044 else: 1010 else:
1045 child.render(request) 1011 child.render(request)
1046 raise failure.Failure(exceptions.CancelError(u"subpage page is used")) 1012 raise failure.Failure(exceptions.CancelError(u"subpage page is used"))
1047 1013
1048 def _prepare_dynamic(self, dummy, request): 1014 def _prepare_dynamic(self, __, request):
1049 # we need to activate dynamic page 1015 # we need to activate dynamic page
1050 # we set data for template, and create/register token 1016 # we set data for template, and create/register token
1051 socket_token = unicode(uuid.uuid4()) 1017 socket_token = unicode(uuid.uuid4())
1052 socket_url = self.host.getWebsocketURL(request) 1018 socket_url = self.host.getWebsocketURL(request)
1053 socket_debug = C.boolConst(self.host.debug) 1019 socket_debug = C.boolConst(self.host.debug)
1058 # we will keep track of handlers to remove 1024 # we will keep track of handlers to remove
1059 request._signals_registered = [] 1025 request._signals_registered = []
1060 # we will cache registered signals until socket is opened 1026 # we will cache registered signals until socket is opened
1061 request._signals_cache = [] 1027 request._signals_cache = []
1062 1028
1063 def _prepare_render(self, dummy, request): 1029 def _prepare_render(self, __, request):
1064 return defer.maybeDeferred(self.prepare_render, self, request) 1030 return defer.maybeDeferred(self.prepare_render, self, request)
1065 1031
1066 def _render_method(self, dummy, request): 1032 def _render_method(self, __, request):
1067 return defer.maybeDeferred(self.render_method, self, request) 1033 return defer.maybeDeferred(self.render_method, self, request)
1068 1034
1069 def _render_template(self, dummy, request): 1035 def _render_template(self, __, request):
1070 template_data = request.template_data 1036 template_data = request.template_data
1071 1037
1072 # if confirm variable is set in case of successfuly data post 1038 # if confirm variable is set in case of successfuly data post
1073 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1039 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1074 if session_data.popPageFlag(self, C.FLAG_CONFIRM): 1040 if session_data.popPageFlag(self, C.FLAG_CONFIRM):
1075 template_data[u"confirm"] = True 1041 template_data[u"confirm"] = True
1042 if self.vhost_root.site_name:
1043 template_data[u'site'] = self.vhost_root.site_name
1076 1044
1077 return self.host.renderer.render( 1045 return self.host.renderer.render(
1078 self.template, 1046 self.template,
1079 media_path="/" + C.MEDIA_DIR, 1047 media_path="/" + C.MEDIA_DIR,
1080 cache_path=session_data.cache_dir, 1048 cache_path=session_data.cache_dir,
1081 main_menu=LiberviaPage.main_menu, 1049 main_menu=self.main_menu,
1082 **template_data 1050 **template_data)
1083 )
1084 1051
1085 def _renderEb(self, failure_, request): 1052 def _renderEb(self, failure_, request):
1086 """don't raise error on CancelError""" 1053 """don't raise error on CancelError"""
1087 failure_.trap(exceptions.CancelError) 1054 failure_.trap(exceptions.CancelError)
1088 1055
1089 def _internalError(self, failure_, request): 1056 def _internalError(self, failure_, request):
1090 """called if an error is not catched""" 1057 """called if an error is not catched"""
1091 log.error( 1058 log.error(_(u"Uncatched error for HTTP request on {url}: {msg}")
1092 _(u"Uncatched error for HTTP request on {url}: {msg}").format( 1059 .format( url=request.URLPath(), msg=failure_))
1093 url=request.URLPath(), msg=failure_
1094 )
1095 )
1096 self.pageError(request, C.HTTP_INTERNAL_ERROR) 1060 self.pageError(request, C.HTTP_INTERNAL_ERROR)
1097 1061
1098 def _on_data_post_redirect(self, ret, request): 1062 def _on_data_post_redirect(self, ret, request):
1099 """called when page's on_data_post has been done successfuly 1063 """called when page's on_data_post has been done successfuly
1100 1064
1101 This will do a Post/Redirect/Get pattern. 1065 This will do a Post/Redirect/Get pattern.
1102 this method redirect to the same page or to request.data['post_redirect_page'] 1066 this method redirect to the same page or to request.data['post_redirect_page']
1103 post_redirect_page can be either a page or a tuple with page as first item, then a list of unicode arguments to append to the url. 1067 post_redirect_page can be either a page or a tuple with page as first item, then
1104 if post_redirect_page is not used, initial request.uri (i.e. the same page as where the data have been posted) will be used for redirection. 1068 a list of unicode arguments to append to the url.
1105 HTTP status code "See Other" (303) is used as it is the recommanded code in this case. 1069 if post_redirect_page is not used, initial request.uri (i.e. the same page as
1070 where the data have been posted) will be used for redirection.
1071 HTTP status code "See Other" (303) is used as it is the recommanded code in
1072 this case.
1106 @param ret(None, unicode, iterable): on_data_post return value 1073 @param ret(None, unicode, iterable): on_data_post return value
1107 see LiberviaPage.__init__ on_data_post docstring 1074 see LiberviaPage.__init__ on_data_post docstring
1108 """ 1075 """
1109 if ret is None: 1076 if ret is None:
1110 ret = () 1077 ret = ()
1135 request.setResponseCode(C.HTTP_SEE_OTHER) 1102 request.setResponseCode(C.HTTP_SEE_OTHER)
1136 request.setHeader("location", redirect_uri) 1103 request.setHeader("location", redirect_uri)
1137 request.finish() 1104 request.finish()
1138 raise failure.Failure(exceptions.CancelError(u"Post/Redirect/Get is used")) 1105 raise failure.Failure(exceptions.CancelError(u"Post/Redirect/Get is used"))
1139 1106
1140 def _on_data_post(self, dummy, request): 1107 def _on_data_post(self, __, request):
1141 csrf_token = self.host.getSessionData( 1108 csrf_token = self.host.getSessionData(
1142 request, session_iface.ISATSession 1109 request, session_iface.ISATSession
1143 ).csrf_token 1110 ).csrf_token
1144 try: 1111 try:
1145 given_csrf = self.getPostedData(request, u"csrf_token") 1112 given_csrf = self.getPostedData(request, u"csrf_token")
1163 @param keys(unicode, iterable[unicode]): name of the value(s) to get 1130 @param keys(unicode, iterable[unicode]): name of the value(s) to get
1164 unicode to get one value 1131 unicode to get one value
1165 iterable to get more than one 1132 iterable to get more than one
1166 @param multiple(bool): True if multiple values are possible/expected 1133 @param multiple(bool): True if multiple values are possible/expected
1167 if False, the first value is returned 1134 if False, the first value is returned
1168 @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]): values received for this(these) key(s) 1135 @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]):
1136 values received for this(these) key(s)
1169 @raise KeyError: one specific key has been requested, and it is missing 1137 @raise KeyError: one specific key has been requested, and it is missing
1170 """ 1138 """
1171 #  FIXME: request.args is already unquoting the value, it seems we are doing double unquote 1139 #  FIXME: request.args is already unquoting the value, it seems we are doing
1140 # double unquote
1172 if isinstance(keys, basestring): 1141 if isinstance(keys, basestring):
1173 keys = [keys] 1142 keys = [keys]
1174 get_first = True 1143 get_first = True
1175 else: 1144 else:
1176 get_first = False 1145 get_first = False
1220 1189
1221 def getRData(self, request): 1190 def getRData(self, request):
1222 """helper method to get request data dict 1191 """helper method to get request data dict
1223 1192
1224 this dictionnary if for the request only, it is not saved in session 1193 this dictionnary if for the request only, it is not saved in session
1225 It is mainly used to pass data between pages/methods called during request workflow 1194 It is mainly used to pass data between pages/methods called during request
1195 workflow
1226 @return (dict): request data 1196 @return (dict): request data
1227 """ 1197 """
1228 try: 1198 try:
1229 return request.data 1199 return request.data
1230 except AttributeError: 1200 except AttributeError:
1264 if not self.dynamic: 1234 if not self.dynamic:
1265 raise exceptions.InternalError( 1235 raise exceptions.InternalError(
1266 _(u"renderPartial must only be used with dynamic pages") 1236 _(u"renderPartial must only be used with dynamic pages")
1267 ) 1237 )
1268 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1238 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1239 if self.vhost_root.site_name:
1240 template_data[u'site'] = self.vhost_root.site_name
1269 1241
1270 return self.host.renderer.render( 1242 return self.host.renderer.render(
1271 template, 1243 template,
1272 media_path="/" + C.MEDIA_DIR, 1244 media_path="/" + C.MEDIA_DIR,
1273 cache_path=session_data.cache_dir, 1245 cache_path=session_data.cache_dir,
1274 main_menu=LiberviaPage.main_menu, 1246 main_menu=self.main_menu,
1275 **template_data 1247 **template_data
1276 ) 1248 )
1277 1249
1278 def renderAndUpdate( 1250 def renderAndUpdate(
1279 self, request, template, selectors, template_data_update, update_type="append" 1251 self, request, template, selectors, template_data_update, update_type="append"
1317 d = defer.Deferred() 1289 d = defer.Deferred()
1318 d.addCallback(self._checkAccess, request) 1290 d.addCallback(self._checkAccess, request)
1319 1291
1320 if self.redirect is not None: 1292 if self.redirect is not None:
1321 d.addCallback( 1293 d.addCallback(
1322 lambda dummy: self.pageRedirect( 1294 lambda __: self.pageRedirect(
1323 self.redirect, request, skip_parse_url=False 1295 self.redirect, request, skip_parse_url=False
1324 ) 1296 )
1325 ) 1297 )
1326 1298
1327 if self.parse_url is not None and not skip_parse_url: 1299 if self.parse_url is not None and not skip_parse_url:
1342 1314
1343 d.addCallback(self._subpagesHandler, request) 1315 d.addCallback(self._subpagesHandler, request)
1344 1316
1345 if request.method not in (C.HTTP_METHOD_GET, C.HTTP_METHOD_POST): 1317 if request.method not in (C.HTTP_METHOD_GET, C.HTTP_METHOD_POST):
1346 # only HTTP GET and POST are handled so far 1318 # only HTTP GET and POST are handled so far
1347 d.addCallback(lambda dummy: self.pageError(request, C.HTTP_BAD_REQUEST)) 1319 d.addCallback(lambda __: self.pageError(request, C.HTTP_BAD_REQUEST))
1348 1320
1349 if request.method == C.HTTP_METHOD_POST: 1321 if request.method == C.HTTP_METHOD_POST:
1350 if self.on_data_post is None: 1322 if self.on_data_post is None:
1351 # if we don't have on_data_post, the page was not expecting POST 1323 # if we don't have on_data_post, the page was not expecting POST
1352 # so we return an error 1324 # so we return an error
1353 d.addCallback(lambda dummy: self.pageError(request, C.HTTP_BAD_REQUEST)) 1325 d.addCallback(lambda __: self.pageError(request, C.HTTP_BAD_REQUEST))
1354 else: 1326 else:
1355 d.addCallback(self._on_data_post, request) 1327 d.addCallback(self._on_data_post, request)
1356 # by default, POST follow normal behaviour after on_data_post is called 1328 # by default, POST follow normal behaviour after on_data_post is called
1357 # this can be changed by a redirection or other method call in on_data_post 1329 # this can be changed by a redirection or other method call in on_data_post
1358 1330