merge stable changes into default
authorNicolas Chauvat <nicolas.chauvat@logilab.fr>
Mon, 07 Mar 2011 17:02:15 +0100
changeset 7040 9b1f9bc74f5d
parent 7025 fee3a1f28ed7 (current diff)
parent 7039 df0e8581b06f (diff)
child 7044 7279de21df34
merge stable changes into default
__pkginfo__.py
devtools/testlib.py
server/checkintegrity.py
server/serverctl.py
server/sources/__init__.py
server/sources/ldapuser.py
--- a/cwconfig.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/cwconfig.py	Mon Mar 07 17:02:15 2011 +0100
@@ -771,7 +771,7 @@
         self.debug('%s loaded', sitefile)
         return module
 
-    def eproperty_definitions(self):
+    def cwproperty_definitions(self):
         cfg = self.persistent_options_configuration()
         for section, options in cfg.options_by_section():
             section = section.lower()
--- a/cwctl.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/cwctl.py	Mon Mar 07 17:02:15 2011 +0100
@@ -664,10 +664,11 @@
     name = 'upgrade'
     actionverb = 'upgraded'
     options = InstanceCommand.options + (
-        ('force-componant-version',
-         {'short': 't', 'type' : 'csv', 'metavar': 'cube1=X.Y.Z,cube2=X.Y.Z',
+        ('force-cube-version',
+         {'short': 't', 'type' : 'named', 'metavar': 'cube1:X.Y.Z,cube2:X.Y.Z',
           'default': None,
-          'help': 'force migration from the indicated  version for the specified cube.'}),
+          'help': 'force migration from the indicated version for the specified cube(s).'}),
+
         ('force-cubicweb-version',
          {'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z',
           'default': None,
@@ -721,12 +722,9 @@
         mih = config.migration_handler()
         repo = mih.repo_connect()
         vcconf = repo.get_versions()
-        if self.config.force_componant_version:
-            packversions = {}
-            for vdef in self.config.force_componant_version:
-                componant, version = vdef.split('=')
-                packversions[componant] = Version(version)
-            vcconf.update(packversions)
+        if self.config.force_cube_version:
+            for cube, version in self.config.force_cube_version.iteritems():
+                vcconf[cube] = Version(version)
         toupgrade = []
         for cube in config.cubes():
             installedversion = config.cube_version(cube)
--- a/cwvreg.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/cwvreg.py	Mon Mar 07 17:02:15 2011 +0100
@@ -554,7 +554,7 @@
         if not self.initialized:
             self['propertydefs'] = {}
             self['propertyvalues'] = self.eprop_values = {}
-            for key, propdef in self.config.eproperty_definitions():
+            for key, propdef in self.config.cwproperty_definitions():
                 self.register_property(key, **propdef)
         CW_EVENT_MANAGER.emit('after-registry-reset', self)
 
--- a/dataimport.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/dataimport.py	Mon Mar 07 17:02:15 2011 +0100
@@ -339,6 +339,7 @@
     def rql(self, *args):
         if self._rql is not None:
             return self._rql(*args)
+        return []
 
     @property
     def nb_inserted_entities(self):
@@ -470,7 +471,7 @@
         eid_from, rtype, eid_to = super(RQLObjectStore, self).relate(
             eid_from, rtype, eid_to)
         self.rql('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype,
-                  {'x': int(eid_from), 'y': int(eid_to)}, ('x', 'y'))
+                 {'x': int(eid_from), 'y': int(eid_to)})
 
 
 # the import controller ########################################################
@@ -513,7 +514,6 @@
             traceback.print_exc(file=tmp)
         else:
             traceback.print_exception(type, value, tb, file=tmp)
-        print tmp.getvalue()
         # use a list to avoid counting a <nb lines> errors instead of one
         errorlog = self.errors.setdefault(key, [])
         if msg is None:
--- a/devtools/__init__.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/devtools/__init__.py	Mon Mar 07 17:02:15 2011 +0100
@@ -85,20 +85,10 @@
     read_instance_schema = False
     init_repository = True
     db_require_setup = True
-    options = cwconfig.merge_options(ServerConfiguration.options + (
-        ('anonymous-user',
-         {'type' : 'string',
-          'default': None,
-          'help': 'login of the CubicWeb user account to use for anonymous user (if you want to allow anonymous)',
-          'group': 'main', 'level': 1,
-          }),
-        ('anonymous-password',
-         {'type' : 'string',
-          'default': None,
-          'help': 'password of the CubicWeb user account matching login',
-          'group': 'main', 'level': 1,
-          }),
-        ))
+    options = cwconfig.merge_options(
+        ServerConfiguration.options +
+        tuple((opt, optdict) for opt, optdict in TwistedConfiguration.options
+              if opt in ('anonymous-user', 'anonymous-password')))
 
     def __init__(self, appid, apphome=None, log_threshold=logging.CRITICAL+10):
         # must be set before calling parent __init__
--- a/devtools/testlib.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/devtools/testlib.py	Mon Mar 07 17:02:15 2011 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -355,7 +355,7 @@
         user = req.create_entity('CWUser', login=unicode(login),
                                  upassword=password, **kwargs)
         req.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'
-                    % ','.join(repr(g) for g in groups),
+                    % ','.join(repr(str(g)) for g in groups),
                     {'x': user.eid})
         user.cw_clear_relation_cache('in_group', 'subject')
         if commit:
@@ -434,6 +434,21 @@
 
     # other utilities #########################################################
 
+    def grant_permission(self, entity, group, pname, plabel=None):
+        """insert a permission on an entity. Will have to commit the main
+        connection to be considered
+        """
+        pname = unicode(pname)
+        plabel = plabel and unicode(plabel) or unicode(group)
+        e = entity.eid
+        with security_enabled(self.session, False, False):
+            peid = self.execute(
+            'INSERT CWPermission X: X name %(pname)s, X label %(plabel)s,'
+            'X require_group G, E require_permission X '
+            'WHERE G name %(group)s, E eid %(e)s',
+            locals())[0][0]
+        return peid
+
     @contextmanager
     def temporary_appobjects(self, *appobjects):
         self.vreg._loadedmods.setdefault(self.__module__, {})
@@ -445,7 +460,20 @@
             for obj in appobjects:
                 self.vreg.unregister(obj)
 
-    # vregistry inspection utilities ###########################################
+    def assertModificationDateGreater(self, entity, olddate):
+        entity.cw_attr_cache.pop('modification_date', None)
+        self.failUnless(entity.modification_date > olddate)
+
+
+    # workflow utilities #######################################################
+
+    def assertPossibleTransitions(self, entity, expected):
+        transitions = entity.cw_adapt_to('IWorkflowable').possible_transitions()
+        self.assertListEqual(sorted(tr.name for tr in transitions),
+                             sorted(expected))
+
+
+    # views and actions registries inspection ##################################
 
     def pviews(self, req, rset):
         return sorted((a.__regid__, a.__class__)
--- a/misc/migration/3.10.9_Any.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/misc/migration/3.10.9_Any.py	Mon Mar 07 17:02:15 2011 +0100
@@ -1,3 +1,10 @@
+from __future__ import with_statement
+
+# fix some corrupted entities noticed on several instances
+rql('DELETE CWConstraint X WHERE NOT E constrained_by X')
+rql('SET X is_instance_of Y WHERE X is Y, NOT X is_instance_of Y')
+commit()
+
 if confirm('fix existing cwuri?'):
     from logilab.common.shellutils import ProgressBar
     from cubicweb.server.session import hooks_control
@@ -10,3 +17,11 @@
                 commit(ask_confirm=False)
             pb.update()
     commit(ask_confirm=False)
+
+try:
+    from cubicweb import devtools
+    option_group_changed('anonymous-user', 'main', 'web')
+    option_group_changed('anonymous-password', 'main', 'web')
+except ImportError:
+    # cubicweb-dev unavailable, nothing needed
+    pass
--- a/server/checkintegrity.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/server/checkintegrity.py	Mon Mar 07 17:02:15 2011 +0100
@@ -34,6 +34,12 @@
 from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.session import security_enabled
 
+def notify_fixed(fix):
+    if fix:
+        print >> sys.stderr, ' [FIXED]'
+    else:
+        print >> sys.stderr
+
 def has_eid(session, sqlcursor, eid, eids):
     """return true if the eid is a valid eid"""
     if eid in eids:
@@ -167,9 +173,7 @@
             print >> sys.stderr, msg % eid,
             if fix:
                 session.system_sql('DELETE FROM appears WHERE uid=%s;' % eid)
-                print >> sys.stderr, ' [FIXED]'
-            else:
-                print >> sys.stderr
+            notify_fixed(fix)
 
 
 def check_entities(schema, session, eids, fix=1):
@@ -183,9 +187,7 @@
             print >> sys.stderr, msg % eid,
             if fix:
                 session.system_sql('DELETE FROM entities WHERE eid=%s;' % eid)
-                print >> sys.stderr, ' [FIXED]'
-            else:
-                print >> sys.stderr
+            notify_fixed(fix)
     print 'Checking entities tables'
     for eschema in schema.entities():
         if eschema.final:
@@ -202,22 +204,19 @@
                 print >> sys.stderr, msg % (eid, eschema.type),
                 if fix:
                     session.system_sql('DELETE FROM %s WHERE %s=%s;' % (table, column, eid))
-                    print >> sys.stderr, ' [FIXED]'
-                else:
-                    print >> sys.stderr
+                notify_fixed(fix)
 
 
 def bad_related_msg(rtype, target, eid, fix):
     msg = '  A relation %s with %s eid %s exists but no such entity in sources'
     print >> sys.stderr, msg % (rtype, target, eid),
-    if fix:
-        print >> sys.stderr, ' [FIXED]'
-    else:
-        print >> sys.stderr
+    notify_fixed(fix)
 
 
 def check_relations(schema, session, eids, fix=1):
-    """check all relations registered in the repo system table"""
+    """check that eids referenced by relations are registered in the repo system
+    table
+    """
     print 'Checking relations'
     for rschema in schema.relations():
         if rschema.final or rschema in PURE_VIRTUAL_RTYPES:
@@ -263,6 +262,54 @@
                     session.system_sql(sql)
 
 
+def check_mandatory_relations(schema, session, eids, fix=1):
+    """check entities missing some mandatory relation"""
+    print 'Checking mandatory relations'
+    for rschema in schema.relations():
+        if rschema.final or rschema in PURE_VIRTUAL_RTYPES:
+            continue
+        smandatory = set()
+        omandatory = set()
+        for rdef in rschema.rdefs.values():
+            if rdef.cardinality[0] in '1+':
+                smandatory.add(rdef.subject)
+            if rdef.cardinality[1] in '1+':
+                omandatory.add(rdef.object)
+        for role, etypes in (('subject', smandatory), ('object', omandatory)):
+            for etype in etypes:
+                if role == 'subject':
+                    rql = 'Any X WHERE NOT X %s Y, X is %s' % (rschema, etype)
+                else:
+                    rql = 'Any X WHERE NOT Y %s X, X is %s' % (rschema, etype)
+                for entity in session.execute(rql).entities():
+                    print >> sys.stderr, '%s #%s is missing mandatory %s relation %s' % (
+                        entity.__regid__, entity.eid, role, rschema)
+                    if fix:
+                        #if entity.cw_describe()['source']['uri'] == 'system': XXX
+                        entity.delete()
+                    notify_fixed(fix)
+
+
+def check_mandatory_attributes(schema, session, eids, fix=1):
+    """check for entities stored in the system source missing some mandatory
+    attribute
+    """
+    print 'Checking mandatory attributes'
+    for rschema in schema.relations():
+        if not rschema.final or rschema in VIRTUAL_RTYPES:
+            continue
+        for rdef in rschema.rdefs.values():
+            if rdef.cardinality[0] in '1+':
+                rql = 'Any X WHERE X %s NULL, X is %s, X cw_source S, S name "system"' % (
+                    rschema, rdef.subject)
+                for entity in session.execute(rql).entities():
+                    print >> sys.stderr, '%s #%s is missing mandatory attribute %s' % (
+                        entity.__regid__, entity.eid, rschema)
+                    if fix:
+                        entity.delete()
+                    notify_fixed(fix)
+
+
 def check_metadata(schema, session, eids, fix=1):
     """check entities has required metadata
 
@@ -285,9 +332,7 @@
                     session.system_sql("UPDATE %s SET %s=%%(v)s WHERE %s=%s ;"
                                        % (table, column, eidcolumn, eid),
                                        {'v': default})
-                    print >> sys.stderr, ' [FIXED]'
-                else:
-                    print >> sys.stderr
+                notify_fixed(fix)
     cursor = session.system_sql('SELECT MIN(%s) FROM %sCWUser;' % (eidcolumn,
                                                                   SQL_PREFIX))
     default_user_eid = cursor.fetchone()[0]
@@ -303,9 +348,7 @@
             if fix:
                 session.system_sql('INSERT INTO %s_relation VALUES (%s, %s) ;'
                                    % (rel, eid, default))
-                print >> sys.stderr, ' [FIXED]'
-            else:
-                print >> sys.stderr
+            notify_fixed(fix)
 
 
 def check(repo, cnx, checks, reindex, fix, withpb=True):
--- a/server/querier.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/server/querier.py	Mon Mar 07 17:02:15 2011 +0100
@@ -586,12 +586,12 @@
     def set_schema(self, schema):
         self.schema = schema
         repo = self._repo
-        # rql st and solution cache. Don't bother using a Cache instance: we
-        # should have a limited number of queries in there, since there are no
-        # entries in this cache for user queries (which have no args)
-        self._rql_cache = {}
-        # rql cache key cache
-        self._rql_ck_cache = Cache(repo.config['rql-cache-size'])
+        # rql st and solution cache.
+        self._rql_cache = Cache(repo.config['rql-cache-size'])
+        # rql cache key cache. Don't bother using a Cache instance: we should
+        # have a limited number of queries in there, since there are no entries
+        # in this cache for user queries (which have no args)
+        self._rql_ck_cache = {}
         # some cache usage stats
         self.cache_hit, self.cache_miss = 0, 0
         # rql parsing / analysing helper
@@ -656,11 +656,15 @@
                 print '*'*80
             print 'querier input', rql, args
         # parse the query and binds variables
+        cachekey = rql
         try:
-            cachekey = rql
             if args:
+                # search for named args in query which are eids (hence
+                # influencing query's solutions)
                 eidkeys = self._rql_ck_cache[rql]
                 if eidkeys:
+                    # if there are some, we need a better cache key, eg (rql +
+                    # entity type of each eid)
                     try:
                         cachekey = self._repo.querier_cache_key(session, rql,
                                                                 args, eidkeys)
@@ -674,15 +678,20 @@
             self.cache_miss += 1
             rqlst = self.parse(rql)
             try:
+                # compute solutions for rqlst and return named args in query
+                # which are eids. Notice that if you may not need `eidkeys`, we
+                # have to compute solutions anyway (kept as annotation on the
+                # tree)
                 eidkeys = self.solutions(session, rqlst, args)
             except UnknownEid:
                 # we want queries such as "Any X WHERE X eid 9999" return an
                 # empty result instead of raising UnknownEid
                 return empty_rset(rql, args, rqlst)
-            self._rql_ck_cache[rql] = eidkeys
-            if eidkeys:
-                cachekey = self._repo.querier_cache_key(session, rql, args,
-                                                        eidkeys)
+            if args and not rql in self._rql_ck_cache:
+                self._rql_ck_cache[rql] = eidkeys
+                if eidkeys:
+                    cachekey = self._repo.querier_cache_key(session, rql, args,
+                                                            eidkeys)
             self._rql_cache[cachekey] = rqlst
         orig_rqlst = rqlst
         if rqlst.TYPE != 'select':
--- a/server/serverctl.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/server/serverctl.py	Mon Mar 07 17:02:15 2011 +0100
@@ -388,6 +388,10 @@
           'default': False,
           'help': 'insert drop statements to remove previously existant \
 tables, indexes... (no by default)'}),
+        ('config-level',
+         {'short': 'l', 'type': 'int', 'default': 1,
+          'help': 'level threshold for questions asked when configuring another source'
+          }),
         )
 
     def run(self, args):
@@ -412,7 +416,7 @@
                 % (config.sources_file(), str(ex).strip()))
         init_repository(config, drop=self.config.drop)
         while ASK.confirm('Enter another source ?', default_is_yes=False):
-            CWCTL.run(['add-source', config.appid])
+            CWCTL.run(['add-source', '--config-level', self.config.config_level, config.appid])
 
 
 class AddSourceCommand(Command):
@@ -424,7 +428,12 @@
     name = 'add-source'
     arguments = '<instance>'
     min_args = max_args = 1
-    options = ()
+    options = (
+        ('config-level',
+         {'short': 'l', 'type': 'int', 'default': 1,
+          'help': 'level threshold for questions asked when configuring another source'
+          }),
+        )
 
     def run(self, args):
         appid = args[0]
@@ -463,7 +472,7 @@
                 else:
                     break
         # XXX configurable inputlevel
-        sconfig = ask_source_config(config, type, inputlevel=0)
+        sconfig = ask_source_config(config, type, inputlevel=self.config.config_level)
         cfgstr = unicode(generate_source_config(sconfig), sys.stdin.encoding)
         req.create_entity('CWSource', name=sourceuri,
                           type=unicode(type), config=cfgstr)
@@ -840,9 +849,12 @@
     options = (
         ('checks',
          {'short': 'c', 'type' : 'csv', 'metavar' : '<check list>',
-          'default' : ('entities', 'relations', 'metadata', 'schema', 'text_index'),
+          'default' : ('entities', 'relations',
+                       'mandatory_relations', 'mandatory_attributes',
+                       'metadata', 'schema', 'text_index'),
           'help': 'Comma separated list of check to run. By default run all \
-checks, i.e. entities, relations, text_index and metadata.'}
+checks, i.e. entities, relations, mandatory_relations, mandatory_attributes, \
+metadata, text_index and schema.'}
          ),
 
         ('autofix',
--- a/server/sources/__init__.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/server/sources/__init__.py	Mon Mar 07 17:02:15 2011 +0100
@@ -572,7 +572,9 @@
         pass
     def cursor(self):
         return None # no actual cursor support
-
+    def close(self):
+        if hasattr(self.cnx, 'close'):
+            self.cnx.close()
 
 from cubicweb.server import SOURCE_TYPES
 
--- a/server/sources/ldapuser.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/server/sources/ldapuser.py	Mon Mar 07 17:02:15 2011 +0100
@@ -95,13 +95,13 @@
          {'type' : 'string',
           'default': '',
           'help': 'user dn to use to open data connection to the ldap (eg used \
-to respond to rql queries).',
+to respond to rql queries). Leave empty for anonymous bind',
           'group': 'ldap-source', 'level': 1,
           }),
         ('data-cnx-password',
          {'type' : 'string',
           'default': '',
-          'help': 'password to use to open data connection to the ldap (eg used to respond to rql queries).',
+          'help': 'password to use to open data connection to the ldap (eg used to respond to rql queries). Leave empty for anonymous bind.',
           'group': 'ldap-source', 'level': 1,
           }),
 
@@ -109,19 +109,19 @@
          {'type' : 'string',
           'default': 'ou=People,dc=logilab,dc=fr',
           'help': 'base DN to lookup for users',
-          'group': 'ldap-source', 'level': 0,
+          'group': 'ldap-source', 'level': 1,
           }),
         ('user-scope',
          {'type' : 'choice',
           'default': 'ONELEVEL',
           'choices': ('BASE', 'ONELEVEL', 'SUBTREE'),
-          'help': 'user search scope',
+          'help': 'user search scope (valid values: "BASE", "ONELEVEL", "SUBTREE")',
           'group': 'ldap-source', 'level': 1,
           }),
         ('user-classes',
          {'type' : 'csv',
           'default': ('top', 'posixAccount'),
-          'help': 'classes of user',
+          'help': 'classes of user (with Active Directory, you want to say "user" here)',
           'group': 'ldap-source', 'level': 1,
           }),
         ('user-filter',
@@ -133,7 +133,7 @@
         ('user-login-attr',
          {'type' : 'string',
           'default': 'uid',
-          'help': 'attribute used as login on authentication',
+          'help': 'attribute used as login on authentication (with Active Directory, you want to use "sAMAccountName" here)',
           'group': 'ldap-source', 'level': 1,
           }),
         ('user-default-group',
@@ -146,7 +146,7 @@
         ('user-attrs-map',
          {'type' : 'named',
           'default': {'uid': 'login', 'gecos': 'email'},
-          'help': 'map from ldap user attributes to cubicweb attributes',
+          'help': 'map from ldap user attributes to cubicweb attributes (with Active Directory, you want to use sAMAccountName:login,mail:email,givenName:firstname,sn:surname)',
           'group': 'ldap-source', 'level': 1,
           }),
 
@@ -273,7 +273,7 @@
             try:
                 self._connect()
             except:
-                self.exception('cant connect to ldap')
+                self.exception('unable to connect to ldap:')
         return ConnectionWrapper(self._conn)
 
     def authenticate(self, session, login, password=None, **kwargs):
--- a/web/webconfig.py	Fri Feb 18 17:11:45 2011 +0100
+++ b/web/webconfig.py	Mon Mar 07 17:02:15 2011 +0100
@@ -207,8 +207,8 @@
     def fckeditor_installed(self):
         return exists(self.uiprops['FCKEDITOR_PATH'])
 
-    def eproperty_definitions(self):
-        for key, pdef in super(WebConfiguration, self).eproperty_definitions():
+    def cwproperty_definitions(self):
+        for key, pdef in super(WebConfiguration, self).cwproperty_definitions():
             if key == 'ui.fckeditor' and not self.fckeditor_installed():
                 continue
             yield key, pdef