# HG changeset patch # User Sylvain Thénault # Date 1327318761 -3600 # Node ID e52a084e955c0cec048ba265935ed2ede6aadb0f # Parent 77aa753dcd6bd87f2d805f17e9abf40612ded743# Parent 2d62b601770087c9f99043e1d9c0a0615aad3c4b backport stable diff -r 77aa753dcd6b -r e52a084e955c devtools/__init__.py --- 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 diff -r 77aa753dcd6b -r e52a084e955c doc/book/en/devrepo/testing.rst --- 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 diff -r 77aa753dcd6b -r e52a084e955c doc/book/en/images/request_session.png Binary file doc/book/en/images/request_session.png has changed diff -r 77aa753dcd6b -r e52a084e955c doc/book/en/images/request_session.svg --- /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 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + Repository + + DB API + connection + session + Client + request + database connection + + Database + + + diff -r 77aa753dcd6b -r e52a084e955c hooks/syncschema.py --- 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 diff -r 77aa753dcd6b -r e52a084e955c test/data_schemareader/schema.py --- /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',)} + + diff -r 77aa753dcd6b -r e52a084e955c test/unittest_schema.py --- 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): diff -r 77aa753dcd6b -r e52a084e955c view.py --- 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): diff -r 77aa753dcd6b -r e52a084e955c web/data/excanvas.js --- 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': diff -r 77aa753dcd6b -r e52a084e955c web/httpcache.py --- 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): diff -r 77aa753dcd6b -r e52a084e955c web/request.py --- 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 diff -r 77aa753dcd6b -r e52a084e955c web/views/startup.py --- 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') )