merge 3.17.11
authorJulien Cristau <julien.cristau@logilab.fr>
Mon, 09 Dec 2013 16:13:10 +0100
changeset 9340 b1e933b0e850
parent 9322 2dae5bf5ea68 (current diff)
parent 9339 cf27006ce813 (diff)
child 9341 099a3a33eaaa
merge 3.17.11
__pkginfo__.py
cubicweb.spec
cwctl.py
devtools/__init__.py
entity.py
server/__init__.py
server/hook.py
server/sqlutils.py
test/unittest_entity.py
web/webconfig.py
--- a/.hgtags	Mon Sep 09 12:43:25 2013 +0200
+++ b/.hgtags	Mon Dec 09 16:13:10 2013 +0100
@@ -316,3 +316,6 @@
 fe0e1863a13772836f40f743cc6fe4865f288ed3 cubicweb-centos-version-3.17.10-1
 fe0e1863a13772836f40f743cc6fe4865f288ed3 cubicweb-version-3.17.10
 fe0e1863a13772836f40f743cc6fe4865f288ed3 cubicweb-debian-version-3.17.10-1
+7f67db7c848ec20152daf489d9e11f0fc8402e9b cubicweb-centos-version-3.17.11-1
+7f67db7c848ec20152daf489d9e11f0fc8402e9b cubicweb-version-3.17.11
+7f67db7c848ec20152daf489d9e11f0fc8402e9b cubicweb-debian-version-3.17.11-1
--- a/__pkginfo__.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/__pkginfo__.py	Mon Dec 09 16:13:10 2013 +0100
@@ -22,7 +22,7 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 17, 10)
+numversion = (3, 17, 11)
 version = '.'.join(str(num) for num in numversion)
 
 description = "a repository of entities / relations for knowledge management"
--- a/cubicweb.spec	Mon Sep 09 12:43:25 2013 +0200
+++ b/cubicweb.spec	Mon Dec 09 16:13:10 2013 +0100
@@ -7,7 +7,7 @@
 %endif
 
 Name:           cubicweb
-Version:        3.17.10
+Version:        3.17.11
 Release:        logilab.1%{?dist}
 Summary:        CubicWeb is a semantic web application framework
 Source0:        http://download.logilab.org/pub/cubicweb/cubicweb-%{version}.tar.gz
--- a/cwctl.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/cwctl.py	Mon Dec 09 16:13:10 2013 +0100
@@ -746,6 +746,7 @@
         print '\n' + underline_title('Upgrading the instance %s' % appid)
         from logilab.common.changelog import Version
         config = cwcfg.config_for(appid)
+        instance_running = exists(config['pid-file'])
         config.repairing = True # notice we're not starting the server
         config.verbosity = self.config.verbosity
         set_sources_mode = getattr(config, 'set_sources_mode', None)
@@ -777,7 +778,7 @@
         if cubicwebversion > applcubicwebversion:
             toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion))
         # only stop once we're sure we have something to do
-        if not (CWDEV or self.config.nostartstop):
+        if instance_running and not (CWDEV or self.config.nostartstop):
             StopInstanceCommand(self.logger).stop_instance(appid)
         # run cubicweb/componants migration scripts
         if self.config.fs_only or toupgrade:
@@ -794,7 +795,7 @@
             return
         print
         print '-> instance migrated.'
-        if not (CWDEV or self.config.nostartstop):
+        if instance_running and not (CWDEV or self.config.nostartstop):
             # restart instance through fork to get a proper environment, avoid
             # uicfg pb (and probably gettext catalogs, to check...)
             forkcmd = '%s start %s' % (sys.argv[0], appid)
--- a/debian/changelog	Mon Sep 09 12:43:25 2013 +0200
+++ b/debian/changelog	Mon Dec 09 16:13:10 2013 +0100
@@ -1,3 +1,9 @@
+cubicweb (3.17.11-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Aurelien Campeas <aurelien.campeas@logilab.fr>  Fri, 06 Dec 2013 15:55:48 +0100
+
 cubicweb (3.17.10-1) unstable; urgency=low
 
   * new upstream release
--- a/devtools/__init__.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/devtools/__init__.py	Mon Dec 09 16:13:10 2013 +0100
@@ -26,6 +26,7 @@
 import pickle
 import glob
 import warnings
+import tempfile
 from hashlib import sha1 # pylint: disable=E0611
 from datetime import timedelta
 from os.path import (abspath, join, exists, split, isabs, isdir)
@@ -328,8 +329,9 @@
         # XXX we dump a dict of the config
         # This is an experimental to help config dependant setup (like BFSS) to
         # be propertly restored
-        with open(config_path, 'wb') as conf_file:
+        with tempfile.NamedTemporaryFile(dir=os.path.dirname(config_path), delete=False) as conf_file:
             conf_file.write(pickle.dumps(dict(self.config)))
+        os.rename(conf_file.name, config_path)
         self.db_cache[self.db_cache_key(db_id)] = (backup_data, config_path)
 
     def _backup_database(self, db_id):
--- a/doc/book/en/annexes/rql/language.rst	Mon Sep 09 12:43:25 2013 +0200
+++ b/doc/book/en/annexes/rql/language.rst	Mon Dec 09 16:13:10 2013 +0100
@@ -81,7 +81,7 @@
 are expressed as explained below:
 
 * string should be between double or single quotes. If the value contains a
-  quote, it should be preceded by a backslash '\'
+  quote, it should be preceded by a backslash '\\'
 
 * floats separator is dot '.'
 
@@ -747,7 +747,7 @@
 
   .. sourcecode:: sql
 
-        INSERT Person X: X name 'foo', X friend  Y WHERE name 'nice'
+        INSERT Person X: X name 'foo', X friend  Y WHERE Y name 'nice'
 
 .. _RQLSetQuery:
 
--- a/doc/book/en/devrepo/entityclasses/application-logic.rst	Mon Sep 09 12:43:25 2013 +0200
+++ b/doc/book/en/devrepo/entityclasses/application-logic.rst	Mon Dec 09 16:13:10 2013 +0100
@@ -2,7 +2,7 @@
 ----------------------------------------
 
 The previous chapters detailed the classes and methods available to
-the developper at the so-called `ORM`_ level. However they say little
+the developer at the so-called `ORM`_ level. However they say little
 about the common patterns of usage of these objects.
 
 .. _`ORM`: http://en.wikipedia.org/wiki/Object-relational_mapping
@@ -13,22 +13,22 @@
 
 Hooks and Operations provide support for the implementation of rules
 such as computed attributes, coherency invariants, etc (they play the
-same role as database triggers, but in a way that is independant of
+same role as database triggers, but in a way that is independent of
 the actual data sources).
 
 So a lot of an application's business rules will be written in Hooks
 (or Operations).
 
 On the web side, views also typically operate using entity
-objects. Obvious entity methods for use in views are the dublin code
-method like dc_title, etc. For separation of concerns reasons, one
+objects. Obvious entity methods for use in views are the Dublin Core
+methods like ``dc_title``. For separation of concerns reasons, one
 should ensure no ui logic pervades the entities level, and also no
 business logic should creep into the views.
 
 In the duration of a transaction, entities objects can be instantiated
 many times, in views and hooks, even for the same database entity. For
 instance, in a classic CubicWeb deployment setup, the repository and
-the web frontend are separated process communicating over the
+the web front-end are separated process communicating over the
 wire. There is no way state can be shared between these processes
 (there is a specific API for that). Hence, it is not possible to use
 entity objects as messengers between these components of an
@@ -38,24 +38,24 @@
 object was built.
 
 Setting an attribute or relation value can be done in the context of a
-Hook/Operation, using the obj.cw_set(x=42) notation or a plain
-RQL SET expression.
+Hook/Operation, using the ``obj.cw_set(x=42)`` notation or a plain
+RQL ``SET`` expression.
 
 In views, it would be preferable to encapsulate the necessary logic in
 a method of an adapter for the concerned entity class(es). But of
-course, this advice is also reasonnable for Hooks/Operations, though
+course, this advice is also reasonable for Hooks/Operations, though
 the separation of concerns here is less stringent than in the case of
 views.
 
 This leads to the practical role of objects adapters: it's where an
-important part of the application logic lie (the other part being
+important part of the application logic lies (the other part being
 located in the Hook/Operations).
 
 Anatomy of an entity class
 --------------------------
 
 We can look now at a real life example coming from the `tracker`_
-cube. Let us begin to study the entities/project.py content.
+cube. Let us begin to study the ``entities/project.py`` content.
 
 .. sourcecode:: python
 
@@ -77,10 +77,10 @@
 
 The fact that the `Project` entity type implements an ``ITree``
 interface is materialized by the ``ProjectAdapter`` class (inheriting
-the pre-defined ``ITreeAdapter`` whose __regid__ is of course
+the pre-defined ``ITreeAdapter`` whose ``__regid__`` is of course
 ``ITree``), which will be selected on `Project` entity types because
 of its selector. On this adapter, we redefine the ``tree_relation``
-attribute of the ITreeAdapter class.
+attribute of the ``ITreeAdapter`` class.
 
 This is typically used in views concerned with the representation of
 tree-like structures (CubicWeb provides several such views).
@@ -95,16 +95,16 @@
 about the transitive closure of the child relation). This is a further
 argument to implement it at entity class level.
 
-`fetch_attrs` configures which attributes should be prefetched when using ORM
-methods retrieving entity of this type. In a same manner, the `cw_fetch_order` is
+``fetch_attrs`` configures which attributes should be pre-fetched when using ORM
+methods retrieving entity of this type. In a same manner, the ``cw_fetch_order`` is
 a class method allowing to control sort order. More on this in :ref:`FetchAttrs`.
 
-We can observe the big TICKET_DEFAULT_STATE_RESTR is a pure
+We can observe the big ``TICKET_DEFAULT_STATE_RESTR`` is a pure
 application domain piece of data. There is, of course, no limitation
 to the amount of class attributes of this kind.
 
 The ``dc_title`` method provides a (unicode string) value likely to be
-consummed by views, but note that here we do not care about output
+consumed by views, but note that here we do not care about output
 encodings. We care about providing data in the most universal format
 possible, because the data could be used by a web view (which would be
 responsible of ensuring XHTML compliance), or a console or file
@@ -113,8 +113,8 @@
 
 .. note::
 
-  The dublin code `dc_xxx` methods are not moved to an adapter as they
-  are extremely prevalent in cubicweb and assorted cubes and should be
+  The Dublin Core `dc_xxx` methods are not moved to an adapter as they
+  are extremely prevalent in CubicWeb and assorted cubes and should be
   available for all entity types.
 
 Let us now dig into more substantial pieces of code, continuing the
@@ -159,8 +159,8 @@
 
 * entity code is concerned with the application domain
 
-* it is NOT concerned with database coherency (this is the realm of
-  Hooks/Operations); in other words, it assumes a coherent world
+* it is NOT concerned with database consistency (this is the realm of
+  Hooks/Operations); in other words, it assumes a consistent world
 
 * it is NOT (directly) concerned with end-user interfaces
 
--- a/entity.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/entity.py	Mon Dec 09 16:13:10 2013 +0100
@@ -1268,8 +1268,8 @@
 
         >>> c = rql('Any X WHERE X is Company').get_entity(0, 0)
         >>> p = rql('Any X WHERE X is Person').get_entity(0, 0)
-        >>> c.set(name=u'Logilab')
-        >>> p.set(firstname=u'John', lastname=u'Doe', works_for=c)
+        >>> c.cw_set(name=u'Logilab')
+        >>> p.cw_set(firstname=u'John', lastname=u'Doe', works_for=c)
 
         You can also set relations where the entity has 'object' role by
         prefixing the relation name by 'reverse_'.  Also, relation values may be
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.17.11_Any.py	Mon Dec 09 16:13:10 2013 +0100
@@ -0,0 +1,7 @@
+for table, column in [
+        ('transactions', 'tx_time'),
+        ('tx_entity_actions', 'tx_uuid'),
+        ('tx_relation_actions', 'tx_uuid')]:
+    session.cnxset.source('system').create_index(session, table, column)
+
+commit()
--- a/pylintext.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/pylintext.py	Mon Dec 09 16:13:10 2013 +0100
@@ -1,7 +1,7 @@
 """https://pastebin.logilab.fr/show/860/"""
 
-from logilab.astng import MANAGER, nodes, scoped_nodes
-from logilab.astng.builder import ASTNGBuilder
+from astroid import MANAGER, InferenceError, nodes, scoped_nodes
+from astroid.builder import AstroidBuilder
 
 def turn_function_to_class(node):
     """turn a Function node into a Class node (in-place)"""
@@ -21,13 +21,16 @@
         for node in assnodes:
             if isinstance(node, scoped_nodes.Function) and node.decorators:
                 for decorator in node.decorators.nodes:
-                    for infered in decorator.infer():
-                        if infered.name in ('objectify_predicate', 'objectify_selector'):
-                            turn_function_to_class(node)
-                            break
-                    else:
+                    try:
+                        for infered in decorator.infer():
+                            if infered.name in ('objectify_predicate', 'objectify_selector'):
+                                turn_function_to_class(node)
+                                break
+                        else:
+                            continue
+                        break
+                    except InferenceError:
                         continue
-                    break
     # add yams base types into 'yams.buildobjs', astng doesn't grasp globals()
     # magic in there
     if module.name == 'yams.buildobjs':
@@ -36,7 +39,7 @@
             module.locals[etype] = [scoped_nodes.Class(etype, None)]
     # add data() to uiprops module
     if module.name.split('.')[-1] == 'uiprops':
-        fake = ASTNGBuilder(MANAGER).string_build('''
+        fake = AstroidBuilder(MANAGER).string_build('''
 def data(string):
   return u''
 ''')
@@ -44,5 +47,5 @@
 
 def register(linter):
     """called when loaded by pylint --load-plugins, nothing to do here"""
-    MANAGER.register_transformer(cubicweb_transform)
+    MANAGER.register_transform(nodes.Module, cubicweb_transform)
 
--- a/rset.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/rset.py	Mon Dec 09 16:13:10 2013 +0100
@@ -53,7 +53,10 @@
         self.args = args
         # entity types for each cell (same shape as rows)
         # maybe discarded if specified when the query has been executed
-        self.description = description or []
+        if description is None:
+            self.description = []
+        else:
+            self.description = description
         # parsed syntax tree
         if rqlst is not None:
             rqlst.schema = None # reset schema in case of pyro transfert
--- a/server/__init__.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/server/__init__.py	Mon Dec 09 16:13:10 2013 +0100
@@ -149,13 +149,13 @@
 
     It can be used either as a context manager:
 
-    >>> with debugged(server.DBG_RQL | server.DBG_REPO):
+    >>> with debugged('DBG_RQL | DBG_REPO'):
     ...     # some code in which you want to debug repository activity,
     ...     # seing information about RQL being executed an repository events.
 
     or as a function decorator:
 
-    >>> @debugged(server.DBG_RQL | server.DBG_REPO)
+    >>> @debugged('DBG_RQL | DBG_REPO')
     ... def some_function():
     ...     # some code in which you want to debug repository activity,
     ...     # seing information about RQL being executed an repository events
@@ -249,7 +249,11 @@
     schemasql = sqlschema(schema, driver)
     #skip_entities=[str(e) for e in schema.entities()
     #               if not repo.system_source.support_entity(str(e))])
-    sqlexec(schemasql, execute, pbtitle=_title, delimiter=';;')
+    failed = sqlexec(schemasql, execute, pbtitle=_title, delimiter=';;')
+    if failed:
+        print 'The following SQL statements failed. You should check your schema.'
+        print failed
+        raise Exception('execution of the sql schema failed, you should check your schema')
     sqlcursor.close()
     sqlcnx.commit()
     sqlcnx.close()
--- a/server/hook.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/server/hook.py	Mon Dec 09 16:13:10 2013 +0100
@@ -169,7 +169,10 @@
 
 * `after_add_relation`, `after_delete_relation`
 
-Take note that relations can be added or deleted, but not updated.
+Specific selectors are shipped for these kinds of events, see in particular
+:class:`~cubicweb.server.hook.match_rtype`.
+
+Also note that relations can be added or deleted, but not updated.
 
 Non data events
 ~~~~~~~~~~~~~~~
@@ -439,11 +442,13 @@
 
 
 class match_rtype(ExpectedValuePredicate):
-    """accept if parameters specified as initializer arguments are specified
-    in named arguments given to the predicate
+    """accept if the relation type is found in expected ones. Optional
+    named parameters `frometypes` and `toetypes` can be used to restrict
+    target subject and/or object entity types of the relation.
 
-    :param \*expected: parameters (eg `basestring`) which are expected to be
-                       found in named arguments (kwargs)
+    :param \*expected: possible relation types
+    :param frometypes: candidate entity types as subject of relation
+    :param toetypes: candidate entity types as object of relation
     """
     def __init__(self, *expected, **more):
         self.expected = expected
--- a/server/sources/native.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/server/sources/native.py	Mon Dec 09 16:13:10 2013 +0100
@@ -1536,6 +1536,7 @@
   tx_time %s NOT NULL
 );;
 CREATE INDEX transactions_tx_user_idx ON transactions(tx_user);;
+CREATE INDEX transactions_tx_time_idx ON transactions(tx_time);;
 
 CREATE TABLE tx_entity_actions (
   tx_uuid CHAR(32) REFERENCES transactions(tx_uuid) ON DELETE CASCADE,
@@ -1550,6 +1551,7 @@
 CREATE INDEX tx_entity_actions_txa_public_idx ON tx_entity_actions(txa_public);;
 CREATE INDEX tx_entity_actions_eid_idx ON tx_entity_actions(eid);;
 CREATE INDEX tx_entity_actions_etype_idx ON tx_entity_actions(etype);;
+CREATE INDEX tx_entity_actions_tx_uuid_idx ON tx_entity_actions(tx_uuid);;
 
 CREATE TABLE tx_relation_actions (
   tx_uuid CHAR(32) REFERENCES transactions(tx_uuid) ON DELETE CASCADE,
@@ -1564,6 +1566,7 @@
 CREATE INDEX tx_relation_actions_txa_public_idx ON tx_relation_actions(txa_public);;
 CREATE INDEX tx_relation_actions_eid_from_idx ON tx_relation_actions(eid_from);;
 CREATE INDEX tx_relation_actions_eid_to_idx ON tx_relation_actions(eid_to);;
+CREATE INDEX tx_relation_actions_tx_uuid_idx ON tx_relation_actions(tx_uuid);;
 """ % (helper.sql_create_sequence('entities_id_seq').replace(';', ';;'),
        typemap['Datetime'], typemap['Datetime'], typemap['Datetime'],
        typemap['Boolean'], typemap['Bytes'], typemap['Boolean'])
--- a/server/sources/rql2sql.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/server/sources/rql2sql.py	Mon Dec 09 16:13:10 2013 +0100
@@ -486,13 +486,10 @@
             return relation._q_sqltable
         rid = 'rel_%s%s' % (relation.r_type, self.count)
         # relation's table is belonging to the root scope if it is the principal
-        # table of one of it's variable and if that variable belong's to parent
+        # table of one of its variable and that variable belong's to parent
         # scope
         for varref in relation.iget_nodes(VariableRef):
             var = varref.variable
-            if isinstance(var, ColumnAlias):
-                scope = 0
-                break
             # XXX may have a principal without being invariant for this generation,
             #     not sure this is a pb or not
             if var.stinfo.get('principal') is relation and var.scope is var.stmt:
--- a/server/sqlutils.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/server/sqlutils.py	Mon Dec 09 16:13:10 2013 +0100
@@ -332,7 +332,8 @@
             if value is not None:
                 self.values.append(value)
         def finalize(self):
-            return ', '.join(self.values)
+            return ', '.join(unicode(v) for v in self.values)
+
     cnx.create_aggregate("GROUP_CONCAT", 1, group_concat)
 
     def _limit_size(text, maxsize, format='text/plain'):
--- a/server/test/unittest_querier.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/server/test/unittest_querier.py	Mon Dec 09 16:13:10 2013 +0100
@@ -383,9 +383,9 @@
         self.execute("INSERT Personne X: X nom 'bidule'")[0]
         rset = self.execute('Any Y where X name TMP, Y nom in (TMP, "bidule")')
         #self.assertEqual(rset.description, [('Personne',), ('Personne',)])
-        self.assert_(('Personne',) in rset.description)
+        self.assertIn(('Personne',), rset.description)
         rset = self.execute('DISTINCT Any Y where X name TMP, Y nom in (TMP, "bidule")')
-        self.assert_(('Personne',) in rset.description)
+        self.assertIn(('Personne',), rset.description)
 
     def test_select_not_attr(self):
         peid = self.execute("INSERT Personne X: X nom 'bidule'")[0][0]
@@ -466,8 +466,8 @@
         self.execute("SET X tags Y WHERE X eid %(t)s, Y eid %(g)s",
                      {'g': geid, 't': teid})
         rset = self.execute("Any GN,TN ORDERBY GN WHERE T? tags G, T name TN, G name GN")
-        self.assertTrue(['users', 'tag'] in rset.rows)
-        self.assertTrue(['activated', None] in rset.rows)
+        self.assertIn(['users', 'tag'], rset.rows)
+        self.assertIn(['activated', None], rset.rows)
         rset = self.execute("Any GN,TN ORDERBY GN WHERE T tags G?, T name TN, G name GN")
         self.assertEqual(rset.rows, [[None, 'tagbis'], ['users', 'tag']])
 
@@ -702,7 +702,7 @@
         rset = self.execute('Any X WHERE X is CWGroup', build_descr=0)
         rset.rows.sort()
         self.assertEqual(tuplify(rset.rows), [(2,), (3,), (4,), (5,)])
-        self.assertEqual(rset.description, [])
+        self.assertEqual(rset.description, ())
 
     def test_select_limit_offset(self):
         rset = self.execute('CWGroup X ORDERBY N LIMIT 2 WHERE X name N')
@@ -818,11 +818,11 @@
         self.execute("INSERT Tag X: X name 'bidule', X creation_date NOW")
         self.execute("INSERT Tag Y: Y name 'toto'")
         rset = self.execute("Any D WHERE X name in ('bidule', 'toto') , X creation_date D")
-        self.assert_(isinstance(rset.rows[0][0], datetime), rset.rows)
+        self.assertIsInstance(rset.rows[0][0], datetime)
         rset = self.execute('Tag X WHERE X creation_date TODAY')
         self.assertEqual(len(rset.rows), 2)
         rset = self.execute('Any MAX(D) WHERE X is Tag, X creation_date D')
-        self.assertTrue(isinstance(rset[0][0], datetime), (rset[0][0], type(rset[0][0])))
+        self.assertIsInstance(rset[0][0], datetime)
 
     def test_today(self):
         self.execute("INSERT Tag X: X name 'bidule', X creation_date TODAY")
--- a/server/test/unittest_rql2sql.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/server/test/unittest_rql2sql.py	Mon Dec 09 16:13:10 2013 +0100
@@ -1478,6 +1478,20 @@
     def test_subquery(self):
         for t in self._parse((
 
+            ('Any X,N '
+             'WHERE NOT EXISTS(X owned_by U) '
+             'WITH X,N BEING '
+             '((Any X,N WHERE X name N, X is State)'
+             ' UNION '
+             '(Any XX,NN WHERE XX name NN, XX is Transition))',
+             '''SELECT _T0.C0, _T0.C1
+FROM ((SELECT _X.cw_eid AS C0, _X.cw_name AS C1
+FROM cw_State AS _X)
+UNION ALL
+(SELECT _XX.cw_eid AS C0, _XX.cw_name AS C1
+FROM cw_Transition AS _XX)) AS _T0
+WHERE NOT (EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0 WHERE rel_owned_by0.eid_from=_T0.C0))'''),
+
             ('Any N ORDERBY 1 WITH N BEING '
              '((Any N WHERE X name N, X is State)'
              ' UNION '
@@ -1551,7 +1565,8 @@
 GROUP BY _T.cw_eid) AS _T1 LEFT OUTER JOIN (SELECT _T.cw_eid AS C0, SUM(_T.cw_duration) AS C1
 FROM cw_Affaire AS _T LEFT OUTER JOIN tags_relation AS rel_tags0 ON (rel_tags0.eid_to=_T.cw_eid) LEFT OUTER JOIN cw_Tag AS _TAG ON (rel_tags0.eid_from=_TAG.cw_eid AND _TAG.cw_name=t)
 GROUP BY _T.cw_eid) AS _T0 ON (_T1.C0=_T0.C0)'''),
-            )):
+
+                             )):
             yield t
 
 
--- a/server/test/unittest_sqlutils.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/server/test/unittest_sqlutils.py	Mon Dec 09 16:13:10 2013 +0100
@@ -1,4 +1,5 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# -*- coding: utf-8 -*-
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -24,6 +25,8 @@
 
 from cubicweb.server.sqlutils import *
 
+from cubicweb.devtools.testlib import CubicWebTC
+
 BASE_CONFIG = {
     'db-driver' : 'Postgres',
     'db-host'   : 'crater',
@@ -44,5 +47,22 @@
         o = SQLAdapterMixIn(config)
         self.assertEqual(o.dbhelper.dbencoding, 'ISO-8859-1')
 
+
+class SQLUtilsTC(CubicWebTC):
+
+    def test_group_concat(self):
+        req = self.request()
+        g = req.create_entity('CWGroup', name=u'héhé')
+        u = req.create_entity('CWUser', login=u'toto', upassword=u'',
+                              in_group=g.eid)
+        rset = self.execute(u'Any L,GROUP_CONCAT(G) GROUPBY L WHERE X login L,'
+                            u'X in_group G, G name GN, NOT G name IN ("users", "héhé")')
+        self.assertEqual([[u'admin', u'3'], [u'anon', u'2']],
+                         rset.rows)
+        rset = self.execute('Any L,GROUP_CONCAT(GN) GROUPBY L WHERE X login L,'
+                            'X in_group G, G name GN, NOT G name "users"')
+        self.assertEqual([[u'admin', u'managers'], [u'anon', u'guests'], [u'toto', u'héhé']],
+                         rset.rows)
+
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_entity.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/test/unittest_entity.py	Mon Dec 09 16:13:10 2013 +0100
@@ -26,6 +26,7 @@
 from cubicweb import Binary, Unauthorized
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.mttransforms import HAS_TAL
+from cubicweb.entity import can_use_rest_path
 from cubicweb.entities import fetch_config
 from cubicweb.uilib import soup2xhtml
 from cubicweb.schema import RQLVocabularyConstraint, RRQLExpression
@@ -713,14 +714,13 @@
         # unique attr with None value (nom in this case)
         friend = req.create_entity('Ami', prenom=u'bob')
         self.assertEqual(friend.rest_path(), unicode(friend.eid))
+
+    def test_can_use_rest_path(self):
+        self.assertTrue(can_use_rest_path(u'zobi'))
         # don't use rest if we have /, ? or & in the path (breaks mod_proxy)
-        person3 = req.create_entity('Personne', nom=u'zo/bi')
-        self.assertEqual(person3.rest_path(), unicode(person3.eid))
-        person4 = req.create_entity('Personne', nom=u'zo&bi')
-        self.assertEqual(person4.rest_path(), unicode(person4.eid))
-        person5 = req.create_entity('Personne', nom=u'zo?bi')
-        self.assertEqual(person5.rest_path(), unicode(person5.eid))
-
+        self.assertFalse(can_use_rest_path(u'zo/bi'))
+        self.assertFalse(can_use_rest_path(u'zo&bi'))
+        self.assertFalse(can_use_rest_path(u'zo?bi'))
 
     def test_cw_set_attributes(self):
         req = self.request()
--- a/test/unittest_rqlrewrite.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/test/unittest_rqlrewrite.py	Mon Dec 09 16:13:10 2013 +0100
@@ -520,6 +520,18 @@
                              'EXISTS(X owned_by %(A)s), X is IN(Division, Note, Societe))',
                              union.as_string())
 
+    def test_ambiguous_optional_diff_exprs(self):
+        """See #3013554"""
+        self.skipTest('bad request generated (may generate duplicated results)')
+        edef1 = self.schema['Societe']
+        edef2 = self.schema['Division']
+        edef3 = self.schema['Note']
+        with self.temporary_permissions((edef1, {'read': (ERQLExpression('X created_by U'),)}),
+                                        (edef2, {'read': ('users',)}),
+                                        (edef3, {'read': (ERQLExpression('X owned_by U'),)})):
+            union = self.process('Any A,AR,X,CD WHERE A concerne X?, A ref AR, X creation_date CD')
+            self.assertEqual(union.as_string(), 'not generated today')
+
 
     def test_xxxx(self):
         edef1 = self.schema['Societe']
--- a/test/unittest_rset.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/test/unittest_rset.py	Mon Dec 09 16:13:10 2013 +0100
@@ -181,6 +181,7 @@
         rs2 = rs.filtered_rset(test_filter)
         self.assertEqual(len(rs2), 2)
         self.assertEqual([login for _, login in rs2], ['adim', 'syt'])
+        self.assertEqual(rs2.description, rs.description[1:])
 
     def test_transform(self):
         rs = ResultSet([[12, 'adim'], [13, 'syt'], [14, 'nico']],
--- a/uilib.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/uilib.py	Mon Dec 09 16:13:10 2013 +0100
@@ -453,7 +453,7 @@
 
 
 def rest_traceback(info, exception):
-    """return a ReST formated traceback"""
+    """return a unicode ReST formated traceback"""
     res = [u'Traceback\n---------\n::\n']
     for stackentry in traceback.extract_tb(info[2]):
         res.append(u'\tFile %s, line %s, function %s' % tuple(stackentry[:3]))
--- a/web/test/unittest_webconfig.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/web/test/unittest_webconfig.py	Mon Dec 09 16:13:10 2013 +0100
@@ -1,4 +1,5 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# -*- coding: utf-8 -*-
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,13 +16,11 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
+"""cubicweb.web.webconfig unit tests"""
 
-"""
 import os
 
 from logilab.common.testlib import TestCase, unittest_main
-
 from cubicweb.devtools import ApptestConfiguration, fake
 
 class WebconfigTC(TestCase):
@@ -45,6 +44,10 @@
         cubicwebcsspath = self.config.locate_resource('cubicweb.css')[0].split(os.sep)
         self.assertTrue('web' in cubicwebcsspath or 'shared' in cubicwebcsspath) # 'shared' if tests under apycot
 
+    def test_sign_text(self):
+        signature = self.config.sign_text(u'hôp')
+        self.assertTrue(self.config.check_text_sign(u'hôp', signature))
+
 if __name__ == '__main__':
     unittest_main()
 
--- a/web/views/tableview.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/web/views/tableview.py	Mon Dec 09 16:13:10 2013 +0100
@@ -356,9 +356,9 @@
         self.colid = None
 
     def __str__(self):
-        return '<%s.%s (column %s)>' % (self.view.__class__.__name__,
+        return '<%s.%s (column %s) at 0x%x>' % (self.view.__class__.__name__,
                                         self.__class__.__name__,
-                                        self.colid)
+                                        self.colid, id(self))
 
     def bind(self, view, colid):
         """Bind the column renderer to its view. This is where `_cw`, `view`,
@@ -446,12 +446,13 @@
     handle_pagination = True
 
     def call(self, **kwargs):
+        self._cw.add_js('cubicweb.ajax.js') # for pagination
         self.layout_render(self.w)
 
     def column_renderer(self, colid, *args, **kwargs):
         """Return a column renderer for column of the given id."""
         try:
-            crenderer = self.column_renderers[colid]
+            crenderer = self.column_renderers[colid].copy()
         except KeyError:
             crenderer = self.default_column_renderer_class(*args, **kwargs)
         crenderer.bind(self, colid)
@@ -621,7 +622,7 @@
             else:
                 msg = '[3.14] %s argument is deprecated' % ', '.join(kwargs)
             warn(msg, DeprecationWarning, stacklevel=2)
-        self.layout_render(self.w)
+        super(RsetTableView, self).call(**kwargs)
 
     def main_var_index(self):
         """returns the index of the first non-attribute variable among the RQL
--- a/web/webconfig.py	Mon Sep 09 12:43:25 2013 +0200
+++ b/web/webconfig.py	Mon Dec 09 16:13:10 2013 +0100
@@ -135,7 +135,7 @@
         ('https-deny-anonymous',
          {'type': 'yn',
           'default': False,
-          'help': 'Prevent anonymous user to browse thought https version of '
+          'help': 'Prevent anonymous user to browse through https version of '
                   'the site (https-url). Login form will then be displayed '
                   'until logged',
           'group': 'web',
@@ -296,6 +296,9 @@
 
     def sign_text(self, text):
         """sign some text for later checking"""
+        # hmac.new expect bytes
+        if isinstance(text, unicode):
+            text = text.encode('utf-8')
         # replace \r\n so we do not depend on whether a browser "reencode"
         # original message using \r\n or not
         return hmac.new(self._instance_salt,
@@ -305,7 +308,6 @@
         """check the text signature is equal to the given signature"""
         return self.sign_text(text) == signature
 
-
     def locate_resource(self, rid):
         """return the (directory, filename) where the given resource
         may be found