Separate into 4 modules
* init_instance: load the cubicweb repository from the
`pyramid_cubicweb.instance` configuration key
* defaults: provides cw-like defaults for the authentication and session
management
* core: make cubicweb use the authentication and session management of
pyramid.
It assumes the application provides the auth policies and session factory,
and that the `cubicweb.*` registry entries are correctly initialised.
This is this only required module or pyramid_cubicweb, the other
ones are optional if the application provides its own versions of what they
do.
* bwcompat: provides a catchall route that delegate the request handling to
an old-fashion cubicweb publisher (ie using url_resolver and controllers).
Related to #4291173
--- a/pyramid_cubicweb/__init__.py Tue Jul 22 23:46:09 2014 +0200
+++ b/pyramid_cubicweb/__init__.py Thu Jul 31 17:48:32 2014 +0200
@@ -1,248 +0,0 @@
-from contextlib import contextmanager
-from warnings import warn
-
-import rql
-
-from cubicweb.web.request import CubicWebRequestBase
-from cubicweb.cwconfig import CubicWebConfiguration
-from cubicweb import repoapi
-
-import cubicweb
-import cubicweb.web
-
-from pyramid import security, httpexceptions
-from pyramid.httpexceptions import HTTPSeeOther
-
-from pyramid_cubicweb import authplugin
-
-import logging
-
-log = logging.getLogger(__name__)
-
-
-@contextmanager
-def cw_to_pyramid(request):
- """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)
- 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.'))
- 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.'))
- except (rql.BadRQLQuery, cubicweb.web.RequestError) as ex:
- raise
-
-
-class CubicWebPyramidRequest(CubicWebRequestBase):
- def __init__(self, request):
- self._request = request
-
- self.path = request.upath_info
-
- vreg = request.registry['cubicweb.appli'].vreg
- https = request.scheme == 'https'
-
- post = request.params
- headers_in = request.headers
-
- super(CubicWebPyramidRequest, self).__init__(vreg, https, post,
- headers=headers_in)
-
- 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)
-
-
-def render_view(request, vid, **kwargs):
- 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 login(request):
- repo = request.registry['cubicweb.repository']
-
- response = request.response
- user_eid = None
-
- if '__login' in request.params:
- login = request.params['__login']
- password = request.params['__password']
-
- try:
- with repo.internal_cnx() as cnx:
- user = repo.authenticate_user(cnx, login, password=password)
- user_eid = user.eid
- except cubicweb.AuthenticationError:
- raise
-
- if user_eid is not None:
- headers = security.remember(request, user_eid)
-
- raise HTTPSeeOther(
- request.params.get('postlogin_path', '/'),
- headers=headers)
-
- response.headerlist.extend(headers)
-
- response.text = render_view(request, 'login')
- return response
-
-
-def _cw_cnx(request):
- cnx = repoapi.ClientConnection(request.cw_session)
-
- def cleanup(request):
- if request.exception is not None:
- cnx.rollback()
- else:
- cnx.commit()
- cnx.__exit__(None, None, None)
-
- request.add_finished_callback(cleanup)
- cnx.__enter__()
- return cnx
-
-
-def _cw_close_session(request):
- request.cw_session.close()
-
-
-def _cw_session(request):
- """Obtains a cw session from a pyramid request"""
- repo = request.registry['cubicweb.repository']
- config = request.registry['cubicweb.config']
-
- if not request.authenticated_userid:
- login, password = config.anonymous_user()
- sessionid = repo.connect(login, password=password)
- session = repo._sessions[sessionid]
- request.add_finished_callback(_cw_close_session)
- else:
- session = request._cw_cached_session
-
- # XXX Ideally we store the cw session data in the pyramid session.
- # BUT some data in the cw session data dictionnary makes pyramid fail.
- session.data = request.session
-
- return session
-
-
-def _cw_request(request):
- req = CubicWebPyramidRequest(request)
- req.set_cnx(request.cw_cnx)
- return req
-
-
-def get_principals(login, request):
- repo = request.registry['cubicweb.repository']
-
- try:
- sessionid = repo.connect(
- str(login), __pyramid_directauth=authplugin.EXT_TOKEN)
- session = repo._sessions[sessionid]
- request._cw_cached_session = session
- request.add_finished_callback(_cw_close_session)
- except:
- log.exception("Failed")
- raise
-
- return session.user.groups
-
-
-from pyramid.authentication import SessionAuthenticationPolicy
-from pyramid.authorization import ACLAuthorizationPolicy
-from pyramid.session import SignedCookieSessionFactory
-
-
-def hello_world(request):
- request.response.text = \
- u"<html><body>Hello %s</body></html>" % request.cw_cnx.user.login
- return request.response
-
-
-def includeme(config):
- appid = config.registry.settings['cubicweb.instance']
- cwconfig = CubicWebConfiguration.config_for(appid)
-
- config.set_session_factory(
- SignedCookieSessionFactory(
- secret=config.registry.settings['session.secret']
- ))
-
- config.set_authentication_policy(
- SessionAuthenticationPolicy(callback=get_principals))
- config.set_authorization_policy(ACLAuthorizationPolicy())
-
- config.registry['cubicweb.config'] = cwconfig
- config.registry['cubicweb.repository'] = repo = cwconfig.repository()
- config.registry['cubicweb.registry'] = repo.vreg
-
- repo.system_source.add_authentifier(authplugin.DirectAuthentifier())
-
- 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)
-
- config.add_route('login', '/login')
- config.add_view(login, route_name='login')
-
- config.add_route('hello', '/hello')
- config.add_view(hello_world, route_name='hello')
-
- config.include('pyramid_cubicweb.handler')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pyramid_cubicweb/bwcompat.py Thu Jul 31 17:48:32 2014 +0200
@@ -0,0 +1,123 @@
+from pyramid import security
+from pyramid.httpexceptions import HTTPSeeOther
+from pyramid import httpexceptions
+
+import cubicweb
+import cubicweb.web
+
+from cubicweb.web.application import CubicWebPublisher
+
+from cubicweb.web import LogOut, cors
+
+from pyramid_cubicweb.core import cw_to_pyramid
+
+
+class PyramidSessionHandler(object):
+ """A CW Session handler that rely on the pyramid API to fetch the needed
+ informations"""
+
+ def __init__(self, appli):
+ self.appli = appli
+
+ def get_session(self, req):
+ return req._request.cw_session
+
+ def logout(self, req, goto_url):
+ raise LogOut(url=goto_url)
+
+
+class CubicWebPyramidHandler(object):
+ def __init__(self, appli):
+ self.appli = appli
+
+ def __call__(self, request):
+ """
+ Handler that mimics what CubicWebPublisher.main_handle_request and
+ CubicWebPublisher.core_handle do
+ """
+
+ # XXX The main handler of CW forbid anonymous https connections
+ # I guess we can drop this "feature" but in doubt I leave this comment
+ # so we don't forget about it. (cdevienne)
+
+ req = request.cw_request
+ vreg = request.registry['cubicweb.registry']
+
+ try:
+ try:
+ with cw_to_pyramid(request):
+ cors.process_request(req, vreg.config)
+ ctrlid, rset = self.appli.url_resolver.process(req, req.path)
+
+ try:
+ controller = vreg['controllers'].select(
+ ctrlid, req, appli=self.appli)
+ except cubicweb.NoSelectableObject:
+ raise httpexceptions.HTTPUnauthorized(
+ req._('not authorized'))
+
+ req.update_search_state()
+ content = controller.publish(rset=rset)
+
+ # XXX this auto-commit should be handled by the cw_request cleanup
+ # or the pyramid transaction manager.
+ # It is kept here to have the ValidationError handling bw
+ # compatible
+ if req.cnx:
+ txuuid = req.cnx.commit()
+ # commited = True
+ if txuuid is not None:
+ req.data['last_undoable_transaction'] = txuuid
+ except cors.CORSPreflight:
+ request.response.status_int = 200
+ except cubicweb.web.ValidationError as ex:
+ # XXX The validation_error_handler implementation is light, we
+ # should redo it better in cw_to_pyramid, so it can be properly
+ # handled when raised from a cubicweb view.
+ # BUT the real handling of validation errors should be done
+ # earlier in the controllers, not here. In the end, the
+ # ValidationError should never by handled here.
+ content = self.appli.validation_error_handler(req, ex)
+ except cubicweb.web.RemoteCallFailed as ex:
+ # XXX The default pyramid error handler (or one that we provide
+ # for this exception) should be enough
+ # content = self.appli.ajax_error_handler(req, ex)
+ raise
+
+ if content is not None:
+ request.response.body = content
+
+ # XXX CubicWebPyramidRequest.headers_out should
+ # access directly the pyramid response headers.
+ request.response.headers.clear()
+ for k, v in req.headers_out.getAllRawHeaders():
+ for item in v:
+ request.response.headers.add(k, item)
+
+ except LogOut as ex:
+ # The actual 'logging out' logic should be in separated function
+ # that is accessible by the pyramid views
+ headers = security.forget(request)
+ raise HTTPSeeOther(ex.url, headers=headers)
+ # except AuthenticationError:
+ # XXX I don't think it makes sens to catch this ex here (cdevienne)
+
+ return request.response
+
+
+def includeme(config):
+ # Set up a defaut route to handle non-catched urls.
+ # This is to keep legacy compatibility for cubes that makes use of the
+ # cubicweb controllers.
+ cwconfig = config.registry['cubicweb.config']
+ repository = config.registry['cubicweb.repository']
+ cwappli = CubicWebPublisher(
+ repository, cwconfig,
+ session_handler_fact=PyramidSessionHandler)
+ handler = CubicWebPyramidHandler(cwappli)
+
+ config.registry['cubicweb.appli'] = cwappli
+ config.registry['cubicweb.handler'] = handler
+
+ config.add_route('catchall', pattern='*path')
+ config.add_view(handler, route_name='catchall')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pyramid_cubicweb/core.py Thu Jul 31 17:48:32 2014 +0200
@@ -0,0 +1,223 @@
+from contextlib import contextmanager
+from warnings import warn
+
+import rql
+
+from cubicweb.web.request import CubicWebRequestBase
+from cubicweb import repoapi
+
+import cubicweb
+import cubicweb.web
+
+from pyramid import security, httpexceptions
+from pyramid.httpexceptions import HTTPSeeOther
+
+from pyramid_cubicweb import authplugin
+
+import logging
+
+log = logging.getLogger(__name__)
+
+
+@contextmanager
+def cw_to_pyramid(request):
+ """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)
+ 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.'))
+ 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.'))
+ except (rql.BadRQLQuery, cubicweb.web.RequestError) as ex:
+ raise
+
+
+class CubicWebPyramidRequest(CubicWebRequestBase):
+ def __init__(self, request):
+ self._request = request
+
+ self.path = request.upath_info
+
+ vreg = request.registry['cubicweb.appli'].vreg
+ https = request.scheme == 'https'
+
+ post = request.params
+ headers_in = request.headers
+
+ super(CubicWebPyramidRequest, self).__init__(vreg, https, post,
+ headers=headers_in)
+
+ 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)
+
+
+def render_view(request, vid, **kwargs):
+ 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 login(request):
+ repo = request.registry['cubicweb.repository']
+
+ response = request.response
+ user_eid = None
+
+ if '__login' in request.params:
+ login = request.params['__login']
+ password = request.params['__password']
+
+ try:
+ with repo.internal_cnx() as cnx:
+ user = repo.authenticate_user(cnx, login, password=password)
+ user_eid = user.eid
+ except cubicweb.AuthenticationError:
+ raise
+
+ if user_eid is not None:
+ headers = security.remember(request, user_eid)
+
+ raise HTTPSeeOther(
+ request.params.get('postlogin_path', '/'),
+ headers=headers)
+
+ response.headerlist.extend(headers)
+
+ response.text = render_view(request, 'login')
+ return response
+
+
+def _cw_cnx(request):
+ cnx = repoapi.ClientConnection(request.cw_session)
+
+ def cleanup(request):
+ if request.exception is not None:
+ cnx.rollback()
+ else:
+ cnx.commit()
+ cnx.__exit__(None, None, None)
+
+ request.add_finished_callback(cleanup)
+ cnx.__enter__()
+ return cnx
+
+
+def _cw_close_session(request):
+ request.cw_session.close()
+
+
+def _cw_session(request):
+ """Obtains a cw session from a pyramid request"""
+ repo = request.registry['cubicweb.repository']
+ config = request.registry['cubicweb.config']
+
+ if not request.authenticated_userid:
+ login, password = config.anonymous_user()
+ sessionid = repo.connect(login, password=password)
+ session = repo._sessions[sessionid]
+ request.add_finished_callback(_cw_close_session)
+ else:
+ session = request._cw_cached_session
+
+ # XXX Ideally we store the cw session data in the pyramid session.
+ # BUT some data in the cw session data dictionnary makes pyramid fail.
+ session.data = request.session
+
+ return session
+
+
+def _cw_request(request):
+ req = CubicWebPyramidRequest(request)
+ req.set_cnx(request.cw_cnx)
+ return req
+
+
+def get_principals(login, request):
+ repo = request.registry['cubicweb.repository']
+
+ try:
+ sessionid = repo.connect(
+ str(login), __pyramid_directauth=authplugin.EXT_TOKEN)
+ session = repo._sessions[sessionid]
+ request._cw_cached_session = session
+ request.add_finished_callback(_cw_close_session)
+ except:
+ log.exception("Failed")
+ raise
+
+ return session.user.groups
+
+
+def hello_world(request):
+ request.response.text = \
+ u"<html><body>Hello %s</body></html>" % request.cw_cnx.user.login
+ return request.response
+
+
+def includeme(config):
+ repo = config.registry['cubicweb.repository']
+
+ repo.system_source.add_authentifier(authplugin.DirectAuthentifier())
+
+ 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)
+
+ config.add_route('login', '/login')
+ config.add_view(login, route_name='login')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pyramid_cubicweb/defaults.py Thu Jul 31 17:48:32 2014 +0200
@@ -0,0 +1,16 @@
+from pyramid.authentication import SessionAuthenticationPolicy
+from pyramid.authorization import ACLAuthorizationPolicy
+from pyramid.session import SignedCookieSessionFactory
+
+from pyramid_cubicweb.core import get_principals
+
+
+def includeme(config):
+ config.set_session_factory(
+ SignedCookieSessionFactory(
+ secret=config.registry.settings['session.secret']
+ ))
+
+ config.set_authentication_policy(
+ SessionAuthenticationPolicy(callback=get_principals))
+ config.set_authorization_policy(ACLAuthorizationPolicy())
--- a/pyramid_cubicweb/handler.py Tue Jul 22 23:46:09 2014 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +0,0 @@
-from pyramid import security
-from pyramid.httpexceptions import HTTPSeeOther
-from pyramid import httpexceptions
-
-import cubicweb
-import cubicweb.web
-
-from cubicweb.web.application import CubicWebPublisher
-
-from cubicweb.web import LogOut, cors
-
-from pyramid_cubicweb import cw_to_pyramid
-
-
-class PyramidSessionHandler(object):
- """A CW Session handler that rely on the pyramid API to fetch the needed
- informations"""
-
- def __init__(self, appli):
- self.appli = appli
-
- def get_session(self, req):
- return req._request.cw_session
-
- def logout(self, req, goto_url):
- raise LogOut(url=goto_url)
-
-
-class CubicWebPyramidHandler(object):
- def __init__(self, appli):
- self.appli = appli
-
- def __call__(self, request):
- """
- Handler that mimics what CubicWebPublisher.main_handle_request and
- CubicWebPublisher.core_handle do
- """
-
- # XXX The main handler of CW forbid anonymous https connections
- # I guess we can drop this "feature" but in doubt I leave this comment
- # so we don't forget about it. (cdevienne)
-
- req = request.cw_request
- vreg = request.registry['cubicweb.registry']
-
- try:
- try:
- with cw_to_pyramid(request):
- cors.process_request(req, vreg.config)
- ctrlid, rset = self.appli.url_resolver.process(req, req.path)
-
- try:
- controller = vreg['controllers'].select(
- ctrlid, req, appli=self.appli)
- except cubicweb.NoSelectableObject:
- raise httpexceptions.HTTPUnauthorized(
- req._('not authorized'))
-
- req.update_search_state()
- content = controller.publish(rset=rset)
-
- # XXX this auto-commit should be handled by the cw_request cleanup
- # or the pyramid transaction manager.
- # It is kept here to have the ValidationError handling bw
- # compatible
- if req.cnx:
- txuuid = req.cnx.commit()
- # commited = True
- if txuuid is not None:
- req.data['last_undoable_transaction'] = txuuid
- except cors.CORSPreflight:
- request.response.status_int = 200
- except cubicweb.web.ValidationError as ex:
- # XXX The validation_error_handler implementation is light, we
- # should redo it better in cw_to_pyramid, so it can be properly
- # handled when raised from a cubicweb view.
- # BUT the real handling of validation errors should be done
- # earlier in the controllers, not here. In the end, the
- # ValidationError should never by handled here.
- content = self.appli.validation_error_handler(req, ex)
- except cubicweb.web.RemoteCallFailed as ex:
- # XXX The default pyramid error handler (or one that we provide
- # for this exception) should be enough
- # content = self.appli.ajax_error_handler(req, ex)
- raise
-
- if content is not None:
- request.response.body = content
-
- # XXX CubicWebPyramidRequest.headers_out should
- # access directly the pyramid response headers.
- request.response.headers.clear()
- for k, v in req.headers_out.getAllRawHeaders():
- for item in v:
- request.response.headers.add(k, item)
-
- except LogOut as ex:
- # The actual 'logging out' logic should be in separated function
- # that is accessible by the pyramid views
- headers = security.forget(request)
- raise HTTPSeeOther(ex.url, headers=headers)
- # except AuthenticationError:
- # XXX I don't think it makes sens to catch this ex here (cdevienne)
-
- return request.response
-
-
-def includeme(config):
- # Set up a defaut route to handle non-catched urls.
- # This is to keep legacy compatibility for cubes that makes use of the
- # cubicweb controllers.
- cwconfig = config.registry['cubicweb.config']
- repository = config.registry['cubicweb.repository']
- cwappli = CubicWebPublisher(
- repository, cwconfig,
- session_handler_fact=PyramidSessionHandler)
- handler = CubicWebPyramidHandler(cwappli)
-
- config.registry['cubicweb.appli'] = cwappli
- config.registry['cubicweb.handler'] = handler
-
- config.add_route('catchall', pattern='*path')
- config.add_view(handler, route_name='catchall')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pyramid_cubicweb/init_instance.py Thu Jul 31 17:48:32 2014 +0200
@@ -0,0 +1,10 @@
+from cubicweb.cwconfig import CubicWebConfiguration
+
+
+def includeme(config):
+ appid = config.registry.settings['cubicweb.instance']
+ cwconfig = CubicWebConfiguration.config_for(appid)
+
+ config.registry['cubicweb.config'] = cwconfig
+ config.registry['cubicweb.repository'] = repo = cwconfig.repository()
+ config.registry['cubicweb.registry'] = repo.vreg
--- a/sampleapp/development.ini Tue Jul 22 23:46:09 2014 +0200
+++ b/sampleapp/development.ini Thu Jul 31 17:48:32 2014 +0200
@@ -13,7 +13,6 @@
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
- pyramid_cubicweb
cubicweb.instance = test
--- a/sampleapp/sampleapp/__init__.py Tue Jul 22 23:46:09 2014 +0200
+++ b/sampleapp/sampleapp/__init__.py Thu Jul 31 17:48:32 2014 +0200
@@ -5,6 +5,10 @@
""" This function returns a Pyramid WSGI application.
"""
config = Configurator(settings=settings)
+ config.include('pyramid_cubicweb.init_instance')
+ config.include('pyramid_cubicweb.defaults')
+ config.include('pyramid_cubicweb.core')
+ config.include('pyramid_cubicweb.bwcompat')
# config.add_static_view('static', 'static', cache_max_age=3600)
# config.add_route('home', '/')
# config.scan()