--- a/__init__.py Thu Jan 24 16:13:40 2013 +0100
+++ b/__init__.py Fri Jan 25 14:33:40 2013 +0100
@@ -199,3 +199,26 @@
CW_EVENT_MANAGER.bind(event, func, *args, **kwargs)
return func
return _decorator
+
+
+from yams.schema import role_name as rname
+
+def validation_error(entity, errors, substitutions=None, i18nvalues=None):
+ """easy way to retrieve a :class:`cubicweb.ValidationError` for an entity or eid.
+
+ You may also have 2-tuple as error keys, :func:`yams.role_name` will be
+ called automatically for them.
+
+ Messages in errors **should not be translated yet**, though marked for
+ internationalization. You may give an additional substition dictionary that
+ will be used for interpolation after the translation.
+ """
+ if substitutions is None:
+ # set empty dict else translation won't be done for backward
+ # compatibility reason (see ValidationError.translate method)
+ substitutions = {}
+ for key in errors.keys():
+ if isinstance(key, tuple):
+ errors[rname(*key)] = errors.pop(key)
+ return ValidationError(getattr(entity, 'eid', entity), errors,
+ substitutions, i18nvalues)
--- a/__pkginfo__.py Thu Jan 24 16:13:40 2013 +0100
+++ b/__pkginfo__.py Fri Jan 25 14:33:40 2013 +0100
@@ -22,7 +22,7 @@
modname = distname = "cubicweb"
-numversion = (3, 15, 8)
+numversion = (3, 16, 0)
version = '.'.join(str(num) for num in numversion)
description = "a repository of entities / relations for knowledge management"
@@ -40,10 +40,10 @@
]
__depends__ = {
- 'logilab-common': '>= 0.58.0',
+ 'logilab-common': '>= 0.59.0',
'logilab-mtconverter': '>= 0.8.0',
'rql': '>= 0.31.2',
- 'yams': '>= 0.34.0',
+ 'yams': '>= 0.36.0',
#gettext # for xgettext, msgcat, etc...
# web dependancies
'simplejson': '>= 2.0.9',
--- a/_exceptions.py Thu Jan 24 16:13:40 2013 +0100
+++ b/_exceptions.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -19,7 +19,7 @@
__docformat__ = "restructuredtext en"
-from yams import ValidationError
+from yams import ValidationError as ValidationError
# abstract exceptions #########################################################
--- a/appobject.py Thu Jan 24 16:13:40 2013 +0100
+++ b/appobject.py Fri Jan 25 14:33:40 2013 +0100
@@ -31,25 +31,25 @@
"""
__docformat__ = "restructuredtext en"
-import types
from logging import getLogger
-from warnings import warn
from logilab.common.deprecation import deprecated, class_renamed
from logilab.common.decorators import classproperty
from logilab.common.logging_ext import set_log_methods
-from logilab.common.registry import yes
-from cubicweb.cwconfig import CubicWebConfiguration
-# XXX for bw compat
-from logilab.common.registry import objectify_predicate, traced_selection, Predicate
+# first line imports for bw compat
+from logilab.common.registry import (objectify_predicate, traced_selection, Predicate,
+ RegistrableObject, yes)
-objectify_selector = deprecated('[3.15] objectify_selector has been renamed to objectify_predicates in logilab.common.registry')(objectify_predicate)
-traced_selection = deprecated('[3.15] traced_selection has been moved to logilab.common.registry')(traced_selection)
-Selector = class_renamed(
- 'Selector', Predicate,
- '[3.15] Selector has been renamed to Predicate in logilab.common.registry')
+objectify_selector = deprecated('[3.15] objectify_selector has been '
+ 'renamed to objectify_predicates in '
+ 'logilab.common.registry')(objectify_predicate)
+traced_selection = deprecated('[3.15] traced_selection has been '
+ 'moved to logilab.common.registry')(traced_selection)
+Selector = class_renamed('Selector', Predicate,
+ '[3.15] Selector has been renamed to Predicate '
+ 'in logilab.common.registry')
@deprecated('[3.15] lltrace decorator can now be removed')
def lltrace(func):
@@ -57,27 +57,12 @@
# the base class for all appobjects ############################################
-class AppObject(object):
+class AppObject(RegistrableObject):
"""This is the base class for CubicWeb application objects which are
- selected according to a context (usually at least a request and a result
- set).
+ selected in a request context.
The following attributes should be set on concrete appobject classes:
- :attr:`__registry__`
- name of the registry for this object (string like 'views',
- 'templates'...)
-
- :attr:`__regid__`
- object's identifier in the registry (string like 'main',
- 'primary', 'folder_box')
-
- :attr:`__select__`
- class'selector
-
- Moreover, the `__abstract__` attribute may be set to True to indicate that a
- class is abstract and should not be registered.
-
At selection time, the following attributes are set on the instance:
:attr:`_cw`
@@ -107,43 +92,9 @@
* do not inherit directly from this class but from a more specific class
such as `AnyEntity`, `EntityView`, `AnyRsetView`, `Action`...
- * to be recordable, a subclass has to define its registry (attribute
- `__registry__`) and its identifier (attribute `__regid__`). Usually
- you don't have to take care of the registry since it's set by the base
- class, only the identifier `id`
-
- * application objects are designed to be loaded by the vregistry and
- should be accessed through it, not by direct instantiation, besides
- to use it as base classe.
-
-
- * 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 must write::
-
- class Truc(PrimaryView):
- def f(self, arg1):
- super(Truc, self).f(arg1)
-
"""
- __registry__ = None
- __regid__ = None
__select__ = yes()
- @classproperty
- def __registries__(cls):
- if cls.__registry__ is None:
- return ()
- return (cls.__registry__,)
-
@classmethod
def __registered__(cls, registry):
"""called by the registry when the appobject has been registered.
--- a/cwconfig.py Thu Jan 24 16:13:40 2013 +0100
+++ b/cwconfig.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -353,28 +353,6 @@
'help': 'permission umask for files created by the server',
'group': 'main', 'level': 2,
}),
- # pyro options
- ('pyro-instance-id',
- {'type' : 'string',
- 'default': Method('default_instance_id'),
- 'help': 'identifier of the CubicWeb instance in the Pyro name server',
- 'group': 'pyro', 'level': 1,
- }),
- ('pyro-ns-host',
- {'type' : 'string',
- 'default': '',
- 'help': 'Pyro name server\'s host. If not set, will be detected by a \
-broadcast query. It may contains port information using <host>:<port> notation. \
-Use "NO_PYRONS" to create a Pyro server but not register to a pyro nameserver',
- 'group': 'pyro', 'level': 1,
- }),
- ('pyro-ns-group',
- {'type' : 'string',
- 'default': 'cubicweb',
- 'help': 'Pyro name server\'s group where the repository will be \
-registered.',
- 'group': 'pyro', 'level': 1,
- }),
# common configuration options which are potentially required as soon as
# you're using "base" application objects (ie to really server/web
# specific)
@@ -671,54 +649,6 @@
cubicweb_appobject_path = set(['entities'])
cube_appobject_path = set(['entities'])
- @classmethod
- def build_vregistry_path(cls, templpath, evobjpath=None, tvobjpath=None):
- """given a list of directories, return a list of sub files and
- directories that should be loaded by the instance objects registry.
-
- :param evobjpath:
- optional list of sub-directories (or files without the .py ext) of
- the cubicweb library that should be tested and added to the output list
- if they exists. If not give, default to `cubicweb_appobject_path` class
- attribute.
- :param tvobjpath:
- optional list of sub-directories (or files without the .py ext) of
- directories given in `templpath` that should be tested and added to
- the output list if they exists. If not give, default to
- `cube_appobject_path` class attribute.
- """
- vregpath = cls.build_vregistry_cubicweb_path(evobjpath)
- vregpath += cls.build_vregistry_cube_path(templpath, tvobjpath)
- return vregpath
-
- @classmethod
- def build_vregistry_cubicweb_path(cls, evobjpath=None):
- vregpath = []
- if evobjpath is None:
- evobjpath = cls.cubicweb_appobject_path
- # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
- # it is clearly a workaround
- for subdir in sorted(evobjpath, key=lambda x:x != 'entities'):
- path = join(CW_SOFTWARE_ROOT, subdir)
- if exists(path):
- vregpath.append(path)
- return vregpath
-
- @classmethod
- def build_vregistry_cube_path(cls, templpath, tvobjpath=None):
- vregpath = []
- if tvobjpath is None:
- tvobjpath = cls.cube_appobject_path
- for directory in templpath:
- # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
- for subdir in sorted(tvobjpath, key=lambda x:x != 'entities'):
- path = join(directory, subdir)
- if exists(path):
- vregpath.append(path)
- elif exists(path + '.py'):
- vregpath.append(path + '.py')
- return vregpath
-
def __init__(self, debugmode=False):
if debugmode:
# in python 2.7, DeprecationWarning are not shown anymore by default
@@ -766,12 +696,57 @@
# configure simpleTal logger
logging.getLogger('simpleTAL').setLevel(logging.ERROR)
- def vregistry_path(self):
+ def appobjects_path(self):
"""return a list of files or directories where the registry will look
for application objects. By default return nothing in NoApp config.
"""
return []
+ def build_appobjects_path(self, templpath, evobjpath=None, tvobjpath=None):
+ """given a list of directories, return a list of sub files and
+ directories that should be loaded by the instance objects registry.
+
+ :param evobjpath:
+ optional list of sub-directories (or files without the .py ext) of
+ the cubicweb library that should be tested and added to the output list
+ if they exists. If not give, default to `cubicweb_appobject_path` class
+ attribute.
+ :param tvobjpath:
+ optional list of sub-directories (or files without the .py ext) of
+ directories given in `templpath` that should be tested and added to
+ the output list if they exists. If not give, default to
+ `cube_appobject_path` class attribute.
+ """
+ vregpath = self.build_appobjects_cubicweb_path(evobjpath)
+ vregpath += self.build_appobjects_cube_path(templpath, tvobjpath)
+ return vregpath
+
+ def build_appobjects_cubicweb_path(self, evobjpath=None):
+ vregpath = []
+ if evobjpath is None:
+ evobjpath = self.cubicweb_appobject_path
+ # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
+ # it is clearly a workaround
+ for subdir in sorted(evobjpath, key=lambda x:x != 'entities'):
+ path = join(CW_SOFTWARE_ROOT, subdir)
+ if exists(path):
+ vregpath.append(path)
+ return vregpath
+
+ def build_appobjects_cube_path(self, templpath, tvobjpath=None):
+ vregpath = []
+ if tvobjpath is None:
+ tvobjpath = self.cube_appobject_path
+ for directory in templpath:
+ # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
+ for subdir in sorted(tvobjpath, key=lambda x:x != 'entities'):
+ path = join(directory, subdir)
+ if exists(path):
+ vregpath.append(path)
+ elif exists(path + '.py'):
+ vregpath.append(path + '.py')
+ return vregpath
+
apphome = None
def load_site_cubicweb(self, paths=None):
@@ -1177,14 +1152,14 @@
self.exception('localisation support error for language %s',
language)
- def vregistry_path(self):
+ def appobjects_path(self):
"""return a list of files or directories where the registry will look
for application objects
"""
templpath = list(reversed(self.cubes_path()))
if self.apphome: # may be unset in tests
templpath.append(self.apphome)
- return self.build_vregistry_path(templpath)
+ return self.build_appobjects_path(templpath)
def set_sources_mode(self, sources):
if not 'all' in sources:
--- a/cwctl.py Thu Jan 24 16:13:40 2013 +0100
+++ b/cwctl.py Fri Jan 25 14:33:40 2013 +0100
@@ -27,6 +27,9 @@
import sys
from warnings import warn
from os import remove, listdir, system, pathsep
+from os.path import exists, join, isfile, isdir, dirname, abspath
+from urlparse import urlparse
+
try:
from os import kill, getpgid
except ImportError:
@@ -36,9 +39,6 @@
"""win32 getpgid implementation"""
-from os.path import exists, join, isfile, isdir, dirname, abspath
-
-from urlparse import urlparse
from logilab.common.clcommands import CommandLine
from logilab.common.shellutils import ASK
@@ -874,64 +874,65 @@
'help': 'URI of the CubicWeb repository to connect to. URI can be \
pyro://[host:port] the Pyro name server host; if the pyro nameserver is not set, \
it will be detected by using a broadcast query, a ZMQ URL or \
-inmemory:// (default) use an in-memory repository.',
+inmemory:// (default) use an in-memory repository. THIS OPTION IS DEPRECATED, \
+directly give URI as instance id instead',
'group': 'remote'
}),
)
- def run(self, args):
- appid = args.pop(0)
- if self.config.repo_uri:
- uri = urlparse(self.config.repo_uri)
- if uri.scheme == 'pyro':
- cnxtype = uri.scheme
- hostport = uri.netloc
- elif uri.scheme == 'inmemory':
- cnxtype = ''
- hostport = ''
- else:
- cnxtype = 'zmq'
- hostport = self.config.repo_uri
+ def _handle_inmemory(self, appid):
+ """ returns migration context handler & shutdown function """
+ config = cwcfg.config_for(appid)
+ if self.config.ext_sources:
+ assert not self.config.system_only
+ sources = self.config.ext_sources
+ elif self.config.system_only:
+ sources = ('system',)
else:
- cnxtype = ''
-
- if cnxtype:
- from cubicweb import AuthenticationError
- from cubicweb.dbapi import connect, ConnectionProperties
- from cubicweb.server.utils import manager_userpasswd
- from cubicweb.server.migractions import ServerMigrationHelper
- cnxprops = ConnectionProperties(cnxtype=cnxtype)
+ sources = ('all',)
+ config.set_sources_mode(sources)
+ config.repairing = self.config.force
+ mih = config.migration_handler()
+ return mih, lambda: mih.shutdown()
- while True:
- try:
- login, pwd = manager_userpasswd(msg=None)
- cnx = connect(appid, login=login, password=pwd,
- host=hostport, mulcnx=False,
- cnxprops=cnxprops)
- except AuthenticationError, ex:
- print ex
- except (KeyboardInterrupt, EOFError):
- print
- sys.exit(0)
- else:
- break
- cnx.load_appobjects()
- repo = cnx._repo
- mih = ServerMigrationHelper(None, repo=repo, cnx=cnx, verbosity=0,
- # hack so it don't try to load fs schema
- schema=1)
+ def _handle_networked(self, appuri):
+ """ returns migration context handler & shutdown function """
+ from cubicweb import AuthenticationError
+ from cubicweb.dbapi import connect
+ from cubicweb.server.utils import manager_userpasswd
+ from cubicweb.server.migractions import ServerMigrationHelper
+ while True:
+ try:
+ login, pwd = manager_userpasswd(msg=None)
+ cnx = connect(appuri, login=login, password=pwd, mulcnx=False)
+ except AuthenticationError, ex:
+ print ex
+ except (KeyboardInterrupt, EOFError):
+ print
+ sys.exit(0)
+ else:
+ break
+ cnx.load_appobjects()
+ repo = cnx._repo
+ mih = ServerMigrationHelper(None, repo=repo, cnx=cnx, verbosity=0,
+ # hack so it don't try to load fs schema
+ schema=1)
+ return mih, lambda: cnx.close()
+
+ def run(self, args):
+ appuri = args.pop(0)
+ if self.config.repo_uri:
+ warn('[3.16] --repo-uri option is deprecated, directly give the URI as instance id',
+ DeprecationWarning)
+ if urlparse(self.config.repo_uri).scheme in ('pyro', 'inmemory'):
+ appuri = '%s/%s' % (self.config.repo_uri.rstrip('/'), appuri)
+
+ from cubicweb.utils import parse_repo_uri
+ protocol, hostport, appid = parse_repo_uri(appuri)
+ if protocol == 'inmemory':
+ mih, shutdown_callback = self._handle_inmemory(appid)
else:
- config = cwcfg.config_for(appid)
- if self.config.ext_sources:
- assert not self.config.system_only
- sources = self.config.ext_sources
- elif self.config.system_only:
- sources = ('system',)
- else:
- sources = ('all',)
- config.set_sources_mode(sources)
- config.repairing = self.config.force
- mih = config.migration_handler()
+ mih, shutdown_callback = self._handle_networked(appuri)
try:
if args:
# use cmdline parser to access left/right attributes only
@@ -943,10 +944,7 @@
else:
mih.interactive_shell()
finally:
- if not cnxtype: # shutdown in-memory repo
- mih.shutdown()
- else:
- cnx.close()
+ shutdown_callback()
class RecompileInstanceCatalogsCommand(InstanceCommand):
--- a/cwvreg.py Thu Jan 24 16:13:40 2013 +0100
+++ b/cwvreg.py Fri Jan 25 14:33:40 2013 +0100
@@ -197,12 +197,13 @@
from os.path import join, dirname, realpath
from warnings import warn
from datetime import datetime, date, time, timedelta
+from functools import partial
from logilab.common.decorators import cached, clear_cache
from logilab.common.deprecation import deprecated, class_deprecated
from logilab.common.modutils import cleanup_sys_modules
from logilab.common.registry import (
- RegistryStore, Registry, classid,
+ RegistryStore, Registry, obj_registries,
ObjectNotFound, NoSelectableObject, RegistryNotFound)
from rql import RQLHelper
@@ -210,13 +211,15 @@
from cubicweb import (CW_SOFTWARE_ROOT, ETYPE_NAME_MAP, CW_EVENT_MANAGER,
Binary, UnknownProperty, UnknownEid)
-from cubicweb.rtags import RTAGS
from cubicweb.predicates import (implements, appobject_selectable,
_reset_is_instance_cache)
-def clear_rtag_objects():
- for rtag in RTAGS:
- rtag.clear()
+# backward compat: those modules are now refering to app objects in
+# cw.web.views.uicfg and import * from backward compat. On registry reload, we
+# should pop those modules from the cache so references are properly updated on
+# subsequent reload
+CW_EVENT_MANAGER.bind('before-registry-reload', partial(sys.modules.pop, 'cubicweb.web.uicfg', None))
+CW_EVENT_MANAGER.bind('before-registry-reload', partial(sys.modules.pop, 'cubicweb.web.uihelper', None))
def use_interfaces(obj):
"""return interfaces required by the given object by searching for
@@ -263,6 +266,15 @@
return getattr(obj, appobjectattr, obj)
+class InstancesRegistry(CWRegistry):
+
+ def selected(self, winner, args, kwargs):
+ """overriden to avoid the default 'instanciation' behaviour, ie
+ winner(*args, **kwargs)
+ """
+ return winner
+
+
class ETypeRegistry(CWRegistry):
def clear_caches(self):
@@ -497,6 +509,7 @@
'views': ViewsRegistry,
'actions': ActionsRegistry,
'ctxcomponents': CtxComponentsRegistry,
+ 'uicfg': InstancesRegistry,
}
def __init__(self, config, initlog=True):
@@ -517,11 +530,6 @@
sys.path.remove(CW_SOFTWARE_ROOT)
self.schema = None
self.initialized = False
- # XXX give force_reload (or refactor [re]loading...)
- if self.config.mode != 'test':
- # don't clear rtags during test, this may cause breakage with
- # manually imported appobject modules
- CW_EVENT_MANAGER.bind('before-registry-reload', clear_rtag_objects)
self['boxes'] = BwCompatCWRegistry(self, 'boxes', 'ctxcomponents')
self['contentnavigation'] = BwCompatCWRegistry(self, 'contentnavigation', 'ctxcomponents')
@@ -544,20 +552,6 @@
def itervalues(self):
return (value for key, value in self.items())
- def load_module(self, module):
- """ variation from the base implementation:
- apply related_appobject to the automatically registered objects
- """
- self.info('loading %s from %s', module.__name__, module.__file__)
- if hasattr(module, 'registration_callback'):
- module.registration_callback(self)
- return
- for objname, obj in vars(module).iteritems():
- if objname.startswith('_'):
- continue
- self._load_ancestors_then_object(module.__name__,
- related_appobject(obj))
-
def reset(self):
CW_EVENT_MANAGER.emit('before-registry-reset', self)
super(CWRegistryStore, self).reset()
@@ -588,7 +582,7 @@
"""set instance'schema and load application objects"""
self._set_schema(schema)
# now we can load application's web objects
- self.reload(self.config.vregistry_path(), force_reload=False)
+ self.reload(self.config.appobjects_path(), force_reload=False)
# map lowered entity type names to their actual name
self.case_insensitive_etypes = {}
for eschema in self.schema.entities():
@@ -598,7 +592,7 @@
clear_cache(eschema, 'meta_attributes')
def reload_if_needed(self):
- path = self.config.vregistry_path()
+ path = self.config.appobjects_path()
if self.is_reload_needed(path):
self.reload(path)
@@ -614,7 +608,7 @@
cfg = self.config
for cube in cfg.expand_cubes(cubes, with_recommends=True):
if not cube in cubes:
- cpath = cfg.build_vregistry_cube_path([cfg.cube_dir(cube)])
+ cpath = cfg.build_appobjects_cube_path([cfg.cube_dir(cube)])
cleanup_sys_modules(cpath)
self.register_objects(path)
CW_EVENT_MANAGER.emit('after-registry-reload')
@@ -709,8 +703,9 @@
or iface
for iface in ifaces)
if not ('Any' in ifaces or ifaces & implemented_interfaces):
+ reg = self[obj_registries(obj)[0]]
self.debug('unregister %s (no implemented '
- 'interface among %s)', classid(obj), ifaces)
+ 'interface among %s)', reg.objid(obj), ifaces)
self.unregister(obj)
# since 3.9: remove appobjects which depending on other, unexistant
# appobjects
@@ -718,8 +713,7 @@
try:
registry = self[regname]
except RegistryNotFound:
- self.debug('unregister %s (no registry %s)', classid(obj),
- regname)
+ self.debug('unregister %s (no registry %s)', obj, regname)
self.unregister(obj)
continue
for regid in regids:
@@ -727,12 +721,14 @@
break
else:
self.debug('unregister %s (no %s object in registry %s)',
- classid(obj), ' or '.join(regids), regname)
+ registry.objid(obj), ' or '.join(regids), regname)
self.unregister(obj)
super(CWRegistryStore, self).initialization_completed()
- for rtag in RTAGS:
- # don't check rtags if we don't want to cleanup_interface_sobjects
- rtag.init(self.schema, check=self.config.cleanup_interface_sobjects)
+ if 'uicfg' in self: # 'uicfg' is not loaded in a pure repository mode
+ for rtags in self['uicfg'].values():
+ for rtag in rtags:
+ # don't check rtags if we don't want to cleanup_interface_sobjects
+ rtag.init(self.schema, check=self.config.cleanup_interface_sobjects)
# rql parsing utilities ####################################################
--- a/dataimport.py Thu Jan 24 16:13:40 2013 +0100
+++ b/dataimport.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -66,13 +66,18 @@
"""
__docformat__ = "restructuredtext en"
+import csv
import sys
-import csv
+import threading
import traceback
+import cPickle
import os.path as osp
-from StringIO import StringIO
+from collections import defaultdict
+from contextlib import contextmanager
from copy import copy
-from datetime import datetime
+from datetime import date, datetime
+from time import asctime
+from StringIO import StringIO
from logilab.common import shellutils, attrdict
from logilab.common.date import strptime
@@ -80,9 +85,11 @@
from logilab.common.deprecation import deprecated
from cubicweb import QueryError
+from cubicweb.utils import make_uid
from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES
+from cubicweb.server.edition import EditedEntity
+from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.utils import eschema_eid
-from cubicweb.server.edition import EditedEntity
def count_lines(stream_or_filename):
@@ -117,15 +124,33 @@
print ' %s rows imported' % rowcount
def ucsvreader(stream, encoding='utf-8', separator=',', quote='"',
- skipfirst=False):
+ skipfirst=False, ignore_errors=False):
"""A csv reader that accepts files with any encoding and outputs unicode
strings
"""
it = iter(csv.reader(stream, delimiter=separator, quotechar=quote))
- if skipfirst:
- it.next()
- for row in it:
- yield [item.decode(encoding) for item in row]
+ if not ignore_errors:
+ if skipfirst:
+ it.next()
+ for row in it:
+ yield [item.decode(encoding) for item in row]
+ else:
+ # Skip first line
+ try:
+ row = it.next()
+ except csv.Error:
+ pass
+ # Safe version, that can cope with error in CSV file
+ while True:
+ try:
+ row = it.next()
+ # End of CSV, break
+ except StopIteration:
+ break
+ # Error in CSV, ignore line and continue
+ except csv.Error:
+ continue
+ yield [item.decode(encoding) for item in row]
def callfunc_every(func, number, iterable):
"""yield items of `iterable` one by one and call function `func`
@@ -299,6 +324,149 @@
if k is not None and len(v) > 1]
+# sql generator utility functions #############################################
+
+
+def _import_statements(sql_connect, statements, nb_threads=3,
+ dump_output_dir=None,
+ support_copy_from=True, encoding='utf-8'):
+ """
+ Import a bunch of sql statements, using different threads.
+ """
+ try:
+ chunksize = (len(statements) / nb_threads) + 1
+ threads = []
+ for i in xrange(nb_threads):
+ chunks = statements[i*chunksize:(i+1)*chunksize]
+ thread = threading.Thread(target=_execmany_thread,
+ args=(sql_connect, chunks,
+ dump_output_dir,
+ support_copy_from,
+ encoding))
+ thread.start()
+ threads.append(thread)
+ for t in threads:
+ t.join()
+ except Exception:
+ print 'Error in import statements'
+
+def _execmany_thread_not_copy_from(cu, statement, data, table=None,
+ columns=None, encoding='utf-8'):
+ """ Execute thread without copy from
+ """
+ cu.executemany(statement, data)
+
+def _execmany_thread_copy_from(cu, statement, data, table,
+ columns, encoding='utf-8'):
+ """ Execute thread with copy from
+ """
+ buf = _create_copyfrom_buffer(data, columns, encoding)
+ if buf is None:
+ _execmany_thread_not_copy_from(cu, statement, data)
+ else:
+ if columns is None:
+ cu.copy_from(buf, table, null='NULL')
+ else:
+ cu.copy_from(buf, table, null='NULL', columns=columns)
+
+def _execmany_thread(sql_connect, statements, dump_output_dir=None,
+ support_copy_from=True, encoding='utf-8'):
+ """
+ Execute sql statement. If 'INSERT INTO', try to use 'COPY FROM' command,
+ or fallback to execute_many.
+ """
+ if support_copy_from:
+ execmany_func = _execmany_thread_copy_from
+ else:
+ execmany_func = _execmany_thread_not_copy_from
+ cnx = sql_connect()
+ cu = cnx.cursor()
+ try:
+ for statement, data in statements:
+ table = None
+ columns = None
+ try:
+ if not statement.startswith('INSERT INTO'):
+ cu.executemany(statement, data)
+ continue
+ table = statement.split()[2]
+ if isinstance(data[0], (tuple, list)):
+ columns = None
+ else:
+ columns = data[0].keys()
+ execmany_func(cu, statement, data, table, columns, encoding)
+ except Exception:
+ print 'unable to copy data into table %s', table
+ # Error in import statement, save data in dump_output_dir
+ if dump_output_dir is not None:
+ pdata = {'data': data, 'statement': statement,
+ 'time': asctime(), 'columns': columns}
+ filename = make_uid()
+ try:
+ with open(osp.join(dump_output_dir,
+ '%s.pickle' % filename), 'w') as fobj:
+ fobj.write(cPickle.dumps(pdata))
+ except IOError:
+ print 'ERROR while pickling in', dump_output_dir, filename+'.pickle'
+ pass
+ cnx.rollback()
+ raise
+ finally:
+ cnx.commit()
+ cu.close()
+
+def _create_copyfrom_buffer(data, columns, encoding='utf-8', replace_sep=None):
+ """
+ Create a StringIO buffer for 'COPY FROM' command.
+ Deals with Unicode, Int, Float, Date...
+ """
+ # Create a list rather than directly create a StringIO
+ # to correctly write lines separated by '\n' in a single step
+ rows = []
+ if isinstance(data[0], (tuple, list)):
+ columns = range(len(data[0]))
+ for row in data:
+ # Iterate over the different columns and the different values
+ # and try to convert them to a correct datatype.
+ # If an error is raised, do not continue.
+ formatted_row = []
+ for col in columns:
+ value = row[col]
+ if value is None:
+ value = 'NULL'
+ elif isinstance(value, (long, int, float)):
+ value = str(value)
+ elif isinstance(value, (str, unicode)):
+ # Remove separators used in string formatting
+ for _char in (u'\t', u'\r', u'\n'):
+ if _char in value:
+ # If a replace_sep is given, replace
+ # the separator instead of returning None
+ # (and thus avoid empty buffer)
+ if replace_sep:
+ value = value.replace(_char, replace_sep)
+ else:
+ return
+ value = value.replace('\\', r'\\')
+ if value is None:
+ return
+ if isinstance(value, unicode):
+ value = value.encode(encoding)
+ elif isinstance(value, (date, datetime)):
+ # Do not use strftime, as it yields issue
+ # with date < 1900
+ value = '%04d-%02d-%02d' % (value.year,
+ value.month,
+ value.day)
+ else:
+ return None
+ # We push the value to the new formatted row
+ # if the value is not None and could be converted to a string.
+ formatted_row.append(value)
+ rows.append('\t'.join(formatted_row))
+ return StringIO('\n'.join(rows))
+
+
# object stores #################################################################
class ObjectStore(object):
@@ -753,3 +921,261 @@
return self.session.user.eid
def gen_owned_by(self, entity):
return self.session.user.eid
+
+
+###########################################################################
+## SQL object store #######################################################
+###########################################################################
+class SQLGenObjectStore(NoHookRQLObjectStore):
+ """Controller of the data import process. This version is based
+ on direct insertions throught SQL command (COPY FROM or execute many).
+
+ >>> store = SQLGenObjectStore(session)
+ >>> store.create_entity('Person', ...)
+ >>> store.flush()
+ """
+
+ def __init__(self, session, dump_output_dir=None, nb_threads_statement=3):
+ """
+ Initialize a SQLGenObjectStore.
+
+ Parameters:
+
+ - session: session on the cubicweb instance
+ - dump_output_dir: a directory to dump failed statements
+ for easier recovery. Default is None (no dump).
+ - nb_threads_statement: number of threads used
+ for SQL insertion (default is 3).
+ """
+ super(SQLGenObjectStore, self).__init__(session)
+ ### hijack default source
+ self.source = SQLGenSourceWrapper(
+ self.source, session.vreg.schema,
+ dump_output_dir=dump_output_dir,
+ nb_threads_statement=nb_threads_statement)
+ ### XXX This is done in super().__init__(), but should be
+ ### redone here to link to the correct source
+ self.add_relation = self.source.add_relation
+ self.indexes_etypes = {}
+
+ def flush(self):
+ """Flush data to the database"""
+ self.source.flush()
+
+ def relate(self, subj_eid, rtype, obj_eid, subjtype=None):
+ if subj_eid is None or obj_eid is None:
+ return
+ # XXX Could subjtype be inferred ?
+ self.source.add_relation(self.session, subj_eid, rtype, obj_eid,
+ self.rschema(rtype).inlined, subjtype)
+
+ def drop_indexes(self, etype):
+ """Drop indexes for a given entity type"""
+ if etype not in self.indexes_etypes:
+ cu = self.session.cnxset['system']
+ def index_to_attr(index):
+ """turn an index name to (database) attribute name"""
+ return index.replace(etype.lower(), '').replace('idx', '').strip('_')
+ indices = [(index, index_to_attr(index))
+ for index in self.source.dbhelper.list_indices(cu, etype)
+ # Do not consider 'cw_etype_pkey' index
+ if not index.endswith('key')]
+ self.indexes_etypes[etype] = indices
+ for index, attr in self.indexes_etypes[etype]:
+ self.session.system_sql('DROP INDEX %s' % index)
+
+ def create_indexes(self, etype):
+ """Recreate indexes for a given entity type"""
+ for index, attr in self.indexes_etypes.get(etype, []):
+ sql = 'CREATE INDEX %s ON cw_%s(%s)' % (index, etype, attr)
+ self.session.system_sql(sql)
+
+
+###########################################################################
+## SQL Source #############################################################
+###########################################################################
+
+class SQLGenSourceWrapper(object):
+
+ def __init__(self, system_source, schema,
+ dump_output_dir=None, nb_threads_statement=3):
+ self.system_source = system_source
+ self._sql = threading.local()
+ # Explicitely backport attributes from system source
+ self._storage_handler = self.system_source._storage_handler
+ self.preprocess_entity = self.system_source.preprocess_entity
+ self.sqlgen = self.system_source.sqlgen
+ self.copy_based_source = self.system_source.copy_based_source
+ self.uri = self.system_source.uri
+ self.eid = self.system_source.eid
+ # Directory to write temporary files
+ self.dump_output_dir = dump_output_dir
+ # Allow to execute code with SQLite backend that does
+ # not support (yet...) copy_from
+ # XXX Should be dealt with in logilab.database
+ spcfrom = system_source.dbhelper.dbapi_module.support_copy_from
+ self.support_copy_from = spcfrom
+ self.dbencoding = system_source.dbhelper.dbencoding
+ self.nb_threads_statement = nb_threads_statement
+ # initialize thread-local data for main thread
+ self.init_thread_locals()
+ self._inlined_rtypes_cache = {}
+ self._fill_inlined_rtypes_cache(schema)
+ self.schema = schema
+ self.do_fti = False
+
+ def _fill_inlined_rtypes_cache(self, schema):
+ cache = self._inlined_rtypes_cache
+ for eschema in schema.entities():
+ for rschema in eschema.ordered_relations():
+ if rschema.inlined:
+ cache[eschema.type] = SQL_PREFIX + rschema.type
+
+ def init_thread_locals(self):
+ """initializes thread-local data"""
+ self._sql.entities = defaultdict(list)
+ self._sql.relations = {}
+ self._sql.inlined_relations = {}
+ # keep track, for each eid of the corresponding data dict
+ self._sql.eid_insertdicts = {}
+
+ def flush(self):
+ print 'starting flush'
+ _entities_sql = self._sql.entities
+ _relations_sql = self._sql.relations
+ _inlined_relations_sql = self._sql.inlined_relations
+ _insertdicts = self._sql.eid_insertdicts
+ try:
+ # try, for each inlined_relation, to find if we're also creating
+ # the host entity (i.e. the subject of the relation).
+ # In that case, simply update the insert dict and remove
+ # the need to make the
+ # UPDATE statement
+ for statement, datalist in _inlined_relations_sql.iteritems():
+ new_datalist = []
+ # for a given inlined relation,
+ # browse each couple to be inserted
+ for data in datalist:
+ keys = data.keys()
+ # For inlined relations, it exists only two case:
+ # (rtype, cw_eid) or (cw_eid, rtype)
+ if keys[0] == 'cw_eid':
+ rtype = keys[1]
+ else:
+ rtype = keys[0]
+ updated_eid = data['cw_eid']
+ if updated_eid in _insertdicts:
+ _insertdicts[updated_eid][rtype] = data[rtype]
+ else:
+ # could not find corresponding insert dict, keep the
+ # UPDATE query
+ new_datalist.append(data)
+ _inlined_relations_sql[statement] = new_datalist
+ _import_statements(self.system_source.get_connection,
+ _entities_sql.items()
+ + _relations_sql.items()
+ + _inlined_relations_sql.items(),
+ dump_output_dir=self.dump_output_dir,
+ nb_threads=self.nb_threads_statement,
+ support_copy_from=self.support_copy_from,
+ encoding=self.dbencoding)
+ except:
+ print 'failed to flush'
+ finally:
+ _entities_sql.clear()
+ _relations_sql.clear()
+ _insertdicts.clear()
+ _inlined_relations_sql.clear()
+ print 'flush done'
+
+ def add_relation(self, session, subject, rtype, object,
+ inlined=False, subjtype=None):
+ if inlined:
+ _sql = self._sql.inlined_relations
+ data = {'cw_eid': subject, SQL_PREFIX + rtype: object}
+ if subjtype is None:
+ # Try to infer it
+ targets = [t.type for t in
+ self.schema.rschema(rtype).targets()]
+ if len(targets) == 1:
+ subjtype = targets[0]
+ else:
+ raise ValueError('You should give the subject etype for '
+ 'inlined relation %s'
+ ', as it cannot be inferred' % rtype)
+ statement = self.sqlgen.update(SQL_PREFIX + subjtype,
+ data, ['cw_eid'])
+ else:
+ _sql = self._sql.relations
+ data = {'eid_from': subject, 'eid_to': object}
+ statement = self.sqlgen.insert('%s_relation' % rtype, data)
+ if statement in _sql:
+ _sql[statement].append(data)
+ else:
+ _sql[statement] = [data]
+
+ def add_entity(self, session, entity):
+ with self._storage_handler(entity, 'added'):
+ attrs = self.preprocess_entity(entity)
+ rtypes = self._inlined_rtypes_cache.get(entity.__regid__, ())
+ if isinstance(rtypes, str):
+ rtypes = (rtypes,)
+ for rtype in rtypes:
+ if rtype not in attrs:
+ attrs[rtype] = None
+ sql = self.sqlgen.insert(SQL_PREFIX + entity.__regid__, attrs)
+ self._sql.eid_insertdicts[entity.eid] = attrs
+ self._append_to_entities(sql, attrs)
+
+ def _append_to_entities(self, sql, attrs):
+ self._sql.entities[sql].append(attrs)
+
+ def _handle_insert_entity_sql(self, session, sql, attrs):
+ # We have to overwrite the source given in parameters
+ # as here, we directly use the system source
+ attrs['source'] = 'system'
+ attrs['asource'] = self.system_source.uri
+ self._append_to_entities(sql, attrs)
+
+ def _handle_is_relation_sql(self, session, sql, attrs):
+ self._append_to_entities(sql, attrs)
+
+ def _handle_is_instance_of_sql(self, session, sql, attrs):
+ self._append_to_entities(sql, attrs)
+
+ def _handle_source_relation_sql(self, session, sql, attrs):
+ self._append_to_entities(sql, attrs)
+
+ # XXX add_info is similar to the one in NativeSQLSource. It is rewritten
+ # here to correctly used the _handle_xxx of the SQLGenSourceWrapper. This
+ # part should be rewritten in a more clearly way.
+ def add_info(self, session, entity, source, extid, complete):
+ """add type and source info for an eid into the system table"""
+ # begin by inserting eid/type/source/extid into the entities table
+ if extid is not None:
+ assert isinstance(extid, str)
+ extid = b64encode(extid)
+ uri = 'system' if source.copy_based_source else source.uri
+ attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
+ 'source': uri, 'asource': source.uri, 'mtime': datetime.utcnow()}
+ self._handle_insert_entity_sql(session, self.sqlgen.insert('entities', attrs), attrs)
+ # insert core relations: is, is_instance_of and cw_source
+ try:
+ self._handle_is_relation_sql(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, eschema_eid(session, entity.e_schema)))
+ except IndexError:
+ # during schema serialization, skip
+ pass
+ else:
+ for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
+ self._handle_is_relation_sql(session,
+ 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, eschema_eid(session, eschema)))
+ if 'CWSource' in self.schema and source.eid is not None: # else, cw < 3.10
+ self._handle_is_relation_sql(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, source.eid))
+ # now we can update the full text index
+ if self.do_fti and self.need_fti_indexation(entity.__regid__):
+ if complete:
+ entity.complete(entity.e_schema.indexable_attributes())
+ self.index_entity(session, entity=entity)
--- a/dbapi.py Thu Jan 24 16:13:40 2013 +0100
+++ b/dbapi.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -31,6 +31,7 @@
from warnings import warn
from os.path import join
from uuid import uuid4
+from urlparse import urlparse
from logilab.common.logging_ext import set_log_methods
from logilab.common.decorators import monkeypatch
@@ -39,6 +40,7 @@
from cubicweb import ETYPE_NAME_MAP, ConnectionError, AuthenticationError,\
cwvreg, cwconfig
from cubicweb.req import RequestSessionBase
+from cubicweb.utils import parse_repo_uri
_MARKER = object()
@@ -80,58 +82,85 @@
class ConnectionProperties(object):
- def __init__(self, cnxtype=None, lang=None, close=True, log=False):
- self.cnxtype = cnxtype or 'pyro'
- self.lang = lang
+ def __init__(self, cnxtype=None, close=True, log=False):
+ if cnxtype is not None:
+ warn('[3.16] cnxtype argument is deprecated', DeprecationWarning,
+ stacklevel=2)
+ self.cnxtype = cnxtype
self.log_queries = log
self.close_on_del = close
-def get_repository(method, database=None, config=None, vreg=None):
- """get a proxy object to the CubicWeb repository, using a specific RPC method.
+def _get_inmemory_repo(config, vreg=None):
+ from cubicweb.server.repository import Repository
+ from cubicweb.server.utils import TasksManager
+ return Repository(config, TasksManager(), vreg=vreg)
- Only 'in-memory' and 'pyro' are supported for now. Either vreg or config
- argument should be given
+def get_repository(uri=None, config=None, vreg=None):
+ """get a repository for the given URI or config/vregistry (in case we're
+ loading the repository for a client, eg web server, configuration).
+
+ The returned repository may be an in-memory repository or a proxy object
+ using a specific RPC method, depending on the given URI (pyro or zmq).
"""
- assert method in ('pyro', 'inmemory', 'zmq')
- assert vreg or config
- if vreg and not config:
- config = vreg.config
- if method == 'inmemory':
- # get local access to the repository
- from cubicweb.server.repository import Repository
- from cubicweb.server.utils import TasksManager
- return Repository(config, TasksManager(), vreg=vreg)
- elif method == 'zmq':
+ try:
+ return _get_repository(uri, config, vreg)
+ except ConnectionError:
+ raise
+ except Exception, exc:
+ raise ConnectionError('cause: %r' % exc)
+
+def _get_repository(uri=None, config=None, vreg=None):
+ """ implements get_repository (see above) """
+ if uri is None:
+ return _get_inmemory_repo(config, vreg)
+
+ protocol, hostport, appid = parse_repo_uri(uri)
+
+ if protocol == 'inmemory':
+ # me may have been called with a dummy 'inmemory://' uri ...
+ return _get_inmemory_repo(config, vreg)
+
+ if protocol == 'pyroloc': # direct connection to the instance
+ from logilab.common.pyro_ext import get_proxy
+ uri = uri.replace('pyroloc', 'PYRO')
+ return get_proxy(uri)
+
+ if protocol == 'pyro': # connection mediated through the pyro ns
+ from logilab.common.pyro_ext import ns_get_proxy
+ path = appid.strip('/')
+ if not path:
+ raise ConnectionError(
+ "can't find instance name in %s (expected to be the path component)"
+ % uri)
+ if '.' in path:
+ nsgroup, nsid = path.rsplit('.', 1)
+ else:
+ nsgroup = 'cubicweb'
+ nsid = path
+ return ns_get_proxy(nsid, defaultnsgroup=nsgroup, nshost=hostport)
+
+ if protocol.startswith('zmqpickle-'):
from cubicweb.zmqclient import ZMQRepositoryClient
- return ZMQRepositoryClient(database)
- else: # method == 'pyro'
- # resolve the Pyro object
- from logilab.common.pyro_ext import ns_get_proxy, get_proxy
- pyroid = database or config['pyro-instance-id'] or config.appid
- try:
- if config['pyro-ns-host'] == 'NO_PYRONS':
- return get_proxy(pyroid)
- else:
- return ns_get_proxy(pyroid, defaultnsgroup=config['pyro-ns-group'],
- nshost=config['pyro-ns-host'])
- except Exception, ex:
- raise ConnectionError(str(ex))
+ return ZMQRepositoryClient(uri)
+ else:
+ raise ConnectionError('unknown protocol: `%s`' % protocol)
-def repo_connect(repo, login, **kwargs):
- """Constructor to create a new connection to the CubicWeb repository.
+
+def _repo_connect(repo, login, **kwargs):
+ """Constructor to create a new connection to the given CubicWeb repository.
Returns a Connection instance.
+
+ Raises AuthenticationError if authentication failed
"""
- if not 'cnxprops' in kwargs:
- kwargs['cnxprops'] = ConnectionProperties('inmemory')
cnxid = repo.connect(unicode(login), **kwargs)
- cnx = Connection(repo, cnxid, kwargs['cnxprops'])
- if kwargs['cnxprops'].cnxtype == 'inmemory':
+ cnx = Connection(repo, cnxid, kwargs.get('cnxprops'))
+ if cnx.is_repo_in_memory:
cnx.vreg = repo.vreg
return cnx
-def connect(database=None, login=None, host=None, group=None,
+def connect(database, login=None,
cnxprops=None, setvreg=True, mulcnx=True, initlog=True, **kwargs):
"""Constructor for creating a connection to the CubicWeb repository.
Returns a :class:`Connection` object.
@@ -140,24 +169,29 @@
cnx = connect('myinstance', login='me', password='toto')
- Arguments:
+ `database` may be:
+
+ * a simple instance id for in-memory connection
+
+ * an uri like scheme://host:port/instanceid where scheme may be one of
+ 'pyro', 'inmemory' or 'zmqpickle'
- :database:
- the instance's pyro identifier.
+ * if scheme is 'pyro', <host:port> determine the name server address. If
+ not specified (e.g. 'pyro:///instanceid'), it will be detected through a
+ broadcast query. The instance id is the name of the instance in the name
+ server and may be prefixed by a group (e.g.
+ 'pyro:///:cubicweb.instanceid')
+
+ * if scheme is handled by ZMQ (eg 'tcp'), you should not specify an
+ instance id
+
+ Other arguments:
:login:
the user login to use to authenticate.
- :host:
- - pyro: nameserver host. Will be detected using broadcast query if unspecified
- - zmq: repository host socket address
-
- :group:
- the instance's pyro nameserver group. You don't have to specify it unless
- tweaked in instance's configuration.
-
:cnxprops:
- an optional :class:`ConnectionProperties` instance, allowing to specify
+ a :class:`ConnectionProperties` instance, allowing to specify
the connection method (eg in memory or pyro). A Pyro connection will be
established if you don't specify that argument.
@@ -179,20 +213,25 @@
there goes authentication tokens. You usually have to specify a password
for the given user, using a named 'password' argument.
"""
- cnxprops = cnxprops or ConnectionProperties()
- method = cnxprops.cnxtype
- if method == 'pyro':
+ if urlparse(database).scheme is None:
+ warn('[3.16] give an qualified URI as database instead of using '
+ 'host/cnxprops to specify the connection method',
+ DeprecationWarning, stacklevel=2)
+ if cnxprops.cnxtype == 'zmq':
+ database = kwargs.pop('host')
+ elif cnxprops.cnxtype == 'inmemory':
+ database = 'inmemory://' + database
+ else:
+ database = 'pyro://%s/%s.%s' % (kwargs.pop('host', ''),
+ kwargs.pop('group', 'cubicweb'),
+ database)
+ puri = urlparse(database)
+ method = puri.scheme.lower()
+ if method == 'inmemory':
+ config = cwconfig.instance_configuration(puri.path)
+ else:
config = cwconfig.CubicWebNoAppConfiguration()
- if host:
- config.global_set_option('pyro-ns-host', host)
- if group:
- config.global_set_option('pyro-ns-group', group)
- elif method == 'zmq':
- config = cwconfig.CubicWebNoAppConfiguration()
- else:
- assert database
- config = cwconfig.instance_configuration(database)
- repo = get_repository(method, database, config=config)
+ repo = get_repository(database, config=config)
if method == 'inmemory':
vreg = repo.vreg
elif setvreg:
@@ -207,7 +246,7 @@
vreg.set_schema(schema)
else:
vreg = None
- cnx = repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
+ cnx = _repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
cnx.vreg = vreg
return cnx
@@ -219,14 +258,7 @@
else:
vreg = None
# get local access to the repository
- return get_repository('inmemory', config=config, vreg=vreg)
-
-def in_memory_cnx(repo, login, **kwargs):
- """Establish a In memory connection to a <repo> for the user with <login>
-
- additionel credential might be required"""
- cnxprops = ConnectionProperties('inmemory')
- return repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
+ return get_repository('inmemory://', config=config, vreg=vreg)
def in_memory_repo_cnx(config, login, **kwargs):
"""useful method for testing and scripting to get a dbapi.Connection
@@ -234,9 +266,9 @@
"""
# connection to the CubicWeb repository
repo = in_memory_repo(config)
- return repo, in_memory_cnx(repo, login, **kwargs)
+ return repo, _repo_connect(repo, login, **kwargs)
-
+# XXX web only method, move to webconfig?
def anonymous_session(vreg):
"""return a new anonymous session
@@ -246,11 +278,9 @@
if anoninfo is None: # no anonymous user
raise AuthenticationError('anonymous access is not authorized')
anon_login, anon_password = anoninfo
- cnxprops = ConnectionProperties(vreg.config.repo_method)
# use vreg's repository cache
repo = vreg.config.repository(vreg)
- anon_cnx = repo_connect(repo, anon_login,
- cnxprops=cnxprops, password=anon_password)
+ anon_cnx = _repo_connect(repo, anon_login, password=anon_password)
anon_cnx.vreg = vreg
return DBAPISession(anon_cnx, anon_login)
@@ -282,7 +312,10 @@
def __repr__(self):
return '<DBAPISession %r>' % self.sessionid
+
class DBAPIRequest(RequestSessionBase):
+ #: Request language identifier eg: 'en'
+ lang = None
def __init__(self, vreg, session=None):
super(DBAPIRequest, self).__init__(vreg)
@@ -292,9 +325,6 @@
self.translations = vreg.config.translations
except AttributeError:
self.translations = {}
- #: Request language identifier eg: 'en'
- self.lang = None
- self.set_default_language(vreg)
#: cache entities built during the request
self._eid_cache = {}
if session is not None:
@@ -304,6 +334,7 @@
# established
self.session = None
self.cnx = self.user = _NeedAuthAccessMock()
+ self.set_default_language(vreg)
def from_controller(self):
return 'view'
@@ -320,7 +351,7 @@
self.cnx = session.cnx
self.execute = session.cnx.cursor(self).execute
if user is None:
- user = self.cnx.user(self, {'lang': self.lang})
+ user = self.cnx.user(self)
if user is not None:
self.user = user
self.set_entity_cache(user)
@@ -333,19 +364,15 @@
def set_default_language(self, vreg):
try:
- self.lang = vreg.property_value('ui.language')
+ lang = vreg.property_value('ui.language')
except Exception: # property may not be registered
- self.lang = 'en'
- # use req.__ to translate a message without registering it to the catalog
+ lang = 'en'
try:
- gettext, pgettext = self.translations[self.lang]
- self._ = self.__ = gettext
- self.pgettext = pgettext
+ self.set_language(lang)
except KeyError:
# this occurs usually during test execution
self._ = self.__ = unicode
self.pgettext = lambda x, y: unicode(y)
- self.debug('request default language: %s', self.lang)
# server-side service call #################################################
@@ -540,15 +567,22 @@
self._repo = repo
self.sessionid = cnxid
self._close_on_del = getattr(cnxprops, 'close_on_del', True)
- self._cnxtype = getattr(cnxprops, 'cnxtype', 'pyro')
self._web_request = False
if cnxprops and cnxprops.log_queries:
self.executed_queries = []
self.cursor_class = LogCursor
- if self._cnxtype == 'pyro':
- # check client/server compat
- if self._repo.get_versions()['cubicweb'] < (3, 8, 6):
- self._txid = lambda cursor=None: {}
+
+ @property
+ def is_repo_in_memory(self):
+ """return True if this is a local, aka in-memory, connection to the
+ repository
+ """
+ try:
+ from cubicweb.server.repository import Repository
+ except ImportError:
+ # code not available, no way
+ return False
+ return isinstance(self._repo, Repository)
def __repr__(self):
if self.anonymous_connection:
@@ -606,9 +640,9 @@
# then init cubes
config.init_cubes(cubes)
# then load appobjects into the registry
- vpath = config.build_vregistry_path(reversed(config.cubes_path()),
- evobjpath=esubpath,
- tvobjpath=subpath)
+ vpath = config.build_appobjects_path(reversed(config.cubes_path()),
+ evobjpath=esubpath,
+ tvobjpath=subpath)
self.vreg.register_objects(vpath)
def use_web_compatible_requests(self, baseurl, sitetitle=None):
@@ -678,11 +712,6 @@
# session data methods #####################################################
@check_not_closed
- def set_session_props(self, **props):
- """raise `BadConnectionId` if the connection is no more valid"""
- self._repo.set_session_props(self.sessionid, props)
-
- @check_not_closed
def get_shared_data(self, key, default=None, pop=False, txdata=False):
"""return value associated to key in the session's data dictionary or
session's transaction's data if `txdata` is true.
@@ -860,3 +889,5 @@
"""
return self._repo.undo_transaction(self.sessionid, txuuid,
**self._txid())
+
+in_memory_cnx = deprecated('[3.16] use _repo_connect instead)')(_repo_connect)
--- a/debian/changelog Thu Jan 24 16:13:40 2013 +0100
+++ b/debian/changelog Fri Jan 25 14:33:40 2013 +0100
@@ -1,3 +1,9 @@
+cubicweb (3.16.0-1) squeeze; urgency=low
+
+ * New upstream release
+
+ -- Aurélien Campéas <aurelien.campeas@logilab.fr> Wed, 23 Jan 2013 17:40:00 +0100
+
cubicweb (3.15.8-2) UNRELEASED; urgency=low
* Don't compress txt files. They're used by the doc's search functionality,
--- a/debian/control Thu Jan 24 16:13:40 2013 +0100
+++ b/debian/control Fri Jan 25 14:33:40 2013 +0100
@@ -107,7 +107,7 @@
Package: cubicweb-common
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.58.0), python-yams (>= 0.34.0), python-rql (>= 0.31.2), python-lxml
+Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.59.0), python-yams (>= 0.36.0), python-rql (>= 0.31.2), python-lxml
Recommends: python-simpletal (>= 4.0), python-crypto
Conflicts: cubicweb-core
Replaces: cubicweb-core
--- a/devtools/__init__.py Thu Jan 24 16:13:40 2013 +0100
+++ b/devtools/__init__.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -217,7 +217,6 @@
class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration):
- repo_method = 'inmemory'
name = 'all-in-one' # so it search for all-in-one.conf, not repository.conf
options = cwconfig.merge_options(TestServerConfiguration.options
+ TwistedConfiguration.options)
@@ -355,7 +354,7 @@
def _restore_database(self, backup_coordinates, config):
"""Actual restore of the current database.
- Use the value tostored in db_cache as input """
+ Use the value stored in db_cache as input """
raise NotImplementedError()
def get_repo(self, startup=False):
@@ -385,15 +384,14 @@
repo.turn_repo_off = partial(turn_repo_off, repo)
return repo
-
def get_cnx(self):
"""return Connection object on the current repository"""
- from cubicweb.dbapi import in_memory_cnx
+ from cubicweb.dbapi import _repo_connect
repo = self.get_repo()
sources = self.config.sources()
login = unicode(sources['admin']['login'])
password = sources['admin']['password'] or 'xxx'
- cnx = in_memory_cnx(repo, login, password=password)
+ cnx = _repo_connect(repo, login, password=password)
return cnx
def get_repo_and_cnx(self, db_id=DEFAULT_EMPTY_DB_ID):
@@ -466,7 +464,6 @@
``pre_setup_func`` to setup the database.
This function backup any database it build"""
-
if self.has_cache(test_db_id):
return #test_db_id, 'already in cache'
if test_db_id is DEFAULT_EMPTY_DB_ID:
@@ -723,7 +720,7 @@
dbfile = self.absolute_dbfile()
self._cleanup_database(dbfile)
shutil.copy(backup_coordinates, dbfile)
- repo = self.get_repo()
+ self.get_repo()
def init_test_database(self):
"""initialize a fresh sqlite databse used for testing purpose"""
--- a/devtools/devctl.py Thu Jan 24 16:13:40 2013 +0100
+++ b/devtools/devctl.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -91,15 +91,10 @@
if mod.__file__ is None:
# odd/rare but real
continue
- for path in config.vregistry_path():
+ for path in config.appobjects_path():
if mod.__file__.startswith(path):
del sys.modules[name]
break
- # fresh rtags
- from cubicweb import rtags
- from cubicweb.web import uicfg
- rtags.RTAGS[:] = []
- reload(uicfg)
def generate_schema_pot(w, cubedir=None):
"""generate a pot file with schema specific i18n messages
@@ -129,7 +124,6 @@
def _generate_schema_pot(w, vreg, schema, libconfig=None):
from copy import deepcopy
from cubicweb.i18n import add_msg
- from cubicweb.web import uicfg
from cubicweb.schema import NO_I18NCONTEXT, CONSTRAINTS
w('# schema pot file, generated on %s\n'
% datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
@@ -138,22 +132,21 @@
w('\n')
vregdone = set()
if libconfig is not None:
- from cubicweb.cwvreg import CWRegistryStore, clear_rtag_objects
+ from cubicweb.cwvreg import CWRegistryStore
libschema = libconfig.load_schema(remove_unused_rtypes=False)
- afs = deepcopy(uicfg.autoform_section)
- appearsin_addmenu = deepcopy(uicfg.actionbox_appearsin_addmenu)
- clear_rtag_objects()
+ afs = vreg['uicfg'].select('autoform_section')
+ appearsin_addmenu = vreg['uicfg'].select('actionbox_appearsin_addmenu')
cleanup_sys_modules(libconfig)
libvreg = CWRegistryStore(libconfig)
libvreg.set_schema(libschema) # trigger objects registration
- libafs = uicfg.autoform_section
- libappearsin_addmenu = uicfg.actionbox_appearsin_addmenu
+ libafs = libvreg['uicfg'].select('autoform_section')
+ libappearsin_addmenu = libvreg['uicfg'].select('actionbox_appearsin_addmenu')
# prefill vregdone set
list(_iter_vreg_objids(libvreg, vregdone))
else:
libschema = {}
- afs = uicfg.autoform_section
- appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
+ afs = vreg['uicfg'].select('autoform_section')
+ appearsin_addmenu = vreg['uicfg'].select('actionbox_appearsin_addmenu')
for cstrtype in CONSTRAINTS:
add_msg(w, cstrtype)
done = set()
@@ -302,10 +295,11 @@
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import globfind, find, rm
from logilab.common.modutils import get_module_files
- from cubicweb.i18n import extract_from_tal, execute
+ from cubicweb.i18n import extract_from_tal, execute2
tempdir = tempfile.mkdtemp(prefix='cw-')
cwi18ndir = WebConfiguration.i18n_lib_dir()
- print '-> extract schema messages.'
+ print '-> extract messages:',
+ print 'schema',
schemapot = osp.join(tempdir, 'schema.pot')
potfiles = [schemapot]
potfiles.append(schemapot)
@@ -314,7 +308,7 @@
schemapotstream = file(schemapot, 'w')
generate_schema_pot(schemapotstream.write, cubedir=None)
schemapotstream.close()
- print '-> extract TAL messages.'
+ print 'TAL',
tali18nfile = osp.join(tempdir, 'tali18n.py')
extract_from_tal(find(osp.join(BASEDIR, 'web'), ('.py', '.pt')),
tali18nfile)
@@ -329,26 +323,29 @@
('tal', [tali18nfile], None),
('js', jsfiles, 'java'),
]:
- cmd = 'xgettext --no-location --omit-header -k_ -o %s %s'
+ potfile = osp.join(tempdir, '%s.pot' % id)
+ cmd = ['xgettext', '--no-location', '--omit-header', '-k_']
if lang is not None:
- cmd += ' -L %s' % lang
- potfile = osp.join(tempdir, '%s.pot' % id)
- execute(cmd % (potfile, ' '.join('"%s"' % f for f in files)))
+ cmd.extend(['-L', lang])
+ cmd.extend(['-o', potfile])
+ cmd.extend(files)
+ execute2(cmd)
if osp.exists(potfile):
potfiles.append(potfile)
else:
print '-> WARNING: %s file was not generated' % potfile
print '-> merging %i .pot files' % len(potfiles)
cubicwebpot = osp.join(tempdir, 'cubicweb.pot')
- execute('msgcat -o %s %s'
- % (cubicwebpot, ' '.join('"%s"' % f for f in potfiles)))
+ cmd = ['msgcat', '-o', cubicwebpot] + potfiles
+ execute2(cmd)
print '-> merging main pot file with existing translations.'
chdir(cwi18ndir)
toedit = []
for lang in CubicWebNoAppConfiguration.cw_languages():
target = '%s.po' % lang
- execute('msgmerge -N --sort-output -o "%snew" "%s" "%s"'
- % (target, target, cubicwebpot))
+ cmd = ['msgmerge', '-N', '--sort-output', '-o',
+ target+'new', target, cubicwebpot]
+ execute2(cmd)
ensure_fs_mode(target)
shutil.move('%snew' % target, target)
toedit.append(osp.abspath(target))
@@ -382,16 +379,21 @@
def update_cubes_catalogs(cubes):
+ from subprocess import CalledProcessError
for cubedir in cubes:
if not osp.isdir(cubedir):
print '-> ignoring %s that is not a directory.' % cubedir
continue
try:
toedit = update_cube_catalogs(cubedir)
+ except CalledProcessError, exc:
+ print '\n*** error while updating catalogs for cube', cubedir
+ print 'cmd:\n%s' % exc.cmd
+ print 'stdout:\n%s\nstderr:\n%s' % exc.data
except Exception:
import traceback
traceback.print_exc()
- print '-> error while updating catalogs for cube', cubedir
+ print '*** error while updating catalogs for cube', cubedir
return False
else:
# instructions pour la suite
@@ -408,7 +410,7 @@
import tempfile
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import find, rm
- from cubicweb.i18n import extract_from_tal, execute
+ from cubicweb.i18n import extract_from_tal, execute2
cube = osp.basename(osp.normpath(cubedir))
tempdir = tempfile.mkdtemp()
print underline_title('Updating i18n catalogs for cube %s' % cube)
@@ -421,7 +423,8 @@
potfiles = [osp.join('i18n', 'static-messages.pot')]
else:
potfiles = []
- print '-> extract schema messages'
+ print '-> extracting messages:',
+ print 'schema',
schemapot = osp.join(tempdir, 'schema.pot')
potfiles.append(schemapot)
# explicit close necessary else the file may not be yet flushed when
@@ -429,50 +432,55 @@
schemapotstream = file(schemapot, 'w')
generate_schema_pot(schemapotstream.write, cubedir)
schemapotstream.close()
- print '-> extract TAL messages'
+ print 'TAL',
tali18nfile = osp.join(tempdir, 'tali18n.py')
ptfiles = find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',))
extract_from_tal(ptfiles, tali18nfile)
- print '-> extract Javascript messages'
+ print 'Javascript'
jsfiles = [jsfile for jsfile in find('.', '.js')
if osp.basename(jsfile).startswith('cub')]
if jsfiles:
tmppotfile = osp.join(tempdir, 'js.pot')
- execute('xgettext --no-location --omit-header -k_ -L java '
- '--from-code=utf-8 -o %s %s' % (tmppotfile, ' '.join(jsfiles)))
+ cmd = ['xgettext', '--no-location', '--omit-header', '-k_', '-L', 'java',
+ '--from-code=utf-8', '-o', tmppotfile] + jsfiles
+ execute2(cmd)
# no pot file created if there are no string to translate
if osp.exists(tmppotfile):
potfiles.append(tmppotfile)
- print '-> create cube-specific catalog'
+ print '-> creating cube-specific catalog'
tmppotfile = osp.join(tempdir, 'generated.pot')
cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
cubefiles.append(tali18nfile)
- execute('xgettext --no-location --omit-header -k_ -o %s %s'
- % (tmppotfile, ' '.join('"%s"' % f for f in cubefiles)))
+ cmd = ['xgettext', '--no-location', '--omit-header', '-k_', '-o', tmppotfile]
+ cmd.extend(cubefiles)
+ execute2(cmd)
if osp.exists(tmppotfile): # doesn't exists of no translation string found
potfiles.append(tmppotfile)
potfile = osp.join(tempdir, 'cube.pot')
- print '-> merging %i .pot files:' % len(potfiles)
- execute('msgcat -o %s %s' % (potfile,
- ' '.join('"%s"' % f for f in potfiles)))
+ print '-> merging %i .pot files' % len(potfiles)
+ cmd = ['msgcat', '-o', potfile]
+ cmd.extend(potfiles)
+ execute2(cmd)
if not osp.exists(potfile):
print 'no message catalog for cube', cube, 'nothing to translate'
# cleanup
rm(tempdir)
return ()
- print '-> merging main pot file with existing translations:'
+ print '-> merging main pot file with existing translations:',
chdir('i18n')
toedit = []
for lang in CubicWebNoAppConfiguration.cw_languages():
- print '-> language', lang
+ print lang,
cubepo = '%s.po' % lang
if not osp.exists(cubepo):
shutil.copy(potfile, cubepo)
else:
- execute('msgmerge -N -s -o %snew %s %s' % (cubepo, cubepo, potfile))
+ cmd = ['msgmerge','-N','-s','-o', cubepo+'new', cubepo, potfile]
+ execute2(cmd)
ensure_fs_mode(cubepo)
shutil.move('%snew' % cubepo, cubepo)
toedit.append(osp.abspath(cubepo))
+ print
# cleanup
rm(tempdir)
return toedit
--- a/devtools/fake.py Thu Jan 24 16:13:40 2013 +0100
+++ b/devtools/fake.py Fri Jan 25 14:33:40 2013 +0100
@@ -155,6 +155,12 @@
def set_entity_cache(self, entity):
pass
+ def security_enabled(self, read=False, write=False):
+ class FakeCM(object):
+ def __enter__(self): pass
+ def __exit__(self, exctype, exc, traceback): pass
+ return FakeCM()
+
# for use with enabled_security context manager
read_security = write_security = True
def init_security(self, *args):
--- a/devtools/repotest.py Thu Jan 24 16:13:40 2013 +0100
+++ b/devtools/repotest.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/devtools/testlib.py Thu Jan 24 16:13:40 2013 +0100
+++ b/devtools/testlib.py Fri Jan 25 14:33:40 2013 +0100
@@ -48,7 +48,7 @@
from cubicweb.utils import json
from cubicweb.sobjects import notification
from cubicweb.web import Redirect, application
-from cubicweb.server.session import Session, security_enabled
+from cubicweb.server.session import Session
from cubicweb.server.hook import SendMailOp
from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
from cubicweb.devtools import BASE_URL, fake, htmlparser, DEFAULT_EMPTY_DB_ID
@@ -403,7 +403,7 @@
autoclose = kwargs.pop('autoclose', True)
if not kwargs:
kwargs['password'] = str(login)
- self.set_cnx(dbapi.repo_connect(self.repo, unicode(login), **kwargs))
+ self.set_cnx(dbapi._repo_connect(self.repo, unicode(login), **kwargs))
self.websession = dbapi.DBAPISession(self.cnx)
if login == self.vreg.config.anonymous_user()[0]:
self.cnx.anonymous_connection = True
@@ -451,7 +451,7 @@
finally:
self.session.set_cnxset() # ensure cnxset still set after commit
- # # server side db api #######################################################
+ # server side db api #######################################################
def sexecute(self, rql, args=None, eid_key=None):
if eid_key is not None:
@@ -1060,7 +1060,7 @@
"""this method populates the database with `how_many` entities
of each possible type. It also inserts random relations between them
"""
- with security_enabled(self.session, read=False, write=False):
+ with self.session.security_enabled(read=False, write=False):
self._auto_populate(how_many)
def _auto_populate(self, how_many):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/3.16.rst Fri Jan 25 14:33:40 2013 +0100
@@ -0,0 +1,97 @@
+What's new in CubicWeb 3.16?
+============================
+
+New functionalities
+--------------------
+
+* Add a new dataimport store (`SQLGenObjectStore`). This store enables a fast
+ import of data (entity creation, link creation) in CubicWeb, by directly
+ flushing information in SQL. This may only be used with PostgreSQL, as it
+ requires the 'COPY FROM' command.
+
+
+API changes
+-----------
+
+* Orm: `set_attributes` and `set_relations` are unified (and
+ deprecated) in favor of `cw_set` that works in all cases.
+
+* db-api/configuration: all the external repository connection information is
+ now in an URL (see `#2521848 <http://www.cubicweb.org/2521848>`_),
+ allowing to drop specific options of pyro nameserver host, group, etc and fix
+ broken `ZMQ <http://www.zeromq.org/>`_ source. Configuration related changes:
+
+ * Dropped 'pyro-ns-host', 'pyro-instance-id', 'pyro-ns-group' from the client side
+ configuration, in favor of 'repository-uri'. **NO MIGRATION IS DONE**,
+ supposing there is no web-only configuration in the wild.
+
+ * Stop discovering the connection method through `repo_method` class attribute
+ of the configuration, varying according to the configuration class. This is
+ a first step on the way to a simpler configuration handling.
+
+ DB-API related changes:
+
+ * Stop indicating the connection method using `ConnectionProperties`.
+
+ * Drop `_cnxtype` attribute from `Connection` and `cnxtype` from
+ `Session`. The former is replaced by a `is_repo_in_memory` property
+ and the later is totaly useless.
+
+ * Turn `repo_connect` into `_repo_connect` to mark it as a private function.
+
+ * Deprecate `in_memory_cnx` which becomes useless, use `_repo_connect` instead
+ if necessary.
+
+* the "tcp://" uri scheme used for `ZMQ <http://www.zeromq.org/>`_
+ communications (in a way reminiscent of Pyro) is now named
+ "zmqpickle-tcp://", so as to make room for future zmq-based lightweight
+ communications (without python objects pickling).
+
+* Request.base_url gets a `secure=True` optional parameter that yields
+ an https url if possible, allowing hook-generated content to send
+ secure urls (e.g. when sending mail notifications)
+
+* Dataimport ucsvreader gets a new boolean `ignore_errors`
+ parameter.
+
+
+Unintrusive API changes
+-----------------------
+
+* Drop of `cubicweb.web.uicfg.AutoformSectionRelationTags.bw_tag_map`,
+ deprecated since 3.6.
+
+
+User interface changes
+----------------------
+
+* The RQL search bar has now some auto-completion support. It means
+ relation types or entity types can be suggested while typing. It is
+ an awesome improvement over the current behaviour !
+
+* The `action box` associated with `table` views (from `tableview.py`)
+ has been transformed into a nice-looking series of small tabs; it
+ means that the possible actions are immediately visible and need not
+ be discovered by clicking on an almost invisible icon on the upper
+ right.
+
+* The `uicfg` module has moved to web/views/ and ui configuration
+ objects are now selectable. This will reduce the amount of
+ subclassing and whole methods replacement usually needed to
+ customize the ui behaviour in many cases.
+
+* Remove changelog view, as neither cubicweb nor known
+ cubes/applications were properly feeding related files.
+
+
+Other changes
+-------------
+
+* 'pyrorql' sources will be automatically updated to use an URL to locate the source
+ rather than configuration option. 'zmqrql' sources were broken before this change,
+ so no upgrade is needed...
+
+* Debugging filters for Hooks and Operations have been added.
+
+* Some cubicweb-ctl commands used to show the output of `msgcat` and
+ `msgfmt`; they don't anymore.
--- a/doc/book/en/annexes/faq.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/annexes/faq.rst Fri Jan 25 14:33:40 2013 +0100
@@ -364,7 +364,7 @@
>>> crypted = crypt_password('joepass')
>>> rset = rql('Any U WHERE U is CWUser, U login "joe"')
>>> joe = rset.get_entity(0,0)
- >>> joe.set_attributes(upassword=Binary(crypted))
+ >>> joe.cw_set(upassword=Binary(crypted))
Please, refer to the script example is provided in the `misc/examples/chpasswd.py` file.
--- a/doc/book/en/annexes/rql/debugging.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/annexes/rql/debugging.rst Fri Jan 25 14:33:40 2013 +0100
@@ -15,6 +15,8 @@
.. autodata:: cubicweb.server.DBG_SQL
.. autodata:: cubicweb.server.DBG_REPO
.. autodata:: cubicweb.server.DBG_MS
+.. autodata:: cubicweb.server.DBG_HOOKS
+.. autodata:: cubicweb.server.DBG_OPS
.. autodata:: cubicweb.server.DBG_MORE
.. autodata:: cubicweb.server.DBG_ALL
@@ -31,6 +33,14 @@
.. autofunction:: cubicweb.server.set_debug
+Another example showing how to debug hooks at a specific code site:
+
+.. sourcecode:: python
+
+ from cubicweb.server import debuged, DBG_HOOKS
+ with debugged(DBG_HOOKS):
+ person.cw_set(works_for=company)
+
Detect largest RQL queries
~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/dataimport.rst Fri Jan 25 14:33:40 2013 +0100
@@ -0,0 +1,58 @@
+. -*- coding: utf-8 -*-
+
+.. _dataimport:
+
+Dataimport
+==========
+
+*CubicWeb* is designed to manipulate huge of amount of data, and provides helper functions to do so.
+These functions insert data within different levels of the *CubicWeb* API,
+allowing different speed/security tradeoffs. Those keeping all the *CubicWeb* hooks
+and security will be slower but the possible errors in insertion
+(bad data types, integrity error, ...) will be raised.
+
+These dataimport function are provided in the file `dataimport.py`.
+
+All the stores have the following API::
+
+ >>> store = ObjectStore()
+ >>> user = store.create_entity('CWUser', login=u'johndoe')
+ >>> group = store.create_entity('CWUser', name=u'unknown')
+ >>> store.relate(user.eid, 'in_group', group.eid)
+
+
+ObjectStore
+-----------
+
+This store keeps objects in memory for *faster* validation. It may be useful
+in development mode. However, as it will not enforce the constraints of the schema,
+it may miss some problems.
+
+
+
+RQLObjectStore
+--------------
+
+This store works with an actual RQL repository, and it may be used in production mode.
+
+
+NoHookRQLObjectStore
+--------------------
+
+This store works similarly to the *RQLObjectStore* but bypasses some *CubicWeb* hooks to be faster.
+
+
+SQLGenObjectStore
+-----------------
+
+This store relies on *COPY FROM*/execute many sql commands to directly push data using SQL commands
+rather than using the whole *CubicWeb* API. For now, **it only works with PostgresSQL** as it requires
+the *COPY FROM* command.
+
+The API is similar to the other stores, but **it requires a flush** after some imports to copy data
+in the database (these flushes may be multiples through the processes, or be done only once at the
+end if there is no memory issue)::
+
+ >>> store = SQLGenObjectStore(session)
+ >>> store.create_entity('Person', ...)
+ >>> store.flush()
--- a/doc/book/en/devrepo/entityclasses/application-logic.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/devrepo/entityclasses/application-logic.rst Fri Jan 25 14:33:40 2013 +0100
@@ -38,7 +38,7 @@
object was built.
Setting an attribute or relation value can be done in the context of a
-Hook/Operation, using the obj.set_relations(x=42) notation or a plain
+Hook/Operation, using the obj.cw_set(x=42) notation or a plain
RQL SET expression.
In views, it would be preferable to encapsulate the necessary logic in
--- a/doc/book/en/devrepo/entityclasses/data-as-objects.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/devrepo/entityclasses/data-as-objects.rst Fri Jan 25 14:33:40 2013 +0100
@@ -16,50 +16,47 @@
`Formatting and output generation`:
-* `view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
+* :meth:`view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
(and returns an unicode string)
-* `absolute_url(*args, **kwargs)`, returns an absolute URL including the base-url
+* :meth:`absolute_url(*args, **kwargs)`, returns an absolute URL including the base-url
-* `rest_path()`, returns a relative REST URL to get the entity
+* :meth:`rest_path()`, returns a relative REST URL to get the entity
-* `printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
+* :meth:`printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
returns a string enabling the display of an attribute value in a given format
(the value is automatically recovered if necessary)
`Data handling`:
-* `as_rset()`, converts the entity into an equivalent result set simulating the
+* :meth:`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 at
+* :meth:`complete(skip_bytes=True)`, executes a request that recovers at
once all the missing attributes of an entity
-* `get_value(name)`, returns the value associated to the attribute name given
+* :meth:`get_value(name)`, returns the value associated to the attribute name given
in parameter
-* `related(rtype, role='subject', limit=None, entities=False)`,
+* :meth:`related(rtype, role='subject', limit=None, entities=False)`,
returns a list of entities related to the current entity by the
relation given in parameter
-* `unrelated(rtype, targettype, role='subject', limit=None)`,
+* :meth:`unrelated(rtype, targettype, role='subject', limit=None)`,
returns a result set corresponding to the entities not (yet)
related to the current entity by the relation given in parameter
and satisfying its constraints
-* `set_attributes(**kwargs)`, updates the attributes list with the corresponding
- values given named parameters
+* :meth:`cw_set(**kwargs)`, updates entity's attributes and/or relation with the
+ corresponding values given named parameters. To set a relation where this
+ entity is the object of the relation, use `reverse_<relation>` as argument
+ name. Values may be an entity, a list of entities, or None (meaning that all
+ relations of the given type from or to this object should be deleted).
-* `set_relations(**kwargs)`, add relations to the given object. To
- set a relation where this entity is the object of the relation,
- use `reverse_<relation>` as argument name. Values may be an
- entity, a list of entities, or None (meaning that all relations of
- the given type from or to this object should be deleted).
-
-* `copy_relations(ceid)`, copies the relations of the entities having the eid
+* :meth:`copy_relations(ceid)`, copies the relations of the entities having the eid
given in the parameters on the current entity
-* `delete()` allows to delete the entity
+* :meth:`cw_delete()` allows to delete the entity
The :class:`AnyEntity` class
@@ -81,40 +78,30 @@
`Standard meta-data (Dublin Core)`:
-* `dc_title()`, returns a unicode string corresponding to the
+* :meth:`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
+* :meth:`dc_long_title()`, same as dc_title but can return a more
detailed title
-* `dc_description(format='text/plain')`, returns a unicode string
+* :meth:`dc_description(format='text/plain')`, returns a unicode string
corresponding to the meta-data `Description` (looks for a
description attribute by default)
-* `dc_authors()`, returns a unicode string corresponding to the meta-data
+* :meth:`dc_authors()`, returns a unicode string corresponding to the meta-data
`Authors` (owners by default)
-* `dc_creator()`, returns a unicode string corresponding to the
+* :meth:`dc_creator()`, returns a unicode string corresponding to the
creator of the entity
-* `dc_date(date_format=None)`, returns a unicode string corresponding to
+* :meth:`dc_date(date_format=None)`, returns a unicode string corresponding to
the meta-data `Date` (update date by default)
-* `dc_type(form='')`, returns a string to display the entity type by
+* :meth:`dc_type(form='')`, returns a string to display the entity type by
specifying the preferred form (`plural` for a plural form)
-* `dc_language()`, returns the language used by the entity
-
-
-`Misc methods`:
-
-* `after_deletion_path`, return (path, parameters) which should be
- used as redirect information when this entity is being deleted
-
-* `pre_web_edit`, callback called by the web editcontroller when an
- entity will be created/modified, to let a chance to do some entity
- specific stuff (does nothing by default)
+* :meth:`dc_language()`, returns the language used by the entity
Inheritance
-----------
--- a/doc/book/en/devrepo/repo/hooks.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/devrepo/repo/hooks.rst Fri Jan 25 14:33:40 2013 +0100
@@ -195,9 +195,9 @@
self._cw.repo.app_instances_bus.publish(['hello', 'world'])
The `zmq-address-pub` configuration variable contains the address used
-by the instance for sending messages, e.g. `tcp://*:1234`. The
+by the instance for sending messages, e.g. `zmqpickle-tcp://*:1234`. The
`zmq-address-sub` variable contains a comma-separated list of addresses
-to listen on, e.g. `tcp://localhost:1234, tcp://192.168.1.1:2345`.
+to listen on, e.g. `zmqpickle-tcp://localhost:1234, zmqpickle-tcp://192.168.1.1:2345`.
Hooks writing tips
@@ -206,10 +206,11 @@
Reminder
~~~~~~~~
-You should never use the `entity.foo = 42` notation to update an
-entity. It will not do what you expect (updating the
-database). Instead, use the :meth:`set_attributes` and
-:meth:`set_relations` methods.
+You should never use the `entity.foo = 42` notation to update an entity. It will
+not do what you expect (updating the database). Instead, use the
+:meth:`~cubicweb.entity.Entity.cw_set` method or direct access to entity's
+:attr:`cw_edited` attribute if you're writing a hook for 'before_add_entity' or
+'before_update_entity' event.
How to choose between a before and an after event ?
--- a/doc/book/en/devrepo/testing.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/devrepo/testing.rst Fri Jan 25 14:33:40 2013 +0100
@@ -70,13 +70,13 @@
def test_cannot_create_cycles(self):
# direct obvious cycle
- self.assertRaises(ValidationError, self.kw1.set_relations,
+ self.assertRaises(ValidationError, self.kw1.cw_set,
subkeyword_of=self.kw1)
# testing indirect cycles
kw3 = self.execute('INSERT Keyword SK: SK name "kwgroup2", SK included_in C, '
'SK subkeyword_of K WHERE C name "classif1", K eid %s'
% self.kw1.eid).get_entity(0,0)
- self.kw1.set_relations(subkeyword_of=kw3)
+ self.kw1.cw_set(subkeyword_of=kw3)
self.assertRaises(ValidationError, self.commit)
The test class defines a :meth:`setup_database` method which populates the
@@ -192,10 +192,10 @@
description=u'cubicweb is beautiful')
blog_entry_1 = req.create_entity('BlogEntry', title=u'hop',
content=u'cubicweb hop')
- blog_entry_1.set_relations(entry_of=cubicweb_blog)
+ blog_entry_1.cw_set(entry_of=cubicweb_blog)
blog_entry_2 = req.create_entity('BlogEntry', title=u'yes',
content=u'cubicweb yes')
- blog_entry_2.set_relations(entry_of=cubicweb_blog)
+ blog_entry_2.cw_set(entry_of=cubicweb_blog)
self.assertEqual(len(MAILBOX), 0)
self.commit()
self.assertEqual(len(MAILBOX), 2)
--- a/doc/book/en/devweb/index.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/devweb/index.rst Fri Jan 25 14:33:40 2013 +0100
@@ -10,6 +10,7 @@
publisher
controllers
request
+ searchbar
views/index
rtags
ajax
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/searchbar.rst Fri Jan 25 14:33:40 2013 +0100
@@ -0,0 +1,41 @@
+.. _searchbar:
+
+RQL search bar
+--------------
+
+The RQL search bar is a visual component, hidden by default, the tiny *search*
+input being enough for common use cases.
+
+An autocompletion helper is provided to help you type valid queries, both
+in terms of syntax and in terms of schema validity.
+
+.. autoclass:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder
+
+
+How search is performed
++++++++++++++++++++++++
+
+You can use the *rql search bar* to either type RQL queries, plain text queries
+or standard shortcuts such as *<EntityType>* or *<EntityType> <attrname> <value>*.
+
+Ultimately, all queries are translated to rql since it's the only
+language understood on the server (data) side. To transform the user
+query into RQL, CubicWeb uses the so-called *magicsearch component*,
+defined in :mod:`cubicweb.web.views.magicsearch`, which in turn
+delegates to a number of query preprocessor that are responsible of
+interpreting the user query and generating corresponding RQL.
+
+The code of the main processor loop is easy to understand:
+
+.. sourcecode:: python
+
+ for proc in self.processors:
+ try:
+ return proc.process_query(uquery, req)
+ except (RQLSyntaxError, BadRQLQuery):
+ pass
+
+The idea is simple: for each query processor, try to translate the
+query. If it fails, try with the next processor, if it succeeds,
+we're done and the RQL query will be executed.
+
--- a/doc/book/en/devweb/views/boxes.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/devweb/views/boxes.rst Fri Jan 25 14:33:40 2013 +0100
@@ -15,7 +15,7 @@
which the box is displayed). By default, the links generated in this
box are computed from the schema properties of the displayed entity,
but it is possible to explicitly specify them thanks to the
-`cubicweb.web.uicfg.rmode` *relation tag*:
+`cubicweb.web.views.uicfg.rmode` *relation tag*:
* `link`, indicates that a relation is in general created pointing
to an existing entity and that we should not to display a link
--- a/doc/book/en/devweb/views/primary.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/devweb/views/primary.rst Fri Jan 25 14:33:40 2013 +0100
@@ -51,7 +51,7 @@
.. sourcecode:: python
- from cubicweb.web import uicfg
+ from cubicweb.web.views import uicfg
uicfg.primaryview_section.tag_attribute(('Blog', 'title'), 'hidden')
**Relations** can be either displayed in one of the three sections or hidden.
--- a/doc/book/en/devweb/views/reledit.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/devweb/views/reledit.rst Fri Jan 25 14:33:40 2013 +0100
@@ -68,7 +68,7 @@
The behaviour of reledited attributes/relations can be finely
controlled using the reledit_ctrl rtag, defined in
-:mod:`cubicweb.web.uicfg`.
+:mod:`cubicweb.web.views.uicfg`.
This rtag provides four control variables:
@@ -93,7 +93,7 @@
.. sourcecode:: python
from logilab.mtconverter import xml_escape
- from cubicweb.web.uicfg import reledit_ctrl
+ from cubicweb.web.views.uicfg import reledit_ctrl
reledit_ctrl.tag_attribute(('Company', 'name'),
{'reload': lambda x:x.eid,
'default_value': xml_escape(u'<logilab tastes better>')})
@@ -125,7 +125,7 @@
.. sourcecode:: python
- import uicfg.primaryview_display_ctrl as _pvdc
+ from cubicweb.web.views.uicfg import primaryview_display_ctrl as _pvdc
_pvdc.tag_attribute(('Company', 'name'), {'vid': 'incontext'})
To deactivate it everywhere it's used automatically, you may use the code snippet
--- a/doc/book/en/tutorials/advanced/part02_security.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part02_security.rst Fri Jan 25 14:33:40 2013 +0100
@@ -196,7 +196,7 @@
for eid in self.get_data():
entity = self.session.entity_from_eid(eid)
if entity.visibility == 'parent':
- entity.set_attributes(visibility=u'authenticated')
+ entity.cw_set(visibility=u'authenticated')
class SetVisibilityHook(hook.Hook):
__regid__ = 'sytweb.setvisibility'
@@ -215,7 +215,7 @@
parent = self._cw.entity_from_eid(self.eidto)
child = self._cw.entity_from_eid(self.eidfrom)
if child.visibility == 'parent':
- child.set_attributes(visibility=parent.visibility)
+ child.cw_set(visibility=parent.visibility)
Notice:
@@ -344,7 +344,7 @@
self.assertEquals(len(req.execute('Folder X')), 0) # restricted...
# may_be_read_by propagation
self.restore_connection()
- folder.set_relations(may_be_read_by=toto)
+ folder.cw_set(may_be_read_by=toto)
self.commit()
photo1.clear_all_caches()
self.failUnless(photo1.may_be_read_by)
--- a/doc/book/en/tutorials/advanced/part04_ui-base.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst Fri Jan 25 14:33:40 2013 +0100
@@ -294,6 +294,7 @@
folder in which the current file (e.g. `self.entity`) is located.
.. Note::
+
The :class:`IBreadCrumbs` interface is a `breadcrumbs` method, but the default
:class:`IBreadCrumbsAdapter` provides a default implementation for it that will look
at the value returned by its `parent_entity` method. It also provides a
@@ -331,6 +332,7 @@
navigate through the web site to see if everything is ok...
.. Note::
+
In the 'cubicweb-ctl i18ncube' command, `sytweb` refers to the **cube**, while
in the two other, it refers to the **instance** (if you can't see the
difference, reread CubicWeb's concept chapter !).
@@ -363,4 +365,4 @@
.. _`several improvments`: http://www.cubicweb.org/blogentry/1179899
.. _`3.8`: http://www.cubicweb.org/blogentry/917107
.. _`first blog of this series`: http://www.cubicweb.org/blogentry/824642
-.. _`an earlier post`: http://www.cubicweb.org/867464
\ No newline at end of file
+.. _`an earlier post`: http://www.cubicweb.org/867464
--- a/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Fri Jan 25 14:33:40 2013 +0100
@@ -215,7 +215,7 @@
from logilab.common.decorators import monkeypatch
from cubicweb import ValidationError
- from cubicweb.web import uicfg, component
+ from cubicweb.web.views import uicfg, component
from cubicweb.web.views import basecontrollers
# hide displayed_on relation using uicfg since it will be displayed by the box below
--- a/doc/book/en/tutorials/base/customizing-the-application.rst Thu Jan 24 16:13:40 2013 +0100
+++ b/doc/book/en/tutorials/base/customizing-the-application.rst Fri Jan 25 14:33:40 2013 +0100
@@ -493,7 +493,7 @@
entering the migration python shell
just type migration commands or arbitrary python code and type ENTER to execute it
type "exit" or Ctrl-D to quit the shell and resume operation
- >>> add_cubes('comment', 'tag')
+ >>> add_cubes(('comment', 'tag'))
>>>
Then restart the instance. Let's look at a blog entry:
--- a/entities/authobjs.py Thu Jan 24 16:13:40 2013 +0100
+++ b/entities/authobjs.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -77,6 +77,19 @@
self._properties = dict((p.pkey, p.value) for p in self.reverse_for_user)
return self._properties
+ def prefered_language(self, language=None):
+ """return language used by this user, if explicitly defined (eg not
+ using http negociation)
+ """
+ language = language or self.property_value('ui.language')
+ vreg = self._cw.vreg
+ try:
+ vreg.config.translations[language]
+ except KeyError:
+ language = vreg.property_value('ui.language')
+ assert language in vreg.config.translations[language], language
+ return language
+
def property_value(self, key):
try:
# properties stored on the user aren't correctly typed
@@ -101,7 +114,7 @@
kwargs['for_user'] = self
self._cw.create_entity('CWProperty', **kwargs)
else:
- prop.set_attributes(value=value)
+ prop.cw_set(value=value)
def matching_groups(self, groups):
"""return the number of the given group(s) in which the user is
--- a/entities/sources.py Thu Jan 24 16:13:40 2013 +0100
+++ b/entities/sources.py Fri Jan 25 14:33:40 2013 +0100
@@ -51,7 +51,7 @@
continue
raise
cfgstr = unicode(generate_source_config(sconfig), self._cw.encoding)
- self.set_attributes(config=cfgstr)
+ self.cw_set(config=cfgstr)
class CWSource(_CWSourceCfgMixIn, AnyEntity):
@@ -181,5 +181,5 @@
def write_log(self, session, **kwargs):
if 'status' not in kwargs:
kwargs['status'] = getattr(self, '_status', u'success')
- self.set_attributes(log=u'<br/>'.join(self._logs), **kwargs)
+ self.cw_set(log=u'<br/>'.join(self._logs), **kwargs)
self._logs = []
--- a/entities/test/unittest_base.py Thu Jan 24 16:13:40 2013 +0100
+++ b/entities/test/unittest_base.py Fri Jan 25 14:33:40 2013 +0100
@@ -70,7 +70,7 @@
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)
- email1.set_relations(prefered_form=email2)
+ email1.cw_set(prefered_form=email2)
self.assertEqual(email1.prefered.eid, email2.eid)
self.assertEqual(email2.prefered.eid, email2.eid)
self.assertEqual(email3.prefered.eid, email3.eid)
@@ -104,10 +104,10 @@
e = self.execute('CWUser U WHERE U login "member"').get_entity(0, 0)
self.assertEqual(e.dc_title(), 'member')
self.assertEqual(e.name(), 'member')
- e.set_attributes(firstname=u'bouah')
+ e.cw_set(firstname=u'bouah')
self.assertEqual(e.dc_title(), 'member')
self.assertEqual(e.name(), u'bouah')
- e.set_attributes(surname=u'lôt')
+ e.cw_set(surname=u'lôt')
self.assertEqual(e.dc_title(), 'member')
self.assertEqual(e.name(), u'bouah lôt')
--- a/entities/test/unittest_wfobjs.py Thu Jan 24 16:13:40 2013 +0100
+++ b/entities/test/unittest_wfobjs.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -20,7 +20,6 @@
from cubicweb import ValidationError
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.server.session import security_enabled
def add_wf(self, etype, name=None, default=False):
@@ -64,7 +63,7 @@
# gnark gnark
bar = wf.add_state(u'bar')
self.commit()
- bar.set_attributes(name=u'foo')
+ bar.cw_set(name=u'foo')
with self.assertRaises(ValidationError) as cm:
self.commit()
self.assertEqual({'name-subject': 'workflow already has a state of that name'},
@@ -88,7 +87,7 @@
# gnark gnark
biz = wf.add_transition(u'biz', (bar,), foo)
self.commit()
- biz.set_attributes(name=u'baz')
+ biz.cw_set(name=u'baz')
with self.assertRaises(ValidationError) as cm:
self.commit()
self.assertEqual(cm.exception.errors, {'name-subject': 'workflow already have a transition of that name'})
@@ -128,8 +127,9 @@
self.assertEqual(trs[0].destination(None).name, u'deactivated')
# test a std user get no possible transition
cnx = self.login('member')
+ req = self.request()
# fetch the entity using the new session
- trs = list(cnx.user().cw_adapt_to('IWorkflowable').possible_transitions())
+ trs = list(req.user.cw_adapt_to('IWorkflowable').possible_transitions())
self.assertEqual(len(trs), 0)
cnx.close()
@@ -156,7 +156,7 @@
wf = add_wf(self, 'CWUser')
s = wf.add_state(u'foo', initial=True)
self.commit()
- with security_enabled(self.session, write=False):
+ with self.session.security_enabled(write=False):
with self.assertRaises(ValidationError) as cm:
self.session.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
{'x': self.user().eid, 's': s.eid})
@@ -175,7 +175,7 @@
def test_goback_transition(self):
req = self.request()
- wf = self.session.user.cw_adapt_to('IWorkflowable').current_workflow
+ wf = req.user.cw_adapt_to('IWorkflowable').current_workflow
asleep = wf.add_state('asleep')
wf.add_transition('rest', (wf.state_by_name('activated'),
wf.state_by_name('deactivated')),
@@ -518,7 +518,7 @@
['rest'])
self.assertEqual(parse_hist(iworkflowable.workflow_history),
[('asleep', 'asleep', 'rest', None)])
- user.set_attributes(surname=u'toto') # fulfill condition
+ user.cw_set(surname=u'toto') # fulfill condition
self.commit()
iworkflowable.fire_transition('rest')
self.commit()
@@ -558,13 +558,12 @@
def setUp(self):
CubicWebTC.setUp(self)
- self.wf = self.session.user.cw_adapt_to('IWorkflowable').current_workflow
- self.session.set_cnxset()
+ req = self.request()
+ self.wf = req.user.cw_adapt_to('IWorkflowable').current_workflow
self.s_activated = self.wf.state_by_name('activated').eid
self.s_deactivated = self.wf.state_by_name('deactivated').eid
self.s_dummy = self.wf.add_state(u'dummy').eid
self.wf.add_transition(u'dummy', (self.s_deactivated,), self.s_dummy)
- req = self.request()
ueid = self.create_user(req, 'stduser', commit=False).eid
# test initial state is set
rset = self.execute('Any N WHERE S name N, X in_state S, X eid %(x)s',
@@ -625,23 +624,22 @@
cnx.close()
def test_transition_checking3(self):
- cnx = self.login('stduser')
- session = self.session
- user = cnx.user(session)
- iworkflowable = user.cw_adapt_to('IWorkflowable')
- iworkflowable.fire_transition('deactivate')
- cnx.commit()
- session.set_cnxset()
- with self.assertRaises(ValidationError) as cm:
+ with self.login('stduser') as cnx:
+ session = self.session
+ user = self.user()
+ iworkflowable = user.cw_adapt_to('IWorkflowable')
iworkflowable.fire_transition('deactivate')
- self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
- u"transition isn't allowed from")
- cnx.rollback()
- session.set_cnxset()
- # get back now
- iworkflowable.fire_transition('activate')
- cnx.commit()
- cnx.close()
+ session.commit()
+ session.set_cnxset()
+ with self.assertRaises(ValidationError) as cm:
+ iworkflowable.fire_transition('deactivate')
+ self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
+ u"transition isn't allowed from")
+ session.rollback()
+ session.set_cnxset()
+ # get back now
+ iworkflowable.fire_transition('activate')
+ session.commit()
if __name__ == '__main__':
--- a/entity.py Thu Jan 24 16:13:40 2013 +0100
+++ b/entity.py Fri Jan 25 14:33:40 2013 +0100
@@ -20,6 +20,7 @@
__docformat__ = "restructuredtext en"
from warnings import warn
+from functools import partial
from logilab.common import interface
from logilab.common.decorators import cached
@@ -452,26 +453,13 @@
return mainattr, needcheck
@classmethod
- def cw_instantiate(cls, execute, **kwargs):
- """add a new entity of this given type
-
- Example (in a shell session):
-
- >>> companycls = vreg['etypes'].etype_class(('Company')
- >>> personcls = vreg['etypes'].etype_class(('Person')
- >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
- >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
- ... works_for=c)
-
- You can also set relation where the entity has 'object' role by
- prefixing the relation by 'reverse_'.
- """
- rql = 'INSERT %s X' % cls.__regid__
+ def _cw_build_entity_query(cls, kwargs):
relations = []
restrictions = set()
- pending_relations = []
+ pendingrels = []
eschema = cls.e_schema
qargs = {}
+ attrcache = {}
for attr, value in kwargs.items():
if attr.startswith('reverse_'):
attr = attr[len('reverse_'):]
@@ -487,10 +475,13 @@
value = iter(value).next()
else:
# prepare IN clause
- pending_relations.append( (attr, role, value) )
+ pendingrels.append( (attr, role, value) )
continue
if rschema.final: # attribute
relations.append('X %s %%(%s)s' % (attr, attr))
+ attrcache[attr] = value
+ elif value is None:
+ pendingrels.append( (attr, role, value) )
else:
rvar = attr.upper()
if role == 'object':
@@ -503,19 +494,51 @@
if hasattr(value, 'eid'):
value = value.eid
qargs[attr] = value
+ rql = u''
if relations:
- rql = '%s: %s' % (rql, ', '.join(relations))
+ rql += ', '.join(relations)
if restrictions:
- rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
- created = execute(rql, qargs).get_entity(0, 0)
- for attr, role, values in pending_relations:
+ rql += ' WHERE %s' % ', '.join(restrictions)
+ return rql, qargs, pendingrels, attrcache
+
+ @classmethod
+ def _cw_handle_pending_relations(cls, eid, pendingrels, execute):
+ for attr, role, values in pendingrels:
if role == 'object':
restr = 'Y %s X' % attr
else:
restr = 'X %s Y' % attr
+ if values is None:
+ execute('DELETE %s WHERE X eid %%(x)s' % restr, {'x': eid})
+ continue
execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
restr, ','.join(str(getattr(r, 'eid', r)) for r in values)),
- {'x': created.eid}, build_descr=False)
+ {'x': eid}, build_descr=False)
+
+ @classmethod
+ def cw_instantiate(cls, execute, **kwargs):
+ """add a new entity of this given type
+
+ Example (in a shell session):
+
+ >>> companycls = vreg['etypes'].etype_class(('Company')
+ >>> personcls = vreg['etypes'].etype_class(('Person')
+ >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
+ >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
+ ... works_for=c)
+
+ You can also set relations where the entity has 'object' role by
+ prefixing the relation name by 'reverse_'. Also, relation values may be
+ an entity or eid, a list of entities or eids.
+ """
+ rql, qargs, pendingrels, attrcache = cls._cw_build_entity_query(kwargs)
+ if rql:
+ rql = 'INSERT %s X: %s' % (cls.__regid__, rql)
+ else:
+ rql = 'INSERT %s X' % (cls.__regid__)
+ created = execute(rql, qargs).get_entity(0, 0)
+ created._cw_update_attr_cache(attrcache)
+ cls._cw_handle_pending_relations(created.eid, pendingrels, execute)
return created
def __init__(self, req, rset=None, row=None, col=0):
@@ -535,6 +558,45 @@
def __cmp__(self, other):
raise NotImplementedError('comparison not implemented for %s' % self.__class__)
+ def _cw_update_attr_cache(self, attrcache):
+ # if context is a repository session, don't consider dont-cache-attrs as
+ # the instance already hold modified values and loosing them could
+ # introduce severe problems
+ get_set = partial(self._cw.get_shared_data, default=(), txdata=True,
+ pop=True)
+ uncached_attrs = set()
+ uncached_attrs.update(get_set('%s.storage-special-process-attrs' % self.eid))
+ if self._cw.is_request:
+ uncached_attrs.update(get_set('%s.dont-cache-attrs' % self.eid))
+ for attr in uncached_attrs:
+ attrcache.pop(attr, None)
+ self.cw_attr_cache.pop(attr, None)
+ self.cw_attr_cache.update(attrcache)
+
+ def _cw_dont_cache_attribute(self, attr, repo_side=False):
+ """Repository side method called when some attribute has been
+ transformed by a hook, hence original value should not be cached by
+ the client.
+
+ If repo_side is True, this means that the attribute has been
+ transformed by a *storage*, hence the original value should
+ not be cached **by anyone**.
+
+ This only applies to a storage special case where the value
+ specified in creation or update is **not** the value that will
+ be transparently exposed later.
+
+ For example we have a special "fs_importing" mode in BFSS
+ where a file path is given as attribute value and stored as is
+ in the data base. Later access to the attribute will provide
+ the content of the file at the specified path. We do not want
+ the "filepath" value to be cached.
+ """
+ self._cw.transaction_data.setdefault('%s.dont-cache-attrs' % self.eid, set()).add(attr)
+ if repo_side:
+ trdata = self._cw.transaction_data
+ trdata.setdefault('%s.storage-special-process-attrs' % self.eid, set()).add(attr)
+
def __json_encode__(self):
"""custom json dumps hook to dump the entity's eid
which is not part of dict structure itself
@@ -1215,54 +1277,41 @@
# raw edition utilities ###################################################
- def set_attributes(self, **kwargs): # XXX cw_set_attributes
+ def cw_set(self, **kwargs):
+ """update this entity using given attributes / relation, working in the
+ same fashion as :meth:`cw_instantiate`.
+
+ Example (in a shell session):
+
+ >>> c = rql('Any X WHERE X is Company').get_entity(0, 0)
+ >>> p = rql('Any X WHERE X is Person').get_entity(0, 0)
+ >>> c.set(name=u'Logilab')
+ >>> p.set(firstname=u'John', lastname=u'Doe', works_for=c)
+
+ You can also set relations where the entity has 'object' role by
+ prefixing the relation name by 'reverse_'. Also, relation values may be
+ an entity or eid, a list of entities or eids, or None (meaning that all
+ relations of the given type from or to this object should be deleted).
+ """
_check_cw_unsafe(kwargs)
assert kwargs
assert self.cw_is_saved(), "should not call set_attributes while entity "\
"hasn't been saved yet"
- relations = ['X %s %%(%s)s' % (key, key) for key in kwargs]
- # and now update the database
- kwargs['x'] = self.eid
- self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
- kwargs)
- kwargs.pop('x')
+ rql, qargs, pendingrels, attrcache = self._cw_build_entity_query(kwargs)
+ if rql:
+ rql = 'SET ' + rql
+ qargs['x'] = self.eid
+ if ' WHERE ' in rql:
+ rql += ', X eid %(x)s'
+ else:
+ rql += ' WHERE X eid %(x)s'
+ self._cw.execute(rql, qargs)
# update current local object _after_ the rql query to avoid
# interferences between the query execution itself and the cw_edited /
# skip_security machinery
- self.cw_attr_cache.update(kwargs)
-
- def set_relations(self, **kwargs): # XXX cw_set_relations
- """add relations to the given object. To set a relation where this entity
- is the object of the relation, use 'reverse_'<relation> as argument name.
-
- Values may be an entity or eid, a list of entities or eids, or None
- (meaning that all relations of the given type from or to this object
- should be deleted).
- """
- # XXX update cache
- _check_cw_unsafe(kwargs)
- for attr, values in kwargs.iteritems():
- if attr.startswith('reverse_'):
- restr = 'Y %s X' % attr[len('reverse_'):]
- else:
- restr = 'X %s Y' % attr
- if values is None:
- self._cw.execute('DELETE %s WHERE X eid %%(x)s' % restr,
- {'x': self.eid})
- continue
- if not isinstance(values, (tuple, list, set, frozenset)):
- values = (values,)
- eids = []
- for val in values:
- try:
- eids.append(str(val.eid))
- except AttributeError:
- try:
- eids.append(str(typed_eid(val)))
- except (ValueError, TypeError):
- raise Exception('expected an Entity or eid, got %s' % val)
- self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
- restr, ','.join(eids)), {'x': self.eid})
+ self._cw_update_attr_cache(attrcache)
+ self._cw_handle_pending_relations(self.eid, pendingrels, self._cw.execute)
+ # XXX update relation cache
def cw_delete(self, **kwargs):
assert self.has_eid(), self.eid
@@ -1277,6 +1326,21 @@
# deprecated stuff #########################################################
+ @deprecated('[3.16] use cw_set() instead')
+ def set_attributes(self, **kwargs): # XXX cw_set_attributes
+ self.cw_set(**kwargs)
+
+ @deprecated('[3.16] use cw_set() instead')
+ def set_relations(self, **kwargs): # XXX cw_set_relations
+ """add relations to the given object. To set a relation where this entity
+ is the object of the relation, use 'reverse_'<relation> as argument name.
+
+ Values may be an entity or eid, a list of entities or eids, or None
+ (meaning that all relations of the given type from or to this object
+ should be deleted).
+ """
+ self.cw_set(**kwargs)
+
@deprecated('[3.13] use entity.cw_clear_all_caches()')
def clear_all_caches(self):
return self.cw_clear_all_caches()
--- a/etwist/server.py Thu Jan 24 16:13:40 2013 +0100
+++ b/etwist/server.py Fri Jan 25 14:33:40 2013 +0100
@@ -85,7 +85,7 @@
config = self.config
# when we have an in-memory repository, clean unused sessions every XX
# seconds and properly shutdown the server
- if config.repo_method == 'inmemory':
+ if config['repository-uri'] == 'inmemory://':
if config.pyro_enabled():
# if pyro is enabled, we have to register to the pyro name
# server, create a pyro daemon, and create a task to handle pyro
--- a/etwist/twconfig.py Thu Jan 24 16:13:40 2013 +0100
+++ b/etwist/twconfig.py Fri Jan 25 14:33:40 2013 +0100
@@ -115,7 +115,6 @@
class AllInOneConfiguration(TwistedConfiguration, ServerConfiguration):
"""repository and web instance in the same twisted process"""
name = 'all-in-one'
- repo_method = 'inmemory'
options = merge_options(TwistedConfiguration.options
+ ServerConfiguration.options)
--- a/ext/tal.py Thu Jan 24 16:13:40 2013 +0100
+++ b/ext/tal.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -261,7 +261,7 @@
return wrapped
def _compiled_template(self, instance):
- for fileordirectory in instance.config.vregistry_path():
+ for fileordirectory in instance.config.appobjects_path():
filepath = join(fileordirectory, self.filename)
if isdir(fileordirectory) and exists(filepath):
return compile_template_file(filepath)
--- a/hooks/bookmark.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/bookmark.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/hooks/email.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/email.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -15,9 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""hooks to ensure use_email / primary_email relations consistency
+"""hooks to ensure use_email / primary_email relations consistency"""
-"""
__docformat__ = "restructuredtext en"
from cubicweb.server import hook
--- a/hooks/integrity.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/integrity.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -20,12 +20,11 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from threading import Lock
-from yams.schema import role_name
-
-from cubicweb import ValidationError
+from cubicweb import validation_error
from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES,
RQLConstraint, RQLUniqueConstraint)
from cubicweb.predicates import is_instance
@@ -87,11 +86,11 @@
continue
if not session.execute(self.base_rql % rtype, {'x': eid}):
etype = session.describe(eid)[0]
- _ = session._
msg = _('at least one relation %(rtype)s is required on '
'%(etype)s (%(eid)s)')
- msg %= {'rtype': _(rtype), 'etype': _(etype), 'eid': eid}
- raise ValidationError(eid, {role_name(rtype, self.role): msg})
+ raise validation_error(eid, {(rtype, self.role): msg},
+ {'rtype': rtype, 'etype': etype, 'eid': eid},
+ ['rtype', 'etype'])
class _CheckSRelationOp(_CheckRequiredRelationOperation):
@@ -231,9 +230,9 @@
rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
rset = self._cw.execute(rql, {'val': val})
if rset and rset[0][0] != entity.eid:
- msg = self._cw._('the value "%s" is already used, use another one')
- qname = role_name(attr, 'subject')
- raise ValidationError(entity.eid, {qname: msg % val})
+ msg = _('the value "%s" is already used, use another one')
+ raise validation_error(entity, {(attr, 'subject'): msg},
+ (val,))
class DontRemoveOwnersGroupHook(IntegrityHook):
@@ -246,15 +245,12 @@
def __call__(self):
entity = self.entity
if self.event == 'before_delete_entity' and entity.name == 'owners':
- msg = self._cw._('can\'t be deleted')
- raise ValidationError(entity.eid, {None: msg})
+ raise validation_error(entity, {None: _("can't be deleted")})
elif self.event == 'before_update_entity' \
and 'name' in entity.cw_edited:
oldname, newname = entity.cw_edited.oldnewvalue('name')
if oldname == 'owners' and newname != oldname:
- qname = role_name('name', 'subject')
- msg = self._cw._('can\'t be changed')
- raise ValidationError(entity.eid, {qname: msg})
+ raise validation_error(entity, {('name', 'subject'): _("can't be changed")})
class TidyHtmlFields(IntegrityHook):
--- a/hooks/metadata.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/metadata.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/hooks/syncschema.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/syncschema.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -24,6 +24,7 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from copy import copy
from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
@@ -31,7 +32,7 @@
from logilab.common.decorators import clear_cache
-from cubicweb import ValidationError
+from cubicweb import validation_error
from cubicweb.predicates import is_instance
from cubicweb.schema import (SCHEMA_TYPES, META_RTYPES, VIRTUAL_RTYPES,
CONSTRAINTS, ETYPE_NAME_MAP, display_name)
@@ -127,10 +128,9 @@
if attr in ro_attrs:
origval, newval = entity.cw_edited.oldnewvalue(attr)
if newval != origval:
- errors[attr] = session._("can't change the %s attribute") % \
- display_name(session, attr)
+ errors[attr] = _("can't change this attribute")
if errors:
- raise ValidationError(entity.eid, errors)
+ raise validation_error(entity, errors)
class _MockEntity(object): # XXX use a named tuple with python 2.6
@@ -913,7 +913,7 @@
# final entities can't be deleted, don't care about that
name = self.entity.name
if name in CORE_TYPES:
- raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+ raise validation_error(self.entity, {None: _("can't be deleted")})
# delete every entities of this type
if name not in ETYPE_NAME_MAP:
self._cw.execute('DELETE %s X' % name)
@@ -983,7 +983,7 @@
def __call__(self):
name = self.entity.name
if name in CORE_TYPES:
- raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+ raise validation_error(self.entity, {None: _("can't be deleted")})
# delete relation definitions using this relation type
self._cw.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',
{'x': self.entity.eid})
--- a/hooks/syncsession.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/syncsession.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -18,9 +18,9 @@
"""Core hooks: synchronize living session on persistent data changes"""
__docformat__ = "restructuredtext en"
+_ = unicode
-from yams.schema import role_name
-from cubicweb import UnknownProperty, ValidationError, BadConnectionId
+from cubicweb import UnknownProperty, BadConnectionId, validation_error
from cubicweb.predicates import is_instance
from cubicweb.server import hook
@@ -165,13 +165,11 @@
try:
value = session.vreg.typed_value(key, value)
except UnknownProperty:
- qname = role_name('pkey', 'subject')
- msg = session._('unknown property key %s') % key
- raise ValidationError(self.entity.eid, {qname: msg})
+ msg = _('unknown property key %s')
+ raise validation_error(self.entity, {('pkey', 'subject'): msg}, (key,))
except ValueError, ex:
- qname = role_name('value', 'subject')
- raise ValidationError(self.entity.eid,
- {qname: session._(str(ex))})
+ raise validation_error(self.entity,
+ {('value', 'subject'): str(ex)})
if not session.user.matching_groups('managers'):
session.add_relation(self.entity.eid, 'for_user', session.user.eid)
else:
@@ -196,8 +194,7 @@
except UnknownProperty:
return
except ValueError, ex:
- qname = role_name('value', 'subject')
- raise ValidationError(entity.eid, {qname: session._(str(ex))})
+ raise validation_error(entity, {('value', 'subject'): str(ex)})
if entity.for_user:
for session_ in get_user_sessions(session.repo, entity.for_user[0].eid):
_ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
@@ -237,10 +234,8 @@
key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
{'x': eidfrom})[0]
if session.vreg.property_info(key)['sitewide']:
- qname = role_name('for_user', 'subject')
- msg = session._("site-wide property can't be set for user")
- raise ValidationError(eidfrom,
- {qname: msg})
+ msg = _("site-wide property can't be set for user")
+ raise validation_error(eidfrom, {('for_user', 'subject'): msg})
for session_ in get_user_sessions(session.repo, self.eidto):
_ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
key=key, value=value)
--- a/hooks/syncsources.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/syncsources.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2010-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -17,12 +17,13 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""hooks for repository sources synchronization"""
+_ = unicode
+
from socket import gethostname
from logilab.common.decorators import clear_cache
-from yams.schema import role_name
-from cubicweb import ValidationError
+from cubicweb import validation_error
from cubicweb.predicates import is_instance
from cubicweb.server import SOURCE_TYPES, hook
@@ -46,12 +47,15 @@
try:
sourcecls = SOURCE_TYPES[self.entity.type]
except KeyError:
- msg = self._cw._('unknown source type')
- raise ValidationError(self.entity.eid,
- {role_name('type', 'subject'): msg})
- sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
- fail_if_unknown=not self._cw.vreg.config.repairing)
- SourceAddedOp(self._cw, entity=self.entity)
+ msg = _('Unknown source type')
+ raise validation_error(self.entity, {('type', 'subject'): msg})
+ # ignore creation of the system source done during database
+ # initialisation, as config for this source is in a file and handling
+ # is done separatly (no need for the operation either)
+ if self.entity.name != 'system':
+ sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
+ fail_if_unknown=not self._cw.vreg.config.repairing)
+ SourceAddedOp(self._cw, entity=self.entity)
class SourceRemovedOp(hook.Operation):
@@ -65,7 +69,8 @@
events = ('before_delete_entity',)
def __call__(self):
if self.entity.name == 'system':
- raise ValidationError(self.entity.eid, {None: 'cant remove system source'})
+ msg = _("You cannot remove the system source")
+ raise validation_error(self.entity, {None: msg})
SourceRemovedOp(self._cw, uri=self.entity.name)
@@ -116,11 +121,18 @@
__select__ = SourceHook.__select__ & is_instance('CWSource')
events = ('before_update_entity',)
def __call__(self):
- if 'config' in self.entity.cw_edited:
- SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
if 'name' in self.entity.cw_edited:
oldname, newname = self.entity.cw_edited.oldnewvalue('name')
+ if oldname == 'system':
+ msg = _("You cannot rename the system source")
+ raise validation_error(self.entity, {('name', 'subject'): msg})
SourceRenamedOp(self._cw, oldname=oldname, newname=newname)
+ if 'config' in self.entity.cw_edited:
+ if self.entity.name == 'system' and self.entity.config:
+ msg = _("Configuration of the system source goes to "
+ "the 'sources' file, not in the database")
+ raise validation_error(self.entity, {('config', 'subject'): msg})
+ SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
class SourceHostConfigUpdatedHook(SourceHook):
@@ -154,8 +166,8 @@
events = ('before_add_relation',)
def __call__(self):
if not self._cw.added_in_transaction(self.eidfrom):
- msg = self._cw._("can't change this relation")
- raise ValidationError(self.eidfrom, {self.rtype: msg})
+ msg = _("You can't change this relation")
+ raise validation_error(self.eidfrom, {self.rtype: msg})
class SourceMappingChangedOp(hook.DataOperationMixIn, hook.Operation):
--- a/hooks/test/unittest_hooks.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/test/unittest_hooks.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -67,10 +67,9 @@
entity = self.request().create_entity('Workflow', name=u'wf1',
description_format=u'text/html',
description=u'yo')
- entity.set_attributes(name=u'wf2')
+ entity.cw_set(name=u'wf2')
self.assertEqual(entity.description, u'yo')
- entity.set_attributes(description=u'R&D<p>yo')
- entity.cw_attr_cache.pop('description')
+ entity.cw_set(description=u'R&D<p>yo')
self.assertEqual(entity.description, u'R&D<p>yo</p>')
def test_metadata_cwuri(self):
@@ -166,13 +165,12 @@
self.execute, 'INSERT CWRType X: X name "in_group"')
def test_validation_unique_constraint(self):
- self.assertRaises(ValidationError,
- self.execute, 'INSERT CWUser X: X login "admin"')
- try:
+ with self.assertRaises(ValidationError) as cm:
self.execute('INSERT CWUser X: X login "admin"')
- except ValidationError, ex:
- self.assertIsInstance(ex.entity, int)
- self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
+ ex = cm.exception
+ ex.translate(unicode)
+ self.assertIsInstance(ex.entity, int)
+ self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
if __name__ == '__main__':
--- a/hooks/test/unittest_syncschema.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/test/unittest_syncschema.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -294,7 +294,7 @@
def test_change_fulltext_container(self):
req = self.request()
target = req.create_entity(u'EmailAddress', address=u'rick.roll@dance.com')
- target.set_relations(reverse_use_email=req.user)
+ target.cw_set(reverse_use_email=req.user)
self.commit()
rset = req.execute('Any X WHERE X has_text "rick.roll"')
self.assertIn(req.user.eid, [item[0] for item in rset])
--- a/hooks/test/unittest_syncsession.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/test/unittest_syncsession.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -31,9 +31,11 @@
def test_unexistant_cwproperty(self):
with self.assertRaises(ValidationError) as cm:
self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
with self.assertRaises(ValidationError) as cm:
self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
def test_site_wide_cwproperty(self):
--- a/hooks/workflow.py Thu Jan 24 16:13:40 2013 +0100
+++ b/hooks/workflow.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -18,12 +18,12 @@
"""Core hooks: workflow related hooks"""
__docformat__ = "restructuredtext en"
+_ = unicode
from datetime import datetime
-from yams.schema import role_name
-from cubicweb import RepositoryError, ValidationError
+from cubicweb import RepositoryError, validation_error
from cubicweb.predicates import is_instance, adaptable
from cubicweb.server import hook
@@ -92,9 +92,8 @@
if mainwf.eid == self.wfeid:
deststate = mainwf.initial
if not deststate:
- qname = role_name('custom_workflow', 'subject')
- msg = session._('workflow has no initial state')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('workflow has no initial state')
+ raise validation_error(entity, {('custom_workflow', 'subject'): msg})
if mainwf.state_by_eid(iworkflowable.current_state.eid):
# nothing to do
return
@@ -119,9 +118,8 @@
outputs = set()
for ep in tr.subworkflow_exit:
if ep.subwf_state.eid in outputs:
- qname = role_name('subworkflow_exit', 'subject')
- msg = self.session._("can't have multiple exits on the same state")
- raise ValidationError(self.treid, {qname: msg})
+ msg = _("can't have multiple exits on the same state")
+ raise validation_error(self.treid, {('subworkflow_exit', 'subject'): msg})
outputs.add(ep.subwf_state.eid)
@@ -137,13 +135,12 @@
wftr = iworkflowable.subworkflow_input_transition()
if wftr is None:
# inconsistency detected
- qname = role_name('to_state', 'subject')
- msg = session._("state doesn't belong to entity's current workflow")
- raise ValidationError(self.trinfo.eid, {'to_state': msg})
+ msg = _("state doesn't belong to entity's current workflow")
+ raise validation_error(self.trinfo, {('to_state', 'subject'): msg})
tostate = wftr.get_exit_point(forentity, trinfo.cw_attr_cache['to_state'])
if tostate is not None:
# reached an exit point
- msg = session._('exiting from subworkflow %s')
+ msg = _('exiting from subworkflow %s')
msg %= session._(iworkflowable.current_workflow.name)
session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
iworkflowable.change_state(tostate, msg, u'text/plain', tr=wftr)
@@ -186,9 +183,8 @@
try:
foreid = entity.cw_attr_cache['wf_info_for']
except KeyError:
- qname = role_name('wf_info_for', 'subject')
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('mandatory relation')
+ raise validation_error(entity, {('wf_info_for', 'subject'): msg})
forentity = session.entity_from_eid(foreid)
# see comment in the TrInfo entity definition
entity.cw_edited['tr_count']=len(forentity.reverse_wf_info_for)
@@ -201,13 +197,13 @@
else:
wf = iworkflowable.current_workflow
if wf is None:
- msg = session._('related entity has no workflow set')
- raise ValidationError(entity.eid, {None: msg})
+ msg = _('related entity has no workflow set')
+ raise validation_error(entity, {None: msg})
# then check it has a state set
fromstate = iworkflowable.current_state
if fromstate is None:
- msg = session._('related entity has no state')
- raise ValidationError(entity.eid, {None: msg})
+ msg = _('related entity has no state')
+ raise validation_error(entity, {None: msg})
# True if we are coming back from subworkflow
swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
cowpowers = (session.user.is_in_group('managers')
@@ -219,47 +215,42 @@
# no transition set, check user is a manager and destination state
# is specified (and valid)
if not cowpowers:
- qname = role_name('by_transition', 'subject')
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('mandatory relation')
+ raise validation_error(entity, {('by_transition', 'subject'): msg})
deststateeid = entity.cw_attr_cache.get('to_state')
if not deststateeid:
- qname = role_name('by_transition', 'subject')
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('mandatory relation')
+ raise validation_error(entity, {('by_transition', 'subject'): msg})
deststate = wf.state_by_eid(deststateeid)
if deststate is None:
- qname = role_name('to_state', 'subject')
- msg = session._("state doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("state doesn't belong to entity's workflow")
+ raise validation_error(entity, {('to_state', 'subject'): msg})
else:
# check transition is valid and allowed, unless we're coming back
# from subworkflow
tr = session.entity_from_eid(treid)
if swtr is None:
- qname = role_name('by_transition', 'subject')
+ qname = ('by_transition', 'subject')
if tr is None:
- msg = session._("transition doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition doesn't belong to entity's workflow")
+ raise validation_error(entity, {qname: msg})
if not tr.has_input_state(fromstate):
- msg = session._("transition %(tr)s isn't allowed from %(st)s") % {
- 'tr': session._(tr.name), 'st': session._(fromstate.name)}
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition %(tr)s isn't allowed from %(st)s")
+ raise validation_error(entity, {qname: msg}, {
+ 'tr': tr.name, 'st': fromstate.name}, ['tr', 'st'])
if not tr.may_be_fired(foreid):
- msg = session._("transition may not be fired")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition may not be fired")
+ raise validation_error(entity, {qname: msg})
deststateeid = entity.cw_attr_cache.get('to_state')
if deststateeid is not None:
if not cowpowers and deststateeid != tr.destination(forentity).eid:
- qname = role_name('by_transition', 'subject')
- msg = session._("transition isn't allowed")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition isn't allowed")
+ raise validation_error(entity, {('by_transition', 'subject'): msg})
if swtr is None:
deststate = session.entity_from_eid(deststateeid)
if not cowpowers and deststate is None:
- qname = role_name('to_state', 'subject')
- msg = session._("state doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("state doesn't belong to entity's workflow")
+ raise validation_error(entity, {('to_state', 'subject'): msg})
else:
deststateeid = tr.destination(forentity).eid
# everything is ok, add missing information on the trinfo entity
@@ -307,20 +298,18 @@
iworkflowable = entity.cw_adapt_to('IWorkflowable')
mainwf = iworkflowable.main_workflow
if mainwf is None:
- msg = session._('entity has no workflow set')
- raise ValidationError(entity.eid, {None: msg})
+ msg = _('entity has no workflow set')
+ raise validation_error(entity, {None: msg})
for wf in mainwf.iter_workflows():
if wf.state_by_eid(self.eidto):
break
else:
- qname = role_name('in_state', 'subject')
- msg = session._("state doesn't belong to entity's workflow. You may "
- "want to set a custom workflow for this entity first.")
- raise ValidationError(self.eidfrom, {qname: msg})
+ msg = _("state doesn't belong to entity's workflow. You may "
+ "want to set a custom workflow for this entity first.")
+ raise validation_error(self.eidfrom, {('in_state', 'subject'): msg})
if iworkflowable.current_workflow and wf.eid != iworkflowable.current_workflow.eid:
- qname = role_name('in_state', 'subject')
- msg = session._("state doesn't belong to entity's current workflow")
- raise ValidationError(self.eidfrom, {qname: msg})
+ msg = _("state doesn't belong to entity's current workflow")
+ raise validation_error(self.eidfrom, {('in_state', 'subject'): msg})
class SetModificationDateOnStateChange(WorkflowHook):
@@ -335,7 +324,7 @@
return
entity = self._cw.entity_from_eid(self.eidfrom)
try:
- entity.set_attributes(modification_date=datetime.now())
+ entity.cw_set(modification_date=datetime.now())
except RepositoryError, ex:
# usually occurs if entity is coming from a read-only source
# (eg ldap user)
--- a/i18n.py Thu Jan 24 16:13:40 2013 +0100
+++ b/i18n.py Fri Jan 25 14:33:40 2013 +0100
@@ -54,19 +54,16 @@
w('msgid "%s"\n' % msgid[0])
w('msgstr ""\n\n')
-
-def execute(cmd):
- """display the command, execute it and raise an Exception if returned
- status != 0
- """
- from subprocess import call
- # use getcwdu as cmd may be unicode and cwd may contains non-ascii
- # characters
- print cmd.replace(os.getcwdu() + os.sep, '')
- status = call(cmd, shell=True)
- if status != 0:
- raise Exception('status = %s' % status)
-
+def execute2(args):
+ # XXX replace this with check_output in Python 2.7
+ from subprocess import Popen, PIPE, CalledProcessError
+ p = Popen(args, stdout=PIPE, stderr=PIPE)
+ out, err = p.communicate()
+ if p.returncode != 0:
+ exc = CalledProcessError(p.returncode, args[0])
+ exc.cmd = args
+ exc.data = (out, err)
+ raise exc
def available_catalogs(i18ndir=None):
if i18ndir is None:
@@ -81,6 +78,7 @@
def compile_i18n_catalogs(sourcedirs, destdir, langs):
"""generate .mo files for a set of languages into the `destdir` i18n directory
"""
+ from subprocess import CalledProcessError
from logilab.common.fileutils import ensure_fs_mode
print '-> compiling message catalogs to %s' % destdir
errors = []
@@ -93,17 +91,21 @@
mergedpo = join(destdir, '%s_merged.po' % lang)
try:
# merge instance/cubes messages catalogs with the stdlib's one
- execute('msgcat --use-first --sort-output --strict -o "%s" %s'
- % (mergedpo, ' '.join('"%s"' % f for f in pofiles)))
+ cmd = ['msgcat', '--use-first', '--sort-output', '--strict',
+ '-o', mergedpo] + pofiles
+ execute2(cmd)
# make sure the .mo file is writeable and compiles with *msgfmt*
applmo = join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
try:
ensure_fs_mode(applmo)
except OSError:
pass # suppose not exists
- execute('msgfmt "%s" -o "%s"' % (mergedpo, applmo))
- except Exception, ex:
- errors.append('while handling language %s: %s' % (lang, ex))
+ execute2(['msgfmt', mergedpo, '-o', applmo])
+ except CalledProcessError, exc:
+ errors.append(u'while handling language %s:\ncmd:\n%s\nstdout:\n%s\nstderr:\n%s\n' %
+ (lang, exc.cmd, repr(exc.data[0]), repr(exc.data[1])))
+ except Exception, exc:
+ errors.append(u'while handling language %s: %s' % (lang, exc))
try:
# clean everything
os.unlink(mergedpo)
--- a/i18n/de.po Thu Jan 24 16:13:40 2013 +0100
+++ b/i18n/de.po Fri Jan 25 14:33:40 2013 +0100
@@ -50,6 +50,14 @@
msgstr ""
#, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
msgid "%(attr)s set to %(newvalue)s"
msgstr "%(attr)s geändert in %(newvalue)s"
@@ -58,10 +66,6 @@
msgstr "%(attr)s geändert von %(oldvalue)s in %(newvalue)s"
#, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "%(cstr)s Einschränkung verletzt für Wert %(value)r"
-
-#, python-format
msgid "%(etype)s by %(author)s"
msgstr ""
@@ -74,10 +78,6 @@
msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
#, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r entspricht nicht dem regulären Ausdruck %(regexp)r"
-
-#, python-format
msgid "%d days"
msgstr "%d Tage"
@@ -427,6 +427,11 @@
msgid "Click to sort on this column"
msgstr ""
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
#, python-format
msgid "Created %(etype)s : %(entity)s"
msgstr ""
@@ -902,6 +907,9 @@
msgid "UniqueConstraint"
msgstr "eindeutige Einschränkung"
+msgid "Unknown source type"
+msgstr ""
+
msgid "Unreachable objects"
msgstr "unzugängliche Objekte"
@@ -960,6 +968,15 @@
msgid "You can use any of the following substitutions in your text"
msgstr "Sie können die folgenden Ersetzungen in Ihrem Text verwenden:"
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
msgid ""
"You have no access to this view or it can not be used to display the current "
"data."
@@ -1006,9 +1023,6 @@
msgid "abstract base class for transitions"
msgstr "abstrakte Basisklasse für Übergänge"
-msgid "action menu"
-msgstr ""
-
msgid "action(s) on this selection"
msgstr "Aktionen(en) bei dieser Auswahl"
@@ -1392,11 +1406,7 @@
msgid "can't be deleted"
msgstr "kann nicht entfernt werden"
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "Kann das Attribut %s nicht ändern."
-
-msgid "can't change this relation"
+msgid "can't change this attribute"
msgstr ""
#, python-format
@@ -2569,6 +2579,9 @@
msgid "foaf"
msgstr "FOAF"
+msgid "focus on this selection"
+msgstr ""
+
msgid "follow"
msgstr "dem Link folgen"
@@ -2859,8 +2872,8 @@
msgstr "Unzulässiger Wert für Überschrift"
#, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "Wert %(value)s ungültig für den Typ \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr ""
msgid "index this attribute's value in the plain text index"
msgstr "indizieren des Wertes dieses Attributs im Volltext-Index"
@@ -2938,8 +2951,8 @@
msgstr "Ungültige Aktion %r"
#, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "Wert %(value)s ungültig, er muss zwischen %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
msgid "is"
msgstr "vom Typ"
@@ -4174,6 +4187,9 @@
msgid "toggle check boxes"
msgstr "Kontrollkästchen umkehren"
+msgid "toggle filter"
+msgstr "filter verbergen/zeigen"
+
msgid "tr_count"
msgstr ""
@@ -4316,9 +4332,6 @@
msgid "unknown property key %s"
msgstr "Unbekannter Eigentumsschlüssel %s"
-msgid "unknown source type"
-msgstr ""
-
msgid "unknown vocabulary:"
msgstr "Unbekanntes Wörterbuch : "
@@ -4464,15 +4477,7 @@
msgstr "Wert"
#, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
msgstr ""
msgid "value associated to this key is not editable manually"
@@ -4481,11 +4486,11 @@
"werden."
#, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
msgstr ""
#, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
msgstr ""
msgid "vcard"
@@ -4630,6 +4635,12 @@
msgid "you should un-inline relation %s which is supported and may be crossed "
msgstr ""
+#~ msgid "%(cstr)s constraint failed for value %(value)r"
+#~ msgstr "%(cstr)s Einschränkung verletzt für Wert %(value)r"
+
+#~ msgid "%(value)r doesn't match the %(regexp)r regular expression"
+#~ msgstr "%(value)r entspricht nicht dem regulären Ausdruck %(regexp)r"
+
#~ msgid ""
#~ "Can't restore relation %(rtype)s of entity %(eid)s, this relation does "
#~ "not exists anymore in the schema."
@@ -4637,8 +4648,11 @@
#~ "Kann die Relation %(rtype)s der Entität %(eid)s nicht wieder herstellen, "
#~ "diese Relation existiert nicht mehr in dem Schema."
-#~ msgid "log out first"
-#~ msgstr "Melden Sie sich zuerst ab."
-
-#~ msgid "week"
-#~ msgstr "Woche"
+#~ msgid "can't change the %s attribute"
+#~ msgstr "Kann das Attribut %s nicht ändern."
+
+#~ msgid "incorrect value (%(value)s) for type \"%(type)s\""
+#~ msgstr "Wert %(value)s ungültig für den Typ \"%(type)s\""
+
+#~ msgid "invalid value %(value)s, it must be one of %(choices)s"
+#~ msgstr "Wert %(value)s ungültig, er muss zwischen %(choices)s"
--- a/i18n/en.po Thu Jan 24 16:13:40 2013 +0100
+++ b/i18n/en.po Fri Jan 25 14:33:40 2013 +0100
@@ -42,6 +42,14 @@
msgstr ""
#, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
msgid "%(attr)s set to %(newvalue)s"
msgstr ""
@@ -50,10 +58,6 @@
msgstr ""
#, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr ""
-
-#, python-format
msgid "%(etype)s by %(author)s"
msgstr ""
@@ -66,10 +70,6 @@
msgstr ""
#, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr ""
-
-#, python-format
msgid "%d days"
msgstr ""
@@ -405,6 +405,11 @@
msgid "Click to sort on this column"
msgstr ""
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
#, python-format
msgid "Created %(etype)s : %(entity)s"
msgstr ""
@@ -878,6 +883,9 @@
msgid "UniqueConstraint"
msgstr "unique constraint"
+msgid "Unknown source type"
+msgstr ""
+
msgid "Unreachable objects"
msgstr ""
@@ -929,6 +937,15 @@
msgid "You can use any of the following substitutions in your text"
msgstr ""
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
msgid ""
"You have no access to this view or it can not be used to display the current "
"data."
@@ -968,9 +985,6 @@
msgid "abstract base class for transitions"
msgstr ""
-msgid "action menu"
-msgstr ""
-
msgid "action(s) on this selection"
msgstr ""
@@ -1349,11 +1363,7 @@
msgid "can't be deleted"
msgstr ""
-#, python-format
-msgid "can't change the %s attribute"
-msgstr ""
-
-msgid "can't change this relation"
+msgid "can't change this attribute"
msgstr ""
#, python-format
@@ -2516,6 +2526,9 @@
msgid "foaf"
msgstr ""
+msgid "focus on this selection"
+msgstr ""
+
msgid "follow"
msgstr ""
@@ -2788,7 +2801,7 @@
msgstr ""
#, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
msgstr ""
msgid "index this attribute's value in the plain text index"
@@ -2865,7 +2878,7 @@
msgstr ""
#, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
msgstr ""
msgid "is"
@@ -4074,6 +4087,9 @@
msgid "toggle check boxes"
msgstr ""
+msgid "toggle filter"
+msgstr ""
+
msgid "tr_count"
msgstr "transition number"
@@ -4216,9 +4232,6 @@
msgid "unknown property key %s"
msgstr ""
-msgid "unknown source type"
-msgstr ""
-
msgid "unknown vocabulary:"
msgstr ""
@@ -4355,26 +4368,18 @@
msgstr ""
#, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
msgstr ""
msgid "value associated to this key is not editable manually"
msgstr ""
#, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
msgstr ""
#, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
msgstr ""
msgid "vcard"
--- a/i18n/es.po Thu Jan 24 16:13:40 2013 +0100
+++ b/i18n/es.po Fri Jan 25 14:33:40 2013 +0100
@@ -51,6 +51,14 @@
"\"role=subject\" o \"role=object\" debe ser especificado en las opciones"
#, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
msgid "%(attr)s set to %(newvalue)s"
msgstr "%(attr)s modificado a %(newvalue)s"
@@ -59,10 +67,6 @@
msgstr "%(attr)s modificado de %(oldvalue)s a %(newvalue)s"
#, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "el valor %(value)r no satisface la condición %(cstr)s"
-
-#, python-format
msgid "%(etype)s by %(author)s"
msgstr "%(etype)s por %(author)s"
@@ -75,10 +79,6 @@
msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
#, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r no corresponde a la expresión regular %(regexp)r"
-
-#, python-format
msgid "%d days"
msgstr "%d días"
@@ -427,6 +427,11 @@
msgid "Click to sort on this column"
msgstr ""
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
#, python-format
msgid "Created %(etype)s : %(entity)s"
msgstr ""
@@ -905,6 +910,9 @@
msgid "UniqueConstraint"
msgstr "Restricción de Unicidad"
+msgid "Unknown source type"
+msgstr ""
+
msgid "Unreachable objects"
msgstr "Objetos inaccesibles"
@@ -965,6 +973,15 @@
"Puede realizar cualquiera de las siguientes sustituciones en el contenido de "
"su email."
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
msgid ""
"You have no access to this view or it can not be used to display the current "
"data."
@@ -1016,9 +1033,6 @@
msgid "abstract base class for transitions"
msgstr "Clase de base abstracta para la transiciones"
-msgid "action menu"
-msgstr ""
-
msgid "action(s) on this selection"
msgstr "Acción(es) en esta selección"
@@ -1403,12 +1417,8 @@
msgid "can't be deleted"
msgstr "No puede ser eliminado"
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "no puede modificar el atributo %s"
-
-msgid "can't change this relation"
-msgstr "no puede modificar esta relación"
+msgid "can't change this attribute"
+msgstr ""
#, python-format
msgid "can't connect to source %s, some data may be missing"
@@ -2611,6 +2621,9 @@
msgid "foaf"
msgstr "Amigo de un Amigo, FOAF"
+msgid "focus on this selection"
+msgstr ""
+
msgid "follow"
msgstr "Seguir la liga"
@@ -2901,8 +2914,8 @@
msgstr "Valor del Captcha incorrecto"
#, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr ""
msgid "index this attribute's value in the plain text index"
msgstr "Indexar el valor de este atributo en el índice de texto simple"
@@ -2981,8 +2994,8 @@
msgstr "Acción %r invalida"
#, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "Valor %(value)s incorrecto, debe estar entre %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
msgid "is"
msgstr "es"
@@ -4224,6 +4237,9 @@
msgid "toggle check boxes"
msgstr "Cambiar valor"
+msgid "toggle filter"
+msgstr "esconder/mostrar el filtro"
+
msgid "tr_count"
msgstr "n° de transición"
@@ -4366,9 +4382,6 @@
msgid "unknown property key %s"
msgstr "Clave de Propiedad desconocida: %s"
-msgid "unknown source type"
-msgstr "tipo de fuente desconocida"
-
msgid "unknown vocabulary:"
msgstr "Vocabulario desconocido: "
@@ -4514,26 +4527,18 @@
msgstr "Vampr"
#, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
msgstr ""
msgid "value associated to this key is not editable manually"
msgstr "El valor asociado a este elemento no es editable manualmente"
#, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
msgstr ""
#, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
msgstr ""
msgid "vcard"
@@ -4681,6 +4686,12 @@
"usted debe quitar la puesta en línea de la relación %s que es aceptada y "
"puede ser cruzada"
+#~ msgid "%(cstr)s constraint failed for value %(value)r"
+#~ msgstr "el valor %(value)r no satisface la condición %(cstr)s"
+
+#~ msgid "%(value)r doesn't match the %(regexp)r regular expression"
+#~ msgstr "%(value)r no corresponde a la expresión regular %(regexp)r"
+
#~ msgid ""
#~ "Can't restore relation %(rtype)s of entity %(eid)s, this relation does "
#~ "not exists anymore in the schema."
@@ -4688,17 +4699,17 @@
#~ "No puede restaurar la relación %(rtype)s de la entidad %(eid)s, esta "
#~ "relación ya no existe en el esquema."
-#~ msgid "day"
-#~ msgstr "día"
-
-#~ msgid "log out first"
-#~ msgstr "Desconéctese primero"
-
-#~ msgid "month"
-#~ msgstr "mes"
-
-#~ msgid "today"
-#~ msgstr "hoy"
-
-#~ msgid "week"
-#~ msgstr "sem."
+#~ msgid "can't change the %s attribute"
+#~ msgstr "no puede modificar el atributo %s"
+
+#~ msgid "can't change this relation"
+#~ msgstr "no puede modificar esta relación"
+
+#~ msgid "incorrect value (%(value)s) for type \"%(type)s\""
+#~ msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
+
+#~ msgid "invalid value %(value)s, it must be one of %(choices)s"
+#~ msgstr "Valor %(value)s incorrecto, debe estar entre %(choices)s"
+
+#~ msgid "unknown source type"
+#~ msgstr "tipo de fuente desconocida"
--- a/i18n/fr.po Thu Jan 24 16:13:40 2013 +0100
+++ b/i18n/fr.po Fri Jan 25 14:33:40 2013 +0100
@@ -50,6 +50,15 @@
"\"role=subject\" ou \"role=object\" doit être specifié dans les options"
#, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr "la valeur %(KEY-value)r ne satisfait pas la contrainte %(KEY-cstr)s"
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+"%(KEY-value)r ne correspond pas à l'expression régulière %(KEY-regexp)r"
+
+#, python-format
msgid "%(attr)s set to %(newvalue)s"
msgstr "%(attr)s modifié à %(newvalue)s"
@@ -58,10 +67,6 @@
msgstr "%(attr)s modifié de %(oldvalue)s à %(newvalue)s"
#, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "la valeur %(value)r ne satisfait pas la contrainte %(cstr)s"
-
-#, python-format
msgid "%(etype)s by %(author)s"
msgstr "%(etype)s par %(author)s"
@@ -74,10 +79,6 @@
msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
#, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r ne correspond pas à l'expression régulière %(regexp)r"
-
-#, python-format
msgid "%d days"
msgstr "%d jours"
@@ -427,6 +428,11 @@
msgid "Click to sort on this column"
msgstr "Cliquer pour trier sur cette colonne"
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
#, python-format
msgid "Created %(etype)s : %(entity)s"
msgstr "Entité %(etype)s crée : %(entity)s"
@@ -907,6 +913,9 @@
msgid "UniqueConstraint"
msgstr "contrainte d'unicité"
+msgid "Unknown source type"
+msgstr "Type de source inconnue"
+
msgid "Unreachable objects"
msgstr "Objets inaccessibles"
@@ -967,6 +976,15 @@
"Vous pouvez utiliser n'importe quelle substitution parmi la liste suivante "
"dans le contenu de votre courriel."
+msgid "You can't change this relation"
+msgstr "Vous ne pouvez pas modifier cette relation"
+
+msgid "You cannot remove the system source"
+msgstr "Vous ne pouvez pas supprimer la source système"
+
+msgid "You cannot rename the system source"
+msgstr "Vous ne pouvez pas renommer la source système"
+
msgid ""
"You have no access to this view or it can not be used to display the current "
"data."
@@ -1018,9 +1036,6 @@
msgid "abstract base class for transitions"
msgstr "classe de base abstraite pour les transitions"
-msgid "action menu"
-msgstr "actions"
-
msgid "action(s) on this selection"
msgstr "action(s) sur cette sélection"
@@ -1260,7 +1275,7 @@
msgstr "anonyme"
msgid "anyrsetview"
-msgstr "vues \"tous les rset\""
+msgstr "vues pour tout rset"
msgid "april"
msgstr "avril"
@@ -1311,7 +1326,7 @@
msgstr "mauvaise valeur"
msgid "badly formatted url"
-msgstr ""
+msgstr "URL mal formattée"
msgid "base url"
msgstr "url de base"
@@ -1399,7 +1414,7 @@
msgstr "impossible d'interpréter les types d'entités :"
msgid "can only have one url"
-msgstr ""
+msgstr "ne supporte qu'une seule URL"
msgid "can't be changed"
msgstr "ne peut-être modifié"
@@ -1407,12 +1422,8 @@
msgid "can't be deleted"
msgstr "ne peut-être supprimé"
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "ne peut changer l'attribut %s"
-
-msgid "can't change this relation"
-msgstr "ne peut modifier cette relation"
+msgid "can't change this attribute"
+msgstr "cet attribut ne peut pas être modifié"
#, python-format
msgid "can't connect to source %s, some data may be missing"
@@ -2621,6 +2632,9 @@
msgid "foaf"
msgstr "foaf"
+msgid "focus on this selection"
+msgstr "afficher cette sélection"
+
msgid "follow"
msgstr "suivre le lien"
@@ -2909,8 +2923,8 @@
msgstr "valeur de captcha incorrecte"
#, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "valeur %(value)s incorrecte pour le type \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr "la valeur %(KEY-value)s est incorrecte pour le type \"%(KEY-type)s\""
msgid "index this attribute's value in the plain text index"
msgstr "indexer la valeur de cet attribut dans l'index plein texte"
@@ -2989,8 +3003,9 @@
msgstr "action %r invalide"
#, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "valeur %(value)s incorrect, doit être parmi %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
+"la valeur %(KEY-value)s est incorrecte, elle doit être parmi %(KEY-choices)s"
msgid "is"
msgstr "de type"
@@ -4235,7 +4250,10 @@
msgstr "à faire par"
msgid "toggle check boxes"
-msgstr "inverser les cases à cocher"
+msgstr "afficher/masquer les cases à cocher"
+
+msgid "toggle filter"
+msgstr "afficher/masquer le filtre"
msgid "tr_count"
msgstr "n° de transition"
@@ -4379,14 +4397,11 @@
msgid "unknown property key %s"
msgstr "clé de propriété inconnue : %s"
-msgid "unknown source type"
-msgstr "type de source inconnu"
-
msgid "unknown vocabulary:"
msgstr "vocabulaire inconnu : "
msgid "unsupported protocol"
-msgstr ""
+msgstr "protocole non supporté"
msgid "upassword"
msgstr "mot de passe"
@@ -4525,27 +4540,23 @@
msgstr "valeur"
#, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr "la valeur %(value)s doit être %(op)s %(boundary)s"
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr "la valeur %(value)s doit être <= %(boundary)s"
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
-msgstr "la valeur %(value)s doit être >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
+msgstr "la valeur %(KEY-value)s n'est pas %(KEY-op)s %(KEY-boundary)s"
msgid "value associated to this key is not editable manually"
msgstr "la valeur associée à cette clé n'est pas éditable manuellement"
#, python-format
-msgid "value should have maximum size of %s but found %s"
-msgstr "la taille maximum est %s mais cette valeur est de taille %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
+msgstr ""
+"la taille maximum est %(KEY-max)s mais cette valeur est de taille "
+"%(KEY-size)s"
#, python-format
-msgid "value should have minimum size of %s but found %s"
-msgstr "la taille minimum est %s mais cette valeur est de taille %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
+msgstr ""
+"la taille minimum est %(KEY-min)s mais cette valeur est de taille "
+"%(KEY-size)s"
msgid "vcard"
msgstr "vcard"
@@ -4699,6 +4710,9 @@
#~ msgid "day"
#~ msgstr "jour"
+#~ msgid "jump to selection"
+#~ msgstr "afficher cette sélection"
+
#~ msgid "log out first"
#~ msgstr "déconnecter vous d'abord"
--- a/misc/migration/3.10.0_Any.py Thu Jan 24 16:13:40 2013 +0100
+++ b/misc/migration/3.10.0_Any.py Fri Jan 25 14:33:40 2013 +0100
@@ -34,5 +34,5 @@
for x in rql('Any X,XK WHERE X pkey XK, '
'X pkey ~= "boxes.%" OR '
'X pkey ~= "contentnavigation.%"').entities():
- x.set_attributes(pkey=u'ctxcomponents.' + x.pkey.split('.', 1)[1])
+ x.cw_set(pkey=u'ctxcomponents.' + x.pkey.split('.', 1)[1])
--- a/misc/migration/3.11.0_Any.py Thu Jan 24 16:13:40 2013 +0100
+++ b/misc/migration/3.11.0_Any.py Fri Jan 25 14:33:40 2013 +0100
@@ -81,5 +81,5 @@
rset = session.execute('Any V WHERE X is CWProperty, X value V, X pkey %(k)s',
{'k': pkey})
timestamp = int(rset[0][0])
- sourceentity.set_attributes(latest_retrieval=datetime.fromtimestamp(timestamp))
+ sourceentity.cw_set(latest_retrieval=datetime.fromtimestamp(timestamp))
session.execute('DELETE CWProperty X WHERE X pkey %(k)s', {'k': pkey})
--- a/misc/migration/3.14.0_Any.py Thu Jan 24 16:13:40 2013 +0100
+++ b/misc/migration/3.14.0_Any.py Fri Jan 25 14:33:40 2013 +0100
@@ -9,5 +9,5 @@
expression = rqlcstr.value
mainvars = guess_rrqlexpr_mainvars(expression)
yamscstr = CONSTRAINTS[rqlcstr.type](expression, mainvars)
- rqlcstr.set_attributes(value=yamscstr.serialize())
+ rqlcstr.cw_set(value=yamscstr.serialize())
print 'updated', rqlcstr.type, rqlcstr.value.strip()
--- a/misc/migration/3.15.0_Any.py Thu Jan 24 16:13:40 2013 +0100
+++ b/misc/migration/3.15.0_Any.py Fri Jan 25 14:33:40 2013 +0100
@@ -4,7 +4,7 @@
config = source.dictconfig
host = config.pop('host', u'ldap')
protocol = config.pop('protocol', u'ldap')
- source.set_attributes(url=u'%s://%s' % (protocol, host))
+ source.cw_set(url=u'%s://%s' % (protocol, host))
source.update_config(skip_unknown=True, **config)
commit()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.16.0_Any.py Fri Jan 25 14:33:40 2013 +0100
@@ -0,0 +1,13 @@
+sync_schema_props_perms('EmailAddress')
+
+for source in rql('CWSource X WHERE X type "pyrorql"').entities():
+ sconfig = source.dictconfig
+ nsid = sconfig.pop('pyro-ns-id', config.appid)
+ nshost = sconfig.pop('pyro-ns-host', '')
+ nsgroup = sconfig.pop('pyro-ns-group', ':cubicweb')
+ if nsgroup:
+ nsgroup += '.'
+ source.cw_set(url=u'pyro://%s/%s%s' % (nshost, nsgroup, nsid))
+ source.update_config(skip_unknown=True, **sconfig)
+
+commit()
--- a/misc/migration/postcreate.py Thu Jan 24 16:13:40 2013 +0100
+++ b/misc/migration/postcreate.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/misc/scripts/chpasswd.py Thu Jan 24 16:13:40 2013 +0100
+++ b/misc/scripts/chpasswd.py Fri Jan 25 14:33:40 2013 +0100
@@ -42,7 +42,7 @@
crypted = crypt_password(pass1)
cwuser = rset.get_entity(0,0)
-cwuser.set_attributes(upassword=Binary(crypted))
+cwuser.cw_set(upassword=Binary(crypted))
commit()
print("password updated.")
--- a/misc/scripts/ldapuser2ldapfeed.py Thu Jan 24 16:13:40 2013 +0100
+++ b/misc/scripts/ldapuser2ldapfeed.py Fri Jan 25 14:33:40 2013 +0100
@@ -87,7 +87,7 @@
source_ent = rql('CWSource S WHERE S eid %(s)s', {'s': source.eid}).get_entity(0, 0)
-source_ent.set_attributes(type=u"ldapfeed", parser=u"ldapfeed")
+source_ent.cw_set(type=u"ldapfeed", parser=u"ldapfeed")
if raw_input('Commit ?') in 'yY':
--- a/predicates.py Thu Jan 24 16:13:40 2013 +0100
+++ b/predicates.py Fri Jan 25 14:33:40 2013 +0100
@@ -737,12 +737,16 @@
See :class:`~cubicweb.predicates.EClassPredicate` documentation for entity
class lookup / score rules according to the input context.
- .. note:: when interface is an entity class, the score will reflect class
- proximity so the most specific object will be selected.
+ .. note::
+
+ when interface is an entity class, the score will reflect class
+ proximity so the most specific object will be selected.
- .. note:: deprecated in cubicweb >= 3.9, use either
- :class:`~cubicweb.predicates.is_instance` or
- :class:`~cubicweb.predicates.adaptable`.
+ .. note::
+
+ deprecated in cubicweb >= 3.9, use either
+ :class:`~cubicweb.predicates.is_instance` or
+ :class:`~cubicweb.predicates.adaptable`.
"""
def __init__(self, *expected_ifaces, **kwargs):
--- a/req.py Thu Jan 24 16:13:40 2013 +0100
+++ b/req.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -62,6 +62,8 @@
:attribute vreg.schema: the instance's schema
:attribute vreg.config: the instance's configuration
"""
+ is_request = True # False for repository session
+
def __init__(self, vreg):
self.vreg = vreg
try:
@@ -75,6 +77,17 @@
self.local_perm_cache = {}
self._ = unicode
+ def set_language(self, lang):
+ """install i18n configuration for `lang` translation.
+
+ Raises :exc:`KeyError` if translation doesn't exist.
+ """
+ self.lang = lang
+ gettext, pgettext = self.vreg.config.translations[lang]
+ # use _cw.__ to translate a message without registering it to the catalog
+ self._ = self.__ = gettext
+ self.pgettext = pgettext
+
def get_option_value(self, option, foreid=None):
raise NotImplementedError
@@ -308,21 +321,12 @@
def user_data(self):
"""returns a dictionary with this user's information"""
userinfo = {}
- if self.is_internal_session:
- userinfo['login'] = "cubicweb"
- userinfo['name'] = "cubicweb"
- userinfo['email'] = ""
- return userinfo
user = self.user
userinfo['login'] = user.login
userinfo['name'] = user.name()
userinfo['email'] = user.cw_adapt_to('IEmailable').get_email()
return userinfo
- def is_internal_session(self):
- """overrided on the server-side"""
- return False
-
# formating methods #######################################################
def view(self, __vid, rset=None, __fallback_oid=None, __registry='views',
@@ -426,7 +430,7 @@
"""return the root url of the instance
"""
if secure:
- raise NotImplementedError()
+ return self.vreg.config.get('https-url', self.vreg.config['base-url'])
return self.vreg.config['base-url']
# abstract methods to override according to the web front-end #############
--- a/rtags.py Thu Jan 24 16:13:40 2013 +0100
+++ b/rtags.py Fri Jan 25 14:33:40 2013 +0100
@@ -38,17 +38,20 @@
__docformat__ = "restructuredtext en"
import logging
+from warnings import warn
from logilab.common.logging_ext import set_log_methods
-
-RTAGS = []
-def register_rtag(rtag):
- RTAGS.append(rtag)
+from logilab.common.registry import RegistrableInstance, yes
def _ensure_str_key(key):
return tuple(str(k) for k in key)
-class RelationTags(object):
+class RegistrableRtags(RegistrableInstance):
+ __registry__ = 'uicfg'
+ __select__ = yes()
+
+
+class RelationTags(RegistrableRtags):
"""a tag store for full relation definitions :
(subject type, relation type, object type, tagged)
@@ -58,18 +61,17 @@
This class associates a single tag to each key.
"""
_allowed_values = None
- _initfunc = None
- def __init__(self, name=None, initfunc=None, allowed_values=None):
- self._name = name or '<unknown>'
+ # _init expected to be a method (introduced in 3.17), while _initfunc a
+ # function given as __init__ argument and kept for bw compat
+ _init = _initfunc = None
+
+ def __init__(self):
self._tagdefs = {}
- if allowed_values is not None:
- self._allowed_values = allowed_values
- if initfunc is not None:
- self._initfunc = initfunc
- register_rtag(self)
def __repr__(self):
- return '%s: %s' % (self._name, repr(self._tagdefs))
+ # find a way to have more infos but keep it readable
+ # (in error messages in case of an ambiguity for instance)
+ return '%s (%s): %s' % (id(self), self.__regid__, self.__class__)
# dict compat
def __getitem__(self, key):
@@ -100,8 +102,8 @@
(stype, rtype, otype, tagged), value, ertype)
self.del_rtag(stype, rtype, otype, tagged)
break
- if self._initfunc is not None:
- self.apply(schema, self._initfunc)
+ if self._init is not None:
+ self.apply(schema, self._init)
def apply(self, schema, func):
for eschema in schema.entities():
@@ -113,7 +115,7 @@
sschema, oschema = eschema, tschema
else:
sschema, oschema = tschema, eschema
- func(self, sschema, rschema, oschema, role)
+ func(sschema, rschema, oschema, role)
# rtag declaration api ####################################################
@@ -142,6 +144,17 @@
self._tagdefs[_ensure_str_key(key)] = tag
return tag
+ def _tag_etype_attr(self, etype, attr, desttype='*', *args, **kwargs):
+ if isinstance(attr, basestring):
+ attr, role = attr, 'subject'
+ else:
+ attr, role = attr
+ if role == 'subject':
+ self.tag_subject_of((etype, attr, desttype), *args, **kwargs)
+ else:
+ self.tag_object_of((desttype, attr, etype), *args, **kwargs)
+
+
# rtag runtime api ########################################################
def del_rtag(self, *key):
@@ -250,4 +263,6 @@
key = list(key)
key[0] = '*'
super(NoTargetRelationTagsDict, self).tag_relation(key, tag)
+
+
set_log_methods(RelationTags, logging.getLogger('cubicweb.rtags'))
--- a/schema.py Thu Jan 24 16:13:40 2013 +0100
+++ b/schema.py Fri Jan 25 14:33:40 2013 +0100
@@ -261,30 +261,34 @@
return self.has_local_role(action) or self.has_perm(req, action)
PermissionMixIn.may_have_permission = may_have_permission
-def has_perm(self, session, action, **kwargs):
+def has_perm(self, _cw, action, **kwargs):
"""return true if the action is granted globaly or localy"""
try:
- self.check_perm(session, action, **kwargs)
+ self.check_perm(_cw, action, **kwargs)
return True
except Unauthorized:
return False
PermissionMixIn.has_perm = has_perm
-def check_perm(self, session, action, **kwargs):
- # NB: session may be a server session or a request object check user is
- # in an allowed group, if so that's enough internal sessions should
- # always stop there
+def check_perm(self, _cw, action, **kwargs):
+ # NB: _cw may be a server transaction or a request object.
+ #
+ # check user is in an allowed group, if so that's enough internal
+ # transactions should always stop there
groups = self.get_groups(action)
- if session.user.matching_groups(groups):
+ if _cw.user.matching_groups(groups):
return
# if 'owners' in allowed groups, check if the user actually owns this
# object, if so that's enough
+ #
+ # NB: give _cw to user.owns since user is not be bound to a transaction on
+ # the repository side
if 'owners' in groups and (
kwargs.get('creating')
- or ('eid' in kwargs and session.user.owns(kwargs['eid']))):
+ or ('eid' in kwargs and _cw.user.owns(kwargs['eid']))):
return
# else if there is some rql expressions, check them
- if any(rqlexpr.check(session, **kwargs)
+ if any(rqlexpr.check(_cw, **kwargs)
for rqlexpr in self.get_rqlexprs(action)):
return
raise Unauthorized(action, str(self))
@@ -467,45 +471,45 @@
return True
return False
- def has_perm(self, session, action, **kwargs):
+ def has_perm(self, _cw, action, **kwargs):
"""return true if the action is granted globaly or localy"""
if self.final:
assert not ('fromeid' in kwargs or 'toeid' in kwargs), kwargs
assert action in ('read', 'update')
if 'eid' in kwargs:
- subjtype = session.describe(kwargs['eid'])[0]
+ subjtype = _cw.describe(kwargs['eid'])[0]
else:
subjtype = objtype = None
else:
assert not 'eid' in kwargs, kwargs
assert action in ('read', 'add', 'delete')
if 'fromeid' in kwargs:
- subjtype = session.describe(kwargs['fromeid'])[0]
+ subjtype = _cw.describe(kwargs['fromeid'])[0]
elif 'frometype' in kwargs:
subjtype = kwargs.pop('frometype')
else:
subjtype = None
if 'toeid' in kwargs:
- objtype = session.describe(kwargs['toeid'])[0]
+ objtype = _cw.describe(kwargs['toeid'])[0]
elif 'toetype' in kwargs:
objtype = kwargs.pop('toetype')
else:
objtype = None
if objtype and subjtype:
- return self.rdef(subjtype, objtype).has_perm(session, action, **kwargs)
+ return self.rdef(subjtype, objtype).has_perm(_cw, action, **kwargs)
elif subjtype:
for tschema in self.targets(subjtype, 'subject'):
rdef = self.rdef(subjtype, tschema)
- if not rdef.has_perm(session, action, **kwargs):
+ if not rdef.has_perm(_cw, action, **kwargs):
return False
elif objtype:
for tschema in self.targets(objtype, 'object'):
rdef = self.rdef(tschema, objtype)
- if not rdef.has_perm(session, action, **kwargs):
+ if not rdef.has_perm(_cw, action, **kwargs):
return False
else:
for rdef in self.rdefs.itervalues():
- if not rdef.has_perm(session, action, **kwargs):
+ if not rdef.has_perm(_cw, action, **kwargs):
return False
return True
@@ -754,17 +758,17 @@
return rql, found, keyarg
return rqlst.as_string(), None, None
- def _check(self, session, **kwargs):
+ def _check(self, _cw, **kwargs):
"""return True if the rql expression is matching the given relation
between fromeid and toeid
- session may actually be a request as well
+ _cw may be a request or a server side transaction
"""
creating = kwargs.get('creating')
if not creating and self.eid is not None:
key = (self.eid, tuple(sorted(kwargs.iteritems())))
try:
- return session.local_perm_cache[key]
+ return _cw.local_perm_cache[key]
except KeyError:
pass
rql, has_perm_defs, keyarg = self.transform_has_permission()
@@ -772,50 +776,50 @@
if creating and 'X' in self.rqlst.defined_vars:
return True
if keyarg is None:
- kwargs.setdefault('u', session.user.eid)
+ kwargs.setdefault('u', _cw.user.eid)
try:
- rset = session.execute(rql, kwargs, build_descr=True)
+ rset = _cw.execute(rql, kwargs, build_descr=True)
except NotImplementedError:
self.critical('cant check rql expression, unsupported rql %s', rql)
if self.eid is not None:
- session.local_perm_cache[key] = False
+ _cw.local_perm_cache[key] = False
return False
except TypeResolverException, ex:
# some expression may not be resolvable with current kwargs
# (type conflict)
self.warning('%s: %s', rql, str(ex))
if self.eid is not None:
- session.local_perm_cache[key] = False
+ _cw.local_perm_cache[key] = False
return False
except Unauthorized, ex:
self.debug('unauthorized %s: %s', rql, str(ex))
if self.eid is not None:
- session.local_perm_cache[key] = False
+ _cw.local_perm_cache[key] = False
return False
else:
- rset = session.eid_rset(kwargs[keyarg])
+ rset = _cw.eid_rset(kwargs[keyarg])
# if no special has_*_permission relation in the rql expression, just
# check the result set contains something
if has_perm_defs is None:
if rset:
if self.eid is not None:
- session.local_perm_cache[key] = True
+ _cw.local_perm_cache[key] = True
return True
elif rset:
# check every special has_*_permission relation is satisfied
- get_eschema = session.vreg.schema.eschema
+ get_eschema = _cw.vreg.schema.eschema
try:
for eaction, col in has_perm_defs:
for i in xrange(len(rset)):
eschema = get_eschema(rset.description[i][col])
- eschema.check_perm(session, eaction, eid=rset[i][col])
+ eschema.check_perm(_cw, eaction, eid=rset[i][col])
if self.eid is not None:
- session.local_perm_cache[key] = True
+ _cw.local_perm_cache[key] = True
return True
except Unauthorized:
pass
if self.eid is not None:
- session.local_perm_cache[key] = False
+ _cw.local_perm_cache[key] = False
return False
@property
@@ -843,15 +847,15 @@
rql += ', U eid %(u)s'
return rql
- def check(self, session, eid=None, creating=False, **kwargs):
+ def check(self, _cw, eid=None, creating=False, **kwargs):
if 'X' in self.rqlst.defined_vars:
if eid is None:
if creating:
- return self._check(session, creating=True, **kwargs)
+ return self._check(_cw, creating=True, **kwargs)
return False
assert creating == False
- return self._check(session, x=eid, **kwargs)
- return self._check(session, **kwargs)
+ return self._check(_cw, x=eid, **kwargs)
+ return self._check(_cw, **kwargs)
def vargraph(rqlst):
@@ -904,7 +908,7 @@
rql += ', U eid %(u)s'
return rql
- def check(self, session, fromeid=None, toeid=None):
+ def check(self, _cw, fromeid=None, toeid=None):
kwargs = {}
if 'S' in self.rqlst.defined_vars:
if fromeid is None:
@@ -914,7 +918,7 @@
if toeid is None:
return False
kwargs['o'] = toeid
- return self._check(session, **kwargs)
+ return self._check(_cw, **kwargs)
# in yams, default 'update' perm for attributes granted to managers and owners.
@@ -1024,7 +1028,7 @@
'expression': self.expression}
raise ValidationError(maineid, {qname: msg})
- def exec_query(self, session, eidfrom, eidto):
+ def exec_query(self, _cw, eidfrom, eidto):
if eidto is None:
# checking constraint for an attribute relation
expression = 'S eid %(s)s, ' + self.expression
@@ -1034,11 +1038,11 @@
args = {'s': eidfrom, 'o': eidto}
if 'U' in self.rqlst.defined_vars:
expression = 'U eid %(u)s, ' + expression
- args['u'] = session.user.eid
+ args['u'] = _cw.user.eid
rql = 'Any %s WHERE %s' % (','.join(sorted(self.mainvars)), expression)
if self.distinct_query:
rql = 'DISTINCT ' + rql
- return session.execute(rql, args, build_descr=False)
+ return _cw.execute(rql, args, build_descr=False)
class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint):
@@ -1061,7 +1065,7 @@
"""
# XXX turns mainvars into a required argument in __init__
distinct_query = True
-
+
def match_condition(self, session, eidfrom, eidto):
return len(self.exec_query(session, eidfrom, eidto)) <= 1
--- a/server/__init__.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/__init__.py Fri Jan 25 14:33:40 2013 +0100
@@ -77,10 +77,14 @@
DBG_REPO = 4
#: multi-sources
DBG_MS = 8
+#: hooks
+DBG_HOOKS = 16
+#: operations
+DBG_OPS = 32
#: more verbosity
-DBG_MORE = 16
+DBG_MORE = 64
#: all level enabled
-DBG_ALL = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_MORE
+DBG_ALL = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_HOOKS + DBG_OPS + DBG_MORE
#: current debug mode
DEBUG = 0
@@ -165,6 +169,8 @@
# on connection
config.creating = True
config.consider_user_state = False
+ config.cubicweb_appobject_path = set(('hooks', 'entities'))
+ config.cube_appobject_path = set(('hooks', 'entities'))
# only enable the system source at initialization time
repo = Repository(config, vreg=vreg)
schema = repo.schema
@@ -224,10 +230,6 @@
config._cubes = None # avoid assertion error
repo, cnx = in_memory_repo_cnx(config, login, password=pwd)
repo.system_source.eid = ssource.eid # redo this manually
- # trigger vreg initialisation of entity classes
- config.cubicweb_appobject_path = set(('entities',))
- config.cube_appobject_path = set(('entities',))
- repo.vreg.set_schema(repo.schema)
assert len(repo.sources) == 1, repo.sources
handler = config.migration_handler(schema, interactive=False,
cnx=cnx, repo=repo)
@@ -246,20 +248,21 @@
# restore initial configuration
config.creating = False
config.consider_user_state = True
+ # (drop instance attribute to get back to class attribute)
+ del config.cubicweb_appobject_path
+ del config.cube_appobject_path
print '-> database for instance %s initialized.' % config.appid
def initialize_schema(config, schema, mhandler, event='create'):
from cubicweb.server.schemaserial import serialize_schema
- from cubicweb.server.session import hooks_control
session = mhandler.session
cubes = config.cubes()
# deactivate every hooks but those responsible to set metadata
# so, NO INTEGRITY CHECKS are done, to have quicker db creation.
# Active integrity is kept else we may pb such as two default
# workflows for one entity type.
- with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata',
- 'activeintegrity'):
+ with session.deny_all_hooks_but('metadata', 'activeintegrity'):
# execute cubicweb's pre<event> script
mhandler.cmd_exec_event_script('pre%s' % event)
# execute cubes pre<event> script if any
--- a/server/checkintegrity.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/checkintegrity.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -32,7 +32,6 @@
from cubicweb.schema import PURE_VIRTUAL_RTYPES, VIRTUAL_RTYPES
from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.session import security_enabled
def notify_fixed(fix):
if fix:
@@ -394,18 +393,18 @@
# yo, launch checks
if checks:
eids_cache = {}
- with security_enabled(session, read=False, write=False): # ensure no read security
+ with session.security_enabled(read=False, write=False): # ensure no read security
for check in checks:
check_func = globals()['check_%s' % check]
check_func(repo.schema, session, eids_cache, fix=fix)
if fix:
- cnx.commit()
+ session.commit()
else:
print
if not fix:
print 'WARNING: Diagnostic run, nothing has been corrected'
if reindex:
- cnx.rollback()
+ session.rollback()
session.set_cnxset()
reindex_entities(repo.schema, session, withpb=withpb)
- cnx.commit()
+ session.commit()
--- a/server/cwzmq.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/cwzmq.py Fri Jan 25 14:33:40 2013 +0100
@@ -31,6 +31,13 @@
ctx = zmq.Context()
+def cwproto_to_zmqaddr(address):
+ """ converts a cw-zmq address (like zmqpickle-tcp://<ip>:<port>)
+ into a proper zmq address (tcp://<ip>:<port>)
+ """
+ assert address.startswith('zmqpickle-'), 'bad protocol string %s' % address
+ return address.split('-', 1)[1] # chop the `zmqpickle-` prefix
+
class ZMQComm(object):
"""
A simple ZMQ-based notification bus.
@@ -140,7 +147,7 @@
self.events = []
def connect(self, address):
- self.address = address
+ self.address = cwproto_to_zmqaddr(address)
def run(self):
"""enter the service loop"""
--- a/server/edition.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/edition.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -103,6 +103,8 @@
assert not self.saved, 'too late to modify edited attributes'
super(EditedEntity, self).__setitem__(attr, value)
self.entity.cw_attr_cache[attr] = value
+ # mark attribute as needing purge by the client
+ self.entity._cw_dont_cache_attribute(attr)
def oldnewvalue(self, attr):
"""returns the couple (old attr value, new attr value)
@@ -141,8 +143,7 @@
for rtype in self]
try:
entity.e_schema.check(dict_protocol_catcher(entity),
- creation=creation, _=entity._cw._,
- relations=relations)
+ creation=creation, relations=relations)
except ValidationError, ex:
ex.entity = self.entity
raise
--- a/server/hook.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/hook.py Fri Jan 25 14:33:40 2013 +0100
@@ -152,7 +152,7 @@
On those events, the entity has no `cw_edited` dictionary.
-.. note:: `self.entity.set_attributes(age=42)` will set the `age` attribute to
+.. note:: `self.entity.cw_set(age=42)` will set the `age` attribute to
42. But to do so, it will generate a rql query that will have to be processed,
hence may trigger some hooks, etc. This could lead to infinitely looping hooks.
@@ -197,14 +197,12 @@
~~~~~~~~~~~~~
It is sometimes convenient to explicitly enable or disable some hooks. For
-instance if you want to disable some integrity checking hook. This can be
+instance if you want to disable some integrity checking hook. This can be
controlled more finely through the `category` class attribute, which is a string
giving a category name. One can then uses the
-:class:`~cubicweb.server.session.hooks_control` context manager to explicitly
-enable or disable some categories.
-
-.. autoclass:: cubicweb.server.session.hooks_control
-
+:meth:`~cubicweb.server.session.Session.deny_all_hooks_but` and
+:meth:`~cubicweb.server.session.Session.allow_all_hooks_but` context managers to
+explicitly enable or disable some categories.
The existing categories are:
@@ -230,10 +228,8 @@
* ``bookmark``, bookmark entities handling hooks
-Nothing precludes one to invent new categories and use the
-:class:`~cubicweb.server.session.hooks_control` context manager to
-filter them in or out. Note that ending the transaction with commit()
-or rollback() will restore the hooks.
+Nothing precludes one to invent new categories and use existing mechanisms to
+filter them in or out.
Hooks specific predicates
@@ -262,13 +258,12 @@
from logilab.common.deprecation import deprecated, class_renamed
from logilab.common.logging_ext import set_log_methods
from logilab.common.registry import (Predicate, NotPredicate, OrPredicate,
- classid, objectify_predicate, yes)
+ objectify_predicate, yes)
-from cubicweb import RegistryNotFound
+from cubicweb import RegistryNotFound, server
from cubicweb.cwvreg import CWRegistry, CWRegistryStore
from cubicweb.predicates import ExpectedValuePredicate, is_instance
from cubicweb.appobject import AppObject
-from cubicweb.server.session import security_enabled
ENTITIES_HOOKS = set(('before_add_entity', 'after_add_entity',
'before_update_entity', 'after_update_entity',
@@ -326,12 +321,15 @@
pruned = self.get_pruned_hooks(session, event,
entities, eids_from_to, kwargs)
# by default, hooks are executed with security turned off
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
for _kwargs in _iter_kwargs(entities, eids_from_to, kwargs):
hooks = sorted(self.filtered_possible_objects(pruned, session, **_kwargs),
key=lambda x: x.order)
- with security_enabled(session, write=False):
+ debug = server.DEBUG & server.DBG_HOOKS
+ with session.security_enabled(write=False):
for hook in hooks:
+ if debug:
+ print event, _kwargs, hook
hook()
def get_pruned_hooks(self, session, event, entities, eids_from_to, kwargs):
@@ -770,7 +768,7 @@
"""delegate event handling to the opertaion"""
if event == 'postcommit_event' and hasattr(self, 'commit_event'):
warn('[3.10] %s: commit_event method has been replaced by postcommit_event'
- % classid(self.__class__), DeprecationWarning)
+ % self.__class__, DeprecationWarning)
self.commit_event() # pylint: disable=E1101
getattr(self, event)()
--- a/server/ldaputils.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/ldaputils.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -136,6 +136,7 @@
_conn = None
def _entity_update(self, source_entity):
+ super(LDAPSourceMixIn, self)._entity_update(source_entity)
if self.urls:
if len(self.urls) > 1:
raise ValidationError(source_entity.eid, {'url': _('can only have one url')})
@@ -150,6 +151,7 @@
"""update configuration from source entity. `typedconfig` is config
properly typed with defaults set
"""
+ super(LDAPSourceMixIn, self).update_config(source_entity, typedconfig)
self.authmode = typedconfig['auth-mode']
self._authenticate = getattr(self, '_auth_%s' % self.authmode)
self.cnx_dn = typedconfig['data-cnx-dn']
--- a/server/migractions.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/migractions.py Fri Jan 25 14:33:40 2013 +0100
@@ -56,7 +56,7 @@
PURE_VIRTUAL_RTYPES,
CubicWebRelationSchema, order_eschemas)
from cubicweb.cwvreg import CW_EVENT_MANAGER
-from cubicweb.dbapi import get_repository, repo_connect
+from cubicweb.dbapi import get_repository, _repo_connect
from cubicweb.migration import MigrationHelper, yes
from cubicweb.server import hook
try:
@@ -132,7 +132,7 @@
@cached
def repo_connect(self):
- self.repo = get_repository(method='inmemory', config=self.config)
+ self.repo = get_repository(config=self.config)
return self.repo
def cube_upgraded(self, cube, version):
@@ -279,7 +279,7 @@
login, pwd = manager_userpasswd()
while True:
try:
- self._cnx = repo_connect(self.repo, login, password=pwd)
+ self._cnx = _repo_connect(self.repo, login, password=pwd)
if not 'managers' in self._cnx.user(self.session).groups:
print 'migration need an account in the managers group'
else:
@@ -1321,7 +1321,7 @@
except Exception:
self.cmd_create_entity('CWProperty', pkey=unicode(pkey), value=value)
else:
- prop.set_attributes(value=value)
+ prop.cw_set(value=value)
# other data migration commands ###########################################
--- a/server/pool.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/pool.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/querier.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/querier.py Fri Jan 25 14:33:40 2013 +0100
@@ -26,21 +26,26 @@
from itertools import repeat
from logilab.common.compat import any
-from rql import RQLSyntaxError
+from rql import RQLSyntaxError, CoercionError
from rql.stmts import Union, Select
+from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
from rql.nodes import (Relation, VariableRef, Constant, SubQuery, Function,
Exists, Not)
+from yams import BASE_TYPES
from cubicweb import ValidationError, Unauthorized, QueryError, UnknownEid
-from cubicweb import server, typed_eid
+from cubicweb import Binary, server, typed_eid
from cubicweb.rset import ResultSet
-from cubicweb.utils import QueryCache
+from cubicweb.utils import QueryCache, RepeatList
from cubicweb.server.utils import cleanup_solutions
from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
from cubicweb.server.edition import EditedEntity
-from cubicweb.server.session import security_enabled
+
+
+ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
+
def empty_rset(rql, args, rqlst=None):
"""build an empty result set object"""
@@ -256,7 +261,7 @@
cached = True
else:
noinvariant = set()
- with security_enabled(self.session, read=False):
+ with self.session.security_enabled(read=False):
self._insert_security(union, noinvariant)
if key is not None:
self.session.transaction_data[key] = (union, self.args)
@@ -751,14 +756,22 @@
if build_descr:
if rqlst.TYPE == 'select':
# sample selection
- descr = session.build_description(orig_rqlst, args, results)
+ if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
+ # easy, all lines are identical
+ selected = rqlst.children[0].selection
+ solution = rqlst.children[0].solutions[0]
+ description = _make_description(selected, args, solution)
+ descr = RepeatList(len(results), tuple(description))
+ else:
+ # hard, delegate the work :o)
+ descr = manual_build_descr(session, rqlst, args, results)
elif rqlst.TYPE == 'insert':
# on insert plan, some entities may have been auto-casted,
# so compute description manually even if there is only
# one solution
basedescr = [None] * len(plan.selected)
todetermine = zip(xrange(len(plan.selected)), repeat(False))
- descr = session._build_descr(results, basedescr, todetermine)
+ descr = _build_descr(session, results, basedescr, todetermine)
# FIXME: get number of affected entities / relations on non
# selection queries ?
# return a result set object
@@ -772,3 +785,77 @@
from cubicweb import set_log_methods
LOGGER = getLogger('cubicweb.querier')
set_log_methods(QuerierHelper, LOGGER)
+
+
+def manual_build_descr(tx, rqlst, args, result):
+ """build a description for a given result by analysing each row
+
+ XXX could probably be done more efficiently during execution of query
+ """
+ # not so easy, looks for variable which changes from one solution
+ # to another
+ unstables = rqlst.get_variable_indices()
+ basedescr = []
+ todetermine = []
+ for i in xrange(len(rqlst.children[0].selection)):
+ ttype = _selection_idx_type(i, rqlst, args)
+ if ttype is None or ttype == 'Any':
+ ttype = None
+ isfinal = True
+ else:
+ isfinal = ttype in BASE_TYPES
+ if ttype is None or i in unstables:
+ basedescr.append(None)
+ todetermine.append( (i, isfinal) )
+ else:
+ basedescr.append(ttype)
+ if not todetermine:
+ return RepeatList(len(result), tuple(basedescr))
+ return _build_descr(tx, result, basedescr, todetermine)
+
+def _build_descr(tx, result, basedescription, todetermine):
+ description = []
+ etype_from_eid = tx.describe
+ todel = []
+ for i, row in enumerate(result):
+ row_descr = basedescription[:]
+ for index, isfinal in todetermine:
+ value = row[index]
+ if value is None:
+ # None value inserted by an outer join, no type
+ row_descr[index] = None
+ continue
+ if isfinal:
+ row_descr[index] = etype_from_pyobj(value)
+ else:
+ try:
+ row_descr[index] = etype_from_eid(value)[0]
+ except UnknownEid:
+ tx.error('wrong eid %s in repository, you should '
+ 'db-check the database' % value)
+ todel.append(i)
+ break
+ else:
+ description.append(tuple(row_descr))
+ for i in reversed(todel):
+ del result[i]
+ return description
+
+def _make_description(selected, args, solution):
+ """return a description for a result set"""
+ description = []
+ for term in selected:
+ description.append(term.get_type(solution, args))
+ return description
+
+def _selection_idx_type(i, rqlst, args):
+ """try to return type of term at index `i` of the rqlst's selection"""
+ for select in rqlst.children:
+ term = select.selection[i]
+ for solution in select.solutions:
+ try:
+ ttype = term.get_type(solution, args)
+ if ttype is not None:
+ return ttype
+ except CoercionError:
+ return None
--- a/server/repository.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/repository.py Fri Jan 25 14:33:40 2013 +0100
@@ -56,8 +56,7 @@
RepositoryError, UniqueTogetherError, typed_eid, onevent)
from cubicweb import cwvreg, schema, server
from cubicweb.server import ShuttingDown, utils, hook, pool, querier, sources
-from cubicweb.server.session import Session, InternalSession, InternalManager, \
- security_enabled
+from cubicweb.server.session import Session, InternalSession, InternalManager
from cubicweb.server.ssplanner import EditedEntity
NO_CACHE_RELATIONS = set( [('owned_by', 'object'),
@@ -109,12 +108,12 @@
# * we don't want read permissions to be applied but we want delete
# permission to be checked
if card[0] in '1?':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
'NOT Y eid %%(y)s' % rtype,
{'x': eidfrom, 'y': eidto})
if card[1] in '1?':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE Y eid %%(y)s, '
'NOT X eid %%(x)s' % rtype,
{'x': eidfrom, 'y': eidto})
@@ -135,7 +134,7 @@
session.update_rel_cache_add(entity.eid, attr, value)
rdef = session.rtype_eids_rdef(attr, entity.eid, value)
if rdef.cardinality[1] in '1?' and activeintegrity:
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE Y eid %%(y)s' % attr,
{'x': entity.eid, 'y': value})
return relations
@@ -187,7 +186,7 @@
self.shutting_down = False
# sources (additional sources info in the system database)
self.system_source = self.get_source('native', 'system',
- config.sources()['system'])
+ config.sources()['system'].copy())
self.sources = [self.system_source]
self.sources_by_uri = {'system': self.system_source}
# querier helper, need to be created after sources initialization
@@ -218,33 +217,21 @@
# information (eg dump/restore/...)
config._cubes = ()
# only load hooks and entity classes in the registry
- config.__class__.cube_appobject_path = set(('hooks', 'entities'))
- config.__class__.cubicweb_appobject_path = set(('hooks', 'entities'))
+ config.cube_appobject_path = set(('hooks', 'entities'))
+ config.cubicweb_appobject_path = set(('hooks', 'entities'))
self.set_schema(config.load_schema())
config['connections-pool-size'] = 1
# will be reinitialized later from cubes found in the database
config._cubes = None
- elif config.creating:
- # repository creation
+ elif config.creating or not config.read_instance_schema:
+ if not config.creating:
+ # test start: use the file system schema (quicker)
+ self.warning("set fs instance'schema")
config.bootstrap_cubes()
- self.set_schema(config.load_schema(), resetvreg=False)
- # need to load the Any and CWUser entity types
- etdirectory = join(CW_SOFTWARE_ROOT, 'entities')
- self.vreg.init_registration([etdirectory])
- for modname in ('__init__', 'authobjs', 'wfobjs'):
- self.vreg.load_file(join(etdirectory, '%s.py' % modname),
- 'cubicweb.entities.%s' % modname)
- hooksdirectory = join(CW_SOFTWARE_ROOT, 'hooks')
- self.vreg.load_file(join(hooksdirectory, 'metadata.py'),
- 'cubicweb.hooks.metadata')
- elif config.read_instance_schema:
+ self.set_schema(config.load_schema())
+ else:
# normal start: load the instance schema from the database
self.fill_schema()
- else:
- # test start: use the file system schema (quicker)
- self.warning("set fs instance'schema")
- config.bootstrap_cubes()
- self.set_schema(config.load_schema())
if not config.creating:
self.init_sources_from_database()
if 'CWProperty' in self.schema:
@@ -761,16 +748,13 @@
def connect(self, login, **kwargs):
"""open a connection for a given user
- base_url may be needed to send mails
- cnxtype indicate if this is a pyro connection or a in-memory connection
-
raise `AuthenticationError` if the authentication failed
raise `ConnectionError` if we can't open a connection
"""
+ cnxprops = kwargs.pop('cnxprops', None)
# use an internal connection
with self.internal_session() as session:
# try to get a user object
- cnxprops = kwargs.pop('cnxprops', None)
user = self.authenticate_user(session, login, **kwargs)
session = Session(user, self, cnxprops)
user._cw = user.cw_rset.req = session
@@ -921,22 +905,9 @@
* update user information on each user's request (i.e. groups and
custom properties)
"""
- session = self._get_session(sessionid, setcnxset=False)
- if props is not None:
- self.set_session_props(sessionid, props)
- user = session.user
+ user = self._get_session(sessionid, setcnxset=False).user
return user.eid, user.login, user.groups, user.properties
- def set_session_props(self, sessionid, props):
- """this method should be used by client to:
- * check session id validity
- * update user information on each user's request (i.e. groups and
- custom properties)
- """
- session = self._get_session(sessionid, setcnxset=False)
- for prop, value in props.items():
- session.change_property(prop, value)
-
def undoable_transactions(self, sessionid, ueid=None, txid=None,
**actionfilters):
"""See :class:`cubicweb.dbapi.Connection.undoable_transactions`"""
@@ -1239,7 +1210,7 @@
source = self.sources_by_eid[scleanup]
# delete remaining relations: if user can delete the entity, he can
# delete all its relations without security checking
- with security_enabled(session, read=False, write=False):
+ with session.security_enabled(read=False, write=False):
eid = entity.eid
for rschema, _, role in entity.e_schema.relation_definitions():
rtype = rschema.type
@@ -1281,7 +1252,7 @@
source = self.sources_by_eid[scleanup]
# delete remaining relations: if user can delete the entity, he can
# delete all its relations without security checking
- with security_enabled(session, read=False, write=False):
+ with session.security_enabled(read=False, write=False):
in_eids = ','.join([str(_e.eid) for _e in entities])
for rschema, _, role in entities[0].e_schema.relation_definitions():
rtype = rschema.type
@@ -1567,7 +1538,7 @@
rdef = session.rtype_eids_rdef(rtype, subjeid, objeid)
card = rdef.cardinality
if card[0] in '?1':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
'NOT Y eid %%(y)s' % rtype,
{'x': subjeid, 'y': objeid})
@@ -1578,7 +1549,7 @@
continue
subjects[subjeid] = len(relations_by_rtype[rtype]) - 1
if card[1] in '?1':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE Y eid %%(y)s, '
'NOT X eid %%(x)s' % rtype,
{'x': subjeid, 'y': objeid})
--- a/server/serverconfig.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/serverconfig.py Fri Jan 25 14:33:40 2013 +0100
@@ -195,7 +195,7 @@
notified of every changes.',
'group': 'email', 'level': 2,
}),
- # pyro server.serverconfig
+ # pyro services config
('pyro-host',
{'type' : 'string',
'default': None,
@@ -204,23 +204,47 @@
and if not set, it will be choosen randomly',
'group': 'pyro', 'level': 3,
}),
+ ('pyro-instance-id',
+ {'type' : 'string',
+ 'default': lgconfig.Method('default_instance_id'),
+ 'help': 'identifier of the CubicWeb instance in the Pyro name server',
+ 'group': 'pyro', 'level': 1,
+ }),
+ ('pyro-ns-host',
+ {'type' : 'string',
+ 'default': '',
+ 'help': 'Pyro name server\'s host. If not set, will be detected by a \
+broadcast query. It may contains port information using <host>:<port> notation. \
+Use "NO_PYRONS" to create a Pyro server but not register to a pyro nameserver',
+ 'group': 'pyro', 'level': 1,
+ }),
+ ('pyro-ns-group',
+ {'type' : 'string',
+ 'default': 'cubicweb',
+ 'help': 'Pyro name server\'s group where the repository will be \
+registered.',
+ 'group': 'pyro', 'level': 1,
+ }),
# zmq services config
('zmq-repository-address',
{'type' : 'string',
'default': None,
- 'help': 'ZMQ URI on which the repository will be bound to.',
+ 'help': ('ZMQ URI on which the repository will be bound '
+ 'to (of the form `zmqpickle-tcp://<ipaddr><port>`).'),
'group': 'zmq', 'level': 3,
}),
('zmq-address-sub',
{'type' : 'csv',
'default' : None,
- 'help': ('List of ZMQ addresses to subscribe to (requires pyzmq)'),
+ 'help': ('List of ZMQ addresses to subscribe to (requires pyzmq) '
+ '(of the form `zmqpickle-tcp://<ipaddr><port>`)'),
'group': 'zmq', 'level': 1,
}),
('zmq-address-pub',
{'type' : 'string',
'default' : None,
- 'help': ('ZMQ address to use for publishing (requires pyzmq)'),
+ 'help': ('ZMQ address to use for publishing (requires pyzmq) '
+ '(of the form `zmqpickle-tcp://<ipaddr><port>`)'),
'group': 'zmq', 'level': 1,
}),
) + CubicWebConfiguration.options)
--- a/server/serverctl.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/serverctl.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -442,7 +442,7 @@
config = ServerConfiguration.config_for(appid)
try:
system = config.sources()['system']
- extra_args=system.get('db-extra-arguments')
+ extra_args = system.get('db-extra-arguments')
extra = extra_args and {'extra_args': extra_args} or {}
get_connection(
system['db-driver'], database=system['db-name'],
--- a/server/session.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/session.py Fri Jan 25 14:33:40 2013 +0100
@@ -30,21 +30,15 @@
from logilab.common.deprecation import deprecated
from logilab.common.textutils import unormalize
from logilab.common.registry import objectify_predicate
-from rql import CoercionError
-from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
-from yams import BASE_TYPES
-from cubicweb import Binary, UnknownEid, QueryError, schema
+from cubicweb import UnknownEid, QueryError, schema, server
from cubicweb.req import RequestSessionBase
-from cubicweb.dbapi import ConnectionProperties
-from cubicweb.utils import make_uid, RepeatList
+from cubicweb.utils import make_uid
from cubicweb.rqlrewrite import RQLRewriter
from cubicweb.server import ShuttingDown
from cubicweb.server.edition import EditedEntity
-ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
-
NO_UNDO_TYPES = schema.SCHEMA_TYPES.copy()
NO_UNDO_TYPES.add('CWCache')
# is / is_instance_of are usually added by sql hooks except when using
@@ -55,25 +49,6 @@
NO_UNDO_TYPES.add('cw_source')
# XXX rememberme,forgotpwd,apycot,vcsfile
-def _make_description(selected, args, solution):
- """return a description for a result set"""
- description = []
- for term in selected:
- description.append(term.get_type(solution, args))
- return description
-
-def selection_idx_type(i, rqlst, args):
- """try to return type of term at index `i` of the rqlst's selection"""
- for select in rqlst.children:
- term = select.selection[i]
- for solution in select.solutions:
- try:
- ttype = term.get_type(solution, args)
- if ttype is not None:
- return ttype
- except CoercionError:
- return None
-
@objectify_predicate
def is_user_session(cls, req, **kwargs):
"""repository side only predicate returning 1 if the session is a regular
@@ -132,6 +107,11 @@
with hooks_control(self.session, self.session.HOOKS_DENY_ALL, 'integrity'):
# ... do stuff with none but 'integrity' hooks activated
+
+ This is an internal api, you should rather use
+ :meth:`~cubicweb.server.session.Session.deny_all_hooks_but` or
+ :meth:`~cubicweb.server.session.Session.allow_all_hooks_but` session
+ methods.
"""
def __init__(self, session, mode, *categories):
self.session = session
@@ -241,16 +221,18 @@
:attr:`running_dbapi_query`, boolean flag telling if the executing query
is coming from a dbapi connection or is a query from within the repository
+
+ .. automethod:: cubicweb.server.session.deny_all_hooks_but
+ .. automethod:: cubicweb.server.session.all_all_hooks_but
"""
+ is_request = False
is_internal_session = False
def __init__(self, user, repo, cnxprops=None, _id=None):
super(Session, self).__init__(repo.vreg)
self.id = _id or make_uid(unormalize(user.login).encode('UTF8'))
- cnxprops = cnxprops or ConnectionProperties('inmemory')
self.user = user
self.repo = repo
- self.cnxtype = cnxprops.cnxtype
self.timestamp = time()
self.default_mode = 'read'
# undo support
@@ -264,7 +246,7 @@
# and the rql server
self.data = {}
# i18n initialization
- self.set_language(cnxprops.lang)
+ self.set_language(user.prefered_language())
# internals
self._tx_data = {}
self.__threaddata = threading.local()
@@ -273,8 +255,8 @@
self._closed_lock = threading.Lock()
def __unicode__(self):
- return '<%ssession %s (%s 0x%x)>' % (
- self.cnxtype, unicode(self.user.login), self.id, id(self))
+ return '<session %s (%s 0x%x)>' % (
+ unicode(self.user.login), self.id, id(self))
def transaction(self, free_cnxset=True):
"""return context manager to enter a transaction for the session: when
@@ -464,28 +446,6 @@
self.cnxset.reconnect(source)
return source.doexec(self, sql, args, rollback=rollback_on_failure)
- def set_language(self, language):
- """i18n configuration for translation"""
- language = language or self.user.property_value('ui.language')
- try:
- gettext, pgettext = self.vreg.config.translations[language]
- self._ = self.__ = gettext
- self.pgettext = pgettext
- except KeyError:
- language = self.vreg.property_value('ui.language')
- try:
- gettext, pgettext = self.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):
- assert prop == 'lang' # this is the only one changeable property for now
- self.set_language(value)
-
def deleted_in_transaction(self, eid):
"""return True if the entity of the given eid is being deleted in the
current transaction
@@ -509,7 +469,7 @@
DEFAULT_SECURITY = object() # evaluated to true by design
- def security_enabled(self, read=False, write=False):
+ def security_enabled(self, read=None, write=None):
return security_enabled(self, read=read, write=write)
def init_security(self, read, write):
@@ -976,16 +936,21 @@
# information:
# - processed by the precommit/commit event or not
# - if processed, is it the failed operation
+ debug = server.DEBUG & server.DBG_OPS
try:
# by default, operations are executed with security turned off
with security_enabled(self, False, False):
processed = []
self.commit_state = 'precommit'
+ if debug:
+ print self.commit_state, '*' * 20
try:
while self.pending_operations:
operation = self.pending_operations.pop(0)
operation.processed = 'precommit'
processed.append(operation)
+ if debug:
+ print operation
operation.handle_event('precommit_event')
self.pending_operations[:] = processed
self.debug('precommit session %s done', self.id)
@@ -1001,7 +966,11 @@
# instead of having to implements rollback, revertprecommit
# and revertcommit, that will be enough in mont case.
operation.failed = True
+ if debug:
+ print self.commit_state, '*' * 20
for operation in reversed(processed):
+ if debug:
+ print operation
try:
operation.handle_event('revertprecommit_event')
except BaseException:
@@ -1014,8 +983,12 @@
raise
self.cnxset.commit()
self.commit_state = 'postcommit'
+ if debug:
+ print self.commit_state, '*' * 20
while self.pending_operations:
operation = self.pending_operations.pop(0)
+ if debug:
+ print operation
operation.processed = 'postcommit'
try:
operation.handle_event('postcommit_event')
@@ -1154,71 +1127,6 @@
self._threaddata._rewriter = RQLRewriter(self)
return self._threaddata._rewriter
- def build_description(self, rqlst, args, result):
- """build a description for a given result"""
- if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
- # easy, all lines are identical
- selected = rqlst.children[0].selection
- solution = rqlst.children[0].solutions[0]
- description = _make_description(selected, args, solution)
- return RepeatList(len(result), tuple(description))
- # hard, delegate the work :o)
- return self.manual_build_descr(rqlst, args, result)
-
- def manual_build_descr(self, rqlst, args, result):
- """build a description for a given result by analysing each row
-
- XXX could probably be done more efficiently during execution of query
- """
- # not so easy, looks for variable which changes from one solution
- # to another
- unstables = rqlst.get_variable_indices()
- basedescr = []
- todetermine = []
- for i in xrange(len(rqlst.children[0].selection)):
- ttype = selection_idx_type(i, rqlst, args)
- if ttype is None or ttype == 'Any':
- ttype = None
- isfinal = True
- else:
- isfinal = ttype in BASE_TYPES
- if ttype is None or i in unstables:
- basedescr.append(None)
- todetermine.append( (i, isfinal) )
- else:
- basedescr.append(ttype)
- if not todetermine:
- return RepeatList(len(result), tuple(basedescr))
- return self._build_descr(result, basedescr, todetermine)
-
- def _build_descr(self, result, basedescription, todetermine):
- description = []
- etype_from_eid = self.describe
- todel = []
- for i, row in enumerate(result):
- row_descr = basedescription[:]
- for index, isfinal in todetermine:
- value = row[index]
- if value is None:
- # None value inserted by an outer join, no type
- row_descr[index] = None
- continue
- if isfinal:
- row_descr[index] = etype_from_pyobj(value)
- else:
- try:
- row_descr[index] = etype_from_eid(value)[0]
- except UnknownEid:
- self.error('wrong eid %s in repository, you should '
- 'db-check the database' % value)
- todel.append(i)
- break
- else:
- description.append(tuple(row_descr))
- for i in reversed(todel):
- del result[i]
- return description
-
# deprecated ###############################################################
@deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)')
@@ -1274,7 +1182,6 @@
super(InternalSession, self).__init__(InternalManager(), repo, cnxprops,
_id='internal')
self.user._cw = self # XXX remove when "vreg = user._cw.vreg" hack in entity.py is gone
- self.cnxtype = 'inmemory'
if not safe:
self.disable_hook_categories('integrity')
@@ -1317,6 +1224,24 @@
return 'en'
return None
+ def prefered_language(self, language=None):
+ # mock CWUser.prefered_language, mainly for testing purpose
+ return self.property_value('ui.language')
+
+ # CWUser compat for notification ###########################################
+
+ def name(self):
+ return 'cubicweb'
+
+ class _IEmailable:
+ @staticmethod
+ def get_email():
+ return ''
+
+ def cw_adapt_to(self, iface):
+ if iface == 'IEmailable':
+ return self._IEmailable
+ return None
from logging import getLogger
from cubicweb import set_log_methods
--- a/server/sources/__init__.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/sources/__init__.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -133,6 +133,8 @@
self.uri = source_config.pop('uri')
set_log_methods(self, getLogger('cubicweb.sources.'+self.uri))
source_config.pop('type')
+ self.update_config(None, self.check_conf_dict(eid, source_config,
+ fail_if_unknown=False))
def __repr__(self):
return '<%s %s source %s @%#x>' % (self.uri, self.__class__.__name__,
@@ -206,7 +208,17 @@
"""update configuration from source entity. `typedconfig` is config
properly typed with defaults set
"""
- pass
+ if source_entity is not None:
+ self._entity_update(source_entity)
+ self.config = typedconfig
+
+ def _entity_update(self, source_entity):
+ source_entity.complete()
+ if source_entity.url:
+ self.urls = [url.strip() for url in source_entity.url.splitlines()
+ if url.strip()]
+ else:
+ self.urls = []
# source initialization / finalization #####################################
@@ -222,7 +234,8 @@
"""method called by the repository once ready to handle request.
`activated` is a boolean flag telling if the source is activated or not.
"""
- pass
+ if activated:
+ self._entity_update(source_entity)
PUBLIC_KEYS = ('type', 'uri', 'use-cwuri-as-url')
def remove_sensitive_information(self, sourcedef):
--- a/server/sources/datafeed.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/sources/datafeed.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -81,43 +81,30 @@
}),
)
- def __init__(self, repo, source_config, eid=None):
- AbstractSource.__init__(self, repo, source_config, eid)
- self.update_config(None, self.check_conf_dict(eid, source_config,
- fail_if_unknown=False))
-
def check_config(self, source_entity):
"""check configuration of source entity"""
- typedconfig = super(DataFeedSource, self).check_config(source_entity)
- if typedconfig['synchronization-interval'] < 60:
+ typed_config = super(DataFeedSource, self).check_config(source_entity)
+ if typed_config['synchronization-interval'] < 60:
_ = source_entity._cw._
msg = _('synchronization-interval must be greater than 1 minute')
raise ValidationError(source_entity.eid, {'config': msg})
- return typedconfig
+ return typed_config
def _entity_update(self, source_entity):
- source_entity.complete()
+ super(DataFeedSource, self)._entity_update(source_entity)
self.parser_id = source_entity.parser
self.latest_retrieval = source_entity.latest_retrieval
- if source_entity.url:
- self.urls = [url.strip() for url in source_entity.url.splitlines()
- if url.strip()]
- else:
- self.urls = []
- def update_config(self, source_entity, typedconfig):
- """update configuration from source entity. `typedconfig` is config
+ def update_config(self, source_entity, typed_config):
+ """update configuration from source entity. `typed_config` is config
properly typed with defaults set
"""
- self.synchro_interval = timedelta(seconds=typedconfig['synchronization-interval'])
- self.max_lock_lifetime = timedelta(seconds=typedconfig['max-lock-lifetime'])
- if source_entity is not None:
- self._entity_update(source_entity)
- self.config = typedconfig
+ super(DataFeedSource, self).update_config(source_entity, typed_config)
+ self.synchro_interval = timedelta(seconds=typed_config['synchronization-interval'])
+ self.max_lock_lifetime = timedelta(seconds=typed_config['max-lock-lifetime'])
def init(self, activated, source_entity):
- if activated:
- self._entity_update(source_entity)
+ super(DataFeedSource, self).init(activated, source_entity)
self.parser_id = source_entity.parser
self.load_mapping(source_entity._cw)
@@ -406,7 +393,7 @@
attrs = dict( (k, v) for k, v in attrs.iteritems()
if v != getattr(entity, k))
if attrs:
- entity.set_attributes(**attrs)
+ entity.cw_set(**attrs)
self.notify_updated(entity)
--- a/server/sources/ldapfeed.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/sources/ldapfeed.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -32,13 +32,3 @@
options = datafeed.DataFeedSource.options + ldaputils.LDAPSourceMixIn.options
- def update_config(self, source_entity, typedconfig):
- """update configuration from source entity. `typedconfig` is config
- properly typed with defaults set
- """
- datafeed.DataFeedSource.update_config(self, source_entity, typedconfig)
- ldaputils.LDAPSourceMixIn.update_config(self, source_entity, typedconfig)
-
- def _entity_update(self, source_entity):
- datafeed.DataFeedSource._entity_update(self, source_entity)
- ldaputils.LDAPSourceMixIn._entity_update(self, source_entity)
--- a/server/sources/ldapuser.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/sources/ldapuser.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -67,26 +67,11 @@
)
- def __init__(self, repo, source_config, eid=None):
- AbstractSource.__init__(self, repo, source_config, eid)
- self.update_config(None, self.check_conf_dict(eid, source_config,
- fail_if_unknown=False))
-
- def _entity_update(self, source_entity):
- # XXX copy from datafeed source
- if source_entity.url:
- self.urls = [url.strip() for url in source_entity.url.splitlines()
- if url.strip()]
- else:
- self.urls = []
- # /end XXX
- ldaputils.LDAPSourceMixIn._entity_update(self, source_entity)
-
def update_config(self, source_entity, typedconfig):
"""update configuration from source entity. `typedconfig` is config
properly typed with defaults set
"""
- ldaputils.LDAPSourceMixIn.update_config(self, source_entity, typedconfig)
+ super(LDAPUserSource, self).update_config(source_entity, typedconfig)
self._interval = typedconfig['synchronization-interval']
self._cache_ttl = max(71, typedconfig['cache-life-time'])
self.reset_caches()
--- a/server/sources/native.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/sources/native.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -961,6 +961,14 @@
cnx.commit()
return eid
+ def _handle_is_relation_sql(self, session, sql, attrs):
+ """ Handler for specific is_relation sql that may be
+ overwritten in some stores"""
+ self.doexec(session, sql % attrs)
+
+ _handle_insert_entity_sql = doexec
+ _handle_is_instance_of_sql = _handle_source_relation_sql = _handle_is_relation_sql
+
def add_info(self, session, entity, source, extid, complete):
"""add type and source info for an eid into the system table"""
# begin by inserting eid/type/source/extid into the entities table
@@ -970,21 +978,22 @@
uri = 'system' if source.copy_based_source else source.uri
attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
'source': uri, 'asource': source.uri, 'mtime': datetime.utcnow()}
- self.doexec(session, self.sqlgen.insert('entities', attrs), attrs)
+ self._handle_insert_entity_sql(session, self.sqlgen.insert('entities', attrs), attrs)
# insert core relations: is, is_instance_of and cw_source
try:
- self.doexec(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)'
- % (entity.eid, eschema_eid(session, entity.e_schema)))
+ self._handle_is_relation_sql(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, eschema_eid(session, entity.e_schema)))
except IndexError:
# during schema serialization, skip
pass
else:
for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
- self.doexec(session, 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)'
- % (entity.eid, eschema_eid(session, eschema)))
+ self._handle_is_relation_sql(session,
+ 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, eschema_eid(session, eschema)))
if 'CWSource' in self.schema and source.eid is not None: # else, cw < 3.10
- self.doexec(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) '
- 'VALUES (%s,%s)' % (entity.eid, source.eid))
+ self._handle_is_relation_sql(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, source.eid))
# now we can update the full text index
if self.do_fti and self.need_fti_indexation(entity.__regid__):
if complete:
--- a/server/sources/pyrorql.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/sources/pyrorql.py Fri Jan 25 14:33:40 2013 +0100
@@ -23,9 +23,6 @@
import threading
from Pyro.errors import PyroError, ConnectionClosedError
-from logilab.common.configuration import REQUIRED
-
-from cubicweb import dbapi
from cubicweb import ConnectionError
from cubicweb.server.sources import ConnectionWrapper
@@ -34,44 +31,6 @@
class PyroRQLSource(RemoteSource):
"""External repository source, using Pyro connection"""
- CNX_TYPE = 'pyro'
-
- options = RemoteSource.options + (
- # XXX pyro-ns host/port
- ('pyro-ns-id',
- {'type' : 'string',
- 'default': REQUIRED,
- 'help': 'identifier of the repository in the pyro name server',
- 'group': 'remote-source', 'level': 0,
- }),
- ('pyro-ns-host',
- {'type' : 'string',
- 'default': None,
- 'help': 'Pyro name server\'s host. If not set, default to the value \
-from all_in_one.conf. It may contains port information using <host>:<port> notation.',
- 'group': 'remote-source', 'level': 1,
- }),
- ('pyro-ns-group',
- {'type' : 'string',
- 'default': None,
- 'help': 'Pyro name server\'s group where the repository will be \
-registered. If not set, default to the value from all_in_one.conf.',
- 'group': 'remote-source', 'level': 2,
- }),
- )
-
- def _get_connection(self):
- """open and return a connection to the source"""
- nshost = self.config.get('pyro-ns-host') or self.repo.config['pyro-ns-host']
- nsgroup = self.config.get('pyro-ns-group') or self.repo.config['pyro-ns-group']
- self.info('connecting to instance :%s.%s for user %s',
- nsgroup, self.config['pyro-ns-id'], self.config['cubicweb-user'])
- return dbapi.connect(database=self.config['pyro-ns-id'],
- login=self.config['cubicweb-user'],
- password=self.config['cubicweb-password'],
- host=nshost, group=nsgroup,
- setvreg=False)
-
def get_connection(self):
try:
return self._get_connection()
--- a/server/sources/remoterql.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/sources/remoterql.py Fri Jan 25 14:33:40 2013 +0100
@@ -49,8 +49,6 @@
class RemoteSource(AbstractSource):
"""Generic external repository source"""
- CNX_TYPE = None # Must be ovewritted !
-
# boolean telling if modification hooks should be called when something is
# modified in this source
should_call_hooks = False
@@ -99,12 +97,11 @@
def __init__(self, repo, source_config, eid=None):
super(RemoteSource, self).__init__(repo, source_config, eid)
- self.update_config(None, self.check_conf_dict(eid, source_config,
- fail_if_unknown=False))
self._query_cache = TimedCache(1800)
def update_config(self, source_entity, processed_config):
"""update configuration from source entity"""
+ super(RemoteSource, self).update_config(source_entity, processed_config)
baseurl = processed_config.get('base-url')
if baseurl and not baseurl.endswith('/'):
processed_config['base-url'] += '/'
@@ -113,14 +110,10 @@
if source_entity is not None:
self.latest_retrieval = source_entity.latest_retrieval
- def _get_connection(self):
- """open and return a connection to the source"""
- self.info('connecting to source %(base-url)s with user %(cubicweb-user)s',
- self.config)
- cnxprops = ConnectionProperties(cnxtype=self.CNX_TYPE)
- return dbapi.connect(login=self.config['cubicweb-user'],
- password=self.config['cubicweb-password'],
- cnxprops=cnxprops)
+ def _entity_update(self, source_entity):
+ super(RemoteSource, self)._entity_update(source_entity)
+ if self.urls and len(self.urls) > 1:
+ raise ValidationError(source_entity.eid, {'url': _('can only have one url')})
def get_connection(self):
try:
@@ -129,6 +122,13 @@
self.critical("can't get connection to source %s: %s", self.uri, ex)
return ConnectionWrapper()
+ def _get_connection(self):
+ """open and return a connection to the source"""
+ self.info('connecting to source %s as user %s',
+ self.urls[0], self.config['cubicweb-user'])
+ # XXX check protocol according to source type (zmq / pyro)
+ return dbapi.connect(self.urls[0], login=self.config['cubicweb-user'],
+ password=self.config['cubicweb-password'])
def reset_caches(self):
"""method called during test to reset potential source caches"""
@@ -239,7 +239,7 @@
"""synchronize content known by this repository with content in the
external repository
"""
- self.info('synchronizing remote %s source %s', (self.CNX_TYPE, self.uri))
+ self.info('synchronizing remote source %s', self.uri)
cnx = self.get_connection()
try:
extrepo = cnx._repo
--- a/server/sources/storages.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/sources/storages.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -152,6 +152,7 @@
"""an entity using this storage for attr has been added"""
if entity._cw.transaction_data.get('fs_importing'):
binary = Binary.from_file(entity.cw_edited[attr].getvalue())
+ entity._cw_dont_cache_attribute(attr, repo_side=True)
else:
binary = entity.cw_edited.pop(attr)
fpath = self.new_fs_path(entity, attr)
@@ -170,6 +171,7 @@
# We do not need to create it but we need to fetch the content of
# the file as the actual content of the attribute
fpath = entity.cw_edited[attr].getvalue()
+ entity._cw_dont_cache_attribute(attr, repo_side=True)
assert fpath is not None
binary = Binary.from_file(fpath)
else:
--- a/server/sources/zmqrql.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/sources/zmqrql.py Fri Jan 25 14:33:40 2013 +0100
@@ -24,4 +24,3 @@
class ZMQRQLSource(RemoteSource):
"""External repository source, using ZMQ sockets"""
- CNX_TYPE = 'zmq'
--- a/server/ssplanner.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/ssplanner.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -27,7 +27,6 @@
from cubicweb import QueryError, typed_eid
from cubicweb.schema import VIRTUAL_RTYPES
from cubicweb.rqlrewrite import add_types_restriction
-from cubicweb.server.session import security_enabled
from cubicweb.server.edition import EditedEntity
READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
@@ -87,7 +86,7 @@
# the generated select substep if not emited (eg nothing
# to be selected)
if checkread and eid not in neweids:
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
eschema(session.describe(eid)[0]).check_perm(
session, 'read', eid=eid)
eidconsts[lhs.variable] = eid
--- a/server/test/unittest_checkintegrity.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_checkintegrity.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -29,8 +29,9 @@
handler = get_test_db_handler(TestServerConfiguration(apphome=self.datadir))
handler.build_db_cache()
self.repo, self.cnx = handler.get_repo_and_cnx()
- self.execute = self.cnx.cursor().execute
- self.session = self.repo._sessions[self.cnx.sessionid]
+ session = self.repo._get_session(self.cnx.sessionid, setcnxset=True)
+ self.session = session
+ self.execute = session.execute
sys.stderr = sys.stdout = StringIO()
def tearDown(self):
--- a/server/test/unittest_datafeed.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_datafeed.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2011-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/test/unittest_hook.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_hook.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/test/unittest_ldapuser.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_ldapuser.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -51,7 +51,7 @@
ldiffile = join(config.apphome, "ldap_test.ldif")
config.info('Initing ldap database')
cmdline = "/usr/sbin/slapadd -f %s -l %s -c" % (slapdconf, ldiffile)
- subprocess.call(cmdline, shell=True)
+ subprocess.check_call(cmdline, shell=True) == 0
#ldapuri = 'ldapi://' + join(basedir, "ldapi").replace('/', '%2f')
port = get_available_port(xrange(9000, 9100))
@@ -115,6 +115,14 @@
stats = source.repo_source.pull_data(session, force=True, raise_on_error=True)
session.commit()
+ def setUp(self):
+ super(LDAPTestBase, self).setUp()
+ # ldap source url in the database may use a different port as the one
+ # just attributed
+ lfsource = self.repo.sources_by_uri['ldapuser']
+ lfsource.urls = [URL]
+
+
class DeleteStuffFromLDAPFeedSourceTC(LDAPTestBase):
test_db_id = 'ldap-feed'
@@ -192,6 +200,7 @@
self.commit()
self.assertRaises(AuthenticationError, self.repo.connect, 'syt', password='syt')
+
class LDAPFeedSourceTC(LDAPTestBase):
test_db_id = 'ldap-feed'
@@ -204,13 +213,6 @@
lfsource = isession.repo.sources_by_uri['ldapuser']
stats = lfsource.pull_data(isession, force=True, raise_on_error=True)
- def setUp(self):
- super(LDAPFeedSourceTC, self).setUp()
- # ldap source url in the database may use a different port as the one
- # just attributed
- lfsource = self.repo.sources_by_uri['ldapuser']
- lfsource.urls = [URL]
-
def assertMetadata(self, entity):
self.assertTrue(entity.creation_date)
self.assertTrue(entity.modification_date)
--- a/server/test/unittest_msplanner.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_msplanner.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/test/unittest_multisources.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_multisources.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -33,7 +33,6 @@
MTIME = datetime.utcnow() - timedelta(0, 10)
EXTERN_SOURCE_CFG = u'''
-pyro-ns-id = extern
cubicweb-user = admin
cubicweb-password = gingkow
base-url=http://extern.org/
@@ -60,9 +59,10 @@
def pre_setup_database_multi(session, config):
session.create_entity('CWSource', name=u'extern', type=u'pyrorql',
- config=EXTERN_SOURCE_CFG)
+ url=u'pyro:///extern', config=EXTERN_SOURCE_CFG)
session.commit()
+
class TwoSourcesTC(CubicWebTC):
"""Main repo -> extern-multi -> extern
\-------------/
@@ -90,7 +90,6 @@
cls.cnx3.close()
TestServerConfiguration.no_sqlite_wrap = False
-
@classmethod
def _init_repo(cls):
repo2_handler = get_test_db_handler(cls._cfg2)
@@ -122,12 +121,11 @@
def pre_setup_database(session, config):
for uri, src_config in [('extern', EXTERN_SOURCE_CFG),
('extern-multi', '''
-pyro-ns-id = extern-multi
cubicweb-user = admin
cubicweb-password = gingkow
''')]:
source = session.create_entity('CWSource', name=unicode(uri),
- type=u'pyrorql',
+ type=u'pyrorql', url=u'pyro:///extern-multi',
config=unicode(src_config))
session.commit()
add_extern_mapping(source)
--- a/server/test/unittest_querier.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_querier.py Fri Jan 25 14:33:40 2013 +0100
@@ -29,10 +29,10 @@
from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.utils import crypt_password
from cubicweb.server.sources.native import make_schema
+from cubicweb.server.querier import manual_build_descr, _make_description
from cubicweb.devtools import get_test_db_handler, TestServerConfiguration
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
-from unittest_session import Variable
class FixedOffset(tzinfo):
def __init__(self, hours=0):
@@ -87,6 +87,30 @@
del repo, cnx
+class Variable:
+ def __init__(self, name):
+ self.name = name
+ self.children = []
+
+ def get_type(self, solution, args=None):
+ return solution[self.name]
+ def as_string(self):
+ return self.name
+
+class Function:
+ def __init__(self, name, varname):
+ self.name = name
+ self.children = [Variable(varname)]
+ def get_type(self, solution, args=None):
+ return 'Int'
+
+class MakeDescriptionTC(TestCase):
+ def test_known_values(self):
+ solution = {'A': 'Int', 'B': 'CWUser'}
+ self.assertEqual(_make_description((Function('max', 'A'), Variable('B')), {}, solution),
+ ['Int','CWUser'])
+
+
class UtilsTC(BaseQuerierTC):
setUpClass = classmethod(setUpClass)
tearDownClass = classmethod(tearDownClass)
@@ -242,6 +266,28 @@
rset = self.execute('Any %(x)s', {'x': u'str'})
self.assertEqual(rset.description[0][0], 'String')
+ def test_build_descr1(self):
+ rset = self.execute('(Any U,L WHERE U login L) UNION (Any G,N WHERE G name N, G is CWGroup)')
+ rset.req = self.session
+ orig_length = len(rset)
+ rset.rows[0][0] = 9999999
+ description = manual_build_descr(rset.req, rset.syntax_tree(), None, rset.rows)
+ self.assertEqual(len(description), orig_length - 1)
+ self.assertEqual(len(rset.rows), orig_length - 1)
+ self.assertNotEqual(rset.rows[0][0], 9999999)
+
+ def test_build_descr2(self):
+ rset = self.execute('Any X,Y WITH X,Y BEING ((Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G))')
+ for x, y in rset.description:
+ if y is not None:
+ self.assertEqual(y, 'CWGroup')
+
+ def test_build_descr3(self):
+ rset = self.execute('(Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G)')
+ for x, y in rset.description:
+ if y is not None:
+ self.assertEqual(y, 'CWGroup')
+
class QuerierTC(BaseQuerierTC):
setUpClass = classmethod(setUpClass)
--- a/server/test/unittest_repository.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_repository.py Fri Jan 25 14:33:40 2013 +0100
@@ -36,7 +36,7 @@
UnknownEid, AuthenticationError, Unauthorized, QueryError)
from cubicweb.predicates import is_instance
from cubicweb.schema import CubicWebSchema, RQLConstraint
-from cubicweb.dbapi import connect, multiple_connections_unfix, ConnectionProperties
+from cubicweb.dbapi import connect, multiple_connections_unfix
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.repotest import tuplify
from cubicweb.server import repository, hook
@@ -360,7 +360,8 @@
def _pyro_client(self, done):
- cnx = connect(self.repo.config.appid, u'admin', password='gingkow',
+ cnx = connect('pyro:///'+self.repo.config.appid,
+ u'admin', password='gingkow',
initlog=False) # don't reset logging configuration
try:
cnx.load_appobjects(subpath=('entities',))
@@ -395,7 +396,7 @@
t.start()
zmq_server = ZMQRepositoryServer(self.repo)
- zmq_server.connect('tcp://127.0.0.1:41415')
+ zmq_server.connect('zmqpickle-tcp://127.0.0.1:41415')
t2 = threading.Thread(target=self._zmq_quit, args=(done, zmq_server,))
t2.start()
@@ -414,10 +415,8 @@
srv.quit()
def _zmq_client(self, done):
- cnxprops = ConnectionProperties('zmq')
try:
- cnx = connect('tcp://127.0.0.1:41415', u'admin', password=u'gingkow',
- cnxprops=cnxprops,
+ cnx = connect('zmqpickle-tcp://127.0.0.1:41415', u'admin', password=u'gingkow',
initlog=False) # don't reset logging configuration
try:
cnx.load_appobjects(subpath=('entities',))
@@ -522,7 +521,7 @@
self.commit()
self.assertEqual(len(c.reverse_fiche), 1)
- def test_set_attributes_in_before_update(self):
+ def test_cw_set_in_before_update(self):
# local hook
class DummyBeforeHook(Hook):
__regid__ = 'dummy-before-hook'
@@ -534,31 +533,31 @@
pendings = self._cw.transaction_data.setdefault('pending', set())
if self.entity.eid not in pendings:
pendings.add(self.entity.eid)
- self.entity.set_attributes(alias=u'foo')
+ self.entity.cw_set(alias=u'foo')
with self.temporary_appobjects(DummyBeforeHook):
req = self.request()
addr = req.create_entity('EmailAddress', address=u'a@b.fr')
- addr.set_attributes(address=u'a@b.com')
+ addr.cw_set(address=u'a@b.com')
rset = self.execute('Any A,AA WHERE X eid %(x)s, X address A, X alias AA',
{'x': addr.eid})
self.assertEqual(rset.rows, [[u'a@b.com', u'foo']])
- def test_set_attributes_in_before_add(self):
+ def test_cw_set_in_before_add(self):
# local hook
class DummyBeforeHook(Hook):
__regid__ = 'dummy-before-hook'
__select__ = Hook.__select__ & is_instance('EmailAddress')
events = ('before_add_entity',)
def __call__(self):
- # set_attributes is forbidden within before_add_entity()
- self.entity.set_attributes(alias=u'foo')
+ # cw_set is forbidden within before_add_entity()
+ self.entity.cw_set(alias=u'foo')
with self.temporary_appobjects(DummyBeforeHook):
req = self.request()
# XXX will fail with python -O
self.assertRaises(AssertionError, req.create_entity,
'EmailAddress', address=u'a@b.fr')
- def test_multiple_edit_set_attributes(self):
+ def test_multiple_edit_cw_set(self):
"""make sure cw_edited doesn't get cluttered
by previous entities on multiple set
"""
@@ -664,7 +663,7 @@
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEqual(rset.rows, [])
- req.user.set_relations(use_email=toto)
+ req.user.cw_set(use_email=toto)
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEqual(rset.rows, [[req.user.eid]])
@@ -674,11 +673,11 @@
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEqual(rset.rows, [])
tutu = req.create_entity('EmailAddress', address=u'tutu@logilab.fr')
- req.user.set_relations(use_email=tutu)
+ req.user.cw_set(use_email=tutu)
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'tutu'})
self.assertEqual(rset.rows, [[req.user.eid]])
- tutu.set_attributes(address=u'hip@logilab.fr')
+ tutu.cw_set(address=u'hip@logilab.fr')
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'tutu'})
self.assertEqual(rset.rows, [])
@@ -790,7 +789,7 @@
personnes.append(p)
abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M')
for j in xrange(0, 2000, 100):
- abraham.set_relations(personne_composite=personnes[j:j+100])
+ abraham.cw_set(personne_composite=personnes[j:j+100])
t1 = time.time()
self.info('creation: %.2gs', (t1 - t0))
req.cnx.commit()
@@ -816,7 +815,7 @@
t1 = time.time()
self.info('creation: %.2gs', (t1 - t0))
for j in xrange(100, 2000, 100):
- abraham.set_relations(personne_composite=personnes[j:j+100])
+ abraham.cw_set(personne_composite=personnes[j:j+100])
t2 = time.time()
self.info('more relations: %.2gs', (t2-t1))
req.cnx.commit()
@@ -836,7 +835,7 @@
t1 = time.time()
self.info('creation: %.2gs', (t1 - t0))
for j in xrange(100, 2000, 100):
- abraham.set_relations(personne_inlined=personnes[j:j+100])
+ abraham.cw_set(personne_inlined=personnes[j:j+100])
t2 = time.time()
self.info('more relations: %.2gs', (t2-t1))
req.cnx.commit()
@@ -917,7 +916,7 @@
p1 = req.create_entity('Personne', nom=u'Vincent')
p2 = req.create_entity('Personne', nom=u'Florent')
w = req.create_entity('Affaire', ref=u'wc')
- w.set_relations(todo_by=[p1,p2])
+ w.cw_set(todo_by=[p1,p2])
w.cw_clear_all_caches()
self.commit()
self.assertEqual(len(w.todo_by), 1)
@@ -928,9 +927,9 @@
p1 = req.create_entity('Personne', nom=u'Vincent')
p2 = req.create_entity('Personne', nom=u'Florent')
w = req.create_entity('Affaire', ref=u'wc')
- w.set_relations(todo_by=p1)
+ w.cw_set(todo_by=p1)
self.commit()
- w.set_relations(todo_by=p2)
+ w.cw_set(todo_by=p2)
w.cw_clear_all_caches()
self.commit()
self.assertEqual(len(w.todo_by), 1)
--- a/server/test/unittest_security.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_security.py Fri Jan 25 14:33:40 2013 +0100
@@ -473,9 +473,9 @@
anon = cu.connection.user(self.session)
# anonymous user can only read itself
rset = cu.execute('Any L WHERE X owned_by U, U login L')
- self.assertEqual(rset.rows, [['anon']])
+ self.assertEqual([['anon']], rset.rows)
rset = cu.execute('CWUser X')
- self.assertEqual(rset.rows, [[anon.eid]])
+ self.assertEqual([[anon.eid]], rset.rows)
# anonymous user can read groups (necessary to check allowed transitions for instance)
self.assert_(cu.execute('CWGroup X'))
# should only be able to read the anonymous user, not another one
@@ -488,7 +488,7 @@
# {'x': self.user.eid})
rset = cu.execute('CWUser X WHERE X eid %(x)s', {'x': anon.eid})
- self.assertEqual(rset.rows, [[anon.eid]])
+ self.assertEqual([[anon.eid]], rset.rows)
# but can't modify it
cu.execute('SET X login "toto" WHERE X eid %(x)s', {'x': anon.eid})
self.assertRaises(Unauthorized, self.commit)
--- a/server/test/unittest_session.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_session.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -17,33 +17,7 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
from __future__ import with_statement
-from logilab.common.testlib import TestCase, unittest_main, mock_object
-
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.server.session import _make_description, hooks_control
-
-class Variable:
- def __init__(self, name):
- self.name = name
- self.children = []
-
- def get_type(self, solution, args=None):
- return solution[self.name]
- def as_string(self):
- return self.name
-
-class Function:
- def __init__(self, name, varname):
- self.name = name
- self.children = [Variable(varname)]
- def get_type(self, solution, args=None):
- return 'Int'
-
-class MakeDescriptionTC(TestCase):
- def test_known_values(self):
- solution = {'A': 'Int', 'B': 'CWUser'}
- self.assertEqual(_make_description((Function('max', 'A'), Variable('B')), {}, solution),
- ['Int','CWUser'])
class InternalSessionTC(CubicWebTC):
@@ -61,7 +35,7 @@
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set())
self.assertEqual(len(session._tx_data), 1)
- with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata'):
+ with session.deny_all_hooks_but('metadata'):
self.assertEqual(session.hooks_mode, session.HOOKS_DENY_ALL)
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set(('metadata',)))
@@ -73,7 +47,7 @@
self.assertEqual(session.hooks_mode, session.HOOKS_DENY_ALL)
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set(('metadata',)))
- with hooks_control(session, session.HOOKS_ALLOW_ALL, 'integrity'):
+ with session.allow_all_hooks_but('integrity'):
self.assertEqual(session.hooks_mode, session.HOOKS_ALLOW_ALL)
self.assertEqual(session.disabled_hook_categories, set(('integrity',)))
self.assertEqual(session.enabled_hook_categories, set(('metadata',))) # not changed in such case
@@ -88,27 +62,7 @@
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set())
- def test_build_descr1(self):
- rset = self.execute('(Any U,L WHERE U login L) UNION (Any G,N WHERE G name N, G is CWGroup)')
- orig_length = len(rset)
- rset.rows[0][0] = 9999999
- description = self.session.build_description(rset.syntax_tree(), None, rset.rows)
- self.assertEqual(len(description), orig_length - 1)
- self.assertEqual(len(rset.rows), orig_length - 1)
- self.assertFalse(rset.rows[0][0] == 9999999)
-
- def test_build_descr2(self):
- rset = self.execute('Any X,Y WITH X,Y BEING ((Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G))')
- for x, y in rset.description:
- if y is not None:
- self.assertEqual(y, 'CWGroup')
-
- def test_build_descr3(self):
- rset = self.execute('(Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G)')
- for x, y in rset.description:
- if y is not None:
- self.assertEqual(y, 'CWGroup')
-
if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
unittest_main()
--- a/server/test/unittest_storage.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_storage.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -99,7 +99,7 @@
f1 = self.create_file()
self.commit()
self.assertEqual(file(expected_filepath).read(), 'the-data')
- f1.set_attributes(data=Binary('the new data'))
+ f1.cw_set(data=Binary('the new data'))
self.rollback()
self.assertEqual(file(expected_filepath).read(), 'the-data')
f1.cw_delete()
@@ -118,7 +118,7 @@
def test_bfss_fs_importing_doesnt_touch_path(self):
self.session.transaction_data['fs_importing'] = True
filepath = osp.abspath(__file__)
- f1 = self.session.create_entity('File', data=Binary(filepath),
+ f1 = self.request().create_entity('File', data=Binary(filepath),
data_format=u'text/plain', data_name=u'foo')
self.assertEqual(self.fspath(f1), filepath)
@@ -196,15 +196,18 @@
filepath = osp.abspath(__file__)
f1 = self.session.create_entity('File', data=Binary(filepath),
data_format=u'text/plain', data_name=u'foo')
- self.assertEqual(f1.data.getvalue(), file(filepath).read(),
- 'files content differ')
+ cw_value = f1.data.getvalue()
+ fs_value = file(filepath).read()
+ if cw_value != fs_value:
+ self.fail('cw value %r is different from file content' % cw_value)
+
@tag('update')
def test_bfss_update_with_existing_data(self):
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
data_format=u'text/plain', data_name=u'foo')
- # NOTE: do not use set_attributes() which would automatically
+ # NOTE: do not use cw_set() which would automatically
# update f1's local dict. We want the pure rql version to work
self.execute('SET F data %(d)s WHERE F eid %(f)s',
{'d': Binary('some other data'), 'f': f1.eid})
@@ -218,7 +221,7 @@
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
data_format=u'text/plain', data_name=u'foo.txt')
- # NOTE: do not use set_attributes() which would automatically
+ # NOTE: do not use cw_set() which would automatically
# update f1's local dict. We want the pure rql version to work
self.commit()
old_path = self.fspath(f1)
@@ -240,7 +243,7 @@
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
data_format=u'text/plain', data_name=u'foo.txt')
- # NOTE: do not use set_attributes() which would automatically
+ # NOTE: do not use cw_set() which would automatically
# update f1's local dict. We want the pure rql version to work
self.commit()
old_path = self.fspath(f1)
@@ -265,7 +268,7 @@
f = self.session.create_entity('Affaire', opt_attr=Binary('toto'))
self.session.commit()
self.session.set_cnxset()
- f.set_attributes(opt_attr=None)
+ f.cw_set(opt_attr=None)
self.session.commit()
@tag('fs_importing', 'update')
--- a/server/test/unittest_undo.py Thu Jan 24 16:13:40 2013 +0100
+++ b/server/test/unittest_undo.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -203,7 +203,7 @@
c.cw_delete()
txuuid = self.commit()
c2 = session.create_entity('Card', title=u'hip', content=u'hip')
- p.set_relations(fiche=c2)
+ p.cw_set(fiche=c2)
self.commit()
self.assertUndoTransaction(txuuid, [
"Can't restore object relation fiche to entity "
@@ -217,7 +217,7 @@
session = self.session
g = session.create_entity('CWGroup', name=u'staff')
session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
- self.toto.set_relations(in_group=g)
+ self.toto.cw_set(in_group=g)
self.commit()
self.toto.cw_delete()
txuuid = self.commit()
@@ -228,6 +228,7 @@
"%s doesn't exist anymore." % g.eid])
with self.assertRaises(ValidationError) as cm:
self.commit()
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.entity, self.toto.eid)
self.assertEqual(cm.exception.errors,
{'in_group-subject': u'at least one relation in_group is '
@@ -265,7 +266,7 @@
email = self.request().create_entity('EmailAddress', address=u'tutu@cubicweb.org')
prop = self.request().create_entity('CWProperty', pkey=u'ui.default-text-format',
value=u'text/html')
- tutu.set_relations(use_email=email, reverse_for_user=prop)
+ tutu.cw_set(use_email=email, reverse_for_user=prop)
self.commit()
with self.assertRaises(ValidationError) as cm:
self.cnx.undo_transaction(txuuid)
@@ -278,7 +279,7 @@
g = session.create_entity('CWGroup', name=u'staff')
txuuid = self.commit()
session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
- self.toto.set_relations(in_group=g)
+ self.toto.cw_set(in_group=g)
self.commit()
with self.assertRaises(ValidationError) as cm:
self.cnx.undo_transaction(txuuid)
@@ -304,7 +305,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis', fiche=c)
self.commit()
- p.set_relations(fiche=None)
+ p.cw_set(fiche=None)
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
self.commit()
@@ -319,7 +320,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis', fiche=c)
self.commit()
- p.set_relations(fiche=None)
+ p.cw_set(fiche=None)
txuuid = self.commit()
c.cw_delete()
self.commit()
@@ -339,7 +340,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis')
self.commit()
- p.set_relations(fiche=c)
+ p.cw_set(fiche=c)
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
self.commit()
@@ -354,7 +355,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis')
self.commit()
- p.set_relations(fiche=c)
+ p.cw_set(fiche=c)
txuuid = self.commit()
c.cw_delete()
self.commit()
@@ -369,7 +370,7 @@
c2 = session.create_entity('Card', title=u'hip', content=u'hip')
p = session.create_entity('Personne', nom=u'louis', fiche=c1)
self.commit()
- p.set_relations(fiche=c2)
+ p.cw_set(fiche=c2)
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
self.commit()
@@ -385,7 +386,7 @@
c2 = session.create_entity('Card', title=u'hip', content=u'hip')
p = session.create_entity('Personne', nom=u'louis', fiche=c1)
self.commit()
- p.set_relations(fiche=c2)
+ p.cw_set(fiche=c2)
txuuid = self.commit()
c1.cw_delete()
self.commit()
@@ -401,7 +402,7 @@
p = session.create_entity('Personne', nom=u'toto')
session.commit()
self.session.set_cnxset()
- p.set_attributes(nom=u'titi')
+ p.cw_set(nom=u'titi')
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
p.cw_clear_all_caches()
@@ -412,7 +413,7 @@
p = session.create_entity('Personne', nom=u'toto')
session.commit()
self.session.set_cnxset()
- p.set_attributes(nom=u'titi')
+ p.cw_set(nom=u'titi')
txuuid = self.commit()
p.cw_delete()
self.commit()
--- a/sobjects/ldapparser.py Thu Jan 24 16:13:40 2013 +0100
+++ b/sobjects/ldapparser.py Fri Jan 25 14:33:40 2013 +0100
@@ -97,7 +97,7 @@
attrs = dict( (k, v) for k, v in attrs.iteritems()
if v != getattr(entity, k))
if attrs:
- entity.set_attributes(**attrs)
+ entity.cw_set(**attrs)
self.notify_updated(entity)
def ldap2cwattrs(self, sdict, tdict=None):
@@ -133,7 +133,7 @@
groups = filter(None, [self._get_group(name)
for name in self.source.user_default_groups])
if groups:
- entity.set_relations(in_group=groups)
+ entity.cw_set(in_group=groups)
self._process_email(entity, sourceparams)
def is_deleted(self, extidplus, etype, eid):
@@ -162,9 +162,9 @@
email = self.extid2entity(emailextid, 'EmailAddress',
address=emailaddr)
if entity.primary_email:
- entity.set_relations(use_email=email)
+ entity.cw_set(use_email=email)
else:
- entity.set_relations(primary_email=email)
+ entity.cw_set(primary_email=email)
elif self.sourceuris:
# pop from sourceuris anyway, else email may be removed by the
# source once import is finished
--- a/test/unittest_cwconfig.py Thu Jan 24 16:13:40 2013 +0100
+++ b/test/unittest_cwconfig.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -101,10 +101,10 @@
self.assertEqual(self.config.expand_cubes(('email', 'comment')),
['email', 'comment', 'file'])
- def test_vregistry_path(self):
+ def test_appobjects_path(self):
self.config.__class__.CUBES_PATH = [CUSTOM_CUBES_DIR]
self.config.adjust_sys_path()
- self.assertEqual([unabsolutize(p) for p in self.config.vregistry_path()],
+ self.assertEqual([unabsolutize(p) for p in self.config.appobjects_path()],
['entities', 'web/views', 'sobjects', 'hooks',
'file/entities', 'file/views.py', 'file/hooks',
'email/entities.py', 'email/views', 'email/hooks.py',
--- a/test/unittest_dbapi.py Thu Jan 24 16:13:40 2013 +0100
+++ b/test/unittest_dbapi.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/test/unittest_entity.py Thu Jan 24 16:13:40 2013 +0100
+++ b/test/unittest_entity.py Fri Jan 25 14:33:40 2013 +0100
@@ -701,23 +701,23 @@
self.assertEqual(card4.rest_path(), unicode(card4.eid))
- def test_set_attributes(self):
+ def test_cw_set_attributes(self):
req = self.request()
person = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien')
self.assertEqual(person.prenom, u'adrien')
self.assertEqual(person.nom, u'di mascio')
- person.set_attributes(prenom=u'sylvain', nom=u'thénault')
+ person.cw_set(prenom=u'sylvain', nom=u'thénault')
person = self.execute('Personne P').get_entity(0, 0) # XXX retreival needed ?
self.assertEqual(person.prenom, u'sylvain')
self.assertEqual(person.nom, u'thénault')
- def test_set_relations(self):
+ def test_cw_set_relations(self):
req = self.request()
person = req.create_entity('Personne', nom=u'chauvat', prenom=u'nicolas')
note = req.create_entity('Note', type=u'x')
- note.set_relations(ecrit_par=person)
+ note.cw_set(ecrit_par=person)
note = req.create_entity('Note', type=u'y')
- note.set_relations(ecrit_par=person.eid)
+ note.cw_set(ecrit_par=person.eid)
self.assertEqual(len(person.reverse_ecrit_par), 2)
def test_metainformation_and_external_absolute_url(self):
--- a/test/unittest_migration.py Thu Jan 24 16:13:40 2013 +0100
+++ b/test/unittest_migration.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -108,7 +108,13 @@
self.assertEqual(source['db-driver'], 'sqlite')
handler = get_test_db_handler(config)
handler.init_test_database()
-
+ handler.build_db_cache()
+ repo, cnx = handler.get_repo_and_cnx()
+ cu = cnx.cursor()
+ self.assertEqual(cu.execute('Any SN WHERE X is CWUser, X login "admin", X in_state S, S name SN').rows,
+ [['activated']])
+ cnx.close()
+ repo.shutdown()
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_rset.py Thu Jan 24 16:13:40 2013 +0100
+++ b/test/unittest_rset.py Fri Jan 25 14:33:40 2013 +0100
@@ -112,6 +112,17 @@
# '%stask/title/go' % baseurl)
# empty _restpath should not crash
self.compare_urls(req.build_url('view', _restpath=''), baseurl)
+ self.assertNotIn('https', req.build_url('view', vid='foo', rql='yo',
+ __secure__=True))
+ try:
+ self.config.global_set_option('https-url', 'https://testing.fr/')
+ self.assertTrue('https', req.build_url('view', vid='foo', rql='yo',
+ __secure__=True))
+ self.compare_urls(req.build_url('view', vid='foo', rql='yo',
+ __secure__=True),
+ '%sview?vid=foo&rql=yo' % req.base_url(secure=True))
+ finally:
+ self.config.global_set_option('https-url', None)
def test_build(self):
--- a/test/unittest_schema.py Thu Jan 24 16:13:40 2013 +0100
+++ b/test/unittest_schema.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -350,8 +350,8 @@
class WorkflowShemaTC(CubicWebTC):
def test_trinfo_default_format(self):
- tr = self.session.user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
- self.assertEqual(tr.comment_format, 'text/plain')
+ tr = self.request().user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
+ self.assertEqual(tr.comment_format, 'text/plain')
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_utils.py Thu Jan 24 16:13:40 2013 +0100
+++ b/test/unittest_utils.py Fri Jan 25 14:33:40 2013 +0100
@@ -26,7 +26,7 @@
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.utils import (make_uid, UStringIO, SizeConstrainedList,
- RepeatList, HTMLHead, QueryCache)
+ RepeatList, HTMLHead, QueryCache, parse_repo_uri)
from cubicweb.entity import Entity
try:
@@ -50,6 +50,25 @@
'some numeric character, got %s' % uid)
d.add(uid)
+
+class TestParseRepoUri(TestCase):
+
+ def test_parse_repo_uri(self):
+ self.assertEqual(('inmemory', None, 'myapp'),
+ parse_repo_uri('myapp'))
+ self.assertEqual(('inmemory', None, 'myapp'),
+ parse_repo_uri('inmemory://myapp'))
+ self.assertEqual(('pyro', 'pyro-ns-host:pyro-ns-port', '/myapp'),
+ parse_repo_uri('pyro://pyro-ns-host:pyro-ns-port/myapp'))
+ self.assertEqual(('pyroloc', 'host:port', '/appkey'),
+ parse_repo_uri('pyroloc://host:port/appkey'))
+ self.assertEqual(('zmqpickle-tcp', '127.0.0.1:666', ''),
+ parse_repo_uri('zmqpickle-tcp://127.0.0.1:666'))
+ with self.assertRaises(NotImplementedError):
+ parse_repo_uri('foo://bar')
+
+
+
class TestQueryCache(TestCase):
def test_querycache(self):
c = QueryCache(ceiling=20)
--- a/utils.py Thu Jan 24 16:13:40 2013 +0100
+++ b/utils.py Fri Jan 25 14:33:40 2013 +0100
@@ -33,6 +33,7 @@
from uuid import uuid4
from warnings import warn
from threading import Lock
+from urlparse import urlparse
from logging import getLogger
@@ -574,6 +575,25 @@
return dict1
+def parse_repo_uri(uri):
+ """ transform a command line uri into a (protocol, hostport, appid), e.g:
+ <myapp> -> 'inmemory', None, '<myapp>'
+ inmemory://<myapp> -> 'inmemory', None, '<myapp>'
+ pyro://[host][:port] -> 'pyro', 'host:port', None
+ zmqpickle://[host][:port] -> 'zmqpickle', 'host:port', None
+ """
+ parseduri = urlparse(uri)
+ scheme = parseduri.scheme
+ if scheme == '':
+ return ('inmemory', None, parseduri.path)
+ if scheme == 'inmemory':
+ return (scheme, None, parseduri.netloc)
+ if scheme in ('pyro', 'pyroloc') or scheme.startswith('zmqpickle-'):
+ return (scheme, parseduri.netloc, parseduri.path)
+ raise NotImplementedError('URI protocol not implemented for `%s`' % uri)
+
+
+
logger = getLogger('cubicweb.utils')
class QueryCache(object):
--- a/view.py Thu Jan 24 16:13:40 2013 +0100
+++ b/view.py Fri Jan 25 14:33:40 2013 +0100
@@ -26,7 +26,7 @@
from functools import partial
from logilab.common.deprecation import deprecated
-from logilab.common.registry import classid, yes
+from logilab.common.registry import yes
from logilab.mtconverter import xml_escape
from rql import nodes
@@ -608,7 +608,7 @@
if hasattr(entity, func.__name__):
warn('[3.9] %s method is deprecated, define it on a custom '
'%s for %s instead' % (func.__name__, iface,
- classid(entity.__class__)),
+ entity.__class__),
DeprecationWarning)
member = getattr(entity, func.__name__)
if callable(member):
--- a/web/application.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/application.py Fri Jan 25 14:33:40 2013 +0100
@@ -462,7 +462,6 @@
result = self.notfound_content(req)
req.status_out = ex.status
except ValidationError, ex:
- req.status_out = httplib.CONFLICT
result = self.validation_error_handler(req, ex)
except RemoteCallFailed, ex:
result = self.ajax_error_handler(req, ex)
@@ -486,7 +485,7 @@
except (AuthenticationError, LogOut):
# the rollback is handled in the finally
raise
- ### Last defence line
+ ### Last defense line
except BaseException, ex:
result = self.error_handler(req, ex, tb=True)
finally:
@@ -517,7 +516,7 @@
return ''
def validation_error_handler(self, req, ex):
- ex.errors = dict((k, v) for k, v in ex.errors.items())
+ ex.translate(req._) # translate messages using ui language
if '__errorurl' in req.form:
forminfo = {'error': ex,
'values': req.form,
@@ -532,6 +531,7 @@
req.headers_out.setHeader('location', str(location))
req.status_out = httplib.SEE_OTHER
return ''
+ req.status_out = httplib.CONFLICT
return self.error_handler(req, ex, tb=False)
def error_handler(self, req, ex, tb=False):
--- a/web/data/cubicweb.ajax.js Thu Jan 24 16:13:40 2013 +0100
+++ b/web/data/cubicweb.ajax.js Fri Jan 25 14:33:40 2013 +0100
@@ -70,7 +70,7 @@
callback.apply(null, args);
}
} catch(error) {
- this.error(this.xhr, null, error);
+ this.error(this._req, null, error);
}
},
@@ -704,7 +704,7 @@
var ajaxArgs = ['render', formparams, registry, compid];
ajaxArgs = ajaxArgs.concat(cw.utils.sliceList(arguments, 4));
var params = ajaxFuncArgs.apply(null, ajaxArgs);
- return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap');
+ return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap', true);
}
/* ajax tabs ******************************************************************/
--- a/web/data/cubicweb.css Thu Jan 24 16:13:40 2013 +0100
+++ b/web/data/cubicweb.css Fri Jan 25 14:33:40 2013 +0100
@@ -545,6 +545,16 @@
padding-left: 2em;
}
+/* actions around tables */
+.tableactions span {
+ padding: 0 18px;
+ height: 24px;
+ background: #F8F8F8;
+ border: 1px solid #DFDFDF;
+ border-bottom: none;
+ border-radius: 4px 4px 0 0;
+}
+
/* custom boxes */
.search_box div.boxBody {
--- a/web/data/cubicweb.facets.js Thu Jan 24 16:13:40 2013 +0100
+++ b/web/data/cubicweb.facets.js Fri Jan 25 14:33:40 2013 +0100
@@ -68,6 +68,14 @@
var bkUrl = $bkLink.attr('cubicweb:target') + '&path=' + encodeURIComponent(bkPath);
$bkLink.attr('href', bkUrl);
}
+ var $focusLink = jQuery('#focusLink');
+ if ($focusLink.length) {
+ var url = baseuri()+ 'view?rql=' + encodeURIComponent(rql);
+ if (vid) {
+ url += '&vid=' + encodeURIComponent(vid);
+ }
+ $focusLink.attr('href', url);
+ }
var toupdate = result[1];
var extraparams = vidargs;
if (paginate) { extraparams['paginate'] = '1'; } // XXX in vidargs
--- a/web/data/cubicweb.old.css Thu Jan 24 16:13:40 2013 +0100
+++ b/web/data/cubicweb.old.css Fri Jan 25 14:33:40 2013 +0100
@@ -899,6 +899,16 @@
padding-left: 0.5em;
}
+/* actions around tables */
+.tableactions span {
+ padding: 0 18px;
+ height: 24px;
+ background: #F8F8F8;
+ border: 1px solid #DFDFDF;
+ border-bottom: none;
+ border-radius: 4px 4px 0 0;
+}
+
/***************************************/
/* error view (views/management.py) */
/***************************************/
--- a/web/formfields.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/formfields.py Fri Jan 25 14:33:40 2013 +0100
@@ -79,8 +79,8 @@
from cubicweb import Binary, tags, uilib
from cubicweb.utils import support_args
from cubicweb.web import INTERNAL_FIELD_VALUE, ProcessFormError, eid_param, \
- formwidgets as fw, uicfg
-
+ formwidgets as fw
+from cubicweb.web.views import uicfg
class UnmodifiedField(Exception):
"""raise this when a field has not actually been edited and you want to skip
@@ -465,8 +465,6 @@
# attribute or relation
return True
# if it's a non final relation, we need the eids
- # XXX underlying regression: getattr(ent, 'foo') used to return
- # a tuple, now we get a list
if isinstance(previous_value, (list, tuple)):
# widget should return a set of untyped eids
previous_value = set(e.eid for e in previous_value)
@@ -1163,7 +1161,7 @@
_AFF_KWARGS = uicfg.autoform_field_kwargs
-def guess_field(eschema, rschema, role='subject', **kwargs):
+def guess_field(eschema, rschema, role='subject', req=None, **kwargs):
"""This function return the most adapted field to edit the given relation
(`rschema`) where the given entity type (`eschema`) is the subject or object
(`role`).
@@ -1211,12 +1209,16 @@
kwargs['max_length'] = cstr.max
return StringField(**kwargs)
if fieldclass is FileField:
+ if req:
+ aff_kwargs = req.vreg['uicfg'].select('autoform_field_kwargs', req)
+ else:
+ aff_kwargs = _AFF_KWARGS
for metadata in KNOWN_METAATTRIBUTES:
metaschema = eschema.has_metadata(rschema, metadata)
if metaschema is not None:
- metakwargs = _AFF_KWARGS.etype_get(eschema, metaschema, 'subject')
+ metakwargs = aff_kwargs.etype_get(eschema, metaschema, 'subject')
kwargs['%s_field' % metadata] = guess_field(eschema, metaschema,
- **metakwargs)
+ req=req, **metakwargs)
return fieldclass(**kwargs)
return RelationField.fromcardinality(card, **kwargs)
--- a/web/httpcache.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/httpcache.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/request.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/request.py Fri Jan 25 14:33:40 2013 +0100
@@ -170,7 +170,6 @@
@property
def authmode(self):
"""Authentification mode of the instance
-
(see :ref:`WebServerConfig`)"""
return self.vreg.config['auth-mode']
@@ -227,14 +226,6 @@
# 3. default language
self.set_default_language(vreg)
- def set_language(self, lang):
- gettext, self.pgettext = self.translations[lang]
- self._ = self.__ = gettext
- self.lang = lang
- self.debug('request language: %s', lang)
- if self.cnx:
- self.cnx.set_session_props(lang=lang)
-
# input form parameters management ########################################
# common form parameters which should be protected against html values
@@ -366,7 +357,7 @@
def update_search_state(self):
"""update the current search state"""
searchstate = self.form.get('__mode')
- if not searchstate and self.cnx:
+ if not searchstate:
searchstate = self.session.data.get('search_state', 'normal')
self.set_search_state(searchstate)
@@ -377,8 +368,7 @@
else:
self.search_state = ('linksearch', searchstate.split(':'))
assert len(self.search_state[-1]) == 4
- if self.cnx:
- self.session.data['search_state'] = searchstate
+ self.session.data['search_state'] = searchstate
def match_search_state(self, rset):
"""when searching an entity to create a relation, return True if entities in
--- a/web/test/data/views.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/data/views.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -15,9 +15,7 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
-"""
from cubicweb.web import Redirect
from cubicweb.web.application import CubicWebPublisher
--- a/web/test/unittest_application.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_application.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/test/unittest_formfields.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_formfields.py Fri Jan 25 14:33:40 2013 +0100
@@ -35,10 +35,14 @@
config.bootstrap_cubes()
schema = config.load_schema()
-class GuessFieldTC(TestCase):
+class GuessFieldTC(CubicWebTC):
+
+ def setUp(self):
+ super(GuessFieldTC, self).setUp()
+ self.req = self.request()
def test_state_fields(self):
- title_field = guess_field(schema['State'], schema['name'])
+ title_field = guess_field(schema['State'], schema['name'], req=self.req)
self.assertIsInstance(title_field, StringField)
self.assertEqual(title_field.required, True)
@@ -48,7 +52,7 @@
# self.assertEqual(synopsis_field.required, False)
# self.assertEqual(synopsis_field.help, 'an abstract for this state')
- description_field = guess_field(schema['State'], schema['description'])
+ description_field = guess_field(schema['State'], schema['description'], req=self.req)
self.assertIsInstance(description_field, RichTextField)
self.assertEqual(description_field.required, False)
self.assertEqual(description_field.format_field, None)
@@ -56,7 +60,8 @@
# description_format_field = guess_field(schema['State'], schema['description_format'])
# self.assertEqual(description_format_field, None)
- description_format_field = guess_field(schema['State'], schema['description_format'])
+ description_format_field = guess_field(schema['State'], schema['description_format'],
+ req=self.req)
self.assertEqual(description_format_field.internationalizable, True)
self.assertEqual(description_format_field.sort, True)
@@ -66,22 +71,22 @@
def test_cwuser_fields(self):
- upassword_field = guess_field(schema['CWUser'], schema['upassword'])
+ upassword_field = guess_field(schema['CWUser'], schema['upassword'], req=self.req)
self.assertIsInstance(upassword_field, StringField)
self.assertIsInstance(upassword_field.widget, PasswordInput)
self.assertEqual(upassword_field.required, True)
- last_login_time_field = guess_field(schema['CWUser'], schema['last_login_time'])
+ last_login_time_field = guess_field(schema['CWUser'], schema['last_login_time'], req=self.req)
self.assertIsInstance(last_login_time_field, DateTimeField)
self.assertEqual(last_login_time_field.required, False)
- in_group_field = guess_field(schema['CWUser'], schema['in_group'])
+ in_group_field = guess_field(schema['CWUser'], schema['in_group'], req=self.req)
self.assertIsInstance(in_group_field, RelationField)
self.assertEqual(in_group_field.required, True)
self.assertEqual(in_group_field.role, 'subject')
self.assertEqual(in_group_field.help, 'groups grant permissions to the user')
- owned_by_field = guess_field(schema['CWUser'], schema['owned_by'], 'object')
+ owned_by_field = guess_field(schema['CWUser'], schema['owned_by'], 'object', req=self.req)
self.assertIsInstance(owned_by_field, RelationField)
self.assertEqual(owned_by_field.required, False)
self.assertEqual(owned_by_field.role, 'object')
@@ -95,7 +100,7 @@
# data_name_field = guess_field(schema['File'], schema['data_name'])
# self.assertEqual(data_name_field, None)
- data_field = guess_field(schema['File'], schema['data'])
+ data_field = guess_field(schema['File'], schema['data'], req=self.req)
self.assertIsInstance(data_field, FileField)
self.assertEqual(data_field.required, True)
self.assertIsInstance(data_field.format_field, StringField)
@@ -103,7 +108,7 @@
self.assertIsInstance(data_field.name_field, StringField)
def test_constraints_priority(self):
- salesterm_field = guess_field(schema['Salesterm'], schema['reason'])
+ salesterm_field = guess_field(schema['Salesterm'], schema['reason'], req=self.req)
constraints = schema['reason'].rdef('Salesterm', 'String').constraints
self.assertEqual([c.__class__ for c in constraints],
[SizeConstraint, StaticVocabularyConstraint])
@@ -112,7 +117,7 @@
def test_bool_field_base(self):
- field = guess_field(schema['CWAttribute'], schema['indexed'])
+ field = guess_field(schema['CWAttribute'], schema['indexed'], req=self.req)
self.assertIsInstance(field, BooleanField)
self.assertEqual(field.required, False)
self.assertIsInstance(field.widget, Radio)
@@ -121,7 +126,7 @@
def test_bool_field_explicit_choices(self):
field = guess_field(schema['CWAttribute'], schema['indexed'],
- choices=[(u'maybe', '1'), (u'no', '')])
+ choices=[(u'maybe', '1'), (u'no', '')], req=self.req)
self.assertIsInstance(field.widget, Radio)
self.assertEqual(field.vocabulary(mock(req=mock(_=unicode))),
[(u'maybe', '1'), (u'no', '')])
--- a/web/test/unittest_formwidgets.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_formwidgets.py Fri Jan 25 14:33:40 2013 +0100
@@ -20,7 +20,7 @@
from logilab.common.testlib import TestCase, unittest_main, mock_object as mock
from cubicweb.devtools import TestServerConfiguration, fake
-from cubicweb.web import uicfg, formwidgets, formfields
+from cubicweb.web import formwidgets, formfields
from cubes.file.entities import File
--- a/web/test/unittest_magicsearch.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_magicsearch.py Fri Jan 25 14:33:40 2013 +0100
@@ -230,5 +230,118 @@
self.assertEqual(rset.rql, 'Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s')
self.assertEqual(rset.args, {'text': u'utilisateur Smith'})
+
+class RQLSuggestionsBuilderTC(CubicWebTC):
+ def suggestions(self, rql):
+ req = self.request()
+ rbs = self.vreg['components'].select('rql.suggestions', req)
+ return rbs.build_suggestions(rql)
+
+ def test_no_restrictions_rql(self):
+ self.assertListEqual([], self.suggestions(''))
+ self.assertListEqual([], self.suggestions('An'))
+ self.assertListEqual([], self.suggestions('Any X'))
+ self.assertListEqual([], self.suggestions('Any X, Y'))
+
+ def test_invalid_rql(self):
+ self.assertListEqual([], self.suggestions('blabla'))
+ self.assertListEqual([], self.suggestions('Any X WHERE foo, bar'))
+
+ def test_is_rql(self):
+ self.assertListEqual(['Any X WHERE X is %s' % eschema
+ for eschema in sorted(self.vreg.schema.entities())
+ if not eschema.final],
+ self.suggestions('Any X WHERE X is'))
+
+ self.assertListEqual(['Any X WHERE X is Personne', 'Any X WHERE X is Project'],
+ self.suggestions('Any X WHERE X is P'))
+
+ self.assertListEqual(['Any X WHERE X is Personne, Y is Personne',
+ 'Any X WHERE X is Personne, Y is Project'],
+ self.suggestions('Any X WHERE X is Personne, Y is P'))
+
+
+ def test_relations_rql(self):
+ self.assertListEqual(['Any X WHERE X is Personne, X ass A',
+ 'Any X WHERE X is Personne, X datenaiss A',
+ 'Any X WHERE X is Personne, X description A',
+ 'Any X WHERE X is Personne, X fax A',
+ 'Any X WHERE X is Personne, X nom A',
+ 'Any X WHERE X is Personne, X prenom A',
+ 'Any X WHERE X is Personne, X promo A',
+ 'Any X WHERE X is Personne, X salary A',
+ 'Any X WHERE X is Personne, X sexe A',
+ 'Any X WHERE X is Personne, X tel A',
+ 'Any X WHERE X is Personne, X test A',
+ 'Any X WHERE X is Personne, X titre A',
+ 'Any X WHERE X is Personne, X travaille A',
+ 'Any X WHERE X is Personne, X web A',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X '))
+ self.assertListEqual(['Any X WHERE X is Personne, X tel A',
+ 'Any X WHERE X is Personne, X test A',
+ 'Any X WHERE X is Personne, X titre A',
+ 'Any X WHERE X is Personne, X travaille A',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X t'))
+ # try completion on selected
+ self.assertListEqual(['Any X WHERE X is Personne, Y is Societe, X tel A',
+ 'Any X WHERE X is Personne, Y is Societe, X test A',
+ 'Any X WHERE X is Personne, Y is Societe, X titre A',
+ 'Any X WHERE X is Personne, Y is Societe, X travaille Y',
+ ],
+ self.suggestions('Any X WHERE X is Personne, Y is Societe, X t'))
+ # invalid relation should not break
+ self.assertListEqual([],
+ self.suggestions('Any X WHERE X is Personne, X asdasd'))
+
+ def test_attribute_vocabulary_rql(self):
+ self.assertListEqual(['Any X WHERE X is Personne, X promo "bon"',
+ 'Any X WHERE X is Personne, X promo "pasbon"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X promo "'))
+ self.assertListEqual(['Any X WHERE X is Personne, X promo "pasbon"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X promo "p'))
+ # "bon" should be considered complete, hence no suggestion
+ self.assertListEqual([],
+ self.suggestions('Any X WHERE X is Personne, X promo "bon"'))
+ # no valid vocabulary starts with "po"
+ self.assertListEqual([],
+ self.suggestions('Any X WHERE X is Personne, X promo "po'))
+
+ def test_attribute_value_rql(self):
+ # suggestions should contain any possible value for
+ # a given attribute (limited to 10)
+ req = self.request()
+ for i in xrange(15):
+ req.create_entity('Personne', nom=u'n%s' % i, prenom=u'p%s' % i)
+ self.assertListEqual(['Any X WHERE X is Personne, X nom "n0"',
+ 'Any X WHERE X is Personne, X nom "n1"',
+ 'Any X WHERE X is Personne, X nom "n10"',
+ 'Any X WHERE X is Personne, X nom "n11"',
+ 'Any X WHERE X is Personne, X nom "n12"',
+ 'Any X WHERE X is Personne, X nom "n13"',
+ 'Any X WHERE X is Personne, X nom "n14"',
+ 'Any X WHERE X is Personne, X nom "n2"',
+ 'Any X WHERE X is Personne, X nom "n3"',
+ 'Any X WHERE X is Personne, X nom "n4"',
+ 'Any X WHERE X is Personne, X nom "n5"',
+ 'Any X WHERE X is Personne, X nom "n6"',
+ 'Any X WHERE X is Personne, X nom "n7"',
+ 'Any X WHERE X is Personne, X nom "n8"',
+ 'Any X WHERE X is Personne, X nom "n9"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X nom "'))
+ self.assertListEqual(['Any X WHERE X is Personne, X nom "n1"',
+ 'Any X WHERE X is Personne, X nom "n10"',
+ 'Any X WHERE X is Personne, X nom "n11"',
+ 'Any X WHERE X is Personne, X nom "n12"',
+ 'Any X WHERE X is Personne, X nom "n13"',
+ 'Any X WHERE X is Personne, X nom "n14"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X nom "n1'))
+
+
if __name__ == '__main__':
unittest_main()
--- a/web/test/unittest_reledit.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_reledit.py Fri Jan 25 14:33:40 2013 +0100
@@ -20,7 +20,7 @@
"""
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.web.uicfg import reledit_ctrl
+from cubicweb.web.views.uicfg import reledit_ctrl
class ReleditMixinTC(object):
@@ -175,8 +175,8 @@
def setup_database(self):
super(ClickAndEditFormUICFGTC, self).setup_database()
- self.tick.set_relations(concerns=self.proj)
- self.proj.set_relations(manager=self.toto)
+ self.tick.cw_set(concerns=self.proj)
+ self.proj.cw_set(manager=self.toto)
def test_with_uicfg(self):
old_rctl = reledit_ctrl._tagdefs.copy()
--- a/web/test/unittest_uicfg.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_uicfg.py Fri Jan 25 14:33:40 2013 +0100
@@ -18,7 +18,8 @@
import copy
from logilab.common.testlib import tag
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.web import uicfg, uihelper, formwidgets as fwdgs
+from cubicweb.web import uihelper, formwidgets as fwdgs
+from cubicweb.web.views import uicfg
abaa = uicfg.actionbox_appearsin_addmenu
@@ -106,6 +107,24 @@
self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {'order': 1})
+class UicfgRegistryTC(CubicWebTC):
+
+ def test_default_uicfg_object(self):
+ 'CW default ui config objects must be registered in uicfg registry'
+ onames = ('autoform_field', 'autoform_section', 'autoform_field_kwargs')
+ for oname in onames:
+ obj = self.vreg['uicfg'].select_or_none(oname)
+ self.assertTrue(obj is not None, '%s not found in uicfg registry'
+ % oname)
+
+ def test_custom_uicfg(self):
+ ASRT = uicfg.AutoformSectionRelationTags
+ custom_afs = ASRT()
+ custom_afs.__select__ = ASRT.__select__ & ASRT.__select__
+ self.vreg['uicfg'].register(custom_afs)
+ obj = self.vreg['uicfg'].select_or_none('autoform_section')
+ self.assertTrue(obj is custom_afs)
+
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
--- a/web/test/unittest_urlrewrite.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_urlrewrite.py Fri Jan 25 14:33:40 2013 +0100
@@ -60,7 +60,6 @@
('/doc/images/(.+?)/?$', dict(fid='\\1', vid='wdocimages')),
('/doc/?$', dict(fid='main', vid='wdoc')),
('/doc/(.+?)/?$', dict(fid='\\1', vid='wdoc')),
- ('/changelog/?$', dict(vid='changelog')),
# now in SchemaBasedRewriter
#('/search/(.+)$', dict(rql=r'Any X WHERE X has_text "\1"')),
])
@@ -105,9 +104,9 @@
def setup_database(self):
req = self.request()
self.p1 = self.create_user(req, u'user1')
- self.p1.set_attributes(firstname=u'joe', surname=u'Dalton')
+ self.p1.cw_set(firstname=u'joe', surname=u'Dalton')
self.p2 = self.create_user(req, u'user2')
- self.p2.set_attributes(firstname=u'jack', surname=u'Dalton')
+ self.p2.cw_set(firstname=u'jack', surname=u'Dalton')
def test_rgx_action_with_transforms(self):
class TestSchemaBasedRewriter(SchemaBasedRewriter):
--- a/web/test/unittest_views_basecontrollers.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_views_basecontrollers.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -77,6 +77,7 @@
}
with self.assertRaises(ValidationError) as cm:
self.ctrl_publish(req)
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.errors, {'login-subject': 'the value "admin" is already used, use another one'})
def test_user_editing_itself(self):
@@ -249,6 +250,7 @@
}
with self.assertRaises(ValidationError) as cm:
self.ctrl_publish(req)
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.errors, {'amount-subject': 'value -10 must be >= 0'})
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
@@ -259,6 +261,7 @@
}
with self.assertRaises(ValidationError) as cm:
self.ctrl_publish(req)
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'})
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
--- a/web/test/unittest_views_basetemplates.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_views_basetemplates.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/test/unittest_views_editforms.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_views_editforms.py Fri Jan 25 14:33:40 2013 +0100
@@ -22,7 +22,7 @@
from logilab.common.compat import any
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.web import uicfg
+from cubicweb.web.views import uicfg
from cubicweb.web.formwidgets import AutoCompletionWidget
AFFK = uicfg.autoform_field_kwargs
--- a/web/test/unittest_views_searchrestriction.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_views_searchrestriction.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/test/unittest_viewselector.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/test/unittest_viewselector.py Fri Jan 25 14:33:40 2013 +0100
@@ -39,7 +39,6 @@
actions.LogoutAction]
SITEACTIONS = [actions.ManageAction]
FOOTERACTIONS = [wdoc.HelpAction,
- wdoc.ChangeLogAction,
wdoc.AboutAction,
actions.PoweredByAction]
MANAGEACTIONS = [actions.SiteConfigurationAction,
@@ -93,8 +92,7 @@
def test_possible_views_none_rset(self):
req = self.request()
self.assertListEqual(self.pviews(req, None),
- [('changelog', wdoc.ChangeLogView),
- ('cw.sources-management', cwsources.CWSourcesManagementView),
+ [('cw.sources-management', cwsources.CWSourcesManagementView),
('cw.users-and-groups-management', cwuser.UsersAndGroupsManagementView),
('gc', debug.GCView),
('index', startup.IndexView),
--- a/web/uicfg.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/uicfg.py Fri Jan 25 14:33:40 2013 +0100
@@ -15,430 +15,15 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""This module (``cubicweb.web.uicfg``) regroups a set of structures that may be
-used to configure various options of the generated web interface.
-
-To configure the interface generation, we use ``RelationTag`` objects.
-
-Index view configuration
-````````````````````````
-:indexview_etype_section:
- entity type category in the index/manage page. May be one of:
-
- * ``application``
- * ``system``
- * ``schema``
- * ``subobject`` (not displayed by default)
-
- By default only entities on the ``application`` category are shown.
-
-.. sourcecode:: python
-
- from cubicweb.web import uicfg
- # force hiding
- uicfg.indexview_etype_section['HideMe'] = 'subobject'
- # force display
- uicfg.indexview_etype_section['ShowMe'] = 'application'
-
-
-Actions box configuration
-`````````````````````````
-:actionbox_appearsin_addmenu:
- simple boolean relation tags used to control the "add entity" submenu.
- Relations whose rtag is True will appears, other won't.
-
-.. sourcecode:: python
-
- # Adds all subjects of the entry_of relation in the add menu of the ``Blog``
- # primary view
- uicfg.actionbox_appearsin_addmenu.tag_object_of(('*', 'entry_of', 'Blog'), True)
+"""
+This module is now deprecated, see web.views.uicfg.
"""
__docformat__ = "restructuredtext en"
from warnings import warn
-
-from logilab.common.compat import any
-
-from cubicweb import neg_role
-from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
- RelationTagsDict, NoTargetRelationTagsDict,
- register_rtag, _ensure_str_key)
-from cubicweb.schema import META_RTYPES, INTERNAL_TYPES, WORKFLOW_TYPES
-
-
-# primary view configuration ##################################################
-
-def init_primaryview_section(rtag, sschema, rschema, oschema, role):
- if rtag.get(sschema, rschema, oschema, role) is None:
- rdef = rschema.rdef(sschema, oschema)
- if rschema.final:
- if rschema.meta or sschema.is_metadata(rschema) \
- or oschema.type in ('Password', 'Bytes'):
- section = 'hidden'
- else:
- section = 'attributes'
- else:
- if rdef.role_cardinality(role) in '1+':
- section = 'attributes'
- elif rdef.composite == neg_role(role):
- section = 'relations'
- else:
- section = 'sideboxes'
- rtag.tag_relation((sschema, rschema, oschema, role), section)
-
-primaryview_section = RelationTags('primaryview_section',
- init_primaryview_section,
- frozenset(('attributes', 'relations',
- 'sideboxes', 'hidden')))
-
-
-class DisplayCtrlRelationTags(NoTargetRelationTagsDict):
- def __init__(self, *args, **kwargs):
- super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs)
- self.counter = 0
-
-def init_primaryview_display_ctrl(rtag, sschema, rschema, oschema, role):
- if role == 'subject':
- oschema = '*'
- else:
- sschema = '*'
- rtag.counter += 1
- rtag.setdefault((sschema, rschema, oschema, role), 'order', rtag.counter)
-
-primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
- init_primaryview_display_ctrl)
+from cubicweb.web.views.uicfg import *
-# index view configuration ####################################################
-# entity type section in the index/manage page. May be one of
-# * 'application'
-# * 'system'
-# * 'schema'
-# * 'hidden'
-# * 'subobject' (not displayed by default)
-
-class InitializableDict(dict):
- def __init__(self, *args, **kwargs):
- super(InitializableDict, self).__init__(*args, **kwargs)
- register_rtag(self)
- self.__defaults = dict(self)
-
- def init(self, schema, check=True):
- self.update(self.__defaults)
- for eschema in schema.entities():
- if eschema.final:
- continue
- if eschema.schema_entity():
- self.setdefault(eschema, 'schema')
- elif eschema in INTERNAL_TYPES or eschema in WORKFLOW_TYPES:
- self.setdefault(eschema, 'system')
- elif eschema.is_subobject(strict=True):
- self.setdefault(eschema, 'subobject')
- else:
- self.setdefault(eschema, 'application')
-
-indexview_etype_section = InitializableDict(
- EmailAddress='subobject',
- Bookmark='system',
- # entity types in the 'system' table by default (managers only)
- CWUser='system', CWGroup='system',
- )
-
-# autoform.AutomaticEntityForm configuration ##################################
-
-def _formsections_as_dict(formsections):
- result = {}
- for formsection in formsections:
- formtype, section = formsection.split('_', 1)
- result[formtype] = section
- return result
-
-def _card_and_comp(sschema, rschema, oschema, role):
- rdef = rschema.rdef(sschema, oschema)
- if role == 'subject':
- card = rdef.cardinality[0]
- composed = not rschema.final and rdef.composite == 'object'
- else:
- card = rdef.cardinality[1]
- composed = not rschema.final and rdef.composite == 'subject'
- return card, composed
-
-class AutoformSectionRelationTags(RelationTagsSet):
- """autoform relations'section"""
-
- bw_tag_map = {
- 'primary': {'main': 'attributes', 'muledit': 'attributes'},
- 'secondary': {'main': 'attributes', 'muledit': 'hidden'},
- 'metadata': {'main': 'metadata'},
- 'generic': {'main': 'relations'},
- 'generated': {'main': 'hidden'},
- }
-
- _allowed_form_types = ('main', 'inlined', 'muledit')
- _allowed_values = {'main': ('attributes', 'inlined', 'relations',
- 'metadata', 'hidden'),
- 'inlined': ('attributes', 'inlined', 'hidden'),
- 'muledit': ('attributes', 'hidden'),
- }
-
- def init(self, schema, check=True):
- super(AutoformSectionRelationTags, self).init(schema, check)
- self.apply(schema, self._initfunc_step2)
-
- @staticmethod
- def _initfunc(self, sschema, rschema, oschema, role):
- formsections = self.init_get(sschema, rschema, oschema, role)
- if formsections is None:
- formsections = self.tag_container_cls()
- if not any(tag.startswith('inlined') for tag in formsections):
- if not rschema.final:
- negsects = self.init_get(sschema, rschema, oschema, neg_role(role))
- if 'main_inlined' in negsects:
- formsections.add('inlined_hidden')
- key = _ensure_str_key( (sschema, rschema, oschema, role) )
- self._tagdefs[key] = formsections
-
- @staticmethod
- def _initfunc_step2(self, sschema, rschema, oschema, role):
- formsections = self.get(sschema, rschema, oschema, role)
- sectdict = _formsections_as_dict(formsections)
- if rschema in META_RTYPES:
- sectdict.setdefault('main', 'hidden')
- sectdict.setdefault('muledit', 'hidden')
- sectdict.setdefault('inlined', 'hidden')
- elif role == 'subject' and rschema in sschema.meta_attributes():
- # meta attribute, usually embeded by the described attribute's field
- # (eg RichTextField, FileField...)
- sectdict.setdefault('main', 'hidden')
- sectdict.setdefault('muledit', 'hidden')
- sectdict.setdefault('inlined', 'hidden')
- # ensure we have a tag for each form type
- if not 'main' in sectdict:
- if not rschema.final and (
- sectdict.get('inlined') == 'attributes' or
- 'inlined_attributes' in self.init_get(sschema, rschema, oschema,
- neg_role(role))):
- sectdict['main'] = 'hidden'
- elif sschema.is_metadata(rschema):
- sectdict['main'] = 'metadata'
- else:
- card, composed = _card_and_comp(sschema, rschema, oschema, role)
- if card in '1+':
- sectdict['main'] = 'attributes'
- if not 'muledit' in sectdict:
- sectdict['muledit'] = 'attributes'
- elif rschema.final:
- sectdict['main'] = 'attributes'
- else:
- sectdict['main'] = 'relations'
- if not 'muledit' in sectdict:
- sectdict['muledit'] = 'hidden'
- if sectdict['main'] == 'attributes':
- card, composed = _card_and_comp(sschema, rschema, oschema, role)
- if card in '1+' and not composed:
- sectdict['muledit'] = 'attributes'
- if not 'inlined' in sectdict:
- sectdict['inlined'] = sectdict['main']
- # recompute formsections and set it to avoid recomputing
- for formtype, section in sectdict.iteritems():
- formsections.add('%s_%s' % (formtype, section))
-
- def tag_relation(self, key, formtype, section):
- if isinstance(formtype, tuple):
- for ftype in formtype:
- self.tag_relation(key, ftype, section)
- return
- assert formtype in self._allowed_form_types, \
- 'formtype should be in (%s), not %s' % (
- ','.join(self._allowed_form_types), formtype)
- assert section in self._allowed_values[formtype], \
- 'section for %s should be in (%s), not %s' % (
- formtype, ','.join(self._allowed_values[formtype]), section)
- rtags = self._tagdefs.setdefault(_ensure_str_key(key),
- self.tag_container_cls())
- # remove previous section for this form type if any
- if rtags:
- for tag in rtags.copy():
- if tag.startswith(formtype):
- rtags.remove(tag)
- rtags.add('%s_%s' % (formtype, section))
- return rtags
+warn('[3.16] moved to cubicweb.web.views.uicfg',
+ DeprecationWarning, stacklevel=2)
- def init_get(self, stype, rtype, otype, tagged):
- key = (stype, rtype, otype, tagged)
- rtags = {}
- for key in self._get_keys(stype, rtype, otype, tagged):
- tags = self._tagdefs.get(key, ())
- for tag in tags:
- assert '_' in tag, (tag, tags)
- section, value = tag.split('_', 1)
- rtags[section] = value
- cls = self.tag_container_cls
- rtags = cls('_'.join([section,value]) for section,value in rtags.iteritems())
- return rtags
-
-
- def get(self, *key):
- # overriden to avoid recomputing done in parent classes
- return self._tagdefs.get(key, ())
-
- def relations_by_section(self, entity, formtype, section, permission,
- strict=False):
- """return a list of (relation schema, target schemas, role) for the
- given entity matching categories and permission.
-
- `strict`:
- bool telling if having local role is enough (strict = False) or not
- """
- tag = '%s_%s' % (formtype, section)
- eschema = entity.e_schema
- permsoverrides = autoform_permissions_overrides
- if entity.has_eid():
- eid = entity.eid
- else:
- eid = None
- strict = False
- if permission == 'update':
- assert section in ('attributes', 'metadata', 'hidden')
- relpermission = 'add'
- else:
- assert section not in ('attributes', 'metadata', 'hidden')
- relpermission = permission
- cw = entity._cw
- for rschema, targetschemas, role in eschema.relation_definitions(True):
- _targetschemas = []
- for tschema in targetschemas:
- # check section's tag first, potentially lower cost than
- # checking permission which may imply rql queries
- if not tag in self.etype_get(eschema, rschema, role, tschema):
- continue
- rdef = rschema.role_rdef(eschema, tschema, role)
- if rschema.final:
- if not rdef.has_perm(cw, permission, eid=eid,
- creating=eid is None):
- continue
- elif strict or not rdef.has_local_role(relpermission):
- if role == 'subject':
- if not rdef.has_perm(cw, relpermission, fromeid=eid):
- continue
- elif role == 'object':
- if not rdef.has_perm(cw, relpermission, toeid=eid):
- continue
- _targetschemas.append(tschema)
- if not _targetschemas:
- continue
- targetschemas = _targetschemas
- rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0])
- # XXX tag allowing to hijack the permission machinery when
- # permission is not verifiable until the entity is actually
- # created...
- if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
- yield (rschema, targetschemas, role)
- continue
- if not rschema.final and role == 'subject':
- # on relation with cardinality 1 or ?, we need delete perm as well
- # if the relation is already set
- if (relpermission == 'add'
- and rdef.role_cardinality(role) in '1?'
- and eid and entity.related(rschema.type, role)
- and not rdef.has_perm(cw, 'delete', fromeid=eid,
- toeid=entity.related(rschema.type, role)[0][0])):
- continue
- elif role == 'object':
- # on relation with cardinality 1 or ?, we need delete perm as well
- # if the relation is already set
- if (relpermission == 'add'
- and rdef.role_cardinality(role) in '1?'
- and eid and entity.related(rschema.type, role)
- and not rdef.has_perm(cw, 'delete', toeid=eid,
- fromeid=entity.related(rschema.type, role)[0][0])):
- continue
- yield (rschema, targetschemas, role)
-
-autoform_section = AutoformSectionRelationTags('autoform_section')
-
-# relations'field class
-autoform_field = RelationTags('autoform_field')
-
-# relations'field explicit kwargs (given to field's __init__)
-autoform_field_kwargs = RelationTagsDict('autoform_field_kwargs')
-
-
-# set of tags of the form <action>_on_new on relations. <action> is a
-# schema action (add/update/delete/read), and when such a tag is found
-# permissions checking is by-passed and supposed to be ok
-autoform_permissions_overrides = RelationTagsSet('autoform_permissions_overrides')
-
-class ReleditTags(NoTargetRelationTagsDict):
- """Associate to relation a dictionary to control `reledit` (e.g. edition of
- attributes / relations from within views).
-
- Possible keys and associated values are:
-
- * `novalue_label`, alternative default value (shown when there is no value).
-
- * `novalue_include_rtype`, when `novalue_label` is not specified, this boolean
- flag control wether the generated default value should contains the
- relation label or not. Will be the opposite of the `showlabel` value found
- in the `primaryview_display_ctrl` rtag by default.
-
- * `reload`, boolean, eid (to reload to) or function taking subject and
- returning bool/eid. This is useful when editing a relation (or attribute)
- that impacts the url or another parts of the current displayed
- page. Defaults to False.
-
- * `rvid`, alternative view id (as str) for relation or composite edition.
- Default is 'autolimited'.
-
- * `edit_target`, may be either 'rtype' (to edit the relation) or 'related'
- (to edit the related entity). This controls whether to edit the relation
- or the target entity of the relation. Currently only one-to-one relations
- support target entity edition. By default, the 'related' option is taken
- whenever the relation is composite.
- """
- _keys = frozenset('novalue_label novalue_include_rtype reload rvid edit_target'.split())
-
- def tag_relation(self, key, tag):
- for tagkey in tag.iterkeys():
- assert tagkey in self._keys, 'tag %r not in accepted tags: %r' % (tag, self._keys)
- return super(ReleditTags, self).tag_relation(key, tag)
-
-def init_reledit_ctrl(rtag, sschema, rschema, oschema, role):
- values = rtag.get(sschema, rschema, oschema, role)
- if not rschema.final:
- composite = rschema.rdef(sschema, oschema).composite == role
- if role == 'subject':
- oschema = '*'
- else:
- sschema = '*'
- edittarget = values.get('edit_target')
- if edittarget not in (None, 'rtype', 'related'):
- rtag.warning('reledit: wrong value for edit_target on relation %s: %s',
- rschema, edittarget)
- edittarget = None
- if not edittarget:
- edittarget = 'related' if composite else 'rtype'
- rtag.tag_relation((sschema, rschema, oschema, role),
- {'edit_target': edittarget})
- if not 'novalue_include_rtype' in values:
- showlabel = primaryview_display_ctrl.get(
- sschema, rschema, oschema, role).get('showlabel', True)
- rtag.tag_relation((sschema, rschema, oschema, role),
- {'novalue_include_rtype': not showlabel})
-
-reledit_ctrl = ReleditTags('reledit', init_reledit_ctrl)
-
-# boxes.EditBox configuration #################################################
-
-# 'link' / 'create' relation tags, used to control the "add entity" submenu
-def init_actionbox_appearsin_addmenu(rtag, sschema, rschema, oschema, role):
- if rtag.get(sschema, rschema, oschema, role) is None:
- if rschema in META_RTYPES:
- rtag.tag_relation((sschema, rschema, oschema, role), False)
- return
- rdef = rschema.rdef(sschema, oschema)
- if not rdef.role_cardinality(role) in '?1' and rdef.composite == role:
- rtag.tag_relation((sschema, rschema, oschema, role), True)
-
-actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
- init_actionbox_appearsin_addmenu)
--- a/web/uihelper.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/uihelper.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2011-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -45,207 +45,30 @@
"""
__docformat__ = "restructuredtext en"
-from cubicweb.web import uicfg
-from functools import partial
-def _tag_rel(rtag, etype, attr, desttype='*', *args, **kwargs):
- if isinstance(attr, basestring):
- attr, role = attr, 'subject'
- else:
- attr, role = attr
- if role == 'subject':
- rtag.tag_subject_of((etype, attr, desttype), *args, **kwargs)
- else:
- rtag.tag_object_of((desttype, attr, etype), *args, **kwargs)
+from logilab.common.deprecation import deprecated
+from cubicweb.web.views import uicfg
## generic uicfg helpers ######################################################
-def append_to_addmenu(etype, attr, createdtype='*'):
- """adds `attr` in the actions box *addrelated* submenu of `etype`.
-
- :param etype: the entity type as a string
- :param attr: the name of the attribute or relation to hide
-
- `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
- """
- _tag_rel(uicfg.actionbox_appearsin_addmenu, etype, attr, createdtype, True)
-
-def remove_from_addmenu(etype, attr, createdtype='*'):
- """removes `attr` from the actions box *addrelated* submenu of `etype`.
-
- :param etype: the entity type as a string
- :param attr: the name of the attribute or relation to hide
-
- `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
- """
- _tag_rel(uicfg.actionbox_appearsin_addmenu, etype, attr, createdtype, False)
-
-
-## form uicfg helpers ##########################################################
-def set_fields_order(etype, attrs):
- """specify the field order in `etype` main edition form.
-
- :param etype: the entity type as a string
- :param attrs: the ordered list of attribute names (or relations)
-
- `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)
-
- Unspecified fields will be displayed after specified ones, their
- order being consistent with the schema definition.
-
- Examples:
-
-.. sourcecode:: python
-
- from cubicweb.web import uihelper
- uihelper.set_fields_order('CWUser', ('firstname', 'surname', 'login'))
- uihelper.set_fields_order('CWUser', ('firstname', ('in_group', 'subject'), 'surname', 'login'))
-
- """
- afk = uicfg.autoform_field_kwargs
- for index, attr in enumerate(attrs):
- _tag_rel(afk, etype, attr, '*', {'order': index})
-
-
-def hide_field(etype, attr, desttype='*', formtype='main'):
- """hide `attr` in `etype` forms.
-
- :param etype: the entity type as a string
- :param attr: the name of the attribute or relation to hide
- :param formtype: which form will be affected ('main', 'inlined', etc.), *main* by default.
-
- `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
- Examples:
-
-.. sourcecode:: python
-
- from cubicweb.web import uihelper
- uihelper.hide_field('CWUser', 'login')
- uihelper.hide_field('*', 'name')
- uihelper.hide_field('CWUser', 'use_email', formtype='inlined')
-
- """
- _tag_rel(uicfg.autoform_section, etype, attr, desttype,
- formtype=formtype, section='hidden')
-
-
-def hide_fields(etype, attrs, formtype='main'):
- """simple for-loop wrapper around :func:`hide_field`.
-
- :param etype: the entity type as a string
- :param attrs: the ordered list of attribute names (or relations)
- :param formtype: which form will be affected ('main', 'inlined', etc.), *main* by default.
-
- `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)
-
- Examples:
-
-.. sourcecode:: python
-
- from cubicweb.web import uihelper
- uihelper.hide_fields('CWUser', ('login', ('use_email', 'subject')), formtype='inlined')
- """
- for attr in attrs:
- hide_field(etype, attr, formtype=formtype)
-
-def set_field_kwargs(etype, attr, **kwargs):
- """tag `attr` field of `etype` with additional named paremeters.
-
- :param etype: the entity type as a string
- :param attr: the name of the attribute or relation
-
- `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
- Examples:
-
-.. sourcecode:: python
-
- from cubicweb.web import uihelper, formwidgets as fwdgs
-
- uihelper.set_field_kwargs('Person', 'works_for', widget=fwdgs.AutoCompletionWidget())
- uihelper.set_field_kwargs('CWUser', 'login', label=_('login or email address'),
- widget=fwdgs.TextInput(attrs={'size': 30}))
- """
- _tag_rel(uicfg.autoform_field_kwargs, etype, attr, '*', kwargs)
-
-
-def set_field(etype, attr, field):
- """sets the `attr` field of `etype`.
-
- :param etype: the entity type as a string
- :param attr: the name of the attribute or relation
-
- `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
- """
- _tag_rel(uicfg.autoform_field, etype, attr, '*', field)
-
-
-def edit_inline(etype, attr, desttype='*', formtype=('main', 'inlined')):
- """edit `attr` with and inlined form.
-
- :param etype: the entity type as a string
- :param attr: the name of the attribute or relation
- :param desttype: the destination type(s) concerned, default is everything
- :param formtype: which form will be affected ('main', 'inlined', etc.), *main* and *inlined* by default.
-
- `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
- Examples:
-
-.. sourcecode:: python
+backward_compat_funcs = (('append_to_addmenu', uicfg.actionbox_appearsin_addmenu),
+ ('remove_from_addmenu', uicfg.actionbox_appearsin_addmenu),
+ ('set_fields_order', uicfg.autoform_field_kwargs),
+ ('hide_field', uicfg.autoform_section),
+ ('hide_fields', uicfg.autoform_section),
+ ('set_field_kwargs', uicfg.autoform_field_kwargs),
+ ('set_field', uicfg.autoform_field),
+ ('edit_inline', uicfg.autoform_section),
+ ('edit_as_attr', uicfg.autoform_section),
+ ('set_muledit_editable', uicfg.autoform_section),
+ )
- from cubicweb.web import uihelper
-
- uihelper.edit_inline('*', 'use_email')
- """
- _tag_rel(uicfg.autoform_section, etype, attr, desttype,
- formtype=formtype, section='inlined')
-
-
-def edit_as_attr(etype, attr, desttype='*', formtype=('main', 'muledit')):
- """make `attr` appear in the *attributes* section of `etype` form.
-
- :param etype: the entity type as a string
- :param attr: the name of the attribute or relation
- :param desttype: the destination type(s) concerned, default is everything
- :param formtype: which form will be affected ('main', 'inlined', etc.), *main* and *muledit* by default.
-
- `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
-
- Examples:
-
-.. sourcecode:: python
-
- from cubicweb.web import uihelper
-
- uihelper.edit_as_attr('CWUser', 'in_group')
- """
- _tag_rel(uicfg.autoform_section, etype, attr, desttype,
- formtype=formtype, section='attributes')
-
-
-def set_muledit_editable(etype, attrs):
- """make `attrs` appear in muledit form of `etype`.
-
- :param etype: the entity type as a string
- :param attrs: the ordered list of attribute names (or relations)
-
- `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)
-
- Examples:
-
-.. sourcecode:: python
-
- from cubicweb.web import uihelper
-
- uihelper.set_muledit_editable('CWUser', ('firstname', 'surname', 'in_group'))
- """
- for attr in attrs:
- edit_as_attr(etype, attr, formtype='muledit')
+for funcname, tag in backward_compat_funcs:
+ msg = ('[3.16] uihelper.%(name)s is deprecated, please use '
+ 'web.uicfg.%(classname)s.%(name)s' % dict(
+ name=funcname, classname=tag.__class__.__name__))
+ globals()[funcname] = deprecated(msg)(getattr(tag, funcname))
class meta_formconfig(type):
@@ -253,17 +76,23 @@
def __init__(cls, name, bases, classdict):
if cls.etype is None:
return
+ if cls.uicfg_afs is None:
+ uicfg_afs = uicfg.autoform_section
+ if cls.uicfg_aff is None:
+ uicfg_aff = uicfg.autoform_field
+ if cls.uicfg_affk is None:
+ uicfg_affk = uicfg.autoform_field_kwargs
for attr_role in cls.hidden:
- hide_field(cls.etype, attr_role, formtype=cls.formtype)
+ uicfg_afs.hide_field(cls.etype, attr_role, formtype=cls.formtype)
for attr_role in cls.rels_as_attrs:
- edit_as_attr(cls.etype, attr_role, formtype=cls.formtype)
+ uicfg_afs.edit_as_attr(cls.etype, attr_role, formtype=cls.formtype)
for attr_role in cls.inlined:
- edit_inline(cls.etype, attr_role, formtype=cls.formtype)
+ uicfg_afs.edit_inline(cls.etype, attr_role, formtype=cls.formtype)
for rtype, widget in cls.widgets.items():
- set_field_kwargs(cls.etype, rtype, widget=widget)
+ uicfg_affk.set_field_kwargs(cls.etype, rtype, widget=widget)
for rtype, field in cls.fields.items():
- set_field(cls.etype, rtype, field)
- set_fields_order(cls.etype, cls.fields_order)
+ uicfg_aff.set_field(cls.etype, rtype, field)
+ uicfg_affk.set_fields_order(cls.etype, cls.fields_order)
super(meta_formconfig, cls).__init__(name, bases, classdict)
@@ -303,6 +132,18 @@
:attr:`fields`
a dictionary mapping attribute names to field instances.
+ :attr:`uicfg_afs`
+ an instance of ``cubicweb.web.uicfg.AutoformSectionRelationTags``
+ Default is None, meaning ``cubicweb.web.uicfg.autoform_section`` is used.
+
+ :attr:`uicfg_aff`
+ an instance of ``cubicweb.web.uicfg.AutoformFieldTags``
+ Default is None, meaning ``cubicweb.web.uicfg.autoform_field`` is used.
+
+ :attr:`uicfg_affk`
+ an instance of ``cubicweb.web.uicfg.AutoformFieldKwargsTags``
+ Default is None, meaning ``cubicweb.web.uicfg.autoform_field_kwargs`` is used.
+
Examples:
.. sourcecode:: python
@@ -333,3 +174,6 @@
fields_order = ()
widgets = {}
fields = {}
+ uicfg_afs = None
+ uicfg_aff = None
+ uicfg_affk = None
--- a/web/views/actions.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/actions.py Fri Jan 25 14:33:40 2013 +0100
@@ -32,8 +32,8 @@
authenticated_user, match_user_groups, match_search_state,
has_permission, has_add_permission, is_instance, debug_mode,
)
-from cubicweb.web import uicfg, controller, action
-from cubicweb.web.views import linksearch_select_url, vid_from_rset
+from cubicweb.web import controller, action
+from cubicweb.web.views import uicfg, linksearch_select_url, vid_from_rset
class has_editable_relation(EntityPredicate):
@@ -291,7 +291,8 @@
method to return an empty list. If you only want some, you can configure
them by using uicfg.actionbox_appearsin_addmenu
"""
- appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
+ appearsin_addmenu = self._cw.vreg['uicfg'].select(
+ 'actionbox_appearsin_addmenu', self._cw, entity=entity)
req = self._cw
eschema = entity.e_schema
for role, rschemas in (('subject', eschema.subject_relations()),
--- a/web/views/ajaxcontroller.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/ajaxcontroller.py Fri Jan 25 14:33:40 2013 +0100
@@ -28,7 +28,7 @@
functions that can be called from the javascript world.
To register a new remote function, either decorate your function
-with the :func:`cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:
+with the :func:`~cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:
.. sourcecode:: python
@@ -39,7 +39,7 @@
def list_users(self):
return [u for (u,) in self._cw.execute('Any L WHERE U login L')]
-or inherit from :class:`cubicwbe.web.views.ajaxcontroller.AjaxFunction` and
+or inherit from :class:`~cubicweb.web.views.ajaxcontroller.AjaxFunction` and
implement the ``__call__`` method:
.. sourcecode:: python
--- a/web/views/authentication.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/authentication.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -27,7 +27,7 @@
from cubicweb import AuthenticationError, BadConnectionId
from cubicweb.view import Component
-from cubicweb.dbapi import repo_connect, ConnectionProperties
+from cubicweb.dbapi import _repo_connect, ConnectionProperties
from cubicweb.web import InvalidSession
from cubicweb.web.application import AbstractAuthenticationManager
@@ -169,9 +169,8 @@
raise AuthenticationError()
def _authenticate(self, login, authinfo):
- cnxprops = ConnectionProperties(self.vreg.config.repo_method,
- close=False, log=self.log_queries)
- cnx = repo_connect(self.repo, login, cnxprops=cnxprops, **authinfo)
+ cnxprops = ConnectionProperties(close=False, log=self.log_queries)
+ cnx = _repo_connect(self.repo, login, cnxprops=cnxprops, **authinfo)
# decorate connection
cnx.vreg = self.vreg
return cnx
--- a/web/views/autoform.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/autoform.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -126,7 +126,6 @@
from logilab.mtconverter import xml_escape
from logilab.common.decorators import iclassmethod, cached
from logilab.common.deprecation import deprecated
-from logilab.common.registry import classid
from cubicweb import typed_eid, neg_role, uilib
from cubicweb.schema import display_name
@@ -135,14 +134,11 @@
match_kwargs, match_form_params, non_final_entity,
specified_etype_implements)
from cubicweb.utils import json_dumps
-from cubicweb.web import (stdmsgs, uicfg, eid_param,
+from cubicweb.web import (stdmsgs, eid_param,
form as f, formwidgets as fw, formfields as ff)
-from cubicweb.web.views import forms
+from cubicweb.web.views import uicfg, forms
from cubicweb.web.views.ajaxcontroller import ajaxfunc
-_AFS = uicfg.autoform_section
-_AFFK = uicfg.autoform_field_kwargs
-
# inlined form handling ########################################################
@@ -755,6 +751,8 @@
def __init__(self, *args, **kwargs):
super(AutomaticEntityForm, self).__init__(*args, **kwargs)
+ self.uicfg_afs = self._cw.vreg['uicfg'].select(
+ 'autoform_section', self._cw, entity=self.edited_entity)
entity = self.edited_entity
if entity.has_eid():
entity.complete()
@@ -820,8 +818,8 @@
def _inlined_form_view_field(self, view):
# XXX allow more customization
- kwargs = _AFFK.etype_get(self.edited_entity.e_schema, view.rtype,
- view.role, view.etype)
+ kwargs = self.uicfg_affk.etype_get(self.edited_entity.e_schema,
+ view.rtype, view.role, view.etype)
if kwargs is None:
kwargs = {}
return InlinedFormField(view=view, **kwargs)
@@ -832,7 +830,7 @@
"""return a list of (relation schema, target schemas, role) matching
given category(ies) and permission
"""
- return _AFS.relations_by_section(
+ return self.uicfg_afs.relations_by_section(
self.edited_entity, self.formtype, section, permission, strict)
def editable_attributes(self, strict=False):
@@ -963,6 +961,7 @@
## default form ui configuration ##############################################
+_AFS = uicfg.autoform_section
# use primary and not generated for eid since it has to be an hidden
_AFS.tag_attribute(('*', 'eid'), 'main', 'attributes')
_AFS.tag_attribute(('*', 'eid'), 'muledit', 'attributes')
@@ -994,6 +993,7 @@
_AFS.tag_subject_of(('CWRelation', 'from_entity', '*'), 'main', 'inlined')
_AFS.tag_subject_of(('CWRelation', 'to_entity', '*'), 'main', 'inlined')
+_AFFK = uicfg.autoform_field_kwargs
_AFFK.tag_attribute(('RQLExpression', 'expression'),
{'widget': fw.TextInput})
_AFFK.tag_subject_of(('TrInfo', 'wf_info_for', '*'),
--- a/web/views/basecomponents.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/basecomponents.py Fri Jan 25 14:33:40 2013 +0100
@@ -36,7 +36,7 @@
from cubicweb.schema import display_name
from cubicweb.utils import wrap_on_write
from cubicweb.uilib import toggle_action
-from cubicweb.web import component, uicfg
+from cubicweb.web import component
from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu
VISIBLE_PROP_DEF = {
@@ -59,6 +59,14 @@
# display multilines query as one line
rql = rset is not None and rset.printable_rql(encoded=False) or req.form.get('rql', '')
rql = rql.replace(u"\n", u" ")
+ rql_suggestion_comp = self._cw.vreg['components'].select_or_none('rql.suggestions', self._cw)
+ if rql_suggestion_comp is not None:
+ # enable autocomplete feature only if the rql
+ # suggestions builder is available
+ self._cw.add_css('jquery.ui.css')
+ self._cw.add_js(('cubicweb.ajax.js', 'jquery.ui.js'))
+ self._cw.add_onload('$("#rql").autocomplete({source: "%s"});'
+ % (req.build_url('json', fname='rql_suggest')))
self.w(u'''<div id="rqlinput" class="%s"><form action="%s"><fieldset>
<input type="text" id="rql" name="rql" value="%s" title="%s" tabindex="%s" accesskey="q" class="searchField" />
''' % (not self.cw_propval('visible') and 'hidden' or '',
--- a/web/views/basecontrollers.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/basecontrollers.py Fri Jan 25 14:33:40 2013 +0100
@@ -191,6 +191,7 @@
def _validation_error(req, ex):
req.cnx.rollback()
+ ex.translate(req._) # translate messages using ui language
# XXX necessary to remove existant validation error?
# imo (syt), it's not necessary
req.session.data.pop(req.form.get('__errorurl'), None)
--- a/web/views/bookmark.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/bookmark.py Fri Jan 25 14:33:40 2013 +0100
@@ -24,9 +24,8 @@
from cubicweb import Unauthorized, typed_eid
from cubicweb.predicates import is_instance, one_line_rset
-from cubicweb.web import (action, component, uicfg, htmlwidgets,
- formwidgets as fw)
-from cubicweb.web.views import primary
+from cubicweb.web import action, component, htmlwidgets, formwidgets as fw
+from cubicweb.web.views import uicfg, primary
from cubicweb.web.views.ajaxcontroller import ajaxfunc
_abaa = uicfg.actionbox_appearsin_addmenu
--- a/web/views/cwproperties.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/cwproperties.py Fri Jan 25 14:33:40 2013 +0100
@@ -28,12 +28,12 @@
from cubicweb.predicates import (one_line_rset, none_rset, is_instance,
match_user_groups, logged_user_in_rset)
from cubicweb.view import StartupView
-from cubicweb.web import uicfg, stdmsgs
+from cubicweb.web import stdmsgs
from cubicweb.web.form import FormViewMixIn
from cubicweb.web.formfields import FIELDS, StringField
from cubicweb.web.formwidgets import (Select, TextInput, Button, SubmitButton,
FieldWidget)
-from cubicweb.web.views import primary, formrenderers, editcontroller
+from cubicweb.web.views import uicfg, primary, formrenderers, editcontroller
from cubicweb.web.views.ajaxcontroller import ajaxfunc
uicfg.primaryview_section.tag_object_of(('*', 'for_user', '*'), 'hidden')
--- a/web/views/cwsources.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/cwsources.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2010-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -33,9 +33,9 @@
match_user_groups, match_kwargs, match_view)
from cubicweb.view import EntityView, StartupView
from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name
-from cubicweb.web import uicfg, formwidgets as wdgs, facet
+from cubicweb.web import formwidgets as wdgs, facet
from cubicweb.web.views import add_etype_button
-from cubicweb.web.views import (tabs, actions, ibreadcrumbs, navigation,
+from cubicweb.web.views import (uicfg, tabs, actions, ibreadcrumbs, navigation,
tableview, pyviews)
--- a/web/views/cwuser.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/cwuser.py Fri Jan 25 14:33:40 2013 +0100
@@ -28,8 +28,8 @@
from cubicweb.schema import display_name
from cubicweb.predicates import one_line_rset, is_instance, match_user_groups
from cubicweb.view import EntityView, StartupView
-from cubicweb.web import action, uicfg, formwidgets
-from cubicweb.web.views import tabs, tableview, actions, add_etype_button
+from cubicweb.web import action, formwidgets
+from cubicweb.web.views import uicfg, tabs, tableview, actions, add_etype_button
_pvs = uicfg.primaryview_section
_pvs.tag_attribute(('CWUser', 'login'), 'hidden')
--- a/web/views/debug.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/debug.py Fri Jan 25 14:33:40 2013 +0100
@@ -103,7 +103,7 @@
% (element, xml_escape(unicode(stats[element])),
element.endswith('percent') and '%' or '' ))
w(u'</table>')
- if req.cnx._cnxtype == 'inmemory' and req.user.is_in_group('managers'):
+ if req.cnx.is_repo_in_memory and req.user.is_in_group('managers'):
w(u'<h3>%s</h3>' % _('opened sessions'))
sessions = repo._sessions.values()
if sessions:
--- a/web/views/editcontroller.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/editcontroller.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/views/editforms.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/editforms.py Fri Jan 25 14:33:40 2013 +0100
@@ -34,10 +34,10 @@
specified_etype_implements, is_instance)
from cubicweb.view import EntityView
from cubicweb.schema import display_name
-from cubicweb.web import uicfg, stdmsgs, eid_param, \
+from cubicweb.web import stdmsgs, eid_param, \
formfields as ff, formwidgets as fw
from cubicweb.web.form import FormViewMixIn, FieldNotFound
-from cubicweb.web.views import forms, reledit
+from cubicweb.web.views import uicfg, forms, reledit
_pvdc = uicfg.primaryview_display_ctrl
--- a/web/views/emailaddress.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/emailaddress.py Fri Jan 25 14:33:40 2013 +0100
@@ -24,8 +24,7 @@
from cubicweb.schema import display_name
from cubicweb.predicates import is_instance
from cubicweb import Unauthorized
-from cubicweb.web import uicfg
-from cubicweb.web.views import baseviews, primary, ibreadcrumbs
+from cubicweb.web.views import uicfg, baseviews, primary, ibreadcrumbs
_pvs = uicfg.primaryview_section
_pvs.tag_subject_of(('*', 'use_email', '*'), 'attributes')
--- a/web/views/facets.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/facets.py Fri Jan 25 14:33:40 2013 +0100
@@ -26,6 +26,7 @@
from logilab.common.decorators import cachedproperty
from logilab.common.registry import objectify_predicate, yes
+from cubicweb import tags
from cubicweb.predicates import (non_final_entity, multi_lines_rset,
match_context_prop, relation_possible)
from cubicweb.utils import json_dumps
@@ -234,6 +235,7 @@
vid = req.form.get('vid')
if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm(req, 'add'):
w(self.bookmark_link(rset))
+ w(self.focus_link(rset))
hiddens = {}
for param in ('subvid', 'vtitle'):
if param in req.form:
@@ -269,6 +271,9 @@
req._('bookmark this search'))
return self.bk_linkbox_template % bk_link
+ def focus_link(self, rset):
+ return self.bk_linkbox_template % tags.a(self._cw._('focus on this selection'),
+ href=self._cw.url(), id='focusLink')
class FilterTable(FacetFilterMixIn, AnyRsetView):
__regid__ = 'facet.filtertable'
--- a/web/views/forms.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/forms.py Fri Jan 25 14:33:40 2013 +0100
@@ -56,7 +56,8 @@
from cubicweb.utils import support_args
from cubicweb.predicates import non_final_entity, match_kwargs, one_line_rset
from cubicweb.web import RequestError, ProcessFormError
-from cubicweb.web import uicfg, form, formwidgets as fwdgs
+from cubicweb.web import form, formwidgets as fwdgs
+from cubicweb.web.views import uicfg
from cubicweb.web.formfields import guess_field
@@ -301,9 +302,6 @@
return processed
-_AFF = uicfg.autoform_field
-_AFF_KWARGS = uicfg.autoform_field_kwargs
-
class EntityFieldsForm(FieldsForm):
"""This class is designed for forms used to edit some entities. It should
handle for you all the underlying stuff necessary to properly work with the
@@ -314,6 +312,8 @@
__select__ = (match_kwargs('entity')
| (one_line_rset() & non_final_entity()))
domid = 'entityForm'
+ uicfg_aff = uicfg.autoform_field
+ uicfg_affk = uicfg.autoform_field_kwargs
@iclassmethod
def field_by_name(cls_or_self, name, role=None, eschema=None):
@@ -329,15 +329,21 @@
rschema = eschema.schema.rschema(name)
# XXX use a sample target type. Document this.
tschemas = rschema.targets(eschema, role)
- fieldcls = _AFF.etype_get(eschema, rschema, role, tschemas[0])
- kwargs = _AFF_KWARGS.etype_get(eschema, rschema, role, tschemas[0])
+ fieldcls = cls_or_self.uicfg_aff.etype_get(
+ eschema, rschema, role, tschemas[0])
+ kwargs = cls_or_self.uicfg_affk.etype_get(
+ eschema, rschema, role, tschemas[0])
if kwargs is None:
kwargs = {}
if fieldcls:
if not isinstance(fieldcls, type):
return fieldcls # already and instance
return fieldcls(name=name, role=role, eidparam=True, **kwargs)
- field = guess_field(eschema, rschema, role, eidparam=True, **kwargs)
+ if isinstance(cls_or_self, type):
+ req = None
+ else:
+ req = cls_or_self._cw
+ field = guess_field(eschema, rschema, role, req=req, eidparam=True, **kwargs)
if field is None:
raise
return field
@@ -349,6 +355,10 @@
self.edited_entity = rset.complete_entity(row or 0, col or 0)
msg = kwargs.pop('submitmsg', None)
super(EntityFieldsForm, self).__init__(_cw, rset, row, col, **kwargs)
+ self.uicfg_aff = self._cw.vreg['uicfg'].select(
+ 'autoform_field', self._cw, entity=self.edited_entity)
+ self.uicfg_affk = self._cw.vreg['uicfg'].select(
+ 'autoform_field_kwargs', self._cw, entity=self.edited_entity)
self.add_hidden('__type', self.edited_entity.__regid__, eidparam=True)
self.add_hidden('eid', self.edited_entity.eid)
# mainform default to true in parent, hence default to True
--- a/web/views/magicsearch.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/magicsearch.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -15,19 +15,23 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""a query processor to handle quick search shortcuts for cubicweb"""
+"""a query processor to handle quick search shortcuts for cubicweb
+"""
__docformat__ = "restructuredtext en"
import re
from logging import getLogger
-from warnings import warn
+
+from yams.interfaces import IVocabularyConstraint
from rql import RQLSyntaxError, BadRQLQuery, parse
+from rql.utils import rqlvar_maker
from rql.nodes import Relation
from cubicweb import Unauthorized, typed_eid
from cubicweb.view import Component
+from cubicweb.web.views.ajaxcontroller import ajaxfunc
LOGGER = getLogger('cubicweb.magicsearch')
@@ -408,3 +412,247 @@
# explicitly specified processor: don't try to catch the exception
return proc.process_query(uquery)
raise BadRQLQuery(self._cw._('sorry, the server is unable to handle this query'))
+
+
+
+## RQL suggestions builder ####################################################
+class RQLSuggestionsBuilder(Component):
+ """main entry point is `build_suggestions()` which takes
+ an incomplete RQL query and returns a list of suggestions to complete
+ the query.
+
+ This component is enabled by default and is used to provide autocompletion
+ in the RQL search bar. If you don't want this feature in your application,
+ just unregister it or make it unselectable.
+
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.build_suggestions
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.etypes_suggestion_set
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.possible_etypes
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.possible_relations
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.vocabulary
+ """
+ __regid__ = 'rql.suggestions'
+
+ #: maximum number of results to fetch when suggesting attribute values
+ attr_value_limit = 20
+
+ def build_suggestions(self, user_rql):
+ """return a list of suggestions to complete `user_rql`
+
+ :param user_rql: an incomplete RQL query
+ """
+ req = self._cw
+ try:
+ if 'WHERE' not in user_rql: # don't try to complete if there's no restriction
+ return []
+ variables, restrictions = [part.strip() for part in user_rql.split('WHERE', 1)]
+ if ',' in restrictions:
+ restrictions, incomplete_part = restrictions.rsplit(',', 1)
+ user_rql = '%s WHERE %s' % (variables, restrictions)
+ else:
+ restrictions, incomplete_part = '', restrictions
+ user_rql = variables
+ select = parse(user_rql, print_errors=False).children[0]
+ req.vreg.rqlhelper.annotate(select)
+ req.vreg.solutions(req, select, {})
+ if restrictions:
+ return ['%s, %s' % (user_rql, suggestion)
+ for suggestion in self.rql_build_suggestions(select, incomplete_part)]
+ else:
+ return ['%s WHERE %s' % (user_rql, suggestion)
+ for suggestion in self.rql_build_suggestions(select, incomplete_part)]
+ except Exception, exc: # we never want to crash
+ self.debug('failed to build suggestions: %s', exc)
+ return []
+
+ ## actual completion entry points #########################################
+ def rql_build_suggestions(self, select, incomplete_part):
+ """
+ :param select: the annotated select node (rql syntax tree)
+ :param incomplete_part: the part of the rql query that needs
+ to be completed, (e.g. ``X is Pr``, ``X re``)
+ """
+ chunks = incomplete_part.split(None, 2)
+ if not chunks: # nothing to complete
+ return []
+ if len(chunks) == 1: # `incomplete` looks like "MYVAR"
+ return self._complete_rqlvar(select, *chunks)
+ elif len(chunks) == 2: # `incomplete` looks like "MYVAR some_rel"
+ return self._complete_rqlvar_and_rtype(select, *chunks)
+ elif len(chunks) == 3: # `incomplete` looks like "MYVAR some_rel something"
+ return self._complete_relation_object(select, *chunks)
+ else: # would be anything else, hard to decide what to do here
+ return []
+
+ # _complete_* methods are considered private, at least while the API
+ # isn't stabilized.
+ def _complete_rqlvar(self, select, rql_var):
+ """return suggestions for "variable only" incomplete_part
+
+ as in :
+
+ - Any X WHERE X
+ - Any X WHERE X is Project, Y
+ - etc.
+ """
+ return ['%s %s %s' % (rql_var, rtype, dest_var)
+ for rtype, dest_var in self.possible_relations(select, rql_var)]
+
+ def _complete_rqlvar_and_rtype(self, select, rql_var, user_rtype):
+ """return suggestions for "variable + rtype" incomplete_part
+
+ as in :
+
+ - Any X WHERE X is
+ - Any X WHERE X is Person, X firstn
+ - etc.
+ """
+ # special case `user_type` == 'is', return every possible type.
+ if user_rtype == 'is':
+ return self._complete_is_relation(select, rql_var)
+ else:
+ return ['%s %s %s' % (rql_var, rtype, dest_var)
+ for rtype, dest_var in self.possible_relations(select, rql_var)
+ if rtype.startswith(user_rtype)]
+
+ def _complete_relation_object(self, select, rql_var, user_rtype, user_value):
+ """return suggestions for "variable + rtype + some_incomplete_value"
+
+ as in :
+
+ - Any X WHERE X is Per
+ - Any X WHERE X is Person, X firstname "
+ - Any X WHERE X is Person, X firstname "Pa
+ - etc.
+ """
+ # special case `user_type` == 'is', return every possible type.
+ if user_rtype == 'is':
+ return self._complete_is_relation(select, rql_var, user_value)
+ elif user_value:
+ if user_value[0] in ('"', "'"):
+ # if finished string, don't suggest anything
+ if len(user_value) > 1 and user_value[-1] == user_value[0]:
+ return []
+ user_value = user_value[1:]
+ return ['%s %s "%s"' % (rql_var, user_rtype, value)
+ for value in self.vocabulary(select, rql_var,
+ user_rtype, user_value)]
+ return []
+
+ def _complete_is_relation(self, select, rql_var, prefix=''):
+ """return every possible types for rql_var
+
+ :param prefix: if specified, will only return entity types starting
+ with the specified value.
+ """
+ return ['%s is %s' % (rql_var, etype)
+ for etype in self.possible_etypes(select, rql_var, prefix)]
+
+ def etypes_suggestion_set(self):
+ """returns the list of possible entity types to suggest
+
+ The default is to return any non-final entity type available
+ in the schema.
+
+ Can be overridden for instance if an application decides
+ to restrict this list to a meaningful set of business etypes.
+ """
+ schema = self._cw.vreg.schema
+ return set(eschema.type for eschema in schema.entities() if not eschema.final)
+
+ def possible_etypes(self, select, rql_var, prefix=''):
+ """return all possible etypes for `rql_var`
+
+ The returned list will always be a subset of meth:`etypes_suggestion_set`
+
+ :param select: the annotated select node (rql syntax tree)
+ :param rql_var: the variable name for which we want to know possible types
+ :param prefix: if specified, will only return etypes starting with it
+ """
+ available_etypes = self.etypes_suggestion_set()
+ possible_etypes = set()
+ for sol in select.solutions:
+ if rql_var in sol and sol[rql_var] in available_etypes:
+ possible_etypes.add(sol[rql_var])
+ if not possible_etypes:
+ # `Any X WHERE X is Person, Y is`
+ # -> won't have a solution, need to give all etypes
+ possible_etypes = available_etypes
+ return sorted(etype for etype in possible_etypes if etype.startswith(prefix))
+
+ def possible_relations(self, select, rql_var, include_meta=False):
+ """returns a list of couple (rtype, dest_var) for each possible
+ relations with `rql_var` as subject.
+
+ ``dest_var`` will be picked among availabel variables if types match,
+ otherwise a new one will be created.
+ """
+ schema = self._cw.vreg.schema
+ relations = set()
+ untyped_dest_var = rqlvar_maker(defined=select.defined_vars).next()
+ # for each solution
+ # 1. find each possible relation
+ # 2. for each relation:
+ # 2.1. if the relation is meta, skip it
+ # 2.2. for each possible destination type, pick up possible
+ # variables for this type or use a new one
+ for sol in select.solutions:
+ etype = sol[rql_var]
+ sol_by_types = {}
+ for varname, var_etype in sol.items():
+ # don't push subject var to avoid "X relation X" suggestion
+ if varname != rql_var:
+ sol_by_types.setdefault(var_etype, []).append(varname)
+ for rschema in schema[etype].subject_relations():
+ if include_meta or not rschema.meta:
+ for dest in rschema.objects(etype):
+ for varname in sol_by_types.get(dest.type, (untyped_dest_var,)):
+ suggestion = (rschema.type, varname)
+ if suggestion not in relations:
+ relations.add(suggestion)
+ return sorted(relations)
+
+ def vocabulary(self, select, rql_var, user_rtype, rtype_incomplete_value):
+ """return acceptable vocabulary for `rql_var` + `user_rtype` in `select`
+
+ Vocabulary is either found from schema (Yams) definition or
+ directly from database.
+ """
+ schema = self._cw.vreg.schema
+ vocab = []
+ for sol in select.solutions:
+ # for each solution :
+ # - If a vocabulary constraint exists on `rql_var+user_rtype`, use it
+ # to define possible values
+ # - Otherwise, query the database to fetch available values from
+ # database (limiting results to `self.attr_value_limit`)
+ try:
+ eschema = schema.eschema(sol[rql_var])
+ rdef = eschema.rdef(user_rtype)
+ except KeyError: # unknown relation
+ continue
+ cstr = rdef.constraint_by_interface(IVocabularyConstraint)
+ if cstr is not None:
+ # a vocabulary is found, use it
+ vocab += [value for value in cstr.vocabulary()
+ if value.startswith(rtype_incomplete_value)]
+ elif rdef.final:
+ # no vocab, query database to find possible value
+ vocab_rql = 'DISTINCT Any V LIMIT %s WHERE X is %s, X %s V' % (
+ self.attr_value_limit, eschema.type, user_rtype)
+ vocab_kwargs = {}
+ if rtype_incomplete_value:
+ vocab_rql += ', X %s LIKE %%(value)s' % user_rtype
+ vocab_kwargs['value'] = '%s%%' % rtype_incomplete_value
+ vocab += [value for value, in
+ self._cw.execute(vocab_rql, vocab_kwargs)]
+ return sorted(set(vocab))
+
+
+
+@ajaxfunc(output_type='json')
+def rql_suggest(self):
+ rql_builder = self._cw.vreg['components'].select_or_none('rql.suggestions', self._cw)
+ if rql_builder:
+ return rql_builder.build_suggestions(self._cw.form['term'])
+ return []
--- a/web/views/management.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/management.py Fri Jan 25 14:33:40 2013 +0100
@@ -76,7 +76,9 @@
domid='ownership%s' % entity.eid,
__redirectvid='security',
__redirectpath=entity.rest_path())
- field = guess_field(entity.e_schema, self._cw.vreg.schema.rschema('owned_by'))
+ field = guess_field(entity.e_schema,
+ self._cw.vreg.schema['owned_by'],
+ req=self._cw)
form.append_field(field)
form.render(w=self.w, display_progress_div=False)
--- a/web/views/primary.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/primary.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -50,7 +50,8 @@
from cubicweb.predicates import match_kwargs, match_context
from cubicweb.view import EntityView
from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name
-from cubicweb.web import uicfg, component
+from cubicweb.web import component
+from cubicweb.web.views import uicfg
class PrimaryView(EntityView):
@@ -96,8 +97,8 @@
title = _('primary')
show_attr_label = True
show_rel_label = True
- rsection = uicfg.primaryview_section
- display_ctrl = uicfg.primaryview_display_ctrl
+ rsection = None
+ display_ctrl = None
main_related_section = True
def html_headers(self):
@@ -110,6 +111,13 @@
def entity_call(self, entity):
entity.complete()
+ uicfg_reg = self._cw.vreg['uicfg']
+ if self.rsection is None:
+ self.rsection = uicfg_reg.select('primaryview_section',
+ self._cw, entity=entity)
+ if self.display_ctrl is None:
+ self.display_ctrl = uicfg_reg.select('primaryview_display_ctrl',
+ self._cw, entity=entity)
self.render_entity(entity)
def render_entity(self, entity):
--- a/web/views/reledit.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/reledit.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -34,7 +34,8 @@
from cubicweb.utils import json, json_dumps
from cubicweb.predicates import non_final_entity, match_kwargs
from cubicweb.view import EntityView
-from cubicweb.web import uicfg, stdmsgs
+from cubicweb.web import stdmsgs
+from cubicweb.web.views import uicfg
from cubicweb.web.form import FieldNotFound
from cubicweb.web.formwidgets import Button, SubmitButton
from cubicweb.web.views.ajaxcontroller import ajaxfunc
@@ -50,8 +51,6 @@
def add_hidden(self, *args):
pass
-rctrl = uicfg.reledit_ctrl
-
class AutoClickAndEditFormView(EntityView):
__regid__ = 'reledit'
__select__ = non_final_entity() & match_kwargs('rtype')
@@ -90,6 +89,7 @@
self._cw.add_js(('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js'))
self.entity = entity
rschema = self._cw.vreg.schema[rtype]
+ rctrl = self._cw.vreg['uicfg'].select('reledit', self._cw, entity=entity)
self._rules = rctrl.etype_get(self.entity.e_schema.type, rschema.type, role, '*')
if rvid is not None or default_value is not None:
warn('[3.9] specifying rvid/default_value on select is deprecated, '
--- a/web/views/schema.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/schema.py Fri Jan 25 14:33:40 2013 +0100
@@ -39,9 +39,9 @@
from cubicweb.utils import make_uid
from cubicweb.view import EntityView, StartupView
from cubicweb import tags, uilib
-from cubicweb.web import action, facet, uicfg, schemaviewer
+from cubicweb.web import action, facet, schemaviewer
from cubicweb.web.views import TmpFileViewMixin
-from cubicweb.web.views import primary, baseviews, tabs, tableview, ibreadcrumbs
+from cubicweb.web.views import uicfg, primary, baseviews, tabs, tableview, ibreadcrumbs
ALWAYS_SKIP_TYPES = BASE_TYPES | SCHEMA_TYPES
SKIP_TYPES = (ALWAYS_SKIP_TYPES | META_RTYPES | SYSTEM_RTYPES | WORKFLOW_TYPES
--- a/web/views/sessions.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/sessions.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/views/startup.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/startup.py Fri Jan 25 14:33:40 2013 +0100
@@ -31,7 +31,8 @@
from cubicweb.view import StartupView
from cubicweb.predicates import match_user_groups, is_instance
from cubicweb.schema import display_name
-from cubicweb.web import uicfg, httpcache
+from cubicweb.web import httpcache
+from cubicweb.web.views import uicfg
class ManageView(StartupView):
""":__regid__: *manage*
@@ -51,7 +52,7 @@
title = _('manage')
http_cache_manager = httpcache.EtagHTTPCacheManager
add_etype_links = ()
- skip_startup_views = set( ('index', 'manage', 'schema', 'owl', 'changelog',
+ skip_startup_views = set( ('index', 'manage', 'schema', 'owl',
'systempropertiesform', 'propertiesform',
'loggedout', 'login',
'cw.users-and-groups-management', 'cw.groups-management',
--- a/web/views/tableview.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/tableview.py Fri Jan 25 14:33:40 2013 +0100
@@ -290,20 +290,17 @@
return attrs
def render_actions(self, w, actions):
- box = MenuWidget('', '', _class='tableActionsBox', islist=False)
- label = tags.span(self._cw._('action menu'))
- menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
- ident='%sActions' % self.view.domid)
- box.append(menu)
+ w(u'<div class="tableactions">')
for action in actions:
- menu.append(action)
- box.render(w=w)
- w(u'<div class="clear"></div>')
+ w(u'<span>')
+ action.render(w)
+ w(u'</span>')
+ w(u'</div>')
def show_hide_filter_actions(self, currentlydisplayed=False):
divid = self.view.domid
showhide = u';'.join(toggle_action('%s%s' % (divid, what))[11:]
- for what in ('Form', 'Show', 'Hide', 'Actions'))
+ for what in ('Form', 'Actions'))
showhide = 'javascript:' + showhide
self._cw.add_onload(u'''\
$(document).ready(function() {
@@ -313,10 +310,8 @@
$('#%(id)sShow').attr('class', 'hidden');
}
});''' % {'id': divid})
- showlabel = self._cw._('show filter form')
- hidelabel = self._cw._('hide filter form')
- return [component.Link(showhide, showlabel, id='%sShow' % divid),
- component.Link(showhide, hidelabel, id='%sHide' % divid)]
+ showlabel = self._cw._('toggle filter')
+ return [component.Link(showhide, showlabel, id='%sToggle' % divid)]
class AbstractColumnRenderer(object):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/uicfg.py Fri Jan 25 14:33:40 2013 +0100
@@ -0,0 +1,663 @@
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+"""This module (``cubicweb.web.views.uicfg``) regroups a set of structures that may be
+used to configure various options of the generated web interface.
+
+To configure the interface generation, we use ``RelationTag`` objects.
+
+Index view configuration
+````````````````````````
+:indexview_etype_section:
+ entity type category in the index/manage page. May be one of:
+
+ * ``application``
+ * ``system``
+ * ``schema``
+ * ``subobject`` (not displayed by default)
+
+ By default only entities on the ``application`` category are shown.
+
+.. sourcecode:: python
+
+ from cubicweb.web.views import uicfg
+ # force hiding
+ uicfg.indexview_etype_section['HideMe'] = 'subobject'
+ # force display
+ uicfg.indexview_etype_section['ShowMe'] = 'application'
+
+
+Actions box configuration
+`````````````````````````
+:actionbox_appearsin_addmenu:
+ simple boolean relation tags used to control the "add entity" submenu.
+ Relations whose rtag is True will appears, other won't.
+
+.. sourcecode:: python
+
+ # Adds all subjects of the entry_of relation in the add menu of the ``Blog``
+ # primary view
+ uicfg.actionbox_appearsin_addmenu.tag_object_of(('*', 'entry_of', 'Blog'), True)
+"""
+__docformat__ = "restructuredtext en"
+
+from warnings import warn
+
+from logilab.common.compat import any
+
+from cubicweb import neg_role
+from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
+ RelationTagsDict, NoTargetRelationTagsDict,
+ _ensure_str_key)
+from cubicweb.schema import META_RTYPES, INTERNAL_TYPES, WORKFLOW_TYPES
+
+
+# primary view configuration ##################################################
+
+class PrimaryViewSectionRelationTags(RelationTags):
+ """primary view section configuration"""
+ __regid__ = 'primaryview_section'
+
+ _allowed_values = frozenset(('attributes', 'relations',
+ 'sideboxes', 'hidden'))
+
+ def _init(self, sschema, rschema, oschema, role):
+ if self.get(sschema, rschema, oschema, role) is None:
+ rdef = rschema.rdef(sschema, oschema)
+ if rschema.final:
+ if rschema.meta or sschema.is_metadata(rschema) \
+ or oschema.type in ('Password', 'Bytes'):
+ section = 'hidden'
+ else:
+ section = 'attributes'
+ else:
+ if rdef.role_cardinality(role) in '1+':
+ section = 'attributes'
+ elif rdef.composite == neg_role(role):
+ section = 'relations'
+ else:
+ section = 'sideboxes'
+ self.tag_relation((sschema, rschema, oschema, role), section)
+
+primaryview_section = PrimaryViewSectionRelationTags()
+
+
+class DisplayCtrlRelationTags(NoTargetRelationTagsDict):
+ """primary view display controller configuration"""
+ __regid__ = 'primaryview_display_ctrl'
+
+ def __init__(self, *args, **kwargs):
+ super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs)
+ self.counter = 0
+
+ def _init(self, sschema, rschema, oschema, role):
+ if role == 'subject':
+ oschema = '*'
+ else:
+ sschema = '*'
+ self.counter += 1
+ self.setdefault((sschema, rschema, oschema, role),
+ 'order',
+ self.counter)
+
+primaryview_display_ctrl = DisplayCtrlRelationTags()
+
+
+# index view configuration ####################################################
+# entity type section in the index/manage page. May be one of
+# * 'application'
+# * 'system'
+# * 'schema'
+# * 'hidden'
+# * 'subobject' (not displayed by default)
+
+class InitializableDict(dict): # XXX not a rtag. Turn into an appobject?
+ def __init__(self, *args, **kwargs):
+ super(InitializableDict, self).__init__(*args, **kwargs)
+ self.__defaults = dict(self)
+
+ def init(self, schema, check=True):
+ self.update(self.__defaults)
+ for eschema in schema.entities():
+ if eschema.final:
+ continue
+ if eschema.schema_entity():
+ self.setdefault(eschema, 'schema')
+ elif eschema in INTERNAL_TYPES or eschema in WORKFLOW_TYPES:
+ self.setdefault(eschema, 'system')
+ elif eschema.is_subobject(strict=True):
+ self.setdefault(eschema, 'subobject')
+ else:
+ self.setdefault(eschema, 'application')
+
+indexview_etype_section = InitializableDict(
+ EmailAddress='subobject',
+ Bookmark='system',
+ # entity types in the 'system' table by default (managers only)
+ CWUser='system', CWGroup='system',
+ )
+
+
+# autoform.AutomaticEntityForm configuration ##################################
+
+def _formsections_as_dict(formsections):
+ result = {}
+ for formsection in formsections:
+ formtype, section = formsection.split('_', 1)
+ result[formtype] = section
+ return result
+
+def _card_and_comp(sschema, rschema, oschema, role):
+ rdef = rschema.rdef(sschema, oschema)
+ if role == 'subject':
+ card = rdef.cardinality[0]
+ composed = not rschema.final and rdef.composite == 'object'
+ else:
+ card = rdef.cardinality[1]
+ composed = not rschema.final and rdef.composite == 'subject'
+ return card, composed
+
+class AutoformSectionRelationTags(RelationTagsSet):
+ """autoform relations'section"""
+ __regid__ = 'autoform_section'
+
+ _allowed_form_types = ('main', 'inlined', 'muledit')
+ _allowed_values = {'main': ('attributes', 'inlined', 'relations',
+ 'metadata', 'hidden'),
+ 'inlined': ('attributes', 'inlined', 'hidden'),
+ 'muledit': ('attributes', 'hidden'),
+ }
+
+ def init(self, schema, check=True):
+ super(AutoformSectionRelationTags, self).init(schema, check)
+ self.apply(schema, self._initfunc_step2)
+
+ def _init(self, sschema, rschema, oschema, role):
+ formsections = self.init_get(sschema, rschema, oschema, role)
+ if formsections is None:
+ formsections = self.tag_container_cls()
+ if not any(tag.startswith('inlined') for tag in formsections):
+ if not rschema.final:
+ negsects = self.init_get(sschema, rschema, oschema, neg_role(role))
+ if 'main_inlined' in negsects:
+ formsections.add('inlined_hidden')
+ key = _ensure_str_key( (sschema, rschema, oschema, role) )
+ self._tagdefs[key] = formsections
+
+ def _initfunc_step2(self, sschema, rschema, oschema, role):
+ formsections = self.get(sschema, rschema, oschema, role)
+ sectdict = _formsections_as_dict(formsections)
+ if rschema in META_RTYPES:
+ sectdict.setdefault('main', 'hidden')
+ sectdict.setdefault('muledit', 'hidden')
+ sectdict.setdefault('inlined', 'hidden')
+ elif role == 'subject' and rschema in sschema.meta_attributes():
+ # meta attribute, usually embeded by the described attribute's field
+ # (eg RichTextField, FileField...)
+ sectdict.setdefault('main', 'hidden')
+ sectdict.setdefault('muledit', 'hidden')
+ sectdict.setdefault('inlined', 'hidden')
+ # ensure we have a tag for each form type
+ if not 'main' in sectdict:
+ if not rschema.final and (
+ sectdict.get('inlined') == 'attributes' or
+ 'inlined_attributes' in self.init_get(sschema, rschema, oschema,
+ neg_role(role))):
+ sectdict['main'] = 'hidden'
+ elif sschema.is_metadata(rschema):
+ sectdict['main'] = 'metadata'
+ else:
+ card, composed = _card_and_comp(sschema, rschema, oschema, role)
+ if card in '1+':
+ sectdict['main'] = 'attributes'
+ if not 'muledit' in sectdict:
+ sectdict['muledit'] = 'attributes'
+ elif rschema.final:
+ sectdict['main'] = 'attributes'
+ else:
+ sectdict['main'] = 'relations'
+ if not 'muledit' in sectdict:
+ sectdict['muledit'] = 'hidden'
+ if sectdict['main'] == 'attributes':
+ card, composed = _card_and_comp(sschema, rschema, oschema, role)
+ if card in '1+' and not composed:
+ sectdict['muledit'] = 'attributes'
+ if not 'inlined' in sectdict:
+ sectdict['inlined'] = sectdict['main']
+ # recompute formsections and set it to avoid recomputing
+ for formtype, section in sectdict.iteritems():
+ formsections.add('%s_%s' % (formtype, section))
+
+ def tag_relation(self, key, formtype, section):
+ if isinstance(formtype, tuple):
+ for ftype in formtype:
+ self.tag_relation(key, ftype, section)
+ return
+ assert formtype in self._allowed_form_types, \
+ 'formtype should be in (%s), not %s' % (
+ ','.join(self._allowed_form_types), formtype)
+ assert section in self._allowed_values[formtype], \
+ 'section for %s should be in (%s), not %s' % (
+ formtype, ','.join(self._allowed_values[formtype]), section)
+ rtags = self._tagdefs.setdefault(_ensure_str_key(key),
+ self.tag_container_cls())
+ # remove previous section for this form type if any
+ if rtags:
+ for tag in rtags.copy():
+ if tag.startswith(formtype):
+ rtags.remove(tag)
+ rtags.add('%s_%s' % (formtype, section))
+ return rtags
+
+ def init_get(self, stype, rtype, otype, tagged):
+ key = (stype, rtype, otype, tagged)
+ rtags = {}
+ for key in self._get_keys(stype, rtype, otype, tagged):
+ tags = self._tagdefs.get(key, ())
+ for tag in tags:
+ assert '_' in tag, (tag, tags)
+ section, value = tag.split('_', 1)
+ rtags[section] = value
+ cls = self.tag_container_cls
+ rtags = cls('_'.join([section,value])
+ for section,value in rtags.iteritems())
+ return rtags
+
+ def get(self, *key):
+ # overriden to avoid recomputing done in parent classes
+ return self._tagdefs.get(key, ())
+
+ def relations_by_section(self, entity, formtype, section, permission,
+ strict=False):
+ """return a list of (relation schema, target schemas, role) for the
+ given entity matching categories and permission.
+
+ `strict`:
+ bool telling if having local role is enough (strict = False) or not
+ """
+ tag = '%s_%s' % (formtype, section)
+ eschema = entity.e_schema
+ cw = entity._cw
+ permsoverrides = cw.vreg['uicfg'].select('autoform_permissions_overrides', cw, entity=entity)
+ if entity.has_eid():
+ eid = entity.eid
+ else:
+ eid = None
+ strict = False
+ if permission == 'update':
+ assert section in ('attributes', 'metadata', 'hidden')
+ relpermission = 'add'
+ else:
+ assert section not in ('attributes', 'metadata', 'hidden')
+ relpermission = permission
+ for rschema, targetschemas, role in eschema.relation_definitions(True):
+ _targetschemas = []
+ for tschema in targetschemas:
+ # check section's tag first, potentially lower cost than
+ # checking permission which may imply rql queries
+ if not tag in self.etype_get(eschema, rschema, role, tschema):
+ continue
+ rdef = rschema.role_rdef(eschema, tschema, role)
+ if rschema.final:
+ if not rdef.has_perm(cw, permission, eid=eid,
+ creating=eid is None):
+ continue
+ elif strict or not rdef.has_local_role(relpermission):
+ if role == 'subject':
+ if not rdef.has_perm(cw, relpermission, fromeid=eid):
+ continue
+ elif role == 'object':
+ if not rdef.has_perm(cw, relpermission, toeid=eid):
+ continue
+ _targetschemas.append(tschema)
+ if not _targetschemas:
+ continue
+ targetschemas = _targetschemas
+ rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0])
+ # XXX tag allowing to hijack the permission machinery when
+ # permission is not verifiable until the entity is actually
+ # created...
+ if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
+ yield (rschema, targetschemas, role)
+ continue
+ if not rschema.final and role == 'subject':
+ # on relation with cardinality 1 or ?, we need delete perm as well
+ # if the relation is already set
+ if (relpermission == 'add'
+ and rdef.role_cardinality(role) in '1?'
+ and eid and entity.related(rschema.type, role)
+ and not rdef.has_perm(cw, 'delete', fromeid=eid,
+ toeid=entity.related(rschema.type, role)[0][0])):
+ continue
+ elif role == 'object':
+ # on relation with cardinality 1 or ?, we need delete perm as well
+ # if the relation is already set
+ if (relpermission == 'add'
+ and rdef.role_cardinality(role) in '1?'
+ and eid and entity.related(rschema.type, role)
+ and not rdef.has_perm(cw, 'delete', toeid=eid,
+ fromeid=entity.related(rschema.type, role)[0][0])):
+ continue
+ yield (rschema, targetschemas, role)
+
+ def hide_field(self, etype, attr, desttype='*', formtype='main'):
+ """hide `attr` in `etype` forms.
+
+ :param etype: the entity type as a string
+ :param attr: the name of the attribute or relation to hide
+ :param formtype: which form will be affected ('main', 'inlined', etc.),
+ *main* by default.
+
+ `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_rel)
+
+ Examples:
+
+ .. sourcecode:: python
+
+ from cubicweb.web.views.uicfg import autoform_section as afs
+ afs.hide_field('CWUser', 'login')
+ afs.hide_field('*', 'name')
+ afs.hide_field('CWUser', 'use_email', formtype='inlined')
+
+ """
+ self._tag_etype_attr(etype, attr, desttype,
+ formtype=formtype, section='hidden')
+
+ def hide_fields(self, etype, attrs, formtype='main'):
+ """simple for-loop wrapper around :func:`hide_field`.
+
+ :param etype: the entity type as a string
+ :param attrs: the ordered list of attribute names (or relations)
+ :param formtype: which form will be affected ('main', 'inlined', etc.),
+ *main* by default.
+
+ `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_rel)
+
+ Examples:
+
+ .. sourcecode:: python
+
+ from cubicweb.web.views.uicfg import autoform_section as afs
+ afs.hide_fields('CWUser', ('login', ('use_email', 'subject')),
+ formtype='inlined')
+ """
+ for attr in attrs:
+ self.hide_field(etype, attr, formtype=formtype)
+
+ def edit_inline(self, etype, attr, desttype='*', formtype=('main', 'inlined')):
+ """edit `attr` with and inlined form.
+
+ :param etype: the entity type as a string
+ :param attr: the name of the attribute or relation
+ :param desttype: the destination type(s) concerned, default is everything
+ :param formtype: which form will be affected ('main', 'inlined', etc.),
+ *main* and *inlined* by default.
+
+ `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+
+ Examples:
+
+ .. sourcecode:: python
+
+ from cubicweb.web.views.uicfg import autoform_section as afs
+
+ afs.edit_inline('*', 'use_email')
+ """
+ self._tag_etype_attr(etype, attr, desttype, formtype=formtype,
+ section='inlined')
+
+ def edit_as_attr(self, etype, attr, desttype='*', formtype=('main', 'muledit')):
+ """make `attr` appear in the *attributes* section of `etype` form.
+
+ :param etype: the entity type as a string
+ :param attr: the name of the attribute or relation
+ :param desttype: the destination type(s) concerned, default is everything
+ :param formtype: which form will be affected ('main', 'inlined', etc.),
+ *main* and *muledit* by default.
+
+ `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+
+ Examples:
+
+ .. sourcecode:: python
+
+ from cubicweb.web.views.uicfg import autoform_section as afs
+
+ afs.edit_as_attr('CWUser', 'in_group')
+ """
+ self._tag_etype_attr(etype, attr, desttype,
+ formtype=formtype, section='attributes')
+
+ def set_muledit_editable(self, etype, attrs):
+ """make `attrs` appear in muledit form of `etype`.
+
+ :param etype: the entity type as a string
+ :param attrs: the ordered list of attribute names (or relations)
+
+ `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)
+
+ Examples:
+
+ .. sourcecode:: python
+
+ from cubicweb.web.views.uicfg import autoform_section as afs
+
+ afs.set_muledit_editable('CWUser', ('firstname', 'surname', 'in_group'))
+ """
+ for attr in attrs:
+ self.edit_as_attr(self, etype, attr, formtype='muledit')
+
+autoform_section = AutoformSectionRelationTags()
+
+
+# relations'field class
+
+class AutoformFieldTags(RelationTags):
+ __regid__ = 'autoform_field'
+
+ def set_field(self, etype, attr, field):
+ """sets the `attr` field of `etype`.
+
+ :param etype: the entity type as a string
+ :param attr: the name of the attribute or relation
+
+ `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+
+ """
+ self._tag_etype_attr(etype, attr, '*', field)
+
+autoform_field = AutoformFieldTags()
+
+
+# relations'field explicit kwargs (given to field's __init__)
+
+class AutoformFieldKwargsTags(RelationTagsDict):
+ __regid__ = 'autoform_field_kwargs'
+
+ def set_fields_order(self, etype, attrs):
+ """specify the field order in `etype` main edition form.
+
+ :param etype: the entity type as a string
+ :param attrs: the ordered list of attribute names (or relations)
+
+ `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_rel)
+
+ Unspecified fields will be displayed after specified ones, their
+ order being consistent with the schema definition.
+
+ Examples:
+
+ .. sourcecode:: python
+
+ from cubicweb.web.views.uicfg import autoform_field_kwargs as affk
+ affk.set_fields_order('CWUser', ('firstname', 'surname', 'login'))
+ affk.set_fields_order('CWUser', ('firstname', ('in_group', 'subject'),
+ 'surname', 'login'))
+
+ """
+ for index, attr in enumerate(attrs):
+ self._tag_etype_attr(etype, attr, '*', {'order': index})
+
+ def set_field_kwargs(self, etype, attr, **kwargs):
+ """tag `attr` field of `etype` with additional named paremeters.
+
+ :param etype: the entity type as a string
+ :param attr: the name of the attribute or relation
+
+ `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+
+ Examples:
+
+ .. sourcecode:: python
+
+ from cubicweb.web.views.uicfg import autoform_field_kwargs as affk
+ affk.set_field_kwargs('Person', 'works_for', widget=fwdgs.AutoCompletionWidget())
+ affk.set_field_kwargs('CWUser', 'login', label=_('login or email address'),
+ widget=fwdgs.TextInput(attrs={'size': 30}))
+ """
+ self._tag_etype_attr(etype, attr, '*', kwargs)
+
+
+autoform_field_kwargs = AutoformFieldKwargsTags()
+
+
+# set of tags of the form <action>_on_new on relations. <action> is a
+# schema action (add/update/delete/read), and when such a tag is found
+# permissions checking is by-passed and supposed to be ok
+class AutoFormPermissionsOverrides(RelationTagsSet):
+ __regid__ = 'autoform_permissions_overrides'
+
+autoform_permissions_overrides = AutoFormPermissionsOverrides()
+
+
+class ReleditTags(NoTargetRelationTagsDict):
+ """Associate to relation a dictionary to control `reledit` (e.g. edition of
+ attributes / relations from within views).
+
+ Possible keys and associated values are:
+
+ * `novalue_label`, alternative default value (shown when there is no value).
+
+ * `novalue_include_rtype`, when `novalue_label` is not specified, this boolean
+ flag control wether the generated default value should contains the
+ relation label or not. Will be the opposite of the `showlabel` value found
+ in the `primaryview_display_ctrl` rtag by default.
+
+ * `reload`, boolean, eid (to reload to) or function taking subject and
+ returning bool/eid. This is useful when editing a relation (or attribute)
+ that impacts the url or another parts of the current displayed
+ page. Defaults to False.
+
+ * `rvid`, alternative view id (as str) for relation or composite edition.
+ Default is 'autolimited'.
+
+ * `edit_target`, may be either 'rtype' (to edit the relation) or 'related'
+ (to edit the related entity). This controls whether to edit the relation
+ or the target entity of the relation. Currently only one-to-one relations
+ support target entity edition. By default, the 'related' option is taken
+ whenever the relation is composite.
+ """
+ __regid__ = 'reledit'
+ _keys = frozenset('novalue_label novalue_include_rtype reload rvid edit_target'.split())
+
+ def tag_relation(self, key, tag):
+ for tagkey in tag.iterkeys():
+ assert tagkey in self._keys, 'tag %r not in accepted tags: %r' % (tag, self._keys)
+ return super(ReleditTags, self).tag_relation(key, tag)
+
+ def _init(self, sschema, rschema, oschema, role):
+ values = self.get(sschema, rschema, oschema, role)
+ if not rschema.final:
+ composite = rschema.rdef(sschema, oschema).composite == role
+ if role == 'subject':
+ oschema = '*'
+ else:
+ sschema = '*'
+ edittarget = values.get('edit_target')
+ if edittarget not in (None, 'rtype', 'related'):
+ self.warning('reledit: wrong value for edit_target on relation %s: %s',
+ rschema, edittarget)
+ edittarget = None
+ if not edittarget:
+ edittarget = 'related' if composite else 'rtype'
+ self.tag_relation((sschema, rschema, oschema, role),
+ {'edit_target': edittarget})
+ if not 'novalue_include_rtype' in values:
+ showlabel = primaryview_display_ctrl.get(
+ sschema, rschema, oschema, role).get('showlabel', True)
+ self.tag_relation((sschema, rschema, oschema, role),
+ {'novalue_include_rtype': not showlabel})
+
+reledit_ctrl = ReleditTags()
+
+
+# boxes.EditBox configuration #################################################
+
+# 'link' / 'create' relation tags, used to control the "add entity" submenu
+
+class ActionBoxUicfg(RelationTagsBool):
+ __regid__ = 'actionbox_appearsin_addmenu'
+
+ def _init(self, sschema, rschema, oschema, role):
+ if self.get(sschema, rschema, oschema, role) is None:
+ if rschema in META_RTYPES:
+ self.tag_relation((sschema, rschema, oschema, role), False)
+ return
+ rdef = rschema.rdef(sschema, oschema)
+ if not rdef.role_cardinality(role) in '?1' and rdef.composite == role:
+ self.tag_relation((sschema, rschema, oschema, role), True)
+
+ def _tag_etype_attr(self, etype, attr, desttype='*', *args, **kwargs):
+ if isinstance(attr, basestring):
+ attr, role = attr, 'subject'
+ else:
+ attr, role = attr
+ if role == 'subject':
+ self.tag_subject_of((etype, attr, desttype), *args, **kwargs)
+ else:
+ self.tag_object_of((desttype, attr, etype), *args, **kwargs)
+
+ def append_to_addmenu(self, etype, attr, createdtype='*'):
+ """adds `attr` in the actions box *addrelated* submenu of `etype`.
+
+ :param etype: the entity type as a string
+ :param attr: the name of the attribute or relation to hide
+ :param createdtype: the target type of the relation (optional, defaults to '*' (all possible types))
+
+ `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+
+ """
+ self._tag_etype_attr(etype, attr, createdtype, True)
+
+ def remove_from_addmenu(self, etype, attr, createdtype='*'):
+ """removes `attr` from the actions box *addrelated* submenu of `etype`.
+
+ :param etype: the entity type as a string
+ :param attr: the name of the attribute or relation to hide
+ :param createdtype: the target type of the relation (optional, defaults to '*' (all possible types))
+
+ `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
+ """
+ self._tag_etype_attr(etype, attr, createdtype, False)
+
+actionbox_appearsin_addmenu = ActionBoxUicfg()
+
+
+
+def registration_callback(vreg):
+ vreg.register_all(globals().values(), __name__)
+ indexview_etype_section.init(vreg.schema)
--- a/web/views/urlrewrite.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/urlrewrite.py Fri Jan 25 14:33:40 2013 +0100
@@ -109,7 +109,6 @@
(rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')),
(rgx('/doc/?'), dict(vid='wdoc', fid=r'main')),
(rgx('/doc/(.+?)/?'), dict(vid='wdoc', fid=r'\1')),
- (rgx('/changelog/?'), dict(vid='changelog')),
]
def rewrite(self, req, uri):
--- a/web/views/wdoc.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/wdoc.py Fri Jan 25 14:33:40 2013 +0100
@@ -205,60 +205,6 @@
self.w(open(join(resourcedir, rid)).read())
-class ChangeLogView(StartupView):
- __regid__ = 'changelog'
- title = _('What\'s new?')
- maxentries = 25
-
- def call(self):
- rid = 'ChangeLog_%s' % (self._cw.lang)
- allentries = []
- title = self._cw._(self.title)
- restdata = ['.. -*- coding: utf-8 -*-', '', title, '='*len(title), '']
- w = restdata.append
- today = date.today()
- for fpath in self._cw.vreg.config.locate_all_files(rid):
- cl = ChangeLog(fpath)
- encoding = 'utf-8'
- # additional content may be found in title
- for line in (cl.title + cl.additional_content).splitlines():
- m = CHARSET_DECL_RGX.search(line)
- if m is not None:
- encoding = m.group(1)
- continue
- elif line.startswith('.. '):
- w(unicode(line, encoding))
- for entry in cl.entries:
- if entry.date:
- edate = todate(strptime(entry.date, '%Y-%m-%d'))
- else:
- edate = today
- messages = []
- for msglines, submsgs in entry.messages:
- msgstr = unicode(' '.join(l.strip() for l in msglines), encoding)
- msgstr += u'\n\n'
- for submsglines in submsgs:
- msgstr += ' - ' + unicode(' '.join(l.strip() for l in submsglines), encoding)
- msgstr += u'\n'
- messages.append(msgstr)
- entry = (edate, messages)
- allentries.insert(bisect_right(allentries, entry), entry)
- latestdate = None
- i = 0
- for edate, messages in reversed(allentries):
- if latestdate != edate:
- fdate = self._cw.format_date(edate)
- w(u'\n%s' % fdate)
- w('~' * len(fdate))
- latestdate = edate
- for msg in messages:
- w(u'* %s' % msg)
- i += 1
- if i > self.maxentries:
- break
- w('') # blank line
- self.w(rest_publish(self, '\n'.join(restdata)))
-
class HelpAction(action.Action):
__regid__ = 'help'
@@ -271,17 +217,6 @@
def url(self):
return self._cw.build_url('doc/main')
-class ChangeLogAction(action.Action):
- __regid__ = 'changelog'
- __select__ = yes()
-
- category = 'footer'
- order = 1
- title = ChangeLogView.title
-
- def url(self):
- return self._cw.build_url('changelog')
-
class AboutAction(action.Action):
__regid__ = 'about'
--- a/web/views/workflow.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/views/workflow.py Fri Jan 25 14:33:40 2013 +0100
@@ -36,10 +36,10 @@
score_entity, is_instance, adaptable)
from cubicweb.view import EntityView
from cubicweb.schema import display_name
-from cubicweb.web import uicfg, stdmsgs, action, component, form, action
+from cubicweb.web import stdmsgs, action, component, form, action
from cubicweb.web import formfields as ff, formwidgets as fwdgs
from cubicweb.web.views import TmpFileViewMixin
-from cubicweb.web.views import forms, primary, ibreadcrumbs
+from cubicweb.web.views import uicfg, forms, primary, ibreadcrumbs
from cubicweb.web.views.tabs import TabbedPrimaryView, PrimaryTab
from cubicweb.web.views.dotgraphview import DotGraphView, DotPropsHandler
--- a/web/wdoc/ChangeLog_en Thu Jan 24 16:13:40 2013 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-.. -*- coding: utf-8 -*-
-.. _`user preferences`: myprefs
-.. _here: sparql
-.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
-.. _schema: schema
-.. _OWL: http://www.w3.org/TR/owl-features/
-.. _CWSource: cwetype/CWSource
-
-2010-10-17 -- 3.10.0
-
- * facets on optional relations now propose to search to entities
- which miss the relation
-
- * box and content navigation components have been merged, so this
- simplify the configuration and you should be able to move
- boxes/components in various places of the interface
-
- * global / context dependant boxes should now be more
- distinguishable to make the interface more intuitive
-
- * upgraded jQuery and jQuery UI respectively to version 1.4.2 and
- 1.8.
-
- * data sources are now stored as CWSource_ entities in the database.
- This allows on multi-sources instance to filter search results
- according to the source entities are coming from.
-
-
-2010-06-11 -- 3.8.4
- * support full text prefix search for instances using postgres > 8.4 as database: try it
- by using search such as 'cubic*'
-
-
-2010-04-20 -- 3.8.0
- * nicer schema_ and workflow views (clickable image!)
-
- * more power to undo, though not yet complete (missing entity updates, soon available...)
-
- * pdf export functionnality moved to its own cube. If it's no more
- present on this site while you found it useful, ask you site
- administrator to install the pdfexport_ cube.
-
-
-2010-03-16 -- 3.7.0
- * experimental support for undoing of deletion. If you're not proposed to *undo*
- deletion of one or several entities, ask you site administrator to activate
- the feature.
-
-
-2010-02-10 -- 3.6.0
- * nice 'demo widget' to edit bookmark's path, e.g. a relative url, splitted
- as path and parameters and dealing nicely with url encodings. Try to
- edit your bookmarks!
-
- * hell lot of refactorings, but you should hopefuly not see that from the outside
-
-2009-09-17 -- 3.5.0
-
- * selectable workflows: authorized users may change the workflow used
- by some workflowable entities
-
-
-2009-08-07 -- 3.4.0
-
- * support for SPARQL_. Click here_ to test it.
-
- * and another step toward the semantic web: new `ExternalUri` entity type
- with its associated `same_as` relation. See
- http://www.w3.org/TR/owl-ref/#sameAs-def for more information and check
- this instance schema_ to see on which entity types it may be applied
-
- * new "view workflow" and "view history" items in the workflow
- sub-menu of the actions box
-
- * you can now edit comments of workflow transition afterward (if authorized,
- of course)
-
- * modification date of an entity is updated when its state is changed
-
-
-2009-02-26 -- 3.1.0
-
- * schema may be exported as OWL_
-
- * new ajax interface for site configuration / `user preferences`_
-
-
-2008-10-24 -- 2.99.0
- * cubicweb is now open source !
--- a/web/wdoc/ChangeLog_fr Thu Jan 24 16:13:40 2013 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-.. -*- coding: utf-8 -*-
-.. _`préférences utilisateurs`: myprefs#fieldset_ui
-.. _ici: sparql
-.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
-.. _schema: schema
-.. _OWL: http://www.w3.org/TR/owl-features/
-.. _pdfexport: http://www.cubicweb.org/project/cubicweb-pdfexport
-.. _CWSource: cwetype/CWSource
-
-2010-10-07 -- 3.10.0
-
- * les facettes sur les relations optionnelles proposent maintenant
- de filter les entité qui n'ont *pas* la relation
-
- * les boîtes et composants contextuels ont été fusionnés, permettant
- de simplifier la configuration et de placer ces nouveaux composants
- comme vous le désirez
-
- * les boîtes globales ou dépendantes du contexte sont plus
- facilement distinguable pour rendre l'interface plus intuitive
-
- * passage à jQuery 1.4.2, et jQuery UI 1.8
-
- * les sources de données sont maintenant stockées dans la base de
- données sous forme d'entités CWSource_. Cela permet sur les
- instances multi-source de filter les résultats de recherche en
- fonction de la source dont viennent les entités.
-
-
-2010-06-11 -- 3.8.4
- * support pour la recherche de préfixe pour les instances utilisant postgres > 8.4 :
- essayez en cherchant par ex. 'cubic*'
-
-2010-04-20 -- 3.8.0
-
- * amélioration des vues de schema_ et des vues de workflows
- (images clickable !)
-
- * meilleure support du "undo", mais il manque toujours le support
- sur la modification d'entité (bientôt...)
-
- * la fonctionnalité d'export d'pdf a été déplacé dans son propre
- cube. Si cette fonctionalité n'est plus disponible sur ce site et
- que vous la trouviez utile, demander à l'administrateur
- d'installer le cube pdfexport_.
-
-
-2010-03-16 -- 3.7.0
-
- * support experimental pour l'annulation ("undo") de la
- suppression. Si, après une suppression d'une ou plusieurs
- entités, on ne vous propose pas d'annuler l'opération, demander à
- l'administrateur d'activé la fonctionnalité
-
-
-2010-02-10 -- 3.6.0
-
- * nouvelle widget (de démonstration :) pour éditer le chemin des
- signets. Celui-ci, une url relative finalement, est décomposée de
- chemin et paramètres que vous pouvez éditer individuellement et
- surtout lisiblement car la gestion de l'échappement de l'url est
- géré de manière transparente
-
- * beaucoup de refactoring, mais vous ne devriez rien remarquer :)
-
-2009-09-17 -- 3.5.0
-
- * workflow sélectionnable: les utilisateurs autorisés peuvent
- changer le workflow à utilister pour les entités le supportant
-
-
-2009-08-07 -- 3.4.0
-
- * support de SPARQL_. Cliquez ici_ pour le tester.
-
- * et encore un pas vers le web sémantique : un nouveau type d'entité
- `ExternalUri` et la relation associée `same_as`. Voir
- http://www.w3.org/TR/owl-ref/#sameAs-def pour plus d'information, ainsi
- que le schema_ de cette instance pour voir à quels types d'entités cela
- s'applique.
-
- * nouveau liens "voir les états possibles" et "voir l'historique" dans le sous-menu
- workflow de la boite actions
-
- * vous pouvez dorénavant éditer les commentaires des passages de transition
- depuis l'historique, pour peu que vous ayez les droits nécessaire bien sûr
-
- * la date de modification d'une entité est mise à jour lorsque son état est changé
-
-
-2009-02-26 -- 3.1.0
-
- * le schéma peut être exporté en OWL_
-
- * nouvelle interface ajax pour la configuration du site et les `préférences utilisateurs`_
-
-
-
--- a/web/webconfig.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/webconfig.py Fri Jan 25 14:33:40 2013 +0100
@@ -84,6 +84,13 @@
uiprops = {'FCKEDITOR_PATH': ''}
options = merge_options(CubicWebConfiguration.options + (
+ ('repository-uri',
+ {'type' : 'string',
+ 'default': 'inmemory://',
+ 'help': 'see `cubicweb.dbapi.connect` documentation for possible value',
+ 'group': 'web', 'level': 2,
+ }),
+
('anonymous-user',
{'type' : 'string',
'default': None,
@@ -238,10 +245,6 @@
continue
yield key, pdef
- # method used to connect to the repository: 'inmemory' / 'pyro'
- # Pyro repository by default
- repo_method = 'pyro'
-
# don't use @cached: we want to be able to disable it while this must still
# be cached
def repository(self, vreg=None):
@@ -250,7 +253,7 @@
return self.__repo
except AttributeError:
from cubicweb.dbapi import get_repository
- repo = get_repository(self.repo_method, vreg=vreg, config=self)
+ repo = get_repository(config=self, vreg=vreg)
self.__repo = repo
return repo
--- a/web/webctl.py Thu Jan 24 16:13:40 2013 +0100
+++ b/web/webctl.py Fri Jan 25 14:33:40 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -46,7 +46,7 @@
if not automatic:
print '\n' + underline_title('Generic web configuration')
config = self.config
- if config.repo_method == 'pyro' or config.pyro_enabled():
+ if config['repository-uri'].startswith('pyro://') or config.pyro_enabled():
print '\n' + underline_title('Pyro configuration')
config.input_config('pyro', inputlevel)
config.input_config('web', inputlevel)
--- a/wsgi/handler.py Thu Jan 24 16:13:40 2013 +0100
+++ b/wsgi/handler.py Fri Jan 25 14:33:40 2013 +0100
@@ -106,8 +106,8 @@
def __init__(self, config, vreg=None):
self.appli = CubicWebPublisher(config, vreg=vreg)
self.config = config
- self.base_url = config['base-url']
- self.https_url = config['https-url']
+ self.base_url = self.config['base-url']
+ self.https_url = self.config['https-url']
self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter')
def _render(self, req):
--- a/zmqclient.py Thu Jan 24 16:13:40 2013 +0100
+++ b/zmqclient.py Fri Jan 25 14:33:40 2013 +0100
@@ -23,6 +23,7 @@
from functools import partial
import zmq
+from cubicweb.server.cwzmq import cwproto_to_zmqaddr
# XXX hack to overpass old zmq limitation that force to have
# only one context per python process
@@ -33,9 +34,9 @@
class ZMQRepositoryClient(object):
"""
- This class delegate the overall repository stuff to a remote source.
+ This class delegates the overall repository stuff to a remote source.
- So calling a method of this repository will results on calling the
+ So calling a method of this repository will result on calling the
corresponding method of the remote source repository.
Any raised exception on the remote source is propagated locally.
@@ -44,8 +45,13 @@
"""
def __init__(self, zmq_address):
+ """A zmq address provided here will be like
+ `zmqpickle-tcp://127.0.0.1:42000`. W
+
+ We chop the prefix to get a real zmq address.
+ """
self.socket = ctx.socket(zmq.REQ)
- self.socket.connect(zmq_address)
+ self.socket.connect(cwproto_to_zmqaddr(zmq_address))
def __zmqcall__(self, name, *args, **kwargs):
self.socket.send_pyobj([name, args, kwargs])