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