# HG changeset patch # User Sylvain Thénault # Date 1301476678 -7200 # Node ID ef29d3ea390917579beb591d5b58a39e1242112f # Parent 61166334815826a4a7cf8ab7b2a3b7cfd7a2c0c8# Parent ba51dac1115da19cafdf0cf674f4c4ee3647462e backport stable diff -r 611663348158 -r ef29d3ea3909 .hgtags --- a/.hgtags Thu Mar 24 15:21:13 2011 +0100 +++ b/.hgtags Wed Mar 30 11:17:58 2011 +0200 @@ -186,3 +186,5 @@ 8daabda9f571863e8754f8ab722744c417ba3abf cubicweb-debian-version-3.11.0-1 d0410eb4d8bbf657d7f32b0c681db09b1f8119a0 cubicweb-version-3.11.1 77318f1ec4aae3523d455e884daf3708c3c79af7 cubicweb-debian-version-3.11.1-1 +56ae3cd5f8553678a2b1d4121b61241598d0ca68 cubicweb-version-3.11.2 +954b5b51cd9278eb45d66be1967064d01ab08453 cubicweb-debian-version-3.11.2-1 diff -r 611663348158 -r ef29d3ea3909 __pkginfo__.py --- a/__pkginfo__.py Thu Mar 24 15:21:13 2011 +0100 +++ b/__pkginfo__.py Wed Mar 30 11:17:58 2011 +0200 @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 11, 1) +numversion = (3, 11, 2) version = '.'.join(str(num) for num in numversion) description = "a repository of entities / relations for knowledge management" @@ -40,7 +40,7 @@ ] __depends__ = { - 'logilab-common': '>= 0.55.0', + 'logilab-common': '>= 0.55.2', 'logilab-mtconverter': '>= 0.8.0', 'rql': '>= 0.28.0', 'yams': '>= 0.30.4', diff -r 611663348158 -r ef29d3ea3909 cwconfig.py --- a/cwconfig.py Thu Mar 24 15:21:13 2011 +0100 +++ b/cwconfig.py Wed Mar 30 11:17:58 2011 +0200 @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# 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. @@ -325,7 +325,8 @@ {'type' : 'string', 'default': '', 'help': 'Pyro name server\'s host. If not set, will be detected by a \ -broadcast query. It may contains port information using : notation.', +broadcast query. It may contains port information using : notation. \ +Use "NO_PYRONS" to create a Pyro server but not register to a pyro nameserver', 'group': 'pyro', 'level': 1, }), ('pyro-ns-group', @@ -843,9 +844,8 @@ if not exists(_INSTANCES_DIR): os.makedirs(_INSTANCES_DIR) - # for some commands (creation...) we don't want to initialize gettext - set_language = True - # set this to true to allow somethings which would'nt be possible + # set to true during repair (shell, migration) to allow some things which + # wouldn't be possible otherwise repairing = False options = CubicWebNoAppConfiguration.options + ( @@ -900,13 +900,13 @@ return mdir @classmethod - def config_for(cls, appid, config=None, debugmode=False): + def config_for(cls, appid, config=None, debugmode=False, creating=False): """return a configuration instance for the given instance identifier """ cls.load_available_configs() config = config or guess_configuration(cls.instance_home(appid)) configcls = configuration_cls(config) - return configcls(appid, debugmode) + return configcls(appid, debugmode, creating) @classmethod def possible_configurations(cls, appid): @@ -966,8 +966,6 @@ log_path = os.path.join(_INSTALL_PREFIX, 'var', 'log', 'cubicweb', '%s-%s.log') return log_path % (self.appid, self.name) - - def default_pid_file(self): """return default path to the pid file of the instance'server""" if self.mode == 'system': @@ -985,8 +983,10 @@ # instance methods used to get instance specific resources ############# - def __init__(self, appid, debugmode=False): + def __init__(self, appid, debugmode=False, creating=False): self.appid = appid + # set to true while creating an instance + self.creating = creating super(CubicWebConfiguration, self).__init__(debugmode) fake_gettext = (unicode, lambda ctx, msgid: unicode(msgid)) for lang in self.available_languages(): @@ -1077,7 +1077,7 @@ def load_configuration(self): """load instance's configuration files""" super(CubicWebConfiguration, self).load_configuration() - if self.apphome and self.set_language: + if self.apphome and not self.creating: # init gettext self._gettext_init() diff -r 611663348158 -r ef29d3ea3909 cwctl.py --- a/cwctl.py Thu Mar 24 15:21:13 2011 +0100 +++ b/cwctl.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -300,6 +300,11 @@ print '* cube %s version %s is installed, but version %s is required by %s' % ( cube, cfgpb.cubes[cube], version, src) +def check_options_consistency(config): + if config.automatic and config.config_level > 0: + raise BadCommandUsage('--automatic and --config-level should not be ' + 'used together') + class CreateInstanceCommand(Command): """Create an instance from a cube. This is an unified command which can handle web / server / all-in-one installation @@ -309,7 +314,7 @@ the name of cube to use (list available cube names using the "list" command). You can use several cubes by separating - them using comma (e.g. 'jpl,eemail') + them using comma (e.g. 'jpl,email') an identifier for the instance to create """ @@ -317,34 +322,39 @@ arguments = ' ' min_args = max_args = 2 options = ( - ("config-level", + ('automatic', + {'short': 'a', 'action' : 'store_true', + 'default': False, + 'help': 'automatic mode: never ask and use default answer to every ' + 'question. this may require that your login match a database super ' + 'user (allowed to create database & all).', + }), + ('config-level', {'short': 'l', 'type' : 'int', 'metavar': '', 'default': 0, - 'help': 'configuration level (0..2): 0 will ask for essential \ -configuration parameters only while 2 will ask for all parameters', - } - ), - ("config", + 'help': 'configuration level (0..2): 0 will ask for essential ' + 'configuration parameters only while 2 will ask for all parameters', + }), + ('config', {'short': 'c', 'type' : 'choice', 'metavar': '', 'choices': ('all-in-one', 'repository', 'twisted'), 'default': 'all-in-one', - 'help': 'installation type, telling which part of an instance \ -should be installed. You can list available configurations using the "list" \ -command. Default to "all-in-one", e.g. an installation embedding both the RQL \ -repository and the web server.', - } - ), + 'help': 'installation type, telling which part of an instance ' + 'should be installed. You can list available configurations using the' + ' "list" command. Default to "all-in-one", e.g. an installation ' + 'embedding both the RQL repository and the web server.', + }), ) def run(self, args): """run the command with its specific arguments""" from logilab.common.textutils import splitstrip + check_options_consistency(self.config) configname = self.config.config cubes, appid = args cubes = splitstrip(cubes) # get the configuration and helper - config = cwcfg.config_for(appid, configname) - config.set_language = False + config = cwcfg.config_for(appid, configname, creating=True) cubes = config.expand_cubes(cubes) config.init_cubes(cubes) helper = self.config_helper(config) @@ -361,15 +371,19 @@ print '\n'+underline_title('Creating the instance %s' % appid) create_dir(config.apphome) # cubicweb-ctl configuration - print '\n'+underline_title('Configuring the instance (%s.conf)' % configname) - config.input_config('main', self.config.config_level) + if not self.config.automatic: + print '\n'+underline_title('Configuring the instance (%s.conf)' + % configname) + config.input_config('main', self.config.config_level) # configuration'specific stuff print - helper.bootstrap(cubes, self.config.config_level) + helper.bootstrap(cubes, self.config.automatic, self.config.config_level) # input for cubes specific options - for section in set(sect.lower() for sect, opt, optdict in config.all_options() - if optdict.get('level') <= self.config.config_level): - if section not in ('main', 'email', 'pyro'): + sections = set(sect.lower() for sect, opt, odict in config.all_options() + if 'type' in odict + and odict.get('level') <= self.config.config_level) + for section in sections: + if section not in ('main', 'email', 'pyro', 'web'): print '\n' + underline_title('%s options' % section) config.input_config(section, self.config.config_level) # write down configuration @@ -384,8 +398,9 @@ errors = config.i18ncompile(langs) if errors: print '\n'.join(errors) - if not ASK.confirm('error while compiling message catalogs, ' - 'continue anyway ?'): + if self.config.automatic \ + or not ASK.confirm('error while compiling message catalogs, ' + 'continue anyway ?'): print 'creation not completed' return # create the additional data directory for this instance @@ -398,7 +413,7 @@ print 'set %s as owner of the data directory' % config['uid'] chown(config.appdatahome, config['uid']) print '\n-> creation done for %r.\n' % config.apphome - helper.postcreate() + helper.postcreate(self.config.automatic) def _handle_win32(self, config, appid): if sys.platform != 'win32': diff -r 611663348158 -r ef29d3ea3909 dataimport.py --- a/dataimport.py Thu Mar 24 15:21:13 2011 +0100 +++ b/dataimport.py Wed Mar 30 11:17:58 2011 +0200 @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# 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. @@ -81,7 +81,7 @@ from logilab.common.deprecation import deprecated from cubicweb.server.utils import eschema_eid -from cubicweb.server.ssplanner import EditedEntity +from cubicweb.server.edition import EditedEntity def count_lines(stream_or_filename): if isinstance(stream_or_filename, basestring): @@ -473,6 +473,11 @@ self.rql('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype, {'x': int(eid_from), 'y': int(eid_to)}) + def find_entities(self, *args, **kwargs): + return self.session.find_entities(*args, **kwargs) + + def find_one_entity(self, *args, **kwargs): + return self.session.find_one_entity(*args, **kwargs) # the import controller ######################################################## diff -r 611663348158 -r ef29d3ea3909 dbapi.py --- a/dbapi.py Thu Mar 24 15:21:13 2011 +0100 +++ b/dbapi.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -102,11 +102,14 @@ return Repository(config, vreg=vreg) else: # method == 'pyro' # resolve the Pyro object - from logilab.common.pyro_ext import ns_get_proxy + from logilab.common.pyro_ext import ns_get_proxy, get_proxy pyroid = database or config['pyro-instance-id'] or config.appid try: - return ns_get_proxy(pyroid, defaultnsgroup=config['pyro-ns-group'], - nshost=config['pyro-ns-host']) + if config['pyro-ns-host'] == 'NO_PYRONS': + return get_proxy(pyroid) + else: + return ns_get_proxy(pyroid, defaultnsgroup=config['pyro-ns-group'], + nshost=config['pyro-ns-host']) except Exception, ex: raise ConnectionError(str(ex)) diff -r 611663348158 -r ef29d3ea3909 debian/changelog --- a/debian/changelog Thu Mar 24 15:21:13 2011 +0100 +++ b/debian/changelog Wed Mar 30 11:17:58 2011 +0200 @@ -1,3 +1,9 @@ +cubicweb (3.11.2-1) unstable; urgency=low + + * new upstream release + + -- Nicolas Chauvat Mon, 28 Mar 2011 19:18:54 +0200 + cubicweb (3.11.1-1) unstable; urgency=low * new upstream release diff -r 611663348158 -r ef29d3ea3909 debian/control --- a/debian/control Thu Mar 24 15:21:13 2011 +0100 +++ b/debian/control Wed Mar 30 11:17:58 2011 +0200 @@ -97,7 +97,7 @@ Package: cubicweb-common Architecture: all XB-Python-Version: ${python:Versions} -Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.0), python-yams (>= 0.30.4), python-rql (>= 0.28.0), python-lxml +Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.30.4), python-rql (>= 0.28.0), python-lxml Recommends: python-simpletal (>= 4.0), python-crypto Conflicts: cubicweb-core Replaces: cubicweb-core diff -r 611663348158 -r ef29d3ea3909 devtools/__init__.py --- a/devtools/__init__.py Thu Mar 24 15:21:13 2011 +0100 +++ b/devtools/__init__.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -127,7 +127,6 @@ class TestServerConfiguration(ServerConfiguration): mode = 'test' - set_language = False read_instance_schema = False init_repository = True @@ -497,7 +496,8 @@ @cached def dbcnx(self): from cubicweb.server.serverctl import _db_sys_cnx - return _db_sys_cnx(self.system_source, 'CREATE DATABASE and / or USER', verbose=0) + return _db_sys_cnx(self.system_source, 'CREATE DATABASE and / or USER', + interactive=False) @property @cached @@ -514,7 +514,8 @@ createdb(self.helper, self.system_source, self.dbcnx, self.cursor) self.dbcnx.commit() - cnx = system_source_cnx(self.system_source, special_privs='LANGUAGE C', verbose=0) + cnx = system_source_cnx(self.system_source, special_privs='LANGUAGE C', + interactive=False) templcursor = cnx.cursor() try: # XXX factorize with db-create code diff -r 611663348158 -r ef29d3ea3909 doc/book/en/devrepo/testing.rst --- a/doc/book/en/devrepo/testing.rst Thu Mar 24 15:21:13 2011 +0100 +++ b/doc/book/en/devrepo/testing.rst Wed Mar 30 11:17:58 2011 +0200 @@ -59,10 +59,10 @@ def setup_database(self): req = self.request() - group_etype = req.execute('Any X WHERE X name "CWGroup"').get_entity(0,0) + group_etype = req.find_one_entity('CWEType', name='CWGroup') c1 = req.create_entity('Classification', name=u'classif1', classifies=group_etype) - user_etype = req.execute('Any X WHERE X name "CWUser"').get_entity(0,0) + user_etype = req.find_one_entity('CWEType', name='CWUser') c2 = req.create_entity('Classification', name=u'classif2', classifies=user_etype) self.kw1 = req.create_entity('Keyword', name=u'kwgroup', included_in=c1) @@ -228,7 +228,7 @@ def test_admin(self): req = self.request() - rset = req.execute('Any C WHERE C is Conference') + rset = req.find_entities('Conference') self.assertListEqual(self.pactions(req, rset), [('workflow', workflow.WorkflowActions), ('edit', confactions.ModifyAction), diff -r 611663348158 -r ef29d3ea3909 entities/authobjs.py --- a/entities/authobjs.py Thu Mar 24 15:21:13 2011 +0100 +++ b/entities/authobjs.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -80,6 +80,20 @@ key, self.login) return self._cw.vreg.property_value(key) + def set_property(self, pkey, value): + value = unicode(value) + try: + prop = self._cw.execute( + 'CWProperty X WHERE X pkey %(k)s, X for_user U, U eid %(u)s', + {'k': pkey, 'u': self.eid}).get_entity(0, 0) + except: + kwargs = dict(pkey=unicode(pkey), value=value) + if self.is_in_group('managers'): + kwargs['for_user'] = self + self._cw.create_entity('CWProperty', **kwargs) + else: + prop.set_attributes(value=value) + def matching_groups(self, groups): """return the number of the given group(s) in which the user is diff -r 611663348158 -r ef29d3ea3909 entity.py --- a/entity.py Thu Mar 24 15:21:13 2011 +0100 +++ b/entity.py Wed Mar 30 11:17:58 2011 +0200 @@ -62,7 +62,6 @@ return True - class Entity(AppObject): """an entity instance has e_schema automagically set on the class and instances has access to their issuing cursor. @@ -808,7 +807,11 @@ else: existant = None # instead of 'SO', improve perfs for select in rqlst.children: - rewriter.rewrite(select, [((searchedvar, searchedvar), rqlexprs)], + varmap = {} + for var in 'SO': + if var in select.defined_vars: + varmap[var] = var + rewriter.rewrite(select, [(varmap, rqlexprs)], select.solutions, args, existant) rql = rqlst.as_string() return rql, args diff -r 611663348158 -r ef29d3ea3909 etwist/twctl.py --- a/etwist/twctl.py Thu Mar 24 15:21:13 2011 +0100 +++ b/etwist/twctl.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -51,10 +51,10 @@ """ cfgname = 'all-in-one' - def bootstrap(self, cubes, inputlevel=0): + def bootstrap(self, cubes, automatic=False, inputlevel=0): """bootstrap this configuration""" - serverctl.RepositoryCreateHandler.bootstrap(self, cubes, inputlevel) - TWCreateHandler.bootstrap(self, cubes, inputlevel) + serverctl.RepositoryCreateHandler.bootstrap(self, cubes, automatic, inputlevel) + TWCreateHandler.bootstrap(self, cubes, automatic, inputlevel) class AllInOneStartHandler(TWStartHandler): cmdname = 'start' diff -r 611663348158 -r ef29d3ea3909 hooks/syncsession.py --- a/hooks/syncsession.py Thu Mar 24 15:21:13 2011 +0100 +++ b/hooks/syncsession.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. diff -r 611663348158 -r ef29d3ea3909 req.py --- a/req.py Thu Mar 24 15:21:13 2011 +0100 +++ b/req.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -35,6 +35,8 @@ ONESECOND = timedelta(0, 1, 0) CACHE_REGISTRY = {} +class FindEntityError(Exception): + """raised when find_one_entity() can not return one and only one entity""" def _check_cw_unsafe(kwargs): if kwargs.pop('_cw_unsafe', False): @@ -140,6 +142,33 @@ cls = self.vreg['etypes'].etype_class(etype) return cls.cw_instantiate(self.execute, **kwargs) + def find_entities(self, etype, **kwargs): + """find entities of the given type and attribute values. + + >>> users = find_entities('CWGroup', name=u'users') + >>> groups = find_entities('CWGroup') + """ + parts = ['Any X WHERE X is %s' % etype] + parts.extend('X %(attr)s %%(%(attr)s)s' % {'attr': attr} for attr in kwargs) + return self.execute(', '.join(parts), kwargs).entities() + + def find_one_entity(self, etype, **kwargs): + """find one entity of the given type and attribute values. + raise :exc:`FindEntityError` if can not return one and only one entity. + + >>> users = find_one_entity('CWGroup', name=u'users') + >>> groups = find_one_entity('CWGroup') + Exception() + """ + parts = ['Any X WHERE X is %s' % etype] + parts.extend('X %(attr)s %%(%(attr)s)s' % {'attr': attr} for attr in kwargs) + rql = ', '.join(parts) + rset = self.execute(rql, kwargs) + if len(rset) != 1: + raise FindEntityError('Found %i entitie(s) when 1 was expected (rql=%s ; %s)' + % (len(rset), rql, repr(kwargs))) + return rset.get_entity(0,0) + def ensure_ro_rql(self, rql): """raise an exception if the given rql is not a select query""" first = rql.split(None, 1)[0].lower() diff -r 611663348158 -r ef29d3ea3909 rqlrewrite.py --- a/rqlrewrite.py Thu Mar 24 15:21:13 2011 +0100 +++ b/rqlrewrite.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -24,6 +24,7 @@ __docformat__ = "restructuredtext en" from rql import nodes as n, stmts, TypeResolverException +from rql.utils import common_parent from yams import BadSchemaDefinition from logilab.common.graph import has_path @@ -180,13 +181,23 @@ def insert_snippets(self, snippets, varexistsmap=None): self.rewritten = {} for varmap, rqlexprs in snippets: + if isinstance(varmap, dict): + varmap = tuple(sorted(varmap.items())) + else: + assert isinstance(varmap, tuple), varmap if varexistsmap is not None and not varmap in varexistsmap: continue - self.varmap = varmap - selectvar, snippetvar = varmap + self.insert_varmap_snippets(varmap, rqlexprs, varexistsmap) + + def insert_varmap_snippets(self, varmap, rqlexprs, varexistsmap): + self.varmap = varmap + self.revvarmap = {} + self.varinfos = [] + for i, (selectvar, snippetvar) in enumerate(varmap): assert snippetvar in 'SOX' - self.revvarmap = {snippetvar: selectvar} - self.varinfo = vi = {} + self.revvarmap[snippetvar] = (selectvar, i) + vi = {} + self.varinfos.append(vi) try: vi['const'] = typed_eid(selectvar) # XXX gae vi['rhs_rels'] = vi['lhs_rels'] = {} @@ -194,42 +205,42 @@ try: vi['stinfo'] = sti = self.select.defined_vars[selectvar].stinfo except KeyError: - # variable has been moved to a newly inserted subquery + # variable may have been moved to a newly inserted subquery # we should insert snippet in that subquery subquery = self.select.aliases[selectvar].query assert len(subquery.children) == 1 subselect = subquery.children[0] RQLRewriter(self.session).rewrite(subselect, [(varmap, rqlexprs)], subselect.solutions, self.kwargs) - continue + return if varexistsmap is None: vi['rhs_rels'] = dict( (r.r_type, r) for r in sti['rhsrelations']) vi['lhs_rels'] = dict( (r.r_type, r) for r in sti['relations'] if not r in sti['rhsrelations']) else: vi['rhs_rels'] = vi['lhs_rels'] = {} - parent = None - inserted = False - for rqlexpr in rqlexprs: - self.current_expr = rqlexpr - if varexistsmap is None: - try: - new = self.insert_snippet(varmap, rqlexpr.snippet_rqlst, parent) - except Unsupported: - continue - inserted = True - if new is not None: - self.exists_snippet[rqlexpr] = new - parent = parent or new - else: - # called to reintroduce snippet due to ambiguity creation, - # so skip snippets which are not introducing this ambiguity - exists = varexistsmap[varmap] - if self.exists_snippet[rqlexpr] is exists: - self.insert_snippet(varmap, rqlexpr.snippet_rqlst, exists) - if varexistsmap is None and not inserted: - # no rql expression found matching rql solutions. User has no access right - raise Unauthorized() + parent = None + inserted = False + for rqlexpr in rqlexprs: + self.current_expr = rqlexpr + if varexistsmap is None: + try: + new = self.insert_snippet(varmap, rqlexpr.snippet_rqlst, parent) + except Unsupported: + continue + inserted = True + if new is not None: + self.exists_snippet[rqlexpr] = new + parent = parent or new + else: + # called to reintroduce snippet due to ambiguity creation, + # so skip snippets which are not introducing this ambiguity + exists = varexistsmap[varmap] + if self.exists_snippet[rqlexpr] is exists: + self.insert_snippet(varmap, rqlexpr.snippet_rqlst, exists) + if varexistsmap is None and not inserted: + # no rql expression found matching rql solutions. User has no access right + raise Unauthorized() # XXX bad constraint when inserting constraints def insert_snippet(self, varmap, snippetrqlst, parent=None): new = snippetrqlst.where.accept(self) @@ -243,16 +254,23 @@ def _insert_snippet(self, varmap, parent, new): if new is not None: if self._insert_scope is None: - insert_scope = self.varinfo.get('stinfo', {}).get('scope', self.select) + insert_scope = None + for vi in self.varinfos: + scope = vi.get('stinfo', {}).get('scope', self.select) + if insert_scope is None: + insert_scope = scope + else: + insert_scope = common_parent(scope, insert_scope) else: insert_scope = self._insert_scope - if self.varinfo.get('stinfo', {}).get('optrelations'): + if any(vi.get('stinfo', {}).get('optrelations') for vi in self.varinfos): assert parent is None self._insert_scope = self.snippet_subquery(varmap, new) self.insert_pending() self._insert_scope = None return - new = n.Exists(new) + if not isinstance(new, (n.Exists, n.Not)): + new = n.Exists(new) if parent is None: insert_scope.add_restriction(new) else: @@ -291,7 +309,7 @@ varname = self.rewritten[key] except KeyError: try: - varname = self.revvarmap[key[-1]] + varname = self.revvarmap[key[-1]][0] except KeyError: # variable isn't used anywhere else, we can't insert security raise Unauthorized() @@ -308,45 +326,51 @@ rqlexprs = eschema.get_rqlexprs(action) if not rqlexprs: raise Unauthorized() - self.insert_snippets([((varname, 'X'), rqlexprs)]) + self.insert_snippets([({varname: 'X'}, rqlexprs)]) def snippet_subquery(self, varmap, transformedsnippet): """introduce the given snippet in a subquery""" subselect = stmts.Select() - selectvar = varmap[0] - subselectvar = subselect.get_variable(selectvar) - subselect.append_selected(n.VariableRef(subselectvar)) snippetrqlst = n.Exists(transformedsnippet.copy(subselect)) - aliases = [selectvar] - stinfo = self.varinfo['stinfo'] - need_null_test = False - for rel in stinfo['relations']: - rschema = self.schema.rschema(rel.r_type) - if rschema.final or (rschema.inlined and - not rel in stinfo['rhsrelations']): - rel.children[0].name = selectvar # XXX explain why - subselect.add_restriction(rel.copy(subselect)) - for vref in rel.children[1].iget_nodes(n.VariableRef): - if isinstance(vref.variable, n.ColumnAlias): - # XXX could probably be handled by generating the subquery - # into the detected subquery - raise BadSchemaDefinition( - "cant insert security because of usage two inlined " - "relations in this query. You should probably at " - "least uninline %s" % rel.r_type) - subselect.append_selected(vref.copy(subselect)) - aliases.append(vref.name) - self.select.remove_node(rel) - # when some inlined relation has to be copied in the subquery, - # we need to test that either value is NULL or that the snippet - # condition is satisfied - if rschema.inlined and rel.optional: - need_null_test = True - if need_null_test: - snippetrqlst = n.Or( - n.make_relation(subselectvar, 'is', (None, None), n.Constant, - operator='='), - snippetrqlst) + aliases = [] + rels_done = set() + for i, (selectvar, snippetvar) in enumerate(varmap): + subselectvar = subselect.get_variable(selectvar) + subselect.append_selected(n.VariableRef(subselectvar)) + aliases.append(selectvar) + vi = self.varinfos[i] + need_null_test = False + stinfo = vi['stinfo'] + for rel in stinfo['relations']: + if rel in rels_done: + continue + rels_done.add(rel) + rschema = self.schema.rschema(rel.r_type) + if rschema.final or (rschema.inlined and + not rel in stinfo['rhsrelations']): + rel.children[0].name = selectvar # XXX explain why + subselect.add_restriction(rel.copy(subselect)) + for vref in rel.children[1].iget_nodes(n.VariableRef): + if isinstance(vref.variable, n.ColumnAlias): + # XXX could probably be handled by generating the + # subquery into the detected subquery + raise BadSchemaDefinition( + "cant insert security because of usage two inlined " + "relations in this query. You should probably at " + "least uninline %s" % rel.r_type) + subselect.append_selected(vref.copy(subselect)) + aliases.append(vref.name) + self.select.remove_node(rel) + # when some inlined relation has to be copied in the + # subquery, we need to test that either value is NULL or + # that the snippet condition is satisfied + if rschema.inlined and rel.optional: + need_null_test = True + if need_null_test: + snippetrqlst = n.Or( + n.make_relation(subselectvar, 'is', (None, None), n.Constant, + operator='='), + snippetrqlst) subselect.add_restriction(snippetrqlst) if self.u_varname: # generate an identifier for the substitution @@ -433,35 +457,37 @@ # no more references, undefine the variable del self.select.defined_vars[vref.name] - def _may_be_shared_with(self, sniprel, target, searchedvarname): + def _may_be_shared_with(self, sniprel, target): """if the snippet relation can be skipped to use a relation from the original query, return that relation node """ rschema = self.schema.rschema(sniprel.r_type) - try: - if target == 'object': - orel = self.varinfo['lhs_rels'][sniprel.r_type] - cardindex = 0 - ttypes_func = rschema.objects - rdef = rschema.rdef - else: # target == 'subject': - orel = self.varinfo['rhs_rels'][sniprel.r_type] - cardindex = 1 - ttypes_func = rschema.subjects - rdef = lambda x, y: rschema.rdef(y, x) - except KeyError: - # may be raised by self.varinfo['xhs_rels'][sniprel.r_type] - return None - # can't share neged relation or relations with different outer join - if (orel.neged(strict=True) or sniprel.neged(strict=True) - or (orel.optional and orel.optional != sniprel.optional)): - return None - # if cardinality is in '?1', we can ignore the snippet relation and use - # variable from the original query - for etype in self.varinfo['stinfo']['possibletypes']: - for ttype in ttypes_func(etype): - if rdef(etype, ttype).cardinality[cardindex] in '+*': - return None + for vi in self.varinfos: + try: + if target == 'object': + orel = vi['lhs_rels'][sniprel.r_type] + cardindex = 0 + ttypes_func = rschema.objects + rdef = rschema.rdef + else: # target == 'subject': + orel = vi['rhs_rels'][sniprel.r_type] + cardindex = 1 + ttypes_func = rschema.subjects + rdef = lambda x, y: rschema.rdef(y, x) + except KeyError: + # may be raised by vi['xhs_rels'][sniprel.r_type] + return None + # can't share neged relation or relations with different outer join + if (orel.neged(strict=True) or sniprel.neged(strict=True) + or (orel.optional and orel.optional != sniprel.optional)): + return None + # if cardinality is in '?1', we can ignore the snippet relation and use + # variable from the original query + for etype in vi['stinfo']['possibletypes']: + for ttype in ttypes_func(etype): + if rdef(etype, ttype).cardinality[cardindex] in '+*': + return None + break return orel def _use_orig_term(self, snippet_varname, term): @@ -560,12 +586,12 @@ if self.existingvars and not self.keep_var(rhs.name): return if lhs.name in self.revvarmap and rhs.name != 'U': - orel = self._may_be_shared_with(node, 'object', lhs.name) + orel = self._may_be_shared_with(node, 'object') if orel is not None: self._use_orig_term(rhs.name, orel.children[1].children[0]) return elif rhs.name in self.revvarmap and lhs.name != 'U': - orel = self._may_be_shared_with(node, 'subject', rhs.name) + orel = self._may_be_shared_with(node, 'subject') if orel is not None: self._use_orig_term(lhs.name, orel.children[0]) return @@ -600,10 +626,11 @@ def visit_variableref(self, node): """get the sql name for a variable reference""" if node.name in self.revvarmap: - if self.varinfo.get('const') is not None: - return n.Constant(self.varinfo['const'], 'Int') # XXX gae - return n.VariableRef(self.select.get_variable( - self.revvarmap[node.name])) + selectvar, index = self.revvarmap[node.name] + vi = self.varinfos[index] + if vi.get('const') is not None: + return n.Constant(vi['const'], 'Int') # XXX gae + return n.VariableRef(self.select.get_variable(selectvar)) vname_or_term = self._get_varname_or_term(node.name) if isinstance(vname_or_term, basestring): return n.VariableRef(self.select.get_variable(vname_or_term)) diff -r 611663348158 -r ef29d3ea3909 server/__init__.py --- a/server/__init__.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/__init__.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -129,7 +129,6 @@ # on connection config.creating = True config.consider_user_state = False - config.set_language = False # only enable the system source at initialization time repo = Repository(config, vreg=vreg) schema = repo.schema @@ -210,7 +209,6 @@ # restore initial configuration config.creating = False config.consider_user_state = True - config.set_language = True print '-> database for instance %s initialized.' % config.appid diff -r 611663348158 -r ef29d3ea3909 server/edition.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/edition.py Wed Mar 30 11:17:58 2011 +0200 @@ -0,0 +1,150 @@ +# 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. +# +# 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 . +"""helper classes to handle server-side edition of entities""" + +from __future__ import with_statement + +__docformat__ = "restructuredtext en" + +from copy import copy +from yams import ValidationError + + +_MARKER = object() + +class dict_protocol_catcher(object): + def __init__(self, entity): + self.__entity = entity + def __getitem__(self, attr): + return self.__entity.cw_edited[attr] + def __setitem__(self, attr, value): + self.__entity.cw_edited[attr] = value + def __getattr__(self, attr): + return getattr(self.__entity, attr) + + +class EditedEntity(dict): + """encapsulate entities attributes being written by an RQL query""" + def __init__(self, entity, **kwargs): + dict.__init__(self, **kwargs) + self.entity = entity + self.skip_security = set() + self.querier_pending_relations = {} + self.saved = False + + def __hash__(self): + # dict|set keyable + return hash(id(self)) + + def __cmp__(self, other): + # we don't want comparison by value inherited from dict + return cmp(id(self), id(other)) + + def __setitem__(self, attr, value): + assert attr != 'eid' + # don't add attribute into skip_security if already in edited + # attributes, else we may accidentaly skip a desired security check + if attr not in self: + self.skip_security.add(attr) + self.edited_attribute(attr, value) + + def __delitem__(self, attr): + assert not self.saved, 'too late to modify edited attributes' + super(EditedEntity, self).__delitem__(attr) + self.entity.cw_attr_cache.pop(attr, None) + + def pop(self, attr, *args): + # don't update skip_security by design (think to storage api) + assert not self.saved, 'too late to modify edited attributes' + value = super(EditedEntity, self).pop(attr, *args) + self.entity.cw_attr_cache.pop(attr, *args) + return value + + def setdefault(self, attr, default): + assert attr != 'eid' + # don't add attribute into skip_security if already in edited + # attributes, else we may accidentaly skip a desired security check + if attr not in self: + self[attr] = default + return self[attr] + + def update(self, values, skipsec=True): + if skipsec: + setitem = self.__setitem__ + else: + setitem = self.edited_attribute + for attr, value in values.iteritems(): + setitem(attr, value) + + def edited_attribute(self, attr, value): + """attribute being edited by a rql query: should'nt be added to + skip_security + """ + assert not self.saved, 'too late to modify edited attributes' + super(EditedEntity, self).__setitem__(attr, value) + self.entity.cw_attr_cache[attr] = value + + def oldnewvalue(self, attr): + """returns the couple (old attr value, new attr value) + + NOTE: will only work in a before_update_entity hook + """ + assert not self.saved, 'too late to get the old value' + # get new value and remove from local dict to force a db query to + # fetch old value + newvalue = self.entity.cw_attr_cache.pop(attr, _MARKER) + oldvalue = getattr(self.entity, attr) + if newvalue is not _MARKER: + self.entity.cw_attr_cache[attr] = newvalue + else: + newvalue = oldvalue + return oldvalue, newvalue + + def set_defaults(self): + """set default values according to the schema""" + for attr, value in self.entity.e_schema.defaults(): + if not attr in self: + self[str(attr)] = value + + def check(self, creation=False): + """check the entity edition against its schema. Only final relation + are checked here, constraint on actual relations are checked in hooks + """ + entity = self.entity + if creation: + # on creations, we want to check all relations, especially + # required attributes + relations = [rschema for rschema in entity.e_schema.subject_relations() + if rschema.final and rschema.type != 'eid'] + else: + relations = [entity._cw.vreg.schema.rschema(rtype) + for rtype in self] + try: + entity.e_schema.check(dict_protocol_catcher(entity), + creation=creation, _=entity._cw._, + relations=relations) + except ValidationError, ex: + ex.entity = self.entity + raise + + def clone(self): + thecopy = EditedEntity(copy(self.entity)) + thecopy.entity.cw_attr_cache = copy(self.entity.cw_attr_cache) + thecopy.entity._cw_related_cache = {} + thecopy.update(self, skipsec=False) + return thecopy diff -r 611663348158 -r ef29d3ea3909 server/migractions.py --- a/server/migractions.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/migractions.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -1307,20 +1307,32 @@ # CWProperty handling ###################################################### def cmd_property_value(self, pkey): - rql = 'Any V WHERE X is CWProperty, X pkey %(k)s, X value V' - rset = self.rqlexec(rql, {'k': pkey}, ask_confirm=False) + """retreive the site-wide persistent property value for the given key. + + To get a user specific property value, use appropriate method on CWUser + instance. + """ + rset = self.rqlexec( + 'Any V WHERE X is CWProperty, X pkey %(k)s, X value V, NOT X for_user U', + {'k': pkey}, ask_confirm=False) return rset[0][0] def cmd_set_property(self, pkey, value): + """set the site-wide persistent property value for the given key to the + given value. + + To set a user specific property value, use appropriate method on CWUser + instance. + """ value = unicode(value) try: - prop = self.rqlexec('CWProperty X WHERE X pkey %(k)s', {'k': pkey}, - ask_confirm=False).get_entity(0, 0) + prop = self.rqlexec( + 'CWProperty X WHERE X pkey %(k)s, NOT X for_user U', + {'k': pkey}, ask_confirm=False).get_entity(0, 0) except: self.cmd_create_entity('CWProperty', pkey=unicode(pkey), value=value) else: - self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s', - {'k': pkey, 'v': value}, ask_confirm=False) + prop.set_attributes(value=value) # other data migration commands ########################################### @@ -1360,6 +1372,18 @@ self.commit() return entity + def cmd_find_entities(self, etype, **kwargs): + """find entities of the given type and attribute values""" + return self._cw.find_entities(etype, **kwargs) + + def cmd_find_one_entity(self, etype, **kwargs): + """find one entity of the given type and attribute values. + + raise :exc:`cubicweb.req.FindEntityError` if can not return one and only + one entity. + """ + return self._cw.find_one_entity(etype, **kwargs) + def cmd_update_etype_fti_weight(self, etype, weight): if self.repo.system_source.dbdriver == 'postgres': self.sqlexec('UPDATE appears SET weight=%(weight)s ' diff -r 611663348158 -r ef29d3ea3909 server/querier.py --- a/server/querier.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/querier.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -38,7 +38,8 @@ from cubicweb.server.utils import cleanup_solutions from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata -from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction, EditedEntity +from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction +from cubicweb.server.edition import EditedEntity from cubicweb.server.session import security_enabled def empty_rset(rql, args, rqlst=None): @@ -353,7 +354,7 @@ myrqlst = select.copy(solutions=lchecksolutions) myunion.append(myrqlst) # in-place rewrite + annotation / simplification - lcheckdef = [((var, 'X'), rqlexprs) for var, rqlexprs in lcheckdef] + lcheckdef = [({var: 'X'}, rqlexprs) for var, rqlexprs in lcheckdef] rewrite(myrqlst, lcheckdef, lchecksolutions, self.args) add_noinvariant(noinvariant, restricted, myrqlst, nbtrees) if () in localchecks: diff -r 611663348158 -r ef29d3ea3909 server/repository.py --- a/server/repository.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/repository.py Wed Mar 30 11:17:58 2011 +0200 @@ -59,6 +59,7 @@ security_enabled from cubicweb.server.ssplanner import EditedEntity + def prefill_entity_caches(entity, relations): session = entity._cw # prefill entity relation caches @@ -134,6 +135,7 @@ vreg = cwvreg.CubicWebVRegistry(config) self.vreg = vreg self.pyro_registered = False + self.pyro_uri = None self.info('starting repository from %s', self.config.apphome) # dictionary of opened sessions self._sessions = {} @@ -415,7 +417,9 @@ self.exception('error while closing %s' % pool) continue if self.pyro_registered: - pyro_unregister(self.config) + if self._use_pyrons(): + pyro_unregister(self.config) + self.pyro_uri = None hits, misses = self.querier.cache_hit, self.querier.cache_miss try: self.info('rql st cache hit/miss: %s/%s (%s%% hits)', hits, misses, @@ -1419,20 +1423,32 @@ config['pyro-instance-id'] = appid return appid + def _use_pyrons(self): + """return True if the pyro-ns-host is set to something else + than NO_PYRONS, meaning we want to go through a pyro + nameserver""" + return self.config['pyro-ns-host'] != 'NO_PYRONS' + def pyro_register(self, host=''): """register the repository as a pyro object""" from logilab.common import pyro_ext as pyro daemon = pyro.register_object(self, self.pyro_appid, daemonhost=self.config['pyro-host'], - nshost=self.config['pyro-ns-host']) + nshost=self.config['pyro-ns-host'], + use_pyrons=self._use_pyrons()) self.info('repository registered as a pyro object %s', self.pyro_appid) + self.pyro_uri = pyro.get_object_uri(self.pyro_appid) + self.info('pyro uri is: %s', self.pyro_uri) self.pyro_registered = True # register a looping task to regularly ensure we're still registered # into the pyro name server - self.looping_task(60*10, self._ensure_pyro_ns) + if self._use_pyrons(): + self.looping_task(60*10, self._ensure_pyro_ns) return daemon def _ensure_pyro_ns(self): + if not self._use_pyrons(): + return from logilab.common import pyro_ext as pyro pyro.ns_reregister(self.pyro_appid, nshost=self.config['pyro-ns-host']) self.info('repository re-registered as a pyro object %s', diff -r 611663348158 -r ef29d3ea3909 server/serverconfig.py --- a/server/serverconfig.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/serverconfig.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -215,8 +215,6 @@ # read the schema from the database read_instance_schema = True - # set to true while creating an instance - creating = False # set this to true to get a minimal repository, for instance to get cubes # information on commands such as i18ninstance, db-restore, etc... quick_start = False diff -r 611663348158 -r ef29d3ea3909 server/serverctl.py --- a/server/serverctl.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/serverctl.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -27,11 +27,11 @@ from logilab.common import nullobject from logilab.common.configuration import Configuration -from logilab.common.shellutils import ASK +from logilab.common.shellutils import ASK, generate_password from cubicweb import AuthenticationError, ExecutionError, ConfigurationError from cubicweb.toolsutils import Command, CommandHandler, underline_title -from cubicweb.cwctl import CWCTL +from cubicweb.cwctl import CWCTL, check_options_consistency from cubicweb.server import SOURCE_TYPES from cubicweb.server.serverconfig import ( USER_OPTIONS, ServerConfiguration, SourceConfiguration, @@ -39,7 +39,7 @@ # utility functions ########################################################### -def source_cnx(source, dbname=None, special_privs=False, verbose=True): +def source_cnx(source, dbname=None, special_privs=False, interactive=True): """open and return a connection to the system database defined in the given server.serverconfig """ @@ -50,21 +50,20 @@ dbname = source['db-name'] driver = source['db-driver'] dbhelper = get_db_helper(driver) - if verbose: + if interactive: print '-> connecting to %s database' % driver, if dbhost: print '%s@%s' % (dbname, dbhost), else: print dbname, if dbhelper.users_support: - if not special_privs and source.get('db-user'): - user = source['db-user'] - if verbose: + if not interactive or (not special_privs and source.get('db-user')): + user = source.get('db-user', os.environ.get('USER', '')) + if interactive: print 'as', user password = source.get('db-password') else: - if verbose: - print + print if special_privs: print 'WARNING' print ('the user will need the following special access rights ' @@ -95,7 +94,7 @@ return cnx def system_source_cnx(source, dbms_system_base=False, - special_privs='CREATE/DROP DATABASE', verbose=True): + special_privs='CREATE/DROP DATABASE', interactive=True): """shortcut to get a connextion to the instance system database defined in the given config. If is True, connect to the dbms system database instead (for task such as @@ -104,10 +103,12 @@ if dbms_system_base: from logilab.database import get_db_helper system_db = get_db_helper(source['db-driver']).system_database() - return source_cnx(source, system_db, special_privs=special_privs, verbose=verbose) - return source_cnx(source, special_privs=special_privs, verbose=verbose) + return source_cnx(source, system_db, special_privs=special_privs, + interactive=interactive) + return source_cnx(source, special_privs=special_privs, + interactive=interactive) -def _db_sys_cnx(source, special_privs, verbose=True): +def _db_sys_cnx(source, special_privs, interactive=True): """return a connection on the RDMS system table (to create/drop a user or a database) """ @@ -118,7 +119,7 @@ helper = get_db_helper(driver) # connect on the dbms system base to create our base cnx = system_source_cnx(source, True, special_privs=special_privs, - verbose=verbose) + interactive=interactive) # disable autocommit (isolation_level(1)) because DROP and # CREATE DATABASE can't be executed in a transaction try: @@ -153,38 +154,49 @@ cmdname = 'create' cfgname = 'repository' - def bootstrap(self, cubes, inputlevel=0): + def bootstrap(self, cubes, automatic=False, inputlevel=0): """create an instance by copying files from the given cube and by asking information necessary to build required configuration files """ config = self.config - print underline_title('Configuring the repository') - config.input_config('email', inputlevel) - # ask for pyro configuration if pyro is activated and we're not using a - # all-in-one config, in which case this is done by the web side command - # handler - if config.pyro_enabled() and config.name != 'all-in-one': - config.input_config('pyro', inputlevel) - print '\n'+underline_title('Configuring the sources') + if not automatic: + print underline_title('Configuring the repository') + config.input_config('email', inputlevel) + # ask for pyro configuration if pyro is activated and we're not + # using a all-in-one config, in which case this is done by the web + # side command handler + if config.pyro_enabled() and config.name != 'all-in-one': + config.input_config('pyro', inputlevel) + print '\n'+underline_title('Configuring the sources') sourcesfile = config.sources_file() - # XXX hack to make Method('default_instance_id') usable in db option - # defs (in native.py) + # hack to make Method('default_instance_id') usable in db option defs + # (in native.py) sconfig = SourceConfiguration(config, options=SOURCE_TYPES['native'].options) - sconfig.input_config(inputlevel=inputlevel) + if not automatic: + sconfig.input_config(inputlevel=inputlevel) + print sourcescfg = {'system': sconfig} - print - sconfig = Configuration(options=USER_OPTIONS) - sconfig.input_config(inputlevel=inputlevel) + if automatic: + # XXX modify a copy + password = generate_password() + print 'Administration account is admin / %s' % password + USER_OPTIONS[1][1]['default'] = password + sconfig = Configuration(options=USER_OPTIONS) + else: + sconfig = Configuration(options=USER_OPTIONS) + sconfig.input_config(inputlevel=inputlevel) sourcescfg['admin'] = sconfig config.write_sources_file(sourcescfg) # remember selected cubes for later initialization of the database config.write_bootstrap_cubes_file(cubes) - def postcreate(self): - if ASK.confirm('Run db-create to create the system database ?'): - verbosity = (self.config.mode == 'installed') and 'y' or 'n' - CWCTL.run(['db-create', self.config.appid, '--verbose=%s' % verbosity]) + def postcreate(self, automatic=False, inputlevel=0): + if automatic: + CWCTL.run(['db-create', '--automatic', self.config.appid]) + elif ASK.confirm('Run db-create to create the system database ?'): + CWCTL.run(['db-create', '--config-level', str(inputlevel), + self.config.appid]) else: print ('-> nevermind, you can do it later with ' '"cubicweb-ctl db-create %s".' % self.config.appid) @@ -292,27 +304,30 @@ arguments = '' min_args = max_args = 1 options = ( + ('automatic', + {'short': 'a', 'action' : 'store_true', + 'default': False, + 'help': 'automatic mode: never ask and use default answer to every ' + 'question. this may require that your login match a database super ' + 'user (allowed to create database & all).', + }), + ('config-level', + {'short': 'l', 'type' : 'int', 'metavar': '', + 'default': 0, + 'help': 'configuration level (0..2): 0 will ask for essential ' + 'configuration parameters only while 2 will ask for all parameters', + }), ('create-db', {'short': 'c', 'type': 'yn', 'metavar': '', 'default': True, - 'help': 'create the database (yes by default)'}), - ('verbose', - {'short': 'v', 'type' : 'yn', 'metavar': '', - 'default': 'n', - 'help': 'verbose mode: will ask all possible configuration questions', - } - ), - ('automatic', - {'short': 'a', 'type' : 'yn', 'metavar': '', - 'default': 'n', - 'help': 'automatic mode: never ask and use default answer to every question', - } - ), + 'help': 'create the database (yes by default)' + }), ) + def run(self, args): """run the command with its specific arguments""" from logilab.database import get_db_helper - verbose = self.get('verbose') + check_options_consistency(self.config) automatic = self.get('automatic') appid = args.pop() config = ServerConfiguration.config_for(appid) @@ -329,7 +344,7 @@ print '\n'+underline_title('Creating the system database') # connect on the dbms system base to create our base dbcnx = _db_sys_cnx(source, 'CREATE/DROP DATABASE and / or USER', - verbose=verbose) + interactive=not automatic) cursor = dbcnx.cursor() try: if helper.users_support: @@ -342,6 +357,8 @@ if automatic or ASK.confirm('Database %s already exists -- do you want to drop it ?' % dbname): cursor.execute('DROP DATABASE %s' % dbname) else: + print ('you may want to run "cubicweb-ctl db-init ' + '--drop %s" manually to continue.' % config.appid) return createdb(helper, source, dbcnx, cursor) dbcnx.commit() @@ -350,7 +367,7 @@ dbcnx.rollback() raise cnx = system_source_cnx(source, special_privs='CREATE LANGUAGE', - verbose=verbose) + interactive=not automatic) cursor = cnx.cursor() helper.init_fti_extensions(cursor) # postgres specific stuff @@ -363,8 +380,12 @@ cnx.commit() print '-> database for instance %s created and necessary extensions installed.' % appid print - if automatic or ASK.confirm('Run db-init to initialize the system database ?'): - CWCTL.run(['db-init', config.appid]) + if automatic: + CWCTL.run(['db-init', '--automatic', '--config-level', '0', + config.appid]) + elif ASK.confirm('Run db-init to initialize the system database ?'): + CWCTL.run(['db-init', '--config-level', + str(self.config.config_level), config.appid]) else: print ('-> nevermind, you can do it later with ' '"cubicweb-ctl db-init %s".' % config.appid) @@ -383,18 +404,27 @@ arguments = '' min_args = max_args = 1 options = ( + ('automatic', + {'short': 'a', 'action' : 'store_true', + 'default': False, + 'help': 'automatic mode: never ask and use default answer to every ' + 'question.', + }), + ('config-level', + {'short': 'l', 'type': 'int', 'default': 1, + 'help': 'level threshold for questions asked when configuring ' + 'another source' + }), ('drop', {'short': 'd', 'action': 'store_true', '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' + 'help': 'insert drop statements to remove previously existant ' + 'tables, indexes... (no by default)' }), ) def run(self, args): + check_options_consistency(self.config) print '\n'+underline_title('Initializing the system database') from cubicweb.server import init_repository from logilab.database import get_connection @@ -415,8 +445,10 @@ 'the %s file. Resolve this first (error: %s).' % (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-level', self.config.config_level, config.appid]) + if not self.config.automatic: + while ASK.confirm('Enter another source ?', default_is_yes=False): + CWCTL.run(['add-source', '--config-level', + str(self.config.config_level), config.appid]) class AddSourceCommand(Command): diff -r 611663348158 -r ef29d3ea3909 server/session.py --- a/server/session.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/session.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -37,6 +37,7 @@ from cubicweb.dbapi import ConnectionProperties from cubicweb.utils import make_uid, RepeatList from cubicweb.rqlrewrite import RQLRewriter +from cubicweb.server.edition import EditedEntity ETYPE_PYOBJ_MAP[Binary] = 'Bytes' @@ -215,8 +216,9 @@ with security_enabled(self, False, False): if self.vreg.schema[rtype].inlined: entity = self.entity_from_eid(fromeid) - entity[rtype] = toeid - self.repo.glob_update_entity(self, entity, set((rtype,))) + edited = EditedEntity(entity) + edited.edited_attribute(rtype, toeid) + self.repo.glob_update_entity(self, edited) else: self.repo.glob_add_relation(self, fromeid, rtype, toeid) @@ -234,7 +236,7 @@ with security_enabled(self, False, False): if self.vreg.schema[rtype].inlined: entity = self.entity_from_eid(fromeid) - entity[rtype] = None + entity.cw_attr_cache[rtype] = None self.repo.glob_update_entity(self, entity, set((rtype,))) else: self.repo.glob_delete_relation(self, fromeid, rtype, toeid) diff -r 611663348158 -r ef29d3ea3909 server/sources/__init__.py --- a/server/sources/__init__.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/sources/__init__.py Wed Mar 30 11:17:58 2011 +0200 @@ -31,7 +31,7 @@ from cubicweb import ValidationError, set_log_methods, server from cubicweb.schema import VIRTUAL_RTYPES from cubicweb.server.sqlutils import SQL_PREFIX -from cubicweb.server.ssplanner import EditedEntity +from cubicweb.server.edition import EditedEntity def dbg_st_search(uri, union, varmap, args, cachekey=None, prefix='rql for'): diff -r 611663348158 -r ef29d3ea3909 server/sources/ldapuser.py --- a/server/sources/ldapuser.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/sources/ldapuser.py Wed Mar 30 11:17:58 2011 +0200 @@ -522,7 +522,8 @@ def _search(self, session, base, scope, searchstr='(objectClass=*)', attrs=()): """make an ldap query""" - self.debug('ldap search %s %s %s %s %s', self.uri, base, scope, searchstr, list(attrs)) + self.debug('ldap search %s %s %s %s %s', self.uri, base, scope, + searchstr, list(attrs)) # XXX for now, we do not have connection pool support for LDAP, so # this is always self._conn cnx = session.pool.connection(self.uri).cnx diff -r 611663348158 -r ef29d3ea3909 server/sources/native.py --- a/server/sources/native.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/sources/native.py Wed Mar 30 11:17:58 2011 +0200 @@ -58,7 +58,7 @@ from cubicweb.server.rqlannotation import set_qdata from cubicweb.server.hook import CleanupDeletedEidsCacheOp from cubicweb.server.session import hooks_control, security_enabled -from cubicweb.server.ssplanner import EditedEntity +from cubicweb.server.edition import EditedEntity from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results from cubicweb.server.sources.rql2sql import SQLGenerator diff -r 611663348158 -r ef29d3ea3909 server/sources/storages.py --- a/server/sources/storages.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/sources/storages.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -24,7 +24,7 @@ from cubicweb import Binary, ValidationError from cubicweb.server import hook -from cubicweb.server.ssplanner import EditedEntity +from cubicweb.server.edition import EditedEntity def set_attribute_storage(repo, etype, attr, storage): diff -r 611663348158 -r ef29d3ea3909 server/ssplanner.py --- a/server/ssplanner.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/ssplanner.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -21,8 +21,6 @@ __docformat__ = "restructuredtext en" -from copy import copy - from rql.stmts import Union, Select from rql.nodes import Constant, Relation @@ -31,6 +29,7 @@ from cubicweb.rqlrewrite import add_types_restriction from cubicweb.server.session import security_enabled from cubicweb.server.hook import CleanupDeletedEidsCacheOp +from cubicweb.server.edition import EditedEntity READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity')) @@ -128,132 +127,6 @@ return select -_MARKER = object() - -class dict_protocol_catcher(object): - def __init__(self, entity): - self.__entity = entity - def __getitem__(self, attr): - return self.__entity.cw_edited[attr] - def __setitem__(self, attr, value): - self.__entity.cw_edited[attr] = value - def __getattr__(self, attr): - return getattr(self.__entity, attr) - - -class EditedEntity(dict): - """encapsulate entities attributes being written by an RQL query""" - def __init__(self, entity, **kwargs): - dict.__init__(self, **kwargs) - self.entity = entity - self.skip_security = set() - self.querier_pending_relations = {} - self.saved = False - - def __hash__(self): - # dict|set keyable - return hash(id(self)) - - def __cmp__(self, other): - # we don't want comparison by value inherited from dict - return cmp(id(self), id(other)) - - def __setitem__(self, attr, value): - assert attr != 'eid' - # don't add attribute into skip_security if already in edited - # attributes, else we may accidentaly skip a desired security check - if attr not in self: - self.skip_security.add(attr) - self.edited_attribute(attr, value) - - def __delitem__(self, attr): - assert not self.saved, 'too late to modify edited attributes' - super(EditedEntity, self).__delitem__(attr) - self.entity.cw_attr_cache.pop(attr, None) - - def pop(self, attr, *args): - # don't update skip_security by design (think to storage api) - assert not self.saved, 'too late to modify edited attributes' - value = super(EditedEntity, self).pop(attr, *args) - self.entity.cw_attr_cache.pop(attr, *args) - return value - - def setdefault(self, attr, default): - assert attr != 'eid' - # don't add attribute into skip_security if already in edited - # attributes, else we may accidentaly skip a desired security check - if attr not in self: - self[attr] = default - return self[attr] - - def update(self, values, skipsec=True): - if skipsec: - setitem = self.__setitem__ - else: - setitem = self.edited_attribute - for attr, value in values.iteritems(): - setitem(attr, value) - - def edited_attribute(self, attr, value): - """attribute being edited by a rql query: should'nt be added to - skip_security - """ - assert not self.saved, 'too late to modify edited attributes' - super(EditedEntity, self).__setitem__(attr, value) - self.entity.cw_attr_cache[attr] = value - - def oldnewvalue(self, attr): - """returns the couple (old attr value, new attr value) - - NOTE: will only work in a before_update_entity hook - """ - assert not self.saved, 'too late to get the old value' - # get new value and remove from local dict to force a db query to - # fetch old value - newvalue = self.entity.cw_attr_cache.pop(attr, _MARKER) - oldvalue = getattr(self.entity, attr) - if newvalue is not _MARKER: - self.entity.cw_attr_cache[attr] = newvalue - else: - newvalue = oldvalue - return oldvalue, newvalue - - def set_defaults(self): - """set default values according to the schema""" - for attr, value in self.entity.e_schema.defaults(): - if not attr in self: - self[str(attr)] = value - - def check(self, creation=False): - """check the entity edition against its schema. Only final relation - are checked here, constraint on actual relations are checked in hooks - """ - entity = self.entity - if creation: - # on creations, we want to check all relations, especially - # required attributes - relations = [rschema for rschema in entity.e_schema.subject_relations() - if rschema.final and rschema.type != 'eid'] - else: - relations = [entity._cw.vreg.schema.rschema(rtype) - for rtype in self] - from yams import ValidationError - try: - entity.e_schema.check(dict_protocol_catcher(entity), - creation=creation, _=entity._cw._, - relations=relations) - except ValidationError, ex: - ex.entity = self.entity - raise - - def clone(self): - thecopy = EditedEntity(copy(self.entity)) - thecopy.entity.cw_attr_cache = copy(self.entity.cw_attr_cache) - thecopy.entity._cw_related_cache = {} - thecopy.update(self, skipsec=False) - return thecopy - - class SSPlanner(object): """SingleSourcePlanner: build execution plan for rql queries diff -r 611663348158 -r ef29d3ea3909 server/test/unittest_ldapuser.py --- a/server/test/unittest_ldapuser.py Thu Mar 24 15:21:13 2011 +0100 +++ b/server/test/unittest_ldapuser.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. diff -r 611663348158 -r ef29d3ea3909 test/unittest_entity.py --- a/test/unittest_entity.py Thu Mar 24 15:21:13 2011 +0100 +++ b/test/unittest_entity.py Wed Mar 30 11:17:58 2011 +0200 @@ -223,38 +223,48 @@ 'Any X,AA ORDERBY AA DESC ' 'WHERE E eid %(x)s, E tags X, X modification_date AA') - def test_unrelated_rql_security_1(self): + def test_unrelated_rql_security_1_manager(self): user = self.request().user rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC ' - 'WHERE NOT S use_email O, S eid %(x)s, O is EmailAddress, O address AA, O alias AB, O modification_date AC') + 'WHERE NOT S use_email O, S eid %(x)s, ' + 'O is EmailAddress, O address AA, O alias AB, O modification_date AC') + + def test_unrelated_rql_security_1_user(self): self.create_user('toto') self.login('toto') user = self.request().user rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC ' - 'WHERE NOT S use_email O, S eid %(x)s, O is EmailAddress, O address AA, O alias AB, O modification_date AC') + 'WHERE NOT S use_email O, S eid %(x)s, ' + 'O is EmailAddress, O address AA, O alias AB, O modification_date AC') user = self.execute('Any X WHERE X login "admin"').get_entity(0, 0) - self.assertRaises(Unauthorized, user.cw_unrelated_rql, 'use_email', 'EmailAddress', 'subject') + rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] + self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE ' + 'NOT EXISTS(S use_email O), S eid %(x)s, ' + 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, ' + 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') + + def test_unrelated_rql_security_1_anon(self): self.login('anon') user = self.request().user - self.assertRaises(Unauthorized, user.cw_unrelated_rql, 'use_email', 'EmailAddress', 'subject') + rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] + self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE ' + 'NOT EXISTS(S use_email O), S eid %(x)s, ' + 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, ' + 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') def test_unrelated_rql_security_2(self): email = self.execute('INSERT EmailAddress X: X address "hop"').get_entity(0, 0) rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0] self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ASC ' 'WHERE NOT S use_email O, O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD') - #rql = email.cw_unrelated_rql('use_email', 'Person', 'object')[0] - #self.assertEqual(rql, '') self.login('anon') email = self.execute('Any X WHERE X eid %(x)s', {'x': email.eid}).get_entity(0, 0) rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0] self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ' 'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, ' 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') - #rql = email.cw_unrelated_rql('use_email', 'Person', 'object')[0] - #self.assertEqual(rql, '') def test_unrelated_rql_security_nonexistant(self): self.login('anon') diff -r 611663348158 -r ef29d3ea3909 test/unittest_rqlrewrite.py --- a/test/unittest_rqlrewrite.py Thu Mar 24 15:21:13 2011 +0100 +++ b/test/unittest_rqlrewrite.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -62,15 +62,17 @@ def simplify(mainrqlst, needcopy=False): rqlhelper.simplify(rqlst, needcopy) rewriter = RQLRewriter(mock_object(vreg=FakeVReg, user=(mock_object(eid=1)))) - for v, snippets in snippets_map.items(): - snippets_map[v] = [isinstance(snippet, basestring) - and mock_object(snippet_rqlst=parse('Any X WHERE '+snippet).children[0], - expression='Any X WHERE '+snippet) - or snippet - for snippet in snippets] + snippets = [] + for v, exprs in snippets_map.items(): + rqlexprs = [isinstance(snippet, basestring) + and mock_object(snippet_rqlst=parse('Any X WHERE '+snippet).children[0], + expression='Any X WHERE '+snippet) + or snippet + for snippet in exprs] + snippets.append((dict([v]), rqlexprs)) rqlhelper.compute_solutions(rqlst.children[0], {'eid': eid_func_map}, kwargs=kwargs) solutions = rqlst.children[0].solutions - rewriter.rewrite(rqlst.children[0], snippets_map.items(), solutions, kwargs, + rewriter.rewrite(rqlst.children[0], snippets, solutions, kwargs, existingvars) test_vrefs(rqlst.children[0]) return rewriter.rewritten diff -r 611663348158 -r ef29d3ea3909 utils.py --- a/utils.py Thu Mar 24 15:21:13 2011 +0100 +++ b/utils.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. diff -r 611663348158 -r ef29d3ea3909 web/data/cubicweb.ajax.js --- a/web/data/cubicweb.ajax.js Thu Mar 24 15:21:13 2011 +0100 +++ b/web/data/cubicweb.ajax.js Wed Mar 30 11:17:58 2011 +0200 @@ -283,7 +283,7 @@ * dictionary, `reqtype` the HTTP request type (get 'GET' or 'POST'). */ function loadRemote(url, form, reqtype, sync) { - if (!url.toLowerCase().startswith(baseuri())) { + if (!url.toLowerCase().startswith(baseuri().toLowerCase())) { url = baseuri() + url; } if (!sync) { diff -r 611663348158 -r ef29d3ea3909 web/data/cubicweb.old.css --- a/web/data/cubicweb.old.css Thu Mar 24 15:21:13 2011 +0100 +++ b/web/data/cubicweb.old.css Wed Mar 30 11:17:58 2011 +0200 @@ -283,7 +283,7 @@ } table#mainLayout{ - margin:0px 3px; + padding: 0px 3px; } table#mainLayout td#contentColumn { diff -r 611663348158 -r ef29d3ea3909 web/data/jquery.ui.datepicker-es.js --- a/web/data/jquery.ui.datepicker-es.js Thu Mar 24 15:21:13 2011 +0100 +++ b/web/data/jquery.ui.datepicker-es.js Wed Mar 30 11:17:58 2011 +0200 @@ -10,9 +10,9 @@ 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'], monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'], - dayNames: ['Domingo','Lunes','Martes','Miércoles','Jueves','Viernes','Sábado'], - dayNamesShort: ['Dom','Lun','Mar','Mié','Juv','Vie','Sáb'], - dayNamesMin: ['Do','Lu','Ma','Mi','Ju','Vi','Sá'], + dayNames: ['Domingo','Lunes','Martes','Miércoles','Jueves','Viernes','Sábado'], + dayNamesShort: ['Dom','Lun','Mar','Mié','Juv','Vie','Sáb'], + dayNamesMin: ['Do','Lu','Ma','Mi','Ju','Vi','Sá'], weekHeader: 'Sm', dateFormat: 'dd/mm/yy', firstDay: 1, diff -r 611663348158 -r ef29d3ea3909 web/facet.py --- a/web/facet.py Thu Mar 24 15:21:13 2011 +0100 +++ b/web/facet.py Wed Mar 30 11:17:58 2011 +0200 @@ -53,7 +53,7 @@ from logilab.mtconverter import xml_escape from logilab.common.graph import has_path from logilab.common.decorators import cached -from logilab.common.date import datetime2ticks +from logilab.common.date import datetime2ticks, ustrftime, ticks2datetime from logilab.common.compat import all from rql import parse, nodes, utils @@ -981,7 +981,11 @@ def formatvalue(self, value): """format `value` before in order to insert it in the RQL query""" - return '"%s"' % date.fromtimestamp(float(value) / 1000).strftime('%Y/%m/%d') + try: + date_value = ticks2datetime(float(value)) + except (ValueError, OverflowError): + return u'"date out-of-range"' + return '"%s"' % ustrftime(date_value, '%Y/%m/%d') class HasRelationFacet(AbstractFacet): diff -r 611663348158 -r ef29d3ea3909 web/formwidgets.py --- a/web/formwidgets.py Thu Mar 24 15:21:13 2011 +0100 +++ b/web/formwidgets.py Wed Mar 30 11:17:58 2011 +0200 @@ -594,8 +594,11 @@ value = self.values(form, field)[0] else: value = self.datestr + attrs = {} + if self.settabindex: + attrs['tabindex'] = req.next_tabindex() return tags.input(id=domid, name=domid, value=value, - type='text', size='10') + type='text', size='10', **attrs) class JQueryTimePicker(FieldWidget): @@ -620,6 +623,9 @@ value = self.values(form, field)[0] else: value = self.timestr + attrs = {} + if self.settabindex: + attrs['tabindex'] = req.next_tabindex() return tags.input(id=domid, name=domid, value=value, type='text', size='5') diff -r 611663348158 -r ef29d3ea3909 web/test/unittest_reledit.py --- a/web/test/unittest_reledit.py Thu Mar 24 15:21:13 2011 +0100 +++ b/web/test/unittest_reledit.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -33,9 +33,9 @@ class ClickAndEditFormTC(ReleditMixinTC, CubicWebTC): def test_default_config(self): - reledit = {'title': """
cubicweb-world-domination
""", - 'long_desc': """
<not specified>
""", - 'manager': """
<not specified>
""", + reledit = {'title': """
cubicweb-world-domination
""", + 'long_desc': """
<not specified>
""", + 'manager': """
<not specified>
""", 'composite_card11_2ttypes': """<not specified>""", 'concerns': """<not specified>"""} @@ -76,7 +76,7 @@ -""", +""", 'long_desc': """
<not specified>
@@ -120,7 +120,7 @@
-
""", +""", 'manager': """
<not specified>
@@ -156,7 +156,7 @@
-
""", +""", 'composite_card11_2ttypes': """<not specified>""", 'concerns': """<not specified>""" } @@ -190,11 +190,11 @@ reledit_ctrl.tag_object_of(('Ticket', 'concerns', 'Project'), {'edit_target': 'rtype'}) reledit = { - 'title': """
cubicweb-world-domination
""", - 'long_desc': """
<long_desc is required>
""", - 'manager': """""", + 'title': """
cubicweb-world-domination
""", + 'long_desc': """
<long_desc is required>
""", + 'manager': """""", 'composite_card11_2ttypes': """<not specified>""", - 'concerns': """""" + 'concerns': """""" } for rschema, ttypes, role in self.proj.e_schema.relation_definitions(includefinal=True): if rschema not in reledit: diff -r 611663348158 -r ef29d3ea3909 web/webconfig.py --- a/web/webconfig.py Thu Mar 24 15:21:13 2011 +0100 +++ b/web/webconfig.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -297,7 +297,7 @@ baseurl = self['base-url'] or self.default_base_url() if baseurl and baseurl[-1] != '/': baseurl += '/' - if not self.repairing: + if not (self.repairing or self.creating): self.global_set_option('base-url', baseurl) httpsurl = self['https-url'] if httpsurl: diff -r 611663348158 -r ef29d3ea3909 web/webctl.py --- a/web/webctl.py Thu Mar 24 15:21:13 2011 +0100 +++ b/web/webctl.py Wed Mar 30 11:17:58 2011 +0200 @@ -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. @@ -17,26 +17,29 @@ # with CubicWeb. If not, see . """cubicweb-ctl commands and command handlers common to twisted/modpython web configuration +""" -""" __docformat__ = "restructuredtext en" +from logilab.common.shellutils import ASK + from cubicweb.toolsutils import CommandHandler, underline_title -from logilab.common.shellutils import ASK class WebCreateHandler(CommandHandler): cmdname = 'create' - def bootstrap(self, cubes, inputlevel=0): + def bootstrap(self, cubes, automatic=False, inputlevel=0): """bootstrap this configuration""" - print '\n' + underline_title('Generic web configuration') - config = self.config - if config.repo_method == 'pyro' or config.pyro_enabled(): - print '\n' + underline_title('Pyro configuration') - config.input_config('pyro', inputlevel) - if ASK.confirm('Allow anonymous access ?', False): - config.global_set_option('anonymous-user', 'anon') - config.global_set_option('anonymous-password', 'anon') + if not automatic: + print '\n' + underline_title('Generic web configuration') + config = self.config + if config.repo_method == 'pyro' or config.pyro_enabled(): + print '\n' + underline_title('Pyro configuration') + config.input_config('pyro', inputlevel) + config.input_config('web', inputlevel) + if ASK.confirm('Allow anonymous access ?', False): + config.global_set_option('anonymous-user', 'anon') + config.global_set_option('anonymous-password', 'anon') - def postcreate(self): + def postcreate(self, *args, **kwargs): """hooks called once instance's initialization has been completed"""