--- a/.hgtags Tue Sep 29 12:09:04 2015 +0200
+++ b/.hgtags Fri Oct 09 17:52:14 2015 +0200
@@ -469,6 +469,9 @@
5932de3d50bf023544c8f54b47898e4db35eac7c 3.19.12
5932de3d50bf023544c8f54b47898e4db35eac7c debian/3.19.12-1
5932de3d50bf023544c8f54b47898e4db35eac7c centos/3.19.12-1
+f933a38d7ab5fc6f2ad593fe1cf9985ce9d7e873 3.19.13
+f933a38d7ab5fc6f2ad593fe1cf9985ce9d7e873 debian/3.19.13-1
+f933a38d7ab5fc6f2ad593fe1cf9985ce9d7e873 centos/3.19.13-1
7e6b7739afe6128589ad51b0318decb767cbae36 3.20.0
7e6b7739afe6128589ad51b0318decb767cbae36 debian/3.20.0-1
7e6b7739afe6128589ad51b0318decb767cbae36 centos/3.20.0-1
@@ -499,6 +502,9 @@
d477e64475821c21632878062bf68d142252ffc2 3.20.9
d477e64475821c21632878062bf68d142252ffc2 debian/3.20.9-1
d477e64475821c21632878062bf68d142252ffc2 centos/3.20.9-1
+8f82e95239625d153a9f1de6e79820d96d9efe8a 3.20.10
+8f82e95239625d153a9f1de6e79820d96d9efe8a debian/3.20.10-1
+8f82e95239625d153a9f1de6e79820d96d9efe8a centos/3.20.10-1
887c6eef807781560adcd4ecd2dea9011f5a6681 3.21.0
887c6eef807781560adcd4ecd2dea9011f5a6681 debian/3.21.0-1
887c6eef807781560adcd4ecd2dea9011f5a6681 centos/3.21.0-1
--- a/debian/changelog Tue Sep 29 12:09:04 2015 +0200
+++ b/debian/changelog Fri Oct 09 17:52:14 2015 +0200
@@ -10,6 +10,12 @@
-- Julien Cristau <julien.cristau@logilab.fr> Fri, 10 Jul 2015 17:04:11 +0200
+cubicweb (3.20.10-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Rémi Cardona <remi.cardona@logilab.fr> Thu, 08 Oct 2015 18:47:24 +0200
+
cubicweb (3.20.9-1) unstable; urgency=low
* New upstream release.
@@ -76,6 +82,12 @@
-- Julien Cristau <julien.cristau@logilab.fr> Tue, 06 Jan 2015 18:11:03 +0100
+cubicweb (3.19.13-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Rémi Cardona <remi.cardona@logilab.fr> Tue, 06 Oct 2015 18:31:33 +0200
+
cubicweb (3.19.12-1) unstable; urgency=low
* New upstream release
--- a/devtools/test/unittest_testlib.py Tue Sep 29 12:09:04 2015 +0200
+++ b/devtools/test/unittest_testlib.py Fri Oct 09 17:52:14 2015 +0200
@@ -196,6 +196,18 @@
self.assertTrue(rdef.permissions['add'])
self.assertTrue(rdef.permissions['read'], ())
+ def test_temporary_permissions_rdef_with_exception(self):
+ rdef = self.schema['CWUser'].rdef('in_group')
+ try:
+ with self.temporary_permissions((rdef, {'read': ()})):
+ self.assertEqual(rdef.permissions['read'], ())
+ self.assertTrue(rdef.permissions['add'])
+ raise ValueError('goto')
+ except ValueError:
+ self.assertTrue(rdef.permissions['read'], ())
+ else:
+ self.fail('exception was caught unexpectedly')
+
def test_temporary_appobjects_registered(self):
class AnAppobject(object):
--- a/devtools/testlib.py Tue Sep 29 12:09:04 2015 +0200
+++ b/devtools/testlib.py Fri Oct 09 17:52:14 2015 +0200
@@ -526,16 +526,18 @@
origperms = erschema.permissions[action]
erschema.set_action_permissions(action, actionperms)
torestore.append([erschema, action, origperms])
- yield
- for erschema, action, permissions in torestore:
- if action is None:
- erschema.permissions = permissions
- else:
- erschema.set_action_permissions(action, permissions)
+ try:
+ yield
+ finally:
+ for erschema, action, permissions in torestore:
+ if action is None:
+ erschema.permissions = permissions
+ else:
+ erschema.set_action_permissions(action, permissions)
def assertModificationDateGreater(self, entity, olddate):
entity.cw_attr_cache.pop('modification_date', None)
- self.assertTrue(entity.modification_date > olddate)
+ self.assertGreater(entity.modification_date, olddate)
def assertMessageEqual(self, req, params, expected_msg):
msg = req.session.data[params['_cwmsgid']]
--- a/hooks/security.py Tue Sep 29 12:09:04 2015 +0200
+++ b/hooks/security.py Fri Oct 09 17:52:14 2015 +0200
@@ -143,10 +143,13 @@
"""
assert rschema.inlined
try:
- entity = cnx.transaction_data['ecache'][eid]
+ entity = cnx.entity_cache(eid)
except KeyError:
return False
- return rschema.type in entity.cw_edited.skip_security
+ edited = getattr(entity, 'cw_edited', None)
+ if edited is None:
+ return False
+ return rschema.type in edited.skip_security
class BeforeAddRelationSecurityHook(SecurityHook):
--- a/hooks/syncschema.py Tue Sep 29 12:09:04 2015 +0200
+++ b/hooks/syncschema.py Fri Oct 09 17:52:14 2015 +0200
@@ -701,6 +701,16 @@
syssource = cnx.repo.system_source
cstrtype = self.oldcstr.type()
if cstrtype == 'SizeConstraint':
+ # if the size constraint is being replaced with a new max size, we'll
+ # call update_rdef_column in CWConstraintAddOp, skip it here
+ for cstr in cnx.transaction_data.get('newsizecstr', ()):
+ rdefentity = cstr.reverse_constrained_by[0]
+ cstrrdef = cnx.vreg.schema.schema_by_eid(rdefentity.eid)
+ if cstrrdef == rdef:
+ return
+
+ # we found that the size constraint for this rdef is really gone,
+ # not just replaced by another
syssource.update_rdef_column(cnx, rdef)
self.size_cstr_changed = True
elif cstrtype == 'UniqueConstraint':
@@ -794,6 +804,13 @@
entity = cstrname = None # for pylint
cols = () # for pylint
+ def insert_index(self):
+ # We need to run before CWConstraintDelOp: if a size constraint is
+ # removed and the column is part of a unique_together constraint, we
+ # remove the unique_together index before changing the column's type.
+ # SQL Server does not support unique indices on unlimited text columns.
+ return 0
+
def precommit_event(self):
cnx = self.cnx
prefix = SQL_PREFIX
@@ -1224,6 +1241,11 @@
events = ('after_add_entity', 'after_update_entity')
def __call__(self):
+ if self.entity.cstrtype[0].name == 'SizeConstraint':
+ txdata = self._cw.transaction_data
+ if 'newsizecstr' not in txdata:
+ txdata['newsizecstr'] = set()
+ txdata['newsizecstr'].add(self.entity)
CWConstraintAddOp(self._cw, entity=self.entity)
--- a/predicates.py Tue Sep 29 12:09:04 2015 +0200
+++ b/predicates.py Fri Oct 09 17:52:14 2015 +0200
@@ -1369,7 +1369,7 @@
score = self.score_class(req.vreg['etypes'].etype_class(etype), req)
if score:
eschema = req.vreg.schema.eschema(etype)
- if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
+ if eschema.may_have_permission('add', req):
return score
return 0
--- a/server/migractions.py Tue Sep 29 12:09:04 2015 +0200
+++ b/server/migractions.py Fri Oct 09 17:52:14 2015 +0200
@@ -452,7 +452,8 @@
rtype = str(rtype)
if rtype in self._synchronized:
return
- self._synchronized.add(rtype)
+ if syncrdefs and syncperms and syncprops:
+ self._synchronized.add(rtype)
rschema = self.fs_schema.rschema(rtype)
reporschema = self.repo.schema.rschema(rtype)
if syncprops:
@@ -483,7 +484,8 @@
etype = str(etype)
if etype in self._synchronized:
return
- self._synchronized.add(etype)
+ if syncrdefs and syncperms and syncprops:
+ self._synchronized.add(etype)
repoeschema = self.repo.schema.eschema(etype)
try:
eschema = self.fs_schema.eschema(etype)
@@ -581,9 +583,10 @@
reporschema = self.repo.schema.rschema(rschema)
if (subjtype, rschema, objtype) in self._synchronized:
return
- self._synchronized.add((subjtype, rschema, objtype))
- if rschema.symmetric:
- self._synchronized.add((objtype, rschema, subjtype))
+ if syncperms and syncprops:
+ self._synchronized.add((subjtype, rschema, objtype))
+ if rschema.symmetric:
+ self._synchronized.add((objtype, rschema, subjtype))
rdef = rschema.rdef(subjtype, objtype)
if rdef.infered:
return # don't try to synchronize infered relation defs
@@ -1079,8 +1082,9 @@
default='n'):
return
self.cmd_add_relation_type(newname, commit=True)
- self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
- ask_confirm=self.verbosity>=2)
+ if not self.repo.schema[oldname].rule:
+ self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname),
+ ask_confirm=self.verbosity>=2)
self.cmd_drop_relation_type(oldname, commit=commit)
def cmd_add_relation_definition(self, subjtype, rtype, objtype, commit=True):
--- a/server/sources/native.py Tue Sep 29 12:09:04 2015 +0200
+++ b/server/sources/native.py Fri Oct 09 17:52:14 2015 +0200
@@ -932,12 +932,12 @@
self._handle_is_relation_sql(cnx, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)',
(entity.eid, source.eid))
# now we can update the full text index
- if self.do_fti and self.need_fti_indexation(entity.cw_etype):
+ if self.need_fti_indexation(entity.cw_etype):
self.index_entity(cnx, entity=entity)
def update_info(self, cnx, entity, need_fti_update):
"""mark entity as being modified, fulltext reindex if needed"""
- if self.do_fti and need_fti_update:
+ if need_fti_update:
# reindex the entity only if this query is updating at least
# one indexable attribute
self.index_entity(cnx, entity=entity)
@@ -1333,7 +1333,8 @@
"""create an operation to [re]index textual content of the given entity
on commit
"""
- FTIndexEntityOp.get_instance(cnx).add_data(entity.eid)
+ if self.do_fti:
+ FTIndexEntityOp.get_instance(cnx).add_data(entity.eid)
def fti_unindex_entities(self, cnx, entities):
"""remove text content for entities from the full text index
@@ -1723,7 +1724,7 @@
self.logger.info('restoring sequence %s', seq)
self.read_sequence(archive, seq)
for numrange in numranges:
- self.logger.info('restoring numrange %s', seq)
+ self.logger.info('restoring numrange %s', numrange)
self.read_numrange(archive, numrange)
for table in tables:
self.logger.info('restoring table %s', table)
--- a/server/test/data-migractions/migratedapp/schema.py Tue Sep 29 12:09:04 2015 +0200
+++ b/server/test/data-migractions/migratedapp/schema.py Fri Oct 09 17:52:14 2015 +0200
@@ -108,6 +108,12 @@
class Personne(EntityType):
+ __permissions__ = {
+ 'read': ('managers', 'users'), # 'guests' was removed
+ 'add': ('managers', 'users'),
+ 'update': ('managers', 'owners'),
+ 'delete': ('managers', 'owners')
+ }
__unique_together__ = [('nom', 'prenom', 'datenaiss')]
nom = String(fulltextindexed=True, required=True, maxsize=64)
prenom = String(fulltextindexed=True, maxsize=64)
--- a/server/test/data/schema.py Tue Sep 29 12:09:04 2015 +0200
+++ b/server/test/data/schema.py Fri Oct 09 17:52:14 2015 +0200
@@ -128,6 +128,12 @@
class Personne(EntityType):
+ __permissions__ = {
+ 'read': ('managers', 'users', 'guests'), # 'guests' will be removed
+ 'add': ('managers', 'users'),
+ 'update': ('managers', 'owners'),
+ 'delete': ('managers', 'owners')
+ }
__unique_together__ = [('nom', 'prenom', 'inline2')]
nom = String(fulltextindexed=True, required=True, maxsize=64)
prenom = String(fulltextindexed=True, maxsize=64)
--- a/server/test/datacomputed/migratedapp/schema.py Tue Sep 29 12:09:04 2015 +0200
+++ b/server/test/datacomputed/migratedapp/schema.py Fri Oct 09 17:52:14 2015 +0200
@@ -55,3 +55,7 @@
class whatever(ComputedRelation):
rule = 'S employees E, O associates E'
+
+
+class renamed(ComputedRelation):
+ rule = 'S employees E, O concerns E'
--- a/server/test/datacomputed/schema.py Tue Sep 29 12:09:04 2015 +0200
+++ b/server/test/datacomputed/schema.py Fri Oct 09 17:52:14 2015 +0200
@@ -36,11 +36,13 @@
class Company(EntityType):
score100 = Float(formula='Any AVG(NN) WHERE X employees E, N concerns E, N note100 NN')
+
class Note(EntityType):
note = Int()
note20 = Int(formula='Any N*20 WHERE X note N')
note100 = Int(formula='Any N*20 WHERE X note N')
+
class concerns(RelationDefinition):
subject = 'Note'
object = 'Employee'
@@ -52,3 +54,7 @@
class whatever(ComputedRelation):
rule = 'S employees E, O concerns E'
+
+
+class to_be_renamed(ComputedRelation):
+ rule = 'S employees E, O concerns E'
--- a/server/test/unittest_migractions.py Tue Sep 29 12:09:04 2015 +0200
+++ b/server/test/unittest_migractions.py Fri Oct 09 17:52:14 2015 +0200
@@ -452,6 +452,9 @@
delete_concerne_rqlexpr = self._rrqlexpr_rset(cnx, 'delete', 'concerne')
add_concerne_rqlexpr = self._rrqlexpr_rset(cnx, 'add', 'concerne')
+ # make sure properties (e.g. etype descriptions) are synced by the
+ # second call to sync_schema
+ mh.cmd_sync_schema_props_perms(syncprops=False, commit=False)
mh.cmd_sync_schema_props_perms(commit=False)
self.assertEqual(cnx.execute('Any D WHERE X name "Personne", X description D')[0][0],
@@ -732,8 +735,7 @@
self.assertNotIn('works_for', self.schema)
with self.mh() as (cnx, mh):
with self.assertRaises(ExecutionError) as exc:
- mh.cmd_add_relation_definition('Employee', 'works_for',
- 'Company')
+ mh.cmd_add_relation_definition('Employee', 'works_for', 'Company')
self.assertEqual(str(exc.exception),
'Cannot add a relation definition for a computed '
'relation (works_for)')
@@ -792,6 +794,12 @@
'Cannot synchronize a relation definition for a computed '
'relation (whatever)')
+ def test_computed_relation_rename_relation_type(self):
+ with self.mh() as (cnx, mh):
+ mh.cmd_rename_relation_type('to_be_renamed', 'renamed')
+ self.assertIn('renamed', self.schema)
+ self.assertNotIn('to_be_renamed', self.schema)
+
# computed attributes migration ############################################
def setup_add_score(self):
@@ -799,9 +807,9 @@
assert not cnx.execute('Company X')
c = cnx.create_entity('Company')
e1 = cnx.create_entity('Employee', reverse_employees=c)
- n1 = cnx.create_entity('Note', note=2, concerns=e1)
+ cnx.create_entity('Note', note=2, concerns=e1)
e2 = cnx.create_entity('Employee', reverse_employees=c)
- n2 = cnx.create_entity('Note', note=4, concerns=e2)
+ cnx.create_entity('Note', note=4, concerns=e2)
cnx.commit()
def assert_score_initialized(self, mh):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/unittest_sources_native.py Fri Oct 09 17:52:14 2015 +0200
@@ -0,0 +1,38 @@
+# 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 logilab.common import tempattr
+
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.server.sources.native import FTIndexEntityOp
+
+class NativeSourceTC(CubicWebTC):
+
+ def test_index_entity_consider_do_fti(self):
+ source = self.repo.system_source
+ with tempattr(source, 'do_fti', False):
+ with self.admin_access.repo_cnx() as cnx:
+ # when do_fti is set to false, call to index_entity (as may be done from hooks)
+ # should have no effect
+ source.index_entity(cnx, cnx.user)
+ self.assertNotIn(cnx.user.eid, FTIndexEntityOp.get_instance(cnx).get_data())
+
+
+if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
+ unittest_main()
--- a/web/test/unittest_views_actions.py Tue Sep 29 12:09:04 2015 +0200
+++ b/web/test/unittest_views_actions.py Fri Oct 09 17:52:14 2015 +0200
@@ -21,6 +21,7 @@
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.web.views import actions, uicfg
+
class ActionsTC(CubicWebTC):
def test_view_action(self):
with self.admin_access.web_request(vid='rss', rql='CWUser X') as req:
@@ -33,12 +34,31 @@
"""ensure has_editable_relation predicate used by ModifyAction
return positive score if there is only some inlined forms
"""
+ # The schema only allows the anonymous user to modify his/her own
+ # EmailAddress if it is set, not to create one. Since the 'anon' CWUser
+ # entity is created without any associated EmailAddress entities, there
+ # are no attributes nor relations that can be edited: the "modify"
+ # action should not appear.
+ with self.new_access('anon').web_request() as req:
+ predicate = actions.has_editable_relation()
+ self.assertEqual(predicate(None, req, rset=req.user.as_rset()),
+ 0)
+ # being allowed to 'add' the relation is not enough
use_email = self.schema['use_email'].rdefs['CWUser', 'EmailAddress']
with self.temporary_permissions((use_email, {'add': ('guests',)})):
with self.new_access('anon').web_request() as req:
predicate = actions.has_editable_relation()
self.assertEqual(predicate(None, req, rset=req.user.as_rset()),
+ 0)
+ # if we also allow creating the target etype, then the "modify" action
+ # should appear
+ with self.temporary_permissions((use_email, {'add': ('guests',)}),
+ EmailAddress={'add': ('guests',)}):
+ with self.new_access('anon').web_request() as req:
+ 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_editforms.py Tue Sep 29 12:09:04 2015 +0200
+++ b/web/test/unittest_views_editforms.py Fri Oct 09 17:52:14 2015 +0200
@@ -20,6 +20,8 @@
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.web.views import uicfg
from cubicweb.web.formwidgets import AutoCompletionWidget
+from cubicweb.schema import RRQLExpression
+
AFFK = uicfg.autoform_field_kwargs
AFS = uicfg.autoform_section
@@ -173,6 +175,35 @@
[rschema.type
for rschema, _ in mform.editable_attributes()])
+ def test_inlined_relations(self):
+ with self.admin_access.web_request() as req:
+ with self.temporary_permissions(EmailAddress={'add': ()}):
+ autoform = self.vreg['forms'].select('edition', req, entity=req.user)
+ self.assertEqual(list(autoform.inlined_form_views()), [])
+
+ def test_check_inlined_rdef_permissions(self):
+ # try to check permissions when creating an entity ('user' below is a
+ # fresh entity without an eid)
+ with self.admin_access.web_request() as req:
+ ttype = 'EmailAddress'
+ rschema = self.schema['use_email']
+ rdef = rschema.rdefs[('CWUser', ttype)]
+ tschema = self.schema[ttype]
+ role = 'subject'
+ with self.temporary_permissions((rdef, {'add': ()})):
+ user = self.vreg['etypes'].etype_class('CWUser')(req)
+ autoform = self.vreg['forms'].select('edition', req, entity=user)
+ self.assertFalse(autoform.check_inlined_rdef_permissions(rschema, role,
+ tschema, ttype))
+ # we actually don't care about the actual expression,
+ # may_have_permission only checks the presence of such expressions
+ expr = RRQLExpression('S use_email O')
+ with self.temporary_permissions((rdef, {'add': (expr,)})):
+ user = self.vreg['etypes'].etype_class('CWUser')(req)
+ autoform = self.vreg['forms'].select('edition', req, entity=user)
+ self.assertTrue(autoform.check_inlined_rdef_permissions(rschema, role,
+ tschema, ttype))
+
class FormViewsTC(CubicWebTC):
--- a/web/views/actions.py Tue Sep 29 12:09:04 2015 +0200
+++ b/web/views/actions.py Fri Oct 09 17:52:14 2015 +0200
@@ -50,7 +50,7 @@
entity=entity, mainform=False)
for dummy in form.editable_relations():
return 1
- for dummy in form.inlined_relations():
+ for dummy in form.inlined_form_views():
return 1
for dummy in form.editable_attributes(strict=True):
return 1
--- a/web/views/autoform.py Tue Sep 29 12:09:04 2015 +0200
+++ b/web/views/autoform.py Fri Oct 09 17:52:14 2015 +0200
@@ -126,6 +126,7 @@
from logilab.mtconverter import xml_escape
from logilab.common.decorators import iclassmethod, cached
from logilab.common.deprecation import deprecated
+from logilab.common.registry import NoSelectableObject
from cubicweb import neg_role, uilib
from cubicweb.schema import display_name
@@ -942,6 +943,8 @@
def check_inlined_rdef_permissions(self, rschema, role, tschema, ttype):
"""return true if permissions are granted on the inlined object and
relation"""
+ if not tschema.has_perm(self._cw, 'add'):
+ return False
entity = self.edited_entity
rdef = entity.e_schema.rdef(rschema, role, ttype)
if entity.has_eid():
@@ -949,10 +952,8 @@
rdefkwargs = {'fromeid': entity.eid}
else:
rdefkwargs = {'toeid': entity.eid}
- else:
- rdefkwargs = {}
- return (tschema.has_perm(self._cw, 'add')
- and rdef.has_perm(self._cw, 'add', **rdefkwargs))
+ return rdef.has_perm(self._cw, 'add', **rdefkwargs)
+ return rdef.may_have_permission('add', self._cw)
def should_hide_add_new_relation_link(self, rschema, card):
@@ -983,11 +984,16 @@
"""yield inline form views to a newly related (hence created) entity
through the given relation
"""
- yield self._cw.vreg['views'].select('inline-creation', self._cw,
- etype=ttype, rtype=rschema, role=role,
- peid=self.edited_entity.eid,
- petype=self.edited_entity.e_schema,
- pform=self)
+ try:
+ yield self._cw.vreg['views'].select('inline-creation', self._cw,
+ etype=ttype, rtype=rschema, role=role,
+ peid=self.edited_entity.eid,
+ petype=self.edited_entity.e_schema,
+ pform=self)
+ except NoSelectableObject:
+ # may be raised if user doesn't have the permission to add ttype entities (no checked
+ # earlier) or if there is some custom selector on the view
+ pass
## default form ui configuration ##############################################