[devtools] change the way we start/stop postgresql
Instead of having the test db handler start a cluster on demand, use the
test module's setUp/tearDown callbacks to do it. This allows to have
one data directory (and thus cluster) per test module, allowing
different test modules to run in parallel, each using its own database
cluster whose path is based on the test module.
# copyright 2003-2013 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/>.## (disable pylint msg for client obj access to protected member as in obj._cw)# pylint: disable=W0212"""The ``ajaxcontroller`` module defines the :class:`AjaxController`controller and the ``ajax-func`` cubicweb registry... autoclass:: cubicweb.web.views.ajaxcontroller.AjaxController :members:``ajax-funcs`` registry hosts exposed remote functions, that isfunctions that can be called from the javascript world.To register a new remote function, either decorate your functionwith the :func:`~cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:.. sourcecode:: python from cubicweb.predicates import mactch_user_groups from cubicweb.web.views.ajaxcontroller import ajaxfunc @ajaxfunc(output_type='json', selector=match_user_groups('managers')) def list_users(self): return [u for (u,) in self._cw.execute('Any L WHERE U login L')]or inherit from :class:`~cubicweb.web.views.ajaxcontroller.AjaxFunction` andimplement the ``__call__`` method:.. sourcecode:: python from cubicweb.web.views.ajaxcontroller import AjaxFunction class ListUser(AjaxFunction): __regid__ = 'list_users' # __regid__ is the name of the exposed function __select__ = match_user_groups('managers') output_type = 'json' def __call__(self): return [u for (u, ) in self._cw.execute('Any L WHERE U login L')].. autoclass:: cubicweb.web.views.ajaxcontroller.AjaxFunction :members:.. autofunction:: cubicweb.web.views.ajaxcontroller.ajaxfunc"""__docformat__="restructuredtext en"fromwarningsimportwarnfromfunctoolsimportpartialfromlogilab.common.dateimportstrptimefromlogilab.common.registryimportyesfromlogilab.common.deprecationimportdeprecatedfromcubicwebimportObjectNotFound,NoSelectableObjectfromcubicweb.appobjectimportAppObjectfromcubicweb.utilsimportjson,json_dumps,UStringIOfromcubicweb.uilibimportexc_messagefromcubicweb.webimportRemoteCallFailed,DirectResponsefromcubicweb.web.controllerimportControllerfromcubicweb.web.viewsimportvid_from_rsetfromcubicweb.web.viewsimportbasecontrollersdefoptional_kwargs(extraargs):ifextraargsisNone:return{}# we receive unicode keys which is not supported by the **syntaxreturndict((str(key),value)forkey,valueinextraargs.iteritems())classAjaxController(Controller):"""AjaxController handles ajax remote calls from javascript The following javascript function call: .. sourcecode:: javascript var d = asyncRemoteExec('foo', 12, "hello"); d.addCallback(function(result) { alert('server response is: ' + result); }); will generate an ajax HTTP GET on the following url:: BASE_URL/ajax?fname=foo&arg=12&arg="hello" The AjaxController controller will therefore be selected to handle those URLs and will itself select the :class:`cubicweb.web.views.ajaxcontroller.AjaxFunction` matching the *fname* parameter. """__regid__='ajax'defpublish(self,rset=None):self._cw.ajax_request=Truetry:fname=self._cw.form['fname']exceptKeyError:raiseRemoteCallFailed('no method specified')# 1/ check first for old-style (JSonController) ajax func for bw compattry:func=getattr(basecontrollers.JSonController,'js_%s'%fname).im_funcfunc=partial(func,self)exceptAttributeError:# 2/ check for new-style (AjaxController) ajax functry:func=self._cw.vreg['ajax-func'].select(fname,self._cw)exceptObjectNotFound:raiseRemoteCallFailed('no %s method'%fname)else:warn('[3.15] remote function %s found on JSonController, ''use AjaxFunction / @ajaxfunc instead'%fname,DeprecationWarning,stacklevel=2)# no <arg> attribute means the callback takes no argumentargs=self._cw.form.get('arg',())ifnotisinstance(args,(list,tuple)):args=(args,)try:args=[json.loads(arg)forarginargs]exceptValueErrorasexc:self.exception('error while decoding json arguments for ''js_%s: %s (err: %s)',fname,args,exc)raiseRemoteCallFailed(exc_message(exc,self._cw.encoding))try:result=func(*args)except(RemoteCallFailed,DirectResponse):raiseexceptExceptionasexc:self.exception('an exception occurred while calling js_%s(%s): %s',fname,args,exc)raiseRemoteCallFailed(exc_message(exc,self._cw.encoding))ifresultisNone:return''# get unicode on @htmlize methods, encoded string on @jsonize methodselifisinstance(result,unicode):returnresult.encode(self._cw.encoding)returnresultclassAjaxFunction(AppObject):""" Attributes on this base class are: :attr: `check_pageid`: make sure the pageid received is valid before proceeding :attr: `output_type`: - *None*: no processing, no change on content-type - *json*: serialize with `json_dumps` and set *application/json* content-type - *xhtml*: wrap result in an XML node and forces HTML / XHTML content-type (use ``_cw.html_content_type()``) """__registry__='ajax-func'__select__=yes()__abstract__=Truecheck_pageid=Falseoutput_type=None@staticmethoddef_rebuild_posted_form(names,values,action=None):form={}forname,valueinzip(names,values):# remove possible __action_xxx inputsifname.startswith('__action'):ifactionisNone:# strip '__action_' to get the actual action nameaction=name[9:]continue# form.setdefault(name, []).append(value)ifnameinform:curvalue=form[name]ifisinstance(curvalue,list):curvalue.append(value)else:form[name]=[curvalue,value]else:form[name]=value# simulate click on __action_%s button to help the controllerifaction:form['__action_%s'%action]=u'whatever'returnformdefvalidate_form(self,action,names,values):self._cw.form=self._rebuild_posted_form(names,values,action)returnbasecontrollers._validate_form(self._cw,self._cw.vreg)def_exec(self,rql,args=None,rocheck=True):"""json mode: execute RQL and return resultset as json"""rql=rql.strip()ifrql.startswith('rql:'):rql=rql[4:]ifrocheck:self._cw.ensure_ro_rql(rql)try:returnself._cw.execute(rql,args)exceptExceptionasex:self.exception("error in _exec(rql=%s): %s",rql,ex)returnNonereturnNonedef_call_view(self,view,paginate=False,**kwargs):divid=self._cw.form.get('divid')# we need to call pagination before with the stream settry:stream=view.set_stream()exceptAttributeError:stream=UStringIO()kwargs['w']=stream.writeassertnotpaginateifdivid=='pageContent':# ensure divid isn't reused by the view (e.g. table view)delself._cw.form['divid']# mimick main template behaviourstream.write(u'<div id="pageContent">')vtitle=self._cw.form.get('vtitle')ifvtitle:stream.write(u'<h1 class="vtitle">%s</h1>\n'%vtitle)paginate=Truenav_html=UStringIO()ifpaginateandnotview.handle_pagination:view.paginate(w=nav_html.write)stream.write(nav_html.getvalue())ifdivid=='pageContent':stream.write(u'<div id="contentmain">')view.render(**kwargs)extresources=self._cw.html_headers.getvalue(skiphead=True)ifextresources:stream.write(u'<div class="ajaxHtmlHead">\n')# XXX use a widget?stream.write(extresources)stream.write(u'</div>\n')ifdivid=='pageContent':stream.write(u'</div>%s</div>'%nav_html.getvalue())returnstream.getvalue()def_ajaxfunc_factory(implementation,selector=yes(),_output_type=None,_check_pageid=False,regid=None):"""converts a standard python function into an AjaxFunction appobject"""classAnAjaxFunc(AjaxFunction):__regid__=regidorimplementation.__name____select__=selectoroutput_type=_output_typecheck_pageid=_check_pageiddefserialize(self,content):ifself.output_typeisNone:returncontentelifself.output_type=='xhtml':self._cw.set_content_type(self._cw.html_content_type())return''.join((u'<div>',content.strip(),u'</div>'))elifself.output_type=='json':self._cw.set_content_type('application/json')returnjson_dumps(content)raiseRemoteCallFailed('no serializer found for output type %s'%self.output_type)def__call__(self,*args,**kwargs):ifself.check_pageid:data=self._cw.session.data.get(self._cw.pageid)ifdataisNone:raiseRemoteCallFailed(self._cw._('pageid-not-found'))returnself.serialize(implementation(self,*args,**kwargs))AnAjaxFunc.__name__=implementation.__name__# make sure __module__ refers to the original module otherwise# vreg.register(obj) will ignore ``obj``.AnAjaxFunc.__module__=implementation.__module__# relate the ``implementation`` object to its wrapper appobject# will be used by e.g.:# import base_module# @ajaxfunc# def foo(self):# return 42# assert foo(object) == 42# vreg.register_and_replace(foo, base_module.older_foo)implementation.__appobject__=AnAjaxFuncreturnimplementationdefajaxfunc(implementation=None,selector=yes(),output_type=None,check_pageid=False,regid=None):"""promote a standard function to an ``AjaxFunction`` appobject. All parameters are optional: :param selector: a custom selector object if needed, default is ``yes()`` :param output_type: either None, 'json' or 'xhtml' to customize output content-type. Default is None :param check_pageid: whether the function requires a valid `pageid` or not to proceed. Default is False. :param regid: a custom __regid__ for the created ``AjaxFunction`` object. Default is to keep the wrapped function name. ``ajaxfunc`` can be used both as a standalone decorator: .. sourcecode:: python @ajaxfunc def my_function(self): return 42 or as a parametrizable decorator: .. sourcecode:: python @ajaxfunc(output_type='json') def my_function(self): return 42 """# if used as a parametrized decorator (e.g. @ajaxfunc(output_type='json'))ifimplementationisNone:def_decorator(func):return_ajaxfunc_factory(func,selector=selector,_output_type=output_type,_check_pageid=check_pageid,regid=regid)return_decorator# else, used as a standalone decorator (i.e. @ajaxfunc)return_ajaxfunc_factory(implementation,selector=selector,_output_type=output_type,_check_pageid=check_pageid,regid=regid)################################################################################ Cubicweb remote functions for : ## - appobject rendering ## - user / page session data management ################################################################################@ajaxfunc(output_type='xhtml')defview(self):# XXX try to use the page-content templatereq=self._cwrql=req.form.get('rql')ifrql:rset=self._exec(rql)elif'eid'inreq.form:rset=self._cw.eid_rset(req.form['eid'])else:rset=Nonevid=req.form.get('vid')orvid_from_rset(req,rset,self._cw.vreg.schema)try:viewobj=self._cw.vreg['views'].select(vid,req,rset=rset)exceptNoSelectableObject:vid=req.form.get('fallbackvid','noresult')viewobj=self._cw.vreg['views'].select(vid,req,rset=rset)viewobj.set_http_cache_headers()ifreq.is_client_cache_valid():return''returnself._call_view(viewobj,paginate=req.form.pop('paginate',False))@ajaxfunc(output_type='xhtml')defcomponent(self,compid,rql,registry='components',extraargs=None):ifrql:rset=self._exec(rql)else:rset=None# XXX while it sounds good, addition of the try/except below cause pb:# when filtering using facets return an empty rset, the edition box# isn't anymore selectable, as expected. The pb is that with the# try/except below, we see a "an error occurred" message in the ui, while# we don't see it without it. Proper fix would probably be to deal with# this by allowing facet handling code to tell to js_component that such# error is expected and should'nt be reported.#try:comp=self._cw.vreg[registry].select(compid,self._cw,rset=rset,**optional_kwargs(extraargs))#except NoSelectableObject:# raise RemoteCallFailed('unselectable')returnself._call_view(comp,**optional_kwargs(extraargs))@ajaxfunc(output_type='xhtml')defrender(self,registry,oid,eid=None,selectargs=None,renderargs=None):ifeidisnotNone:rset=self._cw.eid_rset(eid)# XXX set row=0elifself._cw.form.get('rql'):rset=self._cw.execute(self._cw.form['rql'])else:rset=Noneviewobj=self._cw.vreg[registry].select(oid,self._cw,rset=rset,**optional_kwargs(selectargs))returnself._call_view(viewobj,**optional_kwargs(renderargs))@ajaxfunc(output_type='json')defi18n(self,msgids):"""returns the translation of `msgid`"""return[self._cw._(msgid)formsgidinmsgids]@ajaxfunc(output_type='json')defformat_date(self,strdate):"""returns the formatted date for `msgid`"""date=strptime(strdate,'%Y-%m-%d %H:%M:%S')returnself._cw.format_date(date)@ajaxfunc(output_type='json')defexternal_resource(self,resource):"""returns the URL of the external resource named `resource`"""returnself._cw.uiprops[resource]@ajaxfuncdefunload_page_data(self):"""remove user's session data associated to current pageid"""self._cw.session.data.pop(self._cw.pageid,None)@ajaxfunc(output_type='json')@deprecated("[3.13] use jQuery.cookie(cookiename, cookievalue, {path: '/'}) in js land instead")defset_cookie(self,cookiename,cookievalue):"""generates the Set-Cookie HTTP reponse header corresponding to `cookiename` / `cookievalue`. """cookiename,cookievalue=str(cookiename),str(cookievalue)self._cw.set_cookie(cookiename,cookievalue)@ajaxfuncdefdelete_relation(self,rtype,subjeid,objeid):rql='DELETE S %s O WHERE S eid %%(s)s, O eid %%(o)s'%rtypeself._cw.execute(rql,{'s':subjeid,'o':objeid})@ajaxfuncdefadd_relation(self,rtype,subjeid,objeid):rql='SET S %s O WHERE S eid %%(s)s, O eid %%(o)s'%rtypeself._cw.execute(rql,{'s':subjeid,'o':objeid})