# HG changeset patch # User Sylvain Thénault # Date 1267194990 -3600 # Node ID 716ee59d64a75248c353a1f3d8ef8d4b11d626af # Parent 6267f9c3bfbdcb8cb91b8c1ad493f3e3b381efc9# Parent d4ae6e751b600caa416dcd545e4788c9b5d4ae25 merge diff -r d4ae6e751b60 -r 716ee59d64a7 .hgtags --- a/.hgtags Wed Feb 10 16:34:15 2010 +0100 +++ b/.hgtags Fri Feb 26 15:36:30 2010 +0100 @@ -100,7 +100,5 @@ 4281e1e2d76b9a37f38c0eeb1cbdcaa2fac6533c cubicweb-debian-version-3.5.12-1 5f957e351b0a60d5c5fff60c560b04e666c3a8c6 cubicweb-version-3.6.0 17e88f2485d1ea1fb8a3926a274637ce19e95d69 cubicweb-debian-version-3.6.0-1 -5f957e351b0a60d5c5fff60c560b04e666c3a8c6 cubicweb-version-3.6.0 450804da3ab2476b7ede0c1f956235b4c239734f cubicweb-version-3.6.0 -17e88f2485d1ea1fb8a3926a274637ce19e95d69 cubicweb-debian-version-3.6.0-1 d2ba93fcb8da95ceab08f48f8149a480215f149c cubicweb-debian-version-3.6.0-1 diff -r d4ae6e751b60 -r 716ee59d64a7 appobject.py --- a/appobject.py Wed Feb 10 16:34:15 2010 +0100 +++ b/appobject.py Fri Feb 26 15:36:30 2010 +0100 @@ -13,12 +13,9 @@ from logging import getLogger from warnings import warn -from logilab.common.decorators import classproperty from logilab.common.deprecation import deprecated from logilab.common.logging_ext import set_log_methods -from cubicweb import Unauthorized, NoSelectableObject - # selector base classes and operations ######################################## @@ -268,7 +265,7 @@ pdef['default'] = getattr(cls, propid, pdef['default']) pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide')) registry.vreg.register_property(cls._cwpropkey(propid), **pdef) - assert callable(cls.__select__), obj + assert callable(cls.__select__), cls return cls def __init__(self, req, **extra): @@ -404,7 +401,7 @@ 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_timoe') + @deprecated('[3.6] use self._cw.format_time') def format_time(self, time): return self._cw.format_time(time) diff -r d4ae6e751b60 -r 716ee59d64a7 crypto.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/crypto.py Fri Feb 26 15:36:30 2010 +0100 @@ -0,0 +1,35 @@ +"""Simple cryptographic routines, based on python-crypto. + +:organization: Logilab +:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +__docformat__ = "restructuredtext en" + +from pickle import dumps, loads +from base64 import b64encode, b64decode + +from Crypto.Cipher import Blowfish + + +_CYPHERERS = {} +def _cypherer(seed): + try: + return _CYPHERERS[seed] + except KeyError: + _CYPHERERS[seed] = Blowfish.new(seed, Blowfish.MODE_ECB) + return _CYPHERERS[seed] + + +def encrypt(data, seed): + string = dumps(data) + string = string + '*' * (8 - len(string) % 8) + string = b64encode(_cypherer(seed).encrypt(string)) + return unicode(string) + + +def decrypt(string, seed): + # pickle ignores trailing characters so we do not need to strip them off + string = _cypherer(seed).decrypt(b64decode(string)) + return loads(string) diff -r d4ae6e751b60 -r 716ee59d64a7 cwconfig.py --- a/cwconfig.py Wed Feb 10 16:34:15 2010 +0100 +++ b/cwconfig.py Fri Feb 26 15:36:30 2010 +0100 @@ -236,7 +236,7 @@ options = ( ('log-threshold', {'type' : 'string', # XXX use a dedicated type? - 'default': 'ERROR', + 'default': 'WARNING', 'help': 'server\'s log level', 'group': 'main', 'inputlevel': 1, }), @@ -328,7 +328,7 @@ cubes = set() for directory in cls.cubes_search_path(): if not os.path.exists(directory): - self.error('unexistant directory in cubes search path: %s' + cls.error('unexistant directory in cubes search path: %s' % directory) continue for cube in os.listdir(directory): @@ -347,7 +347,7 @@ path.append(directory) except KeyError: pass - if not cls.CUBES_DIR in path: + if not cls.CUBES_DIR in path and exists(cls.CUBES_DIR): path.append(cls.CUBES_DIR) return path @@ -474,7 +474,7 @@ from logilab.common.modutils import load_module_from_file cls.cls_adjust_sys_path() for ctlfile in ('web/webctl.py', 'etwist/twctl.py', - 'server/serverctl.py', + 'server/serverctl.py', 'devtools/devctl.py', 'goa/goactl.py'): if exists(join(CW_SOFTWARE_ROOT, ctlfile)): try: diff -r d4ae6e751b60 -r 716ee59d64a7 cwctl.py --- a/cwctl.py Wed Feb 10 16:34:15 2010 +0100 +++ b/cwctl.py Fri Feb 26 15:36:30 2010 +0100 @@ -1,16 +1,26 @@ -"""%%prog %s [options] %s +"""the cubicweb-ctl tool, based on logilab.common.clcommands to +provide a pluggable commands system. + -The CubicWeb swiss-knife. +:organization: Logilab +:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +__docformat__ = "restructuredtext en" -%s""" - +# *ctl module should limit the number of import to be imported as quickly as +# possible (for cubicweb-ctl reactivity, necessary for instance for usable bash +# completion). So import locally in command helpers. import sys from os import remove, listdir, system, pathsep try: from os import kill, getpgid except ImportError: - def kill(*args): pass - def getpgid(): pass + def kill(*args): + """win32 kill implementation""" + def getpgid(): + """win32 getpgid implementation""" from os.path import exists, join, isfile, isdir, dirname, abspath @@ -158,6 +168,79 @@ # base commands ############################################################### +def version_strictly_lower(a, b): + from logilab.common.changelog import Version + if a: + a = Version(a) + if b: + b = Version(b) + return a < b + +def max_version(a, b): + from logilab.common.changelog import Version + return str(max(Version(a), Version(b))) + +class ConfigurationProblem(object): + """Each cube has its own list of dependencies on other cubes/versions. + + The ConfigurationProblem is used to record the loaded cubes, then to detect + inconsistencies in their dependencies. + + See configuration management on wikipedia for litterature. + """ + + def __init__(self): + self.cubes = {} + + def add_cube(self, name, info): + self.cubes[name] = info + + def solve(self): + self.warnings = [] + self.errors = [] + self.read_constraints() + for cube, versions in sorted(self.constraints.items()): + oper, version = None, None + # simplify constraints + if versions: + for constraint in versions: + op, ver = constraint.split() + if oper is None: + oper = op + version = ver + elif op == '>=' and oper == '>=': + version = max_version(ver, version) + else: + print 'unable to handle this case', oper, version, op, ver + # "solve" constraint satisfaction problem + if cube not in self.cubes: + self.errors.append( ('add', cube, version) ) + elif versions: + lower_strict = version_strictly_lower(self.cubes[cube].version, version) + if oper in ('>=','='): + if lower_strict: + self.errors.append( ('update', cube, version) ) + else: + print 'unknown operator', oper + + def read_constraints(self): + self.constraints = {} + self.reverse_constraints = {} + for cube, info in self.cubes.items(): + if hasattr(info,'__depends_cubes__'): + use = info.__depends_cubes__ + if not isinstance(use, dict): + use = dict((key, None) for key in use) + self.warnings.append('cube %s should define __depends_cubes__ as a dict not a list') + else: + self.warnings.append('cube %s should define __depends_cubes__' % cube) + use = dict((key, None) for key in info.__use__) + for name, constraint in use.items(): + self.constraints.setdefault(name,set()) + if constraint: + self.constraints[name].add(constraint) + self.reverse_constraints.setdefault(name, set()).add(cube) + class ListCommand(Command): """List configurations, cubes and instances. @@ -185,6 +268,7 @@ continue print ' ', line print + cfgpb = ConfigurationProblem() try: cubesdir = pathsep.join(cwcfg.cubes_search_path()) namesize = max(len(x) for x in cwcfg.available_cubes()) @@ -200,6 +284,7 @@ try: tinfo = cwcfg.cube_pkginfo(cube) tversion = tinfo.version + cfgpb.add_cube(cube, tinfo) except ConfigurationError: tinfo = None tversion = '[missing cube information]' @@ -235,7 +320,21 @@ else: print 'No instance available in %s' % regdir print - + # configuration management problem solving + cfgpb.solve() + if cfgpb.warnings: + print 'Warnings:\n', '\n'.join('* '+txt for txt in cfgpb.warnings) + if cfgpb.errors: + print 'Errors:' + for op, cube, version in cfgpb.errors: + if op == 'add': + print '* cube', cube, + if version: + print ' version', version, + print 'is not installed, but required by %s' % ' '.join(cfgpb.reverse_constraints[cube]) + else: + print '* cube %s version %s is installed, but version %s is required by (%s)' % ( + cube, cfgpb.cubes[cube].version, version, ', '.join(cfgpb.reverse_constraints[cube])) class CreateInstanceCommand(Command): """Create an instance from a cube. This is an unified @@ -297,14 +396,18 @@ # create the registry directory for this instance print '\n'+underline_title('Creating the instance %s' % appid) create_dir(config.apphome) - # load site_cubicweb from the cubes dir (if any) - config.load_site_cubicweb() # cubicweb-ctl configuration print '\n'+underline_title('Configuring the instance (%s.conf)' % configname) config.input_config('main', self.config.config_level) # configuration'specific stuff print helper.bootstrap(cubes, self.config.config_level) + # input for cubes specific options + for section in set(sect.lower() for sect, opt, optdict in config.all_options() + if optdict.get('inputlevel') <= self.config.config_level): + if section not in ('main', 'email', 'pyro'): + print '\n' + underline_title('%s options' % section) + config.input_config(section, self.config.config_level) # write down configuration config.save() self._handle_win32(config, appid) @@ -883,7 +986,12 @@ def run(args): """command line tool""" cwcfg.load_cwctl_plugins() - main_run(args, __doc__) + main_run(args, """%%prog %s [options] %s + +The CubicWeb swiss-knife. + +%s""" +) if __name__ == '__main__': run(sys.argv[1:]) diff -r d4ae6e751b60 -r 716ee59d64a7 cwvreg.py --- a/cwvreg.py Wed Feb 10 16:34:15 2010 +0100 +++ b/cwvreg.py Fri Feb 26 15:36:30 2010 +0100 @@ -8,7 +8,7 @@ __docformat__ = "restructuredtext en" _ = unicode -from logilab.common.decorators import cached, clear_cache, monkeypatch +from logilab.common.decorators import cached, clear_cache from logilab.common.deprecation import deprecated from logilab.common.modutils import cleanup_sys_modules diff -r d4ae6e751b60 -r 716ee59d64a7 dbapi.py --- a/dbapi.py Wed Feb 10 16:34:15 2010 +0100 +++ b/dbapi.py Fri Feb 26 15:36:30 2010 +0100 @@ -27,15 +27,10 @@ def _fake_property_value(self, name): try: - return super(dbapi.DBAPIRequest, self).property_value(name) + return super(DBAPIRequest, self).property_value(name) except KeyError: return '' -def _fix_cls_attrs(reg, appobject): - appobject.vreg = reg.vreg - appobject.schema = reg.schema - appobject.config = reg.config - def multiple_connections_fix(): """some monkey patching necessary when an application has to deal with several connections to different repositories. It tries to hide buggy class @@ -43,21 +38,6 @@ registries. """ defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None] - orig_select_best = defaultcls.orig_select_best = defaultcls._select_best - @monkeypatch(defaultcls) - def _select_best(self, appobjects, *args, **kwargs): - """return an instance of the most specific object according - to parameters - - raise NoSelectableObject if no object apply - """ - for appobjectcls in appobjects: - _fix_cls_attrs(self, appobjectcls) - selected = orig_select_best(self, appobjects, *args, **kwargs) - # redo the same thing on the instance so it won't use equivalent class - # attributes (which may change) - _fix_cls_attrs(self, selected) - return selected etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes'] orig_etype_class = etypescls.orig_etype_class = etypescls.etype_class @@ -74,8 +54,6 @@ return usercls def multiple_connections_unfix(): - defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None] - defaultcls.select_best = defaultcls.orig_select_best etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes'] etypescls.etype_class = etypescls.orig_etype_class @@ -222,7 +200,7 @@ except KeyError: # this occurs usually during test execution self._ = self.__ = unicode - self.pgettext = lambda x,y: y + self.pgettext = lambda x, y: y self.debug('request default language: %s', self.lang) def decorate_rset(self, rset): @@ -263,6 +241,8 @@ def get_session_data(self, key, default=None, pop=False): """return value associated to `key` in session data""" + if self.cnx is None: + return None # before the connection has been established return self.cnx.get_session_data(key, default, pop) def set_session_data(self, key, value): diff -r d4ae6e751b60 -r 716ee59d64a7 debian/changelog --- a/debian/changelog Wed Feb 10 16:34:15 2010 +0100 +++ b/debian/changelog Fri Feb 26 15:36:30 2010 +0100 @@ -1,3 +1,9 @@ +cubicweb (3.6.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Fri, 26 Feb 2010 14:14:01 +0100 + cubicweb (3.6.0-1) unstable; urgency=low * new upstream release diff -r d4ae6e751b60 -r 716ee59d64a7 debian/control --- a/debian/control Wed Feb 10 16:34:15 2010 +0100 +++ b/debian/control Fri Feb 26 15:36:30 2010 +0100 @@ -63,7 +63,7 @@ Architecture: all XB-Python-Version: ${python:Versions} Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3), python-elementtree -Recommends: python-docutils, python-vobject, fckeditor, python-fyzz, python-pysixt, fop +Recommends: python-docutils, python-vobject, fckeditor, python-fyzz, python-pysixt, fop, python-imaging Description: web interface library for the CubicWeb framework CubicWeb is a semantic web application framework. . @@ -77,8 +77,8 @@ Package: cubicweb-common Architecture: all XB-Python-Version: ${python:Versions} -Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.47.0), python-yams (>= 0.27.0), python-rql (>= 0.24.0), python-lxml -Recommends: python-simpletal (>= 4.0) +Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.48.1), python-yams (>= 0.27.0), python-rql (>= 0.24.0), python-lxml +Recommends: python-simpletal (>= 4.0), python-crypto Conflicts: cubicweb-core Replaces: cubicweb-core Description: common library for the CubicWeb framework diff -r d4ae6e751b60 -r 716ee59d64a7 devtools/dataimport.py --- a/devtools/dataimport.py Wed Feb 10 16:34:15 2010 +0100 +++ b/devtools/dataimport.py Fri Feb 26 15:36:30 2010 +0100 @@ -134,7 +134,7 @@ try: for func in funcs: res[dest] = func(res[dest]) - if res[dest] is None or res[dest]==False: + if res[dest] is None: raise AssertionError('undetermined value') except AssertionError, err: if optional in funcs: @@ -152,7 +152,7 @@ def confirm(question): """A confirm function that asks for yes/no/abort and exits on abort.""" - answer = shellutils.ASK.ask(question, ('Y','n','abort'), 'Y') + answer = shellutils.ASK.ask(question, ('Y', 'n', 'abort'), 'Y') if answer == 'abort': sys.exit(1) return answer == 'Y' @@ -220,7 +220,26 @@ return txt.strip() def yesno(value): - return value.lower()[0] in 'yo1' + """simple heuristic that returns boolean value + + >>> yesno("Yes") + True + >>> yesno("oui") + True + >>> yesno("1") + True + >>> yesno("11") + True + >>> yesno("") + False + >>> yesno("Non") + False + >>> yesno("blablabla") + False + """ + if value: + return value.lower()[0] in 'yo1' + return False def isalpha(value): if value.isalpha(): @@ -256,11 +275,12 @@ def check_doubles(buckets): """Extract the keys that have more than one item in their bucket.""" - return [(key, len(value)) for key,value in buckets.items() if len(value) > 1] + return [(k, len(v)) for k, v in buckets.items() if len(v) > 1] def check_doubles_not_none(buckets): """Extract the keys that have more than one item in their bucket.""" - return [(key, len(value)) for key,value in buckets.items() if key is not None and len(value) > 1] + return [(k, len(v)) for k, v in buckets.items() + if k is not None and len(v) > 1] # object stores ################################################################# @@ -411,12 +431,14 @@ return entity def _put(self, type, item): - query = ('INSERT %s X: ' % type) + ', '.join(['X %s %%(%s)s' % (key,key) for key in item]) + query = ('INSERT %s X: ' % type) + ', '.join('X %s %%(%s)s' % (k, k) + for k in item]) return self.rql(query, item)[0][0] def relate(self, eid_from, rtype, eid_to): # if reverse relation is found, eids are exchanged - eid_from, rtype, eid_to = super(RQLObjectStore, self).relate(eid_from, rtype, eid_to) + eid_from, rtype, eid_to = super(RQLObjectStore, self).relate( + eid_from, rtype, eid_to) self.rql('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype, {'x': int(eid_from), 'y': int(eid_to)}, ('x', 'y')) @@ -494,7 +516,7 @@ % (len(self.store.eids), len(self.store.types), len(self.store.relations), nberrors)) if self.errors: - if self.askerror==2 or (self.askerror and confirm('Display errors ?')): + if self.askerror == 2 or (self.askerror and confirm('Display errors ?')): from pprint import pformat for errkey, error in self.errors.items(): self.tell("\n%s (%s): %d\n" % (error[0], errkey, len(error[1]))) diff -r d4ae6e751b60 -r 716ee59d64a7 devtools/devctl.py --- a/devtools/devctl.py Wed Feb 10 16:34:15 2010 +0100 +++ b/devtools/devctl.py Fri Feb 26 15:36:30 2010 +0100 @@ -8,32 +8,23 @@ """ __docformat__ = "restructuredtext en" +# *ctl module should limit the number of import to be imported as quickly as +# possible (for cubicweb-ctl reactivity, necessary for instance for usable bash +# completion). So import locally in command helpers. import sys from datetime import datetime -from os import mkdir, chdir, getcwd +from os import mkdir, chdir from os.path import join, exists, abspath, basename, normpath, split, isdir -from copy import deepcopy from warnings import warn -from tempfile import NamedTemporaryFile -from subprocess import Popen from logilab.common import STD_BLACKLIST -from logilab.common.modutils import get_module_files -from logilab.common.textutils import splitstrip -from logilab.common.shellutils import ASK from logilab.common.clcommands import register_commands, pop_arg -from yams import schema2dot - from cubicweb.__pkginfo__ import version as cubicwebversion from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage from cubicweb.toolsutils import Command, copy_skeleton, underline_title -from cubicweb.schema import CONSTRAINTS from cubicweb.web.webconfig import WebConfiguration from cubicweb.server.serverconfig import ServerConfiguration -from yams import BASE_TYPES -from cubicweb.schema import (META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES, - WORKFLOW_TYPES, INTERNAL_TYPES) class DevConfiguration(ServerConfiguration, WebConfiguration): @@ -110,13 +101,14 @@ # set_schema triggers objects registrations vreg.set_schema(schema) w(DEFAULT_POT_HEAD) - _generate_schema_pot(w, vreg, schema, libconfig=libconfig, cube=cube) + _generate_schema_pot(w, vreg, schema, libconfig=libconfig) -def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None): +def _generate_schema_pot(w, vreg, schema, libconfig=None): + from copy import deepcopy from cubicweb.i18n import add_msg from cubicweb.web import uicfg - from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES + from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES, CONSTRAINTS no_context_rtypes = META_RTYPES | SYSTEM_RTYPES w('# schema pot file, generated on %s\n' % datetime.now().strftime('%Y-%m-%d %H:%M:%S')) w('# \n') @@ -221,7 +213,7 @@ add_msg(w, objid) -def _iter_vreg_objids(vreg, done, prefix=None): +def _iter_vreg_objids(vreg, done): for reg, objdict in vreg.items(): for objects in objdict.values(): for obj in objects: @@ -238,21 +230,6 @@ break -def defined_in_library(etype, rtype, tetype, role): - """return true if the given relation definition exists in cubicweb's library - """ - if libschema is None: - return False - if role == 'subject': - subjtype, objtype = etype, tetype - else: - subjtype, objtype = tetype, etype - try: - return libschema.rschema(rtype).has_rdef(subjtype, objtype) - except KeyError: - return False - - LANGS = ('en', 'fr', 'es') I18NDIR = join(BASEDIR, 'i18n') DEFAULT_POT_HEAD = r'''msgid "" @@ -287,6 +264,7 @@ import yams from logilab.common.fileutils import ensure_fs_mode from logilab.common.shellutils import globfind, find, rm + from logilab.common.modutils import get_module_files from cubicweb.i18n import extract_from_tal, execute tempdir = tempfile.mkdtemp() potfiles = [join(I18NDIR, 'static-messages.pot')] @@ -501,6 +479,7 @@ def run(self, args): + from logilab.common.shellutils import ASK if len(args) != 1: raise BadCommandUsage("exactly one argument (cube name) is expected") cubename, = args @@ -558,6 +537,8 @@ copy_skeleton(skeldir, cubedir, context) def _ask_for_dependancies(self): + from logilab.common.shellutils import ASK + from logilab.common.textutils import splitstrip includes = [] for stdtype in ServerConfiguration.available_cubes(): answer = ASK.ask("Depends on cube %s? " % stdtype, @@ -575,17 +556,16 @@ class ExamineLogCommand(Command): """Examine a rql log file. - usage: python exlog.py < rql.log - will print out the following table total execution time || number of occurences || rql query sorted by descending total execution time - chances are the lines at the top are the ones that will bring - the higher benefit after optimisation. Start there. + chances are the lines at the top are the ones that will bring the higher + benefit after optimisation. Start there. """ + arguments = '< rql.log' name = 'exlog' options = ( ) @@ -662,7 +642,12 @@ ] def run(self, args): + from subprocess import Popen + from tempfile import NamedTemporaryFile from logilab.common.textutils import splitstrip + from yams import schema2dot, BASE_TYPES + from cubicweb.schema import (META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES, + WORKFLOW_TYPES, INTERNAL_TYPES) cubes = splitstrip(pop_arg(args, 1)) dev_conf = DevConfiguration(*cubes) schema = dev_conf.load_schema() diff -r d4ae6e751b60 -r 716ee59d64a7 devtools/fill.py --- a/devtools/fill.py Wed Feb 10 16:34:15 2010 +0100 +++ b/devtools/fill.py Fri Feb 26 15:36:30 2010 +0100 @@ -142,7 +142,7 @@ def generate_date(self, entity, attrname, index): """generates a random date (format is 'yyyy-mm-dd')""" - base = date(randint(2000, 2004), randint(1, 12), randint(1, 28)) + base = date(randint(2000, 2010), 1, 1) + timedelta(randint(1, 365)) return self._constrained_generate(entity, attrname, base, timedelta(days=1), index) def generate_time(self, entity, attrname, index): diff -r d4ae6e751b60 -r 716ee59d64a7 devtools/realdbtest.py --- a/devtools/realdbtest.py Wed Feb 10 16:34:15 2010 +0100 +++ b/devtools/realdbtest.py Fri Feb 26 15:36:30 2010 +0100 @@ -1,4 +1,3 @@ -import logging from cubicweb import toolsutils from cubicweb.devtools import DEFAULT_SOURCES, BaseApptestConfiguration diff -r d4ae6e751b60 -r 716ee59d64a7 devtools/repotest.py --- a/devtools/repotest.py Wed Feb 10 16:34:15 2010 +0100 +++ b/devtools/repotest.py Fri Feb 26 15:36:30 2010 +0100 @@ -191,15 +191,14 @@ rqlst.solutions = remove_unused_solutions(rqlst, rqlst.solutions, {}, self.repo.schema)[0] return rqlst - def _user_session(self, groups=('guests',), ueid=None): + def user_groups_session(self, *groups): + """lightweight session using the current user with hi-jacked groups""" # use self.session.user.eid to get correct owned_by relation, unless explicit eid - if ueid is None: - ueid = self.session.user.eid - u = self.repo._build_user(self.session, ueid) + u = self.repo._build_user(self.session, self.session.user.eid) u._groups = set(groups) s = Session(u, self.repo) s._threaddata.pool = self.pool - return u, s + return s def execute(self, rql, args=None, eid_key=None, build_descr=True): return self.o.execute(self.session, rql, args, eid_key, build_descr) diff -r d4ae6e751b60 -r 716ee59d64a7 devtools/test/unittest_dbfill.py --- a/devtools/test/unittest_dbfill.py Wed Feb 10 16:34:15 2010 +0100 +++ b/devtools/test/unittest_dbfill.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,6 +9,7 @@ import os.path as osp import re +import datetime from logilab.common.testlib import TestCase, unittest_main @@ -41,7 +42,6 @@ def _available_Person_firstname(self, etype, attrname): return [f.strip() for f in file(osp.join(DATADIR, 'firstnames.txt'))] - def setUp(self): config = ApptestConfiguration('data') config.bootstrap_cubes() @@ -52,17 +52,6 @@ self.bug_valgen = MyValueGenerator(e_schema) self.config = config - def _check_date(self, date): - """checks that 'date' is well-formed""" - year = date.year - month = date.month - day = date.day - self.failUnless(day in range(1, 29), '%s not in [0;28]' % day) - self.failUnless(month in range(1, 13), '%s not in [1;12]' % month) - self.failUnless(year in range(2000, 2005), - '%s not in [2000;2004]' % year) - - def test_string(self): """test string generation""" surname = self.person_valgen.generate_attribute_value({}, 'surname', 12) @@ -92,15 +81,14 @@ def test_date(self): """test date generation""" # Test for random index - for index in range(5): + for index in range(10): date_value = self.person_valgen.generate_attribute_value({}, 'birthday', index) - self._check_date(date_value) + self.failUnless(isinstance(date_value, datetime.date)) def test_phone(self): """tests make_tel utility""" self.assertEquals(make_tel(22030405), '22 03 04 05') - def test_customized_generation(self): self.assertEquals(self.bug_valgen.generate_attribute_value({}, 'severity', 12), u'dangerous') @@ -110,7 +98,6 @@ u'yo') - class ConstraintInsertionTC(TestCase): def test_writeme(self): diff -r d4ae6e751b60 -r 716ee59d64a7 devtools/testlib.py --- a/devtools/testlib.py Wed Feb 10 16:34:15 2010 +0100 +++ b/devtools/testlib.py Fri Feb 26 15:36:30 2010 +0100 @@ -66,24 +66,7 @@ return set(schema.entities()) - protected_entities -def get_versions(self, checkversions=False): - """return the a dictionary containing cubes used by this instance - as key with their version as value, including cubicweb version. This is a - public method, not requiring a session id. - - replace Repository.get_versions by this method if you don't want versions - checking - """ - vcconf = {'cubicweb': self.config.cubicweb_version()} - self.config.bootstrap_cubes() - for pk in self.config.cubes(): - version = self.config.cube_version(pk) - vcconf[pk] = version - self.config._cubes = None - return vcconf - - -def refresh_repo(repo): +def refresh_repo(repo, resetschema=False, resetvreg=False): devtools.reset_test_database(repo.config) for pool in repo.pools: pool.reconnect() @@ -92,6 +75,8 @@ repo.querier._rql_cache = {} for source in repo.sources: source.reset_caches() + if resetschema: + repo.set_schema(repo.config.load_schema(), resetvreg=resetvreg) # email handling, to test emails sent by an application ######################## @@ -160,6 +145,7 @@ """ appid = 'data' configcls = devtools.ApptestConfiguration + reset_schema = reset_vreg = False # reset schema / vreg between tests @classproperty def config(cls): @@ -230,7 +216,7 @@ @classmethod def _refresh_repo(cls): - refresh_repo(cls.repo) + refresh_repo(cls.repo, cls.reset_schema, cls.reset_vreg) # global resources accessors ############################################### @@ -488,7 +474,7 @@ Redirect exception """ try: - res = callback(req) + callback(req) except Redirect, ex: try: path, params = ex.location.split('?', 1) diff -r d4ae6e751b60 -r 716ee59d64a7 embedded/README --- a/embedded/README Wed Feb 10 16:34:15 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -This directory contains extra libraries which are needed -to make cubicweb work. - -The mx.DateTime python implementation is directly taken from -the mx.DateTime distribution. The only modification is the -strptime function which has been mocked using the standard -``datetime`` module. (as provided by the python2.5's stdlib) diff -r d4ae6e751b60 -r 716ee59d64a7 entities/test/unittest_wfobjs.py --- a/entities/test/unittest_wfobjs.py Wed Feb 10 16:34:15 2010 +0100 +++ b/entities/test/unittest_wfobjs.py Fri Feb 26 15:36:30 2010 +0100 @@ -98,7 +98,7 @@ trs = list(user.possible_transitions()) self.assertEquals(len(trs), 1) self.assertEquals(trs[0].name, u'deactivate') - self.assertEquals(trs[0].destination().name, u'deactivated') + self.assertEquals(trs[0].destination(None).name, u'deactivated') # test a std user get no possible transition cnx = self.login('member') # fetch the entity using the new session @@ -141,6 +141,27 @@ trinfo = self._test_manager_deactivate(user) self.assertEquals(trinfo.transition.name, 'deactivate') + def test_goback_transition(self): + wf = self.session.user.current_workflow + asleep = wf.add_state('asleep') + wf.add_transition('rest', (wf.state_by_name('activated'), wf.state_by_name('deactivated')), + asleep) + wf.add_transition('wake up', asleep) + user = self.create_user('stduser') + user.fire_transition('rest') + self.commit() + user.fire_transition('wake up') + self.commit() + self.assertEquals(user.state, 'activated') + user.fire_transition('deactivate') + self.commit() + user.fire_transition('rest') + self.commit() + user.fire_transition('wake up') + self.commit() + user.clear_all_caches() + self.assertEquals(user.state, 'deactivated') + # XXX test managers can change state without matching transition def _test_stduser_deactivate(self): @@ -207,7 +228,7 @@ state3 = mwf.add_state(u'state3') swftr1 = mwf.add_wftransition(u'swftr1', swf, state1, [(swfstate2, state2), (swfstate3, state3)]) - self.assertEquals(swftr1.destination().eid, swfstate1.eid) + self.assertEquals(swftr1.destination(None).eid, swfstate1.eid) # workflows built, begin test self.group = self.request().create_entity('CWGroup', name=u'grp1') self.commit() diff -r d4ae6e751b60 -r 716ee59d64a7 entities/wfobjs.py --- a/entities/wfobjs.py Wed Feb 10 16:34:15 2010 +0100 +++ b/entities/wfobjs.py Fri Feb 26 15:36:30 2010 +0100 @@ -123,7 +123,7 @@ self._cw.execute('SET S allowed_transition T ' 'WHERE S eid %(s)s, T eid %(t)s', {'s': state, 't': tr.eid}, ('s', 't')) - tr.set_transition_permissions(requiredgroups, conditions, reset=False) + tr.set_permissions(requiredgroups, conditions, reset=False) return tr def add_transition(self, name, fromstates, tostate=None, @@ -161,9 +161,9 @@ execute = self._cw.unsafe_execute execute('SET X in_state S WHERE S eid %(s)s', {'s': todelstate.eid}, 's') execute('SET X from_state NS WHERE X to_state OS, OS eid %(os)s, NS eid %(ns)s', - {'os': todelstate.eid, 'ns': newstate.eid}, 's') + {'os': todelstate.eid, 'ns': replacement.eid}, 's') execute('SET X to_state NS WHERE X to_state OS, OS eid %(os)s, NS eid %(ns)s', - {'os': todelstate.eid, 'ns': newstate.eid}, 's') + {'os': todelstate.eid, 'ns': replacement.eid}, 's') todelstate.delete() @@ -219,10 +219,9 @@ """ if self.transition_of: return self.transition_of[0].rest_path(), {} - return super(Transition, self).after_deletion_path() + return super(BaseTransition, self).after_deletion_path() - def set_transition_permissions(self, requiredgroups=(), conditions=(), - reset=True): + def set_permissions(self, requiredgroups=(), conditions=(), reset=True): """set or add (if `reset` is False) groups and conditions for this transition """ @@ -251,13 +250,30 @@ 'T condition X WHERE T eid %(x)s',kwargs, 'x') # 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""" __regid__ = 'Transition' - def destination(self): - return self.destination_state[0] + def destination(self, entity): + try: + return self.destination_state[0] + except IndexError: + return entity.latest_trinfo().previous_state + + def potential_destinations(self): + try: + yield self.destination_state[0] + except IndexError: + for incomingstate in self.reverse_allowed_transition: + for tr in incomingstate.reverse_destination_state: + for previousstate in tr.reverse_allowed_transition: + yield previousstate def parent(self): return self.workflow @@ -271,9 +287,12 @@ def subwf(self): return self.subworkflow[0] - def destination(self): + def destination(self, entity): return self.subwf.initial + def potential_destinations(self): + yield self.subwf.initial + def add_exit_point(self, fromstate, tostate): if hasattr(fromstate, 'eid'): fromstate = fromstate.eid @@ -311,7 +330,7 @@ return result def clear_all_caches(self): - super(WorkflowableMixIn, self).clear_all_caches() + super(WorkflowTransition, self).clear_all_caches() clear_cache(self, 'exit_points') diff -r d4ae6e751b60 -r 716ee59d64a7 entity.py --- a/entity.py Wed Feb 10 16:34:15 2010 +0100 +++ b/entity.py Fri Feb 26 15:36:30 2010 +0100 @@ -12,10 +12,8 @@ from logilab.common import interface from logilab.common.compat import all from logilab.common.decorators import cached -from logilab.common.deprecation import deprecated from logilab.mtconverter import TransformData, TransformError, xml_escape -from rql import parse from rql.utils import rqlvar_maker from cubicweb import Unauthorized, typed_eid @@ -638,8 +636,17 @@ rql = '%s WHERE %s' % (rql.split(' ORDERBY ', 1)[0], rql.split(' WHERE ', 1)[1]) elif not ' ORDERBY ' in rql: - args = tuple(rql.split(' WHERE ', 1)) - rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % args + args = rql.split(' WHERE ', 1) + # if modification_date already retreived, 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) + else: + rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % \ + tuple(args) return rql # generic vocabulary methods ############################################## diff -r d4ae6e751b60 -r 716ee59d64a7 etwist/service.py --- a/etwist/service.py Wed Feb 10 16:34:15 2010 +0100 +++ b/etwist/service.py Fri Feb 26 15:36:30 2010 +0100 @@ -1,6 +1,4 @@ import os -import os.path as osp -import sys import win32serviceutil import win32service diff -r d4ae6e751b60 -r 716ee59d64a7 etwist/twconfig.py --- a/etwist/twconfig.py Wed Feb 10 16:34:15 2010 +0100 +++ b/etwist/twconfig.py Fri Feb 26 15:36:30 2010 +0100 @@ -16,7 +16,9 @@ from os.path import join -from cubicweb.web.webconfig import WebConfiguration, merge_options, Method +from logilab.common.configuration import Method + +from cubicweb.web.webconfig import WebConfiguration, merge_options class TwistedConfiguration(WebConfiguration): """web instance (in a twisted web server) client of a RQL server""" diff -r d4ae6e751b60 -r 716ee59d64a7 etwist/twctl.py --- a/etwist/twctl.py Wed Feb 10 16:34:15 2010 +0100 +++ b/etwist/twctl.py Fri Feb 26 15:36:30 2010 +0100 @@ -6,8 +6,6 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ -import sys - from cubicweb.toolsutils import CommandHandler from cubicweb.web.webctl import WebCreateHandler diff -r d4ae6e751b60 -r 716ee59d64a7 ext/rest.py --- a/ext/rest.py Wed Feb 10 16:34:15 2010 +0100 +++ b/ext/rest.py Fri Feb 26 15:36:30 2010 +0100 @@ -147,7 +147,7 @@ try: from pygments import highlight - from pygments.lexers import get_lexer_by_name, LEXERS + from pygments.lexers import get_lexer_by_name from pygments.formatters import HtmlFormatter except ImportError: pygments_directive = None diff -r d4ae6e751b60 -r 716ee59d64a7 ext/xhtml2fo.py --- a/ext/xhtml2fo.py Wed Feb 10 16:34:15 2010 +0100 +++ b/ext/xhtml2fo.py Fri Feb 26 15:36:30 2010 +0100 @@ -1,4 +1,4 @@ -from xml.etree.ElementTree import QName, fromstring +from xml.etree.ElementTree import QName from pysixt.standard.xhtml_xslfo.transformer import XHTML2FOTransformer from pysixt.utils.xslfo.standard import cm from pysixt.utils.xslfo import SimplePageMaster @@ -64,7 +64,7 @@ u'bottom': self.page_bmargin*cm, u'left' : self.page_lmargin*cm, u'right' : self.page_rmargin*cm }) - pm.add_peripheral_region(u"end",self.hf_height) + pm.add_peripheral_region(u"end", self.hf_height) dims = {} dims[u"bottom"] = self.hf_height + 0.25 pm.set_main_region_margins(dims) @@ -82,41 +82,39 @@ props = { u"force-page-count": u"no-force", u"initial-page-number": u"1", u"format": u"1", } - self._output_properties(ps,props) + self._output_properties(ps, props) sc = self.create_staticcontent(ps, u"end") sc_bl = self.create_block(sc) attrs = { u"hyphenate": u"false", } - attrs[u"font-size"] = u"%.1fpt" %(self.font_size*0.7) + attrs[u"font-size"] = u"%.1fpt" % (self.font_size * 0.7) attrs[u"language"] = self.lang attrs[u"text-align"] = u"center" - self._output_properties(sc_bl,attrs) + self._output_properties(sc_bl, attrs) sc_bl.text = u"Page" + u" " # ### Should be localised! pn = self.create_pagenumber(sc_bl) pn.tail = u"/" - lpn = self.create_pagenumbercitation( sc_bl, - u"last-block-of-report-%d" % params[u"context_pos"] - ) + self.create_pagenumbercitation( + sc_bl, u"last-block-of-report-%d" % params[u"context_pos"]) - - fl = self.create_flow(ps,u"body") + fl = self.create_flow(ps, u"body") bl = self.create_block(fl) # Sets on the highest block element the properties of the XHTML body # element. These properties (at the least the inheritable ones) will # be inherited by all the future FO elements. - bodies = list(self.in_tree.getiterator(QName(XHTML_NS,u"body"))) + bodies = list(self.in_tree.getiterator(QName(XHTML_NS, u"body"))) if len(bodies) > 0: attrs = self._extract_properties([bodies[0]]) else: attrs = default_styles[u"body"].copy() - attrs[u"font-size"] = u"%.1fpt" %self.font_size + attrs[u"font-size"] = u"%.1fpt" % self.font_size attrs[u"language"] = self.lang self._output_properties(bl,attrs) # Processes the report content - self._copy_text(in_elt,bl) - self._process_nodes(in_elt.getchildren(),bl) + self._copy_text(in_elt, bl) + self._process_nodes(in_elt.getchildren(), bl) # Inserts an empty block at the end of the report in order to be able # to compute the last page number of this report. @@ -130,7 +128,7 @@ """ Visit function called when starting the process of the input tree. """ - content = [ d for d in self.in_tree.getiterator(QName(XHTML_NS,u"div")) + content = [ d for d in self.in_tree.getiterator(QName(XHTML_NS, u"div")) if d.get(u"id") == self.section ] # Asks the process of the report elements with a specific visit # function diff -r d4ae6e751b60 -r 716ee59d64a7 goa/goactl.py --- a/goa/goactl.py Wed Feb 10 16:34:15 2010 +0100 +++ b/goa/goactl.py Fri Feb 26 15:36:30 2010 +0100 @@ -31,7 +31,6 @@ (docutils.__path__[0], 'docutils'), (roman.__file__.replace('.pyc', '.py'), 'roman.py'), - (join(CW_SOFTWARE_ROOT, 'embedded', 'mx'), 'mx'), ('/usr/share/fckeditor/', 'fckeditor'), (join(CW_SOFTWARE_ROOT, 'web', 'data'), join('cubes', 'shared', 'data')), diff -r d4ae6e751b60 -r 716ee59d64a7 goa/test/pytestconf.py --- a/goa/test/pytestconf.py Wed Feb 10 16:34:15 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -"""this pytestconf automatically adds the mx's python version in the PYTHONPATH -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" -import sys -import os.path as osp - -import cubicweb -# remove 'mx' modules imported by cubicweb -for modname in sys.modules.keys(): - if modname.startswith('mx'): - sys.modules.pop(modname) - -# this is where mx should get imported from -mxpath = osp.abspath(osp.join(osp.dirname(cubicweb.__file__), 'embedded')) -sys.path.insert(1, mxpath) - -# make sure the correct mx is imported -import mx -assert osp.dirname(mx.__file__) == osp.join(mxpath, 'mx'), '%s != %s' % (osp.dirname(mx.__file__), mxpath) diff -r d4ae6e751b60 -r 716ee59d64a7 hooks/email.py --- a/hooks/email.py Wed Feb 10 16:34:15 2010 +0100 +++ b/hooks/email.py Fri Feb 26 15:36:30 2010 +0100 @@ -8,7 +8,6 @@ __docformat__ = "restructuredtext en" from cubicweb.server import hook -from cubicweb.server.repository import ensure_card_respected from logilab.common.compat import any @@ -27,11 +26,6 @@ def precommit_event(self): if self.condition(): - # we've to handle cardinaly by ourselves since we're using unsafe_execute - # but use session.execute and not session.unsafe_execute to check we - # can change the relation - ensure_card_respected(self.session.execute, self.session, - self.entity.eid, self.rtype, self.email.eid) self.session.unsafe_execute( 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype, {'x': self.entity.eid, 'y': self.email.eid}, 'x') diff -r d4ae6e751b60 -r 716ee59d64a7 hooks/metadata.py --- a/hooks/metadata.py Wed Feb 10 16:34:15 2010 +0100 +++ b/hooks/metadata.py Fri Feb 26 15:36:30 2010 +0100 @@ -55,7 +55,13 @@ events = ('before_update_entity',) def __call__(self): - self.entity.setdefault('modification_date', datetime.now()) + # repairing is true during c-c upgrade/shell and similar commands. We + # usually don't want to update modification date in such cases. + # + # XXX to be really clean, we should turn off modification_date update + # explicitly on each command where we do not want that behaviour. + if not self._cw.vreg.config.repairing: + self.entity.setdefault('modification_date', datetime.now()) class _SetCreatorOp(hook.Operation): diff -r d4ae6e751b60 -r 716ee59d64a7 hooks/notification.py --- a/hooks/notification.py Wed Feb 10 16:34:15 2010 +0100 +++ b/hooks/notification.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,7 +9,6 @@ from logilab.common.textutils import normalize_text -from cubicweb import RegistryException from cubicweb.selectors import implements from cubicweb.server import hook from cubicweb.sobjects.supervising import SupervisionMailOp @@ -30,8 +29,8 @@ category = 'notification' def select_view(self, vid, rset, row=0, col=0): - return self._cw.vreg['views'].select_or_none(vid, self._cw, - rset=rset, row=0, col=0) + return self._cw.vreg['views'].select_or_none(vid, self._cw, rset=rset, + row=row, col=col) class StatusChangeHook(NotificationHook): @@ -126,7 +125,7 @@ rqlsel.append(var) rqlrestr.append('X %s %s' % (attr, var)) rql = 'Any %s WHERE %s' % (','.join(rqlsel), ','.join(rqlrestr)) - rset = session.execute(rql, {'x': self.entity.eid}, 'x') + rset = session.unsafe_execute(rql, {'x': self.entity.eid}, 'x') for i, attr in enumerate(attrs): oldvalue = rset[0][i] newvalue = self.entity[attr] diff -r d4ae6e751b60 -r 716ee59d64a7 hooks/security.py --- a/hooks/security.py Wed Feb 10 16:34:15 2010 +0100 +++ b/hooks/security.py Fri Feb 26 15:36:30 2010 +0100 @@ -12,30 +12,31 @@ from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS, hook -def check_entity_attributes(session, entity): +def check_entity_attributes(session, entity, editedattrs=None): eid = entity.eid eschema = entity.e_schema # ._default_set is only there on entity creation to indicate unspecified # attributes which has been set to a default value defined in the schema defaults = getattr(entity, '_default_set', ()) - try: - editedattrs = entity.edited_attributes - except AttributeError: - editedattrs = entity + if editedattrs is None: + try: + editedattrs = entity.edited_attributes + except AttributeError: + editedattrs = entity for attr in editedattrs: if attr in defaults: continue rdef = eschema.rdef(attr) if rdef.final: # non final relation are checked by other hooks # add/delete should be equivalent (XXX: unify them into 'update' ?) - rdef.check_perm(session, 'add', eid=eid) + rdef.check_perm(session, 'update', eid=eid) class _CheckEntityPermissionOp(hook.LateOperation): def precommit_event(self): #print 'CheckEntityPermissionOp', self.session.user, self.entity, self.action self.entity.check_perm(self.action) - check_entity_attributes(self.session, self.entity) + check_entity_attributes(self.session, self.entity, self.editedattrs) def commit_event(self): pass @@ -63,7 +64,9 @@ events = ('after_add_entity',) def __call__(self): - _CheckEntityPermissionOp(self._cw, entity=self.entity, action='add') + _CheckEntityPermissionOp(self._cw, entity=self.entity, + editedattrs=tuple(self.entity.edited_attributes), + action='add') class AfterUpdateEntitySecurityHook(SecurityHook): @@ -77,7 +80,12 @@ check_entity_attributes(self._cw, self.entity) except Unauthorized: self.entity.clear_local_perm_cache('update') - _CheckEntityPermissionOp(self._cw, entity=self.entity, action='update') + # save back editedattrs in case the entity is reedited later in the + # same transaction, which will lead to edited_attributes being + # overwritten + _CheckEntityPermissionOp(self._cw, entity=self.entity, + editedattrs=tuple(self.entity.edited_attributes), + action='update') class BeforeDelEntitySecurityHook(SecurityHook): diff -r d4ae6e751b60 -r 716ee59d64a7 hooks/storages.py --- a/hooks/storages.py Wed Feb 10 16:34:15 2010 +0100 +++ b/hooks/storages.py Fri Feb 26 15:36:30 2010 +0100 @@ -1,7 +1,5 @@ """hooks to handle attributes mapped to a custom storage """ -from os import unlink - from cubicweb.server.hook import Hook from cubicweb.server.sources.storages import ETYPE_ATTR_STORAGE @@ -16,30 +14,28 @@ """""" __regid__ = 'bfss_add_entity' events = ('before_add_entity', ) - #__select__ = Hook.__select__ & implements('Repository') def __call__(self): - for attr in ETYPE_ATTR_STORAGE.get(self.entity.__regid__, ()): - fpath = ETYPE_ATTR_STORAGE[self.entity.__regid__][attr].entity_added(self.entity, attr) - if fpath is not None: - AddFileOp(filepath=fpath) + etype = self.entity.__regid__ + for attr in ETYPE_ATTR_STORAGE.get(etype, ()): + ETYPE_ATTR_STORAGE[etype][attr].entity_added(self.entity, attr) class PreUpdateEntityHook(BFSSHook): """""" __regid__ = 'bfss_update_entity' events = ('before_update_entity', ) - #__select__ = Hook.__select__ & implements('Repository') def __call__(self): - for attr in ETYPE_ATTR_STORAGE.get(self.entity.__regid__, ()): - ETYPE_ATTR_STORAGE[self.entity.__regid__][attr].entity_updated(self.entity, attr) + etype = self.entity.__regid__ + for attr in ETYPE_ATTR_STORAGE.get(etype, ()): + ETYPE_ATTR_STORAGE[etype][attr].entity_updated(self.entity, attr) class PreDeleteEntityHook(BFSSHook): """""" __regid__ = 'bfss_delete_entity' events = ('before_delete_entity', ) - #__select__ = Hook.__select__ & implements('Repository') def __call__(self): - for attr in ETYPE_ATTR_STORAGE.get(self.entity.__regid__, ()): - ETYPE_ATTR_STORAGE[self.entity.__regid__][attr].entity_deleted(self.entity, attr) + etype = self.entity.__regid__ + for attr in ETYPE_ATTR_STORAGE.get(etype, ()): + ETYPE_ATTR_STORAGE[etype][attr].entity_deleted(self.entity, attr) diff -r d4ae6e751b60 -r 716ee59d64a7 hooks/syncschema.py --- a/hooks/syncschema.py Wed Feb 10 16:34:15 2010 +0100 +++ b/hooks/syncschema.py Fri Feb 26 15:36:30 2010 +0100 @@ -18,7 +18,7 @@ from logilab.common.decorators import clear_cache -from cubicweb import ValidationError, RepositoryError +from cubicweb import ValidationError from cubicweb.selectors import implements from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CONSTRAINTS, display_name from cubicweb.server import hook, schemaserial as ss @@ -85,6 +85,7 @@ session.transaction_data.setdefault('createdattrs', []).append( '%s.%s' % (etype, rtype)) + def check_valid_changes(session, entity, ro_attrs=('name', 'final')): errors = {} # don't use getattr(entity, attr), we would get the modified value if any @@ -161,11 +162,16 @@ def commit_event(self): rebuildinfered = self.session.data.get('rebuild-infered', True) repo = self.session.repo - repo.set_schema(repo.schema, rebuildinfered=rebuildinfered) - # CWUser class might have changed, update current session users - cwuser_cls = self.session.vreg['etypes'].etype_class('CWUser') - for session in repo._sessions.values(): - session.user.__class__ = cwuser_cls + # commit event should not raise error, while set_schema has chances to + # do so because it triggers full vreg reloading + try: + repo.set_schema(repo.schema, rebuildinfered=rebuildinfered) + # CWUser class might have changed, update current session users + cwuser_cls = self.session.vreg['etypes'].etype_class('CWUser') + for session in repo._sessions.values(): + session.user.__class__ = cwuser_cls + except: + self.critical('error while setting schmea', exc_info=True) def rollback_event(self): self.precommit_event() @@ -413,7 +419,7 @@ rdef = self.init_rdef(composite=entity.composite) schema = session.vreg.schema rtype = rdef.name - rschema = session.vreg.schema.rschema(rtype) + rschema = schema.rschema(rtype) # this have to be done before permissions setting if rschema.inlined: # need to add a column if the relation is inlined and if this is the @@ -435,15 +441,15 @@ if not (rschema.subjects() or rtype in session.transaction_data.get('createdtables', ())): try: - rschema = session.vreg.schema.rschema(rtype) + rschema = schema.rschema(rtype) tablesql = rschema2sql(rschema) except KeyError: # fake we add it to the schema now to get a correctly # initialized schema but remove it before doing anything # more dangerous... - rschema = session.vreg.schema.add_relation_type(rdef) + rschema = schema.add_relation_type(rdef) tablesql = rschema2sql(rschema) - session.vreg.schema.del_relation_type(rtype) + schema.del_relation_type(rtype) # create the necessary table for sql in tablesql.split(';'): if sql.strip(): @@ -480,6 +486,10 @@ sql = adbh.sql_set_null_allowed(table, column, coltype, self.values['cardinality'][0] != '1') self.session.system_sql(sql) + if 'fulltextindexed' in self.values: + UpdateFTIndexOp(self.session) + self.session.transaction_data.setdefault('fti_update_etypes', + set()).add(etype) class SourceDbCWConstraintAdd(hook.Operation): @@ -685,7 +695,7 @@ erschema = self.session.vreg.schema.schema_by_eid(self.eid) except KeyError: # duh, schema not found, log error and skip operation - self.error('no schema for %s', self.eid) + self.warning('no schema for %s', self.eid) return perms = list(erschema.action_permissions(self.action)) if hasattr(self, 'group_eid'): @@ -712,10 +722,13 @@ erschema = self.session.vreg.schema.schema_by_eid(self.eid) except KeyError: # duh, schema not found, log error and skip operation - self.error('no schema for %s', self.eid) + self.warning('no schema for %s', self.eid) return if isinstance(erschema, RelationSchema): # XXX 3.6 migration return + if isinstance(erschema, RelationDefinitionSchema) and \ + self.action in ('delete', 'add'): # XXX 3.6.1 migration + return perms = list(erschema.action_permissions(self.action)) if hasattr(self, 'group_eid'): perm = self.session.entity_from_eid(self.group_eid).name @@ -927,18 +940,6 @@ SourceDbCWRTypeUpdate(self._cw, rschema=rschema, values=newvalues, entity=entity) -def check_valid_changes(session, entity, ro_attrs=('name', 'final')): - errors = {} - # don't use getattr(entity, attr), we would get the modified value if any - for attr in ro_attrs: - if attr in entity.edited_attributes: - origval, newval = hook.entity_oldnewvalue(entity, attr) - if newval != origval: - errors[attr] = session._("can't change the %s attribute") % \ - display_name(session, attr) - if errors: - raise ValidationError(entity.eid, errors) - class AfterDelRelationTypeHook(SyncSchemaHook): """before deleting a CWAttribute or CWRelation entity: @@ -1127,6 +1128,41 @@ expr=expr) + +class UpdateFTIndexOp(hook.SingleLastOperation): + """operation to update full text indexation of entity whose schema change + + We wait after the commit to as the schema in memory is only updated after the commit. + """ + + def postcommit_event(self): + session = self.session + source = session.repo.system_source + to_reindex = session.transaction_data.get('fti_update_etypes', ()) + self.info('%i etypes need full text indexed reindexation', + len(to_reindex)) + schema = self.session.repo.vreg.schema + for etype in to_reindex: + rset = session.execute('Any X WHERE X is %s' % etype) + self.info('Reindexing full text index for %i entity of type %s', + len(rset), etype) + still_fti = list(schema[etype].indexable_attributes()) + for entity in rset.entities(): + try: + source.fti_unindex_entity(session, entity.eid) + for container in entity.fti_containers(): + if still_fti or container is not entity: + session.repo.index_entity(session, container) + except Exception: + self.critical('Error while updating Full Text Index for' + ' entity %s', entity.eid, exc_info=True) + if len(to_reindex): + # Transaction have already been committed + session.pool.commit() + + + + # specializes synchronization hooks ############################################ diff -r d4ae6e751b60 -r 716ee59d64a7 hooks/syncsession.py --- a/hooks/syncsession.py Wed Feb 10 16:34:15 2010 +0100 +++ b/hooks/syncsession.py Fri Feb 26 15:36:30 2010 +0100 @@ -153,7 +153,7 @@ raise ValidationError(self.entity.eid, {'value': session._(str(ex))}) if not session.user.matching_groups('managers'): - session.add_relation(entity.eid, 'for_user', session.user.eid) + session.add_relation(self.entity.eid, 'for_user', session.user.eid) else: _AddCWPropertyOp(session, cwprop=self.entity) @@ -178,7 +178,7 @@ if entity.for_user: for session_ in get_user_sessions(session.repo, entity.for_user[0].eid): _ChangeCWPropertyOp(session, cwpropdict=session_.user.properties, - key=key, value=value) + key=key, value=value) else: # site wide properties _ChangeCWPropertyOp(session, cwpropdict=session.vreg['propertyvalues'], diff -r d4ae6e751b60 -r 716ee59d64a7 hooks/test/unittest_hooks.py --- a/hooks/test/unittest_hooks.py Wed Feb 10 16:34:15 2010 +0100 +++ b/hooks/test/unittest_hooks.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,22 +9,8 @@ from datetime import datetime -from cubicweb import (ConnectionError, ValidationError, AuthenticationError, - BadConnectionId) -from cubicweb.devtools.testlib import CubicWebTC, get_versions - -from cubicweb.server.sqlutils import SQL_PREFIX -from cubicweb.server.repository import Repository - -orig_get_versions = Repository.get_versions - -def setup_module(*args): - Repository.get_versions = get_versions - -def teardown_module(*args): - Repository.get_versions = orig_get_versions - - +from cubicweb import ValidationError, AuthenticationError, BadConnectionId +from cubicweb.devtools.testlib import CubicWebTC class CoreHooksTC(CubicWebTC): @@ -270,238 +256,5 @@ self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'}) -class SchemaModificationHooksTC(CubicWebTC): - - @classmethod - def init_config(cls, config): - super(SchemaModificationHooksTC, cls).init_config(config) - config._cubes = None - cls.repo.fill_schema() - - def index_exists(self, etype, attr, unique=False): - self.session.set_pool() - dbhelper = self.session.pool.source('system').dbhelper - sqlcursor = self.session.pool['system'] - return dbhelper.index_exists(sqlcursor, SQL_PREFIX + etype, SQL_PREFIX + attr, unique=unique) - - def _set_perms(self, eid): - self.execute('SET X read_permission G WHERE X eid %(x)s, G is CWGroup', - {'x': eid}, 'x') - self.execute('SET X add_permission G WHERE X eid %(x)s, G is CWGroup, G name "managers"', - {'x': eid}, 'x') - self.execute('SET X delete_permission G WHERE X eid %(x)s, G is CWGroup, G name "owners"', - {'x': eid}, 'x') - - def test_base(self): - schema = self.repo.schema - self.session.set_pool() - dbhelper = self.session.pool.source('system').dbhelper - sqlcursor = self.session.pool['system'] - self.failIf(schema.has_entity('Societe2')) - self.failIf(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')) - # have to commit before adding definition relations - self.commit() - self.failUnless(schema.has_entity('Societe2')) - self.failUnless(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] - self._set_perms(attreid) - concerne2_rdef_eid = self.execute( - '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.commit() - self.failUnless('name' in schema['Societe2'].subject_relations()) - self.failUnless('concerne2' in schema['Societe2'].subject_relations()) - self.failUnless(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"') - self.execute('SET X concerne2 X WHERE X name "logilab"') - rset = self.execute('Any X WHERE X concerne2 Y') - self.assertEquals(rset.rows, [[s2eid]]) - # check that when a relation definition is deleted, existing relations are deleted - rdefeid = self.execute('INSERT CWRelation X: X cardinality "**", X relation_type RT, ' - ' X from_entity E, X to_entity E ' - 'WHERE RT name "concerne2", E name "CWUser"')[0][0] - self._set_perms(rdefeid) - self.commit() - self.execute('DELETE CWRelation X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}, 'x') - 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')) - # 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.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()) - - def test_is_instance_of_insertions(self): - seid = self.execute('INSERT Transition T: T name "subdiv"')[0][0] - is_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is ET, ET name ETN' % seid)] - self.assertEquals(is_etypes, ['Transition']) - instanceof_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is_instance_of ET, ET name ETN' % seid)] - self.assertEquals(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) - snames = [name for name, in self.execute('Any N WHERE S is_instance_of BaseTransition, S name N')] - self.failUnless('subdiv' in snames) - - - def test_perms_synchronization_1(self): - schema = self.repo.schema - self.assertEquals(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.execute('DELETE X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"') - self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users', ))) - self.commit() - self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', ))) - self.execute('SET X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"') - self.commit() - self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users',))) - - def test_perms_synchronization_2(self): - schema = self.repo.schema['in_group'].rdefs[('CWUser', 'CWGroup')] - self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests'))) - self.execute('DELETE X read_permission Y WHERE X relation_type RT, RT name "in_group", Y name "guests"') - self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests'))) - self.commit() - self.assertEquals(schema.get_groups('read'), set(('managers', 'users'))) - self.execute('SET X read_permission Y WHERE X relation_type RT, RT name "in_group", Y name "guests"') - self.assertEquals(schema.get_groups('read'), set(('managers', 'users'))) - self.commit() - self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests'))) - - def test_nonregr_user_edit_itself(self): - ueid = self.session.user.eid - groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')] - self.execute('DELETE X in_group Y WHERE X eid %s' % ueid) - self.execute('SET X surname "toto" WHERE X eid %s' % ueid) - self.execute('SET X in_group Y WHERE X eid %s, Y name "managers"' % ueid) - self.commit() - eeid = self.execute('Any X WHERE X is CWEType, X name "CWEType"')[0][0] - self.execute('DELETE X read_permission Y WHERE X eid %s' % eeid) - self.execute('SET X final FALSE WHERE X eid %s' % eeid) - self.execute('SET X read_permission Y WHERE X eid %s, Y eid in (%s, %s)' - % (eeid, groupeids[0], groupeids[1])) - self.commit() - self.execute('Any X WHERE X is CWEType, X name "CWEType"') - - # schema modification hooks tests ######################################### - - def test_uninline_relation(self): - self.session.set_pool() - dbhelper = self.session.pool.source('system').dbhelper - sqlcursor = self.session.pool['system'] - self.failUnless(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.commit() - self.failIf(self.schema['state_of'].inlined) - self.failIf(self.index_exists('State', 'state_of')) - rset = self.execute('Any X, Y WHERE X state_of Y') - self.assertEquals(len(rset), 2) # user states - finally: - self.execute('SET X inlined TRUE WHERE X name "state_of"') - self.failIf(self.schema['state_of'].inlined) - self.commit() - self.failUnless(self.schema['state_of'].inlined) - self.failUnless(self.index_exists('State', 'state_of')) - rset = self.execute('Any X, Y WHERE X state_of Y') - self.assertEquals(len(rset), 2) - - def test_indexed_change(self): - self.session.set_pool() - dbhelper = self.session.pool.source('system').dbhelper - sqlcursor = self.session.pool['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.commit() - self.failIf(self.schema['name'].rdef('Workflow', 'String').indexed) - self.failIf(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.commit() - self.failUnless(self.schema['name'].rdef('Workflow', 'String').indexed) - self.failUnless(self.index_exists('Workflow', 'name')) - - def test_unique_change(self): - self.session.set_pool() - dbhelper = self.session.pool.source('system').dbhelper - sqlcursor = self.session.pool['system'] - try: - 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.commit() - self.failUnless(self.schema['Workflow'].has_unique_values('name')) - self.failUnless(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.commit() - self.failIf(self.schema['Workflow'].has_unique_values('name')) - self.failIf(self.index_exists('Workflow', 'name', unique=True)) - - def test_required_change_1(self): - self.execute('SET DEF cardinality "?1" ' - 'WHERE DEF relation_type RT, DEF from_entity E,' - 'RT name "title", E name "Bookmark"') - self.commit() - # should now be able to add bookmark without title - self.execute('INSERT Bookmark X: X path "/view"') - self.commit() - - def test_required_change_2(self): - self.execute('SET DEF cardinality "11" ' - 'WHERE DEF relation_type RT, DEF from_entity E,' - 'RT name "surname", E name "CWUser"') - self.commit() - # should not be able anymore to add cwuser without surname - self.assertRaises(ValidationError, self.create_user, "toto") - self.execute('SET DEF cardinality "?1" ' - 'WHERE DEF relation_type RT, DEF from_entity E,' - 'RT name "surname", E name "CWUser"') - self.commit() - - - def test_add_attribute_to_base_class(self): - 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 "messageid", E name "BaseTransition", F name "String"')[0][0] - assert self.execute('SET X read_permission Y WHERE X eid %(x)s, Y name "managers"', - {'x': attreid}, 'x') - self.commit() - self.schema.rebuild_infered_relations() - self.failUnless('Transition' in self.schema['messageid'].subjects()) - self.failUnless('WorkflowTransition' in self.schema['messageid'].subjects()) - self.execute('Any X WHERE X is_instance_of BaseTransition, X messageid "hop"') - if __name__ == '__main__': unittest_main() diff -r d4ae6e751b60 -r 716ee59d64a7 hooks/test/unittest_syncschema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hooks/test/unittest_syncschema.py Fri Feb 26 15:36:30 2010 +0100 @@ -0,0 +1,290 @@ +from logilab.common.testlib import TestCase, unittest_main + +from cubicweb import ValidationError +from cubicweb.devtools.testlib import CubicWebTC +from cubicweb.server.sqlutils import SQL_PREFIX + + +SCHEMA_EIDS = {} +class SchemaModificationHooksTC(CubicWebTC): + reset_schema = True + + @classmethod + def init_config(cls, config): + super(SchemaModificationHooksTC, cls).init_config(config) + # we have to read schema from the database to get eid for schema entities + config._cubes = None + cls.repo.fill_schema() + # remember them so we can reread it from the fs instead of the db (too + # costly) between tests + for x in cls.repo.schema.entities(): + SCHEMA_EIDS[x] = x.eid + for x in cls.repo.schema.relations(): + SCHEMA_EIDS[x] = x.eid + for rdef in x.rdefs.itervalues(): + SCHEMA_EIDS[(rdef.subject, rdef.rtype, rdef.object)] = rdef.eid + + @classmethod + def _refresh_repo(cls): + super(SchemaModificationHooksTC, cls)._refresh_repo() + # rebuild schema eid index + schema = cls.repo.schema + for x in schema.entities(): + x.eid = SCHEMA_EIDS[x] + schema._eid_index[x.eid] = x + for x in cls.repo.schema.relations(): + x.eid = SCHEMA_EIDS[x] + schema._eid_index[x.eid] = x + for rdef in x.rdefs.itervalues(): + rdef.eid = SCHEMA_EIDS[(rdef.subject, rdef.rtype, rdef.object)] + schema._eid_index[rdef.eid] = rdef + + def index_exists(self, etype, attr, unique=False): + self.session.set_pool() + dbhelper = self.session.pool.source('system').dbhelper + sqlcursor = self.session.pool['system'] + return dbhelper.index_exists(sqlcursor, SQL_PREFIX + etype, SQL_PREFIX + attr, unique=unique) + + def _set_perms(self, eid): + self.execute('SET X read_permission G WHERE X eid %(x)s, G is CWGroup', + {'x': eid}, 'x') + self.execute('SET X add_permission G WHERE X eid %(x)s, G is CWGroup, G name "managers"', + {'x': eid}, 'x') + self.execute('SET X delete_permission G WHERE X eid %(x)s, G is CWGroup, G name "owners"', + {'x': eid}, 'x') + + def _set_attr_perms(self, eid): + self.execute('SET X read_permission G WHERE X eid %(x)s, G is CWGroup', + {'x': eid}, 'x') + self.execute('SET X update_permission G WHERE X eid %(x)s, G is CWGroup, G name "managers"', + {'x': eid}, 'x') + + def test_base(self): + schema = self.repo.schema + self.session.set_pool() + dbhelper = self.session.pool.source('system').dbhelper + sqlcursor = self.session.pool['system'] + self.failIf(schema.has_entity('Societe2')) + self.failIf(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')) + # have to commit before adding definition relations + self.commit() + self.failUnless(schema.has_entity('Societe2')) + self.failUnless(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] + self._set_attr_perms(attreid) + concerne2_rdef_eid = self.execute( + '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.commit() + self.failUnless('name' in schema['Societe2'].subject_relations()) + self.failUnless('concerne2' in schema['Societe2'].subject_relations()) + self.failUnless(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"') + self.execute('SET X concerne2 X WHERE X name "logilab"') + rset = self.execute('Any X WHERE X concerne2 Y') + self.assertEquals(rset.rows, [[s2eid]]) + # check that when a relation definition is deleted, existing relations are deleted + rdefeid = self.execute('INSERT CWRelation X: X cardinality "**", X relation_type RT, ' + ' X from_entity E, X to_entity E ' + 'WHERE RT name "concerne2", E name "CWUser"')[0][0] + self._set_perms(rdefeid) + self.commit() + self.execute('DELETE CWRelation X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}, 'x') + 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')) + # 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.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()) + + def test_is_instance_of_insertions(self): + seid = self.execute('INSERT Transition T: T name "subdiv"')[0][0] + is_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is ET, ET name ETN' % seid)] + self.assertEquals(is_etypes, ['Transition']) + instanceof_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is_instance_of ET, ET name ETN' % seid)] + self.assertEquals(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) + snames = [name for name, in self.execute('Any N WHERE S is_instance_of BaseTransition, S name N')] + self.failUnless('subdiv' in snames) + + + def test_perms_synchronization_1(self): + schema = self.repo.schema + self.assertEquals(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.execute('DELETE X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"') + self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users', ))) + self.commit() + self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers',))) + self.execute('SET X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"') + self.commit() + self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users',))) + + def test_perms_synchronization_2(self): + schema = self.repo.schema['in_group'].rdefs[('CWUser', 'CWGroup')] + self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests'))) + self.execute('DELETE X read_permission Y WHERE X relation_type RT, RT name "in_group", Y name "guests"') + self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests'))) + self.commit() + self.assertEquals(schema.get_groups('read'), set(('managers', 'users'))) + self.execute('SET X read_permission Y WHERE X relation_type RT, RT name "in_group", Y name "guests"') + self.assertEquals(schema.get_groups('read'), set(('managers', 'users'))) + self.commit() + self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests'))) + + def test_nonregr_user_edit_itself(self): + ueid = self.session.user.eid + groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')] + self.execute('DELETE X in_group Y WHERE X eid %s' % ueid) + self.execute('SET X surname "toto" WHERE X eid %s' % ueid) + self.execute('SET X in_group Y WHERE X eid %s, Y name "managers"' % ueid) + self.commit() + eeid = self.execute('Any X WHERE X is CWEType, X name "CWEType"')[0][0] + self.execute('DELETE X read_permission Y WHERE X eid %s' % eeid) + self.execute('SET X final FALSE WHERE X eid %s' % eeid) + self.execute('SET X read_permission Y WHERE X eid %s, Y eid in (%s, %s)' + % (eeid, groupeids[0], groupeids[1])) + self.commit() + self.execute('Any X WHERE X is CWEType, X name "CWEType"') + + # schema modification hooks tests ######################################### + + def test_uninline_relation(self): + self.session.set_pool() + dbhelper = self.session.pool.source('system').dbhelper + sqlcursor = self.session.pool['system'] + self.failUnless(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.commit() + self.failIf(self.schema['state_of'].inlined) + self.failIf(self.index_exists('State', 'state_of')) + rset = self.execute('Any X, Y WHERE X state_of Y') + self.assertEquals(len(rset), 2) # user states + finally: + self.execute('SET X inlined TRUE WHERE X name "state_of"') + self.failIf(self.schema['state_of'].inlined) + self.commit() + self.failUnless(self.schema['state_of'].inlined) + self.failUnless(self.index_exists('State', 'state_of')) + rset = self.execute('Any X, Y WHERE X state_of Y') + self.assertEquals(len(rset), 2) + + def test_indexed_change(self): + self.session.set_pool() + dbhelper = self.session.pool.source('system').dbhelper + sqlcursor = self.session.pool['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.commit() + self.failIf(self.schema['name'].rdef('Workflow', 'String').indexed) + self.failIf(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.commit() + self.failUnless(self.schema['name'].rdef('Workflow', 'String').indexed) + self.failUnless(self.index_exists('Workflow', 'name')) + + def test_unique_change(self): + self.session.set_pool() + dbhelper = self.session.pool.source('system').dbhelper + sqlcursor = self.session.pool['system'] + try: + 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.commit() + self.failUnless(self.schema['Workflow'].has_unique_values('name')) + self.failUnless(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.commit() + self.failIf(self.schema['Workflow'].has_unique_values('name')) + self.failIf(self.index_exists('Workflow', 'name', unique=True)) + + def test_required_change_1(self): + self.execute('SET DEF cardinality "?1" ' + 'WHERE DEF relation_type RT, DEF from_entity E,' + 'RT name "title", E name "Bookmark"') + self.commit() + # should now be able to add bookmark without title + self.execute('INSERT Bookmark X: X path "/view"') + self.commit() + + def test_required_change_2(self): + self.execute('SET DEF cardinality "11" ' + 'WHERE DEF relation_type RT, DEF from_entity E,' + 'RT name "surname", E name "CWUser"') + self.commit() + # should not be able anymore to add cwuser without surname + self.assertRaises(ValidationError, self.create_user, "toto") + self.execute('SET DEF cardinality "?1" ' + 'WHERE DEF relation_type RT, DEF from_entity E,' + 'RT name "surname", E name "CWUser"') + self.commit() + + + def test_add_attribute_to_base_class(self): + 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 "messageid", E name "BaseTransition", F name "String"')[0][0] + assert self.execute('SET X read_permission Y WHERE X eid %(x)s, Y name "managers"', + {'x': attreid}, 'x') + self.commit() + self.schema.rebuild_infered_relations() + self.failUnless('Transition' in self.schema['messageid'].subjects()) + self.failUnless('WorkflowTransition' in self.schema['messageid'].subjects()) + self.execute('Any X WHERE X is_instance_of BaseTransition, X messageid "hop"') + + def test_change_fulltextindexed(self): + target = self.request().create_entity(u'EmailAddress', address=u'rick.roll@dance.com') + self.commit() + rset = self.execute('Any X Where X has_text "rick.roll"') + self.assertIn(target.eid, [item[0] for item in rset]) + + assert self.execute('''SET A fulltextindexed False + WHERE E is CWEType, + E name "EmailAddress", + A is CWAttribute, + A from_entity E, + A relation_type R, + R name "address" + ''') + self.commit() + rset = self.execute('Any X Where X has_text "rick.roll"') + self.assertNotIn(target.eid, [item[0] for item in rset]) + diff -r d4ae6e751b60 -r 716ee59d64a7 hooks/workflow.py --- a/hooks/workflow.py Wed Feb 10 16:34:15 2010 +0100 +++ b/hooks/workflow.py Fri Feb 26 15:36:30 2010 +0100 @@ -13,7 +13,6 @@ from cubicweb.interfaces import IWorkflowable from cubicweb.selectors import implements from cubicweb.server import hook -from cubicweb.entities.wfobjs import WorkflowTransition def _change_state(session, x, oldstate, newstate): @@ -52,7 +51,6 @@ """try to fire auto transition after state changes""" def precommit_event(self): - session = self.session entity = self.entity autotrs = list(entity.possible_transitions('auto')) if autotrs: @@ -232,7 +230,7 @@ raise ValidationError(entity.eid, {'by_transition': msg}) if entity.get('to_state'): deststateeid = entity['to_state'] - if not cowpowers and deststateeid != tr.destination().eid: + if not cowpowers and deststateeid != tr.destination(forentity).eid: msg = session._("transition isn't allowed") raise ValidationError(entity.eid, {'by_transition': msg}) if swtr is None: @@ -241,7 +239,7 @@ msg = session._("state doesn't belong to entity's workflow") raise ValidationError(entity.eid, {'to_state': msg}) else: - deststateeid = tr.destination().eid + deststateeid = tr.destination(forentity).eid # everything is ok, add missing information on the trinfo entity entity['from_state'] = fromstate.eid entity['to_state'] = deststateeid diff -r d4ae6e751b60 -r 716ee59d64a7 i18n.py --- a/i18n.py Wed Feb 10 16:34:15 2010 +0100 +++ b/i18n.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,7 +9,6 @@ import re import os -import sys from os.path import join, basename, splitext, exists from glob import glob diff -r d4ae6e751b60 -r 716ee59d64a7 i18n/en.po --- a/i18n/en.po Wed Feb 10 16:34:15 2010 +0100 +++ b/i18n/en.po Fri Feb 26 15:36:30 2010 +0100 @@ -125,6 +125,9 @@ msgid "(UNEXISTANT EID)" msgstr "" +msgid "(loading ...)" +msgstr "" + msgid "**" msgstr "0..n 0..n" @@ -191,9 +194,6 @@ msgid "Any" msgstr "" -msgid "Application" -msgstr "" - msgid "Attributes" msgstr "" @@ -348,9 +348,6 @@ msgid "Entities" msgstr "" -msgid "Environment" -msgstr "" - msgid "ExternalUri" msgstr "External Uri" @@ -375,6 +372,9 @@ msgid "Help" msgstr "" +msgid "Instance" +msgstr "" + msgid "Int" msgstr "Integer" @@ -501,7 +501,7 @@ msgid "Relations" msgstr "" -msgid "Request" +msgid "Repository" msgstr "" #, python-format @@ -514,9 +514,6 @@ msgid "Search for" msgstr "" -msgid "Server" -msgstr "" - msgid "SizeConstraint" msgstr "size constraint" @@ -657,6 +654,9 @@ msgid "Used by:" msgstr "" +msgid "Web server" +msgstr "" + msgid "What's new?" msgstr "" @@ -917,32 +917,29 @@ msgid "add Bookmark bookmarked_by CWUser object" msgstr "bookmark" -msgid "add CWAttribute add_permission RQLExpression subject" -msgstr "add rql expression" - msgid "add CWAttribute constrained_by CWConstraint subject" msgstr "constraint" -msgid "add CWAttribute delete_permission RQLExpression subject" -msgstr "delete rql expression" - msgid "add CWAttribute read_permission RQLExpression subject" msgstr "read rql expression" msgid "add CWAttribute relation_type CWRType object" msgstr "attribute definition" +msgid "add CWAttribute update_permission RQLExpression subject" +msgstr "rql expression for update permission" + msgid "add CWEType add_permission RQLExpression subject" -msgstr "rql expression for the add permission" +msgstr "rql expression for add permission" msgid "add CWEType delete_permission RQLExpression subject" -msgstr "rql expression for the delete permission" +msgstr "rql expression for delete permission" msgid "add CWEType read_permission RQLExpression subject" -msgstr "rql expression for the read permission" +msgstr "rql expression for read permission" msgid "add CWEType update_permission RQLExpression subject" -msgstr "rql expression for the update permission" +msgstr "rql expression for update permission" msgid "add CWProperty for_user CWUser object" msgstr "property" @@ -1034,10 +1031,6 @@ msgid "add_permission" msgstr "add permission" -msgctxt "CWAttribute" -msgid "add_permission" -msgstr "add permission" - msgctxt "CWRelation" msgid "add_permission" msgstr "add permission" @@ -1083,9 +1076,6 @@ msgid "allow to set a specific workflow for an entity" msgstr "" -msgid "allowed transition from this state" -msgstr "" - msgid "allowed transitions from this state" msgstr "" @@ -1489,6 +1479,12 @@ msgid "condition_object" msgstr "condition of" +msgid "config mode" +msgstr "" + +msgid "config type" +msgstr "" + msgid "confirm password" msgstr "" @@ -1577,24 +1573,6 @@ msgid "copy" msgstr "" -msgid "" -"core relation giving to a group the permission to add an entity or relation " -"type" -msgstr "" - -msgid "" -"core relation giving to a group the permission to delete an entity or " -"relation type" -msgstr "" - -msgid "" -"core relation giving to a group the permission to read an entity or relation " -"type" -msgstr "" - -msgid "core relation giving to a group the permission to update an entity type" -msgstr "" - msgid "core relation indicating a user's groups" msgstr "" @@ -1661,17 +1639,13 @@ msgstr "creating email address for user %(linkto)s" msgid "" -"creating RQLExpression (CWAttribute %(linkto)s add_permission RQLExpression)" -msgstr "RQL expression granting add permission on %(linkto)s" +"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)" +msgstr "RQL expression granting read permission on %(linkto)s" msgid "" -"creating RQLExpression (CWAttribute %(linkto)s delete_permission " +"creating RQLExpression (CWAttribute %(linkto)s update_permission " "RQLExpression)" -msgstr "RQL expression granting delete permission on %(linkto)s" - -msgid "" -"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)" -msgstr "RQL expression granting read permission on %(linkto)s" +msgstr "RQL expression granting update permission on %(linkto)s" msgid "" "creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)" @@ -1905,10 +1879,6 @@ msgid "delete_permission" msgstr "delete permission" -msgctxt "CWAttribute" -msgid "delete_permission" -msgstr "delete permission" - msgctxt "CWRelation" msgid "delete_permission" msgstr "delete_permission" @@ -2271,6 +2241,9 @@ msgid "follow this link for more information on this %s" msgstr "" +msgid "follow this link if javascript is deactivated" +msgstr "" + msgid "for_user" msgstr "for user" @@ -2385,18 +2358,6 @@ msgid "groups" msgstr "" -msgid "groups allowed to add entities/relations of this type" -msgstr "" - -msgid "groups allowed to delete entities/relations of this type" -msgstr "" - -msgid "groups allowed to read entities/relations of this type" -msgstr "" - -msgid "groups allowed to update entities of this type" -msgstr "" - msgid "groups grant permissions to the user" msgstr "" @@ -2421,9 +2382,6 @@ msgid "hide filter form" msgstr "" -msgid "home" -msgstr "" - msgid "" "how to format date and time in the ui (\"man strftime\" for format " "description)" @@ -2567,6 +2525,9 @@ msgid "inlined" msgstr "inlined" +msgid "instance home" +msgstr "" + msgid "instance schema" msgstr "" @@ -2634,6 +2595,9 @@ msgid "last connection date" msgstr "" +msgid "last usage" +msgstr "" + msgid "last_login_time" msgstr "last login time" @@ -2681,15 +2645,9 @@ msgid "link a workflow to one or more entity type" msgstr "" -msgid "link to each item in" -msgstr "" - msgid "list" msgstr "" -msgid "loading" -msgstr "" - msgid "log in" msgstr "" @@ -2888,6 +2846,9 @@ msgid "no related project" msgstr "" +msgid "no repository sessions found" +msgstr "" + msgid "no selected entities" msgstr "" @@ -2898,6 +2859,9 @@ msgid "no version information" msgstr "" +msgid "no web sessions found" +msgstr "" + msgid "normal" msgstr "" @@ -2934,6 +2898,12 @@ msgid "open all" msgstr "" +msgid "opened sessions" +msgstr "" + +msgid "opened web sessions" +msgstr "" + msgid "order" msgstr "" @@ -3182,6 +3152,9 @@ msgid "required field" msgstr "" +msgid "resources usage" +msgstr "" + msgid "" "restriction part of a rql query. For entity rql expression, X and U are " "predefined respectivly to the current object and to the request user. For " @@ -3195,18 +3168,6 @@ msgid "right" msgstr "" -msgid "rql expression allowing to add entities/relations of this type" -msgstr "" - -msgid "rql expression allowing to delete entities/relations of this type" -msgstr "" - -msgid "rql expression allowing to read entities/relations of this type" -msgstr "" - -msgid "rql expression allowing to update entities of this type" -msgstr "" - msgid "rql expressions" msgstr "" @@ -3300,9 +3261,6 @@ msgid "september" msgstr "" -msgid "server debug information" -msgstr "" - msgid "server information" msgstr "" @@ -3744,6 +3702,10 @@ msgid "update_permission" msgstr "can be updated by" +msgctxt "CWAttribute" +msgid "update_permission" +msgstr "can be updated by" + msgctxt "CWGroup" msgid "update_permission_object" msgstr "has permission to update" @@ -3774,7 +3736,8 @@ msgid "" "use to define a transition from one or multiple states to a destination " -"states in workflow's definitions." +"states in workflow's definitions. Transition without destination state will " +"go back to the state from which we arrived to the current state." msgstr "" msgid "use_email" @@ -3847,6 +3810,9 @@ msgid "vcard" msgstr "" +msgid "versions configuration" +msgstr "" + msgid "view" msgstr "" diff -r d4ae6e751b60 -r 716ee59d64a7 i18n/es.po --- a/i18n/es.po Wed Feb 10 16:34:15 2010 +0100 +++ b/i18n/es.po Fri Feb 26 15:36:30 2010 +0100 @@ -130,6 +130,9 @@ msgid "(UNEXISTANT EID)" msgstr "" +msgid "(loading ...)" +msgstr "(Cargando ...)" + msgid "**" msgstr "0..n 0..n" @@ -199,9 +202,6 @@ msgid "Any" msgstr "Cualquiera" -msgid "Application" -msgstr "Aplicación" - msgid "Attributes" msgstr "Atributos" @@ -356,9 +356,6 @@ msgid "Entities" msgstr "Entidades" -msgid "Environment" -msgstr "Ambiente" - msgid "ExternalUri" msgstr "" @@ -383,6 +380,9 @@ msgid "Help" msgstr "" +msgid "Instance" +msgstr "" + msgid "Int" msgstr "Número entero" @@ -509,8 +509,8 @@ msgid "Relations" msgstr "Relaciones" -msgid "Request" -msgstr "Petición" +msgid "Repository" +msgstr "" #, python-format msgid "Schema %s" @@ -522,9 +522,6 @@ msgid "Search for" msgstr "Buscar" -msgid "Server" -msgstr "Servidor" - msgid "SizeConstraint" msgstr "" @@ -665,6 +662,9 @@ msgid "Used by:" msgstr "Utilizado por :" +msgid "Web server" +msgstr "" + msgid "What's new?" msgstr "Lo último en el sitio" @@ -940,21 +940,18 @@ msgid "add Bookmark bookmarked_by CWUser object" msgstr "Agregar a los favoritos " -msgid "add CWAttribute add_permission RQLExpression subject" -msgstr "" - msgid "add CWAttribute constrained_by CWConstraint subject" msgstr "Restricción" -msgid "add CWAttribute delete_permission RQLExpression subject" -msgstr "" - msgid "add CWAttribute read_permission RQLExpression subject" msgstr "" msgid "add CWAttribute relation_type CWRType object" msgstr "Definición de atributo" +msgid "add CWAttribute update_permission RQLExpression subject" +msgstr "" + msgid "add CWEType add_permission RQLExpression subject" msgstr "Expresión RQL de agregación" @@ -1057,10 +1054,6 @@ msgid "add_permission" msgstr "" -msgctxt "CWAttribute" -msgid "add_permission" -msgstr "" - msgctxt "CWRelation" msgid "add_permission" msgstr "" @@ -1106,9 +1099,6 @@ msgid "allow to set a specific workflow for an entity" msgstr "" -msgid "allowed transition from this state" -msgstr "transición autorizada desde este estado" - msgid "allowed transitions from this state" msgstr "transiciones autorizadas desde este estado" @@ -1520,6 +1510,12 @@ msgid "condition_object" msgstr "condición de" +msgid "config mode" +msgstr "" + +msgid "config type" +msgstr "" + msgid "confirm password" msgstr "Confirmar contraseña" @@ -1610,32 +1606,6 @@ msgid "copy" msgstr "Copiar" -msgid "" -"core relation giving to a group the permission to add an entity or relation " -"type" -msgstr "" -"Relación sistema que otorga a un grupo la autorización de agregar una " -"entidad o una relación" - -msgid "" -"core relation giving to a group the permission to delete an entity or " -"relation type" -msgstr "" -"Relación sistema que otorga a un grupo la autorización de eliminar una " -"entidad o relación" - -msgid "" -"core relation giving to a group the permission to read an entity or relation " -"type" -msgstr "" -"Relación sistema que otorga a un grupo la autorización de leer una entidad o " -"una relación " - -msgid "core relation giving to a group the permission to update an entity type" -msgstr "" -"Relación sistema que otorga a un grupo la autorización de actualizar una " -"entidad" - msgid "core relation indicating a user's groups" msgstr "" "Relación sistema que indica los grupos a los cuales pertenece un usuario" @@ -1708,19 +1678,15 @@ msgstr "Creación de una dirección electrónica para el usuario %(linkto)s" msgid "" -"creating RQLExpression (CWAttribute %(linkto)s add_permission RQLExpression)" +"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)" msgstr "" msgid "" -"creating RQLExpression (CWAttribute %(linkto)s delete_permission " +"creating RQLExpression (CWAttribute %(linkto)s update_permission " "RQLExpression)" msgstr "" msgid "" -"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)" -msgstr "" - -msgid "" "creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)" msgstr "" "Creación de una expresión RQL para la autorización de agregar %(linkto)s" @@ -1956,10 +1922,6 @@ msgid "delete_permission" msgstr "" -msgctxt "CWAttribute" -msgid "delete_permission" -msgstr "" - msgctxt "CWRelation" msgid "delete_permission" msgstr "" @@ -2329,6 +2291,9 @@ msgid "follow this link for more information on this %s" msgstr "" +msgid "follow this link if javascript is deactivated" +msgstr "" + msgid "for_user" msgstr "Para el usuario" @@ -2443,18 +2408,6 @@ msgid "groups" msgstr "Grupos" -msgid "groups allowed to add entities/relations of this type" -msgstr "Grupos autorizados a agregar entidades/relaciones de este tipo" - -msgid "groups allowed to delete entities/relations of this type" -msgstr "Grupos autorizados a eliminar entidades/relaciones de este tipo" - -msgid "groups allowed to read entities/relations of this type" -msgstr "Grupos autorizados a leer entidades/relaciones de este tipo" - -msgid "groups allowed to update entities of this type" -msgstr "Grupos autorizados a actualizar entidades de este tipo" - msgid "groups grant permissions to the user" msgstr "Los grupos otorgan las autorizaciones al usuario" @@ -2479,9 +2432,6 @@ msgid "hide filter form" msgstr "Esconder el filtro" -msgid "home" -msgstr "Inicio" - msgid "" "how to format date and time in the ui (\"man strftime\" for format " "description)" @@ -2634,6 +2584,9 @@ msgid "inlined" msgstr "" +msgid "instance home" +msgstr "" + msgid "instance schema" msgstr "" @@ -2705,6 +2658,9 @@ msgid "last connection date" msgstr "Ultima fecha de conexión" +msgid "last usage" +msgstr "" + msgid "last_login_time" msgstr "Ultima fecha de conexión" @@ -2757,15 +2713,9 @@ msgid "link a workflow to one or more entity type" msgstr "" -msgid "link to each item in" -msgstr "ligar hacia cada elemento en" - msgid "list" msgstr "Lista" -msgid "loading" -msgstr "Cargando" - msgid "log in" msgstr "Identificarse" @@ -2970,6 +2920,9 @@ msgid "no related project" msgstr "no hay proyecto relacionado" +msgid "no repository sessions found" +msgstr "" + msgid "no selected entities" msgstr "no hay entidades seleccionadas" @@ -2980,6 +2933,9 @@ msgid "no version information" msgstr "no información de version" +msgid "no web sessions found" +msgstr "" + msgid "normal" msgstr "" @@ -3016,6 +2972,12 @@ msgid "open all" msgstr "abrir todos" +msgid "opened sessions" +msgstr "" + +msgid "opened web sessions" +msgstr "" + msgid "order" msgstr "orden" @@ -3263,6 +3225,9 @@ msgid "required field" msgstr "Campo requerido" +msgid "resources usage" +msgstr "" + msgid "" "restriction part of a rql query. For entity rql expression, X and U are " "predefined respectivly to the current object and to the request user. For " @@ -3280,18 +3245,6 @@ msgid "right" msgstr "Derecha" -msgid "rql expression allowing to add entities/relations of this type" -msgstr "expresion RQL permitiendo agregar entidades/relaciones de este tipo" - -msgid "rql expression allowing to delete entities/relations of this type" -msgstr "expresion RQL permitiendo eliminar entidades/relaciones de este tipo" - -msgid "rql expression allowing to read entities/relations of this type" -msgstr "expresion RQL permitiendo leer entidades/relaciones de este tipo" - -msgid "rql expression allowing to update entities of this type" -msgstr "expresion RQL permitiendo actualizar entidades de este tipo" - msgid "rql expressions" msgstr "expresiones rql" @@ -3385,9 +3338,6 @@ msgid "september" msgstr "septiembre" -msgid "server debug information" -msgstr "server debug information" - msgid "server information" msgstr "server information" @@ -3833,6 +3783,10 @@ msgid "update_permission" msgstr "" +msgctxt "CWAttribute" +msgid "update_permission" +msgstr "" + msgctxt "CWGroup" msgid "update_permission_object" msgstr "" @@ -3863,10 +3817,9 @@ msgid "" "use to define a transition from one or multiple states to a destination " -"states in workflow's definitions." -msgstr "" -"utilizado para definir una transición desde uno o multiples estados hacia " -"uno o varios estados destino en las definiciones del workflow" +"states in workflow's definitions. Transition without destination state will " +"go back to the state from which we arrived to the current state." +msgstr "" msgid "use_email" msgstr "correo electrónico" @@ -3944,6 +3897,9 @@ msgid "vcard" msgstr "vcard" +msgid "versions configuration" +msgstr "" + msgid "view" msgstr "ver" @@ -4063,12 +4019,24 @@ #~ msgid "%s results matching query" #~ msgstr "%s resultados de la demanda" +#~ msgid "Application" +#~ msgstr "Aplicación" + #~ msgid "Debug level set to %s" #~ msgstr "Nivel de debug puesto a %s" +#~ msgid "Environment" +#~ msgstr "Ambiente" + #~ msgid "No query has been executed" #~ msgstr "Ninguna búsqueda ha sido ejecutada" +#~ msgid "Request" +#~ msgstr "Petición" + +#~ msgid "Server" +#~ msgstr "Servidor" + #~ msgid "There is no workflow defined for this entity." #~ msgstr "No hay workflow para este entidad" @@ -4164,6 +4132,9 @@ #~ "Relación agregada %(rtype)s de %(frometype)s #%(fromeid)s hacia %(toetype)" #~ "s #%(toeid)s" +#~ msgid "allowed transition from this state" +#~ msgstr "transición autorizada desde este estado" + #~ msgid "button_reset" #~ msgstr "Cancelar los cambios" @@ -4177,6 +4148,33 @@ #~ msgstr "Edición de una copia" #~ msgid "" +#~ "core relation giving to a group the permission to add an entity or " +#~ "relation type" +#~ msgstr "" +#~ "Relación sistema que otorga a un grupo la autorización de agregar una " +#~ "entidad o una relación" + +#~ msgid "" +#~ "core relation giving to a group the permission to delete an entity or " +#~ "relation type" +#~ msgstr "" +#~ "Relación sistema que otorga a un grupo la autorización de eliminar una " +#~ "entidad o relación" + +#~ msgid "" +#~ "core relation giving to a group the permission to read an entity or " +#~ "relation type" +#~ msgstr "" +#~ "Relación sistema que otorga a un grupo la autorización de leer una " +#~ "entidad o una relación " + +#~ msgid "" +#~ "core relation giving to a group the permission to update an entity type" +#~ msgstr "" +#~ "Relación sistema que otorga a un grupo la autorización de actualizar una " +#~ "entidad" + +#~ msgid "" #~ "creating RQLExpression (CWRType %(linkto)s add_permission RQLExpression)" #~ msgstr "" #~ "Creación de una expresión RQL para la autorización de agregar relaciones %" @@ -4229,6 +4227,21 @@ #~ msgid "entity types which may use this transition" #~ msgstr "Entidades que pueden utilizar esta transición" +#~ msgid "groups allowed to add entities/relations of this type" +#~ msgstr "Grupos autorizados a agregar entidades/relaciones de este tipo" + +#~ msgid "groups allowed to delete entities/relations of this type" +#~ msgstr "Grupos autorizados a eliminar entidades/relaciones de este tipo" + +#~ msgid "groups allowed to read entities/relations of this type" +#~ msgstr "Grupos autorizados a leer entidades/relaciones de este tipo" + +#~ msgid "groups allowed to update entities of this type" +#~ msgstr "Grupos autorizados a actualizar entidades de este tipo" + +#~ msgid "home" +#~ msgstr "Inicio" + #~ msgid "initial state for entities of this type" #~ msgstr "Estado inicial para las entidades de este tipo" @@ -4241,6 +4254,12 @@ #~ msgid "link a transition to one or more entity type" #~ msgstr "liga una transición a una o mas tipos de entidad" +#~ msgid "link to each item in" +#~ msgstr "ligar hacia cada elemento en" + +#~ msgid "loading" +#~ msgstr "Cargando" + #~ msgid "nothing to edit" #~ msgstr "nada que editar" @@ -4295,6 +4314,29 @@ #~ msgid "remove this Transition" #~ msgstr "Eliminar esta transición" +#~ msgid "rql expression allowing to add entities/relations of this type" +#~ msgstr "expresion RQL permitiendo agregar entidades/relaciones de este tipo" + +#~ msgid "rql expression allowing to delete entities/relations of this type" +#~ msgstr "" +#~ "expresion RQL permitiendo eliminar entidades/relaciones de este tipo" + +#~ msgid "rql expression allowing to read entities/relations of this type" +#~ msgstr "expresion RQL permitiendo leer entidades/relaciones de este tipo" + +#~ msgid "rql expression allowing to update entities of this type" +#~ msgstr "expresion RQL permitiendo actualizar entidades de este tipo" + +#~ msgid "server debug information" +#~ msgstr "server debug information" + +#~ msgid "" +#~ "use to define a transition from one or multiple states to a destination " +#~ "states in workflow's definitions." +#~ msgstr "" +#~ "utilizado para definir una transición desde uno o multiples estados hacia " +#~ "uno o varios estados destino en las definiciones del workflow" + #~ msgid "" #~ "user for which this property is applying. If this relation is not set, " #~ "the property is considered as a global property" diff -r d4ae6e751b60 -r 716ee59d64a7 i18n/fr.po --- a/i18n/fr.po Wed Feb 10 16:34:15 2010 +0100 +++ b/i18n/fr.po Fri Feb 26 15:36:30 2010 +0100 @@ -130,6 +130,9 @@ msgid "(UNEXISTANT EID)" msgstr "(EID INTROUVABLE)" +msgid "(loading ...)" +msgstr "(chargement ...)" + msgid "**" msgstr "0..n 0..n" @@ -198,9 +201,6 @@ msgid "Any" msgstr "N'importe" -msgid "Application" -msgstr "Application" - msgid "Attributes" msgstr "Attributs" @@ -355,9 +355,6 @@ msgid "Entities" msgstr "entités" -msgid "Environment" -msgstr "Environement" - msgid "ExternalUri" msgstr "Uri externe" @@ -382,6 +379,9 @@ msgid "Help" msgstr "Aide" +msgid "Instance" +msgstr "Instance" + msgid "Int" msgstr "Nombre entier" @@ -508,8 +508,8 @@ msgid "Relations" msgstr "Relations" -msgid "Request" -msgstr "Requête" +msgid "Repository" +msgstr "Entrepôt de données" #, python-format msgid "Schema %s" @@ -521,9 +521,6 @@ msgid "Search for" msgstr "Rechercher" -msgid "Server" -msgstr "Serveur" - msgid "SizeConstraint" msgstr "contrainte de taille" @@ -664,6 +661,9 @@ msgid "Used by:" msgstr "Utilisé par :" +msgid "Web server" +msgstr "Serveur web" + msgid "What's new?" msgstr "Nouveautés" @@ -945,21 +945,18 @@ msgid "add Bookmark bookmarked_by CWUser object" msgstr "signet" -msgid "add CWAttribute add_permission RQLExpression subject" -msgstr "expression rql d'ajout" - msgid "add CWAttribute constrained_by CWConstraint subject" msgstr "contrainte" -msgid "add CWAttribute delete_permission RQLExpression subject" -msgstr "expression rql de suppression" - msgid "add CWAttribute read_permission RQLExpression subject" msgstr "expression rql de lecture" msgid "add CWAttribute relation_type CWRType object" msgstr "définition d'attribut" +msgid "add CWAttribute update_permission RQLExpression subject" +msgstr "permission de mise à jour" + msgid "add CWEType add_permission RQLExpression subject" msgstr "définir une expression RQL d'ajout" @@ -1062,10 +1059,6 @@ msgid "add_permission" msgstr "permission d'ajout" -msgctxt "CWAttribute" -msgid "add_permission" -msgstr "permission d'ajout" - msgctxt "CWRelation" msgid "add_permission" msgstr "permission d'ajout" @@ -1113,9 +1106,6 @@ msgid "allow to set a specific workflow for an entity" msgstr "permet de spécifier un workflow donné pour une entité" -msgid "allowed transition from this state" -msgstr "transition autorisée depuis cet état" - msgid "allowed transitions from this state" msgstr "transitions autorisées depuis cet état" @@ -1528,6 +1518,12 @@ msgid "condition_object" msgstr "condition de" +msgid "config mode" +msgstr "mode de configuration" + +msgid "config type" +msgstr "type de configuration" + msgid "confirm password" msgstr "confirmer le mot de passe" @@ -1619,32 +1615,6 @@ msgid "copy" msgstr "copier" -msgid "" -"core relation giving to a group the permission to add an entity or relation " -"type" -msgstr "" -"relation système donnant à un groupe la permission d'ajouter une entité ou " -"une relation" - -msgid "" -"core relation giving to a group the permission to delete an entity or " -"relation type" -msgstr "" -"relation système donnant à un group la permission de supprimer une entité ou " -"une relation" - -msgid "" -"core relation giving to a group the permission to read an entity or relation " -"type" -msgstr "" -"relation système donnant à un group la permission de lire une entité ou une " -"relation" - -msgid "core relation giving to a group the permission to update an entity type" -msgstr "" -"relation système donnant à un groupe la permission de mettre à jour une " -"entityé" - msgid "core relation indicating a user's groups" msgstr "" "relation système indiquant les groupes auxquels appartient l'utilisateur" @@ -1717,18 +1687,14 @@ msgstr "création d'une adresse électronique pour l'utilisateur %(linkto)s" msgid "" -"creating RQLExpression (CWAttribute %(linkto)s add_permission RQLExpression)" -msgstr "création d'une expression rql pour le droit d'ajout de %(linkto)s" +"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)" +msgstr "création d'une expression rql pour le droit de lecture de %(linkto)s" msgid "" -"creating RQLExpression (CWAttribute %(linkto)s delete_permission " +"creating RQLExpression (CWAttribute %(linkto)s update_permission " "RQLExpression)" msgstr "" -"création d'une expression rql pour le droit de suppression de %(linkto)s" - -msgid "" -"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)" -msgstr "création d'une expression rql pour le droit de lecture de %(linkto)s" +"création d'une expression rql pour le droit de mise à jour de %(linkto)s" msgid "" "creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)" @@ -1928,8 +1894,8 @@ msgid "define a relation type, used to build the instance schema" msgstr "définit un type de relation" -msgid "define a rql expression used to define vpermissions" -msgstr "" +msgid "define a rql expression used to define permissions" +msgstr "définit une expression rql donnant une permission" msgid "define a schema constraint" msgstr "définit une contrainte de schema" @@ -1972,10 +1938,6 @@ msgid "delete_permission" msgstr "permission de supprimer" -msgctxt "CWAttribute" -msgid "delete_permission" -msgstr "permission de supprimer" - msgctxt "CWRelation" msgid "delete_permission" msgstr "permission de supprimer" @@ -2351,6 +2313,9 @@ msgid "follow this link for more information on this %s" msgstr "suivez ce lien pour plus d'information sur ce %s" +msgid "follow this link if javascript is deactivated" +msgstr "" + msgid "for_user" msgstr "pour l'utilisateur" @@ -2468,18 +2433,6 @@ msgid "groups" msgstr "groupes" -msgid "groups allowed to add entities/relations of this type" -msgstr "groupes autorisés à ajouter des entités/relations de ce type" - -msgid "groups allowed to delete entities/relations of this type" -msgstr "groupes autorisés à supprimer des entités/relations de ce type" - -msgid "groups allowed to read entities/relations of this type" -msgstr "groupes autorisés à lire des entités/relations de ce type" - -msgid "groups allowed to update entities of this type" -msgstr "groupes autorisés à mettre à jour les entités de ce type" - msgid "groups grant permissions to the user" msgstr "les groupes donnent des permissions à l'utilisateur" @@ -2504,9 +2457,6 @@ msgid "hide filter form" msgstr "cacher le filtre" -msgid "home" -msgstr "maison" - msgid "" "how to format date and time in the ui (\"man strftime\" for format " "description)" @@ -2659,6 +2609,9 @@ msgid "inlined" msgstr "mise en ligne" +msgid "instance home" +msgstr "répertoire de l'instance" + msgid "instance schema" msgstr "schéma de l'instance" @@ -2731,6 +2684,9 @@ msgid "last connection date" msgstr "dernière date de connexion" +msgid "last usage" +msgstr "dernier usage" + msgid "last_login_time" msgstr "dernière date de connexion" @@ -2783,15 +2739,9 @@ msgid "link a workflow to one or more entity type" msgstr "lie un workflow à un ou plusieurs types d'entité" -msgid "link to each item in" -msgstr "lier vers chaque élément dans" - msgid "list" msgstr "liste" -msgid "loading" -msgstr "chargement" - msgid "log in" msgstr "s'identifier" @@ -2992,6 +2942,9 @@ msgid "no related project" msgstr "pas de projet rattaché" +msgid "no repository sessions found" +msgstr "aucune session trouvée" + msgid "no selected entities" msgstr "pas d'entité sélectionnée" @@ -3002,6 +2955,9 @@ msgid "no version information" msgstr "pas d'information de version" +msgid "no web sessions found" +msgstr "aucune session trouvée" + msgid "normal" msgstr "normal" @@ -3038,6 +2994,12 @@ msgid "open all" msgstr "tout ouvrir" +msgid "opened sessions" +msgstr "sessions ouvertes" + +msgid "opened web sessions" +msgstr "sessions web ouvertes" + msgid "order" msgstr "ordre" @@ -3287,6 +3249,9 @@ msgid "required field" msgstr "champ requis" +msgid "resources usage" +msgstr "resources utilisées" + msgid "" "restriction part of a rql query. For entity rql expression, X and U are " "predefined respectivly to the current object and to the request user. For " @@ -3305,22 +3270,6 @@ msgid "right" msgstr "droite" -msgid "rql expression allowing to add entities/relations of this type" -msgstr "" -"expression RQL donnant le droit d'ajouter des entités/relations de ce type" - -msgid "rql expression allowing to delete entities/relations of this type" -msgstr "" -"expression RQL donnant le droit de supprimer des entités/relations de ce type" - -msgid "rql expression allowing to read entities/relations of this type" -msgstr "" -"expression RQL donnant le droit de lire des entités/relations de ce type" - -msgid "rql expression allowing to update entities of this type" -msgstr "" -"expression RQL donnant le droit de modifier des entités/relations de ce type" - msgid "rql expressions" msgstr "conditions rql" @@ -3414,9 +3363,6 @@ msgid "september" msgstr "septembre" -msgid "server debug information" -msgstr "informations de déboguage serveur" - msgid "server information" msgstr "informations serveur" @@ -3866,6 +3812,10 @@ msgid "update_permission" msgstr "permission de modifier" +msgctxt "CWAttribute" +msgid "update_permission" +msgstr "permission de modifier" + msgctxt "CWGroup" msgid "update_permission_object" msgstr "peut modifier" @@ -3896,10 +3846,12 @@ msgid "" "use to define a transition from one or multiple states to a destination " -"states in workflow's definitions." +"states in workflow's definitions. Transition without destination state will " +"go back to the state from which we arrived to the current state." msgstr "" -"utiliser dans une définition de processus pour ajouter une transition depuis " -"un ou plusieurs états vers un état de destination." +"utilisé dans une définition de processus pour ajouter une transition depuis " +"un ou plusieurs états vers un état de destination. Une transition sans état " +"de destination retournera à l'état précédent l'état courant." msgid "use_email" msgstr "adresse électronique" @@ -3975,6 +3927,9 @@ msgid "vcard" msgstr "vcard" +msgid "versions configuration" +msgstr "configuration de version" + msgid "view" msgstr "voir" @@ -4072,7 +4027,7 @@ #, python-format msgid "wrong query parameter line %s" -msgstr "" +msgstr "mauvais paramètre de requête ligne %s" msgid "xbel" msgstr "xbel" diff -r d4ae6e751b60 -r 716ee59d64a7 mail.py --- a/mail.py Wed Feb 10 16:34:15 2010 +0100 +++ b/mail.py Fri Feb 26 15:36:30 2010 +0100 @@ -8,7 +8,6 @@ __docformat__ = "restructuredtext en" from base64 import b64encode, b64decode -from itertools import repeat from time import time from email.MIMEMultipart import MIMEMultipart from email.MIMEText import MIMEText @@ -200,7 +199,8 @@ continue except Exception, ex: # shouldn't make the whole transaction fail because of rendering - # error (unauthorized or such) + # error (unauthorized or such) XXX check it doesn't actually + # occurs due to rollback on such error self.exception(str(ex)) continue msg = format_mail(self.user_data, [emailaddr], content, subject, diff -r d4ae6e751b60 -r 716ee59d64a7 migration.py --- a/migration.py Wed Feb 10 16:34:15 2010 +0100 +++ b/migration.py Fri Feb 26 15:36:30 2010 +0100 @@ -70,8 +70,11 @@ ability to show the script's content """ while True: - answer = ASK.ask('Execute %r ?' % scriptpath, ('Y','n','show'), 'Y') - if answer == 'n': + answer = ASK.ask('Execute %r ?' % scriptpath, + ('Y','n','show','abort'), 'Y') + if answer == 'abort': + raise SystemExit(1) + elif answer == 'n': return False elif answer == 'show': stream = open(scriptpath) @@ -190,7 +193,7 @@ if `retry` is true the r[etry] answer may return 2 """ - possibleanswers = ['y','n'] + possibleanswers = ['y', 'n'] if abort: possibleanswers.append('abort') if shell: diff -r d4ae6e751b60 -r 716ee59d64a7 misc/migration/3.6.1_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/migration/3.6.1_Any.py Fri Feb 26 15:36:30 2010 +0100 @@ -0,0 +1,2 @@ +sync_schema_props_perms(syncprops=False) +sync_schema_props_perms('destination_state', syncperms=False) diff -r d4ae6e751b60 -r 716ee59d64a7 misc/migration/bootstrapmigration_repository.py --- a/misc/migration/bootstrapmigration_repository.py Wed Feb 10 16:34:15 2010 +0100 +++ b/misc/migration/bootstrapmigration_repository.py Fri Feb 26 15:36:30 2010 +0100 @@ -10,24 +10,35 @@ applcubicwebversion, cubicwebversion = versions_map['cubicweb'] -if applcubicwebversion < (3, 6, 0) and cubicwebversion >= (3, 6, 0): - from cubicweb.server import schemaserial as ss +from cubicweb.server import schemaserial as ss +def _add_relation_definition_no_perms(subjtype, rtype, objtype): + rschema = fsschema.rschema(rtype) + for query, args in ss.rdef2rql(rschema, subjtype, objtype, groupmap=None): + rql(query, args, ask_confirm=False) + commit(ask_confirm=False) + +if applcubicwebversion == (3, 6, 0) and cubicwebversion >= (3, 6, 0): + _add_relation_definition_no_perms('CWAttribute', 'update_permission', 'CWGroup') + _add_relation_definition_no_perms('CWAttribute', 'update_permission', 'RQLExpression') + session.set_pool() + session.unsafe_execute('SET X update_permission Y WHERE X is CWAttribute, X add_permission Y') + drop_relation_definition('CWAttribute', 'add_permission', 'CWGroup') + drop_relation_definition('CWAttribute', 'add_permission', 'RQLExpression') + drop_relation_definition('CWAttribute', 'delete_permission', 'CWGroup') + drop_relation_definition('CWAttribute', 'delete_permission', 'RQLExpression') + +elif applcubicwebversion < (3, 6, 0) and cubicwebversion >= (3, 6, 0): session.set_pool() session.execute = session.unsafe_execute permsdict = ss.deserialize_ertype_permissions(session) - def _add_relation_definition_no_perms(subjtype, rtype, objtype): - rschema = fsschema.rschema(rtype) - for query, args in ss.rdef2rql(rschema, subjtype, objtype, groupmap=None): - rql(query, args, ask_confirm=False) - commit(ask_confirm=False) config.disabled_hooks_categories.add('integrity') for rschema in repo.schema.relations(): rpermsdict = permsdict.get(rschema.eid, {}) for rdef in rschema.rdefs.values(): - for action in ('read', 'add', 'delete'): + for action in rdef.ACTIONS: actperms = [] - for something in rpermsdict.get(action, ()): + for something in rpermsdict.get(action == 'update' and 'add' or action, ()): if isinstance(something, tuple): actperms.append(rdef.rql_expression(*something)) else: # group name @@ -36,18 +47,32 @@ for action in ('read', 'add', 'delete'): _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'CWGroup') _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'RQLExpression') + for action in ('read', 'update'): _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'CWGroup') _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'RQLExpression') for action in ('read', 'add', 'delete'): - rql('SET X %s_permission Y WHERE X is IN (CWAttribute, CWRelation), ' + rql('SET X %s_permission Y WHERE X is CWRelation, ' 'RT %s_permission Y, X relation_type RT, Y is CWGroup' % (action, action)) rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, ' - 'X %s_permission Y WHERE X is IN (CWAttribute, CWRelation), ' + 'X %s_permission Y WHERE X is CWRelation, ' 'X relation_type RT, RT %s_permission Y2, Y2 exprtype YET, ' 'Y2 mainvars YMV, Y2 expression YEX' % (action, action)) + rql('SET X read_permission Y WHERE X is CWAttribute, ' + 'RT read_permission Y, X relation_type RT, Y is CWGroup') + rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, ' + 'X read_permission Y WHERE X is CWAttribute, ' + 'X relation_type RT, RT read_permission Y2, Y2 exprtype YET, ' + 'Y2 mainvars YMV, Y2 expression YEX') + rql('SET X update_permission Y WHERE X is CWAttribute, ' + 'RT add_permission Y, X relation_type RT, Y is CWGroup') + rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, ' + 'X update_permission Y WHERE X is CWAttribute, ' + 'X relation_type RT, RT add_permission Y2, Y2 exprtype YET, ' + 'Y2 mainvars YMV, Y2 expression YEX') + for action in ('read', 'add', 'delete'): drop_relation_definition('CWRType', '%s_permission' % action, 'CWGroup', commit=False) drop_relation_definition('CWRType', '%s_permission' % action, 'RQLExpression') - config.disabled_hooks_categories.add('integrity') + config.disabled_hooks_categories.remove('integrity') if applcubicwebversion < (3, 4, 0) and cubicwebversion >= (3, 4, 0): diff -r d4ae6e751b60 -r 716ee59d64a7 misc/migration/postcreate.py --- a/misc/migration/postcreate.py Wed Feb 10 16:34:15 2010 +0100 +++ b/misc/migration/postcreate.py Fri Feb 26 15:36:30 2010 +0100 @@ -5,7 +5,6 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ - # insert versions create_entity('CWProperty', pkey=u'system.version.cubicweb', value=unicode(config.cubicweb_version())) @@ -38,9 +37,8 @@ print 'you are using a manager account as anonymous user.' print 'Hopefully this is not a production instance...' elif anonlogin: - rql('INSERT CWUser X: X login %(login)s, X upassword %(pwd)s,' - 'X in_group G WHERE G name "guests"', - {'login': unicode(anonlogin), 'pwd': anonpwd}) + from cubicweb.server import create_user + create_user(session, unicode(anonlogin), anonpwd, 'guests') # need this since we already have at least one user in the database (the default admin) for user in rql('Any X WHERE X is CWUser').entities(): diff -r d4ae6e751b60 -r 716ee59d64a7 mixins.py --- a/mixins.py Wed Feb 10 16:34:15 2010 +0100 +++ b/mixins.py Fri Feb 26 15:36:30 2010 +0100 @@ -10,10 +10,8 @@ from itertools import chain -from logilab.common.deprecation import deprecated from logilab.common.decorators import cached -from cubicweb import typed_eid from cubicweb.selectors import implements from cubicweb.interfaces import IEmailable, ITree diff -r d4ae6e751b60 -r 716ee59d64a7 rqlrewrite.py --- a/rqlrewrite.py Wed Feb 10 16:34:15 2010 +0100 +++ b/rqlrewrite.py Fri Feb 26 15:36:30 2010 +0100 @@ -12,7 +12,6 @@ from rql import nodes as n, stmts, TypeResolverException -from logilab.common.compat import any from logilab.common.graph import has_path from cubicweb import Unauthorized, typed_eid @@ -110,7 +109,10 @@ return newsolutions -class Unsupported(Exception): pass +class Unsupported(Exception): + """raised when an rql expression can't be inserted in some rql query + because it create an unresolvable query (eg no solutions found) + """ class RQLRewriter(object): @@ -291,7 +293,7 @@ def snippet_subquery(self, varmap, transformedsnippet): """introduce the given snippet in a subquery""" subselect = stmts.Select() - selectvar, snippetvar = varmap + selectvar = varmap[0] subselect.append_selected(n.VariableRef( subselect.get_variable(selectvar))) aliases = [selectvar] @@ -408,7 +410,7 @@ cardindex = 1 ttypes_func = rschema.subjects rdef = lambda x, y: rschema.rdef(y, x) - except KeyError, ex: + except KeyError: # may be raised by self.varinfo['xhs_rels'][sniprel.r_type] return None # can't share neged relation or relations with different outer join @@ -441,9 +443,9 @@ while argname in self.kwargs: argname = select.allocate_varname() # insert "U eid %(u)s" - var = select.get_variable(self.u_varname) - select.add_constant_restriction(select.get_variable(self.u_varname), - 'eid', unicode(argname), 'Substitute') + select.add_constant_restriction( + select.get_variable(self.u_varname), + 'eid', unicode(argname), 'Substitute') self.kwargs[argname] = self.session.user.eid return self.u_varname key = (self.current_expr, self.varmap, vname) diff -r d4ae6e751b60 -r 716ee59d64a7 schema.py --- a/schema.py Wed Feb 10 16:34:15 2010 +0100 +++ b/schema.py Fri Feb 26 15:36:30 2010 +0100 @@ -15,15 +15,14 @@ from logilab.common.decorators import cached, clear_cache, monkeypatch from logilab.common.logging_ext import set_log_methods -from logilab.common.deprecation import deprecated +from logilab.common.deprecation import deprecated, class_moved from logilab.common.graph import get_cycles from logilab.common.compat import any from yams import BadSchemaDefinition, buildobjs as ybo from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \ RelationDefinitionSchema, PermissionMixIn -from yams.constraints import (BaseConstraint, StaticVocabularyConstraint, - FormatConstraint) +from yams.constraints import BaseConstraint, FormatConstraint from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader, obsolete as yobsolete, cleanup_sys_modules) @@ -254,7 +253,9 @@ return # if 'owners' in allowed groups, check if the user actually owns this # object, if so that's enough - if 'owners' in groups and 'eid' in kwargs and session.user.owns(kwargs['eid']): + if 'owners' in groups and ( + kwargs.get('creating') + or ('eid' in kwargs and session.user.owns(kwargs['eid']))): return # else if there is some rql expressions, check them if any(rqlexpr.check(session, **kwargs) @@ -283,22 +284,10 @@ isinstance(group_or_rqlexpr, RQLExpression): msg = "can't use rql expression for read permission of %s" raise BadSchemaDefinition(msg % self) - elif self.final and isinstance(group_or_rqlexpr, RRQLExpression): - if schema.reading_from_database: - # we didn't have final relation earlier, so turn - # RRQLExpression into ERQLExpression now - rqlexpr = group_or_rqlexpr - newrqlexprs = [x for x in self.get_rqlexprs(action) - if not x is rqlexpr] - newrqlexprs.append(ERQLExpression(rqlexpr.expression, - rqlexpr.mainvars, - rqlexpr.eid)) - self.set_rqlexprs(action, newrqlexprs) - else: - msg = "can't use RRQLExpression on %s, use an ERQLExpression" - raise BadSchemaDefinition(msg % self) - elif not self.final and \ - isinstance(group_or_rqlexpr, ERQLExpression): + if self.final and isinstance(group_or_rqlexpr, RRQLExpression): + msg = "can't use RRQLExpression on %s, use an ERQLExpression" + raise BadSchemaDefinition(msg % self) + if not self.final and isinstance(group_or_rqlexpr, ERQLExpression): msg = "can't use ERQLExpression on %s, use a RRQLExpression" raise BadSchemaDefinition(msg % self) RelationDefinitionSchema.check_permission_definitions = check_permission_definitions @@ -314,13 +303,14 @@ if eid is None and edef is not None: eid = getattr(edef, 'eid', None) self.eid = eid - # take care: no _groups attribute when deep-copying - if getattr(self, 'permissions', None): - for groups in self.permissions.itervalues(): - for group_or_rqlexpr in groups: - if isinstance(group_or_rqlexpr, RRQLExpression): - msg = "can't use RRQLExpression on an entity type, use an ERQLExpression (%s)" - raise BadSchemaDefinition(msg % self.type) + + def check_permission_definitions(self): + super(CubicWebEntitySchema, self).check_permission_definitions() + for groups in self.permissions.itervalues(): + for group_or_rqlexpr in groups: + if isinstance(group_or_rqlexpr, RRQLExpression): + msg = "can't use RRQLExpression on %s, use an ERQLExpression" + raise BadSchemaDefinition(msg % self.type) def attribute_definitions(self): """return an iterator on attribute definitions @@ -426,14 +416,24 @@ def has_perm(self, session, action, **kwargs): """return true if the action is granted globaly or localy""" - if 'fromeid' in kwargs: - subjtype = session.describe(kwargs['fromeid'])[0] + if self.final: + assert not ('fromeid' in kwargs or 'toeid' in kwargs), kwargs + assert action in ('read', 'update') + if 'eid' in kwargs: + subjtype = session.describe(kwargs['eid'])[0] + else: + subjtype = objtype = None else: - subjtype = None - if 'toeid' in kwargs: - objtype = session.describe(kwargs['toeid'])[0] - else: - objtype = None + assert not 'eid' in kwargs, kwargs + assert action in ('read', 'add', 'delete') + if 'fromeid' in kwargs: + subjtype = session.describe(kwargs['fromeid'])[0] + else: + subjtype = None + if 'toeid' in kwargs: + objtype = session.describe(kwargs['toeid'])[0] + else: + objtype = None if objtype and subjtype: return self.rdef(subjtype, objtype).has_perm(session, action, **kwargs) elif subjtype: @@ -461,7 +461,6 @@ """set of entities and relations schema defining the possible data sets used in an application - :type name: str :ivar name: name of the schema, usually the instance identifier @@ -766,7 +765,7 @@ rqlst.add_selected(objvar) else: colindex = selected.index(objvar.name) - found.append((action, objvar, colindex)) + found.append((action, colindex)) # remove U eid %(u)s if U is not used in any other relation uvrefs = rqlst.defined_vars['U'].references() if len(uvrefs) == 1: @@ -788,20 +787,25 @@ session may actually be a request as well """ - if self.eid is not None: + creating = kwargs.get('creating') + if not creating and self.eid is not None: key = (self.eid, tuple(sorted(kwargs.iteritems()))) try: return session.local_perm_cache[key] except KeyError: pass rql, has_perm_defs, keyarg = self.transform_has_permission() + if creating: + # when creating an entity, consider has_*_permission satisfied + if has_perm_defs: + return True + return False if keyarg is None: # on the server side, use unsafe_execute, but this is not available # on the client side (session is actually a request) execute = getattr(session, 'unsafe_execute', session.execute) - # XXX what if 'u' in kwargs + kwargs.setdefault('u', session.user.eid) cachekey = kwargs.keys() - kwargs['u'] = session.user.eid try: rset = execute(rql, kwargs, cachekey, build_descr=True) except NotImplementedError: @@ -829,7 +833,7 @@ # check every special has_*_permission relation is satisfied get_eschema = session.vreg.schema.eschema try: - for eaction, var, col in has_perm_defs: + for eaction, col in has_perm_defs: for i in xrange(len(rset)): eschema = get_eschema(rset.description[i][col]) eschema.check_perm(session, eaction, eid=rset[i][col]) @@ -865,12 +869,15 @@ rql += ', U eid %(u)s' return rql - def check(self, session, eid=None): + def check(self, session, eid=None, creating=False, **kwargs): if 'X' in self.rqlst.defined_vars: if eid is None: + if creating: + return self._check(session, creating=True, **kwargs) return False - return self._check(session, x=eid) - return self._check(session) + assert creating == False + return self._check(session, x=eid, **kwargs) + return self._check(session, **kwargs) class RRQLExpression(RQLExpression): @@ -919,6 +926,11 @@ kwargs['o'] = toeid return self._check(session, **kwargs) +# in yams, default 'update' perm for attributes granted to managers and owners. +# Within cw, we want to default to users who may edit the entity holding the +# attribute. +ybo.DEFAULT_ATTRPERMS['update'] = ( + 'managers', ERQLExpression('U has_update_permission X')) # workflow extensions ######################################################### @@ -1078,6 +1090,12 @@ # XXX deprecated from yams.buildobjs import RichString +from yams.constraints import StaticVocabularyConstraint + +RichString = class_moved(RichString) + +StaticVocabularyConstraint = class_moved(StaticVocabularyConstraint) +FormatConstraint = class_moved(FormatConstraint) PyFileReader.context['ERQLExpression'] = yobsolete(ERQLExpression) PyFileReader.context['RRQLExpression'] = yobsolete(RRQLExpression) diff -r d4ae6e751b60 -r 716ee59d64a7 schemas/base.py --- a/schemas/base.py Wed Feb 10 16:34:15 2010 +0100 +++ b/schemas/base.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,7 +9,7 @@ _ = unicode from yams.buildobjs import (EntityType, RelationType, SubjectRelation, - String, Boolean, Datetime, Password) + String, Datetime, Password) from cubicweb.schema import (RQLConstraint, WorkflowableEntityType, ERQLExpression, RRQLExpression) from cubicweb.schemas import META_ETYPE_PERMS, META_RTYPE_PERMS @@ -170,19 +170,11 @@ """link a permission to the entity. This permission should be used in the security definition of the entity's type to be useful. """ - __permissions__ = { - 'read': ('managers', 'users', 'guests'), - 'add': ('managers',), - 'delete': ('managers',), - } + __permissions__ = META_RTYPE_PERMS class require_group(RelationType): """used to grant a permission to a group""" - __permissions__ = { - 'read': ('managers', 'users', 'guests'), - 'add': ('managers',), - 'delete': ('managers',), - } + __permissions__ = META_RTYPE_PERMS class ExternalUri(EntityType): diff -r d4ae6e751b60 -r 716ee59d64a7 schemas/bootstrap.py --- a/schemas/bootstrap.py Wed Feb 10 16:34:15 2010 +0100 +++ b/schemas/bootstrap.py Fri Feb 26 15:36:30 2010 +0100 @@ -8,8 +8,8 @@ __docformat__ = "restructuredtext en" _ = unicode -from yams.buildobjs import (EntityType, RelationType, SubjectRelation, - ObjectRelation, RichString, String, Boolean, Int) +from yams.buildobjs import (EntityType, RelationType, RelationDefinition, + SubjectRelation, RichString, String, Boolean, Int) from cubicweb.schema import RQLConstraint from cubicweb.schemas import META_ETYPE_PERMS, META_RTYPE_PERMS @@ -131,15 +131,6 @@ 'relation\'subject, object and to ' 'the request user. ')) - read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject', - description=_('rql expression allowing to read entities/relations of this type')) - add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject', - description=_('rql expression allowing to add entities/relations of this type')) - delete_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject', - description=_('rql expression allowing to delete entities/relations of this type')) - update_permission = ObjectRelation('CWEType', cardinality='*?', composite='subject', - description=_('rql expression allowing to update entities of this type')) - class CWConstraint(EntityType): """define a schema constraint""" @@ -162,16 +153,6 @@ name = String(required=True, indexed=True, internationalizable=True, unique=True, maxsize=64) - read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='**', - description=_('groups allowed to read entities/relations of this type')) - add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), - description=_('groups allowed to add entities/relations of this type')) - delete_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), - description=_('groups allowed to delete entities/relations of this type')) - update_permission = ObjectRelation('CWEType', - description=_('groups allowed to update entities of this type')) - - class CWProperty(EntityType): """used for cubicweb configuration. Once a property has been created you @@ -214,28 +195,74 @@ __permissions__ = META_RTYPE_PERMS inlined = True -class read_permission(RelationType): - """core relation giving to a group the permission to read an entity or - relation type - """ + +class read_permission_cwgroup(RelationDefinition): + """groups allowed to read entities/relations of this type""" + __permissions__ = META_RTYPE_PERMS + name = 'read_permission' + subject = ('CWEType', 'CWAttribute', 'CWRelation') + object = 'CWGroup' + cardinality = '**' + +class add_permission_cwgroup(RelationDefinition): + """groups allowed to add entities/relations of this type""" __permissions__ = META_RTYPE_PERMS + name = 'add_permission' + subject = ('CWEType', 'CWRelation') + object = 'CWGroup' + cardinality = '**' -class add_permission(RelationType): - """core relation giving to a group the permission to add an entity or - relation type - """ +class delete_permission_cwgroup(RelationDefinition): + """groups allowed to delete entities/relations of this type""" + __permissions__ = META_RTYPE_PERMS + name = 'delete_permission' + subject = ('CWEType', 'CWRelation') + object = 'CWGroup' + cardinality = '**' + +class update_permission_cwgroup(RelationDefinition): + """groups allowed to update entities/relations of this type""" __permissions__ = META_RTYPE_PERMS + name = 'update_permission' + subject = ('CWEType', 'CWAttribute') + object = 'CWGroup' + cardinality = '**' -class delete_permission(RelationType): - """core relation giving to a group the permission to delete an entity or - relation type - """ +class read_permission_rqlexpr(RelationDefinition): + """rql expression allowing to read entities/relations of this type""" + __permissions__ = META_RTYPE_PERMS + name = 'read_permission' + subject = ('CWEType', 'CWAttribute', 'CWRelation') + object = 'RQLExpression' + cardinality = '*?' + composite = 'subject' + +class add_permission_rqlexpr(RelationDefinition): + """rql expression allowing to add entities/relations of this type""" __permissions__ = META_RTYPE_PERMS + name = 'add_permission' + subject = ('CWEType', 'CWRelation') + object = 'RQLExpression' + cardinality = '*?' + composite = 'subject' -class update_permission(RelationType): - """core relation giving to a group the permission to update an entity type - """ +class delete_permission_rqlexpr(RelationDefinition): + """rql expression allowing to delete entities/relations of this type""" __permissions__ = META_RTYPE_PERMS + name = 'delete_permission' + subject = ('CWEType', 'CWRelation') + object = 'RQLExpression' + cardinality = '*?' + composite = 'subject' + +class update_permission_rqlexpr(RelationDefinition): + """rql expression allowing to update entities/relations of this type""" + __permissions__ = META_RTYPE_PERMS + name = 'update_permission' + subject = ('CWEType', 'CWAttribute') + object = 'RQLExpression' + cardinality = '*?' + composite = 'subject' class is_(RelationType): diff -r d4ae6e751b60 -r 716ee59d64a7 schemas/workflow.py --- a/schemas/workflow.py Wed Feb 10 16:34:15 2010 +0100 +++ b/schemas/workflow.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,7 +9,7 @@ _ = unicode from yams.buildobjs import (EntityType, RelationType, SubjectRelation, - ObjectRelation, RichString, String) + RichString, String) from cubicweb.schema import RQLConstraint, RQLUniqueConstraint from cubicweb.schemas import (META_ETYPE_PERMS, META_RTYPE_PERMS, HOOKS_RTYPE_PERMS) @@ -97,12 +97,13 @@ class Transition(BaseTransition): """use to define a transition from one or multiple states to a destination - states in workflow's definitions. + states in workflow's definitions. Transition without destination state will + go back to the state from which we arrived to the current state. """ __specializes_schema__ = True destination_state = SubjectRelation( - 'State', cardinality='1*', + 'State', cardinality='?*', constraints=[RQLConstraint('S transition_of WF, O state_of WF', msg=_('state and transition don\'t belong the the same workflow'))], description=_('destination state for this transition')) @@ -172,6 +173,7 @@ } inlined = True + class workflow_of(RelationType): """link a workflow to one or more entity type""" __permissions__ = META_RTYPE_PERMS @@ -186,20 +188,15 @@ __permissions__ = META_RTYPE_PERMS inlined = True -class subworkflow(RelationType): - """link a transition to one or more workflow""" +class destination_state(RelationType): + """destination state of a transition""" __permissions__ = META_RTYPE_PERMS inlined = True -class exit_point(RelationType): - """link a transition to one or more workflow""" +class allowed_transition(RelationType): + """allowed transitions from this state""" __permissions__ = META_RTYPE_PERMS -class subworkflow_state(RelationType): - """link a transition to one or more workflow""" - __permissions__ = META_RTYPE_PERMS - inlined = True - class initial_state(RelationType): """indicate which state should be used by default when an entity using states is created @@ -207,14 +204,25 @@ __permissions__ = META_RTYPE_PERMS inlined = True -class destination_state(RelationType): - """destination state of a transition""" + +class subworkflow(RelationType): __permissions__ = META_RTYPE_PERMS inlined = True -class allowed_transition(RelationType): - """allowed transitions from this state""" +class exit_point(RelationType): + __permissions__ = META_RTYPE_PERMS + +class subworkflow_state(RelationType): __permissions__ = META_RTYPE_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 ########################## diff -r d4ae6e751b60 -r 716ee59d64a7 selectors.py --- a/selectors.py Wed Feb 10 16:34:15 2010 +0100 +++ b/selectors.py Fri Feb 26 15:36:30 2010 +0100 @@ -43,7 +43,7 @@ __docformat__ = "restructuredtext en" import logging -from warnings import warn, filterwarnings +from warnings import warn from logilab.common.deprecation import class_renamed from logilab.common.compat import all, any @@ -51,8 +51,7 @@ from yams import BASE_TYPES -from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity, - role, typed_eid) +from cubicweb import Unauthorized, NoSelectableObject, NotAnEntity, role # even if not used, let yes here so it's importable through this module from cubicweb.appobject import Selector, objectify_selector, yes from cubicweb.vregistry import class_regid @@ -163,7 +162,6 @@ def score_interfaces(self, req, cls_or_inst, cls): score = 0 etypesreg = req.vreg['etypes'] - eschema = cls_or_inst.e_schema for iface in self.expected_ifaces: if isinstance(iface, basestring): # entity type @@ -528,6 +526,9 @@ return 0 return 1 + def score_class(self, eclass, req): + return 1 # necessarily true if we're there + class implements(ImplementsMixIn, EClassSelector): """Return non-zero score for entity that are of the given type(s) or diff -r d4ae6e751b60 -r 716ee59d64a7 server/__init__.py --- a/server/__init__.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/__init__.py Fri Feb 26 15:36:30 2010 +0100 @@ -93,6 +93,15 @@ # database initialization ###################################################### +def create_user(session, login, pwd, *groups): + # monkey patch this method if you want to customize admin/anon creation + # (that maybe necessary if you change CWUser's schema) + user = session.create_entity('CWUser', login=login, upassword=pwd) + for group in groups: + session.execute('SET U in_group G WHERE U eid %(u)s, G name %(group)s', + {'u': user.eid, 'group': group}) + return user + def init_repository(config, interactive=True, drop=False, vreg=None): """initialise a repository database by creating tables add filling them with the minimal set of entities (ie at least the schema, base groups and @@ -161,9 +170,7 @@ for group in sorted(BASE_GROUPS): session.execute('INSERT CWGroup X: X name %(name)s', {'name': unicode(group)}) - session.execute('INSERT CWUser X: X login %(login)s, X upassword %(pwd)s', - {'login': login, 'pwd': pwd}) - session.execute('SET U in_group G WHERE G name "managers"') + create_user(session, login, pwd, 'managers') session.commit() # reloging using the admin user config._cubes = None # avoid assertion error diff -r d4ae6e751b60 -r 716ee59d64a7 server/checkintegrity.py --- a/server/checkintegrity.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/checkintegrity.py Fri Feb 26 15:36:30 2010 +0100 @@ -64,7 +64,7 @@ else: yield eschema -def reindex_entities(schema, session): +def reindex_entities(schema, session, withpb=True): """reindex all entities in the repository""" # deactivate modification_date hook since we don't want them # to be updated due to the reindexation @@ -92,22 +92,27 @@ etypes.add(container) print 'Reindexing entities of type %s' % \ ', '.join(sorted(str(e) for e in etypes)) - pb = ProgressBar(len(etypes) + 1) + if withpb: + pb = ProgressBar(len(etypes) + 1) # first monkey patch Entity.check to disable validation from cubicweb.entity import Entity _check = Entity.check Entity.check = lambda self, creation=False: True # clear fti table first session.system_sql('DELETE FROM %s' % session.repo.system_source.dbhelper.fti_table) - pb.update() + if withpb: + pb.update() # reindex entities by generating rql queries which set all indexable # attribute to their current value for eschema in etypes: for entity in session.execute('Any X WHERE X is %s' % eschema).entities(): FTIndexEntityOp(session, entity=entity) - pb.update() + if withpb: + pb.update() # restore Entity.check Entity.check = _check + repo.config.disabled_hooks_categories.remove('metadata') + repo.config.disabled_hooks_categories.remove('integrity') def check_schema(schema, session, eids, fix=1): @@ -276,7 +281,7 @@ print >> sys.stderr -def check(repo, cnx, checks, reindex, fix): +def check(repo, cnx, checks, reindex, fix, withpb=True): """check integrity of instance's repository, using given user and password to locally connect to the repository (no running cubicweb server needed) @@ -297,5 +302,5 @@ if reindex: cnx.rollback() session.set_pool() - reindex_entities(repo.schema, session) + reindex_entities(repo.schema, session, withpb=withpb) cnx.commit() diff -r d4ae6e751b60 -r 716ee59d64a7 server/hook.py --- a/server/hook.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/hook.py Fri Feb 26 15:36:30 2010 +0100 @@ -44,7 +44,7 @@ from logilab.common.logging_ext import set_log_methods from cubicweb.cwvreg import CWRegistry, VRegistry -from cubicweb.selectors import (objectify_selector, lltrace, match_search_state, +from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector, implements) from cubicweb.appobject import AppObject @@ -134,7 +134,7 @@ return iter(chain(*self.iterators)) -class match_rtype(match_search_state): +class match_rtype(ExpectedValueSelector): """accept if parameters specified as initializer arguments are specified in named arguments given to the selector @@ -159,7 +159,7 @@ return 1 -class match_rtype_sets(match_search_state): +class match_rtype_sets(ExpectedValueSelector): """accept if parameters specified as initializer arguments are specified in named arguments given to the selector """ diff -r d4ae6e751b60 -r 716ee59d64a7 server/hookhelper.py --- a/server/hookhelper.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/hookhelper.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,7 +9,6 @@ from logilab.common.deprecation import deprecated, class_moved -from cubicweb import RepositoryError from cubicweb.server import hook @deprecated('[3.6] entity_oldnewvalue should be imported from cw.server.hook') diff -r d4ae6e751b60 -r 716ee59d64a7 server/migractions.py --- a/server/migractions.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/migractions.py Fri Feb 26 15:36:30 2010 +0100 @@ -29,12 +29,11 @@ from logilab.common.deprecation import deprecated from logilab.common.decorators import cached, clear_cache -from logilab.common.adbh import get_adv_func_helper from yams.constraints import SizeConstraint from yams.schema2sql import eschema2sql, rschema2sql -from cubicweb import AuthenticationError, ETYPE_NAME_MAP +from cubicweb import AuthenticationError from cubicweb.schema import (META_RTYPES, VIRTUAL_RTYPES, CubicWebRelationSchema, order_eschemas) from cubicweb.dbapi import get_repository, repo_connect @@ -156,7 +155,7 @@ else: bkup = tarfile.open(backupfile, 'w|gz') for filename in os.listdir(tmpdir): - bkup.add(osp.join(tmpdir,filename), filename) + bkup.add(osp.join(tmpdir, filename), filename) bkup.close() # call hooks repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp) @@ -170,7 +169,6 @@ # check if not osp.exists(backupfile): raise Exception("Backup file %s doesn't exist" % backupfile) - return if askconfirm and not self.confirm('Restore %s database from %s ?' % (self.config.appid, backupfile)): return @@ -293,9 +291,6 @@ apc = osp.join(self.config.migration_scripts_dir(), '%s.py' % event) if osp.exists(apc): if self.config.free_wheel: - from cubicweb.server.hooks import setowner_after_add_entity - self.repo.hm.unregister_hook(setowner_after_add_entity, - 'after_add_entity', '') self.cmd_deactivate_verification_hooks() self.info('executing %s', apc) confirm = self.confirm @@ -308,8 +303,6 @@ self.confirm = confirm self.execscript_confirm = execscript_confirm if self.config.free_wheel: - self.repo.hm.register_hook(setowner_after_add_entity, - 'after_add_entity', '') self.cmd_reactivate_verification_hooks() def install_custom_sql_scripts(self, directory, driver): @@ -352,7 +345,7 @@ 'T eid %%(x)s' % perm, {'x': teid}, 'x', ask_confirm=False): if not gname in newgroups: - if not confirm or self.confirm('remove %s permission of %s to %s?' + if not confirm or self.confirm('Remove %s permission of %s to %s?' % (action, erschema, gname)): self.rqlexec('DELETE T %s G WHERE G eid %%(x)s, T eid %s' % (perm, teid), @@ -360,7 +353,7 @@ else: newgroups.remove(gname) for gname in newgroups: - if not confirm or self.confirm('grant %s permission of %s to %s?' + if not confirm or self.confirm('Grant %s permission of %s to %s?' % (action, erschema, gname)): self.rqlexec('SET T %s G WHERE G eid %%(x)s, T eid %s' % (perm, teid), @@ -371,7 +364,7 @@ 'T eid %s' % (perm, teid), ask_confirm=False): if not expression in newexprs: - if not confirm or self.confirm('remove %s expression for %s permission of %s?' + if not confirm or self.confirm('Remove %s expression for %s permission of %s?' % (expression, action, erschema)): # deleting the relation will delete the expression entity self.rqlexec('DELETE T %s E WHERE E eid %%(x)s, T eid %s' @@ -381,7 +374,7 @@ newexprs.pop(expression) for expression in newexprs.values(): expr = expression.expression - if not confirm or self.confirm('add %s expression for %s permission of %s?' + if not confirm or self.confirm('Add %s expression for %s permission of %s?' % (expr, action, erschema)): self.rqlexec('INSERT RQLExpression X: X exprtype %%(exprtype)s, ' 'X expression %%(expr)s, X mainvars %%(vars)s, T %s X ' @@ -528,7 +521,7 @@ def checkpoint(self, ask_confirm=True): """checkpoint action""" - if not ask_confirm or self.confirm('commit now ?', shell=False): + if not ask_confirm or self.confirm('Commit now ?', shell=False): self.commit() def cmd_add_cube(self, cube, update_database=True): @@ -664,7 +657,7 @@ self.cmd_add_attribute(etype, newname, attrtype, commit=True) # skipp NULL values if the attribute is required rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname) - card = eschema.rproperty(newname, 'cardinality')[0] + card = eschema.rdef(newname).cardinality[0] if card == '1': rql += ', NOT X %s NULL' % oldname self.rqlexec(rql, ask_confirm=self.verbosity>=2) @@ -735,10 +728,10 @@ elif role == 'object': subjschema = tschema objschema = spschema - if (rschema.rproperty(subjschema, objschema, 'infered') + if (rschema.rdef(subjschema, objschema).infered or (instschema.has_relation(rschema) and - instschema[rschema].has_rdef(subjschema, objschema))): - continue + (subjschema, objschema) in instschema[rschema].rdefs)): + continue self.cmd_add_relation_definition( subjschema.type, rschema.type, objschema.type) if auto: @@ -936,7 +929,7 @@ if syncprops: self._synchronize_eschema(etype, syncperms=syncperms) else: - self._synchronize_permissions(self.fs_schema[etype], erschema.eid) + self._synchronize_permissions(self.fs_schema[etype], etype.eid) if commit: self.commit() @@ -973,7 +966,7 @@ you usually want to use sync_schema_props_perms instead. """ oldvalue = None - for constr in self.repo.schema.eschema(etype).constraints(rtype): + for constr in self.repo.schema.eschema(etype).rdef(rtype).constraints: if isinstance(constr, SizeConstraint): oldvalue = constr.max if oldvalue == size: @@ -1146,13 +1139,13 @@ should only be used for low level stuff undoable with existing higher level actions """ - if not ask_confirm or self.confirm('execute sql: %s ?' % sql): + if not ask_confirm or self.confirm('Execute sql: %s ?' % sql): self.session.set_pool() # ensure pool is set try: cu = self.session.system_sql(sql, args) except: ex = sys.exc_info()[1] - if self.confirm('error: %s\nabort?' % ex): + if self.confirm('Error: %s\nabort?' % ex): raise return try: @@ -1166,16 +1159,20 @@ if not isinstance(rql, (tuple, list)): rql = ( (rql, kwargs), ) res = None + try: + execute = self._cw.unsafe_execute + except AttributeError: + execute = self._cw.execute for rql, kwargs in rql: if kwargs: msg = '%s (%s)' % (rql, kwargs) else: msg = rql - if not ask_confirm or self.confirm('execute rql: %s ?' % msg): + if not ask_confirm or self.confirm('Execute rql: %s ?' % msg): try: - res = self._cw.execute(rql, kwargs, cachekey) + res = execute(rql, kwargs, cachekey) except Exception, ex: - if self.confirm('error: %s\nabort?' % ex): + if self.confirm('Error: %s\nabort?' % ex): raise return res @@ -1260,12 +1257,12 @@ else: msg = rql if self.ask_confirm: - if not self._h.confirm('execute rql: %s ?' % msg): + if not self._h.confirm('Execute rql: %s ?' % msg): raise StopIteration try: rset = self._h._cw.execute(rql, kwargs) except Exception, ex: - if self._h.confirm('error: %s\nabort?' % ex): + if self._h.confirm('Error: %s\nabort?' % ex): raise else: raise StopIteration diff -r d4ae6e751b60 -r 716ee59d64a7 server/msplanner.py --- a/server/msplanner.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/msplanner.py Fri Feb 26 15:36:30 2010 +0100 @@ -90,7 +90,6 @@ from cubicweb.server.ssplanner import (SSPlanner, OneFetchStep, add_types_restriction) from cubicweb.server.mssteps import * -from cubicweb.server.sources import AbstractSource Variable._ms_table_key = lambda x: x.name Relation._ms_table_key = lambda x: x.r_type @@ -272,7 +271,7 @@ for source in self._sourcesterms: print '-', source for term, sols in self._sourcesterms[source].items(): - print ' -', term, id(term), ':' ,sols + print ' -', term, id(term), ':', sols def copy_solutions(self, solindices): return [self._solutions[solidx].copy() for solidx in solindices] @@ -465,7 +464,7 @@ try: lhsv = self._extern_term(lhs, termssources, inserted) rhsv = self._extern_term(rhs, termssources, inserted) - except KeyError, ex: + except KeyError: continue self._remove_term_sources(lhsv, rel, rhsv, termssources) self._remove_term_sources(rhsv, rel, lhsv, termssources) @@ -684,11 +683,11 @@ # go to the next iteration directly! continue if not sourceterms: - try: - del self._sourcesterms[source] - except KeyError: - # XXX already cleaned - pass + try: + del self._sourcesterms[source] + except KeyError: + # XXX already cleaned + pass # set of terms which should be additionaly selected when # possible needsel = set() @@ -857,7 +856,6 @@ terms = [term] sources = sorted(sources) sourcesterms = self._sourcesterms - nbunlinked = 1 linkedterms = self._linkedterms # term has to belong to the same scope if there is more # than the system source remaining @@ -873,7 +871,7 @@ for source in sources: cross_rels.update(self._crossrelations.get(source, {})) exclude = {} - for rel, crossvars in cross_rels.iteritems(): + for crossvars in cross_rels.itervalues(): vars = [t for t in crossvars if isinstance(t, Variable)] try: exclude[vars[0]] = vars[1] @@ -897,7 +895,6 @@ modified = False for term in candidates[:]: if isinstance(term, Constant): - relation = term.relation() if sorted(set(x[0] for x in self._term_sources(term))) != sources: continue terms.append(term) @@ -1023,12 +1020,6 @@ if server.DEBUG & server.DBG_MS: print '-'*80 print 'PLANNING', rqlst - for select in rqlst.children: - if len(select.solutions) > 1: - hasmultiplesols = True - break - else: - hasmultiplesols = False # preprocess deals with security insertion and returns a new syntax tree # which have to be executed to fulfill the query: according # to permissions for variable's type, different rql queries may have to @@ -1036,7 +1027,7 @@ plan.preprocess(rqlst) ppis = [PartPlanInformation(plan, select, self.rqlhelper) for select in rqlst.children] - steps = self._union_plan(plan, rqlst, ppis) + steps = self._union_plan(plan, ppis) if server.DEBUG & server.DBG_MS: from pprint import pprint for step in plan.steps: @@ -1054,7 +1045,7 @@ for sppi in sppis: if sppi.needsplit or sppi.part_sources != ppi.part_sources: temptable = 'T%s' % make_uid(id(subquery)) - sstep = self._union_plan(plan, subquery.query, sppis, temptable)[0] + sstep = self._union_plan(plan, sppis, temptable)[0] break else: sstep = None @@ -1065,7 +1056,7 @@ ppi.plan.add_step(sstep) return inputmap - def _union_plan(self, plan, union, ppis, temptable=None): + def _union_plan(self, plan, ppis, temptable=None): tosplit, cango, allsources = [], {}, set() for planinfo in ppis: if planinfo.needsplit: @@ -1191,7 +1182,7 @@ for step in steps for select in step.union.children): if temptable: - step = IntersectFetchStep(plan) + step = IntersectFetchStep(plan) # XXX not implemented else: step = IntersectStep(plan) else: diff -r d4ae6e751b60 -r 716ee59d64a7 server/pool.py --- a/server/pool.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/pool.py Fri Feb 26 15:36:30 2010 +0100 @@ -87,7 +87,7 @@ # implementation details of flying insert requires the system source # first yield self.source_cnxs['system'][0] - for uri, (source, cursor) in self.source_cnxs.items(): + for uri, (source, cnx) in self.source_cnxs.items(): if uri == 'system': continue yield source @@ -109,6 +109,11 @@ else: sources = (source,) for source in sources: + try: + # properly close existing connection if any + self.source_cnxs[source.uri][1].close() + except: + pass source.info('trying to reconnect') self.source_cnxs[source.uri] = (source, source.get_connection()) self._cursors.pop(source.uri, None) diff -r d4ae6e751b60 -r 716ee59d64a7 server/querier.py --- a/server/querier.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/querier.py Fri Feb 26 15:36:30 2010 +0100 @@ -12,7 +12,7 @@ from logilab.common.cache import Cache from logilab.common.compat import any -from rql import RQLHelper, RQLSyntaxError +from rql import RQLSyntaxError from rql.stmts import Union, Select from rql.nodes import Relation, VariableRef, Constant, SubQuery @@ -26,7 +26,7 @@ READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity')) -def empty_rset(session, rql, args, rqlst=None): +def empty_rset(rql, args, rqlst=None): """build an empty result set object""" return ResultSet([], rql, args, rqlst=rqlst) @@ -210,7 +210,6 @@ self.cache_key = None def _insert_security(self, union, noinvariant): - rh = self.rqlhelper for select in union.children[:]: for subquery in select.with_: self._insert_security(subquery.query, noinvariant) @@ -572,7 +571,7 @@ """execute a rql query, return resulting rows and their description in a `ResultSet` object - * `rql` should be an unicode string or a plain ascii string + * `rql` should be an Unicode string or a plain ASCII string * `args` the optional parameters dictionary associated to the query * `build_descr` is a boolean flag indicating if the description should be built on select queries (if false, the description will be en empty @@ -580,17 +579,17 @@ * `eid_key` must be both a key in args and a substitution in the rql query. It should be used to enhance cacheability of rql queries. It may be a tuple for keys in args. - eid_key must be providen in case where a eid substitution is providen - and resolve some ambiguity in the possible solutions infered for each + `eid_key` must be provided in cases where a eid substitution is provided + and resolves ambiguities in the possible solutions inferred for each variable in the query. - on INSERT queries, there will be on row with the eid of each inserted + on INSERT queries, there will be one row with the eid of each inserted entity result for DELETE and SET queries is undefined yet to maximize the rql parsing/analyzing cache performance, you should - always use substitute arguments in queries (eg avoid query such as + always use substitute arguments in queries (i.e. avoid query such as 'Any X WHERE X eid 123'!) """ if server.DEBUG & (server.DBG_RQL | server.DBG_SQL): @@ -613,7 +612,7 @@ except UnknownEid: # we want queries such as "Any X WHERE X eid 9999" # return an empty result instead of raising UnknownEid - return empty_rset(session, rql, args) + return empty_rset(rql, args) cachekey.append(etype) # ensure eid is correctly typed in args args[key] = typed_eid(args[key]) @@ -631,7 +630,7 @@ except UnknownEid: # we want queries such as "Any X WHERE X eid 9999" # return an empty result instead of raising UnknownEid - return empty_rset(session, rql, args, rqlst) + return empty_rset(rql, args, rqlst) self._rql_cache[cachekey] = rqlst orig_rqlst = rqlst if not rqlst.TYPE == 'select': diff -r d4ae6e751b60 -r 716ee59d64a7 server/repository.py --- a/server/repository.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/repository.py Fri Feb 26 15:36:30 2010 +0100 @@ -19,7 +19,7 @@ import sys import Queue -from os.path import join, exists +from os.path import join from datetime import datetime from time import time, localtime, strftime @@ -29,7 +29,7 @@ from yams import BadSchemaDefinition from rql import RQLSyntaxError -from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, CW_EVENT_MANAGER, +from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, UnknownEid, AuthenticationError, ExecutionError, ETypeNotSupportedBySources, MultiSourcesError, BadConnectionId, Unauthorized, ValidationError, @@ -101,14 +101,12 @@ this kind of behaviour has to be done in the repository so we don't have hooks order hazardness """ - # skip delete queries (only?) if session is an internal session. This is - # hooks responsability to ensure they do not violate relation's cardinality - if session.is_super_session: + # XXX now that rql in migraction default to unsafe_execute we don't want to + # skip that for super session (though we can still skip it for internal + # sessions). Also we should imo rely on the orm to first fetch existing + # entity if any then delete it. + if session.is_internal_session: return - ensure_card_respected(session.unsafe_execute, session, eidfrom, rtype, eidto) - - -def ensure_card_respected(execute, session, eidfrom, rtype, eidto): card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality') # one may be tented to check for neweids but this may cause more than one # relation even with '1?' cardinality if thoses relations are added in the @@ -116,14 +114,28 @@ # the web interface but may occurs during test or dbapi connection (though # not expected for this). So: don't do it, we pretend to ensure repository # consistency. + # + # XXX we don't want read permissions to be applied but we want delete + # permission to be checked + rschema = session.repo.schema.rschema(rtype) if card[0] in '1?': - rschema = session.repo.schema.rschema(rtype) - if not rschema.inlined: - execute('DELETE X %s Y WHERE X eid %%(x)s,NOT Y eid %%(y)s' % rtype, - {'x': eidfrom, 'y': eidto}, 'x') + if not rschema.inlined: # inlined relations will be implicitly deleted + rset = session.unsafe_execute('Any X,Y WHERE X %s Y, X eid %%(x)s, ' + 'NOT Y eid %%(y)s' % rtype, + {'x': eidfrom, 'y': eidto}, 'x') + if rset: + safe_delete_relation(session, rschema, *rset[0]) if card[1] in '1?': - execute('DELETE X %s Y WHERE NOT X eid %%(x)s, Y eid %%(y)s' % rtype, - {'x': eidfrom, 'y': eidto}, 'y') + rset = session.unsafe_execute('Any X,Y WHERE X %s Y, Y eid %%(y)s, ' + 'NOT X eid %%(x)s' % rtype, + {'x': eidfrom, 'y': eidto}, 'y') + if rset: + safe_delete_relation(session, rschema, *rset[0]) + +def safe_delete_relation(session, rschema, subject, object): + if not rschema.has_perm(session, 'delete', fromeid=subject, toeid=object): + raise Unauthorized() + session.repo.glob_delete_relation(session, subject, rschema.type, object) class Repository(object): @@ -316,8 +328,8 @@ return self._available_pools.get(True, timeout=5) except Queue.Empty: raise Exception('no pool available after 5 secs, probably either a ' - 'bug in code (to many uncommited/rollbacked ' - 'connections) or to much load on the server (in ' + 'bug in code (too many uncommited/rollbacked ' + 'connections) or too much load on the server (in ' 'which case you can try to set a bigger ' 'connections pools size)') @@ -371,6 +383,23 @@ except ZeroDivisionError: pass + def stats(self): # XXX restrict to managers session? + import threading + results = {} + for hits, misses, title in ( + (self.querier.cache_hit, self.querier.cache_miss, 'rqlt_st'), + (self.system_source.cache_hit, self.system_source.cache_miss, 'sql'), + ): + results['%s_cache_hit' % title] = hits + results['%s_cache_miss' % title] = misses + results['%s_cache_hit_percent' % title] = (hits * 100) / (hits + misses) + results['sql_no_cache'] = self.system_source.no_cache + results['nb_open_sessions'] = len(self._sessions) + results['nb_active_threads'] = threading.activeCount() + results['looping_tasks'] = ', '.join(str(t) for t in self._looping_tasks) + results['available_pools'] = self._available_pools.qsize() + return results + def _login_from_email(self, login): session = self.internal_session() try: @@ -435,7 +464,8 @@ public method, not requiring a session id. """ versions = self.get_versions(not (self.config.creating - or self.config.repairing)) + or self.config.repairing + or self.config.mode == 'test')) cubes = list(versions) cubes.remove('cubicweb') return cubes diff -r d4ae6e751b60 -r 716ee59d64a7 server/rqlannotation.py --- a/server/rqlannotation.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/rqlannotation.py Fri Feb 26 15:36:30 2010 +0100 @@ -30,7 +30,7 @@ need_distinct = True # XXX could mark as not invariant break - for name, var in rqlst.defined_vars.items(): + for var in rqlst.defined_vars.itervalues(): stinfo = var.stinfo if stinfo.get('ftirels'): has_text_query = True @@ -130,7 +130,8 @@ -class CantSelectPrincipal(Exception): pass +class CantSelectPrincipal(Exception): + """raised when no 'principal' variable can be found""" def _select_principal(sqlscope, relations, _sort=lambda x:x): """given a list of rqlst relations, select one which will be used to @@ -139,13 +140,11 @@ """ # _sort argument is there for test diffscope_rels = {} - has_same_scope_rel = False ored_rels = set() diffscope_rels = set() for rel in _sort(relations): # note: only eid and has_text among all final relations may be there if rel.r_type in ('eid', 'identity'): - has_same_scope_rel = rel.sqlscope is sqlscope continue if rel.ored(traverse_scope=True): ored_rels.add(rel) diff -r d4ae6e751b60 -r 716ee59d64a7 server/schemaserial.py --- a/server/schemaserial.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/schemaserial.py Fri Feb 26 15:36:30 2010 +0100 @@ -8,8 +8,6 @@ __docformat__ = "restructuredtext en" import os -import sys -import os from itertools import chain from logilab.common.shellutils import ProgressBar @@ -189,13 +187,17 @@ definition dictionary as built by deserialize_ertype_permissions for a given erschema's eid """ + # reset erschema permissions here to avoid getting yams default anyway + erschema.permissions = dict((action, ()) for action in erschema.ACTIONS) try: thispermsdict = permsdict[erschema.eid] except KeyError: return - permissions = erschema.permissions for action, somethings in thispermsdict.iteritems(): - permissions[action] = tuple( + # XXX cw < 3.6.1 bw compat + if isinstance(erschema, schemamod.RelationDefinitionSchema) and erschema.final and action == 'add': + action = 'update' + erschema.permissions[action] = tuple( isinstance(p, tuple) and erschema.rql_expression(*p) or p for p in somethings) @@ -280,23 +282,19 @@ relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] return relations, values -# XXX 2.47 migration -HAS_FULLTEXT_CONTAINER = True - def rschema_relations_values(rschema): values = _ervalues(rschema) values['final'] = rschema.final values['symmetric'] = rschema.symmetric values['inlined'] = rschema.inlined - if HAS_FULLTEXT_CONTAINER: - if isinstance(rschema.fulltext_container, str): - values['fulltext_container'] = unicode(rschema.fulltext_container) - else: - values['fulltext_container'] = rschema.fulltext_container + if isinstance(rschema.fulltext_container, str): + values['fulltext_container'] = unicode(rschema.fulltext_container) + else: + values['fulltext_container'] = rschema.fulltext_container relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] return relations, values -def _rdef_values(rschema, objtype, props): +def _rdef_values(objtype, props): amap = {'order': 'ordernum'} values = {} for prop, default in schemamod.RelationDefinitionSchema.rproperty_defs(objtype).iteritems(): @@ -312,13 +310,13 @@ values[amap.get(prop, prop)] = value return values -def nfrdef_relations_values(rschema, objtype, props): - values = _rdef_values(rschema, objtype, props) +def nfrdef_relations_values(objtype, props): + values = _rdef_values(objtype, props) relations = ['X %s %%(%s)s' % (attr, attr) for attr in sorted(values)] return relations, values -def frdef_relations_values(rschema, objtype, props): - values = _rdef_values(rschema, objtype, props) +def frdef_relations_values(objtype, props): + values = _rdef_values(objtype, props) default = values['default'] del values['default'] if default is not None: @@ -431,7 +429,7 @@ _LOCATE_RDEF_RQL1 = 'SE name %(se)s,ER name %(rt)s,OE name %(oe)s' def frdef2rql(rschema, subjtype, objtype, props): - relations, values = frdef_relations_values(rschema, objtype, props) + relations, values = frdef_relations_values(objtype, props) relations.append(_LOCATE_RDEF_RQL0) values.update({'se': str(subjtype), 'rt': str(rschema), 'oe': str(objtype)}) yield 'INSERT CWAttribute X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values @@ -439,7 +437,7 @@ yield rql + ', EDEF is CWAttribute', values def nfrdef2rql(rschema, subjtype, objtype, props): - relations, values = nfrdef_relations_values(rschema, objtype, props) + relations, values = nfrdef_relations_values(objtype, props) relations.append(_LOCATE_RDEF_RQL0) values.update({'se': str(subjtype), 'rt': str(rschema), 'oe': str(objtype)}) yield 'INSERT CWRelation X: %s WHERE %s' % (','.join(relations), _LOCATE_RDEF_RQL1), values @@ -468,7 +466,12 @@ and CWGroup entities """ for action in erschema.ACTIONS: - for group_or_rqlexpr in erschema.action_permissions(action): + try: + grantedto = erschema.action_permissions(action) + except KeyError: + # may occurs when modifying persistent schema + continue + for group_or_rqlexpr in grantedto: if isinstance(group_or_rqlexpr, basestring): # group try: @@ -501,14 +504,14 @@ return __rdef2rql(genmap, rschema, subjtype, objtype, props) def updatefrdef2rql(rschema, subjtype, objtype, props): - relations, values = frdef_relations_values(rschema, objtype, props) + relations, values = frdef_relations_values(objtype, props) values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype}) yield 'SET %s WHERE %s, %s, X is CWAttribute' % (','.join(relations), _LOCATE_RDEF_RQL0, _LOCATE_RDEF_RQL1), values def updatenfrdef2rql(rschema, subjtype, objtype, props): - relations, values = nfrdef_relations_values(rschema, objtype, props) + relations, values = nfrdef_relations_values(objtype, props) values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype}) yield 'SET %s WHERE %s, %s, X is CWRelation' % (','.join(relations), _LOCATE_RDEF_RQL0, diff -r d4ae6e751b60 -r 716ee59d64a7 server/serverconfig.py --- a/server/serverconfig.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/serverconfig.py Fri Feb 26 15:36:30 2010 +0100 @@ -7,14 +7,12 @@ """ __docformat__ = "restructuredtext en" -import os from os.path import join, exists from logilab.common.configuration import REQUIRED, Method, Configuration, \ ini_format_section from logilab.common.decorators import wproperty, cached, clear_cache -from cubicweb import CW_SOFTWARE_ROOT, RegistryNotFound from cubicweb.toolsutils import read_config, restrict_perms_to_user from cubicweb.cwconfig import CubicWebConfiguration, merge_options from cubicweb.server import SOURCE_TYPES diff -r d4ae6e751b60 -r 716ee59d64a7 server/serverctl.py --- a/server/serverctl.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/serverctl.py Fri Feb 26 15:36:30 2010 +0100 @@ -7,6 +7,9 @@ """ __docformat__ = 'restructuredtext en' +# *ctl module should limit the number of import to be imported as quickly as +# possible (for cubicweb-ctl reactivity, necessary for instance for usable bash +# completion). So import locally in command helpers. import sys import os @@ -17,7 +20,6 @@ from cubicweb import AuthenticationError, ExecutionError, ConfigurationError from cubicweb.toolsutils import Command, CommandHandler, underline_title from cubicweb.server import SOURCE_TYPES -from cubicweb.server.utils import ask_source_config from cubicweb.server.serverconfig import (USER_OPTIONS, ServerConfiguration, SourceConfiguration) @@ -132,6 +134,7 @@ """create an instance by copying files from the given cube and by asking information necessary to build required configuration files """ + from cubicweb.server.utils import ask_source_config config = self.config print underline_title('Configuring the repository') config.input_config('email', inputlevel) @@ -443,7 +446,6 @@ def run(self, args): """run the command with its specific arguments""" - from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX from cubicweb.server.utils import crypt_password, manager_userpasswd appid = pop_arg(args, 1, msg='No instance specified !') config = ServerConfiguration.config_for(appid) @@ -455,13 +457,21 @@ sys.exit(1) cnx = source_cnx(sourcescfg['system']) cursor = cnx.cursor() + # check admin exists + cursor.execute("SELECT * FROM cw_CWUser WHERE cw_login=%(l)s", + {'l': adminlogin}) + if not cursor.fetchall(): + print ("-> error: admin user %r specified in sources doesn't exist " + "in the database" % adminlogin) + print " fix your sources file before running this command" + cnx.close() + sys.exit(1) + # ask for a new password _, passwd = manager_userpasswd(adminlogin, confirm=True, passwdmsg='new password for %s' % adminlogin) try: - sqlexec("UPDATE %(sp)sCWUser SET %(sp)supassword='%(p)s' WHERE %(sp)slogin='%(l)s'" - % {'sp': SQL_PREFIX, - 'p': crypt_password(passwd), 'l': adminlogin}, - cursor, withpb=False) + cursor.execute("UPDATE cw_CWUser SET cw_upassword=%(p)s WHERE cw_login=%(l)s", + {'p': crypt_password(passwd), 'l': adminlogin}) sconfig = Configuration(options=USER_OPTIONS) sconfig['login'] = adminlogin sconfig['password'] = passwd @@ -475,6 +485,7 @@ else: cnx.commit() print '-> password reset, sources file regenerated.' + cnx.close() class StartRepositoryCommand(Command): diff -r d4ae6e751b60 -r 716ee59d64a7 server/session.py --- a/server/session.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/session.py Fri Feb 26 15:36:30 2010 +0100 @@ -71,9 +71,9 @@ self._threads_in_transaction = set() self._closed = False - def __str__(self): - return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login, - self.id, id(self)) + def __unicode__(self): + return '<%ssession %s (%s 0x%x)>' % ( + self.cnxtype, unicode(self.user.login), self.id, id(self)) def hijack_user(self, user): """return a fake request/session using specified user""" @@ -225,7 +225,7 @@ self.pgettext = pgettext except KeyError: self._ = self.__ = unicode - self.pgettext = lambda x,y: y + self.pgettext = lambda x, y: y self.lang = language def change_property(self, prop, value): @@ -314,7 +314,7 @@ except KeyError: pass pool.pool_reset() - self._threaddata.pool = None + del self._threaddata.pool # free pool once everything is done to avoid race-condition self.repo._free_pool(pool) @@ -410,13 +410,13 @@ @property def super_session(self): try: - csession = self._threaddata.childsession + csession = self.childsession except AttributeError: if isinstance(self, (ChildSession, InternalSession)): csession = self else: csession = ChildSession(self) - self._threaddata.childsession = csession + self.childsession = csession # need shared pool set self.set_pool(checkclosed=False) return csession @@ -443,11 +443,24 @@ rset = self._execute(self, rql, kwargs, eid_key, build_descr) return self.decorate_rset(rset, propagate) + def _clear_thread_data(self): + """remove everything from the thread local storage, except pool + which is explicitly removed by reset_pool, and mode which is set anyway + by _touch + """ + store = self._threaddata + for name in ('commit_state', 'transaction_data', 'pending_operations', + '_rewriter'): + try: + delattr(store, name) + except AttributeError: + pass + def commit(self, reset_pool=True): """commit the current session's transaction""" if self.pool is None: assert not self.pending_operations - self.transaction_data.clear() + self._clear_thread_data() self._touch() self.debug('commit session %s done (no db activity)', self.id) return @@ -501,10 +514,8 @@ exc_info=sys.exc_info()) self.info('%s session %s done', trstate, self.id) finally: + self._clear_thread_data() self._touch() - self.commit_state = None - self.pending_operations[:] = [] - self.transaction_data.clear() if reset_pool: self.reset_pool(ignoremode=True) @@ -512,7 +523,7 @@ """rollback the current session's transaction""" if self.pool is None: assert not self.pending_operations - self.transaction_data.clear() + self._clear_thread_data() self._touch() self.debug('rollback session %s done (no db activity)', self.id) return @@ -527,9 +538,8 @@ self.pool.rollback() self.debug('rollback for session %s done', self.id) finally: + self._clear_thread_data() self._touch() - self.pending_operations[:] = [] - self.transaction_data.clear() if reset_pool: self.reset_pool(ignoremode=True) @@ -584,6 +594,7 @@ @property def rql_rewriter(self): + # in thread local storage since the rewriter isn't thread safe try: return self._threaddata._rewriter except AttributeError: diff -r d4ae6e751b60 -r 716ee59d64a7 server/sources/ldapuser.py --- a/server/sources/ldapuser.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/sources/ldapuser.py Fri Feb 26 15:36:30 2010 +0100 @@ -78,7 +78,7 @@ ('auth-realm', {'type' : 'string', 'default': None, - 'help': 'realm to use when using gssapp/kerberos authentication.', + 'help': 'realm to use when using gssapi/kerberos authentication.', 'group': 'ldap-source', 'inputlevel': 1, }), @@ -210,11 +210,11 @@ if res: ldapemailaddr = res[0].get(ldap_emailattr) if ldapemailaddr: - rset = session.execute('EmailAddress X,A WHERE ' + rset = session.execute('EmailAddress A WHERE ' 'U use_email X, U eid %(u)s', {'u': eid}) ldapemailaddr = unicode(ldapemailaddr) - for emaileid, emailaddr in rset: + for emailaddr, in rset: if emailaddr == ldapemailaddr: break else: @@ -448,12 +448,12 @@ def _auth_cram_md5(self, conn, user, userpwd): from ldap import sasl auth_token = sasl.cram_md5(user['dn'], userpwd) - conn.sasl_interactive_bind_s('', auth_tokens) + conn.sasl_interactive_bind_s('', auth_token) def _auth_digest_md5(self, conn, user, userpwd): from ldap import sasl auth_token = sasl.digest_md5(user['dn'], userpwd) - conn.sasl_interactive_bind_s('', auth_tokens) + conn.sasl_interactive_bind_s('', auth_token) def _auth_gssapi(self, conn, user, userpwd): # print XXX not proper sasl/gssapi diff -r d4ae6e751b60 -r 716ee59d64a7 server/sources/native.py --- a/server/sources/native.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/sources/native.py Fri Feb 26 15:36:30 2010 +0100 @@ -421,12 +421,16 @@ # str(query) to avoid error if it's an unicode string cursor.execute(str(query), args) except Exception, ex: - self.critical("sql: %r\n args: %s\ndbms message: %r", - query, args, ex.args[0]) + if self.repo.config.mode != 'test': + # during test we get those message when trying to alter sqlite + # db schema + self.critical("sql: %r\n args: %s\ndbms message: %r", + query, args, ex.args[0]) if rollback: try: session.pool.connection(self.uri).rollback() - self.critical('transaction has been rollbacked') + if self.repo.config.mode != 'test': + self.critical('transaction has been rollbacked') except: pass raise @@ -443,11 +447,15 @@ # str(query) to avoid error if it's an unicode string cursor.executemany(str(query), args) except Exception, ex: - self.critical("sql many: %r\n args: %s\ndbms message: %r", - query, args, ex.args[0]) + if self.repo.config.mode != 'test': + # during test we get those message when trying to alter sqlite + # db schema + self.critical("sql many: %r\n args: %s\ndbms message: %r", + query, args, ex.args[0]) try: session.pool.connection(self.uri).rollback() - self.critical('transaction has been rollbacked') + if self.repo.config.mode != 'test': + self.critical('transaction has been rollbacked') except: pass raise diff -r d4ae6e751b60 -r 716ee59d64a7 server/sources/rql2sql.py --- a/server/sources/rql2sql.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/sources/rql2sql.py Fri Feb 26 15:36:30 2010 +0100 @@ -38,7 +38,6 @@ from rql.nodes import (SortTerm, VariableRef, Constant, Function, Not, Variable, ColumnAlias, Relation, SubQuery, Exists) -from cubicweb import server from cubicweb.server.sqlutils import SQL_PREFIX from cubicweb.server.utils import cleanup_solutions diff -r d4ae6e751b60 -r 716ee59d64a7 server/sources/storages.py --- a/server/sources/storages.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/sources/storages.py Fri Feb 26 15:36:30 2010 +0100 @@ -62,7 +62,7 @@ else: fpath = self.new_fs_path(entity, attr) # bytes storage used to store file's path - entity[attr]= Binary(fpath) + entity[attr] = Binary(fpath) file(fpath, 'w').write(value.getvalue()) AddFileOp(entity._cw, filepath=fpath) # else entity[attr] is expected to be an already existant file path diff -r d4ae6e751b60 -r 716ee59d64a7 server/sqlutils.py --- a/server/sqlutils.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/sqlutils.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,9 +9,7 @@ import os import subprocess -from os.path import exists -from warnings import warn -from datetime import datetime, date, timedelta +from datetime import datetime, date import logilab.common as lgc from logilab.common import db @@ -24,7 +22,6 @@ from cubicweb import Binary, ConfigurationError from cubicweb.uilib import remove_html_tags -from cubicweb.toolsutils import restrict_perms_to_user from cubicweb.schema import PURE_VIRTUAL_RTYPES from cubicweb.server import SQL_CONNECT_HOOKS from cubicweb.server.utils import crypt_password @@ -178,6 +175,7 @@ def backup_to_file(self, backupfile): for cmd in self.dbhelper.backup_commands(self.dbname, self.dbhost, self.dbuser, backupfile, + dbport=self.dbport, keepownership=False): if _run_command(cmd): if not confirm(' [Failed] Continue anyway?', default='n'): @@ -187,6 +185,7 @@ for cmd in self.dbhelper.restore_commands(self.dbname, self.dbhost, self.dbuser, backupfile, self.encoding, + dbport=self.dbport, keepownership=False, drop=drop): if _run_command(cmd): diff -r d4ae6e751b60 -r 716ee59d64a7 server/test/data/schema.py --- a/server/test/data/schema.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/test/data/schema.py Fri Feb 26 15:36:30 2010 +0100 @@ -137,14 +137,13 @@ class para(RelationType): __permissions__ = { 'read': ('managers', 'users', 'guests'), - 'add': ('managers', ERQLExpression('X in_state S, S name "todo"')), - 'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')), + 'update': ('managers', ERQLExpression('X in_state S, S name "todo"')), } class test(RelationType): __permissions__ = {'read': ('managers', 'users', 'guests'), - 'delete': ('managers',), - 'add': ('managers',)} + 'update': ('managers',), + } class multisource_rel(RelationDefinition): subject = ('Card', 'Note') diff -r d4ae6e751b60 -r 716ee59d64a7 server/test/unittest_checkintegrity.py --- a/server/test/unittest_checkintegrity.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/test/unittest_checkintegrity.py Fri Feb 26 15:36:30 2010 +0100 @@ -20,7 +20,7 @@ sys.stderr = sys.stdout = StringIO() try: check(repo, cnx, ('entities', 'relations', 'text_index', 'metadata'), - True, True) + reindex=True, fix=True, withpb=False) finally: sys.stderr = sys.__stderr__ sys.stdout = sys.__stdout__ diff -r d4ae6e751b60 -r 716ee59d64a7 server/test/unittest_migractions.py --- a/server/test/unittest_migractions.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/test/unittest_migractions.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,26 +9,18 @@ from logilab.common.testlib import TestCase, unittest_main from cubicweb import ConfigurationError -from cubicweb.devtools.testlib import CubicWebTC, get_versions +from cubicweb.devtools.testlib import CubicWebTC from cubicweb.schema import CubicWebSchemaLoader from cubicweb.server.sqlutils import SQL_PREFIX -from cubicweb.server.repository import Repository from cubicweb.server.migractions import * -orig_get_versions = Repository.get_versions - -def setup_module(*args): - Repository.get_versions = get_versions - -def teardown_module(*args): - Repository.get_versions = orig_get_versions - class MigrationCommandsTC(CubicWebTC): @classmethod def init_config(cls, config): super(MigrationCommandsTC, cls).init_config(config) + # we have to read schema from the database to get eid for schema entities config._cubes = None cls.repo.fill_schema() cls.origschema = deepcopy(cls.repo.schema) @@ -42,7 +34,7 @@ @classmethod def _refresh_repo(cls): super(MigrationCommandsTC, cls)._refresh_repo() - cls.repo.schema = cls.vreg.schema = deepcopy(cls.origschema) + cls.repo.set_schema(deepcopy(cls.origschema), resetvreg=False) def setUp(self): CubicWebTC.setUp(self) @@ -157,7 +149,7 @@ sorted(str(e) for e in self.schema.entities() if not e.final)) self.assertEquals(self.schema['filed_under2'].objects(), ('Folder2',)) eschema = self.schema.eschema('Folder2') - for cstr in eschema.constraints('name'): + for cstr in eschema.rdef('name').constraints: self.failUnless(hasattr(cstr, 'eid')) def test_add_drop_entity_type(self): @@ -281,7 +273,7 @@ def test_sync_schema_props_perms(self): cursor = self.mh.session cursor.set_pool() - nbrqlexpr_start = len(cursor.execute('RQLExpression X')) + nbrqlexpr_start = cursor.execute('Any COUNT(X) WHERE X is RQLExpression')[0][0] migrschema['titre'].rdefs[('Personne', 'String')].order = 7 migrschema['adel'].rdefs[('Personne', 'String')].order = 6 migrschema['ass'].rdefs[('Personne', 'String')].order = 5 @@ -345,19 +337,22 @@ self.assertEquals(len(self._erqlexpr_rset('delete', 'Affaire')), 1) self.assertEquals(len(self._erqlexpr_rset('add', 'Affaire')), 1) # no change for rqlexpr to add and delete concerne relation - for rdef in self.schema['concerne'].rdefs.values(): - print rdef, rdef.permissions self.assertEquals(len(self._rrqlexpr_rset('delete', 'concerne')), len(delete_concerne_rqlexpr)) self.assertEquals(len(self._rrqlexpr_rset('add', 'concerne')), len(add_concerne_rqlexpr)) # * migrschema involve: - # * 8 deletion (2 in Affaire read + Societe + travaille + para rqlexprs) + # * 7 rqlexprs deletion (2 in (Affaire read + Societe + travaille) + 1 + # in para attribute) # * 1 update (Affaire update) # * 2 new (Note add, ecrit_par add) + # * 2 implicit new for attributes update_permission (Note.para, Personne.test) # remaining orphan rql expr which should be deleted at commit (composite relation) - self.assertEquals(len(cursor.execute('RQLExpression X WHERE NOT ET1 read_permission X, NOT ET2 add_permission X, ' - 'NOT ET3 delete_permission X, NOT ET4 update_permission X')), 8+1) + self.assertEquals(cursor.execute('Any COUNT(X) WHERE X is RQLExpression, ' + 'NOT ET1 read_permission X, NOT ET2 add_permission X, ' + 'NOT ET3 delete_permission X, NOT ET4 update_permission X')[0][0], + 7+1) # finally - self.assertEquals(len(cursor.execute('RQLExpression X')), nbrqlexpr_start + 1 + 2) + self.assertEquals(cursor.execute('Any COUNT(X) WHERE X is RQLExpression')[0][0], + nbrqlexpr_start + 1 + 2 + 2) self.mh.rollback() diff -r d4ae6e751b60 -r 716ee59d64a7 server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/test/unittest_msplanner.py Fri Feb 26 15:36:30 2010 +0100 @@ -5,7 +5,6 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ -from logilab.common.decorators import clear_cache from cubicweb.devtools import init_test_database from cubicweb.devtools.repotest import BasePlannerTC, test_plan @@ -78,12 +77,12 @@ self.prevrqlexpr_affaire = affreadperms[-1] # add access to type attribute so S can't be invariant affreadperms[-1] = ERQLExpression('X concerne S?, S owned_by U, S type "X"') - self.schema['Affaire'].permissions['read'] = tuple(affreadperms) + self.schema['Affaire'].set_action_permissions('read', affreadperms) # hijack CWUser security userreadperms = list(self.schema['CWUser'].permissions['read']) self.prevrqlexpr_user = userreadperms[-1] userreadperms[-1] = ERQLExpression('X owned_by U') - self.schema['CWUser'].permissions['read'] = tuple(userreadperms) + self.schema['CWUser'].set_action_permissions('read', userreadperms) self.add_source(FakeUserROSource, 'ldap') self.add_source(FakeCardSource, 'cards') @@ -96,9 +95,7 @@ def restore_orig_affaire_security(self): affreadperms = list(self.schema['Affaire'].permissions['read']) affreadperms[-1] = self.prevrqlexpr_affaire - self.schema['Affaire'].permissions['read'] = tuple(affreadperms) - clear_cache(self.schema['Affaire'], 'get_rqlexprs') - #clear_cache(self.schema['Affaire'], 'get_groups') + self.schema['Affaire'].set_action_permissions('read', affreadperms) def restore_orig_cwuser_security(self): if hasattr(self, '_orig_cwuser_security_restored'): @@ -106,9 +103,7 @@ self._orig_cwuser_security_restored = True userreadperms = list(self.schema['CWUser'].permissions['read']) userreadperms[-1] = self.prevrqlexpr_user - self.schema['CWUser'].permissions['read'] = tuple(userreadperms) - clear_cache(self.schema['CWUser'], 'get_rqlexprs') - #clear_cache(self.schema['CWUser'], 'get_groups') + self.schema['CWUser'].set_action_permissions('read', userreadperms) class PartPlanInformationTC(BaseMSPlannerTC): @@ -762,7 +757,7 @@ def test_security_has_text(self): # use a guest user - self.session = self._user_session()[1] + self.session = self.user_groups_session('guests') self._test('Any X WHERE X has_text "bla"', [('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])], [self.cards, self.system], None, {'E': 'table0.C0'}, []), @@ -789,7 +784,7 @@ def test_security_has_text_limit_offset(self): # use a guest user - self.session = self._user_session()[1] + self.session = self.user_groups_session('guests') # note: same as the above query but because of the subquery usage, the display differs (not printing solutions for each union) self._test('Any X LIMIT 10 OFFSET 10 WHERE X has_text "bla"', [('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])], @@ -827,7 +822,7 @@ def test_security_user(self): """a guest user trying to see another user: EXISTS(X owned_by U) is automatically inserted""" # use a guest user - self.session = self._user_session()[1] + self.session = self.user_groups_session('guests') self._test('Any X WHERE X login "bla"', [('FetchStep', [('Any X WHERE X login "bla", X is CWUser', [{'X': 'CWUser'}])], @@ -838,7 +833,7 @@ def test_security_complex_has_text(self): # use a guest user - self.session = self._user_session()[1] + self.session = self.user_groups_session('guests') self._test('Any X WHERE X has_text "bla", X firstname "bla"', [('FetchStep', [('Any X WHERE X firstname "bla", X is CWUser', [{'X': 'CWUser'}])], [self.ldap, self.system], None, {'X': 'table0.C0'}, []), @@ -852,7 +847,7 @@ def test_security_complex_has_text_limit_offset(self): # use a guest user - self.session = self._user_session()[1] + self.session = self.user_groups_session('guests') self._test('Any X LIMIT 10 OFFSET 10 WHERE X has_text "bla", X firstname "bla"', [('FetchStep', [('Any X WHERE X firstname "bla", X is CWUser', [{'X': 'CWUser'}])], [self.ldap, self.system], None, {'X': 'table1.C0'}, []), @@ -869,7 +864,7 @@ def test_security_complex_aggregat(self): # use a guest user - self.session = self._user_session()[1] + self.session = self.user_groups_session('guests') 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'}, []), @@ -914,7 +909,7 @@ def test_security_complex_aggregat2(self): # use a guest user - self.session = self._user_session()[1] + self.session = self.user_groups_session('guests') X_ET_ALL_SOLS = [] for s in X_ALL_SOLS: ets = {'ET': 'CWEType'} @@ -978,7 +973,7 @@ def test_security_3sources(self): # use a guest user - self.session = self._user_session()[1] + self.session = self.user_groups_session('guests') self._test('Any X, XT WHERE X is Card, X owned_by U, X title XT, U login "syt"', [('FetchStep', [('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])], @@ -996,8 +991,7 @@ def test_security_3sources_identity(self): self.restore_orig_cwuser_security() # use a guest user - self.session = self._user_session()[1] - print self.session + self.session = self.user_groups_session('guests') self._test('Any X, XT WHERE X is Card, X owned_by U, X title XT, U login "syt"', [('FetchStep', [('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])], @@ -1011,7 +1005,7 @@ def test_security_3sources_identity_optional_var(self): self.restore_orig_cwuser_security() # use a guest user - self.session = self._user_session()[1] + self.session = self.user_groups_session('guests') self._test('Any X,XT,U WHERE X is Card, X owned_by U?, X title XT, U login L', [('FetchStep', [('Any U,L WHERE U identity 5, U login L, U is CWUser', @@ -1032,7 +1026,7 @@ def test_security_3sources_limit_offset(self): # use a guest user - self.session = self._user_session()[1] + self.session = self.user_groups_session('guests') self._test('Any X, XT LIMIT 10 OFFSET 10 WHERE X is Card, X owned_by U, X title XT, U login "syt"', [('FetchStep', [('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])], @@ -1668,7 +1662,7 @@ ]) def test_update3(self): - anoneid = self._user_session()[1].user.eid + anoneid = self.user_groups_session('guests').user.eid # since we are adding a in_state relation for an entity in the system # source, states should only be searched in the system source as well self._test('SET X in_state S WHERE X eid %(x)s, S name "deactivated"', diff -r d4ae6e751b60 -r 716ee59d64a7 server/test/unittest_multisources.py --- a/server/test/unittest_multisources.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/test/unittest_multisources.py Fri Feb 26 15:36:30 2010 +0100 @@ -30,13 +30,25 @@ repo2, cnx2 = init_test_database(config=ExternalSource1Configuration('data')) repo3, cnx3 = init_test_database(config=ExternalSource2Configuration('data')) -# XXX, access existing connection, no pyro connection +# hi-jacking from cubicweb.server.sources.pyrorql import PyroRQLSource -PyroRQLSource.get_connection = lambda x: x.uri == 'extern-multi' and cnx3 or cnx2 -# necessary since the repository is closing its initial connections pool though -# we want to keep cnx2 valid from cubicweb.dbapi import Connection -Connection.close = lambda x: None + +PyroRQLSource_get_connection = PyroRQLSource.get_connection +Connection_close = Connection.close + +def setup_module(*args): + # hi-jack PyroRQLSource.get_connection to access existing connection (no + # pyro connection) + PyroRQLSource.get_connection = lambda x: x.uri == 'extern-multi' and cnx3 or cnx2 + # also necessary since the repository is closing its initial connections + # pool though we want to keep cnx2 valid + Connection.close = lambda x: None + +def teardown_module(*args): + PyroRQLSource.get_connection = PyroRQLSource_get_connection + Connection.close = Connection_close + class TwoSourcesTC(CubicWebTC): config = TwoSourcesConfiguration('data') diff -r d4ae6e751b60 -r 716ee59d64a7 server/test/unittest_querier.py --- a/server/test/unittest_querier.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/test/unittest_querier.py Fri Feb 26 15:36:30 2010 +0100 @@ -78,7 +78,7 @@ def test_preprocess_security(self): plan = self._prepare_plan('Any ETN,COUNT(X) GROUPBY ETN ' 'WHERE X is ET, ET name ETN') - plan.session = self._user_session(('users',))[1] + plan.session = self.user_groups_session('users') union = plan.rqlst plan.preprocess(union) self.assertEquals(len(union.children), 1) @@ -158,7 +158,7 @@ def test_preprocess_security_aggregat(self): plan = self._prepare_plan('Any MAX(X)') - plan.session = self._user_session(('users',))[1] + plan.session = self.user_groups_session('users') union = plan.rqlst plan.preprocess(union) self.assertEquals(len(union.children), 1) @@ -257,7 +257,7 @@ result, descr = rset.rows, rset.description self.assertEquals(descr[0][0], 'String') self.assertEquals(descr[0][1], 'Int') - self.assertEquals(result[0][0], 'CWRelation') + self.assertEquals(result[0][0], 'RQLExpression') # XXX may change as schema evolve def test_select_groupby_orderby(self): rset = self.execute('Any N GROUPBY N ORDERBY N WHERE X is CWGroup, X name N') @@ -928,7 +928,7 @@ self.assertEqual(len(rset.rows), 0, rset.rows) def test_delete_3(self): - u, s = self._user_session(('users',)) + s = self.user_groups_session('users') peid, = self.o.execute(s, "INSERT Personne P: P nom 'toto'")[0] seid, = self.o.execute(s, "INSERT Societe S: S nom 'logilab'")[0] self.o.execute(s, "SET P travaille S") @@ -959,7 +959,7 @@ (using cachekey on sql generation returned always the same query for an eid, whatever the relation) """ - u, s = self._user_session(('users',)) + s = self.user_groups_session('users') aeid, = self.o.execute(s, 'INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')[0] # XXX would be nice if the rql below was enough... #'INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y' diff -r d4ae6e751b60 -r 716ee59d64a7 server/test/unittest_repository.py --- a/server/test/unittest_repository.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/test/unittest_repository.py Fri Feb 26 15:36:30 2010 +0100 @@ -215,7 +215,7 @@ if not r.type in ('eid', 'is', 'is_instance_of', 'identity', 'creation_date', 'modification_date', 'cwuri', 'owned_by', 'created_by', - 'add_permission', 'delete_permission', 'read_permission')], + 'update_permission', 'read_permission')], ['relation_type', 'from_entity', 'to_entity', 'in_basket', 'constrained_by', diff -r d4ae6e751b60 -r 716ee59d64a7 server/test/unittest_schemaserial.py --- a/server/test/unittest_schemaserial.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/test/unittest_schemaserial.py Fri Feb 26 15:36:30 2010 +0100 @@ -68,22 +68,17 @@ def test_rschema2rql2(self): self.assertListEquals(list(rschema2rql(schema.rschema('add_permission'))), [ - ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s', {'description': u'core relation giving to a group the permission to add an entity or relation type', 'symmetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}), - - ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s', - {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWAttribute'}), - ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s', - {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWAttribute'}), + ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s', {'description': u'', 'symmetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}), ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s', - {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWEType'}), + {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 9999, 'cardinality': u'**', 'se': 'CWEType'}), ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s', - {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWEType'}), + {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 9999, 'cardinality': u'*?', 'se': 'CWEType'}), ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s', - {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWRelation'}), + {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 9999, 'cardinality': u'**', 'se': 'CWRelation'}), ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s', - {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWRelation'}), + {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 9999, 'cardinality': u'*?', 'se': 'CWRelation'}), ]) def test_rschema2rql3(self): @@ -133,8 +128,8 @@ expected = [ ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X is CWRType, X name %(rt)s', {'rt': 'add_permission', 'symmetric': False, - 'description': u'core relation giving to a group the permission to add an entity or relation type', - 'final': False, 'fulltext_container': None, 'inlined': False, 'name': u'add_permission'}) + 'description': u'', 'final': False, 'fulltext_container': None, + 'inlined': False, 'name': u'add_permission'}) ] for i, (rql, args) in enumerate(updaterschema2rql(schema.rschema('add_permission'))): yield self.assertEquals, (rql, args), expected[i] @@ -172,10 +167,12 @@ [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}), ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}), ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}), - ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 0}), - ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 1}), - ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 0}), - ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 1}), + ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 0}), + ('INSERT RQLExpression E: ' + 'E expression %(e)s, E exprtype %(t)s, E mainvars %(v)s, ' + 'X update_permission E ' + 'WHERE ', # completed by the outer function + {'e': u'U has_update_permission X', 't': u'ERQLExpression', 'v': u'X'}), ]) #def test_perms2rql(self): diff -r d4ae6e751b60 -r 716ee59d64a7 server/test/unittest_security.py --- a/server/test/unittest_security.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/test/unittest_security.py Fri Feb 26 15:36:30 2010 +0100 @@ -30,7 +30,7 @@ rqlst = self.repo.vreg.rqlhelper.parse(rql).children[0] origgroups = self.schema['Personne'].get_groups('read') self.schema['Personne'].set_action_permissions('read', ('users', 'managers')) - self.repo.vreg.rqlhelper.compute_solutions(rqlst) + self.repo.vreg.solutions(self.session, rqlst, None) solution = rqlst.solutions[0] check_read_access(self.schema, self.session.user, rqlst, solution) cnx = self.login('anon') @@ -494,12 +494,13 @@ # needed to avoid check_perm error session.set_pool() # needed to remove rql expr granting update perm to the user + affaire_perms = self.schema['Affaire'].permissions.copy() self.schema['Affaire'].set_action_permissions('update', self.schema['Affaire'].get_groups('update')) - self.assertRaises(Unauthorized, - self.schema['Affaire'].check_perm, session, 'update', eid=eid) - cu = cnx.cursor() - self.schema['Affaire'].set_action_permissions('read', ('users',)) try: + self.assertRaises(Unauthorized, + self.schema['Affaire'].check_perm, session, 'update', eid=eid) + cu = cnx.cursor() + self.schema['Affaire'].set_action_permissions('read', ('users',)) aff = cu.execute('Any X WHERE X ref "ARCT01"').get_entity(0, 0) aff.fire_transition('abort') cnx.commit() @@ -510,7 +511,9 @@ # from the current state but Unauthorized if it exists but user can't pass it self.assertRaises(ValidationError, user.fire_transition, 'deactivate') finally: - self.schema['Affaire'].set_action_permissions('read', ('managers',)) + # restore orig perms + for action, perms in affaire_perms.iteritems(): + self.schema['Affaire'].set_action_permissions(action, perms) def test_trinfo_security(self): aff = self.execute('INSERT Affaire X: X ref "ARCT01"').get_entity(0, 0) diff -r d4ae6e751b60 -r 716ee59d64a7 server/utils.py --- a/server/utils.py Wed Feb 10 16:34:15 2010 +0100 +++ b/server/utils.py Fri Feb 26 15:36:30 2010 +0100 @@ -107,6 +107,9 @@ self.func = auto_restart_func self.name = func.__name__ + def __str__(self): + return '%s (%s seconds)' % (self.name, self.interval) + def start(self): self._t = Timer(self.interval, self.func) self._t.start() diff -r d4ae6e751b60 -r 716ee59d64a7 setup.py --- a/setup.py Wed Feb 10 16:34:15 2010 +0100 +++ b/setup.py Fri Feb 26 15:36:30 2010 +0100 @@ -1,5 +1,5 @@ #!/usr/bin/env python -""" +"""Generic Setup script, takes package info from __pkginfo__.py file :organization: Logilab :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. @@ -8,9 +8,6 @@ """ # pylint: disable-msg=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611 # -# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later @@ -23,7 +20,6 @@ # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -""" Generic Setup script, takes package info from __pkginfo__.py file """ import os import sys diff -r d4ae6e751b60 -r 716ee59d64a7 skeleton/MANIFEST.in --- a/skeleton/MANIFEST.in Wed Feb 10 16:34:15 2010 +0100 +++ b/skeleton/MANIFEST.in Fri Feb 26 15:36:30 2010 +0100 @@ -1,5 +1,4 @@ include *.py - +include */*.py recursive-include data external_resources *.gif *.png *.css *.ico *.js recursive-include i18n *.pot *.po -recursive-include migration *.py diff -r d4ae6e751b60 -r 716ee59d64a7 sobjects/notification.py --- a/sobjects/notification.py Wed Feb 10 16:34:15 2010 +0100 +++ b/sobjects/notification.py Fri Feb 26 15:36:30 2010 +0100 @@ -11,7 +11,7 @@ from itertools import repeat from logilab.common.textutils import normalize_text -from logilab.common.deprecation import class_renamed, deprecated +from logilab.common.deprecation import class_renamed, class_moved, deprecated from cubicweb.selectors import yes from cubicweb.view import Component @@ -183,7 +183,6 @@ entity.eid, self.user_data['login']) -from logilab.common.deprecation import class_renamed, class_moved, deprecated from cubicweb.hooks.notification import RenderAndSendNotificationView from cubicweb.mail import parse_message_id diff -r d4ae6e751b60 -r 716ee59d64a7 sobjects/test/unittest_email.py --- a/sobjects/test/unittest_email.py Wed Feb 10 16:34:15 2010 +0100 +++ b/sobjects/test/unittest_email.py Fri Feb 26 15:36:30 2010 +0100 @@ -46,10 +46,10 @@ self.commit() cnx = self.login('toto') cu = cnx.cursor() - cu.execute('SET U primary_email E WHERE E eid %(e)s, U login "toto"', - {'e': email1}) - self.assertRaises(Unauthorized, cnx.commit) - + self.assertRaises(Unauthorized, + cu.execute, 'SET U primary_email E WHERE E eid %(e)s, U login "toto"', + {'e': email1}) + cnx.close() if __name__ == '__main__': from logilab.common.testlib import unittest_main diff -r d4ae6e751b60 -r 716ee59d64a7 test/data/rrqlexpr_on_attr.py --- a/test/data/rrqlexpr_on_attr.py Wed Feb 10 16:34:15 2010 +0100 +++ b/test/data/rrqlexpr_on_attr.py Fri Feb 26 15:36:30 2010 +0100 @@ -20,6 +20,5 @@ class attr(RelationType): __permissions__ = { 'read': ('managers', ), - 'add': ('managers', RRQLExpression('S bla Y'),), - 'delete': ('managers',), + 'update': ('managers', RRQLExpression('S bla Y'),), } diff -r d4ae6e751b60 -r 716ee59d64a7 test/unittest_cwconfig.py --- a/test/unittest_cwconfig.py Wed Feb 10 16:34:15 2010 +0100 +++ b/test/unittest_cwconfig.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,6 +9,7 @@ import os from os.path import dirname, join, abspath +from logilab.common.modutils import cleanup_sys_modules from logilab.common.testlib import TestCase, unittest_main from logilab.common.changelog import Version @@ -21,8 +22,12 @@ return '/'.join(parts[i+1:]) raise Exception('duh? %s' % path) +CUSTOM_CUBES_DIR = abspath(join(dirname(__file__), 'data', 'cubes')) + + class CubicWebConfigurationTC(TestCase): def setUp(self): + cleanup_sys_modules([CUSTOM_CUBES_DIR, ApptestConfiguration.CUBES_DIR]) self.config = ApptestConfiguration('data') self.config._cubes = ('email', 'file') @@ -86,16 +91,14 @@ # make sure we don't import the email cube, but the stdlib email package import email self.assertNotEquals(dirname(email.__file__), self.config.CUBES_DIR) - os.environ['CW_CUBES_PATH'] = join(dirname(__file__), 'data', 'cubes') + os.environ['CW_CUBES_PATH'] = CUSTOM_CUBES_DIR self.assertEquals(self.config.cubes_search_path(), - [abspath(join(dirname(__file__), 'data', 'cubes')), - self.config.CUBES_DIR]) - os.environ['CW_CUBES_PATH'] = '%s%s%s%s%s' % (join(dirname(__file__), 'data', 'cubes'), - os.pathsep, self.config.CUBES_DIR, - os.pathsep, 'unexistant') + [CUSTOM_CUBES_DIR, self.config.CUBES_DIR]) + os.environ['CW_CUBES_PATH'] = os.pathsep.join([ + CUSTOM_CUBES_DIR, self.config.CUBES_DIR, 'unexistant']) # filter out unexistant and duplicates self.assertEquals(self.config.cubes_search_path(), - [abspath(join(dirname(__file__), 'data', 'cubes')), + [CUSTOM_CUBES_DIR, self.config.CUBES_DIR]) self.failUnless('mycube' in self.config.available_cubes()) # test cubes python path @@ -104,12 +107,12 @@ self.assertEquals(cubes.__path__, self.config.cubes_search_path()) # this import should succeed once path is adjusted from cubes import mycube - self.assertEquals(mycube.__path__, [abspath(join(dirname(__file__), 'data', 'cubes', 'mycube'))]) + self.assertEquals(mycube.__path__, [join(CUSTOM_CUBES_DIR, 'mycube')]) # file cube should be overriden by the one found in data/cubes sys.modules.pop('cubes.file', None) del cubes.file from cubes import file - self.assertEquals(file.__path__, [abspath(join(dirname(__file__), 'data', 'cubes', 'file'))]) + self.assertEquals(file.__path__, [join(CUSTOM_CUBES_DIR, 'file')]) if __name__ == '__main__': diff -r d4ae6e751b60 -r 716ee59d64a7 test/unittest_entity.py --- a/test/unittest_entity.py Wed Feb 10 16:34:15 2010 +0100 +++ b/test/unittest_entity.py Fri Feb 26 15:36:30 2010 +0100 @@ -175,12 +175,14 @@ Personne.fetch_attrs = pfetch_attrs Societe.fetch_attrs = sfetch_attrs - def test_related_rql(self): + def test_related_rql_base(self): 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',)) p = self.request().create_entity('Personne', nom=u'pouet') self.assertEquals(p.related_rql('evaluee'), 'Any X,AA,AB ORDERBY AA ASC WHERE E eid %(x)s, E evaluee X, ' @@ -188,20 +190,26 @@ Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', )) # XXX self.assertEquals(p.related_rql('evaluee'), - 'Any X,AA ORDERBY Z DESC ' - 'WHERE X modification_date Z, E eid %(x)s, E evaluee X, ' - 'X modification_date AA') + 'Any X,AA ORDERBY AA DESC ' + 'WHERE E eid %(x)s, E evaluee X, X modification_date AA') tag = self.vreg['etypes'].etype_class('Tag')(self.request()) self.assertEquals(tag.related_rql('tags', 'subject'), - 'Any X,AA ORDERBY Z DESC ' - 'WHERE X modification_date Z, E eid %(x)s, E tags X, ' - 'X modification_date AA') + 'Any X,AA ORDERBY AA DESC ' + 'WHERE E eid %(x)s, E tags X, X modification_date AA') self.assertEquals(tag.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, ' 'X modification_date AB') + def test_related_rql_ambigous_cant_use_fetch_order(self): + tag = self.vreg['etypes'].etype_class('Tag')(self.request()) + for ttype in self.schema['tags'].objects(): + self.vreg['etypes'].etype_class(ttype).fetch_attrs = ('modification_date',) + self.assertEquals(tag.related_rql('tags', 'subject'), + 'Any X,AA ORDERBY AA DESC ' + 'WHERE E eid %(x)s, E tags X, X modification_date AA') + def test_unrelated_rql_security_1(self): user = self.request().user rql = user.unrelated_rql('use_email', 'EmailAddress', 'subject')[0] diff -r d4ae6e751b60 -r 716ee59d64a7 test/unittest_schema.py --- a/test/unittest_schema.py Wed Feb 10 16:34:15 2010 +0100 +++ b/test/unittest_schema.py Fri Feb 26 15:36:30 2010 +0100 @@ -257,7 +257,7 @@ self.assertEquals(str(ex), msg) def test_rrqlexpr_on_etype(self): - self._test('rrqlexpr_on_eetype.py', "can't use RRQLExpression on an entity type, use an ERQLExpression (ToTo)") + self._test('rrqlexpr_on_eetype.py', "can't use RRQLExpression on ToTo, use an ERQLExpression") def test_erqlexpr_on_rtype(self): self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on relation ToTo toto TuTu, use a RRQLExpression") diff -r d4ae6e751b60 -r 716ee59d64a7 test/unittest_utils.py --- a/test/unittest_utils.py Wed Feb 10 16:34:15 2010 +0100 +++ b/test/unittest_utils.py Fri Feb 26 15:36:30 2010 +0100 @@ -6,14 +6,18 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ -from logilab.common.testlib import TestCase, unittest_main - -import simplejson +import re import decimal import datetime -from cubicweb.utils import make_uid, UStringIO, SizeConstrainedList, CubicWebJsonEncoder +from logilab.common.testlib import TestCase, unittest_main +from cubicweb.utils import make_uid, UStringIO, SizeConstrainedList +try: + import simplejson + from cubicweb.utils import CubicWebJsonEncoder +except ImportError: + simplejson = None class MakeUidTC(TestCase): def test_1(self): @@ -26,6 +30,9 @@ uid = make_uid('xyz') if uid in d: self.fail(len(d)) + if re.match('\d', uid): + self.fail('make_uid must not return something begining with ' + 'some numeric character, got %s' % uid) d.add(uid) @@ -53,6 +60,9 @@ yield self.assertEquals, l, expected class JSONEncoerTests(TestCase): + def setUp(self): + if simplejson is None: + self.skip('simplejson not available') def encode(self, value): return simplejson.dumps(value, cls=CubicWebJsonEncoder) diff -r d4ae6e751b60 -r 716ee59d64a7 toolsutils.py --- a/toolsutils.py Wed Feb 10 16:34:15 2010 +0100 +++ b/toolsutils.py Fri Feb 26 15:36:30 2010 +0100 @@ -10,6 +10,7 @@ # XXX move most of this in logilab.common (shellutils ?) import os, sys +import subprocess from os import listdir, makedirs, environ, chmod, walk, remove from os.path import exists, join, abspath, normpath @@ -75,13 +76,13 @@ user decision """ import shutil - p_output = os.popen('diff -u %s %s' % (appl_file, ref_file), 'r') - diffs = p_output.read() + pipe = subprocess.Popen(['diff', '-u', appl_file, ref_file], stdout=subprocess.PIPE) + diffs = pipe.stdout.read() if diffs: if askconfirm: print print diffs - action = ASK.ask('Replace ?', ('N','y','q'), 'N') + action = ASK.ask('Replace ?', ('N', 'y', 'q'), 'N') else: action = 'y' if action == 'y': diff -r d4ae6e751b60 -r 716ee59d64a7 utils.py --- a/utils.py Wed Feb 10 16:34:15 2010 +0100 +++ b/utils.py Fri Feb 26 15:36:30 2010 +0100 @@ -7,27 +7,35 @@ """ __docformat__ = "restructuredtext en" -from logilab.mtconverter import xml_escape - -import locale import sys import decimal import datetime -from md5 import md5 -from time import time -from random import randint, seed +import random + +from logilab.mtconverter import xml_escape +from logilab.common.deprecation import deprecated # initialize random seed from current time -seed() +random.seed() if sys.version_info[:2] < (2, 5): + + from time import time + from md5 import md5 + from random import randint + def make_uid(key): """forge a unique identifier - not that unique on win32""" - msg = str(key) + "%.10f" % time() + str(randint(0, 1000000)) - return md5(msg).hexdigest() + XXX not that unique on win32 + """ + key = str(key) + msg = key + "%.10f" % time() + str(randint(0, 1000000)) + return key + md5(msg).hexdigest() + else: + from uuid import uuid4 + def make_uid(key): # remove dash, generated uid are used as identifier sometimes (sql table # names at least) @@ -328,3 +336,12 @@ # we never ever want to fail because of an unknown type, # just return None in those cases. return None + +from logilab.common import date +_THIS_MOD_NS = globals() +for funcname in ('date_range', 'todate', 'todatetime', 'datetime2ticks', + 'days_in_month', 'days_in_year', 'previous_month', + 'next_month', 'first_day', 'last_day', 'ustrftime', + 'strptime'): + msg = '[3.6] %s has been moved to logilab.common.date' % funcname + _THIS_MOD_NS[funcname] = deprecated(msg)(getattr(date, funcname)) diff -r d4ae6e751b60 -r 716ee59d64a7 view.py --- a/view.py Wed Feb 10 16:34:15 2010 +0100 +++ b/view.py Fri Feb 26 15:36:30 2010 +0100 @@ -91,9 +91,9 @@ * the `category` attribute may be used in the interface to regroup related objects together - At instantiation time, the standard `req`, `rset`, and `cursor` - attributes are added and the `w` attribute will be set at rendering - time to a write function to use. + At instantiation time, the standard `_cw`, and `cw_rset` attributes are + added and the `w` attribute will be set at rendering time to a write + function to use. """ __registry__ = 'views' diff -r d4ae6e751b60 -r 716ee59d64a7 vregistry.py --- a/vregistry.py Wed Feb 10 16:34:15 2010 +0100 +++ b/vregistry.py Fri Feb 26 15:36:30 2010 +0100 @@ -23,7 +23,7 @@ import sys from os import listdir, stat -from os.path import dirname, join, realpath, split, isdir, exists +from os.path import dirname, join, realpath, isdir, exists from logging import getLogger from warnings import warn @@ -365,7 +365,6 @@ def initialization_completed(self): for regname, reg in self.iteritems(): - self.debug('available in registry %s: %s', regname, sorted(reg)) reg.initialization_completed() def load_file(self, filepath, modname, force_reload=False): @@ -406,7 +405,6 @@ if objname.startswith('_'): continue self._load_ancestors_then_object(module.__name__, obj) - self.debug('loaded %s', module) def _load_ancestors_then_object(self, modname, appobjectcls): """handle automatic appobject class registration: diff -r d4ae6e751b60 -r 716ee59d64a7 web/action.py --- a/web/action.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/action.py Fri Feb 26 15:36:30 2010 +0100 @@ -10,7 +10,7 @@ from cubicweb import target from cubicweb.selectors import (partial_relation_possible, match_search_state, - one_line_rset, yes) + one_line_rset) from cubicweb.appobject import AppObject diff -r d4ae6e751b60 -r 716ee59d64a7 web/application.py --- a/web/application.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/application.py Fri Feb 26 15:36:30 2010 +0100 @@ -205,6 +205,8 @@ # reopening. Is it actually a problem? self._update_last_login_time(req) args = req.form + for forminternal_key in ('__form_id', '__domid', '__errorurl'): + args.pop(forminternal_key, None) args['__message'] = req._('welcome %s !') % req.user.login if 'vid' in req.form: args['vid'] = req.form['vid'] @@ -379,7 +381,11 @@ 'eidmap': req.data.get('eidmap', {}) } req.set_session_data(req.form['__errorurl'], forminfo) - raise Redirect(req.form['__errorurl']) + # XXX form session key / __error_url should be differentiated: + # session key is 'url + #
%s' % img + return img + super(CaptchaWidget, self).render(form, field, renderer) diff -r d4ae6e751b60 -r 716ee59d64a7 web/controller.py --- a/web/controller.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/controller.py Fri Feb 26 15:36:30 2010 +0100 @@ -1,4 +1,4 @@ -"""abstract controler classe for CubicWeb web client +"""abstract controller classe for CubicWeb web client :organization: Logilab @@ -8,9 +8,6 @@ """ __docformat__ = "restructuredtext en" -import datetime - -from cubicweb import typed_eid from cubicweb.selectors import yes from cubicweb.appobject import AppObject from cubicweb.web import LOGGER, Redirect, RequestError @@ -124,6 +121,9 @@ else: self._cw.set_message(self._cw._('entity deleted')) + def validate_cache(self, view): + view.set_http_cache_headers() + self._cw.validate_cache() def reset(self): """reset form parameters and redirect to a view determinated by given diff -r d4ae6e751b60 -r 716ee59d64a7 web/data/cubicweb.css --- a/web/data/cubicweb.css Wed Feb 10 16:34:15 2010 +0100 +++ b/web/data/cubicweb.css Fri Feb 26 15:36:30 2010 +0100 @@ -392,7 +392,11 @@ color: #111100; } - +ul.boxListing a.boxMenu:hover { + background: #eeedd9 url(puce_down.png) no-repeat scroll 98% 6px; + cursor:pointer; + border-top:medium none; + } a.boxMenu { background: transparent url("puce_down.png") 98% 6px no-repeat; display: block; diff -r d4ae6e751b60 -r 716ee59d64a7 web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Wed Feb 10 16:34:15 2010 +0100 +++ b/web/data/cubicweb.edition.js Fri Feb 26 15:36:30 2010 +0100 @@ -240,12 +240,13 @@ /* * makes an AJAX request to get an inline-creation view's content * @param peid : the parent entity eid + * @param petype : the parent entity type * @param ttype : the target (inlined) entity type * @param rtype : the relation type between both entities */ -function addInlineCreationForm(peid, ttype, rtype, role, i18nctx, insertBefore) { +function addInlineCreationForm(peid, petype, ttype, rtype, role, i18nctx, insertBefore) { insertBefore = insertBefore || getNode('add' + rtype + ':' + peid + 'link').parentNode; - var d = asyncRemoteExec('inline_creation_form', peid, ttype, rtype, role, i18nctx); + var d = asyncRemoteExec('inline_creation_form', peid, petype, ttype, rtype, role, i18nctx); d.addCallback(function (response) { var dom = getDomFromResponse(response); preprocessAjaxLoad(null, dom); diff -r d4ae6e751b60 -r 716ee59d64a7 web/data/cubicweb.iprogress.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/data/cubicweb.iprogress.js Fri Feb 26 15:36:30 2010 +0100 @@ -0,0 +1,65 @@ +function ProgressBar() { + this.budget = 100; + this.todo = 100; + this.done = 100; + this.color_done = "green"; + this.color_budget = "blue"; + this.color_todo = "#cccccc"; // grey + this.height = 16; + this.middle = this.height/2; + this.radius = 4; +} + +ProgressBar.prototype.draw_one_rect = function(ctx, pos, color, fill) { + ctx.beginPath(); + ctx.lineWidth = 1; + ctx.strokeStyle = color; + if (fill) { + ctx.fillStyle = color; + ctx.fillRect(0,0,pos,this.middle*2); + } else { + ctx.lineWidth = 2; + ctx.strokeStyle = "black"; + ctx.moveTo(pos,0); + ctx.lineTo(pos,this.middle*2); + ctx.stroke(); + } +}; + +ProgressBar.prototype.draw_one_circ = function(ctx, pos, color) { + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.strokeStyle = color; + ctx.moveTo(0,this.middle); + ctx.lineTo(pos,this.middle); + ctx.arc(pos,this.middle,this.radius,0,Math.PI*2,true); + ctx.stroke(); +}; + + +ProgressBar.prototype.draw_circ = function(ctx) { + this.draw_one_circ(ctx,this.budget,this.color_budget); + this.draw_one_circ(ctx,this.todo,this.color_todo); + this.draw_one_circ(ctx,this.done,this.color_done); +}; + + +ProgressBar.prototype.draw_rect = function(ctx) { + this.draw_one_rect(ctx,this.todo,this.color_todo,true); + this.draw_one_rect(ctx,this.done,this.color_done,true); + this.draw_one_rect(ctx,this.budget,this.color_budget,false); +}; + + +function draw_progressbar(cid, done, todo, budget, color) { + var canvas = document.getElementById(cid); + if (canvas.getContext) { + var ctx = canvas.getContext("2d"); + var bar = new ProgressBar(); + bar.budget = budget; + bar.todo = todo; + bar.done = done; + bar.color_done = color; + bar.draw_rect(ctx); + } +} diff -r d4ae6e751b60 -r 716ee59d64a7 web/data/cubicweb.login.css --- a/web/data/cubicweb.login.css Wed Feb 10 16:34:15 2010 +0100 +++ b/web/data/cubicweb.login.css Fri Feb 26 15:36:30 2010 +0100 @@ -1,7 +1,7 @@ /* styles for the login popup and login form * * :organization: Logilab - * :copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. + * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr */ @@ -11,8 +11,11 @@ right: 0px; width: 26em; padding: 0px 1px 1px; + background: #E4EAD8; +} + +div#popupLoginBox label{ font-weight: bold; - background: #E4EAD8; } div#popupLoginBox div#loginContent { @@ -72,12 +75,16 @@ } #loginContent input.data { - width:12em; + width: 12em; } -input.loginButton { +.loginButton { border: 1px solid #edecd2; border-color:#edecd2 #cfceb7 #cfceb7 #edecd2; margin: 2px 0px 0px; background: #f0eff0 url("gradient-grey-up.png") left top repeat-x; } + +#loginContent .formButtonBar { + float: right; +} diff -r d4ae6e751b60 -r 716ee59d64a7 web/data/porkys.ttf Binary file web/data/porkys.ttf has changed diff -r d4ae6e751b60 -r 716ee59d64a7 web/facet.py --- a/web/facet.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/facet.py Fri Feb 26 15:36:30 2010 +0100 @@ -15,7 +15,7 @@ from logilab.mtconverter import xml_escape from logilab.common.graph import has_path from logilab.common.decorators import cached -from logilab.common.date import datetime2ticks, ustrftime +from logilab.common.date import datetime2ticks from logilab.common.compat import all from rql import parse, nodes @@ -267,26 +267,18 @@ context = '' needs_update = False start_unfolded = True + cw_rset = None # ensure facets have a cw_rset attribute - def __init__(self, req, rset=None, rqlst=None, filtered_variable=None, + def __init__(self, req, rqlst=None, filtered_variable=None, **kwargs): - super(AbstractFacet, self).__init__(req, rset=rset, **kwargs) - assert rset is not None or rqlst is not None + super(AbstractFacet, self).__init__(req, **kwargs) + assert rqlst is not None assert filtered_variable - # facet retreived using `object_by_id` from an ajax call - if rset is None: - self.init_from_form(rqlst=rqlst) - # facet retreived from `select` using the result set to filter - else: - self.init_from_rset() + # take care: facet may be retreived using `object_by_id` from an ajax call + # or from `select` using the result set to filter + self.rqlst = rqlst self.filtered_variable = filtered_variable - def init_from_rset(self): - self.rqlst = self.cw_rset.syntax_tree().children[0] - - def init_from_form(self, rqlst): - self.rqlst = rqlst - @property def operator(self): # OR between selected values by default @@ -521,6 +513,9 @@ def get_widget(self): """return the widget instance to use to display this facet""" values = set(value for _, value in self.vocabulary() if value is not None) + # Rset with entities (the facet is selected) but without values + if len(values) == 0: + return None return self.wdgclass(self, min(values), max(values)) def infvalue(self): diff -r d4ae6e751b60 -r 716ee59d64a7 web/form.py --- a/web/form.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/form.py Fri Feb 26 15:36:30 2010 +0100 @@ -14,8 +14,7 @@ from cubicweb.appobject import AppObject from cubicweb.view import NOINDEX, NOFOLLOW -from cubicweb import tags -from cubicweb.web import stdmsgs, httpcache, formfields +from cubicweb.web import httpcache, formfields, controller class FormViewMixIn(object): @@ -69,12 +68,43 @@ __metaclass__ = metafieldsform __registry__ = 'forms' + internal_fields = ('__errorurl',) + controller.NAV_FORM_PARAMETERS + parent_form = None force_session_key = None + domid = 'form' + copy_nav_params = False - def __init__(self, req, rset, **kwargs): - super(Form, self).__init__(req, rset=rset, **kwargs) - self.restore_previous_post(self.session_key()) + def __init__(self, req, rset=None, row=None, col=None, + submitmsg=None, mainform=True, **kwargs): + super(Form, self).__init__(req, rset=rset, row=row, col=col) + self.fields = list(self.__class__._fields_) + if mainform: + self.add_hidden(u'__form_id', kwargs.pop('formvid', self.__regid__)) + for key, val in kwargs.iteritems(): + if key in controller.NAV_FORM_PARAMETERS: + self.add_hidden(key, val) + elif key == 'redirect_path': + self.add_hidden(u'__redirectpath', val) + elif hasattr(self.__class__, key) and not key[0] == '_': + setattr(self, key, val) + else: + self.cw_extra_kwargs[key] = val + # skip other parameters, usually given for selection + # (else write a custom class to handle them) + if mainform: + self.add_hidden(u'__errorurl', self.session_key()) + self.add_hidden(u'__domid', self.domid) + self.restore_previous_post(self.session_key()) + # XXX why do we need two different variables (mainform and copy_nav_params ?) + if self.copy_nav_params: + for param in controller.NAV_FORM_PARAMETERS: + if not param in kwargs: + value = req.form.get(param) + if value: + self.add_hidden(param, value) + if submitmsg is not None: + self.add_hidden(u'__message', submitmsg) @property def root_form(self): diff -r d4ae6e751b60 -r 716ee59d64a7 web/formfields.py --- a/web/formfields.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/formfields.py Fri Feb 26 15:36:30 2010 +0100 @@ -91,7 +91,10 @@ optional fieldset to which this field belongs to :order: key used by automatic forms to sort fields - + :ignore_req_params: + when true, this field won't consider value potentialy specified using + request's form parameters (eg you won't be able to specify a value using for + instance url like http://mywebsite.com/form?field=value) """ # default widget associated to this class of fields. May be overriden per # instance @@ -113,6 +116,7 @@ order = None value = _MARKER fallback_on_none_attribute = False + ignore_req_params = False def __init__(self, name=None, label=_MARKER, widget=None, **kwargs): for key, val in kwargs.items(): @@ -348,9 +352,9 @@ def process_form_value(self, form): """process posted form and return correctly typed value""" try: - return form.formvalues[self] + return form.formvalues[(self, form)] except KeyError: - value = form.formvalues[self] = self._process_form_value(form) + value = form.formvalues[(self, form)] = self._process_form_value(form) return value def _process_form_value(self, form): @@ -413,12 +417,17 @@ class PasswordField(StringField): widget = fw.PasswordInput + def form_init(self, form): + if self.eidparam and form.edited_entity.has_eid(): + # see below: value is probably set but we can't retreive it. Ensure + # the field isn't show as a required field on modification + self.required = False def typed_value(self, form, load_bytes=False): if self.eidparam: # no way to fetch actual password value with cw if form.edited_entity.has_eid(): - return INTERNAL_FIELD_VALUE + return '' return self.initial_typed_value(form, load_bytes) return super(PasswordField, self).typed_value(form, load_bytes) @@ -591,7 +600,7 @@ if data: encoding = self.encoding(form) try: - form.formvalues[self] = unicode(data.getvalue(), encoding) + form.formvalues[(self, form)] = unicode(data.getvalue(), encoding) except UnicodeError: pass else: @@ -811,11 +820,23 @@ for field in form.root_form.fields_by_name('__linkto'): if field.value in searchedvalues: form.root_form.remove_field(field) - form.formvalues[self] = value + form.formvalues[(self, form)] = value def format_single_value(self, req, value): return value + def process_form_value(self, form): + """process posted form and return correctly typed value""" + try: + return form.formvalues[(self, form)] + except KeyError: + value = self._process_form_value(form) + # if value is None, there are some remaining pending fields, we'll + # have to recompute this later -> don't cache in formvalues + if value is not None: + form.formvalues[(self, form)] = value + return value + def _process_form_value(self, form): """process posted form and return correctly typed value""" widget = self.get_widget(form) @@ -826,7 +847,6 @@ values = (values,) eids = set() for eid in values: - # XXX 'not eid' for AutoCompletionWidget, deal with this in the widget if not eid or eid == INTERNAL_FIELD_VALUE: continue typed_eid = form.actual_eid(eid) diff -r d4ae6e751b60 -r 716ee59d64a7 web/formwidgets.py --- a/web/formwidgets.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/formwidgets.py Fri Feb 26 15:36:30 2010 +0100 @@ -17,6 +17,7 @@ from cubicweb import tags, uilib from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError + class FieldWidget(object): """abstract widget class""" # javascript / css files required by the widget @@ -58,21 +59,6 @@ def _render(self, form, field, renderer): raise NotImplementedError() - def typed_value(self, form, field): - """return field's *typed* value specified in: - 3. extra form values given to render() - 4. field's typed value - """ - qname = field.input_name(form) - for key in (field, qname): - try: - return form.formvalues[key] - except KeyError: - continue - if field.name != qname and field.name in form.formvalues: - return form.formvalues[field.name] - return field.typed_value(form) - def format_value(self, form, field, value): return field.format_value(form._cw, value) @@ -96,15 +82,20 @@ return self.values(form, field), attrs def values(self, form, field): - qname = field.input_name(form, self.suffix) - if qname in form.form_previous_values: - values = form.form_previous_values[qname] - elif qname in form._cw.form: - values = form._cw.form[qname] - elif field.name != qname and field.name in form._cw.form: - # compat: accept attr=value in req.form to specify value of attr-subject - values = form._cw.form[field.name] - else: + values = None + if not field.ignore_req_params: + qname = field.input_name(form, self.suffix) + # value from a previous post that has raised a validation error + if qname in form.form_previous_values: + values = form.form_previous_values[qname] + # value specified using form parameters + elif qname in form._cw.form: + values = form._cw.form[qname] + elif field.name != qname and field.name in form._cw.form: + # XXX compat: accept attr=value in req.form to specify value of + # attr-subject + values = form._cw.form[field.name] + if values is None: values = self.typed_value(form, field) if values != INTERNAL_FIELD_VALUE: values = self.format_value(form, field, values) @@ -112,6 +103,21 @@ values = (values,) return values + def typed_value(self, form, field): + """return field's *typed* value specified in: + 3. extra form values given to render() + 4. field's typed value + """ + qname = field.input_name(form) + for key in ((field, form), qname): + try: + return form.formvalues[key] + except KeyError: + continue + if field.name != qname and field.name in form.formvalues: + return form.formvalues[field.name] + return field.typed_value(form) + def process_field_data(self, form, field): posted = form._cw.form val = posted.get(field.input_name(form, self.suffix)) @@ -163,9 +169,9 @@ assert self.suffix is None, 'suffix not supported' values, attrs = self.values_and_attributes(form, field) assert len(values) == 1 - id = attrs.pop('id') + domid = attrs.pop('id') inputs = [tags.input(name=field.input_name(form), - value=values[0], type=self.type, id=id, **attrs), + value=values[0], type=self.type, id=domid, **attrs), '
', tags.input(name=field.input_name(form, '-confirm'), value=values[0], type=self.type, **attrs), @@ -317,7 +323,7 @@ iattrs['checked'] = u'checked' tag = tags.input(name=field.input_name(form, self.suffix), type=self.type, value=value, **iattrs) - options.append(tag + label) + options.append(u'%s %s' % (tag, label)) return sep.join(options) @@ -520,9 +526,9 @@ def init_ajax_attributes(attrs, wdgtype, loadtype=u'auto'): try: - attrs['klass'] += u' widget' + attrs['class'] += u' widget' except KeyError: - attrs['klass'] = u'widget' + attrs['class'] = u'widget' attrs.setdefault('cubicweb:wdgtype', wdgtype) attrs.setdefault('cubicweb:loadtype', loadtype) @@ -638,7 +644,7 @@ self.value = '' self.onclick = onclick self.cwaction = cwaction - self.attrs.setdefault('klass', 'validateButton') + self.attrs.setdefault('class', 'validateButton') def render(self, form, field=None, renderer=None): label = form._cw._(self.label) diff -r d4ae6e751b60 -r 716ee59d64a7 web/htmlwidgets.py --- a/web/htmlwidgets.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/htmlwidgets.py Fri Feb 26 15:36:30 2010 +0100 @@ -15,7 +15,7 @@ from logilab.mtconverter import xml_escape from cubicweb.utils import UStringIO -from cubicweb.uilib import toggle_action, limitsize, htmlescape +from cubicweb.uilib import toggle_action, htmlescape from cubicweb.web import jsonize # XXX HTMLWidgets should have access to req (for datadir / static urls, @@ -280,7 +280,7 @@ if value is None: return u'' elif isinstance(value, int): - return u'%09d'%value + return u'%09d' % value else: return unicode(value) @@ -316,7 +316,7 @@ self.w(u'%s' % (' '.join(attrs), column.name)) self.w(u'') self.w(u'') - for rowindex, row in enumerate(self.model.get_rows()): + for rowindex in xrange(len(self.model.get_rows())): klass = (rowindex%2==1) and 'odd' or 'even' self.w(u'' % (klass, self.highlight)) for column, sortvalue in self.itercols(rowindex): @@ -373,14 +373,10 @@ budget = self.budget if budget == 0: pourcent = 100 - todo_pourcent = 0 else: pourcent = done*100./budget - todo_pourcent = min(todo*100./budget, 100-pourcent) - bar_pourcent = pourcent if pourcent > 100.1: color = 'red' - bar_pourcent = 100 elif todo+done > self.red_threshold*budget: color = 'red' elif todo+done > self.orange_threshold*budget: diff -r d4ae6e751b60 -r 716ee59d64a7 web/request.py --- a/web/request.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/request.py Fri Feb 26 15:36:30 2010 +0100 @@ -135,7 +135,7 @@ self.user.properties['ui.language']) self.set_language(lang) return - except KeyError, ex: + except KeyError: pass if vreg.config['language-negociation']: # 2. http negociated language @@ -671,7 +671,7 @@ try: scorekey, scoreval = score.split('=') if scorekey == 'q': # XXX 'level' - score = float(score[2:]) # remove 'q=' + score = float(scoreval) except ValueError: continue values.append((score, value)) diff -r d4ae6e751b60 -r 716ee59d64a7 web/test/unittest_form.py --- a/web/test/unittest_form.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/test/unittest_form.py Fri Feb 26 15:36:30 2010 +0100 @@ -207,9 +207,9 @@ upassword = PasswordField(eidparam=True, role='subject') form = PFForm(self.req, redirect_path='perdu.com', entity=self.entity) self.assertTextEquals(self._render_entity_field('upassword', form), - ''' + '''
- +   confirm password''' % {'eid': self.entity.eid}) diff -r d4ae6e751b60 -r 716ee59d64a7 web/test/unittest_pdf.py --- a/web/test/unittest_pdf.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/test/unittest_pdf.py Fri Feb 26 15:36:30 2010 +0100 @@ -27,6 +27,8 @@ fopproc = sub(['/usr/bin/fop', foptmp.name, pdftmp.name]) fopproc.wait() del foptmp + if fopproc.returncode: + self.skip('fop returned status %s' % fopproc.returncode) pdftmp.seek(0) # a bit superstitious reference = open(osp.join(DATADIR, 'sample1.pdf'), 'r').read() output = pdftmp.read() diff -r d4ae6e751b60 -r 716ee59d64a7 web/test/unittest_urlrewrite.py --- a/web/test/unittest_urlrewrite.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/test/unittest_urlrewrite.py Fri Feb 26 15:36:30 2010 +0100 @@ -39,6 +39,7 @@ ('/notfound', dict(vid='404')), ('/error', dict(vid='error')), ('/sparql', dict(vid='sparql')), + ('/processinfo', dict(vid='processinfo')), ('/schema/([^/]+?)/?$', {'rql': r'Any X WHERE X is CWEType, X name "\1"', 'vid': 'primary'}), ('/add/([^/]+?)/?$' , dict(vid='creation', etype=r'\1')), ('/doc/images/(.+?)/?$', dict(fid='\\1', vid='wdocimages')), diff -r d4ae6e751b60 -r 716ee59d64a7 web/test/unittest_views_editforms.py --- a/web/test/unittest_views_editforms.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/test/unittest_views_editforms.py Fri Feb 26 15:36:30 2010 +0100 @@ -16,7 +16,11 @@ AFS = uicfg.autoform_section def rbc(entity, formtype, section): - return [(rschema.type, x) for rschema, tschemas, x in AFS.relations_by_section(entity, formtype, section)] + if section in ('attributes', 'metadata', 'hidden'): + permission = 'update' + else: + permission = 'add' + return [(rschema.type, x) for rschema, tschemas, x in AFS.relations_by_section(entity, formtype, section, permission)] class AutomaticEntityFormTC(CubicWebTC): @@ -31,9 +35,6 @@ def test_cwuser_relations_by_category(self): - #for (rtype, role, stype, otype), tag in AEF.rcategories._tagdefs.items(): - # if rtype == 'tags': - # print rtype, role, stype, otype, ':', tag e = self.vreg['etypes'].etype_class('CWUser')(self.request()) # see custom configuration in views.cwuser self.assertEquals(rbc(e, 'main', 'attributes'), @@ -59,7 +60,11 @@ ('owned_by', 'subject'), ('bookmarked_by', 'object'), ]) - self.assertListEquals(rbc(e, 'main', 'relations'), + # XXX skip 'tags' relation here and in the hidden category because + # of some test interdependancy when pytest is launched on whole cw + # (appears here while expected in hidden + self.assertListEquals([x for x in rbc(e, 'main', 'relations') + if x != ('tags', 'object')], [('primary_email', 'subject'), ('custom_workflow', 'subject'), ('connait', 'subject'), @@ -69,19 +74,16 @@ [('use_email', 'subject'), ]) # owned_by is defined both as subject and object relations on CWUser - self.assertListEquals(rbc(e, 'main', 'hidden'), - [('in_state', 'subject'), - ('is', 'subject'), - ('is_instance_of', 'subject'), - ('has_text', 'subject'), - ('identity', 'subject'), - ('tags', 'object'), - ('for_user', 'object'), - ('created_by', 'object'), - ('wf_info_for', 'object'), - ('owned_by', 'object'), - ('identity', 'object'), - ]) + self.assertListEquals(sorted(x for x in rbc(e, 'main', 'hidden') + if x != ('tags', 'object')), + sorted([('has_text', 'subject'), + ('identity', 'subject'), + ('for_user', 'object'), + ('created_by', 'object'), + ('wf_info_for', 'object'), + ('owned_by', 'object'), + ('identity', 'object'), + ])) def test_inlined_view(self): self.failUnless('main_inlined' in AFS.etype_get('CWUser', 'use_email', 'subject', 'EmailAddress')) @@ -122,10 +124,8 @@ ('connait', 'object') ]) self.assertListEquals(rbc(e, 'main', 'hidden'), - [('is', 'subject'), - ('has_text', 'subject'), + [('has_text', 'subject'), ('identity', 'subject'), - ('is_instance_of', 'subject'), ('identity', 'object'), ]) @@ -167,14 +167,14 @@ geid = self.execute('CWGroup X LIMIT 1')[0][0] rset = self.execute('CWUser X LIMIT 1') self.view('inline-edition', rset, row=0, col=0, rtype='in_group', - peid=geid, role='object', template=None, i18nctx='', - pform=MOCKPFORM).source + peid=geid, role='object', i18nctx='', pform=MOCKPFORM, + template=None).source def test_automatic_inline_creation_formview(self): geid = self.execute('CWGroup X LIMIT 1')[0][0] self.view('inline-creation', None, etype='CWUser', rtype='in_group', - peid=geid, template=None, i18nctx='', role='object', - pform=MOCKPFORM).source + peid=geid, petype='CWGroup', i18nctx='', role='object', pform=MOCKPFORM, + template=None) MOCKPFORM = mock_object(form_previous_values={}, form_valerror=None) diff -r d4ae6e751b60 -r 716ee59d64a7 web/test/unittest_viewselector.py --- a/web/test/unittest_viewselector.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/test/unittest_viewselector.py Fri Feb 26 15:36:30 2010 +0100 @@ -67,9 +67,8 @@ req = self.request() self.assertListEqual(self.pviews(req, None), [('changelog', wdoc.ChangeLogView), - ('debug', debug.DebugView), ('index', startup.IndexView), - ('info', management.ProcessInformationView), + ('info', debug.ProcessInformationView), ('manage', startup.ManageView), ('owl', owl.OWLView), ('propertiesform', cwproperties.CWPropertiesForm), diff -r d4ae6e751b60 -r 716ee59d64a7 web/uicfg.py --- a/web/uicfg.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/uicfg.py Fri Feb 26 15:36:30 2010 +0100 @@ -112,7 +112,6 @@ from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet, RelationTagsDict, register_rtag, _ensure_str_key) from cubicweb.schema import META_RTYPES -from cubicweb.web import formwidgets # primary view configuration ################################################## @@ -251,7 +250,7 @@ _allowed_form_types = ('main', 'inlined', 'muledit') _allowed_values = {'main': ('attributes', 'inlined', 'relations', 'metadata', 'hidden'), - 'inlined': ('attributes', 'hidden'), + 'inlined': ('attributes', 'inlined', 'hidden'), 'muledit': ('attributes', 'hidden'), } @@ -312,6 +311,10 @@ formsections.add('%s_%s' % (formtype, section)) def tag_relation(self, key, formtype, section=None): + if isinstance(formtype, tuple): + for ftype in formtype: + self.tag_relation(key, ftype, section) + return if section is None: tag = formtype for formtype, section in self.bw_tag_map[tag].iteritems(): @@ -343,8 +346,8 @@ # overriden to avoid recomputing done in parent classes return self._tagdefs.get(key, ()) - def relations_by_section(self, entity, formtype, section, - permission=None, strict=False): + def relations_by_section(self, entity, formtype, section, permission, + strict=False): """return a list of (relation schema, target schemas, role) for the given entity matching categories and permission. @@ -359,52 +362,61 @@ else: eid = None strict = False + if permission == 'update': + assert section in ('attributes', 'metadata', 'hidden') + relpermission = 'add' + else: + assert section not in ('attributes', 'metadata', 'hidden') + relpermission = permission cw = entity._cw for rschema, targetschemas, role in eschema.relation_definitions(True): - # check category first, potentially lower cost than checking - # permission which may imply rql queries _targetschemas = [] for tschema in targetschemas: + # check section's tag first, potentially lower cost than + # checking permission which may imply rql queries if not tag in self.etype_get(eschema, rschema, role, tschema): continue rdef = rschema.role_rdef(eschema, tschema, role) - if permission is not None and \ - not ((not strict and rdef.has_local_role(permission)) or - rdef.has_perm(cw, permission, fromeid=eid)): - continue + if rschema.final: + if not rdef.has_perm(cw, permission, eid=eid, + creating=eid is None): + continue + elif strict or not rdef.has_local_role(relpermission): + if role == 'subject': + if not rdef.has_perm(cw, relpermission, fromeid=eid): + continue + elif role == 'object': + if not rdef.has_perm(cw, relpermission, toeid=eid): + continue _targetschemas.append(tschema) if not _targetschemas: continue targetschemas = _targetschemas - if permission is not None: - rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0]) - # tag allowing to hijack the permission machinery when - # permission is not verifiable until the entity is actually - # created... - if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role): - yield (rschema, targetschemas, role) + rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0]) + # XXX tag allowing to hijack the permission machinery when + # permission is not verifiable until the entity is actually + # created... + if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role): + yield (rschema, targetschemas, role) + continue + if not rschema.final and role == 'subject': + # on relation with cardinality 1 or ?, we need delete perm as well + # if the relation is already set + if (relpermission == 'add' + and rdef.role_cardinality(role) in '1?' + and eid and entity.related(rschema.type, role) + and not rdef.has_perm(cw, 'delete', fromeid=eid, + toeid=entity.related(rschema.type, role)[0][0])): continue - if rschema.final: - if not rdef.has_perm(cw, permission, fromeid=eid): - continue - elif role == 'subject': - # on relation with cardinality 1 or ?, we need delete perm as well - # if the relation is already set - if (permission == 'add' - and rdef.role_cardinality(role) in '1?' - and eid and entity.related(rschema.type, role) - and not rdef.has_perm(cw, 'delete', fromeid=eid, - toeid=entity.related(rschema.type, role)[0][0])): - continue - elif role == 'object': - # on relation with cardinality 1 or ?, we need delete perm as well - # if the relation is already set - if (permission == 'add' - and rdef.role_cardinality(role) in '1?' - and eid and entity.related(rschema.type, role) - and not rdef.has_perm(cw, 'delete', toeid=eid, - fromeid=entity.related(rschema.type, role)[0][0])): - continue + elif role == 'object': + # on relation with cardinality 1 or ?, we need delete perm as well + # if the relation is already set + if (relpermission == 'add' + and rdef.role_cardinality(role) in '1?' + and eid and entity.related(rschema.type, role) + and not rdef.has_perm(cw, 'delete', toeid=eid, + fromeid=entity.related(rschema.type, role)[0][0])): + continue yield (rschema, targetschemas, role) autoform_section = AutoformSectionRelationTags('autoform_section') @@ -442,8 +454,8 @@ class AutoformIsInlined(RelationTags): """XXX for < 3.6 bw compat""" def tag_relation(self, key, tag): - warn('autoform_is_inlined rtag is deprecated, use autoform_section ' - 'with inlined formtype and "attributes" or "hidden" section', + warn('autoform_is_inlined is deprecated, use autoform_section ' + 'with formtype="inlined", section="attributes" or section="hidden"', DeprecationWarning, stacklevel=3) section = tag and 'inlined' or 'hidden' autoform_section.tag_relation(key, 'main', section) diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/actions.py --- a/web/views/actions.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/actions.py Fri Feb 26 15:36:30 2010 +0100 @@ -31,7 +31,7 @@ # if user has no update right but it can modify some relation, # display action anyway form = entity._cw.vreg['forms'].select('edition', entity._cw, - entity=entity) + entity=entity) for dummy in form.editable_relations(): return 1 try: diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/autoform.py --- a/web/views/autoform.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/autoform.py Fri Feb 26 15:36:30 2010 +0100 @@ -85,7 +85,10 @@ def __init__(self, *args, **kwargs): for attr in self._select_attrs: - setattr(self, attr, kwargs.pop(attr, None)) + # don't pop attributes from kwargs, so the end-up in + # self.cw_extra_kwargs which is then passed to the edition form (see + # the .form method) + setattr(self, attr, kwargs.get(attr)) super(InlineEntityEditionFormView, self).__init__(*args, **kwargs) def _entity(self): @@ -166,13 +169,15 @@ :attr etype: the entity type being created in the inline form """ __regid__ = 'inline-creation' - __select__ = (match_kwargs('peid', 'rtype') + __select__ = (match_kwargs('peid', 'petype', 'rtype') & specified_etype_implements('Any')) + _select_attrs = InlineEntityEditionFormView._select_attrs + ('petype',) @property def removejs(self): entity = self._entity() - card = entity.e_schema.rdef(self.rtype, neg_role(self.role)).role_cardinality(self.role) + rdef = entity.e_schema.rdef(self.rtype, neg_role(self.role), self.petype) + card= rdef.role_cardinality(self.role) # when one is adding an inline entity for a relation of a single card, # the 'add a new xxx' link disappears. If the user then cancel the addition, # we have to make this link appears back. This is done by giving add new link @@ -204,7 +209,7 @@ :attr card: the cardinality of the relation according to role of `peid` """ __regid__ = 'inline-addnew-link' - __select__ = (match_kwargs('peid', 'rtype') + __select__ = (match_kwargs('peid', 'petype', 'rtype') & specified_etype_implements('Any')) _select_attrs = InlineEntityCreationFormView._select_attrs + ('card',) @@ -215,8 +220,8 @@ divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid) self.w(u'
' % divid) - js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % ( - self.peid, self.etype, self.rtype, self.role, i18nctx) + js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s', '%s')" % ( + self.peid, self.petype, self.etype, self.rtype, self.role, i18nctx) if self.pform.should_hide_add_new_relation_link(self.rtype, self.card): js = "toggleVisibility('%s'); %s" % (divid, js) __ = self._cw.pgettext @@ -384,7 +389,11 @@ related = [] if entity.has_eid(): rset = entity.related(rschema, role, limit=form.related_limit) - if rschema.has_perm(form._cw, 'delete'): + if role == 'subject': + haspermkwargs = {'fromeid': entity.eid} + else: + haspermkwargs = {'toeid': entity.eid} + if rschema.has_perm(form._cw, 'delete', **haspermkwargs): toggleable_rel_link_func = toggleable_relation_link else: toggleable_rel_link_func = lambda x, y, z: u'' @@ -420,9 +429,6 @@ % (pendingid, entity.eid) rset = form._cw.eid_rset(reid) eview = form._cw.view('text', rset, row=0) - # XXX find a clean way to handle baskets - if rset.description[0][0] == 'Basket': - eview = '%s (%s)' % (eview, display_name(form._cw, 'Basket')) yield rtype, pendingid, jscall, label, reid, eview @@ -454,8 +460,6 @@ options.append('' % (self._cw._('select a'), etypes)) options += self._get_select_options(entity, rschema, role) options += self._get_search_options(entity, rschema, role, targettypes) - if 'Basket' in self._cw.vreg.schema: # XXX - options += self._get_basket_options(entity, rschema, role, targettypes) relname, role = self._cw.form.get('relation').rsplit('_', 1) return u"""\
@@ -502,37 +506,6 @@ xml_escape(url), _('Search for'), eschema.display_name(self._cw)))) return [o for l, o in sorted(options)] - # XXX move this out - def _get_basket_options(self, entity, rschema, role, targettypes): - options = [] - rtype = rschema.type - _ = self._cw._ - for basketeid, basketname in self._get_basket_links(self._cw.user.eid, - role, targettypes): - optionid = relation_id(entity.eid, rtype, role, basketeid) - options.append('' % ( - optionid, basketeid, _('link to each item in'), xml_escape(basketname))) - return options - - def _get_basket_links(self, ueid, role, targettypes): - targettypes = set(targettypes) - for basketeid, basketname, elements in self._get_basket_info(ueid): - baskettypes = elements.column_types(0) - # if every elements in the basket can be attached to the - # edited entity - if baskettypes & targettypes: - yield basketeid, basketname - - def _get_basket_info(self, ueid): - basketref = [] - basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N' - basketresultset = self._cw.execute(basketrql, {'x': ueid}, 'x') - for result in basketresultset: - basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s' - rset = self._cw.execute(basketitemsrql, {'x': result[0]}, 'x') - basketref.append((result[0], result[1], rset)) - return basketref - # The automatic entity form #################################################### @@ -595,18 +568,18 @@ except f.FieldNotFound: # meta attribute such as _format continue + if self.fieldsets_in_order: + fsio = list(self.fieldsets_in_order) + else: + fsio = [None] + self.fieldsets_in_order = fsio + # add fields for relation whose target should have an inline form + for formview in self.inlined_form_views(): + field = self._inlined_form_view_field(formview) + self.fields.append(field) + if not field.fieldset in fsio: + fsio.append(field.fieldset) if self.formtype == 'main': - if self.fieldsets_in_order: - fsio = list(self.fieldsets_in_order) - else: - fsio = [None] - self.fieldsets_in_order = fsio - # add fields for relation whose target should have an inline form - for formview in self.inlined_form_views(): - field = self._inlined_form_view_field(formview) - self.fields.append(field) - if not field.fieldset in fsio: - fsio.append(field.fieldset) # add the generic relation field if necessary if entity.has_eid() and ( self.display_fields is None or @@ -684,9 +657,11 @@ """return a list of (relation schema, role) to edit for the entity""" if self.display_fields is not None: return self.display_fields + if self.edited_entity.has_eid() and not self.edited_entity.has_perm('update'): + return [] # XXX we should simply put eid in the generated section, no? return [(rtype, role) for rtype, _, role in self._relations_by_section( - 'attributes', strict=strict) if rtype != 'eid'] + 'attributes', 'update', strict) if rtype != 'eid'] def editable_relations(self): """return a sorted list of (relation's label, relation'schema, role) for @@ -735,8 +710,9 @@ if self.should_display_add_new_relation_link(rschema, formviews, card): addnewlink = self._cw.vreg['views'].select( 'inline-addnew-link', self._cw, - etype=ttype, rtype=rschema, role=role, - peid=self.edited_entity.eid, pform=self, card=card) + etype=ttype, rtype=rschema, role=role, card=card, + peid=self.edited_entity.eid, + petype=self.edited_entity.e_schema, pform=self) formviews.append(addnewlink) allformviews += formviews return allformviews @@ -796,7 +772,9 @@ """ yield self._cw.vreg['views'].select('inline-creation', self._cw, etype=ttype, rtype=rschema, role=role, - peid=self.edited_entity.eid, pform=self) + peid=self.edited_entity.eid, + petype=self.edited_entity.e_schema, + pform=self) ## default form ui configuration ############################################## diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/basecontrollers.py Fri Feb 26 15:36:30 2010 +0100 @@ -172,7 +172,9 @@ def _validation_error(req, ex): req.cnx.rollback() - forminfo = req.get_session_data(req.form.get('__errorurl'), pop=True) + # XXX necessary to remove existant validation error? + # imo (syt), it's not necessary + req.get_session_data(req.form.get('__errorurl'), pop=True) foreid = ex.entity eidmap = req.data.get('eidmap', {}) for var, eid in eidmap.items(): @@ -351,6 +353,7 @@ except NoSelectableObject: vid = req.form.get('fallbackvid', 'noresult') view = self._cw.vreg['views'].select(vid, req, rset=rset) + self.validate_cache(view) return self._call_view(view) @xhtmlize @@ -383,10 +386,10 @@ @check_pageid @xhtmlize - def js_inline_creation_form(self, peid, ttype, rtype, role, i18nctx): + def js_inline_creation_form(self, peid, petype, ttype, rtype, role, i18nctx): view = self._cw.vreg['views'].select('inline-creation', self._cw, - etype=ttype, peid=peid, rtype=rtype, - role=role) + etype=ttype, rtype=rtype, role=role, + peid=peid, petype=petype) return self._call_view(view, i18nctx=i18nctx) @jsonize @@ -399,7 +402,7 @@ @xhtmlize def js_reledit_form(self): - args = dict((x,self._cw.form[x]) + args = dict((x, self._cw.form[x]) for x in frozenset(('rtype', 'role', 'reload', 'landing_zone'))) entity = self._cw.entity_from_eid(int(self._cw.form['eid'])) # note: default is reserved in js land diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/basetemplates.py --- a/web/views/basetemplates.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/basetemplates.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,12 +9,15 @@ __docformat__ = "restructuredtext en" from logilab.mtconverter import xml_escape +from logilab.common.deprecation import class_renamed from cubicweb.appobject import objectify_selector from cubicweb.selectors import match_kwargs from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW from cubicweb.utils import UStringIO, can_do_pdf_conversion from cubicweb.schema import display_name +from cubicweb.web import formfields as ff, formwidgets as fw +from cubicweb.web.views import forms # main templates ############################################################## @@ -389,14 +392,14 @@ self.w(u'\n') self.w(u'\n') self.wview('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden', - title=False, message=False) + title=False, showmessage=False) def state_header(self): state = self._cw.search_state if state[0] == 'normal': return _ = self._cw._ - value = self.view('oneline', self._cw.eid_rset(state[1][1])) + value = self._cw.view('oneline', self._cw.eid_rset(state[1][1])) msg = ' '.join((_("searching for"), display_name(self._cw, state[1][3]), _("to associate with"), value, @@ -460,14 +463,29 @@ self.w(u'
') -class LogFormTemplate(View): +class LogForm(forms.FieldsForm): + __regid__ = 'logform' + domid = 'loginForm' + needs_css = ('cubicweb.login.css',) + # XXX have to recall fields name since python is mangling __login/__password + __login = ff.StringField('__login', widget=fw.TextInput({'class': 'data'})) + __password = ff.StringField('__password', label=_('password'), + widget=fw.PasswordSingleInput({'class': 'data'})) + form_buttons = [fw.SubmitButton(label=_('log in'), + attrs={'class': 'loginButton'})] + + @property + def action(self): + return xml_escape(login_form_url(self._cw)) + + +class LogFormView(View): __regid__ = 'logform' __select__ = match_kwargs('id', 'klass') title = 'log in' - def call(self, id, klass, title=True, message=True): - self._cw.add_css('cubicweb.login.css') + def call(self, id, klass, title=True, showmessage=True): self.w(u'
' % (id, klass)) if title: stitle = self._cw.property_value('ui.site-title') @@ -477,52 +495,30 @@ stitle = u' ' self.w(u'
%s
' % stitle) self.w(u'
\n') - - if message: - self.display_message() - if self._cw.vreg.config['auth-mode'] == 'http': - # HTTP authentication - pass - else: + if showmessage and self._cw.message: + self.w(u'
%s
\n' % self._cw.message) + if self._cw.vreg.config['auth-mode'] != 'http': # Cookie authentication self.login_form(id) self.w(u'
\n') - def display_message(self): - message = self._cw.message - if message: - self.w(u'
%s
\n' % message) + def login_form(self, id): + cw = self._cw + form = cw.vreg['forms'].select('logform', cw) + if cw.vreg.config['allow-email-login']: + label = cw._('login or email') + else: + label = cw._('login') + form.field_by_name('__login').label = label + self.w(form.render(table_class='', display_progress_div=False)) + cw.html_headers.add_onload('jQuery("#__login:visible").focus()') - def login_form(self, id): - _ = self._cw._ - # XXX turn into a form - self.w(u'\n' - % xml_escape(login_form_url(self._cw.vreg.config, self._cw))) - self.w(u'\n') - self.add_fields() - self.w(u'\n') - self.w(u'' % _('log in')) - self.w(u'\n') - self.w(u'
 \n
\n') - self.w(u'\n') - self._cw.html_headers.add_onload('jQuery("#__login:visible").focus()') +LogFormTemplate = class_renamed('LogFormTemplate', LogFormView) - def add_fields(self): - msg = (self._cw.vreg.config['allow-email-login'] and _('login or email')) or _('login') - self.add_field('__login', msg, 'text') - self.add_field('__password', self._cw._('password'), 'password') - - def add_field(self, name, label, inputtype): - self.w(u'\n') - self.w(u'' % (name, label)) - self.w(u'\n' % - (name, name, inputtype)) - self.w(u'\n') - - -def login_form_url(config, req): +def login_form_url(req): if req.https: return req.url() - if config.get('https-url'): - return req.url().replace(req.base_url(), config['https-url']) + httpsurl = req.vreg.config.get('https-url') + if httpsurl: + return req.url().replace(req.base_url(), httpsurl) return req.url() diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/boxes.py --- a/web/views/boxes.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/boxes.py Fri Feb 26 15:36:30 2010 +0100 @@ -24,7 +24,6 @@ from cubicweb.view import EntityView from cubicweb.schema import display_name from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem -from cubicweb.web import uicfg from cubicweb.web.box import BoxTemplate diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/calendar.py --- a/web/views/calendar.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/calendar.py Fri Feb 26 15:36:30 2010 +0100 @@ -155,7 +155,9 @@ last_day_of_month = date(year + 1, 1, 1) - timedelta(1) else: last_day_of_month = date(year, month + 1, 1) - timedelta(1) - lastday = last_day_of_month + timedelta(6 - last_day_of_month.weekday()) + # date range exclude last day so we should at least add one day, hence + # the 7 + lastday = last_day_of_month + timedelta(7 - last_day_of_month.weekday()) month_dates = list(date_range(firstday, lastday)) dates = {} task_max = 0 @@ -250,7 +252,6 @@ # output header self.w(u'%s%s%s%s%s%s%s' % tuple(self._cw._(day) for day in WEEKDAYS)) - # build calendar for mdate, task_rows in zip(month_dates, days): if mdate.weekday() == 0: diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/cwuser.py --- a/web/views/cwuser.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/cwuser.py Fri Feb 26 15:36:30 2010 +0100 @@ -12,17 +12,16 @@ from cubicweb.selectors import one_line_rset, implements, match_user_groups from cubicweb.view import EntityView from cubicweb.web import action, uicfg -from cubicweb.web.views import primary -uicfg.primaryview_section.tag_attribute(('CWUser', 'login'), 'hidden') - -uicfg.primaryview_section.tag_attribute(('CWGroup', 'name'), 'hidden') -uicfg.primaryview_section.tag_subject_of(('CWGroup', 'read_permission', '*'), 'relations') -uicfg.primaryview_section.tag_subject_of(('CWGroup', 'add_permission', '*'), 'relations') -uicfg.primaryview_section.tag_subject_of(('CWGroup', 'delete_permission', '*'), 'relations') -uicfg.primaryview_section.tag_subject_of(('CWGroup', 'update_permission', '*'), 'relations') -uicfg.primaryview_section.tag_object_of(('*', 'in_group', 'CWGroup'), 'relations') -uicfg.primaryview_section.tag_object_of(('*', 'require_group', 'CWGroup'), 'relations') +_pvs = uicfg.primaryview_section +_pvs.tag_attribute(('CWUser', 'login'), 'hidden') +_pvs.tag_attribute(('CWGroup', 'name'), 'hidden') +_pvs.tag_subject_of(('CWGroup', 'read_permission', '*'), 'relations') +_pvs.tag_subject_of(('CWGroup', 'add_permission', '*'), 'relations') +_pvs.tag_subject_of(('CWGroup', 'delete_permission', '*'), 'relations') +_pvs.tag_subject_of(('CWGroup', 'update_permission', '*'), 'relations') +_pvs.tag_object_of(('*', 'in_group', 'CWGroup'), 'relations') +_pvs.tag_object_of(('*', 'require_group', 'CWGroup'), 'relations') class UserPreferencesEntityAction(action.Action): __regid__ = 'prefs' diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/debug.py --- a/web/views/debug.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/debug.py Fri Feb 26 15:36:30 2010 +0100 @@ -25,41 +25,89 @@ w(u'') -class DebugView(StartupView): - __regid__ = 'debug' + +class ProcessInformationView(StartupView): + __regid__ = 'info' __select__ = none_rset() & match_user_groups('managers') - title = _('server debug information') + + title = _('server information') def call(self, **kwargs): """display server information""" + req = self._cw + dtformat = req.property_value('ui.datetime-format') + _ = req._ w = self.w - w(u'

server sessions

') - sessions = self._cw.cnx._repo._sessions.items() + # generic instance information + w(u'

%s

' % _('Instance')) + w(u'') + w(u'' % ( + _('config type'), self._cw.vreg.config.name)) + w(u'' % ( + _('config mode'), self._cw.vreg.config.mode)) + w(u'' % ( + _('instance home'), self._cw.vreg.config.apphome)) + w(u'
%s%s
%s%s
%s%s
') + vcconf = req.vreg.config.vc_config() + w(u'

%s

' % _('versions configuration')) + w(u'') + w(u'' % ( + 'CubicWeb', vcconf.get('cubicweb', _('no version information')))) + for cube in sorted(self._cw.vreg.config.cubes()): + cubeversion = vcconf.get(cube, _('no version information')) + w(u'' % ( + cube, cubeversion)) + w(u'
%s%s
%s%s
') + # repository information + repo = req.vreg.config.repository(None) + w(u'

%s

' % _('Repository')) + w(u'

%s

' % _('resources usage')) + w(u'') + stats = repo.stats() + for element in sorted(stats): + w(u'' + % (element, xml_escape(unicode(stats[element])), + element.endswith('percent') and '%' or '' )) + w(u'
%s%s %s
') + if req.cnx._cnxtype == 'inmemory': + w(u'

%s

' % _('opened sessions')) + sessions = repo._sessions.values() + if sessions: + w(u'
    ') + for session in sessions: + w(u'
  • %s (%s: %s)
    ' % ( + xml_escape(unicode(session)), + _('last usage'), + strftime(dtformat, localtime(session.timestamp)))) + dict_to_html(w, session.data) + w(u'
  • ') + w(u'
') + else: + w(u'

%s

' % _('no repository sessions found')) + # web server information + w(u'

%s

' % _('Web server')) + w(u'') + w(u'' % ( + _('base url'), req.base_url())) + w(u'' % ( + _('data directory url'), req.datadir_url)) + w(u'
%s%s
%s%s
') + from cubicweb.web.application import SESSION_MANAGER + sessions = SESSION_MANAGER.current_sessions() + w(u'

%s

' % _('opened web sessions')) if sessions: w(u'
    ') - for sid, session in sessions: - w(u'
  • %s (last usage: %s)
    ' % (xml_escape(str(session)), - strftime('%Y-%m-%d %H:%M:%S', - localtime(session.timestamp)))) + for session in sessions: + w(u'
  • %s (%s: %s)
    ' % ( + session.sessionid, + _('last usage'), + strftime(dtformat, localtime(session.last_usage_time)))) dict_to_html(w, session.data) w(u'
  • ') w(u'
') else: - w(u'

no server sessions found

') - from cubicweb.web.application import SESSION_MANAGER - w(u'

web sessions

') - sessions = SESSION_MANAGER.current_sessions() - if sessions: - w(u'
    ') - for session in sessions: - w(u'
  • %s (last usage: %s)
    ' % (session.sessionid, - strftime('%Y-%m-%d %H:%M:%S', - localtime(session.last_usage_time)))) - dict_to_html(w, session.data) - w(u'
  • ') - w(u'
') - else: - w(u'

no web sessions found

') + w(u'

%s

' % _('no web sessions found')) + class RegistryView(StartupView): @@ -74,7 +122,7 @@ self.w(u'

%s

\n' % ' - '.join('%s' % (key, key) for key in keys)) for key in keys: - self.w(u'

%s

' % (key,key)) + self.w(u'

%s

' % (key, key)) items = self._cw.vreg[key].items() if items: self.w(u'') diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/editcontroller.py --- a/web/views/editcontroller.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/editcontroller.py Fri Feb 26 15:36:30 2010 +0100 @@ -159,8 +159,9 @@ field = form.field_by_name(name, role, eschema=entity.e_schema) else: field = form.field_by_name(name, role) - if field.has_been_modified(form): - self.handle_formfield(form, field, rqlquery) + for field in field.actual_fields(form): + if field.has_been_modified(form): + self.handle_formfield(form, field, rqlquery) if self.errors: errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors) raise ValidationError(entity.eid, errors) diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/editforms.py --- a/web/views/editforms.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/editforms.py Fri Feb 26 15:36:30 2010 +0100 @@ -17,7 +17,7 @@ from logilab.common.decorators import cached from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity, - specified_etype_implements, yes) + specified_etype_implements, implements, yes) from cubicweb.view import EntityView from cubicweb import tags from cubicweb.web import uicfg, stdmsgs, eid_param, \ @@ -30,15 +30,13 @@ class DeleteConfForm(forms.CompositeForm): __regid__ = 'deleteconf' - __select__ = non_final_entity() + # XXX non_final_entity does not implement eclass_selector + __select__ = implements('Any') domid = 'deleteconf' copy_nav_params = True form_buttons = [fw.Button(stdmsgs.BUTTON_DELETE, cwaction='delete'), fw.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] - @property - def action(self): - return self._cw.build_url('edit') def __init__(self, *args, **kwargs): super(DeleteConfForm, self).__init__(*args, **kwargs) @@ -98,12 +96,10 @@ def render_form(self, entity): """fetch and render the form""" self.form_title(entity) - form = self._cw.vreg['forms'].select('edition', self._cw, rset=entity.cw_rset, - row=entity.cw_row, col=entity.cw_col, - entity=entity, + form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity, submitmsg=self.submited_message()) self.init_form(form, entity) - self.w(form.render(formvid=u'edition')) + self.w(form.render()) def init_form(self, form, entity): """customize your form before rendering here""" @@ -236,18 +232,18 @@ """a view to edit multiple entities of the same type the first column should be the eid """ - #self.form_title(entity) - form = self._cw.vreg['forms'].select(self.__regid__, self._cw, - rset=self.cw_rset, - copy_nav_params=True) # XXX overriding formvid (eg __form_id) necessary to make work edition: # the edit controller try to select the form with no rset but # entity=entity, and use this form to edit the entity. So we want # edition form there but specifying formvid may have other undesired - # side effect. Maybe we should provide another variable optinally + # side effect. Maybe we should provide another variable optionally # telling which form the edit controller should select (eg difffers # between html generation / post handling form) - self.w(form.render(formvid='edition')) + form = self._cw.vreg['forms'].select(self.__regid__, self._cw, + rset=self.cw_rset, + copy_nav_params=True, + formvid='edition') + self.w(form.render()) # click and edit handling ('reledit') ########################################## @@ -307,7 +303,7 @@ # compute value, checking perms, build form if rschema.final: form = self._build_form(entity, rtype, role, 'base', default, reload, lzone) - if not self.should_edit_attribute(entity, rschema, role, form): + if not self.should_edit_attribute(entity, rschema, form): self.w(entity.printable_value(rtype)) return value = entity.printable_value(rtype) or default @@ -330,14 +326,17 @@ self.relation_form(lzone, value, form, self._build_renderer(entity, rtype, role)) - def should_edit_attribute(self, entity, rschema, role, form): + def should_edit_attribute(self, entity, rschema, form): rtype = str(rschema) - ttype = rschema.targets(entity.__regid__, role)[0] - afs = uicfg.autoform_section.etype_get(entity.__regid__, rtype, role, ttype) + rdef = entity.e_schema.rdef(rtype) + afs = uicfg.autoform_section.etype_get( + entity.__regid__, rtype, 'subject', rdef.object) if 'main_hidden' in afs or not entity.has_perm('update'): return False + if not rdef.has_perm(self._cw, 'update', eid=entity.eid): + return False try: - form.field_by_name(rtype, role, entity.e_schema) + form.field_by_name(rtype, 'subject', entity.e_schema) except FieldNotFound: return False return True diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/editviews.py --- a/web/views/editviews.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/editviews.py Fri Feb 26 15:36:30 2010 +0100 @@ -12,9 +12,10 @@ from logilab.mtconverter import xml_escape from cubicweb import typed_eid -from cubicweb.view import EntityView +from cubicweb.view import EntityView, StartupView from cubicweb.selectors import (one_line_rset, non_final_entity, match_search_state) +from cubicweb.web import httpcache, captcha from cubicweb.web.views import baseviews, linksearch_select_url @@ -93,3 +94,18 @@ self.w(entity.view('reledit', rtype=rtype)) else: super(EditableFinalView, self).cell_call(row, col, props) + + +class CaptchaView(StartupView): + __regid__ = 'captcha' + + http_cache_manager = httpcache.NoHTTPCacheManager + binary = True + templatable = False + content_type = 'image/jpg' + + def call(self): + text, data = captcha.captcha(self._cw.vreg.config['captcha-font-file'], + self._cw.vreg.config['captcha-font-size']) + self._cw.set_session_data('captcha', text) + self.w(data.read()) diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/facets.py --- a/web/views/facets.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/facets.py Fri Feb 26 15:36:30 2010 +0100 @@ -69,37 +69,38 @@ rset, vid, divid, paginate = self._get_context(view) if rset.rowcount < 2: # XXX done by selectors, though maybe necessary when rset has been hijacked return + rqlst = self.cw_rset.syntax_tree() + # union not yet supported + if len(rqlst.children) != 1: + return () + rqlst = rqlst.copy() + req.vreg.rqlhelper.annotate(rqlst) + mainvar, baserql = prepare_facets_rqlst(rqlst, rset.args) + widgets = [] + for facet in self.get_facets(rset, rqlst.children[0], mainvar): + if facet.cw_propval('visible'): + wdg = facet.get_widget() + if wdg is not None: + widgets.append(wdg) + if not widgets: + return if vid is None: vid = req.form.get('vid') - rqlst = rset.syntax_tree() - rqlst.save_state() - try: - mainvar, baserql = prepare_facets_rqlst(rqlst, rset.args) - widgets = [] - for facet in self.get_facets(rset, mainvar): - if facet.cw_propval('visible'): - wdg = facet.get_widget() - if wdg is not None: - widgets.append(wdg) - if not widgets: - return - if self.bk_linkbox_template: - self.display_bookmark_link(rset) - w = self.w - w(u'' % ( - divid, xml_escape(dumps([divid, vid, paginate, self.facetargs()])))) - w(u'
') - hiddens = {'facets': ','.join(wdg.facet.__regid__ for wdg in widgets), - 'baserql': baserql} - for param in ('subvid', 'vtitle'): - if param in req.form: - hiddens[param] = req.form[param] - filter_hiddens(w, **hiddens) - for wdg in widgets: - wdg.render(w=self.w) - w(u'
\n\n') - finally: - rqlst.recover() + if self.bk_linkbox_template: + self.display_bookmark_link(rset) + w = self.w + w(u'' % ( + divid, xml_escape(dumps([divid, vid, paginate, self.facetargs()])))) + w(u'
') + hiddens = {'facets': ','.join(wdg.facet.__regid__ for wdg in widgets), + 'baserql': baserql} + for param in ('subvid', 'vtitle'): + if param in req.form: + hiddens[param] = req.form[param] + filter_hiddens(w, **hiddens) + for wdg in widgets: + wdg.render(w=self.w) + w(u'
\n\n') def display_bookmark_link(self, rset): eschema = self._cw.vreg.schema.eschema('Bookmark') @@ -115,10 +116,10 @@ self._cw._('bookmark this search')) self.w(self.bk_linkbox_template % bk_link) - def get_facets(self, rset, mainvar): - return self._cw.vreg['facets'].poss_visible_objects(self._cw, rset=rset, - context='facetbox', - filtered_variable=mainvar) + def get_facets(self, rset, rqlst, mainvar): + return self._cw.vreg['facets'].poss_visible_objects( + self._cw, rset=rset, rqlst=rqlst, + context='facetbox', filtered_variable=mainvar) # facets ###################################################################### diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/formrenderers.py --- a/web/views/formrenderers.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/formrenderers.py Fri Feb 26 15:36:30 2010 +0100 @@ -85,14 +85,10 @@ if self.display_progress_div: w(u'
%s
' % self._cw._('validating...')) w(u'
') - w(tags.input(type=u'hidden', name=u'__form_id', - value=values.get('formvid', form.__regid__))) - if form.redirect_path: - w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path)) self.render_fields(w, form, values) self.render_buttons(w, form) w(u'
') - w(u'') + w(self.close_form(form, values)) errormsg = self.error_message(form) if errormsg: data.insert(0, errormsg) @@ -171,6 +167,13 @@ tag += ' cubicweb:target="%s"' % xml_escape(form.cwtarget) return tag + '>' + def close_form(self, form, values): + """seem dump but important for consistency w/ close form, and necessary + for form renderers overriding open_form to use something else or more than + and + """ + return '' + def render_fields(self, w, form, values): fields = self._render_hidden_fields(w, form) if fields: @@ -373,10 +376,6 @@ attrs_fs_label += '
' return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values) - def _render_fields(self, fields, w, form): - if not form.edited_entity.has_eid() or form.edited_entity.has_perm('update'): - super(EntityFormRenderer, self)._render_fields(fields, w, form) - def render_buttons(self, w, form): if len(form.form_buttons) == 3: w("""
diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/forms.py --- a/web/views/forms.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/forms.py Fri Feb 26 15:36:30 2010 +0100 @@ -15,9 +15,7 @@ from cubicweb import typed_eid from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset -from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param from cubicweb.web import uicfg, form, formwidgets as fwdgs -from cubicweb.web.controller import NAV_FORM_PARAMETERS from cubicweb.web.formfields import StringField, relvoc_unrelated, guess_field @@ -58,54 +56,20 @@ """ __regid__ = 'base' - internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS # attributes overrideable by subclasses or through __init__ needs_js = ('cubicweb.ajax.js', 'cubicweb.edition.js',) needs_css = ('cubicweb.form.css',) - domid = 'form' action = None onsubmit = "return freezeFormButtons('%(domid)s');" cssclass = None cssstyle = None cwtarget = None redirect_path = None - copy_nav_params = False form_buttons = None form_renderer_id = 'default' fieldsets_in_order = None - def __init__(self, req, rset=None, row=None, col=None, - submitmsg=None, mainform=True, - **kwargs): - super(FieldsForm, self).__init__(req, rset=rset, row=row, col=col) - self.fields = list(self.__class__._fields_) - for key, val in kwargs.items(): - if key in NAV_FORM_PARAMETERS: - self.add_hidden(key, val) - elif hasattr(self.__class__, key) and not key[0] == '_': - setattr(self, key, val) - else: - self.cw_extra_kwargs[key] = val - # skip other parameters, usually given for selection - # (else write a custom class to handle them) - if mainform: - self.add_hidden('__errorurl', self.session_key()) - self.add_hidden('__domid', self.domid) - self.restore_previous_post(self.session_key()) - - # XXX why do we need two different variables (mainform and copy_nav_params ?) - if self.copy_nav_params: - for param in NAV_FORM_PARAMETERS: - if not param in kwargs: - value = req.form.get(param) - if value: - self.add_hidden(param, value) - if submitmsg is not None: - self.add_hidden('__message', submitmsg) - if 'domid' in kwargs:# session key changed - self.restore_previous_post(self.session_key()) - @property def needs_multipart(self): """true if the form needs enctype=multipart/form-data""" @@ -113,6 +77,7 @@ def add_hidden(self, name, value=None, **kwargs): """add an hidden field to the form""" + kwargs.setdefault('ignore_req_params', True) kwargs.setdefault('widget', fwdgs.HiddenInput) field = StringField(name=name, value=value, **kwargs) if 'id' in kwargs: @@ -145,7 +110,7 @@ def default_renderer(self): return self._cw.vreg['formrenderers'].select( self.form_renderer_id, self._cw, - rset=self.cw_rset, row=self.cw_row, col=self.cw_col) + rset=self.cw_rset, row=self.cw_row, col=self.cw_col or 0) formvalues = None def build_context(self, formvalues=None): @@ -178,6 +143,7 @@ renderer = self.default_renderer() return renderer.render(self, values) + _AFF = uicfg.autoform_field _AFF_KWARGS = uicfg.autoform_field_kwargs @@ -248,6 +214,8 @@ # entity primary view if self._cw.json_request and self.edited_entity.has_eid(): return '%s#%s' % (self.edited_entity.absolute_url(), self.domid) + # XXX we should not consider some url parameters that may lead to + # different url after a validation error return '%s#%s' % (self._cw.url(), self.domid) def build_context(self, formvalues=None): @@ -258,14 +226,16 @@ for field in self.fields: if field.eidparam: edited.add(field.role_name()) - self.add_hidden('_cw_edited_fields', u','.join(edited), - eidparam=True) + self.add_hidden('_cw_edited_fields', u','.join(edited), eidparam=True) def default_renderer(self): return self._cw.vreg['formrenderers'].select( self.form_renderer_id, self._cw, rset=self.cw_rset, row=self.cw_row, col=self.cw_col, entity=self.edited_entity) + def should_display_add_new_relation_link(self, rschema, existant, card): + return False + # controller side method (eg POST reception handling) def actual_eid(self, eid): @@ -284,9 +254,6 @@ def editable_relations(self): return () - def should_display_add_new_relation_link(self, rschema, existant, card): - return False - @deprecated('[3.6] use cw.web.formfields.relvoc_unrelated function') def subject_relation_vocabulary(self, rtype, limit=None): """defaut vocabulary method for the given relation, looking for diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/management.py --- a/web/views/management.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/management.py Fri Feb 26 15:36:30 2010 +0100 @@ -12,7 +12,7 @@ from logilab.mtconverter import xml_escape from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user -from cubicweb.view import AnyRsetView, StartupView, EntityView +from cubicweb.view import AnyRsetView, StartupView, EntityView, View from cubicweb.uilib import html_traceback, rest_traceback from cubicweb.web import formwidgets as wdgs from cubicweb.web.formfields import guess_field @@ -273,63 +273,17 @@ return binfo -class ProcessInformationView(StartupView): - __regid__ = 'info' +class CwStats(View): + """A textual stats output for monitoring tools such as munin """ + + __regid__ = 'processinfo' + content_type = 'text/txt' + templatable = False __select__ = none_rset() & match_user_groups('users', 'managers') - title = _('server information') - - def call(self, **kwargs): - """display server information""" - vcconf = self._cw.vreg.config.vc_config() - req = self._cw - _ = req._ - # display main information - self.w(u'

%s

' % _('Application')) - self.w(u'
') - self.w(u'' % ( - 'CubicWeb', vcconf.get('cubicweb', _('no version information')))) - for pkg in self._cw.vreg.config.cubes(): - pkgversion = vcconf.get(pkg, _('no version information')) - self.w(u'' % ( - pkg, pkgversion)) - self.w(u'' % ( - _('home'), self._cw.vreg.config.apphome)) - self.w(u'' % ( - _('base url'), req.base_url())) - self.w(u'' % ( - _('data directory url'), req.datadir_url)) - self.w(u'
%s%s
%s%s
%s%s
%s%s
%s%s
') - self.w(u'
') - # environment and request and server information - try: - # need to remove our adapter and then modpython-apache wrapper... - env = req._areq._req.subprocess_env - except AttributeError: - return - self.w(u'

%s

' % _('Environment')) - self.w(u'') - for attr in env.keys(): - self.w(u'' - % (attr, xml_escape(env[attr]))) - self.w(u'
%s%s
') - self.w(u'

%s

' % _('Request')) - self.w(u'') - for attr in ('filename', 'form', 'hostname', 'main', 'method', - 'path_info', 'protocol', - 'search_state', 'the_request', 'unparsed_uri', 'uri'): - val = getattr(req, attr) - self.w(u'' - % (attr, xml_escape(val))) - self.w(u'
%s%s
') - server = req.server - self.w(u'

%s

' % _('Server')) - self.w(u'') - for attr in dir(server): - val = getattr(server, attr) - if attr.startswith('_') or callable(val): - continue - self.w(u'' - % (attr, xml_escape(val))) - self.w(u'
%s%s
') - + def call(self): + stats = self._cw.vreg.config.repository(None).stats() + results = [] + for element in stats: + results.append(u'%s %s' % (element, stats[element])) + self.w(u'\n'.join(results)) diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/navigation.py --- a/web/views/navigation.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/navigation.py Fri Feb 26 15:36:30 2010 +0100 @@ -59,7 +59,6 @@ nb_chars = 5 def display_func(self, rset, col, attrname): - req = self._cw if attrname is not None: def index_display(row): if not rset[row][col]: # outer join @@ -159,7 +158,7 @@ context = 'navbottom' order = 10 def call(self, view=None): - entity = self.cw_rset.get_entity(0,0) + entity = self.cw_rset.get_entity(0, 0) previous = entity.previous_entity() next = entity.next_entity() if previous or next: diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/old_calendar.py --- a/web/views/old_calendar.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/old_calendar.py Fri Feb 26 15:36:30 2010 +0100 @@ -40,7 +40,6 @@ def nav_header(self, date, smallshift=3, bigshift=9): """prints shortcut links to go to previous/next steps (month|week)""" - prev1 = next1 = prev2 = nex2 = date prev1 = previous_month(date, smallshift) next1 = next_month(date, smallshift) prev2 = previous_month(date, bigshift) @@ -110,7 +109,7 @@ self._cw.add_css('cubicweb.calendar.css') schedule = {} for row in xrange(len(self.cw_rset.rows)): - entity = self.cw_rset.get_entity(row,0) + entity = self.cw_rset.get_entity(row, 0) infos = u'
' infos += self._cw.view(itemvid, self.cw_rset, row=row) infos += u'
' @@ -137,11 +136,11 @@ end = last_day(next_month(day, shift)) return begin, end - def _build_ampm_cells(self, daynum, events): + def _build_ampm_cells(self, events): """create a view without any hourly details. - :param daynum: day of the built cell - :param events: dictionnary with all events classified by hours""" + :param events: dictionnary with all events classified by hours + """ # split events according am/pm am_events = [event for e_time, e_list in events.iteritems() if 0 <= e_time.hour < 12 @@ -318,7 +317,7 @@ day = first_day + timedelta(daynum) events = schedule.get(day) if events: - current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events)) + current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(events)) else: current_row.append((AMPM_DAY % (daynum+1), AMPM_EMPTY % ("amCell", "am"), @@ -387,7 +386,7 @@ def format_day_events(self, day, events): if events: - self.w(u'\n'.join(self._build_ampm_cells(day, events))) + self.w(u'\n'.join(self._build_ampm_cells(events))) else: self.w(u'%s %s'% (AMPM_EMPTY % ("amCell", "am"), AMPM_EMPTY % ("pmCell", "pm"))) @@ -409,7 +408,7 @@ day = first_day + timedelta(daynum) events = schedule.get(day) if events: - current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events)) + current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(events)) else: current_row.append((AMPM_DAY % (daynum+1), AMPM_EMPTY % ("amCell", "am"), diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/plots.py --- a/web/views/plots.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/plots.py Fri Feb 26 15:36:30 2010 +0100 @@ -7,16 +7,12 @@ """ __docformat__ = "restructuredtext en" -import os -import time - from simplejson import dumps -from logilab.common import flatten from logilab.common.date import datetime2ticks from logilab.mtconverter import xml_escape -from cubicweb.utils import make_uid, UStringIO +from cubicweb.utils import UStringIO from cubicweb.appobject import objectify_selector from cubicweb.selectors import multi_columns_rset from cubicweb.web.views import baseviews @@ -98,7 +94,7 @@ # datetime labels on tooltips is to insert an additional column # cf. function onPlotHover in cubicweb.flot.js if self.timemode: - plot = [(datetime2ticks(x), y, datetime2ticks(x)) for x,y in plot] + plot = [(datetime2ticks(x), y, datetime2ticks(x)) for x, y in plot] return dumps(plot) def _render(self, req, width=500, height=400): diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/primary.py --- a/web/views/primary.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/primary.py Fri Feb 26 15:36:30 2010 +0100 @@ -46,8 +46,8 @@ self.render_entity(entity) def render_entity(self, entity): + self.render_entity_toolbox(entity) self.render_entity_title(entity) - self.render_entity_toolbox(entity) # entity's attributes and relations, excluding meta data # if the entity isn't meta itself if self.is_primary(): @@ -161,7 +161,7 @@ try: label, rset, vid, dispctrl = box except ValueError: - warn('box views should now be defined as a 4-uple (label, rset, vid, dispctrl), ' + warn('[3.5] box views should now be defined as a 4-uple (label, rset, vid, dispctrl), ' 'please update %s' % self.__class__.__name__, DeprecationWarning) label, rset, vid = box diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/schema.py --- a/web/views/schema.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/schema.py Fri Feb 26 15:36:30 2010 +0100 @@ -380,7 +380,6 @@ def _generate(self, tmpfile): """display global schema information""" - print 'skipedtypes', skip_types(self._cw) visitor = FullSchemaVisitor(self._cw, self._cw.vreg.schema, skiptypes=skip_types(self._cw)) s2d.schema2dot(outputfile=tmpfile, visitor=visitor) diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/sparql.py --- a/web/views/sparql.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/sparql.py Fri Feb 26 15:36:30 2010 +0100 @@ -1,11 +1,11 @@ """SPARQL integration :organization: Logilab -:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ -v__docformat__ = "restructuredtext en" +__docformat__ = "restructuredtext en" import rql from yams import xy @@ -15,9 +15,9 @@ from cubicweb.view import StartupView, AnyRsetView from cubicweb.web import Redirect, form, formfields, formwidgets as fwdgs -from cubicweb.web.views import forms, urlrewrite +from cubicweb.web.views import forms try: - from cubicweb.spa2rql import Sparql2rqlTranslator + from cubicweb.spa2rql import Sparql2rqlTranslator, UnsupportedQuery except ImportError: # fyzz not available (only a recommends) Sparql2rqlTranslator = None @@ -45,7 +45,7 @@ if sparql: try: qinfo = Sparql2rqlTranslator(self._cw.vreg.schema).translate(sparql) - except rql.TypeResolverException, ex: + except rql.TypeResolverException: self.w(self._cw._('can not resolve entity types:') + u' ' + unicode('ex')) except UnsupportedQuery: self.w(self._cw._('we are not yet ready to handle this query')) diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/tableview.py --- a/web/views/tableview.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/tableview.py Fri Feb 26 15:36:30 2010 +0100 @@ -33,13 +33,13 @@ # union not yet supported if len(rqlst.children) != 1: return () - rqlst.save_state() + rqlst = rqlst.copy() + self._cw.vreg.rqlhelper.annotate(rqlst) mainvar, baserql = prepare_facets_rqlst(rqlst, self.cw_rset.args) wdgs = [facet.get_widget() for facet in self._cw.vreg['facets'].poss_visible_objects( - self._cw, rset=self.cw_rset, context='tablefilter', + self._cw, rset=self.cw_rset, rqlst=rqlst.children[0], context='tablefilter', filtered_variable=mainvar)] wdgs = [wdg for wdg in wdgs if wdg is not None] - rqlst.recover() if wdgs: self._generate_form(divid, baserql, wdgs, hidden, vidargs={'displaycols': displaycols, diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/tabs.py --- a/web/views/tabs.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/tabs.py Fri Feb 26 15:36:30 2010 +0100 @@ -8,6 +8,7 @@ __docformat__ = "restructuredtext en" +from logilab.common.deprecation import class_renamed from logilab.mtconverter import xml_escape from cubicweb import NoSelectableObject, role @@ -47,7 +48,10 @@ tabid or vid, xml_escape(self._cw.build_url('json', **urlparams)))) if show_spinbox: w(u'%s' - % (tabid or vid, self._cw._('loading'))) + % (tabid or vid, self._cw._('(loading ...)'))) + w(u'' + % (tabid or vid, xml_escape(self._cw.build_url(**urlparams)), xml_escape('%s (%s)') % + (tabid or vid, self._cw._('follow this link if javascript is deactivated')))) w(u'
') self._prepare_bindings(tabid or vid, reloadable) @@ -187,11 +191,11 @@ def cell_call(self, row, col): entity = self.cw_rset.complete_entity(row, col) + self.render_entity_toolbox(entity) self.render_entity_title(entity) - # XXX uncomment this in 3.6 - #self.render_entity_toolbox(entity) self.render_tabs(self.tabs, self.default_tab, entity) -TabedPrimaryView = TabbedPrimaryView # XXX deprecate that typo! + +TabedPrimaryView = class_renamed('TabedPrimaryView', TabbedPrimaryView) class PrimaryTab(primary.PrimaryView): __regid__ = 'main_tab' diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/treeview.py --- a/web/views/treeview.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/treeview.py Fri Feb 26 15:36:30 2010 +0100 @@ -9,7 +9,6 @@ import simplejson as json -from logilab.common.decorators import monkeypatch from logilab.mtconverter import xml_escape from cubicweb.utils import make_uid from cubicweb.interfaces import ITree @@ -59,8 +58,12 @@ self._init_headers(treeid, toplevel_thru_ajax) ulid = ' id="tree-%s"' % treeid self.w(u'' % (ulid, self.css_classes)) + # XXX force sorting on x.sortvalue() (which return dc_title by default) + # we need proper ITree & co specification to avoid this. + # (pb when type ambiguity at the other side of the tree relation, + # unability to provide generic implementation on eg Folder...) for i, entity in enumerate(sorted(self.cw_rset.entities(), - key=lambda x: x.dc_title())): + key=lambda x: x.sortvalue())): if i+1 < len(self.cw_rset): morekwargs['is_last'] = False else: @@ -111,7 +114,6 @@ def cell_call(self, row, col, vid='oneline', treeid=None, **morekwargs): assert treeid is not None - entity = self.cw_rset.get_entity(row, col) itemview = self._cw.view(vid, self.cw_rset, row=row, col=col) last_class = morekwargs['is_last'] and ' class="last"' or '' self.w(u'%s' % (last_class, itemview)) @@ -173,8 +175,8 @@ if treeid.startswith('throw_away'): divtail = '' else: - divtail = """ onclick="asyncRemoteExec('node_clicked', '%s', '%s')" """ %\ - (treeid, entity.eid) + divtail = """ onclick="asyncRemoteExec('node_clicked', '%s', '%s')" """ % ( + treeid, entity.eid) w(u'
' % (u' '.join(divclasses), divtail)) # add empty
    because jquery's treeview plugin checks for diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/urlrewrite.py --- a/web/views/urlrewrite.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/urlrewrite.py Fri Feb 26 15:36:30 2010 +0100 @@ -83,6 +83,7 @@ ('/notfound', dict(vid='404')), ('/error', dict(vid='error')), ('/sparql', dict(vid='sparql')), + ('/processinfo', dict(vid='processinfo')), # XXX should be case insensitive as 'create', but I would like to find another way than # relying on the etype_selector (rgx('/schema/([^/]+?)/?'), dict(vid='primary', rql=r'Any X WHERE X is CWEType, X name "\1"')), diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/workflow.py --- a/web/views/workflow.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/workflow.py Fri Feb 26 15:36:30 2010 +0100 @@ -69,7 +69,7 @@ entity.view('oneline'))) msg = self.req._('status will change from %(st1)s to %(st2)s') % { 'st1': entity.printable_state, - 'st2': self._cw._(transition.destination().name)} + 'st2': self._cw._(transition.destination(entity).name)} self.w(u'

    %s

    \n' % msg) self.w(form.render()) @@ -318,7 +318,8 @@ for transition in self.entity.reverse_transition_of: for incomingstate in transition.reverse_allowed_transition: yield incomingstate.eid, transition.eid, transition - yield transition.eid, transition.destination().eid, transition + for outgoingstate in transition.potential_destinations(): + yield transition.eid, outgoingstate.eid, transition class WorkflowImageView(TmpFileViewMixin, view.EntityView): diff -r d4ae6e751b60 -r 716ee59d64a7 web/views/xbel.py --- a/web/views/xbel.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/views/xbel.py Fri Feb 26 15:36:30 2010 +0100 @@ -26,8 +26,6 @@ def call(self): """display a list of entities by calling their view""" - title = self.page_title() - url = self._cw.build_url(rql=self._cw.form.get('rql', '')) self.w(u'\n' % self._cw.encoding) self.w(u'') self.w(u'') diff -r d4ae6e751b60 -r 716ee59d64a7 web/webconfig.py --- a/web/webconfig.py Wed Feb 10 16:34:15 2010 +0100 +++ b/web/webconfig.py Fri Feb 26 15:36:30 2010 +0100 @@ -11,7 +11,6 @@ import os from os.path import join, exists, split -from logilab.common.configuration import Method from logilab.common.decorators import cached from cubicweb.toolsutils import read_config @@ -176,6 +175,22 @@ 'help': 'print the traceback on the error page when an error occured', 'group': 'web', 'inputlevel': 2, }), + + ('captcha-font-file', + {'type' : 'string', + 'default': join(CubicWebConfiguration.shared_dir(), 'data', 'porkys.ttf'), + 'help': 'True type font to use for captcha image generation (you \ +must have the python imaging library installed to use captcha)', + 'group': 'web', 'inputlevel': 2, + }), + ('captcha-font-size', + {'type' : 'int', + 'default': 25, + 'help': 'Font size to use for captcha image generation (you must \ +have the python imaging library installed to use captcha)', + 'group': 'web', 'inputlevel': 2, + }), + )) def fckeditor_installed(self): diff -r d4ae6e751b60 -r 716ee59d64a7 wsgi/handler.py --- a/wsgi/handler.py Wed Feb 10 16:34:15 2010 +0100 +++ b/wsgi/handler.py Fri Feb 26 15:36:30 2010 +0100 @@ -8,9 +8,9 @@ __docformat__ = "restructuredtext en" -from cubicweb import ObjectNotFound, AuthenticationError +from cubicweb import AuthenticationError from cubicweb.web import (NotFound, Redirect, DirectResponse, StatusResponse, - ExplicitLogin) + ExplicitLogin) from cubicweb.web.application import CubicWebPublisher from cubicweb.wsgi.request import CubicWebWsgiRequest