# HG changeset patch # User Sylvain Thénault # Date 1323351177 -3600 # Node ID 29cdde6bb9ef92fff3753c9bc4e700b3e2fc5184 # Parent 6510654269a6c4481f66a3ace78b99aad0d898dd# Parent 7b2c7f3d370303bdcd6ac264da75bc7ef0ce3eda backport stable diff -r 7b2c7f3d3703 -r 29cdde6bb9ef .hgtags --- a/.hgtags Thu Dec 08 14:29:48 2011 +0100 +++ b/.hgtags Thu Dec 08 14:32:57 2011 +0100 @@ -233,5 +233,7 @@ 43f83f5d0a4d57a06e9a4990bc957fcfa691eec3 cubicweb-debian-version-3.13.8-1 07afe32945aa275052747f78ef1f55858aaf6fa9 cubicweb-version-3.13.9 0a3cb5e60d57a7a9851371b4ae487094ec2bf614 cubicweb-debian-version-3.13.9-1 +5c4390eb10c3fe76a81e6fccec109d7097dc1a8d cubicweb-version-3.14.0 +0bfe22fceb383b46d62b437bf5dd0141a714afb8 cubicweb-debian-version-3.14.0-1 2ad4e5173c73a43804c265207bcabb8940bd42f4 cubicweb-version-3.13.10 2eab9a5a6bf8e3b0cf706bee8cdf697759c0a33a cubicweb-debian-version-3.13.10-1 diff -r 7b2c7f3d3703 -r 29cdde6bb9ef __pkginfo__.py --- a/__pkginfo__.py Thu Dec 08 14:29:48 2011 +0100 +++ b/__pkginfo__.py Thu Dec 08 14:32:57 2011 +0100 @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 13, 10) +numversion = (3, 14, 0) version = '.'.join(str(num) for num in numversion) description = "a repository of entities / relations for knowledge management" @@ -40,10 +40,10 @@ ] __depends__ = { - 'logilab-common': '>= 0.56.2', + 'logilab-common': '>= 0.57.0', 'logilab-mtconverter': '>= 0.8.0', 'rql': '>= 0.28.0', - 'yams': '>= 0.33.0', + 'yams': '>= 0.34.0', 'docutils': '>= 0.6', #gettext # for xgettext, msgcat, etc... # web dependancies @@ -52,7 +52,7 @@ 'Twisted': '', # XXX graphviz # server dependencies - 'logilab-database': '>= 1.5.0', + 'logilab-database': '>= 1.8.1', 'pysqlite': '>= 2.5.5', # XXX install pysqlite2 } diff -r 7b2c7f3d3703 -r 29cdde6bb9ef appobject.py --- a/appobject.py Thu Dec 08 14:29:48 2011 +0100 +++ b/appobject.py Thu Dec 08 14:32:57 2011 +0100 @@ -43,12 +43,6 @@ def class_regid(cls): """returns a unique identifier for an appobject class""" - if 'id' in cls.__dict__: - warn('[3.6] %s.%s: id is deprecated, use __regid__' - % (cls.__module__, cls.__name__), DeprecationWarning) - cls.__regid__ = cls.id - if hasattr(cls, 'id') and not isinstance(cls.id, property): - return cls.id return cls.__regid__ # helpers for debugging selectors @@ -414,13 +408,7 @@ the right hook to create an instance for example). By default the appobject is returned without any transformation. """ - try: # XXX < 3.6 bw compat - pdefs = cls.property_defs # pylint: disable=E1101 - except AttributeError: - pdefs = getattr(cls, 'cw_property_defs', {}) - else: - warn('[3.6] property_defs is deprecated, use cw_property_defs in %s' - % cls, DeprecationWarning) + pdefs = getattr(cls, 'cw_property_defs', {}) for propid, pdef in pdefs.items(): pdef = pdef.copy() # may be shared pdef['default'] = getattr(cls, propid, pdef['default']) @@ -471,113 +459,6 @@ """ return self._cw.property_value(self._cwpropkey(propid)) - # deprecated ############################################################### - - @property - @deprecated('[3.6] use self.__regid__') - def id(self): - return self.__regid__ - - @property - @deprecated('[3.6] use self._cw.vreg') - def vreg(self): - return self._cw.vreg - - @property - @deprecated('[3.6] use self._cw.vreg.schema') - def schema(self): - return self._cw.vreg.schema - - @property - @deprecated('[3.6] use self._cw.vreg.config') - def config(self): - return self._cw.vreg.config - - @property - @deprecated('[3.6] use self._cw') - def req(self): - return self._cw - - @deprecated('[3.6] use self.cw_rset') - def get_rset(self): - return self.cw_rset - @deprecated('[3.6] use self.cw_rset') - def set_rset(self, rset): - self.cw_rset = rset - rset = property(get_rset, set_rset) - - @property - @deprecated('[3.6] use self.cw_row') - def row(self): - return self.cw_row - - @property - @deprecated('[3.6] use self.cw_col') - def col(self): - return self.cw_col - - @property - @deprecated('[3.6] use self.cw_extra_kwargs') - def extra_kwargs(self): - return self.cw_extra_kwargs - - @deprecated('[3.6] use self._cw.view') - def view(self, *args, **kwargs): - return self._cw.view(*args, **kwargs) - - @property - @deprecated('[3.6] use self._cw.varmaker') - def varmaker(self): - return self._cw.varmaker - - @deprecated('[3.6] use self._cw.get_cache') - def get_cache(self, cachename): - return self._cw.get_cache(cachename) - - @deprecated('[3.6] use self._cw.build_url') - def build_url(self, *args, **kwargs): - return self._cw.build_url(*args, **kwargs) - - @deprecated('[3.6] use self.cw_rset.limited_rql') - def limited_rql(self): - return self.cw_rset.limited_rql() - - @deprecated('[3.6] use self.cw_rset.complete_entity(row,col) instead') - def complete_entity(self, row, col=0, skip_bytes=True): - return self.cw_rset.complete_entity(row, col, skip_bytes) - - @deprecated('[3.6] use self.cw_rset.get_entity(row,col) instead') - def entity(self, row, col=0): - return self.cw_rset.get_entity(row, col) - - @deprecated('[3.6] use self._cw.user_rql_callback') - def user_rql_callback(self, args, msg=None): - return self._cw.user_rql_callback(args, msg) - - @deprecated('[3.6] use self._cw.user_callback') - def user_callback(self, cb, args, msg=None, nonify=False): - return self._cw.user_callback(cb, args, msg, nonify) - - @deprecated('[3.6] use self._cw.format_date') - def format_date(self, date, date_format=None, time=False): - return self._cw.format_date(date, date_format, time) - - @deprecated('[3.6] use self._cw.format_time') - def format_time(self, time): - return self._cw.format_time(time) - - @deprecated('[3.6] use self._cw.format_float') - def format_float(self, num): - return self._cw.format_float(num) - - @deprecated('[3.6] use self._cw.parse_datetime') - def parse_datetime(self, value, etype='Datetime'): - return self._cw.parse_datetime(value, etype) - - @deprecated('[3.6] use self.cw_propval') - def propval(self, propid): - return self._cw.property_value(self._cwpropkey(propid)) - # these are overridden by set_log_methods below # only defining here to prevent pylint from complaining info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None diff -r 7b2c7f3d3703 -r 29cdde6bb9ef bin/clone_deps.py --- a/bin/clone_deps.py Thu Dec 08 14:29:48 2011 +0100 +++ b/bin/clone_deps.py Thu Dec 08 14:32:57 2011 +0100 @@ -63,7 +63,7 @@ elif len(sys.argv) == 2: base_url = sys.argv[1] else: - print >> sys.stderr, 'usage %s [base_url]' % sys.argv[0] + sys.stderr.write('usage %s [base_url]\n' % sys.argv[0]) sys.exit(1) print len(to_clone), 'repositories will be cloned' base_dir = normpath(join(dirname(__file__), pardir, pardir)) @@ -104,9 +104,9 @@ To get started you may read http://docs.cubicweb.org/tutorials/base/index.html. """ % {'basedir': os.getcwd(), 'baseurl': base_url, 'sep': os.sep} if not_updated: - print >> sys.stderr, 'WARNING: The following repositories were not updated (no debian tag found):' + sys.stderr.write('WARNING: The following repositories were not updated (no debian tag found):\n') for path in not_updated: - print >> sys.stderr, '\t-', path + sys.stderr.write('\t-%s\n' % path) if __name__ == '__main__': main() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef common/__init__.py --- a/common/__init__.py Thu Dec 08 14:29:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# copyright 2003-2010 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 . -"""Common subpackage of cubicweb : defines library functions used both on the -hg stserver side and on the client side - -""" - diff -r 7b2c7f3d3703 -r 29cdde6bb9ef common/mail.py --- a/common/mail.py Thu Dec 08 14:29:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# copyright 2003-2010 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 . -"""pre 3.6 bw compat""" -# pylint: disable=W0614,W0401 -from warnings import warn -warn('moved to cubicweb.mail', DeprecationWarning, stacklevel=2) -from cubicweb.mail import * diff -r 7b2c7f3d3703 -r 29cdde6bb9ef common/mixins.py --- a/common/mixins.py Thu Dec 08 14:29:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# copyright 2003-2010 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 . -"""pre 3.6 bw compat""" -# pylint: disable=W0614,W0401 -from warnings import warn -warn('moved to cubicweb.mixins', DeprecationWarning, stacklevel=2) -from cubicweb.mixins import * diff -r 7b2c7f3d3703 -r 29cdde6bb9ef common/mttransforms.py --- a/common/mttransforms.py Thu Dec 08 14:29:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# copyright 2003-2010 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 . -"""pre 3.6 bw compat""" -# pylint: disable=W0614,W0401 -from warnings import warn -warn('moved to cubicweb.mttransforms', DeprecationWarning, stacklevel=2) -from cubicweb.mttransforms import * diff -r 7b2c7f3d3703 -r 29cdde6bb9ef common/tags.py --- a/common/tags.py Thu Dec 08 14:29:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# copyright 2003-2010 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 . -"""pre 3.6 bw compat""" -# pylint: disable=W0614,W0401 -from warnings import warn -warn('moved to cubicweb.tags', DeprecationWarning, stacklevel=2) -from cubicweb.tags import * diff -r 7b2c7f3d3703 -r 29cdde6bb9ef common/uilib.py --- a/common/uilib.py Thu Dec 08 14:29:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# copyright 2003-2010 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 . -"""pre 3.6 bw compat""" -# pylint: disable=W0614,W0401 -from warnings import warn -warn('moved to cubicweb.uilib', DeprecationWarning, stacklevel=2) -from cubicweb.uilib import * diff -r 7b2c7f3d3703 -r 29cdde6bb9ef cwconfig.py --- a/cwconfig.py Thu Dec 08 14:29:48 2011 +0100 +++ b/cwconfig.py Thu Dec 08 14:32:57 2011 +0100 @@ -255,19 +255,19 @@ ('date-format', {'type' : 'string', 'default': '%Y/%m/%d', - 'help': _('how to format date in the ui ("man strftime" for format description)'), + 'help': _('how to format date in the ui (see this page for format description)'), 'group': 'ui', }), ('datetime-format', {'type' : 'string', 'default': '%Y/%m/%d %H:%M', - 'help': _('how to format date and time in the ui ("man strftime" for format description)'), + 'help': _('how to format date and time in the ui (see this page for format description)'), 'group': 'ui', }), ('time-format', {'type' : 'string', 'default': '%H:%M', - 'help': _('how to format time in the ui ("man strftime" for format description)'), + 'help': _('how to format time in the ui (see this page for format description)'), 'group': 'ui', }), ('float-format', @@ -645,7 +645,6 @@ ctlfile, err) cls.info('loaded cubicweb-ctl plugin %s', ctlfile) for cube in cls.available_cubes(): - oldpluginfile = join(cls.cube_dir(cube), 'ecplugin.py') pluginfile = join(cls.cube_dir(cube), 'ccplugin.py') initfile = join(cls.cube_dir(cube), '__init__.py') if exists(pluginfile): @@ -654,14 +653,6 @@ cls.info('loaded cubicweb-ctl plugin from %s', cube) except Exception: cls.exception('while loading plugin %s', pluginfile) - elif exists(oldpluginfile): - warn('[3.6] %s: ecplugin module should be renamed to ccplugin' % cube, - DeprecationWarning) - try: - __import__('cubes.%s.ecplugin' % cube) - cls.info('loaded cubicweb-ctl plugin from %s', cube) - except Exception: - cls.exception('while loading plugin %s', oldpluginfile) elif exists(initfile): try: __import__('cubes.%s' % cube) @@ -795,13 +786,6 @@ if exists(sitefile) and not sitefile in self._site_loaded: self._load_site_cubicweb(sitefile) self._site_loaded.add(sitefile) - else: - sitefile = join(path, 'site_erudi.py') - if exists(sitefile) and not sitefile in self._site_loaded: - self._load_site_cubicweb(sitefile) - self._site_loaded.add(sitefile) - self.warning('[3.5] site_erudi.py is deprecated, should be ' - 'renamed to site_cubicweb.py') def _load_site_cubicweb(self, sitefile): # XXX extrapath argument to load_module_from_file only in lgc > 0.50.2 diff -r 7b2c7f3d3703 -r 29cdde6bb9ef cwctl.py --- a/cwctl.py Thu Dec 08 14:29:48 2011 +0100 +++ b/cwctl.py Thu Dec 08 14:32:57 2011 +0100 @@ -155,7 +155,7 @@ try: status = max(status, self.run_arg(appid)) except (KeyboardInterrupt, SystemExit): - print >> sys.stderr, '%s aborted' % self.name + sys.stderr.write('%s aborted\n' % self.name) return 2 # specific error code sys.exit(status) @@ -164,14 +164,14 @@ try: status = cmdmeth(appid) except (ExecutionError, ConfigurationError), ex: - print >> sys.stderr, 'instance %s not %s: %s' % ( - appid, self.actionverb, ex) + sys.stderr.write('instance %s not %s: %s\n' % ( + appid, self.actionverb, ex)) status = 4 except Exception, ex: import traceback traceback.print_exc() - print >> sys.stderr, 'instance %s not %s: %s' % ( - appid, self.actionverb, ex) + sys.stderr.write('instance %s not %s: %s\n' % ( + appid, self.actionverb, ex)) status = 8 return status @@ -548,20 +548,19 @@ helper.poststop() # do this anyway pidf = config['pid-file'] if not exists(pidf): - print >> sys.stderr, "%s doesn't exist." % pidf + sys.stderr.write("%s doesn't exist.\n" % pidf) return import signal pid = int(open(pidf).read().strip()) try: kill(pid, signal.SIGTERM) except Exception: - print >> sys.stderr, "process %s seems already dead." % pid + sys.stderr.write("process %s seems already dead.\n" % pid) else: try: wait_process_end(pid) except ExecutionError, ex: - print >> sys.stderr, ex - print >> sys.stderr, 'trying SIGKILL' + sys.stderr.write('%s\ntrying SIGKILL\n' % ex) try: kill(pid, signal.SIGKILL) except Exception: diff -r 7b2c7f3d3703 -r 29cdde6bb9ef cwvreg.py --- a/cwvreg.py Thu Dec 08 14:29:48 2011 +0100 +++ b/cwvreg.py Thu Dec 08 14:32:57 2011 +0100 @@ -243,38 +243,6 @@ def schema(self): return self.vreg.schema - @deprecated('[3.6] select object, then use obj.render()') - def render(self, __oid, req, __fallback_oid=None, rset=None, initargs=None, - **kwargs): - """Select object with the given id (`__oid`) then render it. If the - object isn't selectable, try to select fallback object if - `__fallback_oid` is specified. - - If specified `initargs` is expected to be a dictionnary containing - arguments that should be given to selection (hence to object's __init__ - as well), but not to render(). Other arbitrary keyword arguments will be - given to selection *and* to render(), and so should be handled by - object's call or cell_call method.. - """ - if initargs is None: - initargs = kwargs - else: - initargs.update(kwargs) - try: - obj = self.select(__oid, req, rset=rset, **initargs) - except NoSelectableObject: - if __fallback_oid is None: - raise - obj = self.select(__fallback_oid, req, rset=rset, **initargs) - return obj.render(**kwargs) - - @deprecated('[3.6] use select_or_none and test for obj.cw_propval("visible")') - def select_vobject(self, oid, *args, **kwargs): - selected = self.select_or_none(oid, *args, **kwargs) - if selected and selected.cw_propval('visible'): - return selected - return None - def poss_visible_objects(self, *args, **kwargs): """return an ordered list of possible app objects in a given registry, supposing they support the 'visible' and 'order' properties (as most @@ -283,7 +251,6 @@ return sorted([x for x in self.possible_objects(*args, **kwargs) if x.cw_propval('visible')], key=lambda x: x.cw_propval('order')) - possible_vobjects = deprecated('[3.6] use poss_visible_objects()')(poss_visible_objects) VRegistry.REGISTRY_FACTORY[None] = CWRegistry @@ -816,40 +783,6 @@ self.warning('%s (you should probably delete that property ' 'from the database)', ex) - # deprecated code #################################################### - - @deprecated('[3.4] use vreg["etypes"].etype_class(etype)') - def etype_class(self, etype): - return self["etypes"].etype_class(etype) - - @deprecated('[3.4] use vreg["views"].main_template(*args, **kwargs)') - def main_template(self, req, oid='main-template', **context): - return self["views"].main_template(req, oid, **context) - - @deprecated('[3.4] use vreg[registry].possible_vobjects(*args, **kwargs)') - def possible_vobjects(self, registry, *args, **kwargs): - return self[registry].possible_vobjects(*args, **kwargs) - - @deprecated('[3.4] use vreg["actions"].possible_actions(*args, **kwargs)') - def possible_actions(self, req, rset=None, **kwargs): - return self["actions"].possible_actions(req, rest=rset, **kwargs) - - @deprecated('[3.4] use vreg["ctxcomponents"].select_object(...)') - def select_box(self, oid, *args, **kwargs): - return self['boxes'].select_object(oid, *args, **kwargs) - - @deprecated('[3.4] use vreg["components"].select_object(...)') - def select_component(self, cid, *args, **kwargs): - return self['components'].select_object(cid, *args, **kwargs) - - @deprecated('[3.4] use vreg["actions"].select_object(...)') - def select_action(self, oid, *args, **kwargs): - return self['actions'].select_object(oid, *args, **kwargs) - - @deprecated('[3.4] use vreg["views"].select(...)') - def select_view(self, __vid, req, rset=None, **kwargs): - return self['views'].select(__vid, req, rset=rset, **kwargs) - # XXX unify with yams.constraints.BASE_CONVERTERS? YAMS_TO_PY = BASE_CONVERTERS.copy() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef dbapi.py --- a/dbapi.py Thu Dec 08 14:29:48 2011 +0100 +++ b/dbapi.py Thu Dec 08 14:32:57 2011 +0100 @@ -223,13 +223,32 @@ return repo_connect(repo, login, cnxprops=cnxprops, **kwargs) def in_memory_repo_cnx(config, login, **kwargs): - """usefull method for testing and scripting to get a dbapi.Connection + """useful method for testing and scripting to get a dbapi.Connection object connected to an in-memory repository instance """ # connection to the CubicWeb repository repo = in_memory_repo(config) return repo, in_memory_cnx(repo, login, **kwargs) + +def anonymous_session(vreg): + """return a new anonymous session + + raises an AuthenticationError if anonymous usage is not allowed + """ + anoninfo = vreg.config.anonymous_user() + if anoninfo is None: # no anonymous user + raise AuthenticationError('anonymous access is not authorized') + anon_login, anon_password = anoninfo + cnxprops = ConnectionProperties(vreg.config.repo_method) + # use vreg's repository cache + repo = vreg.config.repository(vreg) + anon_cnx = repo_connect(repo, anon_login, + cnxprops=cnxprops, password=anon_password) + anon_cnx.vreg = vreg + return DBAPISession(anon_cnx, anon_login) + + class _NeedAuthAccessMock(object): def __getattribute__(self, attr): raise AuthenticationError() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef debian/changelog --- a/debian/changelog Thu Dec 08 14:29:48 2011 +0100 +++ b/debian/changelog Thu Dec 08 14:32:57 2011 +0100 @@ -1,3 +1,9 @@ +cubicweb (3.14.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Wed, 09 Nov 2011 17:17:45 +0100 + cubicweb (3.13.10-1) unstable; urgency=low * new upstream release diff -r 7b2c7f3d3703 -r 29cdde6bb9ef debian/control --- a/debian/control Thu Dec 08 14:29:48 2011 +0100 +++ b/debian/control Thu Dec 08 14:32:57 2011 +0100 @@ -35,7 +35,7 @@ Conflicts: cubicweb-multisources Replaces: cubicweb-multisources Provides: cubicweb-multisources -Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.5.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2, python-logilab-common (>= 0.56.2) +Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.8.1), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2 Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version}) Description: server part of the CubicWeb framework CubicWeb is a semantic web application framework. @@ -70,7 +70,7 @@ Architecture: all XB-Python-Version: ${python:Versions} Provides: cubicweb-web-frontend -Depends: ${misc:Depends}, ${python:Depends}, cubicweb-web (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-twisted-web, python-logilab-common (>= 0.56.2) +Depends: ${misc:Depends}, ${python:Depends}, cubicweb-web (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-twisted-web Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version}) Description: twisted-based web interface for the CubicWeb framework CubicWeb is a semantic web application framework. @@ -99,7 +99,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.2), python-yams (>= 0.33.0), python-rql (>= 0.28.0), python-lxml +Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.57.0), python-yams (>= 0.34.0), python-rql (>= 0.28.0), python-lxml Recommends: python-simpletal (>= 4.0), python-crypto Conflicts: cubicweb-core Replaces: cubicweb-core diff -r 7b2c7f3d3703 -r 29cdde6bb9ef debian/cubicweb-common.install.in --- a/debian/cubicweb-common.install.in Thu Dec 08 14:29:48 2011 +0100 +++ b/debian/cubicweb-common.install.in Thu Dec 08 14:32:57 2011 +0100 @@ -1,4 +1,3 @@ -debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/common/ usr/lib/PY_VERSION/site-packages/cubicweb debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/entities/ usr/lib/PY_VERSION/site-packages/cubicweb debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/ext/ usr/lib/PY_VERSION/site-packages/cubicweb debian/tmp/usr/share/cubicweb/cubes/ usr/share/cubicweb/ diff -r 7b2c7f3d3703 -r 29cdde6bb9ef devtools/__init__.py --- a/devtools/__init__.py Thu Dec 08 14:29:48 2011 +0100 +++ b/devtools/__init__.py Thu Dec 08 14:32:57 2011 +0100 @@ -581,7 +581,7 @@ except BaseException: if self.dbcnx is not None: self.dbcnx.rollback() - print >> sys.stderr, 'building', self.dbname, 'failed' + sys.stderr.write('building %s failed\n' % self.dbname) #self._drop(self.dbname) raise diff -r 7b2c7f3d3703 -r 29cdde6bb9ef devtools/devctl.py --- a/devtools/devctl.py Thu Dec 08 14:29:48 2011 +0100 +++ b/devtools/devctl.py Thu Dec 08 14:32:57 2011 +0100 @@ -263,10 +263,7 @@ objid = '%s_%s' % (reg, obj.__regid__) if objid in done: break - try: # XXX < 3.6 bw compat - pdefs = obj.property_defs - except AttributeError: - pdefs = getattr(obj, 'cw_property_defs', {}) + pdefs = getattr(obj, 'cw_property_defs', {}) if pdefs: yield objid done.add(objid) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef devtools/fill.py --- a/devtools/fill.py Thu Dec 08 14:29:48 2011 +0100 +++ b/devtools/fill.py Thu Dec 08 14:32:57 2011 +0100 @@ -20,12 +20,14 @@ __docformat__ = "restructuredtext en" +import logging from random import randint, choice from copy import deepcopy from datetime import datetime, date, time, timedelta from decimal import Decimal from logilab.common import attrdict +from logilab.mtconverter import xml_escape from yams.constraints import (SizeConstraint, StaticVocabularyConstraint, IntervalBoundConstraint, BoundaryConstraint, Attribute, actual_value) @@ -238,6 +240,14 @@ # raise exception return u'text/plain' + def generate_CWDataImport_log(self, entity, index, **kwargs): + # content_format attribute of EmailPart has no vocabulary constraint, we + # need this method else stupid values will be set which make mtconverter + # raise exception + logs = [u'%s\t%s\t%s\t%s
' % (logging.ERROR, 'http://url.com?arg1=hop&arg2=hip', + 1, xml_escape('hjoio&oio"'))] + return u'
'.join(logs) + class autoextend(type): def __new__(mcs, name, bases, classdict): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef devtools/test/unittest_dbfill.py --- a/devtools/test/unittest_dbfill.py Thu Dec 08 14:29:48 2011 +0100 +++ b/devtools/test/unittest_dbfill.py Thu Dec 08 14:32:57 2011 +0100 @@ -72,7 +72,7 @@ """test value generation from a given domain value""" firstname = self.person_valgen.generate_attribute_value({}, 'firstname', 12) possible_choices = self._choice_func('Person', 'firstname') - self.failUnless(firstname in possible_choices, + self.assertTrue(firstname in possible_choices, '%s not in %s' % (firstname, possible_choices)) def test_choice(self): @@ -80,21 +80,21 @@ # Test for random index for index in range(5): sx_value = self.person_valgen.generate_attribute_value({}, 'civility', index) - self.failUnless(sx_value in ('Mr', 'Mrs', 'Ms')) + self.assertTrue(sx_value in ('Mr', 'Mrs', 'Ms')) def test_integer(self): """test integer generation""" # Test for random index for index in range(5): cost_value = self.bug_valgen.generate_attribute_value({}, 'cost', index) - self.failUnless(cost_value in range(index+1)) + self.assertTrue(cost_value in range(index+1)) def test_date(self): """test date generation""" # Test for random index for index in range(10): date_value = self.person_valgen.generate_attribute_value({}, 'birthday', index) - self.failUnless(isinstance(date_value, datetime.date)) + self.assertTrue(isinstance(date_value, datetime.date)) def test_phone(self): """tests make_tel utility""" diff -r 7b2c7f3d3703 -r 29cdde6bb9ef devtools/test/unittest_fill.py --- a/devtools/test/unittest_fill.py Thu Dec 08 14:29:48 2011 +0100 +++ b/devtools/test/unittest_fill.py Thu Dec 08 14:32:57 2011 +0100 @@ -41,31 +41,31 @@ def test_autoextend(self): - self.failIf('generate_server' in dir(ValueGenerator)) + self.assertFalse('generate_server' in dir(ValueGenerator)) class MyValueGenerator(ValueGenerator): def generate_server(self, index): return attrname - self.failUnless('generate_server' in dir(ValueGenerator)) + self.assertTrue('generate_server' in dir(ValueGenerator)) def test_bad_signature_detection(self): - self.failIf('generate_server' in dir(ValueGenerator)) + self.assertFalse('generate_server' in dir(ValueGenerator)) try: class MyValueGenerator(ValueGenerator): def generate_server(self): pass except TypeError: - self.failIf('generate_server' in dir(ValueGenerator)) + self.assertFalse('generate_server' in dir(ValueGenerator)) else: self.fail('TypeError not raised') def test_signature_extension(self): - self.failIf('generate_server' in dir(ValueGenerator)) + self.assertFalse('generate_server' in dir(ValueGenerator)) class MyValueGenerator(ValueGenerator): def generate_server(self, index, foo): pass - self.failUnless('generate_server' in dir(ValueGenerator)) + self.assertTrue('generate_server' in dir(ValueGenerator)) if __name__ == '__main__': diff -r 7b2c7f3d3703 -r 29cdde6bb9ef devtools/testlib.py --- a/devtools/testlib.py Thu Dec 08 14:29:48 2011 +0100 +++ b/devtools/testlib.py Thu Dec 08 14:32:57 2011 +0100 @@ -387,31 +387,6 @@ req.cnx.commit() return user - @iclassmethod # XXX turn into a class method - def grant_permission(self, session, entity, group, pname=None, plabel=None): - """insert a permission on an entity. Will have to commit the main - connection to be considered - """ - if not isinstance(session, Session): - warn('[3.12] grant_permission arguments are now (session, entity, group, pname[, plabel])', - DeprecationWarning, stacklevel=2) - plabel = pname - pname = group - group = entity - entity = session - assert not isinstance(self, type) - session = self.session - pname = unicode(pname) - plabel = plabel and unicode(plabel) or unicode(group) - e = getattr(entity, 'eid', entity) - with security_enabled(session, False, False): - peid = session.execute( - 'INSERT CWPermission X: X name %(pname)s, X label %(plabel)s,' - 'X require_group G, E require_permission X ' - 'WHERE G name %(group)s, E eid %(e)s', - locals())[0][0] - return peid - def login(self, login, **kwargs): """return a connection for the given login/password""" if login == self.admlogin: @@ -851,7 +826,7 @@ output = output.strip() validator = self.get_validator(view, output=output) if validator is None: - return + return output # return raw output if no validator is defined if isinstance(validator, htmlparser.DTDValidator): # XXX remove used in progress widget, unknown in html dtd output = re.sub('', '', output) @@ -929,12 +904,6 @@ DeprecationWarning, stacklevel=2) return self.execute(rql, args, req=req).get_entity(0, 0) - @deprecated('[3.6] use self.request().create_entity(...)') - def add_entity(self, etype, req=None, **kwargs): - if req is None: - req = self.request() - return req.create_entity(etype, **kwargs) - # auto-populating test classes and utilities ################################### @@ -1130,7 +1099,7 @@ tags = AutoPopulateTest.tags | Tags('web', 'generated') def setUp(self): - assert not self.__class__ is AutomaticWebTest, 'Please subclass AutomaticWebTest to pprevent database caching issue' + assert not self.__class__ is AutomaticWebTest, 'Please subclass AutomaticWebTest to prevent database caching issue' super(AutomaticWebTest, self).setUp() # access to self.app for proper initialization of the authentication diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/3.14.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/3.14.rst Thu Dec 08 14:32:57 2011 +0100 @@ -0,0 +1,164 @@ +Whats new in CubicWeb 3.14 +========================== + +First notice CW 3.14 depends on yams 0.34 (which is incompatible with prior +cubicweb releases regarding instance re-creation). + +API changes +----------- + +* `Entity.fetch_rql` `restriction` argument has been deprecated and should be + replaced with a call to the new `Entity.fetch_rqlst` method, get the returned + value (a rql `Select` node) and use the RQL syntax tree API to include the + above-mentionned restrictions. + + Backward compat is kept with proper warning. + +* `Entity.fetch_order` and `Entity.fetch_unrelated_order` class methods have been + replaced by `Entity.cw_fetch_order` and `Entity.cw_fetch_unrelated_order` with + a different prototype: + + - instead of taking (attr, var) as two string argument, they now take (select, + attr, var) where select is the rql syntax tree beinx constructed and var the + variable *node*. + + - instead of returning some string to be inserted in the ORDERBY clause, it has + to modify the syntax tree + + Backward compat is kept with proper warning, BESIDE cases below: + + - custom order method return **something else the a variable name with or + without the sorting order** (e.g. cases where you sort on the value of a + registered procedure as it was done in the tracker for instance). In such + case, an error is logged telling that this sorting is ignored until API + upgrade. + + - client code use direct access to one of those methods on an entity (no code + known to do that). + +* `Entity._rest_attr_info` class method has been renamed to + `Entity.cw_rest_attr_info` + + No backward compat yet since this is a protected method an no code is known to + use it outside cubicweb itself. + +* `AnyEntity.linked_to` has been removed as part of a refactoring of this + functionality (link a entity to another one at creation step). It was replaced + by a `EntityFieldsForm.linked_to` property. + + In the same refactoring, `cubicweb.web.formfield.relvoc_linkedto`, + `cubicweb.web.formfield.relvoc_init` and + `cubicweb.web.formfield.relvoc_unrelated` were removed and replaced by + RelationField methods with the same names, that take a form as a parameter. + + **No backward compatibility yet**. It's still time to cry for it. + Cubes known to be affected: tracker, vcsfile, vcreview. + +* `CWPermission` entity type and its associated require_permission relation type + (abstract) and require_group relation definitions have been moved to a new + `localperms` cube. With this have gone some functions from the + `cubicweb.schemas` package as well as some views. This makes cubicweb itself + smaller while you get all the local permissions stuff into a single, + documented, place. + + Backward compat is kept for existing instances, **though you should have + installed the localperms cubes**. A proper error should be displayed when + trying to migrate to 3.14 an instance the use `CWPermission` without the new + cube installed. For new instances / test, you should add a dependancy on the + new cube in cubes using this feature, along with a dependancy on cubicweb >= + 3.14. + +* jQuery has been updated to 1.6.4 and jquery-tablesorter to 2.0.5. No backward + compat issue known. + +* Table views refactoring : new `RsetTableView` and `EntityTableView`, as well as + rewritten an enhanced version of `PyValTableView` on the same bases, with logic + moved to some column renderers and a layout. Those should be well documented + and deprecates former `TableView`, `EntityAttributesTableView` and `CellView`, + which are however kept for backward compat, with some warnings that may not be + very clear unfortunatly (you may see your own table view subclass name here, + which doesn't make the problem that clear). Notice that `_cw.view('table', + rset, *kwargs)` will be routed to the new `RsetTableView` or to the old + `TableView` depending on given extra arguments. See #1986413. + +* `display_name` don't call .lower() anymore. This may leads to changes in your + user interface. Different msgid for upper/lower cases version of entity type + names, as this is the only proper way to handle this with some languages. + +* `IEditControlAdapter` has been deprecated in favor of `EditController` + overloading, which was made easier by adding dedicated selectors called + `match_edited_type` and `match_form_id`. + +* Pre 3.6 API backward compat has been dropped, though *data* migration + compatibility has been kept. You may have to fix errors due to old API usage + for your instance before to be able to run migration, but then you should be + able to upgrade even a pre 3.6 database. + +* Deprecated `cubicweb.web.views.iprogress` in favor of new `iprogress` cube. + +* Deprecated `cubicweb.web.views.flot` in favor of new `jqplot` cube. + + +Unintrusive API changes +----------------------- + +* Refactored properties forms (eg user preferences and site wide properties) as + well as pagination components to ease overridding. + +* New `cubicweb.web.uihelper` module with high-level helpers for uicfg. + +* New `anonymized_request` decorator to temporary run stuff as an anonymous + user, whatever the currently logged in user. + +* New 'verbatimattr' attribute view. + +* New facet and form widget for Integer used to store binary mask. + +* New `js_href` function to generated proper javascript href. + +* `match_kwargs` and `match_form_params` selectors both accept a new + `once_is_enough` argument. + +* `printable_value` is now a method of request, and may be given dict of + formatters to use. + +* `[Rset]TableView` allows to set None in 'headers', meaning the label should be + fetched from the result set as done by default. + +* Field vocabulary computation on entity creation now takes `__linkto` + information into accounet. + +* Started a `cubicweb.pylintext` pylint plugin to help pylint analyzing cubes. + + +RQL +--- + +* Support for HAVING in 'SET' and 'DELETE' queries. + +* new `AT_TZ` function to get back a timestamp at a given time-zone. + +* new `WEEKDAY` date extraction function + + +User interface changes +---------------------- + +* Datafeed source now present an history of the latest import's log, including + global status and debug/info/warning/error messages issued during + imports. Import logs older than a configurable amount of time are automatically + deleted. + +* Breadcrumbs component is properly kept when creating an entity with '__linkto'. + +* users and groups management now really lead to that (i.e. includes *groups* + management). + +* New 'jsonp' controller with 'jsonexport' and 'ejsonexport' views. + + +Configuration +------------ + +* Added option 'resources-concat' to make javascript/css files concatenation + optional. diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/admin/cubicweb-ctl.rst --- a/doc/book/en/admin/cubicweb-ctl.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/admin/cubicweb-ctl.rst Thu Dec 08 14:32:57 2011 +0100 @@ -109,14 +109,3 @@ Other commands -------------- * ``delete``, deletes an instance (configuration files and database) - -Command to create an instance for Google AppEngine datastore source -------------------------------------------------------------------- -* ``newgapp``, creates the configuration files for an instance - -This command needs to be followed by the commands responsible for -the database initialization. As those are specific to the `datastore`, -specific Google AppEgine database, they are not available for now -in cubicweb-ctl, but they are available in the instance created. - -For more details, please see :ref:`GoogleAppEngineSource` . diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/admin/gae.rst --- a/doc/book/en/admin/gae.rst Thu Dec 08 14:29:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,235 +0,0 @@ -.. -*- coding: utf-8 -*- - -.. _GoogleAppEngineSource: - -CubicWeb in Google AppEngine -============================ - -What is `Google AppEngine` ? ------------------------------- - -`Google AppEngine`_ is provided with a partial port of the `Django` -framework, but Google stated at Google IO 2008 that it would not -support a specific Python web framework and that all -community-supported frameworks would be more than welcome [1]_. - -Therefore `Logilab`_ ported *CubicWeb* to run on top of `Google AppEngine`'s -datastore. - -.. _`Google AppEngine`: http://code.google.com/appengine/docs/whatisgoogleappengine.html -.. _Logilab: http://www.logilab.fr/ -.. [1] for more on this matter, read our blog at http://www.logilab.org/blogentry/5216 - -Download the source -------------------- - -- The `Google AppEngine SDK` can be downloaded from: - http://code.google.com/appengine/downloads.html - - -Please follow instructions on how to install *CubicWeb* framework -(:ref:`SetUpEnv`). - -Installation ------------- - -Once ``cubicweb-ctl`` is installed, then you can create a Google -App Engine extension of our framework by running the command :: - - cubicweb-ctl newgapp - -This will create a directory containing :: - - `-- myapp/ - |-- app.conf - |-- app.yaml - |-- bin/ - | `-- laxctl - |-- boostrap_cubes - |-- cubes/ - | |-- addressbook/ - | .. - | |-- comment - | .. - | `-- zone/ - |-- cubicweb/ - |-- custom.py - |-- cw-cubes/ - |-- dateutil/ - |-- docutils/ - |-- fckeditor/ - |-- i18n/ - |-- index.yaml - |-- loader.py - |-- logilab/ - |-- main.py - |-- migration.py - |-- mx/ - |-- roman.py - |-- rql/ - |-- schema.py - |-- simplejson/ - |-- tools/ - |-- views.py - |-- vobject/ - |-- yams/ - `-- yapps/ - - -This skeleton directory is a working `AppEngine` application. You will -recognize the files ``app.yaml`` and ``main.py``. All the rest is the -*CubicWeb* framework and its third-party libraries. You will notice that -the directory ``cubes`` is a library of reusable cubes. - -The main directories that you should know about are: - - - ``cubes`` : this is a library of reusable yams cubes. To use - those cubes you will list them in the variable - `included-yams-cubes` of ``app.conf``. See also :ref:`cubes`. - - [WHICH OTHER ONES SHOULD BE LISTED HERE?] - -Dependencies -~~~~~~~~~~~~ - -Before starting anything, please make sure the following packages are installed: - - yaml : by default google appengine is providing yaml; make sure you can - import it. We recommend you create a symbolic link yaml instead of installing - and using python-yaml: - yaml -> full/path/to/google_appengine/lib/yaml/lib/yaml/ - - gettext - -Setup -~~~~~ - -Once you executed ``cubicweb-ctl newgapp ``, you can use that ``myapp/`` -as an application directory and do as follows. - -This installation directory provides a configuration for an instance of *CubicWeb* -ported for Google App Engine. It is installed with its own command ``laxctl`` -which is a port of the command tool ``cubicweb-ctl`` originally developped for -*CubicWeb*. - -You can have the details of available commands by running :: - - $ python myapp/bin/laxctl --help - - -Generating translation files -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -*CubicWeb* is fully internationalized. Translation catalogs are found in -``myapp/i18n``. To compile the translation files, use the `gettext` tools -or the ``laxctl`` command :: - - $ python myapp/bin/laxctl i18ncube - $ python myapp/bin/laxctl i18ninstance - -Ignore the errors that print "No translation file found for domain -'cubicweb'". They disappear after the first run of i18ninstance. - -.. note:: The command myapp/bin/laxctl i18ncube needs to be executed - only if your instance is using cubes from cubicweb-apps. - Otherwise, please skip it. - -You will never need to add new entries in the translation catalog. Instead we -would recommand you to use ``self._cw._("msgId")`` in your code to flag new -message id to add to the catalog, where ``_`` refers to xgettext that is used to -collect new strings to translate. While running ``laxctl i18ncube``, new string -will be added to the catalogs. - -Generating the data directory -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to generate the ``myapp/data`` directory that holds the static -files like stylesheets and icons, you need to run the command:: - - $ python myapp/bin/laxctl populatedata - -Generating the schema diagram -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There is a view named ``schema`` that displays a diagram of the -entity-relationship graph defined by the schema. This diagram has to -be generated from the command line:: - - $ python myapp/bin/laxctl genschema - -Instance configuration -------------------------- - -Authentication -~~~~~~~~~~~~~~ - -You have the option of using or not google authentication for your instance. -This has to be define in ``app.conf`` and ``app.yaml``. - -In ``app.conf`` modify the following variable:: -  - # does this instance rely on google authentication service or not. - use-google-auth=no - -In ``app.yaml`` comment the `login: required` set by default in the handler:: - - - url: .* - script: main.py - # comment the line below to allow anonymous access or if you don't want to use - # google authentication service - #login: required - - - - -Quickstart : launch the instance ------------------------------------ - -On Mac OS X platforms, drag that directory on the -`GoogleAppEngineLauncher`. - -On Unix and Windows platforms, run it with the dev_appserver:: - - $ python /path/to/google_appengine/dev_appserver.py /path/to/myapp/ - -Once the local server is started, visit `http://MYAPP_URL/_load `_ and sign in as administrator. -This will initialize the repository and enable you to log in into -the instance and continue the installation. - -You should be redirected to a page displaying a message `content initialized`. - -Initialize the datastore -~~~~~~~~~~~~~~~~~~~~~~~~ - -You, then, want to visit `http://MYAPP_URL/?vid=authinfo `_ . -If you selected not to use google authentication, you will be prompted to a -login form where you should initialize the administrator login (recommended -to use admin/admin at first). You will then be redirected to a page providing -you the value to provide to ``./bin/laxctl --cookie``. - -If you choosed to use google authentication, then you will not need to set up -and administrator login but you will get the cookie value as well. - -This cookie values needs to be provided to ``laxctl`` commands -in order to handle datastore administration requests. - -.. image:: ../images/lax-book_02-cookie-values_en.png - :alt: displaying the detailed view of the cookie values returned - - -.. note:: In case you are not redirected to a page providing the - option --cookie value, please visit one more time - `http://MYAPP_URL/?vid=authinfo `_ . - -Once, you have this value, then return to the shell and execute :: - - $ python myapp/bin/laxctl db-init --cookie='dev_appserver_login=test@example.com:True; __session=7bbe973a6705bc5773a640f8cf4326cc' localhost:8080 - -.. note:: In the case you are not using google authentication, the value returned - by `http://MYAPP_URL/?vid=authinfo `_ - will look like : - --cookie='__session=2b45d1a9c36c03d2a30cedb04bc37b6d' - -Log out by clicking in the menu at the top right corner -and restart browsing from `http://MYAPP_URL/ `_ -as a normal user. - -Unless you did something to change it, http://MYAPP_URL should be -http://localhost:8080/ diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/admin/instance-config.rst --- a/doc/book/en/admin/instance-config.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/admin/instance-config.rst Thu Dec 08 14:32:57 2011 +0100 @@ -169,6 +169,8 @@ file to write messages +.. _PersistentProperties: + Configuring persistent properties --------------------------------- Other configuration settings are in the form of entities `CWProperty` diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/admin/pyro.rst --- a/doc/book/en/admin/pyro.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/admin/pyro.rst Thu Dec 08 14:32:57 2011 +0100 @@ -1,8 +1,8 @@ +.. _UsingPyro: + Working with a distributed client (using Pyro) ============================================== -.. _UsingPyro: - In some circumstances, it is practical to split the repository and web-client parts of the application for load-balancing reasons. Or one wants to access the repository from independant scripts to consult @@ -14,7 +14,7 @@ For this to work, several steps have to be taken in order. You must first ensure that the appropriate software is installed and -running (see ref:`setup`):: +running (see :ref:`ConfigEnv`):: pyro-nsd -x -p 6969 @@ -52,14 +52,11 @@ cur.execute('INSERT Tag T: T name %(n)s', {'n': name}) cnx.commit() -Calling :meth:`cubicweb.dbapi.load_appobjects`, will populates The `cubicweb -registries`_ with the application objects installed on the host where the script -runs. You'll then be allowed to use the ORM goodies and custom entity methods and -views. Of course this is optional, without it you can still get the repository -data through the connection but in a roughly way: only RQL cursors will be -available, e.g. you can't even build entity objects from the result set. - - - -.. _cubicweb registries: VRegistryIntro_ - +Calling :meth:`cubicweb.dbapi.load_appobjects`, will populate the +cubicweb registrires (see :ref:`VRegistryIntro`) with the application +objects installed on the host where the script runs. You'll then be +allowed to use the ORM goodies and custom entity methods and views. Of +course this is optional, without it you can still get the repository +data through the connection but in a roughly way: only RQL cursors +will be available, e.g. you can't even build entity objects from the +result set. diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/annexes/faq.rst --- a/doc/book/en/annexes/faq.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/annexes/faq.rst Thu Dec 08 14:32:57 2011 +0100 @@ -150,9 +150,7 @@ How to load data from a python script ? --------------------------------------- -Please, refer to the `Pyro chapter`_. - -.. _`Pyro chapter`: UsingPyro_ +Please, refer to :ref:`UsingPyro`. How to format an entity date attribute ? diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/annexes/rql/language.rst --- a/doc/book/en/annexes/rql/language.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/annexes/rql/language.rst Thu Dec 08 14:32:57 2011 +0100 @@ -55,6 +55,8 @@ **is** in the restrictions. +.. _VirtualRelations: + Virtual relations ~~~~~~~~~~~~~~~~~ @@ -66,7 +68,7 @@ * `identity`: relation to use to tell that a RQL variable is the same as another when you've to use two different variables for querying purpose. On the - opposite it's also useful together with the :ref:`NOT` operator to tell that two + opposite it's also useful together with the ``NOT`` operator to tell that two variables should not identify the same entity @@ -83,12 +85,12 @@ * floats separator is dot '.' -* boolean values are :keyword:`TRUE` and :keyword:`FALSE` keywords +* boolean values are ``TRUE`` and ``FALSE`` keywords * date and time should be expressed as a string with ISO notation : YYYY/MM/DD - [hh:mm], or using keywords :keyword:`TODAY` and :keyword:`NOW` + [hh:mm], or using keywords ``TODAY`` and ``NOW`` -You may also use the :keyword:`NULL` keyword, meaning 'unspecified'. +You may also use the ``NULL`` keyword, meaning 'unspecified'. .. _RQLOperators: @@ -167,7 +169,9 @@ `VARIABLE attribute VALUE` -The operator `IN` provides a list of possible values: :: +The operator `IN` provides a list of possible values: + +.. sourcecode:: sql Any X WHERE X name IN ('chauvat', 'fayolle', 'di mascio', 'thenault') @@ -180,28 +184,32 @@ LIKE, ILIKE, ~=, REGEXP -The :keyword:`LIKE` string operator can be used with the special character `%` in -a string as wild-card: :: +The ``LIKE`` string operator can be used with the special character `%` in +a string as wild-card: + +.. sourcecode:: sql - # match every entity whose name starts with 'Th' + -- match every entity whose name starts with 'Th' Any X WHERE X name ~= 'Th%' - # match every entity whose name endswith 'lt' + -- match every entity whose name endswith 'lt' Any X WHERE X name LIKE '%lt' - # match every entity whose name contains a 'l' and a 't' + -- match every entity whose name contains a 'l' and a 't' Any X WHERE X name LIKE '%l%t%' -:keyword:`ILIKE` is the case insensitive version of :keyword:`LIKE`. It's not +``ILIKE`` is the case insensitive version of ``LIKE``. It's not available on all backend (e.g. sqlite doesn't support it). If not available for -your backend, :keyword:`ILIKE` will behave like :keyword:`LIKE`. +your backend, ``ILIKE`` will behave like ``LIKE``. -`~=` is a shortcut version of :keyword:`ILIKE`, or of :keyword:`LIKE` when the +`~=` is a shortcut version of ``ILIKE``, or of ``LIKE`` when the former is not available on the back-end. -The :keyword:`REGEXP` is an alternative to :keyword:`LIKE` that supports POSIX -regular expressions:: +The ``REGEXP`` is an alternative to ``LIKE`` that supports POSIX +regular expressions: - # match entities whose title starts with a digit +.. sourcecode:: sql + + -- match entities whose title starts with a digit Any X WHERE X title REGEXP "^[0-9].*" @@ -253,7 +261,9 @@ There will be as much column in the result set as term in this clause, respecting order. -Syntax for function call is somewhat intuitive, for instance: :: +Syntax for function call is somewhat intuitive, for instance: + +.. sourcecode:: sql Any UPPER(N) WHERE P firstname N @@ -261,28 +271,28 @@ Grouping and aggregating ```````````````````````` -The :keyword:`GROUPBY` keyword is followed by a list of terms on which results +The ``GROUPBY`` keyword is followed by a list of terms on which results should be grouped. They are usually used with aggregate functions, responsible to aggregate values for each group (see :ref:`RQLAggregateFunctions`). For grouped queries, all selected variables must be either aggregated (i.e. used -by an aggregate function) or grouped (i.e. listed in the :keyword:`GROUPBY` +by an aggregate function) or grouped (i.e. listed in the ``GROUPBY`` clause). Sorting ``````` -The :keyword:`ORDERBY` keyword if followed by the definition of the selection -order: variable or column number followed by sorting method (:keyword:`ASC`, -:keyword:`DESC`), :keyword:`ASC` being the default. If the sorting method is not +The ``ORDERBY`` keyword if followed by the definition of the selection +order: variable or column number followed by sorting method (``ASC``, +``DESC``), ``ASC`` being the default. If the sorting method is not specified, then the sorting is ascendant (`ASC`). Pagination `````````` -The :keyword:`LIMIT` and :keyword:`OFFSET` keywords may be respectively used to +The ``LIMIT`` and ``OFFSET`` keywords may be respectively used to limit the number of results and to tell from which result line to start (for instance, use `LIMIT 20` to get the first 20 results, then `LIMIT 20 OFFSET 20` to get the next 20. @@ -291,34 +301,34 @@ Restrictions ```````````` -The :keyword:`WHERE` keyword introduce one of the "main" part of the query, where +The ``WHERE`` keyword introduce one of the "main" part of the query, where you "define" variables and add some restrictions telling what you're interested in. It's a list of triplets "subject relation object", e.g. `V1 relation (V2 | )`. Triplets are separated using :ref:`RQLLogicalOperators`. -.. Note: +.. note:: - About the negation operator (:keyword:`NOT`): + About the negation operator (``NOT``): - * "NOT X relation Y" is equivalent to "NOT EXISTS(X relation Y)" + * ``NOT X relation Y`` is equivalent to ``NOT EXISTS(X relation Y)`` - * `Any X WHERE NOT X owned_by U` means "entities that have no relation - `owned_by`". + * ``Any X WHERE NOT X owned_by U`` means "entities that have no relation + ``owned_by``". - * `Any X WHERE NOT X owned_by U, U login "syt"` means "the entity have no - relation `owned_by` with the user syt". They may have a relation "owned_by" + * ``Any X WHERE NOT X owned_by U, U login "syt"`` means "the entity have no + relation ``owned_by`` with the user syt". They may have a relation "owned_by" with another user. -In this clause, you can also use :keyword:`EXISTS` when you want to know if some +In this clause, you can also use ``EXISTS`` when you want to know if some expression is true and do not need the complete set of elements that make it true. Testing for existence is much faster than fetching the complete set of -results, especially when you think about using `OR` against several expressions. For instance +results, especially when you think about using ``OR`` against several expressions. For instance if you want to retrieve versions which are in state "ready" or tagged by "priority", you should write : -:: +.. sourcecode:: sql Any X ORDERBY PN,N WHERE X num N, X version_of P, P name PN, @@ -327,7 +337,7 @@ not -:: +.. sourcecode:: sql Any X ORDERBY PN,N WHERE X num N, X version_of P, P name PN, @@ -356,7 +366,9 @@ You must use the `?` behind a variable to specify that the relation toward it is optional. For instance: -- Bugs of a project attached or not to a version :: +- Bugs of a project attached or not to a version + + .. sourcecode:: sql Any X, V WHERE X concerns P, P eid 42, X corrected_in V? @@ -364,26 +376,34 @@ version in which it's corrected or None for tickets not related to a version. -- All cards and the project they document if any :: +- All cards and the project they document if any + + .. sourcecode:: sql Any C, P WHERE C is Card, P? documented_by C Notice you may also use outer join: -- on the RHS of attribute relation, e.g. :: +- on the RHS of attribute relation, e.g. + + .. sourcecode:: sql Any X WHERE X ref XR, Y name XR? so that Y is outer joined on X by ref/name attributes comparison -- on any side of an `HAVING` expression, e.g. :: +- on any side of an ``HAVING`` expression, e.g. + + .. sourcecode:: sql Any X WHERE X creation_date XC, Y creation_date YC HAVING YEAR(XC)=YEAR(YC)? so that Y is outer joined on X by comparison of the year extracted from their - creation date. :: + creation date. + + .. sourcecode:: sql Any X WHERE X creation_date XC, Y creation_date YC HAVING YEAR(XC)?=YEAR(YC) @@ -394,20 +414,26 @@ Having restrictions ``````````````````` -The :keyword:`HAVING` clause, as in SQL, may be used to restrict a query -according to value returned by an aggregate function, e.g.:: +The ``HAVING`` clause, as in SQL, may be used to restrict a query +according to value returned by an aggregate function, e.g. + +.. sourcecode:: sql Any X GROUPBY X WHERE X relation Y HAVING COUNT(Y) > 10 -It may however be used for something else: In the :keyword:`WHERE` clause, we are +It may however be used for something else: In the ``WHERE`` clause, we are limited to triplet expressions, so some things may not be expressed there. Let's take an example : if you want to get people whose upper-cased first name equals to another person upper-cased first name. There is no proper way to express this -using triplet, so you should use something like: :: +using triplet, so you should use something like: + +.. sourcecode:: sql Any X WHERE X firstname XFN, Y firstname YFN, NOT X identity Y HAVING UPPER(XFN) = UPPER(YFN) -Another example: imagine you want person born in 2000: :: +Another example: imagine you want person born in 2000: + +.. sourcecode:: sql Any X WHERE X birthday XB HAVING YEAR(XB) = 2000 @@ -419,19 +445,21 @@ Sub-queries ``````````` -The :keyword:`WITH` keyword introduce sub-queries clause. Each sub-query has the +The ``WITH`` keyword introduce sub-queries clause. Each sub-query has the form: V1(,V2) BEING (rql query) -Variables at the left of the :keyword:`BEING` keyword defines into which +Variables at the left of the ``BEING`` keyword defines into which variables results from the sub-query will be mapped to into the outer query. Sub-queries are separated from each other using a comma. Let's say we want to retrieve for each project its number of versions and its number of tickets. Due to the nature of relational algebra behind the scene, this can't be achieved using a single query. You have to write something along the -line of: :: +line of: + +.. sourcecode:: sql Any X, VC, TC WHERE X identity XX WITH X, VC BEING (Any X, COUNT(V) GROUPBY X WHERE V version_of X), @@ -439,19 +467,24 @@ Notice that we can't reuse a same variable name as alias for two different sub-queries, hence the usage of 'X' and 'XX' in this example, which are then -unified using the special `identity` relation (see :ref:`XXX`). +unified using the special `identity` relation (see :ref:`VirtualRelations`). -.. Warning: +.. warning:: Sub-queries define a new variable scope, so even if a variable has the same name - in the outer query and in the sub-query, they technically **aren't* the same - variable. So :: + in the outer query and in the sub-query, they technically **aren't** the same + variable. So: + + .. sourcecode:: sql Any W, REF WITH W, REF BEING (Any W, REF WHERE W is Workcase, W ref REF, W concerned_by D, D name "Logilab") + could be written: + .. sourcecode:: sql + Any W, REF WITH W, REF BEING (Any W1, REF1 WHERE W1 is Workcase, W1 ref REF1, W1 concerned_by D, D name "Logilab") @@ -459,14 +492,18 @@ Also, when a variable is coming from a sub-query, you currently can't reference its attribute or inlined relations in the outer query, you've to fetch them in the sub-query. For instance, let's say we want to sort by project name in our - first example, we would have to write :: + first example, we would have to write: + + .. sourcecode:: sql Any X, VC, TC ORDERBY XN WHERE X identity XX WITH X, XN, VC BEING (Any X, COUNT(V) GROUPBY X,XN WHERE V version_of X, X name XN), XX, TC BEING (Any X, COUNT(T) GROUPBY X WHERE T ticket_of X) - instead of :: + instead of: + + .. sourcecode:: sql Any X, VC, TC ORDERBY XN WHERE X identity XX, X name XN, WITH X, XN, VC BEING (Any X, COUNT(V) GROUPBY X WHERE V version_of X), @@ -479,10 +516,10 @@ ````` You may get a result set containing the concatenation of several queries using -the :keyword:`UNION`. The selection of each query should have the same number of +the ``UNION``. The selection of each query should have the same number of columns. -:: +.. sourcecode:: sql (Any X, XN WHERE X is Person, X surname XN) UNION (Any X,XN WHERE X is Company, X name XN) @@ -514,7 +551,7 @@ +--------------------+----------------------------------------------------------+ All aggregate functions above take a single argument. Take care some aggregate -functions (e.g. :keyword:`MAX`, :keyword:`MIN`) may return `None` if there is no +functions (e.g. ``MAX``, ``MIN``) may return `None` if there is no result row. .. _RQLStringFunctions: @@ -522,26 +559,26 @@ String transformation functions ``````````````````````````````` -+-------------------------+-----------------------------------------------------------------+ -| :func:`UPPER(String)` | upper case the string | -+-------------------------+-----------------------------------------------------------------+ -| :func:`LOWER(String)` | lower case the string | -+-------------------------+-----------------------------------------------------------------+ -| :func:`LENGTH(String)` | return the length of the string | -+-------------------------+-----------------------------------------------------------------+ -| :func:`SUBSTRING( | extract from the string a string starting at given index and of | -| String, start, length)`| given length | -+-------------------------+-----------------------------------------------------------------+ -| :func:`LIMIT_SIZE( | if the length of the string is greater than given max size, | -| String, max size)` | strip it and add ellipsis ("..."). The resulting string will | -| | hence have max size + 3 characters | -+-------------------------+-----------------------------------------------------------------+ -| :func:`TEXT_LIMIT_SIZE( | similar to the above, but allow to specify the MIME type of the | -| String, format, | text contained by the string. Supported formats are text/html, | -| max size)` | text/xhtml and text/xml. All others will be considered as plain | -| | text. For non plain text format, sgml tags will be first removed| -| | before limiting the string. | -+-------------------------+-----------------------------------------------------------------+ ++---------------------------------------------------+-----------------------------------------------------------------+ +| :func:`UPPER(String)` | upper case the string | ++---------------------------------------------------+-----------------------------------------------------------------+ +| :func:`LOWER(String)` | lower case the string | ++---------------------------------------------------+-----------------------------------------------------------------+ +| :func:`LENGTH(String)` | return the length of the string | ++---------------------------------------------------+-----------------------------------------------------------------+ +| :func:`SUBSTRING(String, start, length)` | extract from the string a string starting at given index and of | +| | given length | ++---------------------------------------------------+-----------------------------------------------------------------+ +| :func:`LIMIT_SIZE(String, max size)` | if the length of the string is greater than given max size, | +| | strip it and add ellipsis ("..."). The resulting string will | +| | hence have max size + 3 characters | ++---------------------------------------------------+-----------------------------------------------------------------+ +| :func:`TEXT_LIMIT_SIZE(String, format, max size)` | similar to the above, but allow to specify the MIME type of the | +| | text contained by the string. Supported formats are text/html, | +| | text/xhtml and text/xml. All others will be considered as plain | +| | text. For non plain text format, sgml tags will be first removed| +| | before limiting the string. | ++---------------------------------------------------+-----------------------------------------------------------------+ .. _RQLDateFunctions: @@ -588,61 +625,63 @@ ~~~~~~~~ - *Search for the object of identifier 53* - :: - Any WHERE X - X eid 53 + .. sourcecode:: sql + + Any WHERE X eid 53 - *Search material such as comics, owned by syt and available* - :: + + .. sourcecode:: sql - Any X WHERE X is Document - X occurence_of F, F class C, C name 'Comics' - X owned_by U, U login 'syt' - X available TRUE + Any X WHERE X is Document, + X occurence_of F, F class C, C name 'Comics', + X owned_by U, U login 'syt', + X available TRUE - *Looking for people working for eurocopter interested in training* - :: + + .. sourcecode:: sql - Any P WHERE - P is Person, P work_for S, S name 'Eurocopter' - P interested_by T, T name 'training' + Any P WHERE P is Person, P work_for S, S name 'Eurocopter', + P interested_by T, T name 'training' - *Search note less than 10 days old written by jphc or ocy* - :: + + .. sourcecode:: sql - Any N WHERE - N is Note, N written_on D, D day> (today -10), - N written_by P, P name 'jphc' or P name 'ocy' + Any N WHERE N is Note, N written_on D, D day> (today -10), + N written_by P, P name 'jphc' or P name 'ocy' - *Looking for people interested in training or living in Paris* - :: + + .. sourcecode:: sql - Any P WHERE - P is Person, (P interested_by T, T name 'training') OR - (P city 'Paris') + Any P WHERE P is Person, (P interested_by T, T name 'training') OR + (P city 'Paris') - *The surname and firstname of all people* - :: - Any N, P WHERE - X is Person, X name N, X firstname P + .. sourcecode:: sql + + Any N, P WHERE X is Person, X name N, X firstname P Note that the selection of several entities generally force the use of "Any" because the type specification applies otherwise to all the selected variables. We could write here - :: - String N, P WHERE - X is Person, X name N, X first_name P + .. sourcecode:: sql + + String N, P WHERE X is Person, X name N, X first_name P Note: You can not specify several types with * ... where X is FirstType or X is SecondType*. To specify several types explicitly, you have to do - :: - Any X where X is in (FirstType, SecondType) + .. sourcecode:: sql + + Any X WHERE X is IN (FirstType, SecondType) .. _RQLInsertQuery: @@ -662,19 +701,22 @@ *each line result returned by the restriction*. - *Insert a new person named 'foo'* - :: + + .. sourcecode:: sql INSERT Person X: X name 'foo' - *Insert a new person named 'foo', another called 'nice' and a 'friend' relation between them* - :: + + .. sourcecode:: sql INSERT Person X, Person Y: X name 'foo', Y name 'nice', X friend Y - *Insert a new person named 'foo' and a 'friend' relation with an existing person called 'nice'* - :: + + .. sourcecode:: sql INSERT Person X: X name 'foo', X friend Y WHERE name 'nice' @@ -690,13 +732,15 @@ each result line returned by the restriction*. - *Renaming of the person named 'foo' to 'bar' with the first name changed* - :: + + .. sourcecode:: sql SET X name 'bar', X firstname 'original' WHERE X is Person, X name 'foo' - *Insert a relation of type 'know' between objects linked by the relation of type 'friend'* - :: + + .. sourcecode:: sql SET X know Y WHERE X friend Y @@ -713,12 +757,14 @@ each line result returned by the restriction*. - *Deletion of the person named 'foo'* - :: + + .. sourcecode:: sql DELETE Person X WHERE X name 'foo' - *Removal of all relations of type 'friend' from the person named 'foo'* - :: + + .. sourcecode:: sql DELETE X friend Y WHERE X is Person, X name 'foo' diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devrepo/datamodel/baseschema.rst --- a/doc/book/en/devrepo/datamodel/baseschema.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devrepo/datamodel/baseschema.rst Thu Dec 08 14:32:57 2011 +0100 @@ -19,7 +19,6 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * _`CWUser`, system users * _`CWGroup`, users groups -* _`CWPermission`, used to configure the security of the instance Entity types used to manage workflows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devrepo/datamodel/definition.rst --- a/doc/book/en/devrepo/datamodel/definition.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devrepo/datamodel/definition.rst Thu Dec 08 14:32:57 2011 +0100 @@ -646,68 +646,7 @@ RelationType declaration which offers some advantages in the context of reusable cubes. -Definition of permissions -~~~~~~~~~~~~~~~~~~~~~~~~~~ -The entity type `CWPermission` from the standard library -allows to build very complex and dynamic security architectures. The schema of -this entity type is as follow: - -.. sourcecode:: python - - class CWPermission(EntityType): - """entity type that may be used to construct some advanced security configuration - """ - name = String(required=True, indexed=True, internationalizable=True, maxsize=100) - require_group = SubjectRelation('CWGroup', cardinality='+*', - description=_('groups to which the permission is granted')) - require_state = SubjectRelation('State', - description=_("entity's state in which the permission is applicable")) - # can be used on any entity - require_permission = ObjectRelation('**', cardinality='*1', composite='subject', - description=_("link a permission to the entity. This " - "permission should be used in the security " - "definition of the entity's type to be useful.")) - - -Example of configuration: - -.. sourcecode:: python - - class Version(EntityType): - """a version is defining the content of a particular project's release""" - - __permissions__ = {'read': ('managers', 'users', 'guests',), - 'update': ('managers', 'logilab', 'owners',), - 'delete': ('managers', ), - 'add': ('managers', 'logilab', - ERQLExpression('X version_of PROJ, U in_group G,' - 'PROJ require_permission P, P name "add_version",' - 'P require_group G'),)} - - - class version_of(RelationType): - """link a version to its project. A version is necessarily linked to one and only one project. - """ - __permissions__ = {'read': ('managers', 'users', 'guests',), - 'delete': ('managers', ), - 'add': ('managers', 'logilab', - RRQLExpression('O require_permission P, P name "add_version",' - 'U in_group G, P require_group G'),) - } - inlined = True - - -This configuration indicates that an entity `CWPermission` named -"add_version" can be associated to a project and provides rights to create -new versions on this project to specific groups. It is important to notice that: - -* in such case, we have to protect both the entity type "Version" and the relation - associating a version to a project ("version_of") - -* because of the genericity of the entity type `CWPermission`, we have to execute - a unification with the groups and/or the states if necessary in the expression - ("U in_group G, P require_group G" in the above example) - + Handling schema changes diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devrepo/entityclasses/adapters.rst --- a/doc/book/en/devrepo/entityclasses/adapters.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devrepo/entityclasses/adapters.rst Thu Dec 08 14:32:57 2011 +0100 @@ -133,8 +133,8 @@ return sum(captain.age for captain in self.captains) class FooView(EntityView): - __regid__ = 'mycube.fooview' - __select__ = implements('IFoo') + __regid__ = 'mycube.fooview' + __select__ = implements('IFoo') def cell_call(self, row, col): entity = self.cw_rset.get_entity(row, col) @@ -152,8 +152,8 @@ return sum(captain.age for captain in self.entity.captains) class FooView(EntityView): - __regid__ = 'mycube.fooview' - __select__ = adaptable('IFoo') + __regid__ = 'mycube.fooview' + __select__ = adaptable('IFoo') def cell_call(self, row, col): entity = self.cw_rset.get_entity(row, col) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devrepo/entityclasses/application-logic.rst --- a/doc/book/en/devrepo/entityclasses/application-logic.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devrepo/entityclasses/application-logic.rst Thu Dec 08 14:32:57 2011 +0100 @@ -67,8 +67,8 @@ class Project(AnyEntity): __regid__ = 'Project' - fetch_attrs, fetch_order = fetch_config(('name', 'description', - 'description_format', 'summary')) + fetch_attrs, cw_fetch_order = fetch_config(('name', 'description', + 'description_format', 'summary')) TICKET_DEFAULT_STATE_RESTR = 'S name IN ("created","identified","released","scheduled")' @@ -95,11 +95,9 @@ about the transitive closure of the child relation). This is a further argument to implement it at entity class level. -The fetch_attrs, fetch_order class attributes are parameters of the -`ORM`_ layer. They tell which attributes should be loaded at once on -entity object instantiation (by default, only the eid is known, other -attributes are loaded on demand), and which attribute is to be used to -order the .related() and .unrelated() methods output. +`fetch_attrs` configures which attributes should be prefetched when using ORM +methods retrieving entity of this type. In a same manner, the `cw_fetch_order` is +a class method allowing to control sort order. More on this in :ref:`FetchAttrs`. We can observe the big TICKET_DEFAULT_STATE_RESTR is a pure application domain piece of data. There is, of course, no limitation diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devrepo/entityclasses/load-sort.rst --- a/doc/book/en/devrepo/entityclasses/load-sort.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devrepo/entityclasses/load-sort.rst Thu Dec 08 14:32:57 2011 +0100 @@ -4,50 +4,37 @@ Loaded attributes and default sorting management ```````````````````````````````````````````````` -* The class attribute `fetch_attrs` allows to define in an entity class a list - of names of attributes or relations that should be automatically loaded when - entities of this type are fetched from the database. In the case of relations, - we are limited to *subject of cardinality `?` or `1`* relations. +* The class attribute `fetch_attrs` allows to define in an entity class a list of + names of attributes that should be automatically loaded when entities of this + type are fetched from the database using ORM methods retrieving entity of this + type (such as :meth:`related` and :meth:`unrelated`). You can also put relation + names in there, but we are limited to *subject relations of cardinality `?` or + `1`*. -* The class method `fetch_order(attr, var)` expects an attribute (or relation) - name as a parameter and a variable name, and it should return a string - to use in the requirement `ORDERBY` of an RQL query to automatically - sort the list of entities of such type according to this attribute, or - `None` if we do not want to sort on the attribute given in the parameter. - By default, the entities are sorted according to their creation date. +* The :meth:`cw_fetch_order` and :meth:`cw_fetch_unrelated_order` class methods + are respectively responsible to control how entities will be sorted when: + + - retrieving all entities of a given type, or entities related to another -* The class method `fetch_unrelated_order(attr, var)` is similar to - the method `fetch_order` except that it is essentially used to - control the sorting of drop-down lists enabling relations creation - in the editing view of an entity. The default implementation uses - the modification date. Here's how to adapt it for one entity (sort - on the name attribute): :: + - retrieving a list of entities for use in drop-down lists enabling relations + creation in the editing view of an entity + +By default entities will be listed on their modification date descending, +i.e. you'll get entities recently modified first. While this is usually a good +default in drop-down list, you'll probably want to change `cw_fetch_order`. - class MyEntity(AnyEntity): - __regid__ = 'MyEntity' - fetch_attrs = ('modification_date', 'name') +This may easily be done using the :func:`~cubicweb.entities.fetch_config` +function, which simplifies the definition of attributes to load and sorting by +returning a list of attributes to pre-load (considering automatically the +attributes of `AnyEntity`) and a sorting function as described below: - @classmethod - def fetch_unrelated_order(cls, attr, var): - if attr == 'name': - return '%s ASC' % var - return None +.. autofunction:: cubicweb.entities.fetch_config + +In you want something else (such as sorting on the result of a registered +procedure), here is the prototype of those methods: -The function `fetch_config(fetchattrs, mainattr=None)` simplifies the -definition of the attributes to load and the sorting by returning a -list of attributes to pre-load (considering automatically the -attributes of `AnyEntity`) and a sorting function based on the main -attribute (the second parameter if specified, otherwise the first -attribute from the list `fetchattrs`). This function is defined in -`cubicweb.entities`. +.. automethod:: cubicweb.entity.Entity.cw_fetch_order -For example: :: +.. automethod:: cubicweb.entity.Entity.cw_fetch_unrelated_order - class Transition(AnyEntity): - """...""" - __regid__ = 'Transition' - fetch_attrs, fetch_order = fetch_config(['name']) - -Indicates that for the entity type "Transition", you have to pre-load -the attribute `name` and sort by default on this attribute. diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devrepo/vreg.rst --- a/doc/book/en/devrepo/vreg.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devrepo/vreg.rst Thu Dec 08 14:32:57 2011 +0100 @@ -87,8 +87,6 @@ ~~~~~~~~~~~~~~~~~~~~~ Those selectors are looking for properties of the user issuing the request. -.. autoclass:: cubicweb.selectors.anonymous_user -.. autoclass:: cubicweb.selectors.authenticated_user .. autoclass:: cubicweb.selectors.match_user_groups @@ -97,18 +95,24 @@ Those selectors are looking for properties of *web* request, they can not be used on the data repository side. +.. autoclass:: cubicweb.selectors.no_cnx +.. autoclass:: cubicweb.selectors.anonymous_user +.. autoclass:: cubicweb.selectors.authenticated_user .. autoclass:: cubicweb.selectors.match_form_params .. autoclass:: cubicweb.selectors.match_search_state .. autoclass:: cubicweb.selectors.match_context_prop +.. autoclass:: cubicweb.selectors.match_context .. autoclass:: cubicweb.selectors.match_view .. autoclass:: cubicweb.selectors.primary_view +.. autoclass:: cubicweb.selectors.contextual .. autoclass:: cubicweb.selectors.specified_etype_implements .. autoclass:: cubicweb.selectors.attribute_edited +.. autoclass:: cubicweb.selectors.match_transition Other selectors ~~~~~~~~~~~~~~~ -.. autoclass:: cubicweb.selectors.match_transition +.. autoclass:: cubicweb.selectors.match_exception .. autoclass:: cubicweb.selectors.debug_mode You'll also find some other (very) specific selectors hidden in other modules diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/controllers.rst --- a/doc/book/en/devweb/controllers.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/controllers.rst Thu Dec 08 14:32:57 2011 +0100 @@ -26,9 +26,23 @@ typically using JSON as a serialization format for input, and sometimes using either JSON or XML for output; +* the JSonpController is a wrapper around the ``ViewController`` that + provides jsonp_ services. Padding can be specified with the + ``callback`` request parameter. Only *jsonexport* / *ejsonexport* + views can be used. If another ``vid`` is specified, it will be + ignored and replaced by *jsonexport*. Request is anonymized + to avoid returning sensitive data and reduce the risks of CSRF attacks; + * the Login/Logout controllers make effective user login or logout requests +.. warning:: + + JsonController will probably be renamed into AjaxController soon since + it has nothing to do with json per se. + +.. _jsonp: http://en.wikipedia.org/wiki/JSONP + `Edition`: * the Edit controller (see :ref:`edit_controller`) handles CRUD diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/edition/examples.rst --- a/doc/book/en/devweb/edition/examples.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/edition/examples.rst Thu Dec 08 14:32:57 2011 +0100 @@ -165,7 +165,7 @@ msg = format_mail({'email' : self._cw.user.get_email(), 'name' : self._cw.user.dc_title()}, recipients, body, subject) - if not self._cw.vreg.config.sendmails([(msg, recipients]): + if not self._cw.vreg.config.sendmails([(msg, recipients)]): msg = self._cw._('could not connect to the SMTP server') else: msg = self._cw._('emails successfully sent') diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/edition/form.rst --- a/doc/book/en/devweb/edition/form.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/edition/form.rst Thu Dec 08 14:32:57 2011 +0100 @@ -87,15 +87,15 @@ def ticket_done_in_choices(form, field): entity = form.edited_entity # first see if its specified by __linkto form parameters - linkedto = formfields.relvoc_linkedto(entity, 'done_in', 'subject') + linkedto = form.linked_to[('done_in', 'subject')] if linkedto: return linkedto # it isn't, get initial values - vocab = formfields.relvoc_init(entity, 'done_in', 'subject') + vocab = field.relvoc_init(form) veid = None # try to fetch the (already or pending) related version and project if not entity.has_eid(): - peids = entity.linked_to('concerns', 'subject') + peids = form.linked_to[('concerns', 'subject')] peid = peids and peids[0] else: peid = entity.project.eid @@ -112,29 +112,28 @@ and v.eid != veid] return vocab -The first thing we have to do is fetch potential values from the -``__linkto`` url parameter that is often found in entity creation -contexts (the creation action provides such a parameter with a -predetermined value; for instance in this case, ticket creation could -occur in the context of a `Version` entity). The -:mod:`cubicweb.web.formfields` module provides a ``relvoc_linkedto`` -utility function that gets a list suitably filled with vocabulary -values. +The first thing we have to do is fetch potential values from the ``__linkto`` url +parameter that is often found in entity creation contexts (the creation action +provides such a parameter with a predetermined value; for instance in this case, +ticket creation could occur in the context of a `Version` entity). The +:class:`~cubicweb.web.formfields.RelationField` field class provides a +:meth:`~cubicweb.web.formfields.RelationField.relvoc_linkedto` method that gets a +list suitably filled with vocabulary values. .. sourcecode:: python - linkedto = formfields.relvoc_linkedto(entity, 'done_in', 'subject') + linkedto = field.relvoc_linkedto(form) if linkedto: return linkedto -Then, if no ``__linkto`` argument was given, we must prepare the -vocabulary with an initial empty value (because `done_in` is not -mandatory, we must allow the user to not select a verson) and already -linked values. This is done with the ``relvoc_init`` function. +Then, if no ``__linkto`` argument was given, we must prepare the vocabulary with +an initial empty value (because `done_in` is not mandatory, we must allow the +user to not select a verson) and already linked values. This is done with the +:meth:`~cubicweb.web.formfields.RelationField.relvoc_init` method. .. sourcecode:: python - vocab = formfields.relvoc_init(entity, 'done_in', 'subject') + vocab = field.relvoc_init(form) But then, we have to give more: if the ticket is related to a project, we should provide all the non published versions of this project @@ -169,7 +168,7 @@ veid = None if not entity.has_eid(): - peids = entity.linked_to('concerns', 'subject') + peids = form.linked_to[('concerns', 'subject')] peid = peids and peids[0] else: peid = entity.project.eid diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/internationalization.rst --- a/doc/book/en/devweb/internationalization.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/internationalization.rst Thu Dec 08 14:32:57 2011 +0100 @@ -26,7 +26,9 @@ In the Python code and cubicweb-tal templates translatable strings can be marked in one of the following ways : - * by using the *built-in* function `_` :: + * by using the *built-in* function `_`: + + .. sourcecode:: python class PrimaryView(EntityView): """the full view of an non final entity""" @@ -35,7 +37,9 @@ OR - * by using the equivalent request's method :: + * by using the equivalent request's method: + + .. sourcecode:: python class NoResultView(View): """default view when no result has been found""" @@ -79,9 +83,12 @@ particular instance's schema as they are generated automatically. String for various actions are also generated. -For exemple the following schema :: +For exemple the following schema: - Class EntityA(EntityType): +.. sourcecode:: python + + + class EntityA(EntityType): relation_a2b = SubjectRelation('EntityB') class EntityB(EntityType): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/rtags.rst --- a/doc/book/en/devweb/rtags.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/rtags.rst Thu Dec 08 14:32:57 2011 +0100 @@ -19,3 +19,9 @@ .. automodule:: cubicweb.web.uicfg + +The uihelper module +~~~~~~~~~~~~~~~~~~~ + +.. automodule:: cubicweb.web.uihelper + diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/views/baseviews.rst --- a/doc/book/en/devweb/views/baseviews.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/views/baseviews.rst Thu Dec 08 14:32:57 2011 +0100 @@ -15,3 +15,7 @@ .. automodule:: cubicweb.web.views.baseviews +You will also find modules providing some specific services: + +.. automodule:: cubicweb.web.views.navigation + diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/views/index.rst --- a/doc/book/en/devweb/views/index.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/views/index.rst Thu Dec 08 14:32:57 2011 +0100 @@ -19,10 +19,6 @@ table xmlrss .. editforms - -.. toctree:: - :maxdepth: 3 - urlpublish breadcrumbs idownloadable diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/views/primary.rst --- a/doc/book/en/devweb/views/primary.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/views/primary.rst Thu Dec 08 14:32:57 2011 +0100 @@ -180,7 +180,7 @@ from cubicweb.web.views.primary import Primaryview class BlogEntryPrimaryView(PrimaryView): - __select__ = PrimaryView.__select__ & is_instance('BlogEntry') + __select__ = PrimaryView.__select__ & is_instance('BlogEntry') def render_entity_attributes(self, entity): self.w(u'

published on %s

' % diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/views/table.rst --- a/doc/book/en/devweb/views/table.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/views/table.rst Thu Dec 08 14:32:57 2011 +0100 @@ -1,26 +1,7 @@ -Table view ----------- - -(:mod:`cubicweb.web.views.tableview`) - -*table* - Creates a HTML table (``) and call the view `cell` for each cell of - the result set. Applicable on any result set. +Table views +----------- -*editable-table* - Creates an **editable** HTML table (`
`) and call the view `cell` for each cell of - the result set. Applicable on any result set. - -*cell* - By default redirects to the `final` view if this is a final entity or - `outofcontext` view otherwise - - -API -``` - -.. autoclass:: cubicweb.web.views.tableview.TableView - :members: +.. automodule:: cubicweb.web.views.tableview Example ``````` @@ -29,50 +10,136 @@ .. sourcecode:: python - class ActivityTable(EntityView): - __regid__ = 'activitytable' + class ActivityResourcesTable(EntityView): + __regid__ = 'activity.resources.table' __select__ = is_instance('Activity') - title = _('activitytable') def call(self, showresource=True): - _ = self._cw._ - headers = [_("diem"), _("duration"), _("workpackage"), _("description"), _("state"), u""] eids = ','.join(str(row[0]) for row in self.cw_rset) - rql = ('Any R, D, DUR, WO, DESCR, S, A, SN, RT, WT ORDERBY D DESC ' + rql = ('Any R,D,DUR,WO,DESCR,S,A, SN,RT,WT ORDERBY D DESC ' 'WHERE ' ' A is Activity, A done_by R, R title RT, ' ' A diem D, A duration DUR, ' ' A done_for WO, WO title WT, ' - ' A description DESCR, A in_state S, S name SN, A eid IN (%s)' % eids) - if showresource: - displaycols = range(7) - headers.insert(0, display_name(self._cw, 'Resource')) - else: # skip resource column if asked to - displaycols = range(1, 7) + ' A description DESCR, A in_state S, S name SN, ' + ' A eid IN (%s)' % eids) rset = self._cw.execute(rql) - self.wview('editable-table', rset, 'null', - displayfilter=True, displayactions=False, - headers=headers, displaycols=displaycols, - cellvids={3: 'editable-final'}) + self.wview('resource.table', rset, 'null') -To obtain an editable table, specify 'edtitable-table' as vid. You -have to select the entity in the rql request too (in order to kwnow -which entity must be edited). You can specify an optional -`displaycols` argument which defines column's indexes that will be -displayed. In the above example, setting `showresource` to `False` -will only render columns from index 1 to 7. + class ResourcesTable(RsetTableView): + __regid__ = 'resource.table' + # notice you may wish a stricter selector to check rql's shape + __select__ = is_instance('Resource') + # my table headers + headers = ['Resource', 'diem', 'duration', 'workpackage', 'description', 'state'] + # I want a table where attributes are editable (reledit inside) + finalvid = 'editable-final' + + cellvids = {3: 'editable-final'} + # display facets and actions with a menu + layout_args = {'display_filter': 'top', + 'add_view_actions': None} + +To obtain an editable table, you may specify the 'editable-table' view identifier +using some of `cellvids`, `finalvid` or `nonfinalvid`. The previous example results in: .. image:: ../../images/views-table-shadow.png - -In order to activate table filter mechanism, set the `displayfilter` -argument to True. A small arrow will be displayed at the table's top -right corner. Clicking on `show filter form` action, will display the -filter form as below: +In order to activate table filter mechanism, the `display_filter` option is given +as a layout argument. A small arrow will be displayed at the table's top right +corner. Clicking on `show filter form` action, will display the filter form as +below: .. image:: ../../images/views-table-filter-shadow.png -By the same way, you can display all registered actions for the -selected entity, setting `displayactions` argument to True. +By the same way, you can display additional actions for the selected entities +by setting `add_view_actions` layout option to `True`. This will add actions +returned by the view's :meth:`~cubicweb.web.views.tableview.TableMixIn.table_actions`. + +You can notice that all columns of the result set are not displayed. This is +because of given `headers`, implying to display only columns from 0 to +len(headers). + +Also Notice that the `ResourcesTable` view relies on a particular rql shape +(which is not ensured by the way, the only checked thing is that the result set +contains instance of the `Resource` type). That usually implies that you can't +use this view for user specific queries (e.g. generated by facets or typed +manually). + + +So another option would be to write this view using +:class:`~cubicweb.web.views.tableview.EntityTableView`, as below. + +.. sourcecode:: python + + class ResourcesTable(EntityTableView): + __regid__ = 'resource.table' + __select__ = is_instance('Resource') + # table columns definition + columns = ['resource', 'diem', 'duration', 'workpackage', 'description', 'in_state'] + # I want a table where attributes are editable (reledit inside) + finalvid = 'editable-final' + # display facets and actions with a menu + layout_args = {'display_filter': 'top', + 'add_view_actions': None} + + def workpackage_cell(entity): + activity = entity.reverse_done_in[0] + activity.view('reledit', rtype='done_for', role='subject', w=w) + def workpackage_sortvalue(entity): + activity = entity.reverse_done_in[0] + return activity.done_for[0].sortvalue() + + column_renderers = { + 'resource': MainEntityColRenderer(), + 'workpackage': EntityTableColRenderer( + header='Workpackage', + renderfunc=worpackage_cell, + sortfunc=worpackage_sortvalue,), + 'in_state': EntityTableColRenderer( + renderfunc=lambda w,x: w(x.cw_adapt_to('IWorkflowable').printable_state), + sortfunc=lambda x: x.cw_adapt_to('IWorkflowable').printable_state), + } + +Notice the following point: + +* `cell_(w, entity)` will be searched for rendering the content of a + cell. If not found, `column` is expected to be an attribute of `entity`. + +* `cell_sortvalue_(entity)` should return a typed value to use for + javascript sorting or None for not sortable columns (the default). + +* The :func:`etable_entity_sortvalue` decorator will set a 'sortvalue' function + for the column containing the main entity (the one given as argument to all + methods), which will call `entity.sortvalue()`. + +* You can set a column header using the :func:`etable_header_title` decorator. + This header will be translated. If it's not an already existing msgid, think + to mark it using `_()` (the example supposes headers are schema defined msgid). + + +Pro/cons of each approach +````````````````````````` +:class:`EntityTableView` and :class:`RsetableView` provides basically the same +set of features, though they don't share the same properties. Let's try to sum +up pro and cons of each class. + +* `EntityTableView` view is: + + - more verbose, but usually easier to understand + + - easily extended (easy to add/remove columns for instance) + + - doesn't rely on a particular rset shape. Simply give it a title and will be + listed in the 'possible views' box if any. + +* `RsetTableView` view is: + + - hard to beat to display barely a result set, or for cases where some of + `headers`, `displaycols` or `cellvids` could be defined to enhance the table + while you don't care about e.g. pagination or facets. + + - hardly extensible, as you usually have to change places where the view is + called to modify the RQL (hence the view's result set shape). diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/views/urlpublish.rst --- a/doc/book/en/devweb/views/urlpublish.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/views/urlpublish.rst Thu Dec 08 14:32:57 2011 +0100 @@ -10,6 +10,70 @@ .. autoclass:: cubicweb.web.views.urlpublishing.URLPublisherComponent :members: + +You can write your own *URLPathEvaluator* class to handle custom paths. +For instance, if you want */my-card-id* to redirect to the corresponding +card's primary view, you would write: + +.. sourcecode:: python + + class CardWikiidEvaluator(URLPathEvaluator): + priority = 3 # make it be evaluated *before* RestPathEvaluator + + def evaluate_path(self, req, segments): + if len(segments) != 1: + raise PathDontMatch() + rset = req.execute('Any C WHERE C wikiid %(w)s', + {'w': segments[0]}) + if len(rset) == 0: + # Raise NotFound if no card is found + raise PathDontMatch() + return None, rset + +On the other hand, you can also deactivate some of the standard +evaluators in your final application. The only thing you have to +do is to unregister them, for instance in a *registration_callback* +in your cube: + +.. sourcecode:: python + + def registration_callback(vreg): + vreg.unregister(RestPathEvaluator) + +You can even replace the :class:`cubicweb.web.views.urlpublishing.URLPublisherComponent` +class if you want to customize the whole toolchain process or if you want +to plug into an early enough extension point to control your request +parameters: + +.. sourcecode:: python + + class SanitizerPublisherComponent(URLPublisherComponent): + """override default publisher component to explicitly ignore + unauthorized request parameters in anonymous mode. + """ + unauthorized_form_params = ('rql', 'vid', '__login', '__password') + + def process(self, req, path): + if req.session.anonymous_session: + self._remove_unauthorized_params(req) + return super(SanitizerPublisherComponent, self).process(req, path) + + def _remove_unauthorized_params(self, req): + for param in req.form.keys(): + if param in self.unauthorized_form_params: + req.form.pop(param) + + + def registration_callback(vreg): + vreg.register_and_replace(SanitizerPublisherComponent, URLPublisherComponent) + + +.. autoclass:: cubicweb.web.views.urlpublishing.RawPathEvaluator +.. autoclass:: cubicweb.web.views.urlpublishing.EidPathEvaluator +.. autoclass:: cubicweb.web.views.urlpublishing.URLRewriteEvaluator +.. autoclass:: cubicweb.web.views.urlpublishing.RestPathEvaluator +.. autoclass:: cubicweb.web.views.urlpublishing.ActionPathEvaluator + URL rewriting ------------- @@ -52,7 +116,7 @@ dict(rql=('Any X ORDERBY CD DESC LIMIT 20 WHERE X is BlogEntry,' 'X creation_date CD, X created_by U, ' 'U login "%(user)s"' - % {'user': r'\1'}, vid='rss'))), + % {'user': r'\1'}), vid='rss')) ] When a url matches the regular expression, the view with the __regid__ diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/devweb/views/xmlrss.rst --- a/doc/book/en/devweb/views/xmlrss.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/devweb/views/xmlrss.rst Thu Dec 08 14:32:57 2011 +0100 @@ -20,7 +20,9 @@ ++++++++++++++++++++ Assuming you have several blog entries, click on the title of the -search box in the left column. A larger search box should appear. Enter:: +search box in the left column. A larger search box should appear. Enter: + +.. sourcecode:: sql Any X ORDERBY D WHERE X is BlogEntry, X creation_date D @@ -38,14 +40,18 @@ That's it, you have a RSS channel for your blog. -Try again with:: +Try again with: + +.. sourcecode:: sql Any X ORDERBY D WHERE X is BlogEntry, X creation_date D, X entry_of B, B title "MyLife" Another RSS channel, but a bit more focused. -A last one for the road:: +A last one for the road: + +.. sourcecode:: sql Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15 diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/tutorials/advanced/part04_ui-base.rst --- a/doc/book/en/tutorials/advanced/part04_ui-base.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst Thu Dec 08 14:32:57 2011 +0100 @@ -138,20 +138,21 @@ * Also, when viewing an image, there is no clue about the folder to which this image belongs to. -I will first try to explain the ordering problem. By default, when accessing related -entities by using the ORM's API, you should get them ordered according to the target's -class `fetch_order`. If we take a look at the file cube'schema, we can see: +I will first try to explain the ordering problem. By default, when accessing +related entities by using the ORM's API, you should get them ordered according to +the target's class `cw_fetch_order`. If we take a look at the file cube'schema, +we can see: .. sourcecode:: python - class File(AnyEntity): """customized class for File entities""" __regid__ = 'File' - fetch_attrs, fetch_order = fetch_config(['data_name', 'title']) + fetch_attrs, cw_fetch_order = fetch_config(['data_name', 'title']) + -By default, `fetch_config` will return a `fetch_order` method that will order on -the first attribute in the list. So, we could expect to get files ordered by +By default, `fetch_config` will return a `cw_fetch_order` method that will order +on the first attribute in the list. So, we could expect to get files ordered by their name. But we don't. What's up doc ? The problem is that files are related to folder using the `filed_under` relation. diff -r 7b2c7f3d3703 -r 29cdde6bb9ef doc/book/en/tutorials/base/customizing-the-application.rst --- a/doc/book/en/tutorials/base/customizing-the-application.rst Thu Dec 08 14:29:48 2011 +0100 +++ b/doc/book/en/tutorials/base/customizing-the-application.rst Thu Dec 08 14:32:57 2011 +0100 @@ -389,7 +389,7 @@ """customized class for Community entities""" __regid__ = 'Community' - fetch_attrs, fetch_order = fetch_config(['name']) + fetch_attrs, cw_fetch_order = fetch_config(['name']) def dc_title(self): return self.name diff -r 7b2c7f3d3703 -r 29cdde6bb9ef entities/__init__.py --- a/entities/__init__.py Thu Dec 08 14:29:48 2011 +0100 +++ b/entities/__init__.py Thu Dec 08 14:32:57 2011 +0100 @@ -118,54 +118,35 @@ return self.printable_value(rtype, format='text/plain').lower() return value - # edition helper functions ################################################ - - def linked_to(self, rtype, role, remove=True): - """if entity should be linked to another using '__linkto' form param for - the given relation/role, return eids of related entities - - This method is consuming matching link-to information from form params - if `remove` is True (by default). Computed values are stored into a - `cw_linkto` attribute, a dictionary with (relation, role) as key and - linked eids as value. - """ - try: - return self.cw_linkto[(rtype, role)] - except AttributeError: - self.cw_linkto = {} - except KeyError: - pass - linktos = list(self._cw.list_form_param('__linkto')) - linkedto = [] - for linkto in linktos[:]: - ltrtype, eid, ltrole = linkto.split(':') - if rtype == ltrtype and role == ltrole: - # delete __linkto from form param to avoid it being added as - # hidden input - if remove: - linktos.remove(linkto) - self._cw.form['__linkto'] = linktos - linkedto.append(typed_eid(eid)) - self.cw_linkto[(rtype, role)] = linkedto - return linkedto - - # server side helpers ##################################################### - -# XXX: store a reference to the AnyEntity class since it is hijacked in goa -# configuration and we need the actual reference to avoid infinite loops -# in mro -ANYENTITY = AnyEntity def fetch_config(fetchattrs, mainattr=None, pclass=AnyEntity, order='ASC'): - if pclass is ANYENTITY: - pclass = AnyEntity # AnyEntity and ANYENTITY may be different classes + """function to ease basic configuration of an entity class ORM. Basic usage + is: + + .. sourcecode:: python + + class MyEntity(AnyEntity): + + fetch_attrs, cw_fetch_order = fetch_config(['attr1', 'attr2']) + # uncomment line below if you want the same sorting for 'unrelated' entities + # cw_fetch_unrelated_order = cw_fetch_order + + Using this, when using ORM methods retrieving this type of entity, 'attr1' + and 'attr2' will be automatically prefetched and results will be sorted on + 'attr1' ascending (ie the first attribute in the list). + + This function will automatically add to fetched attributes those defined in + parent class given using the `pclass` argument. + + Also, You can use `mainattr` and `order` argument to have a different + sorting. + """ if pclass is not None: fetchattrs += pclass.fetch_attrs if mainattr is None: mainattr = fetchattrs[0] @classmethod - def fetch_order(cls, attr, var): + def fetch_order(cls, select, attr, var): if attr == mainattr: - return '%s %s' % (var, order) - return None + select.add_sort_var(var, order=='ASC') return fetchattrs, fetch_order diff -r 7b2c7f3d3703 -r 29cdde6bb9ef entities/adapters.py --- a/entities/adapters.py Thu Dec 08 14:29:48 2011 +0100 +++ b/entities/adapters.py Thu Dec 08 14:32:57 2011 +0100 @@ -26,15 +26,15 @@ from logilab.mtconverter import TransformError from logilab.common.decorators import cached +from logilab.common.deprecation import class_deprecated -from cubicweb import ValidationError -from cubicweb.view import EntityAdapter, implements_adapter_compat +from cubicweb import ValidationError, view from cubicweb.selectors import (implements, is_instance, relation_possible, match_exception) from cubicweb.interfaces import IDownloadable, ITree, IProgress, IMileStone -class IEmailableAdapter(EntityAdapter): +class IEmailableAdapter(view.EntityAdapter): __regid__ = 'IEmailable' __select__ = relation_possible('primary_email') | relation_possible('use_email') @@ -67,12 +67,12 @@ for attr in self.allowed_massmail_keys() ) -class INotifiableAdapter(EntityAdapter): +class INotifiableAdapter(view.EntityAdapter): __needs_bw_compat__ = True __regid__ = 'INotifiable' __select__ = is_instance('Any') - @implements_adapter_compat('INotifiableAdapter') + @view.implements_adapter_compat('INotifiableAdapter') def notification_references(self, view): """used to control References field of email send on notification for this entity. `view` is the notification view. @@ -86,7 +86,7 @@ return () -class IFTIndexableAdapter(EntityAdapter): +class IFTIndexableAdapter(view.EntityAdapter): __regid__ = 'IFTIndexable' __select__ = is_instance('Any') @@ -156,35 +156,35 @@ for weight, words in newdict.iteritems(): maindict.setdefault(weight, []).extend(words) -class IDownloadableAdapter(EntityAdapter): +class IDownloadableAdapter(view.EntityAdapter): """interface for downloadable entities""" __needs_bw_compat__ = True __regid__ = 'IDownloadable' __select__ = implements(IDownloadable, warn=False) # XXX for bw compat, else should be abstract - @implements_adapter_compat('IDownloadable') + @view.implements_adapter_compat('IDownloadable') def download_url(self, **kwargs): # XXX not really part of this interface """return an url to download entity's content""" raise NotImplementedError - @implements_adapter_compat('IDownloadable') + @view.implements_adapter_compat('IDownloadable') def download_content_type(self): """return MIME type of the downloadable content""" raise NotImplementedError - @implements_adapter_compat('IDownloadable') + @view.implements_adapter_compat('IDownloadable') def download_encoding(self): """return encoding of the downloadable content""" raise NotImplementedError - @implements_adapter_compat('IDownloadable') + @view.implements_adapter_compat('IDownloadable') def download_file_name(self): """return file name of the downloadable content""" raise NotImplementedError - @implements_adapter_compat('IDownloadable') + @view.implements_adapter_compat('IDownloadable') def download_data(self): """return actual data of the downloadable content""" raise NotImplementedError # XXX should propose to use two different relations for children/parent -class ITreeAdapter(EntityAdapter): +class ITreeAdapter(view.EntityAdapter): """This adapter has to be overriden to be configured using the tree_relation, child_role and parent_role class attributes to benefit from this default implementation. @@ -225,12 +225,12 @@ return self.entity.tree_attribute # XXX should be removed from the public interface - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def children_rql(self): """Returns RQL to get the children of the entity.""" return self.entity.cw_related_rql(self.tree_relation, self.parent_role) - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def different_type_children(self, entities=True): """Return children entities of different type as this entity. @@ -244,7 +244,7 @@ return [e for e in res if e.e_schema != eschema] return res.filtered_rset(lambda x: x.e_schema != eschema, self.entity.cw_col) - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def same_type_children(self, entities=True): """Return children entities of the same type as this entity. @@ -258,23 +258,23 @@ return [e for e in res if e.e_schema == eschema] return res.filtered_rset(lambda x: x.e_schema is eschema, self.entity.cw_col) - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def is_leaf(self): """Returns True if the entity does not have any children.""" return len(self.children()) == 0 - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def is_root(self): """Returns true if the entity is root of the tree (e.g. has no parent). """ return self.parent() is None - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def root(self): """Return the root entity of the tree.""" return self._cw.entity_from_eid(self.path()[0]) - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def parent(self): """Returns the parent entity if any, else None (e.g. if we are on the root). @@ -285,7 +285,7 @@ except (KeyError, IndexError): return None - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def children(self, entities=True, sametype=False): """Return children entities. @@ -298,7 +298,7 @@ return self.entity.related(self.tree_relation, self.parent_role, entities=entities) - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def iterparents(self, strict=True): """Return an iterator on the parents of the entity.""" def _uptoroot(self): @@ -313,7 +313,7 @@ return chain([self.entity], _uptoroot(self)) return _uptoroot(self) - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def iterchildren(self, _done=None): """Return an iterator over the item's children.""" if _done is None: @@ -325,7 +325,7 @@ yield child _done.add(child.eid) - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') def prefixiter(self, _done=None): """Return an iterator over the item's descendants in a prefixed order.""" if _done is None: @@ -338,7 +338,7 @@ for entity in child.cw_adapt_to('ITree').prefixiter(_done): yield entity - @implements_adapter_compat('ITree') + @view.implements_adapter_compat('ITree') @cached def path(self): """Returns the list of eids from the root object to this object.""" @@ -363,40 +363,75 @@ return path -class IProgressAdapter(EntityAdapter): +# error handling adapters ###################################################### + +from cubicweb import UniqueTogetherError + +class IUserFriendlyError(view.EntityAdapter): + __regid__ = 'IUserFriendlyError' + __abstract__ = True + def __init__(self, *args, **kwargs): + self.exc = kwargs.pop('exc') + super(IUserFriendlyError, self).__init__(*args, **kwargs) + + +class IUserFriendlyUniqueTogether(IUserFriendlyError): + __select__ = match_exception(UniqueTogetherError) + def raise_user_exception(self): + etype, rtypes = self.exc.args + msg = self._cw._('violates unique_together constraints (%s)') % ( + ', '.join([self._cw._(rtype) for rtype in rtypes])) + raise ValidationError(self.entity.eid, dict((col, msg) for col in rtypes)) + +# deprecated ################################################################### + + +class adapter_deprecated(view.auto_unwrap_bw_compat): + """metaclass to print a warning on instantiation of a deprecated class""" + + def __call__(cls, *args, **kwargs): + msg = getattr(cls, "__deprecation_warning__", + "%(cls)s is deprecated") % {'cls': cls.__name__} + warn(msg, DeprecationWarning, stacklevel=2) + return type.__call__(cls, *args, **kwargs) + + +class IProgressAdapter(view.EntityAdapter): """something that has a cost, a state and a progression. You should at least override progress_info an in_progress methods on concrete implementations. """ + __metaclass__ = adapter_deprecated + __deprecation_warning__ = '[3.14] IProgressAdapter has been moved to iprogress cube' __needs_bw_compat__ = True __regid__ = 'IProgress' __select__ = implements(IProgress, warn=False) # XXX for bw compat, should be abstract @property - @implements_adapter_compat('IProgress') + @view.implements_adapter_compat('IProgress') def cost(self): """the total cost""" return self.progress_info()['estimated'] @property - @implements_adapter_compat('IProgress') + @view.implements_adapter_compat('IProgress') def revised_cost(self): return self.progress_info().get('estimatedcorrected', self.cost) @property - @implements_adapter_compat('IProgress') + @view.implements_adapter_compat('IProgress') def done(self): """what is already done""" return self.progress_info()['done'] @property - @implements_adapter_compat('IProgress') + @view.implements_adapter_compat('IProgress') def todo(self): """what remains to be done""" return self.progress_info()['todo'] - @implements_adapter_compat('IProgress') + @view.implements_adapter_compat('IProgress') def progress_info(self): """returns a dictionary describing progress/estimated cost of the version. @@ -411,17 +446,17 @@ """ raise NotImplementedError - @implements_adapter_compat('IProgress') + @view.implements_adapter_compat('IProgress') def finished(self): """returns True if status is finished""" return not self.in_progress() - @implements_adapter_compat('IProgress') + @view.implements_adapter_compat('IProgress') def in_progress(self): """returns True if status is not finished""" raise NotImplementedError - @implements_adapter_compat('IProgress') + @view.implements_adapter_compat('IProgress') def progress(self): """returns the % progress of the task item""" try: @@ -432,62 +467,44 @@ return 0. return 100 - @implements_adapter_compat('IProgress') + @view.implements_adapter_compat('IProgress') def progress_class(self): return '' class IMileStoneAdapter(IProgressAdapter): + __metaclass__ = adapter_deprecated + __deprecation_warning__ = '[3.14] IMileStoneAdapter has been moved to iprogress cube' __needs_bw_compat__ = True __regid__ = 'IMileStone' __select__ = implements(IMileStone, warn=False) # XXX for bw compat, should be abstract parent_type = None # specify main task's type - @implements_adapter_compat('IMileStone') + @view.implements_adapter_compat('IMileStone') def get_main_task(self): """returns the main ITask entity""" raise NotImplementedError - @implements_adapter_compat('IMileStone') + @view.implements_adapter_compat('IMileStone') def initial_prevision_date(self): """returns the initial expected end of the milestone""" raise NotImplementedError - @implements_adapter_compat('IMileStone') + @view.implements_adapter_compat('IMileStone') def eta_date(self): """returns expected date of completion based on what remains to be done """ raise NotImplementedError - @implements_adapter_compat('IMileStone') + @view.implements_adapter_compat('IMileStone') def completion_date(self): """returns date on which the subtask has been completed""" raise NotImplementedError - @implements_adapter_compat('IMileStone') + @view.implements_adapter_compat('IMileStone') def contractors(self): """returns the list of persons supposed to work on this task""" raise NotImplementedError - -# error handling adapters ###################################################### - -from cubicweb import UniqueTogetherError - -class IUserFriendlyError(EntityAdapter): - __regid__ = 'IUserFriendlyError' - __abstract__ = True - def __init__(self, *args, **kwargs): - self.exc = kwargs.pop('exc') - super(IUserFriendlyError, self).__init__(*args, **kwargs) - - -class IUserFriendlyUniqueTogether(IUserFriendlyError): - __select__ = match_exception(UniqueTogetherError) - def raise_user_exception(self): - etype, rtypes = self.exc.args - msg = self._cw._('violates unique_together constraints (%s)') % ( - ', '.join([self._cw._(rtype) for rtype in rtypes])) - raise ValidationError(self.entity.eid, dict((col, msg) for col in rtypes)) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef entities/authobjs.py --- a/entities/authobjs.py Thu Dec 08 14:29:48 2011 +0100 +++ b/entities/authobjs.py Thu Dec 08 14:32:57 2011 +0100 @@ -26,30 +26,27 @@ class CWGroup(AnyEntity): __regid__ = 'CWGroup' - fetch_attrs, fetch_order = fetch_config(['name']) - fetch_unrelated_order = fetch_order - - def grant_permission(self, entity, pname, plabel=None): - """grant local `pname` permission on `entity` to this group using - :class:`CWPermission`. + fetch_attrs, cw_fetch_order = fetch_config(['name']) + cw_fetch_unrelated_order = cw_fetch_order - If a similar permission already exists, add the group to it, else create - a new one. - """ - if not self._cw.execute( - 'SET X require_group G WHERE E eid %(e)s, G eid %(g)s, ' - 'E require_permission X, X name %(name)s, X label %(label)s', - {'e': entity.eid, 'g': self.eid, - 'name': pname, 'label': plabel}): - self._cw.create_entity('CWPermission', name=pname, label=plabel, - require_group=self, - reverse_require_permission=entity) + def dc_long_title(self): + name = self.name + trname = self._cw._(name) + if trname != name: + return '%s (%s)' % (name, trname) + return name + + @cached + def num_users(self): + """return the number of users in this group""" + return self._cw.execute('Any COUNT(U) WHERE U in_group G, G eid %(g)s', + {'g': self.eid})[0][0] class CWUser(AnyEntity): __regid__ = 'CWUser' - fetch_attrs, fetch_order = fetch_config(['login', 'firstname', 'surname']) - fetch_unrelated_order = fetch_order + fetch_attrs, cw_fetch_order = fetch_config(['login', 'firstname', 'surname']) + cw_fetch_unrelated_order = cw_fetch_order # used by repository to check if the user can log in or not AUTHENTICABLE_STATES = ('activated',) @@ -139,18 +136,6 @@ return False owns = cached(owns, keyarg=1) - def has_permission(self, pname, contexteid=None): - rql = 'Any P WHERE P is CWPermission, U eid %(u)s, U in_group G, '\ - 'P name %(pname)s, P require_group G' - kwargs = {'pname': pname, 'u': self.eid} - if contexteid is not None: - rql += ', X require_permission P, X eid %(x)s' - kwargs['x'] = contexteid - try: - return self._cw.execute(rql, kwargs) - except Unauthorized: - return False - # presentation utilities ################################################## def name(self): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef entities/lib.py --- a/entities/lib.py Thu Dec 08 14:29:48 2011 +0100 +++ b/entities/lib.py Thu Dec 08 14:32:57 2011 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -39,7 +39,7 @@ class EmailAddress(AnyEntity): __regid__ = 'EmailAddress' - fetch_attrs, fetch_order = fetch_config(['address', 'alias']) + fetch_attrs, cw_fetch_order = fetch_config(['address', 'alias']) rest_attr = 'eid' def dc_title(self): @@ -55,10 +55,6 @@ def prefered(self): return self.prefered_form and self.prefered_form[0] or self - @deprecated('[3.6] use .prefered') - def canonical_form(self): - return self.prefered_form and self.prefered_form[0] or self - def related_emails(self, skipeids=None): # XXX move to eemail # check email relations are in the schema first @@ -94,7 +90,7 @@ class Bookmark(AnyEntity): """customized class for Bookmark entities""" __regid__ = 'Bookmark' - fetch_attrs, fetch_order = fetch_config(['title', 'path']) + fetch_attrs, cw_fetch_order = fetch_config(['title', 'path']) def actual_url(self): url = self._cw.build_url(self.path) @@ -114,7 +110,7 @@ class CWProperty(AnyEntity): __regid__ = 'CWProperty' - fetch_attrs, fetch_order = fetch_config(['pkey', 'value']) + fetch_attrs, cw_fetch_order = fetch_config(['pkey', 'value']) rest_attr = 'pkey' def typed_value(self): @@ -130,7 +126,7 @@ class CWCache(AnyEntity): """Cache""" __regid__ = 'CWCache' - fetch_attrs, fetch_order = fetch_config(['name']) + fetch_attrs, cw_fetch_order = fetch_config(['name']) def touch(self): self._cw.execute('SET X timestamp %(t)s WHERE X eid %(x)s', diff -r 7b2c7f3d3703 -r 29cdde6bb9ef entities/schemaobjs.py --- a/entities/schemaobjs.py Thu Dec 08 14:29:48 2011 +0100 +++ b/entities/schemaobjs.py Thu Dec 08 14:32:57 2011 +0100 @@ -31,7 +31,7 @@ class CWEType(AnyEntity): __regid__ = 'CWEType' - fetch_attrs, fetch_order = fetch_config(['name']) + fetch_attrs, cw_fetch_order = fetch_config(['name']) def dc_title(self): return u'%s (%s)' % (self.name, self._cw._(self.name)) @@ -48,7 +48,7 @@ class CWRType(AnyEntity): __regid__ = 'CWRType' - fetch_attrs, fetch_order = fetch_config(['name']) + fetch_attrs, cw_fetch_order = fetch_config(['name']) def dc_title(self): return u'%s (%s)' % (self.name, self._cw._(self.name)) @@ -139,7 +139,7 @@ class CWConstraint(AnyEntity): __regid__ = 'CWConstraint' - fetch_attrs, fetch_order = fetch_config(['value']) + fetch_attrs, cw_fetch_order = fetch_config(['value']) def dc_title(self): return '%s(%s)' % (self.cstrtype[0].name, self.value or u'') @@ -151,7 +151,7 @@ class RQLExpression(AnyEntity): __regid__ = 'RQLExpression' - fetch_attrs, fetch_order = fetch_config(['exprtype', 'mainvars', 'expression']) + fetch_attrs, cw_fetch_order = fetch_config(['exprtype', 'mainvars', 'expression']) def dc_title(self): return self.expression or u'' @@ -176,13 +176,3 @@ def check_expression(self, *args, **kwargs): return self._rqlexpr().check(*args, **kwargs) - - -class CWPermission(AnyEntity): - __regid__ = 'CWPermission' - fetch_attrs, fetch_order = fetch_config(['name', 'label']) - - def dc_title(self): - if self.label: - return '%s (%s)' % (self._cw._(self.name), self.label) - return self._cw._(self.name) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef entities/sources.py --- a/entities/sources.py Thu Dec 08 14:29:48 2011 +0100 +++ b/entities/sources.py Thu Dec 08 14:32:57 2011 +0100 @@ -21,9 +21,11 @@ import re from socket import gethostname +import logging from logilab.common.textutils import text_to_dict from logilab.common.configuration import OptionError +from logilab.mtconverter import xml_escape from cubicweb import ValidationError from cubicweb.entities import AnyEntity, fetch_config @@ -54,7 +56,7 @@ class CWSource(_CWSourceCfgMixIn, AnyEntity): __regid__ = 'CWSource' - fetch_attrs, fetch_order = fetch_config(['name', 'type']) + fetch_attrs, cw_fetch_order = fetch_config(['name', 'type']) @property def host_config(self): @@ -107,7 +109,7 @@ class CWSourceHostConfig(_CWSourceCfgMixIn, AnyEntity): __regid__ = 'CWSourceHostConfig' - fetch_attrs, fetch_order = fetch_config(['match_host', 'config']) + fetch_attrs, cw_fetch_order = fetch_config(['match_host', 'config']) @property def cwsource(self): @@ -119,7 +121,7 @@ class CWSourceSchemaConfig(AnyEntity): __regid__ = 'CWSourceSchemaConfig' - fetch_attrs, fetch_order = fetch_config(['cw_for_source', 'cw_schema', 'options']) + fetch_attrs, cw_fetch_order = fetch_config(['cw_for_source', 'cw_schema', 'options']) def dc_title(self): return self._cw._(self.__regid__) + ' #%s' % self.eid @@ -131,3 +133,53 @@ @property def cwsource(self): return self.cw_for_source[0] + + +class CWDataImport(AnyEntity): + __regid__ = 'CWDataImport' + repo_source = _logs = None # please pylint + + def init(self): + self._logs = [] + self.repo_source = self.cwsource.repo_source + + def dc_title(self): + return '%s [%s]' % (self.printable_value('start_timestamp'), + self.printable_value('status')) + + @property + def cwsource(self): + return self.cw_import_of[0] + + def record_debug(self, msg, path=None, line=None): + self._log(logging.DEBUG, msg, path, line) + self.repo_source.debug(msg) + + def record_info(self, msg, path=None, line=None): + self._log(logging.INFO, msg, path, line) + self.repo_source.info(msg) + + def record_warning(self, msg, path=None, line=None): + self._log(logging.WARNING, msg, path, line) + self.repo_source.warning(msg) + + def record_error(self, msg, path=None, line=None): + self._status = u'failed' + self._log(logging.ERROR, msg, path, line) + self.repo_source.error(msg) + + def record_fatal(self, msg, path=None, line=None): + self._status = u'failed' + self._log(logging.FATAL, msg, path, line) + self.repo_source.fatal(msg) + + def _log(self, severity, msg, path=None, line=None): + encodedmsg = u'%s\t%s\t%s\t%s
' % (severity, path or u'', + line or u'', xml_escape(msg)) + self._logs.append(encodedmsg) + + def write_log(self, session, **kwargs): + if 'status' not in kwargs: + kwargs['status'] = getattr(self, '_status', u'success') + self.set_attributes(log=u'
'.join(self._logs), **kwargs) + self._logs = [] diff -r 7b2c7f3d3703 -r 29cdde6bb9ef entities/test/unittest_base.py --- a/entities/test/unittest_base.py Thu Dec 08 14:29:48 2011 +0100 +++ b/entities/test/unittest_base.py Thu Dec 08 14:32:57 2011 +0100 @@ -49,7 +49,7 @@ self.assertEqual(entity.dc_creator(), u'member') def test_type(self): - self.assertEqual(self.member.dc_type(), 'cwuser') + self.assertEqual(self.member.dc_type(), 'CWUser') def test_entity_meta_attributes(self): # XXX move to yams @@ -88,10 +88,10 @@ def test_matching_groups(self): e = self.execute('CWUser X WHERE X login "admin"').get_entity(0, 0) - self.failUnless(e.matching_groups('managers')) - self.failIf(e.matching_groups('xyz')) - self.failUnless(e.matching_groups(('xyz', 'managers'))) - self.failIf(e.matching_groups(('xyz', 'abcd'))) + self.assertTrue(e.matching_groups('managers')) + self.assertFalse(e.matching_groups('xyz')) + self.assertTrue(e.matching_groups(('xyz', 'managers'))) + self.assertFalse(e.matching_groups(('xyz', 'abcd'))) def test_dc_title_and_name(self): e = self.execute('CWUser U WHERE U login "member"').get_entity(0, 0) @@ -131,12 +131,12 @@ self.vreg['etypes'].initialization_completed() MyUser_ = self.vreg['etypes'].etype_class('CWUser') # a copy is done systematically - self.failUnless(issubclass(MyUser_, MyUser)) - self.failUnless(implements(MyUser_, IMileStone)) - self.failUnless(implements(MyUser_, ICalendarable)) + self.assertTrue(issubclass(MyUser_, MyUser)) + self.assertTrue(implements(MyUser_, IMileStone)) + self.assertTrue(implements(MyUser_, ICalendarable)) # original class should not have beed modified, only the copy - self.failUnless(implements(MyUser, IMileStone)) - self.failIf(implements(MyUser, ICalendarable)) + self.assertTrue(implements(MyUser, IMileStone)) + self.assertFalse(implements(MyUser, ICalendarable)) class SpecializedEntityClassesTC(CubicWebTC): @@ -149,7 +149,7 @@ def test_etype_class_selection_and_specialization(self): # no specific class for Subdivisions, the default one should be selected eclass = self.select_eclass('SubDivision') - self.failUnless(eclass.__autogenerated__) + self.assertTrue(eclass.__autogenerated__) #self.assertEqual(eclass.__bases__, (AnyEntity,)) # build class from most generic to most specific and make # sure the most specific is always selected @@ -159,8 +159,8 @@ __regid__ = etype self.vreg.register(Foo) eclass = self.select_eclass('SubDivision') - self.failUnless(eclass.__autogenerated__) - self.failIf(eclass is Foo) + self.assertTrue(eclass.__autogenerated__) + self.assertFalse(eclass is Foo) if etype == 'SubDivision': self.assertEqual(eclass.__bases__, (Foo,)) else: diff -r 7b2c7f3d3703 -r 29cdde6bb9ef entities/test/unittest_wfobjs.py --- a/entities/test/unittest_wfobjs.py Thu Dec 08 14:29:48 2011 +0100 +++ b/entities/test/unittest_wfobjs.py Thu Dec 08 14:32:57 2011 +0100 @@ -567,7 +567,7 @@ # test initial state is set rset = self.execute('Any N WHERE S name N, X in_state S, X eid %(x)s', {'x' : ueid}) - self.failIf(rset, rset.rows) + self.assertFalse(rset, rset.rows) self.commit() initialstate = self.execute('Any N WHERE S name N, X in_state S, X eid %(x)s', {'x' : ueid})[0][0] diff -r 7b2c7f3d3703 -r 29cdde6bb9ef entities/wfobjs.py --- a/entities/wfobjs.py Thu Dec 08 14:29:48 2011 +0100 +++ b/entities/wfobjs.py Thu Dec 08 14:32:57 2011 +0100 @@ -183,7 +183,7 @@ fired by the logged user """ __regid__ = 'BaseTransition' - fetch_attrs, fetch_order = fetch_config(['name', 'type']) + fetch_attrs, cw_fetch_order = fetch_config(['name', 'type']) def __init__(self, *args, **kwargs): if self.__regid__ == 'BaseTransition': @@ -251,11 +251,6 @@ 'T condition X WHERE T eid %(x)s', kwargs) # XXX clear caches? - @deprecated('[3.6.1] use set_permission') - def set_transition_permissions(self, requiredgroups=(), conditions=(), - reset=True): - return self.set_permissions(requiredgroups, conditions, reset) - class Transition(BaseTransition): """customized class for Transition entities""" @@ -347,7 +342,7 @@ class State(AnyEntity): """customized class for State entities""" __regid__ = 'State' - fetch_attrs, fetch_order = fetch_config(['name']) + fetch_attrs, cw_fetch_order = fetch_config(['name']) rest_attr = 'eid' @property @@ -360,8 +355,8 @@ """customized class for Transition information entities """ __regid__ = 'TrInfo' - fetch_attrs, fetch_order = fetch_config(['creation_date', 'comment'], - pclass=None) # don't want modification_date + fetch_attrs, cw_fetch_order = fetch_config(['creation_date', 'comment'], + pclass=None) # don't want modification_date @property def for_entity(self): return self.wf_info_for[0] @@ -386,10 +381,6 @@ """ @property - @deprecated('[3.5] use printable_state') - def displayable_state(self): - return self._cw._(self.state) - @property @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').main_workflow") def main_workflow(self): return self.cw_adapt_to('IWorkflowable').main_workflow @@ -414,14 +405,6 @@ def workflow_history(self): return self.cw_adapt_to('IWorkflowable').workflow_history - @deprecated('[3.5] get transition from current workflow and use its may_be_fired method') - def can_pass_transition(self, trname): - """return the Transition instance if the current user can fire the - transition with the given name, else None - """ - tr = self.current_workflow and self.current_workflow.transition_by_name(trname) - if tr and tr.may_be_fired(self.eid): - return tr @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').cwetype_workflow()") def cwetype_workflow(self): return self.cw_adapt_to('IWorkflowable').main_workflow() @@ -607,11 +590,7 @@ if hasattr(statename, 'eid'): stateeid = statename.eid else: - if not isinstance(statename, basestring): - warn('[3.5] give a state name', DeprecationWarning, stacklevel=2) - state = self.current_workflow.state_by_eid(statename) - else: - state = self.current_workflow.state_by_name(statename) + state = self.current_workflow.state_by_name(statename) if state is None: raise WorkflowException('not a %s state: %s' % (self.__regid__, statename)) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef entity.py --- a/entity.py Thu Dec 08 14:29:48 2011 +0100 +++ b/entity.py Thu Dec 08 14:32:57 2011 +0100 @@ -27,16 +27,21 @@ from logilab.mtconverter import TransformData, TransformError, xml_escape from rql.utils import rqlvar_maker +from rql.stmts import Select +from rql.nodes import (Not, VariableRef, Constant, make_relation, + Relation as RqlRelation) from cubicweb import Unauthorized, typed_eid, neg_role +from cubicweb.utils import support_args from cubicweb.rset import ResultSet from cubicweb.selectors import yes from cubicweb.appobject import AppObject from cubicweb.req import _check_cw_unsafe -from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint +from cubicweb.schema import (RQLVocabularyConstraint, RQLConstraint, + GeneratedConstraint) from cubicweb.rqlrewrite import RQLRewriter -from cubicweb.uilib import printable_value, soup2xhtml +from cubicweb.uilib import soup2xhtml from cubicweb.mixins import MI_REL_TRIGGERS from cubicweb.mttransforms import ENGINE @@ -61,23 +66,85 @@ return False return True +def rel_vars(rel): + return ((isinstance(rel.children[0], VariableRef) + and rel.children[0].variable or None), + (isinstance(rel.children[1].children[0], VariableRef) + and rel.children[1].children[0].variable or None) + ) -def remove_ambiguous_rels(attr_set, subjtypes, schema): - '''remove from `attr_set` the relations of entity types `subjtypes` that have - different entity type sets as target''' - for attr in attr_set.copy(): - rschema = schema.rschema(attr) - if rschema.final: +def rel_matches(rel, rtype, role, varname, operator='='): + if rel.r_type == rtype and rel.children[1].operator == operator: + same_role_var_idx = 0 if role == 'subject' else 1 + variables = rel_vars(rel) + if variables[same_role_var_idx].name == varname: + return variables[1 - same_role_var_idx] + +def build_cstr_with_linkto_infos(cstr, args, searchedvar, evar, + lt_infos, eidvars): + """restrict vocabulary as much as possible in entity creation, + based on infos provided by __linkto form param. + + Example based on following schema: + + class works_in(RelationDefinition): + subject = 'CWUser' + object = 'Lab' + cardinality = '1*' + constraints = [RQLConstraint('S in_group G, O welcomes G')] + + class welcomes(RelationDefinition): + subject = 'Lab' + object = 'CWGroup' + + If you create a CWUser in the "scientists" CWGroup you can show + only the labs that welcome them using : + + lt_infos = {('in_group', 'subject'): 321} + + You get following restriction : 'O welcomes G, G eid 321' + + """ + st = cstr.snippet_rqlst.copy() + # replace relations in ST by eid infos from linkto where possible + for (info_rtype, info_role), eids in lt_infos.iteritems(): + eid = eids[0] # NOTE: we currently assume a pruned lt_info with only 1 eid + for rel in st.iget_nodes(RqlRelation): + targetvar = rel_matches(rel, info_rtype, info_role, evar.name) + if targetvar is not None: + if targetvar.name in eidvars: + rel.parent.remove(rel) + else: + eidrel = make_relation( + targetvar, 'eid', (targetvar.name, 'Substitute'), + Constant) + rel.parent.replace(rel, eidrel) + args[targetvar.name] = eid + eidvars.add(targetvar.name) + # if modified ST still contains evar references we must discard the + # constraint, otherwise evar is unknown in the final rql query which can + # lead to a SQL table cartesian product and multiple occurences of solutions + evarname = evar.name + for rel in st.iget_nodes(RqlRelation): + for variable in rel_vars(rel): + if variable and evarname == variable.name: + return + # else insert snippets into the global tree + return GeneratedConstraint(st, cstr.mainvars - set(evarname)) + +def pruned_lt_info(eschema, lt_infos): + pruned = {} + for (lt_rtype, lt_role), eids in lt_infos.iteritems(): + # we can only use lt_infos describing relation with a cardinality + # of value 1 towards the linked entity + if not len(eids) == 1: continue - ttypes = None - for subjtype in subjtypes: - cur_ttypes = rschema.objects(subjtype) - if ttypes is None: - ttypes = cur_ttypes - elif cur_ttypes != ttypes: - attr_set.remove(attr) - break - + lt_card = eschema.rdef(lt_rtype, lt_role).cardinality[ + 0 if lt_role == 'subject' else 1] + if lt_card not in '?1': + continue + pruned[(lt_rtype, lt_role)] = eids + return pruned class Entity(AppObject): """an entity instance has e_schema automagically set on @@ -91,16 +158,16 @@ :type e_schema: `cubicweb.schema.EntitySchema` :ivar e_schema: the entity's schema - :type rest_var: str - :cvar rest_var: indicates which attribute should be used to build REST urls - If None is specified, the first non-meta attribute will - be used + :type rest_attr: str + :cvar rest_attr: indicates which attribute should be used to build REST urls + If `None` is specified (the default), the first unique attribute will + be used ('eid' if none found) - :type skip_copy_for: list - :cvar skip_copy_for: a list of relations that should be skipped when copying - this kind of entity. Note that some relations such - as composite relations or relations that have '?1' as object - cardinality are always skipped. + :type cw_skip_copy_for: list + :cvar cw_skip_copy_for: a list of couples (rtype, role) for each relation + that should be skipped when copying this kind of entity. Note that some + relations such as composite relations or relations that have '?1' as + object cardinality are always skipped. """ __registry__ = 'etypes' __select__ = yes() @@ -108,7 +175,8 @@ # class attributes that must be set in class definition rest_attr = None fetch_attrs = None - skip_copy_for = ('in_state',) # XXX turn into a set + skip_copy_for = () # bw compat (< 3.14), use cw_skip_copy_for instead + cw_skip_copy_for = [('in_state', 'subject')] # class attributes set automatically at registration time e_schema = None @@ -153,50 +221,131 @@ cls.info('plugged %s mixins on %s', mixins, cls) fetch_attrs = ('modification_date',) - @classmethod - def fetch_order(cls, attr, var): - """class method used to control sort order when multiple entities of - this type are fetched - """ - return cls.fetch_unrelated_order(attr, var) @classmethod - def fetch_unrelated_order(cls, attr, var): - """class method used to control sort order when multiple entities of - this type are fetched to use in edition (eg propose them to create a + def cw_fetch_order(cls, select, attr, var): + """This class method may be used to control sort order when multiple + entities of this type are fetched through ORM methods. Its arguments + are: + + * `select`, the RQL syntax tree + + * `attr`, the attribute being watched + + * `var`, the variable through which this attribute's value may be + accessed in the query + + When you want to do some sorting on the given attribute, you should + modify the syntax tree accordingly. For instance: + + .. sourcecode:: python + + from rql import nodes + + class Version(AnyEntity): + __regid__ = 'Version' + + fetch_attrs = ('num', 'description', 'in_state') + + @classmethod + def cw_fetch_order(cls, select, attr, var): + if attr == 'num': + func = nodes.Function('version_sort_value') + func.append(nodes.variable_ref(var)) + sterm = nodes.SortTerm(func, asc=False) + select.add_sort_term(sterm) + + The default implementation call + :meth:`~cubicweb.entity.Entity.cw_fetch_unrelated_order` + """ + cls.cw_fetch_unrelated_order(select, attr, var) + + @classmethod + def cw_fetch_unrelated_order(cls, select, attr, var): + """This class method may be used to control sort order when multiple entities of + this type are fetched to use in edition (e.g. propose them to create a new relation on an edited entity). + + See :meth:`~cubicweb.entity.Entity.cw_fetch_unrelated_order` for a + description of its arguments and usage. + + By default entities will be listed on their modification date descending, + i.e. you'll get entities recently modified first. """ if attr == 'modification_date': - return '%s DESC' % var - return None + select.add_sort_var(var, asc=False) @classmethod def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X', settype=True, ordermethod='fetch_order'): - """return a rql to fetch all entities of the class type""" - # XXX update api and implementation to AST manipulation (see unrelated rql) - restrictions = restriction or [] - if settype: - restrictions.append('%s is %s' % (mainvar, cls.__regid__)) - if fetchattrs is None: - fetchattrs = cls.fetch_attrs - selection = [mainvar] - orderby = [] - # start from 26 to avoid possible conflicts with X - # XXX not enough to be sure it'll be no conflicts - varmaker = rqlvar_maker(index=26) - cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection, - orderby, restrictions, user, ordermethod) - rql = 'Any %s' % ','.join(selection) - if orderby: - rql += ' ORDERBY %s' % ','.join(orderby) - rql += ' WHERE %s' % ', '.join(restrictions) + st = cls.fetch_rqlst(user, mainvar=mainvar, fetchattrs=fetchattrs, + settype=settype, ordermethod=ordermethod) + rql = st.as_string() + if restriction: + # cannot use RQLRewriter API to insert 'X rtype %(x)s' restriction + warn('[3.14] fetch_rql: use of `restriction` parameter is ' + 'deprecated, please use fetch_rqlst and supply a syntax' + 'tree with your restriction instead', DeprecationWarning) + insert = ' WHERE ' + ','.join(restriction) + if ' WHERE ' in rql: + select, where = rql.split(' WHERE ', 1) + rql = select + insert + ',' + where + else: + rql += insert return rql @classmethod - def _fetch_restrictions(cls, mainvar, varmaker, fetchattrs, - selection, orderby, restrictions, user, - ordermethod='fetch_order', visited=None): + def fetch_rqlst(cls, user, select=None, mainvar='X', fetchattrs=None, + settype=True, ordermethod='fetch_order'): + if select is None: + select = Select() + mainvar = select.get_variable(mainvar) + select.add_selected(mainvar) + elif isinstance(mainvar, basestring): + assert mainvar in select.defined_vars + mainvar = select.get_variable(mainvar) + # eases string -> syntax tree test transition: please remove once stable + select._varmaker = rqlvar_maker(defined=select.defined_vars, + aliases=select.aliases, index=26) + if settype: + select.add_type_restriction(mainvar, cls.__regid__) + if fetchattrs is None: + fetchattrs = cls.fetch_attrs + cls._fetch_restrictions(mainvar, select, fetchattrs, user, ordermethod) + return select + + @classmethod + def _fetch_ambiguous_rtypes(cls, select, var, fetchattrs, subjtypes, schema): + """find rtypes in `fetchattrs` that relate different subject etypes + taken from (`subjtypes`) to different target etypes; these so called + "ambiguous" relations, are added directly to the `select` syntax tree + selection but removed from `fetchattrs` to avoid the fetch recursion + because we have to choose only one targettype for the recursion and + adding its own fetch attrs to the selection -when we recurse- would + filter out the other possible target types from the result set + """ + for attr in fetchattrs.copy(): + rschema = schema.rschema(attr) + if rschema.final: + continue + ttypes = None + for subjtype in subjtypes: + cur_ttypes = set(rschema.objects(subjtype)) + if ttypes is None: + ttypes = cur_ttypes + elif cur_ttypes != ttypes: + # we found an ambiguous relation: remove it from fetchattrs + fetchattrs.remove(attr) + # ... and add it to the selection + targetvar = select.make_variable() + select.add_selected(targetvar) + rel = make_relation(var, attr, (targetvar,), VariableRef) + select.add_restriction(rel) + break + + @classmethod + def _fetch_restrictions(cls, mainvar, select, fetchattrs, + user, ordermethod='fetch_order', visited=None): eschema = cls.e_schema if visited is None: visited = set((eschema.type,)) @@ -216,51 +365,85 @@ rdef = eschema.rdef(attr) if not user.matching_groups(rdef.get_groups('read')): continue - var = varmaker.next() - selection.append(var) - restriction = '%s %s %s' % (mainvar, attr, var) - restrictions.append(restriction) + if rschema.final or rdef.cardinality[0] in '?1': + var = select.make_variable() + select.add_selected(var) + rel = make_relation(mainvar, attr, (var,), VariableRef) + select.add_restriction(rel) + else: + cls.warning('bad relation %s specified in fetch attrs for %s', + attr, cls) + continue if not rschema.final: - card = rdef.cardinality[0] - if card not in '?1': - cls.warning('bad relation %s specified in fetch attrs for %s', - attr, cls) - selection.pop() - restrictions.pop() - continue # XXX we need outer join in case the relation is not mandatory # (card == '?') *or if the entity is being added*, since in # that case the relation may still be missing. As we miss this # later information here, systematically add it. - restrictions[-1] += '?' + rel.change_optional('right') targettypes = rschema.objects(eschema.type) - # XXX user._cw.vreg iiiirk - etypecls = user._cw.vreg['etypes'].etype_class(targettypes[0]) + vreg = user._cw.vreg # XXX user._cw.vreg iiiirk + etypecls = vreg['etypes'].etype_class(targettypes[0]) if len(targettypes) > 1: # find fetch_attrs common to all destination types - fetchattrs = user._cw.vreg['etypes'].fetch_attrs(targettypes) - remove_ambiguous_rels(fetchattrs, targettypes, user._cw.vreg.schema) + fetchattrs = vreg['etypes'].fetch_attrs(targettypes) + # ... and handle ambiguous relations + cls._fetch_ambiguous_rtypes(select, var, fetchattrs, + targettypes, vreg.schema) else: fetchattrs = etypecls.fetch_attrs - etypecls._fetch_restrictions(var, varmaker, fetchattrs, - selection, orderby, restrictions, + etypecls._fetch_restrictions(var, select, fetchattrs, user, ordermethod, visited=visited) if ordermethod is not None: - orderterm = getattr(cls, ordermethod)(attr, var) - if orderterm: - orderby.append(orderterm) - return selection, orderby, restrictions + try: + cmeth = getattr(cls, ordermethod) + warn('[3.14] %s %s class method should be renamed to cw_%s' + % (cls.__regid__, ordermethod, ordermethod), + DeprecationWarning) + except AttributeError: + cmeth = getattr(cls, 'cw_' + ordermethod) + if support_args(cmeth, 'select'): + cmeth(select, attr, var) + else: + warn('[3.14] %s should now take (select, attr, var) and ' + 'modify the syntax tree when desired instead of ' + 'returning something' % cmeth, DeprecationWarning) + orderterm = cmeth(attr, var.name) + if orderterm is not None: + try: + var, order = orderterm.split() + except ValueError: + if '(' in orderterm: + cls.error('ignore %s until %s is upgraded', + orderterm, cmeth) + orderterm = None + elif not ' ' in orderterm.strip(): + var = orderterm + order = 'ASC' + if orderterm is not None: + select.add_sort_var(select.get_variable(var), + order=='ASC') @classmethod @cached - def _rest_attr_info(cls): + def cw_rest_attr_info(cls): + """this class method return an attribute name to be used in URL for + entities of this type and a boolean flag telling if its value should be + checked for uniqness. + + The attribute returned is, in order of priority: + + * class's `rest_attr` class attribute + * an attribute defined as unique in the class'schema + * 'eid' + """ mainattr, needcheck = 'eid', True if cls.rest_attr: mainattr = cls.rest_attr needcheck = not cls.e_schema.has_unique_values(mainattr) else: for rschema in cls.e_schema.subject_relations(): - if rschema.final and rschema != 'eid' and cls.e_schema.has_unique_values(rschema): + if rschema.final and rschema != 'eid' \ + and cls.e_schema.has_unique_values(rschema): mainattr = str(rschema) needcheck = False break @@ -354,7 +537,7 @@ """custom json dumps hook to dump the entity's eid which is not part of dict structure itself """ - dumpable = dict(self) + dumpable = self.cw_attr_cache.copy() dumpable['eid'] = self.eid return dumpable @@ -440,19 +623,14 @@ kwargs['base_url'] = sourcemeta['base-url'] use_ext_id = True if method in (None, 'view'): - try: - kwargs['_restpath'] = self.rest_path(use_ext_id) - except TypeError: - warn('[3.4] %s: rest_path() now take use_ext_eid argument, ' - 'please update' % self.__regid__, DeprecationWarning) - kwargs['_restpath'] = self.rest_path() + kwargs['_restpath'] = self.rest_path(use_ext_id) else: kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid return self._cw.build_url(method, **kwargs) def rest_path(self, use_ext_eid=False): # XXX cw_rest_path """returns a REST-like (relative) path for this entity""" - mainattr, needcheck = self._rest_attr_info() + mainattr, needcheck = self.cw_rest_attr_info() etype = str(self.e_schema) path = etype.lower() if mainattr != 'eid': @@ -516,8 +694,8 @@ return self._cw_mtc_transform(value.getvalue(), attrformat, format, encoding) return u'' - value = printable_value(self._cw, attrtype, value, props, - displaytime=displaytime) + value = self._cw.printable_value(attrtype, value, props, + displaytime=displaytime) if format == 'text/html': value = xml_escape(value) return value @@ -542,13 +720,22 @@ """ assert self.has_eid() execute = self._cw.execute + skip_copy_for = {'subject': set(), 'object': set()} + for rtype in self.skip_copy_for: + skip_copy_for['subject'].add(rtype) + warn('[3.14] skip_copy_for on entity classes (%s) is deprecated, ' + 'use cw_skip_for instead with list of couples (rtype, role)' % self.__regid__, + DeprecationWarning) + for rtype, role in self.cw_skip_copy_for: + assert role in ('subject', 'object'), role + skip_copy_for[role].add(rtype) for rschema in self.e_schema.subject_relations(): if rschema.final or rschema.meta: continue # skip already defined relations if getattr(self, rschema.type): continue - if rschema.type in self.skip_copy_for: + if rschema.type in skip_copy_for['subject']: continue # skip composite relation rdef = self.e_schema.rdef(rschema) @@ -568,6 +755,8 @@ # skip already defined relations if self.related(rschema.type, 'object'): continue + if rschema.type in skip_copy_for['object']: + continue rdef = self.e_schema.rdef(rschema, 'object') # skip composite relation if rdef.composite: @@ -646,7 +835,7 @@ var = varmaker.next() rql.append('%s %s %s' % (V, attr, var)) selected.append((attr, var)) - # +1 since this doen't include the main variable + # +1 since this doesn't include the main variable lastattr = len(selected) + 1 # don't fetch extra relation if attributes specified or of the entity is # coming from an external source (may lead to error) @@ -738,6 +927,7 @@ if True, an empty rset/list of entities will be returned in case of :exc:`Unauthorized`, else (the default), the exception is propagated """ + rtype = str(rtype) try: return self._cw_relation_cache(rtype, role, entities, limit) except KeyError: @@ -757,94 +947,112 @@ return self.related(rtype, role, limit, entities) def cw_related_rql(self, rtype, role='subject', targettypes=None): - rschema = self._cw.vreg.schema[rtype] + vreg = self._cw.vreg + rschema = vreg.schema[rtype] + select = Select() + mainvar, evar = select.get_variable('X'), select.get_variable('E') + select.add_selected(mainvar) + select.add_eid_restriction(evar, 'x', 'Substitute') if role == 'subject': - restriction = 'E eid %%(x)s, E %s X' % rtype + rel = make_relation(evar, rtype, (mainvar,), VariableRef) + select.add_restriction(rel) if targettypes is None: targettypes = rschema.objects(self.e_schema) else: - restriction += ', X is IN (%s)' % ','.join(targettypes) - card = greater_card(rschema, (self.e_schema,), targettypes, 0) + select.add_constant_restriction(mainvar, 'is', + targettypes, 'etype') + gcard = greater_card(rschema, (self.e_schema,), targettypes, 0) else: - restriction = 'E eid %%(x)s, X %s E' % rtype + rel = make_relation(mainvar, rtype, (evar,), VariableRef) + select.add_restriction(rel) if targettypes is None: targettypes = rschema.subjects(self.e_schema) else: - restriction += ', X is IN (%s)' % ','.join(targettypes) - card = greater_card(rschema, targettypes, (self.e_schema,), 1) - etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0]) + select.add_constant_restriction(mainvar, 'is', targettypes, + 'etype') + gcard = greater_card(rschema, targettypes, (self.e_schema,), 1) + etypecls = vreg['etypes'].etype_class(targettypes[0]) if len(targettypes) > 1: - fetchattrs = self._cw.vreg['etypes'].fetch_attrs(targettypes) - # XXX we should fetch ambiguous relation objects too but not - # recurse on them in _fetch_restrictions; it is easier to remove - # them completely for now, as it would require an deeper api rewrite - remove_ambiguous_rels(fetchattrs, targettypes, self._cw.vreg.schema) + fetchattrs = vreg['etypes'].fetch_attrs(targettypes) + self._fetch_ambiguous_rtypes(select, mainvar, fetchattrs, + targettypes, vreg.schema) else: fetchattrs = etypecls.fetch_attrs - rql = etypecls.fetch_rql(self._cw.user, [restriction], fetchattrs, - settype=False) + etypecls.fetch_rqlst(self._cw.user, select, mainvar, fetchattrs, + settype=False) # optimisation: remove ORDERBY if cardinality is 1 or ? (though # greater_card return 1 for those both cases) - if card == '1': - if ' ORDERBY ' in rql: - rql = '%s WHERE %s' % (rql.split(' ORDERBY ', 1)[0], - rql.split(' WHERE ', 1)[1]) - elif not ' ORDERBY ' in rql: - args = rql.split(' WHERE ', 1) - # if modification_date already retrieved, we should use it instead - # of adding another variable for sort. This should be be problematic - # but it's actually with sqlserver, see ticket #694445 - if 'X modification_date ' in args[1]: - var = args[1].split('X modification_date ', 1)[1].split(',', 1)[0] - args.insert(1, var.strip()) - rql = '%s ORDERBY %s DESC WHERE %s' % tuple(args) + if gcard == '1': + select.remove_sort_terms() + elif not select.orderby: + # if modification_date is already retrieved, we use it instead + # of adding another variable for sorting. This should not be + # problematic, but it is with sqlserver, see ticket #694445 + for rel in select.where.get_nodes(RqlRelation): + if (rel.r_type == 'modification_date' + and rel.children[0].variable == mainvar + and rel.children[1].operator == '='): + var = rel.children[1].children[0].variable + select.add_sort_var(var, asc=False) + break else: - rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % \ - tuple(args) - return rql + mdvar = select.make_variable() + rel = make_relation(mainvar, 'modification_date', + (mdvar,), VariableRef) + select.add_restriction(rel) + select.add_sort_var(mdvar, asc=False) + return select.as_string() # generic vocabulary methods ############################################## def cw_unrelated_rql(self, rtype, targettype, role, ordermethod=None, - vocabconstraints=True): + vocabconstraints=True, lt_infos={}): """build a rql to fetch `targettype` entities unrelated to this entity using (rtype, role) relation. Consider relation permissions so that returned entities may be actually linked by `rtype`. + + `lt_infos` are supplementary informations, usually coming from __linkto + parameter, that can help further restricting the results in case current + entity is not yet created. It is a dict describing entities the current + entity will be linked to, which keys are (rtype, role) tuples and values + are a list of eids. """ ordermethod = ordermethod or 'fetch_unrelated_order' - if isinstance(rtype, basestring): - rtype = self._cw.vreg.schema.rschema(rtype) - rdef = rtype.role_rdef(self.e_schema, targettype, role) + rschema = self._cw.vreg.schema.rschema(rtype) + rdef = rschema.role_rdef(self.e_schema, targettype, role) rewriter = RQLRewriter(self._cw) + select = Select() # initialize some variables according to the `role` of `self` in the - # relation: - # * variable for myself (`evar`) and searched entities (`searchvedvar`) - # * entity type of the subject (`subjtype`) and of the object - # (`objtype`) of the relation + # relation (variable names must respect constraints conventions): + # * variable for myself (`evar`) + # * variable for searched entities (`searchvedvar`) if role == 'subject': - evar, searchedvar = 'S', 'O' - subjtype, objtype = self.e_schema, targettype + evar = subjvar = select.get_variable('S') + searchedvar = objvar = select.get_variable('O') else: - searchedvar, evar = 'S', 'O' - objtype, subjtype = self.e_schema, targettype - # initialize some variables according to `self` existance + searchedvar = subjvar = select.get_variable('S') + evar = objvar = select.get_variable('O') + select.add_selected(searchedvar) + # initialize some variables according to `self` existence if rdef.role_cardinality(neg_role(role)) in '?1': # if cardinality in '1?', we want a target entity which isn't # already linked using this relation - if searchedvar == 'S': - restriction = ['NOT S %s ZZ' % rtype] + variable = select.make_variable() + if role == 'subject': + rel = make_relation(variable, rtype, (searchedvar,), VariableRef) else: - restriction = ['NOT ZZ %s O' % rtype] + rel = make_relation(searchedvar, rtype, (variable,), VariableRef) + select.add_restriction(Not(rel)) elif self.has_eid(): # elif we have an eid, we don't want a target entity which is # already linked to ourself through this relation - restriction = ['NOT S %s O' % rtype] - else: - restriction = [] + rel = make_relation(subjvar, rtype, (objvar,), VariableRef) + select.add_restriction(Not(rel)) if self.has_eid(): - restriction += ['%s eid %%(x)s' % evar] + rel = make_relation(evar, 'eid', ('x', 'Substitute'), Constant) + select.add_restriction(rel) args = {'x': self.eid} if role == 'subject': sec_check_args = {'fromeid': self.eid} @@ -854,12 +1062,15 @@ else: args = {} sec_check_args = {} - existant = searchedvar - # retreive entity class for targettype to compute base rql + existant = searchedvar.name + # undefine unused evar, or the type resolver will consider it + select.undefine_variable(evar) + # retrieve entity class for targettype to compute base rql etypecls = self._cw.vreg['etypes'].etype_class(targettype) - rql = etypecls.fetch_rql(self._cw.user, restriction, - mainvar=searchedvar, ordermethod=ordermethod) - select = self._cw.vreg.parse(self._cw, rql, args).children[0] + etypecls.fetch_rqlst(self._cw.user, select, searchedvar, + ordermethod=ordermethod) + # from now on, we need variable type resolving + self._cw.vreg.solutions(self._cw, select, args) # insert RQL expressions for schema constraints into the rql syntax tree if vocabconstraints: # RQLConstraint is a subclass for RQLVocabularyConstraint, so they @@ -867,14 +1078,26 @@ cstrcls = RQLVocabularyConstraint else: cstrcls = RQLConstraint + lt_infos = pruned_lt_info(self.e_schema, lt_infos or {}) + # if there are still lt_infos, use set to keep track of added eid + # relations (adding twice the same eid relation is incorrect RQL) + eidvars = set() for cstr in rdef.constraints: # consider constraint.mainvars to check if constraint apply - if isinstance(cstr, cstrcls) and searchedvar in cstr.mainvars: - if not self.has_eid() and evar in cstr.mainvars: - continue + if isinstance(cstr, cstrcls) and searchedvar.name in cstr.mainvars: + if not self.has_eid(): + if lt_infos: + # we can perhaps further restrict with linkto infos using + # a custom constraint built from cstr and lt_infos + cstr = build_cstr_with_linkto_infos( + cstr, args, searchedvar, evar, lt_infos, eidvars) + if cstr is None: + continue # could not build constraint -> discard + elif evar.name in cstr.mainvars: + continue # compute a varmap suitable to RQLRewriter.rewrite argument - varmap = dict((v, v) for v in 'SO' if v in select.defined_vars - and v in cstr.mainvars) + varmap = dict((v, v) for v in (searchedvar.name, evar.name) + if v in select.defined_vars and v in cstr.mainvars) # rewrite constraint by constraint since we want a AND between # expressions. rewriter.rewrite(select, [(varmap, (cstr,))], select.solutions, @@ -884,24 +1107,26 @@ rqlexprs = rdef.get_rqlexprs('add') if rqlexprs and not rdef.has_perm(self._cw, 'add', **sec_check_args): # compute a varmap suitable to RQLRewriter.rewrite argument - varmap = dict((v, v) for v in 'SO' if v in select.defined_vars) + varmap = dict((v, v) for v in (searchedvar.name, evar.name) + if v in select.defined_vars) # rewrite all expressions at once since we want a OR between them. rewriter.rewrite(select, [(varmap, rqlexprs)], select.solutions, args, existant) # ensure we have an order defined if not select.orderby: - select.add_sort_var(select.defined_vars[searchedvar]) + select.add_sort_var(select.defined_vars[searchedvar.name]) # we're done, turn the rql syntax tree as a string rql = select.as_string() return rql, args def unrelated(self, rtype, targettype, role='subject', limit=None, - ordermethod=None): # XXX .cw_unrelated + ordermethod=None, lt_infos={}): # XXX .cw_unrelated """return a result set of target type objects that may be related by a given relation, with self as subject or object """ try: - rql, args = self.cw_unrelated_rql(rtype, targettype, role, ordermethod) + rql, args = self.cw_unrelated_rql(rtype, targettype, role, + ordermethod, lt_infos=lt_infos) except Unauthorized: return self._cw.empty_rset() # XXX should be set in unrelated rql when manipulating the AST diff -r 7b2c7f3d3703 -r 29cdde6bb9ef etwist/test/unittest_server.py --- a/etwist/test/unittest_server.py Thu Dec 08 14:29:48 2011 +0100 +++ b/etwist/test/unittest_server.py Thu Dec 08 14:32:57 2011 +0100 @@ -69,7 +69,7 @@ def test_cache(self): concat = ConcatFiles(self.config, ('cubicweb.ajax.js', 'jquery.js')) - self.failUnless(osp.isfile(concat.path)) + self.assertTrue(osp.isfile(concat.path)) def test_404(self): # when not in debug mode, should not crash diff -r 7b2c7f3d3703 -r 29cdde6bb9ef hooks/__init__.py --- a/hooks/__init__.py Thu Dec 08 14:29:48 2011 +0100 +++ b/hooks/__init__.py Thu Dec 08 14:32:57 2011 +0100 @@ -59,13 +59,23 @@ continue session = repo.internal_session(safe=True) try: - stats = source.pull_data(session) - if stats.get('created'): - source.info('added %s entities', len(stats['created'])) - if stats.get('updated'): - source.info('updated %s entities', len(stats['updated'])) + source.pull_data(session) except Exception, exc: session.exception('while trying to update feed %s', source) finally: session.close() self.repo.looping_task(60, update_feeds, self.repo) + + def expire_dataimports(repo=self.repo): + for source in repo.sources_by_eid.itervalues(): + if (not source.copy_based_source + or not repo.config.source_enabled(source)): + continue + session = repo.internal_session() + try: + mindate = datetime.now() - timedelta(seconds=source.config['logs-lifetime']) + session.execute('DELETE CWDataImport X WHERE X start_timestamp < %(time)s', {'time': mindate}) + session.commit() + finally: + session.close() + self.repo.looping_task(60*60*24, expire_dataimports, self.repo) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef hooks/syncschema.py --- a/hooks/syncschema.py Thu Dec 08 14:29:48 2011 +0100 +++ b/hooks/syncschema.py Thu Dec 08 14:32:57 2011 +0100 @@ -300,6 +300,9 @@ self.info('renamed table %s to %s', oldname, newname) sqlexec('UPDATE entities SET type=%(newname)s WHERE type=%(oldname)s', {'newname': newname, 'oldname': oldname}) + for eid, (etype, uri, extid, auri) in self.session.repo._type_source_cache.items(): + if etype == oldname: + self.session.repo._type_source_cache[eid] = (newname, uri, extid, auri) sqlexec('UPDATE deleted_entities SET type=%(newname)s WHERE type=%(oldname)s', {'newname': newname, 'oldname': oldname}) # XXX transaction records @@ -484,6 +487,11 @@ # set default value, using sql for performance and to avoid # modification_date update if default: + if rdefdef.object in ('Date', 'Datetime'): + if default == 'TODAY': + default = syssource.dbhelper.sql_current_date() + elif default == 'NOW': + default = syssource.dbhelper.sql_current_timestamp() session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column), {'default': default}) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef hooks/test/unittest_bookmarks.py --- a/hooks/test/unittest_bookmarks.py Thu Dec 08 14:29:48 2011 +0100 +++ b/hooks/test/unittest_bookmarks.py Thu Dec 08 14:32:57 2011 +0100 @@ -28,10 +28,10 @@ self.commit() self.execute('DELETE X bookmarked_by U WHERE U login "admin"') self.commit() - self.failUnless(self.execute('Any X WHERE X eid %(x)s', {'x': beid})) + self.assertTrue(self.execute('Any X WHERE X eid %(x)s', {'x': beid})) self.execute('DELETE X bookmarked_by U WHERE U login "anon"') self.commit() - self.failIf(self.execute('Any X WHERE X eid %(x)s', {'x': beid})) + self.assertFalse(self.execute('Any X WHERE X eid %(x)s', {'x': beid})) if __name__ == '__main__': unittest_main() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef hooks/test/unittest_hooks.py --- a/hooks/test/unittest_hooks.py Thu Dec 08 14:29:48 2011 +0100 +++ b/hooks/test/unittest_hooks.py Thu Dec 08 14:32:57 2011 +0100 @@ -117,7 +117,7 @@ self.repo.connect, u'toto', password='hop') self.commit() cnxid = self.repo.connect(u'toto', password='hop') - self.failIfEqual(cnxid, self.session.id) + self.assertNotEqual(cnxid, self.session.id) self.execute('DELETE CWUser X WHERE X login "toto"') self.repo.execute(cnxid, 'State X') self.commit() @@ -151,7 +151,7 @@ eid = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"')[0][0] self.execute('DELETE EmailAddress X WHERE X eid %s' % eid) self.commit() - self.failIf(self.execute('Any X WHERE X created_by Y, X eid >= %(x)s', {'x': eid})) + self.assertFalse(self.execute('Any X WHERE X created_by Y, X eid >= %(x)s', {'x': eid})) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef hooks/test/unittest_integrity.py --- a/hooks/test/unittest_integrity.py Thu Dec 08 14:29:48 2011 +0100 +++ b/hooks/test/unittest_integrity.py Thu Dec 08 14:32:57 2011 +0100 @@ -62,7 +62,7 @@ self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"') self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P ' 'WHERE Y is EmailAddress, P is EmailPart') - self.failUnless(self.execute('Email X WHERE X sender Y')) + self.assertTrue(self.execute('Email X WHERE X sender Y')) self.commit() self.execute('DELETE Email X') rset = self.execute('Any X WHERE X is EmailPart') diff -r 7b2c7f3d3703 -r 29cdde6bb9ef hooks/test/unittest_syncschema.py --- a/hooks/test/unittest_syncschema.py Thu Dec 08 14:29:48 2011 +0100 +++ b/hooks/test/unittest_syncschema.py Thu Dec 08 14:32:57 2011 +0100 @@ -60,18 +60,18 @@ self.session.set_cnxset() dbhelper = self.session.cnxset.source('system').dbhelper sqlcursor = self.session.cnxset['system'] - self.failIf(schema.has_entity('Societe2')) - self.failIf(schema.has_entity('concerne2')) + self.assertFalse(schema.has_entity('Societe2')) + self.assertFalse(schema.has_entity('concerne2')) # schema should be update on insertion (after commit) eeid = self.execute('INSERT CWEType X: X name "Societe2", X description "", X final FALSE')[0][0] self._set_perms(eeid) self.execute('INSERT CWRType X: X name "concerne2", X description "", X final FALSE, X symmetric FALSE') - self.failIf(schema.has_entity('Societe2')) - self.failIf(schema.has_entity('concerne2')) + self.assertFalse(schema.has_entity('Societe2')) + self.assertFalse(schema.has_entity('concerne2')) # have to commit before adding definition relations self.commit() - self.failUnless(schema.has_entity('Societe2')) - self.failUnless(schema.has_relation('concerne2')) + self.assertTrue(schema.has_entity('Societe2')) + self.assertTrue(schema.has_relation('concerne2')) attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", ' ' X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F ' 'WHERE RT name "name", E name "Societe2", F name "String"')[0][0] @@ -80,13 +80,13 @@ 'INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E ' 'WHERE RT name "concerne2", E name "Societe2"')[0][0] self._set_perms(concerne2_rdef_eid) - self.failIf('name' in schema['Societe2'].subject_relations()) - self.failIf('concerne2' in schema['Societe2'].subject_relations()) - self.failIf(self.index_exists('Societe2', 'name')) + self.assertFalse('name' in schema['Societe2'].subject_relations()) + self.assertFalse('concerne2' in schema['Societe2'].subject_relations()) + self.assertFalse(self.index_exists('Societe2', 'name')) self.commit() - self.failUnless('name' in schema['Societe2'].subject_relations()) - self.failUnless('concerne2' in schema['Societe2'].subject_relations()) - self.failUnless(self.index_exists('Societe2', 'name')) + self.assertTrue('name' in schema['Societe2'].subject_relations()) + self.assertTrue('concerne2' in schema['Societe2'].subject_relations()) + self.assertTrue(self.index_exists('Societe2', 'name')) # now we should be able to insert and query Societe2 s2eid = self.execute('INSERT Societe2 X: X name "logilab"')[0][0] self.execute('Societe2 X WHERE X name "logilab"') @@ -101,20 +101,20 @@ self.commit() self.execute('DELETE CWRelation X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}) self.commit() - self.failUnless('concerne2' in schema['CWUser'].subject_relations()) - self.failIf('concerne2' in schema['Societe2'].subject_relations()) - self.failIf(self.execute('Any X WHERE X concerne2 Y')) + self.assertTrue('concerne2' in schema['CWUser'].subject_relations()) + self.assertFalse('concerne2' in schema['Societe2'].subject_relations()) + self.assertFalse(self.execute('Any X WHERE X concerne2 Y')) # schema should be cleaned on delete (after commit) self.execute('DELETE CWEType X WHERE X name "Societe2"') self.execute('DELETE CWRType X WHERE X name "concerne2"') - self.failUnless(self.index_exists('Societe2', 'name')) - self.failUnless(schema.has_entity('Societe2')) - self.failUnless(schema.has_relation('concerne2')) + self.assertTrue(self.index_exists('Societe2', 'name')) + self.assertTrue(schema.has_entity('Societe2')) + self.assertTrue(schema.has_relation('concerne2')) self.commit() - self.failIf(self.index_exists('Societe2', 'name')) - self.failIf(schema.has_entity('Societe2')) - self.failIf(schema.has_entity('concerne2')) - self.failIf('concerne2' in schema['CWUser'].subject_relations()) + self.assertFalse(self.index_exists('Societe2', 'name')) + self.assertFalse(schema.has_entity('Societe2')) + self.assertFalse(schema.has_entity('concerne2')) + self.assertFalse('concerne2' in schema['CWUser'].subject_relations()) def test_is_instance_of_insertions(self): seid = self.execute('INSERT Transition T: T name "subdiv"')[0][0] @@ -123,15 +123,15 @@ instanceof_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is_instance_of ET, ET name ETN' % seid)] self.assertEqual(sorted(instanceof_etypes), ['BaseTransition', 'Transition']) snames = [name for name, in self.execute('Any N WHERE S is BaseTransition, S name N')] - self.failIf('subdiv' in snames) + self.assertFalse('subdiv' in snames) snames = [name for name, in self.execute('Any N WHERE S is_instance_of BaseTransition, S name N')] - self.failUnless('subdiv' in snames) + self.assertTrue('subdiv' in snames) def test_perms_synchronization_1(self): schema = self.repo.schema self.assertEqual(schema['CWUser'].get_groups('read'), set(('managers', 'users'))) - self.failUnless(self.execute('Any X, Y WHERE X is CWEType, X name "CWUser", Y is CWGroup, Y name "users"')[0]) + self.assertTrue(self.execute('Any X, Y WHERE X is CWEType, X name "CWUser", Y is CWGroup, Y name "users"')[0]) self.execute('DELETE X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"') self.assertEqual(schema['CWUser'].get_groups('read'), set(('managers', 'users', ))) self.commit() @@ -173,13 +173,13 @@ self.session.set_cnxset() dbhelper = self.session.cnxset.source('system').dbhelper sqlcursor = self.session.cnxset['system'] - self.failUnless(self.schema['state_of'].inlined) + self.assertTrue(self.schema['state_of'].inlined) try: self.execute('SET X inlined FALSE WHERE X name "state_of"') - self.failUnless(self.schema['state_of'].inlined) + self.assertTrue(self.schema['state_of'].inlined) self.commit() - self.failIf(self.schema['state_of'].inlined) - self.failIf(self.index_exists('State', 'state_of')) + self.assertFalse(self.schema['state_of'].inlined) + self.assertFalse(self.index_exists('State', 'state_of')) rset = self.execute('Any X, Y WHERE X state_of Y') self.assertEqual(len(rset), 2) # user states except Exception: @@ -187,10 +187,10 @@ traceback.print_exc() finally: self.execute('SET X inlined TRUE WHERE X name "state_of"') - self.failIf(self.schema['state_of'].inlined) + self.assertFalse(self.schema['state_of'].inlined) self.commit() - self.failUnless(self.schema['state_of'].inlined) - self.failUnless(self.index_exists('State', 'state_of')) + self.assertTrue(self.schema['state_of'].inlined) + self.assertTrue(self.index_exists('State', 'state_of')) rset = self.execute('Any X, Y WHERE X state_of Y') self.assertEqual(len(rset), 2) @@ -200,18 +200,18 @@ sqlcursor = self.session.cnxset['system'] try: self.execute('SET X indexed FALSE WHERE X relation_type R, R name "name"') - self.failUnless(self.schema['name'].rdef('Workflow', 'String').indexed) - self.failUnless(self.index_exists('Workflow', 'name')) + self.assertTrue(self.schema['name'].rdef('Workflow', 'String').indexed) + self.assertTrue(self.index_exists('Workflow', 'name')) self.commit() - self.failIf(self.schema['name'].rdef('Workflow', 'String').indexed) - self.failIf(self.index_exists('Workflow', 'name')) + self.assertFalse(self.schema['name'].rdef('Workflow', 'String').indexed) + self.assertFalse(self.index_exists('Workflow', 'name')) finally: self.execute('SET X indexed TRUE WHERE X relation_type R, R name "name"') - self.failIf(self.schema['name'].rdef('Workflow', 'String').indexed) - self.failIf(self.index_exists('Workflow', 'name')) + self.assertFalse(self.schema['name'].rdef('Workflow', 'String').indexed) + self.assertFalse(self.index_exists('Workflow', 'name')) self.commit() - self.failUnless(self.schema['name'].rdef('Workflow', 'String').indexed) - self.failUnless(self.index_exists('Workflow', 'name')) + self.assertTrue(self.schema['name'].rdef('Workflow', 'String').indexed) + self.assertTrue(self.index_exists('Workflow', 'name')) def test_unique_change(self): self.session.set_cnxset() @@ -221,20 +221,20 @@ self.execute('INSERT CWConstraint X: X cstrtype CT, DEF constrained_by X ' 'WHERE CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,' 'RT name "name", E name "Workflow"') - self.failIf(self.schema['Workflow'].has_unique_values('name')) - self.failIf(self.index_exists('Workflow', 'name', unique=True)) + self.assertFalse(self.schema['Workflow'].has_unique_values('name')) + self.assertFalse(self.index_exists('Workflow', 'name', unique=True)) self.commit() - self.failUnless(self.schema['Workflow'].has_unique_values('name')) - self.failUnless(self.index_exists('Workflow', 'name', unique=True)) + self.assertTrue(self.schema['Workflow'].has_unique_values('name')) + self.assertTrue(self.index_exists('Workflow', 'name', unique=True)) finally: self.execute('DELETE DEF constrained_by X WHERE X cstrtype CT, ' 'CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,' 'RT name "name", E name "Workflow"') - self.failUnless(self.schema['Workflow'].has_unique_values('name')) - self.failUnless(self.index_exists('Workflow', 'name', unique=True)) + self.assertTrue(self.schema['Workflow'].has_unique_values('name')) + self.assertTrue(self.index_exists('Workflow', 'name', unique=True)) self.commit() - self.failIf(self.schema['Workflow'].has_unique_values('name')) - self.failIf(self.index_exists('Workflow', 'name', unique=True)) + self.assertFalse(self.schema['Workflow'].has_unique_values('name')) + self.assertFalse(self.index_exists('Workflow', 'name', unique=True)) def test_required_change_1(self): self.execute('SET DEF cardinality "?1" ' @@ -267,8 +267,8 @@ {'x': attreid}) self.commit() self.schema.rebuild_infered_relations() - self.failUnless('Transition' in self.schema['messageid'].subjects()) - self.failUnless('WorkflowTransition' in self.schema['messageid'].subjects()) + self.assertTrue('Transition' in self.schema['messageid'].subjects()) + self.assertTrue('WorkflowTransition' in self.schema['messageid'].subjects()) self.execute('Any X WHERE X is_instance_of BaseTransition, X messageid "hop"') def test_change_fulltextindexed(self): @@ -283,7 +283,7 @@ 'A from_entity E, A relation_type R, R name "subject"') self.commit() rset = req.execute('Any X WHERE X has_text "rick.roll"') - self.failIf(rset) + self.assertFalse(rset) assert req.execute('SET A fulltextindexed TRUE ' 'WHERE A from_entity E, A relation_type R, ' 'E name "Email", R name "subject"') diff -r 7b2c7f3d3703 -r 29cdde6bb9ef i18n.py --- a/i18n.py Thu Dec 08 14:29:48 2011 +0100 +++ b/i18n.py Thu Dec 08 14:32:57 2011 +0100 @@ -33,7 +33,7 @@ output = open(output_file, 'w') for filepath in files: for match in re.finditer('i18n:(content|replace)="([^"]+)"', open(filepath).read()): - print >> output, '_("%s")' % match.group(2) + output.write('_("%s")' % match.group(2)) output.close() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef i18n/de.po --- a/i18n/de.po Thu Dec 08 14:29:48 2011 +0100 +++ b/i18n/de.po Thu Dec 08 14:32:57 2011 +0100 @@ -106,34 +106,6 @@ msgstr "%d Jahre" #, python-format -msgid "%d days" -msgstr "%d Tage" - -#, python-format -msgid "%d hours" -msgstr "%d Stunden" - -#, python-format -msgid "%d minutes" -msgstr "%d Minuten" - -#, python-format -msgid "%d months" -msgstr "%d Monate" - -#, python-format -msgid "%d seconds" -msgstr "%d Sekunden" - -#, python-format -msgid "%d weeks" -msgstr "%d Wochen" - -#, python-format -msgid "%d years" -msgstr "%d Jahre" - -#, python-format msgid "%s could be supported" msgstr "" @@ -173,9 +145,6 @@ msgid "(UNEXISTANT EID)" msgstr "(EID nicht gefunden)" -msgid "(loading ...)" -msgstr "(laden...)" - msgid "**" msgstr "0..n 0..n" @@ -324,6 +293,12 @@ msgid "CWConstraint_plural" msgstr "Einschränkungen" +msgid "CWDataImport" +msgstr "" + +msgid "CWDataImport_plural" +msgstr "" + msgid "CWEType" msgstr "Entitätstyp" @@ -344,12 +319,6 @@ msgid "CWGroup_plural" msgstr "Gruppen" -msgid "CWPermission" -msgstr "Berechtigung" - -msgid "CWPermission_plural" -msgstr "Berechtigungen" - msgid "CWProperty" msgstr "Eigenschaft" @@ -451,6 +420,12 @@ "Kann die Erstelllung der Entität %(eid)s vom Typ %(etype)s nicht rückgängig " "machen, dieser Typ existiert nicht mehr." +msgid "Click to sort on this column" +msgstr "" + +msgid "DEBUG" +msgstr "" + #, python-format msgid "Data connection graph for %s" msgstr "Graf der Datenverbindungen für %s" @@ -482,6 +457,9 @@ msgid "Download schema as OWL" msgstr "Herunterladen des Schemas im OWL-Format" +msgid "ERROR" +msgstr "" + msgid "EmailAddress" msgstr "Email-Adresse" @@ -504,6 +482,9 @@ msgid "ExternalUri_plural" msgstr "Externe Uris" +msgid "FATAL" +msgstr "" + msgid "Float" msgstr "Gleitkommazahl" @@ -528,6 +509,9 @@ msgid "Help" msgstr "Hilfe" +msgid "INFO" +msgstr "" + msgid "Instance" msgstr "Instanz" @@ -546,12 +530,21 @@ msgid "Interval_plural" msgstr "Intervalle" +msgid "Link:" +msgstr "" + msgid "Looked up classes" msgstr "gesuchte Klassen" msgid "Manage" msgstr "" +msgid "Manage security" +msgstr "Sicherheitsverwaltung" + +msgid "Message threshold" +msgstr "" + msgid "Most referenced classes" msgstr "meist-referenzierte Klassen" @@ -573,15 +566,15 @@ msgid "New CWConstraintType" msgstr "Neuer Einschränkungstyp" +msgid "New CWDataImport" +msgstr "" + msgid "New CWEType" msgstr "Neuer Entitätstyp" msgid "New CWGroup" msgstr "Neue Gruppe" -msgid "New CWPermission" -msgstr "Neue Berechtigung" - msgid "New CWProperty" msgstr "Neue Eigenschaft" @@ -648,6 +641,9 @@ msgid "OR" msgstr "oder" +msgid "Ownership" +msgstr "Eigentum" + msgid "Parent class:" msgstr "Elternklasse" @@ -697,8 +693,8 @@ msgid "Schema %s" msgstr "Schema %s" -msgid "Schema of the data model" -msgstr "Schema des Datenmodells" +msgid "Schema's permissions definitions" +msgstr "Im Schema definierte Rechte" msgid "Search for" msgstr "Suchen" @@ -792,15 +788,15 @@ msgid "This CWConstraintType" msgstr "Dieser Einschränkungstyp" +msgid "This CWDataImport" +msgstr "" + msgid "This CWEType" msgstr "Dieser Entitätstyp" msgid "This CWGroup" msgstr "Diese Gruppe" -msgid "This CWPermission" -msgstr "Diese Berechtigung" - msgid "This CWProperty" msgstr "Diese Eigenschaft" @@ -885,6 +881,12 @@ msgid "Used by:" msgstr "benutzt von:" +msgid "Users and groups management" +msgstr "" + +msgid "WARNING" +msgstr "" + msgid "Web server" msgstr "Web-Server" @@ -948,7 +950,7 @@ msgid "" "a RQL expression which should return some results, else the transition won't " "be available. This query may use X and U variables that will respectivly " -"represents the current entity and the current user" +"represents the current entity and the current user." msgstr "" "ein RQL-Ausdruck, der einige Treffer liefern sollte, sonst wird der Ãœbergang " "nicht verfügbar sein. Diese Abfrage kann X und U Variable benutzen, die " @@ -970,6 +972,9 @@ msgid "abstract base class for transitions" msgstr "abstrakte Basisklasse für Ãœbergänge" +msgid "action menu" +msgstr "" + msgid "action(s) on this selection" msgstr "Aktionen(en) bei dieser Auswahl" @@ -1091,9 +1096,6 @@ msgid "add a EmailAddress" msgstr "Email-Adresse hinzufügen" -msgid "add a new permission" -msgstr "eine Berechtigung hinzufügen" - # subject and object forms for each relation type # (no object form for final relation types) msgid "add_permission" @@ -1253,6 +1255,10 @@ msgid "automatic" msgstr "automatisch" +#, python-format +msgid "back to pagination (%s results)" +msgstr "" + msgid "bad value" msgstr "Unzulässiger Wert" @@ -1756,12 +1762,12 @@ msgid "cstrtype_object" msgstr "Einschränkungstyp von" -msgid "csv entities export" -msgstr "CSV-Export von Entitäten" - msgid "csv export" msgstr "CSV-Export" +msgid "csv export (entities)" +msgstr "" + msgid "ctxcomponents" msgstr "Kontext-Komponenten" @@ -1873,6 +1879,12 @@ msgid "custom_workflow_object" msgstr "angepasster Workflow von" +msgid "cw.groups-management" +msgstr "" + +msgid "cw.users-management" +msgstr "" + msgid "cw_for_source" msgstr "" @@ -1901,6 +1913,20 @@ msgid "cw_host_config_of_object" msgstr "" +msgid "cw_import_of" +msgstr "" + +msgctxt "CWDataImport" +msgid "cw_import_of" +msgstr "" + +msgid "cw_import_of_object" +msgstr "" + +msgctxt "CWSource" +msgid "cw_import_of_object" +msgstr "" + msgid "cw_schema" msgstr "" @@ -1956,6 +1982,9 @@ msgid "cwrtype-permissions" msgstr "Berechtigungen" +msgid "cwsource-imports" +msgstr "" + msgid "cwsource-main" msgstr "" @@ -2080,9 +2109,6 @@ msgid "delete this bookmark" msgstr "dieses Lesezeichen löschen" -msgid "delete this permission" -msgstr "dieses Recht löschen" - msgid "delete this relation" msgstr "diese Relation löschen" @@ -2256,13 +2282,6 @@ msgid "display the facet or not" msgstr "die Facette anzeigen oder nicht" -msgid "" -"distinct label to distinguate between other permission entity of the same " -"name" -msgstr "" -"Zusätzliches Label, um von anderen Berechtigungsentitäten unterscheiden zu " -"können." - msgid "download" msgstr "Herunterladen" @@ -2300,6 +2319,13 @@ msgid "embedding this url is forbidden" msgstr "Einbettung dieses URLs ist nicht erlaubt." +msgid "end_timestamp" +msgstr "" + +msgctxt "CWDataImport" +msgid "end_timestamp" +msgstr "" + msgid "entities deleted" msgstr "Entitäten gelöscht" @@ -2333,12 +2359,6 @@ msgid "entity type" msgstr "Entitätstyp" -msgid "" -"entity type that may be used to construct some advanced security " -"configuration" -msgstr "" -"Entitätstyp zum Aufbau einer fortgeschrittenen Sicherheitskonfiguration." - msgid "entity types which may use this workflow" msgstr "Entitätstypen, die diesen Workflow benutzen können." @@ -2426,6 +2446,12 @@ msgid "facets_cwfinal-facet_description" msgstr "" +msgid "facets_datafeed.dataimport.status" +msgstr "" + +msgid "facets_datafeed.dataimport.status_description" +msgstr "" + msgid "facets_etype-facet" msgstr "\"Entitätstyp\" facet" @@ -2450,6 +2476,9 @@ msgid "facets_in_state-facet_description" msgstr "" +msgid "failed" +msgstr "" + #, python-format msgid "failed to uniquify path (%s, %s)" msgstr "Konnte keinen eindeutigen Dateinamen erzeugen (%s, %s)" @@ -2491,9 +2520,6 @@ msgid "follow this link for more information on this %s" msgstr "Folgend Sie dem Link für mehr Informationen über %s" -msgid "follow this link if javascript is deactivated" -msgstr "Folgen Sie diesem Link, falls Javascript deaktiviert ist." - msgid "for_user" msgstr "für den Nutzer" @@ -2631,9 +2657,6 @@ msgid "groups grant permissions to the user" msgstr "die Gruppen geben dem Nutzer Rechte" -msgid "groups to which the permission is granted" -msgstr "Gruppen, denen dieses Recht verliehen ist" - msgid "guests" msgstr "Gäste" @@ -2653,24 +2676,34 @@ msgstr "Filter verbergen" msgid "" -"how to format date and time in the ui (\"man strftime\" for format " +"how to format date and time in the ui (see this page for format " "description)" msgstr "" -"Wie formatiert man das Datum Interface im (\"man strftime\" für die " -"Beschreibung des neuen Formats" - -msgid "how to format date in the ui (\"man strftime\" for format description)" -msgstr "" -"Wie formatiert man das Datum im Interface (\"man strftime\" für die " -"Beschreibung des Formats)" +"Wie formatiert man das Datum im Interface (Beschreibung des Formats)" + +msgid "" +"how to format date in the ui (see this page for format " +"description)" +msgstr "" +"Wie formatiert man das Datum im Interface (Beschreibung des Formats)" msgid "how to format float numbers in the ui" msgstr "Wie man Dezimalzahlen (float) im Interface formatiert" -msgid "how to format time in the ui (\"man strftime\" for format description)" -msgstr "" -"Wie man die Uhrzeit im Interface (\"man strftime\" für die " -"Formatbeschreibung)" +msgid "" +"how to format time in the ui (see this page for format " +"description)" +msgstr "" +"Wie formatiert man die Uhrzeit im Interface (Beschreibung des " +"Formats)" msgid "i18n_bookmark_url_fqs" msgstr "Parameter" @@ -2730,6 +2763,9 @@ msgid "image" msgstr "Bild" +msgid "in progress" +msgstr "" + msgid "in_group" msgstr "in der Gruppe" @@ -2828,9 +2864,6 @@ msgid "instance home" msgstr "Startseite der Instanz" -msgid "instance schema" -msgstr "Schema der Instanz" - msgid "internal entity uri" msgstr "interner URI" @@ -2891,19 +2924,18 @@ msgid "january" msgstr "Januar" +msgid "json-entities-export-view" +msgstr "" + +msgid "json-export-view" +msgstr "" + msgid "july" msgstr "Juli" msgid "june" msgstr "Juni" -msgid "label" -msgstr "gekennzeichnet" - -msgctxt "CWPermission" -msgid "label" -msgstr "gekennzeichnet" - msgid "language of the user interface" msgstr "Sprache der Nutzer-Schnittstelle" @@ -2926,6 +2958,9 @@ msgid "last_login_time" msgstr "Datum der letzten Verbindung" +msgid "latest import" +msgstr "" + msgid "latest modification time of an entity" msgstr "Datum der letzten Änderung einer Entität" @@ -2945,13 +2980,8 @@ msgid "left" msgstr "links" -msgid "" -"link a permission to the entity. This permission should be used in the " -"security definition of the entity's type to be useful." -msgstr "" -"verknüpft eine Berechtigung mit einer Entität. Um Nützlich zu sein, sollte " -"diese Berechtigung in der Sicherheitsdefinition des Entitätstyps benutzt " -"werden." +msgid "line" +msgstr "" msgid "" "link a property to the user which want this property customization. Unless " @@ -2985,6 +3015,13 @@ msgid "list" msgstr "Liste" +msgid "log" +msgstr "" + +msgctxt "CWDataImport" +msgid "log" +msgstr "" + msgid "log in" msgstr "anmelden" @@ -3037,9 +3074,6 @@ msgid "manage permissions" msgstr "Rechte verwalten" -msgid "manage security" -msgstr "Sicherheitsverwaltung" - msgid "managers" msgstr "Administratoren" @@ -3074,6 +3108,9 @@ msgid "memory leak debugging" msgstr "Fehlersuche bei Speicherlöschern" +msgid "message" +msgstr "" + msgid "milestone" msgstr "Meilenstein" @@ -3131,10 +3168,6 @@ msgid "name" msgstr "Name" -msgctxt "CWPermission" -msgid "name" -msgstr "Name" - msgctxt "CWRType" msgid "name" msgstr "Name" @@ -3172,9 +3205,6 @@ msgid "name of the source" msgstr "" -msgid "name or identifier of the permission" -msgstr "Name (oder Bezeichner) der Berechtigung" - msgid "navbottom" msgstr "zum Seitenende" @@ -3211,9 +3241,6 @@ msgid "no" msgstr "Nein" -msgid "no associated permissions" -msgstr "keine entsprechende Berechtigung" - msgid "no content next link" msgstr "" @@ -3227,6 +3254,9 @@ msgid "no edited fields specified for entity %s" msgstr "kein Eingabefeld spezifiziert Für Entität %s" +msgid "no log to display" +msgstr "" + msgid "no related entity" msgstr "keine verknüpfte Entität" @@ -3261,6 +3291,9 @@ msgid "november" msgstr "November" +msgid "num. users" +msgstr "" + msgid "object" msgstr "Objekt" @@ -3327,9 +3360,6 @@ msgid "owners" msgstr "Besitzer" -msgid "ownership" -msgstr "Eigentum" - msgid "ownerships have been changed" msgstr "Die Eigentumsrechte sind geändert worden." @@ -3361,15 +3391,15 @@ msgid "path" msgstr "Pfad" +msgid "permalink to this message" +msgstr "" + msgid "permission" msgstr "Recht" msgid "permissions" msgstr "Rechte" -msgid "permissions for this entity" -msgstr "Rechte für diese Entität" - msgid "pick existing bookmarks" msgstr "Wählen Sie aus den bestehenden lesezeichen aus" @@ -3444,7 +3474,7 @@ msgid "rdef-permissions" msgstr "Rechte" -msgid "rdf" +msgid "rdf export" msgstr "" msgid "read" @@ -3567,10 +3597,6 @@ msgid "require_group" msgstr "auf Gruppe beschränkt" -msgctxt "CWPermission" -msgid "require_group" -msgstr "auf Gruppe beschränkt" - msgctxt "Transition" msgid "require_group" msgstr "auf Gruppe beschränkt" @@ -3586,12 +3612,6 @@ msgid "require_group_object" msgstr "hat die Rechte" -msgid "require_permission" -msgstr "erfordert Berechtigung" - -msgid "require_permission_object" -msgstr "Berechtigung von" - msgid "required" msgstr "erforderlich" @@ -3635,8 +3655,8 @@ msgid "rql expressions" msgstr "RQL-Ausdrücke" -msgid "rss" -msgstr "RSS" +msgid "rss export" +msgstr "" msgid "same_as" msgstr "identisch mit" @@ -3647,9 +3667,6 @@ msgid "saturday" msgstr "Samstag" -msgid "schema's permissions definitions" -msgstr "Im Schema definierte Rechte" - msgid "schema-diagram" msgstr "Diagramm" @@ -3728,6 +3745,9 @@ msgid "server information" msgstr "Server-Informationen" +msgid "severity" +msgstr "" + msgid "" "should html fields being edited using fckeditor (a HTML WYSIWYG editor). " "You should also select text/html as default text format to actually get " @@ -3764,9 +3784,6 @@ "Eine Eigenschaft für die gesamte Website kann nicht für einen Nutzer gesetzt " "werden." -msgid "siteinfo" -msgstr "" - msgid "some later transaction(s) touch entity, undo them first" msgstr "" "Eine oder mehrere frühere Transaktion(en) betreffen die Tntität. Machen Sie " @@ -3809,6 +3826,13 @@ "synchronization in progress." msgstr "" +msgid "start_timestamp" +msgstr "" + +msgctxt "CWDataImport" +msgid "start_timestamp" +msgstr "" + msgid "startup views" msgstr "Start-Ansichten" @@ -3854,6 +3878,13 @@ msgid "state_of_object" msgstr "enthält die Zustände" +msgid "status" +msgstr "" + +msgctxt "CWDataImport" +msgid "status" +msgstr "" + msgid "status change" msgstr "Zustand ändern" @@ -3924,6 +3955,9 @@ msgid "subworkflow_state_object" msgstr "Endzustand von" +msgid "success" +msgstr "" + msgid "sunday" msgstr "Sonntag" @@ -4281,9 +4315,6 @@ msgid "url" msgstr "" -msgid "use template languages" -msgstr "Verwenden Sie Templating-Sprachen" - msgid "" "use to define a transition from one or multiple states to a destination " "states in workflow's definitions. Transition without destination state will " @@ -4307,9 +4338,6 @@ msgid "use_email_object" msgstr "verwendet von" -msgid "use_template_format" -msgstr "Benutzung des 'cubicweb template'-Formats" - msgid "" "used for cubicweb configuration. Once a property has been created you can't " "change the key." @@ -4323,9 +4351,6 @@ "assoziiert einfache Zustände mit einem Entitätstyp und/oder definiert " "Workflows" -msgid "used to grant a permission to a group" -msgstr "gibt einer Gruppe eine Berechtigung" - msgid "user" msgstr "Nutzer" @@ -4352,9 +4377,6 @@ msgid "users and groups" msgstr "" -msgid "users and groups management" -msgstr "" - msgid "users using this bookmark" msgstr "Nutzer, die dieses Lesezeichen verwenden" @@ -4519,15 +4541,15 @@ msgid "wrong query parameter line %s" msgstr "Falscher Anfrage-Parameter Zeile %s" -msgid "xbel" -msgstr "XBEL" - -msgid "xml" -msgstr "XML" +msgid "xbel export" +msgstr "" msgid "xml export" msgstr "XML-Export" +msgid "xml export (entities)" +msgstr "" + msgid "yes" msgstr "Ja" @@ -4544,3 +4566,46 @@ #, python-format msgid "you should un-inline relation %s which is supported and may be crossed " msgstr "" + +#~ msgid "(loading ...)" +#~ msgstr "(laden...)" + +#~ msgid "Schema of the data model" +#~ msgstr "Schema des Datenmodells" + +#~ msgid "csv entities export" +#~ msgstr "CSV-Export von Entitäten" + +#~ msgid "follow this link if javascript is deactivated" +#~ msgstr "Folgen Sie diesem Link, falls Javascript deaktiviert ist." + +#~ msgid "" +#~ "how to format date and time in the ui (\"man strftime\" for format " +#~ "description)" +#~ msgstr "" +#~ "Wie formatiert man das Datum Interface im (\"man strftime\" für die " +#~ "Beschreibung des neuen Formats" + +#~ msgid "" +#~ "how to format date in the ui (\"man strftime\" for format description)" +#~ msgstr "" +#~ "Wie formatiert man das Datum im Interface (\"man strftime\" für die " +#~ "Beschreibung des Formats)" + +#~ msgid "" +#~ "how to format time in the ui (\"man strftime\" for format description)" +#~ msgstr "" +#~ "Wie man die Uhrzeit im Interface (\"man strftime\" für die " +#~ "Formatbeschreibung)" + +#~ msgid "instance schema" +#~ msgstr "Schema der Instanz" + +#~ msgid "rss" +#~ msgstr "RSS" + +#~ msgid "xbel" +#~ msgstr "XBEL" + +#~ msgid "xml" +#~ msgstr "XML" diff -r 7b2c7f3d3703 -r 29cdde6bb9ef i18n/en.po --- a/i18n/en.po Thu Dec 08 14:29:48 2011 +0100 +++ b/i18n/en.po Thu Dec 08 14:32:57 2011 +0100 @@ -98,34 +98,6 @@ msgstr "" #, python-format -msgid "%d days" -msgstr "" - -#, python-format -msgid "%d hours" -msgstr "" - -#, python-format -msgid "%d minutes" -msgstr "" - -#, python-format -msgid "%d months" -msgstr "" - -#, python-format -msgid "%d seconds" -msgstr "" - -#, python-format -msgid "%d weeks" -msgstr "" - -#, python-format -msgid "%d years" -msgstr "" - -#, python-format msgid "%s could be supported" msgstr "" @@ -165,9 +137,6 @@ msgid "(UNEXISTANT EID)" msgstr "" -msgid "(loading ...)" -msgstr "" - msgid "**" msgstr "0..n 0..n" @@ -313,6 +282,12 @@ msgid "CWConstraint_plural" msgstr "Constraints" +msgid "CWDataImport" +msgstr "Data import" + +msgid "CWDataImport_plural" +msgstr "Data imports" + msgid "CWEType" msgstr "Entity type" @@ -333,12 +308,6 @@ msgid "CWGroup_plural" msgstr "Groups" -msgid "CWPermission" -msgstr "Permission" - -msgid "CWPermission_plural" -msgstr "Permissions" - msgid "CWProperty" msgstr "Property" @@ -427,6 +396,12 @@ "supported" msgstr "" +msgid "Click to sort on this column" +msgstr "" + +msgid "DEBUG" +msgstr "" + #, python-format msgid "Data connection graph for %s" msgstr "" @@ -458,6 +433,9 @@ msgid "Download schema as OWL" msgstr "" +msgid "ERROR" +msgstr "" + msgid "EmailAddress" msgstr "Email address" @@ -480,6 +458,9 @@ msgid "ExternalUri_plural" msgstr "External Uris" +msgid "FATAL" +msgstr "" + msgid "Float" msgstr "Float" @@ -504,6 +485,9 @@ msgid "Help" msgstr "" +msgid "INFO" +msgstr "" + msgid "Instance" msgstr "" @@ -522,12 +506,21 @@ msgid "Interval_plural" msgstr "Intervals" +msgid "Link:" +msgstr "" + msgid "Looked up classes" msgstr "" msgid "Manage" msgstr "" +msgid "Manage security" +msgstr "" + +msgid "Message threshold" +msgstr "" + msgid "Most referenced classes" msgstr "" @@ -549,15 +542,15 @@ msgid "New CWConstraintType" msgstr "New constraint type" +msgid "New CWDataImport" +msgstr "New data import" + msgid "New CWEType" msgstr "New entity type" msgid "New CWGroup" msgstr "New group" -msgid "New CWPermission" -msgstr "New permission" - msgid "New CWProperty" msgstr "New property" @@ -622,6 +615,9 @@ msgid "OR" msgstr "" +msgid "Ownership" +msgstr "" + msgid "Parent class:" msgstr "" @@ -671,7 +667,7 @@ msgid "Schema %s" msgstr "" -msgid "Schema of the data model" +msgid "Schema's permissions definitions" msgstr "" msgid "Search for" @@ -766,15 +762,15 @@ msgid "This CWConstraintType" msgstr "This constraint type" +msgid "This CWDataImport" +msgstr "This data import" + msgid "This CWEType" msgstr "This entity type" msgid "This CWGroup" msgstr "This group" -msgid "This CWPermission" -msgstr "This permission" - msgid "This CWProperty" msgstr "This property" @@ -859,6 +855,12 @@ msgid "Used by:" msgstr "" +msgid "Users and groups management" +msgstr "" + +msgid "WARNING" +msgstr "" + msgid "Web server" msgstr "" @@ -911,7 +913,7 @@ msgid "" "a RQL expression which should return some results, else the transition won't " "be available. This query may use X and U variables that will respectivly " -"represents the current entity and the current user" +"represents the current entity and the current user." msgstr "" msgid "a URI representing an object in external data store" @@ -930,6 +932,9 @@ msgid "abstract base class for transitions" msgstr "" +msgid "action menu" +msgstr "" + msgid "action(s) on this selection" msgstr "" @@ -1051,9 +1056,6 @@ msgid "add a EmailAddress" msgstr "add an email address" -msgid "add a new permission" -msgstr "" - # subject and object forms for each relation type # (no object form for final relation types) msgid "add_permission" @@ -1208,6 +1210,10 @@ msgid "automatic" msgstr "" +#, python-format +msgid "back to pagination (%s results)" +msgstr "" + msgid "bad value" msgstr "" @@ -1709,11 +1715,11 @@ msgid "cstrtype_object" msgstr "constraint type of" -msgid "csv entities export" -msgstr "" - msgid "csv export" -msgstr "" +msgstr "CSV export" + +msgid "csv export (entities)" +msgstr "CSV export (entities)" msgid "ctxcomponents" msgstr "contextual components" @@ -1828,6 +1834,12 @@ msgid "custom_workflow_object" msgstr "custom workflow of" +msgid "cw.groups-management" +msgstr "groups" + +msgid "cw.users-management" +msgstr "users" + msgid "cw_for_source" msgstr "for source" @@ -1856,6 +1868,20 @@ msgid "cw_host_config_of_object" msgstr "host configuration" +msgid "cw_import_of" +msgstr "source" + +msgctxt "CWDataImport" +msgid "cw_import_of" +msgstr "source" + +msgid "cw_import_of_object" +msgstr "imports" + +msgctxt "CWSource" +msgid "cw_import_of_object" +msgstr "imports" + msgid "cw_schema" msgstr "maps" @@ -1911,6 +1937,9 @@ msgid "cwrtype-permissions" msgstr "permissions" +msgid "cwsource-imports" +msgstr "" + msgid "cwsource-main" msgstr "description" @@ -2031,9 +2060,6 @@ msgid "delete this bookmark" msgstr "" -msgid "delete this permission" -msgstr "" - msgid "delete this relation" msgstr "" @@ -2203,11 +2229,6 @@ msgid "display the facet or not" msgstr "" -msgid "" -"distinct label to distinguate between other permission entity of the same " -"name" -msgstr "" - msgid "download" msgstr "" @@ -2245,6 +2266,13 @@ msgid "embedding this url is forbidden" msgstr "" +msgid "end_timestamp" +msgstr "end timestamp" + +msgctxt "CWDataImport" +msgid "end_timestamp" +msgstr "end timestamp" + msgid "entities deleted" msgstr "" @@ -2278,11 +2306,6 @@ msgid "entity type" msgstr "" -msgid "" -"entity type that may be used to construct some advanced security " -"configuration" -msgstr "" - msgid "entity types which may use this workflow" msgstr "" @@ -2368,6 +2391,12 @@ msgid "facets_cwfinal-facet_description" msgstr "" +msgid "facets_datafeed.dataimport.status" +msgstr "" + +msgid "facets_datafeed.dataimport.status_description" +msgstr "" + msgid "facets_etype-facet" msgstr "\"entity type\" facet" @@ -2392,6 +2421,9 @@ msgid "facets_in_state-facet_description" msgstr "" +msgid "failed" +msgstr "" + #, python-format msgid "failed to uniquify path (%s, %s)" msgstr "" @@ -2433,9 +2465,6 @@ msgid "follow this link for more information on this %s" msgstr "" -msgid "follow this link if javascript is deactivated" -msgstr "" - msgid "for_user" msgstr "for user" @@ -2566,9 +2595,6 @@ msgid "groups grant permissions to the user" msgstr "" -msgid "groups to which the permission is granted" -msgstr "" - msgid "guests" msgstr "" @@ -2588,17 +2614,24 @@ msgstr "" msgid "" -"how to format date and time in the ui (\"man strftime\" for format " +"how to format date and time in the ui (see this page for format " "description)" msgstr "" -msgid "how to format date in the ui (\"man strftime\" for format description)" +msgid "" +"how to format date in the ui (see this page for format " +"description)" msgstr "" msgid "how to format float numbers in the ui" msgstr "" -msgid "how to format time in the ui (\"man strftime\" for format description)" +msgid "" +"how to format time in the ui (see this page for format " +"description)" msgstr "" msgid "i18n_bookmark_url_fqs" @@ -2657,6 +2690,9 @@ msgid "image" msgstr "" +msgid "in progress" +msgstr "" + msgid "in_group" msgstr "in group" @@ -2753,9 +2789,6 @@ msgid "instance home" msgstr "" -msgid "instance schema" -msgstr "" - msgid "internal entity uri" msgstr "" @@ -2811,19 +2844,18 @@ msgid "january" msgstr "" +msgid "json-entities-export-view" +msgstr "JSON export (entities)" + +msgid "json-export-view" +msgstr "JSON export" + msgid "july" msgstr "" msgid "june" msgstr "" -msgid "label" -msgstr "" - -msgctxt "CWPermission" -msgid "label" -msgstr "label" - msgid "language of the user interface" msgstr "" @@ -2846,6 +2878,9 @@ msgid "last_login_time" msgstr "last login time" +msgid "latest import" +msgstr "" + msgid "latest modification time of an entity" msgstr "" @@ -2865,9 +2900,7 @@ msgid "left" msgstr "" -msgid "" -"link a permission to the entity. This permission should be used in the " -"security definition of the entity's type to be useful." +msgid "line" msgstr "" msgid "" @@ -2899,6 +2932,13 @@ msgid "list" msgstr "" +msgid "log" +msgstr "" + +msgctxt "CWDataImport" +msgid "log" +msgstr "" + msgid "log in" msgstr "" @@ -2950,9 +2990,6 @@ msgid "manage permissions" msgstr "" -msgid "manage security" -msgstr "" - msgid "managers" msgstr "" @@ -2987,6 +3024,9 @@ msgid "memory leak debugging" msgstr "" +msgid "message" +msgstr "" + msgid "milestone" msgstr "" @@ -3044,10 +3084,6 @@ msgid "name" msgstr "name" -msgctxt "CWPermission" -msgid "name" -msgstr "name" - msgctxt "CWRType" msgid "name" msgstr "name" @@ -3083,9 +3119,6 @@ msgid "name of the source" msgstr "" -msgid "name or identifier of the permission" -msgstr "" - msgid "navbottom" msgstr "page bottom" @@ -3122,9 +3155,6 @@ msgid "no" msgstr "" -msgid "no associated permissions" -msgstr "" - msgid "no content next link" msgstr "" @@ -3138,6 +3168,9 @@ msgid "no edited fields specified for entity %s" msgstr "" +msgid "no log to display" +msgstr "" + msgid "no related entity" msgstr "" @@ -3172,6 +3205,9 @@ msgid "november" msgstr "" +msgid "num. users" +msgstr "" + msgid "object" msgstr "" @@ -3238,9 +3274,6 @@ msgid "owners" msgstr "" -msgid "ownership" -msgstr "" - msgid "ownerships have been changed" msgstr "" @@ -3271,15 +3304,15 @@ msgid "path" msgstr "path" +msgid "permalink to this message" +msgstr "" + msgid "permission" msgstr "" msgid "permissions" msgstr "" -msgid "permissions for this entity" -msgstr "" - msgid "pick existing bookmarks" msgstr "" @@ -3354,8 +3387,8 @@ msgid "rdef-permissions" msgstr "permissions" -msgid "rdf" -msgstr "" +msgid "rdf export" +msgstr "RDF export" msgid "read" msgstr "" @@ -3477,10 +3510,6 @@ msgid "require_group" msgstr "require group" -msgctxt "CWPermission" -msgid "require_group" -msgstr "require group" - msgctxt "Transition" msgid "require_group" msgstr "require group" @@ -3496,12 +3525,6 @@ msgid "require_group_object" msgstr "required by" -msgid "require_permission" -msgstr "require permission" - -msgid "require_permission_object" -msgstr "required by" - msgid "required" msgstr "" @@ -3542,8 +3565,8 @@ msgid "rql expressions" msgstr "" -msgid "rss" -msgstr "" +msgid "rss export" +msgstr "RSS export" msgid "same_as" msgstr "same as" @@ -3554,9 +3577,6 @@ msgid "saturday" msgstr "" -msgid "schema's permissions definitions" -msgstr "" - msgid "schema-diagram" msgstr "diagram" @@ -3635,6 +3655,9 @@ msgid "server information" msgstr "" +msgid "severity" +msgstr "" + msgid "" "should html fields being edited using fckeditor (a HTML WYSIWYG editor). " "You should also select text/html as default text format to actually get " @@ -3666,9 +3689,6 @@ msgid "site-wide property can't be set for user" msgstr "" -msgid "siteinfo" -msgstr "site information" - msgid "some later transaction(s) touch entity, undo them first" msgstr "" @@ -3709,6 +3729,13 @@ "synchronization in progress." msgstr "" +msgid "start_timestamp" +msgstr "start timestamp" + +msgctxt "CWDataImport" +msgid "start_timestamp" +msgstr "start timestamp" + msgid "startup views" msgstr "" @@ -3752,6 +3779,13 @@ msgid "state_of_object" msgstr "use states" +msgid "status" +msgstr "" + +msgctxt "CWDataImport" +msgid "status" +msgstr "status" + msgid "status change" msgstr "" @@ -3820,6 +3854,9 @@ msgid "subworkflow_state_object" msgstr "exit point" +msgid "success" +msgstr "" + msgid "sunday" msgstr "" @@ -4176,9 +4213,6 @@ msgid "url" msgstr "url" -msgid "use template languages" -msgstr "" - msgid "" "use to define a transition from one or multiple states to a destination " "states in workflow's definitions. Transition without destination state will " @@ -4199,9 +4233,6 @@ msgid "use_email_object" msgstr "used by" -msgid "use_template_format" -msgstr "use template format" - msgid "" "used for cubicweb configuration. Once a property has been created you can't " "change the key." @@ -4211,9 +4242,6 @@ "used to associate simple states to an entity type and/or to define workflows" msgstr "" -msgid "used to grant a permission to a group" -msgstr "" - msgid "user" msgstr "" @@ -4238,9 +4266,6 @@ msgid "users and groups" msgstr "" -msgid "users and groups management" -msgstr "" - msgid "users using this bookmark" msgstr "" @@ -4401,14 +4426,14 @@ msgid "wrong query parameter line %s" msgstr "" -msgid "xbel" -msgstr "" - -msgid "xml" -msgstr "" +msgid "xbel export" +msgstr "XBEL export" msgid "xml export" -msgstr "" +msgstr "XML export" + +msgid "xml export (entities)" +msgstr "XML export (entities)" msgid "yes" msgstr "" diff -r 7b2c7f3d3703 -r 29cdde6bb9ef i18n/es.po --- a/i18n/es.po Thu Dec 08 14:29:48 2011 +0100 +++ b/i18n/es.po Thu Dec 08 14:32:57 2011 +0100 @@ -107,34 +107,6 @@ msgstr "%d años" #, python-format -msgid "%d days" -msgstr "%d días" - -#, python-format -msgid "%d hours" -msgstr "%d horas" - -#, python-format -msgid "%d minutes" -msgstr "%d minutos" - -#, python-format -msgid "%d months" -msgstr "%d meses" - -#, python-format -msgid "%d seconds" -msgstr "%d segundos" - -#, python-format -msgid "%d weeks" -msgstr "%d semanas" - -#, python-format -msgid "%d years" -msgstr "%d años" - -#, python-format msgid "%s could be supported" msgstr "%s podría ser mantenido" @@ -174,9 +146,6 @@ msgid "(UNEXISTANT EID)" msgstr "(EID INEXISTENTE" -msgid "(loading ...)" -msgstr "(Cargando ...)" - msgid "**" msgstr "0..n 0..n" @@ -325,6 +294,12 @@ msgid "CWConstraint_plural" msgstr "Restricciones" +msgid "CWDataImport" +msgstr "" + +msgid "CWDataImport_plural" +msgstr "" + msgid "CWEType" msgstr "Tipo de entidad" @@ -345,12 +320,6 @@ msgid "CWGroup_plural" msgstr "Grupos" -msgid "CWPermission" -msgstr "Autorización" - -msgid "CWPermission_plural" -msgstr "Autorizaciones" - msgid "CWProperty" msgstr "Propiedad" @@ -451,6 +420,12 @@ "No puede anular la creación de la entidad %(eid)s de tipo %(etype)s, este " "tipo ya no existe" +msgid "Click to sort on this column" +msgstr "" + +msgid "DEBUG" +msgstr "" + #, python-format msgid "Data connection graph for %s" msgstr "Gráfica de conexión de datos para %s" @@ -482,6 +457,9 @@ msgid "Download schema as OWL" msgstr "Descargar el esquema en formato OWL" +msgid "ERROR" +msgstr "" + msgid "EmailAddress" msgstr "Correo Electrónico" @@ -504,6 +482,9 @@ msgid "ExternalUri_plural" msgstr "Uris externos" +msgid "FATAL" +msgstr "" + msgid "Float" msgstr "Número flotante" @@ -528,6 +509,9 @@ msgid "Help" msgstr "Ayuda" +msgid "INFO" +msgstr "" + msgid "Instance" msgstr "Instancia" @@ -546,12 +530,21 @@ msgid "Interval_plural" msgstr "Duraciones" +msgid "Link:" +msgstr "" + msgid "Looked up classes" msgstr "Clases buscadas" msgid "Manage" msgstr "Administración" +msgid "Manage security" +msgstr "Gestión de seguridad" + +msgid "Message threshold" +msgstr "" + msgid "Most referenced classes" msgstr "Clases más referenciadas" @@ -573,15 +566,15 @@ msgid "New CWConstraintType" msgstr "Agregar tipo de Restricción" +msgid "New CWDataImport" +msgstr "" + msgid "New CWEType" msgstr "Agregar tipo de entidad" msgid "New CWGroup" msgstr "Nuevo grupo" -msgid "New CWPermission" -msgstr "Agregar autorización" - msgid "New CWProperty" msgstr "Agregar Propiedad" @@ -646,6 +639,9 @@ msgid "OR" msgstr "O" +msgid "Ownership" +msgstr "Propiedad" + msgid "Parent class:" msgstr "Clase padre:" @@ -695,8 +691,8 @@ msgid "Schema %s" msgstr "Esquema %s" -msgid "Schema of the data model" -msgstr "Esquema del modelo de datos" +msgid "Schema's permissions definitions" +msgstr "Definiciones de permisos del esquema" msgid "Search for" msgstr "Buscar" @@ -793,15 +789,15 @@ msgid "This CWConstraintType" msgstr "Este tipo de Restricción" +msgid "This CWDataImport" +msgstr "" + msgid "This CWEType" msgstr "Este tipo de Entidad" msgid "This CWGroup" msgstr "Este grupo" -msgid "This CWPermission" -msgstr "Este permiso" - msgid "This CWProperty" msgstr "Esta propiedad" @@ -888,6 +884,12 @@ msgid "Used by:" msgstr "Utilizado por :" +msgid "Users and groups management" +msgstr "Usuarios y grupos de administradores" + +msgid "WARNING" +msgstr "" + msgid "Web server" msgstr "Servidor web" @@ -953,7 +955,7 @@ msgid "" "a RQL expression which should return some results, else the transition won't " "be available. This query may use X and U variables that will respectivly " -"represents the current entity and the current user" +"represents the current entity and the current user." msgstr "" "una expresión RQL que debe haber enviado resultados, para que la transición " "pueda ser realizada. Esta expresión puede utilizar las variables X y U que " @@ -980,6 +982,9 @@ msgid "abstract base class for transitions" msgstr "Clase de base abstracta para la transiciones" +msgid "action menu" +msgstr "" + msgid "action(s) on this selection" msgstr "Acción(es) en esta selección" @@ -1101,9 +1106,6 @@ msgid "add a EmailAddress" msgstr "Agregar correo electrónico" -msgid "add a new permission" -msgstr "Agregar una autorización" - # subject and object forms for each relation type # (no object form for final relation types) msgid "add_permission" @@ -1264,6 +1266,10 @@ msgid "automatic" msgstr "Automático" +#, python-format +msgid "back to pagination (%s results)" +msgstr "" + msgid "bad value" msgstr "Valor erróneo" @@ -1780,12 +1786,12 @@ msgid "cstrtype_object" msgstr "Tipo de restricciones" -msgid "csv entities export" -msgstr "Exportar entidades en csv" - msgid "csv export" msgstr "Exportar en CSV" +msgid "csv export (entities)" +msgstr "" + msgid "ctxcomponents" msgstr "Componentes contextuales" @@ -1902,6 +1908,12 @@ msgid "custom_workflow_object" msgstr "Workflow de" +msgid "cw.groups-management" +msgstr "" + +msgid "cw.users-management" +msgstr "" + msgid "cw_for_source" msgstr "fuente" @@ -1930,6 +1942,20 @@ msgid "cw_host_config_of_object" msgstr "tiene la configuración del host" +msgid "cw_import_of" +msgstr "" + +msgctxt "CWDataImport" +msgid "cw_import_of" +msgstr "" + +msgid "cw_import_of_object" +msgstr "" + +msgctxt "CWSource" +msgid "cw_import_of_object" +msgstr "" + msgid "cw_schema" msgstr "esquema" @@ -1985,6 +2011,9 @@ msgid "cwrtype-permissions" msgstr "Permisos" +msgid "cwsource-imports" +msgstr "" + msgid "cwsource-main" msgstr "descripción" @@ -2116,9 +2145,6 @@ msgid "delete this bookmark" msgstr "Eliminar este favorito" -msgid "delete this permission" -msgstr "Eliminar esta autorización" - msgid "delete this relation" msgstr "Eliminar esta relación" @@ -2295,13 +2321,6 @@ msgid "display the facet or not" msgstr "Mostrar o no la faceta" -msgid "" -"distinct label to distinguate between other permission entity of the same " -"name" -msgstr "" -"Etiqueta que permite distinguir esta autorización de otras que posean el " -"mismo nombre" - msgid "download" msgstr "Descargar" @@ -2339,6 +2358,13 @@ msgid "embedding this url is forbidden" msgstr "La inclusión de este url esta prohibida" +msgid "end_timestamp" +msgstr "" + +msgctxt "CWDataImport" +msgid "end_timestamp" +msgstr "" + msgid "entities deleted" msgstr "Entidades eliminadas" @@ -2374,13 +2400,6 @@ msgid "entity type" msgstr "Tipo de entidad" -msgid "" -"entity type that may be used to construct some advanced security " -"configuration" -msgstr "" -"Tipo de entidad utilizada para definir una configuración de seguridad " -"avanzada" - msgid "entity types which may use this workflow" msgstr "Tipos de entidades que pueden utilizar este Workflow" @@ -2469,6 +2488,12 @@ msgid "facets_cwfinal-facet_description" msgstr "Faceta para las entidades \"finales\"" +msgid "facets_datafeed.dataimport.status" +msgstr "" + +msgid "facets_datafeed.dataimport.status_description" +msgstr "" + msgid "facets_etype-facet" msgstr "Faceta \"es de tipo\"" @@ -2493,6 +2518,9 @@ msgid "facets_in_state-facet_description" msgstr "Faceta en el estado" +msgid "failed" +msgstr "" + #, python-format msgid "failed to uniquify path (%s, %s)" msgstr "No se pudo obtener un dato único (%s, %s)" @@ -2534,9 +2562,6 @@ msgid "follow this link for more information on this %s" msgstr "Seleccione esta liga para obtener mayor información sobre %s" -msgid "follow this link if javascript is deactivated" -msgstr "Seleccione esta liga si javascript esta desactivado" - msgid "for_user" msgstr "Para el usuario" @@ -2673,9 +2698,6 @@ msgid "groups grant permissions to the user" msgstr "Los grupos otorgan los permisos al usuario" -msgid "groups to which the permission is granted" -msgstr "Grupos quienes tienen otorgada esta autorización" - msgid "guests" msgstr "Invitados" @@ -2695,25 +2717,35 @@ msgstr "Esconder el filtro" msgid "" -"how to format date and time in the ui (\"man strftime\" for format " +"how to format date and time in the ui (see this page for format " "description)" msgstr "" -"Formato de fecha y hora que se utilizará por defecto en la interfaz (\"man " -"strftime\" para mayor información del formato)" - -msgid "how to format date in the ui (\"man strftime\" for format description)" +"Formato de fecha y hora que se utilizará por defecto en la interfaz (mayor información del formato)" + +msgid "" +"how to format date in the ui (see this page for format " +"description)" msgstr "" -"Formato de fecha que se utilizará por defecto en la interfaz (\"man strftime" -"\" para mayor información del formato)" +"Formato de fecha que se utilizará por defecto en la interfaz (mayor información del formato)" msgid "how to format float numbers in the ui" msgstr "" "Formato de números flotantes que se utilizará por defecto en la interfaz" -msgid "how to format time in the ui (\"man strftime\" for format description)" +msgid "" +"how to format time in the ui (see this page for format " +"description)" msgstr "" -"Formato de hora que se utilizará por defecto en la interfaz (\"man strftime" -"\" para mayor información del formato)" +"Formato de hora que se utilizará por defecto en la interfaz (mayor información del formato)" msgid "i18n_bookmark_url_fqs" msgstr "Parámetros" @@ -2773,6 +2805,9 @@ msgid "image" msgstr "Imagen" +msgid "in progress" +msgstr "" + msgid "in_group" msgstr "En el grupo" @@ -2872,9 +2907,6 @@ msgid "instance home" msgstr "Repertorio de la Instancia" -msgid "instance schema" -msgstr "Esquema de la Instancia" - msgid "internal entity uri" msgstr "Uri Interna" @@ -2934,19 +2966,18 @@ msgid "january" msgstr "Enero" +msgid "json-entities-export-view" +msgstr "" + +msgid "json-export-view" +msgstr "" + msgid "july" msgstr "Julio" msgid "june" msgstr "Junio" -msgid "label" -msgstr "Etiqueta" - -msgctxt "CWPermission" -msgid "label" -msgstr "Etiqueta" - msgid "language of the user interface" msgstr "Idioma que se utilizará por defecto en la interfaz usuario" @@ -2969,6 +3000,9 @@ msgid "last_login_time" msgstr "Ultima conexión" +msgid "latest import" +msgstr "" + msgid "latest modification time of an entity" msgstr "Fecha de la última modificación de una entidad " @@ -2988,12 +3022,8 @@ msgid "left" msgstr "izquierda" -msgid "" -"link a permission to the entity. This permission should be used in the " -"security definition of the entity's type to be useful." +msgid "line" msgstr "" -"Relacionar un permiso con la entidad. Este permiso debe ser integrado en la " -"definición de seguridad de la entidad para poder ser utilizado." msgid "" "link a property to the user which want this property customization. Unless " @@ -3027,6 +3057,13 @@ msgid "list" msgstr "Lista" +msgid "log" +msgstr "" + +msgctxt "CWDataImport" +msgid "log" +msgstr "" + msgid "log in" msgstr "Acceder" @@ -3078,9 +3115,6 @@ msgid "manage permissions" msgstr "Gestión de permisos" -msgid "manage security" -msgstr "Gestión de seguridad" - msgid "managers" msgstr "Administradores" @@ -3115,6 +3149,9 @@ msgid "memory leak debugging" msgstr "depuración (debugging) de fuga de memoria" +msgid "message" +msgstr "" + msgid "milestone" msgstr "Milestone" @@ -3172,10 +3209,6 @@ msgid "name" msgstr "Nombre" -msgctxt "CWPermission" -msgid "name" -msgstr "Nombre" - msgctxt "CWRType" msgid "name" msgstr "Nombre" @@ -3213,9 +3246,6 @@ msgid "name of the source" msgstr "nombre de la fuente" -msgid "name or identifier of the permission" -msgstr "Nombre o identificador del permiso" - msgid "navbottom" msgstr "Pie de página" @@ -3252,9 +3282,6 @@ msgid "no" msgstr "No" -msgid "no associated permissions" -msgstr "No existe permiso asociado" - msgid "no content next link" msgstr "" @@ -3268,6 +3295,9 @@ msgid "no edited fields specified for entity %s" msgstr "Ningún campo editable especificado para la entidad %s" +msgid "no log to display" +msgstr "" + msgid "no related entity" msgstr "No posee entidad asociada" @@ -3302,6 +3332,9 @@ msgid "november" msgstr "Noviembre" +msgid "num. users" +msgstr "" + msgid "object" msgstr "Objeto" @@ -3368,9 +3401,6 @@ msgid "owners" msgstr "Proprietarios" -msgid "ownership" -msgstr "Propiedad" - msgid "ownerships have been changed" msgstr "Derechos de propiedad modificados" @@ -3402,15 +3432,15 @@ msgid "path" msgstr "Ruta" +msgid "permalink to this message" +msgstr "" + msgid "permission" msgstr "Permiso" msgid "permissions" msgstr "Permisos" -msgid "permissions for this entity" -msgstr "Permisos para esta entidad" - msgid "pick existing bookmarks" msgstr "Seleccionar favoritos existentes" @@ -3485,8 +3515,8 @@ msgid "rdef-permissions" msgstr "Permisos" -msgid "rdf" -msgstr "rdf" +msgid "rdf export" +msgstr "" msgid "read" msgstr "Lectura" @@ -3616,10 +3646,6 @@ msgid "require_group" msgstr "Restringida al Grupo" -msgctxt "CWPermission" -msgid "require_group" -msgstr "Restringida al Grupo" - msgctxt "Transition" msgid "require_group" msgstr "Restringida al Grupo" @@ -3635,12 +3661,6 @@ msgid "require_group_object" msgstr "Posee derechos sobre" -msgid "require_permission" -msgstr "Requiere Permisos" - -msgid "require_permission_object" -msgstr "Requerido por autorización" - msgid "required" msgstr "Requerido" @@ -3685,8 +3705,8 @@ msgid "rql expressions" msgstr "Expresiones RQL" -msgid "rss" -msgstr "RSS" +msgid "rss export" +msgstr "" msgid "same_as" msgstr "Idéntico a" @@ -3697,9 +3717,6 @@ msgid "saturday" msgstr "Sábado" -msgid "schema's permissions definitions" -msgstr "Definiciones de permisos del esquema" - msgid "schema-diagram" msgstr "Gráfica" @@ -3778,6 +3795,9 @@ msgid "server information" msgstr "Información del servidor" +msgid "severity" +msgstr "" + msgid "" "should html fields being edited using fckeditor (a HTML WYSIWYG editor). " "You should also select text/html as default text format to actually get " @@ -3813,9 +3833,6 @@ msgid "site-wide property can't be set for user" msgstr "Una propiedad específica al Sistema no puede ser propia al usuario" -msgid "siteinfo" -msgstr "información" - msgid "some later transaction(s) touch entity, undo them first" msgstr "" "Las transacciones más recientes modificaron esta entidad, anúlelas primero" @@ -3859,6 +3876,13 @@ "synchronization in progress." msgstr "" +msgid "start_timestamp" +msgstr "" + +msgctxt "CWDataImport" +msgid "start_timestamp" +msgstr "" + msgid "startup views" msgstr "Vistas de inicio" @@ -3904,6 +3928,13 @@ msgid "state_of_object" msgstr "Tiene por Estado" +msgid "status" +msgstr "" + +msgctxt "CWDataImport" +msgid "status" +msgstr "" + msgid "status change" msgstr "Cambio de Estatus" @@ -3974,6 +4005,9 @@ msgid "subworkflow_state_object" msgstr "Estado de Salida de" +msgid "success" +msgstr "" + msgid "sunday" msgstr "Domingo" @@ -4331,9 +4365,6 @@ msgid "url" msgstr "url" -msgid "use template languages" -msgstr "Utilizar plantillas de lenguaje" - msgid "" "use to define a transition from one or multiple states to a destination " "states in workflow's definitions. Transition without destination state will " @@ -4357,9 +4388,6 @@ msgid "use_email_object" msgstr "Utilizado por" -msgid "use_template_format" -msgstr "Utilización del formato 'cubicweb template'" - msgid "" "used for cubicweb configuration. Once a property has been created you can't " "change the key." @@ -4373,9 +4401,6 @@ "Se utiliza para asociar estados simples a un tipo de entidad y/o para " "definir Workflows" -msgid "used to grant a permission to a group" -msgstr "Se utiliza para otorgar permisos a un grupo" - msgid "user" msgstr "Usuario" @@ -4402,9 +4427,6 @@ msgid "users and groups" msgstr "usuarios y grupos" -msgid "users and groups management" -msgstr "usuarios y grupos de administradores" - msgid "users using this bookmark" msgstr "Usuarios utilizando este Favorito" @@ -4568,15 +4590,15 @@ msgid "wrong query parameter line %s" msgstr "Parámetro erróneo de consulta línea %s" -msgid "xbel" -msgstr "xbel" - -msgid "xml" -msgstr "xml" +msgid "xbel export" +msgstr "" msgid "xml export" msgstr "Exportar XML" +msgid "xml export (entities)" +msgstr "" + msgid "yes" msgstr "Sí" @@ -4595,3 +4617,55 @@ msgstr "" "usted debe quitar la puesta en línea de la relación %s que es aceptada y " "puede ser cruzada" + +#~ msgid "(loading ...)" +#~ msgstr "(Cargando ...)" + +#~ msgid "Schema of the data model" +#~ msgstr "Esquema del modelo de datos" + +#~ msgid "add a CWSourceSchemaConfig" +#~ msgstr "agregar una parte de mapeo" + +#~ msgid "csv entities export" +#~ msgstr "Exportar entidades en csv" + +#~ msgid "follow this link if javascript is deactivated" +#~ msgstr "Seleccione esta liga si javascript esta desactivado" + +#~ msgid "" +#~ "how to format date and time in the ui (\"man strftime\" for format " +#~ "description)" +#~ msgstr "" +#~ "Formato de fecha y hora que se utilizará por defecto en la interfaz " +#~ "(\"man strftime\" para mayor información del formato)" + +#~ msgid "" +#~ "how to format date in the ui (\"man strftime\" for format description)" +#~ msgstr "" +#~ "Formato de fecha que se utilizará por defecto en la interfaz (\"man " +#~ "strftime\" para mayor información del formato)" + +#~ msgid "" +#~ "how to format time in the ui (\"man strftime\" for format description)" +#~ msgstr "" +#~ "Formato de hora que se utilizará por defecto en la interfaz (\"man " +#~ "strftime\" para mayor información del formato)" + +#~ msgid "instance schema" +#~ msgstr "Esquema de la Instancia" + +#~ msgid "rdf" +#~ msgstr "rdf" + +#~ msgid "rss" +#~ msgstr "RSS" + +#~ msgid "siteinfo" +#~ msgstr "información" + +#~ msgid "xbel" +#~ msgstr "xbel" + +#~ msgid "xml" +#~ msgstr "xml" diff -r 7b2c7f3d3703 -r 29cdde6bb9ef i18n/fr.po --- a/i18n/fr.po Thu Dec 08 14:29:48 2011 +0100 +++ b/i18n/fr.po Thu Dec 08 14:32:57 2011 +0100 @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: cubicweb 2.46.0\n" -"PO-Revision-Date: 2011-06-23 10:23+0200\n" +"PO-Revision-Date: 2011-11-23 11:09+0100\n" "Last-Translator: Logilab Team \n" "Language-Team: fr \n" "Language: \n" @@ -106,34 +106,6 @@ msgstr "%d années" #, python-format -msgid "%d days" -msgstr "%d jours" - -#, python-format -msgid "%d hours" -msgstr "%d heures" - -#, python-format -msgid "%d minutes" -msgstr "%d minutes" - -#, python-format -msgid "%d months" -msgstr "%d mois" - -#, python-format -msgid "%d seconds" -msgstr "%d secondes" - -#, python-format -msgid "%d weeks" -msgstr "%d semaines" - -#, python-format -msgid "%d years" -msgstr "%d années" - -#, python-format msgid "%s could be supported" msgstr "%s pourrait être supporté" @@ -175,9 +147,6 @@ msgid "(UNEXISTANT EID)" msgstr "(EID INTROUVABLE)" -msgid "(loading ...)" -msgstr "(chargement ...)" - msgid "**" msgstr "0..n 0..n" @@ -223,7 +192,7 @@ "
This schema of the data model excludes the meta-data, but you " "can also display a complete schema with meta-data.
" msgstr "" -"
Ce schéma du modèle de données exclue les méta-données, mais " +"
Ce schéma du modèle de données exclut les méta-données, mais " "vous pouvez afficher un schéma complet.
" msgid "" @@ -325,6 +294,12 @@ msgid "CWConstraint_plural" msgstr "Contraintes" +msgid "CWDataImport" +msgstr "Import de données" + +msgid "CWDataImport_plural" +msgstr "Imports de données" + msgid "CWEType" msgstr "Type d'entité" @@ -345,12 +320,6 @@ msgid "CWGroup_plural" msgstr "Groupes" -msgid "CWPermission" -msgstr "Permission" - -msgid "CWPermission_plural" -msgstr "Permissions" - msgid "CWProperty" msgstr "Propriété" @@ -451,6 +420,12 @@ "Ne peut annuler la création de l'entité %(eid)s de type %(etype)s, ce type " "n'existe plus" +msgid "Click to sort on this column" +msgstr "Cliquer pour trier sur cette colonne" + +msgid "DEBUG" +msgstr "DEBUG" + #, python-format msgid "Data connection graph for %s" msgstr "Graphique de connection des données pour %s" @@ -482,6 +457,9 @@ msgid "Download schema as OWL" msgstr "Télécharger le schéma au format OWL" +msgid "ERROR" +msgstr "ERREUR" + msgid "EmailAddress" msgstr "Adresse électronique" @@ -504,6 +482,9 @@ msgid "ExternalUri_plural" msgstr "Uri externes" +msgid "FATAL" +msgstr "FATAL" + msgid "Float" msgstr "Nombre flottant" @@ -528,6 +509,9 @@ msgid "Help" msgstr "Aide" +msgid "INFO" +msgstr "INFO" + msgid "Instance" msgstr "Instance" @@ -546,12 +530,21 @@ msgid "Interval_plural" msgstr "Durées" +msgid "Link:" +msgstr "Lien :" + msgid "Looked up classes" msgstr "Classes recherchées" msgid "Manage" msgstr "Administration" +msgid "Manage security" +msgstr "Gestion de la sécurité" + +msgid "Message threshold" +msgstr "Niveau du message" + msgid "Most referenced classes" msgstr "Classes les plus référencées" @@ -573,15 +566,15 @@ msgid "New CWConstraintType" msgstr "Nouveau type de contrainte" +msgid "New CWDataImport" +msgstr "Nouvel import de données" + msgid "New CWEType" msgstr "Nouveau type d'entité" msgid "New CWGroup" msgstr "Nouveau groupe" -msgid "New CWPermission" -msgstr "Nouvelle permission" - msgid "New CWProperty" msgstr "Nouvelle propriété" @@ -646,6 +639,9 @@ msgid "OR" msgstr "OU" +msgid "Ownership" +msgstr "Propriété" + msgid "Parent class:" msgstr "Classe parente" @@ -695,8 +691,8 @@ msgid "Schema %s" msgstr "Schéma %s" -msgid "Schema of the data model" -msgstr "Schéma du modèle de données" +msgid "Schema's permissions definitions" +msgstr "Permissions définies dans le schéma" msgid "Search for" msgstr "Rechercher" @@ -793,15 +789,15 @@ msgid "This CWConstraintType" msgstr "Ce type de contrainte" +msgid "This CWDataImport" +msgstr "Cet import de données" + msgid "This CWEType" msgstr "Ce type d'entité" msgid "This CWGroup" msgstr "Ce groupe" -msgid "This CWPermission" -msgstr "Cette permission" - msgid "This CWProperty" msgstr "Cette propriété" @@ -888,6 +884,12 @@ msgid "Used by:" msgstr "Utilisé par :" +msgid "Users and groups management" +msgstr "Gestion des utilisateurs et groupes" + +msgid "WARNING" +msgstr "AVERTISSEMENT" + msgid "Web server" msgstr "Serveur web" @@ -952,7 +954,7 @@ msgid "" "a RQL expression which should return some results, else the transition won't " "be available. This query may use X and U variables that will respectivly " -"represents the current entity and the current user" +"represents the current entity and the current user." msgstr "" "une expression RQL devant retourner des résultats pour que la transition " "puisse être passée. Cette expression peut utiliser les variables X et U qui " @@ -980,6 +982,9 @@ msgid "abstract base class for transitions" msgstr "classe de base abstraite pour les transitions" +msgid "action menu" +msgstr "" + msgid "action(s) on this selection" msgstr "action(s) sur cette sélection" @@ -1101,9 +1106,6 @@ msgid "add a EmailAddress" msgstr "ajouter une adresse électronique" -msgid "add a new permission" -msgstr "ajouter une permission" - # subject and object forms for each relation type # (no object form for final relation types) msgid "add_permission" @@ -1265,6 +1267,10 @@ msgid "automatic" msgstr "automatique" +#, python-format +msgid "back to pagination (%s results)" +msgstr "retour à la vue paginée (%s résultats)" + msgid "bad value" msgstr "mauvaise valeur" @@ -1783,12 +1789,12 @@ msgid "cstrtype_object" msgstr "type des contraintes" -msgid "csv entities export" -msgstr "export d'entités en CSV" - msgid "csv export" msgstr "export CSV" +msgid "csv export (entities)" +msgstr "export CSV (entités)" + msgid "ctxcomponents" msgstr "composants contextuels" @@ -1908,6 +1914,12 @@ msgid "custom_workflow_object" msgstr "workflow de" +msgid "cw.groups-management" +msgstr "groupes" + +msgid "cw.users-management" +msgstr "utilisateurs" + msgid "cw_for_source" msgstr "source" @@ -1936,6 +1948,20 @@ msgid "cw_host_config_of_object" msgstr "has host configuration" +msgid "cw_import_of" +msgstr "source" + +msgctxt "CWDataImport" +msgid "cw_import_of" +msgstr "source" + +msgid "cw_import_of_object" +msgstr "imports" + +msgctxt "CWSource" +msgid "cw_import_of_object" +msgstr "imports" + msgid "cw_schema" msgstr "schéma" @@ -1991,6 +2017,9 @@ msgid "cwrtype-permissions" msgstr "permissions" +msgid "cwsource-imports" +msgstr "imports" + msgid "cwsource-main" msgstr "description" @@ -2118,9 +2147,6 @@ msgid "delete this bookmark" msgstr "supprimer ce signet" -msgid "delete this permission" -msgstr "supprimer cette permission" - msgid "delete this relation" msgstr "supprimer cette relation" @@ -2297,13 +2323,6 @@ msgid "display the facet or not" msgstr "afficher la facette ou non" -msgid "" -"distinct label to distinguate between other permission entity of the same " -"name" -msgstr "" -"libellé permettant de distinguer cette permission des autres ayant le même " -"nom" - msgid "download" msgstr "télécharger" @@ -2341,6 +2360,13 @@ msgid "embedding this url is forbidden" msgstr "l'inclusion de cette url est interdite" +msgid "end_timestamp" +msgstr "horodate de fin" + +msgctxt "CWDataImport" +msgid "end_timestamp" +msgstr "horodate de fin" + msgid "entities deleted" msgstr "entités supprimées" @@ -2376,12 +2402,6 @@ msgid "entity type" msgstr "type d'entité" -msgid "" -"entity type that may be used to construct some advanced security " -"configuration" -msgstr "" -"type d'entité à utiliser pour définir une configuration de sécurité avancée" - msgid "entity types which may use this workflow" msgstr "types d'entité pouvant utiliser ce workflow" @@ -2470,6 +2490,12 @@ msgid "facets_cwfinal-facet_description" msgstr "" +msgid "facets_datafeed.dataimport.status" +msgstr "état de l'iport" + +msgid "facets_datafeed.dataimport.status_description" +msgstr "" + msgid "facets_etype-facet" msgstr "facette \"est de type\"" @@ -2494,6 +2520,9 @@ msgid "facets_in_state-facet_description" msgstr "" +msgid "failed" +msgstr "échec" + #, python-format msgid "failed to uniquify path (%s, %s)" msgstr "ne peut obtenir un nom de fichier unique (%s, %s)" @@ -2535,9 +2564,6 @@ msgid "follow this link for more information on this %s" msgstr "suivez ce lien pour plus d'information sur ce %s" -msgid "follow this link if javascript is deactivated" -msgstr "suivez ce lien si javascript est désactivé" - msgid "for_user" msgstr "pour l'utilisateur" @@ -2675,9 +2701,6 @@ msgid "groups grant permissions to the user" msgstr "les groupes donnent des permissions à l'utilisateur" -msgid "groups to which the permission is granted" -msgstr "groupes auquels cette permission est donnée" - msgid "guests" msgstr "invités" @@ -2697,24 +2720,32 @@ msgstr "cacher le filtre" msgid "" -"how to format date and time in the ui (\"man strftime\" for format " +"how to format date and time in the ui (see this page for format " "description)" msgstr "" -"comment formater la date dans l'interface (\"man strftime\" pour la " -"description du format)" - -msgid "how to format date in the ui (\"man strftime\" for format description)" +"comment formater l'horodate dans l'interface (description du " +"format)" + +msgid "" +"how to format date in the ui (see this page for format " +"description)" msgstr "" -"comment formater la date dans l'interface (\"man strftime\" pour la " -"description du format)" +"comment formater la date dans l'interface (description du format)" msgid "how to format float numbers in the ui" msgstr "comment formater les nombres flottants dans l'interface" -msgid "how to format time in the ui (\"man strftime\" for format description)" +msgid "" +"how to format time in the ui (see this page for format " +"description)" msgstr "" -"comment formater l'heure dans l'interface (\"man strftime\" pour la " -"description du format)" +"comment formater l'heure dans l'interface (description du format)" msgid "i18n_bookmark_url_fqs" msgstr "paramètres" @@ -2774,6 +2805,9 @@ msgid "image" msgstr "image" +msgid "in progress" +msgstr "en cours" + msgid "in_group" msgstr "dans le groupe" @@ -2873,9 +2907,6 @@ msgid "instance home" msgstr "répertoire de l'instance" -msgid "instance schema" -msgstr "schéma de l'instance" - msgid "internal entity uri" msgstr "uri interne" @@ -2936,19 +2967,18 @@ msgid "january" msgstr "janvier" +msgid "json-entities-export-view" +msgstr "export JSON (entités)" + +msgid "json-export-view" +msgstr "export JSON" + msgid "july" msgstr "juillet" msgid "june" msgstr "juin" -msgid "label" -msgstr "libellé" - -msgctxt "CWPermission" -msgid "label" -msgstr "libellé" - msgid "language of the user interface" msgstr "langue pour l'interface utilisateur" @@ -2971,6 +3001,9 @@ msgid "last_login_time" msgstr "dernière date de connexion" +msgid "latest import" +msgstr "dernier import" + msgid "latest modification time of an entity" msgstr "date de dernière modification d'une entité" @@ -2990,12 +3023,8 @@ msgid "left" msgstr "gauche" -msgid "" -"link a permission to the entity. This permission should be used in the " -"security definition of the entity's type to be useful." -msgstr "" -"lie une permission à une entité. Cette permission doit généralement être " -"utilisée dans la définition de sécurité du type d'entité pour être utile." +msgid "line" +msgstr "ligne" msgid "" "link a property to the user which want this property customization. Unless " @@ -3029,6 +3058,13 @@ msgid "list" msgstr "liste" +msgid "log" +msgstr "journal" + +msgctxt "CWDataImport" +msgid "log" +msgstr "journal" + msgid "log in" msgstr "s'identifier" @@ -3080,9 +3116,6 @@ msgid "manage permissions" msgstr "gestion des permissions" -msgid "manage security" -msgstr "gestion de la sécurité" - msgid "managers" msgstr "administrateurs" @@ -3117,6 +3150,9 @@ msgid "memory leak debugging" msgstr "Déboguage des fuites de mémoire" +msgid "message" +msgstr "message" + msgid "milestone" msgstr "jalon" @@ -3174,10 +3210,6 @@ msgid "name" msgstr "nom" -msgctxt "CWPermission" -msgid "name" -msgstr "nom" - msgctxt "CWRType" msgid "name" msgstr "nom" @@ -3215,9 +3247,6 @@ msgid "name of the source" msgstr "nom de la source" -msgid "name or identifier of the permission" -msgstr "nom (identifiant) de la permission" - msgid "navbottom" msgstr "bas de page" @@ -3254,9 +3283,6 @@ msgid "no" msgstr "non" -msgid "no associated permissions" -msgstr "aucune permission associée" - msgid "no content next link" msgstr "pas de lien 'suivant'" @@ -3270,6 +3296,9 @@ msgid "no edited fields specified for entity %s" msgstr "aucun champ à éditer spécifié pour l'entité %s" +msgid "no log to display" +msgstr "rien à afficher" + msgid "no related entity" msgstr "pas d'entité liée" @@ -3304,6 +3333,9 @@ msgid "november" msgstr "novembre" +msgid "num. users" +msgstr "nombre d'utilisateurs" + msgid "object" msgstr "objet" @@ -3370,9 +3402,6 @@ msgid "owners" msgstr "propriétaires" -msgid "ownership" -msgstr "propriété" - msgid "ownerships have been changed" msgstr "les droits de propriété ont été modifiés" @@ -3406,15 +3435,15 @@ msgid "path" msgstr "chemin" +msgid "permalink to this message" +msgstr "lien permanent vers ce message" + msgid "permission" msgstr "permission" msgid "permissions" msgstr "permissions" -msgid "permissions for this entity" -msgstr "permissions pour cette entité" - msgid "pick existing bookmarks" msgstr "récupérer des signets existants" @@ -3489,8 +3518,8 @@ msgid "rdef-permissions" msgstr "permissions" -msgid "rdf" -msgstr "rdf" +msgid "rdf export" +msgstr "export RDF" msgid "read" msgstr "lecture" @@ -3619,10 +3648,6 @@ msgid "require_group" msgstr "restreinte au groupe" -msgctxt "CWPermission" -msgid "require_group" -msgstr "restreinte au groupe" - msgctxt "Transition" msgid "require_group" msgstr "restreinte au groupe" @@ -3638,12 +3663,6 @@ msgid "require_group_object" msgstr "a les droits" -msgid "require_permission" -msgstr "require permission" - -msgid "require_permission_object" -msgstr "permission of" - msgid "required" msgstr "requis" @@ -3690,8 +3709,8 @@ msgid "rql expressions" msgstr "conditions rql" -msgid "rss" -msgstr "RSS" +msgid "rss export" +msgstr "export RSS" msgid "same_as" msgstr "identique à" @@ -3702,9 +3721,6 @@ msgid "saturday" msgstr "samedi" -msgid "schema's permissions definitions" -msgstr "permissions définies dans le schéma" - msgid "schema-diagram" msgstr "diagramme" @@ -3783,6 +3799,9 @@ msgid "server information" msgstr "informations serveur" +msgid "severity" +msgstr "sévérité" + msgid "" "should html fields being edited using fckeditor (a HTML WYSIWYG editor). " "You should also select text/html as default text format to actually get " @@ -3817,9 +3836,6 @@ msgid "site-wide property can't be set for user" msgstr "une propriété spécifique au site ne peut être propre à un utilisateur" -msgid "siteinfo" -msgstr "informations" - msgid "some later transaction(s) touch entity, undo them first" msgstr "" "des transactions plus récentes modifient cette entité, annulez les d'abord" @@ -3865,6 +3881,13 @@ msgstr "" "horodate de départ de la synchronisation en cours, ou NULL s'il n'y en a pas." +msgid "start_timestamp" +msgstr "horodate de début" + +msgctxt "CWDataImport" +msgid "start_timestamp" +msgstr "horodate de début" + msgid "startup views" msgstr "vues de départ" @@ -3910,6 +3933,13 @@ msgid "state_of_object" msgstr "contient les états" +msgid "status" +msgstr "état" + +msgctxt "CWDataImport" +msgid "status" +msgstr "état" + msgid "status change" msgstr "changer l'état" @@ -3980,6 +4010,9 @@ msgid "subworkflow_state_object" msgstr "état de sortie de" +msgid "success" +msgstr "succès" + msgid "sunday" msgstr "dimanche" @@ -4337,9 +4370,6 @@ msgid "url" msgstr "url" -msgid "use template languages" -msgstr "utiliser les langages de template" - msgid "" "use to define a transition from one or multiple states to a destination " "states in workflow's definitions. Transition without destination state will " @@ -4363,9 +4393,6 @@ msgid "use_email_object" msgstr "utilisée par" -msgid "use_template_format" -msgstr "utilisation du format 'cubicweb template'" - msgid "" "used for cubicweb configuration. Once a property has been created you can't " "change the key." @@ -4377,9 +4404,6 @@ "used to associate simple states to an entity type and/or to define workflows" msgstr "associe les états à un type d'entité pour définir un workflow" -msgid "used to grant a permission to a group" -msgstr "utiliser pour donner une permission à un groupe" - msgid "user" msgstr "utilisateur" @@ -4406,9 +4430,6 @@ msgid "users and groups" msgstr "utilisateurs et groupes" -msgid "users and groups management" -msgstr "gestion des utilisateurs et groupes" - msgid "users using this bookmark" msgstr "utilisateurs utilisant ce signet" @@ -4573,14 +4594,14 @@ msgid "wrong query parameter line %s" msgstr "mauvais paramètre de requête ligne %s" -msgid "xbel" -msgstr "xbel" - -msgid "xml" -msgstr "xml" +msgid "xbel export" +msgstr "export XBEL" msgid "xml export" -msgstr "export xml" +msgstr "export XML" + +msgid "xml export (entities)" +msgstr "export XML (entités)" msgid "yes" msgstr "oui" @@ -4600,3 +4621,28 @@ msgstr "" "vous devriez enlevé la mise en ligne de la relation %s qui est supportée et " "peut-être croisée" + +#~ msgid "(loading ...)" +#~ msgstr "(chargement ...)" + +#~ msgid "follow this link if javascript is deactivated" +#~ msgstr "suivez ce lien si javascript est désactivé" + +#~ msgid "" +#~ "how to format date and time in the ui (\"man strftime\" for format " +#~ "description)" +#~ msgstr "" +#~ "comment formater la date dans l'interface (\"man strftime\" pour la " +#~ "description du format)" + +#~ msgid "" +#~ "how to format date in the ui (\"man strftime\" for format description)" +#~ msgstr "" +#~ "comment formater la date dans l'interface (\"man strftime\" pour la " +#~ "description du format)" + +#~ msgid "" +#~ "how to format time in the ui (\"man strftime\" for format description)" +#~ msgstr "" +#~ "comment formater l'heure dans l'interface (\"man strftime\" pour la " +#~ "description du format)" diff -r 7b2c7f3d3703 -r 29cdde6bb9ef mail.py --- a/mail.py Thu Dec 08 14:29:48 2011 +0100 +++ b/mail.py Thu Dec 08 14:32:57 2011 +0100 @@ -25,11 +25,7 @@ from email.mime.text import MIMEText from email.mime.image import MIMEImage from email.header import Header -try: - from socket import gethostname -except ImportError: - def gethostname(): # gae - return 'XXX' +from socket import gethostname from cubicweb.view import EntityView from cubicweb.entity import Entity diff -r 7b2c7f3d3703 -r 29cdde6bb9ef migration.py --- a/migration.py Thu Dec 08 14:29:48 2011 +0100 +++ b/migration.py Thu Dec 08 14:32:57 2011 +0100 @@ -34,6 +34,7 @@ from cubicweb import ConfigurationError, ExecutionError from cubicweb.cwconfig import CubicWebConfiguration as cwcfg +from cubicweb.toolsutils import show_diffs def filter_scripts(config, directory, fromversion, toversion, quiet=True): """return a list of paths of migration files to consider to upgrade @@ -420,8 +421,6 @@ return removed def rewrite_configuration(self): - # import locally, show_diffs unavailable in gae environment - from cubicweb.toolsutils import show_diffs configfile = self.config.main_config_file() if self._option_changes: read_old_config(self.config, self._option_changes, configfile) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef misc/cwdesklets/rqlsensor/__init__.py --- a/misc/cwdesklets/rqlsensor/__init__.py Thu Dec 08 14:29:48 2011 +0100 +++ b/misc/cwdesklets/rqlsensor/__init__.py Thu Dec 08 14:32:57 2011 +0100 @@ -56,8 +56,6 @@ def call_action(self, action, path, args=[]): index = path[-1] output = self._new_output() -# import sys -# print >>sys.stderr, action, path, args if action=="enter-line": # change background output.set('resultbg[%s]' % index, 'yellow') diff -r 7b2c7f3d3703 -r 29cdde6bb9ef misc/migration/3.14.0_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/migration/3.14.0_Any.py Thu Dec 08 14:32:57 2011 +0100 @@ -0,0 +1,13 @@ +config['rql-cache-size'] = config['rql-cache-size'] * 10 + +add_entity_type('CWDataImport') + +from cubicweb.schema import CONSTRAINTS, guess_rrqlexpr_mainvars +for rqlcstr in rql('Any X,XT,XV WHERE X is CWConstraint, X cstrtype XT, X value XV,' + 'X cstrtype XT, XT name IN ("RQLUniqueConstraint","RQLConstraint","RQLVocabularyConstraint"),' + 'NOT X value ~= ";%"').entities(): + expression = rqlcstr.value + mainvars = guess_rrqlexpr_mainvars(expression) + yamscstr = CONSTRAINTS[rqlcstr.type](expression, mainvars) + rqlcstr.set_attributes(value=yamscstr.serialize()) + print 'updated', rqlcstr.type, rqlcstr.value.strip() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef misc/migration/bootstrapmigration_repository.py --- a/misc/migration/bootstrapmigration_repository.py Thu Dec 08 14:29:48 2011 +0100 +++ b/misc/migration/bootstrapmigration_repository.py Thu Dec 08 14:32:57 2011 +0100 @@ -41,6 +41,15 @@ 'FROM cw_CWSource, cw_source_relation ' 'WHERE entities.eid=cw_source_relation.eid_from AND cw_source_relation.eid_to=cw_CWSource.cw_eid') +if applcubicwebversion <= (3, 14, 0) and cubicwebversion >= (3, 14, 0): + if 'require_permission' in schema and not 'localperms'in repo.config.cubes(): + from cubicweb import ExecutionError + try: + add_cube('localperms', update_database=False) + except ImportError: + raise ExecutionError('In cubicweb 3.14, CWPermission and related stuff ' + 'has been moved to cube localperms. Install it first.') + if applcubicwebversion == (3, 6, 0) and cubicwebversion >= (3, 6, 0): CSTRMAP = dict(rql('Any T, X WHERE X is CWConstraintType, X name T', ask_confirm=False)) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef misc/migration/postcreate.py --- a/misc/migration/postcreate.py Thu Dec 08 14:29:48 2011 +0100 +++ b/misc/migration/postcreate.py Thu Dec 08 14:32:57 2011 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -69,10 +69,3 @@ if value != default: rql('INSERT CWProperty X: X pkey %(k)s, X value %(v)s', {'k': key, 'v': value}) - -# add PERM_USE_TEMPLATE_FORMAT permission -from cubicweb.schema import PERM_USE_TEMPLATE_FORMAT -usetmplperm = create_entity('CWPermission', name=PERM_USE_TEMPLATE_FORMAT, - label=_('use template languages')) -rql('SET X require_group G WHERE G name "managers", X eid %(x)s', - {'x': usetmplperm.eid}) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef pylintext.py --- a/pylintext.py Thu Dec 08 14:29:48 2011 +0100 +++ b/pylintext.py Thu Dec 08 14:32:57 2011 +0100 @@ -1,6 +1,7 @@ """https://pastebin.logilab.fr/show/860/""" from logilab.astng import MANAGER, nodes, scoped_nodes +from logilab.astng.builder import ASTNGBuilder def turn_function_to_class(node): """turn a Function node into a Class node (in-place)""" @@ -33,9 +34,15 @@ from yams import BASE_TYPES for etype in BASE_TYPES: module.locals[etype] = [scoped_nodes.Class(etype, None)] - -MANAGER.register_transformer(cubicweb_transform) + # add data() to uiprops module + if module.name.endswith('.uiprops'): + fake = ASTNGBuilder(MANAGER).string_build(''' +def data(string): + return u'' +''') + module.locals['data'] = fake.locals['data'] def register(linter): """called when loaded by pylint --load-plugins, nothing to do here""" + MANAGER.register_transformer(cubicweb_transform) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef req.py --- a/req.py Thu Dec 08 14:29:48 2011 +0100 +++ b/req.py Thu Dec 08 14:32:57 2011 +0100 @@ -29,7 +29,7 @@ from logilab.common.deprecation import deprecated from logilab.common.date import ustrftime, strptime, todate, todatetime -from cubicweb import Unauthorized, NoSelectableObject, typed_eid +from cubicweb import Unauthorized, NoSelectableObject, typed_eid, uilib from cubicweb.rset import ResultSet ONESECOND = timedelta(0, 1, 0) @@ -343,6 +343,18 @@ rset=rset, **initargs) return view.render(w=w, **kwargs) + def printable_value(self, attrtype, value, props=None, displaytime=True, + formatters=uilib.PRINTERS): + """return a displayablye value (i.e. unicode string)""" + if value is None: + return u'' + try: + as_string = formatters[attrtype] + except KeyError: + self.error('given bad attrtype %s', attrtype) + return unicode(value) + return as_string(value, self, props, displaytime) + def format_date(self, date, date_format=None, time=False): """return a string for a date time according to instance's configuration @@ -412,13 +424,3 @@ def describe(self, eid, asdict=False): """return a tuple (type, sourceuri, extid) for the entity with id """ raise NotImplementedError - - @property - @deprecated('[3.6] use _cw.vreg.config') - def config(self): - return self.vreg.config - - @property - @deprecated('[3.6] use _cw.vreg.schema') - def schema(self): - return self.vreg.schema diff -r 7b2c7f3d3703 -r 29cdde6bb9ef rqlrewrite.py --- a/rqlrewrite.py Thu Dec 08 14:29:48 2011 +0100 +++ b/rqlrewrite.py Thu Dec 08 14:32:57 2011 +0100 @@ -54,6 +54,7 @@ if varname not in newroot.defined_vars or eschema(etype).final: continue allpossibletypes.setdefault(varname, set()).add(etype) + # XXX could be factorized with add_etypes_restriction from rql 0.31 for varname in sorted(allpossibletypes): var = newroot.defined_vars[varname] stinfo = var.stinfo @@ -207,7 +208,7 @@ vi = {} self.varinfos.append(vi) try: - vi['const'] = typed_eid(selectvar) # XXX gae + vi['const'] = typed_eid(selectvar) vi['rhs_rels'] = vi['lhs_rels'] = {} except ValueError: try: @@ -248,7 +249,7 @@ 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 + raise Unauthorized() # XXX may also be because of bad constraints in schema definition def insert_snippet(self, varmap, snippetrqlst, parent=None): new = snippetrqlst.where.accept(self) @@ -660,7 +661,7 @@ 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.Constant(vi['const'], 'Int') return n.VariableRef(stmt.get_variable(selectvar)) vname_or_term = self._get_varname_or_term(node.name) if isinstance(vname_or_term, basestring): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef rset.py --- a/rset.py Thu Dec 08 14:29:48 2011 +0100 +++ b/rset.py Thu Dec 08 14:32:57 2011 +0100 @@ -481,9 +481,9 @@ eschema = entity.e_schema eid_col, attr_cols, rel_cols = self._rset_structure(eschema, col) entity.eid = rowvalues[eid_col] - for attr, col_idx in attr_cols.items(): + for attr, col_idx in attr_cols.iteritems(): entity.cw_attr_cache[attr] = rowvalues[col_idx] - for (rtype, role), col_idx in rel_cols.items(): + for (rtype, role), col_idx in rel_cols.iteritems(): value = rowvalues[col_idx] if value is None: if role == 'subject': diff -r 7b2c7f3d3703 -r 29cdde6bb9ef schema.py --- a/schema.py Thu Dec 08 14:29:48 2011 +0100 +++ b/schema.py Thu Dec 08 14:32:57 2011 +0100 @@ -59,12 +59,11 @@ 'from_state', 'to_state', 'condition', 'subworkflow', 'subworkflow_state', 'subworkflow_exit', )) -SYSTEM_RTYPES = set(('in_group', 'require_group', 'require_permission', +SYSTEM_RTYPES = set(('in_group', 'require_group', # cwproperty 'for_user', )) | WORKFLOW_RTYPES NO_I18NCONTEXT = META_RTYPES | WORKFLOW_RTYPES -NO_I18NCONTEXT.add('require_permission') SKIP_COMPOSITE_RELS = [('cw_source', 'subject')] @@ -85,7 +84,7 @@ 'WorkflowTransition', 'BaseTransition', 'SubWorkflowExitPoint')) -INTERNAL_TYPES = set(('CWProperty', 'CWPermission', 'CWCache', 'ExternalUri', +INTERNAL_TYPES = set(('CWProperty', 'CWCache', 'ExternalUri', 'CWSource', 'CWSourceHostConfig', 'CWSourceSchemaConfig')) @@ -171,13 +170,10 @@ if form: key = key + '_' + form # ensure unicode - # .lower() in case no translation are available XXX done whatever a translation is there or not! if context is not None: - return unicode(req.pgettext(context, key)).lower() + return unicode(req.pgettext(context, key)) else: - return unicode(req._(key)).lower() - -__builtins__['display_name'] = deprecated('[3.4] display_name should be imported from cubicweb.schema')(display_name) + return unicode(req._(key)) # Schema objects definition ################################################### @@ -852,23 +848,39 @@ return self._check(session, **kwargs) +def vargraph(rqlst): + """ builds an adjacency graph of variables from the rql syntax tree, e.g: + Any O,S WHERE T subworkflow_exit S, T subworkflow WF, O state_of WF + => {'WF': ['O', 'T'], 'S': ['T'], 'T': ['WF', 'S'], 'O': ['WF']} + """ + vargraph = {} + for relation in rqlst.get_nodes(nodes.Relation): + try: + rhsvarname = relation.children[1].children[0].variable.name + lhsvarname = relation.children[0].name + except AttributeError: + pass + else: + vargraph.setdefault(lhsvarname, []).append(rhsvarname) + vargraph.setdefault(rhsvarname, []).append(lhsvarname) + #vargraph[(lhsvarname, rhsvarname)] = relation.r_type + return vargraph + + +class GeneratedConstraint(object): + def __init__(self, rqlst, mainvars): + self.snippet_rqlst = rqlst + self.mainvars = mainvars + self.vargraph = vargraph(rqlst) + + class RRQLExpression(RQLExpression): def __init__(self, expression, mainvars=None, eid=None): if mainvars is None: mainvars = guess_rrqlexpr_mainvars(expression) RQLExpression.__init__(self, expression, mainvars, eid) # graph of links between variable, used by rql rewriter - self.vargraph = {} - for relation in self.rqlst.get_nodes(nodes.Relation): - try: - rhsvarname = relation.children[1].children[0].variable.name - lhsvarname = relation.children[0].name - except AttributeError: - pass - else: - self.vargraph.setdefault(lhsvarname, []).append(rhsvarname) - self.vargraph.setdefault(rhsvarname, []).append(lhsvarname) - #self.vargraph[(lhsvarname, rhsvarname)] = relation.r_type + self.vargraph = vargraph(self.rqlst) @property def full_rql(self): @@ -959,7 +971,7 @@ def repo_check(self, session, eidfrom, rtype, eidto): """raise ValidationError if the relation doesn't satisfy the constraint """ - pass # this is a vocabulary constraint, not enforce + pass # this is a vocabulary constraint, not enforced class RepoEnforcedRQLConstraintMixIn(object): @@ -1176,7 +1188,7 @@ # _() is just there to add messages to the catalog, don't care about actual # translation -PERM_USE_TEMPLATE_FORMAT = _('use_template_format') +MAY_USE_TEMPLATE_FORMAT = set(('managers',)) NEED_PERM_FORMATS = [_('text/cubicweb-page-template')] @monkeypatch(FormatConstraint) @@ -1191,9 +1203,9 @@ # cw is a server session hasperm = not cw.write_security or \ not cw.is_hook_category_activated('integrity') or \ - cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT) + cw.user.matching_groups(MAY_USE_TEMPLATE_FORMAT) else: - hasperm = cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT) + hasperm = cw.user.matching_groups(MAY_USE_TEMPLATE_FORMAT) if hasperm: return self.regular_formats + tuple(NEED_PERM_FORMATS) return self.regular_formats diff -r 7b2c7f3d3703 -r 29cdde6bb9ef schemas/__init__.py --- a/schemas/__init__.py Thu Dec 08 14:29:48 2011 +0100 +++ b/schemas/__init__.py Thu Dec 08 14:32:57 2011 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -15,12 +15,10 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""some utilities to define schema permissions +"""some constants and classes to define schema permissions""" -""" __docformat__ = "restructuredtext en" -from rql.utils import quote from cubicweb.schema import RO_REL_PERMS, RO_ATTR_PERMS, \ PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, \ ERQLExpression, RRQLExpression @@ -35,59 +33,19 @@ # execute, readable by anyone HOOKS_RTYPE_PERMS = RO_REL_PERMS # XXX deprecates -def _perm(names): - if isinstance(names, (list, tuple)): - if len(names) == 1: - names = quote(names[0]) - else: - names = 'IN (%s)' % (','.join(quote(name) for name in names)) - else: - names = quote(names) - #return u' require_permission P, P name %s, U in_group G, P require_group G' % names - return u' require_permission P, P name %s, U has_group_permission P' % names - -def xperm(*names): - return 'X' + _perm(names) - -def xexpr(*names): - return ERQLExpression(xperm(*names)) - -def xrexpr(relation, *names): - return ERQLExpression('X %s Y, Y %s' % (relation, _perm(names))) - -def xorexpr(relation, etype, *names): - return ERQLExpression('Y %s X, X is %s, Y %s' % (relation, etype, _perm(names))) - - -def sexpr(*names): - return RRQLExpression('S' + _perm(names), 'S') +from logilab.common.modutils import LazyObject +from logilab.common.deprecation import deprecated +class MyLazyObject(LazyObject): -def restricted_sexpr(restriction, *names): - rql = '%s, %s' % (restriction, 'S' + _perm(names)) - return RRQLExpression(rql, 'S') - -def restricted_oexpr(restriction, *names): - rql = '%s, %s' % (restriction, 'O' + _perm(names)) - return RRQLExpression(rql, 'O') - -def oexpr(*names): - return RRQLExpression('O' + _perm(names), 'O') - + def _getobj(self): + try: + return super(MyLazyObject, self)._getobj() + except ImportError: + raise ImportError('In cubicweb 3.14, function %s has been moved to ' + 'cube localperms. Install it first.' % self.obj) -# def supdate_perm(): -# return RRQLExpression('U has_update_permission S', 'S') - -# def oupdate_perm(): -# return RRQLExpression('U has_update_permission O', 'O') - -def relxperm(rel, role, *names): - assert role in ('subject', 'object') - if role == 'subject': - zxrel = ', X %s Z' % rel - else: - zxrel = ', Z %s X' % rel - return 'Z' + _perm(names) + zxrel - -def relxexpr(rel, role, *names): - return ERQLExpression(relxperm(rel, role, *names)) +for name in ('xperm', 'xexpr', 'xrexpr', 'xorexpr', 'sexpr', 'restricted_sexpr', + 'restricted_oexpr', 'oexpr', 'relxperm', 'relxexpr', '_perm'): + msg = '[3.14] import %s from cubes.localperms' % name + globals()[name] = deprecated(msg, name=name, doc='deprecated')(MyLazyObject('cubes.localperms', name)) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef schemas/base.py --- a/schemas/base.py Thu Dec 08 14:29:48 2011 +0100 +++ b/schemas/base.py Thu Dec 08 14:32:57 2011 +0100 @@ -181,31 +181,6 @@ cardinality = '?*' -class CWPermission(EntityType): - """entity type that may be used to construct some advanced security configuration - """ - __permissions__ = PUB_SYSTEM_ENTITY_PERMS - - name = String(required=True, indexed=True, internationalizable=True, maxsize=100, - description=_('name or identifier of the permission')) - label = String(required=True, internationalizable=True, maxsize=100, - description=_('distinct label to distinguate between other ' - 'permission entity of the same name')) - require_group = SubjectRelation('CWGroup', - description=_('groups to which the permission is granted')) - -# explicitly add X require_permission CWPermission for each entity that should have -# configurable security -class require_permission(RelationType): - """link a permission to the entity. This permission should be used in the - security definition of the entity's type to be useful. - """ - __permissions__ = PUB_SYSTEM_REL_PERMS - -class require_group(RelationType): - """used to grant a permission to a group""" - __permissions__ = PUB_SYSTEM_REL_PERMS - class ExternalUri(EntityType): """a URI representing an object in external data store""" @@ -330,6 +305,24 @@ cardinality = '1*' composite = 'object' + +class CWDataImport(EntityType): + __permissions__ = ENTITY_MANAGERS_PERMISSIONS + start_timestamp = TZDatetime() + end_timestamp = TZDatetime() + log = String() + status = String(required=True, internationalizable=True, indexed=True, + default='in progress', + vocabulary=[_('in progress'), _('success'), _('failed')]) + +class cw_import_of(RelationDefinition): + __permissions__ = RELATION_MANAGERS_PERMISSIONS + subject = 'CWDataImport' + object = 'CWSource' + cardinality = '1*' + composite = 'object' + + class CWSourceSchemaConfig(EntityType): __permissions__ = ENTITY_MANAGERS_PERMISSIONS cw_for_source = SubjectRelation( @@ -382,3 +375,5 @@ 'add': ('managers', RRQLExpression('U has_update_permission S'),), 'delete': ('managers', RRQLExpression('U has_update_permission S'),), } + + diff -r 7b2c7f3d3703 -r 29cdde6bb9ef schemas/workflow.py --- a/schemas/workflow.py Thu Dec 08 14:29:48 2011 +0100 +++ b/schemas/workflow.py Thu Dec 08 14:32:57 2011 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -21,14 +21,15 @@ __docformat__ = "restructuredtext en" _ = unicode -from yams.buildobjs import (EntityType, RelationType, SubjectRelation, +from yams.buildobjs import (EntityType, RelationType, RelationDefinition, + SubjectRelation, RichString, String, Int) from cubicweb.schema import RQLConstraint, RQLUniqueConstraint -from cubicweb.schemas import (META_ETYPE_PERMS, META_RTYPE_PERMS, - HOOKS_RTYPE_PERMS) +from cubicweb.schemas import (PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, + RO_REL_PERMS) class Workflow(EntityType): - __permissions__ = META_ETYPE_PERMS + __permissions__ = PUB_SYSTEM_ENTITY_PERMS name = String(required=True, indexed=True, internationalizable=True, maxsize=256) @@ -47,7 +48,7 @@ class default_workflow(RelationType): """default workflow for an entity type""" - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS subject = 'CWEType' object = 'Workflow' @@ -60,7 +61,7 @@ """used to associate simple states to an entity type and/or to define workflows """ - __permissions__ = META_ETYPE_PERMS + __permissions__ = PUB_SYSTEM_ENTITY_PERMS name = String(required=True, indexed=True, internationalizable=True, maxsize=256, @@ -83,7 +84,7 @@ class BaseTransition(EntityType): """abstract base class for transitions""" - __permissions__ = META_ETYPE_PERMS + __permissions__ = PUB_SYSTEM_ENTITY_PERMS name = String(required=True, indexed=True, internationalizable=True, maxsize=256, @@ -91,22 +92,34 @@ _('workflow already have a transition of that name'))]) type = String(vocabulary=(_('normal'), _('auto')), default='normal') description = RichString(description=_('semantic description of this transition')) - condition = SubjectRelation('RQLExpression', cardinality='*?', composite='subject', - description=_('a RQL expression which should return some results, ' - 'else the transition won\'t be available. ' - 'This query may use X and U variables ' - 'that will respectivly represents ' - 'the current entity and the current user')) - require_group = SubjectRelation('CWGroup', cardinality='**', - description=_('group in which a user should be to be ' - 'allowed to pass this transition')) transition_of = SubjectRelation('Workflow', cardinality='1*', composite='object', description=_('workflow to which this transition belongs'), constraints=[RQLUniqueConstraint('S name N, Y transition_of O, Y name N', 'Y', _('workflow already have a transition of that name'))]) +class require_group(RelationDefinition): + """group in which a user should be to be allowed to pass this transition""" + __permissions__ = PUB_SYSTEM_REL_PERMS + subject = 'BaseTransition' + object = 'CWGroup' + + +class condition(RelationDefinition): + """a RQL expression which should return some results, else the transition + won't be available. + + This query may use X and U variables that will respectivly represents the + current entity and the current user. + """ + __permissions__ = PUB_SYSTEM_REL_PERMS + subject = 'BaseTransition' + object = 'RQLExpression' + cardinality = '*?' + composite = 'subject' + + class Transition(BaseTransition): """use to define a transition from one or multiple states to a destination states in workflow's definitions. Transition without destination state will @@ -177,11 +190,11 @@ # get actor and date time using owned_by and creation_date class from_state(RelationType): - __permissions__ = HOOKS_RTYPE_PERMS.copy() + __permissions__ = RO_REL_PERMS.copy() inlined = True class to_state(RelationType): - __permissions__ = HOOKS_RTYPE_PERMS.copy() + __permissions__ = RO_REL_PERMS.copy() inlined = True class by_transition(RelationType): @@ -196,60 +209,52 @@ class workflow_of(RelationType): """link a workflow to one or more entity type""" - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS class state_of(RelationType): """link a state to one or more workflow""" - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS inlined = True class transition_of(RelationType): """link a transition to one or more workflow""" - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS inlined = True class destination_state(RelationType): """destination state of a transition""" - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS inlined = True class allowed_transition(RelationType): """allowed transitions from this state""" - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS class initial_state(RelationType): """indicate which state should be used by default when an entity using states is created """ - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS inlined = True class subworkflow(RelationType): - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS inlined = True class exit_point(RelationType): - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS class subworkflow_state(RelationType): - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS inlined = True -class condition(RelationType): - __permissions__ = META_RTYPE_PERMS - -# already defined in base.py -# class require_group(RelationType): -# __permissions__ = META_RTYPE_PERMS - - # "abstract" relations, set by WorkflowableEntityType ########################## class custom_workflow(RelationType): """allow to set a specific workflow for an entity""" - __permissions__ = META_RTYPE_PERMS + __permissions__ = PUB_SYSTEM_REL_PERMS cardinality = '?*' constraints = [RQLConstraint('S is ET, O workflow_of ET', @@ -275,7 +280,7 @@ class in_state(RelationType): """indicate the current state of an entity""" - __permissions__ = HOOKS_RTYPE_PERMS + __permissions__ = RO_REL_PERMS # not inlined intentionnaly since when using ldap sources, user'state # has to be stored outside the CWUser table diff -r 7b2c7f3d3703 -r 29cdde6bb9ef selectors.py --- a/selectors.py Thu Dec 08 14:29:48 2011 +0100 +++ b/selectors.py Thu Dec 08 14:32:57 2011 +0100 @@ -269,17 +269,23 @@ When there are several classes to be evaluated, return the sum of scores for each entity class unless: - - `once_is_enough` is False (the default) and some entity class is scored + - `mode` == 'all' (the default) and some entity class is scored to 0, in which case 0 is returned - - `once_is_enough` is True, in which case the first non-zero score is + - `mode` == 'any', in which case the first non-zero score is returned - `accept_none` is False and some cell in the column has a None value (this may occurs with outer join) """ - def __init__(self, once_is_enough=False, accept_none=True): - self.once_is_enough = once_is_enough + def __init__(self, once_is_enough=None, accept_none=True, mode='all'): + if once_is_enough is not None: + warn("[3.14] once_is_enough is deprecated, use mode='any'", + DeprecationWarning, stacklevel=2) + if once_is_enough: + mode = 'any' + assert mode in ('any', 'all'), 'bad mode %s' % mode + self.once_is_enough = mode == 'any' self.accept_none = accept_none @lltrace @@ -340,10 +346,10 @@ specified specified by the `col` argument or in column 0 if not specified, unless: - - `once_is_enough` is False (the default) and some entity is scored + - `mode` == 'all' (the default) and some entity class is scored to 0, in which case 0 is returned - - `once_is_enough` is True, in which case the first non-zero score is + - `mode` == 'any', in which case the first non-zero score is returned - `accept_none` is False and some cell in the column has a None value @@ -400,19 +406,36 @@ class ExpectedValueSelector(Selector): """Take a list of expected values as initializer argument and store them into the :attr:`expected` set attribute. You may also give a set as single - argument, which will be then be referenced as set of expected values, - allowing modification to the given set to be considered. + argument, which will then be referenced as set of expected values, + allowing modifications to the given set to be considered. + + You should implement one of :meth:`_values_set(cls, req, **kwargs)` or + :meth:`_get_value(cls, req, **kwargs)` method which should respectively + return the set of values or the unique possible value for the given context. + + You may also specify a `mode` behaviour as argument, as explained below. + + Returned score is: - You should implement the :meth:`_get_value(cls, req, **kwargs)` method - which should return the value for the given context. The selector will then - return 1 if the value is expected, else 0. + - 0 if `mode` == 'all' (the default) and at least one expected + values isn't found + + - 0 if `mode` == 'any' and no expected values isn't found at all + + - else the number of matching values + + Notice `mode` = 'any' with a single expected value has no effect at all. """ - def __init__(self, *expected): + def __init__(self, *expected, **kwargs): assert expected, self if len(expected) == 1 and isinstance(expected[0], set): self.expected = expected[0] else: self.expected = frozenset(expected) + mode = kwargs.pop('mode', 'all') + assert mode in ('any', 'all'), 'bad mode %s' % mode + self.once_is_enough = mode == 'any' + assert not kwargs, 'unexpected arguments %s' % kwargs def __str__(self): return '%s(%s)' % (self.__class__.__name__, @@ -420,10 +443,17 @@ @lltrace def __call__(self, cls, req, **kwargs): - if self._get_value(cls, req, **kwargs) in self.expected: - return 1 + values = self._values_set(cls, req, **kwargs) + matching = len(values & self.expected) + if self.once_is_enough: + return matching + if matching == len(self.expected): + return matching return 0 + def _values_set(self, cls, req, **kwargs): + return frozenset( (self._get_value(cls, req, **kwargs),) ) + def _get_value(self, cls, req, **kwargs): raise NotImplementedError() @@ -432,17 +462,18 @@ class match_kwargs(ExpectedValueSelector): """Return non-zero score if parameter names specified as initializer - arguments are specified in the input context. When multiple parameters are - specified, all of them should be specified in the input context. Return a - score corresponding to the number of expected parameters. + arguments are specified in the input context. + + + Return a score corresponding to the number of expected parameters. + + When multiple parameters are expected, all of them should be found in + the input context unless `mode` keyword argument is given to 'any', + in which case a single matching parameter is enough. """ - @lltrace - def __call__(self, cls, req, **kwargs): - for arg in self.expected: - if not arg in kwargs: - return 0 - return len(self.expected) + def _values_set(self, cls, req, **kwargs): + return frozenset(kwargs) class appobject_selectable(Selector): @@ -623,7 +654,7 @@ Page size is searched in (respecting order): * a `page_size` argument * a `page_size` form parameters - * the :ref:`navigation.page-size` property + * the `navigation.page-size` property (see :ref:`PersistentProperties`) """ def __init__(self, nbpages=1): assert nbpages > 0 @@ -842,8 +873,8 @@ See :class:`~cubicweb.selectors.EntitySelector` documentation for entity lookup / score rules according to the input context. """ - def __init__(self, scorefunc, once_is_enough=False): - super(score_entity, self).__init__(once_is_enough) + def __init__(self, scorefunc, once_is_enough=None, mode='all'): + super(score_entity, self).__init__(mode=mode, once_is_enough=once_is_enough) def intscore(*args, **kwargs): score = scorefunc(*args, **kwargs) if not score: @@ -860,8 +891,8 @@ You can give 'image/' to match any image for instance, or 'image/png' to match only PNG images. """ - def __init__(self, mimetype, once_is_enough=False): - super(has_mimetype, self).__init__(once_is_enough) + def __init__(self, mimetype, once_is_enough=None, mode='all'): + super(has_mimetype, self).__init__(mode=mode, once_is_enough=once_is_enough) self.mimetype = mimetype def score_entity(self, entity): @@ -996,12 +1027,7 @@ def complete(self, cls): self.rtype = cls.rtype self.role = role(cls) - self.target_etype = getattr(cls, 'etype', None) - if self.target_etype is not None: - warn('[3.6] please rename etype to target_etype on %s' % cls, - DeprecationWarning) - else: - self.target_etype = getattr(cls, 'target_etype', None) + self.target_etype = getattr(cls, 'target_etype', None) class has_related_entities(EntitySelector): @@ -1053,12 +1079,7 @@ def complete(self, cls): self.rtype = cls.rtype self.role = role(cls) - self.target_etype = getattr(cls, 'etype', None) - if self.target_etype is not None: - warn('[3.6] please rename etype to target_etype on %s' % cls, - DeprecationWarning) - else: - self.target_etype = getattr(cls, 'target_etype', None) + self.target_etype = getattr(cls, 'target_etype', None) class has_permission(EntitySelector): @@ -1173,8 +1194,8 @@ See :class:`~cubicweb.selectors.EntitySelector` documentation for entity lookup / score rules according to the input context. """ - def __init__(self, expression, once_is_enough=False, user_condition=False): - super(rql_condition, self).__init__(once_is_enough) + def __init__(self, expression, once_is_enough=None, mode='all', user_condition=False): + super(rql_condition, self).__init__(mode=mode, once_is_enough=once_is_enough) self.user_condition = user_condition if user_condition: rql = 'Any COUNT(U) WHERE U eid %%(u)s, %s' % expression @@ -1417,11 +1438,8 @@ @lltrace def __call__(self, cls, req, context=None, **kwargs): - try: - if not context in self.expected: - return 0 - except AttributeError: - return 1 # class doesn't care about search state, accept it + if not context in self.expected: + return 0 return 1 @@ -1474,17 +1492,41 @@ class match_form_params(ExpectedValueSelector): """Return non-zero score if parameter names specified as initializer - arguments are specified in request's form parameters. When multiple - parameters are specified, all of them should be found in req.form. Return a - score corresponding to the number of expected parameters. + arguments are specified in request's form parameters. + + Return a score corresponding to the number of expected parameters. + + When multiple parameters are expected, all of them should be found in + the input context unless `mode` keyword argument is given to 'any', + in which case a single matching parameter is enough. + """ + + def _values_set(self, cls, req, **kwargs): + return frozenset(req.form) + + +class match_edited_type(ExpectedValueSelector): + """return non-zero if main edited entity type is the one specified as + initializer argument, or is among initializer arguments if `mode` == 'any'. """ - @lltrace - def __call__(self, cls, req, **kwargs): - for param in self.expected: - if not param in req.form: - return 0 - return len(self.expected) + def _values_set(self, cls, req, **kwargs): + try: + return frozenset((req.form['__type:%s' % req.form['__maineid']],)) + except KeyError: + return frozenset() + + +class match_form_id(ExpectedValueSelector): + """return non-zero if request form identifier is the one specified as + initializer argument, or is among initializer arguments if `mode` == 'any'. + """ + + def _values_set(self, cls, req, **kwargs): + try: + return frozenset((req.form['__form_id'],)) + except KeyError: + return frozenset() class specified_etype_implements(is_instance): @@ -1537,8 +1579,8 @@ is_instance('Version') & (match_transition('ready') | attribute_edited('publication_date')) """ - def __init__(self, attribute, once_is_enough=False): - super(attribute_edited, self).__init__(once_is_enough) + def __init__(self, attribute, once_is_enough=None, mode='all'): + super(attribute_edited, self).__init__(mode=mode, once_is_enough=once_is_enough) self._attribute = attribute def score_entity(self, entity): @@ -1547,13 +1589,13 @@ # Other selectors ############################################################## - class match_exception(ExpectedValueSelector): - """Return 1 if a view is specified an as its registry id is in one of the - expected view id given to the initializer. + """Return 1 if exception given as `exc` in the input context is an instance + of one of the class given on instanciation of this predicate. """ def __init__(self, *expected): assert expected, self + # we want a tuple, not a set as done in the parent class self.expected = expected @lltrace @@ -1568,6 +1610,7 @@ """Return 1 if running in debug mode.""" return req.vreg.config.debugmode and 1 or 0 + ## deprecated stuff ############################################################ diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/__init__.py --- a/server/__init__.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/__init__.py Thu Dec 08 14:32:57 2011 +0100 @@ -148,20 +148,21 @@ repo = Repository(config, vreg=vreg) schema = repo.schema sourcescfg = config.sources() - _title = '-> creating tables ' - print _title, source = sourcescfg['system'] driver = source['db-driver'] sqlcnx = repo.system_source.get_connection() sqlcursor = sqlcnx.cursor() execute = sqlcursor.execute if drop: + _title = '-> drop tables ' dropsql = sqldropschema(schema, driver) try: - sqlexec(dropsql, execute) + sqlexec(dropsql, execute, pbtitle=_title) except Exception, ex: print '-> drop failed, skipped (%s).' % ex sqlcnx.rollback() + _title = '-> creating tables ' + print _title, # schema entities and relations tables # can't skip entities table even if system source doesn't support them, # they are used sometimes by generated sql. Keeping them empty is much diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/checkintegrity.py --- a/server/checkintegrity.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/checkintegrity.py Thu Dec 08 14:32:57 2011 +0100 @@ -36,9 +36,8 @@ def notify_fixed(fix): if fix: - print >> sys.stderr, ' [FIXED]' - else: - print >> sys.stderr + sys.stderr.write(' [FIXED]') + sys.stderr.write('\n') def has_eid(session, sqlcursor, eid, eids): """return true if the eid is a valid eid""" @@ -69,9 +68,9 @@ eids[eid] = False return False elif len(result) > 1: - msg = ' More than one entity with eid %s exists in source !' - print >> sys.stderr, msg % eid - print >> sys.stderr, ' WARNING : Unable to fix this, do it yourself !' + msg = (' More than one entity with eid %s exists in source !\n' + ' WARNING : Unable to fix this, do it yourself !\n') + sys.stderr.write(msg % eid) eids[eid] = True return True @@ -165,12 +164,12 @@ def check_text_index(schema, session, eids, fix=1): """check all entities registered in the text index""" print 'Checking text index' + msg = ' Entity with eid %s exists in the text index but in no source (autofix will remove from text index)' cursor = session.system_sql('SELECT uid FROM appears;') for row in cursor.fetchall(): eid = row[0] if not has_eid(session, cursor, eid, eids): - msg = ' Entity with eid %s exists in the text index but in no source' - print >> sys.stderr, msg % eid, + sys.stderr.write(msg % eid) if fix: session.system_sql('DELETE FROM appears WHERE uid=%s;' % eid) notify_fixed(fix) @@ -179,28 +178,64 @@ def check_entities(schema, session, eids, fix=1): """check all entities registered in the repo system table""" print 'Checking entities system table' + # system table but no source + msg = ' Entity with eid %s exists in the system table but in no source (autofix will delete the entity)' cursor = session.system_sql('SELECT eid FROM entities;') for row in cursor.fetchall(): eid = row[0] if not has_eid(session, cursor, eid, eids): - msg = ' Entity with eid %s exists in the system table but in no source' - print >> sys.stderr, msg % eid, + sys.stderr.write(msg % eid) if fix: session.system_sql('DELETE FROM entities WHERE eid=%s;' % eid) notify_fixed(fix) - session.system_sql('INSERT INTO cw_source_relation (eid_from, eid_to) ' - 'SELECT e.eid, s.cw_eid FROM entities as e, cw_CWSource as s ' - 'WHERE s.cw_name=e.asource AND NOT EXISTS(SELECT 1 FROM cw_source_relation as cs ' - ' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid)') - session.system_sql('INSERT INTO is_relation (eid_from, eid_to) ' - 'SELECT e.eid, s.cw_eid FROM entities as e, cw_CWEType as s ' - 'WHERE s.cw_name=e.type AND NOT EXISTS(SELECT 1 FROM is_relation as cs ' - ' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid)') - session.system_sql('INSERT INTO is_instance_of_relation (eid_from, eid_to) ' - 'SELECT e.eid, s.cw_eid FROM entities as e, cw_CWEType as s ' - 'WHERE s.cw_name=e.type AND NOT EXISTS(SELECT 1 FROM is_instance_of_relation as cs ' - ' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid)') + # source in entities, but no relation cw_source + applcwversion = session.repo.get_versions().get('cubicweb') + if applcwversion >= (3,13,1): # entities.asource appeared in 3.13.1 + cursor = session.system_sql('SELECT e.eid FROM entities as e, cw_CWSource as s ' + 'WHERE s.cw_name=e.asource AND ' + 'NOT EXISTS(SELECT 1 FROM cw_source_relation as cs ' + ' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid) ' + 'ORDER BY e.eid') + msg = (' Entity with eid %s refers to source in entities table, ' + 'but is missing relation cw_source (autofix will create the relation)\n') + for row in cursor.fetchall(): + sys.stderr.write(msg % row[0]) + if fix: + session.system_sql('INSERT INTO cw_source_relation (eid_from, eid_to) ' + 'SELECT e.eid, s.cw_eid FROM entities as e, cw_CWSource as s ' + 'WHERE s.cw_name=e.asource AND NOT EXISTS(SELECT 1 FROM cw_source_relation as cs ' + ' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid)') + notify_fixed(True) + # inconsistencies for 'is' + msg = ' %s #%s is missing relation "is" (autofix will create the relation)\n' + cursor = session.system_sql('SELECT e.type, e.eid FROM entities as e, cw_CWEType as s ' + 'WHERE s.cw_name=e.type AND NOT EXISTS(SELECT 1 FROM is_relation as cs ' + ' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid) ' + 'ORDER BY e.eid') + for row in cursor.fetchall(): + sys.stderr.write(msg % row) + if fix: + session.system_sql('INSERT INTO is_relation (eid_from, eid_to) ' + 'SELECT e.eid, s.cw_eid FROM entities as e, cw_CWEType as s ' + 'WHERE s.cw_name=e.type AND NOT EXISTS(SELECT 1 FROM is_relation as cs ' + ' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid)') + notify_fixed(True) + # inconsistencies for 'is_instance_of' + msg = ' %s #%s is missing relation "is_instance_of" (autofix will create the relation)\n' + cursor = session.system_sql('SELECT e.type, e.eid FROM entities as e, cw_CWEType as s ' + 'WHERE s.cw_name=e.type AND NOT EXISTS(SELECT 1 FROM is_instance_of_relation as cs ' + ' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid) ' + 'ORDER BY e.eid') + for row in cursor.fetchall(): + sys.stderr.write(msg % row) + if fix: + session.system_sql('INSERT INTO is_instance_of_relation (eid_from, eid_to) ' + 'SELECT e.eid, s.cw_eid FROM entities as e, cw_CWEType as s ' + 'WHERE s.cw_name=e.type AND NOT EXISTS(SELECT 1 FROM is_instance_of_relation as cs ' + ' WHERE cs.eid_from=e.eid AND cs.eid_to=s.cw_eid)') + notify_fixed(True) print 'Checking entities tables' + msg = ' Entity with eid %s exists in the %s table but not in the system table (autofix will delete the entity)' for eschema in schema.entities(): if eschema.final: continue @@ -212,8 +247,7 @@ # eids is full since we have fetched everything from the entities table, # no need to call has_eid if not eid in eids or not eids[eid]: - msg = ' Entity with eid %s exists in the %s table but not in the system table' - print >> sys.stderr, msg % (eid, eschema.type), + sys.stderr.write(msg % (eid, eschema.type)) if fix: session.system_sql('DELETE FROM %s WHERE %s=%s;' % (table, column, eid)) notify_fixed(fix) @@ -221,7 +255,7 @@ def bad_related_msg(rtype, target, eid, fix): msg = ' A relation %s with %s eid %s exists but no such entity in sources' - print >> sys.stderr, msg % (rtype, target, eid), + sys.stderr.write(msg % (rtype, target, eid)) notify_fixed(fix) @@ -231,7 +265,7 @@ """ print 'Checking relations' for rschema in schema.relations(): - if rschema.final or rschema in PURE_VIRTUAL_RTYPES: + if rschema.final or rschema.type in PURE_VIRTUAL_RTYPES: continue if rschema.inlined: for subjtype in rschema.subjects(): @@ -277,8 +311,9 @@ def check_mandatory_relations(schema, session, eids, fix=1): """check entities missing some mandatory relation""" print 'Checking mandatory relations' + msg = '%s #%s is missing mandatory %s relation %s (autofix will delete the entity)' for rschema in schema.relations(): - if rschema.final or rschema in PURE_VIRTUAL_RTYPES: + if rschema.final or rschema.type in PURE_VIRTUAL_RTYPES: continue smandatory = set() omandatory = set() @@ -294,11 +329,10 @@ else: rql = 'Any X WHERE NOT Y %s X, X is %s' % (rschema, etype) for entity in session.execute(rql).entities(): - print >> sys.stderr, '%s #%s is missing mandatory %s relation %s' % ( - entity.__regid__, entity.eid, role, rschema), + sys.stderr.write(msg % (entity.__regid__, entity.eid, role, rschema)) if fix: #if entity.cw_describe()['source']['uri'] == 'system': XXX - entity.cw_delete() + entity.cw_delete() # XXX this is BRUTAL! notify_fixed(fix) @@ -307,6 +341,7 @@ attribute """ print 'Checking mandatory attributes' + msg = '%s #%s is missing mandatory attribute %s (autofix will delete the entity)' for rschema in schema.relations(): if not rschema.final or rschema in VIRTUAL_RTYPES: continue @@ -315,8 +350,7 @@ rql = 'Any X WHERE X %s NULL, X is %s, X cw_source S, S name "system"' % ( rschema, rdef.subject) for entity in session.execute(rql).entities(): - print >> sys.stderr, '%s #%s is missing mandatory attribute %s' % ( - entity.__regid__, entity.eid, rschema), + sys.stderr.write(msg % (entity.__regid__, entity.eid, rschema)) if fix: entity.cw_delete() notify_fixed(fix) @@ -330,6 +364,7 @@ print 'Checking metadata' cursor = session.system_sql("SELECT DISTINCT type FROM entities;") eidcolumn = SQL_PREFIX + 'eid' + msg = ' %s with eid %s has no %s (autofix will set it to now)' for etype, in cursor.fetchall(): table = SQL_PREFIX + etype for rel, default in ( ('creation_date', datetime.now()), @@ -338,8 +373,7 @@ cursor = session.system_sql("SELECT %s FROM %s WHERE %s is NULL" % (eidcolumn, table, column)) for eid, in cursor.fetchall(): - msg = ' %s with eid %s has no %s' - print >> sys.stderr, msg % (etype, eid, rel), + sys.stderr.write(msg % (etype, eid, rel)) if fix: session.system_sql("UPDATE %s SET %s=%%(v)s WHERE %s=%s ;" % (table, column, eidcolumn, eid), diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/hook.py --- a/server/hook.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/hook.py Thu Dec 08 14:32:57 2011 +0100 @@ -291,12 +291,6 @@ class HooksRegistry(CWRegistry): - def initialization_completed(self): - for appobjects in self.values(): - for cls in appobjects: - if not cls.enabled: - warn('[3.6] %s: enabled is deprecated' % classid(cls)) - self.unregister(cls) def register(self, obj, **kwargs): obj.check_events() @@ -450,8 +444,8 @@ """accept if parameters specified as initializer arguments are specified in named arguments given to the selector - :param *expected: parameters (eg `basestring`) which are expected to be - found in named arguments (kwargs) + :param \*expected: parameters (eg `basestring`) which are expected to be + found in named arguments (kwargs) """ def __init__(self, *expected, **more): self.expected = expected @@ -534,8 +528,6 @@ events = None category = None order = 0 - # XXX deprecated - enabled = True # stop pylint from complaining about missing attributes in Hooks classes eidfrom = eidto = entity = rtype = repo = None @@ -567,28 +559,6 @@ cls.check_events() return ['%s_hooks' % ev for ev in cls.events] - @classproperty - def __regid__(cls): - warn('[3.6] %s: please specify an id for your hook' % classid(cls), - DeprecationWarning) - return str(id(cls)) - - @classmethod - def __registered__(cls, reg): - super(Hook, cls).__registered__(reg) - if getattr(cls, 'accepts', None): - warn('[3.6] %s: accepts is deprecated, define proper __select__' - % classid(cls), DeprecationWarning) - rtypes = [] - for ertype in cls.accepts: # pylint: disable=E1101 - if ertype.islower(): - rtypes.append(ertype) - else: - cls.__select__ = cls.__select__ & is_instance(ertype) - if rtypes: - cls.__select__ = cls.__select__ & match_rtype(*rtypes) - return cls - known_args = set(('entity', 'rtype', 'eidfrom', 'eidto', 'repo', 'timestamp')) def __init__(self, req, event, **kwargs): for arg in self.known_args: @@ -597,22 +567,6 @@ super(Hook, self).__init__(req, **kwargs) self.event = event - def __call__(self): - if hasattr(self, 'call'): - warn('[3.6] %s: call is deprecated, implement __call__' - % classid(self.__class__), DeprecationWarning) - # pylint: disable=E1101 - if self.event.endswith('_relation'): - self.call(self._cw, self.eidfrom, self.rtype, self.eidto) - elif 'delete' in self.event: - self.call(self._cw, self.entity.eid) - elif self.event.startswith('server_'): - self.call(self.repo) - elif self.event.startswith('session_'): - self.call(self._cw) - else: - self.call(self._cw, self.entity) - set_log_methods(Hook, getLogger('cubicweb.hook')) @@ -831,26 +785,6 @@ def postcommit_event(self): """the observed connections set has committed""" - @property - @deprecated('[3.6] use self.session.user') - def user(self): - return self.session.user - - @property - @deprecated('[3.6] use self.session.repo') - def repo(self): - return self.session.repo - - @property - @deprecated('[3.6] use self.session.vreg.schema') - def schema(self): - return self.session.repo.schema - - @property - @deprecated('[3.6] use self.session.vreg.config') - def config(self): - return self.session.repo.config - # these are overridden by set_log_methods below # only defining here to prevent pylint from complaining info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/hookhelper.py --- a/server/hookhelper.py Thu Dec 08 14:29:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -# copyright 2003-2010 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 functions for application hooks - -""" -__docformat__ = "restructuredtext en" - -from logilab.common.deprecation import deprecated, class_moved - -from cubicweb.server import hook - -@deprecated('[3.6] entity_oldnewvalue should be imported from cw.server.hook') -def entity_oldnewvalue(entity, attr): - return hook.entity_oldnewvalue(entity, attr) - -@deprecated('[3.6] entity_name is deprecated, use entity.name') -def entity_name(session, eid): - """return the "name" attribute of the entity with the given eid""" - return session.entity_from_eid(eid).name - -@deprecated('[3.6] rproperty is deprecated, use session.schema_rproperty') -def rproperty(session, rtype, eidfrom, eidto, rprop): - return session.rproperty(rtype, eidfrom, eidto, rprop) - -SendMailOp = class_moved(hook.SendMailOp) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/migractions.py --- a/server/migractions.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/migractions.py Thu Dec 08 14:32:57 2011 +0100 @@ -117,7 +117,15 @@ # which is called on regular start repo.hm.call_hooks('server_maintenance', repo=repo) if not schema and not getattr(config, 'quick_start', False): - schema = config.load_schema(expand_cubes=True) + insert_lperms = self.repo.get_versions()['cubicweb'] < (3, 14, 0) and 'localperms' in config.available_cubes() + if insert_lperms: + cubes = config._cubes + config._cubes += ('localperms',) + try: + schema = config.load_schema(expand_cubes=True) + finally: + if insert_lperms: + config._cubes = cubes self.fs_schema = schema self._synchronized = set() @@ -152,7 +160,7 @@ return super(ServerMigrationHelper, self).cmd_process_script( migrscript, funcname, *args, **kwargs) except ExecutionError, err: - print >> sys.stderr, "-> %s" % err + sys.stderr.write("-> %s\n" % err) except BaseException: self.rollback() raise @@ -325,7 +333,6 @@ context = super(ServerMigrationHelper, self)._create_context() context.update({'commit': self.checkpoint, 'rollback': self.rollback, - 'checkpoint': deprecated('[3.6] use commit')(self.checkpoint), 'sql': self.sqlexec, 'rql': self.rqlexec, 'rqliter': self.rqliter, @@ -334,9 +341,6 @@ 'fsschema': self.fs_schema, 'session' : self.session, 'repo' : self.repo, - 'synchronize_schema': deprecated()(self.cmd_sync_schema_props_perms), # 3.4 - 'synchronize_eschema': deprecated()(self.cmd_sync_schema_props_perms), # 3.4 - 'synchronize_rschema': deprecated()(self.cmd_sync_schema_props_perms), # 3.4 }) return context @@ -389,14 +393,7 @@ directory = osp.join(CW_SOFTWARE_ROOT, 'schemas') else: directory = osp.join(self.config.cube_dir(cube), 'schema') - sql_scripts = [] - for fpath in glob(osp.join(directory, '*.sql.%s' % driver)): - newname = osp.basename(fpath).replace('.sql.%s' % driver, - '.%s.sql' % driver) - warn('[3.5.6] rename %s into %s' % (fpath, newname), - DeprecationWarning) - sql_scripts.append(fpath) - sql_scripts += glob(osp.join(directory, '*.%s.sql' % driver)) + sql_scripts = glob(osp.join(directory, '*.%s.sql' % driver)) for fpath in sql_scripts: print '-> installing', fpath try: @@ -1241,10 +1238,6 @@ if commit: self.commit() - @deprecated('[3.2] use sync_schema_props_perms(ertype, syncprops=False)') - def cmd_synchronize_permissions(self, ertype, commit=True): - self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit) - # Workflows handling ###################################################### def cmd_make_workflowable(self, etype): @@ -1300,62 +1293,6 @@ {'et': etype}) return rset.get_entity(0, 0) - # XXX remove once cmd_add_[state|transition] are removed - def _get_or_create_wf(self, etypes): - if not isinstance(etypes, (list, tuple)): - etypes = (etypes,) - rset = self.rqlexec('Workflow X WHERE X workflow_of ET, ET name %(et)s', - {'et': etypes[0]}) - if rset: - return rset.get_entity(0, 0) - return self.cmd_add_workflow('%s workflow' % ';'.join(etypes), etypes) - - @deprecated('[3.5] use add_workflow and Workflow.add_state method', - stacklevel=3) - def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs): - """method to ease workflow definition: add a state for one or more - entity type(s) - """ - wf = self._get_or_create_wf(stateof) - state = wf.add_state(name, initial, **kwargs) - if commit: - self.commit() - return state.eid - - @deprecated('[3.5] use add_workflow and Workflow.add_transition method', - stacklevel=3) - def cmd_add_transition(self, name, transitionof, fromstates, tostate, - requiredgroups=(), conditions=(), commit=False, **kwargs): - """method to ease workflow definition: add a transition for one or more - entity type(s), from one or more state and to a single state - """ - wf = self._get_or_create_wf(transitionof) - tr = wf.add_transition(name, fromstates, tostate, requiredgroups, - conditions, **kwargs) - if commit: - self.commit() - return tr.eid - - @deprecated('[3.5] use Transition.set_transition_permissions method', - stacklevel=3) - def cmd_set_transition_permissions(self, treid, - requiredgroups=(), conditions=(), - reset=True, commit=False): - """set or add (if `reset` is False) groups and conditions for a - transition - """ - tr = self._cw.entity_from_eid(treid) - tr.set_transition_permissions(requiredgroups, conditions, reset) - if commit: - self.commit() - - @deprecated('[3.5] use iworkflowable.fire_transition("transition") or ' - 'iworkflowable.change_state("state")', stacklevel=3) - def cmd_set_state(self, eid, statename, commit=False): - self._cw.entity_from_eid(eid).cw_adapt_to('IWorkflowable').change_state(statename) - if commit: - self.commit() - # CWProperty handling ###################################################### def cmd_property_value(self, pkey): @@ -1450,11 +1387,6 @@ from cubicweb.server.checkintegrity import reindex_entities reindex_entities(self.repo.schema, self.session, etypes=etypes) - @deprecated('[3.5] use create_entity', stacklevel=3) - def cmd_add_entity(self, etype, *args, **kwargs): - """add a new entity of the given type""" - return self.cmd_create_entity(etype, *args, **kwargs).eid - @contextmanager def cmd_dropped_constraints(self, etype, attrname, cstrtype=None, droprequired=False): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/querier.py --- a/server/querier.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/querier.py Thu Dec 08 14:32:57 2011 +0100 @@ -25,7 +25,6 @@ from itertools import repeat -from logilab.common.cache import Cache from logilab.common.compat import any from rql import RQLSyntaxError from rql.stmts import Union, Select @@ -36,6 +35,7 @@ from cubicweb import server, typed_eid from cubicweb.rset import ResultSet +from cubicweb.utils import QueryCache 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 @@ -599,7 +599,7 @@ self.schema = schema repo = self._repo # rql st and solution cache. - self._rql_cache = Cache(repo.config['rql-cache-size']) + self._rql_cache = QueryCache(repo.config['rql-cache-size']) # rql cache key cache. Don't bother using a Cache instance: we should # have a limited number of queries in there, since there are no entries # in this cache for user queries (which have no args) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/repository.py --- a/server/repository.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/repository.py Thu Dec 08 14:32:57 2011 +0100 @@ -60,8 +60,7 @@ security_enabled from cubicweb.server.ssplanner import EditedEntity -NO_CACHE_RELATIONS = set( [('require_permission', 'object'), - ('owned_by', 'object'), +NO_CACHE_RELATIONS = set( [('owned_by', 'object'), ('created_by', 'object'), ('cw_source', 'object'), ]) @@ -460,8 +459,9 @@ def _build_user(self, session, eid): """return a CWUser entity for user with the given eid""" cls = self.vreg['etypes'].etype_class('CWUser') - rql = cls.fetch_rql(session.user, ['X eid %(x)s']) - rset = session.execute(rql, {'x': eid}) + st = cls.fetch_rqlst(session.user, ordermethod=None) + st.add_eid_restriction(st.get_variable('X'), 'x', 'Substitute') + rset = session.execute(st.as_string(), {'x': eid}) assert len(rset) == 1, rset cwuser = rset.get_entity(0, 0) # pylint: disable=W0104 @@ -528,6 +528,8 @@ This is a public method, not requiring a session id. """ # XXX we may want to check we don't give sensible information + # XXX the only cube using 'foreid', apycot, stop used this, we probably + # want to drop this argument if foreid is None: return self.config[option] _, sourceuri, extid, _ = self.type_and_source_from_eid(foreid) @@ -1085,6 +1087,9 @@ entity = source.before_entity_insertion( session, extid, etype, eid, sourceparams) if source.should_call_hooks: + # get back a copy of operation for later restore if necessary, + # see below + pending_operations = session.pending_operations[:] self.hm.call_hooks('before_add_entity', session, entity=entity) self.add_info(session, entity, source, extid, complete=complete) source.after_entity_insertion(session, extid, entity, sourceparams) @@ -1096,6 +1101,16 @@ except Exception: if commit or free_cnxset: session.rollback(free_cnxset) + else: + # XXX do some cleanup manually so that the transaction has a + # chance to be commited, with simply this entity discarded + self._extid_cache.pop(cachekey, None) + self._type_source_cache.pop(eid, None) + if 'entity' in locals(): + hook.CleanupDeletedEidsCacheOp.get_instance(session).add_data(entity.eid) + self.system_source.delete_info_multi(session, [entity], uri) + if source.should_call_hooks: + session._threaddata.pending_operations = pending_operations raise def add_info(self, session, entity, source, extid=None, complete=True): @@ -1200,6 +1215,13 @@ rql += ', NOT (Y cw_source S, S eid %(seid)s)' try: session.execute(rql, {'seid': scleanup}, build_descr=False) + except ValidationError: + raise + except Unauthorized: + self.exception('Unauthorized exception while cascading delete for entity %s ' + 'from %s. RQL: %s.\nThis should not happen since security is disabled here.', + entities, sourceuri, rql) + raise except Exception: if self.config.mode == 'test': raise diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/serverconfig.py --- a/server/serverconfig.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/serverconfig.py Thu Dec 08 14:32:57 2011 +0100 @@ -302,9 +302,7 @@ if attr != 'adapter': self.error('skip unknown option %s in sources file') sconfig = _sconfig - print >> stream, '[%s]' % section - print >> stream, generate_source_config(sconfig) - print >> stream + stream.write('[%s]\n%s\n' % (section, generate_source_config(sconfig))) restrict_perms_to_user(sourcesfile) def pyro_enabled(self): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/session.py --- a/server/session.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/session.py Thu Dec 08 14:32:57 2011 +0100 @@ -28,6 +28,7 @@ from warnings import warn from logilab.common.deprecation import deprecated +from logilab.common.textutils import unormalize from rql import CoercionError from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj from yams import BASE_TYPES @@ -244,7 +245,7 @@ def __init__(self, user, repo, cnxprops=None, _id=None): super(Session, self).__init__(repo.vreg) - self.id = _id or make_uid(user.login.encode('UTF8')) + self.id = _id or make_uid(unormalize(user.login).encode('UTF8')) cnxprops = cnxprops or ConnectionProperties('inmemory') self.user = user self.repo = repo @@ -1253,31 +1254,6 @@ """return the original parent session if any, else self""" return self - @property - @deprecated("[3.6] use session.vreg.schema") - def schema(self): - return self.repo.schema - - @deprecated("[3.4] use vreg['etypes'].etype_class(etype)") - def etype_class(self, etype): - """return an entity class for the given entity type""" - return self.vreg['etypes'].etype_class(etype) - - @deprecated('[3.4] use direct access to session.transaction_data') - def query_data(self, key, default=None, setdefault=False, pop=False): - if setdefault: - assert not pop - return self.transaction_data.setdefault(key, default) - if pop: - return self.transaction_data.pop(key, default) - else: - return self.transaction_data.get(key, default) - - @deprecated('[3.4] use entity_from_eid(eid, etype=None)') - def entity(self, eid): - """return a result set for the given eid""" - return self.entity_from_eid(eid) - # these are overridden by set_log_methods below # only defining here to prevent pylint from complaining info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None @@ -1324,9 +1300,6 @@ def owns(self, eid): return True - def has_permission(self, pname, contexteid=None): - return True - def property_value(self, key): if key == 'ui.language': return 'en' diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/sources/datafeed.py --- a/server/sources/datafeed.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/sources/datafeed.py Thu Dec 08 14:32:57 2011 +0100 @@ -27,6 +27,7 @@ from cookielib import CookieJar from lxml import etree +from logilab.mtconverter import xml_escape from cubicweb import RegistryNotFound, ObjectNotFound, ValidationError, UnknownEid from cubicweb.server.sources import AbstractSource @@ -71,7 +72,12 @@ 'external source be deleted?'), 'group': 'datafeed-source', 'level': 2, }), - + ('logs-lifetime', + {'type': 'time', + 'default': '10d', + 'help': ('Time before logs from datafeed imports are deleted.'), + 'group': 'datafeed-source', 'level': 2, + }), ) def __init__(self, repo, source_config, eid=None): AbstractSource.__init__(self, repo, source_config, eid) @@ -91,9 +97,11 @@ source_entity.complete() self.parser_id = source_entity.parser self.latest_retrieval = source_entity.latest_retrieval - self.urls = [url.strip() for url in source_entity.url.splitlines() - if url.strip()] - + if source_entity.url: + self.urls = [url.strip() for url in source_entity.url.splitlines() + if url.strip()] + else: + self.urls = [] def update_config(self, source_entity, typedconfig): """update configuration from source entity. `typedconfig` is config properly typed with defaults set @@ -186,7 +194,8 @@ myuris = self.source_cwuris(session) else: myuris = None - parser = self._get_parser(session, sourceuris=myuris) + importlog = self.init_import_log(session) + parser = self._get_parser(session, sourceuris=myuris, import_log=importlog) if self.process_urls(parser, self.urls, raise_on_error): self.warning("some error occured, don't attempt to delete entities") elif self.config['delete-entities'] and myuris: @@ -198,7 +207,13 @@ session.execute('DELETE %s X WHERE X eid IN (%s)' % (etype, ','.join(eids))) self.update_latest_retrieval(session) - return parser.stats + stats = parser.stats + if stats.get('created'): + importlog.record_info('added %s entities' % len(stats['created'])) + if stats.get('updated'): + importlog.record_info('updated %s entities' % len(stats['updated'])) + importlog.write_log(session, end_timestamp=self.latest_retrieval) + return stats def process_urls(self, parser, urls, raise_on_error=False): error = False @@ -210,8 +225,9 @@ except IOError, exc: if raise_on_error: raise - self.error('could not pull data while processing %s: %s', - url, exc) + parser.import_log.record_error( + 'could not pull data while processing %s: %s' + % (url, exc)) error = True except Exception, exc: if raise_on_error: @@ -253,14 +269,21 @@ return dict((b64decode(uri), (eid, type)) for uri, eid, type in session.system_sql(sql)) + def init_import_log(self, session, **kwargs): + dataimport = session.create_entity('CWDataImport', cw_import_of=self, + start_timestamp=datetime.utcnow(), + **kwargs) + dataimport.init() + return dataimport class DataFeedParser(AppObject): __registry__ = 'parsers' - def __init__(self, session, source, sourceuris=None, **kwargs): + def __init__(self, session, source, sourceuris=None, import_log=None, **kwargs): super(DataFeedParser, self).__init__(session, **kwargs) self.source = source self.sourceuris = sourceuris + self.import_log = import_log self.stats = {'created': set(), 'updated': set()} @@ -295,7 +318,11 @@ complete=False, commit=False, sourceparams=sourceparams) except ValidationError, ex: - self.source.error('error while creating %s: %s', etype, ex) + # XXX use critical so they are seen during tests. Should consider + # raise_on_error instead? + self.source.critical('error while creating %s: %s', etype, ex) + self.import_log.record_error('error while creating %s: %s' + % (etype, ex)) return None if eid < 0: # entity has been moved away from its original source @@ -341,7 +368,7 @@ except Exception, ex: if raise_on_error: raise - self.source.error(str(ex)) + self.import_log.record_error(str(ex)) return True error = False for args in parsed: diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/sources/native.py --- a/server/sources/native.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/sources/native.py Thu Dec 08 14:32:57 2011 +0100 @@ -46,7 +46,6 @@ import sys from logilab.common.compat import any -from logilab.common.cache import Cache from logilab.common.decorators import cached, clear_cache from logilab.common.configuration import Method from logilab.common.shellutils import getlogin @@ -58,6 +57,7 @@ from cubicweb import (UnknownEid, AuthenticationError, ValidationError, Binary, UniqueTogetherError) from cubicweb import transaction as tx, server, neg_role +from cubicweb.utils import QueryCache from cubicweb.schema import VIRTUAL_RTYPES from cubicweb.cwconfig import CubicWebNoAppConfiguration from cubicweb.server import hook @@ -295,7 +295,7 @@ # full text index helper self.do_fti = not repo.config['delay-full-text-indexation'] # sql queries cache - self._cache = Cache(repo.config['rql-cache-size']) + self._cache = QueryCache(repo.config['rql-cache-size']) self._temp_table_data = {} # we need a lock to protect eid attribution function (XXX, really? # explain) @@ -343,7 +343,7 @@ def reset_caches(self): """method called during test to reset potential source caches""" - self._cache = Cache(self.repo.config['rql-cache-size']) + self._cache = QueryCache(self.repo.config['rql-cache-size']) def clear_eid_cache(self, eid, etype): """clear potential caches for the given eid""" @@ -463,7 +463,7 @@ def set_schema(self, schema): """set the instance'schema""" - self._cache = Cache(self.repo.config['rql-cache-size']) + self._cache = QueryCache(self.repo.config['rql-cache-size']) self.cache_hit, self.cache_miss, self.no_cache = 0, 0, 0 self.schema = schema try: diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/sqlutils.py --- a/server/sqlutils.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/sqlutils.py Thu Dec 08 14:32:57 2011 +0100 @@ -129,7 +129,7 @@ dbhelper = db.get_db_helper(driver) w(dbhelper.sql_drop_fti()) w('') - w(dropschema2sql(schema, prefix=SQL_PREFIX, + w(dropschema2sql(dbhelper, schema, prefix=SQL_PREFIX, skip_entities=skip_entities, skip_relations=skip_relations)) w('') diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/ssplanner.py --- a/server/ssplanner.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/ssplanner.py Thu Dec 08 14:32:57 2011 +0100 @@ -103,28 +103,26 @@ When select has nothing selected, search in origrqlst for restriction that should be considered. """ + if origrqlst.where is not None and not select.selection: + # no selection, append one randomly by searching for a relation which is + # neither a type restriction (is) nor an eid specification (not neged + # eid with constant node) + for rel in origrqlst.where.iget_nodes(Relation): + if rel.neged(strict=True) or not ( + rel.is_types_restriction() or + (rel.r_type == 'eid' + and isinstance(rel.get_variable_parts()[1], Constant))): + select.append_selected(rel.children[0].copy(select)) + break + else: + return if select.selection: if origrqlst.where is not None: select.set_where(origrqlst.where.copy(select)) + if getattr(origrqlst, 'having', None): + select.set_having([sq.copy(select) for sq in origrqlst.having]) return select - if origrqlst.where is None: - return - for rel in origrqlst.where.iget_nodes(Relation): - # search for a relation which is neither a type restriction (is) nor an - # eid specification (not neged eid with constant node - if rel.neged(strict=True) or not ( - rel.is_types_restriction() or - (rel.r_type == 'eid' - and isinstance(rel.get_variable_parts()[1], Constant))): - break - else: - return - select.set_where(origrqlst.where.copy(select)) - if not select.selection: - # no selection, append one randomly - select.append_selected(rel.children[0].copy(select)) - return select - + return None class SSPlanner(object): """SingleSourcePlanner: build execution plan for rql queries @@ -204,38 +202,40 @@ steps = [] for etype, var in rqlst.main_variables: step = DeleteEntitiesStep(plan) - step.children += self._sel_variable_step(plan, rqlst.solutions, - rqlst.where, etype, var) + step.children += self._sel_variable_step(plan, rqlst, etype, var) steps.append(step) for relation in rqlst.main_relations: step = DeleteRelationsStep(plan, relation.r_type) - step.children += self._sel_relation_steps(plan, rqlst.solutions, - rqlst.where, relation) + step.children += self._sel_relation_steps(plan, rqlst, relation) steps.append(step) return steps - def _sel_variable_step(self, plan, solutions, restriction, etype, varref): + def _sel_variable_step(self, plan, rqlst, etype, varref): """handle the selection of variables for a delete query""" select = Select() varref = varref.copy(select) select.defined_vars = {varref.name: varref.variable} select.append_selected(varref) - if restriction is not None: - select.set_where(restriction.copy(select)) + if rqlst.where is not None: + select.set_where(rqlst.where.copy(select)) + if getattr(rqlst, 'having', None): + select.set_having([x.copy(select) for x in rqlst.having]) if etype != 'Any': select.add_type_restriction(varref.variable, etype) - return self._select_plan(plan, select, solutions) + return self._select_plan(plan, select, rqlst.solutions) - def _sel_relation_steps(self, plan, solutions, restriction, relation): + def _sel_relation_steps(self, plan, rqlst, relation): """handle the selection of relations for a delete query""" select = Select() lhs, rhs = relation.get_variable_parts() select.append_selected(lhs.copy(select)) select.append_selected(rhs.copy(select)) select.set_where(relation.copy(select)) - if restriction is not None: - select.add_restriction(restriction.copy(select)) - return self._select_plan(plan, select, solutions) + if rqlst.where is not None: + select.add_restriction(rqlst.where.copy(select)) + if getattr(rqlst, 'having', None): + select.set_having([x.copy(select) for x in rqlst.having]) + return self._select_plan(plan, select, rqlst.solutions) def build_set_plan(self, plan, rqlst): """get an execution plan from an SET RQL query""" diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/data/bootstrap_cubes --- a/server/test/data/bootstrap_cubes Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/data/bootstrap_cubes Thu Dec 08 14:32:57 2011 +0100 @@ -1,1 +1,1 @@ -card,comment,folder,tag,basket,email,file +card,comment,folder,tag,basket,email,file,localperms diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/data/migratedapp/schema.py --- a/server/test/data/migratedapp/schema.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/data/migratedapp/schema.py Thu Dec 08 14:32:57 2011 +0100 @@ -120,6 +120,10 @@ concerne2 = SubjectRelation(('Affaire', 'Note'), cardinality='1*') connait = SubjectRelation('Personne', symmetric=True) + +class New(EntityType): + new_name = String() + class Societe(WorkflowableEntityType): __permissions__ = { 'read': ('managers', 'users', 'guests'), diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/data/schema.py --- a/server/test/data/schema.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/data/schema.py Thu Dec 08 14:32:57 2011 +0100 @@ -128,6 +128,9 @@ inline2 = SubjectRelation('Affaire', inlined=True, cardinality='?*') +class Old(EntityType): + name = String() + class connait(RelationType): symmetric = True diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_checkintegrity.py --- a/server/test/unittest_checkintegrity.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_checkintegrity.py Thu Dec 08 14:32:57 2011 +0100 @@ -46,9 +46,9 @@ def test_reindex_all(self): self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"') self.session.commit(False) - self.failUnless(self.execute('Any X WHERE X has_text "tutu"')) + self.assertTrue(self.execute('Any X WHERE X has_text "tutu"')) reindex_entities(self.repo.schema, self.session, withpb=False) - self.failUnless(self.execute('Any X WHERE X has_text "tutu"')) + self.assertTrue(self.execute('Any X WHERE X has_text "tutu"')) def test_reindex_etype(self): self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"') @@ -56,8 +56,8 @@ self.session.commit(False) reindex_entities(self.repo.schema, self.session, withpb=False, etypes=('Personne',)) - self.failUnless(self.execute('Any X WHERE X has_text "tutu"')) - self.failUnless(self.execute('Any X WHERE X has_text "toto"')) + self.assertTrue(self.execute('Any X WHERE X has_text "tutu"')) + self.assertTrue(self.execute('Any X WHERE X has_text "toto"')) if __name__ == '__main__': unittest_main() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_datafeed.py --- a/server/test/unittest_datafeed.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_datafeed.py Thu Dec 08 14:32:57 2011 +0100 @@ -119,8 +119,8 @@ req = self.request() req.execute('DELETE CWSource S WHERE S name "myrenamedfeed"') self.commit() - self.failIf(self.execute('Card X WHERE X title "cubicweb.org"')) - self.failIf(self.execute('Any X WHERE X has_text "cubicweb.org"')) + self.assertFalse(self.execute('Card X WHERE X title "cubicweb.org"')) + self.assertFalse(self.execute('Any X WHERE X has_text "cubicweb.org"')) if __name__ == '__main__': from logilab.common.testlib import unittest_main diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_ldapuser.py --- a/server/test/unittest_ldapuser.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_ldapuser.py Thu Dec 08 14:32:57 2011 +0100 @@ -262,7 +262,7 @@ def test_multiple_entities_from_different_sources(self): req = self.request() self.create_user(req, 'cochon') - self.failUnless(self.sexecute('Any X,Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT})) + self.assertTrue(self.sexecute('Any X,Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT})) def test_exists1(self): self.session.set_cnxset() @@ -288,9 +288,9 @@ self.create_user(req, 'comme') self.create_user(req, 'cochon') self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"') - self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"')) + self.assertTrue(self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"')) self.sexecute('SET X copain Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT}) - self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login %(syt)s, Y login "cochon"', {'syt': SYT})) + self.assertTrue(self.sexecute('Any X, Y WHERE X copain Y, X login %(syt)s, Y login "cochon"', {'syt': SYT})) rset = self.sexecute('Any GN,L WHERE X in_group G, X login L, G name GN, G name "managers" ' 'OR EXISTS(X copain T, T login in ("comme", "cochon"))') self.assertEqual(sorted(rset.rows), [['managers', 'admin'], ['users', 'comme'], ['users', SYT]]) @@ -392,8 +392,8 @@ 'type': 'CWUser', 'extid': None}) self.assertEqual(e.cw_source[0].name, 'system') - self.failUnless(e.creation_date) - self.failUnless(e.modification_date) + self.assertTrue(e.creation_date) + self.assertTrue(e.modification_date) # XXX test some password has been set source.synchronize() rset = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT}) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_migractions.py --- a/server/test/unittest_migractions.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_migractions.py Thu Dec 08 14:32:57 2011 +0100 @@ -74,13 +74,13 @@ self.repo.vreg['etypes'].clear_caches() def test_add_attribute_int(self): - self.failIf('whatever' in self.schema) + self.assertFalse('whatever' in self.schema) self.request().create_entity('Note') self.commit() orderdict = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, ' 'RDEF relation_type RT, RDEF ordernum O, RT name RTN')) self.mh.cmd_add_attribute('Note', 'whatever') - self.failUnless('whatever' in self.schema) + self.assertTrue('whatever' in self.schema) self.assertEqual(self.schema['whatever'].subjects(), ('Note',)) self.assertEqual(self.schema['whatever'].objects(), ('Int',)) self.assertEqual(self.schema['Note'].default('whatever'), 2) @@ -108,12 +108,12 @@ self.mh.rollback() def test_add_attribute_varchar(self): - self.failIf('whatever' in self.schema) + self.assertFalse('whatever' in self.schema) self.request().create_entity('Note') self.commit() - self.failIf('shortpara' in self.schema) + self.assertFalse('shortpara' in self.schema) self.mh.cmd_add_attribute('Note', 'shortpara') - self.failUnless('shortpara' in self.schema) + self.assertTrue('shortpara' in self.schema) self.assertEqual(self.schema['shortpara'].subjects(), ('Note', )) self.assertEqual(self.schema['shortpara'].objects(), ('String', )) # test created column is actually a varchar(64) @@ -128,10 +128,10 @@ self.mh.rollback() def test_add_datetime_with_default_value_attribute(self): - self.failIf('mydate' in self.schema) - self.failIf('shortpara' in self.schema) + self.assertFalse('mydate' in self.schema) + self.assertFalse('shortpara' in self.schema) self.mh.cmd_add_attribute('Note', 'mydate') - self.failUnless('mydate' in self.schema) + self.assertTrue('mydate' in self.schema) self.assertEqual(self.schema['mydate'].subjects(), ('Note', )) self.assertEqual(self.schema['mydate'].objects(), ('Date', )) testdate = date(2005, 12, 13) @@ -167,17 +167,17 @@ self.mh.rollback() def test_rename_attribute(self): - self.failIf('civility' in self.schema) + self.assertFalse('civility' in self.schema) eid1 = self.mh.rqlexec('INSERT Personne X: X nom "lui", X sexe "M"')[0][0] eid2 = self.mh.rqlexec('INSERT Personne X: X nom "l\'autre", X sexe NULL')[0][0] self.mh.cmd_rename_attribute('Personne', 'sexe', 'civility') - self.failIf('sexe' in self.schema) - self.failUnless('civility' in self.schema) + self.assertFalse('sexe' in self.schema) + self.assertTrue('civility' in self.schema) # test data has been backported c1 = self.mh.rqlexec('Any C WHERE X eid %s, X civility C' % eid1)[0][0] - self.failUnlessEqual(c1, 'M') + self.assertEqual(c1, 'M') c2 = self.mh.rqlexec('Any C WHERE X eid %s, X civility C' % eid2)[0][0] - self.failUnlessEqual(c2, None) + self.assertEqual(c2, None) def test_workflow_actions(self): @@ -192,13 +192,14 @@ self.assertEqual(s1, "foo") def test_add_entity_type(self): - self.failIf('Folder2' in self.schema) - self.failIf('filed_under2' in self.schema) + self.assertFalse('Folder2' in self.schema) + self.assertFalse('filed_under2' in self.schema) self.mh.cmd_add_entity_type('Folder2') - self.failUnless('Folder2' in self.schema) - self.failUnless(self.execute('CWEType X WHERE X name "Folder2"')) - self.failUnless('filed_under2' in self.schema) - self.failUnless(self.execute('CWRType X WHERE X name "filed_under2"')) + self.assertTrue('Folder2' in self.schema) + self.assertTrue('Old' in self.schema) + self.assertTrue(self.execute('CWEType X WHERE X name "Folder2"')) + self.assertTrue('filed_under2' in self.schema) + self.assertTrue(self.execute('CWRType X WHERE X name "filed_under2"')) self.schema.rebuild_infered_relations() self.assertEqual(sorted(str(rs) for rs in self.schema['Folder2'].subject_relations()), ['created_by', 'creation_date', 'cw_source', 'cwuri', @@ -209,12 +210,14 @@ 'modification_date', 'name', 'owned_by']) self.assertEqual([str(rs) for rs in self.schema['Folder2'].object_relations()], ['filed_under2', 'identity']) + # Old will be missing as it has been renamed into 'New' in the migrated + # schema while New hasn't been added here. self.assertEqual(sorted(str(e) for e in self.schema['filed_under2'].subjects()), - sorted(str(e) for e in self.schema.entities() if not e.final)) + sorted(str(e) for e in self.schema.entities() if not e.final and e != 'Old')) self.assertEqual(self.schema['filed_under2'].objects(), ('Folder2',)) eschema = self.schema.eschema('Folder2') for cstr in eschema.rdef('name').constraints: - self.failUnless(hasattr(cstr, 'eid')) + self.assertTrue(hasattr(cstr, 'eid')) def test_add_drop_entity_type(self): self.mh.cmd_add_entity_type('Folder2') @@ -227,23 +230,32 @@ self.commit() eschema = self.schema.eschema('Folder2') self.mh.cmd_drop_entity_type('Folder2') - self.failIf('Folder2' in self.schema) - self.failIf(self.execute('CWEType X WHERE X name "Folder2"')) + self.assertFalse('Folder2' in self.schema) + self.assertFalse(self.execute('CWEType X WHERE X name "Folder2"')) # test automatic workflow deletion - self.failIf(self.execute('Workflow X WHERE NOT X workflow_of ET')) - self.failIf(self.execute('State X WHERE NOT X state_of WF')) - self.failIf(self.execute('Transition X WHERE NOT X transition_of WF')) + self.assertFalse(self.execute('Workflow X WHERE NOT X workflow_of ET')) + self.assertFalse(self.execute('State X WHERE NOT X state_of WF')) + self.assertFalse(self.execute('Transition X WHERE NOT X transition_of WF')) + + def test_rename_entity_type(self): + entity = self.mh.create_entity('Old', name=u'old') + self.repo.type_and_source_from_eid(entity.eid) + self.mh.cmd_rename_entity_type('Old', 'New') + self.mh.cmd_rename_attribute('New', 'name', 'new_name') def test_add_drop_relation_type(self): self.mh.cmd_add_entity_type('Folder2', auto=False) self.mh.cmd_add_relation_type('filed_under2') self.schema.rebuild_infered_relations() - self.failUnless('filed_under2' in self.schema) + self.assertTrue('filed_under2' in self.schema) + # Old will be missing as it has been renamed into 'New' in the migrated + # schema while New hasn't been added here. self.assertEqual(sorted(str(e) for e in self.schema['filed_under2'].subjects()), - sorted(str(e) for e in self.schema.entities() if not e.final)) + sorted(str(e) for e in self.schema.entities() + if not e.final and e != 'Old')) self.assertEqual(self.schema['filed_under2'].objects(), ('Folder2',)) self.mh.cmd_drop_relation_type('filed_under2') - self.failIf('filed_under2' in self.schema) + self.assertFalse('filed_under2' in self.schema) def test_add_relation_definition_nortype(self): self.mh.cmd_add_relation_definition('Personne', 'concerne2', 'Affaire') @@ -260,9 +272,9 @@ self.mh.rqlexec('SET X concerne2 Y WHERE X is Personne, Y is Affaire') self.commit() self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Affaire') - self.failUnless('concerne2' in self.schema) + self.assertTrue('concerne2' in self.schema) self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Note') - self.failIf('concerne2' in self.schema) + self.assertFalse('concerne2' in self.schema) def test_drop_relation_definition_existant_rtype(self): self.assertEqual(sorted(str(e) for e in self.schema['concerne'].subjects()), @@ -380,8 +392,8 @@ self.assertEqual(eexpr.reverse_delete_permission, ()) self.assertEqual(eexpr.reverse_update_permission, ()) # no more rqlexpr to delete and add para attribute - self.failIf(self._rrqlexpr_rset('add', 'para')) - self.failIf(self._rrqlexpr_rset('delete', 'para')) + self.assertFalse(self._rrqlexpr_rset('add', 'para')) + self.assertFalse(self._rrqlexpr_rset('delete', 'para')) # new rql expr to add ecrit_par relation rexpr = self._rrqlexpr_entity('add', 'ecrit_par') self.assertEqual(rexpr.expression, @@ -391,13 +403,13 @@ self.assertEqual(rexpr.reverse_read_permission, ()) self.assertEqual(rexpr.reverse_delete_permission, ()) # no more rqlexpr to delete and add travaille relation - self.failIf(self._rrqlexpr_rset('add', 'travaille')) - self.failIf(self._rrqlexpr_rset('delete', 'travaille')) + self.assertFalse(self._rrqlexpr_rset('add', 'travaille')) + self.assertFalse(self._rrqlexpr_rset('delete', 'travaille')) # no more rqlexpr to delete and update Societe entity - self.failIf(self._erqlexpr_rset('update', 'Societe')) - self.failIf(self._erqlexpr_rset('delete', 'Societe')) + self.assertFalse(self._erqlexpr_rset('update', 'Societe')) + self.assertFalse(self._erqlexpr_rset('delete', 'Societe')) # no more rqlexpr to read Affaire entity - self.failIf(self._erqlexpr_rset('read', 'Affaire')) + self.assertFalse(self._erqlexpr_rset('read', 'Affaire')) # rqlexpr to update Affaire entity has been updated eexpr = self._erqlexpr_entity('update', 'Affaire') self.assertEqual(eexpr.expression, 'X concerne S, S owned_by U') @@ -470,13 +482,13 @@ try: self.mh.cmd_remove_cube('email', removedeps=True) # file was there because it's an email dependancy, should have been removed - self.failIf('email' in self.config.cubes()) - self.failIf(self.config.cube_dir('email') in self.config.cubes_path()) - self.failIf('file' in self.config.cubes()) - self.failIf(self.config.cube_dir('file') in self.config.cubes_path()) + self.assertFalse('email' in self.config.cubes()) + self.assertFalse(self.config.cube_dir('email') in self.config.cubes_path()) + self.assertFalse('file' in self.config.cubes()) + self.assertFalse(self.config.cube_dir('file') in self.config.cubes_path()) for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'sender', 'in_thread', 'reply_to', 'data_format'): - self.failIf(ertype in schema, ertype) + self.assertFalse(ertype in schema, ertype) self.assertEqual(sorted(schema['see_also'].rdefs.keys()), sorted([('Folder', 'Folder'), ('Bookmark', 'Bookmark'), @@ -493,13 +505,13 @@ raise finally: self.mh.cmd_add_cube('email') - self.failUnless('email' in self.config.cubes()) - self.failUnless(self.config.cube_dir('email') in self.config.cubes_path()) - self.failUnless('file' in self.config.cubes()) - self.failUnless(self.config.cube_dir('file') in self.config.cubes_path()) + self.assertTrue('email' in self.config.cubes()) + self.assertTrue(self.config.cube_dir('email') in self.config.cubes_path()) + self.assertTrue('file' in self.config.cubes()) + self.assertTrue(self.config.cube_dir('file') in self.config.cubes_path()) for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'sender', 'in_thread', 'reply_to', 'data_format'): - self.failUnless(ertype in schema, ertype) + self.assertTrue(ertype in schema, ertype) self.assertEqual(sorted(schema['see_also'].rdefs.keys()), sorted([('EmailThread', 'EmailThread'), ('Folder', 'Folder'), ('Bookmark', 'Bookmark'), @@ -530,18 +542,18 @@ try: self.mh.cmd_remove_cube('email') cubes.remove('email') - self.failIf('email' in self.config.cubes()) - self.failUnless('file' in self.config.cubes()) + self.assertFalse('email' in self.config.cubes()) + self.assertTrue('file' in self.config.cubes()) for ertype in ('Email', 'EmailThread', 'EmailPart', 'sender', 'in_thread', 'reply_to'): - self.failIf(ertype in schema, ertype) + self.assertFalse(ertype in schema, ertype) except : import traceback traceback.print_exc() raise finally: self.mh.cmd_add_cube('email') - self.failUnless('email' in self.config.cubes()) + self.assertTrue('email' in self.config.cubes()) # trick: overwrite self.maxeid to avoid deletion of just reintroduced # types (and their associated tables!) self.maxeid = self.execute('Any MAX(X)')[0][0] @@ -570,13 +582,13 @@ text = self.execute('INSERT Text X: X para "hip", X summary "hop", X newattr "momo"').get_entity(0, 0) note = self.execute('INSERT Note X: X para "hip", X shortpara "hop", X newattr "momo", X unique_id "x"').get_entity(0, 0) aff = self.execute('INSERT Affaire X').get_entity(0, 0) - self.failUnless(self.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s', + self.assertTrue(self.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s', {'x': text.eid, 'y': aff.eid})) - self.failUnless(self.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s', + self.assertTrue(self.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s', {'x': note.eid, 'y': aff.eid})) - self.failUnless(self.execute('SET X newinlined Y WHERE X eid %(x)s, Y eid %(y)s', + self.assertTrue(self.execute('SET X newinlined Y WHERE X eid %(x)s, Y eid %(y)s', {'x': text.eid, 'y': aff.eid})) - self.failUnless(self.execute('SET X newinlined Y WHERE X eid %(x)s, Y eid %(y)s', + self.assertTrue(self.execute('SET X newinlined Y WHERE X eid %(x)s, Y eid %(y)s', {'x': note.eid, 'y': aff.eid})) # XXX remove specializes by ourselves, else tearDown fails when removing # Para because of Note inheritance. This could be fixed by putting the @@ -601,11 +613,11 @@ def test_add_symmetric_relation_type(self): same_as_sql = self.mh.sqlexec("SELECT sql FROM sqlite_master WHERE type='table' " "and name='same_as_relation'") - self.failIf(same_as_sql) + self.assertFalse(same_as_sql) self.mh.cmd_add_relation_type('same_as') same_as_sql = self.mh.sqlexec("SELECT sql FROM sqlite_master WHERE type='table' " "and name='same_as_relation'") - self.failUnless(same_as_sql) + self.assertTrue(same_as_sql) if __name__ == '__main__': unittest_main() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_msplanner.py Thu Dec 08 14:32:57 2011 +0100 @@ -64,7 +64,7 @@ X_ALL_SOLS = sorted([{'X': 'Affaire'}, {'X': 'BaseTransition'}, {'X': 'Basket'}, {'X': 'Bookmark'}, {'X': 'CWAttribute'}, {'X': 'CWCache'}, - {'X': 'CWConstraint'}, {'X': 'CWConstraintType'}, {'X': 'CWEType'}, + {'X': 'CWConstraint'}, {'X': 'CWConstraintType'}, {'X': 'CWDataImport'}, {'X': 'CWEType'}, {'X': 'CWGroup'}, {'X': 'CWPermission'}, {'X': 'CWProperty'}, {'X': 'CWRType'}, {'X': 'CWRelation'}, {'X': 'CWSource'}, {'X': 'CWSourceHostConfig'}, {'X': 'CWSourceSchemaConfig'}, @@ -72,7 +72,7 @@ {'X': 'Card'}, {'X': 'Comment'}, {'X': 'Division'}, {'X': 'Email'}, {'X': 'EmailAddress'}, {'X': 'EmailPart'}, {'X': 'EmailThread'}, {'X': 'ExternalUri'}, {'X': 'File'}, - {'X': 'Folder'}, {'X': 'Note'}, + {'X': 'Folder'}, {'X': 'Note'}, {'X': 'Old'}, {'X': 'Personne'}, {'X': 'RQLExpression'}, {'X': 'Societe'}, {'X': 'State'}, {'X': 'SubDivision'}, {'X': 'SubWorkflowExitPoint'}, {'X': 'Tag'}, {'X': 'TrInfo'}, {'X': 'Transition'}, @@ -767,7 +767,7 @@ def test_not_identity(self): ueid = self.session.user.eid - self._test('Any X WHERE NOT X identity U, U eid %s' % ueid, + self._test('Any X WHERE NOT X identity U, U eid %s, X is CWUser' % ueid, [('OneFetchStep', [('Any X WHERE NOT X identity %s, X is CWUser' % ueid, [{'X': 'CWUser'}])], None, None, @@ -907,6 +907,7 @@ ALL_SOLS = X_ALL_SOLS[:] ALL_SOLS.remove({'X': 'CWSourceHostConfig'}) # not authorized ALL_SOLS.remove({'X': 'CWSourceSchemaConfig'}) # not authorized + ALL_SOLS.remove({'X': 'CWDataImport'}) # not authorized self._test('Any MAX(X)', [('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])], [self.cards, self.system], None, {'E': 'table1.C0'}, []), @@ -920,7 +921,7 @@ [{'X': 'Card'}, {'X': 'Note'}, {'X': 'State'}])], [self.cards, self.system], {}, {'X': 'table0.C0'}, []), ('FetchStep', - [('Any X WHERE X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)', + [('Any X WHERE X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Old, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)', [{'X': 'BaseTransition'}, {'X': 'Bookmark'}, {'X': 'CWAttribute'}, {'X': 'CWCache'}, {'X': 'CWConstraint'}, {'X': 'CWConstraintType'}, @@ -933,7 +934,7 @@ {'X': 'Email'}, {'X': 'EmailAddress'}, {'X': 'EmailPart'}, {'X': 'EmailThread'}, {'X': 'ExternalUri'}, {'X': 'File'}, - {'X': 'Folder'}, + {'X': 'Folder'}, {'X': 'Old'}, {'X': 'Personne'}, {'X': 'RQLExpression'}, {'X': 'Societe'}, {'X': 'SubDivision'}, {'X': 'SubWorkflowExitPoint'}, {'X': 'Tag'}, @@ -957,7 +958,7 @@ ueid = self.session.user.eid X_ET_ALL_SOLS = [] for s in X_ALL_SOLS: - if s in ({'X': 'CWSourceHostConfig'}, {'X': 'CWSourceSchemaConfig'}): + if s in ({'X': 'CWSourceHostConfig'}, {'X': 'CWSourceSchemaConfig'}, {'X': 'CWDataImport'}): continue # not authorized ets = {'ET': 'CWEType'} ets.update(s) @@ -986,11 +987,12 @@ [self.system], {'X': 'table3.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []), # extra UnionFetchStep could be avoided but has no cost, so don't care ('UnionFetchStep', - [('FetchStep', [('Any ET,X WHERE X is ET, ET is CWEType, X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)', + [('FetchStep', [('Any ET,X WHERE X is ET, ET is CWEType, X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Old, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)', [{'X': 'BaseTransition', 'ET': 'CWEType'}, {'X': 'Bookmark', 'ET': 'CWEType'}, {'X': 'CWAttribute', 'ET': 'CWEType'}, {'X': 'CWCache', 'ET': 'CWEType'}, {'X': 'CWConstraint', 'ET': 'CWEType'}, - {'X': 'CWConstraintType', 'ET': 'CWEType'}, {'X': 'CWEType', 'ET': 'CWEType'}, + {'X': 'CWConstraintType', 'ET': 'CWEType'}, + {'X': 'CWEType', 'ET': 'CWEType'}, {'X': 'CWGroup', 'ET': 'CWEType'}, {'X': 'CWPermission', 'ET': 'CWEType'}, {'X': 'CWProperty', 'ET': 'CWEType'}, {'X': 'CWRType', 'ET': 'CWEType'}, {'X': 'CWSource', 'ET': 'CWEType'}, @@ -1001,7 +1003,7 @@ {'X': 'EmailAddress', 'ET': 'CWEType'}, {'X': 'EmailPart', 'ET': 'CWEType'}, {'X': 'EmailThread', 'ET': 'CWEType'}, {'X': 'ExternalUri', 'ET': 'CWEType'}, {'X': 'File', 'ET': 'CWEType'}, {'X': 'Folder', 'ET': 'CWEType'}, - {'X': 'Personne', 'ET': 'CWEType'}, + {'X': 'Old', 'ET': 'CWEType'}, {'X': 'Personne', 'ET': 'CWEType'}, {'X': 'RQLExpression', 'ET': 'CWEType'}, {'X': 'Societe', 'ET': 'CWEType'}, {'X': 'SubDivision', 'ET': 'CWEType'}, {'X': 'SubWorkflowExitPoint', 'ET': 'CWEType'}, {'X': 'Tag', 'ET': 'CWEType'}, {'X': 'TrInfo', 'ET': 'CWEType'}, @@ -2661,7 +2663,7 @@ 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, CWSource, CWSourceHostConfig, CWSourceSchemaConfig, CWUniqueTogetherConstraint, CWUser, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)', + [(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, CWDataImport, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWSourceHostConfig, CWSourceSchemaConfig, CWUniqueTogetherConstraint, CWUser, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Old, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)', [{'U': 'CWUser', 'X': 'Affaire'}, {'U': 'CWUser', 'X': 'BaseTransition'}, {'U': 'CWUser', 'X': 'Basket'}, @@ -2670,6 +2672,7 @@ {'U': 'CWUser', 'X': 'CWCache'}, {'U': 'CWUser', 'X': 'CWConstraint'}, {'U': 'CWUser', 'X': 'CWConstraintType'}, + {'U': 'CWUser', 'X': 'CWDataImport'}, {'U': 'CWUser', 'X': 'CWEType'}, {'U': 'CWUser', 'X': 'CWGroup'}, {'U': 'CWUser', 'X': 'CWPermission'}, @@ -2689,6 +2692,7 @@ {'U': 'CWUser', 'X': 'ExternalUri'}, {'U': 'CWUser', 'X': 'File'}, {'U': 'CWUser', 'X': 'Folder'}, + {'U': 'CWUser', 'X': 'Old'}, {'U': 'CWUser', 'X': 'Personne'}, {'U': 'CWUser', 'X': 'RQLExpression'}, {'U': 'CWUser', 'X': 'Societe'}, diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_multisources.py --- a/server/test/unittest_multisources.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_multisources.py Thu Dec 08 14:32:57 2011 +0100 @@ -180,10 +180,10 @@ def test_has_text(self): self.repo.sources_by_uri['extern'].synchronize(MTIME) # in case fti_update has been run before - self.failUnless(self.sexecute('Any X WHERE X has_text "affref"')) - self.failUnless(self.sexecute('Affaire X WHERE X has_text "affref"')) - self.failUnless(self.sexecute('Any X ORDERBY FTIRANK(X) WHERE X has_text "affref"')) - self.failUnless(self.sexecute('Affaire X ORDERBY FTIRANK(X) WHERE X has_text "affref"')) + self.assertTrue(self.sexecute('Any X WHERE X has_text "affref"')) + self.assertTrue(self.sexecute('Affaire X WHERE X has_text "affref"')) + self.assertTrue(self.sexecute('Any X ORDERBY FTIRANK(X) WHERE X has_text "affref"')) + self.assertTrue(self.sexecute('Affaire X ORDERBY FTIRANK(X) WHERE X has_text "affref"')) def test_anon_has_text(self): self.repo.sources_by_uri['extern'].synchronize(MTIME) # in case fti_update has been run before @@ -210,13 +210,13 @@ try: # force sync self.repo.sources_by_uri['extern'].synchronize(MTIME) - self.failUnless(self.sexecute('Any X WHERE X has_text "blah"')) - self.failUnless(self.sexecute('Any X WHERE X has_text "affreux"')) + self.assertTrue(self.sexecute('Any X WHERE X has_text "blah"')) + self.assertTrue(self.sexecute('Any X WHERE X has_text "affreux"')) cu.execute('DELETE Affaire X WHERE X eid %(x)s', {'x': aff2}) self.cnx2.commit() self.repo.sources_by_uri['extern'].synchronize(MTIME) rset = self.sexecute('Any X WHERE X has_text "affreux"') - self.failIf(rset) + self.assertFalse(rset) finally: # restore state cu.execute('SET X ref "AFFREF" WHERE X eid %(x)s', {'x': self.aff1}) @@ -389,7 +389,7 @@ req.execute('DELETE CWSource S WHERE S name "extern"') self.commit() cu = self.session.system_sql("SELECT * FROM entities WHERE source='extern'") - self.failIf(cu.fetchall()) + self.assertFalse(cu.fetchall()) if __name__ == '__main__': from logilab.common.testlib import unittest_main diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_querier.py --- a/server/test/unittest_querier.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_querier.py Thu Dec 08 14:32:57 2011 +0100 @@ -145,7 +145,7 @@ 'X': 'Affaire', 'ET': 'CWEType', 'ETN': 'String'}]) rql, solutions = partrqls[1] - self.assertEqual(rql, 'Any ETN,X WHERE X is ET, ET name ETN, ET is CWEType, X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, CWUser, Card, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Note, Personne, RQLExpression, Societe, State, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)') + self.assertEqual(rql, 'Any ETN,X WHERE X is ET, ET name ETN, ET is CWEType, X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, CWUser, Card, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Note, Old, Personne, RQLExpression, Societe, State, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)') self.assertListEqual(sorted(solutions), sorted([{'X': 'BaseTransition', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'Bookmark', 'ETN': 'String', 'ET': 'CWEType'}, @@ -173,6 +173,7 @@ {'X': 'File', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'Folder', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'Note', 'ETN': 'String', 'ET': 'CWEType'}, + {'X': 'Old', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'Personne', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'RQLExpression', 'ETN': 'String', 'ET': 'CWEType'}, {'X': 'Societe', 'ETN': 'String', 'ET': 'CWEType'}, @@ -250,7 +251,7 @@ def test_unknown_eid(self): # should return an empty result set - self.failIf(self.execute('Any X WHERE X eid 99999999')) + self.assertFalse(self.execute('Any X WHERE X eid 99999999')) def test_typed_eid(self): # should return an empty result set @@ -418,8 +419,8 @@ self.execute("SET X tags Y WHERE X eid %(t)s, Y eid %(g)s", {'g': geid, 't': teid}) rset = self.execute("Any GN,TN ORDERBY GN WHERE T? tags G, T name TN, G name GN") - self.failUnless(['users', 'tag'] in rset.rows) - self.failUnless(['activated', None] in rset.rows) + self.assertTrue(['users', 'tag'] in rset.rows) + self.assertTrue(['activated', None] in rset.rows) rset = self.execute("Any GN,TN ORDERBY GN WHERE T tags G?, T name TN, G name GN") self.assertEqual(rset.rows, [[None, 'tagbis'], ['users', 'tag']]) @@ -494,26 +495,26 @@ def test_select_custom_aggregat_concat_string(self): rset = self.execute('Any GROUP_CONCAT(N) WHERE X is CWGroup, X name N') - self.failUnless(rset) - self.failUnlessEqual(sorted(rset[0][0].split(', ')), ['guests', 'managers', + self.assertTrue(rset) + self.assertEqual(sorted(rset[0][0].split(', ')), ['guests', 'managers', 'owners', 'users']) def test_select_custom_regproc_limit_size(self): rset = self.execute('Any TEXT_LIMIT_SIZE(N, 3) WHERE X is CWGroup, X name N, X name "managers"') - self.failUnless(rset) - self.failUnlessEqual(rset[0][0], 'man...') + self.assertTrue(rset) + self.assertEqual(rset[0][0], 'man...') self.execute("INSERT Basket X: X name 'bidule', X description 'hop hop', X description_format 'text/html'") rset = self.execute('Any LIMIT_SIZE(D, DF, 3) WHERE X is Basket, X description D, X description_format DF') - self.failUnless(rset) - self.failUnlessEqual(rset[0][0], 'hop...') + self.assertTrue(rset) + self.assertEqual(rset[0][0], 'hop...') def test_select_regproc_orderby(self): rset = self.execute('DISTINCT Any X,N ORDERBY GROUP_SORT_VALUE(N) WHERE X is CWGroup, X name N, X name "managers"') - self.failUnlessEqual(len(rset), 1) - self.failUnlessEqual(rset[0][1], 'managers') + self.assertEqual(len(rset), 1) + self.assertEqual(rset[0][1], 'managers') rset = self.execute('Any X,N ORDERBY GROUP_SORT_VALUE(N) WHERE X is CWGroup, X name N, NOT U in_group X, U login "admin"') - self.failUnlessEqual(len(rset), 3) - self.failUnlessEqual(rset[0][1], 'owners') + self.assertEqual(len(rset), 3) + self.assertEqual(rset[0][1], 'owners') def test_select_aggregat_sort(self): rset = self.execute('Any G, COUNT(U) GROUPBY G ORDERBY 2 WHERE U in_group G') @@ -528,16 +529,16 @@ self.assertListEqual(rset.rows, [[u'description_format', 12], [u'description', 13], - [u'name', 15], - [u'created_by', 41], - [u'creation_date', 41], - [u'cw_source', 41], - [u'cwuri', 41], - [u'in_basket', 41], - [u'is', 41], - [u'is_instance_of', 41], - [u'modification_date', 41], - [u'owned_by', 41]]) + [u'name', 16], + [u'created_by', 43], + [u'creation_date', 43], + [u'cw_source', 43], + [u'cwuri', 43], + [u'in_basket', 43], + [u'is', 43], + [u'is_instance_of', 43], + [u'modification_date', 43], + [u'owned_by', 43]]) def test_select_aggregat_having_dumb(self): # dumb but should not raise an error @@ -619,7 +620,7 @@ self.assertEqual(len(rset.rows), 2, rset.rows) biduleeids = [r[0] for r in rset.rows] rset = self.execute(u'Any N where NOT N has_text "bidüle"') - self.failIf([r[0] for r in rset.rows if r[0] in biduleeids]) + self.assertFalse([r[0] for r in rset.rows if r[0] in biduleeids]) # duh? rset = self.execute('Any X WHERE X has_text %(text)s', {'text': u'ça'}) @@ -757,7 +758,7 @@ def test_select_explicit_eid(self): rset = self.execute('Any X,E WHERE X owned_by U, X eid E, U eid %(u)s', {'u': self.session.user.eid}) - self.failUnless(rset) + self.assertTrue(rset) self.assertEqual(rset.description[0][1], 'Int') # def test_select_rewritten_optional(self): @@ -774,7 +775,7 @@ rset = self.execute('Tag X WHERE X creation_date TODAY') self.assertEqual(len(rset.rows), 2) rset = self.execute('Any MAX(D) WHERE X is Tag, X creation_date D') - self.failUnless(isinstance(rset[0][0], datetime), (rset[0][0], type(rset[0][0]))) + self.assertTrue(isinstance(rset[0][0], datetime), (rset[0][0], type(rset[0][0]))) def test_today(self): self.execute("INSERT Tag X: X name 'bidule', X creation_date TODAY") @@ -891,11 +892,11 @@ def test_select_date_mathexp(self): rset = self.execute('Any X, TODAY - CD WHERE X is CWUser, X creation_date CD') - self.failUnless(rset) - self.failUnlessEqual(rset.description[0][1], 'Interval') + self.assertTrue(rset) + self.assertEqual(rset.description[0][1], 'Interval') eid, = self.execute("INSERT Personne X: X nom 'bidule'")[0] rset = self.execute('Any X, NOW - CD WHERE X is Personne, X creation_date CD') - self.failUnlessEqual(rset.description[0][1], 'Interval') + self.assertEqual(rset.description[0][1], 'Interval') def test_select_subquery_aggregat_1(self): # percent users by groups @@ -1173,7 +1174,7 @@ rset = self.execute('Any X, Y WHERE X travaille Y') self.assertEqual(len(rset.rows), 1) # test add of an existant relation but with NOT X rel Y protection - self.failIf(self.execute("SET X travaille Y WHERE X eid %(x)s, Y eid %(y)s," + self.assertFalse(self.execute("SET X travaille Y WHERE X eid %(x)s, Y eid %(y)s," "NOT X travaille Y", {'x': str(eid1), 'y': str(eid2)})) @@ -1198,9 +1199,9 @@ peid2 = self.execute("INSERT Personne Y: Y nom 'tutu'")[0][0] self.execute('SET P1 owned_by U, P2 owned_by U ' 'WHERE P1 eid %s, P2 eid %s, U eid %s' % (peid1, peid2, ueid)) - self.failUnless(self.execute('Any X WHERE X eid %s, X owned_by U, U eid %s' + self.assertTrue(self.execute('Any X WHERE X eid %s, X owned_by U, U eid %s' % (peid1, ueid))) - self.failUnless(self.execute('Any X WHERE X eid %s, X owned_by U, U eid %s' + self.assertTrue(self.execute('Any X WHERE X eid %s, X owned_by U, U eid %s' % (peid2, ueid))) def test_update_math_expr(self): @@ -1227,6 +1228,25 @@ self.assertRaises(QueryError, self.execute, "SET X nom 'toto', X has_text 'tutu' WHERE X is Personne") self.assertRaises(QueryError, self.execute, "SET X login 'tutu', X eid %s" % cnx.user(self.session).eid) + # HAVING on write queries test ############################################# + + def test_update_having(self): + peid1 = self.execute("INSERT Personne Y: Y nom 'hop', Y tel 1")[0][0] + peid2 = self.execute("INSERT Personne Y: Y nom 'hop', Y tel 2")[0][0] + rset = self.execute("SET X tel 3 WHERE X tel TEL HAVING TEL&1=1") + self.assertEqual(tuplify(rset.rows), [(peid1, 3)]) + + def test_insert_having(self): + self.skipTest('unsupported yet') + self.execute("INSERT Personne Y: Y nom 'hop', Y tel 1")[0][0] + self.assertFalse(self.execute("INSERT Personne Y: Y nom 'hop', Y tel 2 WHERE X tel XT HAVING XT&2=2")) + self.assertTrue(self.execute("INSERT Personne Y: Y nom 'hop', Y tel 2 WHERE X tel XT HAVING XT&1=1")) + + def test_delete_having(self): + self.execute("INSERT Personne Y: Y nom 'hop', Y tel 1")[0][0] + self.assertFalse(self.execute("DELETE Personne Y WHERE X tel XT HAVING XT&2=2")) + self.assertTrue(self.execute("DELETE Personne Y WHERE X tel XT HAVING XT&1=1")) + # upassword encryption tests ################################################# def test_insert_upassword(self): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_repository.py --- a/server/test/unittest_repository.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_repository.py Thu Dec 08 14:32:57 2011 +0100 @@ -98,13 +98,13 @@ ('nom', 'prenom', 'inline2')) def test_all_entities_have_owner(self): - self.failIf(self.execute('Any X WHERE NOT X owned_by U')) + self.assertFalse(self.execute('Any X WHERE NOT X owned_by U')) def test_all_entities_have_is(self): - self.failIf(self.execute('Any X WHERE NOT X is ET')) + self.assertFalse(self.execute('Any X WHERE NOT X is ET')) def test_all_entities_have_cw_source(self): - self.failIf(self.execute('Any X WHERE NOT X cw_source S')) + self.assertFalse(self.execute('Any X WHERE NOT X cw_source S')) def test_connect(self): cnxid = self.repo.connect(self.admlogin, password=self.admpassword) @@ -147,7 +147,7 @@ 'INSERT CWUser X: X login %(login)s, X upassword %(passwd)s', {'login': u"tutetute", 'passwd': 'tutetute'}) self.assertRaises(ValidationError, self.repo.commit, cnxid) - self.failIf(self.repo.execute(cnxid, 'CWUser X WHERE X login "tutetute"')) + self.assertFalse(self.repo.execute(cnxid, 'CWUser X WHERE X login "tutetute"')) self.repo.close(cnxid) def test_rollback_on_execute_validation_error(self): @@ -160,12 +160,12 @@ with self.temporary_appobjects(ValidationErrorAfterHook): self.assertRaises(ValidationError, self.execute, 'SET X name "toto" WHERE X is CWGroup, X name "guests"') - self.failUnless(self.execute('Any X WHERE X is CWGroup, X name "toto"')) + self.assertTrue(self.execute('Any X WHERE X is CWGroup, X name "toto"')) with self.assertRaises(QueryError) as cm: self.commit() self.assertEqual(str(cm.exception), 'transaction must be rollbacked') self.rollback() - self.failIf(self.execute('Any X WHERE X is CWGroup, X name "toto"')) + self.assertFalse(self.execute('Any X WHERE X is CWGroup, X name "toto"')) def test_rollback_on_execute_unauthorized(self): class UnauthorizedAfterHook(Hook): @@ -177,12 +177,12 @@ with self.temporary_appobjects(UnauthorizedAfterHook): self.assertRaises(Unauthorized, self.execute, 'SET X name "toto" WHERE X is CWGroup, X name "guests"') - self.failUnless(self.execute('Any X WHERE X is CWGroup, X name "toto"')) + self.assertTrue(self.execute('Any X WHERE X is CWGroup, X name "toto"')) with self.assertRaises(QueryError) as cm: self.commit() self.assertEqual(str(cm.exception), 'transaction must be rollbacked') self.rollback() - self.failIf(self.execute('Any X WHERE X is CWGroup, X name "toto"')) + self.assertFalse(self.execute('Any X WHERE X is CWGroup, X name "toto"')) def test_close(self): @@ -364,13 +364,13 @@ cnx.load_appobjects(subpath=('entities',)) # check we can get the schema schema = cnx.get_schema() - self.failUnless(cnx.vreg) - self.failUnless('etypes'in cnx.vreg) + self.assertTrue(cnx.vreg) + self.assertTrue('etypes'in cnx.vreg) cu = cnx.cursor() rset = cu.execute('Any U,G WHERE U in_group G') user = iter(rset.entities()).next() - self.failUnless(user._cw) - self.failUnless(user._cw.vreg) + self.assertTrue(user._cw) + self.assertTrue(user._cw.vreg) from cubicweb.entities import authobjs self.assertIsInstance(user._cw.user, authobjs.CWUser) cnx.close() @@ -425,7 +425,7 @@ def test_schema_is_relation(self): no_is_rset = self.execute('Any X WHERE NOT X is ET') - self.failIf(no_is_rset, no_is_rset.description) + self.assertFalse(no_is_rset, no_is_rset.description) # def test_perfo(self): # self.set_debug(True) @@ -509,7 +509,7 @@ events = ('before_update_entity',) def __call__(self): # invoiced attribute shouldn't be considered "edited" before the hook - self._test.failIf('invoiced' in self.entity.cw_edited, + self._test.assertFalse('invoiced' in self.entity.cw_edited, 'cw_edited cluttered by previous update') self.entity.cw_edited['invoiced'] = 10 with self.temporary_appobjects(DummyBeforeHook): @@ -582,7 +582,7 @@ self.session.set_cnxset() cu = self.session.system_sql('SELECT mtime FROM entities WHERE eid = %s' % eidp) mtime = cu.fetchone()[0] - self.failUnless(omtime < mtime) + self.assertTrue(omtime < mtime) self.commit() date, modified, deleted = self.repo.entities_modified_since(('Personne',), omtime) self.assertEqual(modified, [('Personne', eidp)]) @@ -627,7 +627,7 @@ def test_no_uncessary_ftiindex_op(self): req = self.request() req.create_entity('Workflow', name=u'dummy workflow', description=u'huuuuu') - self.failIf(any(x for x in self.session.pending_operations + self.assertFalse(any(x for x in self.session.pending_operations if isinstance(x, native.FTIndexEntityOp))) @@ -639,7 +639,7 @@ [u'system.version.basket', u'system.version.card', u'system.version.comment', u'system.version.cubicweb', u'system.version.email', u'system.version.file', u'system.version.folder', - u'system.version.tag']) + u'system.version.localperms', u'system.version.tag']) CALLED = [] diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_rql2sql.py Thu Dec 08 14:32:57 2011 +0100 @@ -19,7 +19,7 @@ import sys import os - +from datetime import date from logilab.common.testlib import TestCase, unittest_main, mock_object from rql import BadRQLQuery @@ -46,12 +46,12 @@ for modname in drivers[driver]: try: if not quiet: - print >> sys.stderr, 'Trying %s' % modname + sys.stderr.write('Trying %s\n' % modname) module = db.load_module_from_name(modname, use_sys=False) break except ImportError: if not quiet: - print >> sys.stderr, '%s is not available' % modname + sys.stderr.write('%s is not available\n' % modname) continue else: return mock_object(STRING=1, BOOLEAN=2, BINARY=3, DATETIME=4, NUMBER=5), drivers[driver][0] @@ -1747,7 +1747,7 @@ class SqlServer2005SQLGeneratorTC(PostgresSQLGeneratorTC): backend = 'sqlserver2005' def _norm_sql(self, sql): - return sql.strip().replace(' SUBSTR', ' SUBSTRING').replace(' || ', ' + ').replace(' ILIKE ', ' LIKE ').replace('TRUE', '1').replace('FALSE', '0') + return sql.strip().replace(' SUBSTR', ' SUBSTRING').replace(' || ', ' + ').replace(' ILIKE ', ' LIKE ').replace('TRUE', '1').replace('FALSE', '0').replace('CURRENT_DATE', str(date.today())) def test_has_text(self): for t in self._parse(HAS_TEXT_LG_INDEXER): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_rqlannotation.py --- a/server/test/unittest_rqlannotation.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_rqlannotation.py Thu Dec 08 14:32:57 2011 +0100 @@ -342,7 +342,7 @@ def test_remove_from_deleted_source_1(self): rqlst = self._prepare('Note X WHERE X eid 999998, NOT X cw_source Y') - self.failIf('X' in rqlst.defined_vars) # simplified + self.assertFalse('X' in rqlst.defined_vars) # simplified self.assertEqual(rqlst.defined_vars['Y']._q_invariant, True) def test_remove_from_deleted_source_2(self): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_security.py --- a/server/test/unittest_security.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_security.py Thu Dec 08 14:32:57 2011 +0100 @@ -327,7 +327,7 @@ cu = cnx.cursor() aff2 = cu.execute("INSERT Affaire X: X sujet 'cool'")[0][0] # entity created in transaction are readable *by eid* - self.failUnless(cu.execute('Any X WHERE X eid %(x)s', {'x':aff2})) + self.assertTrue(cu.execute('Any X WHERE X eid %(x)s', {'x':aff2})) # XXX would be nice if it worked rset = cu.execute("Affaire X WHERE X sujet 'cool'") self.assertEqual(len(rset), 0) @@ -347,8 +347,8 @@ cu.execute("SET A concerne S WHERE A eid %(a)s, S eid %(s)s", {'a': aff2, 's': soc1}) cnx.commit() self.assertRaises(Unauthorized, cu.execute, 'Any X WHERE X eid %(x)s', {'x':aff1}) - self.failUnless(cu.execute('Any X WHERE X eid %(x)s', {'x':aff2})) - self.failUnless(cu.execute('Any X WHERE X eid %(x)s', {'x':card1})) + self.assertTrue(cu.execute('Any X WHERE X eid %(x)s', {'x':aff2})) + self.assertTrue(cu.execute('Any X WHERE X eid %(x)s', {'x':card1})) rset = cu.execute("Any X WHERE X has_text 'cool'") self.assertEqual(sorted(eid for eid, in rset.rows), [card1, aff2]) @@ -457,14 +457,14 @@ cnx = self.login('anon') cu = cnx.cursor() rset = cu.execute('CWUser X') - self.failUnless(rset) + self.assertTrue(rset) x = rset.get_entity(0, 0) self.assertEqual(x.login, None) - self.failUnless(x.creation_date) + self.assertTrue(x.creation_date) x = rset.get_entity(1, 0) x.complete() self.assertEqual(x.login, None) - self.failUnless(x.creation_date) + self.assertTrue(x.creation_date) cnx.rollback() cnx.close() @@ -492,7 +492,7 @@ cu = cnx.cursor() cu.execute('DELETE Affaire X WHERE X ref "ARCT01"') cnx.commit() - self.failIf(cu.execute('Affaire X')) + self.assertFalse(cu.execute('Affaire X')) cnx.close() def test_users_and_groups_non_readable_by_guests(self): @@ -570,6 +570,26 @@ self.assertEqual(names, sorted(names, key=lambda x: x.lower())) cnx.close() + def test_restrict_is_instance_ok(self): + from rql import RQLException + rset = self.execute('Any X WHERE X is_instance_of BaseTransition') + rqlst = rset.syntax_tree() + select = rqlst.children[0] + x = select.get_selected_variables().next() + self.assertRaises(RQLException, select.add_type_restriction, + x.variable, 'CWUser') + select.add_type_restriction(x.variable, 'BaseTransition') + select.add_type_restriction(x.variable, 'WorkflowTransition') + self.assertEqual(rqlst.as_string(), 'Any X WHERE X is_instance_of WorkflowTransition') + + def test_restrict_is_instance_no_supported(self): + rset = self.execute('Any X WHERE X is_instance_of IN(CWUser, CWGroup)') + rqlst = rset.syntax_tree() + select = rqlst.children[0] + x = select.get_selected_variables().next() + self.assertRaises(NotImplementedError, select.add_type_restriction, + x.variable, 'WorkflowTransition') + def test_in_state_without_update_perm(self): """check a user change in_state without having update permission on the subject diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_session.py --- a/server/test/unittest_session.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_session.py Thu Dec 08 14:32:57 2011 +0100 @@ -95,7 +95,7 @@ description = self.session.build_description(rset.syntax_tree(), None, rset.rows) self.assertEqual(len(description), orig_length - 1) self.assertEqual(len(rset.rows), orig_length - 1) - self.failIf(rset.rows[0][0] == 9999999) + self.assertFalse(rset.rows[0][0] == 9999999) def test_build_descr2(self): rset = self.execute('Any X,Y WITH X,Y BEING ((Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G))') diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_storage.py --- a/server/test/unittest_storage.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_storage.py Thu Dec 08 14:32:57 2011 +0100 @@ -89,10 +89,10 @@ f1 = self.create_file() expected_filepath = osp.join(self.tempdir, '%s_data_%s' % (f1.eid, f1.data_name)) - self.failUnless(osp.isfile(expected_filepath)) + self.assertTrue(osp.isfile(expected_filepath)) self.assertEqual(file(expected_filepath).read(), 'the-data') self.rollback() - self.failIf(osp.isfile(expected_filepath)) + self.assertFalse(osp.isfile(expected_filepath)) f1 = self.create_file() self.commit() self.assertEqual(file(expected_filepath).read(), 'the-data') @@ -100,12 +100,12 @@ self.rollback() self.assertEqual(file(expected_filepath).read(), 'the-data') f1.cw_delete() - self.failUnless(osp.isfile(expected_filepath)) + self.assertTrue(osp.isfile(expected_filepath)) self.rollback() - self.failUnless(osp.isfile(expected_filepath)) + self.assertTrue(osp.isfile(expected_filepath)) f1.cw_delete() self.commit() - self.failIf(osp.isfile(expected_filepath)) + self.assertFalse(osp.isfile(expected_filepath)) def test_bfss_sqlite_fspath(self): f1 = self.create_file() @@ -219,7 +219,7 @@ # update f1's local dict. We want the pure rql version to work self.commit() old_path = self.fspath(f1) - self.failUnless(osp.isfile(old_path)) + self.assertTrue(osp.isfile(old_path)) self.assertEqual(osp.splitext(old_path)[1], '.txt') self.execute('SET F data %(d)s, F data_name %(dn)s, F data_format %(df)s WHERE F eid %(f)s', {'d': Binary('some other data'), 'f': f1.eid, 'dn': u'bar.jpg', 'df': u'image/jpeg'}) @@ -228,8 +228,8 @@ # the old file is dead f2 = self.execute('Any F WHERE F eid %(f)s, F is File', {'f': f1.eid}).get_entity(0, 0) new_path = self.fspath(f2) - self.failIf(osp.isfile(old_path)) - self.failUnless(osp.isfile(new_path)) + self.assertFalse(osp.isfile(old_path)) + self.assertTrue(osp.isfile(new_path)) self.assertEqual(osp.splitext(new_path)[1], '.jpg') @tag('update', 'extension', 'rollback') @@ -242,7 +242,7 @@ self.commit() old_path = self.fspath(f1) old_data = f1.data.getvalue() - self.failUnless(osp.isfile(old_path)) + self.assertTrue(osp.isfile(old_path)) self.assertEqual(osp.splitext(old_path)[1], '.txt') self.execute('SET F data %(d)s, F data_name %(dn)s, F data_format %(df)s WHERE F eid %(f)s', {'d': Binary('some other data'), 'f': f1.eid, 'dn': u'bar.jpg', 'df': u'image/jpeg'}) @@ -252,7 +252,7 @@ f2 = self.execute('Any F WHERE F eid %(f)s, F is File', {'f': f1.eid}).get_entity(0, 0) new_path = self.fspath(f2) new_data = f2.data.getvalue() - self.failUnless(osp.isfile(new_path)) + self.assertTrue(osp.isfile(new_path)) self.assertEqual(osp.splitext(new_path)[1], '.txt') self.assertEqual(old_path, new_path) self.assertEqual(old_data, new_data) @@ -279,7 +279,7 @@ self.commit() self.assertEqual(f1.data.getvalue(), 'the new data') self.assertEqual(self.fspath(f1), new_fspath) - self.failIf(osp.isfile(old_fspath)) + self.assertFalse(osp.isfile(old_fspath)) @tag('fsimport') def test_clean(self): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef server/test/unittest_undo.py --- a/server/test/unittest_undo.py Thu Dec 08 14:29:48 2011 +0100 +++ b/server/test/unittest_undo.py Thu Dec 08 14:32:57 2011 +0100 @@ -43,13 +43,13 @@ # also check transaction actions have been properly deleted cu = self.session.system_sql( "SELECT * from tx_entity_actions WHERE tx_uuid='%s'" % txuuid) - self.failIf(cu.fetchall()) + self.assertFalse(cu.fetchall()) cu = self.session.system_sql( "SELECT * from tx_relation_actions WHERE tx_uuid='%s'" % txuuid) - self.failIf(cu.fetchall()) + self.assertFalse(cu.fetchall()) def test_undo_api(self): - self.failUnless(self.txuuid) + self.assertTrue(self.txuuid) # test transaction api self.assertRaises(NoSuchTransaction, self.cnx.transaction_info, 'hop') @@ -58,7 +58,7 @@ self.assertRaises(NoSuchTransaction, self.cnx.undo_transaction, 'hop') txinfo = self.cnx.transaction_info(self.txuuid) - self.failUnless(txinfo.datetime) + self.assertTrue(txinfo.datetime) self.assertEqual(txinfo.user_eid, self.session.user.eid) self.assertEqual(txinfo.user().login, 'admin') actions = txinfo.actions_list() @@ -159,9 +159,9 @@ undotxuuid = self.commit() self.assertEqual(undotxuuid, None) # undo not undoable self.assertEqual(errors, []) - self.failUnless(self.execute('Any X WHERE X eid %(x)s', {'x': toto.eid})) - self.failUnless(self.execute('Any X WHERE X eid %(x)s', {'x': e.eid})) - self.failUnless(self.execute('Any X WHERE X has_text "toto@logilab"')) + self.assertTrue(self.execute('Any X WHERE X eid %(x)s', {'x': toto.eid})) + self.assertTrue(self.execute('Any X WHERE X eid %(x)s', {'x': e.eid})) + self.assertTrue(self.execute('Any X WHERE X has_text "toto@logilab"')) self.assertEqual(toto.cw_adapt_to('IWorkflowable').state, 'activated') self.assertEqual(toto.cw_adapt_to('IEmailable').get_email(), 'toto@logilab.org') self.assertEqual([(p.pkey, p.value) for p in toto.reverse_for_user], @@ -231,20 +231,20 @@ txuuid = self.commit() errors = self.cnx.undo_transaction(txuuid) self.commit() - self.failIf(errors) - self.failIf(self.execute('Any X WHERE X eid %(x)s', {'x': c.eid})) - self.failIf(self.execute('Any X WHERE X eid %(x)s', {'x': p.eid})) - self.failIf(self.execute('Any X,Y WHERE X fiche Y')) + self.assertFalse(errors) + self.assertFalse(self.execute('Any X WHERE X eid %(x)s', {'x': c.eid})) + self.assertFalse(self.execute('Any X WHERE X eid %(x)s', {'x': p.eid})) + self.assertFalse(self.execute('Any X,Y WHERE X fiche Y')) self.session.set_cnxset() for eid in (p.eid, c.eid): - self.failIf(session.system_sql( + self.assertFalse(session.system_sql( 'SELECT * FROM entities WHERE eid=%s' % eid).fetchall()) - self.failIf(session.system_sql( + self.assertFalse(session.system_sql( 'SELECT 1 FROM owned_by_relation WHERE eid_from=%s' % eid).fetchall()) # added by sql in hooks (except when using dataimport) - self.failIf(session.system_sql( + self.assertFalse(session.system_sql( 'SELECT 1 FROM is_relation WHERE eid_from=%s' % eid).fetchall()) - self.failIf(session.system_sql( + self.assertFalse(session.system_sql( 'SELECT 1 FROM is_instance_of_relation WHERE eid_from=%s' % eid).fetchall()) self.check_transaction_deleted(txuuid) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef setup.py --- a/setup.py Thu Dec 08 14:29:48 2011 +0100 +++ b/setup.py Thu Dec 08 14:32:57 2011 +0100 @@ -119,7 +119,7 @@ src = '%s/%s' % (directory, filename) dest = to_dir + src[len(from_dir):] if verbose: - print >> sys.stderr, src, '->', dest + sys.stderr.write('%s -> %s\n' % (src, dest)) if os.path.isdir(src): if not exists(dest): os.mkdir(dest) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef skeleton/setup.py --- a/skeleton/setup.py Thu Dec 08 14:29:48 2011 +0100 +++ b/skeleton/setup.py Thu Dec 08 14:32:57 2011 +0100 @@ -105,7 +105,7 @@ src = join(directory, filename) dest = to_dir + src[len(from_dir):] if verbose: - print >> sys.stderr, src, '->', dest + sys.stderr.write('%s -> %s\n' % (src, dest)) if os.path.isdir(src): if not exists(dest): os.mkdir(dest) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef sobjects/parsers.py --- a/sobjects/parsers.py Thu Dec 08 14:29:48 2011 +0100 +++ b/sobjects/parsers.py Thu Dec 08 14:32:57 2011 +0100 @@ -233,13 +233,13 @@ try: related_items = rels[role][rtype] except KeyError: - self.source.error('relation %s-%s not found in xml export of %s', - rtype, role, etype) + self.import_log.record_error('relation %s-%s not found in xml export of %s' + % (rtype, role, etype)) continue try: linker = self.select_linker(action, rtype, role, entity) except RegistryException: - self.source.error('no linker for action %s', action) + self.import_log.record_error('no linker for action %s' % action) else: linker.link_items(related_items, rules) @@ -430,15 +430,15 @@ def issubset(x,y): return all(z in y for z in x) eids = [] # local eids - source = self.parser.source + log = self.parser.import_log for item, rels in others: if item['cwtype'] != ttype: continue if not issubset(searchattrs, item): item, rels = self.parser.complete_item(item, rels) if not issubset(searchattrs, item): - source.error('missing attribute, got %s expected keys %s', - item, searchattrs) + log.record_error('missing attribute, got %s expected keys %s' + % (item, searchattrs)) continue # XXX str() needed with python < 2.6 kwargs = dict((str(attr), item[attr]) for attr in searchattrs) @@ -449,11 +449,11 @@ entity = self._cw.create_entity(item['cwtype'], **kwargs) else: if len(targets) > 1: - source.error('ambiguous link: found %s entity %s with attributes %s', - len(targets), item['cwtype'], kwargs) + log.record_error('ambiguous link: found %s entity %s with attributes %s' + % (len(targets), item['cwtype'], kwargs)) else: - source.error('can not find %s entity with attributes %s', - item['cwtype'], kwargs) + log.record_error('can not find %s entity with attributes %s' + % (item['cwtype'], kwargs)) continue eids.append(entity.eid) self.parser.process_relations(entity, rels) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef sobjects/test/unittest_email.py --- a/sobjects/test/unittest_email.py Thu Dec 08 14:29:48 2011 +0100 +++ b/sobjects/test/unittest_email.py Thu Dec 08 14:32:57 2011 +0100 @@ -51,7 +51,7 @@ self.execute('SET U primary_email E WHERE U login "anon", E address "client@client.com"') self.commit() rset = self.execute('Any X WHERE X use_email E, E eid %(e)s', {'e': email1}) - self.failIf(rset.rowcount != 1, rset) + self.assertFalse(rset.rowcount != 1, rset) def test_security_check(self): req = self.request() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef sobjects/test/unittest_notification.py --- a/sobjects/test/unittest_notification.py Thu Dec 08 14:29:48 2011 +0100 +++ b/sobjects/test/unittest_notification.py Thu Dec 08 14:32:57 2011 +0100 @@ -30,29 +30,29 @@ def test_base(self): msgid1 = construct_message_id('testapp', 21) msgid2 = construct_message_id('testapp', 21) - self.failIfEqual(msgid1, msgid2) - self.failIf('&' in msgid1) - self.failIf('=' in msgid1) - self.failIf('/' in msgid1) - self.failIf('+' in msgid1) + self.assertNotEqual(msgid1, msgid2) + self.assertFalse('&' in msgid1) + self.assertFalse('=' in msgid1) + self.assertFalse('/' in msgid1) + self.assertFalse('+' in msgid1) values = parse_message_id(msgid1, 'testapp') - self.failUnless(values) + self.assertTrue(values) # parse_message_id should work with or without surrounding <> - self.failUnlessEqual(values, parse_message_id(msgid1[1:-1], 'testapp')) - self.failUnlessEqual(values['eid'], '21') - self.failUnless('timestamp' in values) - self.failUnlessEqual(parse_message_id(msgid1[1:-1], 'anotherapp'), None) + self.assertEqual(values, parse_message_id(msgid1[1:-1], 'testapp')) + self.assertEqual(values['eid'], '21') + self.assertTrue('timestamp' in values) + self.assertEqual(parse_message_id(msgid1[1:-1], 'anotherapp'), None) def test_notimestamp(self): msgid1 = construct_message_id('testapp', 21, False) msgid2 = construct_message_id('testapp', 21, False) values = parse_message_id(msgid1, 'testapp') - self.failUnlessEqual(values, {'eid': '21'}) + self.assertEqual(values, {'eid': '21'}) def test_parse_message_doesnt_raise(self): - self.failUnlessEqual(parse_message_id('oijioj@bla.bla', 'tesapp'), None) - self.failUnlessEqual(parse_message_id('oijioj@bla', 'tesapp'), None) - self.failUnlessEqual(parse_message_id('oijioj', 'tesapp'), None) + self.assertEqual(parse_message_id('oijioj@bla.bla', 'tesapp'), None) + self.assertEqual(parse_message_id('oijioj@bla', 'tesapp'), None) + self.assertEqual(parse_message_id('oijioj', 'tesapp'), None) def test_nonregr_empty_message_id(self): @@ -86,7 +86,7 @@ req = self.request() u = self.create_user(req, 'toto') u.cw_adapt_to('IWorkflowable').fire_transition('deactivate', comment=u'yeah') - self.failIf(MAILBOX) + self.assertFalse(MAILBOX) self.commit() self.assertEqual(len(MAILBOX), 1) email = MAILBOX[0] @@ -99,7 +99,7 @@ url: http://testing.fr/cubicweb/cwuser/toto ''') - self.assertEqual(email.subject, 'status changed cwuser #%s (admin)' % u.eid) + self.assertEqual(email.subject, 'status changed CWUser #%s (admin)' % u.eid) if __name__ == '__main__': unittest_main() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/data/bootstrap_cubes --- a/test/data/bootstrap_cubes Thu Dec 08 14:29:48 2011 +0100 +++ b/test/data/bootstrap_cubes Thu Dec 08 14:32:57 2011 +0100 @@ -1,1 +1,1 @@ -card, file, tag +card, file, tag, localperms diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/data/entities.py --- a/test/data/entities.py Thu Dec 08 14:29:48 2011 +0100 +++ b/test/data/entities.py Thu Dec 08 14:32:57 2011 +0100 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -15,9 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -""" -""" from cubicweb.entities import AnyEntity, fetch_config class Societe(AnyEntity): @@ -27,7 +25,7 @@ class Personne(Societe): """customized class forne Person entities""" __regid__ = 'Personne' - fetch_attrs, fetch_order = fetch_config(['nom', 'prenom']) + fetch_attrs, cw_fetch_order = fetch_config(['nom', 'prenom']) rest_attr = 'nom' diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/data/rewrite/bootstrap_cubes --- a/test/data/rewrite/bootstrap_cubes Thu Dec 08 14:29:48 2011 +0100 +++ b/test/data/rewrite/bootstrap_cubes Thu Dec 08 14:32:57 2011 +0100 @@ -1,1 +1,1 @@ -card +card,localperms diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/data/schema.py --- a/test/data/schema.py Thu Dec 08 14:29:48 2011 +0100 +++ b/test/data/schema.py Thu Dec 08 14:32:57 2011 +0100 @@ -37,13 +37,19 @@ # unittest_entity.py RQLVocabularyConstraint('NOT (S connait P, P nom "toto")'), RQLVocabularyConstraint('S travaille P, P nom "tutu"')]) + actionnaire = SubjectRelation('Societe', cardinality='??', + constraints=[RQLConstraint('NOT EXISTS(O contrat_exclusif S)')]) + dirige = SubjectRelation('Societe', cardinality='??', + constraints=[RQLConstraint('S actionnaire O')]) + associe = SubjectRelation('Personne', cardinality='1*', + constraints=[RQLConstraint('S actionnaire SOC, O actionnaire SOC')]) class Societe(EntityType): nom = String() evaluee = SubjectRelation('Note') fournit = SubjectRelation(('Service', 'Produit'), cardinality='1*') - + contrat_exclusif = SubjectRelation('Personne', cardinality='??') class Service(EntityType): fabrique_par = SubjectRelation('Personne', cardinality='1*') diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/unittest_cwconfig.py --- a/test/unittest_cwconfig.py Thu Dec 08 14:29:48 2011 +0100 +++ b/test/unittest_cwconfig.py Thu Dec 08 14:32:57 2011 +0100 @@ -123,7 +123,7 @@ self.assertEqual(self.config.cubes_search_path(), [CUSTOM_CUBES_DIR, self.config.CUBES_DIR]) - self.failUnless('mycube' in self.config.available_cubes()) + self.assertTrue('mycube' in self.config.available_cubes()) # test cubes python path self.config.adjust_sys_path() import cubes diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/unittest_entity.py --- a/test/unittest_entity.py Thu Dec 08 14:29:48 2011 +0100 +++ b/test/unittest_entity.py Thu Dec 08 14:32:57 2011 +0100 @@ -25,7 +25,7 @@ from cubicweb.mttransforms import HAS_TAL from cubicweb.entities import fetch_config from cubicweb.uilib import soup2xhtml - +from cubicweb.schema import RQLVocabularyConstraint class EntityTC(CubicWebTC): @@ -33,16 +33,16 @@ super(EntityTC, self).setUp() self.backup_dict = {} for cls in self.vreg['etypes'].iter_classes(): - self.backup_dict[cls] = (cls.fetch_attrs, cls.fetch_order) + self.backup_dict[cls] = (cls.fetch_attrs, cls.cw_fetch_order) def tearDown(self): super(EntityTC, self).tearDown() for cls in self.vreg['etypes'].iter_classes(): - cls.fetch_attrs, cls.fetch_order = self.backup_dict[cls] + cls.fetch_attrs, cls.cw_fetch_order = self.backup_dict[cls] def test_boolean_value(self): e = self.vreg['etypes'].etype_class('CWUser')(self.request()) - self.failUnless(e) + self.assertTrue(e) def test_yams_inheritance(self): from entities import Note @@ -87,8 +87,8 @@ {'t': oe.eid, 'u': p.eid}) e = req.create_entity('Note', type=u'z') e.copy_relations(oe.eid) - self.failIf(e.ecrit_par) - self.failUnless(oe.ecrit_par) + self.assertFalse(e.ecrit_par) + self.assertTrue(oe.ecrit_par) def test_copy_with_composite(self): user = self.user() @@ -100,8 +100,8 @@ 'WHERE G name "users"')[0][0] e = self.execute('Any X WHERE X eid %(x)s', {'x': usereid}).get_entity(0, 0) e.copy_relations(user.eid) - self.failIf(e.use_email) - self.failIf(e.primary_email) + self.assertFalse(e.use_email) + self.assertFalse(e.primary_email) def test_copy_with_non_initial_state(self): user = self.user() @@ -128,7 +128,7 @@ groups = user.in_group self.assertEqual(sorted(user._cw_related_cache), ['in_group_subject', 'primary_email_subject']) for group in groups: - self.failIf('in_group_subject' in group._cw_related_cache, group._cw_related_cache.keys()) + self.assertFalse('in_group_subject' in group._cw_related_cache, group._cw_related_cache.keys()) def test_related_limit(self): req = self.request() @@ -179,7 +179,7 @@ try: # testing basic fetch_attrs attribute self.assertEqual(Personne.fetch_rql(user), - 'Any X,AA,AB,AC ORDERBY AA ASC ' + 'Any X,AA,AB,AC ORDERBY AA ' 'WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC') # testing unknown attributes Personne.fetch_attrs = ('bloug', 'beep') @@ -187,36 +187,36 @@ # testing one non final relation Personne.fetch_attrs = ('nom', 'prenom', 'travaille') self.assertEqual(Personne.fetch_rql(user), - 'Any X,AA,AB,AC,AD ORDERBY AA ASC ' + 'Any X,AA,AB,AC,AD ORDERBY AA ' 'WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD') # testing two non final relations Personne.fetch_attrs = ('nom', 'prenom', 'travaille', 'evaluee') self.assertEqual(Personne.fetch_rql(user), - 'Any X,AA,AB,AC,AD,AE ORDERBY AA ASC ' + 'Any X,AA,AB,AC,AD,AE ORDERBY AA ' 'WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD, ' 'X evaluee AE?') # testing one non final relation with recursion Personne.fetch_attrs = ('nom', 'prenom', 'travaille') Societe.fetch_attrs = ('nom', 'evaluee') self.assertEqual(Personne.fetch_rql(user), - 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC ' + 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA,AF DESC ' 'WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD, ' 'AC evaluee AE?, AE modification_date AF' ) # testing symmetric relation Personne.fetch_attrs = ('nom', 'connait') - self.assertEqual(Personne.fetch_rql(user), 'Any X,AA,AB ORDERBY AA ASC ' + self.assertEqual(Personne.fetch_rql(user), 'Any X,AA,AB ORDERBY AA ' 'WHERE X is Personne, X nom AA, X connait AB?') # testing optional relation peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '?*' Personne.fetch_attrs = ('nom', 'prenom', 'travaille') Societe.fetch_attrs = ('nom',) self.assertEqual(Personne.fetch_rql(user), - 'Any X,AA,AB,AC,AD ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD') + 'Any X,AA,AB,AC,AD ORDERBY AA WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD') # testing relation with cardinality > 1 peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '**' self.assertEqual(Personne.fetch_rql(user), - 'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB') + 'Any X,AA,AB ORDERBY AA WHERE X is Personne, X nom AA, X prenom AB') # XXX test unauthorized attribute finally: # fetch_attrs restored by generic tearDown @@ -227,15 +227,21 @@ Personne = self.vreg['etypes'].etype_class('Personne') Note = self.vreg['etypes'].etype_class('Note') SubNote = self.vreg['etypes'].etype_class('SubNote') - self.failUnless(issubclass(self.vreg['etypes'].etype_class('SubNote'), Note)) - Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', 'type')) - Note.fetch_attrs, Note.fetch_order = fetch_config(('type',)) - SubNote.fetch_attrs, SubNote.fetch_order = fetch_config(('type',)) + self.assertTrue(issubclass(self.vreg['etypes'].etype_class('SubNote'), Note)) + Personne.fetch_attrs, Personne.cw_fetch_order = fetch_config(('nom', 'type')) + Note.fetch_attrs, Note.cw_fetch_order = fetch_config(('type',)) + SubNote.fetch_attrs, SubNote.cw_fetch_order = fetch_config(('type',)) p = self.request().create_entity('Personne', nom=u'pouet') self.assertEqual(p.cw_related_rql('evaluee'), - 'Any X,AA,AB ORDERBY AA ASC WHERE E eid %(x)s, E evaluee X, ' - 'X type AA, X modification_date AB') - Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', )) + 'Any X,AA,AB ORDERBY AA WHERE E eid %(x)s, E evaluee X, ' + 'X type AA, X modification_date AB') + n = self.request().create_entity('Note') + self.assertEqual(n.cw_related_rql('evaluee', role='object', + targettypes=('Societe', 'Personne')), + "Any X,AA ORDERBY AB DESC WHERE E eid %(x)s, X evaluee E, " + "X is IN(Personne, Societe), X nom AA, " + "X modification_date AB") + Personne.fetch_attrs, Personne.cw_fetch_order = fetch_config(('nom', )) # XXX self.assertEqual(p.cw_related_rql('evaluee'), 'Any X,AA ORDERBY AA DESC ' @@ -246,8 +252,8 @@ 'Any X,AA ORDERBY AA DESC ' 'WHERE E eid %(x)s, E tags X, X modification_date AA') self.assertEqual(tag.cw_related_rql('tags', 'subject', ('Personne',)), - 'Any X,AA,AB ORDERBY AA ASC ' - 'WHERE E eid %(x)s, E tags X, X is IN (Personne), X nom AA, ' + 'Any X,AA,AB ORDERBY AA ' + 'WHERE E eid %(x)s, E tags X, X is Personne, X nom AA, ' 'X modification_date AB') def test_related_rql_ambiguous_cant_use_fetch_order(self): @@ -258,7 +264,7 @@ 'Any X,AA ORDERBY AA DESC ' 'WHERE E eid %(x)s, E tags X, X modification_date AA') - def test_related_rql_cant_fetch_ambiguous_rtype(self): + def test_related_rql_fetch_ambiguous_rtype(self): soc_etype = self.vreg['etypes'].etype_class('Societe') soc = soc_etype(self.request()) soc_etype.fetch_attrs = ('fournit',) @@ -266,15 +272,14 @@ self.vreg['etypes'].etype_class('Produit').fetch_attrs = ('fabrique_par',) self.vreg['etypes'].etype_class('Usine').fetch_attrs = ('lieu',) self.vreg['etypes'].etype_class('Personne').fetch_attrs = ('nom',) - # XXX should be improved: we could fetch fabrique_par object too self.assertEqual(soc.cw_related_rql('fournit', 'subject'), - 'Any X WHERE E eid %(x)s, E fournit X') + 'Any X,A WHERE E eid %(x)s, E fournit X, X fabrique_par A') 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 EXISTS(ZZ use_email O), S eid %(x)s, ' + 'WHERE NOT A 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): @@ -284,37 +289,37 @@ user = req.user rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC ' - 'WHERE NOT EXISTS(ZZ use_email O), S eid %(x)s, ' + 'WHERE NOT A 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) rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC ' - 'WHERE NOT EXISTS(ZZ use_email O, ZZ is CWUser), 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)') + 'WHERE NOT A use_email O, S eid %(x)s, ' + 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, AD eid %(AE)s, ' + 'EXISTS(S identity AD, NOT AD in_group AF, AF name "guests", AF is CWGroup), A is CWUser') def test_unrelated_rql_security_1_anon(self): self.login('anon') 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 EXISTS(ZZ use_email O, ZZ is CWUser), 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)') + 'WHERE NOT A use_email O, S eid %(x)s, ' + 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, AD eid %(AE)s, ' + 'EXISTS(S identity AD, NOT AD in_group AF, AF name "guests", AF is CWGroup), A is CWUser') 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 ' - 'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, ' + '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') 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, ' + '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, ' - 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') + 'AE eid %(AF)s, EXISTS(S identity AE, NOT AE in_group AG, AG name "guests", AG is CWGroup)') def test_unrelated_rql_security_nonexistant(self): self.login('anon') @@ -323,7 +328,7 @@ self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ' 'WHERE 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)') + 'AE eid %(AF)s, EXISTS(S identity AE, NOT AE in_group AG, AG name "guests", AG is CWGroup)') def test_unrelated_rql_constraints_creation_subject(self): person = self.vreg['etypes'].etype_class('Personne')(self.request()) @@ -338,14 +343,15 @@ self.assertEqual( rql, 'Any S,AA,AB,AC ORDERBY AC DESC WHERE ' 'S is Personne, S nom AA, S prenom AB, S modification_date AC, ' - 'NOT (S connait A, A nom "toto"), A is Personne, EXISTS(S travaille B, B nom "tutu")') + 'NOT (S connait AD, AD nom "toto"), AD is Personne, ' + 'EXISTS(S travaille AE, AE nom "tutu")') def test_unrelated_rql_constraints_edition_subject(self): person = self.request().create_entity('Personne', nom=u'sylvain') rql = person.cw_unrelated_rql('connait', 'Personne', 'subject')[0] self.assertEqual( rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE ' - 'NOT EXISTS(S connait O), S eid %(x)s, O is Personne, ' + 'NOT S connait O, S eid %(x)s, O is Personne, ' 'O nom AA, O prenom AB, O modification_date AC, ' 'NOT S identity O') @@ -354,23 +360,93 @@ rql = person.cw_unrelated_rql('connait', 'Personne', 'object')[0] self.assertEqual( rql, 'Any S,AA,AB,AC ORDERBY AC DESC WHERE ' - 'NOT EXISTS(S connait O), O eid %(x)s, S is Personne, ' + 'NOT S connait O, O eid %(x)s, S is Personne, ' 'S nom AA, S prenom AB, S modification_date AC, ' - 'NOT S identity O, NOT (S connait A, A nom "toto"), ' - 'EXISTS(S travaille B, B nom "tutu")') + 'NOT S identity O, NOT (S connait AD, AD nom "toto"), ' + 'EXISTS(S travaille AE, AE nom "tutu")') + + def test_unrelated_rql_s_linkto_s(self): + req = self.request() + person = self.vreg['etypes'].etype_class('Personne')(req) + self.vreg['etypes'].etype_class('Personne').fetch_attrs = () + soc = req.create_entity('Societe', nom=u'logilab') + lt_infos = {('actionnaire', 'subject'): [soc.eid]} + rql, args = person.cw_unrelated_rql('associe', 'Personne', 'subject', + lt_infos=lt_infos) + self.assertEqual(u'Any O ORDERBY O WHERE O is Personne, ' + u'EXISTS(AA eid %(SOC)s, O actionnaire AA)', rql) + self.assertEqual({'SOC': soc.eid}, args) + + def test_unrelated_rql_s_linkto_o(self): + req = self.request() + person = self.vreg['etypes'].etype_class('Personne')(req) + self.vreg['etypes'].etype_class('Societe').fetch_attrs = () + soc = req.create_entity('Societe', nom=u'logilab') + lt_infos = {('contrat_exclusif', 'object'): [soc.eid]} + rql, args = person.cw_unrelated_rql('actionnaire', 'Societe', 'subject', + lt_infos=lt_infos) + self.assertEqual(u'Any O ORDERBY O WHERE NOT A actionnaire O, ' + u'O is Societe, NOT EXISTS(O eid %(O)s), ' + u'A is Personne', rql) + self.assertEqual({'O': soc.eid}, args) + + def test_unrelated_rql_o_linkto_s(self): + req = self.request() + soc = self.vreg['etypes'].etype_class('Societe')(req) + self.vreg['etypes'].etype_class('Personne').fetch_attrs = () + person = req.create_entity('Personne', nom=u'florent') + lt_infos = {('contrat_exclusif', 'subject'): [person.eid]} + rql, args = soc.cw_unrelated_rql('actionnaire', 'Personne', 'object', + lt_infos=lt_infos) + self.assertEqual(u'Any S ORDERBY S WHERE NOT S actionnaire A, ' + u'S is Personne, NOT EXISTS(S eid %(S)s), ' + u'A is Societe', rql) + self.assertEqual({'S': person.eid}, args) + + def test_unrelated_rql_o_linkto_o(self): + req = self.request() + soc = self.vreg['etypes'].etype_class('Societe')(req) + self.vreg['etypes'].etype_class('Personne').fetch_attrs = () + person = req.create_entity('Personne', nom=u'florent') + lt_infos = {('actionnaire', 'object'): [person.eid]} + rql, args = soc.cw_unrelated_rql('dirige', 'Personne', 'object', + lt_infos=lt_infos) + self.assertEqual(u'Any S ORDERBY S WHERE NOT S dirige A, ' + u'S is Personne, EXISTS(S eid %(S)s), ' + u'A is Societe', rql) + self.assertEqual({'S': person.eid}, args) + + def test_unrelated_rql_s_linkto_s_no_info(self): + req = self.request() + person = self.vreg['etypes'].etype_class('Personne')(req) + self.vreg['etypes'].etype_class('Personne').fetch_attrs = () + soc = req.create_entity('Societe', nom=u'logilab') + rql, args = person.cw_unrelated_rql('associe', 'Personne', 'subject') + self.assertEqual(u'Any O ORDERBY O WHERE O is Personne', rql) + self.assertEqual({}, args) + + def test_unrelated_rql_s_linkto_s_unused_info(self): + req = self.request() + person = self.vreg['etypes'].etype_class('Personne')(req) + self.vreg['etypes'].etype_class('Personne').fetch_attrs = () + other_p = req.create_entity('Personne', nom=u'titi') + lt_infos = {('dirige', 'subject'): [other_p.eid]} + rql, args = person.cw_unrelated_rql('associe', 'Personne', 'subject', + lt_infos=lt_infos) + self.assertEqual(u'Any O ORDERBY O WHERE O is Personne', rql) def test_unrelated_base(self): req = self.request() p = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien') e = req.create_entity('Tag', name=u'x') related = [r.eid for r in e.tags] - self.failUnlessEqual(related, []) + self.assertEqual(related, []) unrelated = [r[0] for r in e.unrelated('tags', 'Personne', 'subject')] - self.failUnless(p.eid in unrelated) + self.assertTrue(p.eid in unrelated) self.execute('SET X tags Y WHERE X is Tag, Y is Personne') e = self.execute('Any X WHERE X is Tag').get_entity(0, 0) unrelated = [r[0] for r in e.unrelated('tags', 'Personne', 'subject')] - self.failIf(p.eid in unrelated) + self.assertFalse(p.eid in unrelated) def test_unrelated_limit(self): req = self.request() @@ -538,7 +614,7 @@ p2 = req.create_entity('Personne', nom=u'toto') self.execute('SET X evaluee Y WHERE X nom "di mascio", Y nom "toto"') self.assertEqual(p1.evaluee[0].nom, "toto") - self.failUnless(not p1.reverse_evaluee) + self.assertTrue(not p1.reverse_evaluee) def test_complete_relation(self): session = self.session @@ -547,10 +623,10 @@ 'WHERE U login "admin", S1 name "activated", S2 name "deactivated"')[0][0] trinfo = self.execute('Any X WHERE X eid %(x)s', {'x': eid}).get_entity(0, 0) trinfo.complete() - self.failUnless(isinstance(trinfo.cw_attr_cache['creation_date'], datetime)) - self.failUnless(trinfo.cw_relation_cached('from_state', 'subject')) - self.failUnless(trinfo.cw_relation_cached('to_state', 'subject')) - self.failUnless(trinfo.cw_relation_cached('wf_info_for', 'subject')) + self.assertTrue(isinstance(trinfo.cw_attr_cache['creation_date'], datetime)) + self.assertTrue(trinfo.cw_relation_cached('from_state', 'subject')) + self.assertTrue(trinfo.cw_relation_cached('to_state', 'subject')) + self.assertTrue(trinfo.cw_relation_cached('wf_info_for', 'subject')) self.assertEqual(trinfo.by_transition, ()) def test_request_cache(self): @@ -558,7 +634,7 @@ user = self.execute('CWUser X WHERE X login "admin"', req=req).get_entity(0, 0) state = user.in_state[0] samestate = self.execute('State X WHERE X name "activated"', req=req).get_entity(0, 0) - self.failUnless(state is samestate) + self.assertTrue(state is samestate) def test_rest_path(self): req = self.request() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/unittest_rqlrewrite.py --- a/test/unittest_rqlrewrite.py Thu Dec 08 14:29:48 2011 +0100 +++ b/test/unittest_rqlrewrite.py Thu Dec 08 14:32:57 2011 +0100 @@ -110,10 +110,10 @@ 'P name "read", P require_group G') rqlst = parse('Card C') rewrite(rqlst, {('C', 'X'): (constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - u"Any C WHERE C is Card, B eid %(D)s, " - "EXISTS(C in_state A, B in_group E, F require_state A, " - "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission)") + self.assertEqual(rqlst.as_string(), + u"Any C WHERE C is Card, B eid %(D)s, " + "EXISTS(C in_state A, B in_group E, F require_state A, " + "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission)") def test_multiple_var(self): card_constraint = ('X in_state S, U in_group G, P require_state S,' @@ -123,55 +123,56 @@ rqlst = parse('Any S WHERE S documented_by C, C eid %(u)s') rewrite(rqlst, {('C', 'X'): (card_constraint,), ('S', 'X'): affaire_constraints}, kwargs) - self.assertMultiLineEqual(rqlst.as_string(), - "Any S WHERE S documented_by C, C eid %(u)s, B eid %(D)s, " - "EXISTS(C in_state A, B in_group E, F require_state A, " - "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission), " - "(EXISTS(S ref LIKE 'PUBLIC%')) OR (EXISTS(B in_group G, G name 'public', G is CWGroup)), " - "S is Affaire") - self.failUnless('D' in kwargs) + self.assertMultiLineEqual( + rqlst.as_string(), + "Any S WHERE S documented_by C, C eid %(u)s, B eid %(D)s, " + "EXISTS(C in_state A, B in_group E, F require_state A, " + "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission), " + "(EXISTS(S ref LIKE 'PUBLIC%')) OR (EXISTS(B in_group G, G name 'public', G is CWGroup)), " + "S is Affaire") + self.assertTrue('D' in kwargs) def test_or(self): constraint = '(X identity U) OR (X in_state ST, CL identity U, CL in_state ST, ST name "subscribed")' rqlst = parse('Any S WHERE S owned_by C, C eid %(u)s, S is in (CWUser, CWGroup)') rewrite(rqlst, {('C', 'X'): (constraint,)}, {'u':1}) - self.failUnlessEqual(rqlst.as_string(), - "Any S WHERE S owned_by C, C eid %(u)s, S is IN(CWUser, CWGroup), A eid %(B)s, " - "EXISTS((C identity A) OR (C in_state D, E identity A, " - "E in_state D, D name 'subscribed'), D is State, E is CWUser)") + self.assertEqual(rqlst.as_string(), + "Any S WHERE S owned_by C, C eid %(u)s, S is IN(CWUser, CWGroup), A eid %(B)s, " + "EXISTS((C identity A) OR (C in_state D, E identity A, " + "E in_state D, D name 'subscribed'), D is State, E is CWUser)") def test_simplified_rqlst(self): constraint = ('X in_state S, U in_group G, P require_state S,' 'P name "read", P require_group G') rqlst = parse('Any 2') # this is the simplified rql st for Any X WHERE X eid 12 rewrite(rqlst, {('2', 'X'): (constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - u"Any 2 WHERE B eid %(C)s, " - "EXISTS(2 in_state A, B in_group D, E require_state A, " - "E name 'read', E require_group D, A is State, D is CWGroup, E is CWPermission)") + self.assertEqual(rqlst.as_string(), + u"Any 2 WHERE B eid %(C)s, " + "EXISTS(2 in_state A, B in_group D, E require_state A, " + "E name 'read', E require_group D, A is State, D is CWGroup, E is CWPermission)") def test_optional_var_1(self): constraint = ('X in_state S, U in_group G, P require_state S,' 'P name "read", P require_group G') rqlst = parse('Any A,C WHERE A documented_by C?') rewrite(rqlst, {('C', 'X'): (constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any A,C WHERE A documented_by C?, A is Affaire " - "WITH C BEING " - "(Any C WHERE EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', " - "G require_group F), D eid %(A)s, C is Card)") + self.assertEqual(rqlst.as_string(), + "Any A,C WHERE A documented_by C?, A is Affaire " + "WITH C BEING " + "(Any C WHERE EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', " + "G require_group F), D eid %(A)s, C is Card)") def test_optional_var_2(self): constraint = ('X in_state S, U in_group G, P require_state S,' 'P name "read", P require_group G') rqlst = parse('Any A,C,T WHERE A documented_by C?, C title T') rewrite(rqlst, {('C', 'X'): (constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any A,C,T WHERE A documented_by C?, A is Affaire " - "WITH C,T BEING " - "(Any C,T WHERE C title T, EXISTS(C in_state B, D in_group F, " - "G require_state B, G name 'read', G require_group F), " - "D eid %(A)s, C is Card)") + self.assertEqual(rqlst.as_string(), + "Any A,C,T WHERE A documented_by C?, A is Affaire " + "WITH C,T BEING " + "(Any C,T WHERE C title T, EXISTS(C in_state B, D in_group F, " + "G require_state B, G name 'read', G require_group F), " + "D eid %(A)s, C is Card)") def test_optional_var_3(self): constraint1 = ('X in_state S, U in_group G, P require_state S,' @@ -179,12 +180,12 @@ constraint2 = 'X in_state S, S name "public"' rqlst = parse('Any A,C,T WHERE A documented_by C?, C title T') rewrite(rqlst, {('C', 'X'): (constraint1, constraint2)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any A,C,T WHERE A documented_by C?, A is Affaire " - "WITH C,T BEING (Any C,T WHERE C title T, " - "EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', G require_group F), " - "D eid %(A)s, C is Card, " - "EXISTS(C in_state E, E name 'public'))") + self.assertEqual(rqlst.as_string(), + "Any A,C,T WHERE A documented_by C?, A is Affaire " + "WITH C,T BEING (Any C,T WHERE C title T, " + "EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', G require_group F), " + "D eid %(A)s, C is Card, " + "EXISTS(C in_state E, E name 'public'))") def test_optional_var_4(self): constraint1 = 'A created_by U, X documented_by A' @@ -194,7 +195,7 @@ rewrite(rqlst, {('LA', 'X'): (constraint1, constraint2), ('X', 'X'): (constraint3,), ('Y', 'X'): (constraint3,)}, {}) - self.failUnlessEqual(rqlst.as_string(), + self.assertEqual(rqlst.as_string(), u'Any X,LA,Y WHERE LA? documented_by X, LA concerne Y, B eid %(C)s, ' 'EXISTS(X created_by B), EXISTS(Y created_by B), ' 'X is Card, Y is IN(Division, Note, Societe) ' @@ -209,12 +210,12 @@ ('A', 'X'): (c2,), }, {}) # XXX suboptimal - self.failUnlessEqual(rqlst.as_string(), - "Any C,A,R WITH A,C,R BEING " - "(Any A,C,R WHERE A? inlined_card C, A ref R, " - "(A is NULL) OR (EXISTS(A inlined_card B, B require_permission D, " - "B is Card, D is CWPermission)), " - "A is Affaire, C is Card, EXISTS(C require_permission E, E is CWPermission))") + self.assertEqual(rqlst.as_string(), + "Any C,A,R WITH A,C,R BEING " + "(Any A,C,R WHERE A? inlined_card C, A ref R, " + "(A is NULL) OR (EXISTS(A inlined_card B, B require_permission D, " + "B is Card, D is CWPermission)), " + "A is Affaire, C is Card, EXISTS(C require_permission E, E is CWPermission))") # def test_optional_var_inlined_has_perm(self): # c1 = ('X require_permission P') @@ -223,7 +224,7 @@ # rewrite(rqlst, {('C', 'X'): (c1,), # ('A', 'X'): (c2,), # }, {}) - # self.failUnlessEqual(rqlst.as_string(), + # self.assertEqual(rqlst.as_string(), # "") def test_optional_var_inlined_imbricated_error(self): @@ -242,11 +243,11 @@ rqlst = parse('Any A,W WHERE A inlined_card C?, C inlined_note N, ' 'N inlined_affaire W') rewrite(rqlst, {('C', 'X'): (c1,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - 'Any A,W WHERE A inlined_card C?, A is Affaire ' - 'WITH C,N,W BEING (Any C,N,W WHERE C inlined_note N, ' - 'N inlined_affaire W, EXISTS(C require_permission B), ' - 'C is Card, N is Note, W is Affaire)') + self.assertEqual(rqlst.as_string(), + 'Any A,W WHERE A inlined_card C?, A is Affaire ' + 'WITH C,N,W BEING (Any C,N,W WHERE C inlined_note N, ' + 'N inlined_affaire W, EXISTS(C require_permission B), ' + 'C is Card, N is Note, W is Affaire)') def test_relation_optimization_1_lhs(self): # since Card in_state State as monovalued cardinality, the in_state @@ -255,66 +256,66 @@ snippet = ('X in_state S, S name "hop"') rqlst = parse('Card C WHERE C in_state STATE') rewrite(rqlst, {('C', 'X'): (snippet,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any C WHERE C in_state STATE, C is Card, " - "EXISTS(STATE name 'hop'), STATE is State") + self.assertEqual(rqlst.as_string(), + "Any C WHERE C in_state STATE, C is Card, " + "EXISTS(STATE name 'hop'), STATE is State") def test_relation_optimization_1_rhs(self): snippet = ('TW subworkflow_exit X, TW name "hop"') rqlst = parse('WorkflowTransition C WHERE C subworkflow_exit EXIT') rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any C WHERE C subworkflow_exit EXIT, C is WorkflowTransition, " - "EXISTS(C name 'hop'), EXIT is SubWorkflowExitPoint") + self.assertEqual(rqlst.as_string(), + "Any C WHERE C subworkflow_exit EXIT, C is WorkflowTransition, " + "EXISTS(C name 'hop'), EXIT is SubWorkflowExitPoint") def test_relation_optimization_2_lhs(self): # optional relation can be shared if also optional in the snippet snippet = ('X in_state S?, S name "hop"') rqlst = parse('Card C WHERE C in_state STATE?') rewrite(rqlst, {('C', 'X'): (snippet,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any C WHERE C in_state STATE?, C is Card, " - "EXISTS(STATE name 'hop'), STATE is State") + self.assertEqual(rqlst.as_string(), + "Any C WHERE C in_state STATE?, C is Card, " + "EXISTS(STATE name 'hop'), STATE is State") def test_relation_optimization_2_rhs(self): snippet = ('TW? subworkflow_exit X, TW name "hop"') rqlst = parse('SubWorkflowExitPoint EXIT WHERE C? subworkflow_exit EXIT') rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any EXIT WHERE C? subworkflow_exit EXIT, EXIT is SubWorkflowExitPoint, " - "EXISTS(C name 'hop'), C is WorkflowTransition") + self.assertEqual(rqlst.as_string(), + "Any EXIT WHERE C? subworkflow_exit EXIT, EXIT is SubWorkflowExitPoint, " + "EXISTS(C name 'hop'), C is WorkflowTransition") def test_relation_optimization_3_lhs(self): # optional relation in the snippet but not in the orig tree can be shared snippet = ('X in_state S?, S name "hop"') rqlst = parse('Card C WHERE C in_state STATE') rewrite(rqlst, {('C', 'X'): (snippet,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any C WHERE C in_state STATE, C is Card, " - "EXISTS(STATE name 'hop'), STATE is State") + self.assertEqual(rqlst.as_string(), + "Any C WHERE C in_state STATE, C is Card, " + "EXISTS(STATE name 'hop'), STATE is State") def test_relation_optimization_3_rhs(self): snippet = ('TW? subworkflow_exit X, TW name "hop"') rqlst = parse('WorkflowTransition C WHERE C subworkflow_exit EXIT') rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any C WHERE C subworkflow_exit EXIT, C is WorkflowTransition, " - "EXISTS(C name 'hop'), EXIT is SubWorkflowExitPoint") + self.assertEqual(rqlst.as_string(), + "Any C WHERE C subworkflow_exit EXIT, C is WorkflowTransition, " + "EXISTS(C name 'hop'), EXIT is SubWorkflowExitPoint") def test_relation_non_optimization_1_lhs(self): # but optional relation in the orig tree but not in the snippet can't be shared snippet = ('X in_state S, S name "hop"') rqlst = parse('Card C WHERE C in_state STATE?') rewrite(rqlst, {('C', 'X'): (snippet,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any C WHERE C in_state STATE?, C is Card, " - "EXISTS(C in_state A, A name 'hop', A is State), STATE is State") + self.assertEqual(rqlst.as_string(), + "Any C WHERE C in_state STATE?, C is Card, " + "EXISTS(C in_state A, A name 'hop', A is State), STATE is State") def test_relation_non_optimization_1_rhs(self): snippet = ('TW subworkflow_exit X, TW name "hop"') rqlst = parse('SubWorkflowExitPoint EXIT WHERE C? subworkflow_exit EXIT') rewrite(rqlst, {('EXIT', 'X'): (snippet,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any EXIT WHERE C? subworkflow_exit EXIT, EXIT is SubWorkflowExitPoint, " - "EXISTS(A subworkflow_exit EXIT, A name 'hop', A is WorkflowTransition), " - "C is WorkflowTransition") + self.assertEqual(rqlst.as_string(), + "Any EXIT WHERE C? subworkflow_exit EXIT, EXIT is SubWorkflowExitPoint, " + "EXISTS(A subworkflow_exit EXIT, A name 'hop', A is WorkflowTransition), " + "C is WorkflowTransition") def test_unsupported_constraint_1(self): # CWUser doesn't have require_permission @@ -326,109 +327,109 @@ trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"') rqlst = parse('Any U,T WHERE U is CWUser, T wf_info_for U') rewrite(rqlst, {('T', 'X'): (trinfo_constraint, 'X wf_info_for Y, Y in_group G, G name "managers"')}, {}) - self.failUnlessEqual(rqlst.as_string(), - u"Any U,T WHERE U is CWUser, T wf_info_for U, " - "EXISTS(U in_group B, B name 'managers', B is CWGroup), T is TrInfo") + self.assertEqual(rqlst.as_string(), + u"Any U,T WHERE U is CWUser, T wf_info_for U, " + "EXISTS(U in_group B, B name 'managers', B is CWGroup), T is TrInfo") def test_unsupported_constraint_3(self): self.skipTest('raise unauthorized for now') trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"') rqlst = parse('Any T WHERE T wf_info_for X') rewrite(rqlst, {('T', 'X'): (trinfo_constraint, 'X in_group G, G name "managers"')}, {}) - self.failUnlessEqual(rqlst.as_string(), - u'XXX dunno what should be generated') + self.assertEqual(rqlst.as_string(), + u'XXX dunno what should be generated') def test_add_ambiguity_exists(self): constraint = ('X concerne Y') rqlst = parse('Affaire X') rewrite(rqlst, {('X', 'X'): (constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - u"Any X WHERE X is Affaire, ((EXISTS(X concerne A, A is Division)) OR (EXISTS(X concerne C, C is Societe))) OR (EXISTS(X concerne B, B is Note))") + self.assertEqual(rqlst.as_string(), + u"Any X WHERE X is Affaire, ((EXISTS(X concerne A, A is Division)) OR (EXISTS(X concerne C, C is Societe))) OR (EXISTS(X concerne B, B is Note))") def test_add_ambiguity_outerjoin(self): constraint = ('X concerne Y') rqlst = parse('Any X,C WHERE X? documented_by C') rewrite(rqlst, {('X', 'X'): (constraint,)}, {}) # ambiguity are kept in the sub-query, no need to be resolved using OR - self.failUnlessEqual(rqlst.as_string(), - u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE EXISTS(X concerne A), X is Affaire)") + self.assertEqual(rqlst.as_string(), + u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE EXISTS(X concerne A), X is Affaire)") def test_rrqlexpr_nonexistant_subject_1(self): constraint = RRQLExpression('S owned_by U') rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SU') - self.failUnlessEqual(rqlst.as_string(), - u"Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A)") + self.assertEqual(rqlst.as_string(), + u"Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A)") rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'OU') - self.failUnlessEqual(rqlst.as_string(), - u"Any C WHERE C is Card") + self.assertEqual(rqlst.as_string(), + u"Any C WHERE C is Card") rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SOU') - self.failUnlessEqual(rqlst.as_string(), - u"Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A)") + self.assertEqual(rqlst.as_string(), + u"Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A)") def test_rrqlexpr_nonexistant_subject_2(self): constraint = RRQLExpression('S owned_by U, O owned_by U, O is Card') rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SU') - self.failUnlessEqual(rqlst.as_string(), - 'Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A)') + self.assertEqual(rqlst.as_string(), + 'Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A)') rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'OU') - self.failUnlessEqual(rqlst.as_string(), - 'Any C WHERE C is Card, B eid %(D)s, EXISTS(A owned_by B, A is Card)') + self.assertEqual(rqlst.as_string(), + 'Any C WHERE C is Card, B eid %(D)s, EXISTS(A owned_by B, A is Card)') rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SOU') - self.failUnlessEqual(rqlst.as_string(), - 'Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A, D owned_by A, D is Card)') + self.assertEqual(rqlst.as_string(), + 'Any C WHERE C is Card, A eid %(B)s, EXISTS(C owned_by A, D owned_by A, D is Card)') def test_rrqlexpr_nonexistant_subject_3(self): constraint = RRQLExpression('U in_group G, G name "users"') rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SU') - self.failUnlessEqual(rqlst.as_string(), - u'Any C WHERE C is Card, A eid %(B)s, EXISTS(A in_group D, D name "users", D is CWGroup)') + self.assertEqual(rqlst.as_string(), + u'Any C WHERE C is Card, A eid %(B)s, EXISTS(A in_group D, D name "users", D is CWGroup)') def test_rrqlexpr_nonexistant_subject_4(self): constraint = RRQLExpression('U in_group G, G name "users", S owned_by U') rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SU') - self.failUnlessEqual(rqlst.as_string(), - u'Any C WHERE C is Card, A eid %(B)s, EXISTS(A in_group D, D name "users", C owned_by A, D is CWGroup)') + self.assertEqual(rqlst.as_string(), + u'Any C WHERE C is Card, A eid %(B)s, EXISTS(A in_group D, D name "users", C owned_by A, D is CWGroup)') rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'OU') - self.failUnlessEqual(rqlst.as_string(), - u'Any C WHERE C is Card, A eid %(B)s, EXISTS(A in_group D, D name "users", D is CWGroup)') + self.assertEqual(rqlst.as_string(), + u'Any C WHERE C is Card, A eid %(B)s, EXISTS(A in_group D, D name "users", D is CWGroup)') def test_rrqlexpr_nonexistant_subject_5(self): constraint = RRQLExpression('S owned_by Z, O owned_by Z, O is Card') rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'S') - self.failUnlessEqual(rqlst.as_string(), - u"Any C WHERE C is Card, EXISTS(C owned_by A, A is CWUser)") + self.assertEqual(rqlst.as_string(), + u"Any C WHERE C is Card, EXISTS(C owned_by A, A is CWUser)") def test_rqlexpr_not_relation_1_1(self): constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X') rqlst = parse('Affaire A WHERE NOT EXISTS(A documented_by C)') rewrite(rqlst, {('C', 'X'): (constraint,)}, {}, 'X') - self.failUnlessEqual(rqlst.as_string(), - u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire') + self.assertEqual(rqlst.as_string(), + u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire') def test_rqlexpr_not_relation_1_2(self): constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X') rqlst = parse('Affaire A WHERE NOT EXISTS(A documented_by C)') rewrite(rqlst, {('A', 'X'): (constraint,)}, {}, 'X') - self.failUnlessEqual(rqlst.as_string(), - u'Any A WHERE NOT EXISTS(A documented_by C, C is Card), A is Affaire, EXISTS(A owned_by B, B login "hop", B is CWUser)') + self.assertEqual(rqlst.as_string(), + u'Any A WHERE NOT EXISTS(A documented_by C, C is Card), A is Affaire, EXISTS(A owned_by B, B login "hop", B is CWUser)') def test_rqlexpr_not_relation_2(self): constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X') rqlst = rqlhelper.parse('Affaire A WHERE NOT A documented_by C', annotate=False) rewrite(rqlst, {('C', 'X'): (constraint,)}, {}, 'X') - self.failUnlessEqual(rqlst.as_string(), - u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire') + self.assertEqual(rqlst.as_string(), + u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire') if __name__ == '__main__': diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/unittest_rset.py --- a/test/unittest_rset.py Thu Dec 08 14:29:48 2011 +0100 +++ b/test/unittest_rset.py Thu Dec 08 14:32:57 2011 +0100 @@ -430,7 +430,7 @@ def test_entities(self): rset = self.execute('Any U,G WHERE U in_group G') # make sure we have at least one element - self.failUnless(rset) + self.assertTrue(rset) self.assertEqual(set(e.e_schema.type for e in rset.entities(0)), set(['CWUser',])) self.assertEqual(set(e.e_schema.type for e in rset.entities(1)), @@ -439,7 +439,7 @@ def test_iter_rows_with_entities(self): rset = self.execute('Any U,UN,G,GN WHERE U in_group G, U login UN, G name GN') # make sure we have at least one element - self.failUnless(rset) + self.assertTrue(rset) out = list(rset.iter_rows_with_entities())[0] self.assertEqual( out[0].login, out[1] ) self.assertEqual( out[2].name, out[3] ) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/unittest_schema.py --- a/test/unittest_schema.py Thu Dec 08 14:29:48 2011 +0100 +++ b/test/unittest_schema.py Thu Dec 08 14:32:57 2011 +0100 @@ -106,9 +106,9 @@ # isinstance(cstr, RQLConstraint) # -> expected to return RQLConstraint instances but not # RRQLVocabularyConstraint and QLUniqueConstraint - self.failIf(issubclass(RQLUniqueConstraint, RQLVocabularyConstraint)) - self.failIf(issubclass(RQLUniqueConstraint, RQLConstraint)) - self.failUnless(issubclass(RQLConstraint, RQLVocabularyConstraint)) + self.assertFalse(issubclass(RQLUniqueConstraint, RQLVocabularyConstraint)) + self.assertFalse(issubclass(RQLUniqueConstraint, RQLConstraint)) + self.assertTrue(issubclass(RQLConstraint, RQLVocabularyConstraint)) def test_entity_perms(self): self.assertEqual(eperson.get_groups('read'), set(('managers', 'users', 'guests'))) @@ -161,8 +161,8 @@ entities = sorted([str(e) for e in schema.entities()]) expected_entities = ['BaseTransition', 'BigInt', 'Bookmark', 'Boolean', 'Bytes', 'Card', 'Date', 'Datetime', 'Decimal', - 'CWCache', 'CWConstraint', 'CWConstraintType', 'CWEType', - 'CWAttribute', 'CWGroup', 'EmailAddress', 'CWRelation', + 'CWCache', 'CWConstraint', 'CWConstraintType', 'CWDataImport', + 'CWEType', 'CWAttribute', 'CWGroup', 'EmailAddress', 'CWRelation', 'CWPermission', 'CWProperty', 'CWRType', 'CWSource', 'CWSourceHostConfig', 'CWSourceSchemaConfig', 'CWUniqueTogetherConstraint', 'CWUser', @@ -175,29 +175,29 @@ 'Workflow', 'WorkflowTransition'] self.assertListEqual(sorted(expected_entities), entities) relations = sorted([str(r) for r in schema.relations()]) - expected_relations = ['add_permission', 'address', 'alias', 'allowed_transition', + expected_relations = ['actionnaire', 'add_permission', 'address', 'alias', 'allowed_transition', 'associe', 'bookmarked_by', 'by_transition', 'cardinality', 'comment', 'comment_format', 'composite', 'condition', 'config', 'connait', 'constrained_by', 'constraint_of', - 'content', 'content_format', + 'content', 'content_format', 'contrat_exclusif', 'created_by', 'creation_date', 'cstrtype', 'custom_workflow', - 'cwuri', 'cw_for_source', 'cw_host_config_of', 'cw_schema', 'cw_source', + 'cwuri', 'cw_for_source', 'cw_import_of', 'cw_host_config_of', 'cw_schema', 'cw_source', 'data', 'data_encoding', 'data_format', 'data_name', 'default_workflow', 'defaultval', 'delete_permission', - 'description', 'description_format', 'destination_state', + 'description', 'description_format', 'destination_state', 'dirige', - 'ecrit_par', 'eid', 'evaluee', 'expression', 'exprtype', + 'ecrit_par', 'eid', 'end_timestamp', 'evaluee', 'expression', 'exprtype', 'fabrique_par', 'final', 'firstname', 'for_user', 'fournit', 'from_entity', 'from_state', 'fulltext_container', 'fulltextindexed', - 'has_text', + 'has_group_permission', 'has_text', 'identity', 'in_group', 'in_state', 'in_synchronization', 'indexed', 'initial_state', 'inlined', 'internationalizable', 'is', 'is_instance_of', - 'label', 'last_login_time', 'latest_retrieval', 'lieu', 'login', + 'label', 'last_login_time', 'latest_retrieval', 'lieu', 'log', 'login', 'mainvars', 'match_host', 'modification_date', @@ -209,7 +209,7 @@ 'read_permission', 'relation_type', 'relations', 'require_group', - 'specializes', 'state_of', 'subworkflow', 'subworkflow_exit', 'subworkflow_state', 'surname', 'symmetric', 'synopsis', + 'specializes', 'start_timestamp', 'state_of', 'status', 'subworkflow', 'subworkflow_exit', 'subworkflow_state', 'surname', 'symmetric', 'synopsis', 'tags', 'timestamp', 'title', 'to_entity', 'to_state', 'transition_of', 'travaille', 'type', @@ -225,12 +225,13 @@ rels = sorted(str(r) for r in eschema.subject_relations()) self.assertListEqual(rels, ['created_by', 'creation_date', 'custom_workflow', 'cw_source', 'cwuri', 'eid', - 'evaluee', 'firstname', 'has_text', 'identity', - 'in_group', 'in_state', 'is', - 'is_instance_of', 'last_login_time', - 'login', 'modification_date', 'owned_by', - 'primary_email', 'surname', 'upassword', - 'use_email']) + 'evaluee', 'firstname', 'has_group_permission', + 'has_text', 'identity', + 'in_group', 'in_state', 'is', + 'is_instance_of', 'last_login_time', + 'login', 'modification_date', 'owned_by', + 'primary_email', 'surname', 'upassword', + 'use_email']) rels = sorted(r.type for r in eschema.object_relations()) self.assertListEqual(rels, ['bookmarked_by', 'created_by', 'for_user', 'identity', 'owned_by', 'wf_info_for']) @@ -238,15 +239,15 @@ properties = rschema.rdef('CWAttribute', 'CWRType') self.assertEqual(properties.cardinality, '1*') constraints = properties.constraints - self.failUnlessEqual(len(constraints), 1, constraints) + self.assertEqual(len(constraints), 1, constraints) constraint = constraints[0] - self.failUnless(isinstance(constraint, RQLConstraint)) - self.failUnlessEqual(constraint.expression, 'O final TRUE') + self.assertTrue(isinstance(constraint, RQLConstraint)) + self.assertEqual(constraint.expression, 'O final TRUE') def test_fulltext_container(self): schema = loader.load(config) - self.failUnless('has_text' in schema['CWUser'].subject_relations()) - self.failIf('has_text' in schema['EmailAddress'].subject_relations()) + self.assertTrue('has_text' in schema['CWUser'].subject_relations()) + self.assertFalse('has_text' in schema['EmailAddress'].subject_relations()) def test_permission_settings(self): schema = loader.load(config) diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/unittest_selectors.py --- a/test/unittest_selectors.py Thu Dec 08 14:29:48 2011 +0100 +++ b/test/unittest_selectors.py Thu Dec 08 14:32:57 2011 +0100 @@ -24,7 +24,7 @@ from cubicweb import Binary from cubicweb.devtools.testlib import CubicWebTC from cubicweb.appobject import Selector, AndSelector, OrSelector -from cubicweb.selectors import (is_instance, adaptable, match_user_groups, +from cubicweb.selectors import (is_instance, adaptable, match_kwargs, match_user_groups, multi_lines_rset, score_entity, is_in_state, on_transition, rql_condition, relation_possible) from cubicweb.web import action @@ -87,11 +87,11 @@ def test_composition(self): selector = (_1_() & _1_()) & (_1_() & _1_()) - self.failUnless(isinstance(selector, AndSelector)) + self.assertTrue(isinstance(selector, AndSelector)) self.assertEqual(len(selector.selectors), 4) self.assertEqual(selector(None), 4) selector = (_1_() & _0_()) | (_1_() & _1_()) - self.failUnless(isinstance(selector, OrSelector)) + self.assertTrue(isinstance(selector, OrSelector)) self.assertEqual(len(selector.selectors), 2) self.assertEqual(selector(None), 2) @@ -151,13 +151,13 @@ rset = f.as_rset() anyscore = is_instance('Any')(f.__class__, req, rset=rset) idownscore = adaptable('IDownloadable')(f.__class__, req, rset=rset) - self.failUnless(idownscore > anyscore, (idownscore, anyscore)) + self.assertTrue(idownscore > anyscore, (idownscore, anyscore)) filescore = is_instance('File')(f.__class__, req, rset=rset) - self.failUnless(filescore > idownscore, (filescore, idownscore)) + self.assertTrue(filescore > idownscore, (filescore, idownscore)) def test_etype_inheritance_no_yams_inheritance(self): cls = self.vreg['etypes'].etype_class('Personne') - self.failIf(is_instance('Societe').score_class(cls, self.request())) + self.assertFalse(is_instance('Societe').score_class(cls, self.request())) def test_yams_inheritance(self): cls = self.vreg['etypes'].etype_class('Transition') @@ -327,7 +327,7 @@ self.vreg._loadedmods[__name__] = {} self.vreg.register(SomeAction) SomeAction.__registered__(self.vreg['actions']) - self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions']) + self.assertTrue(SomeAction in self.vreg['actions']['yo'], self.vreg['actions']) try: # login as a simple user req = self.request() @@ -336,18 +336,18 @@ # it should not be possible to use SomeAction not owned objects req = self.request() rset = req.execute('Any G WHERE G is CWGroup, G name "managers"') - self.failIf('yo' in dict(self.pactions(req, rset))) + self.assertFalse('yo' in dict(self.pactions(req, rset))) # insert a new card, and check that we can use SomeAction on our object self.execute('INSERT Card C: C title "zoubidou"') self.commit() req = self.request() rset = req.execute('Card C WHERE C title "zoubidou"') - self.failUnless('yo' in dict(self.pactions(req, rset)), self.pactions(req, rset)) + self.assertTrue('yo' in dict(self.pactions(req, rset)), self.pactions(req, rset)) # make sure even managers can't use the action self.restore_connection() req = self.request() rset = req.execute('Card C WHERE C title "zoubidou"') - self.failIf('yo' in dict(self.pactions(req, rset))) + self.assertFalse('yo' in dict(self.pactions(req, rset))) finally: del self.vreg[SomeAction.__registry__][SomeAction.__regid__] @@ -403,6 +403,20 @@ selector = multi_lines_rset(expected, operator) yield self.assertEqual, selector(None, self.req, rset=self.rset), assertion + def test_match_kwargs_default(self): + selector = match_kwargs( set( ('a', 'b') ) ) + self.assertEqual(selector(None, None, a=1, b=2), 2) + self.assertEqual(selector(None, None, a=1), 0) + self.assertEqual(selector(None, None, c=1), 0) + self.assertEqual(selector(None, None, a=1, c=1), 0) + + def test_match_kwargs_any(self): + selector = match_kwargs( set( ('a', 'b') ), mode='any') + self.assertEqual(selector(None, None, a=1, b=2), 2) + self.assertEqual(selector(None, None, a=1), 1) + self.assertEqual(selector(None, None, c=1), 0) + self.assertEqual(selector(None, None, a=1, c=1), 1) + class ScoreEntitySelectorTC(CubicWebTC): @@ -418,7 +432,7 @@ rset = req.execute('Any G LIMIT 2 WHERE G is CWGroup') selector = score_entity(lambda x: 10) self.assertEqual(selector(None, req, rset=rset), 20) - selector = score_entity(lambda x: 10, once_is_enough=True) + selector = score_entity(lambda x: 10, mode='any') self.assertEqual(selector(None, req, rset=rset), 10) def test_rql_condition_entity(self): diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/unittest_utils.py --- a/test/unittest_utils.py Thu Dec 08 14:29:48 2011 +0100 +++ b/test/unittest_utils.py Thu Dec 08 14:32:57 2011 +0100 @@ -26,7 +26,7 @@ from cubicweb.devtools.testlib import CubicWebTC from cubicweb.utils import (make_uid, UStringIO, SizeConstrainedList, - RepeatList, HTMLHead) + RepeatList, HTMLHead, QueryCache) from cubicweb.entity import Entity try: @@ -50,6 +50,55 @@ 'some numeric character, got %s' % uid) d.add(uid) +class TestQueryCache(TestCase): + def test_querycache(self): + c = QueryCache(ceiling=20) + # write only + for x in xrange(10): + c[x] = x + self.assertEqual(c._usage_report(), + {'transientcount': 0, + 'itemcount': 10, + 'permanentcount': 0}) + c = QueryCache(ceiling=10) + # we should also get a warning + for x in xrange(20): + c[x] = x + self.assertEqual(c._usage_report(), + {'transientcount': 0, + 'itemcount': 10, + 'permanentcount': 0}) + # write + reads + c = QueryCache(ceiling=20) + for n in xrange(4): + for x in xrange(10): + c[x] = x + c[x] + self.assertEqual(c._usage_report(), + {'transientcount': 10, + 'itemcount': 10, + 'permanentcount': 0}) + c = QueryCache(ceiling=20) + for n in xrange(17): + for x in xrange(10): + c[x] = x + c[x] + self.assertEqual(c._usage_report(), + {'transientcount': 0, + 'itemcount': 10, + 'permanentcount': 10}) + c = QueryCache(ceiling=20) + for n in xrange(17): + for x in xrange(10): + c[x] = x + if n % 2: + c[x] + if x % 2: + c[x] + self.assertEqual(c._usage_report(), + {'transientcount': 5, + 'itemcount': 10, + 'permanentcount': 5}) class UStringIOTC(TestCase): def test_boolean_value(self): @@ -67,7 +116,7 @@ # XXX self.assertEqual(l[4], (1, 3)) - self.failIf(RepeatList(0, None)) + self.assertFalse(RepeatList(0, None)) def test_slice(self): l = RepeatList(3, (1, 3)) @@ -159,9 +208,17 @@ self.assertEqual(self.encode(TestCase), 'null') class HTMLHeadTC(CubicWebTC): + + def htmlhead(self, datadir_url): + req = self.request() + base_url = u'http://test.fr/data/' + req.datadir_url = base_url + head = HTMLHead(req) + return head + def test_concat_urls(self): base_url = u'http://test.fr/data/' - head = HTMLHead(base_url) + head = self.htmlhead(base_url) urls = [base_url + u'bob1.js', base_url + u'bob2.js', base_url + u'bob3.js'] @@ -171,7 +228,7 @@ def test_group_urls(self): base_url = u'http://test.fr/data/' - head = HTMLHead(base_url) + head = self.htmlhead(base_url) urls_spec = [(base_url + u'bob0.js', None), (base_url + u'bob1.js', None), (u'http://ext.com/bob2.js', None), @@ -196,7 +253,7 @@ def test_getvalue_with_concat(self): base_url = u'http://test.fr/data/' - head = HTMLHead(base_url) + head = self.htmlhead(base_url) head.add_js(base_url + u'bob0.js') head.add_js(base_url + u'bob1.js') head.add_js(u'http://ext.com/bob2.js') @@ -224,20 +281,22 @@ self.assertEqual(result, expected) def test_getvalue_without_concat(self): - base_url = u'http://test.fr/data/' - head = HTMLHead() - head.add_js(base_url + u'bob0.js') - head.add_js(base_url + u'bob1.js') - head.add_js(u'http://ext.com/bob2.js') - head.add_js(u'http://ext.com/bob3.js') - head.add_css(base_url + u'bob4.css') - head.add_css(base_url + u'bob5.css') - head.add_css(base_url + u'bob6.css', 'print') - head.add_css(base_url + u'bob7.css', 'print') - head.add_ie_css(base_url + u'bob8.css') - head.add_ie_css(base_url + u'bob9.css', 'print', u'[if lt IE 7]') - result = head.getvalue() - expected = u""" + self.config.global_set_option('concat-resources', False) + try: + base_url = u'http://test.fr/data/' + head = self.htmlhead(base_url) + head.add_js(base_url + u'bob0.js') + head.add_js(base_url + u'bob1.js') + head.add_js(u'http://ext.com/bob2.js') + head.add_js(u'http://ext.com/bob3.js') + head.add_css(base_url + u'bob4.css') + head.add_css(base_url + u'bob5.css') + head.add_css(base_url + u'bob6.css', 'print') + head.add_css(base_url + u'bob7.css', 'print') + head.add_ie_css(base_url + u'bob8.css') + head.add_ie_css(base_url + u'bob9.css', 'print', u'[if lt IE 7]') + result = head.getvalue() + expected = u""" @@ -253,7 +312,9 @@ """ - self.assertEqual(result, expected) + self.assertEqual(result, expected) + finally: + self.config.global_set_option('concat-resources', True) class DocTest(DocTest): from cubicweb import utils as module diff -r 7b2c7f3d3703 -r 29cdde6bb9ef test/unittest_vregistry.py --- a/test/unittest_vregistry.py Thu Dec 08 14:29:48 2011 +0100 +++ b/test/unittest_vregistry.py Thu Dec 08 14:32:57 2011 +0100 @@ -56,7 +56,7 @@ def test_load_subinterface_based_appobjects(self): self.vreg.register_objects([join(BASE, 'web', 'views', 'iprogress.py')]) # check progressbar was kicked - self.failIf(self.vreg['views'].get('progressbar')) + self.assertFalse(self.vreg['views'].get('progressbar')) # we've to emulate register_objects to add custom MyCard objects path = [join(BASE, 'entities', '__init__.py'), join(BASE, 'entities', 'adapters.py'), @@ -74,8 +74,8 @@ def test_properties(self): self.vreg.reset() - self.failIf('system.version.cubicweb' in self.vreg['propertydefs']) - self.failUnless(self.vreg.property_info('system.version.cubicweb')) + self.assertFalse('system.version.cubicweb' in self.vreg['propertydefs']) + self.assertTrue(self.vreg.property_info('system.version.cubicweb')) self.assertRaises(UnknownProperty, self.vreg.property_info, 'a.non.existent.key') diff -r 7b2c7f3d3703 -r 29cdde6bb9ef toolsutils.py --- a/toolsutils.py Thu Dec 08 14:29:48 2011 +0100 +++ b/toolsutils.py Thu Dec 08 14:32:57 2011 +0100 @@ -180,7 +180,7 @@ 'Section %s is defined more than once' % section config[section] = current = {} continue - print >> sys.stderr, 'ignoring malformed line\n%r' % line + sys.stderr.write('ignoring malformed line\n%r\n' % line) continue option = option.strip().replace(' ', '_') value = value.strip() diff -r 7b2c7f3d3703 -r 29cdde6bb9ef uilib.py --- a/uilib.py Thu Dec 08 14:29:48 2011 +0100 +++ b/uilib.py Thu Dec 08 14:32:57 2011 +0100 @@ -30,6 +30,7 @@ from logilab.mtconverter import xml_escape, html_unescape from logilab.common.date import ustrftime +from logilab.common.deprecation import deprecated from cubicweb.utils import JSString, json_dumps @@ -60,6 +61,9 @@ return req._(value) return value +def print_int(value, req, props, displaytime=True): + return unicode(value) + def print_date(value, req, props, displaytime=True): return ustrftime(value, req.property_value('ui.date-format')) @@ -79,6 +83,39 @@ return ustrftime(value, req.property_value('ui.datetime-format')) + u' UTC' return ustrftime(value, req.property_value('ui.date-format')) +_('%d years') +_('%d months') +_('%d weeks') +_('%d days') +_('%d hours') +_('%d minutes') +_('%d seconds') + +def print_timedelta(value, req, props, displaytime=True): + if isinstance(value, (int, long)): + # `date - date`, unlike `datetime - datetime` gives an int + # (number of days), not a timedelta + # XXX should rql be fixed to return Int instead of Interval in + # that case? that would be probably the proper fix but we + # loose information on the way... + value = timedelta(days=value) + if value.days > 730 or value.days < -730: # 2 years + return req._('%d years') % (value.days // 365) + elif value.days > 60 or value.days < -60: # 2 months + return req._('%d months') % (value.days // 30) + elif value.days > 14 or value.days < -14: # 2 weeks + return req._('%d weeks') % (value.days // 7) + elif value.days > 2 or value.days < -2: + return req._('%d days') % int(value.days) + else: + minus = 1 if value.days > 0 else -1 + if value.seconds > 3600: + return req._('%d hours') % (int(value.seconds // 3600) * minus) + elif value.seconds >= 120: + return req._('%d minutes') % (int(value.seconds // 60) * minus) + else: + return req._('%d seconds') % (int(value.seconds) * minus) + def print_boolean(value, req, props, displaytime=True): if value: return req._('yes') @@ -90,6 +127,8 @@ PRINTERS = { 'Bytes': print_bytes, 'String': print_string, + 'Int': print_int, + 'BigInt': print_int, 'Date': print_date, 'Time': print_time, 'TZTime': print_tztime, @@ -98,19 +137,29 @@ 'Boolean': print_boolean, 'Float': print_float, 'Decimal': print_float, - # XXX Interval + 'Interval': print_timedelta, } +@deprecated('[3.14] use req.printable_value(attrtype, value, ...)') def printable_value(req, attrtype, value, props=None, displaytime=True): - """return a displayable value (i.e. unicode string)""" - if value is None: - return u'' - try: - printer = PRINTERS[attrtype] - except KeyError: - return unicode(value) - return printer(value, req, props, displaytime) + return req.printable_value(attrtype, value, props, displaytime) +def css_em_num_value(vreg, propname, default): + """ we try to read an 'em' css property + if we get another unit we're out of luck and resort to the given default + (hence, it is strongly advised not to specify but ems for this css prop) + """ + propvalue = vreg.config.uiprops[propname].lower().strip() + if propvalue.endswith('em'): + try: + return float(propvalue[:-2]) + except Exception: + vreg.warning('css property %s looks malformed (%r)', + propname, propvalue) + else: + vreg.warning('css property %s should use em (currently is %r)', + propname, propvalue) + return default # text publishing ############################################################# diff -r 7b2c7f3d3703 -r 29cdde6bb9ef utils.py --- a/utils.py Thu Dec 08 14:29:48 2011 +0100 +++ b/utils.py Thu Dec 08 14:32:57 2011 +0100 @@ -17,17 +17,24 @@ # with CubicWeb. If not, see . """Some utilities for CubicWeb server/clients.""" +from __future__ import division, with_statement + __docformat__ = "restructuredtext en" -import os import sys import decimal import datetime import random +import re + +from operator import itemgetter from inspect import getargspec from itertools import repeat from uuid import uuid4 from warnings import warn +from threading import Lock + +from logging import getLogger from logilab.mtconverter import xml_escape from logilab.common.deprecation import deprecated @@ -227,7 +234,7 @@ xhtml_safe_script_opening = u'' - def __init__(self, datadir_url=None): + def __init__(self, req): super(HTMLHead, self).__init__() self.jsvars = [] self.jsfiles = [] @@ -235,8 +242,8 @@ self.ie_cssfiles = [] self.post_inlined_scripts = [] self.pagedata_unload = False - self.datadir_url = datadir_url - + self._cw = req + self.datadir_url = req.datadir_url def add_raw(self, rawheader): self.write(rawheader) @@ -348,20 +355,26 @@ w(vardecl + u'\n') w(self.xhtml_safe_script_closing) # 2/ css files - for cssfile, media in (self.group_urls(self.cssfiles) if self.datadir_url else self.cssfiles): + ie_cssfiles = ((x, (y, z)) for x, y, z in self.ie_cssfiles) + if self.datadir_url and self._cw.vreg.config['concat-resources']: + cssfiles = self.group_urls(self.cssfiles) + ie_cssfiles = self.group_urls(ie_cssfiles) + jsfiles = (x for x, _ in self.group_urls((x, None) for x in self.jsfiles)) + else: + cssfiles = self.cssfiles + jsfiles = self.jsfiles + for cssfile, media in cssfiles: w(u'\n' % (media, xml_escape(cssfile))) # 3/ ie css if necessary - if self.ie_cssfiles: - ie_cssfiles = ((x, (y, z)) for x, y, z in self.ie_cssfiles) - for cssfile, (media, iespec) in (self.group_urls(ie_cssfiles) if self.datadir_url else ie_cssfiles): + if self.ie_cssfiles: # use self.ie_cssfiles because `ie_cssfiles` is a genexp + for cssfile, (media, iespec) in ie_cssfiles: w(u' \n') # 4/ js files - jsfiles = ((x, None) for x in self.jsfiles) - for jsfile, media in self.group_urls(jsfiles) if self.datadir_url else jsfiles: + for jsfile in jsfiles: if skiphead: # Don't insert