diff -r 058bb3dc685f -r 0b59724cb3f2 cubicweb/web/views/urlrewrite.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cubicweb/web/views/urlrewrite.py Sat Jan 16 13:48:51 2016 +0100 @@ -0,0 +1,234 @@ +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of CubicWeb. +# +# CubicWeb is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) +# any later version. +# +# CubicWeb is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with CubicWeb. If not, see . +"""Rules based url rewriter component, to get configurable RESTful urls""" + +import re + +from six import string_types, add_metaclass + +from cubicweb.uilib import domid +from cubicweb.appobject import AppObject + + +def rgx(pattern, flags=0): + """this is just a convenient shortcut to add the $ sign""" + return re.compile(pattern+'$', flags) + +class metarewriter(type): + """auto-extend rules dictionary""" + def __new__(mcs, name, bases, classdict): + # collect baseclass' rules + rules = [] + ignore_baseclass_rules = classdict.get('ignore_baseclass_rules', False) + if not ignore_baseclass_rules: + for base in bases: + rules[0:0] = getattr(base, 'rules', []) + rules[0:0] = classdict.get('rules', []) + inputs = set() + for data in rules[:]: + try: + input, output, groups = data + except ValueError: + input, output = data + if input in inputs: + rules.remove( (input, output) ) + else: + inputs.add(input) + classdict['rules'] = rules + return super(metarewriter, mcs).__new__(mcs, name, bases, classdict) + + +@add_metaclass(metarewriter) +class URLRewriter(AppObject): + """Base class for URL rewriters. + + Url rewriters should have a `rules` dict that maps an input URI + to something that should be used for rewriting. + + The actual logic that defines how the rules dict is used is implemented + in the `rewrite` method. + + A `priority` attribute might be used to indicate which rewriter + should be tried first. The higher the priority is, the earlier the + rewriter will be tried. + """ + __registry__ = 'urlrewriting' + __abstract__ = True + priority = 1 + + def rewrite(self, req, uri): + raise NotImplementedError + + +class SimpleReqRewriter(URLRewriter): + """The SimpleReqRewriters uses a `rules` dict that maps input URI + (regexp or plain string) to a dictionary to update the request's + form. + + If the input uri is a regexp, group substitution is allowed. + """ + __regid__ = 'simple' + + rules = [ + ('/_', dict(vid='manage')), + ('/_registry', dict(vid='registry')), +# (rgx('/_([^/]+?)/?'), dict(vid=r'\1')), + ('/schema', dict(vid='schema')), + ('/index', dict(vid='index')), + ('/myprefs', dict(vid='propertiesform')), + ('/siteconfig', dict(vid='systempropertiesform')), + ('/siteinfo', dict(vid='siteinfo')), + ('/manage', dict(vid='manage')), + ('/notfound', dict(vid='404')), + ('/error', dict(vid='error')), + ('/sparql', dict(vid='sparql')), + ('/processinfo', dict(vid='processinfo')), + (rgx('/cwuser', re.I), dict(vid='cw.users-and-groups-management', + tab=domid('cw.users-management'))), + (rgx('/cwgroup', re.I), dict(vid='cw.users-and-groups-management', + tab=domid('cw.groups-management'))), + (rgx('/cwsource', re.I), dict(vid='cw.sources-management')), + # XXX should be case insensitive as 'create', but I would like to find another way than + # relying on the etype_selector + (rgx('/schema/([^/]+?)/?'), dict(vid='primary', rql=r'Any X WHERE X is CWEType, X name "\1"')), + (rgx('/add/([^/]+?)/?'), dict(vid='creation', etype=r'\1')), + (rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')), + (rgx('/doc/?'), dict(vid='wdoc', fid=r'main')), + (rgx('/doc/(.+?)/?'), dict(vid='wdoc', fid=r'\1')), + ] + + def rewrite(self, req, uri): + """for each `input`, `output `in rules, if `uri` matches `input`, + req's form is updated with `output` + """ + for data in self.rules: + try: + inputurl, infos, required_groups = data + except ValueError: + inputurl, infos = data + required_groups = None + if required_groups and not req.user.matching_groups(required_groups): + continue + if isinstance(inputurl, string_types): + if inputurl == uri: + req.form.update(infos) + break + elif inputurl.match(uri): # it's a regexp + # XXX what about i18n? (vtitle for instance) + for param, value in infos.items(): + if isinstance(value, string_types): + req.form[param] = inputurl.sub(value, uri) + else: + req.form[param] = value + break + else: + self.debug("no simple rewrite rule found for %s", uri) + raise KeyError(uri) + return None, None + + +def build_rset(rql, rgxgroups=None, setuser=False, + vid=None, vtitle=None, form={}, **kwargs): + + def do_build_rset(inputurl, uri, req, schema, kwargs=kwargs): + kwargs = kwargs.copy() + if rgxgroups: + match = inputurl.match(uri) + for arg, group in rgxgroups: + kwargs[arg] = match.group(group) + req.form.update(form) + if setuser: + kwargs['u'] = req.user.eid + if vid: + req.form['vid'] = vid + if vtitle: + req.form['vtitle'] = req._(vtitle) % kwargs + return None, req.execute(rql, kwargs) + return do_build_rset + +def update_form(**kwargs): + def do_build_rset(inputurl, uri, req, schema): + match = inputurl.match(uri) + kwargs.update(match.groupdict()) + req.form.update(kwargs) + return None, None + return do_build_rset + +def rgx_action(rql=None, args=None, argsgroups=(), setuser=False, + form=None, formgroups=(), transforms={}, rqlformparams=(), controller=None): + def do_build_rset(inputurl, uri, req, schema, + ): + if rql: + kwargs = args and args.copy() or {} + if argsgroups: + match = inputurl.match(uri) + for key in argsgroups: + value = match.group(key) + try: + kwargs[key] = transforms[key](value) + except KeyError: + kwargs[key] = value + if setuser: + kwargs['u'] = req.user.eid + for param in rqlformparams: + kwargs.setdefault(param, req.form.get(param)) + rset = req.execute(rql, kwargs) + else: + rset = None + form2 = form and form.copy() or {} + if formgroups: + match = inputurl.match(uri) + for key in formgroups: + form2[key] = match.group(key) + if "vtitle" in form2: + form2['vtitle'] = req.__(form2['vtitle']) + if form2: + req.form.update(form2) + return controller, rset + return do_build_rset + + +class SchemaBasedRewriter(URLRewriter): + """Here, the rules dict maps regexps or plain strings to callbacks + that will be called with inputurl, uri, req, schema as parameters. + """ + __regid__ = 'schemabased' + rules = [ + # rgxp : callback + (rgx('/search/(.+)'), build_rset(rql=r'Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s', + rgxgroups=[('text', 1)])), + ] + + def rewrite(self, req, uri): + # XXX this could be refacted with SimpleReqRewriter + for data in self.rules: + try: + inputurl, callback, required_groups = data + except ValueError: + inputurl, callback = data + required_groups = None + if required_groups and not req.user.matching_groups(required_groups): + continue + if isinstance(inputurl, string_types): + if inputurl == uri: + return callback(inputurl, uri, req, self._cw.vreg.schema) + elif inputurl.match(uri): # it's a regexp + return callback(inputurl, uri, req, self._cw.vreg.schema) + else: + self.debug("no schemabased rewrite rule found for %s", uri) + raise KeyError(uri)