--- a/devtools/__init__.py Tue Jan 17 11:55:32 2012 +0100
+++ b/devtools/__init__.py Mon Jan 23 12:39:21 2012 +0100
@@ -620,10 +620,12 @@
backup_name = self._backup_name(db_id)
self._drop(backup_name)
self.system_source['db-name'] = backup_name
- self._repo.turn_repo_off()
+ # during postgres database initialization, there is no repo set here.
+ assert self._repo is None
+ #self._repo.turn_repo_off()
createdb(self.helper, self.system_source, self.dbcnx, self.cursor, template=orig_name)
self.dbcnx.commit()
- self._repo.turn_repo_on()
+ #self._repo.turn_repo_on()
return backup_name
finally:
self.system_source['db-name'] = orig_name
--- a/doc/book/en/devrepo/testing.rst Tue Jan 17 11:55:32 2012 +0100
+++ b/doc/book/en/devrepo/testing.rst Mon Jan 23 12:39:21 2012 +0100
@@ -460,3 +460,127 @@
``````````````
.. autoclass:: cubicweb.devtools.testlib.CubicWebTC
:members:
+
+
+What you need to know about request and session
+-----------------------------------------------
+
+
+.. image:: ../images/request_session.png
+
+First, remember to think that some code run on a client side, some
+other on the repository side. More precisely:
+
+* client side: web interface, raw db-api connection (cubicweb-ctl shell for
+ instance);
+
+* repository side: RQL query execution, that may trigger hooks and operation.
+
+The client interact with the repository through a db-api connection.
+
+
+A db-api connection is tied to a session in the repository. The connection and
+request objects are unaccessible from repository code / the session object is
+unaccessible from client code (theorically at least).
+
+The :mod:`cubicweb.dbapi` module provides a base request class. The web interface
+provides an extended request class.
+
+
+The `request` object provides access to all cubicweb resources, eg:
+
+* the registry (which itself provides access to the schema and the
+ configuration);
+
+* an underlying db-api connection (when using req.execute, you actually call the
+ db-api);
+
+* other specific resources depending on the client type (url generation according
+ to base url, form parameters, etc.).
+
+
+A `session` provides an api similar to a request regarding RQL execution and
+access to global resources (registry and all), but also have the following
+responsibilities:
+
+* handle transaction data, that will live during the time of a single
+ transaction. This includes the database connections that will be used to
+ execute RQL queries.
+
+* handle persistent data that may be used across different (web) requests
+
+* security and hooks control (not possible through a request)
+
+
+The `_cw` attribute
+```````````````````
+The `_cw` attribute available on every application object provides access to all
+cubicweb resources, eg:
+
+For code running on the client side (eg web interface view), `_cw` is a request
+instance.
+
+For code running on the repository side (hooks and operation), `_cw` is a session
+instance.
+
+
+Beware some views may be called with a session (eg notifications) or with a
+DB-API request. In the later case, see :meth:`use_web_compatible_requests` on
+:class:`Connection` instances.
+
+
+Request, session and transaction
+````````````````````````````````
+
+In the web interface, an HTTP request is handle by a single request, which will
+be thrown way once the response send.
+
+The web publisher handle the transaction:
+
+* commit / rollback is done automatically
+* you should not commit / rollback explicitly
+
+When using a raw db-api, you're on your own regarding transaction.
+
+On the other hand, db-api connection and session live from a user login to its logout.
+
+Because session lives for a long time, and database connections is a limited
+resource, we can't bound a session to its own database connection for all its
+lifetime. The repository handles a pool of connections (4 by default), and it's
+responsible to attribute them as needed.
+
+Let's detail the process:
+
+1. an incoming RQL query comes from a client to the repository
+
+2. the repository attributes a database connection to the session
+
+3. the repository's querier execute the query
+
+4. this query may trigger hooks. Hooks and operation may execute some rql queries
+ through `_cw.execute`. Those queries go directly to the querier, hence don't
+ touch the database connection, they use the one attributed in 2.
+
+5. the repository's get the result of the query in 1. If it was a RQL read query,
+ the database connection is released. If it was a write query, the connection
+ is then tied to the session until the transaction is commited or rollbacked.
+
+6. results are sent back to the client
+
+This implies several things:
+
+* when using a request, or code executed in hooks, this database connection
+ handling is totally transparent
+
+* however, take care when writing test: you are usually faking / testing both the
+ server and the client side, so you have to decide when to use self.request() /
+ self.session. Ask yourself "where the code I want to test will be running,
+ client or repository side ?". The response is usually : use a request :)
+ However, if you really need using a session:
+
+ - commit / rollback will free the database connection (unless explicitly told
+ not to do so).
+
+ - if you issue a query after that without asking for a database connection
+ (`session.get_cnxset()`), you will end up with a 'None type has no attribute
+ source()' error
Binary file doc/book/en/images/request_session.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/images/request_session.svg Mon Jan 23 12:39:21 2012 +0100
@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="85.960938"
+ height="12.382812"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.1 r9760"
+ sodipodi:docname="request_session.svg">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lend"
+ style="overflow:visible;">
+ <path
+ id="path3822"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ transform="scale(0.8) rotate(180) translate(12.5,0)" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.98994949"
+ inkscape:cx="25.928992"
+ inkscape:cy="-185.87004"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="766"
+ inkscape:window-height="1151"
+ inkscape:window-x="1152"
+ inkscape:window-y="24"
+ inkscape:window-maximized="0"
+ inkscape:snap-global="true" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-263.52249,-495.73373)">
+ <rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.92460138;stroke-opacity:1"
+ id="rect3773"
+ width="214.15233"
+ height="184.80336"
+ x="57.578697"
+ y="366.01306" />
+ <rect
+ id="rect2985"
+ width="216.86372"
+ height="183.54575"
+ x="348.50262"
+ y="367.78079"
+ style="fill:#ffffff;stroke:#000000;stroke-width:0.55298227;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-size:32px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="376.7869"
+ y="399.80365"
+ id="text3755"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3757"
+ x="376.7869"
+ y="399.80365">Repository</tspan></text>
+ <rect
+ style="fill:#ffffff;stroke:#000000;stroke-opacity:1"
+ id="rect3759"
+ width="144.45181"
+ height="104.04572"
+ x="237.38585"
+ y="423.03714" />
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="262.63968"
+ y="470.51431"
+ id="text3761"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3763"
+ x="262.63968"
+ y="470.51431">DB API</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="262.63968"
+ y="507.88998"
+ id="text3765"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3767"
+ x="262.63968"
+ y="507.88998">connection</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="419.21332"
+ y="509.91025"
+ id="text3769"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3771"
+ x="419.21332"
+ y="509.91025">session</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:32px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="102.02541"
+ y="397.78333"
+ id="text3775"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3777"
+ x="102.02541"
+ y="397.78333">Client</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="116.16754"
+ y="507.88995"
+ id="text3779"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3781"
+ x="116.16754"
+ y="507.88995">request</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:16px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="361.50729"
+ y="585.89832"
+ id="text3802"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3804"
+ x="361.50729"
+ y="585.89832">database </tspan><tspan
+ sodipodi:role="line"
+ x="361.50729"
+ y="605.89832"
+ id="tspan3806">connection</tspan></text>
+ <rect
+ style="fill:#ffffff;stroke:#000000;stroke-width:1.48014534;stroke-opacity:1"
+ id="rect3808"
+ width="192.09367"
+ height="58.095726"
+ x="365.79443"
+ y="621.50018" />
+ <text
+ xml:space="preserve"
+ style="font-size:36px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="369.5885"
+ y="662.66992"
+ id="text3810"
+ sodipodi:linespacing="125%"><tspan
+ sodipodi:role="line"
+ id="tspan3812"
+ x="369.5885"
+ y="662.66992">Database</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:none"
+ d="M 197.57252,125.76645 195.76971,55.592808"
+ id="path4260"
+ inkscape:connector-type="polyline"
+ inkscape:connector-curvature="3"
+ inkscape:connection-start="#rect3808"
+ inkscape:connection-start-point="d4"
+ inkscape:connection-end="#rect2985"
+ inkscape:connection-end-point="d4"
+ transform="translate(263.52249,495.73373)" />
+ </g>
+</svg>
--- a/hooks/syncschema.py Tue Jan 17 11:55:32 2012 +0100
+++ b/hooks/syncschema.py Mon Jan 23 12:39:21 2012 +0100
@@ -487,13 +487,17 @@
# set default value, using sql for performance and to avoid
# modification_date update
if default:
- if rdefdef.object in ('Date', 'Datetime'):
+ if rdefdef.object in ('Date', 'Datetime', 'TZDatetime'):
+ # XXX may may want to use creation_date
if default == 'TODAY':
default = syssource.dbhelper.sql_current_date()
elif default == 'NOW':
default = syssource.dbhelper.sql_current_timestamp()
- session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
- {'default': default})
+ session.system_sql('UPDATE %s SET %s=%(default)s'
+ % (table, column, default))
+ else:
+ session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
+ {'default': default})
def revertprecommit_event(self):
# revert changes on in memory schema
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data_schemareader/schema.py Mon Jan 23 12:39:21 2012 +0100
@@ -0,0 +1,11 @@
+from cubicweb.schemas.base import in_group, CWSourceSchemaConfig
+# copy __permissions__ to avoid modifying a shared dictionary
+in_group.__permissions__ = in_group.__permissions__.copy()
+in_group.__permissions__['read'] = ('managers',)
+
+cw_for_source = CWSourceSchemaConfig.get_relation('cw_for_source')
+cw_for_source.__permissions__ = {'read': ('managers', 'users'),
+ 'add': ('managers',),
+ 'delete': ('managers',)}
+
+
--- a/test/unittest_schema.py Tue Jan 17 11:55:32 2012 +0100
+++ b/test/unittest_schema.py Mon Jan 23 12:39:21 2012 +0100
@@ -265,6 +265,20 @@
schema = CubicWebSchema('Test Schema')
schema.add_entity_type(EntityType('NaN'))
+ def test_relation_perm_overriding(self):
+ loader = CubicWebSchemaLoader()
+ config = TestConfiguration('data', apphome=join(dirname(__file__), 'data_schemareader'))
+ config.bootstrap_cubes()
+ schema = loader.load(config)
+ self.assertEqual(schema['in_group'].rdefs.values()[0].permissions,
+ {'read': ('managers',),
+ 'add': ('managers',),
+ 'delete': ('managers',)})
+ self.assertEqual(schema['cw_for_source'].rdefs.values()[0].permissions,
+ {'read': ('managers', 'users'),
+ 'add': ('managers',),
+ 'delete': ('managers',)})
+
class BadSchemaTC(TestCase):
def setUp(self):
--- a/view.py Tue Jan 17 11:55:32 2012 +0100
+++ b/view.py Mon Jan 23 12:39:21 2012 +0100
@@ -417,6 +417,10 @@
"""return some rql to be executed if the result set is None"""
return self.default_rql
+ def no_entities(self, **kwargs):
+ """override to display something when no entities were found"""
+ pass
+
def call(self, **kwargs):
"""override call to execute rql returned by the .startup_rql method if
necessary
@@ -424,8 +428,11 @@
rset = self.cw_rset
if rset is None:
rset = self.cw_rset = self._cw.execute(self.startup_rql())
- for i in xrange(len(rset)):
- self.wview(self.__regid__, rset, row=i, **kwargs)
+ if rset:
+ for i in xrange(len(rset)):
+ self.wview(self.__regid__, rset, row=i, **kwargs)
+ else:
+ self.no_entities(**kwargs)
class AnyRsetView(View):
--- a/web/data/excanvas.js Tue Jan 17 11:55:32 2012 +0100
+++ b/web/data/excanvas.js Mon Jan 23 12:39:21 2012 +0100
@@ -1375,7 +1375,7 @@
case null:
case '':
this.repetition_ = 'repeat';
- break
+ break;
case 'repeat-x':
case 'repeat-y':
case 'no-repeat':
--- a/web/httpcache.py Tue Jan 17 11:55:32 2012 +0100
+++ b/web/httpcache.py Mon Jan 23 12:39:21 2012 +0100
@@ -23,6 +23,7 @@
from datetime import datetime
# time delta usable to convert localized time to GMT time
+# XXX this become erroneous after a DST transition!!!
GMTOFFSET = - (datetime.now() - datetime.utcnow())
class NoHTTPCacheManager(object):
--- a/web/request.py Tue Jan 17 11:55:32 2012 +0100
+++ b/web/request.py Mon Jan 23 12:39:21 2012 +0100
@@ -550,6 +550,10 @@
assert expires is None, 'both max age and expires cant be specified'
expires = maxage + time.time()
elif expires:
+ # we don't want to handle times before the EPOCH (cause bug on
+ # windows). Also use > and not >= else expires == 0 and Cookie think
+ # that means no expire...
+ assert expires + GMTOFFSET > date(1970, 1, 1)
expires = timegm((expires + GMTOFFSET).timetuple())
else:
expires = None
@@ -564,11 +568,7 @@
warn('[3.13] remove_cookie now take only a name as argument',
DeprecationWarning, stacklevel=2)
name = bwcompat
- self.set_cookie(name, '', maxage=0,
- # substracting GMTOFFSET because set_cookie
- # expects a localtime and we don't want to
- # handle times before the EPOCH
- expires=date(1970, 1, 1) - GMTOFFSET)
+ self.set_cookie(name, '', maxage=0, expires=date(2000, 1, 1))
def set_content_type(self, content_type, filename=None, encoding=None):
"""set output content type for this request. An optional filename
--- a/web/views/startup.py Tue Jan 17 11:55:32 2012 +0100
+++ b/web/views/startup.py Mon Jan 23 12:39:21 2012 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -53,7 +53,8 @@
add_etype_links = ()
skip_startup_views = set( ('index', 'manage', 'schema', 'owl', 'changelog',
'systempropertiesform', 'propertiesform',
- 'cw.user-management', 'cw.source-management',
+ 'cw.users-and-groups-management', 'cw.groups-management',
+ 'cw.users-management', 'cw.sources-management',
'siteinfo', 'info', 'registry', 'gc',
'tree') )