merge stable in default (3.18) branch
authorDavid Douard <david.douard@logilab.fr>
Tue, 30 Jul 2013 20:31:57 +0200
changeset 9205 ea32e964fbf8
parent 9149 31ed9dd946d1 (current diff)
parent 9204 5edd09229890 (diff)
child 9217 b4ac21bf6019
merge stable in default (3.18) branch
schema.py
web/formfields.py
--- a/.hgtags	Thu Jul 04 09:26:59 2013 +0200
+++ b/.hgtags	Tue Jul 30 20:31:57 2013 +0200
@@ -30,11 +30,7 @@
 c9c492787a8aa1b7916e22eb6498cba1c8fa316c cubicweb-debian-version-3_2_0-1
 634c251dd032894850080c4e5aeb0a4e09f888c0 cubicweb-version-3_2_1
 e784f8847a124a93e5b385d7a92a2772c050fe82 cubicweb-debian-version-3_2_1-1
-6539ce84f04357ef65ccee0896a30997b16a4ece cubicweb-version-3_2_2
-92d1a15f08f7c5fa87643ffb4273d12cb3f41c63 cubicweb-debian-version-3_2_2-1
-6539ce84f04357ef65ccee0896a30997b16a4ece cubicweb-version-3_2_2
 9b21e068fef73c37bcb4e53d006a7bde485f390b cubicweb-version-3_2_2
-92d1a15f08f7c5fa87643ffb4273d12cb3f41c63 cubicweb-debian-version-3_2_2-1
 0e07514264aa1b0b671226f41725ea4c066c210a cubicweb-debian-version-3_2_2-1
 f60bb84b86cf371f1f25197e00c778b469297721 cubicweb-version-3_2_3
 4003d24974f15f17bd03b7efd6a5047cad4e4c41 cubicweb-debian-version-3_2_3-1
@@ -43,10 +39,8 @@
 a356da3e725bfcb59d8b48a89d04be05ea261fd3 3.3.1
 e3aeb6e6c3bb5c18e8dcf61bae9d654beda6c036 cubicweb-version-3_3_2
 bef5e74e53f9de8220451dca4b5863a24a0216fb cubicweb-debian-version-3_3_2-1
-1cf9e44e2f1f4415253b8892a0adfbd3b69e84fd cubicweb-version-3_3_3
+47b5236774a0cf3b1cfe75f6d4bd2ec989644ace cubicweb-version-3_3_3
 81973c897c9e78e5e52643e03628654916473196 cubicweb-debian-version-3_3_3-1
-1cf9e44e2f1f4415253b8892a0adfbd3b69e84fd cubicweb-version-3_3_3
-47b5236774a0cf3b1cfe75f6d4bd2ec989644ace cubicweb-version-3_3_3
 2ba27ce8ecd9828693ec53c517e1c8810cbbe33e cubicweb-debian-version-3_3_3-2
 d46363eac5d71bc1570d69337955154dfcd8fcc8 cubicweb-version-3.3.4
 7dc22caa7640bf70fcae55afb6d2326829dacced cubicweb-debian-version-3.3.4-1
@@ -82,11 +76,7 @@
 37d025b2aa7735dae4a861059014c560b45b19e6 cubicweb-debian-version-3.5.4-1
 1eca47d59fd932fe23f643ca239cf2408e5b1856 cubicweb-version-3.5.5
 aad818d9d9b6fdb2ffea56c0a9af718c0b69899d cubicweb-debian-version-3.5.5-1
-b79f361839a7251b35eb8378fbc0773de7c8a815 cubicweb-version-3.5.6
-e6225e8e36c6506c774e0a76acc301d8ae1c1028 cubicweb-debian-version-3.5.6-1
-b79f361839a7251b35eb8378fbc0773de7c8a815 cubicweb-version-3.5.6
 4e619e97b3fd70769a0f454963193c10cb87f9d4 cubicweb-version-3.5.6
-e6225e8e36c6506c774e0a76acc301d8ae1c1028 cubicweb-debian-version-3.5.6-1
 5f7c939301a1b915e17eec61c05e8e9ab8bdc182 cubicweb-debian-version-3.5.6-1
 0fc300eb4746e01f2755b9eefd986d58d8366ccf cubicweb-version-3.5.7
 7a96c0544c138a0c5f452e5b2428ce6e2b7cb378 cubicweb-debian-version-3.5.7-1
@@ -98,8 +88,6 @@
 4920121d41f28c8075a4f00461911677396fc566 cubicweb-debian-version-3.5.11-1
 98af3d02b83e7635207781289cc3445fb0829951 cubicweb-version-3.5.12
 4281e1e2d76b9a37f38c0eeb1cbdcaa2fac6533c cubicweb-debian-version-3.5.12-1
-5f957e351b0a60d5c5fff60c560b04e666c3a8c6 cubicweb-version-3.6.0
-17e88f2485d1ea1fb8a3926a274637ce19e95d69 cubicweb-debian-version-3.6.0-1
 450804da3ab2476b7ede0c1f956235b4c239734f cubicweb-version-3.6.0
 d2ba93fcb8da95ceab08f48f8149a480215f149c cubicweb-debian-version-3.6.0-1
 4ae30c9ca11b1edad67d25b76fce672171d02023 cubicweb-version-3.6.1
@@ -107,12 +95,12 @@
 0a16f07112b90fb61d2e905855fece77e5a7e39c cubicweb-debian-version-3.6.1-2
 bfebe3d14d5390492925fc294dfdafad890a7104 cubicweb-version-3.6.2
 f3b4bb9121a0e7ee5961310ff79e61c890948a77 cubicweb-debian-version-3.6.2-1
+9c342fa4f1b73e06917d7dc675949baff442108b cubicweb-version-3.6.3
+f9fce56d6a0c2bc6c4b497b66039a8bbbbdc8074 cubicweb-debian-version-3.6.3-1
 270aba1e6fa21dac6b070e7815e6d1291f9c87cd cubicweb-version-3.7.0
 0c9ff7e496ce344b7e6bf5c9dd2847daf9034e5e cubicweb-debian-version-3.7.0-1
 6b0832bbd1daf27c2ce445af5b5222e1e522fb90 cubicweb-version-3.7.1
 9194740f070e64da5a89f6a9a31050a8401ebf0c cubicweb-debian-version-3.7.1-1
-9c342fa4f1b73e06917d7dc675949baff442108b cubicweb-version-3.6.3
-f9fce56d6a0c2bc6c4b497b66039a8bbbbdc8074 cubicweb-debian-version-3.6.3-1
 d010f749c21d55cd85c5feb442b9cf816282953c cubicweb-version-3.7.2
 8fda29a6c2191ba3cc59242c17b28b34127c75fa cubicweb-debian-version-3.7.2-1
 768beb8e15f15e079f8ee6cfc35125e12b19e140 cubicweb-version-3.7.3
@@ -135,10 +123,11 @@
 5d05b08adeab1ea301e49ed8537e35ede6db92f6 cubicweb-debian-version-3.8.5-1
 1a24c62aefc5e57f61be3d04affd415288e81904 cubicweb-version-3.8.6
 607a90073911b6bb941a49b5ec0b0d2a9cd479af cubicweb-debian-version-3.8.6-1
+a1a334d934390043a4293a4ee42bdceb1343246e cubicweb-version-3.8.7
+1cccf88d6dfe42986e1091de4c364b7b5814c54f cubicweb-debian-version-3.8.7-1
+48f468f33704e401a8e7907e258bf1ac61eb8407 cubicweb-version-3.9.x
 d9936c39d478b6701a4adef17bc28888ffa011c6 cubicweb-version-3.9.0
 eda4940ffef8b7d36127e68de63a52388374a489 cubicweb-debian-version-3.9.0-1
-a1a334d934390043a4293a4ee42bdceb1343246e cubicweb-version-3.8.7
-1cccf88d6dfe42986e1091de4c364b7b5814c54f cubicweb-debian-version-3.8.7-1
 4d75f743ed49dd7baf8bde7b0e475244933fa08e cubicweb-version-3.9.1
 9bd75af3dca36d7be5d25fc5ab1b89b34c811456 cubicweb-debian-version-3.9.1-1
 e51796b9caf389c224c6f66dcb8aa75bf1b82eff cubicweb-version-3.9.2
@@ -155,6 +144,11 @@
 1c01f9dffd64d507863c9f8f68e3585b7aa24374 cubicweb-debian-version-3.9.7-1
 eed788018b595d46a55805bd8d2054c401812b2b cubicweb-version-3.9.8
 e4dba8ae963701a36be94ae58c790bc97ba029bb cubicweb-debian-version-3.9.8-1
+df0b2de62cec10c84a2fff5233db05852cbffe93 cubicweb-version-3.9.9
+1ba51b00fc44faa0d6d57448000aaa1fd5c6ab57 cubicweb-debian-version-3.9.9-1
+b7db1f59355832a409d2032e19c84cfffdb3b265 cubicweb-debian-version-3.9.9-2
+09c98763ae9d43616d047c1b25d82b4e41a4362f cubicweb-debian-version-3.9.9-3
+a62f24e1497e953fbaed5894f6064a64f7ac0be3 cubicweb-version-3.10.x
 0793fe84651be36f8de9b4faba3781436dc07be0 cubicweb-version-3.10.0
 9ef1347f8d99e7daad290738ef93aa894a2c03ce cubicweb-debian-version-3.10.0-1
 6c6859a676732c845af69f92e74d4aafae12f83a cubicweb-version-3.10.1
@@ -163,15 +157,7 @@
 4a87c8af6f3ffe59c6048ebbdc1b6b204d0b9c7f cubicweb-debian-version-3.10.2-1
 8eb58d00a0cedcf7b275b1c7f43b08e2165f655c cubicweb-version-3.10.3
 303b150ebb7a92b2904efd52b446457999cab370 cubicweb-debian-version-3.10.3-1
-3829498510a754b1b8a40582cb8dcbca9145fc9d cubicweb-version-3.10.4
-49f1226f2fab6d9ff17eb27d5a66732a4e5b5add cubicweb-debian-version-3.10.4-1
-df0b2de62cec10c84a2fff5233db05852cbffe93 cubicweb-version-3.9.9
-1ba51b00fc44faa0d6d57448000aaa1fd5c6ab57 cubicweb-debian-version-3.9.9-1
-b7db1f59355832a409d2032e19c84cfffdb3b265 cubicweb-debian-version-3.9.9-2
-09c98763ae9d43616d047c1b25d82b4e41a4362f cubicweb-debian-version-3.9.9-3
-3829498510a754b1b8a40582cb8dcbca9145fc9d cubicweb-version-3.10.4
 d73733479a3af453f06b849ed88d120784ce9224 cubicweb-version-3.10.4
-49f1226f2fab6d9ff17eb27d5a66732a4e5b5add cubicweb-debian-version-3.10.4-1
 7b41930e1d32fea3989a85f6ea7281983300adb1 cubicweb-debian-version-3.10.4-1
 159d0dbe07d9eb1c6ace4c5e160d1ec6e6762086 cubicweb-version-3.10.5
 e2e7410e994777589aec218d31eef9ff8d893f92 cubicweb-debian-version-3.10.5-1
@@ -181,21 +167,20 @@
 bf5d9a1415e3c9abe6b68ba3b24a8ad741f9de3c cubicweb-debian-version-3.10.7-1
 e581a86a68f089946a98c966ebca7aee58a5718f cubicweb-version-3.10.8
 132b525de25bc75ed6389c45aee77e847cb3a437 cubicweb-debian-version-3.10.8-1
-48f468f33704e401a8e7907e258bf1ac61eb8407 cubicweb-version-3.9.x
 37432cede4fe55b97fc2e9be0a2dd20e8837a848 cubicweb-version-3.11.0
 8daabda9f571863e8754f8ab722744c417ba3abf cubicweb-debian-version-3.11.0-1
 d0410eb4d8bbf657d7f32b0c681db09b1f8119a0 cubicweb-version-3.11.1
 77318f1ec4aae3523d455e884daf3708c3c79af7 cubicweb-debian-version-3.11.1-1
 56ae3cd5f8553678a2b1d4121b61241598d0ca68 cubicweb-version-3.11.2
 954b5b51cd9278eb45d66be1967064d01ab08453 cubicweb-debian-version-3.11.2-1
+b7a124f9aed2c7c9c86c6349ddd9f0a07023f0ca cubicweb-version-3.11.3
+b3c6702761a18a41fdbb7bc1083f92aefce07765 cubicweb-debian-version-3.11.3-1
 fd502219eb76f4bfd239d838a498a1d1e8204baf cubicweb-version-3.12.0
 92b56939b7c77bbf443b893c495a20f19bc30702 cubicweb-debian-version-3.12.0-1
 59701627adba73ee97529f6ea0e250a0f3748e32 cubicweb-version-3.12.1
 07e2c9c7df2617c5ecfa84cb819b3ee8ef91d1f2 cubicweb-debian-version-3.12.1-1
 5a9b6bc5653807500c30a7eb0e95b90fd714fec3 cubicweb-version-3.12.2
 6d418fb3ffed273562aae411efe323d5138b592a cubicweb-debian-version-3.12.2-1
-b7a124f9aed2c7c9c86c6349ddd9f0a07023f0ca cubicweb-version-3.11.3
-b3c6702761a18a41fdbb7bc1083f92aefce07765 cubicweb-debian-version-3.11.3-1
 e712bc6f1f71684f032bfcb9bb151a066c707dec cubicweb-version-3.12.3
 ba8fe4f2e408c3fdf6c297cd42c2577dcac50e71 cubicweb-debian-version-3.12.3-1
 5cd0dbc26882f60e3f11ec55e7f058d94505e7ed cubicweb-version-3.12.4
@@ -204,14 +189,16 @@
 6dfe78a0797ccc34962510f8c2a57f63d65ce41e cubicweb-debian-version-3.12.5-1
 a18dac758150fe9c1f9e4958d898717c32a8f679 cubicweb-version-3.12.6
 105767487c7075dbcce36474f1af0485985cbf2c cubicweb-debian-version-3.12.6-1
-b661ef475260ca7d9ea5c36ba2cc86e95e5b17d3 cubicweb-version-3.13.0
-a96137858f571711678954477da6f7f435870cea cubicweb-debian-version-3.13.0-1
 628fe57ce746c1dac87fb1b078b2026057df894e cubicweb-version-3.12.7
 a07517985136bbbfa6610c428a1b42cd04cd530b cubicweb-debian-version-3.12.7-1
 50122a47ce4fb2ecbf3cf20ed2777f4276c93609 cubicweb-version-3.12.8
 cf49ed55685a810d8d73585330ad1a57cc76260d cubicweb-debian-version-3.12.8-1
 cb2990aaa63cbfe593bcf3afdbb9071e4c76815a cubicweb-version-3.12.9
 92464e39134c70e4ddbe6cd78a6e3338a3b88b05 cubicweb-debian-version-3.12.9-1
+074c848a3712a77737d9a1bfbb618c75f5c0cbfa cubicweb-version-3.12.10
+9dfd21fa0a8b9f121a08866ad3e2ebd1dd06790d cubicweb-debian-version-3.12.10-1
+b661ef475260ca7d9ea5c36ba2cc86e95e5b17d3 cubicweb-version-3.13.0
+a96137858f571711678954477da6f7f435870cea cubicweb-debian-version-3.13.0-1
 7d84317ef185a10c5eb78e6086f2297d2f4bd1e3 cubicweb-version-3.13.1
 cc0578049cbe8b1d40009728e36c17e45da1fc6b cubicweb-debian-version-3.13.1-1
 f9227b9d61835f03163b8133a96da35db37a0c8d cubicweb-version-3.13.2
@@ -220,11 +207,8 @@
 fb48c55cb80234bc0164c9bcc0e2cfc428836e5f cubicweb-debian-version-3.13.3-1
 223ecf0620b6c87d997f8011aca0d9f0ee4750af cubicweb-version-3.13.4
 52f26475d764129c5559b2d80fd57e6ea1bdd6ba cubicweb-debian-version-3.13.4-1
-a62f24e1497e953fbaed5894f6064a64f7ac0be3 cubicweb-version-3.10.x
 20d9c550c57eb6f9adcb0cfab1c11b6b8793afb6 cubicweb-version-3.13.5
 2e9dd7d945557c210d3b79153c65f6885e755315 cubicweb-debian-version-3.13.5-1
-074c848a3712a77737d9a1bfbb618c75f5c0cbfa cubicweb-version-3.12.10
-9dfd21fa0a8b9f121a08866ad3e2ebd1dd06790d cubicweb-debian-version-3.12.10-1
 17c007ad845abbac82e12146abab32a634657574 cubicweb-version-3.13.6
 8a8949ca5351d48c5cf795ccdff06c1d4aab2ce0 cubicweb-debian-version-3.13.6-1
 68e8c81fa96d6bcd21cc17bc9832d388ce05a9eb cubicweb-version-3.13.7
@@ -233,10 +217,10 @@
 43f83f5d0a4d57a06e9a4990bc957fcfa691eec3 cubicweb-debian-version-3.13.8-1
 07afe32945aa275052747f78ef1f55858aaf6fa9 cubicweb-version-3.13.9
 0a3cb5e60d57a7a9851371b4ae487094ec2bf614 cubicweb-debian-version-3.13.9-1
+2ad4e5173c73a43804c265207bcabb8940bd42f4 cubicweb-version-3.13.10
+2eab9a5a6bf8e3b0cf706bee8cdf697759c0a33a cubicweb-debian-version-3.13.10-1
 5c4390eb10c3fe76a81e6fccec109d7097dc1a8d cubicweb-version-3.14.0
 0bfe22fceb383b46d62b437bf5dd0141a714afb8 cubicweb-debian-version-3.14.0-1
-2ad4e5173c73a43804c265207bcabb8940bd42f4 cubicweb-version-3.13.10
-2eab9a5a6bf8e3b0cf706bee8cdf697759c0a33a cubicweb-debian-version-3.13.10-1
 793d2d327b3ebf0b82b2735cf3ccb86467d1c08a cubicweb-version-3.14.1
 6928210da4fc25d086b5b8d5ff2029da41aade2e cubicweb-debian-version-3.14.1-1
 049a3819f03dc79d803be054cc3bfe8425313f63 cubicweb-version-3.14.2
@@ -250,8 +234,6 @@
 55fc796ed5d5f31245ae60bd148c9e42657a1af6 cubicweb-debian-version-3.14.5-1
 db021578232b885dc5e55dfca045332ce01e7f35 cubicweb-version-3.14.6
 75364c0994907764715bd5011f6a59d934dbeb7d cubicweb-debian-version-3.14.6-1
-0642b2d03acaa5e065cae7590e82b388a280ca22 cubicweb-version-3.15.0
-925db25a3250c5090cf640fc2b02bde5818b9798 cubicweb-debian-version-3.15.0-1
 3ba3ee5b3a89a54d1dc12ed41d5c12232eda1952 cubicweb-version-3.14.7
 20ee573bd2379a00f29ff27bb88a8a3344d4cdfe cubicweb-debian-version-3.14.7-1
 15fe07ff687238f8cc09d8e563a72981484085b3 cubicweb-version-3.14.8
@@ -260,6 +242,8 @@
 68c762adf2d5a2c338910ef1091df554370586f0 cubicweb-debian-version-3.14.9-1
 0ff798f80138ca8f50a59f42284380ce8f6232e8 cubicweb-version-3.14.10
 197bcd087c87cd3de9f21f5bf40bd6203c074f1f cubicweb-debian-version-3.14.10-1
+0642b2d03acaa5e065cae7590e82b388a280ca22 cubicweb-version-3.15.0
+925db25a3250c5090cf640fc2b02bde5818b9798 cubicweb-debian-version-3.15.0-1
 783a5df54dc742e63c8a720b1582ff08366733bd cubicweb-version-3.15.1
 fe5e60862b64f1beed2ccdf3a9c96502dfcd811b cubicweb-debian-version-3.15.1-1
 2afc157ea9b2b92eccb0f2d704094e22ce8b5a05 cubicweb-version-3.15.2
@@ -291,19 +275,27 @@
 ee860c51f56bd65c4f6ea363462c02700d1dab5a cubicweb-version-3.16.3
 ee860c51f56bd65c4f6ea363462c02700d1dab5a cubicweb-debian-version-3.16.3-1
 ee860c51f56bd65c4f6ea363462c02700d1dab5a cubicweb-centos-version-3.16.3-1
-cc1a0aad580cf93d26959f97d8d6638e786c1082 cubicweb-version-3.17.0
-22be40c492e9034483bfec379ca11462ea97825b cubicweb-debian-version-3.17.0-1
-09a0c7ea6c3cb97bbbeed3795b3c3715ceb9566b cubicweb-debian-version-3.17.0-2
 041804bc48e91e440a5b573ceb0df5bf22863b80 cubicweb-version-3.16.4
 041804bc48e91e440a5b573ceb0df5bf22863b80 cubicweb-debian-version-3.16.4-1
 041804bc48e91e440a5b573ceb0df5bf22863b80 cubicweb-centos-version-3.16.4-1
 810a05fba1a46ab893b6cadac109097a047f8355 cubicweb-version-3.16.5
 810a05fba1a46ab893b6cadac109097a047f8355 cubicweb-debiann-version-3.16.5-1
 810a05fba1a46ab893b6cadac109097a047f8355 cubicweb-centos-version-3.16.5-1
-f98d1c46ed9fd5db5262cf5be1c8e159c90efc8b cubicweb-version-3.17.1
+b4ccaf13081d2798c0414d002e743cb0bf6d81f8 cubicweb-version-3.16.6
+b4ccaf13081d2798c0414d002e743cb0bf6d81f8 cubicweb-centos-version-3.16.6-1
+b4ccaf13081d2798c0414d002e743cb0bf6d81f8 cubicweb-debian-version-3.16.6-1
+cc1a0aad580cf93d26959f97d8d6638e786c1082 cubicweb-version-3.17.0
+22be40c492e9034483bfec379ca11462ea97825b cubicweb-debian-version-3.17.0-1
+09a0c7ea6c3cb97bbbeed3795b3c3715ceb9566b cubicweb-debian-version-3.17.0-2
 f98d1c46ed9fd5db5262cf5be1c8e159c90efc8b cubicweb-version-3.17.1
-73f2ad404716cd211b735e67ee16875f1fff7374 cubicweb-debian-version-3.17.1-1
 f98d1c46ed9fd5db5262cf5be1c8e159c90efc8b cubicweb-debian-version-3.17.1-1
 f98d1c46ed9fd5db5262cf5be1c8e159c90efc8b cubicweb-centos-version-3.17.1-1
+965f894b63cb7c4456acd82257709f563bde848f cubicweb-centos-version-3.17.1-2
 195e519fe97c8d1a5ab5ccb21bf7c88e5801b657 cubicweb-version-3.17.2
 195e519fe97c8d1a5ab5ccb21bf7c88e5801b657 cubicweb-debian-version-3.17.2-1
+32b4d5314fd90fe050c931886190f9a372686148 cubicweb-version-3.17.3
+32b4d5314fd90fe050c931886190f9a372686148 cubicweb-debian-version-3.17.3-1
+32b4d5314fd90fe050c931886190f9a372686148 cubicweb-centos-version-3.17.3-1
+c7ba8e5d2e45e3d1289c1403df40d7dcb5e62acb cubicweb-version-3.17.4
+c7ba8e5d2e45e3d1289c1403df40d7dcb5e62acb cubicweb-debian-version-3.17.4-1
+c7ba8e5d2e45e3d1289c1403df40d7dcb5e62acb cubicweb-centos-version-3.17.4-1
--- a/__pkginfo__.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/__pkginfo__.py	Tue Jul 30 20:31:57 2013 +0200
@@ -22,7 +22,7 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 17, 3)
+numversion = (3, 17, 4)
 version = '.'.join(str(num) for num in numversion)
 
 description = "a repository of entities / relations for knowledge management"
@@ -52,7 +52,6 @@
     # XXX graphviz
     # server dependencies
     'logilab-database': '>= 1.10',
-    'pysqlite': '>= 2.5.5', # XXX install pysqlite2
     'passlib': '',
     }
 
--- a/_exceptions.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/_exceptions.py	Tue Jul 30 20:31:57 2013 +0200
@@ -61,7 +61,7 @@
     """
 
 class AuthenticationError(ConnectionError):
-    """raised when when an attempt to establish a connection failed do to wrong
+    """raised when an attempt to establish a connection failed due to wrong
     connection information (login / password or other authentication token)
     """
 
--- a/cubicweb.spec	Thu Jul 04 09:26:59 2013 +0200
+++ b/cubicweb.spec	Tue Jul 30 20:31:57 2013 +0200
@@ -7,7 +7,7 @@
 %endif
 
 Name:           cubicweb
-Version:        3.17.3
+Version:        3.17.4
 Release:        logilab.1%{?dist}
 Summary:        CubicWeb is a semantic web application framework
 Source0:        http://download.logilab.org/pub/cubicweb/cubicweb-%{version}.tar.gz
@@ -46,11 +46,13 @@
 
 %install
 NO_SETUPTOOLS=1 %{__python} setup.py --quiet install --no-compile --prefix=%{_prefix} --root="$RPM_BUILD_ROOT"
+mkdir -p $RPM_BUILD_ROOT/var/log/cubicweb
 
 %clean
 rm -rf $RPM_BUILD_ROOT
 
 %files 
 %defattr(-, root, root)
+%dir /var/log/cubicweb
 /*
 
--- a/dataimport.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/dataimport.py	Tue Jul 30 20:31:57 2013 +0200
@@ -105,8 +105,8 @@
     return i+1
 
 def ucsvreader_pb(stream_or_path, encoding='utf-8', separator=',', quote='"',
-                  skipfirst=False, withpb=True):
-    """same as ucsvreader but a progress bar is displayed as we iter on rows"""
+                  skipfirst=False, withpb=True, skip_empty=True):
+    """same as :func:`ucsvreader` but a progress bar is displayed as we iter on rows"""
     if isinstance(stream_or_path, basestring):
         if not osp.exists(stream_or_path):
             raise Exception("file doesn't exists: %s" % stream_or_path)
@@ -118,23 +118,30 @@
         rowcount -= 1
     if withpb:
         pb = shellutils.ProgressBar(rowcount, 50)
-    for urow in ucsvreader(stream, encoding, separator, quote, skipfirst):
+    for urow in ucsvreader(stream, encoding, separator, quote,
+                           skipfirst=skipfirst, skip_empty=skip_empty):
         yield urow
         if withpb:
             pb.update()
     print ' %s rows imported' % rowcount
 
 def ucsvreader(stream, encoding='utf-8', separator=',', quote='"',
-               skipfirst=False, ignore_errors=False):
+               skipfirst=False, ignore_errors=False, skip_empty=True):
     """A csv reader that accepts files with any encoding and outputs unicode
     strings
+
+    if skip_empty (the default), lines without any values specified (only
+    separators) will be skipped. This is useful for Excel exports which may be
+    full of such lines.
     """
     it = iter(csv.reader(stream, delimiter=separator, quotechar=quote))
     if not ignore_errors:
         if skipfirst:
             it.next()
         for row in it:
-            yield [item.decode(encoding) for item in row]
+            decoded = [item.decode(encoding) for item in row]
+            if not skip_empty or any(decoded):
+                yield [item.decode(encoding) for item in row]
     else:
         # Skip first line
         try:
@@ -151,7 +158,10 @@
             # Error in CSV, ignore line and continue
             except csv.Error:
                 continue
-            yield [item.decode(encoding) for item in row]
+            decoded = [item.decode(encoding) for item in row]
+            if not skip_empty or any(decoded):
+                yield decoded
+
 
 def callfunc_every(func, number, iterable):
     """yield items of `iterable` one by one and call function `func`
--- a/debian/changelog	Thu Jul 04 09:26:59 2013 +0200
+++ b/debian/changelog	Tue Jul 30 20:31:57 2013 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.17.4-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- David Douard <david.douard@logilab.fr>  Fri, 26 Jul 2013 09:44:19 +0200
+
 cubicweb (3.17.3-1) unstable; urgency=low
 
   * new upstream release
@@ -28,6 +34,12 @@
 
  -- Pierre-Yves David <pierre-yves.david@logilab.fr>  Mon, 29 Apr 2013 11:20:56 +0200
 
+cubicweb (3.16.6-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Florent Cayré <florent.cayre@logilab.fr>  Sat, 13 Jul 2013 05:10:23 +0200
+
 cubicweb (3.16.5-1) unstable; urgency=low
 
   * new upstream release
--- a/debian/control	Thu Jul 04 09:26:59 2013 +0200
+++ b/debian/control	Tue Jul 30 20:31:57 2013 +0200
@@ -10,7 +10,6 @@
 Build-Depends:
  debhelper (>= 7),
  python (>= 2.6),
- python-central (>= 0.5),
  python-sphinx,
  python-logilab-common,
  python-unittest2,
@@ -24,7 +23,6 @@
 
 Package: cubicweb
 Architecture: all
-XB-Python-Version: ${python:Versions}
 Depends:
  ${misc:Depends},
  ${python:Depends},
@@ -45,7 +43,6 @@
 
 Package: cubicweb-server
 Architecture: all
-XB-Python-Version: ${python:Versions}
 Conflicts: cubicweb-multisources
 Replaces: cubicweb-multisources
 Provides: cubicweb-multisources
@@ -101,7 +98,6 @@
 
 Package: cubicweb-twisted
 Architecture: all
-XB-Python-Version: ${python:Versions}
 Provides: cubicweb-web-frontend
 Depends:
  ${misc:Depends},
@@ -123,7 +119,6 @@
 
 Package: cubicweb-web
 Architecture: all
-XB-Python-Version: ${python:Versions}
 Depends:
  ${misc:Depends},
  ${python:Depends},
@@ -148,7 +143,6 @@
 
 Package: cubicweb-common
 Architecture: all
-XB-Python-Version: ${python:Versions}
 Depends:
  ${misc:Depends},
  ${python:Depends},
@@ -173,7 +167,6 @@
 
 Package: cubicweb-ctl
 Architecture: all
-XB-Python-Version: ${python:Versions}
 Depends:
  ${misc:Depends},
  ${python:Depends},
@@ -188,7 +181,6 @@
 
 Package: cubicweb-dev
 Architecture: all
-XB-Python-Version: ${python:Versions}
 Depends:
  ${misc:Depends},
  ${python:Depends},
--- a/debian/rules	Thu Jul 04 09:26:59 2013 +0200
+++ b/debian/rules	Tue Jul 30 20:31:57 2013 +0200
@@ -18,11 +18,6 @@
 	# distributions and we don't want to block a new release of Cubicweb
 	# because of documentation issues.
 	-PYTHONPATH=$${PYTHONPATH:+$${PYTHONPATH}:}$(CURDIR)/debian/pythonpath $(MAKE) -C doc/book/en all
-	# squeeze has a broken combination of jquery and sphinx, fix it up so search works(ish)
-	if grep -q jQuery\\.className doc/html/_static/doctools.js && grep -q "jQuery JavaScript Library v1\.4\." doc/html/_static/jquery.js; then \
-	    echo 'Patching doctools.js for jQuery 1.4 compat'; \
-	    sed -i 's/jQuery\.className.has(node\.parentNode, className)/jQuery(node.parentNode).hasClass(className)/' doc/html/_static/doctools.js; \
-	fi
 	rm -rf debian/pythonpath
 	touch build-stamp
 
@@ -72,7 +67,7 @@
 binary-indep: build install
 	dh_testdir
 	dh_testroot -i
-	dh_pycentral -i
+	dh_python2 -i
 	dh_installinit -i -n --name cubicweb -u"defaults 99"
 	dh_installlogrotate -i
 	dh_installdocs -i -A README
--- a/devtools/devctl.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/devtools/devctl.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -130,24 +130,31 @@
     w('# singular and plural forms for each entity type\n')
     w('\n')
     vregdone = set()
+    afss = vreg['uicfg']['autoform_section']
+    aiams = vreg['uicfg']['actionbox_appearsin_addmenu']
     if libconfig is not None:
+        # processing a cube, libconfig being a config with all its dependencies
+        # (cubicweb incl.)
         from cubicweb.cwvreg import CWRegistryStore
         libschema = libconfig.load_schema(remove_unused_rtypes=False)
-        afs = vreg['uicfg'].select('autoform_section')
-        appearsin_addmenu = vreg['uicfg'].select('actionbox_appearsin_addmenu')
         cleanup_sys_modules(libconfig)
         libvreg = CWRegistryStore(libconfig)
         libvreg.set_schema(libschema) # trigger objects registration
-        libafs = libvreg['uicfg'].select('autoform_section')
-        libappearsin_addmenu = libvreg['uicfg'].select('actionbox_appearsin_addmenu')
+        libafss = libvreg['uicfg']['autoform_section']
+        libaiams = libvreg['uicfg']['actionbox_appearsin_addmenu']
         # prefill vregdone set
         list(_iter_vreg_objids(libvreg, vregdone))
+
+        def is_in_lib(rtags, eschema, rschema, role, tschema, predicate=bool):
+            return any(predicate(rtag.etype_get(eschema, rschema, role, tschema))
+                       for rtag in rtags)
     else:
+        # processing cubicweb itself
         libschema = {}
-        afs = vreg['uicfg'].select('autoform_section')
-        appearsin_addmenu = vreg['uicfg'].select('actionbox_appearsin_addmenu')
         for cstrtype in CONSTRAINTS:
             add_msg(w, cstrtype)
+
+        is_in_lib = lambda: False
     done = set()
     for eschema in sorted(schema.entities()):
         if eschema.type in libschema:
@@ -169,32 +176,34 @@
             if rschema.final:
                 continue
             for tschema in targetschemas:
-                fsections = afs.etype_get(eschema, rschema, role, tschema)
-                if 'main_inlined' in fsections and \
-                       (libconfig is None or not
-                        'main_inlined' in libafs.etype_get(
-                            eschema, rschema, role, tschema)):
-                    add_msg(w, 'add a %s' % tschema,
-                            'inlined:%s.%s.%s' % (etype, rschema, role))
-                    add_msg(w, str(tschema),
-                            'inlined:%s.%s.%s' % (etype, rschema, role))
-                if appearsin_addmenu.etype_get(eschema, rschema, role, tschema):
-                    if libconfig is not None and libappearsin_addmenu.etype_get(
-                        eschema, rschema, role, tschema):
-                        if eschema in libschema and tschema in libschema:
-                            continue
-                    if role == 'subject':
-                        label = 'add %s %s %s %s' % (eschema, rschema,
-                                                     tschema, role)
-                        label2 = "creating %s (%s %%(linkto)s %s %s)" % (
-                            tschema, eschema, rschema, tschema)
-                    else:
-                        label = 'add %s %s %s %s' % (tschema, rschema,
-                                                     eschema, role)
-                        label2 = "creating %s (%s %s %s %%(linkto)s)" % (
-                            tschema, tschema, rschema, eschema)
-                    add_msg(w, label)
-                    add_msg(w, label2)
+
+                for afs in afss:
+                    fsections = afs.etype_get(eschema, rschema, role, tschema)
+                    if 'main_inlined' in fsections and not \
+                            is_in_lib(libafss, eschema, rschema, role, tschema,
+                                      lambda x: 'main_inlined' in x):
+                        add_msg(w, 'add a %s' % tschema,
+                                'inlined:%s.%s.%s' % (etype, rschema, role))
+                        add_msg(w, str(tschema),
+                                'inlined:%s.%s.%s' % (etype, rschema, role))
+                        break
+
+                for aiam in aiams:
+                    if aiam.etype_get(eschema, rschema, role, tschema) and not \
+                            is_in_lib(libaiams, eschema, rschema, role, tschema):
+                        if role == 'subject':
+                            label = 'add %s %s %s %s' % (eschema, rschema,
+                                                         tschema, role)
+                            label2 = "creating %s (%s %%(linkto)s %s %s)" % (
+                                tschema, eschema, rschema, tschema)
+                        else:
+                            label = 'add %s %s %s %s' % (tschema, rschema,
+                                                         eschema, role)
+                            label2 = "creating %s (%s %s %s %%(linkto)s)" % (
+                                tschema, tschema, rschema, eschema)
+                        add_msg(w, label)
+                        add_msg(w, label2)
+                        break
             # XXX also generate "creating ...' messages for actions in the
             # addrelated submenu
     w('# subject and object forms for each relation type\n')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/devtools/test/data/cubes/i18ntestcube/__pkginfo__.py	Tue Jul 30 20:31:57 2013 +0200
@@ -0,0 +1,18 @@
+# pylint: disable=W0622
+"""cubicweb i18n test cube application packaging information"""
+
+modname = 'i18ntestcube'
+distname = 'cubicweb-i18ntestcube'
+
+numversion = (0, 1, 0)
+version = '.'.join(str(num) for num in numversion)
+
+license = 'LGPL'
+author = 'LOGILAB S.A. (Paris, FRANCE)'
+author_email = 'contact@logilab.fr'
+description = 'forum'
+web = 'http://www.cubicweb.org/project/%s' % distname
+
+__depends__ =  {'cubicweb': '>= 3.16.4',
+               }
+__recommends__ = {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/devtools/test/data/cubes/i18ntestcube/i18n/en.po.ref	Tue Jul 30 20:31:57 2013 +0200
@@ -0,0 +1,170 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: cubicweb 3.16.5\n"
+"PO-Revision-Date: 2008-03-28 18:14+0100\n"
+"Last-Translator: Logilab Team <contact@logilab.fr>\n"
+"Language-Team: fr <contact@logilab.fr>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: cubicweb-devtools\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+# schema pot file, generated on 2013-07-12 16:18:12
+#
+# singular and plural forms for each entity type
+# subject and object forms for each relation type
+# (no object form for final or symmetric relation types)
+msgid "Forum"
+msgstr ""
+
+msgid "Forum_plural"
+msgstr ""
+
+msgid "This Forum"
+msgstr ""
+
+msgid "New Forum"
+msgstr ""
+
+msgctxt "inlined:Forum.in_forum.object"
+msgid "add a ForumThread"
+msgstr ""
+
+msgctxt "inlined:Forum.in_forum.object"
+msgid "ForumThread"
+msgstr ""
+
+msgid "add ForumThread in_forum Forum object"
+msgstr ""
+
+msgid "creating ForumThread (ForumThread in_forum Forum %(linkto)s)"
+msgstr ""
+
+msgid "ForumThread"
+msgstr ""
+
+msgid "ForumThread_plural"
+msgstr ""
+
+msgid "This ForumThread"
+msgstr ""
+
+msgid "New ForumThread"
+msgstr ""
+
+msgid "content"
+msgstr ""
+
+msgctxt "ForumThread"
+msgid "content"
+msgstr ""
+
+msgid "content_format"
+msgstr ""
+
+msgctxt "ForumThread"
+msgid "content_format"
+msgstr ""
+
+msgctxt "Forum"
+msgid "description"
+msgstr ""
+
+msgctxt "Forum"
+msgid "description_format"
+msgstr ""
+
+msgid "in_forum"
+msgstr ""
+
+msgctxt "ForumThread"
+msgid "in_forum"
+msgstr ""
+
+msgctxt "Forum"
+msgid "in_forum_object"
+msgstr ""
+
+msgid "in_forum_object"
+msgstr ""
+
+msgid "interested_in"
+msgstr ""
+
+msgctxt "CWUser"
+msgid "interested_in"
+msgstr ""
+
+msgctxt "ForumThread"
+msgid "interested_in_object"
+msgstr ""
+
+msgctxt "Forum"
+msgid "interested_in_object"
+msgstr ""
+
+msgid "interested_in_object"
+msgstr ""
+
+msgid "nosy_list"
+msgstr ""
+
+msgctxt "ForumThread"
+msgid "nosy_list"
+msgstr ""
+
+msgctxt "Forum"
+msgid "nosy_list"
+msgstr ""
+
+msgctxt "CWUser"
+msgid "nosy_list_object"
+msgstr ""
+
+msgid "nosy_list_object"
+msgstr ""
+
+msgctxt "ForumThread"
+msgid "title"
+msgstr ""
+
+msgid "topic"
+msgstr ""
+
+msgctxt "Forum"
+msgid "topic"
+msgstr ""
+
+msgid "Topic"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Number of threads"
+msgstr ""
+
+msgid "Last activity"
+msgstr ""
+
+msgid ""
+"a long\n"
+"tranlated line\n"
+"hop."
+msgstr ""
+
+msgid "Subject"
+msgstr ""
+
+msgid "Created"
+msgstr ""
+
+msgid "Answers"
+msgstr ""
+
+msgid "Last answered"
+msgstr ""
+
+msgid "This forum does not have any thread yet."
+msgstr ""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/devtools/test/data/cubes/i18ntestcube/schema.py	Tue Jul 30 20:31:57 2013 +0200
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+# copyright 2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""cubicweb-forum schema"""
+
+from yams.buildobjs import (String, RichString, EntityType,
+                            RelationDefinition, SubjectRelation)
+from yams.reader import context
+
+class Forum(EntityType):
+    topic = String(maxsize=50, required=True, unique=True)
+    description = RichString()
+
+class ForumThread(EntityType):
+    __permissions__ = {
+        'read': ('managers', 'users'),
+        'add': ('managers', 'users'),
+        'update': ('managers', 'owners'),
+        'delete': ('managers', 'owners')
+        }
+    title = String(required=True, fulltextindexed=True, maxsize=256)
+    content = RichString(required=True, fulltextindexed=True)
+    in_forum = SubjectRelation('Forum', cardinality='1*', inlined=True,
+                               composite='object')
+class interested_in(RelationDefinition):
+    subject = 'CWUser'
+    object = ('ForumThread', 'Forum')
+
+class nosy_list(RelationDefinition):
+    subject = ('Forum', 'ForumThread')
+    object = 'CWUser'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/devtools/test/data/cubes/i18ntestcube/views.py	Tue Jul 30 20:31:57 2013 +0200
@@ -0,0 +1,64 @@
+# copyright 2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""cubicweb-forum views/forms/actions/components for web ui"""
+
+from cubicweb import view
+from cubicweb.predicates import is_instance
+from cubicweb.web.views import primary, baseviews, uicfg
+from cubicweb.web.views.uicfg import autoform_section as afs
+
+class MyAFS(uicfg.AutoformSectionRelationTags):
+    __select__ = is_instance('ForumThread')
+
+_myafs = MyAFS()
+
+# XXX useless ASA logilab.common.registry is fixed
+_myafs.__module__ = "cubes.i18ntestcube.views"
+
+_myafs.tag_object_of(('*', 'in_forum', 'Forum'), 'main', 'inlined')
+
+afs.tag_object_of(('*', 'in_forum', 'Forum'), 'main', 'inlined')
+
+
+class ForumSameETypeListView(baseviews.SameETypeListView):
+    __select__ = baseviews.SameETypeListView.__select__ & is_instance('Forum')
+
+    def call(self, **kwargs):
+        _ = self._cw._
+        _('Topic'), _('Description')
+        _('Number of threads'), _('Last activity')
+        _('''a long
+tranlated line
+hop.''')
+
+
+class ForumLastActivity(view.EntityView):
+    __regid__ = 'forum_last_activity'
+    __select__ = view.EntityView.__select__ & is_instance('Forum')
+
+
+class ForumPrimaryView(primary.PrimaryView):
+    __select__ = primary.PrimaryView.__select__ & is_instance('Forum')
+
+    def render_entity_attributes(self, entity):
+        _ = self._cw._
+        _('Subject'), _('Created'), _('Answers'),
+        _('Last answered')
+        _('This forum does not have any thread yet.')
+
+class ForumThreadPrimaryView(primary.PrimaryView):
+    __select__ = primary.PrimaryView.__select__ & is_instance('ForumThread')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/devtools/test/unittest_i18n.py	Tue Jul 30 20:31:57 2013 +0200
@@ -0,0 +1,79 @@
+# -*- coding: iso-8859-1 -*-
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# 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/>.
+"""unit tests for i18n messages generator"""
+
+import os, os.path as osp
+import sys
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb.cwconfig import CubicWebNoAppConfiguration
+
+DATADIR = osp.join(osp.abspath(osp.dirname(__file__)), 'data')
+
+def load_po(fname):
+    """load a po file and  return a set of encountered (msgid, msgctx)"""
+    msgs = set()
+    msgid = msgctxt = None
+    for line in open(fname):
+        if line.strip() in ('', '#'):
+            continue
+        if line.startswith('msgstr'):
+            assert not (msgid, msgctxt) in msgs
+            msgs.add( (msgid, msgctxt) )
+            msgid = msgctxt = None
+        elif line.startswith('msgid'):
+            msgid = line.split(' ', 1)[1][1:-1]
+        elif line.startswith('msgctx'):
+            msgctxt = line.split(' ', 1)[1][1: -1]
+        elif msgid is not None:
+            msgid += line[1:-1]
+        elif msgctxt is not None:
+            msgctxt += line[1:-1]
+    return msgs
+
+
+class cubePotGeneratorTC(TestCase):
+    """test case for i18n pot file generator"""
+
+    def setUp(self):
+        self._CUBES_PATH = CubicWebNoAppConfiguration.CUBES_PATH[:]
+        CubicWebNoAppConfiguration.CUBES_PATH.append(osp.join(DATADIR, 'cubes'))
+        CubicWebNoAppConfiguration.cls_adjust_sys_path()
+
+    def tearDown(self):
+        CubicWebNoAppConfiguration.CUBES_PATH[:] = self._CUBES_PATH
+
+    def test_i18ncube(self):
+        # MUST import here to make, since the import statement fire
+        # the cube paths setup (and then must occur after the setUp)
+        from cubicweb.devtools.devctl import update_cube_catalogs
+        cube = osp.join(DATADIR, 'cubes', 'i18ntestcube')
+        msgs = load_po(osp.join(cube, 'i18n', 'en.po.ref'))
+        update_cube_catalogs(cube)
+        newmsgs = load_po(osp.join(cube, 'i18n', 'en.po'))
+        self.assertEqual(msgs, newmsgs)
+
+if __name__ == '__main__':
+    # XXX dirty hack to make this test runnable using python (works
+    # fine with pytest, but not with python directly if this hack is
+    # not present)
+    # XXX to remove ASA logilab.common is fixed
+    sys.path.append('')
+    unittest_main()
--- a/doc/book/en/devrepo/devcore/dbapi.rst	Thu Jul 04 09:26:59 2013 +0200
+++ b/doc/book/en/devrepo/devcore/dbapi.rst	Tue Jul 30 20:31:57 2013 +0200
@@ -29,6 +29,11 @@
 
   Also, a rollback is automatically done if an error occurs during commit.
 
+.. note::
+
+   A :exc:`ValidationError` has a `entity` attribute. In CubicWeb,
+   this atttribute is set to the entity's eid (not a reference to the
+   entity itself).
 
 Executing RQL queries from a view or a hook
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- a/doc/book/en/devrepo/repo/hooks.rst	Thu Jul 04 09:26:59 2013 +0200
+++ b/doc/book/en/devrepo/repo/hooks.rst	Tue Jul 30 20:31:57 2013 +0200
@@ -237,7 +237,7 @@
 interface. Hence its constructor is different from the default Exception
 constructor. It accepts, positionally:
 
-* an entity eid,
+* an entity eid (**not the entity itself**),
 
 * a dict whose keys represent attribute (or relation) names and values
   an end-user facing message (hence properly translated) relating the
--- a/doc/book/en/devrepo/repo/sessions.rst	Thu Jul 04 09:26:59 2013 +0200
+++ b/doc/book/en/devrepo/repo/sessions.rst	Tue Jul 30 20:31:57 2013 +0200
@@ -59,7 +59,7 @@
       other credentials elements (calling `authentication_information`),
       giving the request object each time
 
-      * the default retriever (oddly named `LoginPasswordRetreiver`)
+      * the default retriever (named `LoginPasswordRetriever`)
         will in turn defer login and password fetching to the request
         object (which, depending on the authentication mode (`cookie`
         or `http`), will do the appropriate things and return a login
--- a/doc/book/en/devweb/request.rst	Thu Jul 04 09:26:59 2013 +0200
+++ b/doc/book/en/devweb/request.rst	Tue Jul 30 20:31:57 2013 +0200
@@ -30,11 +30,7 @@
 
 * `Session data handling`
 
-  * `session_data()`, returns a dictionary containing all the session data
-  * `get_session_data(key, default=None)`, returns a value associated to the given
-    key or the value `default` if the key is not defined
-  * `set_session_data(key, value)`, assign a value to a key
-  * `del_session_data(key)`,  suppress the value associated to a key
+  * `session.data` is the dictionnary of the session data; it can be manipulated like an ordinary Python dictionnary
 
 * `Edition` (utilities for edition control):
 
--- a/entity.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/entity.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -1181,8 +1181,7 @@
                               if v in select.defined_vars and v in cstr.mainvars)
                 # rewrite constraint by constraint since we want a AND between
                 # expressions.
-                rewriter.rewrite(select, [(varmap, (cstr,))], select.solutions,
-                                 args, existant)
+                rewriter.rewrite(select, [(varmap, (cstr,))], args, existant)
         # insert security RQL expressions granting the permission to 'add' the
         # relation into the rql syntax tree, if necessary
         rqlexprs = rdef.get_rqlexprs('add')
@@ -1194,8 +1193,7 @@
             varmap = dict((v, v) for v in (searchedvar.name, evar.name)
                           if v in select.defined_vars)
             # rewrite all expressions at once since we want a OR between them.
-            rewriter.rewrite(select, [(varmap, rqlexprs)], select.solutions,
-                             args, existant)
+            rewriter.rewrite(select, [(varmap, rqlexprs)], args, existant)
         # ensure we have an order defined
         if not select.orderby:
             select.add_sort_var(select.defined_vars[searchedvar.name])
--- a/misc/migration/bootstrapmigration_repository.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/misc/migration/bootstrapmigration_repository.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -20,6 +20,7 @@
 it should only include low level schema changes
 """
 
+from cubicweb import ConfigurationError
 from cubicweb.server.session import hooks_control
 from cubicweb.server import schemaserial as ss
 
@@ -37,19 +38,19 @@
 if applcubicwebversion < (3, 17, 0) and cubicwebversion >= (3, 17, 0):
     try:
         add_cube('sioc', update_database=False)
-    except ImportError:
+    except ConfigurationError:
         if not confirm('In cubicweb 3.17 sioc views have been moved to the sioc '
                        'cube, which is not installed.  Continue anyway?'):
             raise
     try:
         add_cube('embed', update_database=False)
-    except ImportError:
+    except ConfigurationError:
         if not confirm('In cubicweb 3.17 embedding views have been moved to the embed '
                        'cube, which is not installed.  Continue anyway?'):
             raise
     try:
         add_cube('geocoding', update_database=False)
-    except ImportError:
+    except ConfigurationError:
         if not confirm('In cubicweb 3.17 geocoding views have been moved to the geocoding '
                        'cube, which is not installed.  Continue anyway?'):
             raise
@@ -72,7 +73,7 @@
         from cubicweb import ExecutionError
         try:
             add_cube('localperms', update_database=False)
-        except ImportError:
+        except ConfigurationError:
             raise ExecutionError('In cubicweb 3.14, CWPermission and related stuff '
                                  'has been moved to cube localperms. Install it first.')
 
--- a/predicates.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/predicates.py	Tue Jul 30 20:31:57 2013 +0200
@@ -994,7 +994,11 @@
             return 0 # relation not supported
         if self.action:
             if self.target_etype is not None:
-                rschema = rschema.role_rdef(entity.e_schema, self.target_etype, self.role)
+                try:
+                    rschema = rschema.role_rdef(entity.e_schema,
+                                                self.target_etype, self.role)
+                except KeyError:
+                    return 0
             if self.role == 'subject':
                 if not rschema.has_perm(entity._cw, self.action, fromeid=entity.eid):
                     return 0
--- a/rqlrewrite.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/rqlrewrite.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -33,6 +33,13 @@
 from cubicweb import Unauthorized
 
 
+def cleanup_solutions(rqlst, solutions):
+    for sol in solutions:
+        for vname in list(sol):
+            if not (vname in rqlst.defined_vars or vname in rqlst.aliases):
+                del sol[vname]
+
+
 def add_types_restriction(schema, rqlst, newroot=None, solutions=None):
     if newroot is None:
         assert solutions is None
@@ -132,10 +139,69 @@
     return newsolutions
 
 
+def _add_noinvariant(noinvariant, restricted, select, nbtrees):
+    # a variable can actually be invariant if it has not been restricted for
+    # security reason or if security assertion hasn't modified the possible
+    # solutions for the query
+    for vname in restricted:
+        try:
+            var = select.defined_vars[vname]
+        except KeyError:
+            # this is an alias
+            continue
+        if nbtrees != 1 or len(var.stinfo['possibletypes']) != 1:
+            noinvariant.add(var)
+
+
+def _expand_selection(terms, selected, aliases, select, newselect):
+    for term in terms:
+        for vref in term.iget_nodes(n.VariableRef):
+            if not vref.name in selected:
+                select.append_selected(vref)
+                colalias = newselect.get_variable(vref.name, len(aliases))
+                aliases.append(n.VariableRef(colalias))
+                selected.add(vref.name)
+
+def _has_multiple_cardinality(etypes, rdef, ttypes_func, cardindex):
+    """return True if relation definitions from entity types (`etypes`) to
+    target types returned by the `ttypes_func` function all have single (1 or ?)
+    cardinality.
+    """
+    for etype in etypes:
+        for ttype in ttypes_func(etype):
+            if rdef(etype, ttype).cardinality[cardindex] in '+*':
+                return True
+    return False
+
+def _compatible_relation(relations, stmt, sniprel):
+    """Search among given rql relation nodes if there is one 'compatible' with the
+    snippet relation, and return it if any, else None.
+
+    A relation is compatible if it:
+    * belongs to the currently processed statement,
+    * isn't negged (i.e. direct parent is a NOT node)
+    * isn't optional (outer join) or similarly as the snippet relation
+    """
+    for rel in relations:
+        # don't share if relation's scope is not the current statement
+        if rel.scope is not stmt:
+            continue
+        # don't share neged relation
+        if rel.neged(strict=True):
+            continue
+        # don't share optional relation, unless the snippet relation is
+        # similarly optional
+        if rel.optional and rel.optional != sniprel.optional:
+            continue
+        return rel
+    return None
+
+
 def iter_relations(stinfo):
     # this is a function so that test may return relation in a predictable order
     return stinfo['relations'] - stinfo['rhsrelations']
 
+
 class Unsupported(Exception):
     """raised when an rql expression can't be inserted in some rql query
     because it create an unresolvable query (eg no solutions found)
@@ -164,13 +230,118 @@
         if len(self.select.solutions) < len(self.solutions):
             raise Unsupported()
 
-    def rewrite(self, select, snippets, solutions, kwargs, existingvars=None):
+    def insert_local_checks(self, select, kwargs,
+                            localchecks, restricted, noinvariant):
+        """
+        select: the rql syntax tree Select node
+        kwargs: query arguments
+
+        localchecks: {(('Var name', (rqlexpr1, rqlexpr2)),
+                       ('Var name1', (rqlexpr1, rqlexpr23))): [solution]}
+
+              (see querier._check_permissions docstring for more information)
+
+        restricted: set of variable names to which an rql expression has to be
+              applied
+
+        noinvariant: set of variable names that can't be considered has
+              invariant due to security reason (will be filed by this method)
+        """
+        nbtrees = len(localchecks)
+        myunion = union = select.parent
+        # transform in subquery when len(localchecks)>1 and groups
+        if nbtrees > 1 and (select.orderby or select.groupby or
+                            select.having or select.has_aggregat or
+                            select.distinct or
+                            select.limit or select.offset):
+            newselect = stmts.Select()
+            # only select variables in subqueries
+            origselection = select.selection
+            select.select_only_variables()
+            select.has_aggregat = False
+            # create subquery first so correct node are used on copy
+            # (eg ColumnAlias instead of Variable)
+            aliases = [n.VariableRef(newselect.get_variable(vref.name, i))
+                       for i, vref in enumerate(select.selection)]
+            selected = set(vref.name for vref in aliases)
+            # now copy original selection and groups
+            for term in origselection:
+                newselect.append_selected(term.copy(newselect))
+            if select.orderby:
+                sortterms = []
+                for sortterm in select.orderby:
+                    sortterms.append(sortterm.copy(newselect))
+                    for fnode in sortterm.get_nodes(n.Function):
+                        if fnode.name == 'FTIRANK':
+                            # we've to fetch the has_text relation as well
+                            var = fnode.children[0].variable
+                            rel = iter(var.stinfo['ftirels']).next()
+                            assert not rel.ored(), 'unsupported'
+                            newselect.add_restriction(rel.copy(newselect))
+                            # remove relation from the orig select and
+                            # cleanup variable stinfo
+                            rel.parent.remove(rel)
+                            var.stinfo['ftirels'].remove(rel)
+                            var.stinfo['relations'].remove(rel)
+                            # XXX not properly re-annotated after security insertion?
+                            newvar = newselect.get_variable(var.name)
+                            newvar.stinfo.setdefault('ftirels', set()).add(rel)
+                            newvar.stinfo.setdefault('relations', set()).add(rel)
+                newselect.set_orderby(sortterms)
+                _expand_selection(select.orderby, selected, aliases, select, newselect)
+                select.orderby = () # XXX dereference?
+            if select.groupby:
+                newselect.set_groupby([g.copy(newselect) for g in select.groupby])
+                _expand_selection(select.groupby, selected, aliases, select, newselect)
+                select.groupby = () # XXX dereference?
+            if select.having:
+                newselect.set_having([g.copy(newselect) for g in select.having])
+                _expand_selection(select.having, selected, aliases, select, newselect)
+                select.having = () # XXX dereference?
+            if select.limit:
+                newselect.limit = select.limit
+                select.limit = None
+            if select.offset:
+                newselect.offset = select.offset
+                select.offset = 0
+            myunion = stmts.Union()
+            newselect.set_with([n.SubQuery(aliases, myunion)], check=False)
+            newselect.distinct = select.distinct
+            solutions = [sol.copy() for sol in select.solutions]
+            cleanup_solutions(newselect, solutions)
+            newselect.set_possible_types(solutions)
+            # if some solutions doesn't need rewriting, insert original
+            # select as first union subquery
+            if () in localchecks:
+                myunion.append(select)
+            # we're done, replace original select by the new select with
+            # subqueries (more added in the loop below)
+            union.replace(select, newselect)
+        elif not () in localchecks:
+            union.remove(select)
+        for lcheckdef, lchecksolutions in localchecks.iteritems():
+            if not lcheckdef:
+                continue
+            myrqlst = select.copy(solutions=lchecksolutions)
+            myunion.append(myrqlst)
+            # in-place rewrite + annotation / simplification
+            lcheckdef = [({var: 'X'}, rqlexprs) for var, rqlexprs in lcheckdef]
+            self.rewrite(myrqlst, lcheckdef, kwargs)
+            _add_noinvariant(noinvariant, restricted, myrqlst, nbtrees)
+        if () in localchecks:
+            select.set_possible_types(localchecks[()])
+            add_types_restriction(self.schema, select)
+            _add_noinvariant(noinvariant, restricted, select, nbtrees)
+        self.annotate(union)
+
+    def rewrite(self, select, snippets, kwargs, existingvars=None):
         """
         snippets: (varmap, list of rql expression)
                   with varmap a *tuple* (select var, snippet var)
         """
         self.select = select
-        self.solutions = solutions
+        # remove_solutions used below require a copy
+        self.solutions = solutions = select.solutions[:]
         self.kwargs = kwargs
         self.u_varname = None
         self.removing_ambiguity = False
@@ -195,6 +366,7 @@
             select, solutions, newsolutions))
         if len(newsolutions) > len(solutions):
             newsolutions = self.remove_ambiguities(snippets, newsolutions)
+            assert newsolutions
         select.solutions = newsolutions
         add_types_restriction(self.schema, select)
 
@@ -235,9 +407,14 @@
                                                       subselect.solutions, self.kwargs)
                     return
                 if varexistsmap is None:
-                    vi['rhs_rels'] = dict( (r.r_type, r) for r in sti['rhsrelations'])
-                    vi['lhs_rels'] = dict( (r.r_type, r) for r in sti['relations']
-                                           if not r in sti['rhsrelations'])
+                    # build an index for quick access to relations
+                    vi['rhs_rels'] = {}
+                    for rel in sti['rhsrelations']:
+                        vi['rhs_rels'].setdefault(rel.r_type, []).append(rel)
+                    vi['lhs_rels'] = {}
+                    for rel in sti['relations']:
+                        if not rel in sti['rhsrelations']:
+                            vi['lhs_rels'].setdefault(rel.r_type, []).append(rel)
                 else:
                     vi['rhs_rels'] = vi['lhs_rels'] = {}
         previous = None
@@ -464,7 +641,6 @@
                 exists = var.references()[0].scope
                 exists.add_constant_restriction(var, 'is', etype, 'etype')
         # recompute solutions
-        #select.annotated = False # avoid assertion error
         self.compute_solutions()
         # clean solutions according to initial solutions
         return remove_solutions(self.solutions, self.select.solutions,
@@ -509,38 +685,34 @@
         """if the snippet relation can be skipped to use a relation from the
         original query, return that relation node
         """
+        if sniprel.neged(strict=True):
+            return None # no way
         rschema = self.schema.rschema(sniprel.r_type)
         stmt = self.current_statement()
         for vi in self.varinfos:
             try:
                 if target == 'object':
-                    orel = vi['lhs_rels'][sniprel.r_type]
+                    orels = vi['lhs_rels'][sniprel.r_type]
                     cardindex = 0
                     ttypes_func = rschema.objects
                     rdef = rschema.rdef
                 else: # target == 'subject':
-                    orel = vi['rhs_rels'][sniprel.r_type]
+                    orels = vi['rhs_rels'][sniprel.r_type]
                     cardindex = 1
                     ttypes_func = rschema.subjects
                     rdef = lambda x, y: rschema.rdef(y, x)
             except KeyError:
                 # may be raised by vi['xhs_rels'][sniprel.r_type]
-                return None
-            # don't share if relation's statement is not the current statement
-            if orel.stmt is not stmt:
-                return None
-            # can't share neged relation or relations with different outer join
-            if (orel.neged(strict=True) or sniprel.neged(strict=True)
-                or (orel.optional and orel.optional != sniprel.optional)):
-                return None
-            # if cardinality is in '?1', we can ignore the snippet relation and use
-            # variable from the original query
-            for etype in vi['stinfo']['possibletypes']:
-                for ttype in ttypes_func(etype):
-                    if rdef(etype, ttype).cardinality[cardindex] in '+*':
-                        return None
-            break
-        return orel
+                continue
+            # if cardinality isn't in '?1', we can't ignore the snippet relation
+            # and use variable from the original query
+            if _has_multiple_cardinality(vi['stinfo']['possibletypes'], rdef,
+                                         ttypes_func, cardindex):
+                continue
+            orel = _compatible_relation(orels, stmt, sniprel)
+            if orel is not None:
+                return orel
+        return None
 
     def _use_orig_term(self, snippet_varname, term):
         key = (self.current_expr, self.varmap, snippet_varname)
--- a/schema.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/schema.py	Tue Jul 30 20:31:57 2013 +0200
@@ -93,7 +93,7 @@
                       'WorkflowTransition', 'BaseTransition',
                       'SubWorkflowExitPoint'))
 
-INTERNAL_TYPES = set(('CWProperty', 'CWCache', 'ExternalUri',
+INTERNAL_TYPES = set(('CWProperty', 'CWCache', 'ExternalUri', 'CWDataImport',
                       'CWSource', 'CWSourceHostConfig', 'CWSourceSchemaConfig'))
 
 
@@ -739,6 +739,9 @@
             return self.expression == other.expression
         return False
 
+    def __hash__(self):
+        return hash(self.expression)
+
     def __deepcopy__(self, memo):
         return self.__class__(self.expression, self.mainvars)
     def __getstate__(self):
--- a/server/checkintegrity.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/checkintegrity.py	Tue Jul 30 20:31:57 2013 +0200
@@ -57,6 +57,9 @@
             pass
         eids[eid] = False
         return False
+    if etype not in session.vreg.schema:
+        eids[eid] = False
+        return False
     sqlcursor.execute('SELECT * FROM %s%s WHERE %seid=%s' % (SQL_PREFIX, etype,
                                                              SQL_PREFIX, eid))
     result = sqlcursor.fetchall()
@@ -179,12 +182,12 @@
     """check all entities registered in the repo system table"""
     print 'Checking entities system table'
     # system table but no source
-    msg = '  Entity with eid %s exists in the system table but in no source (autofix will delete the entity)'
-    cursor = session.system_sql('SELECT eid FROM entities;')
+    msg = '  Entity %s with eid %s exists in the system table but in no source (autofix will delete the entity)'
+    cursor = session.system_sql('SELECT eid,type FROM entities;')
     for row in cursor.fetchall():
-        eid = row[0]
+        eid, etype = row
         if not has_eid(session, cursor, eid, eids):
-            sys.stderr.write(msg % eid)
+            sys.stderr.write(msg % (etype, eid))
             if fix:
                 session.system_sql('DELETE FROM entities WHERE eid=%s;' % eid)
             notify_fixed(fix)
@@ -258,6 +261,12 @@
     sys.stderr.write(msg % (rtype, target, eid))
     notify_fixed(fix)
 
+def bad_inlined_msg(rtype, parent_eid, eid, fix):
+    msg = ('  An inlined relation %s from %s to %s exists but the latter '
+           'entity does not exist')
+    sys.stderr.write(msg % (rtype, parent_eid, eid))
+    notify_fixed(fix)
+
 
 def check_relations(schema, session, eids, fix=1):
     """check that eids referenced by relations are registered in the repo system
@@ -271,13 +280,13 @@
             for subjtype in rschema.subjects():
                 table = SQL_PREFIX + str(subjtype)
                 column = SQL_PREFIX +  str(rschema)
-                sql = 'SELECT %s FROM %s WHERE %s IS NOT NULL;' % (
+                sql = 'SELECT cw_eid,%s FROM %s WHERE %s IS NOT NULL;' % (
                     column, table, column)
                 cursor = session.system_sql(sql)
                 for row in cursor.fetchall():
-                    eid = row[0]
+                    parent_eid, eid = row
                     if not has_eid(session, cursor, eid, eids):
-                        bad_related_msg(rschema, 'object', eid, fix)
+                        bad_inlined_msg(rschema, parent_eid, eid, fix)
                         if fix:
                             sql = 'UPDATE %s SET %s=NULL WHERE %s=%s;' % (
                                 table, column, column, eid)
@@ -366,6 +375,13 @@
     eidcolumn = SQL_PREFIX + 'eid'
     msg = '  %s with eid %s has no %s (autofix will set it to now)'
     for etype, in cursor.fetchall():
+        if etype not in session.vreg.schema:
+            sys.stderr.write('entities table references unknown type %s\n' %
+                             etype)
+            if fix:
+                session.system_sql("DELETE FROM entities WHERE type = %(type)s",
+                                   {'type': etype})
+            continue
         table = SQL_PREFIX + etype
         for rel, default in ( ('creation_date', datetime.now()),
                               ('modification_date', datetime.now()), ):
--- a/server/edition.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/edition.py	Tue Jul 30 20:31:57 2013 +0200
@@ -145,7 +145,7 @@
             entity.e_schema.check(dict_protocol_catcher(entity),
                                   creation=creation, relations=relations)
         except ValidationError as ex:
-            ex.entity = self.entity
+            ex.entity = self.entity.eid
             raise
 
     def clone(self):
--- a/server/migractions.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/migractions.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -83,8 +83,8 @@
             repo.vreg.register(ClearGroupMap)
 
 class ServerMigrationHelper(MigrationHelper):
-    """specific migration helper for server side  migration scripts,
-    providind actions related to schema/data migration
+    """specific migration helper for server side migration scripts,
+    providing actions related to schema/data migration
     """
 
     def __init__(self, config, schema, interactive=True,
@@ -575,10 +575,24 @@
                              '      E eid %%(x)s,'
                              '      %s' % ', '.join(restrictions),
                              substs)
+            def possible_unique_constraint(ut):
+                for name in ut:
+                    rschema = repoeschema.subjrels.get(name)
+                    if rschema is None:
+                        print 'dont add %s unique constraint on %s, missing %s' % (
+                            ','.join(ut), eschema, name)
+                        return False
+                    if not (rschema.final or rschema.final.inlined):
+                        (eschema, name)
+                        print 'dont add %s unique constraint on %s, %s is neither final nor inlined' % (
+                            ','.join(ut), eschema, name)
+                        return False
+                return True
             for ut in unique_together - repo_unique_together:
-                rql, substs = ss.uniquetogether2rql(eschema, ut)
-                substs['x'] = repoeschema.eid
-                self.rqlexec(rql, substs)
+                if possible_unique_constraint(ut):
+                    rql, substs = ss.uniquetogether2rql(eschema, ut)
+                    substs['x'] = repoeschema.eid
+                    self.rqlexec(rql, substs)
 
     def _synchronize_rdef_schema(self, subjtype, rtype, objtype,
                                  syncperms=True, syncprops=True):
@@ -688,7 +702,10 @@
         for rschema in newcubes_schema.relations():
             existingschema = self.repo.schema.rschema(rschema.type)
             for (fromtype, totype) in rschema.rdefs:
-                if (fromtype, totype) in existingschema.rdefs:
+                # if rdef already exists or is infered from inheritance,
+                # don't add it
+                if (fromtype, totype) in existingschema.rdefs \
+                        or rschema.rdefs[(fromtype, totype)].infered:
                     continue
                 # check we should actually add the relation definition
                 if not (fromtype in new or totype in new or rschema in new):
@@ -929,6 +946,10 @@
         `newname` is a string giving the name of the renamed entity type
         """
         schema = self.repo.schema
+        if oldname not in schema:
+            print 'warning: entity type %s is unknown, skip renaming' % oldname
+            return
+        # if merging two existing entity types
         if newname in schema:
             assert oldname in ETYPE_NAME_MAP, \
                    '%s should be mapped to %s in ETYPE_NAME_MAP' % (oldname,
@@ -1003,6 +1024,7 @@
             # remove the old type: use rql to propagate deletion
             self.rqlexec('DELETE CWEType ET WHERE ET name %(on)s', {'on': oldname},
                          ask_confirm=False)
+        # elif simply renaming an entity type
         else:
             self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(on)s',
                          {'newname' : unicode(newname), 'on' : oldname},
--- a/server/msplanner.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/msplanner.py	Tue Jul 30 20:31:57 2013 +0200
@@ -100,8 +100,7 @@
 
 from cubicweb import server
 from cubicweb.utils import make_uid
-from cubicweb.rqlrewrite import add_types_restriction
-from cubicweb.server.utils import cleanup_solutions
+from cubicweb.rqlrewrite import add_types_restriction, cleanup_solutions
 from cubicweb.server.ssplanner import SSPlanner, OneFetchStep
 from cubicweb.server.mssteps import *
 
--- a/server/querier.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/querier.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -24,18 +24,15 @@
 
 from logilab.common.compat import any
 from rql import RQLSyntaxError, CoercionError
-from rql.stmts import Union, Select
-from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
-from rql.nodes import (Relation, VariableRef, Constant, SubQuery, Function,
-                       Exists, Not)
+from rql.stmts import Union
+from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj, Relation, Exists, Not
 from yams import BASE_TYPES
 
-from cubicweb import ValidationError, Unauthorized, QueryError, UnknownEid
+from cubicweb import ValidationError, Unauthorized, UnknownEid
 from cubicweb import Binary, server
 from cubicweb.rset import ResultSet
 
 from cubicweb.utils import QueryCache, RepeatList
-from cubicweb.server.utils import cleanup_solutions
 from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
 from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
 from cubicweb.server.edition import EditedEntity
@@ -77,12 +74,13 @@
         return session.describe(term.eval(args))[0]
 
 def check_read_access(session, rqlst, solution, args):
-    """check that the given user has credentials to access data read the
-    query
+    """Check that the given user has credentials to access data read by the
+    query and return a dict defining necessary "local checks" (i.e. rql
+    expression in read permission defined in the schema) where no group grants
+    him the permission.
 
-    return a dict defining necessary local checks (due to use of rql expression
-    in the schema), keys are variable names and values associated rql expression
-    for the associated variable with the given solution
+    Returned dictionary's keys are variable names and values the rql expressions
+    for this variable (with the given solution).
     """
     # use `term_etype` since we've to deal with rewritten constants here,
     # when used as an external source by another repository.
@@ -130,35 +128,6 @@
                 localchecks[varname] = erqlexprs
     return localchecks
 
-def add_noinvariant(noinvariant, restricted, select, nbtrees):
-    # a variable can actually be invariant if it has not been restricted for
-    # security reason or if security assertion hasn't modified the possible
-    # solutions for the query
-    if nbtrees != 1:
-        for vname in restricted:
-            try:
-                noinvariant.add(select.defined_vars[vname])
-            except KeyError:
-                # this is an alias
-                continue
-    else:
-        for vname in restricted:
-            try:
-                var = select.defined_vars[vname]
-            except KeyError:
-                # this is an alias
-                continue
-            if len(var.stinfo['possibletypes']) != 1:
-                noinvariant.add(var)
-
-def _expand_selection(terms, selected, aliases, select, newselect):
-    for term in terms:
-        for vref in term.iget_nodes(VariableRef):
-            if not vref.name in selected:
-                select.append_selected(vref)
-                colalias = newselect.get_variable(vref.name, len(aliases))
-                aliases.append(VariableRef(colalias))
-                selected.add(vref.name)
 
 # Plans #######################################################################
 
@@ -258,9 +227,8 @@
                 self.args = args
                 cached = True
             else:
-                noinvariant = set()
                 with self.session.security_enabled(read=False):
-                    self._insert_security(union, noinvariant)
+                    noinvariant = self._insert_security(union)
                 if key is not None:
                     self.session.transaction_data[key] = (union, self.args)
         else:
@@ -272,121 +240,39 @@
         if union.has_text_query:
             self.cache_key = None
 
-    def _insert_security(self, union, noinvariant):
+    def _insert_security(self, union):
+        noinvariant = set()
         for select in union.children[:]:
             for subquery in select.with_:
-                self._insert_security(subquery.query, noinvariant)
+                self._insert_security(subquery.query)
             localchecks, restricted = self._check_permissions(select)
             if any(localchecks):
-                rewrite = self.session.rql_rewriter.rewrite
-                nbtrees = len(localchecks)
-                myunion = union
-                # transform in subquery when len(localchecks)>1 and groups
-                if nbtrees > 1 and (select.orderby or select.groupby or
-                                    select.having or select.has_aggregat or
-                                    select.distinct or
-                                    select.limit or select.offset):
-                    newselect = Select()
-                    # only select variables in subqueries
-                    origselection = select.selection
-                    select.select_only_variables()
-                    select.has_aggregat = False
-                    # create subquery first so correct node are used on copy
-                    # (eg ColumnAlias instead of Variable)
-                    aliases = [VariableRef(newselect.get_variable(vref.name, i))
-                               for i, vref in enumerate(select.selection)]
-                    selected = set(vref.name for vref in aliases)
-                    # now copy original selection and groups
-                    for term in origselection:
-                        newselect.append_selected(term.copy(newselect))
-                    if select.orderby:
-                        sortterms = []
-                        for sortterm in select.orderby:
-                            sortterms.append(sortterm.copy(newselect))
-                            for fnode in sortterm.get_nodes(Function):
-                                if fnode.name == 'FTIRANK':
-                                    # we've to fetch the has_text relation as well
-                                    var = fnode.children[0].variable
-                                    rel = iter(var.stinfo['ftirels']).next()
-                                    assert not rel.ored(), 'unsupported'
-                                    newselect.add_restriction(rel.copy(newselect))
-                                    # remove relation from the orig select and
-                                    # cleanup variable stinfo
-                                    rel.parent.remove(rel)
-                                    var.stinfo['ftirels'].remove(rel)
-                                    var.stinfo['relations'].remove(rel)
-                                    # XXX not properly re-annotated after security insertion?
-                                    newvar = newselect.get_variable(var.name)
-                                    newvar.stinfo.setdefault('ftirels', set()).add(rel)
-                                    newvar.stinfo.setdefault('relations', set()).add(rel)
-                        newselect.set_orderby(sortterms)
-                        _expand_selection(select.orderby, selected, aliases, select, newselect)
-                        select.orderby = () # XXX dereference?
-                    if select.groupby:
-                        newselect.set_groupby([g.copy(newselect) for g in select.groupby])
-                        _expand_selection(select.groupby, selected, aliases, select, newselect)
-                        select.groupby = () # XXX dereference?
-                    if select.having:
-                        newselect.set_having([g.copy(newselect) for g in select.having])
-                        _expand_selection(select.having, selected, aliases, select, newselect)
-                        select.having = () # XXX dereference?
-                    if select.limit:
-                        newselect.limit = select.limit
-                        select.limit = None
-                    if select.offset:
-                        newselect.offset = select.offset
-                        select.offset = 0
-                    myunion = Union()
-                    newselect.set_with([SubQuery(aliases, myunion)], check=False)
-                    newselect.distinct = select.distinct
-                    solutions = [sol.copy() for sol in select.solutions]
-                    cleanup_solutions(newselect, solutions)
-                    newselect.set_possible_types(solutions)
-                    # if some solutions doesn't need rewriting, insert original
-                    # select as first union subquery
-                    if () in localchecks:
-                        myunion.append(select)
-                    # we're done, replace original select by the new select with
-                    # subqueries (more added in the loop below)
-                    union.replace(select, newselect)
-                elif not () in localchecks:
-                    union.remove(select)
-                for lcheckdef, lchecksolutions in localchecks.iteritems():
-                    if not lcheckdef:
-                        continue
-                    myrqlst = select.copy(solutions=lchecksolutions)
-                    myunion.append(myrqlst)
-                    # in-place rewrite + annotation / simplification
-                    lcheckdef = [({var: 'X'}, rqlexprs) for var, rqlexprs in lcheckdef]
-                    rewrite(myrqlst, lcheckdef, lchecksolutions, self.args)
-                    add_noinvariant(noinvariant, restricted, myrqlst, nbtrees)
-                if () in localchecks:
-                    select.set_possible_types(localchecks[()])
-                    add_types_restriction(self.schema, select)
-                    add_noinvariant(noinvariant, restricted, select, nbtrees)
-                self.rqlhelper.annotate(union)
+                self.session.rql_rewriter.insert_local_checks(
+                    select, self.args, localchecks, restricted, noinvariant)
+        return noinvariant
 
     def _check_permissions(self, rqlst):
-        """return a dict defining "local checks", e.g. RQLExpression defined in
-        the schema that should be inserted in the original query
-
-        solutions where a variable has a type which the user can't definitly read
-        are removed, else if the user may read it (eg if an rql expression is
-        defined for the "read" permission of the related type), the local checks
-        dict for the solution is updated
+        """Return a dict defining "local checks", i.e. RQLExpression defined in
+        the schema that should be inserted in the original query, together with
+        a set of variable names which requires some security to be inserted.
 
-        return a dict with entries for each different local check necessary,
-        with associated solutions as value. A local check is defined by a list
-        of 2-uple, with variable name as first item and the necessary rql
-        expression as second item for each variable which has to be checked.
-        So solutions which don't require local checks will be associated to
-        the empty tuple key.
+        Solutions where a variable has a type which the user can't definitly
+        read are removed, else if the user *may* read it (i.e. if an rql
+        expression is defined for the "read" permission of the related type),
+        the local checks dict is updated.
 
-        note: rqlst should not have been simplified at this point
+        The local checks dict has entries for each different local check
+        necessary, with associated solutions as value, a local check being
+        defined by a list of 2-uple (variable name, rql expressions) for each
+        variable which has to be checked. Solutions which don't require local
+        checks will be associated to the empty tuple key.
+
+        Note rqlst should not have been simplified at this point.
         """
         session = self.session
         msgs = []
-        neweids = session.transaction_data.get('neweids', ())
+        # dict(varname: eid), allowing to check rql expression for variables
+        # which have a known eid
         varkwargs = {}
         if not session.transaction_data.get('security-rqlst-cache'):
             for var in rqlst.defined_vars.itervalues():
@@ -414,20 +300,27 @@
                         rqlexprs = localcheck.pop(varname)
                     except KeyError:
                         continue
-                    if eid in neweids:
+                    # if entity has been added in the current transaction, the
+                    # user can read it whatever rql expressions are associated
+                    # to its type
+                    if session.added_in_transaction(eid):
                         continue
                     for rqlexpr in rqlexprs:
                         if rqlexpr.check(session, eid):
                             break
                     else:
                         raise Unauthorized('No read acces on %r with eid %i.' % (var, eid))
+                # mark variables protected by an rql expression
                 restricted_vars.update(localcheck)
-                localchecks.setdefault(tuple(localcheck.iteritems()), []).append(solution)
+                # turn local check into a dict key
+                localcheck = tuple(sorted(localcheck.iteritems()))
+                localchecks.setdefault(localcheck, []).append(solution)
         # raise Unautorized exception if the user can't access to any solution
         if not newsolutions:
             raise Unauthorized('\n'.join(msgs))
+        # if there is some message, solutions have been modified and must be
+        # reconsidered by the syntax treee
         if msgs:
-            # (else solutions have not been modified)
             rqlst.set_possible_types(newsolutions)
         return localchecks, restricted_vars
 
@@ -728,7 +621,7 @@
             if args:
                 # different SQL generated when some argument is None or not (IS
                 # NULL). This should be considered when computing sql cache key
-                cachekey += tuple(sorted([k for k,v in args.iteritems()
+                cachekey += tuple(sorted([k for k, v in args.iteritems()
                                           if v is None]))
         # make an execution plan
         plan = self.plan_factory(rqlst, args, session)
--- a/server/repository.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/repository.py	Tue Jul 30 20:31:57 2013 +0200
@@ -239,7 +239,7 @@
             # load schema from the file system
             if not config.creating:
                 self.warning("set fs instance'schema")
-            self.set_schema(config.load_schema())
+            self.set_schema(config.load_schema(expand_cubes=True))
         else:
             # normal start: load the instance schema from the database
             self.info('loading schema from the repository')
@@ -352,9 +352,8 @@
             except Exception as ex:
                 import traceback
                 traceback.print_exc()
-                raise Exception('Is the database initialised ? (cause: %s)' %
-                                (ex.args and ex.args[0].strip() or 'unknown')), \
-                                None, sys.exc_info()[-1]
+                raise (Exception('Is the database initialised ? (cause: %s)' % ex),
+                       None, sys.exc_info()[-1])
         return appschema
 
     def _prepare_startup(self):
@@ -794,16 +793,7 @@
                 #       Zeroed to avoid useless overhead with pyro
                 rset._rqlst = None
                 return rset
-            except (Unauthorized, RQLSyntaxError):
-                raise
-            except ValidationError as ex:
-                # need ValidationError normalization here so error may pass
-                # through pyro
-                if hasattr(ex.entity, 'eid'):
-                    ex.entity = ex.entity.eid # error raised by yams
-                    args = list(ex.args)
-                    args[0] = ex.entity
-                    ex.args = tuple(args)
+            except (ValidationError, Unauthorized, RQLSyntaxError):
                 raise
             except Exception:
                 # FIXME: check error to catch internal errors
--- a/server/serverconfig.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/serverconfig.py	Tue Jul 30 20:31:57 2013 +0200
@@ -361,7 +361,7 @@
             self.init_cubes(self.expand_cubes(origcubes))
         schema = CubicWebSchemaLoader().load(self, **kwargs)
         if expand_cubes:
-            # restaure original value
+            # restore original value
             self._cubes = origcubes
         return schema
 
--- a/server/session.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/session.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1259,6 +1259,9 @@
                     self.pending_operations[:] = processed
                     self.debug('precommit session %s done', self.id)
                 except BaseException:
+                    # save exception context, it may be clutered below by
+                    # exception in revert_* event
+                    exc_info = sys.exc_info()
                     # if error on [pre]commit:
                     #
                     # * set .failed = True on the operation causing the failure
@@ -1284,7 +1287,7 @@
                     # read-only property.
                     self.pending_operations[:] = processed + self.pending_operations
                     self.rollback(free_cnxset)
-                    raise
+                    raise exc_info[0], exc_info[1], exc_info[2]
                 self.cnxset.commit()
                 self.commit_state = 'postcommit'
                 if debug:
--- a/server/sources/__init__.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/sources/__init__.py	Tue Jul 30 20:31:57 2013 +0200
@@ -21,6 +21,7 @@
 
 import itertools
 from os.path import join, splitext
+from time import time
 from datetime import datetime, timedelta
 from logging import getLogger
 
@@ -37,7 +38,9 @@
 
 def dbg_st_search(uri, union, varmap, args, cachekey=None, prefix='rql for'):
     if server.DEBUG & server.DBG_RQL:
+        global t
         print '  %s %s source: %s' % (prefix, uri, repr(union.as_string()))
+        t = time()
         if varmap:
             print '    using varmap', varmap
         if server.DEBUG & server.DBG_MORE:
@@ -51,9 +54,10 @@
 def dbg_results(results):
     if server.DEBUG & server.DBG_RQL:
         if len(results) > 10:
-            print '  -->', results[:10], '...', len(results)
+            print '  -->', results[:10], '...', len(results),
         else:
-            print '  -->', results
+            print '  -->', results,
+        print 'time: ', time() - t
     # return true so it can be used as assertion (and so be killed by python -O)
     return True
 
--- a/server/sources/datafeed.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/sources/datafeed.py	Tue Jul 30 20:31:57 2013 +0200
@@ -78,6 +78,12 @@
           'help': ('Time before logs from datafeed imports are deleted.'),
           'group': 'datafeed-source', 'level': 2,
           }),
+        ('http-timeout',
+         {'type': 'time',
+          'default': '1min',
+          'help': ('Timeout of HTTP GET requests, when synchronizing a source.'),
+          'group': 'datafeed-source', 'level': 2,
+          }),
         )
 
     def check_config(self, source_entity):
@@ -101,6 +107,7 @@
         super(DataFeedSource, self).update_config(source_entity, typed_config)
         self.synchro_interval = timedelta(seconds=typed_config['synchronization-interval'])
         self.max_lock_lifetime = timedelta(seconds=typed_config['max-lock-lifetime'])
+        self.http_timeout = typed_config['http-timeout']
 
     def init(self, activated, source_entity):
         super(DataFeedSource, self).init(activated, source_entity)
@@ -438,7 +445,7 @@
         if url.startswith('http'):
             url = self.normalize_url(url)
             self.source.info('GET %s', url)
-            stream = _OPENER.open(url)
+            stream = _OPENER.open(url, timeout=self.http_timeout)
         elif url.startswith('file://'):
             stream = open(url[7:])
         else:
@@ -454,7 +461,8 @@
     def is_deleted(self, extid, etype, eid):
         if extid.startswith('http'):
             try:
-                _OPENER.open(self.normalize_url(extid)) # XXX HTTP HEAD request
+                _OPENER.open(self.normalize_url(extid), # XXX HTTP HEAD request
+                             timeout=self.http_timeout)
             except urllib2.HTTPError as ex:
                 if ex.code == 404:
                     return True
--- a/server/sources/rql2sql.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/sources/rql2sql.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -62,8 +62,8 @@
                        Not, Comparison, ColumnAlias, Relation, SubQuery, Exists)
 
 from cubicweb import QueryError
+from cubicweb.rqlrewrite import cleanup_solutions
 from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.utils import cleanup_solutions
 
 ColumnAlias._q_invariant = False # avoid to check for ColumnAlias / Variable
 
--- a/server/test/unittest_migractions.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/test/unittest_migractions.py	Tue Jul 30 20:31:57 2013 +0200
@@ -370,14 +370,14 @@
                                          'X description D')[0][0],
                           'title for this person')
         rinorder = [n for n, in cursor.execute(
-            'Any N ORDERBY O WHERE X is CWAttribute, X relation_type RT, RT name N,'
+            'Any N ORDERBY O,N WHERE X is CWAttribute, X relation_type RT, RT name N,'
             'X from_entity FE, FE name "Personne",'
             'X ordernum O')]
         expected = [u'nom', u'prenom', u'sexe', u'promo', u'ass', u'adel', u'titre',
-                    u'web', u'tel', u'fax', u'datenaiss', u'tzdatenaiss', u'test',
+                    u'web', u'tel', u'fax', u'datenaiss', u'test', u'tzdatenaiss',
                     u'description', u'firstname',
                     u'creation_date', u'cwuri', u'modification_date']
-        self.assertEqual(rinorder, expected)
+        self.assertEqual(expected, rinorder)
 
         # test permissions synchronization ####################################
         # new rql expr to add note entity
--- a/server/test/unittest_msplanner.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/test/unittest_msplanner.py	Tue Jul 30 20:31:57 2013 +0200
@@ -801,10 +801,8 @@
                          [{'C': 'Division', 'E': 'Note', 'D': 'Affaire', 'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire', 'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire'}])],
                        None, None, [self.system], {'E': 'table0.C0'}, []),
                       ('OneFetchStep',
-                       [('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is Basket' % ueid,
-                         [{'X': 'Basket'}]),
-                        ('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is CWUser' % ueid,
-                         [{'X': 'CWUser'}]),
+                       [('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is IN(Basket, CWUser)' % ueid,
+                         [{'X': 'Basket'}, {'X': 'CWUser'}]),
                         ('Any X WHERE X has_text "bla", X is IN(Card, Comment, Division, Email, EmailThread, File, Folder, Note, Personne, Societe, SubDivision, Tag)',
                          [{'X': 'Card'}, {'X': 'Comment'},
                           {'X': 'Division'}, {'X': 'Email'}, {'X': 'EmailThread'},
@@ -829,10 +827,8 @@
                                             [{'C': 'Division', 'E': 'Note', 'D': 'Affaire', 'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire', 'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire'}])],
                           [self.system], {'E': 'table1.C0'}, {'X': 'table0.C0'}, []),
                          ('FetchStep',
-                          [('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is Basket' % ueid,
-                            [{'X': 'Basket'}]),
-                           ('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is CWUser' % ueid,
-                            [{'X': 'CWUser'}]),
+                          [('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is IN(Basket, CWUser)' % ueid,
+                            [{'X': 'Basket'}, {'X': 'CWUser'}]),
                            ('Any X WHERE X has_text "bla", X is IN(Card, Comment, Division, Email, EmailThread, File, Folder, Note, Personne, Societe, SubDivision, Tag)',
                             [{'X': 'Card'}, {'X': 'Comment'},
                              {'X': 'Division'}, {'X': 'Email'}, {'X': 'EmailThread'},
@@ -909,12 +905,11 @@
         self._test('Any MAX(X)',
                    [('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
                      [self.cards, self.system],  None, {'E': 'table1.C0'}, []),
-                    ('FetchStep', [('Any X WHERE X is CWUser', [{'X': 'CWUser'}])],
+                    ('FetchStep', [('Any X WHERE X is IN(CWUser)', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table2.C0'}, []),
                     ('UnionFetchStep', [
                         ('FetchStep', [('Any X WHERE EXISTS(%s use_email X), X is EmailAddress' % ueid,
-      [{'X': 'EmailAddress'}]),
-                                       ('Any X WHERE EXISTS(X owned_by %s), X is Basket' % ueid, [{'X': 'Basket'}])],
+                                        [{'X': 'EmailAddress'}])],
                           [self.system], {}, {'X': 'table0.C0'}, []),
                         ('UnionFetchStep',
                          [('FetchStep', [('Any X WHERE X is IN(Card, Note, State)',
@@ -942,11 +937,17 @@
                               {'X': 'Workflow'}, {'X': 'WorkflowTransition'}])],
                            [self.system], {}, {'X': 'table0.C0'}, []),
                           ]),
-                        ('FetchStep', [('Any X WHERE EXISTS(X owned_by %s), X is CWUser' % ueid, [{'X': 'CWUser'}])],
-                         [self.system], {'X': 'table2.C0'}, {'X': 'table0.C0'}, []),
                         ('FetchStep', [('Any X WHERE (EXISTS(X owned_by %(ueid)s)) OR ((((EXISTS(D concerne C?, C owned_by %(ueid)s, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by %(ueid)s, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by %(ueid)s, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by %(ueid)s, X identity J, E is Note, J is Affaire))), X is Affaire' % {'ueid': ueid},
                                         [{'C': 'Division', 'E': 'Note', 'D': 'Affaire', 'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire', 'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire'}])],
                          [self.system], {'E': 'table1.C0'}, {'X': 'table0.C0'}, []),
+                        ('UnionFetchStep', [
+                                ('FetchStep', [('Any X WHERE EXISTS(X owned_by %s), X is Basket' % ueid,
+                                                [{'X': 'Basket'}])],
+                                 [self.system], {}, {'X': 'table0.C0'}, []),
+                                ('FetchStep', [('Any X WHERE EXISTS(X owned_by %s), X is CWUser' % ueid,
+                                                [{'X': 'CWUser'}])],
+                                 [self.system], {'X': 'table2.C0'}, {'X': 'table0.C0'}, []),
+                                ]),
                         ]),
                     ('OneFetchStep', [('Any MAX(X)', ALL_SOLS)],
                      None, None, [self.system], {'X': 'table0.C0'}, [])
@@ -969,23 +970,13 @@
                      [self.cards, self.system], None, {'X': 'table1.C0'}, []),
                     ('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
                      [self.cards, self.system],  None, {'E': 'table2.C0'}, []),
-                    ('FetchStep', [('Any X WHERE X is CWUser', [{'X': 'CWUser'}])],
+                    ('FetchStep', [('Any X WHERE X is IN(CWUser)', [{'X': 'CWUser'}])],
                      [self.ldap, self.system], None, {'X': 'table3.C0'}, []),
                     ('UnionFetchStep',
                      [('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(%s use_email X), ET is CWEType, X is EmailAddress' % ueid,
-      [{'ET': 'CWEType', 'X': 'EmailAddress'}]), ('Any ET,X WHERE X is ET, EXISTS(X owned_by %s), ET is CWEType, X is Basket' % ueid,
-                                      [{'ET': 'CWEType', 'X': 'Basket'}])],
+                                      [{'ET': 'CWEType', 'X': 'EmailAddress'}]),
+                                     ],
                        [self.system], {}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
-                      ('FetchStep', [('Any ET,X WHERE X is ET, (EXISTS(X owned_by %(ueid)s)) OR ((((EXISTS(D concerne C?, C owned_by %(ueid)s, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by %(ueid)s, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by %(ueid)s, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by %(ueid)s, X identity J, E is Note, J is Affaire))), ET is CWEType, X is Affaire' % {'ueid': ueid},
-                                      [{'C': 'Division', 'E': 'Note', 'D': 'Affaire',
-                                        'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire',
-                                        'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire',
-                                        'ET': 'CWEType'}])],
-                       [self.system], {'E': 'table2.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'},
-                       []),
-                      ('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by %s), ET is CWEType, X is CWUser' % ueid,
-                                      [{'ET': 'CWEType', 'X': 'CWUser'}])],
-                       [self.system], {'X': 'table3.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
                       # extra UnionFetchStep could be avoided but has no cost, so don't care
                       ('UnionFetchStep',
                        [('FetchStep', [('Any ET,X WHERE X is ET, ET is CWEType, X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, Comment, Division, Email, EmailPart, EmailThread, ExternalUri, File, Folder, Old, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)',
@@ -1018,6 +1009,22 @@
                             {'ET': 'CWEType', 'X': 'State'}])],
                          [self.system], {'X': 'table1.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
                         ]),
+
+                      ('FetchStep', [('Any ET,X WHERE X is ET, (EXISTS(X owned_by %(ueid)s)) OR ((((EXISTS(D concerne C?, C owned_by %(ueid)s, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by %(ueid)s, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by %(ueid)s, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by %(ueid)s, X identity J, E is Note, J is Affaire))), ET is CWEType, X is Affaire' % {'ueid': ueid},
+                                      [{'C': 'Division', 'E': 'Note', 'D': 'Affaire',
+                                        'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire',
+                                        'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire',
+                                        'ET': 'CWEType'}])],
+                       [self.system], {'E': 'table2.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'},
+                       []),
+                      ('UnionFetchStep', [
+                                ('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by %s), ET is CWEType, X is Basket' % ueid,
+                                                [{'ET': 'CWEType', 'X': 'Basket'}])],
+                                 [self.system], {}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
+                                ('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by %s), ET is CWEType, X is CWUser' % ueid,
+                                                [{'ET': 'CWEType', 'X': 'CWUser'}])],
+                                 [self.system], {'X': 'table3.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
+                                ]),
                     ]),
                     ('OneFetchStep',
                      [('Any ET,COUNT(X) GROUPBY ET ORDERBY ET', X_ET_ALL_SOLS)],
--- a/server/utils.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/server/utils.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -91,13 +91,6 @@
     return rloop(seqin, [])
 
 
-def cleanup_solutions(rqlst, solutions):
-    for sol in solutions:
-        for vname in list(sol):
-            if not (vname in rqlst.defined_vars or vname in rqlst.aliases):
-                del sol[vname]
-
-
 def eschema_eid(session, eschema):
     """get eid of the CWEType entity for the given yams type. You should use
     this because when schema has been loaded from the file-system, not from the
--- a/test/data/rewrite/schema.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/test/data/rewrite/schema.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -38,6 +38,7 @@
         'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
         'add': ('managers', 'users',)
         }
+    nom = String()
 
 
 class Division(Societe):
@@ -75,3 +76,9 @@
     object = 'Affaire'
     inlined = True
     cardinality = '?*'
+
+class responsable(RelationDefinition):
+    subject = 'Societe'
+    object = 'CWUser'
+    inlined = True
+    cardinality = '1*'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_dataimport.py	Tue Jul 30 20:31:57 2013 +0200
@@ -0,0 +1,26 @@
+from StringIO import StringIO
+from logilab.common.testlib import TestCase, unittest_main
+from cubicweb import dataimport
+class UcsvreaderTC(TestCase):
+
+    def test_empty_lines_skipped(self):
+        stream = StringIO('''a,b,c,d,
+1,2,3,4,
+,,,,
+,,,,
+''')
+        self.assertEqual([[u'a', u'b', u'c', u'd', u''],
+                          [u'1', u'2', u'3', u'4', u''],
+                          ],
+                         list(dataimport.ucsvreader(stream)))
+        stream.seek(0)
+        self.assertEqual([[u'a', u'b', u'c', u'd', u''],
+                          [u'1', u'2', u'3', u'4', u''],
+                          [u'', u'', u'', u'', u''],
+                          [u'', u'', u'', u'', u'']
+                          ],
+                         list(dataimport.ucsvreader(stream, skip_empty=False)))
+
+
+if __name__ == '__main__':
+    unittest_main()
--- a/test/unittest_entity.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/test/unittest_entity.py	Tue Jul 30 20:31:57 2013 +0200
@@ -643,8 +643,10 @@
         e.cw_attr_cache['data_format'] = 'text/html'
         e.cw_attr_cache['data_encoding'] = 'ascii'
         e._cw.transaction_data = {} # XXX req should be a session
-        self.assertEqual(e.cw_adapt_to('IFTIndexable').get_words(),
-                          {'C': ['an', 'html', 'file', 'du', 'html', 'some', 'data']})
+        words = e.cw_adapt_to('IFTIndexable').get_words()
+        words['C'].sort()
+        self.assertEqual({'C': sorted(['an', 'html', 'file', 'du', 'html', 'some', 'data'])},
+                         words)
 
 
     def test_nonregr_relation_cache(self):
--- a/test/unittest_predicates.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/test/unittest_predicates.py	Tue Jul 30 20:31:57 2013 +0200
@@ -203,6 +203,17 @@
                          select=select, filtered_variable=select.defined_vars['X'])
         self.assertEqual(score, 1)
 
+    def test_ambiguous(self):
+        # Ambiguous relations are :
+        # (Service, fabrique_par, Personne) and (Produit, fabrique_par, Usine)
+        # There used to be a crash here with a bad rdef choice in the strict
+        # checking case.
+        selector = relation_possible('fabrique_par', role='object',
+                                     target_etype='Personne', strict=True)
+        req = self.request()
+        usine = req.create_entity('Usine', lieu=u'here')
+        score = selector(None, req, rset=usine.as_rset())
+        self.assertEqual(0, score)
 
 class MatchUserGroupsTC(CubicWebTC):
     def test_owners_group(self):
--- a/test/unittest_rqlrewrite.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/test/unittest_rqlrewrite.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -23,7 +23,7 @@
 
 from cubicweb import Unauthorized, rqlrewrite
 from cubicweb.schema import RRQLExpression, ERQLExpression
-from cubicweb.devtools import repotest, TestServerConfiguration
+from cubicweb.devtools import repotest, TestServerConfiguration, BaseApptestConfiguration
 
 
 def setUpModule(*args):
@@ -46,7 +46,8 @@
 
 def eid_func_map(eid):
     return {1: 'CWUser',
-            2: 'Card'}[eid]
+            2: 'Card',
+            3: 'Affaire'}[eid]
 
 def rewrite(rqlst, snippets_map, kwargs, existingvars=None):
     class FakeVReg:
@@ -72,9 +73,7 @@
                     for snippet in exprs]
         snippets.append((dict([v]), rqlexprs))
     rqlhelper.compute_solutions(rqlst.children[0], {'eid': eid_func_map}, kwargs=kwargs)
-    solutions = rqlst.children[0].solutions
-    rewriter.rewrite(rqlst.children[0], snippets, solutions, kwargs,
-                     existingvars)
+    rewriter.rewrite(rqlst.children[0], snippets, kwargs, existingvars)
     test_vrefs(rqlst.children[0])
     return rewriter.rewritten
 
@@ -202,6 +201,17 @@
                              'WITH LA BEING (Any LA WHERE (EXISTS(A created_by B, LA documented_by A)) OR (EXISTS(E created_by B, LA concerne E)), '
                              'B eid %(D)s, LA is Affaire)')
 
+
+    def test_ambiguous_optional_same_exprs(self):
+        """See #3013535"""
+        # see test of the same name in RewriteFullTC: original problem is
+        # unreproducible here because it actually lies in
+        # RQLRewriter.insert_local_checks
+        rqlst = parse('Any A,AR,X,CD WHERE A concerne X?, A ref AR, A eid %(a)s, X creation_date CD')
+        rewrite(rqlst, {('X', 'X'): ('X created_by U',),}, {'a': 3})
+        self.assertEqual(rqlst.as_string(),
+                         u'Any A,AR,X,CD WHERE A concerne X?, A ref AR, A eid %(a)s WITH X,CD BEING (Any X,CD WHERE X creation_date CD, EXISTS(X created_by B), B eid %(A)s, X is IN(Division, Note, Societe))')
+
     def test_optional_var_inlined(self):
         c1 = ('X require_permission P')
         c2 = ('X inlined_card O, O require_permission P')
@@ -292,6 +302,7 @@
         self.assertEqual(rqlst.as_string(),
                          "Any C WHERE C in_state STATE, C is Card, "
                          "EXISTS(STATE name 'hop'), STATE is State")
+
     def test_relation_optimization_3_rhs(self):
         snippet = ('TW? subworkflow_exit X, TW name "hop"')
         rqlst = parse('WorkflowTransition C WHERE C subworkflow_exit EXIT')
@@ -308,6 +319,7 @@
         self.assertEqual(rqlst.as_string(),
                          "Any C WHERE C in_state STATE?, C is Card, "
                          "EXISTS(C in_state A, A name 'hop', A is State), STATE is State")
+
     def test_relation_non_optimization_1_rhs(self):
         snippet = ('TW subworkflow_exit X, TW name "hop"')
         rqlst = parse('SubWorkflowExitPoint EXIT WHERE C? subworkflow_exit EXIT')
@@ -317,6 +329,21 @@
                          "EXISTS(A subworkflow_exit EXIT, A name 'hop', A is WorkflowTransition), "
                          "C is WorkflowTransition")
 
+    def test_relation_non_optimization_2(self):
+        """See #3024730"""
+        # 'X inlined_note N' must not be shared with 'C inlined_note N'
+        # previously inserted, else this may introduce duplicated results, as N
+        # will then be shared by multiple EXISTS and so at SQL generation time,
+        # the table will be in the FROM clause of the outermost query
+        rqlst = parse('Any A,C WHERE A inlined_card C')
+        rewrite(rqlst, {('A', 'X'): ('X inlined_card C, C inlined_note N, N owned_by U',),
+                        ('C', 'X'): ('X inlined_note N, N owned_by U',)}, {})
+        self.assertEqual(rqlst.as_string(),
+                         'Any A,C WHERE A inlined_card C, D eid %(E)s, '
+                         'EXISTS(C inlined_note B, B owned_by D, B is Note), '
+                         'EXISTS(C inlined_note F, F owned_by D, F is Note), '
+                         'A is Affaire, C is Card')
+
     def test_unsupported_constraint_1(self):
         # CWUser doesn't have require_permission
         trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"')
@@ -459,5 +486,55 @@
         rqlst = parse('Any A, R WHERE A ref R, S is Affaire')
         rewrite(rqlst, {('A', 'X'): (c_ok, c_bad)}, {})
 
+
+from cubicweb.devtools.testlib import CubicWebTC
+from logilab.common.decorators import classproperty
+
+class RewriteFullTC(CubicWebTC):
+    @classproperty
+    def config(cls):
+        return BaseApptestConfiguration(apphome=cls.datapath('rewrite'))
+
+    def process(self, rql, args=None):
+        if args is None:
+            args = {}
+        querier = self.repo.querier
+        union = querier.parse(rql)
+        querier.solutions(self.session, union, args)
+        querier._annotate(union)
+        plan = querier.plan_factory(union, args, self.session)
+        plan.preprocess(union)
+        return union
+
+    def test_ambiguous_optional_same_exprs(self):
+        """See #3013535"""
+        edef1 = self.schema['Societe']
+        edef2 = self.schema['Division']
+        edef3 = self.schema['Note']
+        with self.temporary_permissions((edef1, {'read': (ERQLExpression('X owned_by U'),)}),
+                                        (edef2, {'read': (ERQLExpression('X owned_by U'),)}),
+                                        (edef3, {'read': (ERQLExpression('X owned_by U'),)})):
+            union = self.process('Any A,AR,X,CD WHERE A concerne X?, A ref AR, X creation_date CD')
+            self.assertEqual('Any A,AR,X,CD WHERE A concerne X?, A ref AR, A is Affaire '
+                             'WITH X,CD BEING (Any X,CD WHERE X creation_date CD, '
+                             'EXISTS(X owned_by %(A)s), X is IN(Division, Note, Societe))',
+                             union.as_string())
+
+
+    def test_xxxx(self):
+        edef1 = self.schema['Societe']
+        edef2 = self.schema['Division']
+        read_expr = ERQLExpression('X responsable E, U has_read_permission E')
+        with self.temporary_permissions((edef1, {'read': (read_expr,)}),
+                                        (edef2, {'read': (read_expr,)})):
+            union = self.process('Any X,AA,AC,AD ORDERBY AD DESC '
+                                 'WHERE X responsable E, X nom AA, '
+                                 'X responsable AC?, AC modification_date AD')
+            self.assertEqual('Any X,AA,AC,AD ORDERBY AD DESC '
+                             'WHERE X responsable E, X nom AA, '
+                             'X responsable AC?, AC modification_date AD, '
+                             'AC is CWUser, E is CWUser, X is IN(Division, Societe)',
+                             union.as_string())
+
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_schema.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/test/unittest_schema.py	Tue Jul 30 20:31:57 2013 +0200
@@ -216,6 +216,8 @@
                               'value',
 
                               'wf_info_for', 'wikiid', 'workflow_of', 'tr_count']
+        if config.cube_version('file') >= (1, 14, 0):
+            expected_relations.append('data_sha1hex')
 
         self.assertListEqual(sorted(expected_relations), relations)
 
--- a/web/application.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/application.py	Tue Jul 30 20:31:57 2013 +0200
@@ -343,17 +343,17 @@
 
 
     def main_handle_request(self, req, path):
-        """Process and http request
+        """Process an http request
 
         Arguments are:
         - a Request object
         - path of the request object
 
-        It return the content of the http response. HTTP header and status are
-        are set on the Request Object.
+        It returns the content of the http response. HTTP header and status are
+        set on the Request object.
         """
         if not isinstance(req, CubicWebRequestBase):
-            warn('[3.15] Application entry poin arguments are now (req, path) '
+            warn('[3.15] Application entry point arguments are now (req, path) '
                  'not (path, req)', DeprecationWarning, 2)
             req, path = path, req
         if req.authmode == 'http':
@@ -399,7 +399,7 @@
         # Wrong, absent or Reseted credential
         except AuthenticationError:
             # If there is an https url configured and
-            # the request do not used https, redirect to login form
+            # the request does not use https, redirect to login form
             https_url = self.vreg.config['https-url']
             if https_url and req.base_url() != https_url:
                 req.status_out = httplib.SEE_OTHER
--- a/web/data/cubicweb.facets.js	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/data/cubicweb.facets.js	Tue Jul 30 20:31:57 2013 +0200
@@ -1,14 +1,13 @@
 /** filter form, aka facets, javascript functions
  *
  *  :organization: Logilab
- *  :copyright: 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ *  :copyright: 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  *  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
  */
 
-var SELECTED_IMG = baseuri() + "data/black-check.png";
-var UNSELECTED_IMG = baseuri() + "data/no-check-no-border.png";
-var UNSELECTED_BORDER_IMG = baseuri() + "data/black-uncheck.png";
-
+var SELECTED_IMG = DATA_URL + 'black-check.png';
+var UNSELECTED_IMG = DATA_URL + 'no-check-no-border.png';
+var UNSELECTED_BORDER_IMG = DATA_URL + 'black-uncheck.png';
 
 function copyParam(origparams, newparams, param) {
     var index = $.inArray(param, origparams[0]);
--- a/web/formfields.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/formfields.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -1153,12 +1153,19 @@
         elif not isinstance(values, list):
             values = (values,)
         eids = set()
+        rschema = form._cw.vreg.schema.rschema(self.name)
         for eid in values:
             if not eid or eid == INTERNAL_FIELD_VALUE:
                 continue
             typed_eid = form.actual_eid(eid)
+            # if entity doesn't exist yet
             if typed_eid is None:
-                form._cw.data['pendingfields'].add( (form, self) )
+                # inlined relations of to-be-created **subject entities** have
+                # to be handled separatly
+                if self.role == 'object' and rschema.inlined:
+                    form._cw.data['pending_inlined'][eid].add( (form, self) )
+                else:
+                    form._cw.data['pending_others'].add( (form, self) )
                 return None
             eids.add(typed_eid)
         return eids
--- a/web/test/data/schema.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/test/data/schema.py	Tue Jul 30 20:31:57 2013 +0200
@@ -24,7 +24,8 @@
 from yams.constraints import IntervalBoundConstraint
 
 class Salesterm(EntityType):
-    described_by_test = SubjectRelation('File', cardinality='1*', composite='subject')
+    described_by_test = SubjectRelation('File', cardinality='1*',
+                                        composite='subject', inlined=True)
     amount = Int(constraints=[IntervalBoundConstraint(0, 100)])
     reason = String(maxsize=20, vocabulary=[u'canceled', u'sold'])
 
--- a/web/test/unittest_views_actions.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/test/unittest_views_actions.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,21 +15,32 @@
 #
 # 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 logilab.common.testlib import unittest_main
 
 from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.web.views import actions, uicfg
 
 class ActionsTC(CubicWebTC):
     def test_view_action(self):
-        req = self.request(__message='bla bla bla', vid='rss', rql='CWUser X')
+        req = self.request(vid='rss', rql='CWUser X')
         rset = self.execute('CWUser X')
         actions = self.vreg['actions'].poss_visible_objects(req, rset=rset)
         vaction = [action for action in actions if action.__regid__ == 'view'][0]
         self.assertEqual(vaction.url(), 'http://testing.fr/cubicweb/view?rql=CWUser%20X')
 
+    def test_has_editable_relations(self):
+        """ensure has_editable_relation predicate used by ModifyAction
+        return positive score if there is only some inlined forms
+        """
+        use_email = self.schema['use_email'].rdefs['CWUser', 'EmailAddress']
+        with self.temporary_permissions((use_email, {'add': ('guests',)}),
+                                        ):
+            with self.login('anon'):
+                req = self.request()
+                predicate = actions.has_editable_relation()
+                self.assertEqual(predicate(None, req, rset=req.user.as_rset()),
+                                 1)
 
 if __name__ == '__main__':
     unittest_main()
--- a/web/test/unittest_views_basecontrollers.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/test/unittest_views_basecontrollers.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -39,6 +39,8 @@
 from cubicweb.web.views.basecontrollers import JSonController, xhtmlize, jsonize
 from cubicweb.web.views.ajaxcontroller import ajaxfunc, AjaxFunction
 import cubicweb.transaction as tx
+from cubicweb.server.hook import Hook, Operation
+from cubicweb.predicates import is_instance
 
 u = unicode
 
@@ -171,6 +173,54 @@
         email = e.use_email[0]
         self.assertEqual(email.address, 'dima@logilab.fr')
 
+    def test_create_mandatory_inlined(self):
+        req = self.request()
+        req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
+
+                    '__type:X': 'Salesterm',
+                    '_cw_entity_fields:X': '',
+
+                    '__type:Y': 'File',
+                    '_cw_entity_fields:Y': 'data-subject,described_by_test-object',
+                    'data-subject:Y': (u'coucou.txt', Binary('coucou')),
+                    'described_by_test-object:Y': 'X',
+                    }
+        path, params = self.expect_redirect_handle_request(req, 'edit')
+        self.assertTrue(path.startswith('salesterm/'), path)
+        eid = path.split('/')[1]
+        salesterm = req.entity_from_eid(eid)
+        # The NOT NULL constraint of mandatory relation implies that the File
+        # must be created before the Salesterm, otherwise Salesterm insertion
+        # will fail.
+        # NOTE: sqlite does have NOT NULL constraint, unlike Postgres so the
+        # insertion does not fail and we have to check dumbly that File is
+        # created before.
+        self.assertGreater(salesterm.eid, salesterm.described_by_test[0].eid)
+
+    def test_create_mandatory_inlined2(self):
+        req = self.request()
+        req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
+
+                    '__type:X': 'Salesterm',
+                    '_cw_entity_fields:X': 'described_by_test-subject',
+                    'described_by_test-subject:X': 'Y',
+
+                    '__type:Y': 'File',
+                    '_cw_entity_fields:Y': 'data-subject',
+                    'data-subject:Y': (u'coucou.txt', Binary('coucou')),
+                    }
+        path, params = self.expect_redirect_handle_request(req, 'edit')
+        self.assertTrue(path.startswith('salesterm/'), path)
+        eid = path.split('/')[1]
+        salesterm = req.entity_from_eid(eid)
+        # The NOT NULL constraint of mandatory relation implies that the File
+        # must be created before the Salesterm, otherwise Salesterm insertion
+        # will fail.
+        # NOTE: sqlite does have NOT NULL constraint, unlike Postgres so the
+        # insertion does not fail and we have to check dumbly that File is
+        # created before.
+        self.assertGreater(salesterm.eid, salesterm.described_by_test[0].eid)
+
     def test_edit_multiple_linked(self):
         req = self.request()
         peid = u(self.create_user(req, 'adim').eid)
@@ -263,6 +313,7 @@
             self.ctrl_publish(req)
         cm.exception.translate(unicode)
         self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'})
+
         req = self.request(rollbackfirst=True)
         req.form = {'eid': ['X'],
                     '__type:X': 'Salesterm',
@@ -276,6 +327,67 @@
         e = self.execute('Salesterm X').get_entity(0, 0)
         self.assertEqual(e.amount, 10)
 
+    def test_interval_bound_constraint_validateform(self):
+        """Test the FormValidatorController controller on entity with
+        constrained attributes"""
+        feid = self.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',
+                            {'data': Binary('yo')})[0][0]
+        seid = self.request().create_entity('Salesterm', amount=0, described_by_test=feid).eid
+        self.commit()
+
+        # ensure a value that violate a constraint is properly detected
+        req = self.request(rollbackfirst=True)
+        req.form = {'eid': [unicode(seid)],
+                    '__type:%s'%seid: 'Salesterm',
+                    '_cw_entity_fields:%s'%seid: 'amount-subject',
+                    'amount-subject:%s'%seid: u'-10',
+                }
+        self.assertEqual('''<script type="text/javascript">
+ window.parent.handleFormValidationResponse('entityForm', null, null, [false, [%s, {"amount-subject": "value -10 must be >= 0"}], null], null);
+</script>'''%seid, self.ctrl_publish(req, 'validateform'))
+
+        # ensure a value that comply a constraint is properly processed
+        req = self.request(rollbackfirst=True)
+        req.form = {'eid': [unicode(seid)],
+                    '__type:%s'%seid: 'Salesterm',
+                    '_cw_entity_fields:%s'%seid: 'amount-subject',
+                    'amount-subject:%s'%seid: u'20',
+                }
+        self.assertEqual('''<script type="text/javascript">
+ window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
+</script>''', self.ctrl_publish(req, 'validateform'))
+        self.assertEqual(20, self.execute('Any V WHERE X amount V, X eid %(eid)s', {'eid': seid})[0][0])
+
+        req = self.request(rollbackfirst=True)
+        req.form = {'eid': ['X'],
+                    '__type:X': 'Salesterm',
+                    '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
+                    'amount-subject:X': u'0',
+                    'described_by_test-subject:X': u(feid),
+                }
+
+        # ensure a value that is modified in an operation on a modify
+        # hook works as it should (see
+        # https://www.cubicweb.org/ticket/2509729 )
+        class MyOperation(Operation):
+            def precommit_event(self):
+                self.entity.cw_set(amount=-10)
+        class ValidationErrorInOpAfterHook(Hook):
+            __regid__ = 'valerror-op-after-hook'
+            __select__ = Hook.__select__ & is_instance('Salesterm')
+            events = ('after_add_entity',)
+            def __call__(self):
+                MyOperation(self._cw, entity=self.entity)
+
+        with self.temporary_appobjects(ValidationErrorInOpAfterHook):
+            self.assertEqual('''<script type="text/javascript">
+ window.parent.handleFormValidationResponse('entityForm', null, null, [false, ["X", {"amount-subject": "value -10 must be >= 0"}], null], null);
+</script>''', self.ctrl_publish(req, 'validateform'))
+
+        self.assertEqual('''<script type="text/javascript">
+ window.parent.handleFormValidationResponse('entityForm', null, null, [true, "http://testing.fr/cubicweb/view", null], null);
+</script>''', self.ctrl_publish(req, 'validateform'))
+
     def test_req_pending_insert(self):
         """make sure req's pending insertions are taken into account"""
         tmpgroup = self.request().create_entity('CWGroup', name=u"test")
@@ -288,7 +400,6 @@
         self.assertItemsEqual(usergroups, ['managers', 'test'])
         self.assertEqual(get_pending_inserts(req), [])
 
-
     def test_req_pending_delete(self):
         """make sure req's pending deletions are taken into account"""
         user = self.user()
--- a/web/uihelper.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/uihelper.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2011-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2011-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -66,8 +66,8 @@
 
 for funcname, tag in backward_compat_funcs:
     msg = ('[3.16] uihelper.%(name)s is deprecated, please use '
-           'web.uicfg.%(classname)s.%(name)s' % dict(
-               name=funcname, classname=tag.__class__.__name__))
+           'web.views.uicfg.%(rtagid)s.%(name)s' % dict(
+               name=funcname, rtagid=tag.__regid__))
     globals()[funcname] = deprecated(msg)(getattr(tag, funcname))
 
 
--- a/web/views/actions.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/views/actions.py	Tue Jul 30 20:31:57 2013 +0200
@@ -50,8 +50,9 @@
                                                entity=entity, mainform=False)
         for dummy in form.editable_relations():
             return 1
-        editableattrs = form.editable_attributes(strict=True)
-        for rschema, role in editableattrs:
+        for dummy in form.inlined_relations():
+            return 1
+        for dummy in form.editable_attributes(strict=True):
             return 1
         return 0
 
--- a/web/views/authentication.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/views/authentication.py	Tue Jul 30 20:31:57 2013 +0200
@@ -22,6 +22,7 @@
 from threading import Lock
 
 from logilab.common.decorators import clear_cache
+from logilab.common.deprecation import class_renamed
 
 from cubicweb import AuthenticationError, BadConnectionId
 from cubicweb.view import Component
@@ -32,18 +33,18 @@
 class NoAuthInfo(Exception): pass
 
 
-class WebAuthInfoRetreiver(Component):
+class WebAuthInfoRetriever(Component):
     __registry__ = 'webauth'
     order = None
     __abstract__ = True
 
     def authentication_information(self, req):
-        """retreive authentication information from the given request, raise
+        """retrieve authentication information from the given request, raise
         NoAuthInfo if expected information is not found.
         """
         raise NotImplementedError()
 
-    def authenticated(self, retreiver, req, cnx, login, authinfo):
+    def authenticated(self, retriever, req, cnx, login, authinfo):
         """callback when return authentication information have opened a
         repository connection successfully. Take care req has no session
         attached yet, hence req.execute isn't available.
@@ -66,12 +67,14 @@
     def cleanup_authentication_information(self, req):
         """called when the retriever has returned some authentication
         information but we get an authentication error when using them, so it
-        get a chance to cleanup things (e.g. remove cookie)
+        get a chance to clean things up (e.g. remove cookie)
         """
         pass
 
+WebAuthInfoRetreiver = class_renamed('WebAuthInfoRetreiver', WebAuthInfoRetriever)
 
-class LoginPasswordRetreiver(WebAuthInfoRetreiver):
+
+class LoginPasswordRetriever(WebAuthInfoRetriever):
     __regid__ = 'loginpwdauth'
     order = 10
 
@@ -90,6 +93,9 @@
     def revalidate_login(self, req):
         return req.get_authorization()[0]
 
+LoginPasswordRetreiver = class_renamed('LoginPasswordRetreiver', LoginPasswordRetriever)
+
+
 class RepositoryAuthenticationManager(AbstractAuthenticationManager):
     """authenticate user associated to a request and check session validity"""
 
--- a/web/views/autoform.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/views/autoform.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -145,8 +145,11 @@
 class InlinedFormField(ff.Field):
     def __init__(self, view=None, **kwargs):
         kwargs.setdefault('label', None)
+        # don't add eidparam=True since this field doesn't actually hold the
+        # relation value (the subform does) hence should not be listed in
+        # _cw_entity_fields
         super(InlinedFormField, self).__init__(name=view.rtype, role=view.role,
-                                               eidparam=True, **kwargs)
+                                               **kwargs)
         self.view = view
 
     def render(self, form, renderer):
--- a/web/views/basetemplates.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/views/basetemplates.py	Tue Jul 30 20:31:57 2013 +0200
@@ -162,6 +162,7 @@
         self.write_doctype()
         # explictly close the <base> tag to avoid IE 6 bugs while browsing DOM
         self._cw.html_headers.define_var('BASE_URL', self._cw.base_url())
+        self._cw.html_headers.define_var('DATA_URL', self._cw.datadir_url)
         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
           % (content_type, self._cw.encoding))
         w(u'\n'.join(additional_headers) + u'\n')
--- a/web/views/editcontroller.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/views/editcontroller.py	Tue Jul 30 20:31:57 2013 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -20,8 +20,10 @@
 __docformat__ = "restructuredtext en"
 
 from warnings import warn
+from collections import defaultdict
 
 from logilab.common.deprecation import deprecated
+from logilab.common.graph import ordered_nodes
 
 from rql.utils import rqlvar_maker
 
@@ -101,6 +103,15 @@
         self.kwargs[var] = eid
         return rql
 
+    def set_attribute(self, attr, value):
+        self.kwargs[attr] = value
+        self.edited.append('X %s %%(%s)s' % (attr, attr))
+
+    def set_inlined(self, relation, value):
+        self.kwargs[relation] = value
+        self.edited.append('X %s %s' % (relation, relation.upper()))
+        self.restrictions.append('%s eid %%(%s)s' % (relation.upper(), relation))
+
 
 class EditController(basecontrollers.ViewController):
     __regid__ = 'edit'
@@ -120,6 +131,49 @@
         self._default_publish()
         self.reset()
 
+    def _ordered_formparams(self):
+        """ Return form parameters dictionaries for each edited entity.
+
+        We ensure that entities can be created in this order accounting for
+        mandatory inlined relations.
+        """
+        req = self._cw
+        graph = {}
+        get_rschema = self._cw.vreg.schema.rschema
+        # minparams = 2, because at least __type and eid are needed
+        values_by_eid = dict((eid, req.extract_entity_params(eid, minparams=2))
+                             for eid in req.edited_eids())
+        # iterate over all the edited entities
+        for eid, values in values_by_eid.iteritems():
+            # add eid to the dependency graph
+            graph.setdefault(eid, set())
+            # search entity's edited fields for mandatory inlined relation
+            for param in values['_cw_entity_fields'].split(','):
+                try:
+                    rtype, role = param.split('-')
+                except ValueError:
+                    # e.g. param='__type'
+                    continue
+                rschema = get_rschema(rtype)
+                if rschema.inlined:
+                    for target in rschema.targets(values['__type'], role):
+                        rdef = rschema.role_rdef(values['__type'], target, role)
+                        # if cardinality is 1 and if the target entity is being
+                        # simultaneously edited, the current entity must be
+                        # created before the target one
+                        if rdef.cardinality[0] == '1':
+                            target_eid = values[param]
+                            if target_eid in values_by_eid:
+                                # add dependency from the target entity to the
+                                # current one
+                                if role == 'object':
+                                    graph.setdefault(target_eid, set()).add(eid)
+                                else:
+                                    graph.setdefault(eid, set()).add(target_eid)
+                                break
+        for eid in reversed(ordered_nodes(graph)):
+            yield values_by_eid[eid]
+
     def _default_publish(self):
         req = self._cw
         self.errors = []
@@ -130,22 +184,27 @@
             req.set_shared_data('__maineid', form['__maineid'], txdata=True)
         # no specific action, generic edition
         self._to_create = req.data['eidmap'] = {}
-        self._pending_fields = req.data['pendingfields'] = set()
+        # those two data variables are used to handle relation from/to entities
+        # which doesn't exist at time where the entity is edited and that
+        # deserves special treatment
+        req.data['pending_inlined'] = defaultdict(set)
+        req.data['pending_others'] = set()
         try:
-            for eid in req.edited_eids():
-                # __type and eid
-                formparams = req.extract_entity_params(eid, minparams=2)
+            for formparams in self._ordered_formparams():
                 eid = self.edit_entity(formparams)
         except (RequestError, NothingToEdit) as ex:
             if '__linkto' in req.form and 'eid' in req.form:
                 self.execute_linkto()
             elif not ('__delete' in req.form or '__insert' in req.form):
                 raise ValidationError(None, {None: unicode(ex)})
-        # handle relations in newly created entities
-        if self._pending_fields:
-            for form, field in self._pending_fields:
-                self.handle_formfield(form, field)
-        # execute rql to set all relations
+        # all pending inlined relations to newly created entities have been
+        # treated now (pop to ensure there are no attempt to add new ones)
+        pending_inlined = req.data.pop('pending_inlined')
+        assert not pending_inlined, pending_inlined
+        # handle all other remaining relations now
+        for form_, field in req.data.pop('pending_others'):
+            self.handle_formfield(form_, field)
+        # then execute rql to set all relations
         for querydef in self.relations_rql:
             self._cw.execute(*querydef)
         # XXX this processes *all* pending operations of *all* entities
@@ -176,10 +235,11 @@
 
     def edit_entity(self, formparams, multiple=False):
         """edit / create / copy an entity and return its eid"""
+        req = self._cw
         etype = formparams['__type']
-        entity = self._cw.vreg['etypes'].etype_class(etype)(self._cw)
+        entity = req.vreg['etypes'].etype_class(etype)(req)
         entity.eid = valerror_eid(formparams['eid'])
-        is_main_entity = self._cw.form.get('__maineid') == formparams['eid']
+        is_main_entity = req.form.get('__maineid') == formparams['eid']
         # let a chance to do some entity specific stuff
         entity.cw_adapt_to('IEditControl').pre_web_edit()
         # create a rql query from parameters
@@ -188,12 +248,12 @@
         # this will generate less rql queries and might be useful in
         # a few dark corners
         if is_main_entity:
-            formid = self._cw.form.get('__form_id', 'edition')
+            formid = req.form.get('__form_id', 'edition')
         else:
             # XXX inlined forms formid should be saved in a different formparams entry
             # inbetween, use cubicweb standard formid for inlined forms
             formid = 'edition'
-        form = self._cw.vreg['forms'].select(formid, self._cw, entity=entity)
+        form = req.vreg['forms'].select(formid, req, entity=entity)
         eid = form.actual_eid(entity.eid)
         try:
             editedfields = formparams['_cw_entity_fields']
@@ -203,10 +263,14 @@
                 warn('[3.13] _cw_edited_fields has been renamed _cw_entity_fields',
                      DeprecationWarning)
             except KeyError:
-                raise RequestError(self._cw._('no edited fields specified for entity %s' % entity.eid))
+                raise RequestError(req._('no edited fields specified for entity %s' % entity.eid))
         form.formvalues = {} # init fields value cache
         for field in form.iter_modified_fields(editedfields, entity):
             self.handle_formfield(form, field, rqlquery)
+        # if there are some inlined field which were waiting for this entity's
+        # creation, add relevant data to the rqlquery
+        for form_, field in req.data['pending_inlined'].pop(entity.eid, ()):
+            rqlquery.set_inlined(field.name, form_.edited_entity.eid)
         if self.errors:
             errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
             raise ValidationError(valerror_eid(entity.eid), errors)
@@ -218,8 +282,8 @@
             self.notify_edited(entity)
         if '__delete' in formparams:
             # XXX deprecate?
-            todelete = self._cw.list_form_param('__delete', formparams, pop=True)
-            autoform.delete_relations(self._cw, todelete)
+            todelete = req.list_form_param('__delete', formparams, pop=True)
+            autoform.delete_relations(req, todelete)
         if '__cloned_eid' in formparams:
             entity.copy_relations(int(formparams['__cloned_eid']))
         if is_main_entity: # only execute linkto for the main entity
@@ -237,8 +301,7 @@
                     continue
                 rschema = self._cw.vreg.schema.rschema(field.name)
                 if rschema.final:
-                    rqlquery.kwargs[field.name] = value
-                    rqlquery.edited.append('X %s %%(%s)s' % (rschema, rschema))
+                    rqlquery.set_attribute(field.name, value)
                 else:
                     if form.edited_entity.has_eid():
                         origvalues = set(entity.eid for entity in form.edited_entity.related(field.name, field.role, entities=True))
@@ -251,19 +314,15 @@
                     elif form.edited_entity.has_eid():
                         self.handle_relation(form, field, value, origvalues)
                     else:
-                        self._pending_fields.add( (form, field) )
-
+                        form._cw.data['pending_others'].add( (form, field) )
         except ProcessFormError as exc:
             self.errors.append((field, exc))
 
     def handle_inlined_relation(self, form, field, values, origvalues, rqlquery):
         """handle edition for the (rschema, x) relation of the given entity
         """
-        attr = field.name
         if values:
-            rqlquery.kwargs[attr] = iter(values).next()
-            rqlquery.edited.append('X %s %s' % (attr, attr.upper()))
-            rqlquery.restrictions.append('%s eid %%(%s)s' % (attr.upper(), attr))
+            rqlquery.set_inlined(field.name, iter(values).next())
         elif form.edited_entity.has_eid():
             self.handle_relation(form, field, values, origvalues)
 
--- a/web/views/staticcontrollers.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/views/staticcontrollers.py	Tue Jul 30 20:31:57 2013 +0200
@@ -27,6 +27,7 @@
 import os.path as osp
 import hashlib
 import mimetypes
+import threading
 from time import mktime
 from datetime import datetime, timedelta
 from logging import getLogger
@@ -105,6 +106,7 @@
         self._resources = {}
         self.config = config
         self.logger = getLogger('cubicweb.web')
+        self.lock = threading.Lock()
 
     def _resource(self, path):
         """get the resouce"""
@@ -143,21 +145,32 @@
     def concat_cached_filepath(self, paths):
         filepath = self.build_filepath(paths)
         if not self._up_to_date(filepath, paths):
-            with open(filepath, 'wb') as f:
-                for path in paths:
-                    dirpath, rid = self._resource(path)
-                    if rid is None:
-                        # In production mode log an error, do not return a 404
-                        # XXX the erroneous content is cached anyway
-                        self.logger.error('concatenated data url error: %r file '
-                                          'does not exist', path)
-                        if self.config.debugmode:
-                            raise NotFound(path)
-                    else:
-                        with open(osp.join(dirpath, rid), 'rb') as source:
-                            for line in source:
-                                f.write(line)
-                        f.write('\n')
+            tmpfile = filepath + '.tmp'
+            try:
+                with self.lock:
+                    if self._up_to_date(filepath, paths):
+                        # first check could have raced with some other thread
+                        # updating the file
+                        return filepath
+                    with open(tmpfile, 'wb') as f:
+                        for path in paths:
+                            dirpath, rid = self._resource(path)
+                            if rid is None:
+                                # In production mode log an error, do not return a 404
+                                # XXX the erroneous content is cached anyway
+                                self.logger.error('concatenated data url error: %r file '
+                                                  'does not exist', path)
+                                if self.config.debugmode:
+                                    raise NotFound(path)
+                            else:
+                                with open(osp.join(dirpath, rid), 'rb') as source:
+                                    for line in source:
+                                        f.write(line)
+                                f.write('\n')
+                    os.rename(tmpfile, filepath)
+            except:
+                os.remove(tmpfile)
+                raise
         return filepath
 
 
--- a/web/views/tableview.py	Thu Jul 04 09:26:59 2013 +0200
+++ b/web/views/tableview.py	Tue Jul 30 20:31:57 2013 +0200
@@ -201,10 +201,11 @@
             facetsform.render(w, vid=self.view.__regid__, cssclass=cssclass,
                               divid=self.view.domid)
         actions = []
-        if self.add_view_actions:
-            actions = self.view.table_actions()
-        if self.display_filter and self.hide_filter and (facetsform or not generate_form):
-            actions += self.show_hide_filter_actions(not generate_form)
+        if self.display_actions:
+            if self.add_view_actions:
+                actions = self.view.table_actions()
+            if self.display_filter and self.hide_filter and (facetsform or not generate_form):
+                actions += self.show_hide_filter_actions(not generate_form)
         self.render_table(w, actions, self.view.paginable)
         if facetsform and self.display_filter == 'bottom':
             cssclass = u'hidden' if self.hide_filter else u''