# HG changeset patch # User Alexandre Fayolle # Date 1270825285 0 # Node ID 1c635a91e403b4daf63bbc73f5e707b325d0efef # Parent 52c9d25101a15d33256363f98945994f72a9a48f# Parent 395f076512a17c2ef669df08064e056f76669c06 merge diff -r 52c9d25101a1 -r 1c635a91e403 devtools/__init__.py --- a/devtools/__init__.py Fri Apr 09 15:01:14 2010 +0000 +++ b/devtools/__init__.py Fri Apr 09 15:01:25 2010 +0000 @@ -158,6 +158,9 @@ sources = super(TestServerConfiguration, self).sources() if not sources: sources = DEFAULT_SOURCES + if sources['system']['db-driver'] == 'sqlite': + # we need an abspath in case tests are changing the cwd + sources['system']['db-name'] = abspath(sources['system']['db-name']) return sources diff -r 52c9d25101a1 -r 1c635a91e403 doc/book/en/.static/sphinx-default.css --- a/doc/book/en/.static/sphinx-default.css Fri Apr 09 15:01:14 2010 +0000 +++ b/doc/book/en/.static/sphinx-default.css Fri Apr 09 15:01:25 2010 +0000 @@ -3,7 +3,7 @@ */ html, body { - background: white; + background: white; } body { @@ -115,7 +115,7 @@ } div.sphinxsidebar h3 { - font-family: 'Verdanda', sans-serif; + font-family: Verdana, sans-serif; color: black; font-size: 1.2em; font-weight: normal; @@ -126,7 +126,7 @@ } div.sphinxsidebar h4 { - font-family: 'Verdana', sans-serif; + font-family: Verdana, sans-serif; color: black; font-size: 1.1em; font-weight: normal; diff -r 52c9d25101a1 -r 1c635a91e403 doc/book/en/annexes/rql/intro.rst --- a/doc/book/en/annexes/rql/intro.rst Fri Apr 09 15:01:14 2010 +0000 +++ b/doc/book/en/annexes/rql/intro.rst Fri Apr 09 15:01:25 2010 +0000 @@ -5,10 +5,10 @@ Goals of RQL ~~~~~~~~~~~~ -The goal is to have a language emphasizing the way of browsing relations. As -such, attributes will be regarded as cases of special relations (in terms of -implementation, the user should see no difference between an attribute and a -relation). +The goal is to have a language making relations browsing easy. As +such, attributes will be regarded as cases of special relations (in +terms of usage, the user should see no syntactic difference between an +attribute and a relation). Comparison with existing languages ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -32,10 +32,10 @@ We should look in more detail, but here are already some ideas for the moment ... Versa_ is the language most similar to what we wanted to do, but the model -underlying data being RDF, there is some number of things such as namespaces or +underlying data being RDF, there are some things such as namespaces or handling of the RDF types which does not interest us. On the functionality level, Versa_ is very comprehensive including through many functions of -conversion and basic types manipulation, which may need to be guided at one time +conversion and basic types manipulation, which we may want to look at one time or another. Finally, the syntax is a little esoteric. diff -r 52c9d25101a1 -r 1c635a91e403 doc/book/en/development/devcore/dbapi.rst --- a/doc/book/en/development/devcore/dbapi.rst Fri Apr 09 15:01:14 2010 +0000 +++ b/doc/book/en/development/devcore/dbapi.rst Fri Apr 09 15:01:25 2010 +0000 @@ -5,9 +5,10 @@ The Python API developped to interface with RQL is inspired from the standard db-api, with a Connection object having the methods cursor, rollback and commit essentially. -The most important method is the `execute` method of a cursor : +The most important method is the `execute` method of a cursor. -`execute(rqlstring, args=None, cachekey=None, build_descr=True)` +.. sourcecode:: python + execute(rqlstring, args=None, cachekey=None, build_descr=True) :rqlstring: the RQL query to execute (unicode) :args: if the query contains substitutions, a dictionary containing the values to use @@ -18,10 +19,11 @@ through this argument -The `Connection` object owns the methods `commit` and `rollback`. You *should -never need to use them* during the development of the web interface based on -the *CubicWeb* framework as it determines the end of the transaction depending -on the query execution success. +The `Connection` object owns the methods `commit` and `rollback`. You +*should never need to use them* during the development of the web +interface based on the *CubicWeb* framework as it determines the end +of the transaction depending on the query execution success. They are +however useful in other contexts such as tests. .. note:: While executing update queries (SET, INSERT, DELETE), if a query generates @@ -30,6 +32,7 @@ Executing RQL queries from a view or a hook ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + When you're within code of the web interface, the db-api like connexion is handled by the request object. You should not have to access it directly, but use the `execute` method directly available on the request, eg: @@ -50,33 +53,30 @@ self._cw.execute('Any T WHERE T in_conf C, C eid %s' % entity.eid) -But it can also be written in a syntax that will benefit from the use +But it must be written in a syntax that will benefit from the use of a cache on the RQL server side: .. sourcecode:: python - self._cw.execute('Any T WHERE T in_conf C, C eid %(x)s', {'x': entity.eid}, 'x') + self._cw.execute('Any T WHERE T in_conf C, C eid %(x)s', {'x': entity.eid}) -Beside proper usage of the `args` argument, notice the latest argument: this is what's called -the cache key. The cache key should be either a string or a tuple containing the names of keys -in args which are referencing eids. *YOU MUST SET THIS PROPERLY* if you don't want weird result -on queries which have ambigous solutions deambiguified by specifing an eid. So the good habit is: -*always put in the cache key all eid keys*. +The syntax tree is built once for the "generic" RQL and can be re-used +with a number of different eids. There rql IN operator is an exception +to this rule. -The syntax tree is build once for the "generic" RQL and can be re-used -with a number of different eid. +.. sourcecode:: python + + self._cw.execute('Any T WHERE T in_conf C, C name IN (%s)' % ','.join(['foo', 'bar'])) -Alternativelly, some of the common data related to an entity can be obtained from -the top-level `entity.related()` method (which is used under the hood by the orm -when you use attribute access notation on an entity to get a relation. The above -would then be translated to: +Alternativelly, some of the common data related to an entity can be +obtained from the `entity.related()` method (which is used under the +hood by the orm when you use attribute access notation on an entity to +get a relation. The initial request would then be translated to: .. sourcecode:: python entity.related('in_conf', 'object') -The `related()` method, as more generally others orm methods, makes extensive use -of the cache mechanisms so you don't have to worry about them. Additionnaly this -use will get you commonly used attributes that you will be able to use in your -view generation without having to ask the data backend. - +Additionnaly this benefits from the fetch_attrs policy eventually +defined on the class element, which says which attributes must be also +loaded when the entity is loaded through the orm. diff -r 52c9d25101a1 -r 1c635a91e403 doc/book/en/development/devrepo/hooks.rst --- a/doc/book/en/development/devrepo/hooks.rst Fri Apr 09 15:01:14 2010 +0000 +++ b/doc/book/en/development/devrepo/hooks.rst Fri Apr 09 15:01:25 2010 +0000 @@ -2,31 +2,103 @@ .. _hooks: -Hooks -===== +Hooks and Operations +==================== + +Principles +---------- -XXX FILLME +Paraphrasing the `emacs`_ documentation, let us say that hooks are an +important mechanism for customizing an application. A hook is +basically a list of functions to be called on some well-defined +occasion (This is called `running the hook`). + +.. _`emacs`: http://www.gnu.org/software/emacs/manual/html_node/emacs/Hooks.html + +In CubicWeb, hooks are classes subclassing the Hook class in +`server/hook.py`, implementing their own `call` method, and defined +over pre-defined `events`. + +There are two families of events: data events and server events. In a +typical application, most of the Hooks are defined over data +events. There can be a lot of them. -*Hooks* are executed before or after updating an entity or a relation in the -repository. +The purpose of data hooks is to complement the data model as defined +in the schema.py, which is static by nature, with dynamic or value +driven behaviours. It is functionally equivalent to a `database +trigger`_, except that database triggers definitions languages are not +standardized, hence not portable (for instance, PL/SQL works with +Oracle and PostgreSQL but not SqlServer nor Sqlite). + +.. _`database trigger`: http://en.wikipedia.org/wiki/Database_trigger + +Data hooks can serve the following purposes: + +* enforcing constraints that the static schema cannot express + (spanning several entities/relations, exotic cardinalities, etc.) -Their prototypes are as follows: +* implement computed attributes (an example could be the maintenance + of a relation representing the transitive closure of another relation) + +Operations are Hook-like objects that are created by Hooks and +scheduled to happen just before (or after) the `commit` event. Hooks +being fired immediately on data operations, it is sometime necessary +to delay the actual work down to a time where all other Hooks have run +and the application state converges towards consistency. Also while +the order of execution of Hooks is data dependant (and thus hard to +predict), it is possible to force an order on Operations. + +Events +------ - * after_add_entity (session, entity) - * after_update_entity (session, entity) - * after_delete_entity (session, eid) - * before_add_entity (session, entity) - * before_update_entity (session, entity) - * before_delete_entity (session, eid) +Hooks are mostly defined and used to handle `dataflow`_ operations. It +means as data gets in (mostly), specific events are issued and the +Hooks matching these events are called. + +.. _`dataflow`: http://en.wikipedia.org/wiki/Dataflow + +Below comes a list of the dataflow events related to entities operations: + +* before_add_entity + +* before_update_entity + +* before_delete_entity + +* after_add_entity + +* after_update_entity + +* after_delete_entity + +These define ENTTIES HOOKS. RELATIONS HOOKS are defined +over the following events: + +* after_add_relation - * after_add_relation (session, fromeid, rtype, toeid) - * after_delete_relation (session, fromeid, rtype, toeid) - * before_add_relation (session, fromeid, rtype, toeid) - * before_delete_relation (session, fromeid, rtype, toeid) +* after_delete_relation + +* before_add_relation + +* before_delete_relation + +This is an occasion to remind us that relations support the add/delete +operation, but no delete. + +Non data events also exist. These are called SYSTEM HOOKS. + +* server_startup - * server_startup - * server_shutdown +* server_shutdown + +* server_maintenance + +* server_backup - * session_open - * session_close +* server_restore + +* session_open +* session_close + + diff -r 52c9d25101a1 -r 1c635a91e403 doc/book/en/development/devweb/js.rst --- a/doc/book/en/development/devweb/js.rst Fri Apr 09 15:01:14 2010 +0000 +++ b/doc/book/en/development/devweb/js.rst Fri Apr 09 15:01:25 2010 +0000 @@ -5,7 +5,7 @@ *CubicWeb* uses quite a bit of javascript in its user interface and ships with jquery (1.3.x) and parts of the jquery UI library, plus a -number of homegrown files and also other thir party libraries. +number of homegrown files and also other third party libraries. All javascript files are stored in cubicweb/web/data/. There are around thirty js files there. In a cube it goes to data/. diff -r 52c9d25101a1 -r 1c635a91e403 doc/book/en/development/testing.rst --- a/doc/book/en/development/testing.rst Fri Apr 09 15:01:14 2010 +0000 +++ b/doc/book/en/development/testing.rst Fri Apr 09 15:01:25 2010 +0000 @@ -24,6 +24,10 @@ In most of the cases, you will inherit `EnvBasedTC` to write Unittest or functional tests for your entities, views, hooks, etc... +XXX pytestconf.py & options (e.g --source to use a different db +backend than sqlite) + + Managing connections or users +++++++++++++++++++++++++++++ @@ -33,10 +37,12 @@ By default, tests run with a user with admin privileges. This user/connection must never be closed. -qwq -Before a self.login, one has to release the connection pool in use with a self.commit, self.rollback or self.close. -When one is logged in as a normal user and wants to switch back to the admin user, one has to use self.restore_connection(). +Before a self.login, one has to release the connection pool in use +with a self.commit, self.rollback or self.close. + +When one is logged in as a normal user and wants to switch back to the +admin user, one has to use self.restore_connection(). Usually it looks like this: diff -r 52c9d25101a1 -r 1c635a91e403 doc/book/en/intro/concepts.rst --- a/doc/book/en/intro/concepts.rst Fri Apr 09 15:01:14 2010 +0000 +++ b/doc/book/en/intro/concepts.rst Fri Apr 09 15:01:25 2010 +0000 @@ -339,5 +339,5 @@ .. Note: RQL queries executed in hooks and operations are *unsafe* by default, e.g. the read and write security is deactivated unless explicitly asked. - + .. |cubicweb| replace:: *CubicWeb* diff -r 52c9d25101a1 -r 1c635a91e403 misc/migration/3.7.4_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/migration/3.7.4_Any.py Fri Apr 09 15:01:25 2010 +0000 @@ -0,0 +1,1 @@ +sync_schema_props_perms('TrInfo', syncprops=False) diff -r 52c9d25101a1 -r 1c635a91e403 schemas/bootstrap.py --- a/schemas/bootstrap.py Fri Apr 09 15:01:14 2010 +0000 +++ b/schemas/bootstrap.py Fri Apr 09 15:01:25 2010 +0000 @@ -311,8 +311,9 @@ def post_build_callback(schema): """set attributes permissions for schema/workflow entities""" from cubicweb.schema import SCHEMA_TYPES, WORKFLOW_TYPES, META_RTYPES + wftypes = WORKFLOW_TYPES - set(('TrInfo',)) for eschema in schema.entities(): - if eschema in SCHEMA_TYPES or eschema in WORKFLOW_TYPES: + if eschema in SCHEMA_TYPES or eschema in wftypes: for rschema in eschema.subject_relations(): if rschema.final and not rschema in META_RTYPES: rdef = eschema.rdef(rschema) diff -r 52c9d25101a1 -r 1c635a91e403 server/hook.py --- a/server/hook.py Fri Apr 09 15:01:14 2010 +0000 +++ b/server/hook.py Fri Apr 09 15:01:25 2010 +0000 @@ -30,7 +30,6 @@ Session hooks (eg session_open, session_close) have no special attribute. - :organization: Logilab :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr @@ -471,7 +470,7 @@ try: session.transaction_data[datakey].add(value) except KeyError: - opcls(session, *opkwargs) + opcls(session, **opkwargs) session.transaction_data[datakey] = set((value,)) diff -r 52c9d25101a1 -r 1c635a91e403 server/repository.py --- a/server/repository.py Fri Apr 09 15:01:14 2010 +0000 +++ b/server/repository.py Fri Apr 09 15:01:25 2010 +0000 @@ -1104,7 +1104,7 @@ eidfrom=entity.eid, rtype=attr, eidto=value) if not only_inline_rels: hm.call_hooks('before_update_entity', session, entity=entity) - source.update_entity(session, entity) + source.update_entity(session, entity) self.system_source.update_info(session, entity, need_fti_update) if source.should_call_hooks: if not only_inline_rels: diff -r 52c9d25101a1 -r 1c635a91e403 server/session.py --- a/server/session.py Fri Apr 09 15:01:14 2010 +0000 +++ b/server/session.py Fri Apr 09 15:01:25 2010 +0000 @@ -946,6 +946,7 @@ class InternalSession(Session): """special session created internaly by the repository""" is_internal_session = True + running_dbapi_query = False def __init__(self, repo, cnxprops=None): super(InternalSession, self).__init__(InternalManager(), repo, cnxprops, diff -r 52c9d25101a1 -r 1c635a91e403 server/sources/storages.py --- a/server/sources/storages.py Fri Apr 09 15:01:14 2010 +0000 +++ b/server/sources/storages.py Fri Apr 09 15:01:25 2010 +0000 @@ -85,9 +85,16 @@ def entity_updated(self, entity, attr): """an entity using this storage for attr has been updatded""" - binary = entity.pop(attr) - fpath = self.current_fs_path(entity, attr) - UpdateFileOp(entity._cw, filepath=fpath, filedata=binary.getvalue()) + if entity._cw.transaction_data.get('fs_importing'): + oldpath = self.current_fs_path(entity, attr) + fpath = entity[attr].getvalue() + if oldpath != fpath: + DeleteFileOp(entity._cw, filepath=oldpath) + binary = Binary(file(fpath).read()) + else: + binary = entity.pop(attr) + fpath = self.current_fs_path(entity, attr) + UpdateFileOp(entity._cw, filepath=fpath, filedata=binary.getvalue()) return binary def entity_deleted(self, entity, attr): diff -r 52c9d25101a1 -r 1c635a91e403 server/test/unittest_session.py --- a/server/test/unittest_session.py Fri Apr 09 15:01:14 2010 +0000 +++ b/server/test/unittest_session.py Fri Apr 09 15:01:25 2010 +0000 @@ -7,6 +7,7 @@ """ from logilab.common.testlib import TestCase, unittest_main, mock_object +from cubicweb.devtools.testlib import CubicWebTC from cubicweb.server.session import _make_description class Variable: @@ -32,5 +33,10 @@ self.assertEquals(_make_description((Function('max', 'A'), Variable('B')), {}, solution), ['Int','CWUser']) +class InternalSessionTC(CubicWebTC): + def test_dbapi_query(self): + session = self.repo.internal_session() + self.assertFalse(session.running_dbapi_query) + if __name__ == '__main__': unittest_main() diff -r 52c9d25101a1 -r 1c635a91e403 server/test/unittest_storage.py --- a/server/test/unittest_storage.py Fri Apr 09 15:01:14 2010 +0000 +++ b/server/test/unittest_storage.py Fri Apr 09 15:01:25 2010 +0000 @@ -57,6 +57,11 @@ return req.create_entity('File', data=Binary(content), data_format=u'text/plain', data_name=u'foo') + def fspath(self, entity): + fspath = self.execute('Any fspath(D) WHERE F eid %(f)s, F data D', + {'f': entity.eid})[0][0] + return fspath.getvalue() + def test_bfss_storage(self): f1 = self.create_file() expected_filepath = osp.join(self.tempdir, '%s_data' % f1.eid) @@ -81,18 +86,14 @@ def test_bfss_sqlite_fspath(self): f1 = self.create_file() expected_filepath = osp.join(self.tempdir, '%s_data' % f1.eid) - fspath = self.execute('Any fspath(D) WHERE F eid %(f)s, F data D', - {'f': f1.eid})[0][0] - self.assertEquals(fspath.getvalue(), expected_filepath) + self.assertEquals(self.fspath(f1), expected_filepath) def test_bfss_fs_importing_doesnt_touch_path(self): self.session.transaction_data['fs_importing'] = True filepath = osp.abspath(__file__) f1 = self.session.create_entity('File', data=Binary(filepath), data_format=u'text/plain', data_name=u'foo') - fspath = self.execute('Any fspath(D) WHERE F eid %(f)s, F data D', - {'f': f1.eid})[0][0] - self.assertEquals(fspath.getvalue(), filepath) + self.assertEquals(self.fspath(f1), filepath) def test_source_storage_transparency(self): with self.temporary_appobjects(DummyBeforeHook, DummyAfterHook): @@ -180,5 +181,21 @@ self.assertEquals(f2.data.getvalue(), 'some other data') + def test_bfss_update_with_fs_importing(self): + # use self.session to use server-side cache + f1 = self.session.create_entity('File', data=Binary('some data'), + data_format=u'text/plain', data_name=u'foo') + old_fspath = self.fspath(f1) + self.session.transaction_data['fs_importing'] = True + new_fspath = osp.join(self.tempdir, 'newfile.txt') + file(new_fspath, 'w').write('the new data') + self.execute('SET F data %(d)s WHERE F eid %(f)s', + {'d': Binary(new_fspath), 'f': f1.eid}) + self.commit() + self.assertEquals(f1.data.getvalue(), 'the new data') + self.assertEquals(self.fspath(f1), new_fspath) + self.failIf(osp.isfile(old_fspath)) + + if __name__ == '__main__': unittest_main() diff -r 52c9d25101a1 -r 1c635a91e403 test/unittest_schema.py --- a/test/unittest_schema.py Fri Apr 09 15:01:14 2010 +0000 +++ b/test/unittest_schema.py Fri Apr 09 15:01:25 2010 +0000 @@ -241,6 +241,17 @@ self.failUnless('has_text' in schema['CWUser'].subject_relations()) self.failIf('has_text' in schema['EmailAddress'].subject_relations()) + def test_permission_settings(self): + schema = loader.load(config) + aschema = schema['TrInfo'].rdef('comment') + self.assertEquals(aschema.get_groups('read'), + set(('managers', 'users', 'guests'))) + self.assertEquals(aschema.get_rqlexprs('read'), + ()) + self.assertEquals(aschema.get_groups('update'), + set(('managers',))) + self.assertEquals([x.expression for x in aschema.get_rqlexprs('update')], + ['U has_update_permission X']) class BadSchemaRQLExprTC(TestCase): def setUp(self): @@ -257,16 +268,20 @@ self.assertEquals(str(ex), msg) def test_rrqlexpr_on_etype(self): - self._test('rrqlexpr_on_eetype.py', "can't use RRQLExpression on ToTo, use an ERQLExpression") + self._test('rrqlexpr_on_eetype.py', + "can't use RRQLExpression on ToTo, use an ERQLExpression") def test_erqlexpr_on_rtype(self): - self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on relation ToTo toto TuTu, use a RRQLExpression") + self._test('erqlexpr_on_ertype.py', + "can't use ERQLExpression on relation ToTo toto TuTu, use a RRQLExpression") def test_rqlexpr_on_rtype_read(self): - self._test('rqlexpr_on_ertype_read.py', "can't use rql expression for read permission of relation ToTo toto TuTu") + self._test('rqlexpr_on_ertype_read.py', + "can't use rql expression for read permission of relation ToTo toto TuTu") def test_rrqlexpr_on_attr(self): - self._test('rrqlexpr_on_attr.py', "can't use RRQLExpression on attribute ToTo.attr[String], use an ERQLExpression") + self._test('rrqlexpr_on_attr.py', + "can't use RRQLExpression on attribute ToTo.attr[String], use an ERQLExpression") class NormalizeExpressionTC(TestCase): @@ -277,8 +292,10 @@ class RQLExpressionTC(TestCase): def test_comparison(self): - self.assertEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWUser', 'X', 0)) - self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWGroup', 'X', 0)) + self.assertEquals(ERQLExpression('X is CWUser', 'X', 0), + ERQLExpression('X is CWUser', 'X', 0)) + self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0), + ERQLExpression('X is CWGroup', 'X', 0)) class GuessRrqlExprMainVarsTC(TestCase): def test_exists(self): diff -r 52c9d25101a1 -r 1c635a91e403 web/_exceptions.py --- a/web/_exceptions.py Fri Apr 09 15:01:14 2010 +0000 +++ b/web/_exceptions.py Fri Apr 09 15:01:25 2010 +0000 @@ -53,8 +53,7 @@ """raised when a json remote call fails """ def __init__(self, reason=''): - #super(RequestError, self).__init__() # XXX require py >= 2.5 - RequestError.__init__(self) + super(RequestError, self).__init__() self.reason = reason def dumps(self): diff -r 52c9d25101a1 -r 1c635a91e403 web/data/cubicweb.ajax.js --- a/web/data/cubicweb.ajax.js Fri Apr 09 15:01:14 2010 +0000 +++ b/web/data/cubicweb.ajax.js Fri Apr 09 15:01:25 2010 +0000 @@ -240,6 +240,7 @@ setProgressCursor(); var props = {'fname' : fname, 'pageid' : pageid, 'arg': map(jQuery.toJSON, sliceList(arguments, 1))}; + // XXX we should inline the content of loadRemote here var deferred = loadRemote(JSON_BASE_URL, props, 'POST'); deferred = deferred.addErrback(remoteCallFailed); deferred = deferred.addErrback(resetCursor); diff -r 52c9d25101a1 -r 1c635a91e403 web/data/cubicweb.compat.js --- a/web/data/cubicweb.compat.js Fri Apr 09 15:01:14 2010 +0000 +++ b/web/data/cubicweb.compat.js Fri Apr 09 15:01:25 2010 +0000 @@ -6,22 +6,26 @@ } } +// XXX looks completely unused (candidate for removal) function getElementsByTagAndClassName(tag, klass, root) { root = root || document; // FIXME root is not used in this compat implementation return jQuery(tag + '.' + klass); } +/* jQUery flattens arrays returned by the mapping function: + >>> y = ['a:b:c', 'd:e'] + >>> jQuery.map(y, function(y) { return y.split(':');}) + ["a", "b", "c", "d", "e"] + // where one would expect: + [ ["a", "b", "c"], ["d", "e"] ] + XXX why not the same argument order as $.map and forEach ? +*/ function map(func, array) { - // XXX jQUery tends to simplify lists with only one element : - // >>> y = ['a:b:c'] - // >>> jQuery.map(y, function(y) { return y.split(':');}) - // ["a", "b", "c"] - // where I would expect : - // [ ["a", "b", "c"] ] - // return jQuery.map(array, func); var result = []; - for (var i=0,length=array.length;i') - for rschema, tschemas, role, dispctrl in entity_attributes: + display_attributes = [] + for rschema, _, role, dispctrl in self._section_def(entity, 'attributes'): vid = dispctrl.get('vid', 'reledit') if rschema.final or vid == 'reledit': value = entity.view(vid, rtype=rschema.type, role=role) @@ -129,16 +126,19 @@ value = self._cw.view(vid, rset) else: value = None - if self.skip_none and (value is None or value == ''): - continue - try: - self._render_attribute(dispctrl, rschema, value, - role=role, table=True) - except TypeError: - warn('[3.6] _render_attribute prototype has changed, ' - 'please update %s' % self.__class___, DeprecationWarning) - self._render_attribute(rschema, value, role=role, table=True) - self.w(u'') + if not self.skip_none or (value is not None and value != ''): + display_attributes.append( (rschema, role, dispctrl, value) ) + if display_attributes: + self.w(u'') + for rschema, role, dispctrl, value in display_attributes: + try: + self._render_attribute(dispctrl, rschema, value, + role=role, table=True) + except TypeError: + warn('[3.6] _render_attribute prototype has changed, please' + ' update %s' % self.__class___, DeprecationWarning) + self._render_attribute(rschema, value, role=role, table=True) + self.w(u'
') def render_entity_relations(self, entity, siderelations=None): for rschema, tschemas, role, dispctrl in self._section_def(entity, 'relations'):