backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 04 Aug 2010 11:13:11 +0200
changeset 6066 953578709324
parent 6055 bb7bd9cafacf (current diff)
parent 6064 2a164fabcbfc (diff)
child 6067 efca814587e2
backport stable
dbapi.py
server/repository.py
web/webconfig.py
--- a/dbapi.py	Mon Aug 02 15:37:45 2010 +0200
+++ b/dbapi.py	Wed Aug 04 11:13:11 2010 +0200
@@ -100,9 +100,9 @@
     else: # method == 'pyro'
         # resolve the Pyro object
         from logilab.common.pyro_ext import ns_get_proxy
+        pyroid = database or config['pyro-instance-id'] or config.appid
         try:
-            return ns_get_proxy(database,
-                                defaultnsgroup=config['pyro-ns-group'],
+            return ns_get_proxy(pyroid, defaultnsgroup=config['pyro-ns-group'],
                                 nshost=config['pyro-ns-host'])
         except Exception, ex:
             raise ConnectionError(str(ex))
--- a/doc/book/en/devrepo/migration.rst	Mon Aug 02 15:37:45 2010 +0200
+++ b/doc/book/en/devrepo/migration.rst	Wed Aug 04 11:13:11 2010 +0200
@@ -182,6 +182,40 @@
 
 * `option_removed(oldname, newname)`, indicates that an option has been deleted.
 
+The `config` variable is an object which can be used to access the
+configuration values, for reading and updating, with a dictionary-like
+syntax. 
+
+Example 1: migration script changing the variable 'sender-addr' in
+all-in-one.conf. The script also checks that in that the instance is
+configured with a known value for that variable, and only updates the
+value in that case.
+
+.. sourcecode:: python
+
+ wrong_addr = 'cubicweb@loiglab.fr' # known wrong address
+ fixed_addr = 'cubicweb@logilab.fr'
+ configured_addr = config.get('sender-addr')
+ # check that the address has not been hand fixed by a sysadmin
+ if configured_addr == wrong_addr: 
+     config['sender-addr'] = fixed-addr
+     config.save()
+
+Example 2: checking the value of the database backend driver, which
+can be useful in case you need to issue backend-dependent raw SQL
+queries in a migration script.
+
+.. sourcecode:: python
+
+ dbdriver  = config.sources()['system']['db-driver']
+ if dbdriver == "sqlserver2005":
+     # this is now correctly handled by CW :-)
+     sql('ALTER TABLE cw_Xxxx ALTER COLUMN cw_name varchar(64) NOT NULL;')
+     commit()
+ else: # postgresql
+     sync_schema_props_perms(ertype=('Xxxx', 'name', 'String'),
+     syncperms=False)
+
 
 Others migration functions
 --------------------------
--- a/server/msplanner.py	Mon Aug 02 15:37:45 2010 +0200
+++ b/server/msplanner.py	Wed Aug 04 11:13:11 2010 +0200
@@ -419,9 +419,9 @@
                         self._set_source_for_term(source, const)
                         # if system source is used, add every rewritten constant
                         # to its supported terms even when associated entity
-                        # doesn't actually come from it so we get a changes
-                        # that allequals will return True as expected when
-                        # computing needsplit
+                        # doesn't actually come from it so we get a changes that
+                        # allequals will return True as expected when computing
+                        # needsplit
                         # check const is used in a relation restriction
                         if const.relation() and self.system_source in sourcesterms:
                             self._set_source_for_term(self.system_source, const)
@@ -432,7 +432,7 @@
             # process non final relations only
             # note: don't try to get schema for 'is' relation (not available
             # during bootstrap)
-            if not rel.is_types_restriction() and not rschema(rel.r_type).final:
+            if not (rel.is_types_restriction() or rschema(rel.r_type).final):
                 # nothing to do if relation is not supported by multiple sources
                 # or if some source has it listed in its cross_relations
                 # attribute
@@ -1429,8 +1429,8 @@
         if not node.is_types_restriction():
             if node in self.skip and self.solindices.issubset(self.skip[node]):
                 if not self.schema.rschema(node.r_type).final:
-                    # can't really skip the relation if one variable is selected and only
-                    # referenced by this relation
+                    # can't really skip the relation if one variable is selected
+                    # and only referenced by this relation
                     for vref in node.iget_nodes(VariableRef):
                         stinfo = vref.variable.stinfo
                         if stinfo['selected'] and len(stinfo['relations']) == 1:
@@ -1441,13 +1441,14 @@
                     return None, node
             if not self._relation_supported(node):
                 raise UnsupportedBranch()
-        # don't copy type restriction unless this is the only relation for the
-        # rhs variable, else they'll be reinserted later as needed (else we may
-        # copy a type restriction while the variable is not actually used)
-        elif not any(self._relation_supported(rel)
-                     for rel in node.children[0].variable.stinfo['relations']):
-            rel, node = self.visit_default(node, newroot, terms)
-            return rel, node
+        # don't copy type restriction unless this is the only supported relation
+        # for the lhs variable, else they'll be reinserted later as needed (in
+        # other cases we may copy a type restriction while the variable is not
+        # actually used)
+        elif not (node.neged(strict=True) or
+                  any(self._relation_supported(rel)
+                      for rel in node.children[0].variable.stinfo['relations'])):
+            return self.visit_default(node, newroot, terms)
         else:
             raise UnsupportedBranch()
         rschema = self.schema.rschema(node.r_type)
--- a/server/repository.py	Mon Aug 02 15:37:45 2010 +0200
+++ b/server/repository.py	Wed Aug 04 11:13:11 2010 +0200
@@ -1227,15 +1227,17 @@
 
     def pyro_register(self, host=''):
         """register the repository as a pyro object"""
-        import tempfile
-        from logilab.common.pyro_ext import register_object, config
-        config.PYRO_STORAGE = tempfile.gettempdir() # XXX until lgc > 0.45.1 is out
-        appid = self.config['pyro-instance-id'] or self.config.appid
-        daemon = register_object(self, appid, self.config['pyro-ns-group'],
-                                 self.config['pyro-host'],
-                                 self.config['pyro-ns-host'])
-        msg = 'repository registered as a pyro object using group %s and id %s'
-        self.info(msg, self.config['pyro-ns-group'], appid)
+        from logilab.common import pyro_ext as pyro
+        config = self.config
+        appid = '%s.%s' % pyro.ns_group_and_id(
+            config['pyro-instance-id'] or config.appid,
+            config['pyro-ns-group'])
+        # ensure config['pyro-instance-id'] is a full qualified pyro name
+        config['pyro-instance-id'] = appid
+        daemon = pyro.register_object(self, appid,
+                                      daemonhost=config['pyro-host'],
+                                      nshost=config['pyro-ns-host'])
+        self.info('repository registered as a pyro object %s', appid)
         self.pyro_registered = True
         return daemon
 
--- a/server/serverctl.py	Mon Aug 02 15:37:45 2010 +0200
+++ b/server/serverctl.py	Wed Aug 04 11:13:11 2010 +0200
@@ -42,32 +42,35 @@
     given server.serverconfig
     """
     from getpass import getpass
-    from logilab.database import get_connection
+    from logilab.database import get_connection, get_db_helper
     dbhost = source.get('db-host')
     if dbname is None:
         dbname = source['db-name']
     driver = source['db-driver']
+    dbhelper = get_db_helper(driver)
     if verbose:
         print '-> connecting to %s database' % driver,
         if dbhost:
             print '%s@%s' % (dbname, dbhost),
         else:
             print dbname,
-    if not verbose or (not special_privs and source.get('db-user')):
-        user = source['db-user']
-        if verbose:
-            print 'as', user
-        if source.get('db-password'):
-            password = source['db-password']
+    if dbhelper.users_support:
+        if not verbose or (not special_privs and source.get('db-user')):
+            user = source['db-user']
+            if verbose:
+                print 'as', user
+            if source.get('db-password'):
+                password = source['db-password']
+            else:
+                password = getpass('password: ')
         else:
-            password = getpass('password: ')
-    else:
-        print
-        if special_privs:
-            print 'WARNING'
-            print 'the user will need the following special access rights on the database:'
-            print special_privs
             print
+            if special_privs:
+                print 'WARNING'
+                print ('the user will need the following special access rights '
+                       'on the database:')
+                print special_privs
+                print
         default_user = source.get('db-user', os.environ.get('USER', ''))
         user = raw_input('Connect as user ? [%r]: ' % default_user)
         user = user or default_user
@@ -75,6 +78,8 @@
             password = source['db-password']
         else:
             password = getpass('password: ')
+    else:
+        user = password = None
     extra_args = source.get('db-extra-arguments')
     extra = extra_args and {'extra_args': extra_args} or {}
     cnx = get_connection(driver, dbhost, dbname, user, password=password,
--- a/server/sources/__init__.py	Mon Aug 02 15:37:45 2010 +0200
+++ b/server/sources/__init__.py	Wed Aug 04 11:13:11 2010 +0200
@@ -182,7 +182,7 @@
             wsupport = self.support_relations[rtype]
         except KeyError:
             rschema = self.schema.rschema(rtype)
-            if not rschema.final or rschema == 'has_text':
+            if not rschema.final or rschema.type == 'has_text':
                 return False
             for etype in rschema.subjects():
                 try:
--- a/server/sources/pyrorql.py	Mon Aug 02 15:37:45 2010 +0200
+++ b/server/sources/pyrorql.py	Wed Aug 04 11:13:11 2010 +0200
@@ -131,8 +131,10 @@
         execfile(mappingfile, mapping)
         self.support_entities = mapping['support_entities']
         self.support_relations = mapping.get('support_relations', {})
-        self.dont_cross_relations = mapping.get('dont_cross_relations', ())
-        self.cross_relations = mapping.get('cross_relations', ())
+        self.dont_cross_relations = set(mapping.get('dont_cross_relations', ()))
+        self.cross_relations = set(mapping.get('cross_relations', ()))
+        self.dont_cross_relations.add('owned_by')
+        self.dont_cross_relations.add('created_by')
         baseurl = source_config.get('base-url')
         if baseurl and not baseurl.endswith('/'):
             source_config['base-url'] += '/'
--- a/server/test/unittest_msplanner.py	Mon Aug 02 15:37:45 2010 +0200
+++ b/server/test/unittest_msplanner.py	Wed Aug 04 11:13:11 2010 +0200
@@ -2196,14 +2196,66 @@
                    {'x': 999999})
 
 
+    def test_nonregr_not_is(self):
+        self._test("Any X WHERE X owned_by U, U login 'anon', NOT X is Comment",
+                   [('FetchStep', [('Any X WHERE X is IN(Card, Note, State)',
+                                    [{'X': 'Note'}, {'X': 'State'}, {'X': 'Card'}])],
+                     [self.cards, self.cards2, self.system],
+                     None, {'X': 'table0.C0'}, []),
+                    ('UnionStep', None, None,
+                     [('OneFetchStep',
+                       [(u'Any X WHERE X owned_by U, U login "anon", U is CWUser, X is IN(Affaire, BaseTransition, Basket, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUser, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)',
+                         [{'U': 'CWUser', 'X': 'Affaire'},
+                          {'U': 'CWUser', 'X': 'BaseTransition'},
+                          {'U': 'CWUser', 'X': 'Basket'},
+                          {'U': 'CWUser', 'X': 'Bookmark'},
+                          {'U': 'CWUser', 'X': 'CWAttribute'},
+                          {'U': 'CWUser', 'X': 'CWCache'},
+                          {'U': 'CWUser', 'X': 'CWConstraint'},
+                          {'U': 'CWUser', 'X': 'CWConstraintType'},
+                          {'U': 'CWUser', 'X': 'CWEType'},
+                          {'U': 'CWUser', 'X': 'CWGroup'},
+                          {'U': 'CWUser', 'X': 'CWPermission'},
+                          {'U': 'CWUser', 'X': 'CWProperty'},
+                          {'U': 'CWUser', 'X': 'CWRType'},
+                          {'U': 'CWUser', 'X': 'CWRelation'},
+                          {'U': 'CWUser', 'X': 'CWUser'},
+                          {'U': 'CWUser', 'X': 'Division'},
+                          {'U': 'CWUser', 'X': 'Email'},
+                          {'U': 'CWUser', 'X': 'EmailAddress'},
+                          {'U': 'CWUser', 'X': 'EmailPart'},
+                          {'U': 'CWUser', 'X': 'EmailThread'},
+                          {'U': 'CWUser', 'X': 'ExternalUri'},
+                          {'U': 'CWUser', 'X': 'File'},
+                          {'U': 'CWUser', 'X': 'Folder'},
+                          {'U': 'CWUser', 'X': 'Personne'},
+                          {'U': 'CWUser', 'X': 'RQLExpression'},
+                          {'U': 'CWUser', 'X': 'Societe'},
+                          {'U': 'CWUser', 'X': 'SubDivision'},
+                          {'U': 'CWUser', 'X': 'SubWorkflowExitPoint'},
+                          {'U': 'CWUser', 'X': 'Tag'},
+                          {'U': 'CWUser', 'X': 'TrInfo'},
+                          {'U': 'CWUser', 'X': 'Transition'},
+                          {'U': 'CWUser', 'X': 'Workflow'},
+                          {'U': 'CWUser', 'X': 'WorkflowTransition'}])],
+                       None, None,
+                       [self.system], {}, []),
+                      ('OneFetchStep',
+                       [(u'Any X WHERE X owned_by U, U login "anon", U is CWUser, X is IN(Card, Note, State)',
+                         [{'U': 'CWUser', 'X': 'Note'},
+                          {'U': 'CWUser', 'X': 'State'},
+                          {'U': 'CWUser', 'X': 'Card'}])],
+                       None, None,
+                       [self.system], {'X': 'table0.C0'}, [])
+                      ])
+                    ])
+
 
 class FakeVCSSource(AbstractSource):
     uri = 'ccc'
     support_entities = {'Card': True, 'Note': True}
     support_relations = {'multisource_inlined_rel': True,
                          'multisource_rel': True}
-    #dont_cross_relations = set(('fiche', 'in_state'))
-    #cross_relations = set(('multisource_crossed_rel',))
 
     def syntax_tree_search(self, *args, **kwargs):
         return []
--- a/web/request.py	Mon Aug 02 15:37:45 2010 +0200
+++ b/web/request.py	Wed Aug 04 11:13:11 2010 +0200
@@ -36,8 +36,8 @@
 
 from cubicweb.dbapi import DBAPIRequest
 from cubicweb.mail import header
-from cubicweb.uilib import remove_html_tags
-from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid, json_dumps
+from cubicweb.uilib import remove_html_tags, js
+from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid
 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
                           RequestError, StatusResponse)
@@ -342,23 +342,37 @@
             return breadcrumbs.pop()
         return self.base_url()
 
-    def user_rql_callback(self, args, msg=None):
+    def user_rql_callback(self, rqlargs, *args, **kwargs):
         """register a user callback to execute some rql query and return an url
-        to call it ready to be inserted in html
+        to call it ready to be inserted in html.
+
+        rqlargs should be a tuple containing argument to give to the execute function.
+
+        For other allowed arguments, see :meth:`user_callback` method
         """
         def rqlexec(req, rql, args=None, key=None):
             req.execute(rql, args, key)
-        return self.user_callback(rqlexec, args, msg)
+        return self.user_callback(rqlexec, rqlargs, *args, **kwargs)
+
+    def user_callback(self, cb, cbargs, *args, **kwargs):
+        """register the given user callback and return an url to call it ready
+        to be inserted in html.
 
-    def user_callback(self, cb, args, msg=None, nonify=False):
-        """register the given user callback and return an url to call it ready to be
-        inserted in html
+        You can specify the underlying js function to call using a 'jsfunc'
+        named args, to one of :func:`userCallback`,
+        ':func:`userCallbackThenUpdateUI`, ':func:`userCallbackThenReloadPage`
+        (the default). Take care arguments may vary according to the used
+        function.
         """
         self.add_js('cubicweb.ajax.js')
-        cbname = self.register_onetime_callback(cb, *args)
-        msg = json_dumps(msg or '')
-        return "javascript:userCallbackThenReloadPage('%s', %s)" % (
-            cbname, msg)
+        jsfunc = kwargs.pop('jsfunc', 'userCallbackThenReloadPage')
+        if 'msg' in kwargs:
+            warn('[3.10] msg should be given as positional argument',
+                 DeprecationWarning, stacklevel=2)
+            args = (kwargs.pop('msg'),) + args
+        assert not kwargs, 'dunno what to do with remaining kwargs: %s' % kwargs
+        cbname = self.register_onetime_callback(cb, *cbargs)
+        return "javascript: %s" % getattr(js, jsfunc)(cbname, *args)
 
     def register_onetime_callback(self, func, *args):
         cbname = 'cb_%s' % (
@@ -589,8 +603,8 @@
         """
         extraparams.setdefault('fname', 'view')
         url = self.build_url('json', **extraparams)
-        return "javascript: $('#%s').loadxhtml(%s, null, 'get', '%s'); noop()" % (
-                nodeid, json_dumps(url), replacemode)
+        return "javascript: $('#%s').%s; noop()" % (
+            nodeid, js.loadxtml(url, None, 'get', replacemode))
 
     # urls/path management ####################################################
 
--- a/web/webconfig.py	Mon Aug 02 15:37:45 2010 +0200
+++ b/web/webconfig.py	Wed Aug 04 11:13:11 2010 +0200
@@ -227,11 +227,7 @@
             return self.__repo
         except AttributeError:
             from cubicweb.dbapi import get_repository
-            if self.repo_method == 'inmemory':
-                repo = get_repository('inmemory', vreg=vreg, config=self)
-            else:
-                repo = get_repository('pyro', self['pyro-instance-id'],
-                                      config=self)
+            repo = get_repository(self.repo_method, vreg=vreg, config=self)
             self.__repo = repo
             return repo