comparison src/server/pages.py @ 1113:cdd389ef97bc

server: code style reformatting using black
author Goffi <goffi@goffi.org>
date Fri, 29 Jun 2018 17:45:26 +0200
parents 045e8bdaed4f
children
comparison
equal deleted inserted replaced
1112:f287fc8bb31a 1113:cdd389ef97bc
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 28 from sat.tools.common import uri as common_uri
29 from sat.tools.common import date_utils 29 from sat.tools.common import date_utils
30 from sat.core.log import getLogger 30 from sat.core.log import getLogger
31
31 log = getLogger(__name__) 32 log = getLogger(__name__)
32 from libervia.server.constants import Const as C 33 from libervia.server.constants import Const as C
33 from libervia.server import session_iface 34 from libervia.server import session_iface
34 from libervia.server.utils import quote, SubPage 35 from libervia.server.utils import quote, SubPage
35 import libervia 36 import libervia
39 import os.path 40 import os.path
40 import urllib 41 import urllib
41 import time 42 import time
42 import hashlib 43 import hashlib
43 44
44 WebsocketMeta = namedtuple("WebsocketMeta", ('url', 'token', 'debug')) 45 WebsocketMeta = namedtuple("WebsocketMeta", ("url", "token", "debug"))
45
46 46
47 47
48 class CacheBase(object): 48 class CacheBase(object):
49
50 def __init__(self): 49 def __init__(self):
51 self._created = time.time() 50 self._created = time.time()
52 self._last_access = self._created 51 self._last_access = self._created
53 52
54 @property 53 @property
63 def last_access(self, timestamp): 62 def last_access(self, timestamp):
64 self._last_access = timestamp 63 self._last_access = timestamp
65 64
66 65
67 class CachePage(CacheBase): 66 class CachePage(CacheBase):
68
69 def __init__(self, rendered): 67 def __init__(self, rendered):
70 super(CachePage, self).__init__() 68 super(CachePage, self).__init__()
71 self._created = time.time() 69 self._created = time.time()
72 self._last_access = self._created 70 self._last_access = self._created
73 self._rendered = rendered 71 self._rendered = rendered
81 def hash(self): 79 def hash(self):
82 return self._hash 80 return self._hash
83 81
84 82
85 class CacheURL(CacheBase): 83 class CacheURL(CacheBase):
86
87 def __init__(self, request): 84 def __init__(self, request):
88 super(CacheURL, self).__init__() 85 super(CacheURL, self).__init__()
89 try: 86 try:
90 self._data = request.data.copy() 87 self._data = request.data.copy()
91 except AttributeError: 88 except AttributeError:
92 self._data = {} 89 self._data = {}
93 self._template_data = request.template_data.copy() 90 self._template_data = request.template_data.copy()
94 self._prepath = request.prepath[:] 91 self._prepath = request.prepath[:]
95 self._postpath = request.postpath[:] 92 self._postpath = request.postpath[:]
96 del self._template_data['csrf_token'] 93 del self._template_data["csrf_token"]
97 94
98 def use(self, request): 95 def use(self, request):
99 self.last_access = time.time() 96 self.last_access = time.time()
100 request.data = self._data.copy() 97 request.data = self._data.copy()
101 request.template_data.update(self._template_data) 98 request.template_data.update(self._template_data)
102 request.prepath = self._prepath[:] 99 request.prepath = self._prepath[:]
103 request.postpath = self._postpath[:] 100 request.postpath = self._postpath[:]
104 101
105 102
106 class LiberviaPage(web_resource.Resource): 103 class LiberviaPage(web_resource.Resource):
107 isLeaf = True # we handle subpages ourself 104 isLeaf = True #  we handle subpages ourself
108 named_pages = {} 105 named_pages = {}
109 uri_callbacks = {} 106 uri_callbacks = {}
110 signals_handlers = {} 107 signals_handlers = {}
111 pages_redirects = {} 108 pages_redirects = {}
112 cache = {} 109 cache = {}
113 cached_urls = {} 110 cached_urls = {}
114 # Set of tuples (service/node/sub_id) of nodes subscribed for caching 111 #  Set of tuples (service/node/sub_id) of nodes subscribed for caching
115 # sub_id can be empty string if not handled by service 112 # sub_id can be empty string if not handled by service
116 cache_pubsub_sub = set() 113 cache_pubsub_sub = set()
117 main_menu = None 114 main_menu = None
118 115
119 def __init__(self, host, root_dir, url, name=None, redirect=None, access=None, dynamic=False, parse_url=None, 116 def __init__(
120 prepare_render=None, render=None, template=None, 117 self,
121 on_data_post=None, on_data=None, on_signal=None, 118 host,
122 url_cache=False): 119 root_dir,
120 url,
121 name=None,
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 ):
123 """initiate LiberviaPages 134 """initiate LiberviaPages
124 135
125 LiberviaPages are the main resources of Libervia, using easy to set python files 136 LiberviaPages are the main resources of Libervia, using easy to set python files
126 The arguments are the variables found in page_meta.py 137 The arguments are the variables found in page_meta.py
127 @param host(Libervia): the running instance of Libervia 138 @param host(Libervia): the running instance of Libervia
167 self.root_dir = root_dir 178 self.root_dir = root_dir
168 self.url = url 179 self.url = url
169 self.name = name 180 self.name = name
170 if name is not None: 181 if name is not None:
171 if name in self.named_pages: 182 if name in self.named_pages:
172 raise exceptions.ConflictError(_(u'a Libervia page named "{}" already exists'.format(name))) 183 raise exceptions.ConflictError(
173 if u'/' in name: 184 _(u'a Libervia page named "{}" already exists'.format(name))
185 )
186 if u"/" in name:
174 raise ValueError(_(u'"/" is not allowed in page names')) 187 raise ValueError(_(u'"/" is not allowed in page names'))
175 if not name: 188 if not name:
176 raise ValueError(_(u"a page name can't be empty")) 189 raise ValueError(_(u"a page name can't be empty"))
177 self.named_pages[name] = self 190 self.named_pages[name] = self
178 if access is None: 191 if access is None:
179 access = C.PAGES_ACCESS_PUBLIC 192 access = C.PAGES_ACCESS_PUBLIC
180 if access not in (C.PAGES_ACCESS_PUBLIC, C.PAGES_ACCESS_PROFILE, C.PAGES_ACCESS_NONE): 193 if access not in (
181 raise NotImplementedError(_(u"{} access is not implemented yet").format(access)) 194 C.PAGES_ACCESS_PUBLIC,
195 C.PAGES_ACCESS_PROFILE,
196 C.PAGES_ACCESS_NONE,
197 ):
198 raise NotImplementedError(
199 _(u"{} access is not implemented yet").format(access)
200 )
182 self.access = access 201 self.access = access
183 self.dynamic = dynamic 202 self.dynamic = dynamic
184 if redirect is not None: 203 if redirect is not None:
185 # only page access and name make sense in case of full redirection 204 # only page access and name make sense in case of full redirection
186 # so we check that rendering methods/values are not set 205 # so we check that rendering methods/values are not set
187 if not all(lambda x: x is not None 206 if not all(
188 for x in (parse_url, prepare_render, render, template)): 207 lambda x: x is not None
189 raise ValueError(_(u"you can't use full page redirection with other rendering method," 208 for x in (parse_url, prepare_render, render, template)
190 u"check self.pageRedirect if you need to use them")) 209 ):
210 raise ValueError(
211 _(
212 u"you can't use full page redirection with other rendering method,"
213 u"check self.pageRedirect if you need to use them"
214 )
215 )
191 self.redirect = redirect 216 self.redirect = redirect
192 else: 217 else:
193 self.redirect = None 218 self.redirect = None
194 self.parse_url = parse_url 219 self.parse_url = parse_url
195 self.prepare_render = prepare_render 220 self.prepare_render = prepare_render
206 log.error(_(u"render and template methods can't be used at the same time")) 231 log.error(_(u"render and template methods can't be used at the same time"))
207 if parse_url is not None and not callable(parse_url): 232 if parse_url is not None and not callable(parse_url):
208 log.error(_(u"parse_url must be a callable")) 233 log.error(_(u"parse_url must be a callable"))
209 234
210 # if not None, next rendering will be cached 235 # if not None, next rendering will be cached
211 # it must then contain a list of the the keys to use (without the page instance) 236 #  it must then contain a list of the the keys to use (without the page instance)
212 # e.g. [C.SERVICE_PROFILE, "pubsub", server@example.tld, pubsub_node]  237 # e.g. [C.SERVICE_PROFILE, "pubsub", server@example.tld, pubsub_node]
213 self._do_cache = None 238 self._do_cache = None
214 239
215 def __unicode__(self): 240 def __unicode__(self):
216 return u'LiberviaPage {name} at {url}'.format( 241 return u"LiberviaPage {name} at {url}".format(
217 name = self.name or u'<anonymous>', 242 name=self.name or u"<anonymous>", url=self.url
218 url = self.url) 243 )
219 244
220 def __str__(self): 245 def __str__(self):
221 return self.__unicode__().encode('utf-8') 246 return self.__unicode__().encode("utf-8")
222 247
223 @classmethod 248 @classmethod
224 def importPages(cls, host, parent=None, path=None): 249 def importPages(cls, host, parent=None, path=None):
225 """Recursively import Libervia pages""" 250 """Recursively import Libervia pages"""
226 if path is None: 251 if path is None:
244 # so we can switch to import 269 # so we can switch to import
245 execfile(meta_path, page_data) 270 execfile(meta_path, page_data)
246 resource = LiberviaPage( 271 resource = LiberviaPage(
247 host, 272 host,
248 dir_path, 273 dir_path,
249 u'/' + u'/'.join(new_path), 274 u"/" + u"/".join(new_path),
250 name=page_data.get('name'), 275 name=page_data.get("name"),
251 redirect=page_data.get('redirect'), 276 redirect=page_data.get("redirect"),
252 access=page_data.get('access'), 277 access=page_data.get("access"),
253 dynamic=page_data.get('dynamic', False), 278 dynamic=page_data.get("dynamic", False),
254 parse_url=page_data.get('parse_url'), 279 parse_url=page_data.get("parse_url"),
255 prepare_render=page_data.get('prepare_render'), 280 prepare_render=page_data.get("prepare_render"),
256 render=page_data.get('render'), 281 render=page_data.get("render"),
257 template=page_data.get('template'), 282 template=page_data.get("template"),
258 on_data_post=page_data.get('on_data_post'), 283 on_data_post=page_data.get("on_data_post"),
259 on_data=page_data.get('on_data'), 284 on_data=page_data.get("on_data"),
260 on_signal=page_data.get('on_signal'), 285 on_signal=page_data.get("on_signal"),
261 url_cache=page_data.get('url_cache', False), 286 url_cache=page_data.get("url_cache", False),
262 ) 287 )
263 parent.putChild(d, resource) 288 parent.putChild(d, resource)
264 log.info(u"Added /{path} page".format(path=u'[...]/'.join(new_path))) 289 log.info(u"Added /{path} page".format(path=u"[...]/".join(new_path)))
265 if 'uri_handlers' in page_data: 290 if "uri_handlers" in page_data:
266 if not isinstance(page_data, dict): 291 if not isinstance(page_data, dict):
267 log.error(_(u'uri_handlers must be a dict')) 292 log.error(_(u"uri_handlers must be a dict"))
268 else: 293 else:
269 for uri_tuple, cb_name in page_data['uri_handlers'].iteritems(): 294 for uri_tuple, cb_name in page_data["uri_handlers"].iteritems():
270 if len(uri_tuple) != 2 or not isinstance(cb_name, basestring): 295 if len(uri_tuple) != 2 or not isinstance(cb_name, basestring):
271 log.error(_(u"invalid uri_tuple")) 296 log.error(_(u"invalid uri_tuple"))
272 continue 297 continue
273 log.info(_(u'setting {}/{} URIs handler').format(*uri_tuple)) 298 log.info(_(u"setting {}/{} URIs handler").format(*uri_tuple))
274 try: 299 try:
275 cb = page_data[cb_name] 300 cb = page_data[cb_name]
276 except KeyError: 301 except KeyError:
277 log.error(_(u'missing {name} method to handle {1}/{2}').format( 302 log.error(
278 name = cb_name, *uri_tuple)) 303 _(u"missing {name} method to handle {1}/{2}").format(
304 name=cb_name, *uri_tuple
305 )
306 )
279 continue 307 continue
280 else: 308 else:
281 resource.registerURI(uri_tuple, cb) 309 resource.registerURI(uri_tuple, cb)
282 310
283 LiberviaPage.importPages(host, resource, new_path) 311 LiberviaPage.importPages(host, resource, new_path)
290 msg = _(u"menu item can't be empty") 318 msg = _(u"menu item can't be empty")
291 log.error(msg) 319 log.error(msg)
292 raise ValueError(msg) 320 raise ValueError(msg)
293 elif isinstance(menu, list): 321 elif isinstance(menu, list):
294 if len(menu) != 2: 322 if len(menu) != 2:
295 msg = _(u"menu item as list must be in the form [page_name, absolue URL]") 323 msg = _(
324 u"menu item as list must be in the form [page_name, absolue URL]"
325 )
296 log.error(msg) 326 log.error(msg)
297 raise ValueError(msg) 327 raise ValueError(msg)
298 page_name, url = menu 328 page_name, url = menu
299 else: 329 else:
300 page_name = menu 330 page_name = menu
301 try: 331 try:
302 url = cls.getPageByName(page_name).url 332 url = cls.getPageByName(page_name).url
303 except KeyError as e: 333 except KeyError as e:
304 log.error(_(u"Can'find a named page ({msg}), please check menu_json in configuration.").format(msg=e)) 334 log.error(
335 _(
336 u"Can'find a named page ({msg}), please check menu_json in configuration."
337 ).format(msg=e)
338 )
305 raise e 339 raise e
306 main_menu.append((page_name, url)) 340 main_menu.append((page_name, url))
307 cls.main_menu = main_menu 341 cls.main_menu = main_menu
308 342
309 def registerURI(self, uri_tuple, get_uri_cb): 343 def registerURI(self, uri_tuple, get_uri_cb):
315 @param get_uri_cb(callable): method which take uri_data dict as only argument 349 @param get_uri_cb(callable): method which take uri_data dict as only argument
316 and return absolute path with correct arguments or None if the page 350 and return absolute path with correct arguments or None if the page
317 can't handle this URL 351 can't handle this URL
318 """ 352 """
319 if uri_tuple in self.uri_callbacks: 353 if uri_tuple in self.uri_callbacks:
320 log.info(_(u"{}/{} URIs are already handled, replacing by the new handler").format(*uri_tuple)) 354 log.info(
355 _(u"{}/{} URIs are already handled, replacing by the new handler").format(
356 *uri_tuple
357 )
358 )
321 self.uri_callbacks[uri_tuple] = (self, get_uri_cb) 359 self.uri_callbacks[uri_tuple] = (self, get_uri_cb)
322 360
323 def registerSignal(self, request, signal, check_profile=True): 361 def registerSignal(self, request, signal, check_profile=True):
324 r"""register a signal handler 362 r"""register a signal handler
325 363
338 """ 376 """
339 # FIXME: add a timeout, if socket is not opened before it, signal handler must be removed 377 # FIXME: add a timeout, if socket is not opened before it, signal handler must be removed
340 if not self.dynamic: 378 if not self.dynamic:
341 log.error(_(u"You can't register signal if page is not dynamic")) 379 log.error(_(u"You can't register signal if page is not dynamic"))
342 return 380 return
343 LiberviaPage.signals_handlers.setdefault(signal, {})[id(request)] = (self, request, check_profile) 381 LiberviaPage.signals_handlers.setdefault(signal, {})[id(request)] = (
382 self,
383 request,
384 check_profile,
385 )
344 request._signals_registered.append(signal) 386 request._signals_registered.append(signal)
345 387
346 @classmethod 388 @classmethod
347 def getPagePathFromURI(cls, uri): 389 def getPagePathFromURI(cls, uri):
348 """Retrieve page URL from xmpp: URI 390 """Retrieve page URL from xmpp: URI
351 @return (unicode,None): absolute path (starting from root "/") to page handling the URI 393 @return (unicode,None): absolute path (starting from root "/") to page handling the URI
352 None is returned if no page has been registered for this URI 394 None is returned if no page has been registered for this URI
353 """ 395 """
354 uri_data = common_uri.parseXMPPUri(uri) 396 uri_data = common_uri.parseXMPPUri(uri)
355 try: 397 try:
356 page, cb = cls.uri_callbacks[uri_data['type'], uri_data['sub_type']] 398 page, cb = cls.uri_callbacks[uri_data["type"], uri_data["sub_type"]]
357 except KeyError: 399 except KeyError:
358 url = None 400 url = None
359 else: 401 else:
360 url = cb(page, uri_data) 402 url = cb(page, uri_data)
361 if url is None: 403 if url is None:
362 # no handler found 404 # no handler found
363 # we try to find a more generic one 405 # we try to find a more generic one
364 try: 406 try:
365 page, cb = cls.uri_callbacks[uri_data['type'], None] 407 page, cb = cls.uri_callbacks[uri_data["type"], None]
366 except KeyError: 408 except KeyError:
367 pass 409 pass
368 else: 410 else:
369 url = cb(page, uri_data) 411 url = cb(page, uri_data)
370 return url 412 return url
377 @return (LiberviaPage): page instance 419 @return (LiberviaPage): page instance
378 @raise KeyError: the page doesn't exist 420 @raise KeyError: the page doesn't exist
379 """ 421 """
380 return cls.named_pages[name] 422 return cls.named_pages[name]
381 423
382 def getPageRedirectURL(self, request, page_name=u'login', url=None): 424 def getPageRedirectURL(self, request, page_name=u"login", url=None):
383 """generate URL for a page with redirect_url parameter set 425 """generate URL for a page with redirect_url parameter set
384 426
385 mainly used for login page with redirection to current page 427 mainly used for login page with redirection to current page
386 @param request(server.Request): current HTTP request 428 @param request(server.Request): current HTTP request
387 @param page_name(unicode): name of the page to go 429 @param page_name(unicode): name of the page to go
388 @param url(None, unicode): url to redirect to 430 @param url(None, unicode): url to redirect to
389 None to use request path (i.e. current page) 431 None to use request path (i.e. current page)
390 @return (unicode): URL to use 432 @return (unicode): URL to use
391 """ 433 """
392 return u'{root_url}?redirect_url={redirect_url}'.format( 434 return u"{root_url}?redirect_url={redirect_url}".format(
393 root_url = self.getPageByName(page_name).url, 435 root_url=self.getPageByName(page_name).url,
394 redirect_url=urllib.quote_plus(request.uri) if url is None else url.encode('utf-8')) 436 redirect_url=urllib.quote_plus(request.uri)
437 if url is None
438 else url.encode("utf-8"),
439 )
395 440
396 def getURL(self, *args): 441 def getURL(self, *args):
397 """retrieve URL of the page set arguments 442 """retrieve URL of the page set arguments
398 443
399 *args(list[unicode]): argument to add to the URL as path elements 444 *args(list[unicode]): argument to add to the URL as path elements
400 empty or None arguments will be ignored 445 empty or None arguments will be ignored
401 """ 446 """
402 url_args = [quote(a) for a in args if a] 447 url_args = [quote(a) for a in args if a]
403 448
404 if self.name is not None and self.name in self.pages_redirects: 449 if self.name is not None and self.name in self.pages_redirects:
405 # we check for redirection 450 #  we check for redirection
406 redirect_data = self.pages_redirects[self.name] 451 redirect_data = self.pages_redirects[self.name]
407 args_hash = tuple(args) 452 args_hash = tuple(args)
408 for limit in xrange(len(args)+1): 453 for limit in xrange(len(args) + 1):
409 current_hash = args_hash[:limit] 454 current_hash = args_hash[:limit]
410 if current_hash in redirect_data: 455 if current_hash in redirect_data:
411 url_base = redirect_data[current_hash] 456 url_base = redirect_data[current_hash]
412 remaining = args[limit:] 457 remaining = args[limit:]
413 remaining_url = '/'.join(remaining) 458 remaining_url = "/".join(remaining)
414 return os.path.join('/', url_base, remaining_url) 459 return os.path.join("/", url_base, remaining_url)
415 460
416 return os.path.join(self.url, *url_args) 461 return os.path.join(self.url, *url_args)
417 462
418 def getCurrentURL(self, request): 463 def getCurrentURL(self, request):
419 """retrieve URL used to access this page 464 """retrieve URL used to access this page
424 # request.prepath) because request.prepath may have been modified by 469 # request.prepath) because request.prepath may have been modified by
425 # redirection (if redirection args have been specified), while path reflect 470 # redirection (if redirection args have been specified), while path reflect
426 # the real request 471 # the real request
427 472
428 # we ignore empty path elements (i.e. double '/' or '/' at the end) 473 # we ignore empty path elements (i.e. double '/' or '/' at the end)
429 path_elts = [p for p in request.path.split('/') if p] 474 path_elts = [p for p in request.path.split("/") if p]
430 475
431 if request.postpath: 476 if request.postpath:
432 if not request.postpath[-1]: 477 if not request.postpath[-1]:
433 # we remove trailing slash 478 #  we remove trailing slash
434 request.postpath = request.postpath[:-1] 479 request.postpath = request.postpath[:-1]
435 if request.postpath: 480 if request.postpath:
436 # getSubPageURL must return subpage from the point where 481 #  getSubPageURL must return subpage from the point where
437 # the it is called, so we have to remove remanining 482 # the it is called, so we have to remove remanining
438 # path elements 483 # path elements
439 path_elts = path_elts[:-len(request.postpath)] 484 path_elts = path_elts[: -len(request.postpath)]
440 485
441 return u'/' + '/'.join(path_elts).decode('utf-8') 486 return u"/" + "/".join(path_elts).decode("utf-8")
442 487
443 def getParamURL(self, request, **kwargs): 488 def getParamURL(self, request, **kwargs):
444 """use URL of current request but modify the parameters in query part 489 """use URL of current request but modify the parameters in query part
445 490
446 **kwargs(dict[str, unicode]): argument to use as query parameters 491 **kwargs(dict[str, unicode]): argument to use as query parameters
447 @return (unicode): constructed URL 492 @return (unicode): constructed URL
448 """ 493 """
449 current_url = self.getCurrentURL(request) 494 current_url = self.getCurrentURL(request)
450 if kwargs: 495 if kwargs:
451 encoded = urllib.urlencode({k:v.encode('utf-8') for k,v in kwargs.iteritems()}).decode('utf-8') 496 encoded = urllib.urlencode(
452 current_url = current_url + u'?' + encoded 497 {k: v.encode("utf-8") for k, v in kwargs.iteritems()}
498 ).decode("utf-8")
499 current_url = current_url + u"?" + encoded
453 return current_url 500 return current_url
454 501
455 def getSubPageByName(self, subpage_name, parent=None): 502 def getSubPageByName(self, subpage_name, parent=None):
456 """retrieve a subpage and its path using its name 503 """retrieve a subpage and its path using its name
457 504
466 parent = self 513 parent = self
467 for path, child in parent.children.iteritems(): 514 for path, child in parent.children.iteritems():
468 try: 515 try:
469 child_name = child.name 516 child_name = child.name
470 except AttributeError: 517 except AttributeError:
471 # LiberviaPages have a name, but maybe this is an other Resource 518 #  LiberviaPages have a name, but maybe this is an other Resource
472 continue 519 continue
473 if child_name == subpage_name: 520 if child_name == subpage_name:
474 return path, child 521 return path, child
475 raise exceptions.NotFound(_(u'requested sub page has not been found')) 522 raise exceptions.NotFound(_(u"requested sub page has not been found"))
476 523
477 def getSubPageURL(self, request, page_name, *args): 524 def getSubPageURL(self, request, page_name, *args):
478 """retrieve a page in direct children and build its URL according to request 525 """retrieve a page in direct children and build its URL according to request
479 526
480 request's current path is used as base (at current parsing point, 527 request's current path is used as base (at current parsing point,
494 if an arg is None, it will be ignored 541 if an arg is None, it will be ignored
495 @return (unicode): absolute URL to the sub page 542 @return (unicode): absolute URL to the sub page
496 """ 543 """
497 current_url = self.getCurrentURL(request) 544 current_url = self.getCurrentURL(request)
498 path, child = self.getSubPageByName(page_name) 545 path, child = self.getSubPageByName(page_name)
499 return os.path.join(u'/', current_url, path, *[quote(a) for a in args if a is not None]) 546 return os.path.join(
547 u"/", current_url, path, *[quote(a) for a in args if a is not None]
548 )
500 549
501 def getURLByNames(self, named_path): 550 def getURLByNames(self, named_path):
502 """retrieve URL from pages names and arguments 551 """retrieve URL from pages names and arguments
503 552
504 @param named_path(list[tuple[unicode, list[unicode]]]): path to the page as a list 553 @param named_path(list[tuple[unicode, list[unicode]]]): path to the page as a list
513 for page_name, page_args in named_path: 562 for page_name, page_args in named_path:
514 if current_page is None: 563 if current_page is None:
515 current_page = self.getPageByName(page_name) 564 current_page = self.getPageByName(page_name)
516 path.append(current_page.getURL(*page_args)) 565 path.append(current_page.getURL(*page_args))
517 else: 566 else:
518 sub_path, current_page = self.getSubPageByName(page_name, parent=current_page) 567 sub_path, current_page = self.getSubPageByName(
568 page_name, parent=current_page
569 )
519 path.append(sub_path) 570 path.append(sub_path)
520 if page_args: 571 if page_args:
521 path.extend([quote(a) for a in page_args]) 572 path.extend([quote(a) for a in page_args])
522 return self.host.checkRedirection(u'/'.join(path)) 573 return self.host.checkRedirection(u"/".join(path))
523 574
524 def getURLByPath(self, *args): 575 def getURLByPath(self, *args):
525 """generate URL by path 576 """generate URL by path
526 577
527 this method as a similar effect as getURLByNames, but it is more readable 578 this method as a similar effect as getURLByNames, but it is more readable
531 - if util.SubPage instance, must be the name of a subpage 582 - if util.SubPage instance, must be the name of a subpage
532 @return (unicode): generated path 583 @return (unicode): generated path
533 """ 584 """
534 args = list(args) 585 args = list(args)
535 if not args: 586 if not args:
536 raise ValueError('You must specify path elements') 587 raise ValueError("You must specify path elements")
537 # root page is the one needed to construct the base of the URL 588 # root page is the one needed to construct the base of the URL
538 # if first arg is not a SubPage instance, we use current page 589 # if first arg is not a SubPage instance, we use current page
539 if not isinstance(args[0], SubPage): 590 if not isinstance(args[0], SubPage):
540 root = self 591 root = self
541 else: 592 else:
554 if not args: 605 if not args:
555 break 606 break
556 else: 607 else:
557 path, current_page = current_page.getSubPageByName(args.pop(0)) 608 path, current_page = current_page.getSubPageByName(args.pop(0))
558 arguments = [path] 609 arguments = [path]
559 return self.host.checkRedirection(u'/'.join(url_elts)) 610 return self.host.checkRedirection(u"/".join(url_elts))
560 611
561 def getChildWithDefault(self, path, request): 612 def getChildWithDefault(self, path, request):
562 # we handle children ourselves 613 # we handle children ourselves
563 raise exceptions.InternalError(u"this method should not be used with LiberviaPage") 614 raise exceptions.InternalError(
615 u"this method should not be used with LiberviaPage"
616 )
564 617
565 def nextPath(self, request): 618 def nextPath(self, request):
566 """get next URL path segment, and update request accordingly 619 """get next URL path segment, and update request accordingly
567 620
568 will move first segment of postpath in prepath 621 will move first segment of postpath in prepath
570 @return (unicode): unquoted segment 623 @return (unicode): unquoted segment
571 @raise IndexError: there is no segment left 624 @raise IndexError: there is no segment left
572 """ 625 """
573 pathElement = request.postpath.pop(0) 626 pathElement = request.postpath.pop(0)
574 request.prepath.append(pathElement) 627 request.prepath.append(pathElement)
575 return urllib.unquote(pathElement).decode('utf-8') 628 return urllib.unquote(pathElement).decode("utf-8")
576 629
577 def _filterPathValue(self, value, handler, name, request): 630 def _filterPathValue(self, value, handler, name, request):
578 """Modify a path value according to handler (see [getPathArgs])""" 631 """Modify a path value according to handler (see [getPathArgs])"""
579 if handler in (u'@', u'@jid') and value == u'@': 632 if handler in (u"@", u"@jid") and value == u"@":
580 value = None 633 value = None
581 634
582 if handler in (u'', u'@'): 635 if handler in (u"", u"@"):
583 if value is None: 636 if value is None:
584 return u'' 637 return u""
585 elif handler in (u'jid', u'@jid'): 638 elif handler in (u"jid", u"@jid"):
586 if value: 639 if value:
587 try: 640 try:
588 return jid.JID(value) 641 return jid.JID(value)
589 except RuntimeError: 642 except RuntimeError:
590 log.warning(_(u'invalid jid argument: {value}').format(value=value)) 643 log.warning(_(u"invalid jid argument: {value}").format(value=value))
591 self.pageError(request, C.HTTP_BAD_REQUEST) 644 self.pageError(request, C.HTTP_BAD_REQUEST)
592 else: 645 else:
593 return u'' 646 return u""
594 else: 647 else:
595 return handler(self, value, name, request) 648 return handler(self, value, name, request)
596 649
597 return value 650 return value
598 651
613 - '@jid': if value of arguments is empty or '@', empty string will be used, else it will be converted to jid 666 - '@jid': if value of arguments is empty or '@', empty string will be used, else it will be converted to jid
614 """ 667 """
615 data = self.getRData(request) 668 data = self.getRData(request)
616 669
617 for idx, name in enumerate(names): 670 for idx, name in enumerate(names):
618 if name[0] == u'*': 671 if name[0] == u"*":
619 value = data[name[1:]] = [] 672 value = data[name[1:]] = []
620 while True: 673 while True:
621 try: 674 try:
622 value.append(self.nextPath(request)) 675 value.append(self.nextPath(request))
623 except IndexError: 676 except IndexError:
624 idx-=1 677 idx -= 1
625 break 678 break
626 else: 679 else:
627 idx+=1 680 idx += 1
628 else: 681 else:
629 try: 682 try:
630 value = data[name] = self.nextPath(request) 683 value = data[name] = self.nextPath(request)
631 except IndexError: 684 except IndexError:
632 data[name] = None 685 data[name] = None
633 idx-=1 686 idx -= 1
634 break 687 break
635 688
636 values_count = idx+1 689 values_count = idx + 1
637 if values_count < min_args: 690 if values_count < min_args:
638 log.warning(_(u"Missing arguments in URL (got {count}, expected at least {min_args})").format( 691 log.warning(
639 count = values_count, min_args = min_args)) 692 _(
693 u"Missing arguments in URL (got {count}, expected at least {min_args})"
694 ).format(count=values_count, min_args=min_args)
695 )
640 self.pageError(request, C.HTTP_BAD_REQUEST) 696 self.pageError(request, C.HTTP_BAD_REQUEST)
641 697
642 for name in names[values_count:]: 698 for name in names[values_count:]:
643 data[name] = None 699 data[name] = None
644 700
645 for name, handler in kwargs.iteritems(): 701 for name, handler in kwargs.iteritems():
646 if name[0] == '*': 702 if name[0] == "*":
647 data[name] = [self._filterPathValue(v, handler, name, request) for v in data[name]] 703 data[name] = [
704 self._filterPathValue(v, handler, name, request) for v in data[name]
705 ]
648 else: 706 else:
649 data[name] = self._filterPathValue(data[name], handler, name, request) 707 data[name] = self._filterPathValue(data[name], handler, name, request)
650
651 708
652 ## Cache handling ## 709 ## Cache handling ##
653 710
654 def _setCacheHeaders(self, request, cache): 711 def _setCacheHeaders(self, request, cache):
655 """Set ETag and Last-Modified HTTP headers, used for caching""" 712 """Set ETag and Last-Modified HTTP headers, used for caching"""
656 request.setHeader('ETag', cache.hash) 713 request.setHeader("ETag", cache.hash)
657 last_modified = self.host.getHTTPDate(cache.created) 714 last_modified = self.host.getHTTPDate(cache.created)
658 request.setHeader('Last-Modified', last_modified) 715 request.setHeader("Last-Modified", last_modified)
659 716
660 def _checkCacheHeaders(self, request, cache): 717 def _checkCacheHeaders(self, request, cache):
661 """Check if a cache condition is set on the request 718 """Check if a cache condition is set on the request
662 719
663 if condition is valid, C.HTTP_NOT_MODIFIED is returned 720 if condition is valid, C.HTTP_NOT_MODIFIED is returned
664 """ 721 """
665 etag_match = request.getHeader('If-None-Match') 722 etag_match = request.getHeader("If-None-Match")
666 if etag_match is not None: 723 if etag_match is not None:
667 if cache.hash == etag_match: 724 if cache.hash == etag_match:
668 self.pageError(request, C.HTTP_NOT_MODIFIED, no_body=True) 725 self.pageError(request, C.HTTP_NOT_MODIFIED, no_body=True)
669 else: 726 else:
670 modified_match = request.getHeader('If-Modified-Since') 727 modified_match = request.getHeader("If-Modified-Since")
671 if modified_match is not None: 728 if modified_match is not None:
672 modified = date_utils.date_parse(modified_match) 729 modified = date_utils.date_parse(modified_match)
673 if modified >= int(cache.created): 730 if modified >= int(cache.created):
674 self.pageError(request, C.HTTP_NOT_MODIFIED, no_body=True) 731 self.pageError(request, C.HTTP_NOT_MODIFIED, no_body=True)
675 732
696 node: pubsub node 753 node: pubsub node
697 short: short name of feature (needed if node is empty to find namespace) 754 short: short name of feature (needed if node is empty to find namespace)
698 755
699 """ 756 """
700 if request.postpath: 757 if request.postpath:
701 # we are not on the final page, no need to go further 758 #  we are not on the final page, no need to go further
702 return 759 return
703 760
704 profile = self.getProfile(request) or C.SERVICE_PROFILE 761 profile = self.getProfile(request) or C.SERVICE_PROFILE
705 762
706 if cache_type == C.CACHE_PUBSUB: 763 if cache_type == C.CACHE_PUBSUB:
707 service, node = kwargs['service'], kwargs['node'] 764 service, node = kwargs["service"], kwargs["node"]
708 if not node: 765 if not node:
709 try: 766 try:
710 short = kwargs['short'] 767 short = kwargs["short"]
711 node = self.host.ns_map[short] 768 node = self.host.ns_map[short]
712 except KeyError: 769 except KeyError:
713 log.warning(_(u"Can't use cache for empty node without namespace set, please ensure to set \"short\" and that it is registered")) 770 log.warning(
771 _(
772 u'Can\'t use cache for empty node without namespace set, please ensure to set "short" and that it is registered'
773 )
774 )
714 return 775 return
715 if profile != C.SERVICE_PROFILE: 776 if profile != C.SERVICE_PROFILE:
716 # only service profile is cache for now 777 #  only service profile is cache for now
717 return 778 return
718 try: 779 try:
719 cache = self.cache[profile][cache_type][service][node][request.uri][self] 780 cache = self.cache[profile][cache_type][service][node][request.uri][self]
720 except KeyError: 781 except KeyError:
721 # no cache yet, let's subscribe to the pubsub node 782 # no cache yet, let's subscribe to the pubsub node
722 d1 = self.host.bridgeCall('psSubscribe', service.full(), node, {}, profile) 783 d1 = self.host.bridgeCall(
784 "psSubscribe", service.full(), node, {}, profile
785 )
723 d1.addCallback(self.checkCacheSubscribeCb, service, node) 786 d1.addCallback(self.checkCacheSubscribeCb, service, node)
724 d1.addErrback(self.checkCacheSubscribeEb, service, node) 787 d1.addErrback(self.checkCacheSubscribeEb, service, node)
725 d2 = self.host.bridgeCall('psNodeWatchAdd', service.full(), node, profile) 788 d2 = self.host.bridgeCall("psNodeWatchAdd", service.full(), node, profile)
726 d2.addErrback(self.psNodeWatchAddEb, service, node) 789 d2.addErrback(self.psNodeWatchAddEb, service, node)
727 self._do_cache = [self, profile, cache_type, service, node, request.uri] 790 self._do_cache = [self, profile, cache_type, service, node, request.uri]
728 # we don't return the Deferreds as it is not needed to wait for 791 #  we don't return the Deferreds as it is not needed to wait for
729 # the subscription to continue with page rendering 792 # the subscription to continue with page rendering
730 return 793 return
731 794
732 else: 795 else:
733 raise exceptions.InternalError(u'Unknown cache_type') 796 raise exceptions.InternalError(u"Unknown cache_type")
734 log.debug(u'using cache for {page}'.format(page=self)) 797 log.debug(u"using cache for {page}".format(page=self))
735 cache.last_access = time.time() 798 cache.last_access = time.time()
736 self._setCacheHeaders(request, cache) 799 self._setCacheHeaders(request, cache)
737 self._checkCacheHeaders(request, cache) 800 self._checkCacheHeaders(request, cache)
738 request.write(cache.rendered) 801 request.write(cache.rendered)
739 request.finish() 802 request.finish()
740 raise failure.Failure(exceptions.CancelError(u'cache is used')) 803 raise failure.Failure(exceptions.CancelError(u"cache is used"))
741 804
742 def _cacheURL(self, dummy, request, profile): 805 def _cacheURL(self, dummy, request, profile):
743 self.cached_urls.setdefault(profile, {})[request.uri] = CacheURL(request) 806 self.cached_urls.setdefault(profile, {})[request.uri] = CacheURL(request)
744 807
745 @classmethod 808 @classmethod
746 def onNodeEvent(cls, host, service, node, event_type, items, profile): 809 def onNodeEvent(cls, host, service, node, event_type, items, profile):
747 """Invalidate cache for all pages linked to this node""" 810 """Invalidate cache for all pages linked to this node"""
748 try: 811 try:
749 cache = cls.cache[profile][C.CACHE_PUBSUB][jid.JID(service)][node] 812 cache = cls.cache[profile][C.CACHE_PUBSUB][jid.JID(service)][node]
750 except KeyError: 813 except KeyError:
751 log.info(_(u'Removing subscription for {service}/{node}: ' 814 log.info(
752 u'the page is not cached').format(service=service, node=node)) 815 _(
753 d1 = host.bridgeCall('psUnsubscribe', service, node, profile) 816 u"Removing subscription for {service}/{node}: "
754 d1.addErrback(lambda failure_: 817 u"the page is not cached"
755 log.warning(_(u"Can't unsubscribe from {service}/{node}: {msg}").format( 818 ).format(service=service, node=node)
756 service=service, node=node, msg=failure_))) 819 )
757 d2 = host.bridgeCall('psNodeWatchAdd', service, node, profile) 820 d1 = host.bridgeCall("psUnsubscribe", service, node, profile)
821 d1.addErrback(
822 lambda failure_: log.warning(
823 _(u"Can't unsubscribe from {service}/{node}: {msg}").format(
824 service=service, node=node, msg=failure_
825 )
826 )
827 )
828 d2 = host.bridgeCall("psNodeWatchAdd", service, node, profile)
758 # TODO: check why the page is not in cache, remove subscription? 829 # TODO: check why the page is not in cache, remove subscription?
759 d2.addErrback(lambda failure_: 830 d2.addErrback(
760 log.warning(_(u"Can't remove watch for {service}/{node}: {msg}").format( 831 lambda failure_: log.warning(
761 service=service, node=node, msg=failure_))) 832 _(u"Can't remove watch for {service}/{node}: {msg}").format(
833 service=service, node=node, msg=failure_
834 )
835 )
836 )
762 else: 837 else:
763 cache.clear() 838 cache.clear()
764 839
765 @classmethod 840 @classmethod
766 def onSignal(cls, host, signal, *args): 841 def onSignal(cls, host, signal, *args):
769 if a callback is registered for this signal, call it 844 if a callback is registered for this signal, call it
770 @param host: Libervia instance 845 @param host: Libervia instance
771 @param signal(unicode): name of the signal 846 @param signal(unicode): name of the signal
772 @param *args: args of the signals 847 @param *args: args of the signals
773 """ 848 """
774 for page, request, check_profile in cls.signals_handlers.get(signal, {}).itervalues(): 849 for page, request, check_profile in cls.signals_handlers.get(
850 signal, {}
851 ).itervalues():
775 if check_profile: 852 if check_profile:
776 signal_profile = args[-1] 853 signal_profile = args[-1]
777 request_profile = page.getProfile(request) 854 request_profile = page.getProfile(request)
778 if not request_profile: 855 if not request_profile:
779 # if you want to use signal without session, unset check_profile 856 # if you want to use signal without session, unset check_profile
780 # (be sure to know what you are doing) 857 # (be sure to know what you are doing)
781 log.error(_(u"no session started, signal can't be checked")) 858 log.error(_(u"no session started, signal can't be checked"))
782 continue 859 continue
783 if signal_profile != request_profile: 860 if signal_profile != request_profile:
784 # we ignore the signal, it's not for our profile 861 #  we ignore the signal, it's not for our profile
785 continue 862 continue
786 if request._signals_cache is not None: 863 if request._signals_cache is not None:
787 # socket is not yet opened, we cache the signal 864 # socket is not yet opened, we cache the signal
788 request._signals_cache.append((request, signal, args)) 865 request._signals_cache.append((request, signal, args))
789 log.debug(u"signal [{signal}] cached: {args}".format( 866 log.debug(
790 signal = signal, 867 u"signal [{signal}] cached: {args}".format(signal=signal, args=args)
791 args = args)) 868 )
792 else: 869 else:
793 page.on_signal(page, request, signal, *args) 870 page.on_signal(page, request, signal, *args)
794 871
795 def onSocketOpen(self, request): 872 def onSocketOpen(self, request):
796 """Called for dynamic pages when socket has just been opened 873 """Called for dynamic pages when socket has just been opened
810 """ 887 """
811 for signal in request._signals_registered: 888 for signal in request._signals_registered:
812 try: 889 try:
813 del LiberviaPage.signals_handlers[signal][id(request)] 890 del LiberviaPage.signals_handlers[signal][id(request)]
814 except KeyError: 891 except KeyError:
815 log.error(_(u"Can't find signal handler for [{signal}], this should not happen").format( 892 log.error(
816 signal = signal)) 893 _(
894 u"Can't find signal handler for [{signal}], this should not happen"
895 ).format(signal=signal)
896 )
817 else: 897 else:
818 log.debug(_(u"Removed signal handler")) 898 log.debug(_(u"Removed signal handler"))
819 899
820 def delegateToResource(self, request, resource): 900 def delegateToResource(self, request, resource):
821 """continue workflow with Twisted Resource""" 901 """continue workflow with Twisted Resource"""
823 if buf == server.NOT_DONE_YET: 903 if buf == server.NOT_DONE_YET:
824 pass 904 pass
825 else: 905 else:
826 request.write(buf) 906 request.write(buf)
827 request.finish() 907 request.finish()
828 raise failure.Failure(exceptions.CancelError(u'resource delegation')) 908 raise failure.Failure(exceptions.CancelError(u"resource delegation"))
829 909
830 def HTTPRedirect(self, request, url): 910 def HTTPRedirect(self, request, url):
831 """redirect to an URL using HTTP redirection 911 """redirect to an URL using HTTP redirection
832 912
833 @param request(server.Request): current HTTP request 913 @param request(server.Request): current HTTP request
834 @param url(unicode): url to redirect to 914 @param url(unicode): url to redirect to
835 """ 915 """
836 web_util.redirectTo(url.encode('utf-8'), request) 916 web_util.redirectTo(url.encode("utf-8"), request)
837 request.finish() 917 request.finish()
838 raise failure.Failure(exceptions.CancelError(u'HTTP redirection is used')) 918 raise failure.Failure(exceptions.CancelError(u"HTTP redirection is used"))
839 919
840 def redirectOrContinue(self, request, redirect_arg=u'redirect_url'): 920 def redirectOrContinue(self, request, redirect_arg=u"redirect_url"):
841 """helper method to redirect a page to an url given as arg 921 """helper method to redirect a page to an url given as arg
842 922
843 if the arg is not present, the page will continue normal workflow 923 if the arg is not present, the page will continue normal workflow
844 @param request(server.Request): current HTTP request 924 @param request(server.Request): current HTTP request
845 @param redirect_arg(unicode): argument to use to get redirection URL 925 @param redirect_arg(unicode): argument to use to get redirection URL
846 @interrupt: redirect the page to requested URL 926 @interrupt: redirect the page to requested URL
847 @interrupt pageError(C.HTTP_BAD_REQUEST): empty or non local URL is used 927 @interrupt pageError(C.HTTP_BAD_REQUEST): empty or non local URL is used
848 """ 928 """
849 try: 929 try:
850 url = request.args['redirect_url'][0] 930 url = request.args["redirect_url"][0]
851 except (KeyError, IndexError): 931 except (KeyError, IndexError):
852 pass 932 pass
853 else: 933 else:
854 # a redirection is requested 934 #  a redirection is requested
855 if not url or url[0] != u'/': 935 if not url or url[0] != u"/":
856 # we only want local urls 936 # we only want local urls
857 self.pageError(request, C.HTTP_BAD_REQUEST) 937 self.pageError(request, C.HTTP_BAD_REQUEST)
858 else: 938 else:
859 self.HTTPRedirect(request, url) 939 self.HTTPRedirect(request, url)
860 940
877 @param skip_parse_url(bool): if True, parse_url method on redirect page will be skipped 957 @param skip_parse_url(bool): if True, parse_url method on redirect page will be skipped
878 @param path_args(list[unicode], None): path arguments to use in redirected page 958 @param path_args(list[unicode], None): path arguments to use in redirected page
879 @raise KeyError: there is no known page with this name 959 @raise KeyError: there is no known page with this name
880 """ 960 """
881 # FIXME: render non LiberviaPage resources 961 # FIXME: render non LiberviaPage resources
882 path = page_path.rstrip(u'/').split(u'/') 962 path = page_path.rstrip(u"/").split(u"/")
883 if not path[0]: 963 if not path[0]:
884 redirect_page = self.host.root 964 redirect_page = self.host.root
885 else: 965 else:
886 redirect_page = self.named_pages[path[0]] 966 redirect_page = self.named_pages[path[0]]
887 967
899 # if cache is needed, it will be handled by final page 979 # if cache is needed, it will be handled by final page
900 redirect_page._do_cache = self._do_cache 980 redirect_page._do_cache = self._do_cache
901 self._do_cache = None 981 self._do_cache = None
902 982
903 redirect_page.renderPage(request, skip_parse_url=skip_parse_url) 983 redirect_page.renderPage(request, skip_parse_url=skip_parse_url)
904 raise failure.Failure(exceptions.CancelError(u'page redirection is used')) 984 raise failure.Failure(exceptions.CancelError(u"page redirection is used"))
905 985
906 def pageError(self, request, code=C.HTTP_NOT_FOUND, no_body=False): 986 def pageError(self, request, code=C.HTTP_NOT_FOUND, no_body=False):
907 """generate an error page and terminate the request 987 """generate an error page and terminate the request
908 988
909 @param request(server.Request): HTTP request 989 @param request(server.Request): HTTP request
912 """ 992 """
913 request.setResponseCode(code) 993 request.setResponseCode(code)
914 if no_body: 994 if no_body:
915 request.finish() 995 request.finish()
916 else: 996 else:
917 template = u'error/' + unicode(code) + '.html' 997 template = u"error/" + unicode(code) + ".html"
918 998
919 rendered = self.host.renderer.render( 999 rendered = self.host.renderer.render(
920 template, 1000 template,
921 root_path = '/templates/', 1001 root_path="/templates/",
922 error_code = code, 1002 error_code=code,
923 **request.template_data) 1003 **request.template_data
1004 )
924 1005
925 self.writeData(rendered, request) 1006 self.writeData(rendered, request)
926 raise failure.Failure(exceptions.CancelError(u'error page is used')) 1007 raise failure.Failure(exceptions.CancelError(u"error page is used"))
927 1008
928 def writeData(self, data, request): 1009 def writeData(self, data, request):
929 """write data to transport and finish the request""" 1010 """write data to transport and finish the request"""
930 if data is None: 1011 if data is None:
931 self.pageError(request) 1012 self.pageError(request)
932 data_encoded = data.encode('utf-8') 1013 data_encoded = data.encode("utf-8")
933 1014
934 if self._do_cache is not None: 1015 if self._do_cache is not None:
935 redirected_page = self._do_cache.pop(0) 1016 redirected_page = self._do_cache.pop(0)
936 cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache) 1017 cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache)
937 page_cache = cache[redirected_page] = CachePage(data_encoded) 1018 page_cache = cache[redirected_page] = CachePage(data_encoded)
938 self._setCacheHeaders(request, page_cache) 1019 self._setCacheHeaders(request, page_cache)
939 log.debug(_(u'{page} put in cache for [{profile}]').format( 1020 log.debug(
940 page=self, 1021 _(u"{page} put in cache for [{profile}]").format(
941 profile=self._do_cache[0])) 1022 page=self, profile=self._do_cache[0]
1023 )
1024 )
942 self._do_cache = None 1025 self._do_cache = None
943 self._checkCacheHeaders(request, page_cache) 1026 self._checkCacheHeaders(request, page_cache)
944 1027
945 request.write(data_encoded) 1028 request.write(data_encoded)
946 request.finish() 1029 request.finish()
959 child = self.children[subpage] 1042 child = self.children[subpage]
960 except KeyError: 1043 except KeyError:
961 self.pageError(request) 1044 self.pageError(request)
962 else: 1045 else:
963 child.render(request) 1046 child.render(request)
964 raise failure.Failure(exceptions.CancelError(u'subpage page is used')) 1047 raise failure.Failure(exceptions.CancelError(u"subpage page is used"))
965 1048
966 def _prepare_dynamic(self, dummy, request): 1049 def _prepare_dynamic(self, dummy, request):
967 # we need to activate dynamic page 1050 # we need to activate dynamic page
968 # we set data for template, and create/register token 1051 # we set data for template, and create/register token
969 socket_token = unicode(uuid.uuid4()) 1052 socket_token = unicode(uuid.uuid4())
970 socket_url = self.host.getWebsocketURL(request) 1053 socket_url = self.host.getWebsocketURL(request)
971 socket_debug = C.boolConst(self.host.debug) 1054 socket_debug = C.boolConst(self.host.debug)
972 request.template_data['websocket'] = WebsocketMeta(socket_url, socket_token, socket_debug) 1055 request.template_data["websocket"] = WebsocketMeta(
1056 socket_url, socket_token, socket_debug
1057 )
973 self.host.registerWSToken(socket_token, self, request) 1058 self.host.registerWSToken(socket_token, self, request)
974 # we will keep track of handlers to remove 1059 # we will keep track of handlers to remove
975 request._signals_registered = [] 1060 request._signals_registered = []
976 # we will cache registered signals until socket is opened 1061 # we will cache registered signals until socket is opened
977 request._signals_cache = [] 1062 request._signals_cache = []
986 template_data = request.template_data 1071 template_data = request.template_data
987 1072
988 # if confirm variable is set in case of successfuly data post 1073 # if confirm variable is set in case of successfuly data post
989 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1074 session_data = self.host.getSessionData(request, session_iface.ISATSession)
990 if session_data.popPageFlag(self, C.FLAG_CONFIRM): 1075 if session_data.popPageFlag(self, C.FLAG_CONFIRM):
991 template_data[u'confirm'] = True 1076 template_data[u"confirm"] = True
992 1077
993 return self.host.renderer.render( 1078 return self.host.renderer.render(
994 self.template, 1079 self.template,
995 root_path = '/templates/', 1080 root_path="/templates/",
996 media_path = '/' + C.MEDIA_DIR, 1081 media_path="/" + C.MEDIA_DIR,
997 cache_path = session_data.cache_dir, 1082 cache_path=session_data.cache_dir,
998 main_menu = LiberviaPage.main_menu, 1083 main_menu=LiberviaPage.main_menu,
999 **template_data) 1084 **template_data
1085 )
1000 1086
1001 def _renderEb(self, failure_, request): 1087 def _renderEb(self, failure_, request):
1002 """don't raise error on CancelError""" 1088 """don't raise error on CancelError"""
1003 failure_.trap(exceptions.CancelError) 1089 failure_.trap(exceptions.CancelError)
1004 1090
1005 def _internalError(self, failure_, request): 1091 def _internalError(self, failure_, request):
1006 """called if an error is not catched""" 1092 """called if an error is not catched"""
1007 log.error(_(u"Uncatched error for HTTP request on {url}: {msg}").format( 1093 log.error(
1008 url = request.URLPath(), 1094 _(u"Uncatched error for HTTP request on {url}: {msg}").format(
1009 msg = failure_)) 1095 url=request.URLPath(), msg=failure_
1096 )
1097 )
1010 self.pageError(request, C.HTTP_INTERNAL_ERROR) 1098 self.pageError(request, C.HTTP_INTERNAL_ERROR)
1011 1099
1012 def _on_data_post_redirect(self, ret, request): 1100 def _on_data_post_redirect(self, ret, request):
1013 """called when page's on_data_post has been done successfuly 1101 """called when page's on_data_post has been done successfuly
1014 1102
1024 ret = () 1112 ret = ()
1025 elif isinstance(ret, basestring): 1113 elif isinstance(ret, basestring):
1026 ret = (ret,) 1114 ret = (ret,)
1027 else: 1115 else:
1028 ret = tuple(ret) 1116 ret = tuple(ret)
1029 raise NotImplementedError(_(u'iterable in on_data_post return value is not used yet')) 1117 raise NotImplementedError(
1118 _(u"iterable in on_data_post return value is not used yet")
1119 )
1030 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1120 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1031 request_data = self.getRData(request) 1121 request_data = self.getRData(request)
1032 if 'post_redirect_page' in request_data: 1122 if "post_redirect_page" in request_data:
1033 redirect_page_data = request_data['post_redirect_page'] 1123 redirect_page_data = request_data["post_redirect_page"]
1034 if isinstance(redirect_page_data, tuple): 1124 if isinstance(redirect_page_data, tuple):
1035 redirect_page = redirect_page_data[0] 1125 redirect_page = redirect_page_data[0]
1036 redirect_page_args = redirect_page_data[1:] 1126 redirect_page_args = redirect_page_data[1:]
1037 redirect_uri = redirect_page.getURL(*redirect_page_args) 1127 redirect_uri = redirect_page.getURL(*redirect_page_args)
1038 else: 1128 else:
1045 if not C.POST_NO_CONFIRM in ret: 1135 if not C.POST_NO_CONFIRM in ret:
1046 session_data.setPageFlag(redirect_page, C.FLAG_CONFIRM) 1136 session_data.setPageFlag(redirect_page, C.FLAG_CONFIRM)
1047 request.setResponseCode(C.HTTP_SEE_OTHER) 1137 request.setResponseCode(C.HTTP_SEE_OTHER)
1048 request.setHeader("location", redirect_uri) 1138 request.setHeader("location", redirect_uri)
1049 request.finish() 1139 request.finish()
1050 raise failure.Failure(exceptions.CancelError(u'Post/Redirect/Get is used')) 1140 raise failure.Failure(exceptions.CancelError(u"Post/Redirect/Get is used"))
1051 1141
1052 def _on_data_post(self, dummy, request): 1142 def _on_data_post(self, dummy, request):
1053 csrf_token = self.host.getSessionData(request, session_iface.ISATSession).csrf_token 1143 csrf_token = self.host.getSessionData(
1144 request, session_iface.ISATSession
1145 ).csrf_token
1054 try: 1146 try:
1055 given_csrf = self.getPostedData(request, u'csrf_token') 1147 given_csrf = self.getPostedData(request, u"csrf_token")
1056 except KeyError: 1148 except KeyError:
1057 given_csrf = None 1149 given_csrf = None
1058 if given_csrf is None or given_csrf != csrf_token: 1150 if given_csrf is None or given_csrf != csrf_token:
1059 log.warning(_(u"invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format( 1151 log.warning(
1060 url=request.uri, 1152 _(u"invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format(
1061 ip=request.getClientIP())) 1153 url=request.uri, ip=request.getClientIP()
1154 )
1155 )
1062 self.pageError(request, C.HTTP_UNAUTHORIZED) 1156 self.pageError(request, C.HTTP_UNAUTHORIZED)
1063 d = defer.maybeDeferred(self.on_data_post, self, request) 1157 d = defer.maybeDeferred(self.on_data_post, self, request)
1064 d.addCallback(self._on_data_post_redirect, request) 1158 d.addCallback(self._on_data_post_redirect, request)
1065 return d 1159 return d
1066 1160
1074 @param multiple(bool): True if multiple values are possible/expected 1168 @param multiple(bool): True if multiple values are possible/expected
1075 if False, the first value is returned 1169 if False, the first value is returned
1076 @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]): values received for this(these) key(s) 1170 @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]): values received for this(these) key(s)
1077 @raise KeyError: one specific key has been requested, and it is missing 1171 @raise KeyError: one specific key has been requested, and it is missing
1078 """ 1172 """
1079 # FIXME: request.args is already unquoting the value, it seems we are doing double unquote 1173 #  FIXME: request.args is already unquoting the value, it seems we are doing double unquote
1080 if isinstance(keys, basestring): 1174 if isinstance(keys, basestring):
1081 keys = [keys] 1175 keys = [keys]
1082 get_first = True 1176 get_first = True
1083 else: 1177 else:
1084 get_first = False 1178 get_first = False
1085 1179
1086 ret = [] 1180 ret = []
1087 for key in keys: 1181 for key in keys:
1088 gen = (urllib.unquote(v).decode('utf-8') for v in request.args.get(key,[])) 1182 gen = (urllib.unquote(v).decode("utf-8") for v in request.args.get(key, []))
1089 if multiple: 1183 if multiple:
1090 ret.append(gen) 1184 ret.append(gen)
1091 else: 1185 else:
1092 try: 1186 try:
1093 ret.append(next(gen)) 1187 ret.append(next(gen))
1103 @param except_(iterable[unicode]): key of values to ignore 1197 @param except_(iterable[unicode]): key of values to ignore
1104 csrf_token will always be ignored 1198 csrf_token will always be ignored
1105 @param multiple(bool): if False, only the first values are returned 1199 @param multiple(bool): if False, only the first values are returned
1106 @return (dict[unicode, list[unicode]]): post values 1200 @return (dict[unicode, list[unicode]]): post values
1107 """ 1201 """
1108 except_ = tuple(except_) + (u'csrf_token',) 1202 except_ = tuple(except_) + (u"csrf_token",)
1109 ret = {} 1203 ret = {}
1110 for key, values in request.args.iteritems(): 1204 for key, values in request.args.iteritems():
1111 key = urllib.unquote(key).decode('utf-8') 1205 key = urllib.unquote(key).decode("utf-8")
1112 if key in except_: 1206 if key in except_:
1113 continue 1207 continue
1114 if not multiple: 1208 if not multiple:
1115 ret[key] = urllib.unquote(values[0]).decode('utf-8') 1209 ret[key] = urllib.unquote(values[0]).decode("utf-8")
1116 else: 1210 else:
1117 ret[key] = [urllib.unquote(v).decode('utf-8') for v in values] 1211 ret[key] = [urllib.unquote(v).decode("utf-8") for v in values]
1118 return ret 1212 return ret
1119 1213
1120 def getProfile(self, request): 1214 def getProfile(self, request):
1121 """helper method to easily get current profile 1215 """helper method to easily get current profile
1122 1216
1168 dynamic pages 1262 dynamic pages
1169 @param template(unicode): path of the template to render 1263 @param template(unicode): path of the template to render
1170 @param template_data(dict): template_data to use 1264 @param template_data(dict): template_data to use
1171 """ 1265 """
1172 if not self.dynamic: 1266 if not self.dynamic:
1173 raise exceptions.InternalError(_(u"renderPartial must only be used with dynamic pages")) 1267 raise exceptions.InternalError(
1268 _(u"renderPartial must only be used with dynamic pages")
1269 )
1174 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1270 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1175 1271
1176 return self.host.renderer.render( 1272 return self.host.renderer.render(
1177 template, 1273 template,
1178 root_path = '/templates/', 1274 root_path="/templates/",
1179 media_path = '/' + C.MEDIA_DIR, 1275 media_path="/" + C.MEDIA_DIR,
1180 cache_path = session_data.cache_dir, 1276 cache_path=session_data.cache_dir,
1181 main_menu = LiberviaPage.main_menu, 1277 main_menu=LiberviaPage.main_menu,
1182 **template_data) 1278 **template_data
1183 1279 )
1184 def renderAndUpdate(self, request, template, selectors, template_data_update, update_type="append"): 1280
1281 def renderAndUpdate(
1282 self, request, template, selectors, template_data_update, update_type="append"
1283 ):
1185 """Helper method to render a partial page element and update the page 1284 """Helper method to render a partial page element and update the page
1186 1285
1187 this is NOT the normal page rendering method, it is used only to update 1286 this is NOT the normal page rendering method, it is used only to update
1188 dynamic pages 1287 dynamic pages
1189 @param request(server.Request): current HTTP request 1288 @param request(server.Request): current HTTP request
1196 append: append rendered element to selected element 1295 append: append rendered element to selected element
1197 """ 1296 """
1198 template_data = request.template_data.copy() 1297 template_data = request.template_data.copy()
1199 template_data.update(template_data_update) 1298 template_data.update(template_data_update)
1200 html = self.renderPartial(request, template, template_data) 1299 html = self.renderPartial(request, template, template_data)
1201 request.sendData(u'dom', 1300 request.sendData(u"dom", selectors=selectors, update_type=update_type, html=html)
1202 selectors=selectors,
1203 update_type=update_type,
1204 html=html)
1205 1301
1206 def renderPage(self, request, skip_parse_url=False): 1302 def renderPage(self, request, skip_parse_url=False):
1207 """Main method to handle the workflow of a LiberviaPage""" 1303 """Main method to handle the workflow of a LiberviaPage"""
1208 1304
1209 # template_data are the variables passed to template 1305 # template_data are the variables passed to template
1210 if not hasattr(request, 'template_data'): 1306 if not hasattr(request, "template_data"):
1211 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1307 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1212 csrf_token = session_data.csrf_token 1308 csrf_token = session_data.csrf_token
1213 request.template_data = {u'profile': session_data.profile, 1309 request.template_data = {
1214 u'csrf_token': csrf_token} 1310 u"profile": session_data.profile,
1311 u"csrf_token": csrf_token,
1312 }
1215 1313
1216 # XXX: here is the code which need to be executed once 1314 # XXX: here is the code which need to be executed once
1217 # at the beginning of the request hanling 1315 # at the beginning of the request hanling
1218 if request.postpath and not request.postpath[-1]: 1316 if request.postpath and not request.postpath[-1]:
1219 # we don't differenciate URLs finishing with '/' or not 1317 # we don't differenciate URLs finishing with '/' or not
1221 1319
1222 d = defer.Deferred() 1320 d = defer.Deferred()
1223 d.addCallback(self._checkAccess, request) 1321 d.addCallback(self._checkAccess, request)
1224 1322
1225 if self.redirect is not None: 1323 if self.redirect is not None:
1226 d.addCallback(lambda dummy: self.pageRedirect(self.redirect, request, skip_parse_url=False)) 1324 d.addCallback(
1325 lambda dummy: self.pageRedirect(
1326 self.redirect, request, skip_parse_url=False
1327 )
1328 )
1227 1329
1228 if self.parse_url is not None and not skip_parse_url: 1330 if self.parse_url is not None and not skip_parse_url:
1229 if self.url_cache: 1331 if self.url_cache:
1230 profile = self.getProfile(request) 1332 profile = self.getProfile(request)
1231 try: 1333 try:
1232 cache_url = self.cached_urls[profile][request.uri] 1334 cache_url = self.cached_urls[profile][request.uri]
1233 except KeyError: 1335 except KeyError:
1234 # no cache for this URI yet 1336 # no cache for this URI yet
1235 # we do normal URL parsing, and then the cache 1337 #  we do normal URL parsing, and then the cache
1236 d.addCallback(self.parse_url, request) 1338 d.addCallback(self.parse_url, request)
1237 d.addCallback(self._cacheURL, request, profile) 1339 d.addCallback(self._cacheURL, request, profile)
1238 else: 1340 else:
1239 log.debug(_(u"using URI cache for {page}").format(page=self)) 1341 log.debug(_(u"using URI cache for {page}").format(page=self))
1240 cache_url.use(request) 1342 cache_url.use(request)