# HG changeset patch # User Rémi Cardona # Date 1444640015 -7200 # Node ID 9ca33768473c1407a0b7c331464f209a26854081 # Parent 3cc6154b94a3306a701b73bb033dc9d0f4ebeac4# Parent 4be98ec89fef432d543c47337c9def09778f94d1 merge with 3.21.2 diff -r 3cc6154b94a3 -r 9ca33768473c .hgtags --- 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 diff -r 3cc6154b94a3 -r 9ca33768473c __pkginfo__.py --- 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 diff -r 3cc6154b94a3 -r 9ca33768473c cubicweb.spec --- 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 diff -r 3cc6154b94a3 -r 9ca33768473c debian/changelog --- 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 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 Fri, 10 Jul 2015 17:04:11 +0200 +cubicweb (3.20.10-1) unstable; urgency=medium + + * New upstream release. + + -- Rémi Cardona 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 Tue, 06 Jan 2015 18:11:03 +0100 +cubicweb (3.19.13-1) unstable; urgency=medium + + * New upstream release. + + -- Rémi Cardona Tue, 06 Oct 2015 18:31:33 +0200 + cubicweb (3.19.12-1) unstable; urgency=low * New upstream release diff -r 3cc6154b94a3 -r 9ca33768473c devtools/__init__.py --- 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 diff -r 3cc6154b94a3 -r 9ca33768473c devtools/test/unittest_testlib.py --- 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): diff -r 3cc6154b94a3 -r 9ca33768473c devtools/testlib.py --- 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']] diff -r 3cc6154b94a3 -r 9ca33768473c hooks/security.py --- 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): diff -r 3cc6154b94a3 -r 9ca33768473c hooks/syncschema.py --- 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) diff -r 3cc6154b94a3 -r 9ca33768473c misc/migration/3.21.0_Any.py --- 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() diff -r 3cc6154b94a3 -r 9ca33768473c misc/migration/3.21.2_Any.py --- /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() diff -r 3cc6154b94a3 -r 9ca33768473c predicates.py --- 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 diff -r 3cc6154b94a3 -r 9ca33768473c schemas/base.py --- 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 diff -r 3cc6154b94a3 -r 9ca33768473c server/checkintegrity.py --- 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 ' diff -r 3cc6154b94a3 -r 9ca33768473c server/migractions.py --- 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. diff -r 3cc6154b94a3 -r 9ca33768473c server/sources/__init__.py --- 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 """ + """return a tuple (type, extid, source) for the entity with id """ raise NotImplementedError(self) def create_eid(self, cnx): diff -r 3cc6154b94a3 -r 9ca33768473c server/sources/native.py --- 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 """ + """return a tuple (type, extid, source) for the entity with id """ 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 """ + """return a tuple (type, extid, source) for the entity with id """ 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) diff -r 3cc6154b94a3 -r 9ca33768473c server/sources/rql2sql.py --- 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 diff -r 3cc6154b94a3 -r 9ca33768473c server/sqlutils.py --- 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 diff -r 3cc6154b94a3 -r 9ca33768473c server/test/data-migractions/migratedapp/schema.py --- 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) diff -r 3cc6154b94a3 -r 9ca33768473c server/test/data/schema.py --- 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) diff -r 3cc6154b94a3 -r 9ca33768473c server/test/datacomputed/migratedapp/schema.py --- 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' diff -r 3cc6154b94a3 -r 9ca33768473c server/test/datacomputed/schema.py --- 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' diff -r 3cc6154b94a3 -r 9ca33768473c server/test/unittest_ldapsource.py --- 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() diff -r 3cc6154b94a3 -r 9ca33768473c server/test/unittest_migractions.py --- 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): diff -r 3cc6154b94a3 -r 9ca33768473c server/test/unittest_querier.py --- 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): diff -r 3cc6154b94a3 -r 9ca33768473c server/test/unittest_sources_native.py --- /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 . + +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() diff -r 3cc6154b94a3 -r 9ca33768473c web/test/unittest_form.py --- 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 diff -r 3cc6154b94a3 -r 9ca33768473c web/test/unittest_views_actions.py --- 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() diff -r 3cc6154b94a3 -r 9ca33768473c web/test/unittest_views_editforms.py --- 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): diff -r 3cc6154b94a3 -r 9ca33768473c web/views/actions.py --- 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 diff -r 3cc6154b94a3 -r 9ca33768473c web/views/autoform.py --- 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 ############################################## diff -r 3cc6154b94a3 -r 9ca33768473c web/views/debug.py --- 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'%s%s' % ( _('data directory url'), req.datadir_url)) w(u'') - 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'

%s

' % _('opened web sessions')) if sessions: