backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 05 Aug 2011 14:23:55 +0200
changeset 7749 cbb0a0ce3795
parent 7721 d313666c171e (current diff)
parent 7748 997b8eebd4b4 (diff)
child 7761 46a8cf85d186
backport stable
--- a/.hgtags	Wed Jul 27 20:17:45 2011 +0200
+++ b/.hgtags	Fri Aug 05 14:23:55 2011 +0200
@@ -218,3 +218,5 @@
 9ad5411199e00b2611366439b82f35d7d3285423 cubicweb-debian-version-3.13.2-1
 0e82e7e5a34f57d7239c7a42e48ba4d5e53abab2 cubicweb-version-3.13.3
 fb48c55cb80234bc0164c9bcc0e2cfc428836e5f cubicweb-debian-version-3.13.3-1
+223ecf0620b6c87d997f8011aca0d9f0ee4750af cubicweb-version-3.13.4
+52f26475d764129c5559b2d80fd57e6ea1bdd6ba cubicweb-debian-version-3.13.4-1
--- a/README	Wed Jul 27 20:17:45 2011 +0200
+++ b/README	Fri Aug 05 14:23:55 2011 +0200
@@ -5,20 +5,21 @@
 developped at Logilab.
 
 This package contains:
-* a repository server
-* a RQL command line client to the repository
-* an adaptative modpython interface to the server
-* a bunch of other management tools
+
+- a repository server
+- a RQL command line client to the repository
+- an adaptative modpython interface to the server
+- a bunch of other management tools
 
 Install
 -------
 
-More details at http://www.cubicweb.org/doc/en/admin/setup
+More details at http://docs.cubicweb.org/admin/setup
 
 Getting started
 ---------------
 
-Execute:
+Execute::
 
  apt-get install cubicweb cubicweb-dev cubicweb-blog
  cubicweb-ctl create blog myblog
--- a/__pkginfo__.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/__pkginfo__.py	Fri Aug 05 14:23:55 2011 +0200
@@ -22,7 +22,7 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 13, 3)
+numversion = (3, 13, 4)
 version = '.'.join(str(num) for num in numversion)
 
 description = "a repository of entities / relations for knowledge management"
@@ -43,7 +43,7 @@
     'logilab-common': '>= 0.55.2',
     'logilab-mtconverter': '>= 0.8.0',
     'rql': '>= 0.28.0',
-    'yams': '>= 0.32.0',
+    'yams': '>= 0.33.0',
     'docutils': '>= 0.6',
     #gettext                    # for xgettext, msgcat, etc...
     # web dependancies
--- a/debian/changelog	Wed Jul 27 20:17:45 2011 +0200
+++ b/debian/changelog	Fri Aug 05 14:23:55 2011 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.13.4-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Fri, 05 Aug 2011 12:22:11 +0200
+
 cubicweb (3.13.3-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Wed Jul 27 20:17:45 2011 +0200
+++ b/debian/control	Fri Aug 05 14:23:55 2011 +0200
@@ -99,7 +99,7 @@
 Package: cubicweb-common
 Architecture: all
 XB-Python-Version: ${python:Versions}
-Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.32.0), python-rql (>= 0.28.0), python-lxml
+Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.33.0), python-rql (>= 0.28.0), python-lxml
 Recommends: python-simpletal (>= 4.0), python-crypto
 Conflicts: cubicweb-core
 Replaces: cubicweb-core
--- a/devtools/__init__.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/devtools/__init__.py	Fri Aug 05 14:23:55 2011 +0200
@@ -657,6 +657,24 @@
 class SQLiteTestDataBaseHandler(TestDataBaseHandler):
     DRIVER = 'sqlite'
 
+    __TMPDB = set()
+
+    @classmethod
+    def _cleanup_all_tmpdb(cls):
+        for dbpath in cls.__TMPDB:
+            cls._cleanup_database(dbpath)
+
+
+
+    def __init__(self, *args, **kwargs):
+        super(SQLiteTestDataBaseHandler, self).__init__(*args, **kwargs)
+        # use a dedicated base for each process.
+        if 'global-db-name' not in self.system_source:
+            self.system_source['global-db-name'] = self.system_source['db-name']
+            process_db = self.system_source['db-name'] + str(os.getpid())
+            self.__TMPDB.add(process_db)
+            self.system_source['db-name'] = process_db
+
     @staticmethod
     def _cleanup_database(dbfile):
         try:
@@ -665,6 +683,10 @@
         except OSError:
             pass
 
+    @property
+    def dbname(self):
+        return self.system_source['global-db-name']
+
     def absolute_dbfile(self):
         """absolute path of current database file"""
         dbfile = join(self._ensure_test_backup_db_dir(),
@@ -707,6 +729,9 @@
         self._cleanup_database(self.absolute_dbfile())
         init_repository(self.config, interactive=False)
 
+import atexit
+atexit.register(SQLiteTestDataBaseHandler._cleanup_all_tmpdb)
+
 
 def install_sqlite_patch(querier):
     """This patch hotfixes the following sqlite bug :
--- a/devtools/devctl.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/devtools/devctl.py	Fri Aug 05 14:23:55 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -155,6 +155,9 @@
             add_msg(w, cstrtype)
     done = set()
     for eschema in sorted(schema.entities()):
+        if eschema.type in libschema:
+            done.add(eschema.description)
+    for eschema in sorted(schema.entities()):
         etype = eschema.type
         if etype not in libschema:
             add_msg(w, etype)
@@ -203,15 +206,19 @@
     w('# (no object form for final or symmetric relation types)\n')
     w('\n')
     for rschema in sorted(schema.relations()):
+        if rschema.type in libschema:
+            done.add(rschema.type)
+            done.add(rschema.description)
+    for rschema in sorted(schema.relations()):
         rtype = rschema.type
         if rtype not in libschema:
             # bw compat, necessary until all translation of relation are done
             # properly...
             add_msg(w, rtype)
+            done.add(rtype)
             if rschema.description and rschema.description not in done:
-                done.add(rschema.description)
                 add_msg(w, rschema.description)
-            done.add(rtype)
+            done.add(rschema.description)
             librschema = None
         else:
             librschema = libschema.rschema(rtype)
@@ -221,7 +228,7 @@
             for subjschema in rschema.subjects():
                 if not subjschema in libsubjects:
                     add_msg(w, rtype, subjschema.type)
-        if not (schema.rschema(rtype).final or rschema.symmetric):
+        if not (rschema.final or rschema.symmetric):
             if rschema not in NO_I18NCONTEXT:
                 libobjects = librschema and librschema.objects() or ()
                 for objschema in rschema.objects():
@@ -231,6 +238,14 @@
                 # bw compat, necessary until all translation of relation are
                 # done properly...
                 add_msg(w, '%s_object' % rtype)
+        for rdef in rschema.rdefs.itervalues():
+            if not rdef.description or rdef.description in done:
+                continue
+            if (librschema is None or
+                (rdef.subject, rdef.object) not in librschema.rdefs or
+                librschema.rdefs[(rdef.subject, rdef.object)].description != rdef.description):
+                add_msg(w, rdef.description)
+            done.add(rdef.description)
     for objid in _iter_vreg_objids(vreg, vregdone):
         add_msg(w, '%s_description' % objid)
         add_msg(w, objid)
--- a/doc/book/en/annexes/rql/language.rst	Wed Jul 27 20:17:45 2011 +0200
+++ b/doc/book/en/annexes/rql/language.rst	Fri Aug 05 14:23:55 2011 +0200
@@ -113,9 +113,38 @@
 ``````````````````````
 ::
 
-     +, -, *, /
++==========+=====================+===========+========+
+| Operator |    Description      | Example   | Result |
++==========+=====================+===========+========+
+|  +       | addition            | 2 + 3     | 5      |
++----------+---------------------+-----------+--------+
+|  -       | subtraction         | 2 - 3     | -1     |
++----------+---------------------+-----------+--------+
+|  *       | multiplication      | 2 * 3     | 6      |
++----------+---------------------+-----------+--------+
+|  /       | division            | 4 / 2     | 2      |
++----------+---------------------+-----------+--------+
+|  %       | modulo (remainder)  | 5 % 4     | 1      |
++----------+---------------------+-----------+--------+
+|  ^       | exponentiation      | 2.0 ^ 3.0 | 8      |
++----------+---------------------+-----------+--------+
+|  &       | bitwise AND         | 91 & 15   | 11     |
++----------+---------------------+-----------+--------+
+|  |       | bitwise OR          | 32 | 3    | 35     |
++----------+---------------------+-----------+--------+
+|  #       | bitwise XOR         | 17 # 5    | 20     |
++----------+---------------------+-----------+--------+
+|  ~       | bitwise NOT         | ~1        | -2     |
++----------+---------------------+-----------+--------+
+|  <<      | bitwise shift left  | 1 << 4    | 16     |
++----------+---------------------+-----------+--------+
+|  >>      | bitwise shift right | 8 >> 2    | 2      |
++----------+---------------------+-----------+--------+
 
-Those should behave as you expect.
+  +, -, *, /
+
+Notice integer division truncates results depending on the backend behaviour. For
+instance, postgresql does.
 
 
 .. _RQLComparisonOperators:
@@ -191,13 +220,14 @@
 Operators priority
 ``````````````````
 
-#. "(", ")"
-#. '*', '/'
-#. '+', '-'
-#. 'NOT'
-#. 'AND'
-#. 'OR'
-#. ','
+#. `(`, `)`
+#. `^`, `<<`, `>>`
+#. `*`, `/`, `%`, `&`
+#. `+`, `-`, `|`, `#`
+#. `NOT`
+#. `AND`
+#. `OR`
+#. `,`
 
 
 .. _RQLSearchQuery:
@@ -328,7 +358,7 @@
 You must use the `?` behind a variable to specify that the relation toward it
 is optional. For instance:
 
-- Anomalies of a project attached or not to a version ::
+- Bugs of a project attached or not to a version ::
 
        Any X, V WHERE X concerns P, P eid 42, X corrected_in V?
 
@@ -340,6 +370,28 @@
 
        Any C, P WHERE C is Card, P? documented_by C
 
+Notice you may also use outer join:
+
+- on the RHS of attribute relation, e.g. ::
+
+       Any X WHERE X ref XR, Y name XR?
+
+  so that Y is outer joined on X by ref/name attributes comparison
+
+
+- on any side of an `HAVING` expression, e.g. ::
+
+       Any X WHERE X creation_date XC, Y creation_date YC
+       HAVING YEAR(XC)=YEAR(YC)?
+
+  so that Y is outer joined on X by comparison of the year extracted from their
+  creation date. ::
+
+       Any X WHERE X creation_date XC, Y creation_date YC
+       HAVING YEAR(XC)?=YEAR(YC)
+
+  would outer join X on Y instead.
+
 
 Having restrictions
 ```````````````````
--- a/entities/lib.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/entities/lib.py	Fri Aug 05 14:23:55 2011 +0200
@@ -40,6 +40,7 @@
 class EmailAddress(AnyEntity):
     __regid__ = 'EmailAddress'
     fetch_attrs, fetch_order = fetch_config(['address', 'alias'])
+    rest_attr = 'eid'
 
     def dc_title(self):
         if self.alias:
--- a/ext/rest.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/ext/rest.py	Fri Aug 05 14:23:55 2011 +0200
@@ -105,11 +105,17 @@
     else:
         rql, vid = text, None
     _cw.ensure_ro_rql(rql)
-    rset = _cw.execute(rql, {'userid': _cw.user.eid})
-    if vid is None:
-        vid = vid_from_rset(_cw, rset, _cw.vreg.schema)
-    view = _cw.vreg['views'].select(vid, _cw, rset=rset)
-    content = view.render()
+    try:
+        rset = _cw.execute(rql, {'userid': _cw.user.eid})
+        if rset:
+            if vid is None:
+                vid = vid_from_rset(_cw, rset, _cw.vreg.schema)
+        else:
+            vid = 'noresult'
+        view = _cw.vreg['views'].select(vid, _cw, rset=rset)
+        content = view.render()
+    except Exception, exc:
+        content = 'an error occured while interpreting this rql directive: %r' % exc
     set_classes(options)
     return [nodes.raw('', content, format='html')], []
 
--- a/ext/test/unittest_rest.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/ext/test/unittest_rest.py	Fri Aug 05 14:23:55 2011 +0200
@@ -63,6 +63,16 @@
         self.assert_(out.endswith('<a href="http://testing.fr/cubicweb/cwuser/anon" title="">anon</a>'
                                   '</td></tr></tbody></table></div>\n</div>\n</p>\n'))
 
+    def test_rql_role_with_vid_empty_rset(self):
+        context = self.context()
+        out = rest_publish(context, ':rql:`Any X WHERE X is CWUser, X login "nono":table`')
+        self.assert_(out.endswith('<p><div class="searchMessage"><strong>No result matching query</strong></div>\n</p>\n'))
+
+    def test_rql_role_with_unknown_vid(self):
+        context = self.context()
+        out = rest_publish(context, ':rql:`Any X WHERE X is CWUser:toto`')
+        self.assert_(out.startswith("<p>an error occured while interpreting this rql directive: ObjectNotFound(u'toto',)</p>"))
+
     def test_rql_role_without_vid(self):
         context = self.context()
         out = rest_publish(context, ':rql:`Any X WHERE X is CWUser`')
--- a/hooks/syncsources.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/hooks/syncsources.py	Fri Aug 05 14:23:55 2011 +0200
@@ -19,6 +19,7 @@
 
 from socket import gethostname
 
+from logilab.common.decorators import clear_cache
 from yams.schema import role_name
 
 from cubicweb import ValidationError
@@ -66,7 +67,7 @@
         SourceRemovedOp(self._cw, uri=self.entity.name)
 
 
-class SourceUpdatedOp(hook.DataOperationMixIn, hook.Operation):
+class SourceConfigUpdatedOp(hook.DataOperationMixIn, hook.Operation):
 
     def precommit_event(self):
         self.__processed = []
@@ -79,13 +80,45 @@
         for source, conf in self.__processed:
             source.repo_source.update_config(source, conf)
 
+
+class SourceRenamedOp(hook.LateOperation):
+
+    def precommit_event(self):
+        source = self.session.repo.sources_by_uri[self.oldname]
+        if source.copy_based_source:
+            sql = 'UPDATE entities SET asource=%(newname)s WHERE asource=%(oldname)s'
+        else:
+            sql = 'UPDATE entities SET source=%(newname)s, asource=%(newname)s WHERE source=%(oldname)s'
+        self.session.system_sql(sql, {'oldname': self.oldname,
+                                      'newname': self.newname})
+
+    def postcommit_event(self):
+        repo = self.session.repo
+        # XXX race condition
+        source = repo.sources_by_uri.pop(self.oldname)
+        source.uri = self.newname
+        source.public_config['uri'] = self.newname
+        repo.sources_by_uri[self.newname] = source
+        repo._type_source_cache.clear()
+        clear_cache(repo, 'source_defs')
+        if not source.copy_based_source:
+            repo._extid_cache.clear()
+            repo._clear_planning_caches()
+            for cnxset in repo.cnxsets:
+                cnxset.source_cnxs[self.oldname] = cnxset.source_cnxs.pop(self.oldname)
+
+
 class SourceUpdatedHook(SourceHook):
     __regid__ = 'cw.sources.configupdate'
     __select__ = SourceHook.__select__ & is_instance('CWSource')
-    events = ('after_update_entity',)
+    events = ('before_update_entity',)
     def __call__(self):
         if 'config' in self.entity.cw_edited:
-            SourceUpdatedOp.get_instance(self._cw).add_data(self.entity)
+            SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
+        if 'name' in self.entity.cw_edited:
+            oldname, newname = self.entity.cw_edited.oldnewvalue('name')
+            SourceRenamedOp(self._cw, oldname=oldname, newname=newname)
+
 
 class SourceHostConfigUpdatedHook(SourceHook):
     __regid__ = 'cw.sources.hostconfigupdate'
@@ -97,7 +130,7 @@
                    not 'config' in self.entity.cw_edited:
                 return
             try:
-                SourceUpdatedOp.get_instance(self._cw).add_data(self.entity.cwsource)
+                SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity.cwsource)
             except IndexError:
                 # XXX no source linked to the host config yet
                 pass
--- a/i18n/de.po	Wed Jul 27 20:17:45 2011 +0200
+++ b/i18n/de.po	Fri Aug 05 14:23:55 2011 +0200
@@ -1250,6 +1250,9 @@
 msgid "auto"
 msgstr "automatisch"
 
+msgid "autocomputed attribute used to ensure transition coherency"
+msgstr ""
+
 msgid "automatic"
 msgstr "automatisch"
 
@@ -2288,9 +2291,6 @@
 msgid "eid"
 msgstr ""
 
-msgid "email address to use for notification"
-msgstr "E-Mail-Adresse für Mitteilungen."
-
 msgid "emails successfully sent"
 msgstr "E-Mails erfolgreich versandt."
 
@@ -2619,6 +2619,18 @@
 msgid "groups"
 msgstr "Gruppen"
 
+msgid "groups allowed to add entities/relations of this type"
+msgstr ""
+
+msgid "groups allowed to delete entities/relations of this type"
+msgstr ""
+
+msgid "groups allowed to read entities/relations of this type"
+msgstr ""
+
+msgid "groups allowed to update entities/relations of this type"
+msgstr ""
+
 msgid "groups grant permissions to the user"
 msgstr "die Gruppen geben dem Nutzer Rechte"
 
@@ -3604,6 +3616,18 @@
 msgid "right"
 msgstr "rechts"
 
+msgid "rql expression allowing to add entities/relations of this type"
+msgstr ""
+
+msgid "rql expression allowing to delete entities/relations of this type"
+msgstr ""
+
+msgid "rql expression allowing to read entities/relations of this type"
+msgstr ""
+
+msgid "rql expression allowing to update entities/relations of this type"
+msgstr ""
+
 msgid "rql expressions"
 msgstr "RQL-Ausdrücke"
 
@@ -4323,6 +4347,9 @@
 msgid "user preferences"
 msgstr "Nutzereinstellungen"
 
+msgid "user's email account"
+msgstr ""
+
 msgid "users"
 msgstr "Nutzer"
 
@@ -4521,3 +4548,6 @@
 #, python-format
 msgid "you should un-inline relation %s which is supported and may be crossed "
 msgstr ""
+
+#~ msgid "email address to use for notification"
+#~ msgstr "E-Mail-Adresse für Mitteilungen."
--- a/i18n/en.po	Wed Jul 27 20:17:45 2011 +0200
+++ b/i18n/en.po	Fri Aug 05 14:23:55 2011 +0200
@@ -1205,6 +1205,9 @@
 msgid "auto"
 msgstr "automatic"
 
+msgid "autocomputed attribute used to ensure transition coherency"
+msgstr ""
+
 msgid "automatic"
 msgstr ""
 
@@ -2233,9 +2236,6 @@
 msgid "eid"
 msgstr ""
 
-msgid "email address to use for notification"
-msgstr ""
-
 msgid "emails successfully sent"
 msgstr ""
 
@@ -2554,6 +2554,18 @@
 msgid "groups"
 msgstr ""
 
+msgid "groups allowed to add entities/relations of this type"
+msgstr ""
+
+msgid "groups allowed to delete entities/relations of this type"
+msgstr ""
+
+msgid "groups allowed to read entities/relations of this type"
+msgstr ""
+
+msgid "groups allowed to update entities/relations of this type"
+msgstr ""
+
 msgid "groups grant permissions to the user"
 msgstr ""
 
@@ -3511,6 +3523,18 @@
 msgid "right"
 msgstr ""
 
+msgid "rql expression allowing to add entities/relations of this type"
+msgstr ""
+
+msgid "rql expression allowing to delete entities/relations of this type"
+msgstr ""
+
+msgid "rql expression allowing to read entities/relations of this type"
+msgstr ""
+
+msgid "rql expression allowing to update entities/relations of this type"
+msgstr ""
+
 msgid "rql expressions"
 msgstr ""
 
@@ -4209,6 +4233,9 @@
 msgid "user preferences"
 msgstr ""
 
+msgid "user's email account"
+msgstr ""
+
 msgid "users"
 msgstr ""
 
--- a/i18n/es.po	Wed Jul 27 20:17:45 2011 +0200
+++ b/i18n/es.po	Fri Aug 05 14:23:55 2011 +0200
@@ -1261,6 +1261,9 @@
 msgid "auto"
 msgstr "Automático"
 
+msgid "autocomputed attribute used to ensure transition coherency"
+msgstr ""
+
 msgid "automatic"
 msgstr "Automático"
 
@@ -2327,9 +2330,6 @@
 msgid "eid"
 msgstr "eid"
 
-msgid "email address to use for notification"
-msgstr "Dirección electrónica a utilizarse para notificar"
-
 msgid "emails successfully sent"
 msgstr "Mensajes enviados con éxito"
 
@@ -2661,6 +2661,18 @@
 msgid "groups"
 msgstr "Grupos"
 
+msgid "groups allowed to add entities/relations of this type"
+msgstr ""
+
+msgid "groups allowed to delete entities/relations of this type"
+msgstr ""
+
+msgid "groups allowed to read entities/relations of this type"
+msgstr ""
+
+msgid "groups allowed to update entities/relations of this type"
+msgstr ""
+
 msgid "groups grant permissions to the user"
 msgstr "Los grupos otorgan los permisos al usuario"
 
@@ -3654,6 +3666,18 @@
 msgid "right"
 msgstr "Derecha"
 
+msgid "rql expression allowing to add entities/relations of this type"
+msgstr ""
+
+msgid "rql expression allowing to delete entities/relations of this type"
+msgstr ""
+
+msgid "rql expression allowing to read entities/relations of this type"
+msgstr ""
+
+msgid "rql expression allowing to update entities/relations of this type"
+msgstr ""
+
 msgid "rql expressions"
 msgstr "Expresiones RQL"
 
@@ -4373,6 +4397,9 @@
 msgid "user preferences"
 msgstr "Preferencias"
 
+msgid "user's email account"
+msgstr ""
+
 msgid "users"
 msgstr "Usuarios"
 
@@ -4576,3 +4603,6 @@
 #~ msgctxt "CWAttribute"
 #~ msgid "cw_schema_object"
 #~ msgstr "mapeado por"
+
+#~ msgid "email address to use for notification"
+#~ msgstr "Dirección electrónica a utilizarse para notificar"
--- a/i18n/fr.po	Wed Jul 27 20:17:45 2011 +0200
+++ b/i18n/fr.po	Fri Aug 05 14:23:55 2011 +0200
@@ -164,7 +164,7 @@
 #, python-format
 msgid ""
 "'%s' action for in_state relation should at least have 'linkattr=name' option"
-msgstr ""
+msgstr "l'action '%s' pour la relation in_state doit au moins avoir l'option 'linkattr=name'"
 
 #, python-format
 msgid "'%s' action requires 'linkattr' option"
@@ -1174,7 +1174,7 @@
 
 #, python-format
 msgid "allowed values for \"action\" are %s"
-msgstr ""
+msgstr "les valeurs autorisées pour \"action\" sont %s"
 
 msgid "allowed_transition"
 msgstr "transitions autorisées"
@@ -1259,6 +1259,9 @@
 msgid "auto"
 msgstr "automatique"
 
+msgid "autocomputed attribute used to ensure transition coherency"
+msgstr "attribut calculé automatiquement pour assurer la cohérence de la transition"
+
 msgid "automatic"
 msgstr "automatique"
 
@@ -2326,9 +2329,6 @@
 msgid "eid"
 msgstr "eid"
 
-msgid "email address to use for notification"
-msgstr "adresse email à utiliser pour la notification"
-
 msgid "emails successfully sent"
 msgstr "courriels envoyés avec succès"
 
@@ -2660,6 +2660,18 @@
 msgid "groups"
 msgstr "groupes"
 
+msgid "groups allowed to add entities/relations of this type"
+msgstr "groupes autorisés à ajouter des entités/relations de ce type"
+
+msgid "groups allowed to delete entities/relations of this type"
+msgstr "groupes autorisés à supprimer des entités/relations de ce type"
+
+msgid "groups allowed to read entities/relations of this type"
+msgstr "groupes autorisés à lire des entités/relations de ce type"
+
+msgid "groups allowed to update entities/relations of this type"
+msgstr "groupes autorisés à mettre à jour des entités/relations de ce type"
+
 msgid "groups grant permissions to the user"
 msgstr "les groupes donnent des permissions à l'utilisateur"
 
@@ -3239,10 +3251,10 @@
 msgstr "aucune permission associée"
 
 msgid "no content next link"
-msgstr ""
+msgstr "pas de lien 'suivant'"
 
 msgid "no content prev link"
-msgstr ""
+msgstr "pas de lien 'précédent'"
 
 msgid "no edited fields specified"
 msgstr "aucun champ à éditer spécifié"
@@ -3655,6 +3667,18 @@
 msgid "right"
 msgstr "droite"
 
+msgid "rql expression allowing to add entities/relations of this type"
+msgstr "expression rql autorisant à ajouter des entités/relations de ce type"
+
+msgid "rql expression allowing to delete entities/relations of this type"
+msgstr "expression rql autorisant à supprimer des entités/relations de ce type"
+
+msgid "rql expression allowing to read entities/relations of this type"
+msgstr "expression rql autorisant à lire des entités/relations de ce type"
+
+msgid "rql expression allowing to update entities/relations of this type"
+msgstr "expression rql autorisant à mettre à jour des entités/relations de ce type"
+
 msgid "rql expressions"
 msgstr "conditions rql"
 
@@ -4013,10 +4037,10 @@
 msgstr "la valeur \"%s\" est déjà utilisée, veuillez utiliser une autre valeur"
 
 msgid "there is no next page"
-msgstr ""
+msgstr "il n'y a pas de page suivante"
 
 msgid "there is no previous page"
-msgstr ""
+msgstr "il n'y a pas de page précédente"
 
 msgid "this action is not reversible!"
 msgstr ""
@@ -4372,6 +4396,9 @@
 msgid "user preferences"
 msgstr "préférences utilisateur"
 
+msgid "user's email account"
+msgstr "email de l'utilisateur"
+
 msgid "users"
 msgstr "utilisateurs"
 
@@ -4572,7 +4599,3 @@
 msgstr ""
 "vous devriez enlevé la mise en ligne de la relation %s qui est supportée et "
 "peut-être croisée"
-
-#~ msgctxt "CWAttribute"
-#~ msgid "cw_schema_object"
-#~ msgstr "mappé par"
--- a/misc/migration/bootstrapmigration_repository.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/misc/migration/bootstrapmigration_repository.py	Fri Aug 05 14:23:55 2011 +0200
@@ -36,7 +36,7 @@
     commit(ask_confirm=False)
 
 if applcubicwebversion <= (3, 13, 0) and cubicwebversion >= (3, 13, 1):
-    sql('ALTER TABLE entities ADD COLUMN asource VARCHAR(64)')
+    sql('ALTER TABLE entities ADD asource VARCHAR(64)')
     sql('UPDATE entities SET asource=cw_name  '
         'FROM cw_CWSource, cw_source_relation '
         'WHERE entities.eid=cw_source_relation.eid_from AND cw_source_relation.eid_to=cw_CWSource.cw_eid')
--- a/schemas/base.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/schemas/base.py	Fri Aug 05 14:23:55 2011 +0200
@@ -42,12 +42,6 @@
     firstname = String(maxsize=64)
     surname   = String(maxsize=64)
     last_login_time  = Datetime(description=_('last connection date'))
-    # allowing an email to be the primary email of multiple entities is necessary for
-    # test at least :-/
-    primary_email = SubjectRelation('EmailAddress', cardinality='??',
-                                    description=_('email address to use for notification'))
-    use_email     = SubjectRelation('EmailAddress', cardinality='*?', composite='subject')
-
     in_group = SubjectRelation('CWGroup', cardinality='+*',
                                constraints=[RQLConstraint('NOT O name "owners"')],
                                description=_('groups grant permissions to the user'))
@@ -71,17 +65,35 @@
 to indicate which is the preferred form.'))
 
 class use_email(RelationType):
-    """ """
+    fulltext_container = 'subject'
+
+
+class use_email_relation(RelationDefinition):
+    """user's email account"""
+    name = "use_email"
     __permissions__ = {
         'read':   ('managers', 'users', 'guests',),
         'add':    ('managers', RRQLExpression('U has_update_permission S'),),
         'delete': ('managers', RRQLExpression('U has_update_permission S'),),
         }
-    fulltext_container = 'subject'
+    subject = "CWUser"
+    object = "EmailAddress"
+    cardinality = '*?'
+    composite = 'subject'
+
 
-class primary_email(RelationType):
+class primary_email(RelationDefinition):
     """the prefered email"""
-    __permissions__ = use_email.__permissions__
+    __permissions__ = {
+        'read':   ('managers', 'users', 'guests',),
+        'add':    ('managers', RRQLExpression('U has_update_permission S'),),
+        'delete': ('managers', RRQLExpression('U has_update_permission S'),),
+        }
+    subject = "CWUser"
+    object = "EmailAddress"
+    cardinality = '??'
+    constraints= [RQLConstraint('S use_email O')]
+
 
 class prefered_form(RelationType):
     __permissions__ = {
--- a/server/repository.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/server/repository.py	Fri Aug 05 14:23:55 2011 +0200
@@ -505,12 +505,7 @@
 
         This is a public method, not requiring a session id.
         """
-        try:
-            # necessary to support pickling used by pyro
-            self.schema.__hashmode__ = 'pickle'
-            return self.schema
-        finally:
-            self.schema.__hashmode__ = None
+        return self.schema
 
     def get_cubes(self):
         """Return the list of cubes used by this instance.
--- a/server/rqlannotation.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/server/rqlannotation.py	Fri Aug 05 14:23:55 2011 +0200
@@ -109,8 +109,9 @@
                         ostinfo = rhs.children[0].variable.stinfo
                     else:
                         ostinfo = lhs.variable.stinfo
-                    if not any(orel for orel in ostinfo['relations']
-                               if orel.optional and orel is not rel):
+                    if not (ostinfo.get('optcomparisons') or
+                            any(orel for orel in ostinfo['relations']
+                                if orel.optional and orel is not rel)):
                         break
             if rschema.final or (onlhs and rschema.inlined):
                 if rschema.type != 'has_text':
--- a/server/session.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/server/session.py	Fri Aug 05 14:23:55 2011 +0200
@@ -1180,7 +1180,8 @@
     def _build_descr(self, result, basedescription, todetermine):
         description = []
         etype_from_eid = self.describe
-        for row in result:
+        todel = []
+        for i, row in enumerate(result):
             row_descr = basedescription[:]
             for index, isfinal in todetermine:
                 value = row[index]
@@ -1194,10 +1195,14 @@
                     try:
                         row_descr[index] = etype_from_eid(value)[0]
                     except UnknownEid:
-                        self.critical('wrong eid %s in repository, you should '
-                                      'db-check the database' % value)
-                        row_descr[index] = row[index] = None
-            description.append(tuple(row_descr))
+                        self.error('wrong eid %s in repository, you should '
+                                   'db-check the database' % value)
+                        todel.append(i)
+                        break
+            else:
+                description.append(tuple(row_descr))
+        for i in reversed(todel):
+            del result[i]
         return description
 
     # deprecated ###############################################################
--- a/server/sources/datafeed.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/server/sources/datafeed.py	Fri Aug 05 14:23:55 2011 +0200
@@ -218,7 +218,7 @@
         """
         entity = super(DataFeedSource, self).before_entity_insertion(
             session, lid, etype, eid, sourceparams)
-        entity.cw_edited['cwuri'] = unicode(lid)
+        entity.cw_edited['cwuri'] = lid.decode('utf-8')
         entity.cw_edited.set_defaults()
         sourceparams['parser'].before_entity_copy(entity, sourceparams)
         return entity
@@ -275,6 +275,8 @@
         else:
             source = self.source
         sourceparams['parser'] = self
+        if isinstance(uri, unicode):
+            uri = uri.encode('utf-8')
         try:
             eid = session.repo.extid2eid(source, str(uri), etype, session,
                                          complete=False, commit=False,
@@ -351,10 +353,10 @@
 
     def parse(self, url):
         if url.startswith('http'):
-            from cubicweb.sobjects.parsers import HOST_MAPPING
-            for mappedurl in HOST_MAPPING:
+            from cubicweb.sobjects.parsers import URL_MAPPING
+            for mappedurl in URL_MAPPING:
                 if url.startswith(mappedurl):
-                    url = url.replace(mappedurl, HOST_MAPPING[mappedurl], 1)
+                    url = url.replace(mappedurl, URL_MAPPING[mappedurl], 1)
                     break
             self.source.info('GET %s', url)
             stream = _OPENER.open(url)
--- a/server/sources/rql2sql.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/server/sources/rql2sql.py	Fri Aug 05 14:23:55 2011 +0200
@@ -1238,35 +1238,47 @@
 
 
     def _visit_outer_join_inlined_relation(self, relation, rschema):
-        leftvar, leftconst, rightvar, rightconst = relation_info(relation)
-        assert not (leftconst and rightconst), "doesn't make sense"
-        if relation.optional != 'right':
-            leftvar, rightvar = rightvar, leftvar
-            leftconst, rightconst = rightconst, leftconst
-        outertype = 'FULL' if relation.optional == 'both' else 'LEFT'
-        leftalias = self._var_table(leftvar)
+        lhsvar, lhsconst, rhsvar, rhsconst = relation_info(relation)
+        assert not (lhsconst and rhsconst), "doesn't make sense"
         attr = 'eid' if relation.r_type == 'identity' else relation.r_type
-        lhs, rhs = relation.get_variable_parts()
+        lhsalias = self._var_table(lhsvar)
+        rhsalias = rhsvar and self._var_table(rhsvar)
         try:
-            lhssql = self._varmap['%s.%s' % (lhs.name, attr)]
+            lhssql = self._varmap['%s.%s' % (lhsvar.name, attr)]
         except KeyError:
-            lhssql = '%s.%s%s' % (self._var_table(lhs.variable), SQL_PREFIX, attr)
-        if rightvar is not None:
-            rightalias = self._var_table(rightvar)
-            if rightalias is None:
-                if rightconst is not None:
-                    # inlined relation with invariant as rhs
-                    condition = '%s=%s' % (lhssql, rightconst.accept(self))
-                    if relation.r_type != 'identity':
-                        condition = '(%s OR %s IS NULL)' % (condition, lhssql)
-                    if not leftvar.stinfo.get('optrelations'):
-                        return condition
-                    self._state.add_outer_join_condition(leftalias, condition)
-                return
-        if leftalias is None:
-            leftalias = leftvar._q_sql.split('.', 1)[0]
-        self._state.replace_tables_by_outer_join(
-            leftalias, rightalias, outertype, '%s=%s' % (lhssql, rhs.accept(self)))
+            if lhsalias is None:
+                lhssql = lhsconst.accept(self)
+            else:
+                lhssql = '%s.%s%s' % (lhsalias, SQL_PREFIX, attr)
+        condition = '%s=%s' % (lhssql, (rhsconst or rhsvar).accept(self))
+        # this is not a typo, rhs optional variable means lhs outer join and vice-versa
+        if relation.optional == 'left':
+            lhsvar, rhsvar = rhsvar, lhsvar
+            lhsconst, rhsconst = rhsconst, lhsconst
+            lhsalias, rhsalias = rhsalias, lhsalias
+            outertype = 'LEFT'
+        elif relation.optional == 'both':
+            outertype = 'FULL'
+        else:
+            outertype = 'LEFT'
+        if rhsalias is None:
+            if rhsconst is not None:
+                # inlined relation with invariant as rhs
+                if relation.r_type != 'identity':
+                    condition = '(%s OR %s IS NULL)' % (condition, lhssql)
+                if not lhsvar.stinfo.get('optrelations'):
+                    return condition
+                self._state.add_outer_join_condition(lhsalias, condition)
+            return
+        if lhsalias is None:
+            if lhsconst is not None and not rhsvar.stinfo.get('optrelations'):
+                return condition
+            lhsalias = lhsvar._q_sql.split('.', 1)[0]
+        if lhsalias == rhsalias:
+            self._state.add_outer_join_condition(lhsalias, condition)
+        else:
+            self._state.replace_tables_by_outer_join(
+                lhsalias, rhsalias, outertype, condition)
         return ''
 
     def _visit_var_attr_relation(self, relation, rhs_vars):
@@ -1442,6 +1454,10 @@
             pass
         return '(%s %s %s)'% (lhs.accept(self), operator, rhs.accept(self))
 
+    def visit_unaryexpression(self, uexpr):
+        """generate SQL for a unary expression"""
+        return '%s%s'% (uexpr.operator, uexpr.children[0].accept(self))
+
     def visit_function(self, func):
         """generate SQL name for a function"""
         if func.name == 'FTIRANK':
--- a/server/test/unittest_datafeed.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/server/test/unittest_datafeed.py	Fri Aug 05 14:23:55 2011 +0200
@@ -94,9 +94,26 @@
         self.assertTrue(dfsource.latest_retrieval)
         self.assertTrue(dfsource.fresh())
 
+        # test_rename_source
+        req = self.request()
+        req.execute('SET S name "myrenamedfeed" WHERE S is CWSource, S name "myfeed"')
+        self.commit()
+        entity = self.execute('Card X').get_entity(0, 0)
+        self.assertEqual(entity.cwuri, 'http://www.cubicweb.org/')
+        self.assertEqual(entity.cw_source[0].name, 'myrenamedfeed')
+        self.assertEqual(entity.cw_metainformation(),
+                         {'type': 'Card',
+                          'source': {'uri': 'myrenamedfeed', 'type': 'datafeed', 'use-cwuri-as-url': True},
+                          'extid': 'http://www.cubicweb.org/'}
+                         )
+        self.assertEqual(self.repo._type_source_cache[entity.eid],
+                         ('Card', 'system', 'http://www.cubicweb.org/', 'myrenamedfeed'))
+        self.assertEqual(self.repo._extid_cache[('http://www.cubicweb.org/', 'system')],
+                         entity.eid)
+
         # test_delete_source
         req = self.request()
-        req.execute('DELETE CWSource S WHERE S name "myfeed"')
+        req.execute('DELETE CWSource S WHERE S name "myrenamedfeed"')
         self.commit()
         self.failIf(self.execute('Card X WHERE X title "cubicweb.org"'))
         self.failIf(self.execute('Any X WHERE X has_text "cubicweb.org"'))
--- a/server/test/unittest_repository.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/server/test/unittest_repository.py	Fri Aug 05 14:23:55 2011 +0200
@@ -366,7 +366,6 @@
             schema = cnx.get_schema()
             self.failUnless(cnx.vreg)
             self.failUnless('etypes'in cnx.vreg)
-            self.assertEqual(schema.__hashmode__, None)
             cu = cnx.cursor()
             rset = cu.execute('Any U,G WHERE U in_group G')
             user = iter(rset.entities()).next()
--- a/server/test/unittest_rql2sql.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/server/test/unittest_rql2sql.py	Fri Aug 05 14:23:55 2011 +0200
@@ -88,7 +88,6 @@
     ]
 
 BASIC = [
-
     ("Any AS WHERE AS is Affaire",
      '''SELECT _AS.cw_eid
 FROM cw_Affaire AS _AS'''),
@@ -201,7 +200,12 @@
     ('Any X WHERE  X title V, NOT X wikiid V, NOT X title "parent", X is Card',
      '''SELECT _X.cw_eid
 FROM cw_Card AS _X
-WHERE NOT (_X.cw_wikiid=_X.cw_title) AND NOT (_X.cw_title=parent)''')
+WHERE NOT (_X.cw_wikiid=_X.cw_title) AND NOT (_X.cw_title=parent)'''),
+
+    ("Any -AS WHERE AS is Affaire",
+     '''SELECT -_AS.cw_eid
+FROM cw_Affaire AS _AS'''),
+
 ]
 
 BASIC_WITH_LIMIT = [
@@ -972,7 +976,7 @@
     ('Any CASE, CALIBCFG, CFG '
      'WHERE CASE eid 1, CFG ecrit_par CASE, CALIBCFG? ecrit_par CASE',
      '''SELECT _CFG.cw_ecrit_par, _CALIBCFG.cw_eid, _CFG.cw_eid
-FROM cw_Note AS _CFG LEFT OUTER JOIN cw_Note AS _CALIBCFG ON (_CALIBCFG.cw_ecrit_par=_CFG.cw_ecrit_par)
+FROM cw_Note AS _CFG LEFT OUTER JOIN cw_Note AS _CALIBCFG ON (_CALIBCFG.cw_ecrit_par=1)
 WHERE _CFG.cw_ecrit_par=1'''),
 
     ('Any U,G WHERE U login UL, G name GL, G is CWGroup HAVING UPPER(UL)=UPPER(GL)?',
@@ -986,6 +990,17 @@
     ('Any U,G WHERE U login UL, G name GL, G is CWGroup HAVING UPPER(UL)?=UPPER(GL)?',
      '''SELECT _U.cw_eid, _G.cw_eid
 FROM cw_CWUser AS _U FULL OUTER JOIN cw_CWGroup AS _G ON (UPPER(_U.cw_login)=UPPER(_G.cw_name))'''),
+
+    ('Any H, COUNT(X), SUM(XCE)/1000 '
+     'WHERE X type "0", X date XSCT, X para XCE, X? ecrit_par F, F eid 999999, F is Personne, '
+     'DH is Affaire, DH ref H '
+     'HAVING XSCT?=H',
+     '''SELECT _DH.cw_ref, COUNT(_X.cw_eid), (SUM(_X.cw_para) / 1000)
+FROM cw_Affaire AS _DH LEFT OUTER JOIN cw_Note AS _X ON (_X.cw_date=_DH.cw_ref AND _X.cw_type=0 AND _X.cw_ecrit_par=999999)'''),
+
+    ('Any C WHERE X ecrit_par C?, X? inline1 F, F eid 1, X type XT, Z is Personne, Z nom ZN HAVING ZN=XT?',
+     '''SELECT _X.cw_ecrit_par
+FROM cw_Personne AS _Z LEFT OUTER JOIN cw_Note AS _X ON (_Z.cw_nom=_X.cw_type AND _X.cw_inline1=1)'''),
     ]
 
 VIRTUAL_VARS = [
@@ -1721,7 +1736,7 @@
 class SqlServer2005SQLGeneratorTC(PostgresSQLGeneratorTC):
     backend = 'sqlserver2005'
     def _norm_sql(self, sql):
-        return sql.strip().replace(' SUBSTR', ' SUBSTRING').replace(' || ', ' + ').replace(' ILIKE ', ' LIKE ')
+        return sql.strip().replace(' SUBSTR', ' SUBSTRING').replace(' || ', ' + ').replace(' ILIKE ', ' LIKE ').replace('TRUE', '1').replace('FALSE', '0')
 
     def test_has_text(self):
         for t in self._parse(HAS_TEXT_LG_INDEXER):
--- a/server/test/unittest_session.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/server/test/unittest_session.py	Fri Aug 05 14:23:55 2011 +0200
@@ -78,5 +78,15 @@
         self.assertEqual(session._tx_data, {})
         self.assertEqual(session.cnxset, None)
 
+    def test_build_descr(self):
+        rset = self.execute('(Any U,L WHERE U login L) UNION (Any G,N WHERE G name N, G is CWGroup)')
+        orig_length = len(rset)
+        rset.rows[0][0] = 9999999
+        description = self.session.build_description(rset.syntax_tree(), None, rset.rows)
+        self.assertEqual(len(description), orig_length - 1)
+        self.assertEqual(len(rset.rows), orig_length - 1)
+        self.failIf(rset.rows[0][0] == 9999999)
+
+
 if __name__ == '__main__':
     unittest_main()
--- a/sobjects/parsers.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/sobjects/parsers.py	Fri Aug 05 14:23:55 2011 +0200
@@ -459,10 +459,10 @@
 
 def registration_callback(vreg):
     vreg.register_all(globals().values(), __name__)
-    global HOST_MAPPING
-    HOST_MAPPING = {}
+    global URL_MAPPING
+    URL_MAPPING = {}
     if vreg.config.apphome:
-        host_mapping_file = osp.join(vreg.config.apphome, 'hostmapping.py')
-        if osp.exists(host_mapping_file):
-            HOST_MAPPING = eval(file(host_mapping_file).read())
-            vreg.info('using host mapping %s from %s', HOST_MAPPING, host_mapping_file)
+        url_mapping_file = osp.join(vreg.config.apphome, 'urlmapping.py')
+        if osp.exists(url_mapping_file):
+            URL_MAPPING = eval(file(url_mapping_file).read())
+            vreg.info('using url mapping %s from %s', URL_MAPPING, url_mapping_file)
--- a/web/data/cubicweb.reset.css	Wed Jul 27 20:17:45 2011 +0200
+++ b/web/data/cubicweb.reset.css	Fri Aug 05 14:23:55 2011 +0200
@@ -1,53 +1,48 @@
-/* http://meyerweb.com/eric/tools/css/reset/ */
-/* v1.0 | 20080212 */
+/* http://meyerweb.com/eric/tools/css/reset/ 
+   v2.0 | 20110126
+   License: none (public domain)
+*/
 
 html, body, div, span, applet, object, iframe,
 h1, h2, h3, h4, h5, h6, p, blockquote, pre,
 a, abbr, acronym, address, big, cite, code,
-del, dfn, em, font, img, ins, kbd, q, s, samp,
+del, dfn, em, img, ins, kbd, q, s, samp,
 small, strike, strong, sub, sup, tt, var,
 b, u, i, center,
 dl, dt, dd, ol, ul, li,
 fieldset, form, label, legend,
-table, caption, tbody, tfoot, thead, tr, th, td {
-  margin: 0;
-  padding: 0;
-  border: 0;
-  outline: 0;
-  font-size: 100%;
-  vertical-align: baseline;
-  background: transparent;
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed, 
+figure, figcaption, footer, header, hgroup, 
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	font-size: 100%;
+	font: inherit;
+	vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure, 
+footer, header, hgroup, menu, nav, section {
+	display: block;
 }
 body {
-  line-height: 1;
+	line-height: 1;
 }
 ol, ul {
-  list-style: none;
+	list-style: none;
 }
 blockquote, q {
-  quotes: none;
+	quotes: none;
 }
 blockquote:before, blockquote:after,
 q:before, q:after {
-  content: '';
-  content: none;
-}
-
-/* remember to define focus styles! */
-:focus {
-  outline: 0;
+	content: '';
+	content: none;
 }
-
-/* remember to highlight inserts somehow! */
-ins {
-  text-decoration: none;
-}
-del {
-  text-decoration: line-through;
-}
-
-/* tables still need 'cellspacing="0"' in the markup */
 table {
-  border-collapse: collapse;
-  border-spacing: 0;
+	border-collapse: collapse;
+	border-spacing: 0;
 }
\ No newline at end of file
--- a/web/test/unittest_views_editforms.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/web/test/unittest_views_editforms.py	Fri Aug 05 14:23:55 2011 +0200
@@ -77,9 +77,9 @@
         # (appears here while expected in hidden
         self.assertListEqual([x for x in rbc(e, 'main', 'relations')
                                if x != ('tags', 'object')],
-                              [('primary_email', 'subject'),
-                               ('connait', 'subject'),
+                              [('connait', 'subject'),
                                ('custom_workflow', 'subject'),
+                               ('primary_email', 'subject'),
                                ('checked_by', 'object'),
                                ])
         self.assertListEqual(rbc(e, 'main', 'inlined'),
--- a/web/views/actions.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/web/views/actions.py	Fri Aug 05 14:23:55 2011 +0200
@@ -135,8 +135,13 @@
         params = self._cw.form.copy()
         for param in ('vid', '__message') + controller.NAV_FORM_PARAMETERS:
             params.pop(param, None)
-        return self._cw.build_url(self._cw.relative_path(includeparams=False),
-                                  **params)
+        if self._cw.json_request:
+            path = 'view'
+            if self.cw_rset is not None:
+                params = {'rql': self.cw_rset.printable_rql()}
+        else:
+            path = self._cw.relative_path(includeparams=False)
+        return self._cw.build_url(path, **params)
 
 
 class ModifyAction(action.Action):
@@ -163,7 +168,7 @@
     order = 10
 
     def url(self):
-        return self._cw.build_url('view', rql=self.cw_rset.rql, vid='muledit')
+        return self._cw.build_url('view', rql=self.cw_rset.printable_rql(), vid='muledit')
 
 
 # generic "more" actions #######################################################
--- a/web/views/cwuser.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/web/views/cwuser.py	Fri Aug 05 14:23:55 2011 +0200
@@ -168,6 +168,10 @@
 
 class CWUserManagementView(StartupView):
     __regid__ = 'cw.user-management'
+    # XXX one could wish to display for instance only user's firstname/surname
+    # for non managers but filtering out NULL cause crash with an ldapuser
+    # source.
+    __select__ = StartupView.__select__ & match_user_groups('managers')
     rql = ('Any U,USN,F,S,U,UAA,UDS, L,UAA,UDSN ORDERBY L WHERE U is CWUser, '
            'U login L, U firstname F, U surname S, '
            'U in_state US, US name USN, '
--- a/web/views/emailaddress.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/web/views/emailaddress.py	Fri Aug 05 14:23:55 2011 +0200
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""Specific views for email addresses entities
+"""Specific views for email addresses entities"""
 
-"""
 __docformat__ = "restructuredtext en"
 
 from logilab.mtconverter import xml_escape
@@ -85,8 +84,7 @@
 class EmailAddressOneLineView(baseviews.OneLineView):
     __select__ = is_instance('EmailAddress')
 
-    def cell_call(self, row, col, **kwargs):
-        entity = self.cw_rset.get_entity(row, col)
+    def entity_call(self, entity, **kwargs):
         if entity.reverse_primary_email:
             self.w(u'<b>')
         if entity.alias:
@@ -106,8 +104,7 @@
     __regid__ = 'mailto'
     __select__ = is_instance('EmailAddress')
 
-    def cell_call(self, row, col, **kwargs):
-        entity = self.cw_rset.get_entity(row, col)
+    def entity_call(self, entity, **kwargs):
         if entity.reverse_primary_email:
             self.w(u'<b>')
         if entity.alias:
@@ -130,7 +127,10 @@
     __select__ = is_instance('EmailAddress')
 
     def cell_call(self, row, col, **kwargs):
-        self.wview('mailto', self.cw_rset, row=row, col=col, **kwargs)
+        if self._cw.vreg.config['mangle-emails']:
+            self.wview('oneline', self.cw_rset, row=row, col=col, **kwargs)
+        else:
+            self.wview('mailto', self.cw_rset, row=row, col=col, **kwargs)
 
 
 class EmailAddressTextView(baseviews.TextView):
--- a/web/views/forms.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/web/views/forms.py	Fri Aug 05 14:23:55 2011 +0200
@@ -384,7 +384,8 @@
                 else:
                     msg = self._cw._('entity linked')
         if msg:
-            self.add_hidden('__message', msg)
+            msgid = self._cw.set_redirect_message(msg)
+            self.add_hidden('_cwmsgid', msgid)
 
     def session_key(self):
         """return the key that may be used to store / retreive data about a
--- a/web/views/tabs.py	Wed Jul 27 20:17:45 2011 +0200
+++ b/web/views/tabs.py	Fri Aug 05 14:23:55 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.