diff src/server/server.py @ 980:bcacf970f970

core (pages redirection): inverted redirection + getSubPageURL: - page redirection now also work in other redirection meaning that if "/redirecting_url" => "/page_name/arg_1/arg_2" is used, a getURL(page_name, arg_1, arg_2) will return the redirecting_url. query_args are not handled yet by getURL. - page redirection dict now use path_args and query_args insteand of args and kwargs, which show the relation with URL. - new getSubPageURL return absolute URL to a subpage, which is more solid than relative URL and allows to use it in sub-hierarchy.
author Goffi <goffi@goffi.org>
date Tue, 14 Nov 2017 08:35:17 +0100
parents 1d558dfb32ca
children f0fc28b3bd1e
line wrap: on
line diff
--- a/src/server/server.py	Sun Nov 12 12:56:46 2017 +0100
+++ b/src/server/server.py	Tue Nov 14 08:35:17 2017 +0100
@@ -134,12 +134,28 @@
                 elif 'page' in new_data:
                     new = new_data
                     new['type'] = 'page'
-                    if 'args' in new:
-                        # we need lists in args because it will be used
-                        # as it in request.args
-                        for k,v in new['args'].iteritems():
-                            if isinstance(v, basestring):
-                                new['args'][k] = [v]
+                    new.setdefault('path_args', [])
+                    if not isinstance(new['path_args'], list):
+                        log.error(_(u'"path_args" in redirection of {old} must be a list. Ignoring the redirection'.format(
+                            old = old)))
+                        continue
+                    new.setdefault('query_args', {})
+                    if not isinstance(new['query_args'], dict):
+                        log.error(_(u'"query_args" in redirection of {old} must be a dictionary. Ignoring the redirection'.format(
+                            old = old)))
+                        continue
+                    new['path_args'] = [quote(a) for a in new['path_args']]
+                    # we keep an inversed dict of page redirection (page/path_args => redirecting URL)
+                    # so getURL can return the redirecting URL if the same arguments are used
+                    # making the URL consistent
+                    args_hash = tuple(new['path_args'])
+                    LiberviaPage.pages_redirects.setdefault(new_data['page'], {})[args_hash] = old
+
+                    # we need lists in query_args because it will be used
+                    # as it in request.path_args
+                    for k,v in new['query_args'].iteritems():
+                        if isinstance(v, basestring):
+                            new['query_args'][k] = [v]
                 elif 'path' in new_data:
                     new = 'file:{}'.format(urllib.quote(new_data['path']))
             else:
@@ -298,12 +314,13 @@
                     log.error(_(u"Can't find page named \"{name}\" requested in redirection").format(
                         name = request_data['page']))
                     return web_resource.NoResource()
+                request.postpath = request_data['path_args'][:] + request.postpath
+
                 try:
-                    request.args.update(request_data['args'])
-                except KeyError:
-                    pass
-                except Exception:
-                    log.error(_(u"Invalid args in redirection: {args}").format(request_data['args']))
+                    request.args.update(request_data['query_args'])
+                except (TypeError, ValueError):
+                    log.error(_(u"Invalid args in redirection: {query_args}").format(
+                        query_args=request_data['query_args']))
                     return web_resource.NoResource()
                 return page
             else:
@@ -1353,6 +1370,7 @@
     isLeaf = True  # we handle subpages ourself
     named_pages = {}
     uri_callbacks = {}
+    pages_redirects = {}
 
     def __init__(self, host, root_dir, url, name=None, redirect=None, access=None, parse_url=None,
                  prepare_render=None, render=None, template=None, on_data_post=None):
@@ -1397,6 +1415,7 @@
         self.host = host
         self.root_dir = root_dir
         self.url = url
+        self.name = name
         if name is not None:
             if name in self.named_pages:
                 raise exceptions.ConflictError(_(u'a Libervia page named "{}" already exists'.format(name)))
@@ -1412,10 +1431,11 @@
         self.access = access
         if redirect is not None:
             # only page access and name make sense in case of full redirection
+            # so we check that rendering methods/values are not set
             if not all(lambda x: x is not None
                 for x in (parse_url, prepare_render, render, template)):
-                    raise ValueError(_(u"you can't use full page redirection with other rendering method,"
-                                       u"check self.pageRedirect if you need to use them"))
+                raise ValueError(_(u"you can't use full page redirection with other rendering method,"
+                                   u"check self.pageRedirect if you need to use them"))
             self.redirect = redirect
         else:
             self.redirect = None
@@ -1554,8 +1574,70 @@
         *args(list[unicode]): argument to add to the URL as path elements
         """
         url_args = [quote(a) for a in args]
+
+        if self.name is not None and self.name in self.pages_redirects:
+            # we check for redirection
+            redirect_data = self.pages_redirects[self.name]
+            args_hash = tuple(args)
+            for limit in xrange(len(args)+1):
+                current_hash = args_hash[:limit]
+                if current_hash in redirect_data:
+                    url_base = redirect_data[current_hash]
+                    remaining = args[limit:]
+                    remaining_url = '/'.join(remaining)
+                    return os.path.join('/', url_base, remaining_url)
+
         return os.path.join(self.url, *url_args)
 
+    def getSubPageURL(self, request, page_name, *args):
+        """retrieve a page in direct children and build its URL according to request
+
+        request's current path is used as base (at current parsing point,
+        i.e. it's more prepath than path).
+        Requested page is checked in children and an absolute URL is then built
+        by the resulting combination.
+        This method is useful to construct absolute URLs for children instead of
+        using relative path, which may not work in subpages, and are linked to the
+        names of directories (i.e. relative URL will break if subdirectory is renamed
+        while getSubPageURL won't as long as page_name is consistent).
+        Also, request.path is used, keeping real path used by user,
+        and potential redirections.
+        @param request(server.Request): current HTTP request
+        @param page_name(unicode): name of the page to retrieve
+            it must be a direct children of current page
+        @param *args(list[unicode]): arguments to add as path elements
+        @return unicode: absolute URL to the sub page
+        """
+        # we get url in the following way (splitting request.path instead of using
+        # request.prepath) because request.prepath may have been modified by
+        # redirection (if redirection args have been specified), while path reflect
+        # the real request
+
+        # we ignore empty path elements (i.e. double '/' or '/' at the end)
+        path_elts = [p for p in request.path.split('/') if p]
+
+        if request.postpath:
+            if not request.postpath[-1]:
+                # we remove trailing slash
+                request.postpath = request.postpath[:-1]
+            if request.postpath:
+                # getSubPageURL must return subpage from the point where
+                # the it is called, so we have to remove remanining
+                # path elements
+                path_elts = path_elts[:-len(request.postpath)]
+
+        current_url = '/' + '/'.join(path_elts).decode('utf-8')
+
+        for path, child in self.children.iteritems():
+            try:
+                child_name = child.name
+            except AttributeError:
+                # LiberviaPage have a name, but maybe this is an other Resource
+                continue
+            if child_name == page_name:
+                return os.path.join(u'/', current_url, path, *args)
+        raise exceptions.NotFound(_(u'requested sub page has not been found'))
+
     def getChildWithDefault(self, path, request):
         # we handle children ourselves
         raise exceptions.InternalError(u"this method should not be used with LiberviaPage")