[rset] Add a line break the first result in repr of multiple rows rset
Improve readability of rset while debugging by aligning the first value with the
others if the rset have multiple row. This first line was usually not read as
it' was on the same line that the first part of the repr ("<rset <RQL>, (%i
rows) ...")
# copyright 2003-2010 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 <http://www.gnu.org/licenses/>.
"""Associate url's path to view identifier / rql queries.
It currently handles url path with the forms:
* <publishing_method>
* minimal REST publishing:
* <eid>
* <etype>[/<attribute name>/<attribute value>]*
* folder navigation
You can actually control URL (more exactly path) resolution using an
URL path evaluator.
.. note::
Actionpath and Folderpath execute a query whose results is lost
because of redirecting instead of direct traversal.
"""
__docformat__ = "restructuredtext en"
from rql import TypeResolverException
from cubicweb import RegistryException, typed_eid
from cubicweb.web import NotFound, Redirect, component
class PathDontMatch(Exception):
"""exception used by url evaluators to notify they can't evaluate
a path
"""
class URLPublisherComponent(component.Component):
"""Associate url path to view identifier / rql queries, by
applying a chain of urlpathevaluator components.
An evaluator is a URLPathEvaluator subclass with an .evaluate_path
method taking the request object and the path to publish as
argument. It will either return a publishing method identifier
and an rql query on success or raise a `PathDontMatch` exception
on failure. URL evaluators are called according to their
`priority` attribute, with 0 as the greatest priority and greater
values as lower priority. The first evaluator returning a result
or raising something else than `PathDontMatch` will stop the
handlers chain.
"""
__regid__ = 'urlpublisher'
vreg = None # XXX necessary until property for deprecation warning is on appobject
def __init__(self, vreg, default_method='view'):
super(URLPublisherComponent, self).__init__()
self.vreg = vreg
self.default_method = default_method
evaluators = []
for evaluatorcls in vreg['components']['urlpathevaluator']:
# instantiation needed
evaluator = evaluatorcls(self)
evaluators.append(evaluator)
self.evaluators = sorted(evaluators, key=lambda x: x.priority)
def process(self, req, path):
"""Given an url (essentialy caracterized by a path on the
server, but additional information may be found in the request
object), return a publishing method identifier
(e.g. controller) and an optional result set.
:type req: `cubicweb.web.request.CubicWebRequestBase`
:param req: the request object
:type path: str
:param path: the path of the resource to publish
:rtype: tuple(str, `cubicweb.rset.ResultSet` or None)
:return: the publishing method identifier and an optional result set
:raise NotFound: if no handler is able to decode the given path
"""
parts = [part for part in path.split('/')
if part != ''] or (self.default_method,)
if req.form.get('rql'):
if parts[0] in self.vreg['controllers']:
return parts[0], None
return 'view', None
for evaluator in self.evaluators:
try:
pmid, rset = evaluator.evaluate_path(req, parts[:])
break
except PathDontMatch:
continue
else:
raise NotFound(path)
if pmid is None:
pmid = self.default_method
return pmid, rset
class URLPathEvaluator(component.Component):
__abstract__ = True
__regid__ = 'urlpathevaluator'
vreg = None # XXX necessary until property for deprecation warning is on appobject
def __init__(self, urlpublisher):
self.urlpublisher = urlpublisher
self.vreg = urlpublisher.vreg
class RawPathEvaluator(URLPathEvaluator):
"""handle path of the form::
<publishing_method>?parameters...
"""
priority = 0
def evaluate_path(self, req, parts):
if len(parts) == 1 and parts[0] in self.vreg['controllers']:
return parts[0], None
raise PathDontMatch()
class EidPathEvaluator(URLPathEvaluator):
"""handle path with the form::
<eid>
"""
priority = 1
def evaluate_path(self, req, parts):
if len(parts) != 1:
raise PathDontMatch()
try:
rset = req.execute('Any X WHERE X eid %(x)s', {'x': typed_eid(parts[0])})
except ValueError:
raise PathDontMatch()
if rset.rowcount == 0:
raise NotFound()
return None, rset
class RestPathEvaluator(URLPathEvaluator):
"""handle path with the form::
<etype>[[/<attribute name>]/<attribute value>]*
"""
priority = 2
def evaluate_path(self, req, parts):
if not (0 < len(parts) < 4):
raise PathDontMatch()
try:
etype = self.vreg.case_insensitive_etypes[parts.pop(0).lower()]
except KeyError:
raise PathDontMatch()
cls = self.vreg['etypes'].etype_class(etype)
if parts:
if len(parts) == 2:
attrname = parts.pop(0).lower()
try:
cls.e_schema.subjrels[attrname]
except KeyError:
raise PathDontMatch()
else:
attrname = cls._rest_attr_info()[0]
value = req.url_unquote(parts.pop(0))
rset = self.attr_rset(req, etype, attrname, value)
else:
rset = self.cls_rset(req, cls)
if rset.rowcount == 0:
raise NotFound()
return None, rset
def cls_rset(self, req, cls):
return req.execute(cls.fetch_rql(req.user))
def attr_rset(self, req, etype, attrname, value):
rql = u'Any X WHERE X is %s, X %s %%(x)s' % (etype, attrname)
if attrname == 'eid':
try:
rset = req.execute(rql, {'x': typed_eid(value)})
except (ValueError, TypeResolverException):
# conflicting eid/type
raise PathDontMatch()
else:
rset = req.execute(rql, {'x': value})
return rset
class URLRewriteEvaluator(URLPathEvaluator):
"""tries to find a rewrite rule to apply
URL rewrite rule definitions are stored in URLRewriter objects
"""
priority = 3
def evaluate_path(self, req, parts):
# uri <=> req._twreq.path or req._twreq.uri
uri = req.url_unquote('/' + '/'.join(parts))
evaluators = sorted(self.vreg['urlrewriting'].all_objects(),
key=lambda x: x.priority, reverse=True)
for rewritercls in evaluators:
rewriter = rewritercls(req)
try:
# XXX we might want to chain url rewrites
return rewriter.rewrite(req, uri)
except KeyError:
continue
raise PathDontMatch()
class ActionPathEvaluator(URLPathEvaluator):
"""handle path with the form::
<any evaluator path>/<action>
"""
priority = 4
def evaluate_path(self, req, parts):
if len(parts) < 2:
raise PathDontMatch()
# remove last part and see if this is something like an actions
# if so, call
# XXX bad smell: refactor to simpler code
try:
actionsreg = self.vreg['actions']
requested = parts.pop(-1)
actions = actionsreg[requested]
except RegistryException:
raise PathDontMatch()
for evaluator in self.urlpublisher.evaluators:
if evaluator is self or evaluator.priority == 0:
continue
try:
pmid, rset = evaluator.evaluate_path(req, parts[:])
except PathDontMatch:
continue
else:
try:
action = actionsreg._select_best(actions, req, rset=rset)
except RegistryException:
continue
else:
# XXX avoid redirect
raise Redirect(action.url())
raise PathDontMatch()