# HG changeset patch # User Sylvain Thénault # Date 1253269239 -7200 # Node ID c4c07aab1c3968881619b612462bddb060352c62 # Parent cfa77453e742887a0de128482a8af04f63a08af4# Parent d6ae24439beecc4ccf9b58bfaeb0a5ed0ce71e2a backport old head diff -r d6ae24439bee -r c4c07aab1c39 .hgtags --- a/.hgtags Thu Sep 10 08:03:18 2009 +0200 +++ b/.hgtags Fri Sep 18 12:20:39 2009 +0200 @@ -64,3 +64,13 @@ 52dba800ca4d4b82c47f3befb824bd91ef015368 cubicweb-debian-version-3.4.6-1 0e549b299f0b357837ea620c561aa843f46de17a cubicweb-version-3.4.7 ebb92e62eb040a070deb1f2d2434734cfac3af01 cubicweb-debian-version-3.4.7-1 +ba43e084e8841f62c3b4c2449b26a7546233e5fb cubicweb-version-3.4.8 +97273eeaaead11c0f422dc5a4fe2d4f14fc6a2dd cubicweb-debian-version-3.4.8-1 +e916f1e856c83aced0fe73f7ae9068e37edcc38c cubicweb-version-3.4.9 +24ea70f19a48cce60248ab18695925755009bcb8 cubicweb-debian-version-3.4.9-1 +f3d2adf483320d7726136433a41c57b130cbdc15 cubicweb-version-3.4.11 +635a25031f4abdd89c44d17f5d2b0d0d43914511 cubicweb-debian-version-3.4.11-1 +70c0dd1c3b7d747c3a268396a7f79d9a7a3340e6 cubicweb-version-3.5.0 +7e5d0ae8d2026c77f12ab512a4cde9911dcd8896 cubicweb-debian-version-3.5.0-1 +77ed72f3c2602bf300929f8863447653ce1beb0c cubicweb-version-3.5.1 +f476cecd46904f215bd29249ded8508d8f5634d7 cubicweb-debian-version-3.5.1-1 diff -r d6ae24439bee -r c4c07aab1c39 __pkginfo__.py --- a/__pkginfo__.py Thu Sep 10 08:03:18 2009 +0200 +++ b/__pkginfo__.py Fri Sep 18 12:20:39 2009 +0200 @@ -7,7 +7,7 @@ distname = "cubicweb" modname = "cubicweb" -numversion = (3, 5, 0) +numversion = (3, 5, 1) version = '.'.join(str(num) for num in numversion) license = 'LGPL v2' diff -r d6ae24439bee -r c4c07aab1c39 common/i18n.py --- a/common/i18n.py Thu Sep 10 08:03:18 2009 +0200 +++ b/common/i18n.py Fri Sep 18 12:20:39 2009 +0200 @@ -26,10 +26,14 @@ output.close() -def add_msg(w, msgid): +def add_msg(w, msgid, msgctx=None): """write an empty pot msgid definition""" if isinstance(msgid, unicode): msgid = msgid.encode('utf-8') + if msgctx: + if isinstance(msgctx, unicode): + msgctx = msgctx.encode('utf-8') + w('msgctxt "%s"\n' % msgctx) msgid = msgid.replace('"', r'\"').splitlines() if len(msgid) > 1: w('msgid ""\n') @@ -45,11 +49,8 @@ status != 0 """ print cmd.replace(os.getcwd() + os.sep, '') - if sys.platform == 'win32': - from subprocess import call - else: - call = os.system - status = call(cmd) + from subprocess import call + status = call(cmd, shell=True) if status != 0: raise Exception('status = %s' % status) diff -r d6ae24439bee -r c4c07aab1c39 common/migration.py --- a/common/migration.py Thu Sep 10 08:03:18 2009 +0200 +++ b/common/migration.py Fri Sep 18 12:20:39 2009 +0200 @@ -228,7 +228,10 @@ else: readline.set_completer(Completer(local_ctx).complete) readline.parse_and_bind('tab: complete') - histfile = os.path.join(os.environ["HOME"], ".eshellhist") + home_key = 'HOME' + if sys.platform == 'win32': + home_key = 'USERPROFILE' + histfile = os.path.join(os.environ[home_key], ".eshellhist") try: readline.read_history_file(histfile) except IOError: diff -r d6ae24439bee -r c4c07aab1c39 common/uilib.py --- a/common/uilib.py Thu Sep 10 08:03:18 2009 +0200 +++ b/common/uilib.py Fri Sep 18 12:20:39 2009 +0200 @@ -46,9 +46,9 @@ if attrtype == 'Time': return ustrftime(value, req.property_value('ui.time-format')) if attrtype == 'Datetime': - if not displaytime: - return ustrftime(value, req.property_value('ui.date-format')) - return ustrftime(value, req.property_value('ui.datetime-format')) + if displaytime: + return ustrftime(value, req.property_value('ui.datetime-format')) + return ustrftime(value, req.property_value('ui.date-format')) if attrtype == 'Boolean': if value: return req._('yes') diff -r d6ae24439bee -r c4c07aab1c39 cwconfig.py --- a/cwconfig.py Thu Sep 10 08:03:18 2009 +0200 +++ b/cwconfig.py Fri Sep 18 12:20:39 2009 +0200 @@ -392,7 +392,11 @@ 'server/serverctl.py', 'hercule.py', 'devtools/devctl.py', 'goa/goactl.py'): if exists(join(CW_SOFTWARE_ROOT, ctlfile)): - load_module_from_file(join(CW_SOFTWARE_ROOT, ctlfile)) + try: + load_module_from_file(join(CW_SOFTWARE_ROOT, ctlfile)) + except ImportError, err: + cls.critical('could not import the command provider %s (cause : %s)' % + (ctlfile, err)) cls.info('loaded cubicweb-ctl plugin %s', ctlfile) for cube in cls.available_cubes(): pluginfile = join(cls.cube_dir(cube), 'ecplugin.py') @@ -808,7 +812,7 @@ self.info("loading language %s", language) try: tr = translation('cubicweb', path, languages=[language]) - self.translations[language] = tr.ugettext + self.translations[language] = (tr.ugettext, tr.upgettext) except (ImportError, AttributeError, IOError): self.exception('localisation support error for language %s', language) diff -r d6ae24439bee -r c4c07aab1c39 cwctl.py --- a/cwctl.py Thu Sep 10 08:03:18 2009 +0200 +++ b/cwctl.py Fri Sep 18 12:20:39 2009 +0200 @@ -152,6 +152,7 @@ else: self.run_arg(appid) + # base commands ############################################################### class ListCommand(Command): @@ -365,7 +366,7 @@ # instance commands ######################################################## -class StartInstanceCommand(InstanceCommand): +class StartInstanceCommand(InstanceCommandFork): """Start the given instances. If no instance is given, start them all. ... @@ -397,17 +398,15 @@ def start_instance(self, appid): """start the instance's server""" - # use get() since start may be used from other commands (eg upgrade) - # without all options defined - debug = self.get('debug') - force = self.get('force') - loglevel = self.get('loglevel') + debug = self['debug'] + force = self['force'] + loglevel = self['loglevel'] config = cwcfg.config_for(appid) if loglevel is not None: loglevel = 'LOG_%s' % loglevel.upper() config.global_set_option('log-threshold', loglevel) config.init_log(loglevel, debug=debug, force=True) - if self.get('profile'): + if self['profile']: config.global_set_option('profile', self.config.profile) helper = self.config_helper(config, cmdname='start') pidf = config['pid-file'] @@ -415,8 +414,10 @@ msg = "%s seems to be running. Remove %s by hand if necessary or use \ the --force option." raise ExecutionError(msg % (appid, pidf)) - helper.start_command(config, debug) - return True + helper.start_server(config, debug) + if not debug: + # in debug mode, we reach this point once the instance is stopped... + print 'instance %s %s' % (appid, self.actionverb) class StopInstanceCommand(InstanceCommand): @@ -469,8 +470,7 @@ print 'instance %s stopped' % appid -class RestartInstanceCommand(StartInstanceCommand, - StopInstanceCommand): +class RestartInstanceCommand(StartInstanceCommand): """Restart the given instances. ... @@ -489,14 +489,12 @@ print ('some specific start order is specified, will first stop all ' 'instances then restart them.') # get instances in startorder - stopped = [] for appid in args: if askconfirm: print '*'*72 if not ASK.confirm('%s instance %r ?' % (self.name, appid)): continue - self.stop_instance(appid) - stopped.append(appid) + StopInstanceCommand().stop_instance(appid) forkcmd = [w for w in sys.argv if not w in args] forkcmd[1] = 'start' forkcmd = ' '.join(forkcmd) @@ -506,9 +504,8 @@ sys.exit(status) def restart_instance(self, appid): - self.stop_instance(appid) - if self.start_instance(appid): - print 'instance %s %s' % (appid, self.actionverb) + StopInstanceCommand().stop_instance(appid) + self.start_instance(appid) class ReloadConfigurationCommand(RestartInstanceCommand): @@ -559,9 +556,7 @@ print "running with pid %s" % (pid) -class UpgradeInstanceCommand(InstanceCommandFork, - StartInstanceCommand, - StopInstanceCommand): +class UpgradeInstanceCommand(InstanceCommandFork): """Upgrade an instance after cubicweb and/or component(s) upgrade. For repository update, you will be prompted for a login / password to use @@ -617,10 +612,6 @@ }), ) - def ordered_instances(self): - # need this since mro return StopInstanceCommand implementation - return InstanceCommand.ordered_instances(self) - def upgrade_instance(self, appid): print '\n' + underline_title('Upgrading the instance %s' % appid) from logilab.common.changelog import Version @@ -667,7 +658,7 @@ print '-> migration needed from %s to %s for %s' % (fromversion, toversion, cube) # only stop once we're sure we have something to do if not (cwcfg.mode == 'dev' or self.config.nostartstop): - self.stop_instance(appid) + StopInstanceCommand().stop_instance(appid) # run cubicweb/componants migration scripts mih.migrate(vcconf, reversed(toupgrade), self.config) # rewrite main configuration file @@ -690,7 +681,7 @@ print print '-> instance migrated.' if not (cwcfg.mode == 'dev' or self.config.nostartstop): - self.start_instance(appid) + StartInstanceCommand().start_instance(appid) print diff -r d6ae24439bee -r c4c07aab1c39 cwvreg.py --- a/cwvreg.py Thu Sep 10 08:03:18 2009 +0200 +++ b/cwvreg.py Fri Sep 18 12:20:39 2009 +0200 @@ -130,24 +130,26 @@ baseschemas = [eschema] + eschema.ancestors() # browse ancestors from most specific to most generic and try to find an # associated custom entity class + cls = None for baseschema in baseschemas: try: btype = ETYPE_NAME_MAP[baseschema] except KeyError: btype = str(baseschema) - try: - objects = self[btype] - assert len(objects) == 1, objects - if btype == etype: - cls = objects[0] - else: - # recurse to ensure issubclass(etype_class('Child'), - # etype_class('Parent')) - cls = self.etype_class(btype) - break - except ObjectNotFound: - pass - else: + if cls is None: + try: + objects = self[btype] + assert len(objects) == 1, objects + if btype == etype: + cls = objects[0] + else: + cls = self.etype_class(btype) + except ObjectNotFound: + continue + else: + # ensure parent classes are built first + self.etype_class(btype) + if cls is None: # no entity class for any of the ancestors, fallback to the default # one objects = self['Any'] @@ -289,8 +291,7 @@ def set_schema(self, schema): """set instance'schema and load application objects""" - self.schema = schema - clear_cache(self, 'rqlhelper') + self._set_schema(schema) # now we can load application's web objects searchpath = self.config.vregistry_path() self.reset(searchpath, force_reload=False) @@ -301,6 +302,11 @@ etype = str(etype) self.case_insensitive_etypes[etype.lower()] = etype + def _set_schema(self, schema): + """set instance'schema""" + self.schema = schema + clear_cache(self, 'rqlhelper') + def update_schema(self, schema): """update .schema attribute on registered objects, necessary for some tests @@ -384,16 +390,7 @@ # objects on automatic reloading self._needs_iface.clear() - def parse(self, session, rql, args=None): - rqlst = self.rqlhelper.parse(rql) - def type_from_eid(eid, session=session): - return session.describe(eid)[0] - try: - self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args) - except UnknownEid: - for select in rqlst.children: - select.solutions = [] - return rqlst + # rql parsing utilities #################################################### @property @cached @@ -401,38 +398,19 @@ return RQLHelper(self.schema, special_relations={'eid': 'uid', 'has_text': 'fti'}) - - @deprecated('use vreg["etypes"].etype_class(etype)') - def etype_class(self, etype): - return self["etypes"].etype_class(etype) - - @deprecated('use vreg["views"].main_template(*args, **kwargs)') - def main_template(self, req, oid='main-template', **context): - return self["views"].main_template(req, oid, **context) - - @deprecated('use vreg[registry].possible_vobjects(*args, **kwargs)') - def possible_vobjects(self, registry, *args, **kwargs): - return self[registry].possible_vobjects(*args, **kwargs) + def solutions(self, req, rqlst, args): + def type_from_eid(eid, req=req): + return req.describe(eid)[0] + self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args) - @deprecated('use vreg["actions"].possible_actions(*args, **kwargs)') - def possible_actions(self, req, rset=None, **kwargs): - return self["actions"].possible_actions(req, rest=rset, **kwargs) - - @deprecated("use vreg['boxes'].select_object(...)") - def select_box(self, oid, *args, **kwargs): - return self['boxes'].select_object(oid, *args, **kwargs) - - @deprecated("use vreg['components'].select_object(...)") - def select_component(self, cid, *args, **kwargs): - return self['components'].select_object(cid, *args, **kwargs) - - @deprecated("use vreg['actions'].select_object(...)") - def select_action(self, oid, *args, **kwargs): - return self['actions'].select_object(oid, *args, **kwargs) - - @deprecated("use vreg['views'].select(...)") - def select_view(self, __vid, req, rset=None, **kwargs): - return self['views'].select(__vid, req, rset=rset, **kwargs) + def parse(self, req, rql, args=None): + rqlst = self.rqlhelper.parse(rql) + try: + self.solutions(req, rqlst, args) + except UnknownEid: + for select in rqlst.children: + select.solutions = [] + return rqlst # properties handling ##################################################### @@ -505,6 +483,40 @@ self.warning('%s (you should probably delete that property ' 'from the database)', ex) + # deprecated code #################################################### + + @deprecated('[3.4] use vreg["etypes"].etype_class(etype)') + def etype_class(self, etype): + return self["etypes"].etype_class(etype) + + @deprecated('[3.4] use vreg["views"].main_template(*args, **kwargs)') + def main_template(self, req, oid='main-template', **context): + return self["views"].main_template(req, oid, **context) + + @deprecated('[3.4] use vreg[registry].possible_vobjects(*args, **kwargs)') + def possible_vobjects(self, registry, *args, **kwargs): + return self[registry].possible_vobjects(*args, **kwargs) + + @deprecated('[3.4] use vreg["actions"].possible_actions(*args, **kwargs)') + def possible_actions(self, req, rset=None, **kwargs): + return self["actions"].possible_actions(req, rest=rset, **kwargs) + + @deprecated('[3.4] use vreg["boxes"].select_object(...)') + def select_box(self, oid, *args, **kwargs): + return self['boxes'].select_object(oid, *args, **kwargs) + + @deprecated('[3.4] use vreg["components"].select_object(...)') + def select_component(self, cid, *args, **kwargs): + return self['components'].select_object(cid, *args, **kwargs) + + @deprecated('[3.4] use vreg["actions"].select_object(...)') + def select_action(self, oid, *args, **kwargs): + return self['actions'].select_object(oid, *args, **kwargs) + + @deprecated('[3.4] use vreg["views"].select(...)') + def select_view(self, __vid, req, rset=None, **kwargs): + return self['views'].select(__vid, req, rset=rset, **kwargs) + from datetime import datetime, date, time, timedelta diff -r d6ae24439bee -r c4c07aab1c39 dbapi.py --- a/dbapi.py Thu Sep 10 08:03:18 2009 +0200 +++ b/dbapi.py Fri Sep 18 12:20:39 2009 +0200 @@ -214,10 +214,13 @@ self.lang = 'en' # use req.__ to translate a message without registering it to the catalog try: - self._ = self.__ = self.translations[self.lang] + gettext, pgettext = self.translations[self.lang] + self._ = self.__ = gettext + self.pgettext = pgettext except KeyError: # this occurs usually during test execution self._ = self.__ = unicode + self.pgettext = lambda x,y: y self.debug('request default language: %s', self.lang) def decorate_rset(self, rset): @@ -360,7 +363,7 @@ # connection object ########################################################### class Connection(object): - """DB-API 2.0 compatible Connection object for CubicWebt + """DB-API 2.0 compatible Connection object for CubicWeb """ # make exceptions available through the connection object ProgrammingError = ProgrammingError diff -r d6ae24439bee -r c4c07aab1c39 debian/changelog --- a/debian/changelog Thu Sep 10 08:03:18 2009 +0200 +++ b/debian/changelog Fri Sep 18 12:20:39 2009 +0200 @@ -1,3 +1,39 @@ +cubicweb (3.5.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Fri, 18 Sep 2009 10:35:00 +0200 + +cubicweb (3.5.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Wed, 16 Sep 2009 17:51:13 +0200 + +cubicweb (3.4.11-1) unstable; urgency=low + + * new upstream release + + -- Aurélien Campéas Tue, 11 Sep 2009 12:20:00 +0200 + +cubicweb (3.4.10-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Fri, 11 Sep 2009 17:19:37 +0200 + +cubicweb (3.4.9-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Fri, 11 Sep 2009 14:27:39 +0200 + +cubicweb (3.4.8-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Thu, 10 Sep 2009 15:27:17 +0200 + cubicweb (3.4.7-1) unstable; urgency=low * new upstream release diff -r d6ae24439bee -r c4c07aab1c39 devtools/apptest.py --- a/devtools/apptest.py Thu Sep 10 08:03:18 2009 +0200 +++ b/devtools/apptest.py Fri Sep 18 12:20:39 2009 +0200 @@ -230,6 +230,23 @@ return [(a.id, a.__class__) for a in self.vreg['actions'].possible_vobjects(req, rset=rset) if a.category not in skipcategories] + def action_submenu(self, req, rset, id): + return self._test_action(self.vreg['actions'].select(id, req, rset=rset)) + + def _test_action(self, action): + class fake_menu(list): + @property + def items(self): + return self + class fake_box(object): + def mk_action(self, label, url, **kwargs): + return (label, url) + def box_action(self, action, **kwargs): + return (action.title, action.url()) + submenu = fake_menu() + action.fill_menu(fake_box(), submenu) + return submenu + def pactions_by_cats(self, req, rset, categories=('addrelated',)): return [(a.id, a.__class__) for a in self.vreg['actions'].possible_vobjects(req, rset=rset) if a.category in categories] diff -r d6ae24439bee -r c4c07aab1c39 devtools/devctl.py --- a/devtools/devctl.py Thu Sep 10 08:03:18 2009 +0200 +++ b/devtools/devctl.py Fri Sep 18 12:20:39 2009 +0200 @@ -12,6 +12,7 @@ from datetime import datetime from os import mkdir, chdir, getcwd from os.path import join, exists, abspath, basename, normpath, split, isdir +from copy import deepcopy from warnings import warn from logilab.common import STD_BLACKLIST @@ -20,7 +21,8 @@ from logilab.common.shellutils import ASK from logilab.common.clcommands import register_commands -from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage, underline_title +from cubicweb import (CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage, + underline_title) from cubicweb.__pkginfo__ import version as cubicwebversion from cubicweb.toolsutils import Command, copy_skeleton from cubicweb.web.webconfig import WebConfiguration @@ -111,88 +113,100 @@ def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None): from cubicweb.common.i18n import add_msg + from cubicweb.web import uicfg + from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES + 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') w('# singular and plural forms for each entity type\n') w('\n') + vregdone = set() if libconfig is not None: + from cubicweb.cwvreg import CubicWebVRegistry, clear_rtag_objects libschema = libconfig.load_schema(remove_unused_rtypes=False) - entities = [e for e in schema.entities() if not e in libschema] + rinlined = deepcopy(uicfg.autoform_is_inlined) + appearsin_addmenu = deepcopy(uicfg.actionbox_appearsin_addmenu) + clear_rtag_objects() + cleanup_sys_modules(libconfig) + libvreg = CubicWebVRegistry(libconfig) + libvreg.set_schema(libschema) # trigger objects registration + librinlined = uicfg.autoform_is_inlined + libappearsin_addmenu = uicfg.actionbox_appearsin_addmenu + # prefill vregdone set + list(_iter_vreg_objids(libvreg, vregdone)) else: - entities = schema.entities() + libschema = {} + rinlined = uicfg.autoform_is_inlined + appearsin_addmenu = uicfg.actionbox_appearsin_addmenu done = set() - for eschema in sorted(entities): + for eschema in sorted(schema.entities()): etype = eschema.type - add_msg(w, etype) - add_msg(w, '%s_plural' % etype) - if not eschema.is_final(): - add_msg(w, 'This %s' % etype) - add_msg(w, 'New %s' % etype) - add_msg(w, 'add a %s' % etype) - add_msg(w, 'remove this %s' % etype) - if eschema.description and not eschema.description in done: - done.add(eschema.description) - add_msg(w, eschema.description) - w('# subject and object forms for each relation type\n') - w('# (no object form for final relation types)\n') - w('\n') - if libconfig is not None: - relations = [r for r in schema.relations() if not r in libschema] - else: - relations = schema.relations() - for rschema in sorted(set(relations)): - rtype = rschema.type - add_msg(w, rtype) - done.add(rtype) - if not (schema.rschema(rtype).is_final() or rschema.symetric): - add_msg(w, '%s_object' % rtype) - if rschema.description and rschema.description not in done: - done.add(rschema.description) - add_msg(w, rschema.description) - w('# add related box generated message\n') - w('\n') - actionbox = vreg['boxes']['edit_box'][0] - for eschema in schema.entities(): + if etype not in libschema: + add_msg(w, etype) + add_msg(w, '%s_plural' % etype) + if not eschema.is_final(): + add_msg(w, 'This %s' % etype) + add_msg(w, 'New %s' % etype) + if eschema.description and not eschema.description in done: + done.add(eschema.description) + add_msg(w, eschema.description) if eschema.is_final(): continue - for role, rschemas in (('subject', eschema.subject_relations()), - ('object', eschema.object_relations())): - for rschema in rschemas: - if rschema.is_final(): - continue - if libconfig is not None: - librschema = libschema.get(rschema) - for teschema in rschema.targets(eschema, role): - if libconfig is not None and librschema is not None: - if role == 'subject': - subjtype, objtype = eschema, teschema - else: - subjtype, objtype = teschema, eschema - if librschema.has_rdef(subjtype, objtype): - continue - if actionbox.appearsin_addmenu.etype_get(eschema, rschema, - role, teschema): - if role == 'subject': - label = 'add %s %s %s %s' % (eschema, rschema, - teschema, role) - label2 = "creating %s (%s %%(linkto)s %s %s)" % ( - teschema, eschema, rschema, teschema) - else: - label = 'add %s %s %s %s' % (teschema, rschema, - eschema, role) - label2 = "creating %s (%s %s %s %%(linkto)s)" % ( - teschema, teschema, rschema, eschema) - add_msg(w, label) - add_msg(w, label2) - #cube = (cube and 'cubes.%s.' % cube or 'cubicweb.') - done = set() - if libconfig is not None: - from cubicweb.cwvreg import CubicWebVRegistry - libvreg = CubicWebVRegistry(libconfig) - libvreg.set_schema(libschema) # trigger objects registration - # prefill done set - list(_iter_vreg_objids(libvreg, done)) - for objid in _iter_vreg_objids(vreg, done): + for rschema, targetschemas, role in eschema.relation_definitions(True): + for tschema in targetschemas: + if rinlined.etype_get(eschema, rschema, role, tschema) and \ + (libconfig is None or not + librinlined.etype_get(eschema, rschema, role, tschema)): + add_msg(w, 'add a %s' % tschema, + 'inlined:%s.%s.%s' % (etype, rschema, role)) + add_msg(w, 'remove this %s' % tschema, + 'inlined:%s:%s:%s' % (etype, rschema, role)) + if appearsin_addmenu.etype_get(eschema, rschema, role, tschema) and \ + (libconfig is None or not + libappearsin_addmenu.etype_get(eschema, rschema, role, tschema)): + if role == 'subject': + label = 'add %s %s %s %s' % (eschema, rschema, + tschema, role) + label2 = "creating %s (%s %%(linkto)s %s %s)" % ( + tschema, eschema, rschema, tschema) + else: + label = 'add %s %s %s %s' % (tschema, rschema, + eschema, role) + label2 = "creating %s (%s %s %s %%(linkto)s)" % ( + tschema, tschema, rschema, eschema) + add_msg(w, label) + add_msg(w, label2) + w('# subject and object forms for each relation type\n') + w('# (no object form for final or symetric relation types)\n') + w('\n') + for rschema in sorted(schema.relations()): + rtype = rschema.type + if rtype not in libschema: + # bw compat, necessary until all translation of relation are done properly... + add_msg(w, rtype) + if rschema.description and rschema.description not in done: + done.add(rschema.description) + add_msg(w, rschema.description) + done.add(rtype) + librschema = None + else: + librschema = libschema.rschema(rtype) + # add context information only for non-metadata rtypes + if rschema not in no_context_rtypes: + libsubjects = librschema and librschema.subjects() or () + for subjschema in rschema.subjects(): + if not subjschema in libsubjects: + add_msg(w, rtype, subjschema.type) + if not (schema.rschema(rtype).is_final() or rschema.symetric): + if rschema not in no_context_rtypes: + libobjects = librschema and librschema.objects() or () + for objschema in rschema.objects(): + if not objschema in libobjects: + add_msg(w, '%s_object' % rtype, objschema.type) + if rtype not in libschema: + # bw compat, necessary until all translation of relation are done properly... + add_msg(w, '%s_object' % rtype) + for objid in _iter_vreg_objids(vreg, vregdone): add_msg(w, '%s_description' % objid) add_msg(w, objid) diff -r d6ae24439bee -r c4c07aab1c39 devtools/fake.py --- a/devtools/fake.py Thu Sep 10 08:03:18 2009 +0200 +++ b/devtools/fake.py Fri Sep 18 12:20:39 2009 +0200 @@ -13,6 +13,7 @@ from indexer import get_indexer from cubicweb import RequestSessionMixIn +from cubicweb.cwvreg import CubicWebVRegistry from cubicweb.web.request import CubicWebRequestBase from cubicweb.devtools import BASE_URL, BaseApptestConfiguration @@ -35,39 +36,13 @@ def sources(self): return {} -class FakeVReg(dict): - def __init__(self, schema=None, config=None): - self.schema = schema - self.config = config or FakeConfig() - self.properties = {'ui.encoding': 'UTF8', - 'ui.language': 'en', - } - self.update({ - 'controllers' : {'login': []}, - 'views' : {}, - }) - - def property_value(self, key): - return self.properties[key] - - def etype_class(self, etype): - class Entity(dict): - e_schema = self.schema[etype] - def __init__(self, session, eid, row=0, col=0): - self.req = session - self.eid = eid - self.row, self.col = row, col - def set_eid(self, eid): - self.eid = self['eid'] = eid - return Entity - class FakeRequest(CubicWebRequestBase): """test implementation of an cubicweb request object""" def __init__(self, *args, **kwargs): if not (args or 'vreg' in kwargs): - kwargs['vreg'] = FakeVReg() + kwargs['vreg'] = CubicWebVRegistry(FakeConfig(), initlog=False) kwargs['https'] = False self._url = kwargs.pop('url', 'view?rql=Blop&vid=blop') super(FakeRequest, self).__init__(*args, **kwargs) @@ -148,25 +123,6 @@ return self.execute(*args, **kwargs) -# class FakeRequestNoCnx(FakeRequest): -# def get_session_data(self, key, default=None, pop=False): -# """return value associated to `key` in session data""" -# if pop: -# return self._session_data.pop(key, default) -# else: -# return self._session_data.get(key, default) - -# def set_session_data(self, key, value): -# """set value associated to `key` in session data""" -# self._session_data[key] = value - -# def del_session_data(self, key): -# try: -# del self._session_data[key] -# except KeyError: -# pass - - class FakeUser(object): login = 'toto' eid = 0 @@ -177,7 +133,7 @@ class FakeSession(RequestSessionMixIn): def __init__(self, repo=None, user=None): self.repo = repo - self.vreg = getattr(self.repo, 'vreg', FakeVReg()) + self.vreg = getattr(self.repo, 'vreg', CubicWebVRegistry(FakeConfig(), initlog=False)) self.pool = FakePool() self.user = user or FakeUser() self.is_internal_session = False @@ -210,8 +166,9 @@ self.eids = {} self._count = 0 self.schema = schema - self.vreg = vreg or FakeVReg() self.config = config or FakeConfig() + self.vreg = vreg or CubicWebVRegistry(self.config, initlog=False) + self.vreg.schema = schema def internal_session(self): return FakeSession(self) @@ -249,10 +206,3 @@ class FakePool(object): def source(self, uri): return FakeSource(uri) - -# commented until proven to be useful -## from logging import getLogger -## from cubicweb import set_log_methods -## for cls in (FakeConfig, FakeVReg, FakeRequest, FakeSession, FakeRepo, -## FakeSource, FakePool): -## set_log_methods(cls, getLogger('fake')) diff -r d6ae24439bee -r c4c07aab1c39 devtools/repotest.py --- a/devtools/repotest.py Thu Sep 10 08:03:18 2009 +0200 +++ b/devtools/repotest.py Fri Sep 18 12:20:39 2009 +0200 @@ -108,9 +108,10 @@ schema = None # set this in concret test def setUp(self): + self.repo = FakeRepo(self.schema) self.rqlhelper = RQLHelper(self.schema, special_relations={'eid': 'uid', 'has_text': 'fti'}) - self.qhelper = QuerierHelper(FakeRepo(self.schema), self.schema) + self.qhelper = QuerierHelper(self.repo, self.schema) ExecutionPlan._check_permissions = _dummy_check_permissions rqlannotation._select_principal = _select_principal @@ -129,7 +130,7 @@ #print '********* solutions', solutions self.rqlhelper.simplify(union) #print '********* simplified', union.as_string() - plan = self.qhelper.plan_factory(union, {}, FakeSession()) + plan = self.qhelper.plan_factory(union, {}, FakeSession(self.repo)) plan.preprocess(union) for select in union.children: select.solutions.sort() @@ -167,7 +168,7 @@ set_debug(debug) def _rqlhelper(self): - rqlhelper = self.o._rqlhelper + rqlhelper = self.repo.vreg.rqlhelper # reset uid_func so it don't try to get type from eids rqlhelper._analyser.uid_func = None rqlhelper._analyser.uid_func_mapping = {} @@ -241,7 +242,7 @@ rqlst = self.o.parse(rql, annotate=True) self.o.solutions(self.session, rqlst, kwargs) if rqlst.TYPE == 'select': - self.o._rqlhelper.annotate(rqlst) + self.repo.vreg.rqlhelper.annotate(rqlst) for select in rqlst.children: select.solutions.sort() else: @@ -251,7 +252,7 @@ # monkey patch some methods to get predicatable results ####################### -from cubicweb.server.rqlrewrite import RQLRewriter +from cubicweb.rqlrewrite import RQLRewriter _orig_insert_snippets = RQLRewriter.insert_snippets _orig_build_variantes = RQLRewriter.build_variantes diff -r d6ae24439bee -r c4c07aab1c39 devtools/testlib.py --- a/devtools/testlib.py Thu Sep 10 08:03:18 2009 +0200 +++ b/devtools/testlib.py Fri Sep 18 12:20:39 2009 +0200 @@ -333,7 +333,7 @@ # resultset's syntax tree rset = backup_rset for action in self.list_actions_for(rset): - yield InnerTest(self._testname(rset, action.id, 'action'), action.url) + yield InnerTest(self._testname(rset, action.id, 'action'), self._test_action, action) for box in self.list_boxes_for(rset): yield InnerTest(self._testname(rset, box.id, 'box'), box.render) diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/datamodel/definition.rst --- a/doc/book/en/development/datamodel/definition.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/datamodel/definition.rst Fri Sep 18 12:20:39 2009 +0200 @@ -89,7 +89,7 @@ * `*`: 0..n - `meta` : boolean indicating that the relation is a meta-relation (false by - default) + default, will disappear in *CubicWeb* 3.5) * optional properties for attributes : @@ -216,8 +216,8 @@ of the cube (``migration/precreate.py``). -Use of RQL expression for writing rights -````````````````````````````````````````` +Use of RQL expression for write permissions +``````````````````````````````````````````` It is possible to define RQL expression to provide update permission (`add`, `delete` and `update`) on relation and entity types. @@ -249,7 +249,7 @@ * we can also defined rights on attributes of an entity (non-final relation), knowing that : - - to defines RQL expression, we have to use the class `RQLExpression` + - to define RQL expression, we have to use the class `RQLExpression` in which X represents the entity the attribute belongs to - the permissions `add` and `delete` are equivalent. Only `add`/`read` @@ -321,7 +321,7 @@ attr_name = attr_type(properties*) where `attr_type` is one of the type listed above and `properties` is -a list of the attribute needs to statisfy (see :ref:`properties` +a list of the attribute needs to statisfy (see :ref:`properties` for more details). @@ -434,6 +434,6 @@ * in such case, we have to protect both the entity type "Version" and the relation associating a version to a project ("version_of") -* because of the genricity of the entity type `CWPermission`, we have to execute +* because of the genericity of the entity type `CWPermission`, we have to execute a unification with the groups and/or the states if necessary in the expression ("U in_group G, P require_group G" in the above example) diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/datamodel/inheritance.rst --- a/doc/book/en/development/datamodel/inheritance.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/datamodel/inheritance.rst Fri Sep 18 12:20:39 2009 +0200 @@ -5,4 +5,14 @@ When describing a data model, entities can inherit from other entities as is common in object-oriented programming. +You have the possibility to adapt some entity attributes, as follow: + +.. sourcecode:: python + + from cubes.OTHER_CUBE import entities + class EntityExample(entities.EntityExample): + def dc_long_title(self): + return '%s (%s)' % (self.name, self.description) + + XXX WRITME diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/devcore/appobject.rst --- a/doc/book/en/development/devcore/appobject.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/devcore/appobject.rst Fri Sep 18 12:20:39 2009 +0200 @@ -16,7 +16,7 @@ We can find a certain number of attributes and methods defined in this class and common to all the application objects. -At the recording, the following attributes are dynamically added to +At recording time, the following attributes are dynamically added to the *subclasses*: * `vreg`, the `vregistry` of the instance @@ -27,28 +27,15 @@ * `req`, `Request` instance * `rset`, the *result set* associated to the object if necessary -* `cursor`, rql cursor on the session - :URL handling: - * `build_url(method=None, **kwargs)`, returns an absolute URL based on - the given arguments. The *controller* supposed to handle the response, - can be specified through the special parameter `method` (the connection - is theoretically done automatically :). - - * `datadir_url()`, returns the directory of the instance data - (contains static files such as images, css, js...) - - * `base_url()`, shortcut to `req.base_url()` - - * `url_quote(value)`, version *unicode safe* of the function `urllib.quote` + * `build_url(*args, **kwargs)`, returns an absolute URL based on the + given arguments. The *controller* supposed to handle the response, + can be specified through the first positional parameter (the + connection is theoretically done automatically :). :Data manipulation: - * `etype_rset(etype, size=1)`, shortcut to `vreg.etype_rset()` - - * `eid_rset(eid, rql=None, descr=True)`, returns a *result set* object for - the given eid * `entity(row, col=0)`, returns the entity corresponding to the data position in the *result set* associated to the object @@ -57,15 +44,12 @@ :Data formatting: * `format_date(date, date_format=None, time=False)` returns a string for a - mx date time according to instance's configuration - * `format_time(time)` returns a string for a mx date time according to + date time according to instance's configuration + * `format_time(time)` returns a string for a date time according to instance's configuration :And more...: - * `external_resource(rid, default=_MARKER)`, access to a value defined in the - configuration file `external_resource` - * `tal_render(template, variables)`, renders a precompiled page template with variables in the given dictionary as context @@ -73,13 +57,14 @@ When we inherit from `AppObject` (even not directly), you *always* have to use **super()** to get the methods and attributes of the superclasses, and not use the class identifier. + For example, instead of writting: :: class Truc(PrimaryView): def f(self, arg1): PrimaryView.f(self, arg1) - You'd better write: :: + You must write: :: class Truc(PrimaryView): def f(self, arg1): diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/devcore/vreg.rst --- a/doc/book/en/development/devcore/vreg.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/devcore/vreg.rst Fri Sep 18 12:20:39 2009 +0200 @@ -22,16 +22,15 @@ Once the function `registration_callback(vreg)` is implemented, all the objects have to be explicitly registered as it disables the automatic object registering. -* the old registration mechanism will be removed when there will be no reference - left to the registerers module in cubicweb and the library of cubes. - Examples: .. sourcecode:: python # web/views/basecomponents.py def registration_callback(vreg): + # register everything in the module except SeeAlsoComponent vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) + # conditionally register SeeAlsoVComponent if 'see_also' in vreg.schema: vreg.register(SeeAlsoVComponent) @@ -66,7 +65,7 @@ The object's selector is defined by its `__select__` class attribute. -When two selectors are combined using the `&` operator (former `chainall`), it +When two selectors are combined using the `&` operator (formerly `chainall`), it means that both should return a positive score. On success, the sum of scores is returned. When two selectors are combined using the `|` operator (former `chainfirst`), it @@ -76,38 +75,38 @@ Of course you can use paren to balance expressions. -For instance, if you're selecting the primary (eg `id = 'primary'`) view (eg +For instance, if you are selecting the primary (eg `id = 'primary'`) view (eg `__registry__ = 'view'`) for a result set containing a `Card` entity, 2 objects will probably be selectable: -* the default primary view (`__select__ = implements('Any')`), meaning that the object is selectable for any kind of entity type - -* the specific `Card` primary view (`__select__ = implements('Card')`, meaning that the object is selectable for Card entities +* the default primary view (`__select__ = implements('Any')`), meaning + that the object is selectable for any kind of entity type -Other primary views specific to other entity types won't be selectable in this -case. Among selectable objects, the implements selector will return a higher score -to the second view since it's more specific, so it will be selected as expected. +* the specific `Card` primary view (`__select__ = implements('Card')`, + meaning that the object is selectable for Card entities + +Other primary views specific to other entity types won't be selectable +in this case. Among selectable objects, the implements selector will +return a higher score than the second view since it's more specific, +so it will be selected as expected. Example ```````` -XXX this part needs to be translated - -The goal : when on a Blog, one wants the RSS link to refer to blog +The goal: when on a Blog, one wants the RSS link to refer to blog entries, not to the blog entity itself. To do that, one defines a method on entity classes that returns the -RSS stream url for a given entity. With a default implementation on -AnyEntity and a specific implementation on Blog, which will do what we -want. +RSS stream url for a given entity. The default implementation on +AnyEntity and a specific implementation on Blog will do what we want. -There's a limitation to this schema : when we have a result set -containing several Blog entities (or different entities), we don't -know on which entity to call the aforementioned method. In this case, -we keep the current behaviour (e.g : call to limited_rql). +But when we have a result set containing several Blog entities (or +different entities), we don't know on which entity to call the +aforementioned method. In this case, we keep the current behaviour +(e.g : call to limited_rql). -Hence we want two cases here, one for a single-entity rsets, the other +Hence we have two cases here, one for a single-entity rsets, the other for multi-entities rsets. In web/views/boxes.py lies the RSSIconBox class. Look at its selector :: @@ -116,7 +115,7 @@ """just display the RSS icon on uniform result set""" __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity() -It takes into account : +It takes into account: * the inherited selection criteria (one has to look them up in the class hierarchy to know the details) @@ -125,7 +124,7 @@ entities (a 'final entity' being synonym for entity attribute) This matches our second case. Hence we have to provide a specific -component for the first case :: +component for the first case:: class EntityRSSIconBox(RSSIconBox): """just display the RSS icon on uniform result set for a single entity""" @@ -152,7 +151,7 @@ ``````````````````````` Selectors are to be used whenever arises the need of dispatching on -the shape or content of a result set. +the shape or content of a result set. That is, almost all the time. Debugging ````````` diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/devweb/internationalization.rst --- a/doc/book/en/development/devweb/internationalization.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/devweb/internationalization.rst Fri Sep 18 12:20:39 2009 +0200 @@ -45,7 +45,8 @@ The goal of the *built-in* function `_` is only **to mark the translatable strings**, it will only return the string to translate -it-self, but not its translation (it's actually refering to the `unicode` builtin). +itself, but not its translation (it's actually another name for the +`unicode` builtin). In the other hand the request's method `self.req._` is meant to retrieve the proper translation of translation strings in the requested language. @@ -81,8 +82,8 @@ * `i18ncubicweb` updates Cubicweb framework's translation - catalogs. Unless you work on the framework development, you don't - need to use this command. + catalogs. Unless you actually work on the framework itself, you + don't need to use this command. * `i18ncube` updates the translation catalogs of *one particular cube* (or of all cubes). After this command is @@ -90,7 +91,7 @@ directory of your template. This command will of course not remove existing translations still in use. -* `i18ninstance` recompile the translation catalogs of *one particular +* `i18ninstance` recompiles the translation catalogs of *one particular instance* (or of all instances) after the translation catalogs of its cubes have been updated. This command is automatically called every time you create or update your instance. The compiled diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/devweb/js.rst --- a/doc/book/en/development/devweb/js.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/devweb/js.rst Fri Sep 18 12:20:39 2009 +0200 @@ -3,16 +3,70 @@ Javascript ---------- -XXX jquery... +*CubicWeb* uses quite a bit of javascript in its user interface and +ships with jquery (1.3.x) and parts of the jquery UI +library, plus a number of homegrown files and also other thirparty +libraries. + +All javascript files are stored in cubicweb/web/data/. There are +around thirty js files there. In a cube it goes to data/. + +Obviously one does not want javascript pieces to be loaded all at +once, hence the framework provides a number of mechanisms and +conventions to deal with javascript resources. Conventions ~~~~~~~~~~~ -XXX external_resources variable - naming convention - request.add_js +It is good practice to name cube specific js files after the name of +the cube, like this : 'cube.mycube.js', so as to avoid name clashes. +XXX external_resources variable (which needs love) CubicWeb javascrip api ~~~~~~~~~~~~~~~~~~~~~~ -XXX explain diffenrent files and main functions + +Javascript resources are typically loaded on demand, from views. The +request object (available as self.req from most application objects, +for instance views and entities objects) has a few methods to do that: + +* `add_js(self, jsfiles, localfile=True)` which takes a sequence of + javascript files and writes proper entries into the HTML header + section. The localfile parameter allows to declare resources which + are not from web/data (for instance, residing on a content delivery + network). + +* `add_onload(self, jscode)` which adds one raw javascript code + snippet inline in the html headers. This is quite useful for setting + up early jQuery(document).ready(...) initialisations. + +Overview of what's available +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* jquery.* : jquery and jquery UI library + +* cubicweb.python.js : adds a number of practical extension to stdanrd + javascript objects (on Date, Array, String, some list and dictionary + operations), and a pythonesque way to build classes. Defines a + CubicWeb namespace. + +* cubicweb.htmlhelpers.js : a small bag of convenience functions used + in various other cubicweb javascript resources (baseuri, progress + cursor handling, popup login box, html2dom function, etc.) + +* cubicweb.ajax.js : concentrates all ajax related facilities (it + extends jQuery with the loahxhtml function, provides a handfull of + high-level ajaxy operations like asyncRemoteExec, reloadComponent, + replacePageChunk, getDomFromResponse) + +* cubicweb.widgets.js : provides a widget namespace and constructors + and helpers for various widgets (mainly facets and timeline) + +* cubicweb.edition.js : used by edition forms + +* cubicweb.preferences.js : used by the preference form + +* cubicweb.facets.js : used by the facets mechanism + +xxx massmailing, gmap, fckcwconfig, timeline-bundle, timeline-ext, +calendar, goa, flotn tazy, tabs, bookmarks diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/devweb/views.rst --- a/doc/book/en/development/devweb/views.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/devweb/views.rst Fri Sep 18 12:20:39 2009 +0200 @@ -34,26 +34,32 @@ * 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. +At instantiation time, the standard `req` and `rset` attributes are +added and the `w` attribute will be set at rendering time. -A view writes to its output stream thanks to its attribute `w` (`UStreamIO`). +A view writes to its output stream thanks to its attribute `w` (an +`UStreamIO`). The basic interface for views is as follows (remember that the result set has a tabular structure with rows and columns, hence cells): -* `dispatch(**context)`, render the view by calling `call` or +* `render(**context)`, render the view by calling `call` or `cell_call` depending on the given parameters -* `call(**kwargs)`, call the view for a complete result set or null (default - implementation calls `cell_call()` on each cell of the result set) -* `cell_call(row, col, **kwargs)`, call the view for a given cell of a result set + +* `call(**kwargs)`, call the view for a complete result set or null + (the default implementation calls `cell_call()` on each cell of the + result set) + +* `cell_call(row, col, **kwargs)`, call the view for a given cell of a + result set + * `url()`, returns the URL enabling us to get the view with the current result set + * `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier `__vid` on the given result set. It is possible to give a view identifier of fallback that will be used if the view requested is not applicable to the - result set + result set. This is actually defined on the AppObject class. * `wview(__vid, rset, __fallback_vid=None, **kwargs)`, similar to `view` except the flow is automatically passed in the parameters @@ -70,8 +76,8 @@ * `EntityView`, view applying to lines or cell containing an entity (e.g. an eid) * `StartupView`, start view that does not require a result set to apply to -* `AnyRsetView`, view applied to any result set -* `EmptyRsetView`, view applied to an empty result set +* `AnyRsetView`, view applicable to any result set +* `EmptyRsetView`, view applicable to an empty result set Examples of views class @@ -103,11 +109,8 @@ __select__ = one_line_rset() & match_search_state('linksearch') & implements('Any') -Example of a view customization -------------------------------- - -[FIXME] XXX Example needs to be rewritten as it shows how to modify cell_call which -contredicts our advise of not modifying it. +Example of view customization and creation +------------------------------------------ We'll show you now an example of a ``primary`` view and how to customize it. @@ -116,32 +119,26 @@ .. sourcecode:: python - from cubicweb.view import EntityView - from cubicweb.selectors import implements + from cubicweb.selectors import implements + from cubicweb.web.views.primary improt Primaryview - class BlogEntryPrimaryView(EntityView): - id = 'primary' - __select__ =implements('Blog') + class BlogEntryPrimaryView(PrimaryView): + __select__ = PrimaryView.__select__ & implements('BlogEntry') - def cell_call(self, row, col): - entity = self.entity(row, col) - self.w(u'

%s

' % entity.title) - self.w(u'

published on %s in category %s

' % \ - (entity.publish_date.strftime('%Y-%m-%d'), entity.category)) - self.w(u'

%s

' % entity.text) + def render_entity_attributes(self, entity): + self.w(u'

published on %s

' % + entity.publish_date.strftime('%Y-%m-%d')) + super(BlogEntryPrimaryView, self).render_entity_attributes(entity) -The above source code defines a new primary view (`line 03`) for -``BlogEntry`` (`line 05`). +The above source code defines a new primary view for +``BlogEntry``. The `id` class attribute is not repeated there since it +is inherited through the `primary.PrimaryView` class. -Since views are applied to result sets which can be tables of -data, we have to recover the entity from its (row,col)-coordinates (`line 08`). -We will get to this in more detail later. +The selector for this view chains the selector of the inherited class +with its own specific criterion. The view method ``self.w()`` is used to output data. Here `lines -09-12` output HTML tags and values of the entity's attributes. - -When displaying the same blog entry as before, you will notice that the -page is now looking much nicer. [FIXME: it is not clear to what this refers.] +08-09` output HTML for the publication date of the entry. .. image:: ../../images/lax-book.09-new-view-blogentry.en.png :alt: blog entries now look much nicer @@ -150,34 +147,74 @@ .. sourcecode:: python - class BlogPrimaryView(EntityView): + from logilab.mtconverter import xml_escape + from cubicweb.selectors import implements, one_line_rset + from cubicweb.web.views.primary import Primaryview + + class BlogPrimaryView(PrimaryView): id = 'primary' - __select__ =implements('Blog') + __select__ = PrimaryView.__select__ & implements('Blog') + rql = 'Any BE ORDERBY D DESC WHERE BE entry_of B, BE publish_date D, B eid %(b)s' + + def render_entity_relations(self, entity): + rset = self.req.execute(self.rql, {'b' : entity.eid}) + for entry in rset.entities(): + self.w(u'

%s

' % entry.view('inblogcontext')) + + class BlogEntryInBlogView(EntityView): + 'inblogcontext' + __select__ = implements('BlogEntry') def cell_call(self, row, col): - entity = self.entity(row, col) - self.w(u'

%s

' % entity.title) - self.w(u'

%s

' % entity.description) - rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid) - self.wview('primary', rset) + entity = self.rset.get_entity(row, col) + self.w(u'%s' % + entity.absolute_url(), + xml_escape(entity.content[:50]), + xml_escape(entity.description)) -In the above source code, `lines 01-08` are similar to the previous -view we defined. [FIXME: defined where ?] +This happens in two places. First we override the +render_entity_relations method of a Blog's primary view. Here we want +to display our blog entries in a custom way. -At `line 09`, a simple request is made to build a result set with all +At `line 10`, a simple request is made to build a result set with all the entities linked to the current ``Blog`` entity by the relationship ``entry_of``. The part of the framework handling the request knows -about the schema and infer that such entities have to be of the -``BlogEntry`` kind and retrieves them. +about the schema and infers that such entities have to be of the +``BlogEntry`` kind and retrieves them (in the prescribed publish_date +order). + +The request returns a selection of data called a result set. Result +set objects have an .entities() method returning a generator on +requested entities (going transparently through the `ORM` layer). + +At `line 13` the view 'inblogcontext' is applied to each blog entry to +output HTML. (Note that the 'inblogcontext' view is not defined +whatsoever in *CubicWeb*. You are absolutely free to define whole view +families.) We juste arrange to wrap each blogentry output in a 'p' +html element. -The request returns a selection of data called a result set. At -`line 10` the view 'primary' is applied to this result set to output -HTML. +Next, we define the 'inblogcontext' view. This is NOT a primary view, +with its well-defined sections (title, metadata, attribtues, +relations/boxes). All a basic view has to define is cell_call. + +Since views are applied to result sets which can be tables of data, we +have to recover the entity from its (row,col)-coordinates (`line +20`). Then we can spit some HTML. + +But careful: all strings manipulated in *CubicWeb* are actually +unicode strings. While web browsers are usually tolerant to incoherent +encodings they are being served, we should not abuse it. Hence we have +to properly escape our data. The xml_escape() function has to be used +to safely fill (X)HTML elements from Python unicode strings. + **This is to be compared to interfaces and protocols in object-oriented languages. Applying a given view called 'a_view' to all the entities of a result set only requires to have for each entity of this result set, -an available view called 'a_view' which accepts the entity.** +an available view called 'a_view' which accepts the entity. + +Instead of merely using type based dispatch, we do predicate dispatch +which quite more powerful** Assuming we added entries to the blog titled `MyLife`, displaying it now allows to read its description and all its entries. diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/entityclasses/data-as-objects.rst --- a/doc/book/en/development/entityclasses/data-as-objects.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/entityclasses/data-as-objects.rst Fri Sep 18 12:20:39 2009 +0200 @@ -1,7 +1,8 @@ Access to persistent data -------------------------- -XXX is provided by the :class:`Entity ` class +Python-level access to persistent data is provided by the +:class:`Entity ` class. An entity class is bound to a schema entity type. Descriptors are added when classes are registered in order to initialize the class according to its schema: @@ -22,8 +23,6 @@ * `rest_path()`, returns a relative REST URL to get the entity - * `format(attr)`, returns the format (MIME type) of the field given un parameter - * `printable_value(attr, value=_marker, attrtype=None, format='text/html')`, returns a string enabling the display of an attribute value in a given format (the value is automatically recovered if necessary) @@ -33,7 +32,7 @@ * `as_rset()`, converts the entity into an equivalent result set simulating the request `Any X WHERE X eid _eid_` - * `complete(skip_bytes=True)`, executes a request that recovers in one time + * `complete(skip_bytes=True)`, executes a request that recovers all at once all the missing attributes of an entity * `get_value(name)`, returns the value associated to the attribute name given @@ -52,9 +51,6 @@ * `copy_relations(ceid)`, copies the relations of the entities having the eid given in the parameters on the current entity - * `last_modified(view)`, returns the date the object has been modified - (used by HTTP cache handling) - * `delete()` allows to delete the entity @@ -66,24 +62,25 @@ in `mycube.entities` module (or in a submodule if we want to split code among multiple files) so that it will be available on both server and client side. -The class `AnyEntity` is loaded dynamically from the class `Entity` -(`cubciweb.entity`). We define a sub-class to add methods or to -specialize the handling of a given entity type +The class `AnyEntity` is a sub-class of Entity that add methods to it, +and helps specializing (by further subclassing) the handling of a +given entity type. -The methods defined for `AnyEntity` or `Entity` are the following ones: +The methods defined for `AnyEntity`, in addition to `Entity`, are the +following ones: :Standard meta-data (Dublin Core): - * `dc_title()`, returns a unicode string corresponding to the meta-data - `Title` (used by default the first attribute non-meta of the entity - schema) + * `dc_title()`, returns a unicode string corresponding to the + meta-data `Title` (used by default is the first non-meta attribute + of the entity schema) * `dc_long_title()`, same as dc_title but can return a more - detailled title + detailed title * `dc_description(format='text/plain')`, returns a unicode string - corresponding to the meta-data `Description` (look for a description - attribute by default) + corresponding to the meta-data `Description` (looks for a + description attribute by default) * `dc_authors()`, returns a unicode string corresponding to the meta-data `Authors` (owners by default) diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/entityclasses/load-sort.rst --- a/doc/book/en/development/entityclasses/load-sort.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/entityclasses/load-sort.rst Fri Sep 18 12:20:39 2009 +0200 @@ -16,18 +16,30 @@ `None` if we do not want to sort on the attribute given in the parameter. By default, the entities are sorted according to their creation date. -* The class method `fetch_unrelated_order(attr, var)` is similar to the - method `fetch_order` except that it is essentially used to control - the sorting of drop-down lists enabling relations creation in - the editing view of an entity. +* The class method `fetch_unrelated_order(attr, var)` is similar to + the method `fetch_order` except that it is essentially used to + control the sorting of drop-down lists enabling relations creation + in the editing view of an entity. The default implementation uses + the modification date. Here's how to adapt it for one entity (sort + on the name attribute): :: + + class MyEntity(AnyEntity): + fetch_attrs = ('modification_date', 'name') + + @classmethod + def fetch_unrelated_order(cls, attr, var): + if attr == 'name': + return '%s ASC' % var + return None + The function `fetch_config(fetchattrs, mainattr=None)` simplifies the definition of the attributes to load and the sorting by returning a -list of attributes to pre-load (considering automatically the attributes -of `AnyEntity`) and a sorting function based on the main attribute -(the second parameter if specified otherwisethe first attribute from -the list `fetchattrs`). -This function is defined in `cubicweb.entities`. +list of attributes to pre-load (considering automatically the +attributes of `AnyEntity`) and a sorting function based on the main +attribute (the second parameter if specified, otherwise the first +attribute from the list `fetchattrs`). This function is defined in +`cubicweb.entities`. For example: :: diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/webstdlib/baseviews.rst --- a/doc/book/en/development/webstdlib/baseviews.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/webstdlib/baseviews.rst Fri Sep 18 12:20:39 2009 +0200 @@ -30,10 +30,11 @@ Entity views ```````````` *incontext, outofcontext* - Those are used to display a link to an entity, depending if the entity is - considered as displayed in or out of context (of another entity). By default - it respectively returns the result of `textincontext` and `textoutofcontext` - wrapped in a link leading to the primary view of the entity. + Those are used to display a link to an entity, depending on the + entity having to be displayed in or out of context + (of another entity). By default it respectively returns the + result of `textincontext` and `textoutofcontext` wrapped in a link + leading to the primary view of the entity. *oneline* This view is used when we can't tell if the entity should be considered as @@ -56,6 +57,12 @@ *adaptedlistitem* This view redirects by default to the `outofcontext` view. +*csv* + This view applies to entity groups, which are individually + displayed using the `incontext` view. It displays each entity as a + coma separated list. It is NOT related to the well-known text file + format. + Text entity views ~~~~~~~~~~~~~~~~~ *text* diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/development/webstdlib/primary.rst --- a/doc/book/en/development/webstdlib/primary.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/development/webstdlib/primary.rst Fri Sep 18 12:20:39 2009 +0200 @@ -9,8 +9,9 @@ Rendering methods and attributes for ``PrimaryView`` ---------------------------------------------------- -By default, *CubicWeb* provides a primary view for each new entity type -you create. The first view you might be interested in modifying. +By default, *CubicWeb* provides a primary view for every available +entity type. This is the first view you might be interested in +modifying. Let's have a quick look at the EntityView ``PrimaryView`` as well as its rendering method @@ -24,55 +25,63 @@ show_attr_label = True show_rel_label = True skip_none = True - skip_attrs = ('eid', 'creation_date', 'modification_date') - skip_rels = () + rsection = uicfg.primaryview_section + display_ctrl = uicfg.primaryview_display_ctrl main_related_section = True ... def cell_call(self, row, col): self.row = row - self.render_entity(self.complete_entity(row, col)) + self.maxrelated = self.req.property_value('navigation.related-limit') + entity = self.complete_entity(row, col) + self.render_entity(entity) def render_entity(self, entity): - """return html to display the given entity""" - siderelations = [] self.render_entity_title(entity) self.render_entity_metadata(entity) # entity's attributes and relations, excluding meta data # if the entity isn't meta itself - self.w(u'
') + boxes = self._prepare_side_boxes(entity) + if boxes or hasattr(self, 'render_side_related'): + self.w(u'
') + self.render_entity_summary(entity) self.w(u'
') - self.render_entity_attributes(entity, siderelations) - self.w(u'
') self.content_navigation_components('navcontenttop') + self.render_entity_attributes(entity) if self.main_related_section: - self.render_entity_relations(entity, siderelations) + self.render_entity_relations(entity) self.w(u'') # side boxes - self.w(u'
') - self.render_side_related(entity, siderelations) - self.w(u'
') - self.w(u'
') + if boxes or hasattr(self, 'render_side_related'): + self.w(u'
') + self.w(u'
') + if hasattr(self, 'render_side_related'): + warn('render_side_related is deprecated') + self.render_side_related(entity, []) + self.render_side_boxes(boxes) + self.w(u'
') + self.w(u'
') self.content_navigation_components('navcontentbottom') ... -``cell_call`` is executed for each entity of a result set and apply ``render_entity``. +``cell_call`` is executed for each entity of a result set. The methods you want to modify while customizing a ``PrimaryView`` are: *render_entity_title(self, entity)* Renders the entity title based on the assumption that the method - ``def content_title(self)`` is implemented for the given entity type. + ``def dc_title(self)`` is implemented for the given entity type. *render_entity_metadata(self, entity)* - Renders the entity metadata based on the assumption that the method - ``def summary(self)`` is implemented for the given entity type. + Renders the entity metadata by calling the 'metadata' view on the + entity. This generic view is in cubicweb.views.baseviews. -*render_entity_attributes(self, entity, siderelations)* - Renders all the attribute of an entity with the exception of attribute - of type `Password` and `Bytes`. +*render_entity_attributes(self, entity)* + Renders all the attribute of an entity with the exception of + attribute of type `Password` and `Bytes`. The skip_none class + attribute controls the display of None valued attributes. *content_navigation_components(self, context)* This method is applicable only for entity type implementing the interface @@ -81,15 +90,15 @@ entities of this type, either at the top or at the bottom of the page given the context (navcontent{top|bottom}). -*render_entity_relations(self, entity, siderelations)* +*render_entity_relations(self, entity)* Renders all the relations of the entity in the main section of the page. -*render_side_related(self, entity, siderelations)* +*render_side_boxes(self, entity, boxes)* Renders all the relations of the entity in a side box. This is equivalent to *render_entity_relations* in addition to render the relations in a box. -Also, please note that by setting the following attributes in you class, +Also, please note that by setting the following attributes in your class, you can already customize some of the rendering: *show_attr_label* diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/intro/concepts/index.rst --- a/doc/book/en/intro/concepts/index.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/intro/concepts/index.rst Fri Sep 18 12:20:39 2009 +0200 @@ -59,15 +59,15 @@ .. image:: ../../images/archi_globale.en.png -The command ``cubicweb-ctl list`` displays the list of instances installed on -your system. +The command ``cubicweb-ctl list`` also displays the list of instances +installed on your system. On a Unix system, the instances are usually stored in the directory :file:`/etc/cubicweb.d/`. During development, the :file:`~/etc/cubicweb.d/` directory is looked up, as well as the paths in :envvar:`CW_INSTANCES_DIR` environment variable. -The term application is used to refer at "something that should do something as a +The term application is used to refer to "something that should do something as a whole", eg more like a project and so can refer to an instance or to a cube, depending on the context. This book will try to use *application*, *cube* and *instance* as appropriate. @@ -77,7 +77,7 @@ The data repository [#]_ provides access to one or more data sources (including SQL databases, LDAP repositories, Mercurial or Subversion version control -systems, other CubicWeb repositories, GAE's DataStore, etc). +systems, other CubicWeb instance repositories, GAE's DataStore, etc). All interactions with the repository are done using the Relation Query Language (RQL). The repository federates the data sources and hides them from the @@ -99,13 +99,13 @@ Web Engine ---------- -The web engine replies to http requests and runs the user interface and most of -the application logic. +The web engine replies to http requests and runs the user interface +and most of the application logic. -By default the web engine provides a generated user interface based on the data -model of the instance. Entities can be created, displayed, updated and -deleted. As the default user interface is not very fancy, it is usually -necessary to develop your own. +By default the web engine provides a default user interface based on +the data model of the instance. Entities can be created, displayed, +updated and deleted. As the default user interface is not very fancy, +it is usually necessary to develop your own. Schema (Data Model) ------------------- @@ -120,7 +120,7 @@ `Date`, `Time`, `Datetime`, `Interval`, `Password`, `Bytes`, `RichString`. See :ref:`yams.BASE_TYPES` for details. -A `relation type` is used to define a binary oriented relation between two +A `relation type` is used to define an oriented binary relation between two entity types. The left-hand part of a relation is named the `subject` and the right-hand part is named the `object`. @@ -143,8 +143,6 @@ Registries and Objects ---------------------- -XXX registry, register, registries, registers ??? - Application objects ~~~~~~~~~~~~~~~~~~~ @@ -159,15 +157,15 @@ object's `__registry__` : object's `id` : [list of app objects] -The base class of appobjects is `AppRsetObject` (module `cubicweb.appobject`). +The base class of appobjects is `AppObject` (module `cubicweb.appobject`). The `vregistry` ~~~~~~~~~~~~~~~ -At startup, the `registry` or registers base, inspects a number of directories -looking for compatible classes definition. After a recording process, the objects -are assigned to registers so that they can be selected dynamically while the -instance is running. +At startup, the `registry` inspects a number of directories looking +for compatible classes definition. After a recording process, the +objects are assigned to registers so that they can be selected +dynamically while the instance is running. Selectors ~~~~~~~~~ @@ -185,7 +183,7 @@ that case, the object with the greatest score is selected. There should always be a single appobject with a greater score than others. -* get all appobjects applying to a context by specifying a registry.In +* get all appobjects applying to a context by specifying a registry. In that case, every object with the a postive score is selected. * get the object within a particular registry/identifier. In that case no @@ -195,10 +193,9 @@ Selector sets are the glue that tie views to the data model. Using them appropriately is an essential part of the construction of well behaved cubes. - When no score is higher than the others, an exception is raised in development mode to let you know that the engine was not able to identify the view to -apply. This error is silented in production mode and one of the objects with the +apply. This error is silenced in production mode and one of the objects with the higher score is picked. If no object has a positive score, ``NoSelectableObject`` exception is raised. @@ -231,7 +228,14 @@ Result set ~~~~~~~~~~ -XXX feed me +Every request made (using RQL) to the data repository returns an +object we call a Result Set. It enables easy use of the retrieved +data, providing a translation layer between the backend's native +datatypes and *CubicWeb* schema's EntityTypes. + +Result sets provide access to the raw data, yielding either basic +Python data types, or schema-defined high-level entities, in a +straightforward way. Views @@ -239,6 +243,10 @@ ** *CubicWeb* is data driven ** +The view system is loosely coupled to data through a selection +system. Views are, in essence, defined by an id, a selection predicate +and an entry point (generaly producing html). + XXX feed me. @@ -246,4 +254,31 @@ ----- ** *CubicWeb* provides an extensible data repository ** -XXX feed me. +The data model defined using Yams types allows to express the data +model in a comfortable way. However several aspects of the data model +can not be expressed there. For instance: + +* managing computed attributes + +* enforcing complicated structural invariants + +* real-world side-effects linked to data events (email notification + being a prime example) + +The hook system is much like the triggers of an SQL database engine, +except that: + +* it is not limited to one specific SQL backend (every one of them + having an idiomatic way to encode triggers), nor to SQL backends at + all (think about LDAP or a Subversion repository) + +* it is well-coupled to the rest of the framework + +Hooks are basically functions that dispatch on both: + +* events : after/before add/update/delete on entities/relations + +* entity or relation types + +They are an essential building block of any moderately complicated +cubicweb application. diff -r d6ae24439bee -r c4c07aab1c39 doc/book/en/intro/tutorial/create-cube.rst --- a/doc/book/en/intro/tutorial/create-cube.rst Thu Sep 10 08:03:18 2009 +0200 +++ b/doc/book/en/intro/tutorial/create-cube.rst Fri Sep 18 12:20:39 2009 +0200 @@ -3,12 +3,12 @@ Create your cube ---------------- -The packages ``cubicweb`` and ``cubicweb-dev`` installs a command line tool -for *CubicWeb* called ``cubicweb-ctl``. This tool provides a wide range of -commands described in details in :ref:`cubicweb-ctl`. +The packages ``cubicweb`` and ``cubicweb-dev`` install a command line +tool for *CubicWeb* called ``cubicweb-ctl``. This tool provides a wide +range of commands described in details in :ref:`cubicweb-ctl`. -Once your *CubicWeb* development environment is set up, you can create a new -cube:: +Once your *CubicWeb* development environment is set up, you can create +a new cube:: cubicweb-ctl newcube blog @@ -40,15 +40,15 @@ A Blog has a title and a description. The title is a string that is -required by the class EntityType and must be less than 50 characters. -The description is a string that is not constrained. +required and must be less than 50 characters. The +description is a string that is not constrained. A BlogEntry has a title, a publish_date and a content. The title is a string that is required and must be less than 100 characters. The publish_date is a Date with a default value of TODAY, meaning that when a BlogEntry is created, its publish_date will be the current day unless it is modified. The content is a string that will be indexed in -the full-text index and has no constraint. +the database full-text index and has no constraint. A BlogEntry also has a relationship ``entry_of`` that links it to a Blog. The cardinality ``?*`` means that a BlogEntry can be part of @@ -172,9 +172,9 @@ Define your entity views ------------------------ -Each entity defined in a model inherits default views allowing -different rendering of the data. You can redefine each of them -according to your needs and preferences. So let's see how the +Each entity defined in a model is associated with default views +allowing different rendering of the data. You can redefine each of +them according to your needs and preferences. So let's see how the views are defined. @@ -183,72 +183,74 @@ A view is defined by a Python class which includes: - - an identifier (all objects in *CubicWeb* are entered in a registry - and this identifier will be used as a key) + - an identifier (all objects in *CubicWeb* are recorded in a + registry and this identifier will be used as a key) - a filter to select the result sets it can be applied to -A view has a set of methods complying -with the `View` class interface (`cubicweb.common.view`). +A view has a set of methods complying with the `View` class interface +(`cubicweb.common.view`). *CubicWeb* provides a lot of standard views for the type `EntityView`; for a complete list, read the code in directory ``cubicweb/web/views/``. -A view is applied on a `result set` which contains a set of -entities we are trying to display. *CubicWeb* uses a selector -mechanism which computes for each available view a score: -the view with the highest score is then used to display the given `result set`. -The standard library of selectors is in -``cubicweb.common.selector`` and a library of methods used to -compute scores is available in ``cubicweb.vregistry.vreq``. +A view is applied on a `result set` which contains a set of entities +we are trying to display. *CubicWeb* uses a selector mechanism which +computes for each available view a score: the view with the highest +score is then used to display the given `result set`. The standard +library of selectors is in ``cubicweb.selector``. It is possible to define multiple views for the same identifier and to associate selectors and filters to allow the application -to find the best way to render the data. +to find the most appropriate way to render the data. -For example, the view named ``primary`` is the one used to display -a single entity. We will now show you how to customize this view. +For example, the view named ``primary`` is the one used to display a +single entity. We will now show you how to create a primary view for +BlogEntry. -View customization -~~~~~~~~~~~~~~~~~~ +Primary view customization +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you wish to modify the way a `BlogEntry` is rendered, you will have +to subclass the `primary` view, for instance in the module ``views`` +of the cube ``cubes/blog/views.py``. + +The standard primary view is the most sophisticated view of all. It +has more than a call() method. It is a template. Actually the entry +point calls the following sequence of (redefinable) methods: + + * render_entity_title -If you wish to modify the way a `BlogEntry` is rendered, you will have to -overwrite the `primary` view defined in the module ``views`` of the cube -``cubes/blog/views.py``. + * render_entity_metadata + + * render_entity_attributes + + * render_entity_relations -We can for example add in front of the publication date a prefix specifying -that the date we see is the publication date. + * render_side_boxes + +Excepted side boxes, we can see all of them already in action in the +blog entry view. This is all described in more details in +:ref:`primary`. + +We can for example add in front of the publication date a prefix +specifying that the date we see is the publication date. To do so, please apply the following changes: .. sourcecode:: python - from cubicweb.web.views import baseviews - - - class BlogEntryPrimaryView(baseviews.PrimaryView): - - accepts = ('BlogEntry',) - - def render_entity_title(self, entity): - self.w(u'

%s

' % html_escape(entity.dc_title())) - - def content_format(self, entity): - return entity.view('reledit', rtype='content_format') + from cubicweb.selectors import implements + from cubicweb.web.views import primary - def cell_call(self, row, col): - entity = self.entity(row, col) + class BlogEntryPrimaryView(primary.PrimaryView): + __select__ = implements('BlogEntry') - # display entity attributes with prefixes - self.w(u'

%s

' % entity.title) - self.w(u'

published on %s

' % entity.publish_date.strftime('%Y-%m-%d')) - self.w(u'

%s

' % entity.content) - - # display relations - siderelations = [] - if self.main_related_section: - self.render_entity_relations(entity, siderelations) + def render_entity_attributes(self, entity): + self.w(u'

published on %s

' % + entity.publish_date.strftime('%Y-%m-%d')) + super(BlogEntryPrimaryView, self).render_entity_attributes(entity) .. note:: When a view is modified, it is not required to restart the instance diff -r d6ae24439bee -r c4c07aab1c39 entities/lib.py --- a/entities/lib.py Thu Sep 10 08:03:18 2009 +0200 +++ b/entities/lib.py Fri Sep 18 12:20:39 2009 +0200 @@ -10,7 +10,7 @@ from urlparse import urlsplit, urlunsplit from datetime import datetime -from logilab.common.decorators import cached +from logilab.common.deprecation import deprecated from cubicweb import UnknownProperty from cubicweb.entity import _marker @@ -25,7 +25,7 @@ class EmailAddress(AnyEntity): id = 'EmailAddress' - fetch_attrs, fetch_order = fetch_config(['address', 'alias', 'canonical']) + fetch_attrs, fetch_order = fetch_config(['address', 'alias']) def dc_title(self): if self.alias: @@ -36,15 +36,13 @@ def email_of(self): return self.reverse_use_email and self.reverse_use_email[0] - @cached + @property + def prefered(self): + return self.prefered_form and self.prefered_form[0] or self + + @deprecated('use .prefered') def canonical_form(self): - if self.canonical: - return self - rql = 'EmailAddress X WHERE X identical_to Y, X canonical TRUE, Y eid %(y)s' - cnrset = self.req.execute(rql, {'y': self.eid}, 'y') - if cnrset: - return cnrset.get_entity(0, 0) - return None + return self.prefered_form and self.prefered_form[0] or self def related_emails(self, skipeids=None): # XXX move to eemail diff -r d6ae24439bee -r c4c07aab1c39 entities/test/unittest_base.py --- a/entities/test/unittest_base.py Thu Sep 10 08:03:18 2009 +0200 +++ b/entities/test/unittest_base.py Fri Sep 18 12:20:39 2009 +0200 @@ -59,16 +59,13 @@ class EmailAddressTC(BaseEntityTC): def test_canonical_form(self): - eid1 = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"')[0][0] - eid2 = self.execute('INSERT EmailAddress X: X address "maarten@philips.com", X canonical TRUE')[0][0] - self.execute('SET X identical_to Y WHERE X eid %s, Y eid %s' % (eid1, eid2)) - email1 = self.entity('Any X WHERE X eid %(x)s', {'x':eid1}, 'x') - email2 = self.entity('Any X WHERE X eid %(x)s', {'x':eid2}, 'x') - self.assertEquals(email1.canonical_form().eid, eid2) - self.assertEquals(email2.canonical_form(), email2) - eid3 = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"')[0][0] - email3 = self.entity('Any X WHERE X eid %s'%eid3) - self.assertEquals(email3.canonical_form(), None) + email1 = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"').get_entity(0, 0) + email2 = self.execute('INSERT EmailAddress X: X address "maarten@philips.com"').get_entity(0, 0) + email3 = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"').get_entity(0, 0) + self.execute('SET X prefered_form Y WHERE X eid %s, Y eid %s' % (email1.eid, email2.eid)) + self.assertEquals(email1.prefered.eid, email2.eid) + self.assertEquals(email2.prefered.eid, email2.eid) + self.assertEquals(email3.prefered.eid, email3.eid) def test_mangling(self): eid = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"')[0][0] @@ -109,10 +106,13 @@ self.vreg.register_appobject_class(MyUser) self.vreg['etypes'].initialization_completed() MyUser_ = self.vreg['etypes'].etype_class('CWUser') - self.failIf(MyUser is MyUser_.__bases__) - self.failUnless(MyUser in MyUser_.__bases__) + # a copy is done systematically + self.failUnless(issubclass(MyUser_, MyUser)) self.failUnless(implements(MyUser_, IMileStone)) self.failUnless(implements(MyUser_, IWorkflowable)) + # original class should not have beed modified, only the copy + self.failUnless(implements(MyUser, IMileStone)) + self.failIf(implements(MyUser, IWorkflowable)) class SpecializedEntityClassesTC(EnvBasedTC): diff -r d6ae24439bee -r c4c07aab1c39 entities/test/unittest_wfobjs.py --- a/entities/test/unittest_wfobjs.py Thu Sep 10 08:03:18 2009 +0200 +++ b/entities/test/unittest_wfobjs.py Fri Sep 18 12:20:39 2009 +0200 @@ -335,12 +335,12 @@ self.assertEquals(self.member.state, 'asleep')# no change before commit self.commit() self.member.clear_all_caches() - self.assertEquals(self.member.current_workflow.name, "CWUser workflow") + self.assertEquals(self.member.current_workflow.name, "default user workflow") self.assertEquals(self.member.state, 'activated') self.assertEquals(parse_hist(self.member.workflow_history), [('activated', 'deactivated', 'deactivate', None), ('deactivated', 'asleep', None, 'workflow changed to "CWUser"'), - ('asleep', 'activated', None, 'workflow changed to "CWUser workflow"'),]) + ('asleep', 'activated', None, 'workflow changed to "default user workflow"'),]) from cubicweb.devtools.apptest import RepositoryBasedTC diff -r d6ae24439bee -r c4c07aab1c39 entity.py --- a/entity.py Thu Sep 10 08:03:18 2009 +0200 +++ b/entity.py Fri Sep 18 12:20:39 2009 +0200 @@ -15,12 +15,14 @@ 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 from cubicweb.rset import ResultSet from cubicweb.selectors import yes from cubicweb.appobject import AppObject +from cubicweb.rqlrewrite import RQLRewriter from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype from cubicweb.common.uilib import printable_value, soup2xhtml @@ -82,6 +84,7 @@ except AttributeError: continue + class _metaentity(type): """this metaclass sets the relation tags on the entity class and deals with the `widgets` attribute @@ -457,7 +460,8 @@ return self.mtc_transform(value.getvalue(), attrformat, format, encoding) return u'' - value = printable_value(self.req, attrtype, value, props, displaytime) + value = printable_value(self.req, attrtype, value, props, + displaytime=displaytime) if format == 'text/html': value = xml_escape(value) return value @@ -718,24 +722,13 @@ # generic vocabulary methods ############################################## - @deprecated('see new form api') - def vocabulary(self, rtype, role='subject', limit=None): - """vocabulary functions must return a list of couples - (label, eid) that will typically be used to fill the - edition view's combobox. - - If `eid` is None in one of these couples, it should be - interpreted as a separator in case vocabulary results are grouped - """ - from logilab.common.testlib import mock_object - form = self.vreg.select('forms', 'edition', self.req, entity=self) - field = mock_object(name=rtype, role=role) - return form.form_field_vocabulary(field, limit) - def unrelated_rql(self, rtype, targettype, role, ordermethod=None, vocabconstraints=True): """build a rql to fetch `targettype` entities unrelated to this entity - using (rtype, role) relation + using (rtype, role) relation. + + Consider relation permissions so that returned entities may be actually + linked by `rtype`. """ ordermethod = ordermethod or 'fetch_unrelated_order' if isinstance(rtype, basestring): @@ -748,8 +741,17 @@ objtype, subjtype = self.e_schema, targettype if self.has_eid(): restriction = ['NOT S %s O' % rtype, '%s eid %%(x)s' % evar] + args = {'x': self.eid} + if role == 'subject': + securitycheck_args = {'fromeid': self.eid} + else: + securitycheck_args = {'toeid': self.eid} else: restriction = [] + args = {} + securitycheck_args = {} + insertsecurity = (rtype.has_local_role('add') and not + rtype.has_perm(self.req, 'add', **securitycheck_args)) constraints = rtype.rproperty(subjtype, objtype, 'constraints') if vocabconstraints: # RQLConstraint is a subclass for RQLVocabularyConstraint, so they @@ -766,20 +768,29 @@ if not ' ORDERBY ' in rql: before, after = rql.split(' WHERE ', 1) rql = '%s ORDERBY %s WHERE %s' % (before, searchedvar, after) - return rql + if insertsecurity: + rqlexprs = rtype.get_rqlexprs('add') + rewriter = RQLRewriter(self.req) + rqlst = self.req.vreg.parse(self.req, rql, args) + for select in rqlst.children: + rewriter.rewrite(select, [((searchedvar, searchedvar), rqlexprs)], + select.solutions, args) + rql = rqlst.as_string() + return rql, args def unrelated(self, rtype, targettype, role='subject', limit=None, ordermethod=None): """return a result set of target type objects that may be related by a given relation, with self as subject or object """ - rql = self.unrelated_rql(rtype, targettype, role, ordermethod) + try: + rql, args = self.unrelated_rql(rtype, targettype, role, ordermethod) + except Unauthorized: + return self.req.empty_rset() if limit is not None: before, after = rql.split(' WHERE ', 1) rql = '%s LIMIT %s WHERE %s' % (before, limit, after) - if self.has_eid(): - return self.req.execute(rql, {'x': self.eid}) - return self.req.execute(rql) + return self.req.execute(rql, args, tuple(args)) # relations cache handling ################################################ @@ -935,6 +946,20 @@ words += entity.get_words() return words + @deprecated('[3.2] see new form api') + def vocabulary(self, rtype, role='subject', limit=None): + """vocabulary functions must return a list of couples + (label, eid) that will typically be used to fill the + edition view's combobox. + + If `eid` is None in one of these couples, it should be + interpreted as a separator in case vocabulary results are grouped + """ + from logilab.common.testlib import mock_object + form = self.vreg.select('forms', 'edition', self.req, entity=self) + field = mock_object(name=rtype, role=role) + return form.form_field_vocabulary(field, limit) + # attribute and relation descriptors ########################################## diff -r d6ae24439bee -r c4c07aab1c39 etwist/server.py --- a/etwist/server.py Thu Sep 10 08:03:18 2009 +0200 +++ b/etwist/server.py Fri Sep 18 12:20:39 2009 +0200 @@ -16,12 +16,6 @@ import hotshot from twisted.application import strports -try: - from twisted.scripts._twistd_unix import daemonize -except ImportError: - def daemonize(): - raise NotImplementedError('not yet for win32') - from twisted.internet import reactor, task, threads from twisted.internet.defer import maybeDeferred from twisted.web2 import channel, http, server, iweb @@ -35,6 +29,29 @@ from cubicweb.etwist.request import CubicWebTwistedRequestAdapter +def daemonize(): + # XXX unix specific + # XXX factorize w/ code in cw.server.server and cw.server.serverctl + # (start-repository command) + # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16 + if os.fork(): # launch child and... + return -1 + os.setsid() + if os.fork(): # launch child and... + os._exit(0) # kill off parent again. + # move to the root to avoit mount pb + os.chdir('/') + # set paranoid umask + os.umask(077) + null = os.open('/dev/null', os.O_RDWR) + for i in range(3): + try: + os.dup2(null, i) + except OSError, e: + if e.errno != errno.EBADF: + raise + os.close(null) + return None def start_task(interval, func): lc = task.LoopingCall(func) @@ -373,7 +390,9 @@ logger = getLogger('cubicweb.twisted') logger.info('instance started on %s', baseurl) if not debug: - daemonize() + if daemonize(): + # child process + return if config['pid-file']: # ensure the directory where the pid-file should be set exists (for # instance /var/run/cubicweb may be deleted on computer restart) @@ -381,6 +400,13 @@ if not os.path.exists(piddir): os.makedirs(piddir) file(config['pid-file'], 'w').write(str(os.getpid())) + if config['uid'] is not None: + try: + uid = int(config['uid']) + except ValueError: + from pwd import getpwnam + uid = getpwnam(config['uid']).pw_uid + os.setuid(uid) if config['profile']: prof = hotshot.Profile(config['profile']) prof.runcall(reactor.run) diff -r d6ae24439bee -r c4c07aab1c39 etwist/twctl.py --- a/etwist/twctl.py Thu Sep 10 08:03:18 2009 +0200 +++ b/etwist/twctl.py Fri Sep 18 12:20:39 2009 +0200 @@ -18,7 +18,7 @@ cmdname = 'start' cfgname = 'twisted' - def start_command(self, config, debug): + def start_server(self, config, debug): from cubicweb.etwist import server server.run(config, debug) diff -r d6ae24439bee -r c4c07aab1c39 gettext.py --- a/gettext.py Thu Sep 10 08:03:18 2009 +0200 +++ b/gettext.py Fri Sep 18 12:20:39 2009 +0200 @@ -8,7 +8,6 @@ languages. L10N refers to the adaptation of your program, once internationalized, to the local language and cultural habits. -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ # This module represents the integration of work, contributions, feedback, and @@ -47,7 +46,7 @@ # find this format documented anywhere. -import copy, os, re, struct, sys +import locale, copy, os, re, struct, sys from errno import ENOENT @@ -78,7 +77,10 @@ Python lambda function that implements an equivalent expression. """ # Security check, allow only the "n" identifier - from StringIO import StringIO + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO import token, tokenize tokens = tokenize.generate_tokens(StringIO(plural).readline) try: @@ -172,6 +174,7 @@ def __init__(self, fp=None): self._info = {} self._charset = None + self._output_charset = None self._fallback = None if fp is not None: self._parse(fp) @@ -190,6 +193,21 @@ return self._fallback.gettext(message) return message + def pgettext(self, context, message): + if self._fallback: + return self._fallback.pgettext(context, message) + return message + + def lgettext(self, message): + if self._fallback: + return self._fallback.lgettext(message) + return message + + def lpgettext(self, context, message): + if self._fallback: + return self._fallback.lpgettext(context, message) + return message + def ngettext(self, msgid1, msgid2, n): if self._fallback: return self._fallback.ngettext(msgid1, msgid2, n) @@ -198,11 +216,40 @@ else: return msgid2 + def npgettext(self, context, msgid1, msgid2, n): + if self._fallback: + return self._fallback.npgettext(context, msgid1, msgid2, n) + if n == 1: + return msgid1 + else: + return msgid2 + + def lngettext(self, msgid1, msgid2, n): + if self._fallback: + return self._fallback.lngettext(msgid1, msgid2, n) + if n == 1: + return msgid1 + else: + return msgid2 + + def lnpgettext(self, context, msgid1, msgid2, n): + if self._fallback: + return self._fallback.lnpgettext(context, msgid1, msgid2, n) + if n == 1: + return msgid1 + else: + return msgid2 + def ugettext(self, message): if self._fallback: return self._fallback.ugettext(message) return unicode(message) + def upgettext(self, context, message): + if self._fallback: + return self._fallback.upgettext(context, message) + return unicode(message) + def ungettext(self, msgid1, msgid2, n): if self._fallback: return self._fallback.ungettext(msgid1, msgid2, n) @@ -211,15 +258,49 @@ else: return unicode(msgid2) + def unpgettext(self, context, msgid1, msgid2, n): + if self._fallback: + return self._fallback.unpgettext(context, msgid1, msgid2, n) + if n == 1: + return unicode(msgid1) + else: + return unicode(msgid2) + def info(self): return self._info def charset(self): return self._charset - def install(self, unicode=False): + def output_charset(self): + return self._output_charset + + def set_output_charset(self, charset): + self._output_charset = charset + + def install(self, unicode=False, names=None): import __builtin__ __builtin__.__dict__['_'] = unicode and self.ugettext or self.gettext + if hasattr(names, "__contains__"): + if "gettext" in names: + __builtin__.__dict__['gettext'] = __builtin__.__dict__['_'] + if "pgettext" in names: + __builtin__.__dict__['pgettext'] = (unicode and self.upgettext + or self.pgettext) + if "ngettext" in names: + __builtin__.__dict__['ngettext'] = (unicode and self.ungettext + or self.ngettext) + if "npgettext" in names: + __builtin__.__dict__['npgettext'] = \ + (unicode and self.unpgettext or self.npgettext) + if "lgettext" in names: + __builtin__.__dict__['lgettext'] = self.lgettext + if "lpgettext" in names: + __builtin__.__dict__['lpgettext'] = self.lpgettext + if "lngettext" in names: + __builtin__.__dict__['lngettext'] = self.lngettext + if "lnpgettext" in names: + __builtin__.__dict__['lnpgettext'] = self.lnpgettext class GNUTranslations(NullTranslations): @@ -227,6 +308,10 @@ LE_MAGIC = 0x950412deL BE_MAGIC = 0xde120495L + # The encoding of a msgctxt and a msgid in a .mo file is + # msgctxt + "\x04" + msgid (gettext version >= 0.15) + CONTEXT_ENCODING = "%s\x04%s" + def _parse(self, fp): """Override this method to support alternative .mo formats.""" unpack = struct.unpack @@ -262,18 +347,19 @@ # See if we're looking at GNU .mo conventions for metadata if mlen == 0: # Catalog description - # don't handle multi-lines fields here, and skip - # lines which don't look like a header description - # (e.g. "header: value") lastk = k = None for item in tmsg.splitlines(): item = item.strip() - if not item or not ':' in item: + if not item: continue - k, v = item.split(':', 1) - k = k.strip().lower() - v = v.strip() - self._info[k] = v + if ':' in item: + k, v = item.split(':', 1) + k = k.strip().lower() + v = v.strip() + self._info[k] = v + lastk = k + elif lastk: + self._info[lastk] += '\n' + item if k == 'content-type': self._charset = v.split('charset=')[1] elif k == 'plural-forms': @@ -289,7 +375,7 @@ # cause no problems since us-ascii should always be a subset of # the charset encoding. We may want to fall back to 8-bit msgids # if the Unicode conversion fails. - if msg.find('\x00') >= 0: + if '\x00' in msg: # Plural forms msgid1, msgid2 = msg.split('\x00') tmsg = tmsg.split('\x00') @@ -315,14 +401,56 @@ return self._fallback.gettext(message) return message # Encode the Unicode tmsg back to an 8-bit string, if possible - if self._charset: + if self._output_charset: + return tmsg.encode(self._output_charset) + elif self._charset: + return tmsg.encode(self._charset) + return tmsg + + def pgettext(self, context, message): + ctxt_msg_id = self.CONTEXT_ENCODING % (context, message) + missing = object() + tmsg = self._catalog.get(ctxt_msg_id, missing) + if tmsg is missing: + if self._fallback: + return self._fallback.pgettext(context, message) + return message + # Encode the Unicode tmsg back to an 8-bit string, if possible + if self._output_charset: + return tmsg.encode(self._output_charset) + elif self._charset: return tmsg.encode(self._charset) return tmsg + def lgettext(self, message): + missing = object() + tmsg = self._catalog.get(message, missing) + if tmsg is missing: + if self._fallback: + return self._fallback.lgettext(message) + return message + if self._output_charset: + return tmsg.encode(self._output_charset) + return tmsg.encode(locale.getpreferredencoding()) + + def lpgettext(self, context, message): + ctxt_msg_id = self.CONTEXT_ENCODING % (context, message) + missing = object() + tmsg = self._catalog.get(ctxt_msg_id, missing) + if tmsg is missing: + if self._fallback: + return self._fallback.lpgettext(context, message) + return message + if self._output_charset: + return tmsg.encode(self._output_charset) + return tmsg.encode(locale.getpreferredencoding()) + def ngettext(self, msgid1, msgid2, n): try: tmsg = self._catalog[(msgid1, self.plural(n))] - if self._charset: + if self._output_charset: + return tmsg.encode(self._output_charset) + elif self._charset: return tmsg.encode(self._charset) return tmsg except KeyError: @@ -333,6 +461,52 @@ else: return msgid2 + def npgettext(self, context, msgid1, msgid2, n): + ctxt_msg_id = self.CONTEXT_ENCODING % (context, msgid1) + try: + tmsg = self._catalog[(ctxt_msg_id, self.plural(n))] + if self._output_charset: + return tmsg.encode(self._output_charset) + elif self._charset: + return tmsg.encode(self._charset) + return tmsg + except KeyError: + if self._fallback: + return self._fallback.npgettext(context, msgid1, msgid2, n) + if n == 1: + return msgid1 + else: + return msgid2 + + def lngettext(self, msgid1, msgid2, n): + try: + tmsg = self._catalog[(msgid1, self.plural(n))] + if self._output_charset: + return tmsg.encode(self._output_charset) + return tmsg.encode(locale.getpreferredencoding()) + except KeyError: + if self._fallback: + return self._fallback.lngettext(msgid1, msgid2, n) + if n == 1: + return msgid1 + else: + return msgid2 + + def lnpgettext(self, context, msgid1, msgid2, n): + ctxt_msg_id = self.CONTEXT_ENCODING % (context, msgid1) + try: + tmsg = self._catalog[(ctxt_msg_id, self.plural(n))] + if self._output_charset: + return tmsg.encode(self._output_charset) + return tmsg.encode(locale.getpreferredencoding()) + except KeyError: + if self._fallback: + return self._fallback.lnpgettext(context, msgid1, msgid2, n) + if n == 1: + return msgid1 + else: + return msgid2 + def ugettext(self, message): missing = object() tmsg = self._catalog.get(message, missing) @@ -342,6 +516,18 @@ return unicode(message) return tmsg + def upgettext(self, context, message): + ctxt_message_id = self.CONTEXT_ENCODING % (context, message) + missing = object() + tmsg = self._catalog.get(ctxt_message_id, missing) + if tmsg is missing: + # XXX logilab patch for compat w/ catalog generated by cw < 3.5 + return self.ugettext(message) + if self._fallback: + return self._fallback.upgettext(context, message) + return unicode(message) + return tmsg + def ungettext(self, msgid1, msgid2, n): try: tmsg = self._catalog[(msgid1, self.plural(n))] @@ -354,6 +540,19 @@ tmsg = unicode(msgid2) return tmsg + def unpgettext(self, context, msgid1, msgid2, n): + ctxt_message_id = self.CONTEXT_ENCODING % (context, msgid1) + try: + tmsg = self._catalog[(ctxt_message_id, self.plural(n))] + except KeyError: + if self._fallback: + return self._fallback.unpgettext(context, msgid1, msgid2, n) + if n == 1: + tmsg = unicode(msgid1) + else: + tmsg = unicode(msgid2) + return tmsg + # Locate a .mo file using the gettext strategy def find(domain, localedir=None, languages=None, all=0): @@ -397,7 +596,7 @@ _translations = {} def translation(domain, localedir=None, languages=None, - class_=None, fallback=False): + class_=None, fallback=False, codeset=None): if class_ is None: class_ = GNUTranslations mofiles = find(domain, localedir, languages, all=1) @@ -414,9 +613,12 @@ t = _translations.get(key) if t is None: t = _translations.setdefault(key, class_(open(mofile, 'rb'))) - # Copy the translation object to allow setting fallbacks. - # All other instance data is shared with the cached object. + # Copy the translation object to allow setting fallbacks and + # output charset. All other instance data is shared with the + # cached object. t = copy.copy(t) + if codeset: + t.set_output_charset(codeset) if result is None: result = t else: @@ -424,13 +626,16 @@ return result -def install(domain, localedir=None, unicode=False): - translation(domain, localedir, fallback=True).install(unicode) +def install(domain, localedir=None, unicode=False, codeset=None, names=None): + t = translation(domain, localedir, fallback=True, codeset=codeset) + t.install(unicode, names) # a mapping b/w domains and locale directories _localedirs = {} +# a mapping b/w domains and codesets +_localecodesets = {} # current global domain, `messages' used for compatibility w/ GNU gettext _current_domain = 'messages' @@ -443,22 +648,55 @@ def bindtextdomain(domain, localedir=None): + global _localedirs if localedir is not None: _localedirs[domain] = localedir return _localedirs.get(domain, _default_localedir) +def bind_textdomain_codeset(domain, codeset=None): + global _localecodesets + if codeset is not None: + _localecodesets[domain] = codeset + return _localecodesets.get(domain) + + def dgettext(domain, message): try: - t = translation(domain, _localedirs.get(domain, None)) + t = translation(domain, _localedirs.get(domain, None), + codeset=_localecodesets.get(domain)) except IOError: return message return t.gettext(message) +def dpgettext(domain, context, message): + try: + t = translation(domain, _localedirs.get(domain, None), + codeset=_localecodesets.get(domain)) + except IOError: + return message + return t.pgettext(context, message) + +def ldgettext(domain, message): + try: + t = translation(domain, _localedirs.get(domain, None), + codeset=_localecodesets.get(domain)) + except IOError: + return message + return t.lgettext(message) + +def ldpgettext(domain, context, message): + try: + t = translation(domain, _localedirs.get(domain, None), + codeset=_localecodesets.get(domain)) + except IOError: + return message + return t.lpgettext(context, message) def dngettext(domain, msgid1, msgid2, n): try: - t = translation(domain, _localedirs.get(domain, None)) + t = translation(domain, _localedirs.get(domain, None), + codeset=_localecodesets.get(domain)) except IOError: if n == 1: return msgid1 @@ -466,14 +704,62 @@ return msgid2 return t.ngettext(msgid1, msgid2, n) +def dnpgettext(domain, context, msgid1, msgid2, n): + try: + t = translation(domain, _localedirs.get(domain, None), + codeset=_localecodesets.get(domain)) + except IOError: + if n == 1: + return msgid1 + else: + return msgid2 + return t.npgettext(context, msgid1, msgid2, n) + +def ldngettext(domain, msgid1, msgid2, n): + try: + t = translation(domain, _localedirs.get(domain, None), + codeset=_localecodesets.get(domain)) + except IOError: + if n == 1: + return msgid1 + else: + return msgid2 + return t.lngettext(msgid1, msgid2, n) + +def ldnpgettext(domain, context, msgid1, msgid2, n): + try: + t = translation(domain, _localedirs.get(domain, None), + codeset=_localecodesets.get(domain)) + except IOError: + if n == 1: + return msgid1 + else: + return msgid2 + return t.lnpgettext(context, msgid1, msgid2, n) def gettext(message): return dgettext(_current_domain, message) +def pgettext(context, message): + return dpgettext(_current_domain, context, message) + +def lgettext(message): + return ldgettext(_current_domain, message) + +def lpgettext(context, message): + return ldpgettext(_current_domain, context, message) def ngettext(msgid1, msgid2, n): return dngettext(_current_domain, msgid1, msgid2, n) +def npgettext(context, msgid1, msgid2, n): + return dnpgettext(_current_domain, context, msgid1, msgid2, n) + +def lngettext(msgid1, msgid2, n): + return ldngettext(_current_domain, msgid1, msgid2, n) + +def lnpgettext(context, msgid1, msgid2, n): + return ldnpgettext(_current_domain, context, msgid1, msgid2, n) # dcgettext() has been deemed unnecessary and is not implemented. diff -r d6ae24439bee -r c4c07aab1c39 goa/gaesource.py --- a/goa/gaesource.py Thu Sep 10 08:03:18 2009 +0200 +++ b/goa/gaesource.py Fri Sep 18 12:20:39 2009 +0200 @@ -149,7 +149,7 @@ # ISource interface ####################################################### def compile_rql(self, rql): - rqlst = self.repo.querier._rqlhelper.parse(rql) + rqlst = self.repo.vreg.parse(rql) rqlst.restricted_vars = () rqlst.children[0].solutions = self._sols return rqlst diff -r d6ae24439bee -r c4c07aab1c39 goa/goactl.py --- a/goa/goactl.py Thu Sep 10 08:03:18 2009 +0200 +++ b/goa/goactl.py Fri Sep 18 12:20:39 2009 +0200 @@ -8,7 +8,6 @@ __docformat__ = "restructuredtext en" from os.path import exists, join, split, basename, normpath, abspath - from logilab.common.clcommands import register_commands from cubicweb import CW_SOFTWARE_ROOT, BadCommandUsage @@ -19,9 +18,9 @@ from logilab import common as lgc from logilab import constraint as lgcstr from logilab import mtconverter as lgmtc -import rql, yams, yapps, simplejson, dateutil, vobject, docutils, roman +import rql, yams, yapps, simplejson, docutils, roman -SLINK_DIRECTORIES = ( +SLINK_DIRECTORIES = [ (lgc.__path__[0], 'logilab/common'), (lgmtc.__path__[0], 'logilab/mtconverter'), (lgcstr.__path__[0], 'logilab/constraint'), @@ -29,8 +28,6 @@ (simplejson.__path__[0], 'simplejson'), (yams.__path__[0], 'yams'), (yapps.__path__[0], 'yapps'), - (dateutil.__path__[0], 'dateutil'), - (vobject.__path__[0], 'vobject'), (docutils.__path__[0], 'docutils'), (roman.__file__.replace('.pyc', '.py'), 'roman.py'), @@ -42,7 +39,15 @@ (join(CW_SOFTWARE_ROOT, 'i18n'), join('cubes', 'shared', 'i18n')), (join(CW_SOFTWARE_ROOT, 'goa', 'tools'), 'tools'), (join(CW_SOFTWARE_ROOT, 'goa', 'bin'), 'bin'), - ) + ] + +try: + import dateutil + import vobject + SLINK_DIRECTORIES.extend([ (dateutil.__path__[0], 'dateutil'), + (vobject.__path__[0], 'vobject') ] ) +except ImportError: + pass COPY_CW_FILES = ( '__init__.py', @@ -54,6 +59,7 @@ 'cwconfig.py', 'entity.py', 'interfaces.py', + 'rqlrewrite.py', 'rset.py', 'schema.py', 'schemaviewer.py', @@ -78,7 +84,6 @@ 'server/pool.py', 'server/querier.py', 'server/repository.py', - 'server/rqlrewrite.py', 'server/securityhooks.py', 'server/session.py', 'server/serverconfig.py', diff -r d6ae24439bee -r c4c07aab1c39 i18n/en.po --- a/i18n/en.po Thu Sep 10 08:03:18 2009 +0200 +++ b/i18n/en.po Fri Sep 18 12:20:39 2009 +0200 @@ -5,7 +5,7 @@ msgstr "" "Project-Id-Version: 2.0\n" "POT-Creation-Date: 2006-01-12 17:35+CET\n" -"PO-Revision-Date: 2009-08-05 08:39+0200\n" +"PO-Revision-Date: 2009-09-17 11:53+0200\n" "Last-Translator: Sylvain Thenault \n" "Language-Team: English \n" "MIME-Version: 1.0\n" @@ -107,10 +107,6 @@ msgstr "" #, python-format -msgid "%s is not the initial state (%s) for this entity" -msgstr "" - -#, python-format msgid "%s not estimated" msgstr "" @@ -197,6 +193,15 @@ msgid "Attributes" msgstr "" +# schema pot file, generated on 2009-09-16 16:46:55 +# +# singular and plural forms for each entity type +msgid "BaseTransition" +msgstr "Transition (abstract)" + +msgid "BaseTransition_plural" +msgstr "Transitions (abstract)" + msgid "Bookmark" msgstr "Bookmark" @@ -351,6 +356,9 @@ msgid "Interval_plural" msgstr "Intervals" +msgid "New BaseTransition" +msgstr "XXX" + msgid "New Bookmark" msgstr "New bookmark" @@ -358,7 +366,7 @@ msgstr "New attribute" msgid "New CWCache" -msgstr "" +msgstr "New cache" msgid "New CWConstraint" msgstr "New constraint" @@ -399,12 +407,21 @@ msgid "New State" msgstr "New state" +msgid "New SubWorkflowExitPoint" +msgstr "New subworkflow exit-point" + msgid "New TrInfo" msgstr "New transition information" msgid "New Transition" msgstr "New transition" +msgid "New Workflow" +msgstr "New workflow" + +msgid "New WorkflowTransition" +msgstr "New workflow-transition" + msgid "No query has been executed" msgstr "" @@ -472,6 +489,12 @@ msgid "String_plural" msgstr "Strings" +msgid "SubWorkflowExitPoint" +msgstr "Subworkflow exit-point" + +msgid "SubWorkflowExitPoint_plural" +msgstr "subworkflow exit-points" + msgid "Subject:" msgstr "" @@ -492,12 +515,8 @@ msgid "The view %s could not be found" msgstr "" -msgid "There is no workflow defined for this entity." -msgstr "" - -#, python-format -msgid "This %s" -msgstr "" +msgid "This BaseTransition" +msgstr "This abstract transition" msgid "This Bookmark" msgstr "This bookmark" @@ -506,7 +525,7 @@ msgstr "This attribute" msgid "This CWCache" -msgstr "" +msgstr "This cache" msgid "This CWConstraint" msgstr "This constraint" @@ -547,12 +566,21 @@ msgid "This State" msgstr "This state" +msgid "This SubWorkflowExitPoint" +msgstr "This subworkflow exit-point" + msgid "This TrInfo" msgstr "This transition information" msgid "This Transition" msgstr "This transition" +msgid "This Workflow" +msgstr "This workflow" + +msgid "This WorkflowTransition" +msgstr "This workflow-transition" + msgid "Time" msgstr "Time" @@ -584,9 +612,21 @@ msgid "What's new?" msgstr "" +msgid "Workflow" +msgstr "Workflow" + msgid "Workflow history" msgstr "" +msgid "WorkflowTransition" +msgstr "Workflow-transition" + +msgid "WorkflowTransition_plural" +msgstr "Workflow-transitions" + +msgid "Workflow_plural" +msgstr "Workflows" + msgid "You are not connected to an instance !" msgstr "" @@ -622,9 +662,6 @@ msgid "[%s supervision] changes summary" msgstr "" -msgid "__msg state changed" -msgstr "state changed" - msgid "" "a RQL expression which should return some results, else the transition won't " "be available. This query may use X and U variables that will respectivly " @@ -644,12 +681,12 @@ msgid "about this site" msgstr "" +msgid "abstract base class for transitions" +msgstr "" + msgid "access type" msgstr "" -msgid "account state" -msgstr "" - msgid "action(s) on this selection" msgstr "" @@ -662,6 +699,12 @@ msgid "actions_addentity_description" msgstr "" +msgid "actions_addrelated" +msgstr "" + +msgid "actions_addrelated_description" +msgstr "" + msgid "actions_cancel" msgstr "cancel the selection" @@ -848,7 +891,10 @@ msgid "add State allowed_transition Transition subject" msgstr "allowed transition" -msgid "add State state_of CWEType object" +msgid "add State allowed_transition WorkflowTransition subject" +msgstr "workflow-transition" + +msgid "add State state_of Workflow object" msgstr "state" msgid "add Transition condition RQLExpression subject" @@ -860,63 +906,34 @@ msgid "add Transition destination_state State subject" msgstr "destination state" -msgid "add Transition transition_of CWEType object" +msgid "add Transition transition_of Workflow object" msgstr "transition" -msgid "add a Bookmark" -msgstr "add a bookmark" - -msgid "add a CWAttribute" -msgstr "add an attribute" - -msgid "add a CWCache" -msgstr "add a cubicweb cache" - -msgid "add a CWConstraint" -msgstr "add a constraint" - -msgid "add a CWConstraintType" -msgstr "add a constraint type" - +msgid "add WorkflowTransition condition RQLExpression subject" +msgstr "workflow-transition" + +msgid "add WorkflowTransition subworkflow_exit SubWorkflowExitPoint subject" +msgstr "subworkflow exit-point" + +msgid "add WorkflowTransition transition_of Workflow object" +msgstr "workflow-transition" + +msgctxt "inlined:CWRelation.from_entity.subject" msgid "add a CWEType" msgstr "add an entity type" -msgid "add a CWGroup" -msgstr "add a group" - -msgid "add a CWPermission" -msgstr "add a permission" - -msgid "add a CWProperty" -msgstr "add a property" - +msgctxt "inlined:CWRelation.to_entity.subject" +msgid "add a CWEType" +msgstr "add an entity type" + +msgctxt "inlined:CWRelation.relation_type.subject" msgid "add a CWRType" msgstr "add a relation type" -msgid "add a CWRelation" -msgstr "add a relation" - -msgid "add a CWUser" -msgstr "add a user" - +msgctxt "inlined:CWUser.use_email.subject" msgid "add a EmailAddress" msgstr "add an email address" -msgid "add a ExternalUri" -msgstr "and an external uri" - -msgid "add a RQLExpression" -msgstr "add a rql expression" - -msgid "add a State" -msgstr "add a state" - -msgid "add a TrInfo" -msgstr "add a transition information" - -msgid "add a Transition" -msgstr "add a transition" - msgid "add a new permission" msgstr "" @@ -931,6 +948,24 @@ msgid "add_permission" msgstr "can be added by" +# subject and object forms for each relation type +# (no object form for final relation types) +msgctxt "CWEType" +msgid "add_permission" +msgstr "" + +msgctxt "CWRType" +msgid "add_permission" +msgstr "" + +msgctxt "CWGroup" +msgid "add_permission_object" +msgstr "" + +msgctxt "RQLExpression" +msgid "add_permission_object" +msgstr "" + msgid "add_permission_object" msgstr "has permission to add" @@ -944,12 +979,26 @@ "(toeid)s" msgstr "" +msgid "addrelated" +msgstr "" + +msgid "address" +msgstr "" + +msgctxt "EmailAddress" msgid "address" msgstr "" msgid "alias" msgstr "" +msgctxt "EmailAddress" +msgid "alias" +msgstr "" + +msgid "allow to set a specific workflow for an entity" +msgstr "" + msgid "allowed transition from this state" msgstr "" @@ -959,6 +1008,22 @@ msgid "allowed_transition" msgstr "allowed transition" +msgctxt "State" +msgid "allowed_transition" +msgstr "" + +msgctxt "BaseTransition" +msgid "allowed_transition_object" +msgstr "" + +msgctxt "Transition" +msgid "allowed_transition_object" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "allowed_transition_object" +msgstr "" + msgid "allowed_transition_object" msgstr "incoming states" @@ -1038,6 +1103,14 @@ msgid "bookmarked_by" msgstr "bookmarked by" +msgctxt "Bookmark" +msgid "bookmarked_by" +msgstr "bookmarked by" + +msgctxt "CWUser" +msgid "bookmarked_by_object" +msgstr "" + msgid "bookmarked_by_object" msgstr "has bookmarks" @@ -1122,6 +1195,28 @@ msgid "by relation" msgstr "" +msgid "by_transition" +msgstr "" + +msgctxt "TrInfo" +msgid "by_transition" +msgstr "by transition" + +msgctxt "BaseTransition" +msgid "by_transition_object" +msgstr "" + +msgctxt "Transition" +msgid "by_transition_object" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "by_transition_object" +msgstr "" + +msgid "by_transition_object" +msgstr "transition information" + msgid "calendar" msgstr "" @@ -1152,6 +1247,9 @@ msgid "can't display data, unexpected error: %s" msgstr "" +msgid "can't have multiple exits on the same state" +msgstr "" + #, python-format msgid "" "can't set inlined=%(inlined)s, %(stype)s %(rtype)s %(otype)s has cardinality=" @@ -1164,9 +1262,14 @@ msgid "cancel this insert" msgstr "" -msgid "canonical" -msgstr "" - +msgid "cardinality" +msgstr "" + +msgctxt "CWAttribute" +msgid "cardinality" +msgstr "cardinality" + +msgctxt "CWRelation" msgid "cardinality" msgstr "" @@ -1192,9 +1295,14 @@ msgid "comment" msgstr "" -msgid "comment:" -msgstr "" - +msgctxt "TrInfo" +msgid "comment" +msgstr "" + +msgid "comment_format" +msgstr "format" + +msgctxt "TrInfo" msgid "comment_format" msgstr "format" @@ -1246,6 +1354,12 @@ msgid "components_navigation_description" msgstr "pagination component for large resultsets" +msgid "components_pdfview" +msgstr "" + +msgid "components_pdfview_description" +msgstr "" + msgid "components_rqlinput" msgstr "rql input box" @@ -1255,12 +1369,32 @@ msgid "composite" msgstr "" +msgctxt "CWRelation" +msgid "composite" +msgstr "" + +msgid "condition" +msgstr "" + +msgctxt "BaseTransition" +msgid "condition" +msgstr "" + +msgctxt "Transition" +msgid "condition" +msgstr "" + +msgctxt "WorkflowTransition" msgid "condition" msgstr "" msgid "condition:" msgstr "" +msgctxt "RQLExpression" +msgid "condition_object" +msgstr "" + msgid "condition_object" msgstr "condition of" @@ -1270,6 +1404,18 @@ msgid "constrained_by" msgstr "constrained by" +msgctxt "CWAttribute" +msgid "constrained_by" +msgstr "constrained by" + +msgctxt "CWRelation" +msgid "constrained_by" +msgstr "constrained by" + +msgctxt "CWConstraint" +msgid "constrained_by_object" +msgstr "" + msgid "constrained_by_object" msgstr "constraints" @@ -1445,23 +1591,43 @@ msgid "creating RQLExpression (Transition %(linkto)s condition RQLExpression)" msgstr "creating rql expression for transition %(linkto)s" +msgid "" +"creating RQLExpression (WorkflowTransition %(linkto)s condition " +"RQLExpression)" +msgstr "creating rql expression for workflow-transition %(linkto)s" + msgid "creating State (State allowed_transition Transition %(linkto)s)" msgstr "creating a state able to trigger transition %(linkto)s" -msgid "creating State (State state_of CWEType %(linkto)s)" -msgstr "creating state for the %(linkto)s entity type" +msgid "creating State (State state_of Workflow %(linkto)s)" +msgstr "creating state of workflow %(linkto)s" msgid "creating State (Transition %(linkto)s destination_state State)" msgstr "creating destination state for transition %(linkto)s" +msgid "" +"creating SubWorkflowExitPoint (WorkflowTransition %(linkto)s " +"subworkflow_exit SubWorkflowExitPoint)" +msgstr "creating subworkflow exit-point for workflow-transition %(linkto)s" + msgid "creating Transition (State %(linkto)s allowed_transition Transition)" msgstr "creating triggerable transition for state %(linkto)s" msgid "creating Transition (Transition destination_state State %(linkto)s)" msgstr "creating transition leading to state %(linkto)s" -msgid "creating Transition (Transition transition_of CWEType %(linkto)s)" -msgstr "creating transition for the %(linkto)s entity type" +msgid "creating Transition (Transition transition_of Workflow %(linkto)s)" +msgstr "creating transition of workflow %(linkto)s" + +msgid "" +"creating WorkflowTransition (State %(linkto)s allowed_transition " +"WorkflowTransition)" +msgstr "creating workflow-transition leading to state %(linkto)s" + +msgid "" +"creating WorkflowTransition (WorkflowTransition transition_of Workflow %" +"(linkto)s)" +msgstr "creating workflow-transition of workflow %(linkto)s" msgid "creation" msgstr "" @@ -1475,6 +1641,14 @@ msgid "cstrtype" msgstr "constraint's type" +msgctxt "CWConstraint" +msgid "cstrtype" +msgstr "" + +msgctxt "CWConstraintType" +msgid "cstrtype_object" +msgstr "" + msgid "cstrtype_object" msgstr "used by" @@ -1488,6 +1662,12 @@ msgid "currently attached file: %s" msgstr "" +msgid "custom_workflow" +msgstr "custom workflow" + +msgid "custom_workflow_object" +msgstr "custom workflow of" + msgid "cwetype-schema-image" msgstr "schema" @@ -1524,6 +1704,30 @@ msgid "default text format for rich text fields." msgstr "" +msgid "default user workflow" +msgstr "" + +msgid "default workflow for an entity type" +msgstr "" + +msgid "default_workflow" +msgstr "default workflow" + +msgctxt "CWEType" +msgid "default_workflow" +msgstr "default workflow" + +msgctxt "Workflow" +msgid "default_workflow_object" +msgstr "" + +msgid "default_workflow_object" +msgstr "default workflow of" + +msgid "defaultval" +msgstr "default value" + +msgctxt "CWAttribute" msgid "defaultval" msgstr "default value" @@ -1558,6 +1762,9 @@ msgid "define an entity type, used to build the instance schema" msgstr "" +msgid "define how we get out from a sub-workflow" +msgstr "" + msgid "" "defines what's the property is applied for. You must select this first to be " "able to set value" @@ -1581,6 +1788,22 @@ msgid "delete_permission" msgstr "can be deleted by" +msgctxt "CWEType" +msgid "delete_permission" +msgstr "delete permission" + +msgctxt "CWRType" +msgid "delete_permission" +msgstr "" + +msgctxt "CWGroup" +msgid "delete_permission_object" +msgstr "" + +msgctxt "RQLExpression" +msgid "delete_permission_object" +msgstr "" + msgid "delete_permission_object" msgstr "has permission to delete" @@ -1600,9 +1823,84 @@ msgid "description" msgstr "" +msgctxt "CWEType" +msgid "description" +msgstr "" + +msgctxt "CWRelation" +msgid "description" +msgstr "" + +msgctxt "Workflow" +msgid "description" +msgstr "" + +msgctxt "CWAttribute" +msgid "description" +msgstr "" + +msgctxt "Transition" +msgid "description" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "description" +msgstr "" + +msgctxt "State" +msgid "description" +msgstr "" + +msgctxt "CWRType" +msgid "description" +msgstr "" + +msgctxt "BaseTransition" +msgid "description" +msgstr "" + msgid "description_format" msgstr "format" +msgctxt "CWEType" +msgid "description_format" +msgstr "" + +msgctxt "CWRelation" +msgid "description_format" +msgstr "" + +msgctxt "Workflow" +msgid "description_format" +msgstr "format" + +msgctxt "CWAttribute" +msgid "description_format" +msgstr "format" + +msgctxt "Transition" +msgid "description_format" +msgstr "format" + +msgctxt "WorkflowTransition" +msgid "description_format" +msgstr "format" + +msgctxt "State" +msgid "description_format" +msgstr "format" + +msgctxt "CWRType" +msgid "description_format" +msgstr "format" + +msgctxt "BaseTransition" +msgid "description_format" +msgstr "format" + +msgid "destination state" +msgstr "" + msgid "destination state for this transition" msgstr "" @@ -1612,6 +1910,18 @@ msgid "destination_state" msgstr "destination state" +msgctxt "Transition" +msgid "destination_state" +msgstr "" + +msgctxt "SubWorkflowExitPoint" +msgid "destination_state" +msgstr "destination state" + +msgctxt "State" +msgid "destination_state_object" +msgstr "" + msgid "destination_state_object" msgstr "destination of" @@ -1640,6 +1950,9 @@ msgid "display the component or not" msgstr "" +msgid "display the pdf icon or not" +msgstr "" + msgid "" "distinct label to distinguate between other permission entity of the same " "name" @@ -1655,6 +1968,9 @@ msgid "download icon" msgstr "" +msgid "download page as pdf" +msgstr "" + msgid "download schema as owl" msgstr "" @@ -1712,6 +2028,9 @@ msgid "entity edited" msgstr "" +msgid "entity has no workflow set" +msgstr "" + msgid "entity linked" msgstr "" @@ -1723,10 +2042,7 @@ "configuration" msgstr "" -msgid "entity types which may use this state" -msgstr "" - -msgid "entity types which may use this transition" +msgid "entity types which may use this workflow" msgstr "" msgid "error while embedding page" @@ -1746,15 +2062,33 @@ msgid "eta_date" msgstr "" +msgid "exit_point" +msgstr "" + +msgid "exit_point_object" +msgstr "" + +#, python-format +msgid "exiting from subworkflow %s" +msgstr "" + msgid "expected:" msgstr "" msgid "expression" msgstr "" +msgctxt "RQLExpression" +msgid "expression" +msgstr "" + msgid "exprtype" msgstr "expression's type" +msgctxt "RQLExpression" +msgid "exprtype" +msgstr "" + msgid "external page" msgstr "" @@ -1806,6 +2140,18 @@ msgid "final" msgstr "" +msgctxt "CWEType" +msgid "final" +msgstr "" + +msgctxt "CWRType" +msgid "final" +msgstr "" + +msgid "firstname" +msgstr "" + +msgctxt "CWUser" msgid "firstname" msgstr "" @@ -1818,6 +2164,14 @@ msgid "for_user" msgstr "for user" +msgctxt "CWProperty" +msgid "for_user" +msgstr "" + +msgctxt "CWUser" +msgid "for_user_object" +msgstr "" + msgid "for_user_object" msgstr "use properties" @@ -1834,6 +2188,18 @@ msgid "from_entity" msgstr "from entity" +msgctxt "CWAttribute" +msgid "from_entity" +msgstr "from entity" + +msgctxt "CWRelation" +msgid "from_entity" +msgstr "from entity" + +msgctxt "CWEType" +msgid "from_entity_object" +msgstr "" + msgid "from_entity_object" msgstr "subjet relation" @@ -1843,6 +2209,14 @@ msgid "from_state" msgstr "from state" +msgctxt "TrInfo" +msgid "from_state" +msgstr "from state" + +msgctxt "State" +msgid "from_state_object" +msgstr "" + msgid "from_state_object" msgstr "transitions from this state" @@ -1852,9 +2226,17 @@ msgid "fulltext_container" msgstr "" +msgctxt "CWRType" +msgid "fulltext_container" +msgstr "fulltext container" + msgid "fulltextindexed" msgstr "fulltext indexed" +msgctxt "CWAttribute" +msgid "fulltextindexed" +msgstr "" + msgid "generic plot" msgstr "" @@ -1873,6 +2255,10 @@ msgid "granted to groups" msgstr "" +#, python-format +msgid "graphical representation of %s" +msgstr "" + msgid "graphical representation of the instance'schema" msgstr "" @@ -1961,6 +2347,9 @@ msgid "id of main template used to render pages" msgstr "" +msgid "identical to" +msgstr "" + msgid "identical_to" msgstr "identical to" @@ -1987,6 +2376,14 @@ msgid "in_group" msgstr "in group" +msgctxt "CWUser" +msgid "in_group" +msgstr "in group" + +msgctxt "CWGroup" +msgid "in_group_object" +msgstr "" + msgid "in_group_object" msgstr "contains" @@ -2012,6 +2409,10 @@ msgid "indexed" msgstr "" +msgctxt "CWAttribute" +msgid "indexed" +msgstr "" + msgid "indicate the current state of an entity" msgstr "" @@ -2027,18 +2428,30 @@ msgid "initial estimation %s" msgstr "" -msgid "initial state for entities of this type" +msgid "initial state for this workflow" msgstr "" msgid "initial_state" msgstr "initial state" +msgctxt "Workflow" +msgid "initial_state" +msgstr "initial state" + +msgctxt "State" +msgid "initial_state_object" +msgstr "" + msgid "initial_state_object" msgstr "initial state of" msgid "inlined" msgstr "" +msgctxt "CWRType" +msgid "inlined" +msgstr "" + msgid "instance schema" msgstr "" @@ -2048,6 +2461,10 @@ msgid "internationalizable" msgstr "" +msgctxt "CWAttribute" +msgid "internationalizable" +msgstr "" + #, python-format msgid "invalid action %r" msgstr "" @@ -2084,7 +2501,7 @@ msgstr "" msgid "is_instance_of_object" -msgstr "" +msgstr "is instance of" msgid "is_object" msgstr "has instances" @@ -2101,6 +2518,10 @@ msgid "label" msgstr "" +msgctxt "CWPermission" +msgid "label" +msgstr "" + msgid "language of the user interface" msgstr "" @@ -2110,6 +2531,10 @@ msgid "last_login_time" msgstr "last login time" +msgctxt "CWUser" +msgid "last_login_time" +msgstr "last login time" + msgid "latest modification time of an entity" msgstr "" @@ -2138,13 +2563,16 @@ msgid "link a relation definition to its subject entity type" msgstr "" -msgid "link a state to one or more entity type" +msgid "link a state to one or more workflow" msgstr "" msgid "link a transition information to its object" msgstr "" -msgid "link a transition to one or more entity type" +msgid "link a transition to one or more workflow" +msgstr "" + +msgid "link a workflow to one or more entity type" msgstr "" msgid "link to each item in" @@ -2162,6 +2590,10 @@ msgid "login" msgstr "" +msgctxt "CWUser" +msgid "login" +msgstr "" + msgid "login or email" msgstr "" @@ -2181,6 +2613,10 @@ msgid "mainvars" msgstr "" +msgctxt "RQLExpression" +msgid "mainvars" +msgstr "" + msgid "manage" msgstr "" @@ -2196,6 +2632,9 @@ msgid "managers" msgstr "" +msgid "mandatory relation" +msgstr "" + msgid "march" msgstr "" @@ -2242,6 +2681,50 @@ msgid "name" msgstr "" +msgctxt "CWEType" +msgid "name" +msgstr "" + +msgctxt "Transition" +msgid "name" +msgstr "" + +msgctxt "Workflow" +msgid "name" +msgstr "" + +msgctxt "CWGroup" +msgid "name" +msgstr "" + +msgctxt "CWConstraintType" +msgid "name" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "name" +msgstr "" + +msgctxt "State" +msgid "name" +msgstr "" + +msgctxt "CWPermission" +msgid "name" +msgstr "" + +msgctxt "CWRType" +msgid "name" +msgstr "" + +msgctxt "BaseTransition" +msgid "name" +msgstr "" + +msgctxt "CWCache" +msgid "name" +msgstr "" + msgid "name of the cache" msgstr "" @@ -2347,6 +2830,14 @@ msgid "ordernum" msgstr "order" +msgctxt "CWAttribute" +msgid "ordernum" +msgstr "" + +msgctxt "CWRelation" +msgid "ordernum" +msgstr "" + msgid "owl" msgstr "" @@ -2381,6 +2872,10 @@ msgid "path" msgstr "" +msgctxt "Bookmark" +msgid "path" +msgstr "" + msgid "permission" msgstr "" @@ -2402,6 +2897,10 @@ msgid "pkey" msgstr "key" +msgctxt "CWProperty" +msgid "pkey" +msgstr "" + msgid "please correct errors below" msgstr "" @@ -2414,6 +2913,20 @@ msgid "powered by CubicWeb" msgstr "" +msgid "prefered_form" +msgstr "" + +msgctxt "EmailAddress" +msgid "prefered_form" +msgstr "" + +msgctxt "EmailAddress" +msgid "prefered_form_object" +msgstr "" + +msgid "prefered_form_object" +msgstr "" + msgid "preferences" msgstr "" @@ -2426,6 +2939,14 @@ msgid "primary_email" msgstr "primary email" +msgctxt "CWUser" +msgid "primary_email" +msgstr "primary email" + +msgctxt "EmailAddress" +msgid "primary_email_object" +msgstr "" + msgid "primary_email_object" msgstr "primary email of" @@ -2442,17 +2963,39 @@ msgstr "" msgid "read_perm" -msgstr "" +msgstr "read perm" msgid "read_permission" msgstr "can be read by" +msgctxt "CWEType" +msgid "read_permission" +msgstr "read permission" + +msgctxt "CWRType" +msgid "read_permission" +msgstr "read permission" + +msgctxt "CWGroup" +msgid "read_permission_object" +msgstr "" + +msgctxt "RQLExpression" +msgid "read_permission_object" +msgstr "" + msgid "read_permission_object" msgstr "has permission to delete" msgid "registry" msgstr "" +msgid "related entity has no state" +msgstr "" + +msgid "related entity has no workflow set" +msgstr "" + #, python-format msgid "relation %(relname)s of %(ent)s" msgstr "" @@ -2460,6 +3003,18 @@ msgid "relation_type" msgstr "relation type" +msgctxt "CWAttribute" +msgid "relation_type" +msgstr "relation type" + +msgctxt "CWRelation" +msgid "relation_type" +msgstr "relation type" + +msgctxt "CWRType" +msgid "relation_type_object" +msgstr "" + msgid "relation_type_object" msgstr "relation definitions" @@ -2472,71 +3027,53 @@ msgid "relative url of the bookmarked page" msgstr "" -msgid "remove this Bookmark" -msgstr "remove this bookmark" - -msgid "remove this CWAttribute" -msgstr "remove this attribute" - -msgid "remove this CWCache" -msgstr "remove this cubicweb cache" - -msgid "remove this CWConstraint" -msgstr "remove this constraint" - -msgid "remove this CWConstraintType" -msgstr "remove this constraint type" - +msgctxt "inlined:CWRelation:from_entity:subject" +msgid "remove this CWEType" +msgstr "remove this entity type" + +msgctxt "inlined:CWRelation:to_entity:subject" msgid "remove this CWEType" msgstr "remove this entity type" -msgid "remove this CWGroup" -msgstr "remove this group" - -msgid "remove this CWPermission" -msgstr "remove this permission" - -msgid "remove this CWProperty" -msgstr "remove this property" - +msgctxt "inlined:CWRelation:relation_type:subject" msgid "remove this CWRType" msgstr "remove this relation type" -msgid "remove this CWRelation" -msgstr "remove this relation" - -msgid "remove this CWUser" -msgstr "remove this user" - +msgctxt "inlined:CWUser:use_email:subject" msgid "remove this EmailAddress" msgstr "remove this email address" -msgid "remove this ExternalUri" -msgstr "" - -msgid "remove this RQLExpression" -msgstr "remove this RQL expression" - -msgid "remove this State" -msgstr "remove this state" - -msgid "remove this TrInfo" -msgstr "remove this transition information" - -msgid "remove this Transition" -msgstr "remove this transition" - msgid "require_group" msgstr "require the group" +msgctxt "BaseTransition" +msgid "require_group" +msgstr "require group" + +msgctxt "Transition" +msgid "require_group" +msgstr "require group" + +msgctxt "CWPermission" +msgid "require_group" +msgstr "require group" + +msgctxt "WorkflowTransition" +msgid "require_group" +msgstr "require group" + +msgctxt "CWGroup" +msgid "require_group_object" +msgstr "" + msgid "require_group_object" msgstr "required by" msgid "require_permission" -msgstr "" +msgstr "require permission" msgid "require_permission_object" -msgstr "" +msgstr "required by" msgid "required attribute" msgstr "" @@ -2653,6 +3190,9 @@ msgid "semantic description of this transition" msgstr "" +msgid "semantic description of this workflow" +msgstr "" + msgid "send email" msgstr "" @@ -2705,11 +3245,22 @@ msgid "sparql xml" msgstr "" +msgid "special transition allowing to go through a sub-workflow" +msgstr "" + msgid "specializes" msgstr "" +msgctxt "CWEType" +msgid "specializes" +msgstr "" + +msgctxt "CWEType" +msgid "specializes_object" +msgstr "" + msgid "specializes_object" -msgstr "" +msgstr "specialized by" msgid "startup views" msgstr "" @@ -2717,9 +3268,28 @@ msgid "state" msgstr "" +msgid "state doesn't belong to entity's current workflow" +msgstr "" + +msgid "state doesn't belong to entity's workflow" +msgstr "" + +msgid "" +"state doesn't belong to entity's workflow. You may want to set a custom " +"workflow for this entity first." +msgstr "" + msgid "state_of" msgstr "state of" +msgctxt "State" +msgid "state_of" +msgstr "" + +msgctxt "Workflow" +msgid "state_of_object" +msgstr "" + msgid "state_of_object" msgstr "use states" @@ -2742,12 +3312,65 @@ msgid "subject_plural:" msgstr "subjects:" +msgid "subworkflow" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "subworkflow" +msgstr "" + +msgid "subworkflow state" +msgstr "" + +msgid "subworkflow_exit" +msgstr "subworkflow exit" + +msgctxt "WorkflowTransition" +msgid "subworkflow_exit" +msgstr "" + +msgctxt "SubWorkflowExitPoint" +msgid "subworkflow_exit_object" +msgstr "" + +msgid "subworkflow_exit_object" +msgstr "subworkflow exit of" + +msgctxt "Workflow" +msgid "subworkflow_object" +msgstr "" + +msgid "subworkflow_object" +msgstr "subworkflow of" + +msgid "subworkflow_state" +msgstr "subworkflow state" + +msgctxt "SubWorkflowExitPoint" +msgid "subworkflow_state" +msgstr "subworkflow state" + +msgctxt "State" +msgid "subworkflow_state_object" +msgstr "" + +msgid "subworkflow_state_object" +msgstr "" + msgid "sunday" msgstr "" msgid "surname" msgstr "" +msgctxt "CWUser" +msgid "surname" +msgstr "" + +msgid "symetric" +msgstr "" + +msgctxt "CWRType" msgid "symetric" msgstr "" @@ -2806,6 +3429,10 @@ msgid "timestamp" msgstr "" +msgctxt "CWCache" +msgid "timestamp" +msgstr "" + msgid "timestamp of the latest source synchronization." msgstr "" @@ -2815,6 +3442,10 @@ msgid "title" msgstr "" +msgctxt "Bookmark" +msgid "title" +msgstr "" + msgid "to" msgstr "" @@ -2828,6 +3459,18 @@ msgid "to_entity" msgstr "to entity" +msgctxt "CWAttribute" +msgid "to_entity" +msgstr "to entity" + +msgctxt "CWRelation" +msgid "to_entity" +msgstr "to entity" + +msgctxt "CWEType" +msgid "to_entity_object" +msgstr "" + msgid "to_entity_object" msgstr "object relations" @@ -2837,6 +3480,14 @@ msgid "to_state" msgstr "to state" +msgctxt "TrInfo" +msgid "to_state" +msgstr "" + +msgctxt "State" +msgid "to_state_object" +msgstr "" + msgid "to_state_object" msgstr "transitions to this state" @@ -2846,13 +3497,34 @@ msgid "toggle check boxes" msgstr "" -#, python-format -msgid "transition from %s to %s does not exist or is not allowed" +msgid "transition doesn't belong to entity's workflow" +msgstr "" + +msgid "transition isn't allowed" +msgstr "" + +msgid "transition may not be fired" msgstr "" msgid "transition_of" msgstr "transition of" +msgctxt "BaseTransition" +msgid "transition_of" +msgstr "transition of" + +msgctxt "Transition" +msgid "transition_of" +msgstr "transition of" + +msgctxt "WorkflowTransition" +msgid "transition_of" +msgstr "" + +msgctxt "Workflow" +msgid "transition_of_object" +msgstr "" + msgid "transition_of_object" msgstr "use transitions" @@ -2925,6 +3597,10 @@ msgid "upassword" msgstr "password" +msgctxt "CWUser" +msgid "upassword" +msgstr "" + msgid "update" msgstr "" @@ -2934,6 +3610,18 @@ msgid "update_permission" msgstr "can be updated by" +msgctxt "CWEType" +msgid "update_permission" +msgstr "" + +msgctxt "CWGroup" +msgid "update_permission_object" +msgstr "" + +msgctxt "RQLExpression" +msgid "update_permission_object" +msgstr "" + msgid "update_permission_object" msgstr "has permission to update" @@ -2944,6 +3632,10 @@ msgid "uri" msgstr "" +msgctxt "ExternalUri" +msgid "uri" +msgstr "" + msgid "use template languages" msgstr "" @@ -2955,6 +3647,14 @@ msgid "use_email" msgstr "use email" +msgctxt "CWUser" +msgid "use_email" +msgstr "" + +msgctxt "EmailAddress" +msgid "use_email_object" +msgstr "" + msgid "use_email_object" msgstr "used by" @@ -3005,6 +3705,14 @@ msgid "value" msgstr "" +msgctxt "CWConstraint" +msgid "value" +msgstr "" + +msgctxt "CWProperty" +msgid "value" +msgstr "" + msgid "value associated to this key is not editable manually" msgstr "" @@ -3056,12 +3764,41 @@ msgid "" "when multiple addresses are equivalent (such as python-projects@logilab.org " -"and python-projects@lists.logilab.org), set this to true on one of them " -"which is the preferred form." +"and python-projects@lists.logilab.org), set this to indicate which is the " +"preferred form." +msgstr "" + +msgid "workflow" msgstr "" #, python-format -msgid "workflow for %s" +msgid "workflow changed to \"%s\"" +msgstr "" + +msgid "workflow has no initial state" +msgstr "" + +msgid "workflow history item" +msgstr "" + +msgid "workflow to which this state belongs" +msgstr "" + +msgid "workflow to which this transition belongs" +msgstr "" + +msgid "workflow_of" +msgstr "" + +msgctxt "Workflow" +msgid "workflow_of" +msgstr "" + +msgctxt "CWEType" +msgid "workflow_of_object" +msgstr "" + +msgid "workflow_of_object" msgstr "" msgid "xbel" diff -r d6ae24439bee -r c4c07aab1c39 i18n/es.po --- a/i18n/es.po Thu Sep 10 08:03:18 2009 +0200 +++ b/i18n/es.po Fri Sep 18 12:20:39 2009 +0200 @@ -112,10 +112,6 @@ msgstr "%s reporte de errores" #, python-format -msgid "%s is not the initial state (%s) for this entity" -msgstr "" - -#, python-format msgid "%s not estimated" msgstr "%s no estimado(s)" @@ -205,6 +201,15 @@ msgid "Attributes" msgstr "Atributos" +# schema pot file, generated on 2009-09-16 16:46:55 +# +# singular and plural forms for each entity type +msgid "BaseTransition" +msgstr "" + +msgid "BaseTransition_plural" +msgstr "" + msgid "Bookmark" msgstr "Favorito" @@ -359,6 +364,9 @@ msgid "Interval_plural" msgstr "Duraciones" +msgid "New BaseTransition" +msgstr "" + msgid "New Bookmark" msgstr "Agregar a Favoritos" @@ -407,12 +415,21 @@ msgid "New State" msgstr "Agregar Estado" +msgid "New SubWorkflowExitPoint" +msgstr "" + msgid "New TrInfo" msgstr "Agregar Información de Transición" msgid "New Transition" msgstr "Agregar transición" +msgid "New Workflow" +msgstr "" + +msgid "New WorkflowTransition" +msgstr "" + msgid "No query has been executed" msgstr "Ninguna búsqueda ha sido ejecutada" @@ -480,6 +497,12 @@ msgid "String_plural" msgstr "Cadenas de caracteres" +msgid "SubWorkflowExitPoint" +msgstr "" + +msgid "SubWorkflowExitPoint_plural" +msgstr "" + msgid "Subject:" msgstr "Sujeto:" @@ -500,12 +523,8 @@ msgid "The view %s could not be found" msgstr "La vista %s no ha podido ser encontrada" -msgid "There is no workflow defined for this entity." -msgstr "No hay workflow para este entidad" - -#, python-format -msgid "This %s" -msgstr "Este %s" +msgid "This BaseTransition" +msgstr "" msgid "This Bookmark" msgstr "Este favorito" @@ -555,12 +574,21 @@ msgid "This State" msgstr "Este estado" +msgid "This SubWorkflowExitPoint" +msgstr "" + msgid "This TrInfo" msgstr "Esta información de transición" msgid "This Transition" msgstr "Esta transición" +msgid "This Workflow" +msgstr "" + +msgid "This WorkflowTransition" +msgstr "" + msgid "Time" msgstr "Hora" @@ -592,9 +620,21 @@ msgid "What's new?" msgstr "Lo último en el sitio" +msgid "Workflow" +msgstr "" + msgid "Workflow history" msgstr "Histórico del Workflow" +msgid "WorkflowTransition" +msgstr "" + +msgid "WorkflowTransition_plural" +msgstr "" + +msgid "Workflow_plural" +msgstr "" + msgid "You are not connected to an instance !" msgstr "" @@ -628,7 +668,8 @@ msgid "" "You have no access to this view or it can not be used to display the current " "data." -msgstr "No tiene acceso a esta vista o No se puede utilizare para los datos actuales." +msgstr "" +"No tiene acceso a esta vista o No se puede utilizare para los datos actuales." msgid "" "You're not authorized to access this page. If you think you should, please " @@ -641,9 +682,6 @@ msgid "[%s supervision] changes summary" msgstr "[%s supervision] descripción de cambios" -msgid "__msg state changed" -msgstr "El estado a cambiado" - msgid "" "a RQL expression which should return some results, else the transition won't " "be available. This query may use X and U variables that will respectivly " @@ -666,12 +704,12 @@ msgid "about this site" msgstr "Sobre este Espacio" +msgid "abstract base class for transitions" +msgstr "" + msgid "access type" msgstr "Tipo de Acceso" -msgid "account state" -msgstr "Estado de la Cuenta" - msgid "action(s) on this selection" msgstr "acción(es) en esta selección" @@ -684,6 +722,12 @@ msgid "actions_addentity_description" msgstr "" +msgid "actions_addrelated" +msgstr "" + +msgid "actions_addrelated_description" +msgstr "" + msgid "actions_cancel" msgstr "Anular" @@ -829,28 +873,28 @@ msgstr "Definición de atributo" msgid "add CWEType add_permission RQLExpression subject" -msgstr "Agregar una autorización" +msgstr "Expresión RQL de agregación" msgid "add CWEType delete_permission RQLExpression subject" -msgstr "Eliminar una autorización" +msgstr "Expresión RQL de eliminación" msgid "add CWEType read_permission RQLExpression subject" -msgstr "Definir una expresión RQL de lectura" +msgstr "Expresión RQL de lectura" msgid "add CWEType update_permission RQLExpression subject" msgstr "Definir una expresión RQL de actualización" msgid "add CWProperty for_user CWUser object" -msgstr "Agregar Propiedad" +msgstr "Propiedad" msgid "add CWRType add_permission RQLExpression subject" -msgstr "Agregar expresión RQL de agregación" +msgstr "Expresión RQL de agregación" msgid "add CWRType delete_permission RQLExpression subject" -msgstr "Agregar expresión RQL de eliminación" +msgstr "Expresión RQL de eliminación" msgid "add CWRType read_permission RQLExpression subject" -msgstr "Agregar expresión RQL de lectura" +msgstr "Expresión RQL de lectura" msgid "add CWRelation constrained_by CWConstraint subject" msgstr "Restricción" @@ -859,85 +903,59 @@ msgstr "Definición de relación" msgid "add CWUser in_group CWGroup object" -msgstr "Agregar usuario" +msgstr "Usuario" msgid "add CWUser use_email EmailAddress subject" -msgstr "Agregar email" +msgstr "Email" msgid "add State allowed_transition Transition object" -msgstr "Agregar un estado en entrada" +msgstr "Estado en entrada" msgid "add State allowed_transition Transition subject" -msgstr "Agregar una transición en salida" - -msgid "add State state_of CWEType object" -msgstr "Agregar un estado" +msgstr "Transición en salida" + +msgid "add State allowed_transition WorkflowTransition subject" +msgstr "" + +msgid "add State state_of Workflow object" +msgstr "" msgid "add Transition condition RQLExpression subject" -msgstr "Agregar una Restricción" +msgstr "Restricción" msgid "add Transition destination_state State object" -msgstr "Agregar una transición de entrada" +msgstr "Transición de entrada" msgid "add Transition destination_state State subject" -msgstr "Agregar el estado de salida" - -msgid "add Transition transition_of CWEType object" -msgstr "Agregar una transición" - -msgid "add a Bookmark" -msgstr "Agregar un Favorito" - -msgid "add a CWAttribute" -msgstr "Agregar un tipo de relación" - -msgid "add a CWCache" -msgstr "Agregar un cache" - -msgid "add a CWConstraint" -msgstr "Agregar una Restricción" - -msgid "add a CWConstraintType" -msgstr "Agregar un tipo de Restricción" - +msgstr "Estado de salida" + +msgid "add Transition transition_of Workflow object" +msgstr "" + +msgid "add WorkflowTransition condition RQLExpression subject" +msgstr "" + +msgid "add WorkflowTransition subworkflow_exit SubWorkflowExitPoint subject" +msgstr "" + +msgid "add WorkflowTransition transition_of Workflow object" +msgstr "" + +msgctxt "inlined:CWRelation.from_entity.subject" msgid "add a CWEType" -msgstr "Agregar un tipo de entidad" - -msgid "add a CWGroup" -msgstr "Agregar un grupo de usuarios" - -msgid "add a CWPermission" -msgstr "Agregar una autorización" - -msgid "add a CWProperty" -msgstr "Agregar una propiedad" - +msgstr "" + +msgctxt "inlined:CWRelation.to_entity.subject" +msgid "add a CWEType" +msgstr "" + +msgctxt "inlined:CWRelation.relation_type.subject" msgid "add a CWRType" -msgstr "Agregar un tipo de relación" - -msgid "add a CWRelation" -msgstr "Agregar una relación" - -msgid "add a CWUser" -msgstr "Agregar un usuario" - +msgstr "" + +msgctxt "inlined:CWUser.use_email.subject" msgid "add a EmailAddress" -msgstr "Agregar un email" - -msgid "add a ExternalUri" -msgstr "" - -msgid "add a RQLExpression" -msgstr "Agregar una expresión rql" - -msgid "add a State" -msgstr "Agregar un estado" - -msgid "add a TrInfo" -msgstr "Agregar una información de transición" - -msgid "add a Transition" -msgstr "Agregar una transición" +msgstr "" msgid "add a new permission" msgstr "Agregar una autorización" @@ -953,6 +971,24 @@ msgid "add_permission" msgstr "Autorización para agregar" +# subject and object forms for each relation type +# (no object form for final relation types) +msgctxt "CWEType" +msgid "add_permission" +msgstr "" + +msgctxt "CWRType" +msgid "add_permission" +msgstr "" + +msgctxt "CWGroup" +msgid "add_permission_object" +msgstr "" + +msgctxt "RQLExpression" +msgid "add_permission_object" +msgstr "" + msgid "add_permission_object" msgstr "tiene la autorización para agregar" @@ -968,12 +1004,26 @@ "Relación agregada %(rtype)s de %(frometype)s #%(fromeid)s hacia %(toetype)s #" "%(toeid)s" +msgid "addrelated" +msgstr "" + msgid "address" msgstr "dirección" +msgctxt "EmailAddress" +msgid "address" +msgstr "" + msgid "alias" msgstr "alias" +msgctxt "EmailAddress" +msgid "alias" +msgstr "" + +msgid "allow to set a specific workflow for an entity" +msgstr "" + msgid "allowed transition from this state" msgstr "transición autorizada desde este estado" @@ -983,6 +1033,22 @@ msgid "allowed_transition" msgstr "transición autorizada" +msgctxt "State" +msgid "allowed_transition" +msgstr "" + +msgctxt "BaseTransition" +msgid "allowed_transition_object" +msgstr "" + +msgctxt "Transition" +msgid "allowed_transition_object" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "allowed_transition_object" +msgstr "" + msgid "allowed_transition_object" msgstr "Estados de entrada" @@ -1064,6 +1130,14 @@ msgid "bookmarked_by" msgstr "está en los favoritos de" +msgctxt "Bookmark" +msgid "bookmarked_by" +msgstr "" + +msgctxt "CWUser" +msgid "bookmarked_by_object" +msgstr "" + msgid "bookmarked_by_object" msgstr "selecciona en sus favoritos a" @@ -1149,6 +1223,28 @@ msgid "by relation" msgstr "por relación" +msgid "by_transition" +msgstr "" + +msgctxt "TrInfo" +msgid "by_transition" +msgstr "" + +msgctxt "BaseTransition" +msgid "by_transition_object" +msgstr "" + +msgctxt "Transition" +msgid "by_transition_object" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "by_transition_object" +msgstr "" + +msgid "by_transition_object" +msgstr "" + msgid "calendar" msgstr "mostrar un calendario" @@ -1179,6 +1275,9 @@ msgid "can't display data, unexpected error: %s" msgstr "imposible de mostrar los datos, a causa del siguiente error: %s" +msgid "can't have multiple exits on the same state" +msgstr "" + #, python-format msgid "" "can't set inlined=%(inlined)s, %(stype)s %(rtype)s %(otype)s has cardinality=" @@ -1193,12 +1292,17 @@ msgid "cancel this insert" msgstr "Cancelar esta inserción" -msgid "canonical" -msgstr "canónico" - msgid "cardinality" msgstr "cardinalidad" +msgctxt "CWAttribute" +msgid "cardinality" +msgstr "" + +msgctxt "CWRelation" +msgid "cardinality" +msgstr "" + msgid "category" msgstr "categoria" @@ -1221,12 +1325,17 @@ msgid "comment" msgstr "Comentario" -msgid "comment:" -msgstr "Comentario:" +msgctxt "TrInfo" +msgid "comment" +msgstr "" msgid "comment_format" msgstr "Formato" +msgctxt "TrInfo" +msgid "comment_format" +msgstr "" + msgid "components" msgstr "Componentes" @@ -1278,6 +1387,12 @@ "Componente que permite distribuir sobre varias páginas las búsquedas que " "arrojan mayores resultados que un número previamente elegido" +msgid "components_pdfview" +msgstr "" + +msgid "components_pdfview_description" +msgstr "" + msgid "components_rqlinput" msgstr "Barra rql" @@ -1287,12 +1402,32 @@ msgid "composite" msgstr "composite" +msgctxt "CWRelation" +msgid "composite" +msgstr "" + msgid "condition" msgstr "condición" +msgctxt "BaseTransition" +msgid "condition" +msgstr "" + +msgctxt "Transition" +msgid "condition" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "condition" +msgstr "" + msgid "condition:" msgstr "condición:" +msgctxt "RQLExpression" +msgid "condition_object" +msgstr "" + msgid "condition_object" msgstr "condición de" @@ -1302,6 +1437,18 @@ msgid "constrained_by" msgstr "Restricción hecha por" +msgctxt "CWAttribute" +msgid "constrained_by" +msgstr "" + +msgctxt "CWRelation" +msgid "constrained_by" +msgstr "" + +msgctxt "CWConstraint" +msgid "constrained_by_object" +msgstr "" + msgid "constrained_by_object" msgstr "ha restringido" @@ -1501,23 +1648,43 @@ msgid "creating RQLExpression (Transition %(linkto)s condition RQLExpression)" msgstr "Creación de una expresión RQL para la transición %(linkto)s" +msgid "" +"creating RQLExpression (WorkflowTransition %(linkto)s condition " +"RQLExpression)" +msgstr "" + msgid "creating State (State allowed_transition Transition %(linkto)s)" msgstr "Creación de un estado que pueda ir hacia la transición %(linkto)s" -msgid "creating State (State state_of CWEType %(linkto)s)" -msgstr "Creación de un estado por el tipo %(linkto)s" +msgid "creating State (State state_of Workflow %(linkto)s)" +msgstr "" msgid "creating State (Transition %(linkto)s destination_state State)" msgstr "Creación de un estado destinación de la transición %(linkto)s" +msgid "" +"creating SubWorkflowExitPoint (WorkflowTransition %(linkto)s " +"subworkflow_exit SubWorkflowExitPoint)" +msgstr "" + msgid "creating Transition (State %(linkto)s allowed_transition Transition)" msgstr "Creación de una transición autorizada desde el estado %(linkto)s" msgid "creating Transition (Transition destination_state State %(linkto)s)" msgstr "Creación de un transición hacia el estado %(linkto)s" -msgid "creating Transition (Transition transition_of CWEType %(linkto)s)" -msgstr "Creación de una transición para el tipo %(linkto)s" +msgid "creating Transition (Transition transition_of Workflow %(linkto)s)" +msgstr "" + +msgid "" +"creating WorkflowTransition (State %(linkto)s allowed_transition " +"WorkflowTransition)" +msgstr "" + +msgid "" +"creating WorkflowTransition (WorkflowTransition transition_of Workflow %" +"(linkto)s)" +msgstr "" msgid "creation" msgstr "Creación" @@ -1531,6 +1698,14 @@ msgid "cstrtype" msgstr "Tipo de condición" +msgctxt "CWConstraint" +msgid "cstrtype" +msgstr "" + +msgctxt "CWConstraintType" +msgid "cstrtype_object" +msgstr "" + msgid "cstrtype_object" msgstr "utilizado por" @@ -1544,6 +1719,12 @@ msgid "currently attached file: %s" msgstr "archivo adjunto: %s" +msgid "custom_workflow" +msgstr "" + +msgid "custom_workflow_object" +msgstr "" + msgid "cwetype-schema-image" msgstr "Esquema" @@ -1580,9 +1761,33 @@ msgid "default text format for rich text fields." msgstr "Formato de texto como opción por defecto para los campos texto" +msgid "default user workflow" +msgstr "" + +msgid "default workflow for an entity type" +msgstr "" + +msgid "default_workflow" +msgstr "" + +msgctxt "CWEType" +msgid "default_workflow" +msgstr "" + +msgctxt "Workflow" +msgid "default_workflow_object" +msgstr "" + +msgid "default_workflow_object" +msgstr "" + msgid "defaultval" msgstr "Valor por defecto" +msgctxt "CWAttribute" +msgid "defaultval" +msgstr "" + msgid "define a CubicWeb user" msgstr "Define un usuario CubicWeb" @@ -1614,6 +1819,9 @@ msgid "define an entity type, used to build the instance schema" msgstr "" +msgid "define how we get out from a sub-workflow" +msgstr "" + msgid "" "defines what's the property is applied for. You must select this first to be " "able to set value" @@ -1639,6 +1847,22 @@ msgid "delete_permission" msgstr "Autorización de eliminar" +msgctxt "CWEType" +msgid "delete_permission" +msgstr "" + +msgctxt "CWRType" +msgid "delete_permission" +msgstr "" + +msgctxt "CWGroup" +msgid "delete_permission_object" +msgstr "" + +msgctxt "RQLExpression" +msgid "delete_permission_object" +msgstr "" + msgid "delete_permission_object" msgstr "posee la autorización de eliminar" @@ -1660,9 +1884,84 @@ msgid "description" msgstr "Descripción" +msgctxt "CWEType" +msgid "description" +msgstr "" + +msgctxt "CWRelation" +msgid "description" +msgstr "" + +msgctxt "Workflow" +msgid "description" +msgstr "" + +msgctxt "CWAttribute" +msgid "description" +msgstr "" + +msgctxt "Transition" +msgid "description" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "description" +msgstr "" + +msgctxt "State" +msgid "description" +msgstr "" + +msgctxt "CWRType" +msgid "description" +msgstr "" + +msgctxt "BaseTransition" +msgid "description" +msgstr "" + msgid "description_format" msgstr "Formato" +msgctxt "CWEType" +msgid "description_format" +msgstr "" + +msgctxt "CWRelation" +msgid "description_format" +msgstr "" + +msgctxt "Workflow" +msgid "description_format" +msgstr "" + +msgctxt "CWAttribute" +msgid "description_format" +msgstr "" + +msgctxt "Transition" +msgid "description_format" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "description_format" +msgstr "" + +msgctxt "State" +msgid "description_format" +msgstr "" + +msgctxt "CWRType" +msgid "description_format" +msgstr "" + +msgctxt "BaseTransition" +msgid "description_format" +msgstr "" + +msgid "destination state" +msgstr "" + msgid "destination state for this transition" msgstr "Estado destino para esta transición" @@ -1672,6 +1971,18 @@ msgid "destination_state" msgstr "Estado destino" +msgctxt "Transition" +msgid "destination_state" +msgstr "" + +msgctxt "SubWorkflowExitPoint" +msgid "destination_state" +msgstr "" + +msgctxt "State" +msgid "destination_state_object" +msgstr "" + msgid "destination_state_object" msgstr "Destino de" @@ -1700,6 +2011,9 @@ msgid "display the component or not" msgstr "Mostrar el componente o no" +msgid "display the pdf icon or not" +msgstr "" + msgid "" "distinct label to distinguate between other permission entity of the same " "name" @@ -1717,6 +2031,9 @@ msgid "download icon" msgstr "ícono de descarga" +msgid "download page as pdf" +msgstr "" + msgid "download schema as owl" msgstr "Descargar esquema en OWL" @@ -1774,6 +2091,9 @@ msgid "entity edited" msgstr "entidad modificada" +msgid "entity has no workflow set" +msgstr "" + msgid "entity linked" msgstr "entidad asociada" @@ -1787,11 +2107,8 @@ "Tipo de entidad utilizada para definir una configuración de seguridad " "avanzada" -msgid "entity types which may use this state" -msgstr "Tipo de entidades que pueden utilizar este estado" - -msgid "entity types which may use this transition" -msgstr "Entidades que pueden utilizar esta transición" +msgid "entity types which may use this workflow" +msgstr "" msgid "error while embedding page" msgstr "Error durante la inclusión de la página" @@ -1813,15 +2130,33 @@ msgid "eta_date" msgstr "fecha de fin" +msgid "exit_point" +msgstr "" + +msgid "exit_point_object" +msgstr "" + +#, python-format +msgid "exiting from subworkflow %s" +msgstr "" + msgid "expected:" msgstr "Previsto :" msgid "expression" msgstr "Expresión" +msgctxt "RQLExpression" +msgid "expression" +msgstr "" + msgid "exprtype" msgstr "Tipo de la expresión" +msgctxt "RQLExpression" +msgid "exprtype" +msgstr "" + msgid "external page" msgstr "Página externa" @@ -1873,9 +2208,21 @@ msgid "final" msgstr "Final" +msgctxt "CWEType" +msgid "final" +msgstr "" + +msgctxt "CWRType" +msgid "final" +msgstr "" + msgid "firstname" msgstr "Nombre" +msgctxt "CWUser" +msgid "firstname" +msgstr "" + msgid "foaf" msgstr "Amigo de un Amigo, FOAF" @@ -1885,6 +2232,14 @@ msgid "for_user" msgstr "Para el usuario" +msgctxt "CWProperty" +msgid "for_user" +msgstr "" + +msgctxt "CWUser" +msgid "for_user_object" +msgstr "" + msgid "for_user_object" msgstr "Utiliza las propiedades" @@ -1901,6 +2256,18 @@ msgid "from_entity" msgstr "De la entidad" +msgctxt "CWAttribute" +msgid "from_entity" +msgstr "" + +msgctxt "CWRelation" +msgid "from_entity" +msgstr "" + +msgctxt "CWEType" +msgid "from_entity_object" +msgstr "" + msgid "from_entity_object" msgstr "Relación sujeto" @@ -1910,6 +2277,14 @@ msgid "from_state" msgstr "De el estado" +msgctxt "TrInfo" +msgid "from_state" +msgstr "" + +msgctxt "State" +msgid "from_state_object" +msgstr "" + msgid "from_state_object" msgstr "Transiciones desde este estado" @@ -1919,9 +2294,17 @@ msgid "fulltext_container" msgstr "Contenedor de texto indexado" +msgctxt "CWRType" +msgid "fulltext_container" +msgstr "" + msgid "fulltextindexed" msgstr "Indexación de texto" +msgctxt "CWAttribute" +msgid "fulltextindexed" +msgstr "" + msgid "generic plot" msgstr "Trazado de curbas estándares" @@ -1940,6 +2323,10 @@ msgid "granted to groups" msgstr "Otorgado a los grupos" +#, python-format +msgid "graphical representation of %s" +msgstr "" + msgid "graphical representation of the instance'schema" msgstr "" @@ -2034,6 +2421,9 @@ msgid "id of main template used to render pages" msgstr "ID del template principal" +msgid "identical to" +msgstr "" + msgid "identical_to" msgstr "idéntico a" @@ -2062,6 +2452,14 @@ msgid "in_group" msgstr "En el grupo" +msgctxt "CWUser" +msgid "in_group" +msgstr "" + +msgctxt "CWGroup" +msgid "in_group_object" +msgstr "" + msgid "in_group_object" msgstr "Miembros" @@ -2087,6 +2485,10 @@ msgid "indexed" msgstr "Indexado" +msgctxt "CWAttribute" +msgid "indexed" +msgstr "" + msgid "indicate the current state of an entity" msgstr "Indica el estado actual de una entidad" @@ -2103,18 +2505,30 @@ msgid "initial estimation %s" msgstr "Estimación inicial %s" -msgid "initial state for entities of this type" -msgstr "Estado inicial para las entidades de este tipo" +msgid "initial state for this workflow" +msgstr "" msgid "initial_state" msgstr "estado inicial" +msgctxt "Workflow" +msgid "initial_state" +msgstr "" + +msgctxt "State" +msgid "initial_state_object" +msgstr "" + msgid "initial_state_object" msgstr "es el estado inicial de" msgid "inlined" msgstr "Puesto en línea" +msgctxt "CWRType" +msgid "inlined" +msgstr "" + msgid "instance schema" msgstr "" @@ -2124,6 +2538,10 @@ msgid "internationalizable" msgstr "Internacionalizable" +msgctxt "CWAttribute" +msgid "internationalizable" +msgstr "" + #, python-format msgid "invalid action %r" msgstr "Acción %r invalida" @@ -2181,6 +2599,10 @@ msgid "label" msgstr "Etiqueta" +msgctxt "CWPermission" +msgid "label" +msgstr "" + msgid "language of the user interface" msgstr "Idioma para la interface del usuario" @@ -2190,6 +2612,10 @@ msgid "last_login_time" msgstr "Ultima fecha de conexión" +msgctxt "CWUser" +msgid "last_login_time" +msgstr "" + msgid "latest modification time of an entity" msgstr "Fecha de la última modificación de una entidad " @@ -2223,14 +2649,17 @@ msgid "link a relation definition to its subject entity type" msgstr "liga una definición de relación a su tipo de entidad" -msgid "link a state to one or more entity type" -msgstr "liga un estado a una o mas entidades" +msgid "link a state to one or more workflow" +msgstr "" msgid "link a transition information to its object" msgstr "liga una transcion de informacion a los objetos asociados" -msgid "link a transition to one or more entity type" -msgstr "liga una transición a una o mas tipos de entidad" +msgid "link a transition to one or more workflow" +msgstr "" + +msgid "link a workflow to one or more entity type" +msgstr "" msgid "link to each item in" msgstr "ligar hacia cada elemento en" @@ -2247,6 +2676,10 @@ msgid "login" msgstr "Clave de acesso" +msgctxt "CWUser" +msgid "login" +msgstr "" + msgid "login or email" msgstr "Clave de acesso o dirección de correo" @@ -2266,6 +2699,10 @@ msgid "mainvars" msgstr "Principales variables" +msgctxt "RQLExpression" +msgid "mainvars" +msgstr "" + msgid "manage" msgstr "Administracion del Sitio" @@ -2281,6 +2718,9 @@ msgid "managers" msgstr "editores" +msgid "mandatory relation" +msgstr "" + msgid "march" msgstr "Marzo" @@ -2327,6 +2767,50 @@ msgid "name" msgstr "Nombre" +msgctxt "CWEType" +msgid "name" +msgstr "" + +msgctxt "Transition" +msgid "name" +msgstr "" + +msgctxt "Workflow" +msgid "name" +msgstr "" + +msgctxt "CWGroup" +msgid "name" +msgstr "" + +msgctxt "CWConstraintType" +msgid "name" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "name" +msgstr "" + +msgctxt "State" +msgid "name" +msgstr "" + +msgctxt "CWPermission" +msgid "name" +msgstr "" + +msgctxt "CWRType" +msgid "name" +msgstr "" + +msgctxt "BaseTransition" +msgid "name" +msgstr "" + +msgctxt "CWCache" +msgid "name" +msgstr "" + msgid "name of the cache" msgstr "Nombre del Cache" @@ -2438,6 +2922,14 @@ msgid "ordernum" msgstr "orden" +msgctxt "CWAttribute" +msgid "ordernum" +msgstr "" + +msgctxt "CWRelation" +msgid "ordernum" +msgstr "" + msgid "owl" msgstr "owl" @@ -2471,6 +2963,10 @@ msgid "path" msgstr "Ruta" +msgctxt "Bookmark" +msgid "path" +msgstr "" + msgid "permission" msgstr "Permiso" @@ -2492,6 +2988,10 @@ msgid "pkey" msgstr "pkey" +msgctxt "CWProperty" +msgid "pkey" +msgstr "" + msgid "please correct errors below" msgstr "Favor de corregir errores" @@ -2504,6 +3004,20 @@ msgid "powered by CubicWeb" msgstr "" +msgid "prefered_form" +msgstr "" + +msgctxt "EmailAddress" +msgid "prefered_form" +msgstr "" + +msgctxt "EmailAddress" +msgid "prefered_form_object" +msgstr "" + +msgid "prefered_form_object" +msgstr "" + msgid "preferences" msgstr "Preferencias" @@ -2516,6 +3030,14 @@ msgid "primary_email" msgstr "Dirección de email principal" +msgctxt "CWUser" +msgid "primary_email" +msgstr "" + +msgctxt "EmailAddress" +msgid "primary_email_object" +msgstr "" + msgid "primary_email_object" msgstr "Dirección de email principal (objeto)" @@ -2537,12 +3059,34 @@ msgid "read_permission" msgstr "Permiso de lectura" +msgctxt "CWEType" +msgid "read_permission" +msgstr "" + +msgctxt "CWRType" +msgid "read_permission" +msgstr "" + +msgctxt "CWGroup" +msgid "read_permission_object" +msgstr "" + +msgctxt "RQLExpression" +msgid "read_permission_object" +msgstr "" + msgid "read_permission_object" msgstr "Objeto_permiso_lectura" msgid "registry" msgstr "" +msgid "related entity has no state" +msgstr "" + +msgid "related entity has no workflow set" +msgstr "" + #, python-format msgid "relation %(relname)s of %(ent)s" msgstr "relación %(relname)s de %(ent)s" @@ -2550,6 +3094,18 @@ msgid "relation_type" msgstr "tipo de relación" +msgctxt "CWAttribute" +msgid "relation_type" +msgstr "" + +msgctxt "CWRelation" +msgid "relation_type" +msgstr "" + +msgctxt "CWRType" +msgid "relation_type_object" +msgstr "" + msgid "relation_type_object" msgstr "Definición" @@ -2562,63 +3118,45 @@ msgid "relative url of the bookmarked page" msgstr "Url relativa de la pagina" -msgid "remove this Bookmark" -msgstr "Eliminar este Favorito" - -msgid "remove this CWAttribute" -msgstr "Eliminar este atributo" - -msgid "remove this CWCache" -msgstr "Eliminar esta cache de aplicación" - -msgid "remove this CWConstraint" -msgstr "Eliminar esta restricción" - -msgid "remove this CWConstraintType" -msgstr "Eliminar este tipo de restricción" - +msgctxt "inlined:CWRelation:from_entity:subject" +msgid "remove this CWEType" +msgstr "" + +msgctxt "inlined:CWRelation:to_entity:subject" msgid "remove this CWEType" -msgstr "Eliminar este tipo de entidad" - -msgid "remove this CWGroup" -msgstr "Eliminar este grupo" - -msgid "remove this CWPermission" -msgstr "Eliminar este permiso" - -msgid "remove this CWProperty" -msgstr "Eliminar esta propiedad" - +msgstr "" + +msgctxt "inlined:CWRelation:relation_type:subject" msgid "remove this CWRType" -msgstr "Eliminar esta definición de relación" - -msgid "remove this CWRelation" -msgstr "Eliminar esta relación" - -msgid "remove this CWUser" -msgstr "Eliminar este usuario" - +msgstr "" + +msgctxt "inlined:CWUser:use_email:subject" msgid "remove this EmailAddress" -msgstr "Eliminar este correo electronico" - -msgid "remove this ExternalUri" -msgstr "" - -msgid "remove this RQLExpression" -msgstr "Eliminar esta expresión RQL" - -msgid "remove this State" -msgstr "Eliminar este estado" - -msgid "remove this TrInfo" -msgstr "Eliminar información de esta transición" - -msgid "remove this Transition" -msgstr "Eliminar esta transición" +msgstr "" msgid "require_group" msgstr "Requiere grupo" +msgctxt "BaseTransition" +msgid "require_group" +msgstr "" + +msgctxt "Transition" +msgid "require_group" +msgstr "" + +msgctxt "CWPermission" +msgid "require_group" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "require_group" +msgstr "" + +msgctxt "CWGroup" +msgid "require_group_object" +msgstr "" + msgid "require_group_object" msgstr "Requerido por grupo" @@ -2747,6 +3285,9 @@ msgid "semantic description of this transition" msgstr "descripcion semantica de esta transición" +msgid "semantic description of this workflow" +msgstr "" + msgid "send email" msgstr "enviar email" @@ -2803,9 +3344,20 @@ msgid "sparql xml" msgstr "" +msgid "special transition allowing to go through a sub-workflow" +msgstr "" + msgid "specializes" msgstr "derivado de" +msgctxt "CWEType" +msgid "specializes" +msgstr "" + +msgctxt "CWEType" +msgid "specializes_object" +msgstr "" + msgid "specializes_object" msgstr "objeto_derivado" @@ -2815,9 +3367,28 @@ msgid "state" msgstr "estado" +msgid "state doesn't belong to entity's current workflow" +msgstr "" + +msgid "state doesn't belong to entity's workflow" +msgstr "" + +msgid "" +"state doesn't belong to entity's workflow. You may want to set a custom " +"workflow for this entity first." +msgstr "" + msgid "state_of" msgstr "estado_de" +msgctxt "State" +msgid "state_of" +msgstr "" + +msgctxt "Workflow" +msgid "state_of_object" +msgstr "" + msgid "state_of_object" msgstr "objeto_estado_de" @@ -2840,15 +3411,68 @@ msgid "subject_plural:" msgstr "sujetos:" +msgid "subworkflow" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "subworkflow" +msgstr "" + +msgid "subworkflow state" +msgstr "" + +msgid "subworkflow_exit" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "subworkflow_exit" +msgstr "" + +msgctxt "SubWorkflowExitPoint" +msgid "subworkflow_exit_object" +msgstr "" + +msgid "subworkflow_exit_object" +msgstr "" + +msgctxt "Workflow" +msgid "subworkflow_object" +msgstr "" + +msgid "subworkflow_object" +msgstr "" + +msgid "subworkflow_state" +msgstr "" + +msgctxt "SubWorkflowExitPoint" +msgid "subworkflow_state" +msgstr "" + +msgctxt "State" +msgid "subworkflow_state_object" +msgstr "" + +msgid "subworkflow_state_object" +msgstr "" + msgid "sunday" msgstr "domingo" msgid "surname" msgstr "apellido" +msgctxt "CWUser" +msgid "surname" +msgstr "" + msgid "symetric" msgstr "simetrico" +msgctxt "CWRType" +msgid "symetric" +msgstr "" + msgid "system entities" msgstr "entidades de sistema" @@ -2904,6 +3528,10 @@ msgid "timestamp" msgstr "fecha" +msgctxt "CWCache" +msgid "timestamp" +msgstr "" + msgid "timestamp of the latest source synchronization." msgstr "fecha de la ultima sincronización de la fuente." @@ -2913,6 +3541,10 @@ msgid "title" msgstr "titulo" +msgctxt "Bookmark" +msgid "title" +msgstr "" + msgid "to" msgstr "a" @@ -2926,6 +3558,18 @@ msgid "to_entity" msgstr "hacia entidad" +msgctxt "CWAttribute" +msgid "to_entity" +msgstr "" + +msgctxt "CWRelation" +msgid "to_entity" +msgstr "" + +msgctxt "CWEType" +msgid "to_entity_object" +msgstr "" + msgid "to_entity_object" msgstr "hacia entidad objeto" @@ -2935,6 +3579,14 @@ msgid "to_state" msgstr "hacia el estado" +msgctxt "TrInfo" +msgid "to_state" +msgstr "" + +msgctxt "State" +msgid "to_state_object" +msgstr "" + msgid "to_state_object" msgstr "hacia objeto estado" @@ -2944,13 +3596,34 @@ msgid "toggle check boxes" msgstr "cambiar valor" -#, python-format -msgid "transition from %s to %s does not exist or is not allowed" +msgid "transition doesn't belong to entity's workflow" +msgstr "" + +msgid "transition isn't allowed" +msgstr "" + +msgid "transition may not be fired" msgstr "" msgid "transition_of" msgstr "transicion de" +msgctxt "BaseTransition" +msgid "transition_of" +msgstr "" + +msgctxt "Transition" +msgid "transition_of" +msgstr "" + +msgctxt "WorkflowTransition" +msgid "transition_of" +msgstr "" + +msgctxt "Workflow" +msgid "transition_of_object" +msgstr "" + msgid "transition_of_object" msgstr "objeto de transición" @@ -3023,6 +3696,10 @@ msgid "upassword" msgstr "clave de acceso" +msgctxt "CWUser" +msgid "upassword" +msgstr "" + msgid "update" msgstr "modificación" @@ -3032,6 +3709,18 @@ msgid "update_permission" msgstr "Permiso de modificación" +msgctxt "CWEType" +msgid "update_permission" +msgstr "" + +msgctxt "CWGroup" +msgid "update_permission_object" +msgstr "" + +msgctxt "RQLExpression" +msgid "update_permission_object" +msgstr "" + msgid "update_permission_object" msgstr "objeto de autorización de modificaciones" @@ -3042,6 +3731,10 @@ msgid "uri" msgstr "" +msgctxt "ExternalUri" +msgid "uri" +msgstr "" + msgid "use template languages" msgstr "utilizar plantillas de lenguaje" @@ -3055,6 +3748,14 @@ msgid "use_email" msgstr "correo electrónico" +msgctxt "CWUser" +msgid "use_email" +msgstr "" + +msgctxt "EmailAddress" +msgid "use_email_object" +msgstr "" + msgid "use_email_object" msgstr "objeto email utilizado" @@ -3113,6 +3814,14 @@ msgid "value" msgstr "valor" +msgctxt "CWConstraint" +msgid "value" +msgstr "" + +msgctxt "CWProperty" +msgid "value" +msgstr "" + msgid "value associated to this key is not editable manually" msgstr "el valor asociado a este elemento no es editable manualmente" @@ -3164,16 +3873,42 @@ msgid "" "when multiple addresses are equivalent (such as python-projects@logilab.org " -"and python-projects@lists.logilab.org), set this to true on one of them " -"which is the preferred form." -msgstr "" -"cuando multiples direcciones de correo son equivalentes (como python-" -"projects@logilab.org y python-projects@lists.logilab.org), establecer esto " -"como verdadero en una de ellas es la forma preferida " +"and python-projects@lists.logilab.org), set this to indicate which is the " +"preferred form." +msgstr "" + +msgid "workflow" +msgstr "" #, python-format -msgid "workflow for %s" -msgstr "workflow para %s" +msgid "workflow changed to \"%s\"" +msgstr "" + +msgid "workflow has no initial state" +msgstr "" + +msgid "workflow history item" +msgstr "" + +msgid "workflow to which this state belongs" +msgstr "" + +msgid "workflow to which this transition belongs" +msgstr "" + +msgid "workflow_of" +msgstr "" + +msgctxt "Workflow" +msgid "workflow_of" +msgstr "" + +msgctxt "CWEType" +msgid "workflow_of_object" +msgstr "" + +msgid "workflow_of_object" +msgstr "" msgid "xbel" msgstr "xbel" @@ -3193,6 +3928,162 @@ msgid "you should probably delete that property" msgstr "deberia probablamente suprimir esta propriedad" +#~ msgid "There is no workflow defined for this entity." +#~ msgstr "No hay workflow para este entidad" + #~ msgid "" #~ "You have no access to this view or it's not applyable to current data" #~ msgstr "No tiene acceso a esta vista o No es aplicable a los datos actuales" + +#~ msgid "__msg state changed" +#~ msgstr "El estado a cambiado" + +#~ msgid "account state" +#~ msgstr "Estado de la Cuenta" + +#~ msgid "add State state_of CWEType object" +#~ msgstr "Estado" + +#~ msgid "add Transition transition_of CWEType object" +#~ msgstr "Transición" + +#~ msgid "add a Bookmark" +#~ msgstr "Agregar un Favorito" + +#~ msgid "add a CWAttribute" +#~ msgstr "Agregar un tipo de relación" + +#~ msgid "add a CWCache" +#~ msgstr "Agregar un cache" + +#~ msgid "add a CWConstraint" +#~ msgstr "Agregar una Restricción" + +#~ msgid "add a CWConstraintType" +#~ msgstr "Agregar un tipo de Restricción" + +#~ msgid "add a CWEType" +#~ msgstr "Agregar un tipo de entidad" + +#~ msgid "add a CWGroup" +#~ msgstr "Agregar un grupo de usuarios" + +#~ msgid "add a CWPermission" +#~ msgstr "Agregar una autorización" + +#~ msgid "add a CWProperty" +#~ msgstr "Agregar una propiedad" + +#~ msgid "add a CWRType" +#~ msgstr "Agregar un tipo de relación" + +#~ msgid "add a CWRelation" +#~ msgstr "Agregar una relación" + +#~ msgid "add a CWUser" +#~ msgstr "Agregar un usuario" + +#~ msgid "add a EmailAddress" +#~ msgstr "Agregar un email" + +#~ msgid "add a RQLExpression" +#~ msgstr "Agregar una expresión rql" + +#~ msgid "add a State" +#~ msgstr "Agregar un estado" + +#~ msgid "add a TrInfo" +#~ msgstr "Agregar una información de transición" + +#~ msgid "add a Transition" +#~ msgstr "Agregar una transición" + +#~ msgid "canonical" +#~ msgstr "canónico" + +#~ msgid "comment:" +#~ msgstr "Comentario:" + +#~ msgid "creating State (State state_of CWEType %(linkto)s)" +#~ msgstr "Creación de un estado por el tipo %(linkto)s" + +#~ msgid "creating Transition (Transition transition_of CWEType %(linkto)s)" +#~ msgstr "Creación de una transición para el tipo %(linkto)s" + +#~ msgid "entity types which may use this state" +#~ msgstr "Tipo de entidades que pueden utilizar este estado" + +#~ msgid "entity types which may use this transition" +#~ msgstr "Entidades que pueden utilizar esta transición" + +#~ msgid "initial state for entities of this type" +#~ msgstr "Estado inicial para las entidades de este tipo" + +#~ msgid "link a state to one or more entity type" +#~ msgstr "liga un estado a una o mas entidades" + +#~ msgid "link a transition to one or more entity type" +#~ msgstr "liga una transición a una o mas tipos de entidad" + +#~ msgid "remove this Bookmark" +#~ msgstr "Eliminar este Favorito" + +#~ msgid "remove this CWAttribute" +#~ msgstr "Eliminar este atributo" + +#~ msgid "remove this CWCache" +#~ msgstr "Eliminar esta cache de aplicación" + +#~ msgid "remove this CWConstraint" +#~ msgstr "Eliminar esta restricción" + +#~ msgid "remove this CWConstraintType" +#~ msgstr "Eliminar este tipo de restricción" + +#~ msgid "remove this CWEType" +#~ msgstr "Eliminar este tipo de entidad" + +#~ msgid "remove this CWGroup" +#~ msgstr "Eliminar este grupo" + +#~ msgid "remove this CWPermission" +#~ msgstr "Eliminar este permiso" + +#~ msgid "remove this CWProperty" +#~ msgstr "Eliminar esta propiedad" + +#~ msgid "remove this CWRType" +#~ msgstr "Eliminar esta definición de relación" + +#~ msgid "remove this CWRelation" +#~ msgstr "Eliminar esta relación" + +#~ msgid "remove this CWUser" +#~ msgstr "Eliminar este usuario" + +#~ msgid "remove this EmailAddress" +#~ msgstr "Eliminar este correo electronico" + +#~ msgid "remove this RQLExpression" +#~ msgstr "Eliminar esta expresión RQL" + +#~ msgid "remove this State" +#~ msgstr "Eliminar este estado" + +#~ msgid "remove this TrInfo" +#~ msgstr "Eliminar información de esta transición" + +#~ msgid "remove this Transition" +#~ msgstr "Eliminar esta transición" + +#~ msgid "" +#~ "when multiple addresses are equivalent (such as python-projects@logilab." +#~ "org and python-projects@lists.logilab.org), set this to true on one of " +#~ "them which is the preferred form." +#~ msgstr "" +#~ "cuando multiples direcciones de correo son equivalentes (como python-" +#~ "projects@logilab.org y python-projects@lists.logilab.org), establecer " +#~ "esto como verdadero en una de ellas es la forma preferida " + +#~ msgid "workflow for %s" +#~ msgstr "workflow para %s" diff -r d6ae24439bee -r c4c07aab1c39 i18n/fr.po --- a/i18n/fr.po Thu Sep 10 08:03:18 2009 +0200 +++ b/i18n/fr.po Fri Sep 18 12:20:39 2009 +0200 @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: cubicweb 2.46.0\n" -"PO-Revision-Date: 2009-08-05 08:37+0200\n" +"PO-Revision-Date: 2009-09-17 12:03+0200\n" "Last-Translator: Logilab Team \n" "Language-Team: fr \n" "MIME-Version: 1.0\n" @@ -112,10 +112,6 @@ msgstr "%s rapport d'erreur" #, python-format -msgid "%s is not the initial state (%s) for this entity" -msgstr "%s n'est pas l'état initial (%s) de cette entité" - -#, python-format msgid "%s not estimated" msgstr "%s non estimé(s)" @@ -204,6 +200,15 @@ msgid "Attributes" msgstr "Attributs" +# schema pot file, generated on 2009-09-16 16:46:55 +# +# singular and plural forms for each entity type +msgid "BaseTransition" +msgstr "Transition (abstraite)" + +msgid "BaseTransition_plural" +msgstr "Transitions (abstraites)" + msgid "Bookmark" msgstr "Signet" @@ -358,6 +363,9 @@ msgid "Interval_plural" msgstr "Durées" +msgid "New BaseTransition" +msgstr "XXX" + msgid "New Bookmark" msgstr "Nouveau signet" @@ -406,12 +414,21 @@ msgid "New State" msgstr "Nouvel état" +msgid "New SubWorkflowExitPoint" +msgstr "Nouvelle sortie de sous-workflow" + msgid "New TrInfo" msgstr "Nouvelle information de transition" msgid "New Transition" msgstr "Nouvelle transition" +msgid "New Workflow" +msgstr "Nouveau workflow" + +msgid "New WorkflowTransition" +msgstr "Nouvelle transition workflow" + msgid "No query has been executed" msgstr "Aucune requête n'a été éxécuté" @@ -479,6 +496,12 @@ msgid "String_plural" msgstr "Chaînes de caractères" +msgid "SubWorkflowExitPoint" +msgstr "Sortie de sous-workflow" + +msgid "SubWorkflowExitPoint_plural" +msgstr "Sorties de sous-workflow" + msgid "Subject:" msgstr "Sujet :" @@ -499,12 +522,8 @@ msgid "The view %s could not be found" msgstr "La vue %s est introuvable" -msgid "There is no workflow defined for this entity." -msgstr "Il n'y a pas de workflow défini pour ce type d'entité" - -#, python-format -msgid "This %s" -msgstr "Ce %s" +msgid "This BaseTransition" +msgstr "Cette transition abstraite" msgid "This Bookmark" msgstr "Ce signet" @@ -554,12 +573,21 @@ msgid "This State" msgstr "Cet état" +msgid "This SubWorkflowExitPoint" +msgstr "Cette sortie de sous-workflow" + msgid "This TrInfo" msgstr "Cette information de transition" msgid "This Transition" msgstr "Cette transition" +msgid "This Workflow" +msgstr "Ce workflow" + +msgid "This WorkflowTransition" +msgstr "Cette transition workflow" + msgid "Time" msgstr "Heure" @@ -591,9 +619,21 @@ msgid "What's new?" msgstr "Nouveautés" +msgid "Workflow" +msgstr "Workflow" + msgid "Workflow history" msgstr "Historique des changements d'état" +msgid "WorkflowTransition" +msgstr "Transition workflow" + +msgid "WorkflowTransition_plural" +msgstr "Transitions workflow" + +msgid "Workflow_plural" +msgstr "Workflows" + msgid "You are not connected to an instance !" msgstr "Vous n'êtes pas connecté à une instance" @@ -627,7 +667,8 @@ msgid "" "You have no access to this view or it can not be used to display the current " "data." -msgstr "Vous n'avez pas accès à cette vue ou elle ne peut pas afficher ces données." +msgstr "" +"Vous n'avez pas accès à cette vue ou elle ne peut pas afficher ces données." msgid "" "You're not authorized to access this page. If you think you should, please " @@ -640,9 +681,6 @@ msgid "[%s supervision] changes summary" msgstr "[%s supervision] description des changements" -msgid "__msg state changed" -msgstr "l'état a été changé" - msgid "" "a RQL expression which should return some results, else the transition won't " "be available. This query may use X and U variables that will respectivly " @@ -671,12 +709,12 @@ msgid "about this site" msgstr "à propos de ce site" +msgid "abstract base class for transitions" +msgstr "classe de base abstraite pour les transitions" + msgid "access type" msgstr "type d'accès" -msgid "account state" -msgstr "état du compte" - msgid "action(s) on this selection" msgstr "action(s) sur cette sélection" @@ -689,6 +727,12 @@ msgid "actions_addentity_description" msgstr "" +msgid "actions_addrelated" +msgstr "menu ajouter" + +msgid "actions_addrelated_description" +msgstr "" + msgid "actions_cancel" msgstr "annuler la sélection" @@ -867,82 +911,56 @@ msgstr "utilisateur" msgid "add CWUser use_email EmailAddress subject" -msgstr "ajouter une addresse email" +msgstr "addresse email" msgid "add State allowed_transition Transition object" -msgstr "ajouter un état en entrée" +msgstr "état en entrée" msgid "add State allowed_transition Transition subject" -msgstr "ajouter une transition en sortie" - -msgid "add State state_of CWEType object" -msgstr "ajouter un état" +msgstr "transition en sortie" + +msgid "add State allowed_transition WorkflowTransition subject" +msgstr "transition workflow en sortie" + +msgid "add State state_of Workflow object" +msgstr "état" msgid "add Transition condition RQLExpression subject" -msgstr "ajouter une condition" +msgstr "condition" msgid "add Transition destination_state State object" -msgstr "ajouter une transition en entrée" +msgstr "transition en entrée" msgid "add Transition destination_state State subject" -msgstr "ajouter l'état de sortie" - -msgid "add Transition transition_of CWEType object" -msgstr "ajouter une transition" - -msgid "add a Bookmark" -msgstr "ajouter un signet" - -msgid "add a CWAttribute" -msgstr "ajouter un type de relation" - -msgid "add a CWCache" -msgstr "ajouter un cache applicatif" - -msgid "add a CWConstraint" -msgstr "ajouter une contrainte" - -msgid "add a CWConstraintType" -msgstr "ajouter un type de contrainte" - +msgstr "état de sortie" + +msgid "add Transition transition_of Workflow object" +msgstr "transition" + +msgid "add WorkflowTransition condition RQLExpression subject" +msgstr "condition" + +msgid "add WorkflowTransition subworkflow_exit SubWorkflowExitPoint subject" +msgstr "sortie de sous-workflow" + +msgid "add WorkflowTransition transition_of Workflow object" +msgstr "transition workflow" + +msgctxt "inlined:CWRelation.from_entity.subject" msgid "add a CWEType" -msgstr "ajouter un type d'entité" - -msgid "add a CWGroup" -msgstr "ajouter un groupe d'utilisateurs" - -msgid "add a CWPermission" -msgstr "ajouter une permission" - -msgid "add a CWProperty" -msgstr "ajouter une propriété" - +msgstr "ajouter un type d'entité sujet" + +msgctxt "inlined:CWRelation.to_entity.subject" +msgid "add a CWEType" +msgstr "ajouter un type d'entité objet" + +msgctxt "inlined:CWRelation.relation_type.subject" msgid "add a CWRType" msgstr "ajouter un type de relation" -msgid "add a CWRelation" -msgstr "ajouter une relation" - -msgid "add a CWUser" -msgstr "ajouter un utilisateur" - +msgctxt "inlined:CWUser.use_email.subject" msgid "add a EmailAddress" -msgstr "ajouter une adresse email" - -msgid "add a ExternalUri" -msgstr "ajouter une Uri externe" - -msgid "add a RQLExpression" -msgstr "ajouter une expression rql" - -msgid "add a State" -msgstr "ajouter un état" - -msgid "add a TrInfo" -msgstr "ajouter une information de transition" - -msgid "add a Transition" -msgstr "ajouter une transition" +msgstr "ajouter une adresse électronique" msgid "add a new permission" msgstr "ajouter une permission" @@ -956,7 +974,25 @@ # subject and object forms for each relation type # (no object form for final relation types) msgid "add_permission" -msgstr "permission d'ajouter" +msgstr "peut ajouter" + +# subject and object forms for each relation type +# (no object form for final relation types) +msgctxt "CWEType" +msgid "add_permission" +msgstr "permission d'ajout" + +msgctxt "CWRType" +msgid "add_permission" +msgstr "permission d'ajout" + +msgctxt "CWGroup" +msgid "add_permission_object" +msgstr "a la permission d'ajouter" + +msgctxt "RQLExpression" +msgid "add_permission_object" +msgstr "a la permission d'ajouter" msgid "add_permission_object" msgstr "a la permission d'ajouter" @@ -973,12 +1009,26 @@ "ajout de la relation %(rtype)s de %(frometype)s #%(fromeid)s vers %(toetype)" "s #%(toeid)s" +msgid "addrelated" +msgstr "ajouter" + +msgid "address" +msgstr "adresse électronique" + +msgctxt "EmailAddress" msgid "address" msgstr "adresse électronique" msgid "alias" msgstr "alias" +msgctxt "EmailAddress" +msgid "alias" +msgstr "alias" + +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" @@ -986,7 +1036,23 @@ msgstr "transitions autorisées depuis cet état" msgid "allowed_transition" -msgstr "transition autorisée" +msgstr "transitions autorisées" + +msgctxt "State" +msgid "allowed_transition" +msgstr "transitions autorisées" + +msgctxt "BaseTransition" +msgid "allowed_transition_object" +msgstr "transition autorisée de" + +msgctxt "Transition" +msgid "allowed_transition_object" +msgstr "transition autorisée de" + +msgctxt "WorkflowTransition" +msgid "allowed_transition_object" +msgstr "transition autorisée de" msgid "allowed_transition_object" msgstr "états en entrée" @@ -1069,6 +1135,14 @@ msgid "bookmarked_by" msgstr "utilisé par" +msgctxt "Bookmark" +msgid "bookmarked_by" +msgstr "utilisé par" + +msgctxt "CWUser" +msgid "bookmarked_by_object" +msgstr "utilise le(s) signet(s)" + msgid "bookmarked_by_object" msgstr "a pour signets" @@ -1155,6 +1229,28 @@ msgid "by relation" msgstr "via la relation" +msgid "by_transition" +msgstr "transition" + +msgctxt "TrInfo" +msgid "by_transition" +msgstr "transition" + +msgctxt "BaseTransition" +msgid "by_transition_object" +msgstr "a pour information" + +msgctxt "Transition" +msgid "by_transition_object" +msgstr "a pour information" + +msgctxt "WorkflowTransition" +msgid "by_transition_object" +msgstr "a pour information" + +msgid "by_transition_object" +msgstr "changement d'états" + msgid "calendar" msgstr "afficher un calendrier" @@ -1185,6 +1281,9 @@ msgid "can't display data, unexpected error: %s" msgstr "impossible d'afficher les données à cause de l'erreur suivante: %s" +msgid "can't have multiple exits on the same state" +msgstr "ne peut avoir plusieurs sorties sur le même état" + #, python-format msgid "" "can't set inlined=%(inlined)s, %(stype)s %(rtype)s %(otype)s has cardinality=" @@ -1199,9 +1298,14 @@ msgid "cancel this insert" msgstr "annuler cette insertion" -msgid "canonical" -msgstr "canonique" - +msgid "cardinality" +msgstr "cardinalité" + +msgctxt "CWAttribute" +msgid "cardinality" +msgstr "cardinalité" + +msgctxt "CWRelation" msgid "cardinality" msgstr "cardinalité" @@ -1227,9 +1331,14 @@ msgid "comment" msgstr "commentaire" -msgid "comment:" -msgstr "commentaire :" - +msgctxt "TrInfo" +msgid "comment" +msgstr "commentaire" + +msgid "comment_format" +msgstr "format" + +msgctxt "TrInfo" msgid "comment_format" msgstr "format" @@ -1284,6 +1393,12 @@ "composant permettant de présenter sur plusieurs pages les requêtes renvoyant " "plus d'un certain nombre de résultat" +msgid "components_pdfview" +msgstr "icône pdf" + +msgid "components_pdfview_description" +msgstr "l'icône pdf pour obtenir la page courant au format PDF" + msgid "components_rqlinput" msgstr "barre rql" @@ -1293,12 +1408,32 @@ msgid "composite" msgstr "composite" +msgctxt "CWRelation" +msgid "composite" +msgstr "composite" + +msgid "condition" +msgstr "condition" + +msgctxt "BaseTransition" +msgid "condition" +msgstr "condition" + +msgctxt "Transition" +msgid "condition" +msgstr "condition" + +msgctxt "WorkflowTransition" msgid "condition" msgstr "condition" msgid "condition:" msgstr "condition :" +msgctxt "RQLExpression" +msgid "condition_object" +msgstr "condition de" + msgid "condition_object" msgstr "condition de" @@ -1308,6 +1443,18 @@ msgid "constrained_by" msgstr "contraint par" +msgctxt "CWAttribute" +msgid "constrained_by" +msgstr "contraint par" + +msgctxt "CWRelation" +msgid "constrained_by" +msgstr "contraint par" + +msgctxt "CWConstraint" +msgid "constrained_by_object" +msgstr "contrainte de" + msgid "constrained_by_object" msgstr "contrainte de" @@ -1508,23 +1655,43 @@ msgid "creating RQLExpression (Transition %(linkto)s condition RQLExpression)" msgstr "création d'une expression RQL pour la transition %(linkto)s" +msgid "" +"creating RQLExpression (WorkflowTransition %(linkto)s condition " +"RQLExpression)" +msgstr "création d'une expression RQL pour la transition workflow %(linkto)s" + msgid "creating State (State allowed_transition Transition %(linkto)s)" msgstr "création d'un état pouvant aller vers la transition %(linkto)s" -msgid "creating State (State state_of CWEType %(linkto)s)" -msgstr "création d'un état pour le type %(linkto)s" +msgid "creating State (State state_of Workflow %(linkto)s)" +msgstr "création d'un état du workflow %(linkto)s" msgid "creating State (Transition %(linkto)s destination_state State)" msgstr "création d'un état destination de la transition %(linkto)s" +msgid "" +"creating SubWorkflowExitPoint (WorkflowTransition %(linkto)s " +"subworkflow_exit SubWorkflowExitPoint)" +msgstr "création d'un point de sortie de la transition workflow %(linkto)s" + msgid "creating Transition (State %(linkto)s allowed_transition Transition)" msgstr "création d'une transition autorisée depuis l'état %(linkto)s" msgid "creating Transition (Transition destination_state State %(linkto)s)" msgstr "création d'une transition vers l'état %(linkto)s" -msgid "creating Transition (Transition transition_of CWEType %(linkto)s)" -msgstr "création d'une transition pour le type %(linkto)s" +msgid "creating Transition (Transition transition_of Workflow %(linkto)s)" +msgstr "création d'une transition du workflow %(linkto)s" + +msgid "" +"creating WorkflowTransition (State %(linkto)s allowed_transition " +"WorkflowTransition)" +msgstr "création d'une transition workflow autorisée depuis l'état %(linkto)s" + +msgid "" +"creating WorkflowTransition (WorkflowTransition transition_of Workflow %" +"(linkto)s)" +msgstr "création d'une transition workflow du workflow %(linkto)s" msgid "creation" msgstr "création" @@ -1538,6 +1705,14 @@ msgid "cstrtype" msgstr "type de constrainte" +msgctxt "CWConstraint" +msgid "cstrtype" +msgstr "type" + +msgctxt "CWConstraintType" +msgid "cstrtype_object" +msgstr "type des contraintes" + msgid "cstrtype_object" msgstr "utilisé par" @@ -1551,6 +1726,12 @@ msgid "currently attached file: %s" msgstr "fichie actuellement attaché %s" +msgid "custom_workflow" +msgstr "workflow spécifique" + +msgid "custom_workflow_object" +msgstr "workflow de" + msgid "cwetype-schema-image" msgstr "schéma" @@ -1587,6 +1768,30 @@ msgid "default text format for rich text fields." msgstr "format de texte par défaut pour les champs textes" +msgid "default user workflow" +msgstr "workflow par défaut des utilisateurs" + +msgid "default workflow for an entity type" +msgstr "workflow par défaut pour un type d'entité" + +msgid "default_workflow" +msgstr "workflow par défaut" + +msgctxt "CWEType" +msgid "default_workflow" +msgstr "workflow par défaut" + +msgctxt "Workflow" +msgid "default_workflow_object" +msgstr "workflow par défaut de" + +msgid "default_workflow_object" +msgstr "workflow par défaut de" + +msgid "defaultval" +msgstr "valeur par défaut" + +msgctxt "CWAttribute" msgid "defaultval" msgstr "valeur par défaut" @@ -1626,6 +1831,9 @@ msgid "define an entity type, used to build the instance schema" msgstr "définit un type d'entité" +msgid "define how we get out from a sub-workflow" +msgstr "définit comment sortir d'un sous-workflow" + msgid "" "defines what's the property is applied for. You must select this first to be " "able to set value" @@ -1651,6 +1859,22 @@ msgid "delete_permission" msgstr "permission de supprimer" +msgctxt "CWEType" +msgid "delete_permission" +msgstr "permission de supprimer" + +msgctxt "CWRType" +msgid "delete_permission" +msgstr "permission de supprimer" + +msgctxt "CWGroup" +msgid "delete_permission_object" +msgstr "peut supprimer" + +msgctxt "RQLExpression" +msgid "delete_permission_object" +msgstr "peut supprimer" + msgid "delete_permission_object" msgstr "a la permission de supprimer" @@ -1672,9 +1896,84 @@ msgid "description" msgstr "description" +msgctxt "CWEType" +msgid "description" +msgstr "description" + +msgctxt "CWRelation" +msgid "description" +msgstr "description" + +msgctxt "Workflow" +msgid "description" +msgstr "description" + +msgctxt "CWAttribute" +msgid "description" +msgstr "description" + +msgctxt "Transition" +msgid "description" +msgstr "description" + +msgctxt "WorkflowTransition" +msgid "description" +msgstr "description" + +msgctxt "State" +msgid "description" +msgstr "description" + +msgctxt "CWRType" +msgid "description" +msgstr "description" + +msgctxt "BaseTransition" +msgid "description" +msgstr "description" + msgid "description_format" msgstr "format" +msgctxt "CWEType" +msgid "description_format" +msgstr "format" + +msgctxt "CWRelation" +msgid "description_format" +msgstr "format" + +msgctxt "Workflow" +msgid "description_format" +msgstr "format" + +msgctxt "CWAttribute" +msgid "description_format" +msgstr "format" + +msgctxt "Transition" +msgid "description_format" +msgstr "format" + +msgctxt "WorkflowTransition" +msgid "description_format" +msgstr "format" + +msgctxt "State" +msgid "description_format" +msgstr "format" + +msgctxt "CWRType" +msgid "description_format" +msgstr "format" + +msgctxt "BaseTransition" +msgid "description_format" +msgstr "format" + +msgid "destination state" +msgstr "état de destination" + msgid "destination state for this transition" msgstr "états accessibles par cette transition" @@ -1684,6 +1983,18 @@ msgid "destination_state" msgstr "état de destination" +msgctxt "Transition" +msgid "destination_state" +msgstr "état de destination" + +msgctxt "SubWorkflowExitPoint" +msgid "destination_state" +msgstr "état de destination" + +msgctxt "State" +msgid "destination_state_object" +msgstr "état final de" + msgid "destination_state_object" msgstr "destination de" @@ -1712,6 +2023,9 @@ msgid "display the component or not" msgstr "afficher le composant ou non" +msgid "display the pdf icon or not" +msgstr "afficher l'icône pdf ou non" + msgid "" "distinct label to distinguate between other permission entity of the same " "name" @@ -1729,6 +2043,9 @@ msgid "download icon" msgstr "icône de téléchargement" +msgid "download page as pdf" +msgstr "télécharger la page au format PDF" + msgid "download schema as owl" msgstr "télécharger le schéma OWL" @@ -1786,6 +2103,9 @@ msgid "entity edited" msgstr "entité éditée" +msgid "entity has no workflow set" +msgstr "l'entité n'a pas de workflow" + msgid "entity linked" msgstr "entité liée" @@ -1798,11 +2118,8 @@ msgstr "" "type d'entité à utiliser pour définir une configuration de sécurité avancée" -msgid "entity types which may use this state" -msgstr "type d'entités opuvant utiliser cet état" - -msgid "entity types which may use this transition" -msgstr "entités qui peuvent utiliser cette transition" +msgid "entity types which may use this workflow" +msgstr "types d'entité pouvant utiliser ce workflow" msgid "error while embedding page" msgstr "erreur pendant l'inclusion de la page" @@ -1824,15 +2141,33 @@ msgid "eta_date" msgstr "date de fin" +msgid "exit_point" +msgstr "état de sortie" + +msgid "exit_point_object" +msgstr "état de sortie de" + +#, python-format +msgid "exiting from subworkflow %s" +msgstr "sortie du sous-workflow %s" + msgid "expected:" msgstr "attendu :" msgid "expression" msgstr "expression" +msgctxt "RQLExpression" +msgid "expression" +msgstr "rql de l'expression" + msgid "exprtype" msgstr "type de l'expression" +msgctxt "RQLExpression" +msgid "exprtype" +msgstr "type" + msgid "external page" msgstr "page externe" @@ -1884,6 +2219,18 @@ msgid "final" msgstr "final" +msgctxt "CWEType" +msgid "final" +msgstr "final" + +msgctxt "CWRType" +msgid "final" +msgstr "final" + +msgid "firstname" +msgstr "prénom" + +msgctxt "CWUser" msgid "firstname" msgstr "prénom" @@ -1896,6 +2243,14 @@ msgid "for_user" msgstr "pour l'utilisateur" +msgctxt "CWProperty" +msgid "for_user" +msgstr "propriété de l'utilisateur" + +msgctxt "CWUser" +msgid "for_user_object" +msgstr "a pour préférence" + msgid "for_user_object" msgstr "utilise les propriétés" @@ -1912,6 +2267,18 @@ msgid "from_entity" msgstr "de l'entité" +msgctxt "CWAttribute" +msgid "from_entity" +msgstr "attribut de l'entité" + +msgctxt "CWRelation" +msgid "from_entity" +msgstr "relation de l'entité" + +msgctxt "CWEType" +msgid "from_entity_object" +msgstr "entité de" + msgid "from_entity_object" msgstr "relation sujet" @@ -1921,6 +2288,14 @@ msgid "from_state" msgstr "de l'état" +msgctxt "TrInfo" +msgid "from_state" +msgstr "état de départ" + +msgctxt "State" +msgid "from_state_object" +msgstr "état de départ de" + msgid "from_state_object" msgstr "transitions depuis cet état" @@ -1930,9 +2305,17 @@ msgid "fulltext_container" msgstr "conteneur du texte indexé" +msgctxt "CWRType" +msgid "fulltext_container" +msgstr "objet à indexer" + msgid "fulltextindexed" msgstr "indexation du texte" +msgctxt "CWAttribute" +msgid "fulltextindexed" +msgstr "texte indexé" + msgid "generic plot" msgstr "tracé de courbes standard" @@ -1951,6 +2334,10 @@ msgid "granted to groups" msgstr "accordée aux groupes" +#, python-format +msgid "graphical representation of %s" +msgstr "représentation graphique de %s" + msgid "graphical representation of the instance'schema" msgstr "représentation graphique du schéma de l'instance" @@ -2046,6 +2433,9 @@ msgid "id of main template used to render pages" msgstr "id du template principal" +msgid "identical to" +msgstr "identique à" + msgid "identical_to" msgstr "identique à" @@ -2074,6 +2464,14 @@ msgid "in_group" msgstr "dans le groupe" +msgctxt "CWUser" +msgid "in_group" +msgstr "fait partie du groupe" + +msgctxt "CWGroup" +msgid "in_group_object" +msgstr "contient les utilisateurs" + msgid "in_group_object" msgstr "membres" @@ -2099,6 +2497,10 @@ msgid "indexed" msgstr "index" +msgctxt "CWAttribute" +msgid "indexed" +msgstr "indexé" + msgid "indicate the current state of an entity" msgstr "indique l'état courant d'une entité" @@ -2115,15 +2517,27 @@ msgid "initial estimation %s" msgstr "estimation initiale %s" -msgid "initial state for entities of this type" -msgstr "état initial pour les entités de ce type" +msgid "initial state for this workflow" +msgstr "état initial pour ce workflow" msgid "initial_state" msgstr "état initial" +msgctxt "Workflow" +msgid "initial_state" +msgstr "état initial" + +msgctxt "State" msgid "initial_state_object" msgstr "état initial de" +msgid "initial_state_object" +msgstr "état initial de" + +msgid "inlined" +msgstr "mise en ligne" + +msgctxt "CWRType" msgid "inlined" msgstr "mise en ligne" @@ -2136,6 +2550,10 @@ msgid "internationalizable" msgstr "internationalisable" +msgctxt "CWAttribute" +msgid "internationalizable" +msgstr "internationalisable" + #, python-format msgid "invalid action %r" msgstr "action %r invalide" @@ -2194,6 +2612,10 @@ msgid "label" msgstr "libellé" +msgctxt "CWPermission" +msgid "label" +msgstr "libellé" + msgid "language of the user interface" msgstr "langue pour l'interface utilisateur" @@ -2203,6 +2625,10 @@ msgid "last_login_time" msgstr "dernière date de connexion" +msgctxt "CWUser" +msgid "last_login_time" +msgstr "dernière date de connexion" + msgid "latest modification time of an entity" msgstr "date de dernière modification d'une entité" @@ -2236,14 +2662,17 @@ msgid "link a relation definition to its subject entity type" msgstr "lie une définition de relation à son type d'entité sujet" -msgid "link a state to one or more entity type" -msgstr "lier un état à une ou plusieurs entités" +msgid "link a state to one or more workflow" +msgstr "lie un état à un ou plusieurs workflow" msgid "link a transition information to its object" msgstr "lié une enregistrement de transition vers l'objet associé" -msgid "link a transition to one or more entity type" -msgstr "lie une transition à un ou plusieurs types d'entités" +msgid "link a transition to one or more workflow" +msgstr "lie une transition à un ou plusieurs workflow" + +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" @@ -2260,6 +2689,10 @@ msgid "login" msgstr "identifiant" +msgctxt "CWUser" +msgid "login" +msgstr "identifiant" + msgid "login or email" msgstr "identifiant ou email" @@ -2279,6 +2712,10 @@ msgid "mainvars" msgstr "variables principales" +msgctxt "RQLExpression" +msgid "mainvars" +msgstr "variables principales" + msgid "manage" msgstr "gestion du site" @@ -2294,6 +2731,9 @@ msgid "managers" msgstr "administrateurs" +msgid "mandatory relation" +msgstr "relation obligatoire" + msgid "march" msgstr "mars" @@ -2340,6 +2780,50 @@ msgid "name" msgstr "nom" +msgctxt "CWEType" +msgid "name" +msgstr "nom" + +msgctxt "Transition" +msgid "name" +msgstr "nom" + +msgctxt "Workflow" +msgid "name" +msgstr "nom" + +msgctxt "CWGroup" +msgid "name" +msgstr "nom" + +msgctxt "CWConstraintType" +msgid "name" +msgstr "nom" + +msgctxt "WorkflowTransition" +msgid "name" +msgstr "nom" + +msgctxt "State" +msgid "name" +msgstr "nom" + +msgctxt "CWPermission" +msgid "name" +msgstr "nom" + +msgctxt "CWRType" +msgid "name" +msgstr "nom" + +msgctxt "BaseTransition" +msgid "name" +msgstr "nom" + +msgctxt "CWCache" +msgid "name" +msgstr "nom" + msgid "name of the cache" msgstr "nom du cache applicatif" @@ -2447,6 +2931,14 @@ msgid "ordernum" msgstr "ordre" +msgctxt "CWAttribute" +msgid "ordernum" +msgstr "numéro d'ordre" + +msgctxt "CWRelation" +msgid "ordernum" +msgstr "numéro d'ordre" + msgid "owl" msgstr "owl" @@ -2482,6 +2974,10 @@ msgid "path" msgstr "chemin" +msgctxt "Bookmark" +msgid "path" +msgstr "chemin" + msgid "permission" msgstr "permission" @@ -2503,6 +2999,10 @@ msgid "pkey" msgstr "clé" +msgctxt "CWProperty" +msgid "pkey" +msgstr "code de la propriété" + msgid "please correct errors below" msgstr "veuillez corriger les erreurs ci-dessous" @@ -2513,7 +3013,21 @@ msgstr "vues possibles" msgid "powered by CubicWeb" -msgstr "" +msgstr "utilise la technologie CubicWeb" + +msgid "prefered_form" +msgstr "forme préférée" + +msgctxt "EmailAddress" +msgid "prefered_form" +msgstr "forme préférée" + +msgctxt "EmailAddress" +msgid "prefered_form_object" +msgstr "forme préférée de" + +msgid "prefered_form_object" +msgstr "forme préférée à" msgid "preferences" msgstr "préférences" @@ -2527,6 +3041,14 @@ msgid "primary_email" msgstr "adresse email principale" +msgctxt "CWUser" +msgid "primary_email" +msgstr "email principal" + +msgctxt "EmailAddress" +msgid "primary_email_object" +msgstr "adresse principale de" + msgid "primary_email_object" msgstr "adresse email principale (object)" @@ -2548,12 +3070,34 @@ msgid "read_permission" msgstr "permission de lire" +msgctxt "CWEType" +msgid "read_permission" +msgstr "permission d'ajouter" + +msgctxt "CWRType" +msgid "read_permission" +msgstr "permission d'ajouter" + +msgctxt "CWGroup" +msgid "read_permission_object" +msgstr "peut lire" + +msgctxt "RQLExpression" +msgid "read_permission_object" +msgstr "peut lire" + msgid "read_permission_object" msgstr "a la permission de lire" msgid "registry" msgstr "registre" +msgid "related entity has no state" +msgstr "l'entité lié n'a pas d'état" + +msgid "related entity has no workflow set" +msgstr "l'entité lié n'a pas de workflow" + #, python-format msgid "relation %(relname)s of %(ent)s" msgstr "relation %(relname)s de %(ent)s" @@ -2561,6 +3105,18 @@ msgid "relation_type" msgstr "type de relation" +msgctxt "CWAttribute" +msgid "relation_type" +msgstr "type de relation" + +msgctxt "CWRelation" +msgid "relation_type" +msgstr "type de relation" + +msgctxt "CWRType" +msgid "relation_type_object" +msgstr "définition" + msgid "relation_type_object" msgstr "définition" @@ -2573,63 +3129,45 @@ msgid "relative url of the bookmarked page" msgstr "url relative de la page" -msgid "remove this Bookmark" -msgstr "supprimer ce signet" - -msgid "remove this CWAttribute" -msgstr "supprimer cet attribut" - -msgid "remove this CWCache" -msgstr "supprimer ce cache applicatif" - -msgid "remove this CWConstraint" -msgstr "supprimer cette contrainte" - -msgid "remove this CWConstraintType" -msgstr "supprimer ce type de contrainte" - +msgctxt "inlined:CWRelation:from_entity:subject" +msgid "remove this CWEType" +msgstr "supprimer ce type d'entité" + +msgctxt "inlined:CWRelation:to_entity:subject" msgid "remove this CWEType" msgstr "supprimer ce type d'entité" -msgid "remove this CWGroup" -msgstr "supprimer ce groupe" - -msgid "remove this CWPermission" -msgstr "supprimer cette permission" - -msgid "remove this CWProperty" -msgstr "supprimer cette propriété" - +msgctxt "inlined:CWRelation:relation_type:subject" msgid "remove this CWRType" -msgstr "supprimer cette définition de relation" - -msgid "remove this CWRelation" -msgstr "supprimer cette relation" - -msgid "remove this CWUser" -msgstr "supprimer cet utilisateur" - +msgstr "supprimer ce type de relation" + +msgctxt "inlined:CWUser:use_email:subject" msgid "remove this EmailAddress" -msgstr "supprimer cette adresse email" - -msgid "remove this ExternalUri" -msgstr "supprimer cette Uri externe" - -msgid "remove this RQLExpression" -msgstr "supprimer cette expression rql" - -msgid "remove this State" -msgstr "supprimer cet état" - -msgid "remove this TrInfo" -msgstr "retirer cette information de transition" - -msgid "remove this Transition" -msgstr "supprimer cette transition" +msgstr "supprimer cette adresse électronique" msgid "require_group" msgstr "nécessite le groupe" +msgctxt "BaseTransition" +msgid "require_group" +msgstr "restreinte au groupe" + +msgctxt "Transition" +msgid "require_group" +msgstr "restreinte au groupe" + +msgctxt "CWPermission" +msgid "require_group" +msgstr "restreinte au groupe" + +msgctxt "WorkflowTransition" +msgid "require_group" +msgstr "restreinte au groupe" + +msgctxt "CWGroup" +msgid "require_group_object" +msgstr "dé" + msgid "require_group_object" msgstr "à les droits" @@ -2763,6 +3301,9 @@ msgid "semantic description of this transition" msgstr "description sémantique de cette transition" +msgid "semantic description of this workflow" +msgstr "description sémantique de ce workflow" + msgid "send email" msgstr "envoyer un courriel" @@ -2818,9 +3359,20 @@ msgid "sparql xml" msgstr "XML Sparql" +msgid "special transition allowing to go through a sub-workflow" +msgstr "transition spécial permettant d'aller dans un sous-workfow" + msgid "specializes" msgstr "dérive de" +msgctxt "CWEType" +msgid "specializes" +msgstr "spécialise" + +msgctxt "CWEType" +msgid "specializes_object" +msgstr "parent de" + msgid "specializes_object" msgstr "parent de" @@ -2830,9 +3382,30 @@ msgid "state" msgstr "état" +msgid "state doesn't belong to entity's current workflow" +msgstr "l'état n'appartient pas au workflow courant de l'entité" + +msgid "state doesn't belong to entity's workflow" +msgstr "l'état n'appartient pas au workflow de l'entité" + +msgid "" +"state doesn't belong to entity's workflow. You may want to set a custom " +"workflow for this entity first." +msgstr "" +"l'état n'appartient pas au workflow courant de l'entité. Vous désirez peut-" +"être spécifier que cette entité doit utiliser ce workflow." + msgid "state_of" msgstr "état de" +msgctxt "State" +msgid "state_of" +msgstr "état de" + +msgctxt "Workflow" +msgid "state_of_object" +msgstr "contient les états" + msgid "state_of_object" msgstr "a pour état" @@ -2855,12 +3428,65 @@ msgid "subject_plural:" msgstr "sujets :" +msgid "subworkflow" +msgstr "sous-workflow" + +msgctxt "WorkflowTransition" +msgid "subworkflow" +msgstr "sous-workflow" + +msgid "subworkflow state" +msgstr "état de sous-workflow" + +msgid "subworkflow_exit" +msgstr "sortie de sous-workflow" + +msgctxt "WorkflowTransition" +msgid "subworkflow_exit" +msgstr "sortie du sous-workflow" + +msgctxt "SubWorkflowExitPoint" +msgid "subworkflow_exit_object" +msgstr "états de sortie" + +msgid "subworkflow_exit_object" +msgstr "états de sortie" + +msgctxt "Workflow" +msgid "subworkflow_object" +msgstr "" + +msgid "subworkflow_object" +msgstr "utilisé par la transition" + +msgid "subworkflow_state" +msgstr "état du sous-workflow" + +msgctxt "SubWorkflowExitPoint" +msgid "subworkflow_state" +msgstr "état" + +msgctxt "State" +msgid "subworkflow_state_object" +msgstr "" + +msgid "subworkflow_state_object" +msgstr "état de sortie de" + msgid "sunday" msgstr "dimanche" msgid "surname" msgstr "nom" +msgctxt "CWUser" +msgid "surname" +msgstr "nom de famille" + +msgid "symetric" +msgstr "symétrique" + +msgctxt "CWRType" msgid "symetric" msgstr "symétrique" @@ -2920,6 +3546,10 @@ msgid "timestamp" msgstr "date" +msgctxt "CWCache" +msgid "timestamp" +msgstr "valide depuis" + msgid "timestamp of the latest source synchronization." msgstr "date de la dernière synchronisation avec la source." @@ -2929,6 +3559,10 @@ msgid "title" msgstr "titre" +msgctxt "Bookmark" +msgid "title" +msgstr "libellé" + msgid "to" msgstr "à" @@ -2942,6 +3576,18 @@ msgid "to_entity" msgstr "vers l'entité" +msgctxt "CWAttribute" +msgid "to_entity" +msgstr "pour l'entité" + +msgctxt "CWRelation" +msgid "to_entity" +msgstr "pour l'entité" + +msgctxt "CWEType" +msgid "to_entity_object" +msgstr "relation objet" + msgid "to_entity_object" msgstr "relation objet" @@ -2951,8 +3597,16 @@ msgid "to_state" msgstr "vers l'état" +msgctxt "TrInfo" +msgid "to_state" +msgstr "état de destination" + +msgctxt "State" msgid "to_state_object" -msgstr "transitions vers cette état" +msgstr "transition vers cet état" + +msgid "to_state_object" +msgstr "transitions vers cet état" msgid "todo_by" msgstr "à faire par" @@ -2960,13 +3614,34 @@ msgid "toggle check boxes" msgstr "inverser les cases à cocher" -#, python-format -msgid "transition from %s to %s does not exist or is not allowed" -msgstr "la transition de %s à %s n'existe pas ou n'est pas permise" +msgid "transition doesn't belong to entity's workflow" +msgstr "la transition n'appartient pas au workflow de l'entité" + +msgid "transition isn't allowed" +msgstr "la transition n'est pas autorisée" + +msgid "transition may not be fired" +msgstr "la transition ne peut-être déclenchée" msgid "transition_of" msgstr "transition de" +msgctxt "BaseTransition" +msgid "transition_of" +msgstr "transition de" + +msgctxt "Transition" +msgid "transition_of" +msgstr "transition de" + +msgctxt "WorkflowTransition" +msgid "transition_of" +msgstr "transition de" + +msgctxt "Workflow" +msgid "transition_of_object" +msgstr "a pour transition" + msgid "transition_of_object" msgstr "a pour transition" @@ -3039,6 +3714,10 @@ msgid "upassword" msgstr "mot de passe" +msgctxt "CWUser" +msgid "upassword" +msgstr "mot de passe" + msgid "update" msgstr "modification" @@ -3048,6 +3727,18 @@ msgid "update_permission" msgstr "permission de modification" +msgctxt "CWEType" +msgid "update_permission" +msgstr "permission de modifier" + +msgctxt "CWGroup" +msgid "update_permission_object" +msgstr "peut modifier" + +msgctxt "RQLExpression" +msgid "update_permission_object" +msgstr "peut modifier" + msgid "update_permission_object" msgstr "à la permission de modifier" @@ -3058,6 +3749,10 @@ msgid "uri" msgstr "uri" +msgctxt "ExternalUri" +msgid "uri" +msgstr "uri" + msgid "use template languages" msgstr "utiliser les langages de template" @@ -3071,6 +3766,14 @@ msgid "use_email" msgstr "adresse électronique" +msgctxt "CWUser" +msgid "use_email" +msgstr "utilise l'adresse électronique" + +msgctxt "EmailAddress" +msgid "use_email_object" +msgstr "utilisée par" + msgid "use_email_object" msgstr "adresse utilisée par" @@ -3127,6 +3830,14 @@ msgid "value" msgstr "valeur" +msgctxt "CWConstraint" +msgid "value" +msgstr "contrainte" + +msgctxt "CWProperty" +msgid "value" +msgstr "valeur" + msgid "value associated to this key is not editable manually" msgstr "la valeur associée à cette clé n'est pas éditable manuellement" @@ -3179,16 +3890,42 @@ msgid "" "when multiple addresses are equivalent (such as python-projects@logilab.org " -"and python-projects@lists.logilab.org), set this to true on one of them " -"which is the preferred form." +"and python-projects@lists.logilab.org), set this to indicate which is the " +"preferred form." msgstr "" -"quand plusieurs adresses sont équivalentes (comme python-projects@logilab." -"org et python-projects@lists.logilab.org), mettez cette propriété à vrai sur " -"l'une d'entre-elle qui sera la forme canonique" + +msgid "workflow" +msgstr "workflow" #, python-format -msgid "workflow for %s" -msgstr "workflow pour %s" +msgid "workflow changed to \"%s\"" +msgstr "workflow changé à \"%s\"" + +msgid "workflow has no initial state" +msgstr "le workflow n'a pas d'état initial" + +msgid "workflow history item" +msgstr "entrée de l'historique de workflow" + +msgid "workflow to which this state belongs" +msgstr "workflow auquel cet état appartient" + +msgid "workflow to which this transition belongs" +msgstr "workflow auquel cette transition appartient" + +msgid "workflow_of" +msgstr "workflow de" + +msgctxt "Workflow" +msgid "workflow_of" +msgstr "workflow de" + +msgctxt "CWEType" +msgid "workflow_of_object" +msgstr "a pour workflow" + +msgid "workflow_of_object" +msgstr "a pour workflow" msgid "xbel" msgstr "xbel" @@ -3207,11 +3944,3 @@ msgid "you should probably delete that property" msgstr "vous devriez probablement supprimer cette propriété" - -#~ msgid "" -#~ "You have no access to this view or it's not applyable to current data" -#~ msgstr "" -#~ "Vous n'avez pas accès à cette vue ou elle ne s'applique pas aux données" - -#~ msgid "download image" -#~ msgstr "image de téléchargement" diff -r d6ae24439bee -r c4c07aab1c39 misc/migration/3.5.0_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/migration/3.5.0_Any.py Fri Sep 18 12:20:39 2009 +0200 @@ -0,0 +1,10 @@ +add_relation_type('prefered_form') + +rql('SET X prefered_form Y WHERE Y canonical TRUE, X identical_to Y') +checkpoint() + +drop_attribute('EmailAddress', 'canonical') +drop_relation_definition('EmailAddress', 'identical_to', 'EmailAddress') + +if 'see_also' in schema: + sync_schema_props_perms('see_also', syncprops=False, syncrdefs=False) diff -r d6ae24439bee -r c4c07aab1c39 misc/migration/bootstrapmigration_repository.py --- a/misc/migration/bootstrapmigration_repository.py Thu Sep 10 08:03:18 2009 +0200 +++ b/misc/migration/bootstrapmigration_repository.py Fri Sep 18 12:20:39 2009 +0200 @@ -35,7 +35,17 @@ add_entity_type('BaseTransition') add_entity_type('WorkflowTransition') add_entity_type('SubWorkflowExitPoint') - drop_relation_definition('State', 'allowed_transition', 'Transition') # should be infered + # drop explicit 'State allowed_transition Transition' since it should be + # infered due to yams inheritance. However we've to disable the schema + # sync hook first to avoid to destroy existing data... + from cubicweb.server.schemahooks import after_del_relation_type + repo.hm.unregister_hook(after_del_relation_type, + 'after_delete_relation', 'relation_type') + try: + drop_relation_definition('State', 'allowed_transition', 'Transition') + finally: + repo.hm.register_hook(after_del_relation_type, + 'after_delete_relation', 'relation_type') schema.rebuild_infered_relations() # need to be explicitly called once everything is in place for et in rql('DISTINCT Any ET,ETN WHERE S state_of ET, ET name ETN', @@ -46,7 +56,7 @@ {'et': et.eid, 'wf': wf.eid}, 'et', ask_confirm=False) rql('SET T transition_of WF WHERE T transition_of ET, ET eid %(et)s, WF eid %(wf)s', {'et': et.eid, 'wf': wf.eid}, 'et', ask_confirm=False) - rql('SET WF initial_state S WHERE ET initial_state S, S state_of ET, ET eid %(et)s, WF eid %(wf)s', + rql('SET WF initial_state S WHERE ET initial_state S, ET eid %(et)s, WF eid %(wf)s', {'et': et.eid, 'wf': wf.eid}, 'et', ask_confirm=False) diff -r d6ae24439bee -r c4c07aab1c39 rqlrewrite.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rqlrewrite.py Fri Sep 18 12:20:39 2009 +0200 @@ -0,0 +1,480 @@ +"""RQL rewriting utilities : insert rql expression snippets into rql syntax +tree. + +This is used for instance for read security checking in the repository. + +:organization: Logilab +:copyright: 2007-2009 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 rql import nodes as n, stmts, TypeResolverException + +from logilab.common.compat import any + +from cubicweb import Unauthorized, server, typed_eid +from cubicweb.server.ssplanner import add_types_restriction + + +def remove_solutions(origsolutions, solutions, defined): + """when a rqlst has been generated from another by introducing security + assertions, this method returns solutions which are contained in orig + solutions + """ + newsolutions = [] + for origsol in origsolutions: + for newsol in solutions[:]: + for var, etype in origsol.items(): + try: + if newsol[var] != etype: + try: + defined[var].stinfo['possibletypes'].remove(newsol[var]) + except KeyError: + pass + break + except KeyError: + # variable has been rewritten + continue + else: + newsolutions.append(newsol) + solutions.remove(newsol) + return newsolutions + + +class Unsupported(Exception): pass + + +class RQLRewriter(object): + """insert some rql snippets into another rql syntax tree + + this class *isn't thread safe* + """ + + def __init__(self, session): + self.session = session + vreg = session.vreg + self.schema = vreg.schema + self.annotate = vreg.rqlhelper.annotate + self._compute_solutions = vreg.solutions + + def compute_solutions(self): + self.annotate(self.select) + try: + self._compute_solutions(self.session, self.select, self.kwargs) + except TypeResolverException: + raise Unsupported(str(self.select)) + if len(self.select.solutions) < len(self.solutions): + raise Unsupported() + + def rewrite(self, select, snippets, solutions, kwargs): + """ + snippets: (varmap, list of rql expression) + with varmap a *tuple* (select var, snippet var) + """ + if server.DEBUG: + print '---- rewrite', select, snippets, solutions + self.select = self.insert_scope = select + self.solutions = solutions + self.kwargs = kwargs + self.u_varname = None + self.removing_ambiguity = False + self.exists_snippet = {} + self.pending_keys = [] + # we have to annotate the rqlst before inserting snippets, even though + # we'll have to redo it latter + self.annotate(select) + self.insert_snippets(snippets) + if not self.exists_snippet and self.u_varname: + # U has been inserted than cancelled, cleanup + select.undefine_variable(select.defined_vars[self.u_varname]) + # clean solutions according to initial solutions + newsolutions = remove_solutions(solutions, select.solutions, + select.defined_vars) + assert len(newsolutions) >= len(solutions), ( + 'rewritten rql %s has lost some solutions, there is probably ' + 'something wrong in your schema permission (for instance using a ' + 'RQLExpression which insert a relation which doesn\'t exists in ' + 'the schema)\nOrig solutions: %s\nnew solutions: %s' % ( + select, solutions, newsolutions)) + if len(newsolutions) > len(solutions): + newsolutions = self.remove_ambiguities(snippets, newsolutions) + select.solutions = newsolutions + add_types_restriction(self.schema, select) + if server.DEBUG: + print '---- rewriten', select + + def insert_snippets(self, snippets, varexistsmap=None): + self.rewritten = {} + for varmap, rqlexprs in snippets: + if varexistsmap is not None and not varmap in varexistsmap: + continue + self.varmap = varmap + selectvar, snippetvar = varmap + assert snippetvar in 'SOX' + self.revvarmap = {snippetvar: selectvar} + self.varinfo = vi = {} + try: + vi['const'] = typed_eid(selectvar) # XXX gae + vi['rhs_rels'] = vi['lhs_rels'] = {} + except ValueError: + vi['stinfo'] = sti = self.select.defined_vars[selectvar].stinfo + if varexistsmap is None: + vi['rhs_rels'] = dict( (r.r_type, r) for r in sti['rhsrelations']) + vi['lhs_rels'] = dict( (r.r_type, r) for r in sti['relations'] + if not r in sti['rhsrelations']) + else: + vi['rhs_rels'] = vi['lhs_rels'] = {} + parent = None + inserted = False + for rqlexpr in rqlexprs: + self.current_expr = rqlexpr + if varexistsmap is None: + try: + new = self.insert_snippet(varmap, rqlexpr.snippet_rqlst, parent) + except Unsupported: + import traceback + traceback.print_exc() + continue + inserted = True + if new is not None: + self.exists_snippet[rqlexpr] = new + parent = parent or new + else: + # called to reintroduce snippet due to ambiguity creation, + # so skip snippets which are not introducing this ambiguity + exists = varexistsmap[varmap] + if self.exists_snippet[rqlexpr] is exists: + self.insert_snippet(varmap, rqlexpr.snippet_rqlst, exists) + if varexistsmap is None and not inserted: + # no rql expression found matching rql solutions. User has no access right + raise Unauthorized() + + def insert_snippet(self, varmap, snippetrqlst, parent=None): + new = snippetrqlst.where.accept(self) + if new is not None: + if self.varinfo.get('stinfo', {}).get('optrelations'): + assert parent is None + self.insert_scope = self.snippet_subquery(varmap, new) + self.insert_pending() + self.insert_scope = self.select + return + new = n.Exists(new) + if parent is None: + self.insert_scope.add_restriction(new) + else: + grandpa = parent.parent + or_ = n.Or(parent, new) + grandpa.replace(parent, or_) + if not self.removing_ambiguity: + try: + self.compute_solutions() + except Unsupported: + # some solutions have been lost, can't apply this rql expr + if parent is None: + self.select.remove_node(new, undefine=True) + else: + parent.parent.replace(or_, or_.children[0]) + self._cleanup_inserted(new) + raise + else: + self.insert_scope = new + self.insert_pending() + self.insert_scope = self.select + return new + self.insert_pending() + + def insert_pending(self): + """pending_keys hold variable referenced by U has__permission X + relation. + + Once the snippet introducing this has been inserted and solutions + recomputed, we have to insert snippet defined for of entity + types taken by X + """ + while self.pending_keys: + key, action = self.pending_keys.pop() + try: + varname = self.rewritten[key] + except KeyError: + try: + varname = self.revvarmap[key[-1]] + except KeyError: + # variable isn't used anywhere else, we can't insert security + raise Unauthorized() + ptypes = self.select.defined_vars[varname].stinfo['possibletypes'] + if len(ptypes) > 1: + # XXX dunno how to handle this + self.session.error( + 'cant check security of %s, ambigous type for %s in %s', + self.select, varname, key[0]) # key[0] == the rql expression + raise Unauthorized() + etype = iter(ptypes).next() + eschema = self.schema.eschema(etype) + if not eschema.has_perm(self.session, action): + rqlexprs = eschema.get_rqlexprs(action) + if not rqlexprs: + raise Unauthorised() + self.insert_snippets([((varname, 'X'), rqlexprs)]) + + def snippet_subquery(self, varmap, transformedsnippet): + """introduce the given snippet in a subquery""" + subselect = stmts.Select() + selectvar, snippetvar = varmap + subselect.append_selected(n.VariableRef( + subselect.get_variable(selectvar))) + aliases = [selectvar] + subselect.add_restriction(transformedsnippet.copy(subselect)) + stinfo = self.varinfo['stinfo'] + for rel in stinfo['relations']: + rschema = self.schema.rschema(rel.r_type) + if rschema.is_final() or (rschema.inlined and + not rel in stinfo['rhsrelations']): + self.select.remove_node(rel) + rel.children[0].name = selectvar + subselect.add_restriction(rel.copy(subselect)) + for vref in rel.children[1].iget_nodes(n.VariableRef): + subselect.append_selected(vref.copy(subselect)) + aliases.append(vref.name) + if self.u_varname: + # generate an identifier for the substitution + argname = subselect.allocate_varname() + while argname in self.kwargs: + argname = subselect.allocate_varname() + subselect.add_constant_restriction(subselect.get_variable(self.u_varname), + 'eid', unicode(argname), 'Substitute') + self.kwargs[argname] = self.session.user.eid + add_types_restriction(self.schema, subselect, subselect, + solutions=self.solutions) + myunion = stmts.Union() + myunion.append(subselect) + aliases = [n.VariableRef(self.select.get_variable(name, i)) + for i, name in enumerate(aliases)] + self.select.add_subquery(n.SubQuery(aliases, myunion), check=False) + self._cleanup_inserted(transformedsnippet) + try: + self.compute_solutions() + except Unsupported: + # some solutions have been lost, can't apply this rql expr + self.select.remove_subquery(new, undefine=True) + raise + return subselect + + def remove_ambiguities(self, snippets, newsolutions): + # the snippet has introduced some ambiguities, we have to resolve them + # "manually" + variantes = self.build_variantes(newsolutions) + # insert "is" where necessary + varexistsmap = {} + self.removing_ambiguity = True + for (erqlexpr, varmap, oldvarname), etype in variantes[0].iteritems(): + varname = self.rewritten[(erqlexpr, varmap, oldvarname)] + var = self.select.defined_vars[varname] + exists = var.references()[0].scope + exists.add_constant_restriction(var, 'is', etype, 'etype') + varexistsmap[varmap] = exists + # insert ORED exists where necessary + for variante in variantes[1:]: + self.insert_snippets(snippets, varexistsmap) + for key, etype in variante.iteritems(): + varname = self.rewritten[key] + try: + var = self.select.defined_vars[varname] + except KeyError: + # not a newly inserted variable + continue + exists = var.references()[0].scope + exists.add_constant_restriction(var, 'is', etype, 'etype') + # recompute solutions + #select.annotated = False # avoid assertion error + self.compute_solutions() + # clean solutions according to initial solutions + return remove_solutions(self.solutions, self.select.solutions, + self.select.defined_vars) + + def build_variantes(self, newsolutions): + variantes = set() + for sol in newsolutions: + variante = [] + for key, newvar in self.rewritten.iteritems(): + variante.append( (key, sol[newvar]) ) + variantes.add(tuple(variante)) + # rebuild variantes as dict + variantes = [dict(variante) for variante in variantes] + # remove variable which have always the same type + for key in self.rewritten: + it = iter(variantes) + etype = it.next()[key] + for variante in it: + if variante[key] != etype: + break + else: + for variante in variantes: + del variante[key] + return variantes + + def _cleanup_inserted(self, node): + # cleanup inserted variable references + for vref in node.iget_nodes(n.VariableRef): + vref.unregister_reference() + if not vref.variable.stinfo['references']: + # no more references, undefine the variable + del self.select.defined_vars[vref.name] + + def _may_be_shared(self, relation, target, searchedvarname): + """return True if the snippet relation can be skipped to use a relation + from the original query + """ + # if cardinality is in '?1', we can ignore the relation and use variable + # from the original query + rschema = self.schema.rschema(relation.r_type) + if target == 'object': + cardindex = 0 + ttypes_func = rschema.objects + rprop = rschema.rproperty + else: # target == 'subject': + cardindex = 1 + ttypes_func = rschema.subjects + rprop = lambda x, y, z: rschema.rproperty(y, x, z) + for etype in self.varinfo['stinfo']['possibletypes']: + for ttype in ttypes_func(etype): + if rprop(etype, ttype, 'cardinality')[cardindex] in '+*': + return False + return True + + def _use_outer_term(self, snippet_varname, term): + key = (self.current_expr, self.varmap, snippet_varname) + if key in self.rewritten: + insertedvar = self.select.defined_vars.pop(self.rewritten[key]) + for inserted_vref in insertedvar.references(): + inserted_vref.parent.replace(inserted_vref, term.copy(self.select)) + self.rewritten[key] = term.name + + def _get_varname_or_term(self, vname): + if vname == 'U': + if self.u_varname is None: + select = self.select + self.u_varname = select.allocate_varname() + # generate an identifier for the substitution + argname = select.allocate_varname() + 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') + self.kwargs[argname] = self.session.user.eid + return self.u_varname + key = (self.current_expr, self.varmap, vname) + try: + return self.rewritten[key] + except KeyError: + self.rewritten[key] = newvname = self.select.allocate_varname() + return newvname + + # visitor methods ########################################################## + + def _visit_binary(self, node, cls): + newnode = cls() + for c in node.children: + new = c.accept(self) + if new is None: + continue + newnode.append(new) + if len(newnode.children) == 0: + return None + if len(newnode.children) == 1: + return newnode.children[0] + return newnode + + def _visit_unary(self, node, cls): + newc = node.children[0].accept(self) + if newc is None: + return None + newnode = cls() + newnode.append(newc) + return newnode + + def visit_and(self, node): + return self._visit_binary(node, n.And) + + def visit_or(self, node): + return self._visit_binary(node, n.Or) + + def visit_not(self, node): + return self._visit_unary(node, n.Not) + + def visit_exists(self, node): + return self._visit_unary(node, n.Exists) + + def visit_relation(self, node): + lhs, rhs = node.get_variable_parts() + if node.r_type in ('has_add_permission', 'has_update_permission', + 'has_delete_permission', 'has_read_permission'): + assert lhs.name == 'U' + action = node.r_type.split('_')[1] + key = (self.current_expr, self.varmap, rhs.name) + self.pending_keys.append( (key, action) ) + return + if lhs.name in self.revvarmap: + # on lhs + # see if we can reuse this relation + rels = self.varinfo['lhs_rels'] + if (node.r_type in rels and isinstance(rhs, n.VariableRef) + and rhs.name != 'U' and not rels[node.r_type].neged(strict=True) + and self._may_be_shared(node, 'object', lhs.name)): + # ok, can share variable + term = rels[node.r_type].children[1].children[0] + self._use_outer_term(rhs.name, term) + return + elif isinstance(rhs, n.VariableRef) and rhs.name in self.revvarmap and lhs.name != 'U': + # on rhs + # see if we can reuse this relation + rels = self.varinfo['rhs_rels'] + if (node.r_type in rels and not rels[node.r_type].neged(strict=True) + and self._may_be_shared(node, 'subject', rhs.name)): + # ok, can share variable + term = rels[node.r_type].children[0] + self._use_outer_term(lhs.name, term) + return + rel = n.Relation(node.r_type, node.optional) + for c in node.children: + rel.append(c.accept(self)) + return rel + + def visit_comparison(self, node): + cmp_ = n.Comparison(node.operator) + for c in node.children: + cmp_.append(c.accept(self)) + return cmp_ + + def visit_mathexpression(self, node): + cmp_ = n.MathExpression(node.operator) + for c in cmp.children: + cmp_.append(c.accept(self)) + return cmp_ + + def visit_function(self, node): + """generate filter name for a function""" + function_ = n.Function(node.name) + for c in node.children: + function_.append(c.accept(self)) + return function_ + + def visit_constant(self, node): + """generate filter name for a constant""" + return n.Constant(node.value, node.type) + + def visit_variableref(self, node): + """get the sql name for a variable reference""" + if node.name in self.revvarmap: + if self.varinfo.get('const') is not None: + return n.Constant(self.varinfo['const'], 'Int') # XXX gae + return n.VariableRef(self.select.get_variable( + self.revvarmap[node.name])) + vname_or_term = self._get_varname_or_term(node.name) + if isinstance(vname_or_term, basestring): + return n.VariableRef(self.select.get_variable(vname_or_term)) + # shared term + return vname_or_term.copy(self.select) diff -r d6ae24439bee -r c4c07aab1c39 schema.py --- a/schema.py Thu Sep 10 08:03:18 2009 +0200 +++ b/schema.py Fri Sep 18 12:20:39 2009 +0200 @@ -44,6 +44,7 @@ 'owned_by', 'created_by', 'is', 'is_instance_of', 'identity', 'eid', 'creation_date', 'modification_date', 'has_text', 'cwuri', )) +SYSTEM_RTYPES = set(('require_permission', 'custom_workflow', 'in_state', 'wf_info_for')) # set of entity and relation types used to build the schema SCHEMA_TYPES = set(( @@ -100,7 +101,7 @@ etype = ETYPE_NAME_MAP[etype] return etype -def display_name(req, key, form=''): +def display_name(req, key, form='', context=None): """return a internationalized string for the key (schema entity or relation name) in a given form """ @@ -110,8 +111,12 @@ if form: key = key + '_' + form # ensure unicode - # added .lower() in case no translation are available - return unicode(req._(key)).lower() + # .lower() in case no translation are available XXX done whatever a translation is there or not! + if context is not None: + return unicode(req.pgettext(context, key)).lower() + else: + return unicode(req._(key)).lower() + __builtins__['display_name'] = deprecated('display_name should be imported from cubicweb.schema')(display_name) def ERSchema_display_name(self, req, form=''): @@ -642,6 +647,8 @@ if len(self.rqlst.defined_vars[mainvar].references()) <= 2: _LOGGER.warn('You did not use the %s variable in your RQL ' 'expression %s', mainvar, self) + # syntax tree used by read security (inserted in queries when necessary + self.snippet_rqlst = parse(self.minimal_rql, print_errors=False).children[0] def __str__(self): return self.full_rql @@ -767,8 +774,6 @@ class ERQLExpression(RQLExpression): def __init__(self, expression, mainvars=None, eid=None): RQLExpression.__init__(self, expression, mainvars or 'X', eid) - # syntax tree used by read security (inserted in queries when necessary - self.snippet_rqlst = parse(self.minimal_rql, print_errors=False).children[0] @property def full_rql(self): diff -r d6ae24439bee -r c4c07aab1c39 schemas/base.py --- a/schemas/base.py Thu Sep 10 08:03:18 2009 +0200 +++ b/schemas/base.py Fri Sep 18 12:20:39 2009 +0200 @@ -52,11 +52,10 @@ alias = String(fulltextindexed=True, maxsize=56) address = String(required=True, fulltextindexed=True, indexed=True, unique=True, maxsize=128) - canonical = Boolean(default=False, - description=_('when multiple addresses are equivalent \ + prefered_form = SubjectRelation('EmailAddress', cardinality='?*', + description=_('when multiple addresses are equivalent \ (such as python-projects@logilab.org and python-projects@lists.logilab.org), set this \ -to true on one of them which is the preferred form.')) - identical_to = SubjectRelation('EmailAddress') +to indicate which is the preferred form.')) class use_email(RelationType): """ """ @@ -71,9 +70,7 @@ """the prefered email""" permissions = use_email.permissions -class identical_to(RelationType): - """identical_to""" - symetric = True +class prefered_form(RelationType): permissions = { 'read': ('managers', 'users', 'guests',), # XXX should have update permissions on both subject and object, @@ -207,10 +204,6 @@ } -class see_also(RelationType): - """generic relation to link one entity to another""" - symetric = True - class ExternalUri(EntityType): """a URI representing an object in external data store""" uri = String(required=True, unique=True, maxsize=256, @@ -254,3 +247,30 @@ name = String(required=True, unique=True, indexed=True, maxsize=128, description=_('name of the cache')) timestamp = Datetime(default='NOW') + + +# "abtract" relation types, not used in cubicweb itself + +class identical_to(RelationType): + """identical to""" + symetric = True + permissions = { + 'read': ('managers', 'users', 'guests',), + # XXX should have update permissions on both subject and object, + # though by doing this we will probably have no way to add + # this relation in the web ui. The easiest way to acheive this + # is probably to be able to have "U has_update_permission O" as + # RQLConstraint of the relation definition, though this is not yet + # possible + 'add': ('managers', RRQLExpression('U has_update_permission S'),), + 'delete': ('managers', RRQLExpression('U has_update_permission S'),), + } + +class see_also(RelationType): + """generic relation to link one entity to another""" + symetric = True + permissions = { + 'read': ('managers', 'users', 'guests',), + 'add': ('managers', RRQLExpression('U has_update_permission S'),), + 'delete': ('managers', RRQLExpression('U has_update_permission S'),), + } diff -r d6ae24439bee -r c4c07aab1c39 schemas/workflow.py --- a/schemas/workflow.py Thu Sep 10 08:03:18 2009 +0200 +++ b/schemas/workflow.py Fri Sep 18 12:20:39 2009 +0200 @@ -27,13 +27,12 @@ constraints=[RQLConstraint('O final FALSE')]) initial_state = SubjectRelation('State', cardinality='?*', - # S initial_state O, O state_of S constraints=[RQLConstraint('O state_of S')], description=_('initial state for this workflow')) class default_workflow(RelationType): - """default workflow for this entity types""" + """default workflow for an entity type""" permissions = META_RTYPE_PERMS subject = 'CWEType' diff -r d6ae24439bee -r c4c07aab1c39 selectors.py --- a/selectors.py Thu Sep 10 08:03:18 2009 +0200 +++ b/selectors.py Fri Sep 18 12:20:39 2009 +0200 @@ -960,7 +960,14 @@ """ def __init__(self, scorefunc, once_is_enough=False): super(score_entity, self).__init__(once_is_enough) - self.score_entity = scorefunc + def intscore(*args, **kwargs): + score = scorefunc(*args, **kwargs) + if not score: + return 0 + if isinstance(score, (int, long)): + return score + return 1 + self.score_entity = intscore # XXX DEPRECATED ############################################################## diff -r d6ae24439bee -r c4c07aab1c39 server/migractions.py --- a/server/migractions.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/migractions.py Fri Sep 18 12:20:39 2009 +0200 @@ -116,7 +116,7 @@ config = self.config repo = self.repo_connect() # paths - timestamp = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') + timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') instbkdir = osp.join(config.appdatahome, 'backup') if not osp.exists(instbkdir): os.makedirs(instbkdir) @@ -415,11 +415,12 @@ espschema = eschema.specializes() if repospschema and not espschema: self.rqlexec('DELETE X specializes Y WHERE X is CWEType, X name %(x)s', - {'x': str(repoeschema)}) + {'x': str(repoeschema)}, ask_confirm=False) elif not repospschema and espschema: self.rqlexec('SET X specializes Y WHERE X is CWEType, X name %(x)s, ' 'Y is CWEType, Y name %(y)s', - {'x': str(repoeschema), 'y': str(espschema)}) + {'x': str(repoeschema), 'y': str(espschema)}, + ask_confirm=False) self.rqlexecall(ss.updateeschema2rql(eschema), ask_confirm=self.verbosity >= 2) for rschema, targettypes, role in eschema.relation_definitions(True): diff -r d6ae24439bee -r c4c07aab1c39 server/msplanner.py --- a/server/msplanner.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/msplanner.py Fri Sep 18 12:20:39 2009 +0200 @@ -344,7 +344,7 @@ # * at least one supported relation specified if not varobj._q_invariant or \ any(imap(source.support_relation, - (r.r_type for r in rels if r.r_type != 'eid'))): + (r.r_type for r in rels if r.r_type not in ('identity', 'eid')))): sourcesterms.setdefault(source, {}).setdefault(varobj, set()).add(i) # if variable is not invariant and is used by a relation # not supported by this source, we'll have to split the diff -r d6ae24439bee -r c4c07aab1c39 server/querier.py --- a/server/querier.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/querier.py Fri Sep 18 12:20:39 2009 +0200 @@ -138,8 +138,8 @@ # various resource accesors self.querier = querier self.schema = querier.schema - self.rqlhelper = querier._rqlhelper self.sqlannotate = querier.sqlgen_annotate + self.rqlhelper = session.vreg.rqlhelper def annotate_rqlst(self): if not self.rqlst.annotated: @@ -265,6 +265,8 @@ myrqlst = select.copy(solutions=lchecksolutions) myunion.append(myrqlst) # in-place rewrite + annotation / simplification + lcheckdef = [((varmap, 'X'), rqlexprs) + for varmap, rqlexprs in lcheckdef] rewrite(myrqlst, lcheckdef, lchecksolutions, self.args) noinvariant.update(noinvariant_vars(restricted, myrqlst, nbtrees)) if () in localchecks: @@ -508,7 +510,7 @@ if repo.schema.rschema(rtype).inlined: entity = session.entity_from_eid(subj) entity[rtype] = obj - repo.glob_update_entity(session, entity) + repo.glob_update_entity(session, entity, set((rtype,))) else: repo.glob_add_relation(session, subj, rtype, obj) @@ -524,37 +526,33 @@ def set_schema(self, schema): self.schema = schema + repo = self._repo # rql parsing / analysing helper - self._rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid', - 'has_text': 'fti'}) - self._rql_cache = Cache(self._repo.config['rql-cache-size']) + self.solutions = repo.vreg.solutions + self._rql_cache = Cache(repo.config['rql-cache-size']) self.cache_hit, self.cache_miss = 0, 0 # rql planner # note: don't use repo.sources, may not be built yet, and also "admin" # isn't an actual source - if len([uri for uri in self._repo.config.sources() if uri != 'admin']) < 2: + rqlhelper = repo.vreg.rqlhelper + self._parse = rqlhelper.parse + self._annotate = rqlhelper.annotate + if len([uri for uri in repo.config.sources() if uri != 'admin']) < 2: from cubicweb.server.ssplanner import SSPlanner - self._planner = SSPlanner(schema, self._rqlhelper) + self._planner = SSPlanner(schema, rqlhelper) else: from cubicweb.server.msplanner import MSPlanner - self._planner = MSPlanner(schema, self._rqlhelper) + self._planner = MSPlanner(schema, rqlhelper) # sql generation annotator self.sqlgen_annotate = SQLGenAnnotator(schema).annotate def parse(self, rql, annotate=False): """return a rql syntax tree for the given rql""" try: - return self._rqlhelper.parse(unicode(rql), annotate=annotate) + return self._parse(unicode(rql), annotate=annotate) except UnicodeError: raise RQLSyntaxError(rql) - def solutions(self, session, rqlst, args): - assert session is not None - def type_from_eid(eid, type_from_eid=self._repo.type_from_eid, - session=session): - return type_from_eid(eid, session) - self._rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args) - def plan_factory(self, rqlst, args, session): """create an execution plan for an INSERT RQL query""" if rqlst.TYPE == 'insert': @@ -642,7 +640,7 @@ # bother modifying it. This is not necessary on write queries since # a new syntax tree is built from them. rqlst = rqlst.copy() - self._rqlhelper.annotate(rqlst) + self._annotate(rqlst) # make an execution plan plan = self.plan_factory(rqlst, args, session) plan.cache_key = cachekey diff -r d6ae24439bee -r c4c07aab1c39 server/repository.py --- a/server/repository.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/repository.py Fri Sep 18 12:20:39 2009 +0200 @@ -108,6 +108,9 @@ # hooks responsability to ensure they do not violate relation's cardinality if session.is_super_session: return + ensure_card_respected(session.unsafe_execute, session, eidfrom, rtype, eidto) + +def ensure_card_respected(execute, session, eidfrom, rtype, eidto): card = rproperty(session, 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 @@ -118,14 +121,11 @@ if card[0] in '1?': rschema = session.repo.schema.rschema(rtype) if not rschema.inlined: - session.unsafe_execute( - 'DELETE X %s Y WHERE X eid %%(x)s, NOT Y eid %%(y)s' % rtype, - {'x': eidfrom, 'y': eidto}, 'x') + execute('DELETE X %s Y WHERE X eid %%(x)s,NOT Y eid %%(y)s' % rtype, + {'x': eidfrom, 'y': eidto}, 'x') if card[1] in '1?': - session.unsafe_execute( - 'DELETE X %s Y WHERE NOT X eid %%(x)s, Y eid %%(y)s' % rtype, - {'x': eidfrom, 'y': eidto}, 'y') - + execute('DELETE X %s Y WHERE NOT X eid %%(x)s, Y eid %%(y)s' % rtype, + {'x': eidfrom, 'y': eidto}, 'y') class Repository(object): """a repository provides access to a set of persistent storages for @@ -149,6 +149,7 @@ self._running_threads = [] # initial schema, should be build or replaced latter self.schema = CubicWebSchema(config.appid) + self.vreg.schema = self.schema # until actual schema is loaded... # querier helper, need to be created after sources initialization self.querier = QuerierHelper(self, self.schema) # should we reindex in changes? @@ -192,7 +193,6 @@ config.bootstrap_cubes() self.set_bootstrap_schema(config.load_schema()) # need to load the Any and CWUser entity types - self.vreg.schema = self.schema etdirectory = join(CW_SOFTWARE_ROOT, 'entities') self.vreg.init_registration([etdirectory]) self.vreg.load_file(join(etdirectory, '__init__.py'), @@ -246,15 +246,16 @@ if rebuildinfered: schema.rebuild_infered_relations() self.info('set schema %s %#x', schema.name, id(schema)) - self.debug(', '.join(sorted(str(e) for e in schema.entities()))) + if resetvreg: + # full reload of all appobjects + self.vreg.reset() + self.vreg.set_schema(schema) + else: + self.vreg._set_schema(schema) self.querier.set_schema(schema) for source in self.sources: source.set_schema(schema) self.schema = schema - if resetvreg: - # full reload of all appobjects - self.vreg.reset() - self.vreg.set_schema(schema) self.reset_hooks() def reset_hooks(self): diff -r d6ae24439bee -r c4c07aab1c39 server/rqlannotation.py --- a/server/rqlannotation.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/rqlannotation.py Fri Sep 18 12:20:39 2009 +0200 @@ -331,7 +331,7 @@ if isinstance(term, VariableRef) and self.is_ambiguous(term.variable): var = term.variable if len(var.stinfo['relations'] - var.stinfo['typerels']) == 1 \ - or rel.sqlscope is var.sqlscope: + or rel.sqlscope is var.sqlscope or rel.r_type == 'identity': self.restrict(var, frozenset(etypes_func())) try: self.maydeambrels[var].add(rel) diff -r d6ae24439bee -r c4c07aab1c39 server/rqlrewrite.py --- a/server/rqlrewrite.py Thu Sep 10 08:03:18 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,396 +0,0 @@ -"""RQL rewriting utilities, used for read security checking - -:organization: Logilab -:copyright: 2007-2009 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 -""" - -from rql import nodes, stmts, TypeResolverException -from cubicweb import Unauthorized, server, typed_eid -from cubicweb.server.ssplanner import add_types_restriction - -def remove_solutions(origsolutions, solutions, defined): - """when a rqlst has been generated from another by introducing security - assertions, this method returns solutions which are contained in orig - solutions - """ - newsolutions = [] - for origsol in origsolutions: - for newsol in solutions[:]: - for var, etype in origsol.items(): - try: - if newsol[var] != etype: - try: - defined[var].stinfo['possibletypes'].remove(newsol[var]) - except KeyError: - pass - break - except KeyError: - # variable has been rewritten - continue - else: - newsolutions.append(newsol) - solutions.remove(newsol) - return newsolutions - -class Unsupported(Exception): pass - -class RQLRewriter(object): - """insert some rql snippets into another rql syntax tree""" - def __init__(self, querier, session): - self.session = session - self.annotate = querier._rqlhelper.annotate - self._compute_solutions = querier.solutions - self.schema = querier.schema - - def compute_solutions(self): - self.annotate(self.select) - try: - self._compute_solutions(self.session, self.select, self.kwargs) - except TypeResolverException: - raise Unsupported() - if len(self.select.solutions) < len(self.solutions): - raise Unsupported() - - def rewrite(self, select, snippets, solutions, kwargs): - if server.DEBUG: - print '---- rewrite', select, snippets, solutions - self.select = select - self.solutions = solutions - self.kwargs = kwargs - self.u_varname = None - self.removing_ambiguity = False - self.exists_snippet = {} - # we have to annotate the rqlst before inserting snippets, even though - # we'll have to redo it latter - self.annotate(select) - self.insert_snippets(snippets) - if not self.exists_snippet and self.u_varname: - # U has been inserted than cancelled, cleanup - select.undefine_variable(select.defined_vars[self.u_varname]) - # clean solutions according to initial solutions - newsolutions = remove_solutions(solutions, select.solutions, - select.defined_vars) - assert len(newsolutions) >= len(solutions), \ - 'rewritten rql %s has lost some solutions, there is probably something '\ - 'wrong in your schema permission (for instance using a '\ - 'RQLExpression which insert a relation which doesn\'t exists in '\ - 'the schema)\nOrig solutions: %s\nnew solutions: %s' % ( - select, solutions, newsolutions) - if len(newsolutions) > len(solutions): - # the snippet has introduced some ambiguities, we have to resolve them - # "manually" - variantes = self.build_variantes(newsolutions) - # insert "is" where necessary - varexistsmap = {} - self.removing_ambiguity = True - for (erqlexpr, mainvar, oldvarname), etype in variantes[0].iteritems(): - varname = self.rewritten[(erqlexpr, mainvar, oldvarname)] - var = select.defined_vars[varname] - exists = var.references()[0].scope - exists.add_constant_restriction(var, 'is', etype, 'etype') - varexistsmap[mainvar] = exists - # insert ORED exists where necessary - for variante in variantes[1:]: - self.insert_snippets(snippets, varexistsmap) - for (erqlexpr, mainvar, oldvarname), etype in variante.iteritems(): - varname = self.rewritten[(erqlexpr, mainvar, oldvarname)] - try: - var = select.defined_vars[varname] - except KeyError: - # not a newly inserted variable - continue - exists = var.references()[0].scope - exists.add_constant_restriction(var, 'is', etype, 'etype') - # recompute solutions - #select.annotated = False # avoid assertion error - self.compute_solutions() - # clean solutions according to initial solutions - newsolutions = remove_solutions(solutions, select.solutions, - select.defined_vars) - select.solutions = newsolutions - add_types_restriction(self.schema, select) - if server.DEBUG: - print '---- rewriten', select - - def build_variantes(self, newsolutions): - variantes = set() - for sol in newsolutions: - variante = [] - for (erqlexpr, mainvar, oldvar), newvar in self.rewritten.iteritems(): - variante.append( ((erqlexpr, mainvar, oldvar), sol[newvar]) ) - variantes.add(tuple(variante)) - # rebuild variantes as dict - variantes = [dict(variante) for variante in variantes] - # remove variable which have always the same type - for erqlexpr, mainvar, oldvar in self.rewritten: - it = iter(variantes) - etype = it.next()[(erqlexpr, mainvar, oldvar)] - for variante in it: - if variante[(erqlexpr, mainvar, oldvar)] != etype: - break - else: - for variante in variantes: - del variante[(erqlexpr, mainvar, oldvar)] - return variantes - - def insert_snippets(self, snippets, varexistsmap=None): - self.rewritten = {} - for varname, erqlexprs in snippets: - if varexistsmap is not None and not varname in varexistsmap: - continue - try: - self.const = typed_eid(varname) - self.varname = self.const - self.rhs_rels = self.lhs_rels = {} - except ValueError: - self.varname = varname - self.const = None - self.varstinfo = stinfo = self.select.defined_vars[varname].stinfo - if varexistsmap is None: - self.rhs_rels = dict( (rel.r_type, rel) for rel in stinfo['rhsrelations']) - self.lhs_rels = dict( (rel.r_type, rel) for rel in stinfo['relations'] - if not rel in stinfo['rhsrelations']) - else: - self.rhs_rels = self.lhs_rels = {} - parent = None - inserted = False - for erqlexpr in erqlexprs: - self.current_expr = erqlexpr - if varexistsmap is None: - try: - new = self.insert_snippet(varname, erqlexpr.snippet_rqlst, parent) - except Unsupported: - continue - inserted = True - if new is not None: - self.exists_snippet[erqlexpr] = new - parent = parent or new - else: - # called to reintroduce snippet due to ambiguity creation, - # so skip snippets which are not introducing this ambiguity - exists = varexistsmap[varname] - if self.exists_snippet[erqlexpr] is exists: - self.insert_snippet(varname, erqlexpr.snippet_rqlst, exists) - if varexistsmap is None and not inserted: - # no rql expression found matching rql solutions. User has no access right - raise Unauthorized() - - def insert_snippet(self, varname, snippetrqlst, parent=None): - new = snippetrqlst.where.accept(self) - if new is not None: - try: - var = self.select.defined_vars[varname] - except KeyError: - # not a variable - pass - else: - if var.stinfo['optrelations']: - # use a subquery - subselect = stmts.Select() - subselect.append_selected(nodes.VariableRef(subselect.get_variable(varname))) - subselect.add_restriction(new.copy(subselect)) - aliases = [varname] - for rel in var.stinfo['relations']: - rschema = self.schema.rschema(rel.r_type) - if rschema.is_final() or (rschema.inlined and not rel in var.stinfo['rhsrelations']): - self.select.remove_node(rel) - rel.children[0].name = varname - subselect.add_restriction(rel.copy(subselect)) - for vref in rel.children[1].iget_nodes(nodes.VariableRef): - subselect.append_selected(vref.copy(subselect)) - aliases.append(vref.name) - if self.u_varname: - # generate an identifier for the substitution - argname = subselect.allocate_varname() - while argname in self.kwargs: - argname = subselect.allocate_varname() - subselect.add_constant_restriction(subselect.get_variable(self.u_varname), - 'eid', unicode(argname), 'Substitute') - self.kwargs[argname] = self.session.user.eid - add_types_restriction(self.schema, subselect, subselect, solutions=self.solutions) - assert parent is None - myunion = stmts.Union() - myunion.append(subselect) - aliases = [nodes.VariableRef(self.select.get_variable(name, i)) - for i, name in enumerate(aliases)] - self.select.add_subquery(nodes.SubQuery(aliases, myunion), check=False) - self._cleanup_inserted(new) - try: - self.compute_solutions() - except Unsupported: - # some solutions have been lost, can't apply this rql expr - self.select.remove_subquery(new, undefine=True) - raise - return - new = nodes.Exists(new) - if parent is None: - self.select.add_restriction(new) - else: - grandpa = parent.parent - or_ = nodes.Or(parent, new) - grandpa.replace(parent, or_) - if not self.removing_ambiguity: - try: - self.compute_solutions() - except Unsupported: - # some solutions have been lost, can't apply this rql expr - if parent is None: - self.select.remove_node(new, undefine=True) - else: - parent.parent.replace(or_, or_.children[0]) - self._cleanup_inserted(new) - raise - return new - - def _cleanup_inserted(self, node): - # cleanup inserted variable references - for vref in node.iget_nodes(nodes.VariableRef): - vref.unregister_reference() - if not vref.variable.stinfo['references']: - # no more references, undefine the variable - del self.select.defined_vars[vref.name] - - def _visit_binary(self, node, cls): - newnode = cls() - for c in node.children: - new = c.accept(self) - if new is None: - continue - newnode.append(new) - if len(newnode.children) == 0: - return None - if len(newnode.children) == 1: - return newnode.children[0] - return newnode - - def _visit_unary(self, node, cls): - newc = node.children[0].accept(self) - if newc is None: - return None - newnode = cls() - newnode.append(newc) - return newnode - - def visit_and(self, et): - return self._visit_binary(et, nodes.And) - - def visit_or(self, ou): - return self._visit_binary(ou, nodes.Or) - - def visit_not(self, node): - return self._visit_unary(node, nodes.Not) - - def visit_exists(self, node): - return self._visit_unary(node, nodes.Exists) - - def visit_relation(self, relation): - lhs, rhs = relation.get_variable_parts() - if lhs.name == 'X': - # on lhs - # see if we can reuse this relation - if relation.r_type in self.lhs_rels and isinstance(rhs, nodes.VariableRef) and rhs.name != 'U': - if self._may_be_shared(relation, 'object'): - # ok, can share variable - term = self.lhs_rels[relation.r_type].children[1].children[0] - self._use_outer_term(rhs.name, term) - return - elif isinstance(rhs, nodes.VariableRef) and rhs.name == 'X' and lhs.name != 'U': - # on rhs - # see if we can reuse this relation - if relation.r_type in self.rhs_rels and self._may_be_shared(relation, 'subject'): - # ok, can share variable - term = self.rhs_rels[relation.r_type].children[0] - self._use_outer_term(lhs.name, term) - return - rel = nodes.Relation(relation.r_type, relation.optional) - for c in relation.children: - rel.append(c.accept(self)) - return rel - - def visit_comparison(self, cmp): - cmp_ = nodes.Comparison(cmp.operator) - for c in cmp.children: - cmp_.append(c.accept(self)) - return cmp_ - - def visit_mathexpression(self, mexpr): - cmp_ = nodes.MathExpression(mexpr.operator) - for c in cmp.children: - cmp_.append(c.accept(self)) - return cmp_ - - def visit_function(self, function): - """generate filter name for a function""" - function_ = nodes.Function(function.name) - for c in function.children: - function_.append(c.accept(self)) - return function_ - - def visit_constant(self, constant): - """generate filter name for a constant""" - return nodes.Constant(constant.value, constant.type) - - def visit_variableref(self, vref): - """get the sql name for a variable reference""" - if vref.name == 'X': - if self.const is not None: - return nodes.Constant(self.const, 'Int') - return nodes.VariableRef(self.select.get_variable(self.varname)) - vname_or_term = self._get_varname_or_term(vref.name) - if isinstance(vname_or_term, basestring): - return nodes.VariableRef(self.select.get_variable(vname_or_term)) - # shared term - return vname_or_term.copy(self.select) - - def _may_be_shared(self, relation, target): - """return True if the snippet relation can be skipped to use a relation - from the original query - """ - # if cardinality is in '?1', we can ignore the relation and use variable - # from the original query - rschema = self.schema.rschema(relation.r_type) - if target == 'object': - cardindex = 0 - ttypes_func = rschema.objects - rprop = rschema.rproperty - else: # target == 'subject': - cardindex = 1 - ttypes_func = rschema.subjects - rprop = lambda x, y, z: rschema.rproperty(y, x, z) - for etype in self.varstinfo['possibletypes']: - for ttype in ttypes_func(etype): - if rprop(etype, ttype, 'cardinality')[cardindex] in '+*': - return False - return True - - def _use_outer_term(self, snippet_varname, term): - key = (self.current_expr, self.varname, snippet_varname) - if key in self.rewritten: - insertedvar = self.select.defined_vars.pop(self.rewritten[key]) - for inserted_vref in insertedvar.references(): - inserted_vref.parent.replace(inserted_vref, term.copy(self.select)) - self.rewritten[key] = term - - def _get_varname_or_term(self, vname): - if vname == 'U': - if self.u_varname is None: - select = self.select - self.u_varname = select.allocate_varname() - # generate an identifier for the substitution - argname = select.allocate_varname() - 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') - self.kwargs[argname] = self.session.user.eid - return self.u_varname - key = (self.current_expr, self.varname, vname) - try: - return self.rewritten[key] - except KeyError: - self.rewritten[key] = newvname = self.select.allocate_varname() - return newvname diff -r d6ae24439bee -r c4c07aab1c39 server/serverctl.py --- a/server/serverctl.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/serverctl.py Fri Sep 18 12:20:39 2009 +0200 @@ -210,12 +210,12 @@ cmdname = 'start' cfgname = 'repository' - def start_command(self, ctlconf, debug): + def start_server(self, ctlconf, debug): command = ['cubicweb-ctl start-repository '] if debug: command.append('--debug') command.append(self.config.appid) - return ' '.join(command) + os.system(' '.join(command)) class RepositoryStopHandler(CommandHandler): @@ -312,7 +312,7 @@ # postgres specific stuff if driver == 'postgres': # install plpythonu/plpgsql language if not installed by the cube - langs = ('plpgsql',) if sys.platform == 'win32' else ('plpythonu', 'plpgsql') + langs = sys.platform == 'win32' and ('plpgsql',) or ('plpythonu', 'plpgsql') for extlang in langs: helper.create_language(cursor, extlang) cursor.close() diff -r d6ae24439bee -r c4c07aab1c39 server/session.py --- a/server/session.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/session.py Fri Sep 18 12:20:39 2009 +0200 @@ -18,7 +18,7 @@ from cubicweb import RequestSessionMixIn, Binary, UnknownEid from cubicweb.dbapi import ConnectionProperties from cubicweb.utils import make_uid -from cubicweb.server.rqlrewrite import RQLRewriter +from cubicweb.rqlrewrite import RQLRewriter ETYPE_PYOBJ_MAP[Binary] = 'Bytes' @@ -84,13 +84,13 @@ session._threaddata = self.actual_session()._threaddata return session - def _change_relation(self, cb, fromeid, rtype, toeid): + def _super_call(self, __cb, *args, **kwargs): if self.is_super_session: - cb(self, fromeid, rtype, toeid) + __cb(self, *args, **kwargs) return self.is_super_session = True try: - cb(self, fromeid, rtype, toeid) + __cb(self, *args, **kwargs) finally: self.is_super_session = False @@ -105,8 +105,15 @@ You may use this in hooks when you know both eids of the relation you want to add. """ - self._change_relation(self.repo.glob_add_relation, - fromeid, rtype, toeid) + if self.vreg.schema[rtype].inlined: + entity = self.entity_from_eid(fromeid) + entity[rtype] = toeid + self._super_call(self.repo.glob_update_entity, + entity, set((rtype,))) + else: + self._super_call(self.repo.glob_add_relation, + fromeid, rtype, toeid) + def delete_relation(self, fromeid, rtype, toeid): """provide direct access to the repository method to delete a relation. @@ -118,8 +125,14 @@ You may use this in hooks when you know both eids of the relation you want to delete. """ - self._change_relation(self.repo.glob_delete_relation, - fromeid, rtype, toeid) + if self.vreg.schema[rtype].inlined: + entity = self.entity_from_eid(fromeid) + entity[rtype] = None + self._super_call(self.repo.glob_update_entity, + entity, set((rtype,))) + else: + self._super_call(self.repo.glob_delete_relation, + fromeid, rtype, toeid) # relations cache handling ################################################# @@ -152,8 +165,12 @@ if not isinstance(rset.description, list): # else description not set rset.description = list(rset.description) rset.description.append([self.describe(targeteid)[0]]) + targetentity = self.entity_from_eid(targeteid) + if targetentity.rset is None: + targetentity.rset = rset + targetentity.row = rset.rowcount + targetentity.col = 0 rset.rowcount += 1 - targetentity = self.entity_from_eid(targeteid) entities.append(targetentity) def _update_entity_rel_cache_del(self, eid, rtype, role, targeteid): @@ -193,13 +210,18 @@ vreg = self.vreg language = language or self.user.property_value('ui.language') try: - self._ = self.__ = vreg.config.translations[language] + gettext, pgettext = vreg.config.translations[language] + self._ = self.__ = gettext + self.pgettext = pgettext except KeyError: language = vreg.property_value('ui.language') try: - self._ = self.__ = vreg.config.translations[language] + gettext, pgettext = vreg.config.translations[language] + self._ = self.__ = gettext + self.pgettext = pgettext except KeyError: self._ = self.__ = unicode + self.pgettext = lambda x,y: y self.lang = language def change_property(self, prop, value): @@ -526,7 +548,7 @@ try: return self._threaddata._rewriter except AttributeError: - self._threaddata._rewriter = RQLRewriter(self.repo.querier, self) + self._threaddata._rewriter = RQLRewriter(self) return self._threaddata._rewriter def build_description(self, rqlst, args, result): diff -r d6ae24439bee -r c4c07aab1c39 server/sources/ldapuser.py --- a/server/sources/ldapuser.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/sources/ldapuser.py Fri Sep 18 12:20:39 2009 +0200 @@ -336,7 +336,7 @@ if sol[varname] == 'CWUser': mainvars.append(varname) break - assert mainvars + assert mainvars, rqlst columns, globtransforms = self.prepare_columns(mainvars, rqlst) eidfilters = [] allresults = [] diff -r d6ae24439bee -r c4c07aab1c39 server/sources/native.py --- a/server/sources/native.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/sources/native.py Fri Sep 18 12:20:39 2009 +0200 @@ -231,7 +231,7 @@ # ISource interface ####################################################### def compile_rql(self, rql): - rqlst = self.repo.querier._rqlhelper.parse(rql) + rqlst = self.repo.vreg.rqlhelper.parse(rql) rqlst.restricted_vars = () rqlst.children[0].solutions = self._sols self.repo.querier.sqlgen_annotate(rqlst) diff -r d6ae24439bee -r c4c07aab1c39 server/sources/rql2sql.py --- a/server/sources/rql2sql.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/sources/rql2sql.py Fri Sep 18 12:20:39 2009 +0200 @@ -336,6 +336,7 @@ self._varmap = varmap self._query_attrs = {} self._state = None + self._not_scope_offset = 0 try: # union query for each rqlst / solution sql = self.union_sql(union) @@ -553,7 +554,11 @@ def visit_not(self, node): self._state.push_scope() + if isinstance(node.children[0], Relation): + self._not_scope_offset += 1 csql = node.children[0].accept(self) + if isinstance(node.children[0], Relation): + self._not_scope_offset -= 1 sqls, tables = self._state.pop_scope() if node in self._state.done or not csql: # already processed or no sql generated by children @@ -651,10 +656,6 @@ sql = self._visit_outer_join_relation(relation, rschema) elif rschema.inlined: sql = self._visit_inlined_relation(relation) -# elif isinstance(relation.parent, Not): -# self._state.done.add(relation.parent) -# # NOT relation -# sql = self._visit_not_relation(relation, rschema) else: # regular (non final) relation sql = self._visit_relation(relation, rschema) @@ -1080,12 +1081,16 @@ # a EXISTS node if var.sqlscope is var.stmt: scope = 0 + # don't consider not_scope_offset if the variable is only used in one + # relation + elif len(var.stinfo['relations']) > 1: + scope = -1 - self._not_scope_offset else: scope = -1 try: sql = self._varmap[var.name] table = sql.split('.', 1)[0] - if scope == -1: + if scope < 0: scope = self._varmap_table_scope(var.stmt, table) self.add_table(table, scope=scope) except KeyError: @@ -1095,7 +1100,8 @@ raise BadRQLQuery(var.stmt.root) table = var.name sql = '%s.%seid' % (table, SQL_PREFIX) - self.add_table('%s%s AS %s' % (SQL_PREFIX, etype, table), table, scope=scope) + self.add_table('%s%s AS %s' % (SQL_PREFIX, etype, table), table, + scope=scope) return sql, table def _inlined_var_sql(self, var, rtype): @@ -1146,8 +1152,8 @@ key = table if key in self._state.tables: return - if scope == -1: - scope = len(self._state.actual_tables) - 1 + if scope < 0: + scope = len(self._state.actual_tables) + scope self._state.tables[key] = (scope, table) self._state.actual_tables[scope].append(table) diff -r d6ae24439bee -r c4c07aab1c39 server/test/unittest_extlite.py --- a/server/test/unittest_extlite.py Thu Sep 10 08:03:18 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -import threading, os, time - -from logilab.common.testlib import TestCase, unittest_main -from logilab.common.db import get_connection - -class SQLiteTC(TestCase): - sqlite_file = '_extlite_test.sqlite' - - def _cleanup(self): - try: - os.remove(self.sqlite_file) - except: - pass - - def setUp(self): - self._cleanup() - cnx1 = get_connection('sqlite', database=self.sqlite_file) - cu = cnx1.cursor() - cu.execute('CREATE TABLE toto(name integer);') - cnx1.commit() - cnx1.close() - - def tearDown(self): - self._cleanup() - - def test(self): - lock1 = threading.Lock() - lock2 = threading.Lock() - - def run_thread(): - cnx2 = get_connection('sqlite', database=self.sqlite_file) - lock1.acquire() - cu = cnx2.cursor() - cu.execute('SELECT name FROM toto') - self.failIf(cu.fetchall()) - cnx2.commit() - lock1.release() - lock2.acquire() - cu.execute('SELECT name FROM toto') - self.failUnless(cu.fetchall()) - lock2.release() - - cnx1 = get_connection('sqlite', database=self.sqlite_file) - lock1.acquire() - lock2.acquire() - thread = threading.Thread(target=run_thread) - thread.start() - cu = cnx1.cursor() - cu.execute('SELECT name FROM toto') - lock1.release() - cnx1.commit() - cu.execute("INSERT INTO toto(name) VALUES ('toto')") - cnx1.commit() - lock2.release() - -if __name__ == '__main__': - unittest_main() diff -r d6ae24439bee -r c4c07aab1c39 server/test/unittest_migractions.py --- a/server/test/unittest_migractions.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/test/unittest_migractions.py Fri Sep 18 12:20:39 2009 +0200 @@ -45,6 +45,7 @@ assert self.cnx is self.mh._cnx assert self.session is self.mh.session, (self.session.id, self.mh.session.id) + def test_add_attribute_int(self): self.failIf('whatever' in self.schema) orderdict = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, ' @@ -130,6 +131,7 @@ self.failUnless(self.execute('CWEType X WHERE X name "Folder2"')) self.failUnless('filed_under2' in self.schema) self.failUnless(self.execute('CWRType X WHERE X name "filed_under2"')) + self.schema.rebuild_infered_relations() self.assertEquals(sorted(str(rs) for rs in self.schema['Folder2'].subject_relations()), ['created_by', 'creation_date', 'cwuri', 'description', 'description_format', @@ -148,10 +150,11 @@ def test_add_drop_entity_type(self): self.mh.cmd_add_entity_type('Folder2') - todoeid = self.mh.cmd_add_state(u'todo', 'Folder2', initial=True) - doneeid = self.mh.cmd_add_state(u'done', 'Folder2') - self.mh.cmd_add_transition(u'redoit', 'Folder2', (doneeid,), todoeid) - self.mh.cmd_add_transition(u'markasdone', 'Folder2', (todoeid,), doneeid) + wf = self.mh.cmd_add_workflow(u'folder2 wf', 'Folder2') + todo = wf.add_state(u'todo', initial=True) + done = wf.add_state(u'done') + wf.add_transition(u'redoit', done, todo) + wf.add_transition(u'markasdone', todo, done) self.commit() eschema = self.schema.eschema('Folder2') self.mh.cmd_drop_entity_type('Folder2') @@ -165,6 +168,7 @@ def test_add_drop_relation_type(self): self.mh.cmd_add_entity_type('Folder2', auto=False) self.mh.cmd_add_relation_type('filed_under2') + self.schema.rebuild_infered_relations() self.failUnless('filed_under2' in self.schema) self.assertEquals(sorted(str(e) for e in self.schema['filed_under2'].subjects()), sorted(str(e) for e in self.schema.entities() if not e.is_final())) @@ -182,8 +186,8 @@ '1*') self.mh.cmd_add_relation_definition('Personne', 'concerne2', 'Note') self.assertEquals(sorted(self.schema['concerne2'].objects()), ['Affaire', 'Note']) - self.mh.add_entity('Personne', nom=u'tot') - self.mh.add_entity('Affaire') + self.mh.create_entity('Personne', nom=u'tot') + self.mh.create_entity('Affaire') self.mh.rqlexec('SET X concerne2 Y WHERE X is Personne, Y is Affaire') self.commit() self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Affaire') @@ -218,11 +222,17 @@ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), + ['Affaire', 'Division', 'Note', 'SubDivision']) + self.schema.rebuild_infered_relations() # need to be explicitly called once everything is in place + self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Note']) self.mh.cmd_add_relation_definition('Affaire', 'concerne', 'Societe') self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), + ['Affaire', 'Note', 'Societe']) + self.schema.rebuild_infered_relations() # need to be explicitly called once everything is in place + self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) # trick: overwrite self.maxeid to avoid deletion of just reintroduced types self.maxeid = self.execute('Any MAX(X)')[0][0] @@ -454,12 +464,6 @@ ex = self.assertRaises(ConfigurationError, self.mh.cmd_remove_cube, 'file') self.assertEquals(str(ex), "can't remove cube file, used as a dependency") - def test_set_state(self): - user = self.session.user - self.mh.set_state(user.eid, 'deactivated') - user.clear_related_cache('in_state', 'subject') - self.assertEquals(user.state, 'deactivated') - def test_introduce_base_class(self): self.mh.cmd_add_entity_type('Para') self.mh.repo.schema.rebuild_infered_relations() diff -r d6ae24439bee -r c4c07aab1c39 server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/test/unittest_msplanner.py Fri Sep 18 12:20:39 2009 +0200 @@ -348,7 +348,7 @@ def setUp(self): BaseMSPlannerTC.setUp(self) - self.planner = MSPlanner(self.o.schema, self.o._rqlhelper) + self.planner = MSPlanner(self.o.schema, self.repo.vreg.rqlhelper) _test = test_plan @@ -1961,13 +1961,23 @@ None, None, [self.system], {}, [])], {'x': 999998, 'u': 999999}) - def test_nonregr_identity_no_source_access(self): + def test_nonregr_identity_no_source_access_1(self): repo._type_source_cache[999999] = ('CWUser', 'ldap', 999998) self._test('Any S WHERE S identity U, S eid %(s)s, U eid %(u)s', [('OneFetchStep', [('Any 999999 WHERE 999999 identity 999999', [{}])], None, None, [self.system], {}, [])], {'s': 999999, 'u': 999999}) + def test_nonregr_identity_no_source_access_2(self): + repo._type_source_cache[999999] = ('EmailAddress', 'system', 999999) + repo._type_source_cache[999998] = ('CWUser', 'ldap', 999998) + self._test('Any X WHERE O use_email X, ((EXISTS(O identity U)) OR (EXISTS(O in_group G, G name IN("managers", "staff")))) OR (EXISTS(O in_group G2, U in_group G2, NOT G2 name "users")), X eid %(x)s, U eid %(u)s', + [('OneFetchStep', [('Any 999999 WHERE O use_email 999999, ((EXISTS(O identity 999998)) OR (EXISTS(O in_group G, G name IN("managers", "staff")))) OR (EXISTS(O in_group G2, 999998 in_group G2, NOT G2 name "users"))', + [{'G': 'CWGroup', 'G2': 'CWGroup', 'O': 'CWUser'}])], + None, None, [self.system], {}, [])], + {'x': 999999, 'u': 999998}) + + class MSPlannerTwoSameExternalSourcesTC(BasePlannerTC): """test planner related feature on a 3-sources repository: @@ -1979,7 +1989,7 @@ self.setup() self.add_source(FakeCardSource, 'cards') self.add_source(FakeCardSource, 'cards2') - self.planner = MSPlanner(self.o.schema, self.o._rqlhelper) + self.planner = MSPlanner(self.o.schema, self.repo.vreg.rqlhelper) assert repo.sources_by_uri['cards2'].support_relation('multisource_crossed_rel') assert 'multisource_crossed_rel' in repo.sources_by_uri['cards2'].cross_relations assert repo.sources_by_uri['cards'].support_relation('multisource_crossed_rel') @@ -2132,7 +2142,7 @@ def setUp(self): self.setup() self.add_source(FakeVCSSource, 'vcs') - self.planner = MSPlanner(self.o.schema, self.o._rqlhelper) + self.planner = MSPlanner(self.o.schema, self.repo.vreg.rqlhelper) _test = test_plan def test_multisource_inlined_rel_skipped(self): diff -r d6ae24439bee -r c4c07aab1c39 server/test/unittest_multisources.py --- a/server/test/unittest_multisources.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/test/unittest_multisources.py Fri Sep 18 12:20:39 2009 +0200 @@ -233,11 +233,12 @@ def test_not_relation(self): states = set(tuple(x) for x in self.execute('Any S,SN WHERE S is State, S name SN')) + self.session.user.clear_all_caches() userstate = self.session.user.in_state[0] states.remove((userstate.eid, userstate.name)) notstates = set(tuple(x) for x in self.execute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s', {'x': self.session.user.eid}, 'x')) - self.assertEquals(notstates, states) + self.assertSetEquals(notstates, states) aff1 = self.execute('Any X WHERE X is Affaire, X ref "AFFREF"')[0][0] aff1stateeid, aff1statename = self.execute('Any S,SN WHERE X eid %(x)s, X in_state S, S name SN', {'x': aff1}, 'x')[0] self.assertEquals(aff1statename, 'pitetre') diff -r d6ae24439bee -r c4c07aab1c39 server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/test/unittest_rql2sql.py Fri Sep 18 12:20:39 2009 +0200 @@ -163,7 +163,6 @@ ] ADVANCED= [ - ("Societe S WHERE S nom 'Logilab' OR S nom 'Caesium'", '''SELECT S.cw_eid FROM cw_Societe AS S @@ -642,6 +641,13 @@ WHERE X.cw_in_state=S.cw_eid ORDER BY 2) AS T1'''), + ('Any O,AA,AB,AC ORDERBY AC DESC ' + 'WHERE NOT S use_email O, S eid 1, O is EmailAddress, O address AA, O alias AB, O modification_date AC, ' + 'EXISTS(A use_email O, EXISTS(A identity B, NOT B in_group D, D name "guests", D is CWGroup), A is CWUser), B eid 2', + '''SELECT O.cw_eid, O.cw_address, O.cw_alias, O.cw_modification_date +FROM cw_EmailAddress AS O +WHERE NOT EXISTS(SELECT 1 FROM use_email_relation AS rel_use_email0 WHERE rel_use_email0.eid_from=1 AND rel_use_email0.eid_to=O.cw_eid) AND EXISTS(SELECT 1 FROM use_email_relation AS rel_use_email1 WHERE rel_use_email1.eid_to=O.cw_eid AND EXISTS(SELECT 1 FROM cw_CWGroup AS D WHERE rel_use_email1.eid_from=2 AND NOT EXISTS(SELECT 1 FROM in_group_relation AS rel_in_group2 WHERE rel_in_group2.eid_from=2 AND rel_in_group2.eid_to=D.cw_eid) AND D.cw_name=guests)) +ORDER BY 4 DESC'''), ] MULTIPLE_SEL = [ diff -r d6ae24439bee -r c4c07aab1c39 server/test/unittest_rqlannotation.py --- a/server/test/unittest_rqlannotation.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/test/unittest_rqlannotation.py Fri Sep 18 12:20:39 2009 +0200 @@ -95,6 +95,11 @@ self.assertEquals(rqlst.defined_vars['X']._q_invariant, False) self.assertEquals(rqlst.defined_vars['Y']._q_invariant, False) + def test_diff_scope_identity_deamb(self): + rqlst = self._prepare('Any X WHERE X concerne Y, Y is Note, EXISTS(Y identity Z, Z migrated_from N)') + self.assertEquals(rqlst.defined_vars['Z']._q_invariant, True) + self.assertEquals(rqlst.defined_vars['Y']._q_invariant, True) + def test_optional_inlined(self): rqlst = self._prepare('Any X,S where X from_state S?') self.assertEquals(rqlst.defined_vars['X']._q_invariant, False) diff -r d6ae24439bee -r c4c07aab1c39 server/test/unittest_rqlrewrite.py --- a/server/test/unittest_rqlrewrite.py Thu Sep 10 08:03:18 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,193 +0,0 @@ -""" - -:organization: Logilab -:copyright: 2001-2009 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 -""" -from logilab.common.testlib import unittest_main, TestCase -from logilab.common.testlib import mock_object - -from rql import parse, nodes, RQLHelper - -from cubicweb import Unauthorized -from cubicweb.server.rqlrewrite import RQLRewriter -from cubicweb.devtools import repotest, TestServerConfiguration - -config = TestServerConfiguration('data') -config.bootstrap_cubes() -schema = config.load_schema() -schema.add_relation_def(mock_object(subject='Card', name='in_state', object='State', cardinality='1*')) - -rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid', - 'has_text': 'fti'}) - -def setup_module(*args): - repotest.do_monkey_patch() - -def teardown_module(*args): - repotest.undo_monkey_patch() - -def eid_func_map(eid): - return {1: 'CWUser', - 2: 'Card'}[eid] - -def rewrite(rqlst, snippets_map, kwargs): - class FakeQuerier: - schema = schema - @staticmethod - def solutions(sqlcursor, mainrqlst, kwargs): - rqlhelper.compute_solutions(rqlst, {'eid': eid_func_map}, kwargs=kwargs) - class _rqlhelper: - @staticmethod - def annotate(rqlst): - rqlhelper.annotate(rqlst) - @staticmethod - def simplify(mainrqlst, needcopy=False): - rqlhelper.simplify(rqlst, needcopy) - rewriter = RQLRewriter(FakeQuerier, mock_object(user=(mock_object(eid=1)))) - for v, snippets in snippets_map.items(): - snippets_map[v] = [mock_object(snippet_rqlst=parse('Any X WHERE '+snippet).children[0], - expression='Any X WHERE '+snippet) - for snippet in snippets] - rqlhelper.compute_solutions(rqlst.children[0], {'eid': eid_func_map}, kwargs=kwargs) - solutions = rqlst.children[0].solutions - rewriter.rewrite(rqlst.children[0], snippets_map.items(), solutions, kwargs) - test_vrefs(rqlst.children[0]) - return rewriter.rewritten - -def test_vrefs(node): - vrefmap = {} - for vref in node.iget_nodes(nodes.VariableRef): - vrefmap.setdefault(vref.name, set()).add(vref) - for var in node.defined_vars.itervalues(): - assert not (var.stinfo['references'] ^ vrefmap[var.name]) - assert (var.stinfo['references']) - -class RQLRewriteTC(TestCase): - """a faire: - - * optimisation: detecter les relations utilisees dans les rqlexpressions qui - sont presentes dans la requete de depart pour les reutiliser si possible - - * "has__permission" ? - """ - - def test_base_var(self): - card_constraint = ('X in_state S, U in_group G, P require_state S,' - 'P name "read", P require_group G') - rqlst = parse('Card C') - rewrite(rqlst, {'C': (card_constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - u"Any C WHERE C is Card, B eid %(D)s, " - "EXISTS(C in_state A, B in_group E, F require_state A, " - "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission)") - - def test_multiple_var(self): - card_constraint = ('X in_state S, U in_group G, P require_state S,' - 'P name "read", P require_group G') - affaire_constraints = ('X ref LIKE "PUBLIC%"', 'U in_group G, G name "public"') - kwargs = {'u':2} - rqlst = parse('Any S WHERE S documented_by C, C eid %(u)s') - rewrite(rqlst, {'C': (card_constraint,), 'S': affaire_constraints}, - kwargs) - self.assertTextEquals(rqlst.as_string(), - "Any S WHERE S documented_by C, C eid %(u)s, B eid %(D)s, " - "EXISTS(C in_state A, B in_group E, F require_state A, " - "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission), " - "(EXISTS(S ref LIKE 'PUBLIC%')) OR (EXISTS(B in_group G, G name 'public', G is CWGroup)), " - "S is Affaire") - self.failUnless('D' in kwargs) - - def test_or(self): - constraint = '(X identity U) OR (X in_state ST, CL identity U, CL in_state ST, ST name "subscribed")' - rqlst = parse('Any S WHERE S owned_by C, C eid %(u)s, S is in (CWUser, CWGroup)') - rewrite(rqlst, {'C': (constraint,)}, {'u':1}) - self.failUnlessEqual(rqlst.as_string(), - "Any S WHERE S owned_by C, C eid %(u)s, S is IN(CWUser, CWGroup), A eid %(B)s, " - "EXISTS((C identity A) OR (C in_state D, E identity A, " - "E in_state D, D name 'subscribed'), D is State, E is CWUser)") - - def test_simplified_rqlst(self): - card_constraint = ('X in_state S, U in_group G, P require_state S,' - 'P name "read", P require_group G') - rqlst = parse('Any 2') # this is the simplified rql st for Any X WHERE X eid 12 - rewrite(rqlst, {'2': (card_constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - u"Any 2 WHERE B eid %(C)s, " - "EXISTS(2 in_state A, B in_group D, E require_state A, " - "E name 'read', E require_group D, A is State, D is CWGroup, E is CWPermission)") - - def test_optional_var(self): - card_constraint = ('X in_state S, U in_group G, P require_state S,' - 'P name "read", P require_group G') - rqlst = parse('Any A,C WHERE A documented_by C?') - rewrite(rqlst, {'C': (card_constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any A,C WHERE A documented_by C?, A is Affaire " - "WITH C BEING " - "(Any C WHERE C in_state B, D in_group F, G require_state B, G name 'read', " - "G require_group F, D eid %(A)s, C is Card)") - rqlst = parse('Any A,C,T WHERE A documented_by C?, C title T') - rewrite(rqlst, {'C': (card_constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - "Any A,C,T WHERE A documented_by C?, A is Affaire " - "WITH C,T BEING " - "(Any C,T WHERE C in_state B, D in_group F, G require_state B, G name 'read', " - "G require_group F, C title T, D eid %(A)s, C is Card)") - - def test_relation_optimization(self): - # since Card in_state State as monovalued cardinality, the in_state - # relation used in the rql expression can be ignored and S replaced by - # the variable from the incoming query - card_constraint = ('X in_state S, U in_group G, P require_state S,' - 'P name "read", P require_group G') - rqlst = parse('Card C WHERE C in_state STATE') - rewrite(rqlst, {'C': (card_constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - u"Any C WHERE C in_state STATE, C is Card, A eid %(B)s, " - "EXISTS(A in_group D, E require_state STATE, " - "E name 'read', E require_group D, D is CWGroup, E is CWPermission), " - "STATE is State") - - def test_unsupported_constraint_1(self): - # CWUser doesn't have require_permission - trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"') - rqlst = parse('Any U,T WHERE U is CWUser, T wf_info_for U') - self.assertRaises(Unauthorized, rewrite, rqlst, {'T': (trinfo_constraint,)}, {}) - - def test_unsupported_constraint_2(self): - trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"') - rqlst = parse('Any U,T WHERE U is CWUser, T wf_info_for U') - rewrite(rqlst, {'T': (trinfo_constraint, 'X wf_info_for Y, Y in_group G, G name "managers"')}, {}) - self.failUnlessEqual(rqlst.as_string(), - u"Any U,T WHERE U is CWUser, T wf_info_for U, " - "EXISTS(U in_group B, B name 'managers', B is CWGroup), T is TrInfo") - - def test_unsupported_constraint_3(self): - self.skip('raise unauthorized for now') - trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"') - rqlst = parse('Any T WHERE T wf_info_for X') - rewrite(rqlst, {'T': (trinfo_constraint, 'X in_group G, G name "managers"')}, {}) - self.failUnlessEqual(rqlst.as_string(), - u'XXX dunno what should be generated') - - def test_add_ambiguity_exists(self): - constraint = ('X concerne Y') - rqlst = parse('Affaire X') - rewrite(rqlst, {'X': (constraint,)}, {}) - self.failUnlessEqual(rqlst.as_string(), - u"Any X WHERE X is Affaire, (((EXISTS(X concerne A, A is Division)) OR (EXISTS(X concerne D, D is SubDivision))) OR (EXISTS(X concerne C, C is Societe))) OR (EXISTS(X concerne B, B is Note))") - - def test_add_ambiguity_outerjoin(self): - constraint = ('X concerne Y') - rqlst = parse('Any X,C WHERE X? documented_by C') - rewrite(rqlst, {'X': (constraint,)}, {}) - # ambiguity are kept in the sub-query, no need to be resolved using OR - self.failUnlessEqual(rqlst.as_string(), - u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE X concerne A, X is Affaire)") - - - -if __name__ == '__main__': - unittest_main() diff -r d6ae24439bee -r c4c07aab1c39 server/test/unittest_security.py --- a/server/test/unittest_security.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/test/unittest_security.py Fri Sep 18 12:20:39 2009 +0200 @@ -27,10 +27,10 @@ def test_check_read_access(self): rql = u'Personne U where U nom "managers"' - rqlst = self.repo.querier._rqlhelper.parse(rql).children[0] + rqlst = self.repo.vreg.rqlhelper.parse(rql).children[0] origgroups = self.schema['Personne'].get_groups('read') self.schema['Personne'].set_groups('read', ('users', 'managers')) - self.repo.querier._rqlhelper.compute_solutions(rqlst) + self.repo.vreg.rqlhelper.compute_solutions(rqlst) solution = rqlst.solutions[0] check_read_access(self.schema, self.session.user, rqlst, solution) cnx = self.login('anon') diff -r d6ae24439bee -r c4c07aab1c39 server/test/unittest_ssplanner.py --- a/server/test/unittest_ssplanner.py Thu Sep 10 08:03:18 2009 +0200 +++ b/server/test/unittest_ssplanner.py Fri Sep 18 12:20:39 2009 +0200 @@ -18,7 +18,7 @@ def setUp(self): BasePlannerTC.setUp(self) - self.planner = SSPlanner(self.o.schema, self.o._rqlhelper) + self.planner = SSPlanner(self.o.schema, self.repo.vreg.rqlhelper) self.system = self.o._repo.system_source def tearDown(self): diff -r d6ae24439bee -r c4c07aab1c39 skeleton/migration/postcreate.py --- a/skeleton/migration/postcreate.py Thu Sep 10 08:03:18 2009 +0200 +++ b/skeleton/migration/postcreate.py Fri Sep 18 12:20:39 2009 +0200 @@ -1,4 +1,4 @@ -# postcreate script. You could setup a workflow here for example +# postcreate script. You could setup site properties or a workflow here for example """ :organization: Logilab @@ -7,3 +7,6 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ +# Example of site property change +#set_property('ui.site-title', "") + diff -r d6ae24439bee -r c4c07aab1c39 sobjects/email.py --- a/sobjects/email.py Thu Sep 10 08:03:18 2009 +0200 +++ b/sobjects/email.py Fri Sep 18 12:20:39 2009 +0200 @@ -9,6 +9,7 @@ from cubicweb.server.hooksmanager import Hook from cubicweb.server.pool import PreCommitOperation +from cubicweb.server.repository import ensure_card_respected class SetUseEmailRelationOp(PreCommitOperation): """delay this operation to commit to avoid conflict with a late rql query @@ -26,6 +27,11 @@ def precommit_event(self): session = self.session 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(session.execute, session, + self.fromeid, self.rtype, self.toeid) session.unsafe_execute( 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype, {'x': self.fromeid, 'y': self.toeid}, 'x') diff -r d6ae24439bee -r c4c07aab1c39 sobjects/test/unittest_email.py --- a/sobjects/test/unittest_email.py Thu Sep 10 08:03:18 2009 +0200 +++ b/sobjects/test/unittest_email.py Fri Sep 18 12:20:39 2009 +0200 @@ -5,6 +5,7 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ +from cubicweb import Unauthorized from cubicweb.devtools.apptest import EnvBasedTC class EmailAddressHooksTC(EnvBasedTC): @@ -30,6 +31,24 @@ self.assertEquals(self.execute('Any A WHERE U use_email X, U login "admin", X address A')[0][0], 'admin@logilab.fr') + def test_cardinality_check(self): + email1 = self.execute('INSERT EmailAddress E: E address "client@client.com", U use_email E WHERE U login "admin"')[0][0] + self.commit() + self.execute('SET U primary_email E WHERE U login "anon", E address "client@client.com"') + self.commit() + rset = self.execute('Any X WHERE X use_email E, E eid %(e)s', {'e': email1}) + self.failIf(rset.rowcount != 1, rset) + + def test_security_check(self): + self.create_user('toto') + email1 = self.execute('INSERT EmailAddress E: E address "client@client.com", U use_email E WHERE U login "admin"')[0][0] + 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) + if __name__ == '__main__': from logilab.common.testlib import unittest_main diff -r d6ae24439bee -r c4c07aab1c39 test/data/rewrite/__init__.py diff -r d6ae24439bee -r c4c07aab1c39 test/data/rewrite/bootstrap_cubes --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/data/rewrite/bootstrap_cubes Fri Sep 18 12:20:39 2009 +0200 @@ -0,0 +1,1 @@ +card, person diff -r d6ae24439bee -r c4c07aab1c39 test/data/rewrite/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/data/rewrite/schema.py Fri Sep 18 12:20:39 2009 +0200 @@ -0,0 +1,42 @@ +from yams.buildobjs import EntityType, RelationDefinition, String, SubjectRelation +from cubicweb.schema import ERQLExpression + +class Affaire(EntityType): + permissions = { + 'read': ('managers', + ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')), + 'add': ('managers', ERQLExpression('X concerne S, S owned_by U')), + 'update': ('managers', 'owners', ERQLExpression('X in_state S, S name in ("pitetre", "en cours")')), + 'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')), + } + ref = String(fulltextindexed=True, indexed=True, + constraints=[SizeConstraint(16)]) + documented_by = SubjectRelation('Card') + concerne = SubjectRelation(('Societe', 'Note')) + + +class Societe(EntityType): + permissions = { + 'read': ('managers', 'users', 'guests'), + 'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')), + 'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')), + 'add': ('managers', 'users',) + } + + +class Division(Societe): + __specializes_schema__ = True + + +class Note(EntityType): + pass + + +class require_permission(RelationDefinition): + subject = ('Card', 'Note', 'Person') + object = 'CWPermission' + + +class require_state(RelationDefinition): + subject = 'CWPermission' + object = 'State' diff -r d6ae24439bee -r c4c07aab1c39 test/unittest_entity.py --- a/test/unittest_entity.py Thu Sep 10 08:03:18 2009 +0200 +++ b/test/unittest_entity.py Fri Sep 18 12:20:39 2009 +0200 @@ -9,9 +9,10 @@ from datetime import datetime -from cubicweb import Binary +from cubicweb import Binary, Unauthorized from cubicweb.devtools.apptest import EnvBasedTC from cubicweb.common.mttransforms import HAS_TAL +from cubicweb.entities import fetch_config class EntityTC(EnvBasedTC): @@ -179,7 +180,6 @@ Societe.fetch_attrs = sfetch_attrs def test_related_rql(self): - from cubicweb.entities import fetch_config Personne = self.vreg['etypes'].etype_class('Personne') Note = self.vreg['etypes'].etype_class('Note') self.failUnless(issubclass(self.vreg['etypes'].etype_class('SubNote'), Note)) @@ -194,7 +194,40 @@ 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') - def test_entity_unrelated(self): + def test_unrelated_rql_security_1(self): + user = self.request().user + rql = user.unrelated_rql('use_email', 'EmailAddress', 'subject')[0] + self.assertEquals(rql, 'Any O,AA,AB,AC ORDERBY AC DESC ' + 'WHERE NOT S use_email O, S eid %(x)s, O is EmailAddress, O address AA, O alias AB, O modification_date AC') + self.create_user('toto') + self.login('toto') + user = self.request().user + rql = user.unrelated_rql('use_email', 'EmailAddress', 'subject')[0] + self.assertEquals(rql, 'Any O,AA,AB,AC ORDERBY AC DESC ' + 'WHERE NOT S use_email O, S eid %(x)s, O is EmailAddress, O address AA, O alias AB, O modification_date AC') + user = self.execute('Any X WHERE X login "admin"').get_entity(0, 0) + self.assertRaises(Unauthorized, user.unrelated_rql, 'use_email', 'EmailAddress', 'subject') + self.login('anon') + user = self.request().user + self.assertRaises(Unauthorized, user.unrelated_rql, 'use_email', 'EmailAddress', 'subject') + + def test_unrelated_rql_security_2(self): + email = self.execute('INSERT EmailAddress X: X address "hop"').get_entity(0, 0) + rql = email.unrelated_rql('use_email', 'CWUser', 'object')[0] + self.assertEquals(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ASC ' + 'WHERE NOT S use_email O, O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD') + #rql = email.unrelated_rql('use_email', 'Person', 'object')[0] + #self.assertEquals(rql, '') + self.login('anon') + email = self.execute('Any X WHERE X eid %(x)s', {'x': email.eid}, 'x').get_entity(0, 0) + rql = email.unrelated_rql('use_email', 'CWUser', 'object')[0] + self.assertEquals(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ' + 'WHERE NOT S use_email O, O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, ' + 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)') + #rql = email.unrelated_rql('use_email', 'Person', 'object')[0] + #self.assertEquals(rql, '') + + def test_unrelated_base(self): p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien') e = self.add_entity('Tag', name=u'x') related = [r.eid for r in e.tags] @@ -206,14 +239,40 @@ unrelated = [r[0] for r in e.unrelated('tags', 'Personne', 'subject')] self.failIf(p.eid in unrelated) - def test_entity_unrelated_limit(self): + def test_unrelated_limit(self): e = self.add_entity('Tag', name=u'x') self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien') - self.add_entity('Personne', nom=u'di mascio', prenom=u'gwen') + self.add_entity('Personne', nom=u'thenault', prenom=u'sylvain') self.assertEquals(len(e.unrelated('tags', 'Personne', 'subject', limit=1)), 1) - def test_new_entity_unrelated(self): + def test_unrelated_security(self): + email = self.execute('INSERT EmailAddress X: X address "hop"').get_entity(0, 0) + rset = email.unrelated('use_email', 'CWUser', 'object') + self.assertEquals([x.login for x in rset.entities()], [u'admin', u'anon']) + user = self.request().user + rset = user.unrelated('use_email', 'EmailAddress', 'subject') + self.assertEquals([x.address for x in rset.entities()], [u'hop']) + self.create_user('toto') + self.login('toto') + email = self.execute('Any X WHERE X eid %(x)s', {'x': email.eid}, 'x').get_entity(0, 0) + rset = email.unrelated('use_email', 'CWUser', 'object') + self.assertEquals([x.login for x in rset.entities()], ['toto']) + user = self.request().user + rset = user.unrelated('use_email', 'EmailAddress', 'subject') + self.assertEquals([x.address for x in rset.entities()], ['hop']) + user = self.execute('Any X WHERE X login "admin"').get_entity(0, 0) + rset = user.unrelated('use_email', 'EmailAddress', 'subject') + self.assertEquals([x.address for x in rset.entities()], []) + self.login('anon') + email = self.execute('Any X WHERE X eid %(x)s', {'x': email.eid}, 'x').get_entity(0, 0) + rset = email.unrelated('use_email', 'CWUser', 'object') + self.assertEquals([x.login for x in rset.entities()], []) + user = self.request().user + rset = user.unrelated('use_email', 'EmailAddress', 'subject') + self.assertEquals([x.address for x in rset.entities()], []) + + def test_unrelated_new_entity(self): e = self.etype_instance('CWUser') unrelated = [r[0] for r in e.unrelated('in_group', 'CWGroup', 'subject')] # should be default groups but owners, i.e. managers, users, guests diff -r d6ae24439bee -r c4c07aab1c39 test/unittest_rqlrewrite.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unittest_rqlrewrite.py Fri Sep 18 12:20:39 2009 +0200 @@ -0,0 +1,193 @@ +""" + +:organization: Logilab +:copyright: 2001-2009 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 +""" +from logilab.common.testlib import unittest_main, TestCase +from logilab.common.testlib import mock_object + +from rql import parse, nodes, RQLHelper + +from cubicweb import Unauthorized +from cubicweb.rqlrewrite import RQLRewriter +from cubicweb.devtools import repotest, TestServerConfiguration + +config = TestServerConfiguration('data/rewrite') +config.bootstrap_cubes() +schema = config.load_schema() +schema.add_relation_def(mock_object(subject='Card', name='in_state', object='State', cardinality='1*')) + +rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid', + 'has_text': 'fti'}) + +def setup_module(*args): + repotest.do_monkey_patch() + +def teardown_module(*args): + repotest.undo_monkey_patch() + +def eid_func_map(eid): + return {1: 'CWUser', + 2: 'Card'}[eid] + +def rewrite(rqlst, snippets_map, kwargs): + class FakeVReg: + schema = schema + @staticmethod + def solutions(sqlcursor, mainrqlst, kwargs): + rqlhelper.compute_solutions(rqlst, {'eid': eid_func_map}, kwargs=kwargs) + class rqlhelper: + @staticmethod + def annotate(rqlst): + rqlhelper.annotate(rqlst) + @staticmethod + def simplify(mainrqlst, needcopy=False): + rqlhelper.simplify(rqlst, needcopy) + rewriter = RQLRewriter(mock_object(vreg=FakeVReg, user=(mock_object(eid=1)))) + for v, snippets in snippets_map.items(): + snippets_map[v] = [mock_object(snippet_rqlst=parse('Any X WHERE '+snippet).children[0], + expression='Any X WHERE '+snippet) + for snippet in snippets] + rqlhelper.compute_solutions(rqlst.children[0], {'eid': eid_func_map}, kwargs=kwargs) + solutions = rqlst.children[0].solutions + rewriter.rewrite(rqlst.children[0], snippets_map.items(), solutions, kwargs) + test_vrefs(rqlst.children[0]) + return rewriter.rewritten + +def test_vrefs(node): + vrefmap = {} + for vref in node.iget_nodes(nodes.VariableRef): + vrefmap.setdefault(vref.name, set()).add(vref) + for var in node.defined_vars.itervalues(): + assert not (var.stinfo['references'] ^ vrefmap[var.name]) + assert (var.stinfo['references']) + +class RQLRewriteTC(TestCase): + """a faire: + + * optimisation: detecter les relations utilisees dans les rqlexpressions qui + sont presentes dans la requete de depart pour les reutiliser si possible + + * "has__permission" ? + """ + + def test_base_var(self): + card_constraint = ('X in_state S, U in_group G, P require_state S,' + 'P name "read", P require_group G') + rqlst = parse('Card C') + rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + u"Any C WHERE C is Card, B eid %(D)s, " + "EXISTS(C in_state A, B in_group E, F require_state A, " + "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission)") + + def test_multiple_var(self): + card_constraint = ('X in_state S, U in_group G, P require_state S,' + 'P name "read", P require_group G') + affaire_constraints = ('X ref LIKE "PUBLIC%"', 'U in_group G, G name "public"') + kwargs = {'u':2} + rqlst = parse('Any S WHERE S documented_by C, C eid %(u)s') + rewrite(rqlst, {('C', 'X'): (card_constraint,), ('S', 'X'): affaire_constraints}, + kwargs) + self.assertTextEquals(rqlst.as_string(), + "Any S WHERE S documented_by C, C eid %(u)s, B eid %(D)s, " + "EXISTS(C in_state A, B in_group E, F require_state A, " + "F name 'read', F require_group E, A is State, E is CWGroup, F is CWPermission), " + "(EXISTS(S ref LIKE 'PUBLIC%')) OR (EXISTS(B in_group G, G name 'public', G is CWGroup)), " + "S is Affaire") + self.failUnless('D' in kwargs) + + def test_or(self): + constraint = '(X identity U) OR (X in_state ST, CL identity U, CL in_state ST, ST name "subscribed")' + rqlst = parse('Any S WHERE S owned_by C, C eid %(u)s, S is in (CWUser, CWGroup)') + rewrite(rqlst, {('C', 'X'): (constraint,)}, {'u':1}) + self.failUnlessEqual(rqlst.as_string(), + "Any S WHERE S owned_by C, C eid %(u)s, S is IN(CWUser, CWGroup), A eid %(B)s, " + "EXISTS((C identity A) OR (C in_state D, E identity A, " + "E in_state D, D name 'subscribed'), D is State, E is CWUser)") + + def test_simplified_rqlst(self): + card_constraint = ('X in_state S, U in_group G, P require_state S,' + 'P name "read", P require_group G') + rqlst = parse('Any 2') # this is the simplified rql st for Any X WHERE X eid 12 + rewrite(rqlst, {('2', 'X'): (card_constraint,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + u"Any 2 WHERE B eid %(C)s, " + "EXISTS(2 in_state A, B in_group D, E require_state A, " + "E name 'read', E require_group D, A is State, D is CWGroup, E is CWPermission)") + + def test_optional_var(self): + card_constraint = ('X in_state S, U in_group G, P require_state S,' + 'P name "read", P require_group G') + rqlst = parse('Any A,C WHERE A documented_by C?') + rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any A,C WHERE A documented_by C?, A is Affaire " + "WITH C BEING " + "(Any C WHERE C in_state B, D in_group F, G require_state B, G name 'read', " + "G require_group F, D eid %(A)s, C is Card)") + rqlst = parse('Any A,C,T WHERE A documented_by C?, C title T') + rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any A,C,T WHERE A documented_by C?, A is Affaire " + "WITH C,T BEING " + "(Any C,T WHERE C in_state B, D in_group F, G require_state B, G name 'read', " + "G require_group F, C title T, D eid %(A)s, C is Card)") + + def test_relation_optimization(self): + # since Card in_state State as monovalued cardinality, the in_state + # relation used in the rql expression can be ignored and S replaced by + # the variable from the incoming query + card_constraint = ('X in_state S, U in_group G, P require_state S,' + 'P name "read", P require_group G') + rqlst = parse('Card C WHERE C in_state STATE') + rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + u"Any C WHERE C in_state STATE, C is Card, A eid %(B)s, " + "EXISTS(A in_group D, E require_state STATE, " + "E name 'read', E require_group D, D is CWGroup, E is CWPermission), " + "STATE is State") + + def test_unsupported_constraint_1(self): + # CWUser doesn't have require_permission + trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"') + rqlst = parse('Any U,T WHERE U is CWUser, T wf_info_for U') + self.assertRaises(Unauthorized, rewrite, rqlst, {('T', 'X'): (trinfo_constraint,)}, {}) + + def test_unsupported_constraint_2(self): + trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"') + rqlst = parse('Any U,T WHERE U is CWUser, T wf_info_for U') + rewrite(rqlst, {('T', 'X'): (trinfo_constraint, 'X wf_info_for Y, Y in_group G, G name "managers"')}, {}) + self.failUnlessEqual(rqlst.as_string(), + u"Any U,T WHERE U is CWUser, T wf_info_for U, " + "EXISTS(U in_group B, B name 'managers', B is CWGroup), T is TrInfo") + + def test_unsupported_constraint_3(self): + self.skip('raise unauthorized for now') + trinfo_constraint = ('X wf_info_for Y, Y require_permission P, P name "read"') + rqlst = parse('Any T WHERE T wf_info_for X') + rewrite(rqlst, {('T', 'X'): (trinfo_constraint, 'X in_group G, G name "managers"')}, {}) + self.failUnlessEqual(rqlst.as_string(), + u'XXX dunno what should be generated') + + def test_add_ambiguity_exists(self): + constraint = ('X concerne Y') + rqlst = parse('Affaire X') + rewrite(rqlst, {('X', 'X'): (constraint,)}, {}) + self.failUnlessEqual(rqlst.as_string(), + u"Any X WHERE X is Affaire, ((EXISTS(X concerne A, A is Division)) OR (EXISTS(X concerne C, C is Societe))) OR (EXISTS(X concerne B, B is Note))") + + def test_add_ambiguity_outerjoin(self): + constraint = ('X concerne Y') + rqlst = parse('Any X,C WHERE X? documented_by C') + rewrite(rqlst, {('X', 'X'): (constraint,)}, {}) + # ambiguity are kept in the sub-query, no need to be resolved using OR + self.failUnlessEqual(rqlst.as_string(), + u"Any X,C WHERE X? documented_by C, C is Card WITH X BEING (Any X WHERE X concerne A, X is Affaire)") + + + +if __name__ == '__main__': + unittest_main() diff -r d6ae24439bee -r c4c07aab1c39 test/unittest_schema.py --- a/test/unittest_schema.py Thu Sep 10 08:03:18 2009 +0200 +++ b/test/unittest_schema.py Fri Sep 18 12:20:39 2009 +0200 @@ -162,7 +162,7 @@ expected_relations = ['add_permission', 'address', 'alias', 'allowed_transition', 'bookmarked_by', 'by_transition', - 'canonical', 'cardinality', 'comment', 'comment_format', + 'cardinality', 'comment', 'comment_format', 'composite', 'condition', 'connait', 'constrained_by', 'content', 'content_format', 'created_by', 'creation_date', 'cstrtype', 'custom_workflow', 'cwuri', @@ -175,7 +175,7 @@ 'from_entity', 'from_state', 'fulltext_container', 'fulltextindexed', 'has_text', - 'identical_to', 'identity', 'in_group', 'in_state', 'indexed', + 'identity', 'in_group', 'in_state', 'indexed', 'initial_state', 'inlined', 'internationalizable', 'is', 'is_instance_of', 'label', 'last_login_time', 'login', @@ -186,7 +186,7 @@ 'ordernum', 'owned_by', - 'path', 'pkey', 'prenom', 'primary_email', + 'path', 'pkey', 'prefered_form', 'prenom', 'primary_email', 'read_permission', 'relation_type', 'require_group', diff -r d6ae24439bee -r c4c07aab1c39 test/unittest_utils.py --- a/test/unittest_utils.py Thu Sep 10 08:03:18 2009 +0200 +++ b/test/unittest_utils.py Fri Sep 18 12:20:39 2009 +0200 @@ -8,7 +8,11 @@ from logilab.common.testlib import TestCase, unittest_main -from cubicweb.utils import make_uid, UStringIO, SizeConstrainedList +import simplejson +import decimal +import datetime + +from cubicweb.utils import make_uid, UStringIO, SizeConstrainedList, CubicWebJsonEncoder class MakeUidTC(TestCase): @@ -48,6 +52,24 @@ l.extend(extension) yield self.assertEquals, l, expected +class JSONEncoerTests(TestCase): + + def encode(self, value): + return simplejson.dumps(value, cls=CubicWebJsonEncoder) + + def test_encoding_dates(self): + self.assertEquals(self.encode(datetime.datetime(2009, 9, 9, 20, 30)), + '"2009/09/09 20:30:00"') + self.assertEquals(self.encode(datetime.date(2009, 9, 9)), + '"2009/09/09"') + self.assertEquals(self.encode(datetime.time(20, 30)), + '"20:30:00"') + + def test_encoding_decimal(self): + self.assertEquals(self.encode(decimal.Decimal('1.2')), '1.2') + + def test_encoding_unknown_stuff(self): + self.assertEquals(self.encode(TestCase), 'null') if __name__ == '__main__': unittest_main() diff -r d6ae24439bee -r c4c07aab1c39 utils.py --- a/utils.py Thu Sep 10 08:03:18 2009 +0200 +++ b/utils.py Fri Sep 18 12:20:39 2009 +0200 @@ -10,13 +10,17 @@ from logilab.mtconverter import xml_escape import locale +import sys +import decimal +import datetime as pydatetime from md5 import md5 -import sys from datetime import datetime, timedelta, date from time import time, mktime from random import randint, seed from calendar import monthrange +import simplejson + # initialize random seed from current time seed() try: @@ -355,3 +359,22 @@ return False __answer[0] = True return True + + +class CubicWebJsonEncoder(simplejson.JSONEncoder): + """define a simplejson encoder to be able to encode yams std types""" + def default(self, obj): + if isinstance(obj, pydatetime.datetime): + return obj.strftime('%Y/%m/%d %H:%M:%S') + elif isinstance(obj, pydatetime.date): + return obj.strftime('%Y/%m/%d') + elif isinstance(obj, pydatetime.time): + return obj.strftime('%H:%M:%S') + elif isinstance(obj, decimal.Decimal): + return float(obj) + try: + return simplejson.JSONEncoder.default(self, obj) + except TypeError: + # we never ever want to fail because of an unknown type, + # just return None in those cases. + return None diff -r d6ae24439bee -r c4c07aab1c39 web/action.py --- a/web/action.py Thu Sep 10 08:03:18 2009 +0200 +++ b/web/action.py Fri Sep 18 12:20:39 2009 +0200 @@ -32,8 +32,18 @@ 'useractions', 'siteactions', 'hidden'), help=_('context where this component should be displayed')), } - site_wide = True # don't want user to configuration actions eproperties + site_wide = True # don't want user to configurate actions category = 'moreactions' + # actions in category 'moreactions' can specify a sub-menu in which they should be filed + submenu = None + + def actual_actions(self): + yield self + + def fill_menu(self, box, menu): + """add action(s) to the given submenu of the given box""" + for action in self.actual_actions(): + menu.append(box.box_action(action)) def url(self): """return the url associated with this action""" @@ -45,6 +55,9 @@ if self.category: return 'box' + self.category.capitalize() + def build_action(self, title, path, **kwargs): + return UnregisteredAction(self.req, self.rset, title, path, **kwargs) + class UnregisteredAction(Action): """non registered action used to build boxes. Unless you set them @@ -75,13 +88,12 @@ & partial_may_add_relation()) registered = accepts_compat(Action.registered) - category = 'addrelated' + submenu = 'addrelated' def url(self): current_entity = self.rset.get_entity(self.row or 0, self.col or 0) linkto = '%s:%s:%s' % (self.rtype, current_entity.eid, target(self)) - return self.build_url(vid='creation', etype=self.etype, - __linkto=linkto, + return self.build_url('add/%s' % self.etype, __linkto=linkto, __redirectpath=current_entity.rest_path(), # should not be url quoted! __redirectvid=self.req.form.get('__redirectvid', '')) diff -r d6ae24439bee -r c4c07aab1c39 web/box.py --- a/web/box.py Thu Sep 10 08:03:18 2009 +0200 +++ b/web/box.py Fri Sep 18 12:20:39 2009 +0200 @@ -60,7 +60,8 @@ result = [] actions_by_cat = {} for action in actions: - actions_by_cat.setdefault(action.category, []).append((action.title, action)) + actions_by_cat.setdefault(action.category, []).append( + (action.title, action) ) for key, values in actions_by_cat.items(): actions_by_cat[key] = [act for title, act in sorted(values)] for cat in self.categories_in_order: @@ -150,7 +151,7 @@ __select__ = EntityBoxTemplate.__select__ & partial_has_related_entities() def cell_call(self, row, col, **kwargs): - entity = self.entity(row, col) + entity = self.rset.get_entity(row, col) limit = self.req.property_value('navigation.related-limit') + 1 role = get_role(self) self.w(u'