merge 3.20.8 in 3.21
authorRémi Cardona <remi.cardona@logilab.fr>
Mon, 22 Jun 2015 14:27:37 +0200
changeset 10411 4ee15441f2eb
parent 10410 eb681a030699 (current diff)
parent 10409 843e4b3ccfbc (diff)
child 10412 3540131a8405
merge 3.20.8 in 3.21
__pkginfo__.py
cwconfig.py
doc/book/en/annexes/faq.rst
entities/__init__.py
entities/test/unittest_base.py
hooks/test/data/bootstrap_cubes
schemas/base.py
server/schemaserial.py
server/serverctl.py
server/sources/native.py
server/sqlutils.py
server/test/unittest_querier.py
web/application.py
web/data/cubicweb.ajax.js
web/formwidgets.py
web/httpcache.py
web/test/unittest_views_editforms.py
web/test/unittest_web.py
web/views/basecontrollers.py
web/views/baseviews.py
web/views/management.py
--- a/.hgtags	Mon Jun 22 14:15:16 2015 +0200
+++ b/.hgtags	Mon Jun 22 14:27:37 2015 +0200
@@ -426,3 +426,73 @@
 7f64859dcbcdc6394421b8a5175896ba2e5caeb5 cubicweb-version-3.20.6
 7f64859dcbcdc6394421b8a5175896ba2e5caeb5 cubicweb-debian-version-3.20.6-1
 7f64859dcbcdc6394421b8a5175896ba2e5caeb5 cubicweb-centos-version-3.20.6-1
+359d68bc12602c73559531b09d00399f4cbca785 cubicweb-version-3.20.7
+359d68bc12602c73559531b09d00399f4cbca785 cubicweb-debian-version-3.20.7-1
+359d68bc12602c73559531b09d00399f4cbca785 cubicweb-centos-version-3.20.7-1
+1141927b8494aabd16e31b0d0d9a50fe1fed5f2f 3.19.0
+1141927b8494aabd16e31b0d0d9a50fe1fed5f2f debian/3.19.0-1
+1141927b8494aabd16e31b0d0d9a50fe1fed5f2f centos/3.19.0-1
+1fe4bc4a8ac8831a379e9ebea08d75fbb6fc5c2a 3.19.1
+1fe4bc4a8ac8831a379e9ebea08d75fbb6fc5c2a debian/3.19.1-1
+1fe4bc4a8ac8831a379e9ebea08d75fbb6fc5c2a centos/3.19.1-1
+8ac2202866e747444ce12778ff8789edd9c92eae 3.19.2
+8ac2202866e747444ce12778ff8789edd9c92eae debian/3.19.2-1
+8ac2202866e747444ce12778ff8789edd9c92eae centos/3.19.2-1
+37f7c60f89f13dfcf326a4ea0a98ca20d959f7bd 3.19.3
+37f7c60f89f13dfcf326a4ea0a98ca20d959f7bd debian/3.19.3-1
+37f7c60f89f13dfcf326a4ea0a98ca20d959f7bd centos/3.19.3-1
+c4e740e50fc7d371d14df17d26bc42d1f8060261 3.19.4
+c4e740e50fc7d371d14df17d26bc42d1f8060261 debian/3.19.4-1
+c4e740e50fc7d371d14df17d26bc42d1f8060261 centos/3.19.4-1
+3ac86df519af2a1194cb3fc882d30d0e1bf44e3b 3.19.5
+3ac86df519af2a1194cb3fc882d30d0e1bf44e3b debian/3.19.5-1
+3ac86df519af2a1194cb3fc882d30d0e1bf44e3b centos/3.19.5-1
+934341b848a6874688314d7c154183aca3aed530 3.19.6
+934341b848a6874688314d7c154183aca3aed530 debian/3.19.6-1
+934341b848a6874688314d7c154183aca3aed530 centos/3.19.6-1
+ac4f5f615597575bec32f8f591260e5a91e53855 3.19.7
+ac4f5f615597575bec32f8f591260e5a91e53855 debian/3.19.7-1
+ac4f5f615597575bec32f8f591260e5a91e53855 centos/3.19.7-1
+efc8645ece4300958e3628db81464fef12d5f6e8 3.19.8
+efc8645ece4300958e3628db81464fef12d5f6e8 debian/3.19.8-1
+efc8645ece4300958e3628db81464fef12d5f6e8 centos/3.19.8-1
+b7c373d74754f5ba9344575cb179b47282c413b6 3.19.9
+b7c373d74754f5ba9344575cb179b47282c413b6 debian/3.19.9-1
+b7c373d74754f5ba9344575cb179b47282c413b6 centos/3.19.9-1
+3bab0b9b0ee7355a6fea45c2adca88bffe130e5d 3.19.10
+3bab0b9b0ee7355a6fea45c2adca88bffe130e5d debian/3.19.10-1
+3bab0b9b0ee7355a6fea45c2adca88bffe130e5d centos/3.19.10-1
+1ae64186af9448dffbeebdef910c8c7391c04313 3.19.11
+1ae64186af9448dffbeebdef910c8c7391c04313 debian/3.19.11-1
+1ae64186af9448dffbeebdef910c8c7391c04313 centos/3.19.11-1
+6d265ea7d56fe49e9dff261d3b2caf3c2b6f9409 debian/3.19.11-2
+5932de3d50bf023544c8f54b47898e4db35eac7c 3.19.12
+5932de3d50bf023544c8f54b47898e4db35eac7c debian/3.19.12-1
+5932de3d50bf023544c8f54b47898e4db35eac7c centos/3.19.12-1
+7e6b7739afe6128589ad51b0318decb767cbae36 3.20.0
+7e6b7739afe6128589ad51b0318decb767cbae36 debian/3.20.0-1
+7e6b7739afe6128589ad51b0318decb767cbae36 centos/3.20.0-1
+43eef610ef11673d01750459356aec5a96174ca0 3.20.1
+43eef610ef11673d01750459356aec5a96174ca0 debian/3.20.1-1
+43eef610ef11673d01750459356aec5a96174ca0 centos/3.20.1-1
+138464fc1c3397979b729cca3a30bc4481fd1e2d 3.20.2
+138464fc1c3397979b729cca3a30bc4481fd1e2d debian/3.20.2-1
+138464fc1c3397979b729cca3a30bc4481fd1e2d centos/3.20.2-1
+7d3a583ed5392ba528e56ef6902ced5468613f4d 3.20.3
+7d3a583ed5392ba528e56ef6902ced5468613f4d debian/3.20.3-1
+7d3a583ed5392ba528e56ef6902ced5468613f4d centos/3.20.3-1
+49831fdc84dc7e7bed01d5e8110a46242b5ccda6 3.20.4
+49831fdc84dc7e7bed01d5e8110a46242b5ccda6 debian/3.20.4-1
+49831fdc84dc7e7bed01d5e8110a46242b5ccda6 centos/3.20.4-1
+51aa56e7d507958b3326abbb6a31d0e6dde6b47b 3.20.5
+51aa56e7d507958b3326abbb6a31d0e6dde6b47b debian/3.20.5-1
+51aa56e7d507958b3326abbb6a31d0e6dde6b47b centos/3.20.5-1
+7f64859dcbcdc6394421b8a5175896ba2e5caeb5 3.20.6
+7f64859dcbcdc6394421b8a5175896ba2e5caeb5 debian/3.20.6-1
+7f64859dcbcdc6394421b8a5175896ba2e5caeb5 centos/3.20.6-1
+359d68bc12602c73559531b09d00399f4cbca785 3.20.7
+359d68bc12602c73559531b09d00399f4cbca785 debian/3.20.7-1
+359d68bc12602c73559531b09d00399f4cbca785 centos/3.20.7-1
+ec284980ed9e214fe6c15cc4cf9617961d88928d 3.20.8
+ec284980ed9e214fe6c15cc4cf9617961d88928d debian/3.20.8-1
+ec284980ed9e214fe6c15cc4cf9617961d88928d centos/3.20.8-1
--- a/__pkginfo__.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/__pkginfo__.py	Mon Jun 22 14:27:37 2015 +0200
@@ -22,7 +22,7 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 20, 6)
+numversion = (3, 20, 8)
 version = '.'.join(str(num) for num in numversion)
 
 description = "a repository of entities / relations for knowledge management"
--- a/cubicweb.spec	Mon Jun 22 14:15:16 2015 +0200
+++ b/cubicweb.spec	Mon Jun 22 14:27:37 2015 +0200
@@ -7,7 +7,7 @@
 %endif
 
 Name:           cubicweb
-Version:        3.20.6
+Version:        3.20.8
 Release:        logilab.1%{?dist}
 Summary:        CubicWeb is a semantic web application framework
 Source0:        http://download.logilab.org/pub/cubicweb/cubicweb-%{version}.tar.gz
--- a/cwconfig.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/cwconfig.py	Mon Jun 22 14:27:37 2015 +0200
@@ -1104,7 +1104,7 @@
             self._gettext_init()
 
     def _load_site_cubicweb(self, sitefile):
-        # overriden to register cube specific options
+        # overridden to register cube specific options
         mod = super(CubicWebConfiguration, self)._load_site_cubicweb(sitefile)
         if getattr(mod, 'options', None):
             self.register_options(mod.options)
--- a/debian/changelog	Mon Jun 22 14:15:16 2015 +0200
+++ b/debian/changelog	Mon Jun 22 14:27:37 2015 +0200
@@ -1,3 +1,15 @@
+cubicweb (3.20.8-1) unstable; urgency=low
+
+  * New upstream release.
+
+ -- Rémi Cardona <remi.cardona@logilab.fr>  Mon, 22 Jun 2015 12:50:11 +0200
+
+cubicweb (3.20.7-1) unstable; urgency=low
+
+  * New upstream release.
+
+ -- Rémi Cardona <remi.cardona@logilab.fr>  Wed, 22 Apr 2015 17:47:35 +0200
+
 cubicweb (3.20.6-1) unstable; urgency=low
 
   * new upstream release
@@ -46,7 +58,13 @@
 
  -- Julien Cristau <julien.cristau@logilab.fr>  Tue, 06 Jan 2015 18:11:03 +0100
 
-cubicweb (3.19.11-2) UNRELEASED; urgency=low
+cubicweb (3.19.12-1) unstable; urgency=low
+
+  * New upstream release
+
+ -- Rémi Cardona <remi.cardona@logilab.fr>  Fri, 19 Jun 2015 10:51:23 +0200
+
+cubicweb (3.19.11-2) unstable; urgency=low
 
   * Fix cubicweb-dev dependencies.
 
--- a/doc/book/en/annexes/faq.rst	Mon Jun 22 14:15:16 2015 +0200
+++ b/doc/book/en/annexes/faq.rst	Mon Jun 22 14:27:37 2015 +0200
@@ -102,21 +102,17 @@
 How to change the instance logo ?
 ---------------------------------
 
-There are two ways of changing the logo.
-
-1. The easiest way to use a different logo is to replace the existing
-   ``logo.png`` in ``myapp/data`` by your prefered icon and refresh.
-   By default all instance will look for a ``logo.png`` to be
-   rendered in the logo section.
+The logo is managed by css. You must provide a custom css that will contain
+the code below: 
 
-   .. image:: ../images/lax-book_06-main-template-logo_en.png
+::
+   
+     #logo {
+        background-image: url("logo.jpg");
+     }
 
-2. In your cube directory, you can specify which file to use for the logo.
-   This is configurable in ``mycube/uiprops.py``: ::
 
-     LOGO = data('mylogo.gif')
-
-   ``mylogo.gif`` is in ``mycube/data`` directory.
+``logo.jpg`` is in ``mycube/data`` directory.
 
 How to create an anonymous user ?
 ---------------------------------
--- a/doc/book/en/annexes/rql/language.rst	Mon Jun 22 14:15:16 2015 +0200
+++ b/doc/book/en/annexes/rql/language.rst	Mon Jun 22 14:27:37 2015 +0200
@@ -131,7 +131,7 @@
 +----------+---------------------+-----------+--------+
 |  &       | bitwise AND         | 91 & 15   | 11     |
 +----------+---------------------+-----------+--------+
-|  |       | bitwise OR          | 32 | 3    | 35     |
+|  `|`     | bitwise OR          | 32 | 3    | 35     |
 +----------+---------------------+-----------+--------+
 |  #       | bitwise XOR         | 17 # 5    | 20     |
 +----------+---------------------+-----------+--------+
--- a/doc/book/en/tutorials/advanced/part02_security.rst	Mon Jun 22 14:15:16 2015 +0200
+++ b/doc/book/en/tutorials/advanced/part02_security.rst	Mon Jun 22 14:27:37 2015 +0200
@@ -313,8 +313,7 @@
 
     class SecurityTC(CubicWebTC):
 
-	def test_visibility_propagation(self):
-
+        def test_visibility_propagation(self):
             with self.admin_access.repo_cnx() as cnx:
                 # create a user for later security checks
                 toto = self.create_user(cnx, 'toto')
@@ -322,9 +321,9 @@
                 # init some data using the default manager connection
                 folder = cnx.create_entity('Folder',
                                            name=u'restricted',
-    				           visibility=u'restricted')
+                                           visibility=u'restricted')
                 photo1 = cnx.create_entity('File',
-    	                                   data_name=u'photo1.jpg',
+                                           data_name=u'photo1.jpg',
                                            data=Binary('xxx'),
                                            filed_under=folder)
                 cnx.commit()
@@ -333,29 +332,30 @@
                 # unless explicitly specified
                 photo2 = cnx.create_entity('File',
                                            data_name=u'photo2.jpg',
-				           data=Binary('xxx'),
-				           visibility=u'public',
-				           filed_under=folder)
+                                           data=Binary('xxx'),
+                                           visibility=u'public',
+                                           filed_under=folder)
                 cnx.commit()
                 self.assertEquals(photo2.visibility, 'public')
-
             with self.new_access('toto').repo_cnx() as cnx:
                 # test security
                 self.assertEqual(1, len(cnx.execute('File X'))) # only the public one
                 self.assertEqual(0, len(cnx.execute('Folder X'))) # restricted...
+            with self.admin_access.repo_cnx() as cnx:
                 # may_be_read_by propagation
                 folder = cnx.entity_from_eid(folder.eid)
                 folder.cw_set(may_be_read_by=toto)
                 cnx.commit()
-                photo1 = cnx.entity_from_eid(photo1)
+            with self.new_access('toto').repo_cnx() as cnx:
+                photo1 = cnx.entity_from_eid(photo1.eid)
                 self.failUnless(photo1.may_be_read_by)
                 # test security with permissions
                 self.assertEquals(2, len(cnx.execute('File X'))) # now toto has access to photo2
                 self.assertEquals(1, len(cnx.execute('Folder X'))) # and to restricted folder
 
     if __name__ == '__main__':
-	from logilab.common.testlib import unittest_main
-	unittest_main()
+        from logilab.common.testlib import unittest_main
+        unittest_main()
 
 It's not complete, but shows most things you'll want to do in tests: adding some
 content, creating users and connecting as them in the test, etc...
@@ -433,7 +433,7 @@
 
 To migrate my instance I simply type::
 
-   cubicweb-ctl upgrade sytweb
+   cubicweb-ctl upgrade sytweb_instance
 
 You'll then be asked some questions to do the migration step by step. You should say
 YES when it asks if a backup of your database should be done, so you can get back
--- a/doc/book/en/tutorials/advanced/part03_bfss.rst	Mon Jun 22 14:15:16 2015 +0200
+++ b/doc/book/en/tutorials/advanced/part03_bfss.rst	Mon Jun 22 14:27:37 2015 +0200
@@ -62,7 +62,7 @@
 
 ::
 
-   $ cubicweb-ctl shell sytweb
+   $ cubicweb-ctl shell sytweb_instance
    entering the migration python shell
    just type migration commands or arbitrary python code and type ENTER to execute it
    type "exit" or Ctrl-D to quit the shell and resume operation
@@ -81,7 +81,7 @@
 site. For instance if I have a 'photos/201005WePyrenees' containing pictures for
 a particular event, I can import it to my web site by typing ::
 
-  $ cubicweb-ctl fsimport -F sytweb photos/201005WePyrenees/
+  $ cubicweb-ctl fsimport -F sytweb_instance photos/201005WePyrenees/
   ** importing directory /home/syt/photos/201005WePyrenees
   importing IMG_8314.JPG
   importing IMG_8274.JPG
--- a/doc/book/en/tutorials/advanced/part04_ui-base.rst	Mon Jun 22 14:15:16 2015 +0200
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst	Mon Jun 22 14:27:37 2015 +0200
@@ -26,6 +26,7 @@
     from cubicweb.predicates import is_instance
     from cubicweb.web import component
     from cubicweb.web.views import error
+    from cubicweb.predicates import anonymous_user
 
     class FourOhFour(error.FourOhFour):
 	__select__ = error.FourOhFour.__select__ & anonymous_user()
--- a/entities/__init__.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/entities/__init__.py	Mon Jun 22 14:27:37 2015 +0200
@@ -68,7 +68,7 @@
             if rschema.meta:
                 continue
             value = self.cw_attr_value(rschema.type)
-            if value:
+            if value is not None:
                 # make the value printable (dates, floats, bytes, etc.)
                 return self.printable_value(rschema.type, value, attrschema.type,
                                             format='text/plain')
--- a/entities/test/data/schema.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/entities/test/data/schema.py	Mon Jun 22 14:27:37 2015 +0200
@@ -17,10 +17,11 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """entities tests schema"""
 
-from yams.buildobjs import EntityType, String, RichString
+from yams.buildobjs import EntityType, String, RichString, Int
 from cubicweb.schema import make_workflowable
 
 class Company(EntityType):
+    order = Int()
     name = String()
     description = RichString()
 
--- a/entities/test/unittest_base.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/entities/test/unittest_base.py	Mon Jun 22 14:27:37 2015 +0200
@@ -137,10 +137,16 @@
             self.assertEqual(e.dc_title(), 'member')
             self.assertEqual(e.name(), u'bouah lôt')
 
+    def test_falsey_dc_title(self):
+        with self.admin_access.repo_cnx() as cnx:
+            e = cnx.create_entity('Company', order=0, name=u'pythonian')
+            cnx.commit()
+            self.assertEqual(u'0', e.dc_title())
+
     def test_allowed_massmail_keys(self):
         with self.admin_access.repo_cnx() as cnx:
             e = cnx.execute('CWUser U WHERE U login "member"').get_entity(0, 0)
-            # Bytes/Password attributes should be omited
+            # Bytes/Password attributes should be omitted
             self.assertEqual(e.cw_adapt_to('IEmailable').allowed_massmail_keys(),
                               set(('surname', 'firstname', 'login', 'last_login_time',
                                    'creation_date', 'modification_date', 'cwuri', 'eid'))
--- a/hooks/security.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/hooks/security.py	Mon Jun 22 14:27:37 2015 +0200
@@ -136,6 +136,19 @@
         self.entity.cw_check_perm('delete')
 
 
+def skip_inlined_relation_security(cnx, rschema, eid):
+    """return True if security for the given inlined relation should be skipped,
+    in case where the relation has been set through modification of
+    `entity.cw_edited` in a hook
+    """
+    assert rschema.inlined
+    try:
+        entity = cnx.transaction_data['ecache'][eid]
+    except KeyError:
+        return False
+    return rschema.type in entity.cw_edited.skip_security
+
+
 class BeforeAddRelationSecurityHook(SecurityHook):
     __regid__ = 'securitybeforeaddrelation'
     events = ('before_add_relation',)
@@ -146,6 +159,9 @@
             if (self.eidfrom, self.rtype, self.eidto) in nocheck:
                 return
             rschema = self._cw.repo.schema[self.rtype]
+            if rschema.inlined and skip_inlined_relation_security(
+                    self._cw, rschema, self.eidfrom):
+                return
             rdef = rschema.rdef(self._cw.entity_metas(self.eidfrom)['type'],
                                 self._cw.entity_metas(self.eidto)['type'])
             rdef.check_perm(self._cw, 'add', fromeid=self.eidfrom, toeid=self.eidto)
@@ -156,11 +172,14 @@
     events = ('after_add_relation',)
 
     def __call__(self):
-        if not self.rtype in BEFORE_ADD_RELATIONS:
+        if self.rtype not in BEFORE_ADD_RELATIONS:
             nocheck = self._cw.transaction_data.get('skip-security', ())
             if (self.eidfrom, self.rtype, self.eidto) in nocheck:
                 return
             rschema = self._cw.repo.schema[self.rtype]
+            if rschema.inlined and skip_inlined_relation_security(
+                    self._cw, rschema, self.eidfrom):
+                return
             if self.rtype in ON_COMMIT_ADD_RELATIONS:
                 CheckRelationPermissionOp.get_instance(self._cw).add_data(
                     ('add', rschema, self.eidfrom, self.eidto) )
@@ -179,6 +198,9 @@
         if (self.eidfrom, self.rtype, self.eidto) in nocheck:
             return
         rschema = self._cw.repo.schema[self.rtype]
+        if rschema.inlined and skip_inlined_relation_security(
+                self._cw, rschema, self.eidfrom):
+            return
         rdef = rschema.rdef(self._cw.entity_metas(self.eidfrom)['type'],
                             self._cw.entity_metas(self.eidto)['type'])
         rdef.check_perm(self._cw, 'delete', fromeid=self.eidfrom, toeid=self.eidto)
--- a/hooks/synccomputed.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/hooks/synccomputed.py	Mon Jun 22 14:27:37 2015 +0200
@@ -43,18 +43,16 @@
         for computed_attribute_rdef, eids in self.get_data().iteritems():
             attr = computed_attribute_rdef.rtype
             formula  = computed_attribute_rdef.formula
-            rql = formula.replace('Any ', 'Any X, ', 1)
-            kwargs = None
-            # add constraint on X to the formula
-            if None in eids : # recompute for all etype if None is found
-                rql += ', X is %s' % computed_attribute_rdef.subject
-            elif len(eids) == 1:
-                rql += ', X eid %(x)s'
-                kwargs = {'x': eids.pop()}
+            select = self.cnx.repo.vreg.rqlhelper.parse(formula).children[0]
+            xvar = select.get_variable('X')
+            select.add_selected(xvar, index=0)
+            select.add_group_var(xvar, index=0)
+            if None in eids:
+                select.add_type_restriction(xvar, computed_attribute_rdef.subject)
             else:
-                rql += ', X eid IN (%s)' % ', '.join((str(eid) for eid in eids))
+                select.add_eid_restriction(xvar, eids)
             update_rql = 'SET X %s %%(value)s WHERE X eid %%(x)s' % attr
-            for eid, value in self.cnx.execute(rql, kwargs):
+            for eid, value in self.cnx.execute(select.as_string()):
                 self.cnx.execute(update_rql, {'value': value, 'x': eid})
 
 
--- a/hooks/test/data/bootstrap_cubes	Mon Jun 22 14:15:16 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-email
--- a/hooks/test/data/schema.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/hooks/test/data/schema.py	Mon Jun 22 14:27:37 2015 +0200
@@ -16,7 +16,13 @@
 # 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 RelationDefinition, EntityType, String
+from yams.buildobjs import (RelationDefinition, RelationType, EntityType,
+                            String, Datetime, Int)
+from yams.reader import context
+
+from cubicweb.schema import ERQLExpression
+
+_ = unicode
 
 class friend(RelationDefinition):
     subject = ('CWUser', 'CWGroup')
@@ -36,3 +42,44 @@
     subject = 'Folder'
     object = 'Folder'
     composite = 'subject'
+
+
+class Email(EntityType):
+    """electronic mail"""
+    subject   = String(fulltextindexed=True)
+    date      = Datetime(description=_('UTC time on which the mail was sent'))
+    messageid = String(required=True, indexed=True)
+    headers   = String(description=_('raw headers'))
+
+
+
+class EmailPart(EntityType):
+    """an email attachment"""
+    __permissions__ = {
+        'read':   ('managers', 'users', 'guests',), # XXX if E parts X, U has_read_permission E
+        'add':    ('managers', ERQLExpression('E parts X, U has_update_permission E'),),
+        'delete': ('managers', ERQLExpression('E parts X, U has_update_permission E')),
+        'update': ('managers', 'owners',),
+        }
+
+    content  = String(fulltextindexed=True)
+    content_format = String(required=True, maxsize=50)
+    ordernum = Int(required=True)
+
+
+class parts(RelationType):
+    subject = 'Email'
+    object = 'EmailPart'
+    cardinality = '*1'
+    composite = 'subject'
+    fulltext_container = 'subject'
+
+class sender(RelationDefinition):
+    subject = 'Email'
+    object = 'EmailAddress'
+    cardinality = '?*'
+    inlined = True
+
+class recipients(RelationDefinition):
+    subject = 'Email'
+    object = 'EmailAddress'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/test/unittest_security.py	Mon Jun 22 14:27:37 2015 +0200
@@ -0,0 +1,56 @@
+# copyright 2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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 cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.server import hook
+from cubicweb.predicates import is_instance
+
+
+class SecurityHooksTC(CubicWebTC):
+    def setup_database(self):
+        with self.admin_access.repo_cnx() as cnx:
+            self.add_eid = cnx.create_entity('EmailAddress',
+                                             address=u'hop@perdu.com',
+                                             reverse_use_email=cnx.user.eid).eid
+            cnx.commit()
+
+    def test_inlined_cw_edited_relation(self):
+        """modification of cw_edited to add an inlined relation shouldn't trigger a security error.
+
+        Test for https://www.cubicweb.org/ticket/5477315
+        """
+        sender = self.repo.schema['Email'].rdef('sender')
+        with self.temporary_permissions((sender, {'add': ()})):
+
+            class MyHook(hook.Hook):
+                __regid__ = 'test.pouet'
+                __select__ = hook.Hook.__select__ & is_instance('Email')
+                events = ('before_add_entity',)
+
+                def __call__(self):
+                    self.entity.cw_edited['sender'] = self._cw.user.primary_email[0].eid
+
+            with self.temporary_appobjects(MyHook):
+                with self.admin_access.repo_cnx() as cnx:
+                    email = cnx.create_entity('Email', messageid=u'1234')
+                    cnx.commit()
+                    self.assertEqual(email.sender[0].eid, self.add_eid)
+
+if __name__ == '__main__':
+    from logilab.common.testlib import unittest_main
+    unittest_main()
--- a/i18n/fr.po	Mon Jun 22 14:15:16 2015 +0200
+++ b/i18n/fr.po	Mon Jun 22 14:27:37 2015 +0200
@@ -727,7 +727,7 @@
 "the source."
 msgstr ""
 "Configuration de la source pour un hôte spécifique. Une clé=valeur par "
-"ligne, les clés autorisées dépendantes du type de source. Les valeur "
+"ligne, les clés autorisées dépendantes du type de source. Les valeurs "
 "surchargent celles définies sur la source."
 
 msgid "Startup views"
--- a/mail.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/mail.py	Mon Jun 22 14:27:37 2015 +0200
@@ -25,6 +25,7 @@
 from email.mime.text import MIMEText
 from email.mime.image import MIMEImage
 from email.header import Header
+from email.utils import formatdate
 from socket import gethostname
 
 def header(ustring):
@@ -110,6 +111,7 @@
         msg['Message-id'] = msgid
     if references:
         msg['References'] = ', '.join(references)
+    msg['Date'] = formatdate()
     return msg
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.20.7_Any.py	Mon Jun 22 14:27:37 2015 +0200
@@ -0,0 +1,2 @@
+if repo.system_source.dbdriver == 'postgres':
+    install_custom_sql_scripts()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.20.8_Any.py	Mon Jun 22 14:27:37 2015 +0200
@@ -0,0 +1,1 @@
+sync_schema_props_perms('cwuri')
--- a/predicates.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/predicates.py	Mon Jun 22 14:27:37 2015 +0200
@@ -188,6 +188,7 @@
 from warnings import warn
 from operator import eq
 
+from logilab.common.deprecation import deprecated
 from logilab.common.registry import Predicate, objectify_predicate, yes
 
 from yams.schema import BASE_TYPES, role_name
@@ -195,12 +196,10 @@
 
 from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity,
                       CW_EVENT_MANAGER, role)
-# even if not used, let yes here so it's importable through this module
 from cubicweb.uilib import eid_param
 from cubicweb.schema import split_expression
 
-# remember, these imports are there for bw compat only
-__BACKWARD_COMPAT_IMPORTS = (yes,)
+yes = deprecated('[3.15] import yes() from use logilab.common.registry')(yes)
 
 
 # abstract predicates / mixin helpers ###########################################
--- a/schemas/_regproc.postgres.sql	Mon Jun 22 14:15:16 2015 +0200
+++ b/schemas/_regproc.postgres.sql	Mon Jun 22 14:27:37 2015 +0200
@@ -11,6 +11,7 @@
 $$ LANGUAGE SQL;;
 
 
+DROP FUNCTION IF EXISTS cw_array_append_unique (anyarray, anyelement) CASCADE;
 CREATE FUNCTION cw_array_append_unique (anyarray, anyelement) RETURNS anyarray AS $$
     SELECT array_append($1, (SELECT $2 WHERE $2 <> ALL($1)))
 $$ LANGUAGE SQL;;
@@ -25,7 +26,6 @@
 );;
 
 
-
 DROP FUNCTION IF EXISTS limit_size (fulltext text, format text, maxsize integer);
 CREATE FUNCTION limit_size (fulltext text, format text, maxsize integer) RETURNS text AS $$
 DECLARE
@@ -35,7 +35,7 @@
        RETURN fulltext;
     END IF;
     IF format = 'text/html' OR format = 'text/xhtml' OR format = 'text/xml' THEN
-       plaintext := regexp_replace(fulltext, '<[\\w/][^>]+>', '', 'g');
+       plaintext := regexp_replace(fulltext, '<[a-zA-Z/][^>]*>', '', 'g');
     ELSE
        plaintext := fulltext;
     END IF;
--- a/schemas/base.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/schemas/base.py	Mon Jun 22 14:27:37 2015 +0200
@@ -26,7 +26,8 @@
                             Boolean, UniqueConstraint)
 from cubicweb.schema import (
     RQLConstraint, WorkflowableEntityType, ERQLExpression, RRQLExpression,
-    PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, PUB_SYSTEM_ATTR_PERMS)
+    PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, PUB_SYSTEM_ATTR_PERMS,
+    RO_ATTR_PERMS)
 
 class CWUser(WorkflowableEntityType):
     """define a CubicWeb user"""
@@ -160,7 +161,7 @@
 
 class cwuri(RelationType):
     """internal entity uri"""
-    __permissions__ = PUB_SYSTEM_ATTR_PERMS
+    __permissions__ = RO_ATTR_PERMS
     cardinality = '11'
     subject = '*'
     object = 'String'
--- a/server/schemaserial.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/server/schemaserial.py	Mon Jun 22 14:27:37 2015 +0200
@@ -21,8 +21,9 @@
 
 import os
 import json
+import sys
 
-from logilab.common.shellutils import ProgressBar
+from logilab.common.shellutils import ProgressBar, DummyProgressBar
 
 from yams import BadSchemaDefinition, schema as schemamod, buildobjs as ybo
 
@@ -348,7 +349,10 @@
     pb_size = (len(eschemas + schema.relations())
                + len(CONSTRAINTS)
                + len([x for x in eschemas if x.specializes()]))
-    pb = ProgressBar(pb_size, title=_title)
+    if sys.stdout.isatty():
+        pb = ProgressBar(pb_size, title=_title)
+    else:
+        pb = DummyProgressBar()
     groupmap = group_mapping(cnx, interactive=False)
     # serialize all entity types, assuring CWEType is serialized first for proper
     # is / is_instance_of insertion
--- a/server/serverctl.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/server/serverctl.py	Mon Jun 22 14:27:37 2015 +0200
@@ -972,13 +972,19 @@
     name = 'source-sync'
     arguments = '<instance> <source>'
     min_args = max_args = 2
+    options = (
+            ('loglevel',
+             {'short': 'l', 'type' : 'choice', 'metavar': '<log level>',
+              'default': 'info', 'choices': ('debug', 'info', 'warning', 'error'),
+             }),
+    )
 
     def run(self, args):
+        from cubicweb.cwctl import init_cmdline_log_threshold
         config = ServerConfiguration.config_for(args[0])
         config.global_set_option('log-file', None)
         config.log_format = '%(levelname)s %(name)s: %(message)s'
-        logger = logging.getLogger('cubicweb.sources')
-        logger.setLevel(logging.INFO)
+        init_cmdline_log_threshold(config, self['loglevel'])
         # only retrieve cnx to trigger authentication, close it right away
         repo, cnx = repo_cnx(config)
         cnx.close()
--- a/server/sources/native.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/server/sources/native.py	Mon Jun 22 14:27:37 2015 +0200
@@ -718,7 +718,7 @@
                     if mo is not None:
                         raise UniqueTogetherError(cnx, cstrname=mo.group(0))
                     # old sqlite
-                    mo = re.search('columns (.*) are not unique', arg)
+                    mo = re.search('columns? (.*) (?:is|are) not unique', arg)
                     if mo is not None: # sqlite in use
                         # we left chop the 'cw_' prefix of attribute names
                         rtypes = [c.strip()[3:]
--- a/server/sqlutils.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/server/sqlutils.py	Mon Jun 22 14:27:37 2015 +0200
@@ -28,7 +28,7 @@
 from logging import getLogger
 
 from logilab import database as db, common as lgc
-from logilab.common.shellutils import ProgressBar
+from logilab.common.shellutils import ProgressBar, DummyProgressBar
 from logilab.common.deprecation import deprecated
 from logilab.common.logging_ext import set_log_methods
 from logilab.database.sqlgen import SQLGenerator
@@ -72,7 +72,10 @@
         sqlstmts_as_string = True
         sqlstmts = sqlstmts.split(delimiter)
     if withpb:
-        pb = ProgressBar(len(sqlstmts), title=pbtitle)
+        if sys.stdout.isatty():
+            pb = ProgressBar(len(sqlstmts), title=pbtitle)
+        else:
+            pb = DummyProgressBar()
     failed = []
     for sql in sqlstmts:
         sql = sql.strip()
--- a/server/test/unittest_postgres.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/server/test/unittest_postgres.py	Mon Jun 22 14:27:37 2015 +0200
@@ -113,6 +113,24 @@
             self.assertEqual(datenaiss.tzinfo, None)
             self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 2, 0))
 
+class PostgresLimitSizeTC(CubicWebTC):
+    configcls = PostgresApptestConfiguration
+
+    def test(self):
+        with self.admin_access.repo_cnx() as cnx:
+            def sql(string):
+                return cnx.system_sql(string).fetchone()[0]
+            yield self.assertEqual, sql("SELECT limit_size('<p>hello</p>', 'text/html', 20)"), \
+                '<p>hello</p>'
+            yield self.assertEqual, sql("SELECT limit_size('<p>hello</p>', 'text/html', 2)"), \
+                'he...'
+            yield self.assertEqual, sql("SELECT limit_size('<br/>hello', 'text/html', 2)"), \
+                'he...'
+            yield self.assertEqual, sql("SELECT limit_size('<span class=\"1\">he</span>llo', 'text/html', 2)"), \
+                'he...'
+            yield self.assertEqual, sql("SELECT limit_size('<span>a>b</span>', 'text/html', 2)"), \
+                'a>...'
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/server/test/unittest_querier.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/server/test/unittest_querier.py	Mon Jun 22 14:27:37 2015 +0200
@@ -360,7 +360,7 @@
         result, descr = rset.rows, rset.description
         self.assertEqual(descr[0][0], 'String')
         self.assertEqual(descr[0][1], 'Int')
-        self.assertEqual(result[0][0], 'CWRelation') # XXX may change as schema evolve
+        self.assertEqual(result[0][0], 'RQLExpression') # XXX may change as schema evolve
 
     def test_select_groupby_orderby(self):
         rset = self.qexecute('Any N GROUPBY N ORDERBY N WHERE X is CWGroup, X name N')
--- a/test/unittest_mail.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/test/unittest_mail.py	Mon Jun 22 14:27:37 2015 +0200
@@ -21,6 +21,7 @@
 """
 
 import os
+import re
 import sys
 
 from logilab.common.testlib import unittest_main
@@ -51,7 +52,9 @@
         mail = format_mail({'name': 'oim', 'email': 'oim@logilab.fr'},
                            ['test@logilab.fr'], u'un petit cöucou', u'bïjour',
                            config=self.config)
-        self.assertMultiLineEqual(mail.as_string(), """\
+        result = mail.as_string()
+        result = re.sub('^Date: .*$', 'Date: now', result, flags=re.MULTILINE)
+        self.assertMultiLineEqual(result, """\
 MIME-Version: 1.0
 Content-Type: text/plain; charset="utf-8"
 Content-Transfer-Encoding: base64
@@ -60,6 +63,7 @@
 Reply-to: =?utf-8?q?oim?= <oim@logilab.fr>, =?utf-8?q?BimBam?= <bim@boum.fr>
 X-CW: data
 To: test@logilab.fr
+Date: now
 
 dW4gcGV0aXQgY8O2dWNvdQ==
 """)
@@ -74,7 +78,9 @@
     def test_format_mail_euro(self):
         mail = format_mail({'name': u'oîm', 'email': u'oim@logilab.fr'},
                            ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €')
-        self.assertMultiLineEqual(mail.as_string(), """\
+        result = mail.as_string()
+        result = re.sub('^Date: .*$', 'Date: now', result, flags=re.MULTILINE)
+        self.assertMultiLineEqual(result, """\
 MIME-Version: 1.0
 Content-Type: text/plain; charset="utf-8"
 Content-Transfer-Encoding: base64
@@ -82,6 +88,7 @@
 From: =?utf-8?q?o=C3=AEm?= <oim@logilab.fr>
 Reply-to: =?utf-8?q?o=C3=AEm?= <oim@logilab.fr>
 To: test@logilab.fr
+Date: now
 
 dW4gcGV0aXQgY8O2dWNvdSDigqw=
 """)
--- a/test/unittest_uilib.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/test/unittest_uilib.py	Mon Jun 22 14:27:37 2015 +0200
@@ -30,7 +30,7 @@
 
 from logilab.common.testlib import DocTest, TestCase, unittest_main
 
-from cubicweb import uilib
+from cubicweb import uilib, utils as cwutils
 
 lxml_version = pkg_resources.get_distribution('lxml').version.split('.')
 
@@ -171,6 +171,11 @@
                           'cw.pouet(1,"2")')
         self.assertEqual(str(uilib.js.cw.pouet(1, "2").pouet(None)),
                           'cw.pouet(1,"2").pouet(null)')
+        self.assertEqual(str(uilib.js.cw.pouet(1, cwutils.JSString("$")).pouet(None)),
+                         'cw.pouet(1,$).pouet(null)')
+        self.assertEqual(str(uilib.js.cw.pouet(1, {'callback': cwutils.JSString("cw.cb")}).pouet(None)),
+                         'cw.pouet(1,{callback: cw.cb}).pouet(null)')
+
 
     def test_embedded_css(self):
         incoming = u"""voir le ticket <style type="text/css">@font-face { font-family: "Cambria"; }p.MsoNormal, li.MsoNormal, div.MsoNormal { margin: 0cm 0cm 10pt; font-size: 12pt; font-family: "Times New Roman"; }a:link, span.MsoHyperlink { color: blue; text-decoration: underline; }a:visited, span.MsoHyperlinkFollowed { color: purple; text-decoration: underline; }div.Section1 { page: Section1; }</style></p><p class="MsoNormal">text</p>"""
--- a/uilib.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/uilib.py	Mon Jun 22 14:27:37 2015 +0200
@@ -32,7 +32,7 @@
 from logilab.common.date import ustrftime
 from logilab.common.deprecation import deprecated
 
-from cubicweb.utils import JSString, json_dumps
+from cubicweb.utils import js_dumps
 
 
 def rql_for_eid(eid):
@@ -353,10 +353,7 @@
     def __unicode__(self):
         args = []
         for arg in self.args:
-            if isinstance(arg, JSString):
-                args.append(arg)
-            else:
-                args.append(json_dumps(arg))
+            args.append(js_dumps(arg))
         if self.parent:
             return u'%s(%s)' % (self.parent, ','.join(args))
         return ','.join(args)
@@ -378,6 +375,8 @@
 'cw.pouet(1,"2").pouet(null)'
 >>> str(js.cw.pouet(1, JSString("$")).pouet(None))
 'cw.pouet(1,$).pouet(null)'
+>>> str(js.cw.pouet(1, {'callback': JSString("cw.cb")}).pouet(None))
+'cw.pouet(1,{callback: cw.cb}).pouet(null)'
 """
 
 def domid(string):
--- a/web/application.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/application.py	Mon Jun 22 14:27:37 2015 +0200
@@ -473,7 +473,6 @@
             req.data['ex'] = ex
             if tb:
                 req.data['excinfo'] = excinfo
-            req.form['vid'] = 'error'
             errview = self.vreg['views'].select('error', req)
             template = self.main_template_id(req)
             content = self.vreg['views'].main_template(req, template, view=errview)
--- a/web/data/cubicweb.ajax.js	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/data/cubicweb.ajax.js	Mon Jun 22 14:27:37 2015 +0200
@@ -363,7 +363,7 @@
 }
 
 /**
- * .. function:: loadRemote(url, form, reqtype='GET', sync=false)
+ * .. function:: loadRemote(url, form, reqtype='POST', sync=false)
  *
  * Asynchronously (unless `sync` argument is set to true) load a URL or path
  * and return a deferred whose callbacks args are decoded according to the
--- a/web/data/cubicweb.edition.js	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/data/cubicweb.edition.js	Mon Jun 22 14:27:37 2015 +0200
@@ -573,6 +573,8 @@
                 width: '0px',
                 height: '0px'
             }));
+            form.removeAttr('cubicweb:target'); // useles from now on, pop it
+                                                // to make IE9 happy
         }
     });
 }
--- a/web/data/cubicweb.js	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/data/cubicweb.js	Mon Jun 22 14:27:37 2015 +0200
@@ -15,13 +15,10 @@
     removeEventListener: function() {},
     detachEvent: function() {},
 
-    log: function () {
-        var args = [];
-        for (var i = 0; i < arguments.length; i++) {
-            args.push(arguments[i]);
-        }
+    log: function log() {
         if (typeof(window) != "undefined" && window.console && window.console.log) {
-            window.console.log(args.join(' '));
+            // NOTE console.log requires "console" to be the console to be "this"
+            window.console.log.apply(console, arguments);
         }
     },
 
--- a/web/formwidgets.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/formwidgets.py	Mon Jun 22 14:27:37 2015 +0200
@@ -400,6 +400,9 @@
 
 class TextArea(FieldWidget):
     """Simple <textarea>, will return a unicode string."""
+    _minrows = 2
+    _maxrows = 15
+    _columns = 80
 
     def _render(self, form, field, renderer):
         values, attrs = self.values_and_attributes(form, field)
@@ -413,9 +416,9 @@
         lines = value.splitlines()
         linecount = len(lines)
         for line in lines:
-            linecount += len(line) / 80
-        attrs.setdefault('cols', 80)
-        attrs.setdefault('rows', min(15, linecount + 2))
+            linecount += len(line) / self._columns
+        attrs.setdefault('cols', self._columns)
+        attrs.setdefault('rows', min(self._maxrows, linecount + self._minrows))
         return tags.textarea(value, name=field.input_name(form, self.suffix),
                              **attrs)
 
--- a/web/httpcache.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/httpcache.py	Mon Jun 22 14:27:37 2015 +0200
@@ -71,7 +71,7 @@
             self.req.set_header('Cache-control', 'no-cache')
             return
         req.set_header('Cache-control',
-                       'must-revalidate;max-age=%s' % self.max_age())
+                       'must-revalidate,max-age=%s' % self.max_age())
         mdate = self.last_modified()
         # use a timestamp, not a formatted raw header, and let
         # the front-end correctly generate it
--- a/web/test/unittest_urlrewrite.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/test/unittest_urlrewrite.py	Mon Jun 22 14:27:37 2015 +0200
@@ -145,6 +145,7 @@
             _pmid, rset = rewriter.rewrite(req, u'/DaLToN/JoE')
             self.assertEqual(len(rset), 1)
             self.assertEqual(rset[0][0], self.p1eid)
+            self.assertEqual(rset.description[0][0], 'CWUser')
 
     def test_inheritance_precedence(self):
         RQL1 = 'Any C WHERE C is CWEType'
--- a/web/test/unittest_views_editforms.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/test/unittest_views_editforms.py	Mon Jun 22 14:27:37 2015 +0200
@@ -67,7 +67,6 @@
                                   [('last_login_time', 'subject'),
                                    ('cw_source', 'subject'),
                                    ('creation_date', 'subject'),
-                                   ('cwuri', 'subject'),
                                    ('modification_date', 'subject'),
                                    ('created_by', 'subject'),
                                    ('owned_by', 'subject'),
@@ -127,7 +126,6 @@
             self.assertCountEqual(rbc(e, 'main', 'metadata'),
                                   [('cw_source', 'subject'),
                                    ('creation_date', 'subject'),
-                                   ('cwuri', 'subject'),
                                    ('modification_date', 'subject'),
                                    ('created_by', 'subject'),
                                    ('owned_by', 'subject'),
--- a/web/test/unittest_views_json.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/test/unittest_views_json.py	Mon Jun 22 14:27:37 2015 +0200
@@ -43,10 +43,11 @@
 
     def test_json_rsetexport_with_jsonp(self):
         with self.admin_access.web_request() as req:
-            req.form.update({'callback': 'foo',
-                             'rql': 'Any GN,COUNT(X) GROUPBY GN ORDERBY GN '
+            req.form.update({'callback': u'foo',
+                             'rql': u'Any GN,COUNT(X) GROUPBY GN ORDERBY GN '
                              'WHERE X in_group G, G name GN'})
             data = self.ctrl_publish(req, ctrl='jsonp')
+            self.assertIsInstance(data, str)
             self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/javascript'])
             # because jsonp anonymizes data, only 'guests' group should be found
             self.assertEqual(data, 'foo(%s)' % self.res_jsonp_data)
--- a/web/test/unittest_views_staticcontrollers.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/test/unittest_views_staticcontrollers.py	Mon Jun 22 14:27:37 2015 +0200
@@ -51,7 +51,17 @@
         with self._publish_static_files('data/cubicweb.css', next_headers) as req:
             self.assertEqual(304, req.status_out)
 
+class StaticDirectoryControllerTC(staticfilespublishermixin, CubicWebTC):
 
+    def test_check_static_dir_access(self):
+        """write a file in the static directory and test the access"""
+        staticdir = osp.join(self.session.vreg.config.static_directory)
+        if not os.path.exists(staticdir):
+            os.makedirs(staticdir)
+        filename = osp.join(staticdir, 'test')
+        with open(filename, 'a') as f:
+            with self._publish_static_files('static/test') as req:
+                self.assertEqual(200, req.status_out)
 
 class DataControllerTC(staticfilespublishermixin, CubicWebTC):
     tags = CubicWebTC.tags | Tags('static_controller', 'data', 'http')
--- a/web/test/unittest_web.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/test/unittest_web.py	Mon Jun 22 14:27:37 2015 +0200
@@ -50,7 +50,7 @@
         cbname = url.split()[1][:-2]
         self.assertMultiLineEqual(
             'function %s() { $("#foo").loadxhtml("http://testing.fr/cubicweb/ajax?%s",'
-            '{"pageid": "%s"},"get","replace"); }' %
+            '{pageid: "%s"},"get","replace"); }' %
             (cbname, qs, req.pageid),
             req.html_headers.post_inlined_scripts[0])
 
--- a/web/views/basecontrollers.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/views/basecontrollers.py	Mon Jun 22 14:27:37 2015 +0200
@@ -110,10 +110,7 @@
         #   anonymous connection is allowed and the page will be displayed or
         #   we'll be redirected to the login form
         msg = self._cw._('you have been logged out')
-        # force base_url so on dual http/https configuration, we generate a URL
-        # on the http version of the site
-        return self._cw.build_url('view', vid='loggedout',
-                                  base_url=self._cw.vreg.config['base-url'])
+        return self._cw.build_url('view', vid='loggedout')
 
 
 class ViewController(Controller):
--- a/web/views/baseviews.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/views/baseviews.py	Mon Jun 22 14:27:37 2015 +0200
@@ -422,6 +422,7 @@
     """
     __regid__ = 'csv'
     redirect_vid = 'incontext'
+    separator = u', '
 
     def call(self, subvid=None, **kwargs):
         kwargs['vid'] = subvid
@@ -429,7 +430,7 @@
         for i in xrange(len(rset)):
             self.cell_call(i, 0, **kwargs)
             if i < rset.rowcount-1:
-                self.w(u", ")
+                self.w(self.separator)
 
 
 # XXX to be documented views ###################################################
--- a/web/views/editcontroller.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/views/editcontroller.py	Mon Jun 22 14:27:37 2015 +0200
@@ -161,7 +161,9 @@
                         # simultaneously edited, the current entity must be
                         # created before the target one
                         if rdef.cardinality[0 if role == 'subject' else 1] == '1':
-                            target_eid = values[param]
+                            # use .get since param may be unspecified (though it will usually lead
+                            # to a validation error later)
+                            target_eid = values.get(param)
                             if target_eid in values_by_eid:
                                 # add dependency from the target entity to the
                                 # current one
--- a/web/views/json.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/views/json.py	Mon Jun 22 14:27:37 2015 +0200
@@ -20,11 +20,13 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
+from cubicweb.uilib import rest_traceback
+
 from cubicweb.utils import json_dumps
-from cubicweb.predicates import any_rset, empty_rset
+from cubicweb.predicates import ExpectedValuePredicate, any_rset, empty_rset
 from cubicweb.view import EntityView, AnyRsetView
 from cubicweb.web.application import anonymized_request
-from cubicweb.web.views import basecontrollers
+from cubicweb.web.views import basecontrollers, management
 
 class JsonpController(basecontrollers.ViewController):
     """The jsonp controller is the same as a ViewController but :
@@ -58,11 +60,11 @@
     def _get_json_data(self, rset):
         json_data = super(JsonpController, self).publish(rset)
         if 'callback' in self._cw.form: # jsonp
-            json_padding = self._cw.form['callback']
-            # use ``application/javascript`` is ``callback`` parameter is
-            # provided, let ``application/json`` otherwise
+            json_padding = self._cw.form['callback'].encode('ascii')
+            # use ``application/javascript`` if ``callback`` parameter is
+            # provided, keep ``application/json`` otherwise
             self._cw.set_content_type('application/javascript')
-            json_data = '%s(%s)' % (json_padding, json_data)
+            json_data = b'%s(%s)' % (json_padding, json_data)
         return json_data
 
 
@@ -119,3 +121,31 @@
                     })
             entities.append(entity)
         self.wdata(entities)
+
+
+class _requested_vid(ExpectedValuePredicate):
+    """predicate that checks vid parameter value
+
+    It differs from ``match_view`` in that it doesn't expect a ``view``
+    parameter to be given to ``select`` but will rather check
+    ``req.form['vid']`` to match expected vid.
+    """
+    def __call__(self, cls, req, rset=None, **kwargs):
+        return req.form.get('vid') in self.expected
+
+
+class JsonErrorView(JsonMixIn, management.ErrorView):
+    """custom error view selected when client asks for a json view
+
+    The returned json object will contain err / traceback informations.
+    """
+    __select__ = (management.ErrorView.__select__ &
+                  _requested_vid('jsonexport', 'ejsonexport'))
+
+    def call(self):
+        errmsg, exclass, excinfo = self._excinfo()
+        self.wdata({
+            'errmsg': errmsg,
+            'exclass': exclass,
+            'traceback': rest_traceback(excinfo, errmsg),
+            })
--- a/web/views/management.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/views/management.py	Mon Jun 22 14:27:37 2015 +0200
@@ -105,19 +105,24 @@
         """
         return self._cw._('an error occurred')
 
+    def _excinfo(self):
+        req = self._cw
+        ex = req.data.get('ex')
+        excinfo = req.data.get('excinfo')
+        if 'errmsg' in req.data:
+            errmsg = req.data['errmsg']
+            exclass = None
+        else:
+            errmsg = exc_message(ex, req.encoding)
+            exclass = ex.__class__.__name__
+        return errmsg, exclass, excinfo
+
     def call(self):
         req = self._cw.reset_headers()
         w = self.w
-        ex = req.data.get('ex')#_("unable to find exception information"))
-        excinfo = req.data.get('excinfo')
         title = self._cw._('an error occurred')
         w(u'<h2>%s</h2>' % title)
-        if 'errmsg' in req.data:
-            ex = req.data['errmsg']
-            exclass = None
-        else:
-            exclass = ex.__class__.__name__
-            ex = exc_message(ex, req.encoding)
+        ex, exclass, excinfo = self._excinfo()
         if excinfo is not None and self._cw.vreg.config['print-traceback']:
             if exclass is None:
                 w(u'<div class="tb">%s</div>'
--- a/web/views/reledit.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/views/reledit.py	Mon Jun 22 14:27:37 2015 +0200
@@ -213,8 +213,12 @@
         entity = self.entity
         if role == 'subject':
             kwargs = {'fromeid': entity.eid, 'toeid': rentity.eid}
+            cardinality = rschema.rdefs[(entity.cw_etype, rentity.cw_etype)].cardinality[0]
         else:
             kwargs = {'fromeid': rentity.eid, 'toeid': entity.eid}
+            cardinality = rschema.rdefs[(rentity.cw_etype, entity.cw_etype)].cardinality[1]
+        if cardinality in '1+':
+            return False
         # NOTE: should be sufficient given a well built schema/security
         return rschema.has_perm(self._cw, 'delete', **kwargs)
 
--- a/web/views/schema.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/views/schema.py	Mon Jun 22 14:27:37 2015 +0200
@@ -84,12 +84,13 @@
     """mixin providing methods to display security information for a entity,
     relation or relation definition schema
     """
+    cssclass = "listing schemaInfo"
 
     def permissions_table(self, erschema, permissions=None):
         self._cw.add_css('cubicweb.acl.css')
         w = self.w
         _ = self._cw._
-        w(u'<table class="listing schemaInfo">')
+        w(u'<table class="%s">' % self.cssclass)
         w(u'<tr><th>%s</th><th>%s</th><th>%s</th></tr>' % (
             _("permission"), _('granted to groups'), _('rql expressions')))
         for action in erschema.ACTIONS:
--- a/web/views/staticcontrollers.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/views/staticcontrollers.py	Mon Jun 22 14:27:37 2015 +0200
@@ -243,7 +243,7 @@
 
     def publish(self, rset=None):
         staticdir = self._cw.vreg.config.static_directory
-        relpath = self.relpath
+        relpath = self.relpath[len(self.__regid__) + 1:]
         return self.static_file(osp.join(staticdir, relpath))
 
 STATIC_CONTROLLERS = [DataController, FCKEditorController,
--- a/web/views/treeview.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/views/treeview.py	Mon Jun 22 14:27:37 2015 +0200
@@ -244,6 +244,8 @@
         entity = self.cw_rset.get_entity(row, col)
         itree = entity.cw_adapt_to('ITree')
         liclasses = []
+        if self._cw.url(includeparams=False) == entity.absolute_url():
+            liclasses.append(u'selected')
         is_open = self.open_state(entity.eid, treeid)
         is_leaf = itree is None or itree.is_leaf()
         if is_leaf:
--- a/web/views/urlrewrite.py	Mon Jun 22 14:15:16 2015 +0200
+++ b/web/views/urlrewrite.py	Mon Jun 22 14:27:37 2015 +0200
@@ -167,16 +167,13 @@
         return None, None
     return do_build_rset
 
-def rgx_action(rql=None, args=None, cachekey=None, argsgroups=(), setuser=False,
+def rgx_action(rql=None, args=None, argsgroups=(), setuser=False,
                form=None, formgroups=(), transforms={}, rqlformparams=(), controller=None):
     def do_build_rset(inputurl, uri, req, schema,
-                      cachekey=cachekey # necessary to avoid UnboundLocalError
                       ):
         if rql:
             kwargs = args and args.copy() or {}
             if argsgroups:
-                if cachekey is not None and isinstance(cachekey, basestring):
-                    cachekey = (cachekey,)
                 match = inputurl.match(uri)
                 for key in argsgroups:
                     value = match.group(key)
@@ -184,13 +181,11 @@
                         kwargs[key] = transforms[key](value)
                     except KeyError:
                         kwargs[key] = value
-                    if cachekey is not None and key in cachekey:
-                        kwargs[key] = int(value)
             if setuser:
                 kwargs['u'] = req.user.eid
             for param in rqlformparams:
                 kwargs.setdefault(param, req.form.get(param))
-            rset = req.execute(rql, kwargs, cachekey)
+            rset = req.execute(rql, kwargs)
         else:
             rset = None
         form2 = form and form.copy() or {}