# HG changeset patch # User Goffi # Date 1510644917 -3600 # Node ID bcacf970f97002fab44bc3c628bc177710244731 # Parent 1d558dfb32caca3f026290df3595dbb77ae0702c 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. diff -r 1d558dfb32ca -r bcacf970f970 src/server/server.py --- 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")