--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/pyramid/core.py Mon Sep 26 14:52:12 2016 +0200
@@ -0,0 +1,400 @@
+import itertools
+
+from contextlib import contextmanager
+from warnings import warn
+from cgi import FieldStorage
+
+import rql
+
+from cubicweb.web.request import CubicWebRequestBase
+from cubicweb import repoapi
+
+import cubicweb
+import cubicweb.web
+from cubicweb.server import session as cwsession
+
+from pyramid import httpexceptions
+
+from cubicweb.pyramid import tools
+
+import logging
+
+log = logging.getLogger(__name__)
+
+
+CW_321 = cubicweb.__pkginfo__.numversion >= (3, 21, 0)
+
+
+class Connection(cwsession.Connection):
+ """ A specialised Connection that access the session data through a
+ property.
+
+ This behavior makes sure the actual session data is not loaded until
+ actually accessed.
+ """
+ def __init__(self, session, *args, **kw):
+ super(Connection, self).__init__(session, *args, **kw)
+ self._session = session
+
+ def _get_session_data(self):
+ return self._session.data
+
+ def _set_session_data(self, data):
+ pass
+
+ _session_data = property(_get_session_data, _set_session_data)
+
+
+class Session(cwsession.Session):
+ """ A Session that access the session data through a property.
+
+ Along with :class:`Connection`, it avoid any load of the pyramid session
+ data until it is actually accessed.
+ """
+ def __init__(self, pyramid_request, user, repo):
+ super(Session, self).__init__(user, repo)
+ self._pyramid_request = pyramid_request
+
+ def get_data(self):
+ if not getattr(self, '_protect_data_access', False):
+ self._data_accessed = True
+ return self._pyramid_request.session
+
+ def set_data(self, data):
+ if getattr(self, '_data_accessed', False):
+ self._pyramid_request.session.clear()
+ self._pyramid_request.session.update(data)
+
+ data = property(get_data, set_data)
+
+ def new_cnx(self):
+ self._protect_data_access = True
+ try:
+ return Connection(self)
+ finally:
+ self._protect_data_access = False
+
+
+def cw_headers(request):
+ return itertools.chain(
+ *[[(k, item) for item in v]
+ for k, v in request.cw_request.headers_out.getAllRawHeaders()])
+
+
+@contextmanager
+def cw_to_pyramid(request):
+ """ Context manager to wrap a call to the cubicweb API.
+
+ All CW exceptions will be transformed into their pyramid equivalent.
+ When needed, some CW reponse bits may be converted too (mainly headers)"""
+ try:
+ yield
+ except cubicweb.web.Redirect as ex:
+ assert 300 <= ex.status < 400
+ raise httpexceptions.status_map[ex.status](
+ ex.location, headers=cw_headers(request))
+ except cubicweb.web.StatusResponse as ex:
+ warn('[3.16] StatusResponse is deprecated use req.status_out',
+ DeprecationWarning, stacklevel=2)
+ request.body = ex.content
+ request.status_int = ex.status
+ except cubicweb.web.Unauthorized as ex:
+ raise httpexceptions.HTTPForbidden(
+ request.cw_request._(
+ 'You\'re not authorized to access this page. '
+ 'If you think you should, please contact the site '
+ 'administrator.'),
+ headers=cw_headers(request))
+ except cubicweb.web.Forbidden:
+ raise httpexceptions.HTTPForbidden(
+ request.cw_request._(
+ 'This action is forbidden. '
+ 'If you think it should be allowed, please contact the site '
+ 'administrator.'),
+ headers=cw_headers(request))
+ except (rql.BadRQLQuery, cubicweb.web.RequestError) as ex:
+ raise
+
+
+class CubicWebPyramidRequest(CubicWebRequestBase):
+ """ A CubicWeb request that only wraps a pyramid request.
+
+ :param request: A pyramid request
+
+ """
+ def __init__(self, request):
+ self._request = request
+
+ self.path = request.upath_info
+
+ vreg = request.registry['cubicweb.registry']
+ https = request.scheme == 'https'
+
+ post = request.params.mixed()
+ headers_in = request.headers
+
+ super(CubicWebPyramidRequest, self).__init__(vreg, https, post,
+ headers=headers_in)
+
+ self.content = request.body_file_seekable
+
+ def setup_params(self, params):
+ self.form = {}
+ for param, val in params.items():
+ if param in self.no_script_form_params and val:
+ val = self.no_script_form_param(param, val)
+ if isinstance(val, FieldStorage) and val.file:
+ val = (val.filename, val.file)
+ if param == '_cwmsgid':
+ self.set_message_id(val)
+ elif param == '__message':
+ warn('[3.13] __message in request parameter is deprecated '
+ '(may only be given to .build_url). Seeing this message '
+ 'usualy means your application hold some <form> where '
+ 'you should replace use of __message hidden input by '
+ 'form.set_message, so new _cwmsgid mechanism is properly '
+ 'used',
+ DeprecationWarning)
+ self.set_message(val)
+ else:
+ self.form[param] = val
+
+ def is_secure(self):
+ return self._request.scheme == 'https'
+
+ def relative_path(self, includeparams=True):
+ path = self._request.path[1:]
+ if includeparams and self._request.query_string:
+ return '%s?%s' % (path, self._request.query_string)
+ return path
+
+ def instance_uri(self):
+ return self._request.application_url
+
+ def get_full_path(self):
+ path = self._request.path
+ if self._request.query_string:
+ return '%s?%s' % (path, self._request.query_string)
+ return path
+
+ def http_method(self):
+ return self._request.method
+
+ def _set_status_out(self, value):
+ self._request.response.status_int = value
+
+ def _get_status_out(self):
+ return self._request.response.status_int
+
+ status_out = property(_get_status_out, _set_status_out)
+
+ @property
+ def message(self):
+ """Returns a '<br>' joined list of the cubicweb current message and the
+ default pyramid flash queue messages.
+ """
+ return u'\n<br>\n'.join(
+ self._request.session.pop_flash()
+ + self._request.session.pop_flash('cubicweb'))
+
+ def set_message(self, msg):
+ self.reset_message()
+ self._request.session.flash(msg, 'cubicweb')
+
+ def set_message_id(self, msgid):
+ self.reset_message()
+ self.set_message(
+ self._request.session.pop(msgid, u''))
+
+ def reset_message(self):
+ self._request.session.pop_flash('cubicweb')
+
+
+def render_view(request, vid, **kwargs):
+ """ Helper function to render a CubicWeb view.
+
+ :param request: A pyramid request
+ :param vid: A CubicWeb view id
+ :param **kwargs: Keyword arguments to select and instanciate the view
+ :returns: The rendered view content
+ """
+ vreg = request.registry['cubicweb.registry']
+ # XXX The select() function could, know how to handle a pyramid
+ # request, and feed it directly to the views that supports it.
+ # On the other hand, we could refine the View concept and decide it works
+ # with a cnx, and never with a WebRequest
+
+ with cw_to_pyramid(request):
+ view = vreg['views'].select(vid, request.cw_request, **kwargs)
+ view.set_stream()
+ view.render()
+ return view._stream.getvalue()
+
+
+def _cw_cnx(request):
+ """ Obtains a cw session from a pyramid request
+
+ The connection will be commited or rolled-back in a request finish
+ callback (this is temporary, we should make use of the transaction manager
+ in a later version).
+
+ Not meant for direct use, use ``request.cw_cnx`` instead.
+
+ :param request: A pyramid request
+ :returns type: :class:`cubicweb.server.session.Connection`
+ """
+ session = request.cw_session
+ if session is None:
+ return None
+
+ if CW_321:
+ cnx = session.new_cnx()
+
+ def commit_state(cnx):
+ return cnx.commit_state
+ else:
+ cnx = repoapi.ClientConnection(session)
+
+ def commit_state(cnx):
+ return cnx._cnx.commit_state
+
+ def cleanup(request):
+ try:
+ if (request.exception is not None and not isinstance(
+ request.exception, (
+ httpexceptions.HTTPSuccessful,
+ httpexceptions.HTTPRedirection))):
+ cnx.rollback()
+ elif commit_state(cnx) == 'uncommitable':
+ cnx.rollback()
+ else:
+ cnx.commit()
+ finally:
+ cnx.__exit__(None, None, None)
+
+ request.add_finished_callback(cleanup)
+ cnx.__enter__()
+ return cnx
+
+
+def repo_connect(request, repo, eid):
+ """A lightweight version of
+ :meth:`cubicweb.server.repository.Repository.connect` that does not keep
+ track of opened sessions, removing the need of closing them"""
+ user = tools.cached_build_user(repo, eid)
+ session = Session(request, user, repo)
+ tools.cnx_attach_entity(session, user)
+ # Calling the hooks should be done only once, disabling it completely for
+ # now
+ #with session.new_cnx() as cnx:
+ #repo.hm.call_hooks('session_open', cnx)
+ #cnx.commit()
+ # repo._sessions[session.sessionid] = session
+ return session
+
+
+def _cw_session(request):
+ """Obtains a cw session from a pyramid request
+
+ :param request: A pyramid request
+ :returns type: :class:`cubicweb.server.session.Session`
+
+ Not meant for direct use, use ``request.cw_session`` instead.
+ """
+ repo = request.registry['cubicweb.repository']
+
+ if not request.authenticated_userid:
+ eid = request.registry.get('cubicweb.anonymous_eid')
+ if eid is None:
+ return None
+ session = repo_connect(request, repo, eid=eid)
+ else:
+ session = request._cw_cached_session
+
+ return session
+
+
+def _cw_request(request):
+ """ Obtains a CubicWeb request wrapper for the pyramid request.
+
+ :param request: A pyramid request
+ :return: A CubicWeb request
+ :returns type: :class:`CubicWebPyramidRequest`
+
+ Not meant for direct use, use ``request.cw_request`` instead.
+
+ """
+ req = CubicWebPyramidRequest(request)
+ cnx = request.cw_cnx
+ if cnx is not None:
+ req.set_cnx(request.cw_cnx)
+ return req
+
+
+def get_principals(login, request):
+ """ Returns the group names of the authenticated user.
+
+ This function is meant to be used as an authentication policy callback.
+
+ It also pre-open the cubicweb session and put it in
+ request._cw_cached_session for later usage by :func:`_cw_session`.
+
+ .. note::
+
+ If the default authentication policy is not used, make sure this
+ function gets called by the active authentication policy.
+
+ :param login: A cubicweb user eid
+ :param request: A pyramid request
+ :returns: A list of group names
+ """
+ repo = request.registry['cubicweb.repository']
+
+ try:
+ session = repo_connect(request, repo, eid=login)
+ request._cw_cached_session = session
+ except:
+ log.exception("Failed")
+ raise
+
+ return session.user.groups
+
+
+def includeme(config):
+ """ Enables the core features of Pyramid CubicWeb.
+
+ Automatically called by the 'pyramid' command, or via
+ ``config.include('cubicweb.pyramid.code')``. In the later case,
+ the following registry entries must be defined first:
+
+ 'cubicweb.config'
+ A cubicweb 'config' instance.
+
+ 'cubicweb.repository'
+ The correponding cubicweb repository.
+
+ 'cubicweb.registry'
+ The vreg.
+ """
+ repo = config.registry['cubicweb.repository']
+
+ with repo.internal_cnx() as cnx:
+ login = config.registry['cubicweb.config'].anonymous_user()[0]
+ if login is not None:
+ config.registry['cubicweb.anonymous_eid'] = cnx.find(
+ 'CWUser', login=login).one().eid
+
+ config.add_request_method(
+ _cw_session, name='cw_session', property=True, reify=True)
+ config.add_request_method(
+ _cw_cnx, name='cw_cnx', property=True, reify=True)
+ config.add_request_method(
+ _cw_request, name='cw_request', property=True, reify=True)
+
+ cwcfg = config.registry['cubicweb.config']
+ for cube in cwcfg.cubes():
+ pkgname = 'cubes.' + cube
+ mod = __import__(pkgname)
+ mod = getattr(mod, cube)
+ if hasattr(mod, 'includeme'):
+ config.include('cubes.' + cube)