--- 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