web/views/urlrewrite.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """Rules based url rewriter component, to get configurable RESTful urls"""
       
    19 
       
    20 import re
       
    21 
       
    22 from six import string_types, add_metaclass
       
    23 
       
    24 from cubicweb.uilib import domid
       
    25 from cubicweb.appobject import AppObject
       
    26 
       
    27 
       
    28 def rgx(pattern, flags=0):
       
    29     """this is just a convenient shortcut to add the $ sign"""
       
    30     return re.compile(pattern+'$', flags)
       
    31 
       
    32 class metarewriter(type):
       
    33     """auto-extend rules dictionary"""
       
    34     def __new__(mcs, name, bases, classdict):
       
    35         # collect baseclass' rules
       
    36         rules = []
       
    37         ignore_baseclass_rules = classdict.get('ignore_baseclass_rules', False)
       
    38         if not ignore_baseclass_rules:
       
    39             for base in bases:
       
    40                 rules[0:0] = getattr(base, 'rules', [])
       
    41         rules[0:0] = classdict.get('rules', [])
       
    42         inputs = set()
       
    43         for data in rules[:]:
       
    44             try:
       
    45                 input, output, groups = data
       
    46             except ValueError:
       
    47                 input, output = data
       
    48             if input in inputs:
       
    49                 rules.remove( (input, output) )
       
    50             else:
       
    51                 inputs.add(input)
       
    52         classdict['rules'] = rules
       
    53         return super(metarewriter, mcs).__new__(mcs, name, bases, classdict)
       
    54 
       
    55 
       
    56 @add_metaclass(metarewriter)
       
    57 class URLRewriter(AppObject):
       
    58     """Base class for URL rewriters.
       
    59 
       
    60     Url rewriters should have a `rules` dict that maps an input URI
       
    61     to something that should be used for rewriting.
       
    62 
       
    63     The actual logic that defines how the rules dict is used is implemented
       
    64     in the `rewrite` method.
       
    65 
       
    66     A `priority` attribute might be used to indicate which rewriter
       
    67     should be tried first. The higher the priority is, the earlier the
       
    68     rewriter will be tried.
       
    69     """
       
    70     __registry__ = 'urlrewriting'
       
    71     __abstract__ = True
       
    72     priority = 1
       
    73 
       
    74     def rewrite(self, req, uri):
       
    75         raise NotImplementedError
       
    76 
       
    77 
       
    78 class SimpleReqRewriter(URLRewriter):
       
    79     """The SimpleReqRewriters uses a `rules` dict that maps input URI
       
    80     (regexp or plain string) to a dictionary to update the request's
       
    81     form.
       
    82 
       
    83     If the input uri is a regexp, group substitution is allowed.
       
    84     """
       
    85     __regid__ = 'simple'
       
    86 
       
    87     rules = [
       
    88         ('/_', dict(vid='manage')),
       
    89         ('/_registry', dict(vid='registry')),
       
    90 #        (rgx('/_([^/]+?)/?'), dict(vid=r'\1')),
       
    91         ('/schema',  dict(vid='schema')),
       
    92         ('/index', dict(vid='index')),
       
    93         ('/myprefs', dict(vid='propertiesform')),
       
    94         ('/siteconfig', dict(vid='systempropertiesform')),
       
    95         ('/siteinfo', dict(vid='siteinfo')),
       
    96         ('/manage', dict(vid='manage')),
       
    97         ('/notfound', dict(vid='404')),
       
    98         ('/error', dict(vid='error')),
       
    99         ('/sparql', dict(vid='sparql')),
       
   100         ('/processinfo', dict(vid='processinfo')),
       
   101         (rgx('/cwuser', re.I), dict(vid='cw.users-and-groups-management',
       
   102                                     tab=domid('cw.users-management'))),
       
   103         (rgx('/cwgroup', re.I), dict(vid='cw.users-and-groups-management',
       
   104                                      tab=domid('cw.groups-management'))),
       
   105         (rgx('/cwsource', re.I), dict(vid='cw.sources-management')),
       
   106         # XXX should be case insensitive as 'create', but I would like to find another way than
       
   107         # relying on the etype_selector
       
   108         (rgx('/schema/([^/]+?)/?'),  dict(vid='primary', rql=r'Any X WHERE X is CWEType, X name "\1"')),
       
   109         (rgx('/add/([^/]+?)/?'), dict(vid='creation', etype=r'\1')),
       
   110         (rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')),
       
   111         (rgx('/doc/?'), dict(vid='wdoc', fid=r'main')),
       
   112         (rgx('/doc/(.+?)/?'), dict(vid='wdoc', fid=r'\1')),
       
   113         ]
       
   114 
       
   115     def rewrite(self, req, uri):
       
   116         """for each `input`, `output `in rules, if `uri` matches `input`,
       
   117         req's form is updated with `output`
       
   118         """
       
   119         for data in self.rules:
       
   120             try:
       
   121                 inputurl, infos, required_groups = data
       
   122             except ValueError:
       
   123                 inputurl, infos = data
       
   124                 required_groups = None
       
   125             if required_groups and not req.user.matching_groups(required_groups):
       
   126                 continue
       
   127             if isinstance(inputurl, string_types):
       
   128                 if inputurl == uri:
       
   129                     req.form.update(infos)
       
   130                     break
       
   131             elif inputurl.match(uri): # it's a regexp
       
   132                 # XXX what about i18n? (vtitle for instance)
       
   133                 for param, value in infos.items():
       
   134                     if isinstance(value, string_types):
       
   135                         req.form[param] = inputurl.sub(value, uri)
       
   136                     else:
       
   137                         req.form[param] = value
       
   138                 break
       
   139         else:
       
   140             self.debug("no simple rewrite rule found for %s", uri)
       
   141             raise KeyError(uri)
       
   142         return None, None
       
   143 
       
   144 
       
   145 def build_rset(rql, rgxgroups=None, setuser=False,
       
   146                vid=None, vtitle=None, form={}, **kwargs):
       
   147 
       
   148     def do_build_rset(inputurl, uri, req, schema, kwargs=kwargs):
       
   149         kwargs = kwargs.copy()
       
   150         if rgxgroups:
       
   151             match = inputurl.match(uri)
       
   152             for arg, group in rgxgroups:
       
   153                 kwargs[arg] = match.group(group)
       
   154         req.form.update(form)
       
   155         if setuser:
       
   156             kwargs['u'] = req.user.eid
       
   157         if vid:
       
   158             req.form['vid'] = vid
       
   159         if vtitle:
       
   160             req.form['vtitle'] = req._(vtitle) % kwargs
       
   161         return None, req.execute(rql, kwargs)
       
   162     return do_build_rset
       
   163 
       
   164 def update_form(**kwargs):
       
   165     def do_build_rset(inputurl, uri, req, schema):
       
   166         match = inputurl.match(uri)
       
   167         kwargs.update(match.groupdict())
       
   168         req.form.update(kwargs)
       
   169         return None, None
       
   170     return do_build_rset
       
   171 
       
   172 def rgx_action(rql=None, args=None, argsgroups=(), setuser=False,
       
   173                form=None, formgroups=(), transforms={}, rqlformparams=(), controller=None):
       
   174     def do_build_rset(inputurl, uri, req, schema,
       
   175                       ):
       
   176         if rql:
       
   177             kwargs = args and args.copy() or {}
       
   178             if argsgroups:
       
   179                 match = inputurl.match(uri)
       
   180                 for key in argsgroups:
       
   181                     value = match.group(key)
       
   182                     try:
       
   183                         kwargs[key] = transforms[key](value)
       
   184                     except KeyError:
       
   185                         kwargs[key] = value
       
   186             if setuser:
       
   187                 kwargs['u'] = req.user.eid
       
   188             for param in rqlformparams:
       
   189                 kwargs.setdefault(param, req.form.get(param))
       
   190             rset = req.execute(rql, kwargs)
       
   191         else:
       
   192             rset = None
       
   193         form2 = form and form.copy() or {}
       
   194         if formgroups:
       
   195             match = inputurl.match(uri)
       
   196             for key in formgroups:
       
   197                 form2[key] = match.group(key)
       
   198         if "vtitle" in form2:
       
   199             form2['vtitle'] = req.__(form2['vtitle'])
       
   200         if form2:
       
   201             req.form.update(form2)
       
   202         return controller, rset
       
   203     return do_build_rset
       
   204 
       
   205 
       
   206 class SchemaBasedRewriter(URLRewriter):
       
   207     """Here, the rules dict maps regexps or plain strings to callbacks
       
   208     that will be called with inputurl, uri, req, schema as parameters.
       
   209     """
       
   210     __regid__ = 'schemabased'
       
   211     rules = [
       
   212         # rgxp : callback
       
   213         (rgx('/search/(.+)'), build_rset(rql=r'Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s',
       
   214                                          rgxgroups=[('text', 1)])),
       
   215         ]
       
   216 
       
   217     def rewrite(self, req, uri):
       
   218         # XXX this could be refacted with SimpleReqRewriter
       
   219         for data in self.rules:
       
   220             try:
       
   221                 inputurl, callback, required_groups = data
       
   222             except ValueError:
       
   223                 inputurl, callback = data
       
   224                 required_groups = None
       
   225             if required_groups and not req.user.matching_groups(required_groups):
       
   226                 continue
       
   227             if isinstance(inputurl, string_types):
       
   228                 if inputurl == uri:
       
   229                     return callback(inputurl, uri, req, self._cw.vreg.schema)
       
   230             elif inputurl.match(uri): # it's a regexp
       
   231                 return callback(inputurl, uri, req, self._cw.vreg.schema)
       
   232         else:
       
   233             self.debug("no schemabased rewrite rule found for %s", uri)
       
   234             raise KeyError(uri)