fix optimisation with super session that may lead to integrity loss
at some point I've decided to stop ensuring ?1 cardinality was respected
when adding a new relation using a super session, to avoid the cost of
the delete query. That was yet discussable because it introduced unexpected
difference between execute and unsafe_execute, which is imo not worth it.
Also, now that rql() in migration script default to unsafe_execute, we
definitly don't want that implicit behaviour change (which already cause
bug when for instance adding another default workflow for an entity type:
without that fix we end up with *two* default workflows while the schema
tells we can have only one.
IMO we should go to the direction that super session skip all security
check, but nothing else, unless explicitly asked.
"""Rules based url rewriter component, to get configurable RESTful urls
:organization: Logilab
:copyright: 2007-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
import re
from cubicweb import typed_eid
from cubicweb.appobject import AppObject
def rgx(pattern, flags=0):
"""this is just a convenient shortcout to add the $ sign"""
return re.compile(pattern+'$', flags)
class metarewriter(type):
"""auto-extend rules dictionnary"""
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)
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
"""
__metaclass__ = metarewriter
__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='info')),
('/manage', dict(vid='manage')),
('/notfound', dict(vid='404')),
('/error', dict(vid='error')),
('/sparql', dict(vid='sparql')),
# 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')),
(rgx('/changelog/?'), dict(vid='changelog')),
]
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, basestring):
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, basestring):
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, cachekey=None, setuser=False,
vid=None, vtitle=None, form={}, **kwargs):
def do_build_rset(inputurl, uri, req, schema):
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, cachekey)
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, cachekey=None, argsgroups=(), setuser=False,
form=None, formgroups=(), transforms={}, controller=None):
def do_build_rset(inputurl, uri, req, schema,
cachekey=cachekey # necessary to avoid UnboundLocalError
):
if rql:
kwargs = args and args.copy() or {}
if argsgroups:
if cachekey is not None and isinstance(cachekey, basestring):
cachekey = (cachekey,)
match = inputurl.match(uri)
for key in argsgroups:
value = match.group(key)
try:
kwargs[key] = transforms[key](value)
except KeyError:
kwargs[key] = value
if cachekey is not None and key in cachekey:
kwargs[key] = typed_eid(value)
if setuser:
kwargs['u'] = req.user.eid
rset = req.execute(rql, kwargs, cachekey)
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 (input, uri, req, schema)
"""
__regid__ = 'schemabased'
rules = [
# rgxp : callback
(rgx('/search/(.+)'), build_rset(rql=r'Any X 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, basestring):
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)