# HG changeset patch # User Sylvain Thénault # Date 1280913191 -7200 # Node ID 9535787093244ccc0574caa3845306aaed5dbea8 # Parent bb7bd9cafacfe410e3111100346ae03069f5c3ce# Parent 2a164fabcbfc6944ef00a49c43a511c6b76ada92 backport stable diff -r bb7bd9cafacf -r 953578709324 dbapi.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)) diff -r bb7bd9cafacf -r 953578709324 doc/book/en/devrepo/migration.rst --- 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 -------------------------- diff -r bb7bd9cafacf -r 953578709324 server/msplanner.py --- 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) diff -r bb7bd9cafacf -r 953578709324 server/repository.py --- 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 diff -r bb7bd9cafacf -r 953578709324 server/serverctl.py --- 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, diff -r bb7bd9cafacf -r 953578709324 server/sources/__init__.py --- 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: diff -r bb7bd9cafacf -r 953578709324 server/sources/pyrorql.py --- 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'] += '/' diff -r bb7bd9cafacf -r 953578709324 server/test/unittest_msplanner.py --- 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 [] diff -r bb7bd9cafacf -r 953578709324 web/request.py --- 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 #################################################### diff -r bb7bd9cafacf -r 953578709324 web/webconfig.py --- 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