web/views/urlrewrite.py
changeset 0 b97547f5f1fa
child 688 cddfbdee0eb3
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """Rules based url rewriter component, to get configurable RESTful urls
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 import re
       
     8 
       
     9 from cubicweb.vregistry import autoselectors
       
    10 
       
    11 from cubicweb.common.registerers import accepts_registerer
       
    12 from cubicweb.common.appobject import AppObject
       
    13 
       
    14 
       
    15 def rgx(pattern, flags=0):
       
    16     """this is just a convenient shortcout to add the $ sign"""
       
    17     return re.compile(pattern+'$', flags)
       
    18 
       
    19 class metarewriter(autoselectors):
       
    20     """auto-extend rules dictionnary"""
       
    21     def __new__(mcs, name, bases, classdict):
       
    22         # collect baseclass' rules
       
    23         rules = []
       
    24         ignore_baseclass_rules = classdict.get('ignore_baseclass_rules', False)
       
    25         if not ignore_baseclass_rules:
       
    26             for base in bases:
       
    27                 rules[0:0] = getattr(base, 'rules', [])
       
    28         rules[0:0] = classdict.get('rules', [])
       
    29         inputs = set()
       
    30         for data in rules[:]:
       
    31             try:
       
    32                 input, output, groups = data
       
    33             except ValueError:
       
    34                 input, output = data
       
    35             if input in inputs:
       
    36                 rules.remove( (input, output) )
       
    37             else:
       
    38                 inputs.add(input)
       
    39         classdict['rules'] = rules
       
    40         return super(metarewriter, mcs).__new__(mcs, name, bases, classdict)
       
    41 
       
    42 
       
    43 class URLRewriter(AppObject):
       
    44     """base class for URL rewriters
       
    45 
       
    46     url rewriters should have a `rules` dict that maps an input URI
       
    47     to something that should be used for rewriting.
       
    48 
       
    49     The actual logic that defines how the rules dict is used is implemented
       
    50     in the `rewrite` method
       
    51 
       
    52     A `priority` attribute might be used to indicate which rewriter
       
    53     should be tried first. The higher the priority is, the earlier the
       
    54     rewriter will be tried
       
    55     """
       
    56     __metaclass__ = metarewriter
       
    57     __registry__ = 'urlrewriting'
       
    58     __registerer__ = accepts_registerer
       
    59     __abstract__ = True
       
    60 
       
    61     id = 'urlrewriting'
       
    62     accepts = ('Any',)
       
    63     priority = 1
       
    64 
       
    65     def rewrite(self, req, uri):
       
    66         raise NotImplementedError
       
    67 
       
    68 
       
    69 class SimpleReqRewriter(URLRewriter):
       
    70     """The SimpleReqRewriters uses a `rules` dict that maps
       
    71     input URI (regexp or plain string) to a dictionary to update the
       
    72     request's form
       
    73 
       
    74     If the input uri is a regexp, group substitution is allowed
       
    75     """
       
    76     id = 'simple'
       
    77 
       
    78     rules = [
       
    79         ('/schema',  dict(vid='schema')),
       
    80         ('/index', dict(vid='index')),
       
    81         ('/myprefs', dict(vid='epropertiesform')),
       
    82         ('/siteconfig', dict(vid='systemepropertiesform')),
       
    83         ('/manage', dict(vid='manage')),
       
    84         ('/notfound', dict(vid='404')),
       
    85         ('/error', dict(vid='error')),
       
    86         (rgx('/schema/([^/]+?)/?'),  dict(vid='eschema', rql=r'Any X WHERE X is EEType, X name "\1"')),
       
    87         (rgx('/add/([^/]+?)/?'), dict(vid='creation', etype=r'\1')),
       
    88         (rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')),
       
    89         (rgx('/doc/?'), dict(vid='wdoc', fid=r'main')),
       
    90         (rgx('/doc/(.+?)/?'), dict(vid='wdoc', fid=r'\1')),
       
    91         (rgx('/changelog/?'), dict(vid='changelog')),
       
    92         ]
       
    93     
       
    94     def rewrite(self, req, uri):
       
    95         """for each `input`, `output `in rules, if `uri` matches `input`,
       
    96         req's form is updated with `output`
       
    97         """
       
    98         rset = None
       
    99         for data in self.rules:
       
   100             try:
       
   101                 inputurl, infos, required_groups = data
       
   102             except ValueError:
       
   103                 inputurl, infos = data
       
   104                 required_groups = None
       
   105             if required_groups and not req.user.matching_groups(required_groups):
       
   106                 continue
       
   107             if isinstance(inputurl, basestring):
       
   108                 if inputurl == uri:
       
   109                     req.form.update(infos)
       
   110                     break
       
   111             elif inputurl.match(uri): # it's a regexp
       
   112                 # XXX what about i18n ? (vtitle for instance)
       
   113                 for param, value in infos.items():
       
   114                     if isinstance(value, basestring):
       
   115                         req.form[param]= inputurl.sub(value, uri)
       
   116                     else:
       
   117                         req.form[param] = value
       
   118                 break
       
   119         else:
       
   120             self.debug("no simple rewrite rule found for %s", uri)
       
   121             raise KeyError(uri)
       
   122         return None, None
       
   123 
       
   124 
       
   125 def build_rset(rql, rgxgroups=None, cachekey=None, setuser=False,
       
   126                vid=None, vtitle=None, form={}, **kwargs):
       
   127 
       
   128     def do_build_rset(inputurl, uri, req, schema):
       
   129         if rgxgroups:
       
   130             match = inputurl.match(uri)
       
   131             for arg, group in rgxgroups:
       
   132                 kwargs[arg] = match.group(group)
       
   133         req.form.update(form)
       
   134         if setuser:
       
   135             kwargs['u'] = req.user.eid
       
   136         if vid:
       
   137             req.form['vid'] = vid
       
   138         if vtitle:
       
   139             req.form['vtitle'] = req._(vtitle) % kwargs
       
   140         return None, req.execute(rql, kwargs, cachekey)
       
   141     return do_build_rset
       
   142 
       
   143 def update_form(**kwargs):
       
   144     def do_build_rset(inputurl, uri, req, schema):
       
   145         match = inputurl.match(uri)
       
   146         kwargs.update(match.groupdict())
       
   147         req.form.update(kwargs)
       
   148         return None, None
       
   149     return do_build_rset
       
   150 
       
   151 def rgx_action(rql=None, args=None, cachekey=None, argsgroups=(), setuser=False,
       
   152                form=None, formgroups=(), transforms={}, controller=None):
       
   153     def do_build_rset(inputurl, uri, req, schema):
       
   154         if rql:
       
   155             kwargs = args and args.copy() or {}
       
   156             if argsgroups:
       
   157                 match = inputurl.match(uri)
       
   158                 for key in argsgroups:
       
   159                     value = match.group(key)
       
   160                     try:
       
   161                         kwargs[key] = transforms[key](value)
       
   162                     except KeyError:
       
   163                         kwargs[key] = value
       
   164             if setuser:
       
   165                 kwargs['u'] = req.user.eid
       
   166             rset = req.execute(rql, kwargs, cachekey)
       
   167         else:
       
   168             rset = None
       
   169         form2 = form and form.copy() or {}
       
   170         if formgroups:
       
   171             match = inputurl.match(uri)
       
   172             for key in formgroups:
       
   173                 form2[key] = match.group(key)
       
   174         if form2:
       
   175             req.form.update(form2)
       
   176         return controller, rset
       
   177     return do_build_rset
       
   178 
       
   179 
       
   180 class SchemaBasedRewriter(URLRewriter):
       
   181     """Here, the rules dict maps regexps or plain strings to
       
   182     callbacks that will be called with (input, uri, req, schema)
       
   183     """
       
   184     id = 'schemabased'
       
   185     rules = [
       
   186         # rgxp : callback
       
   187         (rgx('/search/(.+)'), build_rset(rql=r'Any X WHERE X has_text %(text)s',
       
   188                                          rgxgroups=[('text', 1)])), 
       
   189         ]
       
   190 
       
   191     def rewrite(self, req, uri):
       
   192         # XXX this could be refacted with SimpleReqRewriter
       
   193         for data in self.rules:
       
   194             try:
       
   195                 inputurl, callback, required_groups = data
       
   196             except ValueError:
       
   197                 inputurl, callback = data
       
   198                 required_groups = None
       
   199             if required_groups and not req.user.matching_groups(required_groups):
       
   200                 continue
       
   201             if isinstance(inputurl, basestring):
       
   202                 if inputurl == uri:
       
   203                     return callback(inputurl, uri, req, self.schema)
       
   204             elif inputurl.match(uri): # it's a regexp
       
   205                 return callback(inputurl, uri, req, self.schema)
       
   206         else:
       
   207             self.debug("no schemabased rewrite rule found for %s", uri)
       
   208             raise KeyError(uri)