--- a/.hgtags Mon Sep 14 11:15:47 2015 +0200
+++ b/.hgtags Mon Oct 12 10:53:35 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,9 +502,15 @@
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
a8a0de0298a58306d63dbc998ad60c48bf18c80a 3.21.1
a8a0de0298a58306d63dbc998ad60c48bf18c80a debian/3.21.1-1
a8a0de0298a58306d63dbc998ad60c48bf18c80a centos/3.21.1-1
+a5428e1ab36491a8e6d66ce09d23b708b97e1337 3.21.2
+a5428e1ab36491a8e6d66ce09d23b708b97e1337 debian/3.21.2-1
+a5428e1ab36491a8e6d66ce09d23b708b97e1337 centos/3.21.2-1
--- a/__pkginfo__.py Mon Sep 14 11:15:47 2015 +0200
+++ b/__pkginfo__.py Mon Oct 12 10:53:35 2015 +0200
@@ -22,7 +22,7 @@
modname = distname = "cubicweb"
-numversion = (3, 21, 1)
+numversion = (3, 21, 2)
version = '.'.join(str(num) for num in numversion)
description = "a repository of entities / relations for knowledge management"
@@ -56,7 +56,6 @@
__recommends__ = {
'docutils': '>= 0.6',
- 'Pyro': '>= 3.9.1, < 4.0.0',
'Pillow': '', # for captcha
'pycrypto': '', # for crypto extensions
'fyzz': '>= 0.1.0', # for sparql
--- a/cubicweb.spec Mon Sep 14 11:15:47 2015 +0200
+++ b/cubicweb.spec Mon Oct 12 10:53:35 2015 +0200
@@ -7,7 +7,7 @@
%endif
Name: cubicweb
-Version: 3.21.1
+Version: 3.21.2
Release: logilab.1%{?dist}
Summary: CubicWeb is a semantic web application framework
Source0: http://download.logilab.org/pub/cubicweb/cubicweb-%{version}.tar.gz
--- a/debian/changelog Mon Sep 14 11:15:47 2015 +0200
+++ b/debian/changelog Mon Oct 12 10:53:35 2015 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.21.2-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Rémi Cardona <remi.cardona@logilab.fr> Fri, 09 Oct 2015 18:00:39 +0200
+
cubicweb (3.21.1-1) unstable; urgency=medium
* new upstream release
@@ -10,6 +16,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 +88,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/__init__.py Mon Sep 14 11:15:47 2015 +0200
+++ b/devtools/__init__.py Mon Oct 12 10:53:35 2015 +0200
@@ -629,6 +629,11 @@
return backup_name
return None
+ def has_cache(self, db_id):
+ backup_name = self._backup_name(db_id)
+ return (super(PostgresTestDataBaseHandler, self).has_cache(db_id)
+ and backup_name in self.helper.list_databases(self.cursor))
+
def init_test_database(self):
"""initialize a fresh postgresql database used for testing purpose"""
from cubicweb.server import init_repository
--- a/devtools/test/unittest_testlib.py Mon Sep 14 11:15:47 2015 +0200
+++ b/devtools/test/unittest_testlib.py Mon Oct 12 10:53:35 2015 +0200
@@ -197,6 +197,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 Mon Sep 14 11:15:47 2015 +0200
+++ b/devtools/testlib.py Mon Oct 12 10:53:35 2015 +0200
@@ -528,16 +528,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 Mon Sep 14 11:15:47 2015 +0200
+++ b/hooks/security.py Mon Oct 12 10:53:35 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 Mon Sep 14 11:15:47 2015 +0200
+++ b/hooks/syncschema.py Mon Oct 12 10:53:35 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/misc/migration/3.21.0_Any.py Mon Sep 14 11:15:47 2015 +0200
+++ b/misc/migration/3.21.0_Any.py Mon Oct 12 10:53:35 2015 +0200
@@ -5,7 +5,7 @@
def add_foreign_keys():
- source = repo.sources_by_uri['system']
+ source = repo.system_source
if not source.dbhelper.alter_column_support:
return
for rschema in schema.relations():
@@ -35,14 +35,25 @@
sql('DELETE FROM %(r)s_relation '
'WHERE eid_to IN (SELECT eid_to FROM %(r)s_relation EXCEPT SELECT eid FROM entities)' % args)
- sql('ALTER TABLE %(r)s_relation DROP CONSTRAINT IF EXISTS %(r)s_relation_eid_from_fkey' % args,
- ask_confirm=False)
- sql('ALTER TABLE %(r)s_relation DROP CONSTRAINT IF EXISTS %(r)s_relation_eid_to_fkey' % args,
- ask_confirm=False)
- sql('ALTER TABLE %(r)s_relation ADD CONSTRAINT %(r)s_relation_eid_from_fkey '
+ args['from_fk'] = '%(r)s_relation_eid_from_fkey' % args
+ args['to_fk'] = '%(r)s_relation_eid_to_fkey' % args
+ args['table'] = '%(r)s_relation' % args
+ if repo.system_source.dbdriver == 'postgres':
+ sql('ALTER TABLE %(table)s DROP CONSTRAINT IF EXISTS %(from_fk)s' % args,
+ ask_confirm=False)
+ sql('ALTER TABLE %(table)s DROP CONSTRAINT IF EXISTS %(to_fk)s' % args,
+ ask_confirm=False)
+ elif repo.system_source.dbdriver.startswith('sqlserver'):
+ sql("IF OBJECT_ID('%(from_fk)s', 'F') IS NOT NULL "
+ "ALTER TABLE %(table)s DROP CONSTRAINT %(from_fk)s" % args,
+ ask_confirm=False)
+ sql("IF OBJECT_ID('%(to_fk)s', 'F') IS NOT NULL "
+ "ALTER TABLE %(table)s DROP CONSTRAINT %(to_fk)s" % args,
+ ask_confirm=False)
+ sql('ALTER TABLE %(table)s ADD CONSTRAINT %(from_fk)s '
'FOREIGN KEY (eid_from) REFERENCES entities (eid)' % args,
ask_confirm=False)
- sql('ALTER TABLE %(r)s_relation ADD CONSTRAINT %(r)s_relation_eid_to_fkey '
+ sql('ALTER TABLE %(table)s ADD CONSTRAINT %(to_fk)s '
'FOREIGN KEY (eid_to) REFERENCES entities (eid)' % args,
ask_confirm=False)
@@ -75,8 +86,14 @@
print('%(e)s.%(r)s references unknown entities, deleting relation' % args)
sql('UPDATE cw_%(e)s SET cw_%(r)s = NULL WHERE cw_%(r)s IS NOT NULL AND cw_%(r)s IN '
'(SELECT cw_%(r)s FROM cw_%(e)s EXCEPT SELECT eid FROM entities)' % args)
- sql('ALTER TABLE cw_%(e)s DROP CONSTRAINT IF EXISTS %(c)s' % args,
- ask_confirm=False)
+
+ if repo.system_source.dbdriver == 'postgres':
+ sql('ALTER TABLE cw_%(e)s DROP CONSTRAINT IF EXISTS %(c)s' % args,
+ ask_confirm=False)
+ elif repo.system_source.dbdriver.startswith('sqlserver'):
+ sql("IF OBJECT_ID('%(c)s', 'F') IS NOT NULL "
+ "ALTER TABLE cw_%(e)s DROP CONSTRAINT %(c)s" % args,
+ ask_confirm=False)
sql('ALTER TABLE cw_%(e)s ADD CONSTRAINT %(c)s '
'FOREIGN KEY (cw_%(r)s) references entities(eid)' % args,
ask_confirm=False)
@@ -92,9 +109,15 @@
print('%(e)s has nonexistent entities, deleting' % args)
sql('DELETE FROM cw_%(e)s WHERE cw_eid IN '
'(SELECT cw_eid FROM cw_%(e)s EXCEPT SELECT eid FROM entities)' % args)
- sql('ALTER TABLE cw_%(e)s DROP CONSTRAINT IF EXISTS cw_%(e)s_cw_eid_fkey' % args,
- ask_confirm=False)
- sql('ALTER TABLE cw_%(e)s ADD CONSTRAINT cw_%(e)s_cw_eid_fkey '
+ args['c'] = 'cw_%(e)s_cw_eid_fkey' % args
+ if repo.system_source.dbdriver == 'postgres':
+ sql('ALTER TABLE cw_%(e)s DROP CONSTRAINT IF EXISTS %(c)s' % args,
+ ask_confirm=False)
+ elif repo.system_source.dbdriver.startswith('sqlserver'):
+ sql("IF OBJECT_ID('%(c)s', 'F') IS NOT NULL "
+ "ALTER TABLE cw_%(e)s DROP CONSTRAINT %(c)s" % args,
+ ask_confirm=False)
+ sql('ALTER TABLE cw_%(e)s ADD CONSTRAINT %(c)s '
'FOREIGN KEY (cw_eid) REFERENCES entities (eid)' % args,
ask_confirm=False)
@@ -105,7 +128,10 @@
helper = repo.system_source.dbhelper
helper.drop_index(cu, 'entities', 'extid', False)
-helper.create_index(cu, 'entities', 'extid', True)
+# don't use create_index because it doesn't work for columns that may be NULL
+# on sqlserver
+for query in helper.sqls_create_multicol_unique_index('entities', ['extid']):
+ cu.execute(query)
if 'moved_entities' not in helper.list_tables(cu):
sql('''
@@ -117,9 +143,10 @@
moved_entities = sql('SELECT -eid, extid FROM entities WHERE eid < 0',
ask_confirm=False)
-cu.executemany('INSERT INTO moved_entities (eid, extid) VALUES (%s, %s)',
- moved_entities)
-sql('DELETE FROM entities WHERE eid < 0')
+if moved_entities:
+ cu.executemany('INSERT INTO moved_entities (eid, extid) VALUES (%s, %s)',
+ moved_entities)
+ sql('DELETE FROM entities WHERE eid < 0')
commit()
@@ -137,6 +164,11 @@
continue
cstrname, check = check_constraint(rdef.subject, rdef.object, rdef.rtype.type,
cstr, helper, prefix='cw_')
- sql('ALTER TABLE %s%s DROP CONSTRAINT IF EXISTS %s' % ('cw_', rdef.subject.type, cstrname))
- sql('ALTER TABLE %s%s ADD CONSTRAINT %s CHECK(%s)' % ('cw_', rdef.subject.type, cstrname, check))
+ args = {'e': rdef.subject.type, 'c': cstrname, 'v': check}
+ if repo.system_source.dbdriver == 'postgres':
+ sql('ALTER TABLE cw_%(e)s DROP CONSTRAINT IF EXISTS %(c)s' % args)
+ elif repo.system_source.dbdriver.startswith('sqlserver'):
+ sql("IF OBJECT_ID('%(c)s', 'C') IS NOT NULL "
+ "ALTER TABLE cw_%(e)s DROP CONSTRAINT %(c)s" % args)
+ sql('ALTER TABLE cw_%(e)s ADD CONSTRAINT %(c)s CHECK(%(v)s)' % args)
commit()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.21.2_Any.py Mon Oct 12 10:53:35 2015 +0200
@@ -0,0 +1,7 @@
+sync_schema_props_perms('cwuri')
+
+helper = repo.system_source.dbhelper
+cu = session.cnxset.cu
+helper.set_null_allowed(cu, 'moved_entities', 'extid', 'VARCHAR(256)', False)
+
+commit()
--- a/predicates.py Mon Sep 14 11:15:47 2015 +0200
+++ b/predicates.py Mon Oct 12 10:53:35 2015 +0200
@@ -1372,7 +1372,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/schemas/base.py Mon Sep 14 11:15:47 2015 +0200
+++ b/schemas/base.py Mon Oct 12 10:53:35 2015 +0200
@@ -165,7 +165,6 @@
cardinality = '11'
subject = '*'
object = 'String'
- constraints = [UniqueConstraint()]
# XXX find a better relation name
--- a/server/checkintegrity.py Mon Sep 14 11:15:47 2015 +0200
+++ b/server/checkintegrity.py Mon Oct 12 10:53:35 2015 +0200
@@ -209,7 +209,7 @@
' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid) '
'ORDER BY e.eid')
for row in cursor.fetchall():
- sys.stderr.write(msg % row)
+ sys.stderr.write(msg % tuple(row))
if fix:
cnx.system_sql('INSERT INTO is_relation (eid_from, eid_to) '
'SELECT e.eid, s.cw_eid FROM entities as e, cw_CWEType as s '
@@ -223,7 +223,7 @@
' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid) '
'ORDER BY e.eid')
for row in cursor.fetchall():
- sys.stderr.write(msg % row)
+ sys.stderr.write(msg % tuple(row))
if fix:
cnx.system_sql('INSERT INTO is_instance_of_relation (eid_from, eid_to) '
'SELECT e.eid, s.cw_eid FROM entities as e, cw_CWEType as s '
--- a/server/migractions.py Mon Sep 14 11:15:47 2015 +0200
+++ b/server/migractions.py Mon Oct 12 10:53:35 2015 +0200
@@ -62,9 +62,6 @@
from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX
-def mock_object(**params):
- return type('Mock', (), params)()
-
class ClearGroupMap(hook.Hook):
__regid__ = 'cw.migration.clear_group_mapping'
__select__ = hook.Hook.__select__ & is_instance('CWGroup')
@@ -451,7 +448,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:
@@ -482,7 +480,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)
@@ -580,9 +579,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
@@ -1076,10 +1076,11 @@
if not self.confirm('Relation %s is still present in the filesystem schema,'
' do you really want to drop it?' % oldname,
default='n'):
- raise SystemExit(1)
+ 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):
@@ -1476,19 +1477,28 @@
* the actual schema won't be updated until next startup
"""
rschema = self.repo.schema.rschema(attr)
- oldtype = rschema.objects(etype)[0]
- rdefeid = rschema.rdef(etype, oldtype).eid
- allownull = rschema.rdef(etype, oldtype).cardinality[0] != '1'
+ oldschema = rschema.objects(etype)[0]
+ rdef = rschema.rdef(etype, oldschema)
sql = ("UPDATE cw_CWAttribute "
"SET cw_to_entity=(SELECT cw_eid FROM cw_CWEType WHERE cw_name='%s')"
- "WHERE cw_eid=%s") % (newtype, rdefeid)
+ "WHERE cw_eid=%s") % (newtype, rdef.eid)
self.sqlexec(sql, ask_confirm=False)
dbhelper = self.repo.system_source.dbhelper
sqltype = dbhelper.TYPE_MAPPING[newtype]
cursor = self.cnx.cnxset.cu
- dbhelper.change_col_type(cursor, 'cw_%s' % etype, 'cw_%s' % attr, sqltype, allownull)
+ allownull = rdef.cardinality[0] != '1'
+ dbhelper.change_col_type(cursor, 'cw_%s' % etype, 'cw_%s' % attr, sqltype, allownull)
if commit:
self.commit()
+ # manually update live schema
+ eschema = self.repo.schema[etype]
+ rschema._subj_schemas[eschema].remove(oldschema)
+ rschema._obj_schemas[oldschema].remove(eschema)
+ newschema = self.repo.schema[newtype]
+ rschema._update(eschema, newschema)
+ rdef.object = newschema
+ del rschema.rdefs[(eschema, oldschema)]
+ rschema.rdefs[(eschema, newschema)] = rdef
def cmd_add_entity_type_table(self, etype, commit=True):
"""low level method to create the sql table for an existing entity.
--- a/server/sources/__init__.py Mon Sep 14 11:15:47 2015 +0200
+++ b/server/sources/__init__.py Mon Oct 12 10:53:35 2015 +0200
@@ -132,6 +132,9 @@
def __eq__(self, other):
return self.uri == other.uri
+ def __ne__(self, other):
+ return not (self == other)
+
def backup(self, backupfile, confirm, format='native'):
"""method called to create a backup of source's data"""
pass
@@ -405,7 +408,7 @@
# system source interface #################################################
def eid_type_source(self, cnx, eid):
- """return a tuple (type, source, extid) for the entity with id <eid>"""
+ """return a tuple (type, extid, source) for the entity with id <eid>"""
raise NotImplementedError(self)
def create_eid(self, cnx):
--- a/server/sources/native.py Mon Sep 14 11:15:47 2015 +0200
+++ b/server/sources/native.py Mon Oct 12 10:53:35 2015 +0200
@@ -852,7 +852,7 @@
raise UnknownEid(eid)
def eid_type_source(self, cnx, eid): # pylint: disable=E0202
- """return a tuple (type, source, extid) for the entity with id <eid>"""
+ """return a tuple (type, extid, source) for the entity with id <eid>"""
sql = 'SELECT type, extid, asource FROM entities WHERE eid=%s' % eid
res = self._eid_type_source(cnx, eid, sql)
if not isinstance(res, list):
@@ -861,7 +861,7 @@
return res
def eid_type_source_pre_131(self, cnx, eid):
- """return a tuple (type, source, extid) for the entity with id <eid>"""
+ """return a tuple (type, extid, source) for the entity with id <eid>"""
sql = 'SELECT type, extid FROM entities WHERE eid=%s' % eid
res = self._eid_type_source(cnx, eid, sql)
if not isinstance(res, list):
@@ -933,12 +933,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)
@@ -1334,7 +1334,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
@@ -1401,12 +1402,12 @@
eid INTEGER PRIMARY KEY NOT NULL,
type VARCHAR(64) NOT NULL,
asource VARCHAR(128) NOT NULL,
- extid VARCHAR(256) UNIQUE
+ extid VARCHAR(256)
);;
CREATE INDEX entities_type_idx ON entities(type);;
CREATE TABLE moved_entities (
eid INTEGER PRIMARY KEY NOT NULL,
- extid VARCHAR(256) UNIQUE
+ extid VARCHAR(256) UNIQUE NOT NULL
);;
CREATE TABLE transactions (
@@ -1459,18 +1460,22 @@
DELETE FROM tx_relation_actions WHERE tx_uuid=OLD.tx_uuid;
END;;
'''
+ schema += ';;'.join(helper.sqls_create_multicol_unique_index('entities', ['extid']))
+ schema += ';;\n'
return schema
def sql_drop_schema(driver):
helper = get_db_helper(driver)
return """
+%s;
%s
DROP TABLE entities;
DROP TABLE tx_entity_actions;
DROP TABLE tx_relation_actions;
DROP TABLE transactions;
-""" % helper.sql_drop_numrange('entities_id_seq')
+""" % (';'.join(helper.sqls_drop_multicol_unique_index('entities', ['extid'])),
+ helper.sql_drop_numrange('entities_id_seq'))
def grant_schema(user, set_owner=True):
@@ -1720,7 +1725,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/sources/rql2sql.py Mon Sep 14 11:15:47 2015 +0200
+++ b/server/sources/rql2sql.py Mon Oct 12 10:53:35 2015 +0200
@@ -50,11 +50,9 @@
__docformat__ = "restructuredtext en"
import threading
-from datetime import datetime, time
from six.moves import range
-from logilab.common.date import utcdatetime, utctime
from logilab.database import FunctionDescr, SQL_FUNCTIONS_REGISTRY
from rql import BadRQLQuery, CoercionError
@@ -1518,14 +1516,6 @@
_id = value
if isinstance(_id, unicode):
_id = _id.encode()
- # convert timestamp to utc.
- # expect SET TiME ZONE to UTC at connection opening time.
- # This shouldn't change anything for datetime without TZ.
- value = self._args[_id]
- if isinstance(value, datetime) and value.tzinfo is not None:
- self._query_attrs[_id] = utcdatetime(value)
- elif isinstance(value, time) and value.tzinfo is not None:
- self._query_attrs[_id] = utctime(value)
else:
_id = str(id(constant)).replace('-', '', 1)
self._query_attrs[_id] = value
--- a/server/sqlutils.py Mon Sep 14 11:15:47 2015 +0200
+++ b/server/sqlutils.py Mon Oct 12 10:53:35 2015 +0200
@@ -21,11 +21,11 @@
__docformat__ = "restructuredtext en"
import sys
-import os
import re
import subprocess
from os.path import abspath
from logging import getLogger
+from datetime import time, datetime
from six import string_types
from six.moves import filter
@@ -34,6 +34,7 @@
from logilab.common.shellutils import ProgressBar, DummyProgressBar
from logilab.common.deprecation import deprecated
from logilab.common.logging_ext import set_log_methods
+from logilab.common.date import utctime, utcdatetime
from logilab.database.sqlgen import SQLGenerator
from cubicweb import Binary, ConfigurationError
@@ -376,8 +377,17 @@
# convert cubicweb binary into db binary
if isinstance(val, Binary):
val = self._binary(val.getvalue())
+ # convert timestamp to utc.
+ # expect SET TiME ZONE to UTC at connection opening time.
+ # This shouldn't change anything for datetime without TZ.
+ elif isinstance(val, datetime) and val.tzinfo is not None:
+ val = utcdatetime(val)
+ elif isinstance(val, time) and val.tzinfo is not None:
+ val = utctime(val)
newargs[key] = val
# should not collide
+ assert not (frozenset(newargs) & frozenset(query_args)), \
+ 'unexpected collision: %s' % (frozenset(newargs) & frozenset(query_args))
newargs.update(query_args)
return newargs
return query_args
--- a/server/test/data-migractions/migratedapp/schema.py Mon Sep 14 11:15:47 2015 +0200
+++ b/server/test/data-migractions/migratedapp/schema.py Mon Oct 12 10:53:35 2015 +0200
@@ -19,7 +19,7 @@
import datetime as dt
from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
SubjectRelation, Bytes,
- RichString, String, Int, Boolean, Datetime, Date)
+ RichString, String, Int, Boolean, Datetime, Date, Float)
from yams.constraints import SizeConstraint, UniqueConstraint
from cubicweb.schema import (WorkflowableEntityType, RQLConstraint,
RQLVocabularyConstraint,
@@ -49,7 +49,7 @@
}
nom = String(maxsize=64, fulltextindexed=True)
web = String(maxsize=128)
- tel = Int()
+ tel = Float()
fax = Int()
rncs = String(maxsize=128)
ad1 = String(maxsize=128)
@@ -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 Mon Sep 14 11:15:47 2015 +0200
+++ b/server/test/data/schema.py Mon Oct 12 10:53:35 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 Mon Sep 14 11:15:47 2015 +0200
+++ b/server/test/datacomputed/migratedapp/schema.py Mon Oct 12 10:53:35 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 Mon Sep 14 11:15:47 2015 +0200
+++ b/server/test/datacomputed/schema.py Mon Oct 12 10:53:35 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_ldapsource.py Mon Sep 14 11:15:47 2015 +0200
+++ b/server/test/unittest_ldapsource.py Mon Oct 12 10:53:35 2015 +0200
@@ -29,8 +29,6 @@
from six import string_types
from six.moves import range
-from logilab.common.testlib import TestCase, unittest_main, mock_object, Tags
-
from cubicweb import AuthenticationError
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.repotest import RQLGeneratorTC
@@ -484,4 +482,5 @@
if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
unittest_main()
--- a/server/test/unittest_migractions.py Mon Sep 14 11:15:47 2015 +0200
+++ b/server/test/unittest_migractions.py Mon Oct 12 10:53:35 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],
@@ -704,6 +707,18 @@
mh.cmd_add_relation_type('same_as')
self.assertTrue(self.table_sql(mh, 'same_as_relation'))
+ def test_change_attribute_type(self):
+ with self.mh() as (cnx, mh):
+ mh.cmd_create_entity('Societe', tel=1)
+ mh.commit()
+ mh.change_attribute_type('Societe', 'tel', 'Float')
+ self.assertNotIn(('Societe', 'Int'), self.schema['tel'].rdefs)
+ self.assertIn(('Societe', 'Float'), self.schema['tel'].rdefs)
+ self.assertEqual(self.schema['tel'].rdefs[('Societe', 'Float')].object, 'Float')
+ tel = mh.rqlexec('Any T WHERE X tel T')[0][0]
+ self.assertEqual(tel, 1.0)
+ self.assertIsInstance(tel, float)
+
class MigrationCommandsComputedTC(MigrationTC):
""" Unit tests for computed relations and attributes
@@ -720,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)')
@@ -780,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):
@@ -787,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):
--- a/server/test/unittest_querier.py Mon Sep 14 11:15:47 2015 +0200
+++ b/server/test/unittest_querier.py Mon Oct 12 10:53:35 2015 +0200
@@ -1399,6 +1399,13 @@
self.assertEqual(datenaiss.tzinfo, None)
self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0))
+ def test_tz_datetime_cache_nonregr(self):
+ datenaiss = datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))
+ self.qexecute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s",
+ {'date': datenaiss})
+ self.assertTrue(self.qexecute("Any X WHERE X tzdatenaiss %(d)s", {'d': datenaiss}))
+ self.assertFalse(self.qexecute("Any X WHERE X tzdatenaiss %(d)s", {'d': datenaiss - timedelta(1)}))
+
# non regression tests #####################################################
def test_nonregr_1(self):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/unittest_sources_native.py Mon Oct 12 10:53:35 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_form.py Mon Sep 14 11:15:47 2015 +0200
+++ b/web/test/unittest_form.py Mon Oct 12 10:53:35 2015 +0200
@@ -21,7 +21,7 @@
from xml.etree.ElementTree import fromstring
from lxml import html
-from logilab.common.testlib import unittest_main, mock_object
+from logilab.common.testlib import unittest_main
from cubicweb import Binary, ValidationError
from cubicweb.devtools.testlib import CubicWebTC
--- a/web/test/unittest_views_actions.py Mon Sep 14 11:15:47 2015 +0200
+++ b/web/test/unittest_views_actions.py Mon Oct 12 10:53:35 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 Mon Sep 14 11:15:47 2015 +0200
+++ b/web/test/unittest_views_editforms.py Mon Oct 12 10:53:35 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 Mon Sep 14 11:15:47 2015 +0200
+++ b/web/views/actions.py Mon Oct 12 10:53:35 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 Mon Sep 14 11:15:47 2015 +0200
+++ b/web/views/autoform.py Mon Oct 12 10:53:35 2015 +0200
@@ -128,6 +128,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
@@ -947,6 +948,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():
@@ -954,10 +957,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):
@@ -988,11 +989,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 ##############################################
--- a/web/views/debug.py Mon Sep 14 11:15:47 2015 +0200
+++ b/web/views/debug.py Mon Oct 12 10:53:35 2015 +0200
@@ -132,8 +132,8 @@
w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
_('data directory url'), req.datadir_url))
w(u'</table>')
- if req.user.is_in_group('managers'):
- from cubicweb.web.application import SESSION_MANAGER
+ from cubicweb.web.application import SESSION_MANAGER
+ if SESSION_MANAGER is not None and req.user.is_in_group('managers'):
sessions = SESSION_MANAGER.current_sessions()
w(u'<h3>%s</h3>' % _('opened web sessions'))
if sessions: