# HG changeset patch # User Aurelien Campeas # Date 1319644746 -7200 # Node ID 98222e3bb804dee0ebf9fc9b618bb976a5810274 # Parent ff8ea924b10f21766af8599028d40e3fbebe2804# Parent 11caeed5ae80deac7c70663e1b91a454cb581e23 backport stable diff -r 11caeed5ae80 -r 98222e3bb804 __pkginfo__.py --- a/__pkginfo__.py Wed Oct 26 17:58:21 2011 +0200 +++ b/__pkginfo__.py Wed Oct 26 17:59:06 2011 +0200 @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 13, 9) +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 diff -r 11caeed5ae80 -r 98222e3bb804 appobject.py --- a/appobject.py Wed Oct 26 17:58:21 2011 +0200 +++ b/appobject.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 bin/clone_deps.py --- a/bin/clone_deps.py Wed Oct 26 17:58:21 2011 +0200 +++ b/bin/clone_deps.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 common/__init__.py --- a/common/__init__.py Wed Oct 26 17:58:21 2011 +0200 +++ /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 11caeed5ae80 -r 98222e3bb804 common/mail.py --- a/common/mail.py Wed Oct 26 17:58:21 2011 +0200 +++ /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 11caeed5ae80 -r 98222e3bb804 common/mixins.py --- a/common/mixins.py Wed Oct 26 17:58:21 2011 +0200 +++ /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 11caeed5ae80 -r 98222e3bb804 common/mttransforms.py --- a/common/mttransforms.py Wed Oct 26 17:58:21 2011 +0200 +++ /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 11caeed5ae80 -r 98222e3bb804 common/tags.py --- a/common/tags.py Wed Oct 26 17:58:21 2011 +0200 +++ /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 11caeed5ae80 -r 98222e3bb804 common/uilib.py --- a/common/uilib.py Wed Oct 26 17:58:21 2011 +0200 +++ /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 11caeed5ae80 -r 98222e3bb804 cwconfig.py --- a/cwconfig.py Wed Oct 26 17:58:21 2011 +0200 +++ b/cwconfig.py Wed Oct 26 17:59:06 2011 +0200 @@ -612,7 +612,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): @@ -621,14 +620,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) @@ -762,13 +753,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 11caeed5ae80 -r 98222e3bb804 cwctl.py --- a/cwctl.py Wed Oct 26 17:58:21 2011 +0200 +++ b/cwctl.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 cwvreg.py --- a/cwvreg.py Wed Oct 26 17:58:21 2011 +0200 +++ b/cwvreg.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 dbapi.py --- a/dbapi.py Wed Oct 26 17:58:21 2011 +0200 +++ b/dbapi.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 debian/control --- a/debian/control Wed Oct 26 17:58:21 2011 +0200 +++ b/debian/control Wed Oct 26 17:59:06 2011 +0200 @@ -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.5.0), 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 11caeed5ae80 -r 98222e3bb804 devtools/__init__.py --- a/devtools/__init__.py Wed Oct 26 17:58:21 2011 +0200 +++ b/devtools/__init__.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 devtools/devctl.py --- a/devtools/devctl.py Wed Oct 26 17:58:21 2011 +0200 +++ b/devtools/devctl.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 devtools/fill.py --- a/devtools/fill.py Wed Oct 26 17:58:21 2011 +0200 +++ b/devtools/fill.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 devtools/test/unittest_dbfill.py --- a/devtools/test/unittest_dbfill.py Wed Oct 26 17:58:21 2011 +0200 +++ b/devtools/test/unittest_dbfill.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 devtools/test/unittest_fill.py --- a/devtools/test/unittest_fill.py Wed Oct 26 17:58:21 2011 +0200 +++ b/devtools/test/unittest_fill.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 devtools/testlib.py --- a/devtools/testlib.py Wed Oct 26 17:58:21 2011 +0200 +++ b/devtools/testlib.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 doc/3.14.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/3.14.rst Wed Oct 26 17:59:06 2011 +0200 @@ -0,0 +1,110 @@ +Whats new in CubicWeb 3.14 +========================== + +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. No backward compat issue known (yet...) + +* Table views refactoring : new RsetTableView and EntityTableView, as well as + rewritten an enhanced version of PyValTableView on the same bases. Those + 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. + + +Unintrusive API changes +----------------------- + +* refactored properties forms (eg user preferences and site wide properties) to + ease overridding + +* table view allows to set None in 'headers', meaning the label should be fetched + from the result set as done by default + +* new `anonymized_request` decorator to temporary run stuff as an anonymous + user, whatever the currently logged in user + +* new 'verbatimattr' attribute view + + +User interface changes +---------------------- + +* breadcrumb 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 +------------ + +* add option 'resources-concat' to make javascript/css files concatenation + optional diff -r 11caeed5ae80 -r 98222e3bb804 doc/book/en/devrepo/datamodel/baseschema.rst --- a/doc/book/en/devrepo/datamodel/baseschema.rst Wed Oct 26 17:58:21 2011 +0200 +++ b/doc/book/en/devrepo/datamodel/baseschema.rst Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 doc/book/en/devrepo/datamodel/definition.rst --- a/doc/book/en/devrepo/datamodel/definition.rst Wed Oct 26 17:58:21 2011 +0200 +++ b/doc/book/en/devrepo/datamodel/definition.rst Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 doc/book/en/devrepo/entityclasses/application-logic.rst --- a/doc/book/en/devrepo/entityclasses/application-logic.rst Wed Oct 26 17:58:21 2011 +0200 +++ b/doc/book/en/devrepo/entityclasses/application-logic.rst Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 doc/book/en/devrepo/entityclasses/load-sort.rst --- a/doc/book/en/devrepo/entityclasses/load-sort.rst Wed Oct 26 17:58:21 2011 +0200 +++ b/doc/book/en/devrepo/entityclasses/load-sort.rst Wed Oct 26 17:59:06 2011 +0200 @@ -4,50 +4,36 @@ 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: -* 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 all entities of a given type, or entities related to another - class MyEntity(AnyEntity): - __regid__ = 'MyEntity' - fetch_attrs = ('modification_date', 'name') + - retrieving a list of entities for use in drop-down lists enabling relations + creation in the editing view of an entity - @classmethod - def fetch_unrelated_order(cls, attr, var): - if attr == 'name': - return '%s ASC' % var - return None +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`. +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: -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`. +.. 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: -For example: :: +.. autofunction:: cubicweb.entity.Entity.cw_fetch_order - class Transition(AnyEntity): - """...""" - __regid__ = 'Transition' - fetch_attrs, fetch_order = fetch_config(['name']) +.. autofunction:: cubicweb.entity.Entity.cw_fetch_unrelated_order -Indicates that for the entity type "Transition", you have to pre-load -the attribute `name` and sort by default on this attribute. diff -r 11caeed5ae80 -r 98222e3bb804 doc/book/en/devrepo/vreg.rst --- a/doc/book/en/devrepo/vreg.rst Wed Oct 26 17:58:21 2011 +0200 +++ b/doc/book/en/devrepo/vreg.rst Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 doc/book/en/devweb/controllers.rst --- a/doc/book/en/devweb/controllers.rst Wed Oct 26 17:58:21 2011 +0200 +++ b/doc/book/en/devweb/controllers.rst Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 doc/book/en/devweb/edition/form.rst --- a/doc/book/en/devweb/edition/form.rst Wed Oct 26 17:58:21 2011 +0200 +++ b/doc/book/en/devweb/edition/form.rst Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 doc/book/en/devweb/views/table.rst --- a/doc/book/en/devweb/views/table.rst Wed Oct 26 17:58:21 2011 +0200 +++ b/doc/book/en/devweb/views/table.rst Wed Oct 26 17:59:06 2011 +0200 @@ -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,135 @@ .. 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.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. + + + 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 11caeed5ae80 -r 98222e3bb804 doc/book/en/tutorials/advanced/part04_ui-base.rst --- a/doc/book/en/tutorials/advanced/part04_ui-base.rst Wed Oct 26 17:58:21 2011 +0200 +++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 doc/book/en/tutorials/base/customizing-the-application.rst --- a/doc/book/en/tutorials/base/customizing-the-application.rst Wed Oct 26 17:58:21 2011 +0200 +++ b/doc/book/en/tutorials/base/customizing-the-application.rst Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 entities/__init__.py --- a/entities/__init__.py Wed Oct 26 17:58:21 2011 +0200 +++ b/entities/__init__.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 entities/authobjs.py --- a/entities/authobjs.py Wed Oct 26 17:58:21 2011 +0200 +++ b/entities/authobjs.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 entities/lib.py --- a/entities/lib.py Wed Oct 26 17:58:21 2011 +0200 +++ b/entities/lib.py Wed Oct 26 17:59:06 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -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 11caeed5ae80 -r 98222e3bb804 entities/schemaobjs.py --- a/entities/schemaobjs.py Wed Oct 26 17:58:21 2011 +0200 +++ b/entities/schemaobjs.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 entities/sources.py --- a/entities/sources.py Wed Oct 26 17:58:21 2011 +0200 +++ b/entities/sources.py Wed Oct 26 17:59:06 2011 +0200 @@ -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,52 @@ @property def cwsource(self): return self.cw_for_source[0] + + +class CWDataImport(AnyEntity): + __regid__ = 'CWDataImport' + + def __init__(self, *args, **kwargs): + super(CWDataImport, self).__init__(*args, **kwargs) + self._logs = [] + + 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.debug(msg) + + def record_info(self, msg, path=None, line=None): + self._log(logging.INFO, msg, path, line) + self.info(msg) + + def record_warning(self, msg, path=None, line=None): + self._log(logging.WARNING, msg, path, line) + self.warning(msg) + + def record_error(self, msg, path=None, line=None): + self._status = u'failed' + self._log(logging.ERROR, msg, path, line) + self.error(msg) + + def record_fatal(self, msg, path=None, line=None): + self._status = u'failed' + self._log(logging.FATAL, msg, path, line) + self.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 11caeed5ae80 -r 98222e3bb804 entities/test/unittest_base.py --- a/entities/test/unittest_base.py Wed Oct 26 17:58:21 2011 +0200 +++ b/entities/test/unittest_base.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 entities/test/unittest_wfobjs.py --- a/entities/test/unittest_wfobjs.py Wed Oct 26 17:58:21 2011 +0200 +++ b/entities/test/unittest_wfobjs.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 entities/wfobjs.py --- a/entities/wfobjs.py Wed Oct 26 17:58:21 2011 +0200 +++ b/entities/wfobjs.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 entity.py --- a/entity.py Wed Oct 26 17:58:21 2011 +0200 +++ b/entity.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 etwist/test/unittest_server.py --- a/etwist/test/unittest_server.py Wed Oct 26 17:58:21 2011 +0200 +++ b/etwist/test/unittest_server.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 hooks/__init__.py --- a/hooks/__init__.py Wed Oct 26 17:58:21 2011 +0200 +++ b/hooks/__init__.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 hooks/test/unittest_bookmarks.py --- a/hooks/test/unittest_bookmarks.py Wed Oct 26 17:58:21 2011 +0200 +++ b/hooks/test/unittest_bookmarks.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 hooks/test/unittest_hooks.py --- a/hooks/test/unittest_hooks.py Wed Oct 26 17:58:21 2011 +0200 +++ b/hooks/test/unittest_hooks.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 hooks/test/unittest_integrity.py --- a/hooks/test/unittest_integrity.py Wed Oct 26 17:58:21 2011 +0200 +++ b/hooks/test/unittest_integrity.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 hooks/test/unittest_syncschema.py --- a/hooks/test/unittest_syncschema.py Wed Oct 26 17:58:21 2011 +0200 +++ b/hooks/test/unittest_syncschema.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 i18n.py --- a/i18n.py Wed Oct 26 17:58:21 2011 +0200 +++ b/i18n.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 i18n/de.po --- a/i18n/de.po Wed Oct 26 17:58:21 2011 +0200 +++ b/i18n/de.po Wed Oct 26 17:59:06 2011 +0200 @@ -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 "" @@ -324,6 +296,12 @@ msgid "CWConstraint_plural" msgstr "Einschränkungen" +msgid "CWDataImport" +msgstr "" + +msgid "CWDataImport_plural" +msgstr "" + msgid "CWEType" msgstr "Entitätstyp" @@ -344,12 +322,6 @@ msgid "CWGroup_plural" msgstr "Gruppen" -msgid "CWPermission" -msgstr "Berechtigung" - -msgid "CWPermission_plural" -msgstr "Berechtigungen" - msgid "CWProperty" msgstr "Eigenschaft" @@ -451,6 +423,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 +460,9 @@ msgid "Download schema as OWL" msgstr "Herunterladen des Schemas im OWL-Format" +msgid "ERROR" +msgstr "" + msgid "EmailAddress" msgstr "Email-Adresse" @@ -504,6 +485,9 @@ msgid "ExternalUri_plural" msgstr "Externe Uris" +msgid "FATAL" +msgstr "" + msgid "Float" msgstr "Gleitkommazahl" @@ -528,6 +512,9 @@ msgid "Help" msgstr "Hilfe" +msgid "INFO" +msgstr "" + msgid "Instance" msgstr "Instanz" @@ -552,6 +539,12 @@ 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 " @@ -1091,9 +1093,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" @@ -1873,6 +1872,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 +1906,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 +1975,9 @@ msgid "cwrtype-permissions" msgstr "Berechtigungen" +msgid "cwsource-imports" +msgstr "" + msgid "cwsource-main" msgstr "" @@ -2080,9 +2102,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 +2275,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 +2312,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 +2352,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 +2439,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 +2469,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)" @@ -2631,9 +2653,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" @@ -2730,6 +2749,9 @@ msgid "image" msgstr "Bild" +msgid "in progress" +msgstr "" + msgid "in_group" msgstr "in der Gruppe" @@ -2828,9 +2850,6 @@ msgid "instance home" msgstr "Startseite der Instanz" -msgid "instance schema" -msgstr "Schema der Instanz" - msgid "internal entity uri" msgstr "interner URI" @@ -2891,19 +2910,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 +2944,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 +2966,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 +3001,13 @@ msgid "list" msgstr "Liste" +msgid "log" +msgstr "" + +msgctxt "CWDataImport" +msgid "log" +msgstr "" + msgid "log in" msgstr "anmelden" @@ -3037,9 +3060,6 @@ msgid "manage permissions" msgstr "Rechte verwalten" -msgid "manage security" -msgstr "Sicherheitsverwaltung" - msgid "managers" msgstr "Administratoren" @@ -3074,6 +3094,9 @@ msgid "memory leak debugging" msgstr "Fehlersuche bei Speicherlöschern" +msgid "message" +msgstr "" + msgid "milestone" msgstr "Meilenstein" @@ -3131,10 +3154,6 @@ msgid "name" msgstr "Name" -msgctxt "CWPermission" -msgid "name" -msgstr "Name" - msgctxt "CWRType" msgid "name" msgstr "Name" @@ -3172,9 +3191,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 +3227,6 @@ msgid "no" msgstr "Nein" -msgid "no associated permissions" -msgstr "keine entsprechende Berechtigung" - msgid "no content next link" msgstr "" @@ -3227,6 +3240,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 +3277,9 @@ msgid "november" msgstr "November" +msgid "num. users" +msgstr "" + msgid "object" msgstr "Objekt" @@ -3327,9 +3346,6 @@ msgid "owners" msgstr "Besitzer" -msgid "ownership" -msgstr "Eigentum" - msgid "ownerships have been changed" msgstr "Die Eigentumsrechte sind geändert worden." @@ -3361,15 +3377,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" @@ -3567,10 +3583,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 +3598,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" @@ -3647,9 +3653,6 @@ msgid "saturday" msgstr "Samstag" -msgid "schema's permissions definitions" -msgstr "Im Schema definierte Rechte" - msgid "schema-diagram" msgstr "Diagramm" @@ -3728,6 +3731,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 +3770,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 +3812,13 @@ "synchronization in progress." msgstr "" +msgid "start_timestamp" +msgstr "" + +msgctxt "CWDataImport" +msgid "start_timestamp" +msgstr "" + msgid "startup views" msgstr "Start-Ansichten" @@ -3854,6 +3864,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 +3941,9 @@ msgid "subworkflow_state_object" msgstr "Endzustand von" +msgid "success" +msgstr "" + msgid "sunday" msgstr "Sonntag" @@ -4281,9 +4301,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 +4324,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 +4337,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 +4363,6 @@ msgid "users and groups" msgstr "" -msgid "users and groups management" -msgstr "" - msgid "users using this bookmark" msgstr "Nutzer, die dieses Lesezeichen verwenden" @@ -4544,3 +4552,30 @@ #, python-format msgid "you should un-inline relation %s which is supported and may be crossed " msgstr "" + +#~ msgid "%d days" +#~ msgstr "%d Tage" + +#~ msgid "%d hours" +#~ msgstr "%d Stunden" + +#~ msgid "%d minutes" +#~ msgstr "%d Minuten" + +#~ msgid "%d months" +#~ msgstr "%d Monate" + +#~ msgid "%d seconds" +#~ msgstr "%d Sekunden" + +#~ msgid "%d weeks" +#~ msgstr "%d Wochen" + +#~ msgid "%d years" +#~ msgstr "%d Jahre" + +#~ msgid "Schema of the data model" +#~ msgstr "Schema des Datenmodells" + +#~ msgid "instance schema" +#~ msgstr "Schema der Instanz" diff -r 11caeed5ae80 -r 98222e3bb804 i18n/en.po --- a/i18n/en.po Wed Oct 26 17:58:21 2011 +0200 +++ b/i18n/en.po Wed Oct 26 17:59:06 2011 +0200 @@ -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 "" @@ -313,6 +285,12 @@ msgid "CWConstraint_plural" msgstr "Constraints" +msgid "CWDataImport" +msgstr "Data import" + +msgid "CWDataImport_plural" +msgstr "Data imports" + msgid "CWEType" msgstr "Entity type" @@ -333,12 +311,6 @@ msgid "CWGroup_plural" msgstr "Groups" -msgid "CWPermission" -msgstr "Permission" - -msgid "CWPermission_plural" -msgstr "Permissions" - msgid "CWProperty" msgstr "Property" @@ -427,6 +399,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 +436,9 @@ msgid "Download schema as OWL" msgstr "" +msgid "ERROR" +msgstr "" + msgid "EmailAddress" msgstr "Email address" @@ -480,6 +461,9 @@ msgid "ExternalUri_plural" msgstr "External Uris" +msgid "FATAL" +msgstr "" + msgid "Float" msgstr "Float" @@ -504,6 +488,9 @@ msgid "Help" msgstr "" +msgid "INFO" +msgstr "" + msgid "Instance" msgstr "" @@ -528,6 +515,12 @@ 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" @@ -1051,9 +1053,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" @@ -1828,6 +1827,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 +1861,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 +1930,9 @@ msgid "cwrtype-permissions" msgstr "permissions" +msgid "cwsource-imports" +msgstr "" + msgid "cwsource-main" msgstr "description" @@ -2031,9 +2053,6 @@ msgid "delete this bookmark" msgstr "" -msgid "delete this permission" -msgstr "" - msgid "delete this relation" msgstr "" @@ -2203,11 +2222,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 +2259,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 +2299,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 +2384,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 +2414,9 @@ msgid "facets_in_state-facet_description" msgstr "" +msgid "failed" +msgstr "" + #, python-format msgid "failed to uniquify path (%s, %s)" msgstr "" @@ -2566,9 +2591,6 @@ msgid "groups grant permissions to the user" msgstr "" -msgid "groups to which the permission is granted" -msgstr "" - msgid "guests" msgstr "" @@ -2657,6 +2679,9 @@ msgid "image" msgstr "" +msgid "in progress" +msgstr "" + msgid "in_group" msgstr "in group" @@ -2753,9 +2778,6 @@ msgid "instance home" msgstr "" -msgid "instance schema" -msgstr "" - msgid "internal entity uri" msgstr "" @@ -2811,19 +2833,18 @@ msgid "january" msgstr "" +msgid "json-entities-export-view" +msgstr "" + +msgid "json-export-view" +msgstr "" + msgid "july" msgstr "" msgid "june" msgstr "" -msgid "label" -msgstr "" - -msgctxt "CWPermission" -msgid "label" -msgstr "label" - msgid "language of the user interface" msgstr "" @@ -2846,6 +2867,9 @@ msgid "last_login_time" msgstr "last login time" +msgid "latest import" +msgstr "" + msgid "latest modification time of an entity" msgstr "" @@ -2865,9 +2889,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 +2921,13 @@ msgid "list" msgstr "" +msgid "log" +msgstr "" + +msgctxt "CWDataImport" +msgid "log" +msgstr "" + msgid "log in" msgstr "" @@ -2950,9 +2979,6 @@ msgid "manage permissions" msgstr "" -msgid "manage security" -msgstr "" - msgid "managers" msgstr "" @@ -2987,6 +3013,9 @@ msgid "memory leak debugging" msgstr "" +msgid "message" +msgstr "" + msgid "milestone" msgstr "" @@ -3044,10 +3073,6 @@ msgid "name" msgstr "name" -msgctxt "CWPermission" -msgid "name" -msgstr "name" - msgctxt "CWRType" msgid "name" msgstr "name" @@ -3083,9 +3108,6 @@ msgid "name of the source" msgstr "" -msgid "name or identifier of the permission" -msgstr "" - msgid "navbottom" msgstr "page bottom" @@ -3122,9 +3144,6 @@ msgid "no" msgstr "" -msgid "no associated permissions" -msgstr "" - msgid "no content next link" msgstr "" @@ -3138,6 +3157,9 @@ msgid "no edited fields specified for entity %s" msgstr "" +msgid "no log to display" +msgstr "" + msgid "no related entity" msgstr "" @@ -3172,6 +3194,9 @@ msgid "november" msgstr "" +msgid "num. users" +msgstr "" + msgid "object" msgstr "" @@ -3238,9 +3263,6 @@ msgid "owners" msgstr "" -msgid "ownership" -msgstr "" - msgid "ownerships have been changed" msgstr "" @@ -3271,15 +3293,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 "" @@ -3477,10 +3499,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 +3514,6 @@ msgid "require_group_object" msgstr "required by" -msgid "require_permission" -msgstr "require permission" - -msgid "require_permission_object" -msgstr "required by" - msgid "required" msgstr "" @@ -3554,9 +3566,6 @@ msgid "saturday" msgstr "" -msgid "schema's permissions definitions" -msgstr "" - msgid "schema-diagram" msgstr "diagram" @@ -3635,6 +3644,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 +3678,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 +3718,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 +3768,13 @@ msgid "state_of_object" msgstr "use states" +msgid "status" +msgstr "" + +msgctxt "CWDataImport" +msgid "status" +msgstr "status" + msgid "status change" msgstr "" @@ -3820,6 +3843,9 @@ msgid "subworkflow_state_object" msgstr "exit point" +msgid "success" +msgstr "" + msgid "sunday" msgstr "" @@ -4176,9 +4202,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 +4222,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 +4231,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 +4255,6 @@ msgid "users and groups" msgstr "" -msgid "users and groups management" -msgstr "" - msgid "users using this bookmark" msgstr "" diff -r 11caeed5ae80 -r 98222e3bb804 i18n/es.po --- a/i18n/es.po Wed Oct 26 17:58:21 2011 +0200 +++ b/i18n/es.po Wed Oct 26 17:59:06 2011 +0200 @@ -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" @@ -325,6 +297,12 @@ msgid "CWConstraint_plural" msgstr "Restricciones" +msgid "CWDataImport" +msgstr "" + +msgid "CWDataImport_plural" +msgstr "" + msgid "CWEType" msgstr "Tipo de entidad" @@ -345,12 +323,6 @@ msgid "CWGroup_plural" msgstr "Grupos" -msgid "CWPermission" -msgstr "Autorización" - -msgid "CWPermission_plural" -msgstr "Autorizaciones" - msgid "CWProperty" msgstr "Propiedad" @@ -451,6 +423,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 +460,9 @@ msgid "Download schema as OWL" msgstr "Descargar el esquema en formato OWL" +msgid "ERROR" +msgstr "" + msgid "EmailAddress" msgstr "Correo Electrónico" @@ -504,6 +485,9 @@ msgid "ExternalUri_plural" msgstr "Uris externos" +msgid "FATAL" +msgstr "" + msgid "Float" msgstr "Número flotante" @@ -528,6 +512,9 @@ msgid "Help" msgstr "Ayuda" +msgid "INFO" +msgstr "" + msgid "Instance" msgstr "Instancia" @@ -552,6 +539,12 @@ 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 " @@ -1101,9 +1103,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" @@ -1902,6 +1901,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 +1935,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 +2004,9 @@ msgid "cwrtype-permissions" msgstr "Permisos" +msgid "cwsource-imports" +msgstr "" + msgid "cwsource-main" msgstr "descripción" @@ -2116,9 +2138,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 +2314,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 +2351,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 +2393,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 +2481,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 +2511,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)" @@ -2673,9 +2694,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" @@ -2773,6 +2791,9 @@ msgid "image" msgstr "Imagen" +msgid "in progress" +msgstr "" + msgid "in_group" msgstr "En el grupo" @@ -2872,9 +2893,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 +2952,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 +2986,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 +3008,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 +3043,13 @@ msgid "list" msgstr "Lista" +msgid "log" +msgstr "" + +msgctxt "CWDataImport" +msgid "log" +msgstr "" + msgid "log in" msgstr "Acceder" @@ -3078,9 +3101,6 @@ msgid "manage permissions" msgstr "Gestión de permisos" -msgid "manage security" -msgstr "Gestión de seguridad" - msgid "managers" msgstr "Administradores" @@ -3115,6 +3135,9 @@ msgid "memory leak debugging" msgstr "depuración (debugging) de fuga de memoria" +msgid "message" +msgstr "" + msgid "milestone" msgstr "Milestone" @@ -3172,10 +3195,6 @@ msgid "name" msgstr "Nombre" -msgctxt "CWPermission" -msgid "name" -msgstr "Nombre" - msgctxt "CWRType" msgid "name" msgstr "Nombre" @@ -3213,9 +3232,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 +3268,6 @@ msgid "no" msgstr "No" -msgid "no associated permissions" -msgstr "No existe permiso asociado" - msgid "no content next link" msgstr "" @@ -3268,6 +3281,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 +3318,9 @@ msgid "november" msgstr "Noviembre" +msgid "num. users" +msgstr "" + msgid "object" msgstr "Objeto" @@ -3368,9 +3387,6 @@ msgid "owners" msgstr "Proprietarios" -msgid "ownership" -msgstr "Propiedad" - msgid "ownerships have been changed" msgstr "Derechos de propiedad modificados" @@ -3402,15 +3418,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" @@ -3616,10 +3632,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 +3647,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" @@ -3697,9 +3703,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 +3781,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 +3819,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 +3862,13 @@ "synchronization in progress." msgstr "" +msgid "start_timestamp" +msgstr "" + +msgctxt "CWDataImport" +msgid "start_timestamp" +msgstr "" + msgid "startup views" msgstr "Vistas de inicio" @@ -3904,6 +3914,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 +3991,9 @@ msgid "subworkflow_state_object" msgstr "Estado de Salida de" +msgid "success" +msgstr "" + msgid "sunday" msgstr "Domingo" @@ -4331,9 +4351,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 +4374,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 +4387,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 +4413,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" @@ -4595,3 +4603,36 @@ msgstr "" "usted debe quitar la puesta en línea de la relación %s que es aceptada y " "puede ser cruzada" + +#~ msgid "%d days" +#~ msgstr "%d días" + +#~ msgid "%d hours" +#~ msgstr "%d horas" + +#~ msgid "%d minutes" +#~ msgstr "%d minutos" + +#~ msgid "%d months" +#~ msgstr "%d meses" + +#~ msgid "%d seconds" +#~ msgstr "%d segundos" + +#~ msgid "%d weeks" +#~ msgstr "%d semanas" + +#~ msgid "%d years" +#~ msgstr "%d años" + +#~ msgid "Schema of the data model" +#~ msgstr "Esquema del modelo de datos" + +#~ msgid "add a CWSourceSchemaConfig" +#~ msgstr "agregar una parte de mapeo" + +#~ msgid "instance schema" +#~ msgstr "Esquema de la Instancia" + +#~ msgid "siteinfo" +#~ msgstr "información" diff -r 11caeed5ae80 -r 98222e3bb804 i18n/fr.po --- a/i18n/fr.po Wed Oct 26 17:58:21 2011 +0200 +++ b/i18n/fr.po Wed Oct 26 17:59:06 2011 +0200 @@ -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é" @@ -223,7 +195,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 +297,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 +323,6 @@ msgid "CWGroup_plural" msgstr "Groupes" -msgid "CWPermission" -msgstr "Permission" - -msgid "CWPermission_plural" -msgstr "Permissions" - msgid "CWProperty" msgstr "Propriété" @@ -451,6 +423,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 +460,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 +485,9 @@ msgid "ExternalUri_plural" msgstr "Uri externes" +msgid "FATAL" +msgstr "FATAL" + msgid "Float" msgstr "Nombre flottant" @@ -528,6 +512,9 @@ msgid "Help" msgstr "Aide" +msgid "INFO" +msgstr "INFO" + msgid "Instance" msgstr "Instance" @@ -552,6 +539,12 @@ 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 " @@ -1101,9 +1103,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" @@ -1908,6 +1907,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 +1941,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 +2010,9 @@ msgid "cwrtype-permissions" msgstr "permissions" +msgid "cwsource-imports" +msgstr "imports" + msgid "cwsource-main" msgstr "description" @@ -2118,9 +2140,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 +2316,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 +2353,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 +2395,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 +2483,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 +2513,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)" @@ -2675,9 +2697,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" @@ -2774,6 +2793,9 @@ msgid "image" msgstr "image" +msgid "in progress" +msgstr "en cours" + msgid "in_group" msgstr "dans le groupe" @@ -2873,9 +2895,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 +2955,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 +2989,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 +3011,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 +3046,13 @@ msgid "list" msgstr "liste" +msgid "log" +msgstr "journal" + +msgctxt "CWDataImport" +msgid "log" +msgstr "journal" + msgid "log in" msgstr "s'identifier" @@ -3080,9 +3104,6 @@ msgid "manage permissions" msgstr "gestion des permissions" -msgid "manage security" -msgstr "gestion de la sécurité" - msgid "managers" msgstr "administrateurs" @@ -3117,6 +3138,9 @@ msgid "memory leak debugging" msgstr "Déboguage des fuites de mémoire" +msgid "message" +msgstr "message" + msgid "milestone" msgstr "jalon" @@ -3174,10 +3198,6 @@ msgid "name" msgstr "nom" -msgctxt "CWPermission" -msgid "name" -msgstr "nom" - msgctxt "CWRType" msgid "name" msgstr "nom" @@ -3215,9 +3235,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 +3271,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 +3284,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 +3321,9 @@ msgid "november" msgstr "novembre" +msgid "num. users" +msgstr "nombre d'utilisateurs" + msgid "object" msgstr "objet" @@ -3370,9 +3390,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 +3423,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" @@ -3619,10 +3636,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 +3651,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" @@ -3702,9 +3709,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 +3787,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 +3824,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 +3869,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 +3921,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 +3998,9 @@ msgid "subworkflow_state_object" msgstr "état de sortie de" +msgid "success" +msgstr "succès" + msgid "sunday" msgstr "dimanche" @@ -4337,9 +4358,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 +4381,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 +4392,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 +4418,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" diff -r 11caeed5ae80 -r 98222e3bb804 misc/cwdesklets/rqlsensor/__init__.py --- a/misc/cwdesklets/rqlsensor/__init__.py Wed Oct 26 17:58:21 2011 +0200 +++ b/misc/cwdesklets/rqlsensor/__init__.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 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 Wed Oct 26 17:59:06 2011 +0200 @@ -0,0 +1,3 @@ +config['rql-cache-size'] = config['rql-cache-size'] * 10 + +add_entity_type('CWDataImport') diff -r 11caeed5ae80 -r 98222e3bb804 misc/migration/bootstrapmigration_repository.py --- a/misc/migration/bootstrapmigration_repository.py Wed Oct 26 17:58:21 2011 +0200 +++ b/misc/migration/bootstrapmigration_repository.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 misc/migration/postcreate.py --- a/misc/migration/postcreate.py Wed Oct 26 17:58:21 2011 +0200 +++ b/misc/migration/postcreate.py Wed Oct 26 17:59:06 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -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 11caeed5ae80 -r 98222e3bb804 req.py --- a/req.py Wed Oct 26 17:58:21 2011 +0200 +++ b/req.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 rset.py --- a/rset.py Wed Oct 26 17:58:21 2011 +0200 +++ b/rset.py Wed Oct 26 17:59:06 2011 +0200 @@ -480,9 +480,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 11caeed5ae80 -r 98222e3bb804 schema.py --- a/schema.py Wed Oct 26 17:58:21 2011 +0200 +++ b/schema.py Wed Oct 26 17:59:06 2011 +0200 @@ -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): @@ -917,9 +929,6 @@ @classmethod def deserialize(cls, value): - # XXX < 3.5.10 bw compat - if not value.startswith(';'): - return cls(value) _, mainvars, expression = value.split(';', 2) return cls(expression, mainvars) @@ -959,7 +968,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): @@ -974,9 +983,6 @@ self.msg or '') def deserialize(cls, value): - # XXX < 3.5.10 bw compat - if not value.startswith(';'): - return cls(value) value, msg = value.split('\n', 1) _, mainvars, expression = value.split(';', 2) return cls(expression, mainvars, msg) @@ -1176,7 +1182,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 +1197,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 11caeed5ae80 -r 98222e3bb804 schemas/__init__.py --- a/schemas/__init__.py Wed Oct 26 17:58:21 2011 +0200 +++ b/schemas/__init__.py Wed Oct 26 17:59:06 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -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 11caeed5ae80 -r 98222e3bb804 schemas/base.py --- a/schemas/base.py Wed Oct 26 17:58:21 2011 +0200 +++ b/schemas/base.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 schemas/workflow.py --- a/schemas/workflow.py Wed Oct 26 17:58:21 2011 +0200 +++ b/schemas/workflow.py Wed Oct 26 17:59:06 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -21,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 11caeed5ae80 -r 98222e3bb804 selectors.py --- a/selectors.py Wed Oct 26 17:58:21 2011 +0200 +++ b/selectors.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 @@ -401,18 +407,35 @@ """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. + 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 respectivly + 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): @@ -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,17 @@ 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. """ - @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): + return frozenset(req.form) class specified_etype_implements(is_instance): @@ -1537,8 +1555,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 +1565,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 +1586,7 @@ """Return 1 if running in debug mode.""" return req.vreg.config.debugmode and 1 or 0 + ## deprecated stuff ############################################################ diff -r 11caeed5ae80 -r 98222e3bb804 server/__init__.py --- a/server/__init__.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/__init__.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/checkintegrity.py --- a/server/checkintegrity.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/checkintegrity.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/hook.py --- a/server/hook.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/hook.py Wed Oct 26 17:59:06 2011 +0200 @@ -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() @@ -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 11caeed5ae80 -r 98222e3bb804 server/hookhelper.py --- a/server/hookhelper.py Wed Oct 26 17:58:21 2011 +0200 +++ /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 11caeed5ae80 -r 98222e3bb804 server/migractions.py --- a/server/migractions.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/migractions.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 = self.config.cube_dir(cube) - 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 11caeed5ae80 -r 98222e3bb804 server/querier.py --- a/server/querier.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/querier.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/repository.py --- a/server/repository.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/repository.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 @@ -1085,6 +1085,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 +1099,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): diff -r 11caeed5ae80 -r 98222e3bb804 server/serverconfig.py --- a/server/serverconfig.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/serverconfig.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/session.py --- a/server/session.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/session.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/sources/datafeed.py --- a/server/sources/datafeed.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/sources/datafeed.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 @@ -253,14 +268,20 @@ 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) + 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 +316,9 @@ 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) return None if eid < 0: # entity has been moved away from its original source diff -r 11caeed5ae80 -r 98222e3bb804 server/sources/native.py --- a/server/sources/native.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/sources/native.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/sqlutils.py --- a/server/sqlutils.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/sqlutils.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/ssplanner.py --- a/server/ssplanner.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/ssplanner.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/test/data/bootstrap_cubes --- a/server/test/data/bootstrap_cubes Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/data/bootstrap_cubes Wed Oct 26 17:59:06 2011 +0200 @@ -1,1 +1,1 @@ -card,comment,folder,tag,basket,email,file +card,comment,folder,tag,basket,email,file,localperms diff -r 11caeed5ae80 -r 98222e3bb804 server/test/unittest_checkintegrity.py --- a/server/test/unittest_checkintegrity.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_checkintegrity.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/test/unittest_datafeed.py --- a/server/test/unittest_datafeed.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_datafeed.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/test/unittest_ldapuser.py --- a/server/test/unittest_ldapuser.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_ldapuser.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/test/unittest_migractions.py --- a/server/test/unittest_migractions.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_migractions.py Wed Oct 26 17:59:06 2011 +0200 @@ -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,13 @@ 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(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', @@ -214,7 +214,7 @@ 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 +227,23 @@ 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_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) 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)) 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 +260,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 +380,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 +391,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 +470,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 +493,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 +530,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 +570,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 +601,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 11caeed5ae80 -r 98222e3bb804 server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_msplanner.py Wed Oct 26 17:59:06 2011 +0200 @@ -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'}, @@ -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'}, []), @@ -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) @@ -990,7 +991,8 @@ [{'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'}, @@ -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, 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'}, diff -r 11caeed5ae80 -r 98222e3bb804 server/test/unittest_multisources.py --- a/server/test/unittest_multisources.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_multisources.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/test/unittest_querier.py --- a/server/test/unittest_querier.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_querier.py Wed Oct 26 17:59:06 2011 +0200 @@ -250,7 +250,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 +418,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 +494,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') @@ -529,15 +529,15 @@ [[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'created_by', 42], + [u'creation_date', 42], + [u'cw_source', 42], + [u'cwuri', 42], + [u'in_basket', 42], + [u'is', 42], + [u'is_instance_of', 42], + [u'modification_date', 42], + [u'owned_by', 42]]) def test_select_aggregat_having_dumb(self): # dumb but should not raise an error @@ -619,7 +619,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 +757,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 +774,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 +891,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 +1173,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 +1198,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 +1227,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 11caeed5ae80 -r 98222e3bb804 server/test/unittest_repository.py --- a/server/test/unittest_repository.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_repository.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_rql2sql.py Wed Oct 26 17:59:06 2011 +0200 @@ -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] diff -r 11caeed5ae80 -r 98222e3bb804 server/test/unittest_rqlannotation.py --- a/server/test/unittest_rqlannotation.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_rqlannotation.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/test/unittest_security.py --- a/server/test/unittest_security.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_security.py Wed Oct 26 17:59:06 2011 +0200 @@ -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): diff -r 11caeed5ae80 -r 98222e3bb804 server/test/unittest_session.py --- a/server/test/unittest_session.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_session.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/test/unittest_storage.py --- a/server/test/unittest_storage.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_storage.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 server/test/unittest_undo.py --- a/server/test/unittest_undo.py Wed Oct 26 17:58:21 2011 +0200 +++ b/server/test/unittest_undo.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 setup.py --- a/setup.py Wed Oct 26 17:58:21 2011 +0200 +++ b/setup.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 skeleton/setup.py --- a/skeleton/setup.py Wed Oct 26 17:58:21 2011 +0200 +++ b/skeleton/setup.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 sobjects/parsers.py --- a/sobjects/parsers.py Wed Oct 26 17:58:21 2011 +0200 +++ b/sobjects/parsers.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 sobjects/test/unittest_email.py --- a/sobjects/test/unittest_email.py Wed Oct 26 17:58:21 2011 +0200 +++ b/sobjects/test/unittest_email.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 sobjects/test/unittest_notification.py --- a/sobjects/test/unittest_notification.py Wed Oct 26 17:58:21 2011 +0200 +++ b/sobjects/test/unittest_notification.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 test/data/bootstrap_cubes --- a/test/data/bootstrap_cubes Wed Oct 26 17:58:21 2011 +0200 +++ b/test/data/bootstrap_cubes Wed Oct 26 17:59:06 2011 +0200 @@ -1,1 +1,1 @@ -card, file, tag +card, file, tag, localperms diff -r 11caeed5ae80 -r 98222e3bb804 test/data/entities.py --- a/test/data/entities.py Wed Oct 26 17:58:21 2011 +0200 +++ b/test/data/entities.py Wed Oct 26 17:59:06 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -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 11caeed5ae80 -r 98222e3bb804 test/data/rewrite/bootstrap_cubes --- a/test/data/rewrite/bootstrap_cubes Wed Oct 26 17:58:21 2011 +0200 +++ b/test/data/rewrite/bootstrap_cubes Wed Oct 26 17:59:06 2011 +0200 @@ -1,1 +1,1 @@ -card +card,localperms diff -r 11caeed5ae80 -r 98222e3bb804 test/data/schema.py --- a/test/data/schema.py Wed Oct 26 17:58:21 2011 +0200 +++ b/test/data/schema.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 test/unittest_cwconfig.py --- a/test/unittest_cwconfig.py Wed Oct 26 17:58:21 2011 +0200 +++ b/test/unittest_cwconfig.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 test/unittest_entity.py --- a/test/unittest_entity.py Wed Oct 26 17:58:21 2011 +0200 +++ b/test/unittest_entity.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 test/unittest_rqlrewrite.py --- a/test/unittest_rqlrewrite.py Wed Oct 26 17:58:21 2011 +0200 +++ b/test/unittest_rqlrewrite.py Wed Oct 26 17:59:06 2011 +0200 @@ -110,7 +110,7 @@ 'P name "read", P require_group G') rqlst = parse('Card C') rewrite(rqlst, {('C', 'X'): (constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), + 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)") @@ -129,13 +129,13 @@ "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.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(), + 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)") @@ -145,7 +145,7 @@ '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(), + 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)") @@ -155,7 +155,7 @@ '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(), + 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', " @@ -166,7 +166,7 @@ '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(), + 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, " @@ -179,7 +179,7 @@ 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(), + 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), " @@ -194,7 +194,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 +209,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 +223,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): @@ -255,7 +255,7 @@ 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(), + self.assertEqual(rqlst.as_string(), "Any C WHERE C in_state STATE, C is Card, " "EXISTS(STATE name 'hop'), STATE is State") @@ -263,7 +263,7 @@ 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(), + self.assertEqual(rqlst.as_string(), "Any C WHERE C subworkflow_exit EXIT, C is WorkflowTransition, " "EXISTS(C name 'hop'), EXIT is SubWorkflowExitPoint") @@ -272,14 +272,14 @@ 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(), + 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(), + self.assertEqual(rqlst.as_string(), "Any EXIT WHERE C? subworkflow_exit EXIT, EXIT is SubWorkflowExitPoint, " "EXISTS(C name 'hop'), C is WorkflowTransition") @@ -288,14 +288,14 @@ 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(), + 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(), + self.assertEqual(rqlst.as_string(), "Any C WHERE C subworkflow_exit EXIT, C is WorkflowTransition, " "EXISTS(C name 'hop'), EXIT is SubWorkflowExitPoint") @@ -304,14 +304,14 @@ 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(), + 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(), + 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") @@ -326,7 +326,7 @@ 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(), + 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") @@ -335,14 +335,14 @@ 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(), + 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(), + 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): @@ -350,7 +350,7 @@ 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(), + 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)") @@ -358,76 +358,76 @@ constraint = RRQLExpression('S owned_by U') rqlst = parse('Card C') rewrite(rqlst, {('C', 'S'): (constraint,)}, {}, 'SU') - self.failUnlessEqual(rqlst.as_string(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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') diff -r 11caeed5ae80 -r 98222e3bb804 test/unittest_rset.py --- a/test/unittest_rset.py Wed Oct 26 17:58:21 2011 +0200 +++ b/test/unittest_rset.py Wed Oct 26 17:59:06 2011 +0200 @@ -401,7 +401,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)), @@ -410,7 +410,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 11caeed5ae80 -r 98222e3bb804 test/unittest_schema.py --- a/test/unittest_schema.py Wed Oct 26 17:58:21 2011 +0200 +++ b/test/unittest_schema.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 test/unittest_selectors.py --- a/test/unittest_selectors.py Wed Oct 26 17:58:21 2011 +0200 +++ b/test/unittest_selectors.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 test/unittest_utils.py --- a/test/unittest_utils.py Wed Oct 26 17:58:21 2011 +0200 +++ b/test/unittest_utils.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 test/unittest_vregistry.py --- a/test/unittest_vregistry.py Wed Oct 26 17:58:21 2011 +0200 +++ b/test/unittest_vregistry.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 toolsutils.py --- a/toolsutils.py Wed Oct 26 17:58:21 2011 +0200 +++ b/toolsutils.py Wed Oct 26 17:59:06 2011 +0200 @@ -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 11caeed5ae80 -r 98222e3bb804 uilib.py --- a/uilib.py Wed Oct 26 17:58:21 2011 +0200 +++ b/uilib.py Wed Oct 26 17:59:06 2011 +0200 @@ -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,18 +137,12 @@ '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) # text publishing ############################################################# diff -r 11caeed5ae80 -r 98222e3bb804 utils.py --- a/utils.py Wed Oct 26 17:58:21 2011 +0200 +++ b/utils.py Wed Oct 26 17:59:06 2011 +0200 @@ -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