"""associate url's path to view identifier / rql queriesIt currently handle url's path with the forms* <publishing_method>* minimal REST publishing: * <eid> * <etype>[/<attribute name>/<attribute value>]** folder navigationYou can actually control URL (more exactly path) resolution using URL pathevaluator.XXX actionpath and folderpath execute a query whose results is lostbecause of redirecting instead of direct traversal:organization: Logilab:copyright: 2001-2009 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"""__docformat__="restructuredtext en"fromrqlimportTypeResolverExceptionfromcubicwebimportRegistryException,typed_eidfromcubicweb.webimportNotFound,Redirectfromcubicweb.web.componentimportComponent,ComponentclassPathDontMatch(Exception):"""exception used by url evaluators to notify they can't evaluate a path """classURLPublisherComponent(Component):"""associate url's path to view identifier / rql queries, by applying a chain of urlpathevaluator components. An evaluator is a URLPathEvaluator subclass with a .evaluate_path method taking the request object and the path to publish as argument. It will either returns a publishing method identifier and a rql query on success or raises 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. """id='urlpublisher'def__init__(self,default_method='view'):super(URLPublisherComponent,self).__init__()self.default_method=default_methodevaluators=[]forevaluatorclsinself.vreg['components']['urlpathevaluator']:# instantiation neededevaluator=evaluatorcls(self)evaluators.append(evaluator)self.evaluators=sorted(evaluators,key=lambdax:x.priority)defprocess(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 (eg controller) and an optional result set :type req: `cubicweb.web.Request` :param req: the request object :type path: str :param path: the path of the resource to publish :rtype: tuple(str, `cubicweb.common.utils.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=[partforpartinpath.split('/')ifpart!='']or(self.default_method,)ifreq.form.get('rql'):ifparts[0]inself.vreg['controllers']:returnparts[0],Nonereturn'view',Noneforevaluatorinself.evaluators:try:pmid,rset=evaluator.evaluate_path(req,parts[:])breakexceptPathDontMatch:continueelse:raiseNotFound(path)ifpmidisNone:pmid=self.default_methodreturnpmid,rsetclassURLPathEvaluator(Component):__abstract__=Trueid='urlpathevaluator'def__init__(self,urlpublisher):self.urlpublisher=urlpublisherclassRawPathEvaluator(URLPathEvaluator):"""handle path of the form:: <publishing_method>?parameters... """priority=0defevaluate_path(self,req,parts):iflen(parts)==1andparts[0]inself.vreg['controllers']:returnparts[0],NoneraisePathDontMatch()classEidPathEvaluator(URLPathEvaluator):"""handle path with the form:: <eid> """priority=1defevaluate_path(self,req,parts):iflen(parts)!=1:raisePathDontMatch()try:rset=req.execute('Any X WHERE X eid %(x)s',{'x':typed_eid(parts[0])},'x')exceptValueError:raisePathDontMatch()ifrset.rowcount==0:raiseNotFound()returnNone,rsetclassRestPathEvaluator(URLPathEvaluator):"""handle path with the form:: <etype>[[/<attribute name>]/<attribute value>]* """priority=2defevaluate_path(self,req,parts):ifnot(0<len(parts)<4):raisePathDontMatch()try:etype=self.vreg.case_insensitive_etypes[parts.pop(0).lower()]exceptKeyError:raisePathDontMatch()cls=self.vreg['etypes'].etype_class(etype)ifparts:iflen(parts)==2:attrname=parts.pop(0).lower()try:cls.e_schema.subject_relation(attrname)exceptKeyError:raisePathDontMatch()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)ifrset.rowcount==0:raiseNotFound()returnNone,rsetdefcls_rset(self,req,cls):returnreq.execute(cls.fetch_rql(req.user))defattr_rset(self,req,etype,attrname,value):rql=u'Any X WHERE X is %s, X %s%%(x)s'%(etype,attrname)ifattrname=='eid':try:rset=req.execute(rql,{'x':typed_eid(value)},'x')except(ValueError,TypeResolverException):# conflicting eid/typeraisePathDontMatch()else:rset=req.execute(rql,{'x':value})returnrsetclassURLRewriteEvaluator(URLPathEvaluator):"""tries to find a rewrite rule to apply URL rewrite rule definitions are stored in URLRewriter objects """priority=3defevaluate_path(self,req,parts):# uri <=> req._twreq.path or req._twreq.uriuri=req.url_unquote('/'+'/'.join(parts))evaluators=sorted(self.vreg['urlrewriting'].all_objects(),key=lambdax:x.priority,reverse=True)forrewriterclsinevaluators:rewriter=rewritercls()try:# XXX we might want to chain url rewritesreturnrewriter.rewrite(req,uri)exceptKeyError:continueraisePathDontMatch()classActionPathEvaluator(URLPathEvaluator):"""handle path with the form:: <any evaluator path>/<action> """priority=4defevaluate_path(self,req,parts):iflen(parts)<2:raisePathDontMatch()# remove last part and see if this is something like an actions# if so, calltry:actionsreg=self.vreg['actions']requested=parts.pop(-1)actions=actionsreg[requested]exceptRegistryException:raisePathDontMatch()forevaluatorinself.urlpublisher.evaluators:ifevaluatorisselforevaluator.priority==0:continuetry:pmid,rset=evaluator.evaluate_path(req,parts[:])exceptPathDontMatch:continueelse:try:action=actionsreg.select_best(actions,req,rset=rset)exceptRegistryException:continueelse:# XXX avoid redirectraiseRedirect(action.url())raisePathDontMatch()