Mercurial > libervia-web
comparison libervia/server/pages.py @ 1509:106bae41f5c8
massive refactoring from camelCase -> snake_case. See backend commit log for more details
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 08 Apr 2023 13:44:11 +0200 |
parents | ce879da7fcf7 |
children | 16228994ca3b |
comparison
equal
deleted
inserted
replaced
1508:ec3ad9abf9f9 | 1509:106bae41f5c8 |
---|---|
37 from twisted.words.protocols.jabber import jid | 37 from twisted.words.protocols.jabber import jid |
38 from twisted.python import failure | 38 from twisted.python import failure |
39 | 39 |
40 from sat.core.i18n import _ | 40 from sat.core.i18n import _ |
41 from sat.core import exceptions | 41 from sat.core import exceptions |
42 from sat.tools.utils import asDeferred | 42 from sat.tools.utils import as_deferred |
43 from sat.tools.common import date_utils | 43 from sat.tools.common import date_utils |
44 from sat.tools.common import utils | 44 from sat.tools.common import utils |
45 from sat.tools.common import data_format | 45 from sat.tools.common import data_format |
46 from sat.core.log import getLogger | 46 from sat.core.log import getLogger |
47 from sat_frontends.bridge.bridge_frontend import BridgeException | 47 from sat_frontends.bridge.bridge_frontend import BridgeException |
135 this URL may not be valid, as pages may require path arguments | 135 this URL may not be valid, as pages may require path arguments |
136 @param name(unicode, None): if not None, a unique name to identify the page | 136 @param name(unicode, None): if not None, a unique name to identify the page |
137 can then be used for e.g. redirection | 137 can then be used for e.g. redirection |
138 "/" is not allowed in names (as it can be used to construct URL paths) | 138 "/" is not allowed in names (as it can be used to construct URL paths) |
139 @param redirect(unicode, None): if not None, this page will be redirected. | 139 @param redirect(unicode, None): if not None, this page will be redirected. |
140 A redirected parameter is used as in self.pageRedirect. | 140 A redirected parameter is used as in self.page_redirect. |
141 parse_url will not be skipped | 141 parse_url will not be skipped |
142 using this redirect parameter is called "full redirection" | 142 using this redirect parameter is called "full redirection" |
143 using self.pageRedirect is called "partial redirection" (because some | 143 using self.page_redirect is called "partial redirection" (because some |
144 rendering method can still be used, e.g. parse_url) | 144 rendering method can still be used, e.g. parse_url) |
145 @param access(unicode, None): permission needed to access the page | 145 @param access(unicode, None): permission needed to access the page |
146 None means public access. | 146 None means public access. |
147 Pages inherit from parent pages: e.g. if a "settings" page is restricted | 147 Pages inherit from parent pages: e.g. if a "settings" page is restricted |
148 to admins, and if "settings/blog" is public, it still can only be accessed by | 148 to admins, and if "settings/blog" is public, it still can only be accessed by |
215 lambda x: x is not None | 215 lambda x: x is not None |
216 for x in (parse_url, prepare_render, render, template) | 216 for x in (parse_url, prepare_render, render, template) |
217 ): | 217 ): |
218 raise ValueError( | 218 raise ValueError( |
219 _("you can't use full page redirection with other rendering" | 219 _("you can't use full page redirection with other rendering" |
220 "method, check self.pageRedirect if you need to use them")) | 220 "method, check self.page_redirect if you need to use them")) |
221 self.redirect = redirect | 221 self.redirect = redirect |
222 else: | 222 else: |
223 self.redirect = None | 223 self.redirect = None |
224 self.parse_url = parse_url | 224 self.parse_url = parse_url |
225 self.add_breadcrumb = add_breadcrumb | 225 self.add_breadcrumb = add_breadcrumb |
272 @property | 272 @property |
273 def site_themes(self): | 273 def site_themes(self): |
274 return self.vhost_root.site_themes | 274 return self.vhost_root.site_themes |
275 | 275 |
276 @staticmethod | 276 @staticmethod |
277 def createPage(host, meta_path, vhost_root, url_elts, replace_on_conflict=False): | 277 def create_page(host, meta_path, vhost_root, url_elts, replace_on_conflict=False): |
278 """Create a LiberviaPage instance | 278 """Create a LiberviaPage instance |
279 | 279 |
280 @param meta_path(Path): path to the page_meta.py file | 280 @param meta_path(Path): path to the page_meta.py file |
281 @param vhost_root(resource.Resource): root resource of the virtual host | 281 @param vhost_root(resource.Resource): root resource of the virtual host |
282 @param url_elts(list[unicode]): list of path element from root site to this page | 282 @param url_elts(list[unicode]): list of path element from root site to this page |
312 url_cache=page_data.get("url_cache", False), | 312 url_cache=page_data.get("url_cache", False), |
313 replace_on_conflict=replace_on_conflict | 313 replace_on_conflict=replace_on_conflict |
314 ) | 314 ) |
315 | 315 |
316 @staticmethod | 316 @staticmethod |
317 def createBrowserData( | 317 def create_browser_data( |
318 vhost_root, | 318 vhost_root, |
319 resource: Optional[LiberviaPage], | 319 resource: Optional[LiberviaPage], |
320 browser_path: Path, | 320 browser_path: Path, |
321 path_elts: Optional[List[str]], | 321 path_elts: Optional[List[str]], |
322 engine: str = "brython" | 322 engine: str = "brython" |
353 vhost_root.browser_modules.setdefault( | 353 vhost_root.browser_modules.setdefault( |
354 engine, []).append(dyn_data) | 354 engine, []).append(dyn_data) |
355 | 355 |
356 | 356 |
357 @classmethod | 357 @classmethod |
358 def importPages(cls, host, vhost_root, root_path=None, _parent=None, _path=None, | 358 def import_pages(cls, host, vhost_root, root_path=None, _parent=None, _path=None, |
359 _extra_pages=False): | 359 _extra_pages=False): |
360 """Recursively import Libervia pages | 360 """Recursively import Libervia pages |
361 | 361 |
362 @param host(Libervia): Libervia instance | 362 @param host(Libervia): Libervia instance |
363 @param vhost_root(LiberviaRootResource): root of this VirtualHost | 363 @param vhost_root(LiberviaRootResource): root of this VirtualHost |
379 root_dir = root_path / C.PAGES_DIR | 379 root_dir = root_path / C.PAGES_DIR |
380 _extra_pages = True | 380 _extra_pages = True |
381 _parent = vhost_root | 381 _parent = vhost_root |
382 root_browser_path = root_dir / C.PAGES_BROWSER_DIR | 382 root_browser_path = root_dir / C.PAGES_BROWSER_DIR |
383 if root_browser_path.is_dir(): | 383 if root_browser_path.is_dir(): |
384 cls.createBrowserData(vhost_root, None, root_browser_path, None) | 384 cls.create_browser_data(vhost_root, None, root_browser_path, None) |
385 else: | 385 else: |
386 root_dir = _parent.root_dir | 386 root_dir = _parent.root_dir |
387 | 387 |
388 for d in os.listdir(root_dir): | 388 for d in os.listdir(root_dir): |
389 dir_path = root_dir / d | 389 dir_path = root_dir / d |
395 continue | 395 continue |
396 meta_path = dir_path / C.PAGES_META_FILE | 396 meta_path = dir_path / C.PAGES_META_FILE |
397 if meta_path.is_file(): | 397 if meta_path.is_file(): |
398 new_path = _path + [d] | 398 new_path = _path + [d] |
399 try: | 399 try: |
400 page_data, resource = cls.createPage( | 400 page_data, resource = cls.create_page( |
401 host, meta_path, vhost_root, new_path) | 401 host, meta_path, vhost_root, new_path) |
402 except exceptions.ConflictError as e: | 402 except exceptions.ConflictError as e: |
403 if _extra_pages: | 403 if _extra_pages: |
404 # extra pages are discarded if there is already an existing page | 404 # extra pages are discarded if there is already an existing page |
405 continue | 405 continue |
429 except KeyError: | 429 except KeyError: |
430 log.error(_("missing {name} method to handle {1}/{2}") | 430 log.error(_("missing {name} method to handle {1}/{2}") |
431 .format(name=cb_name, *uri_tuple)) | 431 .format(name=cb_name, *uri_tuple)) |
432 continue | 432 continue |
433 else: | 433 else: |
434 resource.registerURI(uri_tuple, cb) | 434 resource.register_uri(uri_tuple, cb) |
435 | 435 |
436 LiberviaPage.importPages( | 436 LiberviaPage.import_pages( |
437 host, vhost_root, _parent=resource, _path=new_path, | 437 host, vhost_root, _parent=resource, _path=new_path, |
438 _extra_pages=_extra_pages) | 438 _extra_pages=_extra_pages) |
439 # now we check if there is some code for browser | 439 # now we check if there is some code for browser |
440 browser_path = dir_path / C.PAGES_BROWSER_DIR | 440 browser_path = dir_path / C.PAGES_BROWSER_DIR |
441 if browser_path.is_dir(): | 441 if browser_path.is_dir(): |
442 cls.createBrowserData(vhost_root, resource, browser_path, new_path) | 442 cls.create_browser_data(vhost_root, resource, browser_path, new_path) |
443 | 443 |
444 @classmethod | 444 @classmethod |
445 def onFileChange(cls, host, file_path, flags, site_root, site_path): | 445 def on_file_change(cls, host, file_path, flags, site_root, site_path): |
446 """Method triggered by file_watcher when something is changed in files | 446 """Method triggered by file_watcher when something is changed in files |
447 | 447 |
448 This method is used in dev mode to reload pages when needed | 448 This method is used in dev mode to reload pages when needed |
449 @param file_path(filepath.FilePath): path of the file which triggered the event | 449 @param file_path(filepath.FilePath): path of the file which triggered the event |
450 @param flags[list[unicode]): human readable flags of the event (from | 450 @param flags[list[unicode]): human readable flags of the event (from |
504 except AttributeError: | 504 except AttributeError: |
505 parent = page | 505 parent = page |
506 | 506 |
507 try: | 507 try: |
508 # we (re)create a page with the new/modified code | 508 # we (re)create a page with the new/modified code |
509 __, resource = cls.createPage(host, path, site_root, path_elts, | 509 __, resource = cls.create_page(host, path, site_root, path_elts, |
510 replace_on_conflict=True) | 510 replace_on_conflict=True) |
511 if not new_page: | 511 if not new_page: |
512 try: | 512 try: |
513 resource.children = page.original.children | 513 resource.children = page.original.children |
514 except AttributeError: | 514 except AttributeError: |
526 parent.putChild(url_elt, resource) | 526 parent.putChild(url_elt, resource) |
527 | 527 |
528 # is there any browser data to create? | 528 # is there any browser data to create? |
529 browser_path = resource.root_dir / C.PAGES_BROWSER_DIR | 529 browser_path = resource.root_dir / C.PAGES_BROWSER_DIR |
530 if browser_path.is_dir(): | 530 if browser_path.is_dir(): |
531 cls.createBrowserData( | 531 cls.create_browser_data( |
532 resource.vhost_root, | 532 resource.vhost_root, |
533 resource, | 533 resource, |
534 browser_path, | 534 browser_path, |
535 resource.url.split('/') | 535 resource.url.split('/') |
536 ) | 536 ) |
538 if new_page: | 538 if new_page: |
539 log.info(_("{page} created").format(page=resource)) | 539 log.info(_("{page} created").format(page=resource)) |
540 else: | 540 else: |
541 log.info(_("{page} reloaded").format(page=resource)) | 541 log.info(_("{page} reloaded").format(page=resource)) |
542 | 542 |
543 def checkCSRF(self, request): | 543 def check_csrf(self, request): |
544 session = self.host.getSessionData( | 544 session = self.host.get_session_data( |
545 request, session_iface.IWebSession | 545 request, session_iface.IWebSession |
546 ) | 546 ) |
547 if session.profile is None: | 547 if session.profile is None: |
548 # CSRF doesn't make sense when no user is logged | 548 # CSRF doesn't make sense when no user is logged |
549 log.debug("disabling CSRF check because service profile is used") | 549 log.debug("disabling CSRF check because service profile is used") |
550 return | 550 return |
551 csrf_token = session.csrf_token | 551 csrf_token = session.csrf_token |
552 given_csrf = request.getHeader("X-Csrf-Token") | 552 given_csrf = request.getHeader("X-Csrf-Token") |
553 if given_csrf is None: | 553 if given_csrf is None: |
554 try: | 554 try: |
555 given_csrf = self.getPostedData(request, "csrf_token") | 555 given_csrf = self.get_posted_data(request, "csrf_token") |
556 except KeyError: | 556 except KeyError: |
557 pass | 557 pass |
558 if given_csrf is None or given_csrf != csrf_token: | 558 if given_csrf is None or given_csrf != csrf_token: |
559 log.warning( | 559 log.warning( |
560 _("invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format( | 560 _("invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format( |
561 url=request.uri, ip=request.getClientIP() | 561 url=request.uri, ip=request.getClientIP() |
562 ) | 562 ) |
563 ) | 563 ) |
564 self.pageError(request, C.HTTP_FORBIDDEN) | 564 self.page_error(request, C.HTTP_FORBIDDEN) |
565 | 565 |
566 def exposeToScripts( | 566 def expose_to_scripts( |
567 self, | 567 self, |
568 request: server.Request, | 568 request: server.Request, |
569 **kwargs: str | 569 **kwargs: str |
570 ) -> None: | 570 ) -> None: |
571 """Make a local variable available to page script as a global variable | 571 """Make a local variable available to page script as a global variable |
583 value = repr(str(value)) | 583 value = repr(str(value)) |
584 else: | 584 else: |
585 value = repr(value) | 585 value = repr(value) |
586 scripts.add(Script(content=f"var {name}={value};")) | 586 scripts.add(Script(content=f"var {name}={value};")) |
587 | 587 |
588 def registerURI(self, uri_tuple, get_uri_cb): | 588 def register_uri(self, uri_tuple, get_uri_cb): |
589 """Register a URI handler | 589 """Register a URI handler |
590 | 590 |
591 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler | 591 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler |
592 type/subtype as returned by tools/common/parseXMPPUri | 592 type/subtype as returned by tools/common/parse_xmpp_uri |
593 or type/None to handle all subtypes | 593 or type/None to handle all subtypes |
594 @param get_uri_cb(callable): method which take uri_data dict as only argument | 594 @param get_uri_cb(callable): method which take uri_data dict as only argument |
595 and return absolute path with correct arguments or None if the page | 595 and return absolute path with correct arguments or None if the page |
596 can't handle this URL | 596 can't handle this URL |
597 """ | 597 """ |
598 if uri_tuple in self.uri_callbacks: | 598 if uri_tuple in self.uri_callbacks: |
599 log.info(_("{}/{} URIs are already handled, replacing by the new handler") | 599 log.info(_("{}/{} URIs are already handled, replacing by the new handler") |
600 .format( *uri_tuple)) | 600 .format( *uri_tuple)) |
601 self.uri_callbacks[uri_tuple] = (self, get_uri_cb) | 601 self.uri_callbacks[uri_tuple] = (self, get_uri_cb) |
602 | 602 |
603 def getConfig(self, key, default=None, value_type=None): | 603 def config_get(self, key, default=None, value_type=None): |
604 return self.host.getConfig(self.vhost_root, key=key, default=default, | 604 return self.host.config_get(self.vhost_root, key=key, default=default, |
605 value_type=value_type) | 605 value_type=value_type) |
606 | 606 |
607 def getBuildPath(self, session_data): | 607 def get_build_path(self, session_data): |
608 return session_data.cache_dir + self.vhost.site_name | 608 return session_data.cache_dir + self.vhost.site_name |
609 | 609 |
610 def getPageByName(self, name): | 610 def get_page_by_name(self, name): |
611 return self.vhost_root.getPageByName(name) | 611 return self.vhost_root.get_page_by_name(name) |
612 | 612 |
613 def getPagePathFromURI(self, uri): | 613 def get_page_path_from_uri(self, uri): |
614 return self.vhost_root.getPagePathFromURI(uri) | 614 return self.vhost_root.get_page_path_from_uri(uri) |
615 | 615 |
616 def getPageRedirectURL(self, request, page_name="login", url=None): | 616 def get_page_redirect_url(self, request, page_name="login", url=None): |
617 """generate URL for a page with redirect_url parameter set | 617 """generate URL for a page with redirect_url parameter set |
618 | 618 |
619 mainly used for login page with redirection to current page | 619 mainly used for login page with redirection to current page |
620 @param request(server.Request): current HTTP request | 620 @param request(server.Request): current HTTP request |
621 @param page_name(unicode): name of the page to go | 621 @param page_name(unicode): name of the page to go |
622 @param url(None, unicode): url to redirect to | 622 @param url(None, unicode): url to redirect to |
623 None to use request path (i.e. current page) | 623 None to use request path (i.e. current page) |
624 @return (unicode): URL to use | 624 @return (unicode): URL to use |
625 """ | 625 """ |
626 return "{root_url}?redirect_url={redirect_url}".format( | 626 return "{root_url}?redirect_url={redirect_url}".format( |
627 root_url=self.getPageByName(page_name).url, | 627 root_url=self.get_page_by_name(page_name).url, |
628 redirect_url=urllib.parse.quote_plus(request.uri) | 628 redirect_url=urllib.parse.quote_plus(request.uri) |
629 if url is None | 629 if url is None |
630 else url.encode("utf-8"), | 630 else url.encode("utf-8"), |
631 ) | 631 ) |
632 | 632 |
633 def getURL(self, *args: str, **kwargs: str) -> str: | 633 def get_url(self, *args: str, **kwargs: str) -> str: |
634 """retrieve URL of the page set arguments | 634 """retrieve URL of the page set arguments |
635 | 635 |
636 @param *args: arguments to add to the URL as path elements empty or None | 636 @param *args: arguments to add to the URL as path elements empty or None |
637 arguments will be ignored | 637 arguments will be ignored |
638 @param **kwargs: query parameters | 638 @param **kwargs: query parameters |
660 encoded = urllib.parse.urlencode( | 660 encoded = urllib.parse.urlencode( |
661 {k: v for k, v in kwargs.items()} | 661 {k: v for k, v in kwargs.items()} |
662 ) | 662 ) |
663 url += f"?{encoded}" | 663 url += f"?{encoded}" |
664 | 664 |
665 return self.host.checkRedirection( | 665 return self.host.check_redirection( |
666 self.vhost_root, | 666 self.vhost_root, |
667 url | 667 url |
668 ) | 668 ) |
669 | 669 |
670 def getCurrentURL(self, request): | 670 def get_current_url(self, request): |
671 """retrieve URL used to access this page | 671 """retrieve URL used to access this page |
672 | 672 |
673 @return(unicode): current URL | 673 @return(unicode): current URL |
674 """ | 674 """ |
675 # we get url in the following way (splitting request.path instead of using | 675 # we get url in the following way (splitting request.path instead of using |
683 if request.postpath: | 683 if request.postpath: |
684 if not request.postpath[-1]: | 684 if not request.postpath[-1]: |
685 # we remove trailing slash | 685 # we remove trailing slash |
686 request.postpath = request.postpath[:-1] | 686 request.postpath = request.postpath[:-1] |
687 if request.postpath: | 687 if request.postpath: |
688 # getSubPageURL must return subpage from the point where | 688 # get_sub_page_url must return subpage from the point where |
689 # the it is called, so we have to remove remanining | 689 # the it is called, so we have to remove remanining |
690 # path elements | 690 # path elements |
691 path_elts = path_elts[: -len(request.postpath)] | 691 path_elts = path_elts[: -len(request.postpath)] |
692 | 692 |
693 return "/" + "/".join(path_elts) | 693 return "/" + "/".join(path_elts) |
694 | 694 |
695 def getParamURL(self, request, **kwargs): | 695 def get_param_url(self, request, **kwargs): |
696 """use URL of current request but modify the parameters in query part | 696 """use URL of current request but modify the parameters in query part |
697 | 697 |
698 **kwargs(dict[str, unicode]): argument to use as query parameters | 698 **kwargs(dict[str, unicode]): argument to use as query parameters |
699 @return (unicode): constructed URL | 699 @return (unicode): constructed URL |
700 """ | 700 """ |
701 current_url = self.getCurrentURL(request) | 701 current_url = self.get_current_url(request) |
702 if kwargs: | 702 if kwargs: |
703 encoded = urllib.parse.urlencode( | 703 encoded = urllib.parse.urlencode( |
704 {k: v for k, v in kwargs.items()} | 704 {k: v for k, v in kwargs.items()} |
705 ) | 705 ) |
706 current_url = current_url + "?" + encoded | 706 current_url = current_url + "?" + encoded |
707 return current_url | 707 return current_url |
708 | 708 |
709 def getSubPageByName(self, subpage_name, parent=None): | 709 def get_sub_page_by_name(self, subpage_name, parent=None): |
710 """retrieve a subpage and its path using its name | 710 """retrieve a subpage and its path using its name |
711 | 711 |
712 @param subpage_name(unicode): name of the sub page | 712 @param subpage_name(unicode): name of the sub page |
713 it must be a direct children of parent page | 713 it must be a direct children of parent page |
714 @param parent(LiberviaPage, None): parent page | 714 @param parent(LiberviaPage, None): parent page |
728 return path.decode('utf-8'), child | 728 return path.decode('utf-8'), child |
729 raise exceptions.NotFound( | 729 raise exceptions.NotFound( |
730 _("requested sub page has not been found ({subpage_name})").format( | 730 _("requested sub page has not been found ({subpage_name})").format( |
731 subpage_name=subpage_name)) | 731 subpage_name=subpage_name)) |
732 | 732 |
733 def getSubPageURL(self, request, page_name, *args): | 733 def get_sub_page_url(self, request, page_name, *args): |
734 """retrieve a page in direct children and build its URL according to request | 734 """retrieve a page in direct children and build its URL according to request |
735 | 735 |
736 request's current path is used as base (at current parsing point, | 736 request's current path is used as base (at current parsing point, |
737 i.e. it's more prepath than path). | 737 i.e. it's more prepath than path). |
738 Requested page is checked in children and an absolute URL is then built | 738 Requested page is checked in children and an absolute URL is then built |
739 by the resulting combination. | 739 by the resulting combination. |
740 This method is useful to construct absolute URLs for children instead of | 740 This method is useful to construct absolute URLs for children instead of |
741 using relative path, which may not work in subpages, and are linked to the | 741 using relative path, which may not work in subpages, and are linked to the |
742 names of directories (i.e. relative URL will break if subdirectory is renamed | 742 names of directories (i.e. relative URL will break if subdirectory is renamed |
743 while getSubPageURL won't as long as page_name is consistent). | 743 while get_sub_page_url won't as long as page_name is consistent). |
744 Also, request.path is used, keeping real path used by user, | 744 Also, request.path is used, keeping real path used by user, |
745 and potential redirections. | 745 and potential redirections. |
746 @param request(server.Request): current HTTP request | 746 @param request(server.Request): current HTTP request |
747 @param page_name(unicode): name of the page to retrieve | 747 @param page_name(unicode): name of the page to retrieve |
748 it must be a direct children of current page | 748 it must be a direct children of current page |
749 @param *args(list[unicode]): arguments to add as path elements | 749 @param *args(list[unicode]): arguments to add as path elements |
750 if an arg is None, it will be ignored | 750 if an arg is None, it will be ignored |
751 @return (unicode): absolute URL to the sub page | 751 @return (unicode): absolute URL to the sub page |
752 """ | 752 """ |
753 current_url = self.getCurrentURL(request) | 753 current_url = self.get_current_url(request) |
754 path, child = self.getSubPageByName(page_name) | 754 path, child = self.get_sub_page_by_name(page_name) |
755 return os.path.join( | 755 return os.path.join( |
756 "/", current_url, path, *[quote(a) for a in args if a is not None] | 756 "/", current_url, path, *[quote(a) for a in args if a is not None] |
757 ) | 757 ) |
758 | 758 |
759 def getURLByNames(self, named_path): | 759 def get_url_by_names(self, named_path): |
760 """Retrieve URL from pages names and arguments | 760 """Retrieve URL from pages names and arguments |
761 | 761 |
762 @param named_path(list[tuple[unicode, list[unicode]]]): path to the page as a list | 762 @param named_path(list[tuple[unicode, list[unicode]]]): path to the page as a list |
763 of tuples of 2 items: | 763 of tuples of 2 items: |
764 - first item is page name | 764 - first item is page name |
768 """ | 768 """ |
769 current_page = None | 769 current_page = None |
770 path = [] | 770 path = [] |
771 for page_name, page_args in named_path: | 771 for page_name, page_args in named_path: |
772 if current_page is None: | 772 if current_page is None: |
773 current_page = self.getPageByName(page_name) | 773 current_page = self.get_page_by_name(page_name) |
774 path.append(current_page.getURL(*page_args)) | 774 path.append(current_page.get_url(*page_args)) |
775 else: | 775 else: |
776 sub_path, current_page = self.getSubPageByName( | 776 sub_path, current_page = self.get_sub_page_by_name( |
777 page_name, parent=current_page | 777 page_name, parent=current_page |
778 ) | 778 ) |
779 path.append(sub_path) | 779 path.append(sub_path) |
780 if page_args: | 780 if page_args: |
781 path.extend([quote(a) for a in page_args]) | 781 path.extend([quote(a) for a in page_args]) |
782 return self.host.checkRedirection(self.vhost_root, "/".join(path)) | 782 return self.host.check_redirection(self.vhost_root, "/".join(path)) |
783 | 783 |
784 def getURLByPath(self, *args): | 784 def get_url_by_path(self, *args): |
785 """Generate URL by path | 785 """Generate URL by path |
786 | 786 |
787 this method as a similar effect as getURLByNames, but it is more readable | 787 this method as a similar effect as get_url_by_names, but it is more readable |
788 by using SubPage to get pages instead of using tuples | 788 by using SubPage to get pages instead of using tuples |
789 @param *args: path element: | 789 @param *args: path element: |
790 - if unicode, will be used as argument | 790 - if unicode, will be used as argument |
791 - if util.SubPage instance, must be the name of a subpage | 791 - if util.SubPage instance, must be the name of a subpage |
792 @return (unicode): generated path | 792 @return (unicode): generated path |
797 # root page is the one needed to construct the base of the URL | 797 # root page is the one needed to construct the base of the URL |
798 # if first arg is not a SubPage instance, we use current page | 798 # if first arg is not a SubPage instance, we use current page |
799 if not isinstance(args[0], SubPage): | 799 if not isinstance(args[0], SubPage): |
800 root = self | 800 root = self |
801 else: | 801 else: |
802 root = self.getPageByName(args.pop(0)) | 802 root = self.get_page_by_name(args.pop(0)) |
803 # we keep track of current page to check subpage | 803 # we keep track of current page to check subpage |
804 current_page = root | 804 current_page = root |
805 url_elts = [] | 805 url_elts = [] |
806 arguments = [] | 806 arguments = [] |
807 while True: | 807 while True: |
808 while args and not isinstance(args[0], SubPage): | 808 while args and not isinstance(args[0], SubPage): |
809 arguments.append(quote(args.pop(0))) | 809 arguments.append(quote(args.pop(0))) |
810 if not url_elts: | 810 if not url_elts: |
811 url_elts.append(root.getURL(*arguments)) | 811 url_elts.append(root.get_url(*arguments)) |
812 else: | 812 else: |
813 url_elts.extend(arguments) | 813 url_elts.extend(arguments) |
814 if not args: | 814 if not args: |
815 break | 815 break |
816 else: | 816 else: |
817 path, current_page = current_page.getSubPageByName(args.pop(0)) | 817 path, current_page = current_page.get_sub_page_by_name(args.pop(0)) |
818 arguments = [path] | 818 arguments = [path] |
819 return self.host.checkRedirection(self.vhost_root, "/".join(url_elts)) | 819 return self.host.check_redirection(self.vhost_root, "/".join(url_elts)) |
820 | 820 |
821 def getChildWithDefault(self, path, request): | 821 def getChildWithDefault(self, path, request): |
822 # we handle children ourselves | 822 # we handle children ourselves |
823 raise exceptions.InternalError( | 823 raise exceptions.InternalError( |
824 "this method should not be used with LiberviaPage" | 824 "this method should not be used with LiberviaPage" |
825 ) | 825 ) |
826 | 826 |
827 def nextPath(self, request): | 827 def next_path(self, request): |
828 """get next URL path segment, and update request accordingly | 828 """get next URL path segment, and update request accordingly |
829 | 829 |
830 will move first segment of postpath in prepath | 830 will move first segment of postpath in prepath |
831 @param request(server.Request): current HTTP request | 831 @param request(server.Request): current HTTP request |
832 @return (unicode): unquoted segment | 832 @return (unicode): unquoted segment |
834 """ | 834 """ |
835 pathElement = request.postpath.pop(0) | 835 pathElement = request.postpath.pop(0) |
836 request.prepath.append(pathElement) | 836 request.prepath.append(pathElement) |
837 return urllib.parse.unquote(pathElement.decode('utf-8')) | 837 return urllib.parse.unquote(pathElement.decode('utf-8')) |
838 | 838 |
839 def _filterPathValue(self, value, handler, name, request): | 839 def _filter_path_value(self, value, handler, name, request): |
840 """Modify a path value according to handler (see [getPathArgs])""" | 840 """Modify a path value according to handler (see [get_path_args])""" |
841 if handler in ("@", "@jid") and value == "@": | 841 if handler in ("@", "@jid") and value == "@": |
842 value = None | 842 value = None |
843 | 843 |
844 if handler in ("", "@"): | 844 if handler in ("", "@"): |
845 if value is None: | 845 if value is None: |
848 if value: | 848 if value: |
849 try: | 849 try: |
850 return jid.JID(value) | 850 return jid.JID(value) |
851 except (RuntimeError, jid.InvalidFormat): | 851 except (RuntimeError, jid.InvalidFormat): |
852 log.warning(_("invalid jid argument: {value}").format(value=value)) | 852 log.warning(_("invalid jid argument: {value}").format(value=value)) |
853 self.pageError(request, C.HTTP_BAD_REQUEST) | 853 self.page_error(request, C.HTTP_BAD_REQUEST) |
854 else: | 854 else: |
855 return "" | 855 return "" |
856 else: | 856 else: |
857 return handler(self, value, name, request) | 857 return handler(self, value, name, request) |
858 | 858 |
859 return value | 859 return value |
860 | 860 |
861 def getPathArgs(self, request, names, min_args=0, **kwargs): | 861 def get_path_args(self, request, names, min_args=0, **kwargs): |
862 """get several path arguments at once | 862 """get several path arguments at once |
863 | 863 |
864 Arguments will be put in request data. | 864 Arguments will be put in request data. |
865 Missing arguments will have None value | 865 Missing arguments will have None value |
866 @param names(list[unicode]): list of arguments to get | 866 @param names(list[unicode]): list of arguments to get |
875 - 'jid': value must be converted to jid.JID if it exists, else empty | 875 - 'jid': value must be converted to jid.JID if it exists, else empty |
876 string is used | 876 string is used |
877 - '@jid': if value of arguments is empty or '@', empty string will be | 877 - '@jid': if value of arguments is empty or '@', empty string will be |
878 used, else it will be converted to jid | 878 used, else it will be converted to jid |
879 """ | 879 """ |
880 data = self.getRData(request) | 880 data = self.get_r_data(request) |
881 | 881 |
882 for idx, name in enumerate(names): | 882 for idx, name in enumerate(names): |
883 if name[0] == "*": | 883 if name[0] == "*": |
884 value = data[name[1:]] = [] | 884 value = data[name[1:]] = [] |
885 while True: | 885 while True: |
886 try: | 886 try: |
887 value.append(self.nextPath(request)) | 887 value.append(self.next_path(request)) |
888 except IndexError: | 888 except IndexError: |
889 idx -= 1 | 889 idx -= 1 |
890 break | 890 break |
891 else: | 891 else: |
892 idx += 1 | 892 idx += 1 |
893 else: | 893 else: |
894 try: | 894 try: |
895 value = data[name] = self.nextPath(request) | 895 value = data[name] = self.next_path(request) |
896 except IndexError: | 896 except IndexError: |
897 data[name] = None | 897 data[name] = None |
898 idx -= 1 | 898 idx -= 1 |
899 break | 899 break |
900 | 900 |
901 values_count = idx + 1 | 901 values_count = idx + 1 |
902 if values_count < min_args: | 902 if values_count < min_args: |
903 log.warning(_("Missing arguments in URL (got {count}, expected at least " | 903 log.warning(_("Missing arguments in URL (got {count}, expected at least " |
904 "{min_args})").format(count=values_count, min_args=min_args)) | 904 "{min_args})").format(count=values_count, min_args=min_args)) |
905 self.pageError(request, C.HTTP_BAD_REQUEST) | 905 self.page_error(request, C.HTTP_BAD_REQUEST) |
906 | 906 |
907 for name in names[values_count:]: | 907 for name in names[values_count:]: |
908 data[name] = None | 908 data[name] = None |
909 | 909 |
910 for name, handler in kwargs.items(): | 910 for name, handler in kwargs.items(): |
911 if name[0] == "*": | 911 if name[0] == "*": |
912 data[name] = [ | 912 data[name] = [ |
913 self._filterPathValue(v, handler, name, request) for v in data[name] | 913 self._filter_path_value(v, handler, name, request) for v in data[name] |
914 ] | 914 ] |
915 else: | 915 else: |
916 data[name] = self._filterPathValue(data[name], handler, name, request) | 916 data[name] = self._filter_path_value(data[name], handler, name, request) |
917 | 917 |
918 ## Pagination/Filtering ## | 918 ## Pagination/Filtering ## |
919 | 919 |
920 def getPubsubExtra(self, request, page_max=10, params=None, extra=None, | 920 def get_pubsub_extra(self, request, page_max=10, params=None, extra=None, |
921 order_by=C.ORDER_BY_CREATION): | 921 order_by=C.ORDER_BY_CREATION): |
922 """Set extra dict to retrieve PubSub items corresponding to URL parameters | 922 """Set extra dict to retrieve PubSub items corresponding to URL parameters |
923 | 923 |
924 Following parameters are used: | 924 Following parameters are used: |
925 - after: set rsm_after with ID of item | 925 - after: set rsm_after with ID of item |
926 - before: set rsm_before with ID of item | 926 - before: set rsm_before with ID of item |
927 @param request(server.Request): current HTTP request | 927 @param request(server.Request): current HTTP request |
928 @param page_max(int): required number of items per page | 928 @param page_max(int): required number of items per page |
929 @param params(None, dict[unicode, list[unicode]]): params as returned by | 929 @param params(None, dict[unicode, list[unicode]]): params as returned by |
930 self.getAllPostedData. | 930 self.get_all_posted_data. |
931 None to parse URL automatically | 931 None to parse URL automatically |
932 @param extra(None, dict): extra dict to use, or None to use a new one | 932 @param extra(None, dict): extra dict to use, or None to use a new one |
933 @param order_by(unicode, None): key to order by | 933 @param order_by(unicode, None): key to order by |
934 None to not specify order | 934 None to not specify order |
935 @return (dict): fill extra data | 935 @return (dict): fill extra data |
936 """ | 936 """ |
937 if params is None: | 937 if params is None: |
938 params = self.getAllPostedData(request, multiple=False) | 938 params = self.get_all_posted_data(request, multiple=False) |
939 if extra is None: | 939 if extra is None: |
940 extra = {} | 940 extra = {} |
941 else: | 941 else: |
942 assert not {"rsm_max", "rsm_after", "rsm_before", | 942 assert not {"rsm_max", "rsm_after", "rsm_before", |
943 C.KEY_ORDER_BY}.intersection(list(extra.keys())) | 943 C.KEY_ORDER_BY}.intersection(list(extra.keys())) |
952 # RSM returns list in order (oldest first), but we want most recent first | 952 # RSM returns list in order (oldest first), but we want most recent first |
953 # so we start by the end | 953 # so we start by the end |
954 extra['rsm_before'] = "" | 954 extra['rsm_before'] = "" |
955 return extra | 955 return extra |
956 | 956 |
957 def setPagination(self, request: server.Request, pubsub_data: dict) -> None: | 957 def set_pagination(self, request: server.Request, pubsub_data: dict) -> None: |
958 """Add to template_data if suitable | 958 """Add to template_data if suitable |
959 | 959 |
960 "previous_page_url" and "next_page_url" will be added using respectively | 960 "previous_page_url" and "next_page_url" will be added using respectively |
961 "before" and "after" URL parameters | 961 "before" and "after" URL parameters |
962 @param request: current HTTP request | 962 @param request: current HTTP request |
970 except KeyError: | 970 except KeyError: |
971 # no pagination available | 971 # no pagination available |
972 return | 972 return |
973 | 973 |
974 # if we have a search query, we must keep it | 974 # if we have a search query, we must keep it |
975 search = self.getPostedData(request, 'search', raise_on_missing=False) | 975 search = self.get_posted_data(request, 'search', raise_on_missing=False) |
976 if search is not None: | 976 if search is not None: |
977 extra['search'] = search.strip() | 977 extra['search'] = search.strip() |
978 | 978 |
979 # same for page_max | 979 # same for page_max |
980 page_max = self.getPostedData(request, 'page_max', raise_on_missing=False) | 980 page_max = self.get_posted_data(request, 'page_max', raise_on_missing=False) |
981 if page_max is not None: | 981 if page_max is not None: |
982 extra['page_max'] = page_max | 982 extra['page_max'] = page_max |
983 | 983 |
984 if rsm.get("index", 1) > 0: | 984 if rsm.get("index", 1) > 0: |
985 # We only show previous button if it's not the first page already. | 985 # We only show previous button if it's not the first page already. |
986 # If we have no index, we default to display the button anyway | 986 # If we have no index, we default to display the button anyway |
987 # as we can't know if we are on the first page or not. | 987 # as we can't know if we are on the first page or not. |
988 first_id = rsm["first"] | 988 first_id = rsm["first"] |
989 template_data['previous_page_url'] = self.getParamURL( | 989 template_data['previous_page_url'] = self.get_param_url( |
990 request, before=first_id, **extra) | 990 request, before=first_id, **extra) |
991 if not pubsub_data["complete"]: | 991 if not pubsub_data["complete"]: |
992 # we also show the page next button if complete is None because we | 992 # we also show the page next button if complete is None because we |
993 # can't know where we are in the feed in this case. | 993 # can't know where we are in the feed in this case. |
994 template_data['next_page_url'] = self.getParamURL( | 994 template_data['next_page_url'] = self.get_param_url( |
995 request, after=last_id, **extra) | 995 request, after=last_id, **extra) |
996 | 996 |
997 | 997 |
998 ## Cache handling ## | 998 ## Cache handling ## |
999 | 999 |
1000 def _setCacheHeaders(self, request, cache): | 1000 def _set_cache_headers(self, request, cache): |
1001 """Set ETag and Last-Modified HTTP headers, used for caching""" | 1001 """Set ETag and Last-Modified HTTP headers, used for caching""" |
1002 request.setHeader("ETag", cache.hash) | 1002 request.setHeader("ETag", cache.hash) |
1003 last_modified = self.host.getHTTPDate(cache.created) | 1003 last_modified = self.host.get_http_date(cache.created) |
1004 request.setHeader("Last-Modified", last_modified) | 1004 request.setHeader("Last-Modified", last_modified) |
1005 | 1005 |
1006 def _checkCacheHeaders(self, request, cache): | 1006 def _check_cache_headers(self, request, cache): |
1007 """Check if a cache condition is set on the request | 1007 """Check if a cache condition is set on the request |
1008 | 1008 |
1009 if condition is valid, C.HTTP_NOT_MODIFIED is returned | 1009 if condition is valid, C.HTTP_NOT_MODIFIED is returned |
1010 """ | 1010 """ |
1011 etag_match = request.getHeader("If-None-Match") | 1011 etag_match = request.getHeader("If-None-Match") |
1012 if etag_match is not None: | 1012 if etag_match is not None: |
1013 if cache.hash == etag_match: | 1013 if cache.hash == etag_match: |
1014 self.pageError(request, C.HTTP_NOT_MODIFIED, no_body=True) | 1014 self.page_error(request, C.HTTP_NOT_MODIFIED, no_body=True) |
1015 else: | 1015 else: |
1016 modified_match = request.getHeader("If-Modified-Since") | 1016 modified_match = request.getHeader("If-Modified-Since") |
1017 if modified_match is not None: | 1017 if modified_match is not None: |
1018 modified = date_utils.date_parse(modified_match) | 1018 modified = date_utils.date_parse(modified_match) |
1019 if modified >= int(cache.created): | 1019 if modified >= int(cache.created): |
1020 self.pageError(request, C.HTTP_NOT_MODIFIED, no_body=True) | 1020 self.page_error(request, C.HTTP_NOT_MODIFIED, no_body=True) |
1021 | 1021 |
1022 def checkCacheSubscribeCb(self, sub_id, service, node): | 1022 def check_cache_subscribe_cb(self, sub_id, service, node): |
1023 self.cache_pubsub_sub.add((service, node, sub_id)) | 1023 self.cache_pubsub_sub.add((service, node, sub_id)) |
1024 | 1024 |
1025 def checkCacheSubscribeEb(self, failure_, service, node): | 1025 def check_cache_subscribe_eb(self, failure_, service, node): |
1026 log.warning(_("Can't subscribe to node: {msg}").format(msg=failure_)) | 1026 log.warning(_("Can't subscribe to node: {msg}").format(msg=failure_)) |
1027 # FIXME: cache must be marked as unusable here | 1027 # FIXME: cache must be marked as unusable here |
1028 | 1028 |
1029 def psNodeWatchAddEb(self, failure_, service, node): | 1029 def ps_node_watch_add_eb(self, failure_, service, node): |
1030 log.warning(_("Can't add node watched: {msg}").format(msg=failure_)) | 1030 log.warning(_("Can't add node watched: {msg}").format(msg=failure_)) |
1031 | 1031 |
1032 def useCache(self, request: server.Request) -> bool: | 1032 def use_cache(self, request: server.Request) -> bool: |
1033 """Indicate if the cache should be used | 1033 """Indicate if the cache should be used |
1034 | 1034 |
1035 test request header to see if it is requested to skip the cache | 1035 test request header to see if it is requested to skip the cache |
1036 @return: True if cache should be used | 1036 @return: True if cache should be used |
1037 """ | 1037 """ |
1038 return request.getHeader('cache-control') != 'no-cache' | 1038 return request.getHeader('cache-control') != 'no-cache' |
1039 | 1039 |
1040 def checkCache(self, request, cache_type, **kwargs): | 1040 def check_cache(self, request, cache_type, **kwargs): |
1041 """check if a page is in cache and return cached version if suitable | 1041 """check if a page is in cache and return cached version if suitable |
1042 | 1042 |
1043 this method may perform extra operation to handle cache (e.g. subscribing to a | 1043 this method may perform extra operation to handle cache (e.g. subscribing to a |
1044 pubsub node) | 1044 pubsub node) |
1045 @param request(server.Request): current HTTP request | 1045 @param request(server.Request): current HTTP request |
1058 if request.uri != request.path: | 1058 if request.uri != request.path: |
1059 # we don't cache page with query arguments as there can be a lot of variants | 1059 # we don't cache page with query arguments as there can be a lot of variants |
1060 # influencing page results (e.g. search terms) | 1060 # influencing page results (e.g. search terms) |
1061 log.debug("ignoring cache due to query arguments") | 1061 log.debug("ignoring cache due to query arguments") |
1062 | 1062 |
1063 no_cache = not self.useCache(request) | 1063 no_cache = not self.use_cache(request) |
1064 | 1064 |
1065 profile = self.getProfile(request) or C.SERVICE_PROFILE | 1065 profile = self.get_profile(request) or C.SERVICE_PROFILE |
1066 | 1066 |
1067 if cache_type == C.CACHE_PUBSUB: | 1067 if cache_type == C.CACHE_PUBSUB: |
1068 service, node = kwargs["service"], kwargs["node"] | 1068 service, node = kwargs["service"], kwargs["node"] |
1069 if not node: | 1069 if not node: |
1070 try: | 1070 try: |
1076 'registered')) | 1076 'registered')) |
1077 return | 1077 return |
1078 if profile != C.SERVICE_PROFILE: | 1078 if profile != C.SERVICE_PROFILE: |
1079 # only service profile is cached for now | 1079 # only service profile is cached for now |
1080 return | 1080 return |
1081 session_data = self.host.getSessionData(request, session_iface.IWebSession) | 1081 session_data = self.host.get_session_data(request, session_iface.IWebSession) |
1082 locale = session_data.locale | 1082 locale = session_data.locale |
1083 if locale == C.DEFAULT_LOCALE: | 1083 if locale == C.DEFAULT_LOCALE: |
1084 # no need to duplicate cache here | 1084 # no need to duplicate cache here |
1085 locale = None | 1085 locale = None |
1086 try: | 1086 try: |
1087 cache = (self.cache[profile][cache_type][service][node] | 1087 cache = (self.cache[profile][cache_type][service][node] |
1088 [self.vhost_root][request.uri][locale][self]) | 1088 [self.vhost_root][request.uri][locale][self]) |
1089 except KeyError: | 1089 except KeyError: |
1090 # no cache yet, let's subscribe to the pubsub node | 1090 # no cache yet, let's subscribe to the pubsub node |
1091 d1 = self.host.bridgeCall( | 1091 d1 = self.host.bridge_call( |
1092 "psSubscribe", service.full(), node, "", profile | 1092 "ps_subscribe", service.full(), node, "", profile |
1093 ) | 1093 ) |
1094 d1.addCallback(self.checkCacheSubscribeCb, service, node) | 1094 d1.addCallback(self.check_cache_subscribe_cb, service, node) |
1095 d1.addErrback(self.checkCacheSubscribeEb, service, node) | 1095 d1.addErrback(self.check_cache_subscribe_eb, service, node) |
1096 d2 = self.host.bridgeCall("psNodeWatchAdd", service.full(), node, profile) | 1096 d2 = self.host.bridge_call("ps_node_watch_add", service.full(), node, profile) |
1097 d2.addErrback(self.psNodeWatchAddEb, service, node) | 1097 d2.addErrback(self.ps_node_watch_add_eb, service, node) |
1098 self._do_cache = [self, profile, cache_type, service, node, | 1098 self._do_cache = [self, profile, cache_type, service, node, |
1099 self.vhost_root, request.uri, locale] | 1099 self.vhost_root, request.uri, locale] |
1100 # we don't return the Deferreds as it is not needed to wait for | 1100 # we don't return the Deferreds as it is not needed to wait for |
1101 # the subscription to continue with page rendering | 1101 # the subscription to continue with page rendering |
1102 return | 1102 return |
1109 | 1109 |
1110 else: | 1110 else: |
1111 raise exceptions.InternalError("Unknown cache_type") | 1111 raise exceptions.InternalError("Unknown cache_type") |
1112 log.debug("using cache for {page}".format(page=self)) | 1112 log.debug("using cache for {page}".format(page=self)) |
1113 cache.last_access = time.time() | 1113 cache.last_access = time.time() |
1114 self._setCacheHeaders(request, cache) | 1114 self._set_cache_headers(request, cache) |
1115 self._checkCacheHeaders(request, cache) | 1115 self._check_cache_headers(request, cache) |
1116 request.write(cache.rendered) | 1116 request.write(cache.rendered) |
1117 request.finish() | 1117 request.finish() |
1118 raise failure.Failure(exceptions.CancelError("cache is used")) | 1118 raise failure.Failure(exceptions.CancelError("cache is used")) |
1119 | 1119 |
1120 def _cacheURL(self, request, profile): | 1120 def _cache_url(self, request, profile): |
1121 self.cached_urls.setdefault(profile, {})[request.uri] = CacheURL(request) | 1121 self.cached_urls.setdefault(profile, {})[request.uri] = CacheURL(request) |
1122 | 1122 |
1123 @classmethod | 1123 @classmethod |
1124 def onNodeEvent(cls, host, service, node, event_type, items, profile): | 1124 def on_node_event(cls, host, service, node, event_type, items, profile): |
1125 """Invalidate cache for all pages linked to this node""" | 1125 """Invalidate cache for all pages linked to this node""" |
1126 try: | 1126 try: |
1127 cache = cls.cache[profile][C.CACHE_PUBSUB][jid.JID(service)][node] | 1127 cache = cls.cache[profile][C.CACHE_PUBSUB][jid.JID(service)][node] |
1128 except KeyError: | 1128 except KeyError: |
1129 log.info(_( | 1129 log.info(_( |
1130 "Removing subscription for {service}/{node}: " | 1130 "Removing subscription for {service}/{node}: " |
1131 "the page is not cached").format(service=service, node=node)) | 1131 "the page is not cached").format(service=service, node=node)) |
1132 d1 = host.bridgeCall("psUnsubscribe", service, node, profile) | 1132 d1 = host.bridge_call("ps_unsubscribe", service, node, profile) |
1133 d1.addErrback( | 1133 d1.addErrback( |
1134 lambda failure_: log.warning( | 1134 lambda failure_: log.warning( |
1135 _("Can't unsubscribe from {service}/{node}: {msg}").format( | 1135 _("Can't unsubscribe from {service}/{node}: {msg}").format( |
1136 service=service, node=node, msg=failure_))) | 1136 service=service, node=node, msg=failure_))) |
1137 d2 = host.bridgeCall("psNodeWatchAdd", service, node, profile) | 1137 d2 = host.bridge_call("ps_node_watch_add", service, node, profile) |
1138 # TODO: check why the page is not in cache, remove subscription? | 1138 # TODO: check why the page is not in cache, remove subscription? |
1139 d2.addErrback( | 1139 d2.addErrback( |
1140 lambda failure_: log.warning( | 1140 lambda failure_: log.warning( |
1141 _("Can't remove watch for {service}/{node}: {msg}").format( | 1141 _("Can't remove watch for {service}/{node}: {msg}").format( |
1142 service=service, node=node, msg=failure_))) | 1142 service=service, node=node, msg=failure_))) |
1143 else: | 1143 else: |
1144 cache.clear() | 1144 cache.clear() |
1145 | 1145 |
1146 # identities | 1146 # identities |
1147 | 1147 |
1148 async def fillMissingIdentities( | 1148 async def fill_missing_identities( |
1149 self, | 1149 self, |
1150 request: server.Request, | 1150 request: server.Request, |
1151 entities: List[Union[str, jid.JID, None]], | 1151 entities: List[Union[str, jid.JID, None]], |
1152 ) -> None: | 1152 ) -> None: |
1153 """Check if all entities have an identity cache, get missing ones from backend | 1153 """Check if all entities have an identity cache, get missing ones from backend |
1154 | 1154 |
1155 @param request: request with a plugged profile | 1155 @param request: request with a plugged profile |
1156 @param entities: entities to check, None or empty strings will be filtered | 1156 @param entities: entities to check, None or empty strings will be filtered |
1157 """ | 1157 """ |
1158 entities = {str(e) for e in entities if e} | 1158 entities = {str(e) for e in entities if e} |
1159 profile = self.getProfile(request) or C.SERVICE_PROFILE | 1159 profile = self.get_profile(request) or C.SERVICE_PROFILE |
1160 identities = self.host.getSessionData( | 1160 identities = self.host.get_session_data( |
1161 request, | 1161 request, |
1162 session_iface.IWebSession | 1162 session_iface.IWebSession |
1163 ).identities | 1163 ).identities |
1164 for e in entities: | 1164 for e in entities: |
1165 if e not in identities: | 1165 if e not in identities: |
1166 id_raw = await self.host.bridgeCall( | 1166 id_raw = await self.host.bridge_call( |
1167 'identityGet', e, [], True, profile) | 1167 'identity_get', e, [], True, profile) |
1168 identities[e] = data_format.deserialise(id_raw) | 1168 identities[e] = data_format.deserialise(id_raw) |
1169 | 1169 |
1170 # signals, server => browser communication | 1170 # signals, server => browser communication |
1171 | 1171 |
1172 def delegateToResource(self, request, resource): | 1172 def delegate_to_resource(self, request, resource): |
1173 """continue workflow with Twisted Resource""" | 1173 """continue workflow with Twisted Resource""" |
1174 buf = resource.render(request) | 1174 buf = resource.render(request) |
1175 if buf == server.NOT_DONE_YET: | 1175 if buf == server.NOT_DONE_YET: |
1176 pass | 1176 pass |
1177 else: | 1177 else: |
1178 request.write(buf) | 1178 request.write(buf) |
1179 request.finish() | 1179 request.finish() |
1180 raise failure.Failure(exceptions.CancelError("resource delegation")) | 1180 raise failure.Failure(exceptions.CancelError("resource delegation")) |
1181 | 1181 |
1182 def HTTPRedirect(self, request, url): | 1182 def http_redirect(self, request, url): |
1183 """redirect to an URL using HTTP redirection | 1183 """redirect to an URL using HTTP redirection |
1184 | 1184 |
1185 @param request(server.Request): current HTTP request | 1185 @param request(server.Request): current HTTP request |
1186 @param url(unicode): url to redirect to | 1186 @param url(unicode): url to redirect to |
1187 """ | 1187 """ |
1188 web_util.redirectTo(url.encode("utf-8"), request) | 1188 web_util.redirectTo(url.encode("utf-8"), request) |
1189 request.finish() | 1189 request.finish() |
1190 raise failure.Failure(exceptions.CancelError("HTTP redirection is used")) | 1190 raise failure.Failure(exceptions.CancelError("HTTP redirection is used")) |
1191 | 1191 |
1192 def redirectOrContinue(self, request, redirect_arg="redirect_url"): | 1192 def redirect_or_continue(self, request, redirect_arg="redirect_url"): |
1193 """Helper method to redirect a page to an url given as arg | 1193 """Helper method to redirect a page to an url given as arg |
1194 | 1194 |
1195 if the arg is not present, the page will continue normal workflow | 1195 if the arg is not present, the page will continue normal workflow |
1196 @param request(server.Request): current HTTP request | 1196 @param request(server.Request): current HTTP request |
1197 @param redirect_arg(unicode): argument to use to get redirection URL | 1197 @param redirect_arg(unicode): argument to use to get redirection URL |
1198 @interrupt: redirect the page to requested URL | 1198 @interrupt: redirect the page to requested URL |
1199 @interrupt pageError(C.HTTP_BAD_REQUEST): empty or non local URL is used | 1199 @interrupt page_error(C.HTTP_BAD_REQUEST): empty or non local URL is used |
1200 """ | 1200 """ |
1201 redirect_arg = redirect_arg.encode('utf-8') | 1201 redirect_arg = redirect_arg.encode('utf-8') |
1202 try: | 1202 try: |
1203 url = request.args[redirect_arg][0].decode('utf-8') | 1203 url = request.args[redirect_arg][0].decode('utf-8') |
1204 except (KeyError, IndexError): | 1204 except (KeyError, IndexError): |
1205 pass | 1205 pass |
1206 else: | 1206 else: |
1207 # a redirection is requested | 1207 # a redirection is requested |
1208 if not url or url[0] != "/": | 1208 if not url or url[0] != "/": |
1209 # we only want local urls | 1209 # we only want local urls |
1210 self.pageError(request, C.HTTP_BAD_REQUEST) | 1210 self.page_error(request, C.HTTP_BAD_REQUEST) |
1211 else: | 1211 else: |
1212 self.HTTPRedirect(request, url) | 1212 self.http_redirect(request, url) |
1213 | 1213 |
1214 def pageRedirect(self, page_path, request, skip_parse_url=True, path_args=None): | 1214 def page_redirect(self, page_path, request, skip_parse_url=True, path_args=None): |
1215 """redirect a page to a named page | 1215 """redirect a page to a named page |
1216 | 1216 |
1217 the workflow will continue with the workflow of the named page, | 1217 the workflow will continue with the workflow of the named page, |
1218 skipping named page's parse_url method if it exist. | 1218 skipping named page's parse_url method if it exist. |
1219 If you want to do a HTTP redirection, use HTTPRedirect | 1219 If you want to do a HTTP redirection, use http_redirect |
1220 @param page_path(unicode): path to page (elements are separated by "/"): | 1220 @param page_path(unicode): path to page (elements are separated by "/"): |
1221 if path starts with a "/": | 1221 if path starts with a "/": |
1222 path is a full path starting from root | 1222 path is a full path starting from root |
1223 else: | 1223 else: |
1224 - first element is name as registered in name variable | 1224 - first element is name as registered in name variable |
1254 # if cache is needed, it will be handled by final page | 1254 # if cache is needed, it will be handled by final page |
1255 redirect_page._do_cache = self._do_cache | 1255 redirect_page._do_cache = self._do_cache |
1256 self._do_cache = None | 1256 self._do_cache = None |
1257 | 1257 |
1258 defer.ensureDeferred( | 1258 defer.ensureDeferred( |
1259 redirect_page.renderPage(request, skip_parse_url=skip_parse_url) | 1259 redirect_page.render_page(request, skip_parse_url=skip_parse_url) |
1260 ) | 1260 ) |
1261 raise failure.Failure(exceptions.CancelError("page redirection is used")) | 1261 raise failure.Failure(exceptions.CancelError("page redirection is used")) |
1262 | 1262 |
1263 def pageError(self, request, code=C.HTTP_NOT_FOUND, no_body=False): | 1263 def page_error(self, request, code=C.HTTP_NOT_FOUND, no_body=False): |
1264 """generate an error page and terminate the request | 1264 """generate an error page and terminate the request |
1265 | 1265 |
1266 @param request(server.Request): HTTP request | 1266 @param request(server.Request): HTTP request |
1267 @param core(int): error code to use | 1267 @param core(int): error code to use |
1268 @param no_body: don't write body if True | 1268 @param no_body: don't write body if True |
1274 if no_body: | 1274 if no_body: |
1275 request.finish() | 1275 request.finish() |
1276 else: | 1276 else: |
1277 template = "error/" + str(code) + ".html" | 1277 template = "error/" + str(code) + ".html" |
1278 template_data = request.template_data | 1278 template_data = request.template_data |
1279 session_data = self.host.getSessionData(request, session_iface.IWebSession) | 1279 session_data = self.host.get_session_data(request, session_iface.IWebSession) |
1280 if session_data.locale is not None: | 1280 if session_data.locale is not None: |
1281 template_data['locale'] = session_data.locale | 1281 template_data['locale'] = session_data.locale |
1282 if self.vhost_root.site_name: | 1282 if self.vhost_root.site_name: |
1283 template_data['site'] = self.vhost_root.site_name | 1283 template_data['site'] = self.vhost_root.site_name |
1284 | 1284 |
1290 site_themes=self.site_themes, | 1290 site_themes=self.site_themes, |
1291 error_code=code, | 1291 error_code=code, |
1292 **template_data | 1292 **template_data |
1293 ) | 1293 ) |
1294 | 1294 |
1295 self.writeData(rendered, request) | 1295 self.write_data(rendered, request) |
1296 raise failure.Failure(exceptions.CancelError("error page is used")) | 1296 raise failure.Failure(exceptions.CancelError("error page is used")) |
1297 | 1297 |
1298 def writeData(self, data, request): | 1298 def write_data(self, data, request): |
1299 """write data to transport and finish the request""" | 1299 """write data to transport and finish the request""" |
1300 if data is None: | 1300 if data is None: |
1301 self.pageError(request) | 1301 self.page_error(request) |
1302 data_encoded = data.encode("utf-8") | 1302 data_encoded = data.encode("utf-8") |
1303 | 1303 |
1304 if self._do_cache is not None: | 1304 if self._do_cache is not None: |
1305 redirected_page = self._do_cache.pop(0) | 1305 redirected_page = self._do_cache.pop(0) |
1306 cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache) | 1306 cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache) |
1307 page_cache = cache[redirected_page] = CachePage(data_encoded) | 1307 page_cache = cache[redirected_page] = CachePage(data_encoded) |
1308 self._setCacheHeaders(request, page_cache) | 1308 self._set_cache_headers(request, page_cache) |
1309 log.debug(_("{page} put in cache for [{profile}]") | 1309 log.debug(_("{page} put in cache for [{profile}]") |
1310 .format( page=self, profile=self._do_cache[0])) | 1310 .format( page=self, profile=self._do_cache[0])) |
1311 self._do_cache = None | 1311 self._do_cache = None |
1312 self._checkCacheHeaders(request, page_cache) | 1312 self._check_cache_headers(request, page_cache) |
1313 | 1313 |
1314 try: | 1314 try: |
1315 request.write(data_encoded) | 1315 request.write(data_encoded) |
1316 except AttributeError: | 1316 except AttributeError: |
1317 log.warning(_("Can't write page, the request has probably been cancelled " | 1317 log.warning(_("Can't write page, the request has probably been cancelled " |
1318 "(browser tab closed or reloaded)")) | 1318 "(browser tab closed or reloaded)")) |
1319 return | 1319 return |
1320 request.finish() | 1320 request.finish() |
1321 | 1321 |
1322 def _subpagesHandler(self, request): | 1322 def _subpages_handler(self, request): |
1323 """render subpage if suitable | 1323 """render subpage if suitable |
1324 | 1324 |
1325 this method checks if there is still an unmanaged part of the path | 1325 this method checks if there is still an unmanaged part of the path |
1326 and check if it corresponds to a subpage. If so, it render the subpage | 1326 and check if it corresponds to a subpage. If so, it render the subpage |
1327 else it render a NoResource. | 1327 else it render a NoResource. |
1328 If there is no unmanaged part of the segment, current page workflow is pursued | 1328 If there is no unmanaged part of the segment, current page workflow is pursued |
1329 """ | 1329 """ |
1330 if request.postpath: | 1330 if request.postpath: |
1331 subpage = self.nextPath(request).encode('utf-8') | 1331 subpage = self.next_path(request).encode('utf-8') |
1332 try: | 1332 try: |
1333 child = self.children[subpage] | 1333 child = self.children[subpage] |
1334 except KeyError: | 1334 except KeyError: |
1335 self.pageError(request) | 1335 self.page_error(request) |
1336 else: | 1336 else: |
1337 child.render(request) | 1337 child.render(request) |
1338 raise failure.Failure(exceptions.CancelError("subpage page is used")) | 1338 raise failure.Failure(exceptions.CancelError("subpage page is used")) |
1339 | 1339 |
1340 def _prepare_dynamic(self, request): | 1340 def _prepare_dynamic(self, request): |
1341 session_data = self.host.getSessionData(request, session_iface.IWebSession) | 1341 session_data = self.host.get_session_data(request, session_iface.IWebSession) |
1342 # we need to activate dynamic page | 1342 # we need to activate dynamic page |
1343 # we set data for template, and create/register token | 1343 # we set data for template, and create/register token |
1344 # socket_token = str(uuid.uuid4()) | 1344 # socket_token = str(uuid.uuid4()) |
1345 socket_url = self.host.get_websocket_url(request) | 1345 socket_url = self.host.get_websocket_url(request) |
1346 # as for CSRF, it is important to not let the socket token if we use the service | 1346 # as for CSRF, it is important to not let the socket token if we use the service |
1347 # profile, as those pages can be cached, and then the token leaked. | 1347 # profile, as those pages can be cached, and then the token leaked. |
1348 socket_token = '' if session_data.profile is None else session_data.ws_token | 1348 socket_token = '' if session_data.profile is None else session_data.ws_token |
1349 socket_debug = C.boolConst(self.host.debug) | 1349 socket_debug = C.bool_const(self.host.debug) |
1350 request.template_data["websocket"] = WebsocketMeta( | 1350 request.template_data["websocket"] = WebsocketMeta( |
1351 socket_url, socket_token, socket_debug | 1351 socket_url, socket_token, socket_debug |
1352 ) | 1352 ) |
1353 # we will keep track of handlers to remove | 1353 # we will keep track of handlers to remove |
1354 request._signals_registered = [] | 1354 request._signals_registered = [] |
1357 | 1357 |
1358 def _render_template(self, request): | 1358 def _render_template(self, request): |
1359 template_data = request.template_data | 1359 template_data = request.template_data |
1360 | 1360 |
1361 # if confirm variable is set in case of successfuly data post | 1361 # if confirm variable is set in case of successfuly data post |
1362 session_data = self.host.getSessionData(request, session_iface.IWebSession) | 1362 session_data = self.host.get_session_data(request, session_iface.IWebSession) |
1363 template_data['identities'] = session_data.identities | 1363 template_data['identities'] = session_data.identities |
1364 if session_data.popPageFlag(self, C.FLAG_CONFIRM): | 1364 if session_data.pop_page_flag(self, C.FLAG_CONFIRM): |
1365 template_data["confirm"] = True | 1365 template_data["confirm"] = True |
1366 notifs = session_data.popPageNotifications(self) | 1366 notifs = session_data.pop_page_notifications(self) |
1367 if notifs: | 1367 if notifs: |
1368 template_data["notifications"] = notifs | 1368 template_data["notifications"] = notifs |
1369 if session_data.jid is not None: | 1369 if session_data.jid is not None: |
1370 template_data["own_jid"] = session_data.jid | 1370 template_data["own_jid"] = session_data.jid |
1371 if session_data.locale is not None: | 1371 if session_data.locale is not None: |
1391 for key, value in data_common["template"].items(): | 1391 for key, value in data_common["template"].items(): |
1392 if key not in template_data: | 1392 if key not in template_data: |
1393 template_data[key] = value | 1393 template_data[key] = value |
1394 | 1394 |
1395 theme = session_data.theme or self.default_theme | 1395 theme = session_data.theme or self.default_theme |
1396 self.exposeToScripts( | 1396 self.expose_to_scripts( |
1397 request, | 1397 request, |
1398 cache_path=session_data.cache_dir, | 1398 cache_path=session_data.cache_dir, |
1399 templates_root_url=str(self.vhost_root.getFrontURL(theme)), | 1399 templates_root_url=str(self.vhost_root.get_front_url(theme)), |
1400 profile=session_data.profile) | 1400 profile=session_data.profile) |
1401 | 1401 |
1402 uri = request.uri.decode() | 1402 uri = request.uri.decode() |
1403 try: | 1403 try: |
1404 template_data["current_page"] = next( | 1404 template_data["current_page"] = next( |
1409 | 1409 |
1410 return self.host.renderer.render( | 1410 return self.host.renderer.render( |
1411 self.template, | 1411 self.template, |
1412 theme=theme, | 1412 theme=theme, |
1413 site_themes=self.site_themes, | 1413 site_themes=self.site_themes, |
1414 page_url=self.getURL(), | 1414 page_url=self.get_url(), |
1415 media_path=f"/{C.MEDIA_DIR}", | 1415 media_path=f"/{C.MEDIA_DIR}", |
1416 build_path=f"/{C.BUILD_DIR}/", | 1416 build_path=f"/{C.BUILD_DIR}/", |
1417 cache_path=session_data.cache_dir, | 1417 cache_path=session_data.cache_dir, |
1418 main_menu=self.main_menu, | 1418 main_menu=self.main_menu, |
1419 **template_data) | 1419 **template_data) |
1439 else: | 1439 else: |
1440 ret = tuple(ret) | 1440 ret = tuple(ret) |
1441 raise NotImplementedError( | 1441 raise NotImplementedError( |
1442 _("iterable in on_data_post return value is not used yet") | 1442 _("iterable in on_data_post return value is not used yet") |
1443 ) | 1443 ) |
1444 session_data = self.host.getSessionData(request, session_iface.IWebSession) | 1444 session_data = self.host.get_session_data(request, session_iface.IWebSession) |
1445 request_data = self.getRData(request) | 1445 request_data = self.get_r_data(request) |
1446 if "post_redirect_page" in request_data: | 1446 if "post_redirect_page" in request_data: |
1447 redirect_page_data = request_data["post_redirect_page"] | 1447 redirect_page_data = request_data["post_redirect_page"] |
1448 if isinstance(redirect_page_data, tuple): | 1448 if isinstance(redirect_page_data, tuple): |
1449 redirect_page = redirect_page_data[0] | 1449 redirect_page = redirect_page_data[0] |
1450 redirect_page_args = redirect_page_data[1:] | 1450 redirect_page_args = redirect_page_data[1:] |
1451 redirect_uri = redirect_page.getURL(*redirect_page_args) | 1451 redirect_uri = redirect_page.get_url(*redirect_page_args) |
1452 else: | 1452 else: |
1453 redirect_page = redirect_page_data | 1453 redirect_page = redirect_page_data |
1454 redirect_uri = redirect_page.url | 1454 redirect_uri = redirect_page.url |
1455 else: | 1455 else: |
1456 redirect_page = self | 1456 redirect_page = self |
1457 redirect_uri = request.uri | 1457 redirect_uri = request.uri |
1458 | 1458 |
1459 if not C.POST_NO_CONFIRM in ret: | 1459 if not C.POST_NO_CONFIRM in ret: |
1460 session_data.setPageFlag(redirect_page, C.FLAG_CONFIRM) | 1460 session_data.set_page_flag(redirect_page, C.FLAG_CONFIRM) |
1461 request.setResponseCode(C.HTTP_SEE_OTHER) | 1461 request.setResponseCode(C.HTTP_SEE_OTHER) |
1462 request.setHeader(b"location", redirect_uri) | 1462 request.setHeader(b"location", redirect_uri) |
1463 request.finish() | 1463 request.finish() |
1464 raise failure.Failure(exceptions.CancelError("Post/Redirect/Get is used")) | 1464 raise failure.Failure(exceptions.CancelError("Post/Redirect/Get is used")) |
1465 | 1465 |
1466 async def _on_data_post(self, request): | 1466 async def _on_data_post(self, request): |
1467 self.checkCSRF(request) | 1467 self.check_csrf(request) |
1468 try: | 1468 try: |
1469 ret = await asDeferred(self.on_data_post, self, request) | 1469 ret = await as_deferred(self.on_data_post, self, request) |
1470 except exceptions.DataError as e: | 1470 except exceptions.DataError as e: |
1471 # something is wrong with the posted data, we re-display the page with a | 1471 # something is wrong with the posted data, we re-display the page with a |
1472 # warning notification | 1472 # warning notification |
1473 session_data = self.host.getSessionData(request, session_iface.IWebSession) | 1473 session_data = self.host.get_session_data(request, session_iface.IWebSession) |
1474 session_data.setPageNotification(self, str(e), C.LVL_WARNING) | 1474 session_data.set_page_notification(self, str(e), C.LVL_WARNING) |
1475 request.setResponseCode(C.HTTP_SEE_OTHER) | 1475 request.setResponseCode(C.HTTP_SEE_OTHER) |
1476 request.setHeader("location", request.uri) | 1476 request.setHeader("location", request.uri) |
1477 request.finish() | 1477 request.finish() |
1478 raise failure.Failure(exceptions.CancelError("Post/Redirect/Get is used")) | 1478 raise failure.Failure(exceptions.CancelError("Post/Redirect/Get is used")) |
1479 else: | 1479 else: |
1480 if ret != "continue": | 1480 if ret != "continue": |
1481 self._on_data_post_redirect(ret, request) | 1481 self._on_data_post_redirect(ret, request) |
1482 | 1482 |
1483 def getPostedData( | 1483 def get_posted_data( |
1484 self, | 1484 self, |
1485 request: server.Request, | 1485 request: server.Request, |
1486 keys, | 1486 keys, |
1487 multiple: bool = False, | 1487 multiple: bool = False, |
1488 raise_on_missing: bool = True, | 1488 raise_on_missing: bool = True, |
1530 if len(keys) == 1: | 1530 if len(keys) == 1: |
1531 return ret[0] | 1531 return ret[0] |
1532 else: | 1532 else: |
1533 return ret | 1533 return ret |
1534 | 1534 |
1535 def getAllPostedData(self, request, except_=(), multiple=True): | 1535 def get_all_posted_data(self, request, except_=(), multiple=True): |
1536 """get all posted data | 1536 """get all posted data |
1537 | 1537 |
1538 @param request(server.Request): request linked to the session | 1538 @param request(server.Request): request linked to the session |
1539 @param except_(iterable[unicode]): key of values to ignore | 1539 @param except_(iterable[unicode]): key of values to ignore |
1540 csrf_token will always be ignored | 1540 csrf_token will always be ignored |
1553 ret[key] = urllib.parse.unquote(values[0]) | 1553 ret[key] = urllib.parse.unquote(values[0]) |
1554 else: | 1554 else: |
1555 ret[key] = [urllib.parse.unquote(v) for v in values] | 1555 ret[key] = [urllib.parse.unquote(v) for v in values] |
1556 return ret | 1556 return ret |
1557 | 1557 |
1558 def getProfile(self, request): | 1558 def get_profile(self, request): |
1559 """Helper method to easily get current profile | 1559 """Helper method to easily get current profile |
1560 | 1560 |
1561 @return (unicode, None): current profile | 1561 @return (unicode, None): current profile |
1562 None if no profile session is started | 1562 None if no profile session is started |
1563 """ | 1563 """ |
1564 web_session = self.host.getSessionData(request, session_iface.IWebSession) | 1564 web_session = self.host.get_session_data(request, session_iface.IWebSession) |
1565 return web_session.profile | 1565 return web_session.profile |
1566 | 1566 |
1567 def getJid(self, request): | 1567 def get_jid(self, request): |
1568 """Helper method to easily get current jid | 1568 """Helper method to easily get current jid |
1569 | 1569 |
1570 @return: current jid | 1570 @return: current jid |
1571 """ | 1571 """ |
1572 web_session = self.host.getSessionData(request, session_iface.IWebSession) | 1572 web_session = self.host.get_session_data(request, session_iface.IWebSession) |
1573 return web_session.jid | 1573 return web_session.jid |
1574 | 1574 |
1575 | 1575 |
1576 def getRData(self, request): | 1576 def get_r_data(self, request): |
1577 """Helper method to get request data dict | 1577 """Helper method to get request data dict |
1578 | 1578 |
1579 this dictionnary if for the request only, it is not saved in session | 1579 this dictionnary if for the request only, it is not saved in session |
1580 It is mainly used to pass data between pages/methods called during request | 1580 It is mainly used to pass data between pages/methods called during request |
1581 workflow | 1581 workflow |
1585 return request.data | 1585 return request.data |
1586 except AttributeError: | 1586 except AttributeError: |
1587 request.data = {} | 1587 request.data = {} |
1588 return request.data | 1588 return request.data |
1589 | 1589 |
1590 def getPageData(self, request, key): | 1590 def get_page_data(self, request, key): |
1591 """Helper method to retrieve reload resistant data""" | 1591 """Helper method to retrieve reload resistant data""" |
1592 web_session = self.host.getSessionData(request, session_iface.IWebSession) | 1592 web_session = self.host.get_session_data(request, session_iface.IWebSession) |
1593 return web_session.getPageData(self, key) | 1593 return web_session.get_page_data(self, key) |
1594 | 1594 |
1595 def setPageData(self, request, key, value): | 1595 def set_page_data(self, request, key, value): |
1596 """Helper method to set reload resistant data""" | 1596 """Helper method to set reload resistant data""" |
1597 web_session = self.host.getSessionData(request, session_iface.IWebSession) | 1597 web_session = self.host.get_session_data(request, session_iface.IWebSession) |
1598 return web_session.setPageData(self, key, value) | 1598 return web_session.set_page_data(self, key, value) |
1599 | 1599 |
1600 def handleSearch(self, request, extra): | 1600 def handle_search(self, request, extra): |
1601 """Manage Full-Text Search | 1601 """Manage Full-Text Search |
1602 | 1602 |
1603 Check if "search" query argument is present, and add MAM filter for it if | 1603 Check if "search" query argument is present, and add MAM filter for it if |
1604 necessary. | 1604 necessary. |
1605 If used, the "search" variable will also be available in template data, thus | 1605 If used, the "search" variable will also be available in template data, thus |
1606 frontend can display some information about it. | 1606 frontend can display some information about it. |
1607 """ | 1607 """ |
1608 search = self.getPostedData(request, 'search', raise_on_missing=False) | 1608 search = self.get_posted_data(request, 'search', raise_on_missing=False) |
1609 if search is not None: | 1609 if search is not None: |
1610 search = search.strip() | 1610 search = search.strip() |
1611 if search: | 1611 if search: |
1612 try: | 1612 try: |
1613 extra[f'mam_filter_{self.host.ns_map["fulltextmam"]}'] = search | 1613 extra[f'mam_filter_{self.host.ns_map["fulltextmam"]}'] = search |
1614 except KeyError: | 1614 except KeyError: |
1615 log.warning(_("Full-text search is not available")) | 1615 log.warning(_("Full-text search is not available")) |
1616 else: | 1616 else: |
1617 request.template_data['search'] = search | 1617 request.template_data['search'] = search |
1618 | 1618 |
1619 def _checkAccess(self, request): | 1619 def _check_access(self, request): |
1620 """Check access according to self.access | 1620 """Check access according to self.access |
1621 | 1621 |
1622 if access is not granted, show a HTTP_FORBIDDEN pageError and stop request, | 1622 if access is not granted, show a HTTP_FORBIDDEN page_error and stop request, |
1623 else return data (so it can be inserted in deferred chain | 1623 else return data (so it can be inserted in deferred chain |
1624 """ | 1624 """ |
1625 if self.access == C.PAGES_ACCESS_PUBLIC: | 1625 if self.access == C.PAGES_ACCESS_PUBLIC: |
1626 pass | 1626 pass |
1627 elif self.access == C.PAGES_ACCESS_PROFILE: | 1627 elif self.access == C.PAGES_ACCESS_PROFILE: |
1628 profile = self.getProfile(request) | 1628 profile = self.get_profile(request) |
1629 if not profile: | 1629 if not profile: |
1630 # registration allowed, we redirect to login page | 1630 # registration allowed, we redirect to login page |
1631 login_url = self.getPageRedirectURL(request) | 1631 login_url = self.get_page_redirect_url(request) |
1632 self.HTTPRedirect(request, login_url) | 1632 self.http_redirect(request, login_url) |
1633 | 1633 |
1634 def setBestLocale(self, request): | 1634 def set_best_locale(self, request): |
1635 """Guess the best locale when it is not specified explicitly by user | 1635 """Guess the best locale when it is not specified explicitly by user |
1636 | 1636 |
1637 This method will check "accept-language" header, and set locale to first | 1637 This method will check "accept-language" header, and set locale to first |
1638 matching value with available translations. | 1638 matching value with available translations. |
1639 """ | 1639 """ |
1646 lang = lang.split(';')[0].strip().lower() | 1646 lang = lang.split(';')[0].strip().lower() |
1647 if not lang: | 1647 if not lang: |
1648 continue | 1648 continue |
1649 for a in available: | 1649 for a in available: |
1650 if a.lower().startswith(lang): | 1650 if a.lower().startswith(lang): |
1651 session_data = self.host.getSessionData(request, | 1651 session_data = self.host.get_session_data(request, |
1652 session_iface.IWebSession) | 1652 session_iface.IWebSession) |
1653 session_data.locale = a | 1653 session_data.locale = a |
1654 return | 1654 return |
1655 | 1655 |
1656 async def renderPage(self, request, skip_parse_url=False): | 1656 async def render_page(self, request, skip_parse_url=False): |
1657 """Main method to handle the workflow of a LiberviaPage""" | 1657 """Main method to handle the workflow of a LiberviaPage""" |
1658 # template_data are the variables passed to template | 1658 # template_data are the variables passed to template |
1659 if not hasattr(request, "template_data"): | 1659 if not hasattr(request, "template_data"): |
1660 # if template_data doesn't exist, it's the beginning of the request workflow | 1660 # if template_data doesn't exist, it's the beginning of the request workflow |
1661 # so we fill essential data | 1661 # so we fill essential data |
1662 session_data = self.host.getSessionData(request, session_iface.IWebSession) | 1662 session_data = self.host.get_session_data(request, session_iface.IWebSession) |
1663 profile = session_data.profile | 1663 profile = session_data.profile |
1664 request.template_data = { | 1664 request.template_data = { |
1665 "profile": profile, | 1665 "profile": profile, |
1666 # it's important to not add CSRF token and session uuid if service profile | 1666 # it's important to not add CSRF token and session uuid if service profile |
1667 # is used because the page may be cached, and the token then leaked | 1667 # is used because the page may be cached, and the token then leaked |
1692 locale = None | 1692 locale = None |
1693 session_data.locale = locale | 1693 session_data.locale = locale |
1694 | 1694 |
1695 # if locale is not specified, we try to find one requested by browser | 1695 # if locale is not specified, we try to find one requested by browser |
1696 if session_data.locale is None: | 1696 if session_data.locale is None: |
1697 self.setBestLocale(request) | 1697 self.set_best_locale(request) |
1698 | 1698 |
1699 # theme | 1699 # theme |
1700 key_theme = C.KEY_THEME.encode() | 1700 key_theme = C.KEY_THEME.encode() |
1701 if key_theme in request.args: | 1701 if key_theme in request.args: |
1702 theme = request.args.pop(key_theme)[0].decode() | 1702 theme = request.args.pop(key_theme)[0].decode() |
1708 else: | 1708 else: |
1709 session_data.theme = theme | 1709 session_data.theme = theme |
1710 try: | 1710 try: |
1711 | 1711 |
1712 try: | 1712 try: |
1713 self._checkAccess(request) | 1713 self._check_access(request) |
1714 | 1714 |
1715 if self.redirect is not None: | 1715 if self.redirect is not None: |
1716 self.pageRedirect(self.redirect, request, skip_parse_url=False) | 1716 self.page_redirect(self.redirect, request, skip_parse_url=False) |
1717 | 1717 |
1718 if self.parse_url is not None and not skip_parse_url: | 1718 if self.parse_url is not None and not skip_parse_url: |
1719 if self.url_cache: | 1719 if self.url_cache: |
1720 profile = self.getProfile(request) | 1720 profile = self.get_profile(request) |
1721 try: | 1721 try: |
1722 cache_url = self.cached_urls[profile][request.uri] | 1722 cache_url = self.cached_urls[profile][request.uri] |
1723 except KeyError: | 1723 except KeyError: |
1724 # no cache for this URI yet | 1724 # no cache for this URI yet |
1725 # we do normal URL parsing, and then the cache | 1725 # we do normal URL parsing, and then the cache |
1726 await asDeferred(self.parse_url, self, request) | 1726 await as_deferred(self.parse_url, self, request) |
1727 self._cacheURL(request, profile) | 1727 self._cache_url(request, profile) |
1728 else: | 1728 else: |
1729 log.debug(f"using URI cache for {self}") | 1729 log.debug(f"using URI cache for {self}") |
1730 cache_url.use(request) | 1730 cache_url.use(request) |
1731 else: | 1731 else: |
1732 await asDeferred(self.parse_url, self, request) | 1732 await as_deferred(self.parse_url, self, request) |
1733 | 1733 |
1734 if self.add_breadcrumb is None: | 1734 if self.add_breadcrumb is None: |
1735 label = ( | 1735 label = ( |
1736 self.label | 1736 self.label |
1737 or self.name | 1737 or self.name |
1741 "url": self.url, | 1741 "url": self.url, |
1742 "label": label.title(), | 1742 "label": label.title(), |
1743 } | 1743 } |
1744 request.template_data["breadcrumbs"].append(breadcrumb) | 1744 request.template_data["breadcrumbs"].append(breadcrumb) |
1745 else: | 1745 else: |
1746 await asDeferred( | 1746 await as_deferred( |
1747 self.add_breadcrumb, | 1747 self.add_breadcrumb, |
1748 self, | 1748 self, |
1749 request, | 1749 request, |
1750 request.template_data["breadcrumbs"] | 1750 request.template_data["breadcrumbs"] |
1751 ) | 1751 ) |
1752 | 1752 |
1753 self._subpagesHandler(request) | 1753 self._subpages_handler(request) |
1754 | 1754 |
1755 if request.method not in (C.HTTP_METHOD_GET, C.HTTP_METHOD_POST): | 1755 if request.method not in (C.HTTP_METHOD_GET, C.HTTP_METHOD_POST): |
1756 # only HTTP GET and POST are handled so far | 1756 # only HTTP GET and POST are handled so far |
1757 self.pageError(request, C.HTTP_BAD_REQUEST) | 1757 self.page_error(request, C.HTTP_BAD_REQUEST) |
1758 | 1758 |
1759 if request.method == C.HTTP_METHOD_POST: | 1759 if request.method == C.HTTP_METHOD_POST: |
1760 if self.on_data_post == 'continue': | 1760 if self.on_data_post == 'continue': |
1761 pass | 1761 pass |
1762 elif self.on_data_post is None: | 1762 elif self.on_data_post is None: |
1763 # if we don't have on_data_post, the page was not expecting POST | 1763 # if we don't have on_data_post, the page was not expecting POST |
1764 # so we return an error | 1764 # so we return an error |
1765 self.pageError(request, C.HTTP_BAD_REQUEST) | 1765 self.page_error(request, C.HTTP_BAD_REQUEST) |
1766 else: | 1766 else: |
1767 await self._on_data_post(request) | 1767 await self._on_data_post(request) |
1768 # by default, POST follow normal behaviour after on_data_post is called | 1768 # by default, POST follow normal behaviour after on_data_post is called |
1769 # this can be changed by a redirection or other method call in on_data_post | 1769 # this can be changed by a redirection or other method call in on_data_post |
1770 | 1770 |
1771 if self.dynamic: | 1771 if self.dynamic: |
1772 self._prepare_dynamic(request) | 1772 self._prepare_dynamic(request) |
1773 | 1773 |
1774 if self.prepare_render: | 1774 if self.prepare_render: |
1775 await asDeferred(self.prepare_render, self, request) | 1775 await as_deferred(self.prepare_render, self, request) |
1776 | 1776 |
1777 if self.template: | 1777 if self.template: |
1778 rendered = self._render_template(request) | 1778 rendered = self._render_template(request) |
1779 elif self.render_method: | 1779 elif self.render_method: |
1780 rendered = await asDeferred(self.render_method, self, request) | 1780 rendered = await as_deferred(self.render_method, self, request) |
1781 else: | 1781 else: |
1782 raise exceptions.InternalError( | 1782 raise exceptions.InternalError( |
1783 "No method set to render page, please set a template or use a " | 1783 "No method set to render page, please set a template or use a " |
1784 "render method" | 1784 "render method" |
1785 ) | 1785 ) |
1786 | 1786 |
1787 self.writeData(rendered, request) | 1787 self.write_data(rendered, request) |
1788 | 1788 |
1789 except failure.Failure as f: | 1789 except failure.Failure as f: |
1790 # we have to unpack the Failure to catch the right Exception | 1790 # we have to unpack the Failure to catch the right Exception |
1791 raise f.value | 1791 raise f.value |
1792 | 1792 |
1793 except exceptions.CancelError: | 1793 except exceptions.CancelError: |
1794 pass | 1794 pass |
1795 except BridgeException as e: | 1795 except BridgeException as e: |
1796 if e.condition == 'not-allowed': | 1796 if e.condition == 'not-allowed': |
1797 log.warning("not allowed exception catched") | 1797 log.warning("not allowed exception catched") |
1798 self.pageError(request, C.HTTP_FORBIDDEN) | 1798 self.page_error(request, C.HTTP_FORBIDDEN) |
1799 elif e.condition == 'item-not-found' or e.classname == 'NotFound': | 1799 elif e.condition == 'item-not-found' or e.classname == 'NotFound': |
1800 self.pageError(request, C.HTTP_NOT_FOUND) | 1800 self.page_error(request, C.HTTP_NOT_FOUND) |
1801 elif e.condition == 'remote-server-not-found': | 1801 elif e.condition == 'remote-server-not-found': |
1802 self.pageError(request, C.HTTP_NOT_FOUND) | 1802 self.page_error(request, C.HTTP_NOT_FOUND) |
1803 elif e.condition == 'forbidden': | 1803 elif e.condition == 'forbidden': |
1804 if self.getProfile(request) is None: | 1804 if self.get_profile(request) is None: |
1805 log.debug("access forbidden, we're redirecting to log-in page") | 1805 log.debug("access forbidden, we're redirecting to log-in page") |
1806 self.HTTPRedirect(request, self.getPageRedirectURL(request)) | 1806 self.http_redirect(request, self.get_page_redirect_url(request)) |
1807 else: | 1807 else: |
1808 self.pageError(request, C.HTTP_FORBIDDEN) | 1808 self.page_error(request, C.HTTP_FORBIDDEN) |
1809 else: | 1809 else: |
1810 log.error( | 1810 log.error( |
1811 _("Uncatched bridge exception for HTTP request on {url}: {e}\n" | 1811 _("Uncatched bridge exception for HTTP request on {url}: {e}\n" |
1812 "page name: {name}\npath: {path}\nURL: {full_url}\n{tb}") | 1812 "page name: {name}\npath: {path}\nURL: {full_url}\n{tb}") |
1813 .format( | 1813 .format( |
1818 full_url=request.URLPath(), | 1818 full_url=request.URLPath(), |
1819 tb=traceback.format_exc(), | 1819 tb=traceback.format_exc(), |
1820 ) | 1820 ) |
1821 ) | 1821 ) |
1822 try: | 1822 try: |
1823 self.pageError(request, C.HTTP_INTERNAL_ERROR) | 1823 self.page_error(request, C.HTTP_INTERNAL_ERROR) |
1824 except exceptions.CancelError: | 1824 except exceptions.CancelError: |
1825 pass | 1825 pass |
1826 except Exception as e: | 1826 except Exception as e: |
1827 log.error( | 1827 log.error( |
1828 _("Uncatched error for HTTP request on {url}: {e}\npage name: " | 1828 _("Uncatched error for HTTP request on {url}: {e}\npage name: " |
1835 full_url=request.URLPath(), | 1835 full_url=request.URLPath(), |
1836 tb=traceback.format_exc(), | 1836 tb=traceback.format_exc(), |
1837 ) | 1837 ) |
1838 ) | 1838 ) |
1839 try: | 1839 try: |
1840 self.pageError(request, C.HTTP_INTERNAL_ERROR) | 1840 self.page_error(request, C.HTTP_INTERNAL_ERROR) |
1841 except exceptions.CancelError: | 1841 except exceptions.CancelError: |
1842 pass | 1842 pass |
1843 | 1843 |
1844 def render_GET(self, request): | 1844 def render_GET(self, request): |
1845 defer.ensureDeferred(self.renderPage(request)) | 1845 defer.ensureDeferred(self.render_page(request)) |
1846 return server.NOT_DONE_YET | 1846 return server.NOT_DONE_YET |
1847 | 1847 |
1848 def render_POST(self, request): | 1848 def render_POST(self, request): |
1849 defer.ensureDeferred(self.renderPage(request)) | 1849 defer.ensureDeferred(self.render_page(request)) |
1850 return server.NOT_DONE_YET | 1850 return server.NOT_DONE_YET |