merge 3.18.4 into default
authorJulien Cristau <julien.cristau@logilab.fr>
Mon, 07 Apr 2014 17:02:04 +0200
changeset 9635 aaf099172bb9
parent 9632 c60c8dec0e0e (current diff)
parent 9634 e01121bbd4fe (diff)
child 9636 e35ae8617c03
merge 3.18.4 into default server/test/unittest_migractions fails due to interaction between new constraint test (from 3.18) and composite deletion change (in 3.19).
__pkginfo__.py
cubicweb.spec
dataimport.py
debian/control
devtools/__init__.py
devtools/testlib.py
entity.py
hooks/syncschema.py
misc/migration/3.18.0_Any.py
schema.py
server/migractions.py
server/test/data/schema.py
server/test/unittest_migractions.py
web/views/editcontroller.py
web/webconfig.py
--- a/.hgtags	Mon Apr 07 14:15:35 2014 +0200
+++ b/.hgtags	Mon Apr 07 17:02:04 2014 +0200
@@ -326,6 +326,9 @@
 09b4ebb9b0f179009491410c07cd013a60258fc6 cubicweb-version-3.17.13
 09b4ebb9b0f179009491410c07cd013a60258fc6 cubicweb-debian-version-3.17.13-1
 09b4ebb9b0f179009491410c07cd013a60258fc6 cubicweb-centos-version-3.17.13-1
+fa00fc251d57f61e619d9c905502745fae21c58c cubicweb-centos-version-3.17.14-1
+fa00fc251d57f61e619d9c905502745fae21c58c cubicweb-version-3.17.14
+fa00fc251d57f61e619d9c905502745fae21c58c cubicweb-debian-version-3.17.14-1
 db37bf35a1474843ded0a537f9cb4838f4a78cda cubicweb-version-3.18.0
 db37bf35a1474843ded0a537f9cb4838f4a78cda cubicweb-debian-version-3.18.0-1
 db37bf35a1474843ded0a537f9cb4838f4a78cda cubicweb-centos-version-3.18.0-1
@@ -338,3 +341,6 @@
 afd21fea201a745051357b7aa6be3c7da1ae5bd2 cubicweb-version-3.18.3
 afd21fea201a745051357b7aa6be3c7da1ae5bd2 cubicweb-debian-version-3.18.3-1
 afd21fea201a745051357b7aa6be3c7da1ae5bd2 cubicweb-centos-version-3.18.3-1
+0176da9bc75293e200de4f7b934c5d4c7c805199 cubicweb-version-3.18.4
+0176da9bc75293e200de4f7b934c5d4c7c805199 cubicweb-debian-version-3.18.4-1
+0176da9bc75293e200de4f7b934c5d4c7c805199 cubicweb-centos-version-3.18.4-1
--- a/__pkginfo__.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/__pkginfo__.py	Mon Apr 07 17:02:04 2014 +0200
@@ -22,7 +22,7 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 18, 3)
+numversion = (3, 18, 4)
 version = '.'.join(str(num) for num in numversion)
 
 description = "a repository of entities / relations for knowledge management"
@@ -42,7 +42,7 @@
     'logilab-common': '>= 0.60.0',
     'logilab-mtconverter': '>= 0.8.0',
     'rql': '>= 0.31.2',
-    'yams': '>= 0.39.0',
+    'yams': '>= 0.39.1',
     #gettext                    # for xgettext, msgcat, etc...
     # web dependancies
     'simplejson': '>= 2.0.9',
--- a/cubicweb.spec	Mon Apr 07 14:15:35 2014 +0200
+++ b/cubicweb.spec	Mon Apr 07 17:02:04 2014 +0200
@@ -7,7 +7,7 @@
 %endif
 
 Name:           cubicweb
-Version:        3.18.3
+Version:        3.18.4
 Release:        logilab.1%{?dist}
 Summary:        CubicWeb is a semantic web application framework
 Source0:        http://download.logilab.org/pub/cubicweb/cubicweb-%{version}.tar.gz
@@ -23,7 +23,7 @@
 Requires:       %{python}-logilab-common >= 0.60.0
 Requires:       %{python}-logilab-mtconverter >= 0.8.0
 Requires:       %{python}-rql >= 0.31.2
-Requires:       %{python}-yams >= 0.39.0
+Requires:       %{python}-yams >= 0.39.1
 Requires:       %{python}-logilab-database >= 1.12.0
 Requires:       %{python}-passlib
 Requires:       %{python}-lxml
--- a/dataimport.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/dataimport.py	Mon Apr 07 17:02:04 2014 +0200
@@ -802,7 +802,7 @@
         assert not rtype.startswith('reverse_')
         self.add_relation(self.session, eid_from, rtype, eid_to,
                           self.rschema(rtype).inlined)
-        if self.rschema[rtype].symmetric:
+        if self.rschema(rtype).symmetric:
             self.add_relation(self.session, eid_to, rtype, eid_from,
                               self.rschema(rtype).inlined)
         self._nb_inserted_relations += 1
@@ -931,7 +931,7 @@
         # XXX Could subjtype be inferred ?
         self.source.add_relation(self.session, subj_eid, rtype, obj_eid,
                                  self.rschema(rtype).inlined, **kwargs)
-        if self.rschema[rtype].symmetric:
+        if self.rschema(rtype).symmetric:
             self.source.add_relation(self.session, obj_eid, rtype, subj_eid,
                                      self.rschema(rtype).inlined, **kwargs)
 
--- a/debian/changelog	Mon Apr 07 14:15:35 2014 +0200
+++ b/debian/changelog	Mon Apr 07 17:02:04 2014 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.18.4-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Aurelien Campeas <aurelien.campeas@logilab.fr>  Fri, 28 Mar 2014 17:12:48 +0100
+
 cubicweb (3.18.3-1) unstable; urgency=low
 
   * new upstream release
@@ -22,6 +28,12 @@
 
  -- Julien Cristau <julien.cristau@logilab.fr>  Fri, 10 Jan 2014 17:14:18 +0100
 
+cubicweb (3.17.14-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Aurelien Campeas <aurelien.campeas@logilab.fr>  Wed, 05 Mar 2014 15:48:38 +0100
+
 cubicweb (3.17.13-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Mon Apr 07 14:15:35 2014 +0200
+++ b/debian/control	Mon Apr 07 17:02:04 2014 +0200
@@ -15,7 +15,7 @@
  python-unittest2 | python (>= 2.7),
  python-logilab-mtconverter,
  python-rql,
- python-yams (>= 0.39),
+ python-yams (>= 0.39.1),
  python-lxml,
 Standards-Version: 3.9.1
 Homepage: http://www.cubicweb.org
@@ -153,7 +153,7 @@
  gettext,
  python-logilab-mtconverter (>= 0.8.0),
  python-logilab-common (>= 0.60.0),
- python-yams (>= 0.39.0),
+ python-yams (>= 0.39.1),
  python-rql (>= 0.31.2),
  python-lxml
 Recommends:
--- a/devtools/__init__.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/devtools/__init__.py	Mon Apr 07 17:02:04 2014 +0200
@@ -118,6 +118,7 @@
         repo._needs_refresh = True
         repo._has_started = False
 
+
 def turn_repo_on(repo):
     """Idea: this is less costly than a full re-creation of the repo object.
     on:
@@ -850,7 +851,22 @@
 
 
 # XXX a class method on Test ?
+
+_CONFIG = None
 def get_test_db_handler(config):
+    global _CONFIG
+    if _CONFIG is not None and config is not _CONFIG:
+        from logilab.common.modutils import cleanup_sys_modules
+        # cleanup all dynamically loaded modules and everything in the instance
+        # directory
+        apphome = _CONFIG.apphome
+        if apphome: # may be unset in tests
+            cleanup_sys_modules([apphome])
+        # also cleanup sys.path
+        if apphome in sys.path:
+            sys.path.remove(apphome)
+    _CONFIG = config
+    config.adjust_sys_path()
     handler = HCACHE.get(config)
     if handler is not None:
         return handler
--- a/devtools/testlib.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/devtools/testlib.py	Mon Apr 07 17:02:04 2014 +0200
@@ -704,11 +704,6 @@
         entity.cw_attr_cache.pop('modification_date', None)
         self.assertTrue(entity.modification_date > olddate)
 
-    def assertItemsEqual(self, it1, it2, *args, **kwargs):
-        it1 = set(getattr(x, 'eid', x) for x in it1)
-        it2 = set(getattr(x, 'eid', x) for x in it2)
-        super(CubicWebTC, self).assertItemsEqual(it1, it2, *args, **kwargs)
-
     def assertMessageEqual(self, req, params, expected_msg):
         msg = req.session.data[params['_cwmsgid']]
         self.assertEqual(expected_msg, msg)
--- a/doc/book/en/annexes/rql/debugging.rst	Mon Apr 07 14:15:35 2014 +0200
+++ b/doc/book/en/annexes/rql/debugging.rst	Mon Apr 07 17:02:04 2014 +0200
@@ -37,7 +37,7 @@
 
 .. sourcecode:: python
 
-    from cubicweb.server import debuged, DBG_HOOKS
+    from cubicweb.server import debugged, DBG_HOOKS
     with debugged(DBG_HOOKS):
         person.cw_set(works_for=company)
 
--- a/entity.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/entity.py	Mon Apr 07 17:02:04 2014 +0200
@@ -788,13 +788,13 @@
             assert role in ('subject', 'object'), role
             skip_copy_for[role].add(rtype)
         for rschema in self.e_schema.subject_relations():
+            if rschema.type in skip_copy_for['subject']:
+                continue
             if rschema.final or rschema.meta:
                 continue
             # skip already defined relations
             if getattr(self, rschema.type):
                 continue
-            if rschema.type in skip_copy_for['subject']:
-                continue
             # skip composite relation
             rdef = self.e_schema.rdef(rschema)
             if rdef.composite:
--- a/ext/rest.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/ext/rest.py	Mon Apr 07 17:02:04 2014 +0200
@@ -437,7 +437,7 @@
         # necessary for proper garbage collection, else a ref is kept somewhere in docutils...
         del pub.settings.context
         return res
-    except Exception:
+    except BaseException:
         LOGGER.exception('error while publishing ReST text')
         if not isinstance(data, unicode):
             data = unicode(data, encoding, 'replace')
--- a/hooks/syncschema.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/hooks/syncschema.py	Mon Apr 07 17:02:04 2014 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -196,13 +196,11 @@
                 clear_cache(eschema, 'ordered_relations')
 
     def postcommit_event(self):
-        rebuildinfered = self.cnx.get_shared_data('rebuild-infered', True)
         repo = self.cnx.repo
         # commit event should not raise error, while set_schema has chances to
         # do so because it triggers full vreg reloading
         try:
-            if rebuildinfered:
-                repo.schema.rebuild_infered_relations()
+            repo.schema.rebuild_infered_relations()
             # trigger vreg reload
             repo.set_schema(repo.schema)
             # CWUser class might have changed, update current session users
@@ -650,7 +648,11 @@
         rdef = self.rdef
         # in-place modification of in-memory schema first
         _set_modifiable_constraints(rdef)
-        rdef.constraints.remove(self.oldcstr)
+        if self.oldcstr in rdef.constraints:
+            rdef.constraints.remove(self.oldcstr)
+        else:
+            self.critical('constraint %s for rdef %s was missing or already removed',
+                          self.oldcstr, rdef)
         # then update database: alter the physical schema on size/unique
         # constraint changes
         syssource = cnx.repo.system_source
@@ -1136,7 +1138,7 @@
             rdef = schema.schema_by_eid(entity.reverse_constrained_by[0].eid)
             # IndexError
             cstr = rdef.constraint_by_type(entity.type)
-        except (IndexError, KeyError):
+        except (KeyError, IndexError):
             self._cw.critical('constraint type no more accessible')
         else:
             CWConstraintDelOp(self._cw, rdef=rdef, oldcstr=cstr)
--- a/misc/migration/3.18.0_Any.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/misc/migration/3.18.0_Any.py	Mon Apr 07 17:02:04 2014 +0200
@@ -74,7 +74,12 @@
 rql('SET X to_entity B WHERE X is CWAttribute, X from_entity Y, Y name "CWAttribute", '
     'X relation_type Z, Z name "defaultval", B name "Bytes", NOT X to_entity B')
 
-schema['defaultval'].rdefs.values()[0].object = schema['Bytes']
+oldrdef = schema['CWAttribute'].rdef('defaultval')
+import yams.buildobjs as ybo
+newrdef = ybo.RelationDefinition('CWAttribute', 'defaultval', 'Bytes')
+newrdef.eid = oldrdef.eid
+schema.add_relation_def(newrdef)
+schema.del_relation_def('CWAttribute', 'defaultval', 'String')
 
 commit()
 
--- a/rqlrewrite.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/rqlrewrite.py	Mon Apr 07 17:02:04 2014 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -405,7 +405,7 @@
                     assert len(subquery.children) == 1
                     subselect = subquery.children[0]
                     RQLRewriter(self.session).rewrite(subselect, [(varmap, rqlexprs)],
-                                                      subselect.solutions, self.kwargs)
+                                                      self.kwargs)
                     return
                 if varexistsmap is None:
                     # build an index for quick access to relations
@@ -790,7 +790,7 @@
         vargraph = self.current_expr.vargraph
         for existingvar in self.existingvars:
             #path = has_path(vargraph, varname, existingvar)
-            if has_path(vargraph, varname, existingvar):
+            if not varname in vargraph or has_path(vargraph, varname, existingvar):
                 return True
         # no path from this variable to an existing variable
         return False
--- a/schema.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/schema.py	Mon Apr 07 17:02:04 2014 +0200
@@ -350,6 +350,13 @@
             return self._check(_cw, x=eid, **kwargs)
         return self._check(_cw, **kwargs)
 
+def constraint_by_eid(self, eid):
+    for cstr in self.constraints:
+        if cstr.eid == eid:
+            return cstr
+    raise ValueError('No constraint with eid %d' % eid)
+RelationDefinitionSchema.constraint_by_eid = constraint_by_eid
+
 
 def vargraph(rqlst):
     """ builds an adjacency graph of variables from the rql syntax tree, e.g:
--- a/server/migractions.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/server/migractions.py	Mon Apr 07 17:02:04 2014 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -103,7 +103,6 @@
         # no config on shell to a remote instance
         if config is not None and (cnx or connect):
             repo = self.repo
-            self.session.set_shared_data('rebuild-infered', False)
             # register a hook to clear our group_mapping cache and the
             # self._synchronized set when some group is added or updated
             ClearGroupMap.mih = self
@@ -596,12 +595,18 @@
             self.rqlexecall(ss.updaterdef2rql(rdef, repordef.eid),
                             ask_confirm=confirm)
             # constraints
-            newconstraints = list(rdef.constraints)
+            # 0. eliminate the set of unmodified constraints from the sets of
+            # old/new constraints
+            newconstraints = set(rdef.constraints)
+            oldconstraints = set(repordef.constraints)
+            unchanged_constraints = newconstraints & oldconstraints
+            newconstraints -= unchanged_constraints
+            oldconstraints -= unchanged_constraints
             # 1. remove old constraints and update constraints of the same type
             # NOTE: don't use rschema.constraint_by_type because it may be
             #       out of sync with newconstraints when multiple
             #       constraints of the same type are used
-            for cstr in repordef.constraints:
+            for cstr in oldconstraints:
                 for newcstr in newconstraints:
                     if newcstr.type() == cstr.type():
                         break
--- a/server/test/data/migratedapp/schema.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/server/test/data/migratedapp/schema.py	Mon Apr 07 17:02:04 2014 +0200
@@ -22,6 +22,7 @@
                             RichString, String, Int, Boolean, Datetime, Date)
 from yams.constraints import SizeConstraint, UniqueConstraint
 from cubicweb.schema import (WorkflowableEntityType, RQLConstraint,
+                             RQLVocabularyConstraint,
                              ERQLExpression, RRQLExpression)
 
 class Affaire(EntityType):
@@ -177,4 +178,3 @@
 class same_as(RelationDefinition):
     subject = ('Societe',)
     object = 'ExternalUri'
-
--- a/server/test/data/schema.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/server/test/data/schema.py	Mon Apr 07 17:02:04 2014 +0200
@@ -22,6 +22,7 @@
 from yams.constraints import SizeConstraint
 from cubicweb.schema import (WorkflowableEntityType,
                              RQLConstraint, RQLUniqueConstraint,
+                             RQLVocabularyConstraint,
                              ERQLExpression, RRQLExpression)
 
 class Affaire(WorkflowableEntityType):
@@ -157,6 +158,8 @@
         }
     subject = 'Personne'
     object = 'Societe'
+    constraints = [RQLVocabularyConstraint('S owned_by U'),
+                   RQLVocabularyConstraint('S created_by U')]
 
 class comments(RelationDefinition):
     subject = 'Comment'
@@ -187,6 +190,10 @@
 class evaluee(RelationDefinition):
     subject = ('Personne', 'CWUser', 'Societe')
     object = ('Note')
+    constraints = [
+        RQLVocabularyConstraint('S created_by U'),
+        RQLVocabularyConstraint('S owned_by U'),
+    ]
 
 class ecrit_par(RelationType):
     inlined = True
--- a/server/test/unittest_migractions.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/server/test/unittest_migractions.py	Mon Apr 07 17:02:04 2014 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -224,7 +224,6 @@
         self.assertTrue(self.session.execute('CWEType X WHERE X name "Folder2"'))
         self.assertTrue('filed_under2' in self.schema)
         self.assertTrue(self.session.execute('CWRType X WHERE X name "filed_under2"'))
-        self.schema.rebuild_infered_relations()
         self.assertEqual(sorted(str(rs) for rs in self.schema['Folder2'].subject_relations()),
                           ['created_by', 'creation_date', 'cw_source', 'cwuri',
                            'description', 'description_format',
@@ -270,7 +269,6 @@
     def test_add_drop_relation_type(self):
         self.mh.cmd_add_entity_type('Folder2', auto=False)
         self.mh.cmd_add_relation_type('filed_under2')
-        self.schema.rebuild_infered_relations()
         self.assertTrue('filed_under2' in self.schema)
         # Old will be missing as it has been renamed into 'New' in the migrated
         # schema while New hasn't been added here.
@@ -327,17 +325,11 @@
         self.assertEqual(sorted(str(e) for e in self.schema['concerne'].subjects()),
                           ['Affaire', 'Personne'])
         self.assertEqual(sorted(str(e) for e in self.schema['concerne'].objects()),
-                          ['Affaire', 'Division', 'Note', 'SubDivision'])
-        self.schema.rebuild_infered_relations() # need to be explicitly called once everything is in place
-        self.assertEqual(sorted(str(e) for e in self.schema['concerne'].objects()),
                           ['Affaire', 'Note'])
         self.mh.cmd_add_relation_definition('Affaire', 'concerne', 'Societe')
         self.assertEqual(sorted(str(e) for e in self.schema['concerne'].subjects()),
                           ['Affaire', 'Personne'])
         self.assertEqual(sorted(str(e) for e in self.schema['concerne'].objects()),
-                          ['Affaire', 'Note', 'Societe'])
-        self.schema.rebuild_infered_relations() # need to be explicitly called once everything is in place
-        self.assertEqual(sorted(str(e) for e in self.schema['concerne'].objects()),
                           ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
         # trick: overwrite self.maxeid to avoid deletion of just reintroduced types
         self.maxeid = self.session.execute('Any MAX(X)')[0][0]
@@ -371,6 +363,26 @@
             self.mh.cmd_change_relation_props('Personne', 'adel', 'String',
                                               fulltextindexed=False)
 
+    def test_sync_schema_props_perms_rqlconstraints(self):
+        # Drop one of the RQLConstraint.
+        rdef = self.schema['evaluee'].rdefs[('Personne', 'Note')]
+        oldconstraints = rdef.constraints
+        self.assertIn('S created_by U',
+                      [cstr.expression for cstr in oldconstraints])
+        self.mh.cmd_sync_schema_props_perms('evaluee', commit=True)
+        newconstraints = rdef.constraints
+        self.assertNotIn('S created_by U',
+                         [cstr.expression for cstr in newconstraints])
+
+        # Drop all RQLConstraint.
+        rdef = self.schema['travaille'].rdefs[('Personne', 'Societe')]
+        oldconstraints = rdef.constraints
+        self.assertEqual(len(oldconstraints), 2)
+        self.mh.cmd_sync_schema_props_perms('travaille', commit=True)
+        rdef = self.schema['travaille'].rdefs[('Personne', 'Societe')]
+        newconstraints = rdef.constraints
+        self.assertEqual(len(newconstraints), 0)
+
     @tag('longrun')
     def test_sync_schema_props_perms(self):
         cursor = self.mh.session
@@ -598,12 +610,10 @@
     @tag('longrun')
     def test_introduce_base_class(self):
         self.mh.cmd_add_entity_type('Para')
-        self.mh.repo.schema.rebuild_infered_relations()
         self.assertEqual(sorted(et.type for et in self.schema['Para'].specialized_by()),
                           ['Note'])
         self.assertEqual(self.schema['Note'].specializes().type, 'Para')
         self.mh.cmd_add_entity_type('Text')
-        self.mh.repo.schema.rebuild_infered_relations()
         self.assertEqual(sorted(et.type for et in self.schema['Para'].specialized_by()),
                           ['Note', 'Text'])
         self.assertEqual(self.schema['Text'].specializes().type, 'Para')
@@ -627,12 +637,8 @@
         #
         # also we need more tests about introducing/removing base classes or
         # specialization relationship...
-        self.session.set_shared_data('rebuild-infered', True)
-        try:
-            self.session.execute('DELETE X specializes Y WHERE Y name "Para"')
-            self.session.commit(free_cnxset=False)
-        finally:
-            self.session.set_shared_data('rebuild-infered', False)
+        self.session.execute('DELETE X specializes Y WHERE Y name "Para"')
+        self.session.commit(free_cnxset=False)
         self.assertEqual(sorted(et.type for et in self.schema['Para'].specialized_by()),
                           [])
         self.assertEqual(self.schema['Note'].specializes(), None)
--- a/server/test/unittest_security.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/server/test/unittest_security.py	Mon Apr 07 17:02:04 2014 +0200
@@ -544,25 +544,6 @@
             names = [t for t, in cu.execute('Any N ORDERBY lower(N) WHERE X name N')]
             self.assertEqual(names, sorted(names, key=lambda x: x.lower()))
 
-    def test_restrict_is_instance_ok(self):
-        rset = self.execute('Any X WHERE X is_instance_of BaseTransition')
-        rqlst = rset.syntax_tree()
-        select = rqlst.children[0]
-        x = select.get_selected_variables().next()
-        self.assertRaises(RQLException, select.add_type_restriction,
-                          x.variable, 'CWUser')
-        select.add_type_restriction(x.variable, 'BaseTransition')
-        select.add_type_restriction(x.variable, 'WorkflowTransition')
-        self.assertEqual(rqlst.as_string(), 'Any X WHERE X is_instance_of WorkflowTransition')
-
-    def test_restrict_is_instance_no_supported(self):
-        rset = self.execute('Any X WHERE X is_instance_of IN(CWUser, CWGroup)')
-        rqlst = rset.syntax_tree()
-        select = rqlst.children[0]
-        x = select.get_selected_variables().next()
-        self.assertRaises(NotImplementedError, select.add_type_restriction,
-                          x.variable, 'WorkflowTransition')
-
     def test_in_state_without_update_perm(self):
         """check a user change in_state without having update permission on the
         subject
--- a/test/data/rrqlexpr_on_attr.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/test/data/rrqlexpr_on_attr.py	Mon Apr 07 17:02:04 2014 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,9 +15,7 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
 
-"""
 from yams.buildobjs import EntityType, RelationType, String
 from cubicweb.schema import RRQLExpression
 
@@ -34,4 +32,5 @@
     __permissions__ = {
         'read': ('managers', ),
         'update': ('managers', RRQLExpression('S bla Y'),),
+        'add': ('managers', RRQLExpression('S bla Y'),),
         }
--- a/test/unittest_rqlrewrite.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/test/unittest_rqlrewrite.py	Mon Apr 07 17:02:04 2014 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -555,5 +555,40 @@
                              'AC is CWUser, E is CWUser, X is IN(Division, Societe)',
                              union.as_string())
 
+    def test_question_mark_attribute_snippet(self):
+        # see #3661918
+        from cubicweb.rqlrewrite import RQLRewriter
+        from logilab.common.decorators import monkeypatch
+        repotest.undo_monkey_patch()
+        orig_insert_snippets = RQLRewriter.insert_snippets
+        # patch insert_snippets and not rewrite, insert_snippets is already
+        # monkey patches (see above setupModule/repotest)
+        @monkeypatch(RQLRewriter)
+        def insert_snippets(self, snippets, varexistsmap=None):
+            # crash occurs if snippets are processed in a specific order, force
+            # destiny
+            if snippets[0][0] != {u'N': 'X'}:
+                snippets = list(reversed(snippets))
+            return orig_insert_snippets(self, snippets, varexistsmap)
+        try:
+            with self.temporary_permissions(
+                    (self.schema['Affaire'],
+                     {'read': (ERQLExpression('X ref "blah"'), )}),
+                    (self.schema['Note'],
+                     {'read': (ERQLExpression(
+                         'EXISTS(X inlined_affaire Z), EXISTS(Z owned_by U)'), )}),
+            ):
+                union = self.process(
+                    'Any A,COUNT(N) GROUPBY A '
+                    'WHERE A is Affaire, N? inlined_affaire A')
+                self.assertEqual('Any A,COUNT(N) GROUPBY A WHERE A is Affaire '
+                                 'WITH N,A BEING (Any N,A WHERE N? inlined_affaire A, '
+                                 '(N is NULL) OR (EXISTS(EXISTS(N inlined_affaire B), '
+                                 'EXISTS(B owned_by %(E)s), B is Affaire)), '
+                                 'A is Affaire, N is Note, EXISTS(A ref "blah"))',
+                                 union.as_string())
+        finally:
+            RQLRewriter.insert_snippets = orig_insert_snippets
+
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_utils.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/test/unittest_utils.py	Mon Apr 07 17:02:04 2014 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -271,6 +271,7 @@
         self.assertEqual(list(result), expected)
 
     def test_getvalue_with_concat(self):
+        self.config.global_set_option('concat-resources', True)
         base_url = u'http://test.fr/data/'
         head = self.htmlhead(base_url)
         head.add_js(base_url + u'bob0.js')
--- a/web/component.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/web/component.py	Mon Apr 07 17:02:04 2014 +0200
@@ -108,8 +108,12 @@
         view = self.cw_extra_kwargs.get('view')
         if view is not None and hasattr(view, 'page_navigation_url'):
             url = view.page_navigation_url(self, path, params)
+        elif path in ('json', 'ajax'):
+            # 'ajax' is the new correct controller, but the old 'json'
+            # controller should still be supported
+            url = self.ajax_page_url(**params)
         else:
-            url = self.ajax_page_url(**params)
+            url = self._cw.build_url(path, **params)
         # XXX hack to avoid opening a new page containing the evaluation of the
         # js expression on ajax call
         if url.startswith('javascript:'):
@@ -118,9 +122,9 @@
 
     def ajax_page_url(self, **params):
         divid = params.setdefault('divid', 'pageContent')
-        params['fname'] = 'view'
         params['rql'] = self.cw_rset.printable_rql()
-        return self._cw.build_url('ajax', **params)
+        return js_href("$(%s).loadxhtml(AJAX_PREFIX_URL, %s, 'get', 'swap')" % (
+            json_dumps('#'+divid), js.ajaxFuncArgs('view', params)))
 
     def page_link(self, path, params, start, stop, content):
         url = xml_escape(self.page_url(path, params, start, stop))
--- a/web/views/editcontroller.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/web/views/editcontroller.py	Mon Apr 07 17:02:04 2014 +0200
@@ -264,7 +264,7 @@
             errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
             raise ValidationError(valerror_eid(entity.eid), errors)
         if eid is None: # creation or copy
-            entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery)
+            entity.eid = eid = self._insert_entity(etype, formparams['eid'], rqlquery)
         elif rqlquery.edited: # edition of an existant entity
             self._update_entity(eid, rqlquery)
         if is_main_entity:
--- a/web/views/navigation.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/web/views/navigation.py	Mon Apr 07 17:02:04 2014 +0200
@@ -55,7 +55,6 @@
 from logilab.mtconverter import xml_escape
 from logilab.common.deprecation import deprecated
 
-from cubicweb.utils import json_dumps
 from cubicweb.predicates import paginated_rset, sorted_rset, adaptable
 from cubicweb.uilib import cut
 from cubicweb.view import EntityAdapter
@@ -281,13 +280,6 @@
     nav = req.vreg['components'].select_or_none(
         'navigation', req, rset=rset, page_size=page_size, view=view)
     if nav:
-        domid = getattr(view, 'domid', 'pageContent')
-        view._cw.add_onload('''
-        jQuery('div.displayAllLink a, div.pagination a').click(function() {
-            cw.jqNode(%s).loadxhtml(this.href, null, 'get', 'swap');
-            return false;
-        });
-            ''' % json_dumps(domid))
         if w is None:
             w = view.w
         if req.form.get('__force_display'):
--- a/web/webconfig.py	Mon Apr 07 14:15:35 2014 +0200
+++ b/web/webconfig.py	Mon Apr 07 17:02:04 2014 +0200
@@ -225,7 +225,7 @@
           }),
         ('concat-resources',
          {'type' : 'yn',
-          'default': True,
+          'default': False,
           'help': 'use modconcat-like URLS to concat and serve JS / CSS files',
           'group': 'web', 'level': 2,
           }),