# HG changeset patch
# User Julien Cristau
# Date 1386601990 -3600
# Node ID b1e933b0e8509c29055fa7c9ff0d8fd56faac7d9
# Parent 2dae5bf5ea68e8d543762bdf718f249db613fb3c# Parent cf27006ce8132b7db8510b9d909c281b64001af6
merge 3.17.11
diff -r cf27006ce813 -r b1e933b0e850 __init__.py
--- a/__init__.py Fri Dec 06 17:20:59 2013 +0100
+++ b/__init__.py Mon Dec 09 16:13:10 2013 +0100
@@ -22,6 +22,8 @@
# ignore the pygments UserWarnings
import warnings
+import cPickle
+import zlib
warnings.filterwarnings('ignore', category=UserWarning,
message='.*was already imported',
module='.*pygments')
@@ -120,6 +122,26 @@
binary.seek(0)
return binary
+ def __eq__(self, other):
+ if not isinstance(other, Binary):
+ return False
+ return self.getvalue(), other.getvalue()
+
+
+ # Binary helpers to store/fetch python objects
+
+ @classmethod
+ def zpickle(cls, obj):
+ """ return a Binary containing a gzipped pickle of obj """
+ retval = cls()
+ retval.write(zlib.compress(cPickle.dumps(obj, protocol=2)))
+ return retval
+
+ def unzpickle(self):
+ """ decompress and loads the stream before returning it """
+ return cPickle.loads(zlib.decompress(self.getvalue()))
+
+
def str_or_binary(value):
if isinstance(value, Binary):
return value
@@ -127,7 +149,6 @@
BASE_CONVERTERS['Password'] = str_or_binary
-
# use this dictionary to rename entity types while keeping bw compat
ETYPE_NAME_MAP = {}
diff -r cf27006ce813 -r b1e933b0e850 __pkginfo__.py
--- a/__pkginfo__.py Fri Dec 06 17:20:59 2013 +0100
+++ b/__pkginfo__.py Mon Dec 09 16:13:10 2013 +0100
@@ -40,10 +40,10 @@
]
__depends__ = {
- 'logilab-common': '>= 0.59.0',
+ 'logilab-common': '>= 0.60.0',
'logilab-mtconverter': '>= 0.8.0',
'rql': '>= 0.31.2',
- 'yams': '>= 0.37.0',
+ 'yams': '>= 0.39.0',
#gettext # for xgettext, msgcat, etc...
# web dependancies
'simplejson': '>= 2.0.9',
diff -r cf27006ce813 -r b1e933b0e850 cubicweb.spec
--- a/cubicweb.spec Fri Dec 06 17:20:59 2013 +0100
+++ b/cubicweb.spec Mon Dec 09 16:13:10 2013 +0100
@@ -23,7 +23,7 @@
Requires: %{python}-logilab-common >= 0.59.0
Requires: %{python}-logilab-mtconverter >= 0.8.0
Requires: %{python}-rql >= 0.31.2
-Requires: %{python}-yams >= 0.37.0
+Requires: %{python}-yams >= 0.39.0
Requires: %{python}-logilab-database >= 1.10.0
Requires: %{python}-passlib
Requires: %{python}-lxml
diff -r cf27006ce813 -r b1e933b0e850 cwconfig.py
--- a/cwconfig.py Fri Dec 06 17:20:59 2013 +0100
+++ b/cwconfig.py Mon Dec 09 16:13:10 2013 +0100
@@ -53,8 +53,7 @@
If you are not administrator of you machine or if you need to play with some
specific version of |cubicweb| you can use `virtualenv`_ a tool to create
-isolated Python environments. Since version 3.9 |cubicweb| is **`virtualenv`
-friendly** and won't write any file outside the virtualenv directory.
+isolated Python environments.
- instances are stored in :file:`/etc/cubicweb.d`
- temporary files (such as pid file) in :file:`/var/run/cubicweb`
@@ -206,7 +205,7 @@
"""return a list of installed configurations in a directory
according to \*-ctl files
"""
- return [name for name in ('repository', 'twisted', 'all-in-one')
+ return [name for name in ('repository', 'all-in-one')
if exists(join(directory, '%s.conf' % name))]
def guess_configuration(directory):
@@ -328,7 +327,7 @@
# the format below can be useful to debug multi thread issues:
# log_format = '%(asctime)s - [%(threadName)s] (%(name)s) %(levelname)s: %(message)s'
# nor remove appobjects based on unused interface [???]
- cleanup_interface_sobjects = True
+ cleanup_unused_appobjects = True
if (CWDEV and _forced_mode != 'system'):
mode = 'user'
@@ -499,21 +498,11 @@
try:
gendeps = getattr(pkginfo, key.replace('_cubes', ''))
except AttributeError:
- # bw compat
- if hasattr(pkginfo, oldkey):
- warn('[3.8] cube %s: %s is deprecated, use %s dict'
- % (cube, oldkey, key), DeprecationWarning)
- deps = getattr(pkginfo, oldkey)
- else:
- deps = {}
+ deps = {}
else:
deps = dict( (x[len('cubicweb-'):], v)
for x, v in gendeps.iteritems()
if x.startswith('cubicweb-'))
- if not isinstance(deps, dict):
- deps = dict((key, None) for key in deps)
- warn('[3.8] cube %s should define %s as a dict' % (cube, key),
- DeprecationWarning)
for depcube in deps:
try:
newname = CW_MIGRATION_MAP[depcube]
@@ -940,10 +929,9 @@
' "cubicweb-ctl list")' % appid)
return home
- MODES = ('common', 'repository', 'Any', 'web')
+ MODES = ('common', 'repository', 'Any')
MCOMPAT = {'all-in-one': MODES,
- 'repository': ('common', 'repository', 'Any'),
- 'twisted' : ('common', 'web'),}
+ 'repository': ('common', 'repository', 'Any')}
@classmethod
def accept_mode(cls, mode):
#assert mode in cls.MODES, mode
diff -r cf27006ce813 -r b1e933b0e850 cwctl.py
--- a/cwctl.py Fri Dec 06 17:20:59 2013 +0100
+++ b/cwctl.py Mon Dec 09 16:13:10 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.
@@ -266,12 +266,7 @@
if tinfo:
descr = getattr(tinfo, 'description', '')
if not descr:
- descr = getattr(tinfo, 'short_desc', '')
- if descr:
- warn('[3.8] short_desc is deprecated, update %s'
- ' pkginfo' % cube, DeprecationWarning)
- else:
- descr = tinfo.__doc__
+ descr = tinfo.__doc__
if descr:
print ' '+ ' \n'.join(descr.splitlines())
modes = detect_available_modes(cwcfg.cube_dir(cube))
@@ -357,7 +352,7 @@
}),
('config',
{'short': 'c', 'type' : 'choice', 'metavar': '',
- 'choices': ('all-in-one', 'repository', 'twisted'),
+ 'choices': ('all-in-one', 'repository'),
'default': 'all-in-one',
'help': 'installation type, telling which part of an instance '
'should be installed. You can list available configurations using the'
@@ -1039,9 +1034,56 @@
raise ConfigurationError('unknown configuration key "%s" for mode %s' % (key, appcfg.name))
appcfg.save()
+
+# WSGI #########
+
+def wsgichoices():
+ try:
+ from werkzeug import serving
+ except ImportError:
+ return ('stdlib',)
+ return ('stdlib', 'werkzeug')
+
+class WSGIDebugStartHandler(InstanceCommand):
+ """Start an interactive wsgi server """
+ name = 'wsgi'
+ actionverb = 'started'
+ arguments = ''
+ options = (
+ ('method',
+ {'short': 'm',
+ 'type': 'choice',
+ 'metavar': '',
+ 'default': 'stdlib',
+ 'choices': wsgichoices(),
+ 'help': 'wsgi utility/method'}),
+ ('loglevel',
+ {'short': 'l',
+ 'type' : 'choice',
+ 'metavar': '',
+ 'default': 'debug',
+ 'choices': ('debug', 'info', 'warning', 'error'),
+ 'help': 'debug if -D is set, error otherwise',
+ }),
+ )
+
+ def wsgi_instance(self, appid):
+ config = cwcfg.config_for(appid, debugmode=1)
+ init_cmdline_log_threshold(config, self['loglevel'])
+ assert config.name == 'all-in-one'
+ meth = self['method']
+ if meth == 'stdlib':
+ from cubicweb.wsgi import server
+ else:
+ from cubicweb.wsgi import wz as server
+ return server.run(config)
+
+
+
for cmdcls in (ListCommand,
CreateInstanceCommand, DeleteInstanceCommand,
StartInstanceCommand, StopInstanceCommand, RestartInstanceCommand,
+ WSGIDebugStartHandler,
ReloadConfigurationCommand, StatusCommand,
UpgradeInstanceCommand,
ListVersionsInstanceCommand,
@@ -1052,6 +1094,8 @@
):
CWCTL.register(cmdcls)
+
+
def run(args):
"""command line tool"""
import os
diff -r cf27006ce813 -r b1e933b0e850 cwvreg.py
--- a/cwvreg.py Fri Dec 06 17:20:59 2013 +0100
+++ b/cwvreg.py Mon Dec 09 16:13:10 2013 +0100
@@ -211,8 +211,7 @@
from cubicweb import (CW_SOFTWARE_ROOT, ETYPE_NAME_MAP, CW_EVENT_MANAGER,
onevent, Binary, UnknownProperty, UnknownEid)
-from cubicweb.predicates import (implements, appobject_selectable,
- _reset_is_instance_cache)
+from cubicweb.predicates import appobject_selectable, _reset_is_instance_cache
@onevent('before-registry-reload')
@@ -230,15 +229,6 @@
sys.modules.pop('cubicweb.web.uicfg', None)
sys.modules.pop('cubicweb.web.uihelper', None)
-def use_interfaces(obj):
- """return interfaces required by the given object by searching for
- `implements` predicate
- """
- impl = obj.__select__.search_selector(implements)
- if impl:
- return sorted(impl.expected_ifaces)
- return ()
-
def require_appobject(obj):
"""return appobjects required by the given object by searching for
`appobject_selectable` predicate
@@ -568,7 +558,6 @@
def reset(self):
CW_EVENT_MANAGER.emit('before-registry-reset', self)
super(CWRegistryStore, self).reset()
- self._needs_iface = {}
self._needs_appobject = {}
# two special registries, propertydefs which care all the property
# definitions, and propertyvals which contains values for those
@@ -641,20 +630,6 @@
for obj in objects:
obj.schema = schema
- @deprecated('[3.9] use .register instead')
- def register_if_interface_found(self, obj, ifaces, **kwargs):
- """register `obj` but remove it if no entity class implements one of
- the given `ifaces` interfaces at the end of the registration process.
-
- Extra keyword arguments are given to the
- :meth:`~cubicweb.cwvreg.CWRegistryStore.register` function.
- """
- self.register(obj, **kwargs)
- if not isinstance(ifaces, (tuple, list)):
- self._needs_iface[obj] = (ifaces,)
- else:
- self._needs_iface[obj] = ifaces
-
def register(self, obj, *args, **kwargs):
"""register `obj` application object into `registryname` or
`obj.__registry__` if not specified, with identifier `oid` or
@@ -665,15 +640,6 @@
"""
obj = related_appobject(obj)
super(CWRegistryStore, self).register(obj, *args, **kwargs)
- # XXX bw compat
- ifaces = use_interfaces(obj)
- if ifaces:
- if not obj.__name__.endswith('Adapter') and \
- any(iface for iface in ifaces if not isinstance(iface, basestring)):
- warn('[3.9] %s: interfaces in implements selector are '
- 'deprecated in favor of adapters / adaptable '
- 'selector' % obj.__name__, DeprecationWarning)
- self._needs_iface[obj] = ifaces
depends_on = require_appobject(obj)
if depends_on is not None:
self._needs_appobject[obj] = depends_on
@@ -687,41 +653,14 @@
def initialization_completed(self):
"""cw specific code once vreg initialization is completed:
- * remove objects requiring a missing interface, unless
- config.cleanup_interface_sobjects is false
+ * remove objects requiring a missing appobject, unless
+ config.cleanup_unused_appobjects is false
* init rtags
"""
# we may want to keep interface dependent objects (e.g.for i18n
# catalog generation)
- if self.config.cleanup_interface_sobjects:
- # XXX deprecated with cw 3.9: remove appobjects that don't support
- # any available interface
- implemented_interfaces = set()
- if 'Any' in self.get('etypes', ()):
- for etype in self.schema.entities():
- if etype.final:
- continue
- cls = self['etypes'].etype_class(etype)
- if cls.__implements__:
- warn('[3.9] %s: using __implements__/interfaces are '
- 'deprecated in favor of adapters' % cls.__name__,
- DeprecationWarning)
- for iface in cls.__implements__:
- implemented_interfaces.update(iface.__mro__)
- implemented_interfaces.update(cls.__mro__)
- for obj, ifaces in self._needs_iface.items():
- ifaces = frozenset(isinstance(iface, basestring)
- and iface in self.schema
- and self['etypes'].etype_class(iface)
- 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)', reg.objid(obj), ifaces)
- self.unregister(obj)
- # since 3.9: remove appobjects which depending on other, unexistant
- # appobjects
+ if self.config.cleanup_unused_appobjects:
+ # remove appobjects which depend on other, unexistant appobjects
for obj, (regname, regids) in self._needs_appobject.items():
try:
registry = self[regname]
@@ -740,8 +679,8 @@
if 'uicfg' in self: # 'uicfg' is not loaded in a pure repository mode
for rtags in self['uicfg'].itervalues():
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)
+ # don't check rtags if we don't want to cleanup_unused_appobjects
+ rtag.init(self.schema, check=self.config.cleanup_unused_appobjects)
# rql parsing utilities ####################################################
diff -r cf27006ce813 -r b1e933b0e850 dbapi.py
--- a/dbapi.py Fri Dec 06 17:20:59 2013 +0100
+++ b/dbapi.py Mon Dec 09 16:13:10 2013 +0100
@@ -422,25 +422,6 @@
req.set_session(self.session, user)
return req
- @deprecated('[3.8] use direct access to req.session.data dictionary')
- def session_data(self):
- """return a dictionary containing session data"""
- return self.session.data
-
- @deprecated('[3.8] use direct access to req.session.data dictionary')
- def get_session_data(self, key, default=None, pop=False):
- if pop:
- return self.session.data.pop(key, default)
- return self.session.data.get(key, default)
-
- @deprecated('[3.8] use direct access to req.session.data dictionary')
- def set_session_data(self, key, value):
- self.session.data[key] = value
-
- @deprecated('[3.8] use direct access to req.session.data dictionary')
- def del_session_data(self, key):
- self.session.data.pop(key, None)
-
# these are overridden by set_log_methods below
# only defining here to prevent pylint from complaining
info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
@@ -486,7 +467,7 @@
def _txid(self):
return self.connection._txid(self)
- def execute(self, rql, args=None, eid_key=None, build_descr=True):
+ def execute(self, rql, args=None, build_descr=True):
"""execute a rql query, return resulting rows and their description in
a :class:`~cubicweb.rset.ResultSet` object
@@ -517,10 +498,6 @@
execute('Any X WHERE X eid %(x)s', {'x': 123})
"""
- if eid_key is not None:
- warn('[3.8] eid_key is deprecated, you can safely remove this argument',
- DeprecationWarning, stacklevel=2)
- # XXX use named argument for build_descr in case repo is < 3.8
rset = self._repo.execute(self._sessid, rql, args,
build_descr=build_descr, **self._txid())
rset.req = self.req
diff -r cf27006ce813 -r b1e933b0e850 debian/control
--- a/debian/control Fri Dec 06 17:20:59 2013 +0100
+++ b/debian/control Mon Dec 09 16:13:10 2013 +0100
@@ -15,7 +15,7 @@
python-unittest2 | python (>= 2.7),
python-logilab-mtconverter,
python-rql,
- python-yams (>= 0.37),
+ python-yams (>= 0.39),
python-lxml,
Standards-Version: 3.9.1
Homepage: http://www.cubicweb.org
@@ -131,6 +131,8 @@
python-fyzz,
python-imaging,
python-rdflib
+Breaks:
+ cubicweb-inlinedit (<< 1.1.1),
Description: web interface library for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
@@ -150,7 +152,7 @@
gettext,
python-logilab-mtconverter (>= 0.8.0),
python-logilab-common (>= 0.59.0),
- python-yams (>= 0.37.0),
+ python-yams (>= 0.39.0),
python-rql (>= 0.31.2),
python-lxml
Recommends:
@@ -158,6 +160,10 @@
python-crypto
Conflicts: cubicweb-core
Replaces: cubicweb-core
+Breaks:
+ cubicweb-comment (<< 1.9.1),
+ cubicweb-person (<< 1.8.0),
+ cubicweb-geocoding (<< 0.2.0),
Description: common library for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
diff -r cf27006ce813 -r b1e933b0e850 devtools/__init__.py
--- a/devtools/__init__.py Fri Dec 06 17:20:59 2013 +0100
+++ b/devtools/__init__.py Mon Dec 09 16:13:10 2013 +0100
@@ -38,7 +38,7 @@
from cubicweb import ExecutionError, BadConnectionId
from cubicweb import schema, cwconfig
from cubicweb.server.serverconfig import ServerConfiguration
-from cubicweb.etwist.twconfig import TwistedConfiguration
+from cubicweb.etwist.twconfig import WebConfigurationBase
cwconfig.CubicWebConfiguration.cls_adjust_sys_path()
@@ -214,12 +214,12 @@
return BASE_URL
-class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration):
+class BaseApptestConfiguration(TestServerConfiguration, WebConfigurationBase):
name = 'all-in-one' # so it search for all-in-one.conf, not repository.conf
options = cwconfig.merge_options(TestServerConfiguration.options
- + TwistedConfiguration.options)
- cubicweb_appobject_path = TestServerConfiguration.cubicweb_appobject_path | TwistedConfiguration.cubicweb_appobject_path
- cube_appobject_path = TestServerConfiguration.cube_appobject_path | TwistedConfiguration.cube_appobject_path
+ + WebConfigurationBase.options)
+ cubicweb_appobject_path = TestServerConfiguration.cubicweb_appobject_path | WebConfigurationBase.cubicweb_appobject_path
+ cube_appobject_path = TestServerConfiguration.cube_appobject_path | WebConfigurationBase.cube_appobject_path
def available_languages(self, *args):
return self.cw_languages()
diff -r cf27006ce813 -r b1e933b0e850 devtools/devctl.py
--- a/devtools/devctl.py Fri Dec 06 17:20:59 2013 +0100
+++ b/devtools/devctl.py Mon Dec 09 16:13:10 2013 +0100
@@ -46,7 +46,7 @@
a cube or for cubicweb (without a home)
"""
creating = True
- cleanup_interface_sobjects = False
+ cleanup_unused_appobjects = False
cubicweb_appobject_path = (ServerConfiguration.cubicweb_appobject_path
| WebConfiguration.cubicweb_appobject_path)
diff -r cf27006ce813 -r b1e933b0e850 devtools/testlib.py
--- a/devtools/testlib.py Fri Dec 06 17:20:59 2013 +0100
+++ b/devtools/testlib.py Mon Dec 09 16:13:10 2013 +0100
@@ -419,13 +419,10 @@
return self.cnx.cursor(req or self.request())
@nocoverage
- def execute(self, rql, args=None, eidkey=None, req=None):
+ def execute(self, rql, args=None, req=None):
"""executes , builds a resultset, and returns a couple (rset, req)
where req is a FakeRequest
"""
- if eidkey is not None:
- warn('[3.8] eidkey is deprecated, you can safely remove this argument',
- DeprecationWarning, stacklevel=2)
req = req or self.request(rql=rql)
return req.execute(unicode(rql), args)
@@ -447,10 +444,7 @@
# server side db api #######################################################
- def sexecute(self, rql, args=None, eid_key=None):
- if eid_key is not None:
- warn('[3.8] eid_key is deprecated, you can safely remove this argument',
- DeprecationWarning, stacklevel=2)
+ def sexecute(self, rql, args=None):
self.session.set_cnxset()
return self.session.execute(rql, args)
@@ -986,15 +980,6 @@
self.assertEqual(len(MAILBOX), nb_msgs)
return messages
- # deprecated ###############################################################
-
- @deprecated('[3.8] use self.execute(...).get_entity(0, 0)')
- def entity(self, rql, args=None, eidkey=None, req=None):
- if eidkey is not None:
- warn('[3.8] eidkey is deprecated, you can safely remove this argument',
- DeprecationWarning, stacklevel=2)
- return self.execute(rql, args, req=req).get_entity(0, 0)
-
# auto-populating test classes and utilities ###################################
diff -r cf27006ce813 -r b1e933b0e850 doc/3.18.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/3.18.rst Mon Dec 09 16:13:10 2013 +0100
@@ -0,0 +1,41 @@
+What's new in CubicWeb 3.18?
+============================
+
+New functionalities
+--------------------
+
+* add a security debugging tool
+ (see `#2920304 `_)
+
+
+API changes
+-----------
+
+* not really an API change, but the entity permission checks are now
+ systematically deferred to an operation, instead of a) trying in a
+ hook and b) if it failed, retrying later in an operation
+
+* The default value storage for attributes is no longer String, but
+ Bytes. This opens the road to storing arbitrary python objects, e.g.
+ numpy arrays, and fixes a bug where default values whose truth value
+ was False were not properly migrated.
+
+Deprecation
+---------------------
+
+* the old multi-source system
+
+
+Deprecated Code Drops
+----------------------
+
+* ``ldapuser`` have been dropped; use ``ldapfeed`` now
+ (see `#2936496 `_)
+
+* action ``GotRhythm`` was removed, make sure you do not
+ import it in your cubes (even to unregister it)
+ (see `#3093362 `_)
+
+* all 3.8 backward compat is gone
+
+* all 3.9 backward compat (including the javascript side) is gone
diff -r cf27006ce813 -r b1e933b0e850 doc/4.0.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/4.0.rst Mon Dec 09 16:13:10 2013 +0100
@@ -0,0 +1,8 @@
+What's new in CubicWeb 4.0?
+============================
+
+Deprecated Code Drops
+----------------------
+
+* The ldapuser source has been dropped. ldapfeed is the only official source
+ remaining for ldap.
diff -r cf27006ce813 -r b1e933b0e850 doc/book/en/admin/config.rst
--- a/doc/book/en/admin/config.rst Fri Dec 06 17:20:59 2013 +0100
+++ b/doc/book/en/admin/config.rst Mon Dec 09 16:13:10 2013 +0100
@@ -57,19 +57,28 @@
PostgreSQL
~~~~~~~~~~
-For installation, please refer to the `PostgreSQL project online documentation`_.
-
-.. _`PostgreSQL project online documentation`: http://www.postgresql.org/
+Many Linux distributions ship with the appropriate PostgreSQL packages.
+Basically, you need to install the following packages:
-You need to install the three following packages: `postgresql-8.X`,
-`postgresql-client-8.X`, and `postgresql-plpython-8.X`. If you run postgres
-version prior to 8.3, you'll also need the `postgresql-contrib-8.X` package for
-full-text search extension.
+* `postgresql` and `postgresql-client`, which will pull the respective
+ versioned packages (e.g. `postgresql-9.1` and `postgresql-client-9.1`) and,
+ optionally,
+* a `postgresql-plpython-X.Y` package with a version corresponding to that of
+ the aforementioned packages (e.g. `postgresql-plpython-9.1`).
+
+If you run postgres version prior to 8.3, you'll also need the
+`postgresql-contrib-8.X` package for full-text search extension.
If you run postgres on another host than the |cubicweb| repository, you should
install the `postgresql-client` package on the |cubicweb| host, and others on the
database host.
+For extra details concerning installation, please refer to the `PostgreSQL
+project online documentation`_.
+
+.. _`PostgreSQL project online documentation`: http://www.postgresql.org/docs
+
+
Database cluster
++++++++++++++++
diff -r cf27006ce813 -r b1e933b0e850 doc/book/en/admin/setup.rst
--- a/doc/book/en/admin/setup.rst Fri Dec 06 17:20:59 2013 +0100
+++ b/doc/book/en/admin/setup.rst Mon Dec 09 16:13:10 2013 +0100
@@ -121,10 +121,10 @@
`Virtualenv` install
--------------------
-Since version 3.9, |cubicweb| can be safely installed, used and contained inside
-a `virtualenv`_. You can use either :ref:`pip ` or
-:ref:`easy_install ` to install |cubicweb| inside an
-activated virtual environment.
+|cubicweb| can be safely installed, used and contained inside a
+`virtualenv`_. You can use either :ref:`pip ` or
+:ref:`easy_install ` to install |cubicweb|
+inside an activated virtual environment.
.. _PipInstallation:
diff -r cf27006ce813 -r b1e933b0e850 doc/book/en/devrepo/datamodel/definition.rst
--- a/doc/book/en/devrepo/datamodel/definition.rst Fri Dec 06 17:20:59 2013 +0100
+++ b/doc/book/en/devrepo/datamodel/definition.rst Mon Dec 09 16:13:10 2013 +0100
@@ -226,13 +226,13 @@
* `SizeConstraint`: allows to specify a minimum and/or maximum size on
string (generic case of `maxsize`)
-* `BoundConstraint`: allows to specify a minimum and/or maximum value
+* `BoundaryConstraint`: allows to specify a minimum and/or maximum value
on numeric types and date
.. sourcecode:: python
- from yams.constraints import BoundConstraint, TODAY
- BoundConstraint('<=', TODAY())
+ from yams.constraints import BoundaryConstraint, TODAY
+ BoundaryConstraint('<=', TODAY())
* `IntervalBoundConstraint`: allows to specify an interval with
included values
@@ -476,13 +476,8 @@
Here are the current rules:
-1. permission to add/update entity and its attributes are checked:
-
- - on commit if the entity has been added
-
- - in an 'after_update_entity' hook if the entity has been updated. If it fails
- at this time, it will be retried on commit (hence you get the permission if
- you have it just after the modification or *at* commit time)
+1. permission to add/update entity and its attributes are checked on
+ commit
2. permission to delete an entity is checked in 'before_delete_entity' hook
diff -r cf27006ce813 -r b1e933b0e850 doc/book/en/devrepo/entityclasses/adapters.rst
--- a/doc/book/en/devrepo/entityclasses/adapters.rst Fri Dec 06 17:20:59 2013 +0100
+++ b/doc/book/en/devrepo/entityclasses/adapters.rst Mon Dec 09 16:13:10 2013 +0100
@@ -10,13 +10,7 @@
.. _`interfaces`: http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
.. _`adapter`: http://en.wikipedia.org/wiki/Adapter_pattern
-In |cubicweb| adapters provide logical functionalities to entity types. They
-are introduced in version `3.9`. Before that one had to implement Interfaces in
-entity classes to achieve a similar goal. However, the problem with this
-approach is that is clutters the entity class's namespace, exposing name
-collision risks with schema attributes/relations or even methods names
-(different interfaces may define the same method with not necessarily the same
-behaviour expected).
+In |cubicweb| adapters provide logical functionalities to entity types.
Definition of an adapter is quite trivial. An excerpt from cubicweb
itself (found in :mod:`cubicweb.entities.adapters`):
diff -r cf27006ce813 -r b1e933b0e850 doc/book/en/devrepo/migration.rst
--- a/doc/book/en/devrepo/migration.rst Fri Dec 06 17:20:59 2013 +0100
+++ b/doc/book/en/devrepo/migration.rst Mon Dec 09 16:13:10 2013 +0100
@@ -46,7 +46,7 @@
Again in the directory `migration`, the file `depends.map` allows to indicate
that for the migration to a particular model version, you always have to first
migrate to a particular *CubicWeb* version. This file can contain comments (lines
-starting by `#`) and a dependancy is listed as follows: ::
+starting with `#`) and a dependency is listed as follows: ::
:
@@ -170,9 +170,9 @@
* `rql(rql, kwargs=None, cachekey=None, ask_confirm=True)`, executes an arbitrary RQL
query, either to interrogate or update. A result set object is returned.
-* `add_entity(etype, *args, **kwargs)`, adds a nes entity type of the given
- type. The attribute and relation values are specified using the named and
- positionned parameters.
+* `add_entity(etype, *args, **kwargs)`, adds a new entity of the given type.
+ The attribute and relation values are specified as named positional
+ arguments.
Workflow creation
-----------------
diff -r cf27006ce813 -r b1e933b0e850 doc/book/en/devrepo/testing.rst
--- a/doc/book/en/devrepo/testing.rst Fri Dec 06 17:20:59 2013 +0100
+++ b/doc/book/en/devrepo/testing.rst Mon Dec 09 16:13:10 2013 +0100
@@ -18,12 +18,7 @@
convenience methods to help test all of this.
In the realm of views, automatic tests check that views are valid
-XHTML. See :ref:`automatic_views_tests` for details. Since 3.9, bases
-for web functional testing using `windmill
-`_ are set. See test cases in
-cubicweb/web/test/windmill and python wrapper in
-cubicweb/web/test_windmill/ if you want to use this in your own cube.
-
+XHTML. See :ref:`automatic_views_tests` for details.
Most unit tests need a live database to work against. This is achieved
by CubicWeb using automatically sqlite (bundled with Python, see
diff -r cf27006ce813 -r b1e933b0e850 doc/book/en/tutorials/advanced/part04_ui-base.rst
--- a/doc/book/en/tutorials/advanced/part04_ui-base.rst Fri Dec 06 17:20:59 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst Mon Dec 09 16:13:10 2013 +0100
@@ -194,8 +194,6 @@
.. Note::
- * Adapters have been introduced in CubicWeb 3.9 / cubicweb-folder 1.8.
-
* As seen earlier, we want to **replace** the folder's `ITree` adapter by our
implementation, hence the custom `registration_callback` method.
@@ -241,12 +239,6 @@
ascendant/descendant ordering and a strict comparison with current file's name
(the "X" variable representing the current file).
-.. Note::
-
- * Former `implements` selector should be replaced by one of `is_instance` /
- `adaptable` selector with CubicWeb >= 3.9. In our case, `is_instance` to
- tell our adapter is able to adapt `File` entities.
-
Notice that this query supposes we wont have two files of the same name in the
same folder, else things may go wrong. Fixing this is out of the scope of this
blog. And as I would like to have at some point a smarter, context sensitive
@@ -358,7 +350,7 @@
You'll have to answer some questions, as we've seen in `an earlier post`_.
Now that everything is tested, I can transfer the new code to the production
-server, `apt-get upgrade` cubicweb 3.9 and its dependencies, and eventually
+server, `apt-get upgrade` cubicweb and its dependencies, and eventually
upgrade the production instance.
diff -r cf27006ce813 -r b1e933b0e850 doc/book/en/tutorials/advanced/part05_ui-advanced.rst
--- a/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Fri Dec 06 17:20:59 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Mon Dec 09 16:13:10 2013 +0100
@@ -1,8 +1,6 @@
Building my photos web site with |cubicweb| part V: let's make it even more user friendly
=========================================================================================
-We'll now see how to benefit from features introduced in 3.9 and 3.10 releases of CubicWeb
-
.. _uiprops:
Step 1: tired of the default look?
@@ -29,9 +27,9 @@
LOGO = data('logo.jpg')
-The uiprops machinery has been introduced in `CubicWeb 3.9`_. It is used to define
-some static file resources, such as the logo, default Javascript / CSS files, as
-well as CSS properties (we'll see that later).
+The uiprops machinery is used to define some static file resources,
+such as the logo, default Javascript / CSS files, as well as CSS
+properties (we'll see that later).
.. Note::
This file is imported specifically by |cubicweb|, with a predefined name space,
@@ -373,5 +371,4 @@
.. _`CubicWeb 3.10`: http://www.cubicweb.org/blogentry/1330518
-.. _`CubicWeb 3.9`: http://www.cubicweb.org/blogentry/1179899
.. _`here`: http://webdesign.about.com/od/css3/f/blfaqbgsize.htm
diff -r cf27006ce813 -r b1e933b0e850 doc/tools/pyjsrest.py
--- a/doc/tools/pyjsrest.py Fri Dec 06 17:20:59 2013 +0100
+++ b/doc/tools/pyjsrest.py Mon Dec 09 16:13:10 2013 +0100
@@ -136,7 +136,6 @@
'cubicweb.preferences',
'cubicweb.edition',
'cubicweb.reledit',
- 'cubicweb.rhythm',
'cubicweb.timeline-ext',
]
diff -r cf27006ce813 -r b1e933b0e850 entities/adapters.py
--- a/entities/adapters.py Fri Dec 06 17:20:59 2013 +0100
+++ b/entities/adapters.py Mon Dec 09 16:13:10 2013 +0100
@@ -28,9 +28,7 @@
from logilab.common.decorators import cached
from cubicweb import ValidationError, view
-from cubicweb.predicates import (implements, is_instance, relation_possible,
- match_exception)
-from cubicweb.interfaces import IDownloadable, ITree
+from cubicweb.predicates import is_instance, relation_possible, match_exception
class IEmailableAdapter(view.EntityAdapter):
@@ -67,11 +65,9 @@
class INotifiableAdapter(view.EntityAdapter):
- __needs_bw_compat__ = True
__regid__ = 'INotifiable'
__select__ = is_instance('Any')
- @view.implements_adapter_compat('INotifiableAdapter')
def notification_references(self, view):
"""used to control References field of email send on notification
for this entity. `view` is the notification view.
@@ -167,27 +163,25 @@
class IDownloadableAdapter(view.EntityAdapter):
"""interface for downloadable entities"""
- __needs_bw_compat__ = True
__regid__ = 'IDownloadable'
- __select__ = implements(IDownloadable, warn=False) # XXX for bw compat, else should be abstract
+ __abstract__ = True
- @view.implements_adapter_compat('IDownloadable')
def download_url(self, **kwargs): # XXX not really part of this interface
"""return an url to download entity's content"""
raise NotImplementedError
- @view.implements_adapter_compat('IDownloadable')
+
def download_content_type(self):
"""return MIME type of the downloadable content"""
raise NotImplementedError
- @view.implements_adapter_compat('IDownloadable')
+
def download_encoding(self):
"""return encoding of the downloadable content"""
raise NotImplementedError
- @view.implements_adapter_compat('IDownloadable')
+
def download_file_name(self):
"""return file name of the downloadable content"""
raise NotImplementedError
- @view.implements_adapter_compat('IDownloadable')
+
def download_data(self):
"""return actual data of the downloadable content"""
raise NotImplementedError
@@ -219,27 +213,16 @@
.. automethod: children_rql
.. automethod: path
"""
- __needs_bw_compat__ = True
__regid__ = 'ITree'
- __select__ = implements(ITree, warn=False) # XXX for bw compat, else should be abstract
+ __abstract__ = True
child_role = 'subject'
parent_role = 'object'
- @property
- def tree_relation(self):
- warn('[3.9] tree_attribute is deprecated, define tree_relation on a custom '
- 'ITree for %s instead' % (self.entity.__class__),
- DeprecationWarning)
- return self.entity.tree_attribute
-
- # XXX should be removed from the public interface
- @view.implements_adapter_compat('ITree')
def children_rql(self):
"""Returns RQL to get the children of the entity."""
return self.entity.cw_related_rql(self.tree_relation, self.parent_role)
- @view.implements_adapter_compat('ITree')
def different_type_children(self, entities=True):
"""Return children entities of different type as this entity.
@@ -253,7 +236,6 @@
return [e for e in res if e.e_schema != eschema]
return res.filtered_rset(lambda x: x.e_schema != eschema, self.entity.cw_col)
- @view.implements_adapter_compat('ITree')
def same_type_children(self, entities=True):
"""Return children entities of the same type as this entity.
@@ -267,23 +249,19 @@
return [e for e in res if e.e_schema == eschema]
return res.filtered_rset(lambda x: x.e_schema is eschema, self.entity.cw_col)
- @view.implements_adapter_compat('ITree')
def is_leaf(self):
"""Returns True if the entity does not have any children."""
return len(self.children()) == 0
- @view.implements_adapter_compat('ITree')
def is_root(self):
"""Returns true if the entity is root of the tree (e.g. has no parent).
"""
return self.parent() is None
- @view.implements_adapter_compat('ITree')
def root(self):
"""Return the root entity of the tree."""
return self._cw.entity_from_eid(self.path()[0])
- @view.implements_adapter_compat('ITree')
def parent(self):
"""Returns the parent entity if any, else None (e.g. if we are on the
root).
@@ -294,7 +272,6 @@
except (KeyError, IndexError):
return None
- @view.implements_adapter_compat('ITree')
def children(self, entities=True, sametype=False):
"""Return children entities.
@@ -307,7 +284,6 @@
return self.entity.related(self.tree_relation, self.parent_role,
entities=entities)
- @view.implements_adapter_compat('ITree')
def iterparents(self, strict=True):
"""Return an iterator on the parents of the entity."""
def _uptoroot(self):
@@ -322,7 +298,6 @@
return chain([self.entity], _uptoroot(self))
return _uptoroot(self)
- @view.implements_adapter_compat('ITree')
def iterchildren(self, _done=None):
"""Return an iterator over the item's children."""
if _done is None:
@@ -334,7 +309,6 @@
yield child
_done.add(child.eid)
- @view.implements_adapter_compat('ITree')
def prefixiter(self, _done=None):
"""Return an iterator over the item's descendants in a prefixed order."""
if _done is None:
@@ -347,7 +321,6 @@
for entity in child.cw_adapt_to('ITree').prefixiter(_done):
yield entity
- @view.implements_adapter_compat('ITree')
@cached
def path(self):
"""Returns the list of eids from the root object to this object."""
diff -r cf27006ce813 -r b1e933b0e850 entities/test/unittest_base.py
--- a/entities/test/unittest_base.py Fri Dec 06 17:20:59 2013 +0100
+++ b/entities/test/unittest_base.py Mon Dec 09 16:13:10 2013 +0100
@@ -25,7 +25,6 @@
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.interfaces import IMileStone, ICalendarable
from cubicweb.entities import AnyEntity
@@ -134,27 +133,6 @@
self.request().create_entity('CWGroup', name=u'logilab', reverse_in_group=e)
-class InterfaceTC(CubicWebTC):
-
- def test_nonregr_subclasses_and_mixins_interfaces(self):
- from cubicweb.entities.wfobjs import WorkflowableMixIn
- WorkflowableMixIn.__implements__ = (ICalendarable,)
- CWUser = self.vreg['etypes'].etype_class('CWUser')
- class MyUser(CWUser):
- __implements__ = (IMileStone,)
- self.vreg._loadedmods[__name__] = {}
- self.vreg.register(MyUser)
- self.vreg['etypes'].initialization_completed()
- MyUser_ = self.vreg['etypes'].etype_class('CWUser')
- # a copy is done systematically
- self.assertTrue(issubclass(MyUser_, MyUser))
- self.assertTrue(implements(MyUser_, IMileStone))
- self.assertTrue(implements(MyUser_, ICalendarable))
- # original class should not have beed modified, only the copy
- self.assertTrue(implements(MyUser, IMileStone))
- self.assertFalse(implements(MyUser, ICalendarable))
-
-
class SpecializedEntityClassesTC(CubicWebTC):
def select_eclass(self, etype):
diff -r cf27006ce813 -r b1e933b0e850 entities/wfobjs.py
--- a/entities/wfobjs.py Fri Dec 06 17:20:59 2013 +0100
+++ b/entities/wfobjs.py Mon Dec 09 16:13:10 2013 +0100
@@ -32,7 +32,6 @@
from cubicweb.entities import AnyEntity, fetch_config
from cubicweb.view import EntityAdapter
from cubicweb.predicates import relation_possible
-from cubicweb.mixins import MI_REL_TRIGGERS
class WorkflowException(Exception): pass
@@ -379,65 +378,8 @@
return self.by_transition and self.by_transition[0] or None
-class WorkflowableMixIn(object):
- """base mixin providing workflow helper methods for workflowable entities.
- This mixin will be automatically set on class supporting the 'in_state'
- relation (which implies supporting 'wf_info_for' as well)
- """
- @property
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').main_workflow")
- def main_workflow(self):
- return self.cw_adapt_to('IWorkflowable').main_workflow
- @property
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').current_workflow")
- def current_workflow(self):
- return self.cw_adapt_to('IWorkflowable').current_workflow
- @property
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').current_state")
- def current_state(self):
- return self.cw_adapt_to('IWorkflowable').current_state
- @property
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').state")
- def state(self):
- return self.cw_adapt_to('IWorkflowable').state
- @property
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').printable_state")
- def printable_state(self):
- return self.cw_adapt_to('IWorkflowable').printable_state
- @property
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').workflow_history")
- def workflow_history(self):
- return self.cw_adapt_to('IWorkflowable').workflow_history
-
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').cwetype_workflow()")
- def cwetype_workflow(self):
- return self.cw_adapt_to('IWorkflowable').main_workflow()
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').latest_trinfo()")
- def latest_trinfo(self):
- return self.cw_adapt_to('IWorkflowable').latest_trinfo()
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').possible_transitions()")
- def possible_transitions(self, type='normal'):
- return self.cw_adapt_to('IWorkflowable').possible_transitions(type)
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').fire_transition()")
- def fire_transition(self, tr, comment=None, commentformat=None):
- return self.cw_adapt_to('IWorkflowable').fire_transition(tr, comment, commentformat)
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').change_state()")
- def change_state(self, statename, comment=None, commentformat=None, tr=None):
- return self.cw_adapt_to('IWorkflowable').change_state(statename, comment, commentformat, tr)
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').subworkflow_input_trinfo()")
- def subworkflow_input_trinfo(self):
- return self.cw_adapt_to('IWorkflowable').subworkflow_input_trinfo()
- @deprecated("[3.9] use entity.cw_adapt_to('IWorkflowable').subworkflow_input_transition()")
- def subworkflow_input_transition(self):
- return self.cw_adapt_to('IWorkflowable').subworkflow_input_transition()
-
-
-MI_REL_TRIGGERS[('in_state', 'subject')] = WorkflowableMixIn
-
-
-
-class IWorkflowableAdapter(WorkflowableMixIn, EntityAdapter):
+class IWorkflowableAdapter(EntityAdapter):
"""base adapter providing workflow helper methods for workflowable entities.
"""
__regid__ = 'IWorkflowable'
diff -r cf27006ce813 -r b1e933b0e850 entity.py
--- a/entity.py Fri Dec 06 17:20:59 2013 +0100
+++ b/entity.py Mon Dec 09 16:13:10 2013 +0100
@@ -42,7 +42,6 @@
from cubicweb.rqlrewrite import RQLRewriter
from cubicweb.uilib import soup2xhtml
-from cubicweb.mixins import MI_REL_TRIGGERS
from cubicweb.mttransforms import ENGINE
_marker = object()
@@ -194,31 +193,11 @@
setattr(cls, rschema.type, Attribute(rschema.type))
mixins = []
for rschema, _, role in eschema.relation_definitions():
- if (rschema, role) in MI_REL_TRIGGERS:
- mixin = MI_REL_TRIGGERS[(rschema, role)]
- if not (issubclass(cls, mixin) or mixin in mixins): # already mixed ?
- mixins.append(mixin)
- for iface in getattr(mixin, '__implements__', ()):
- if not interface.implements(cls, iface):
- interface.extend(cls, iface)
if role == 'subject':
attr = rschema.type
else:
attr = 'reverse_%s' % rschema.type
setattr(cls, attr, Relation(rschema, role))
- if mixins:
- # see etype class instantation in cwvreg.ETypeRegistry.etype_class method:
- # due to class dumping, cls is the generated top level class with actual
- # user class as (only) parent. Since we want to be able to override mixins
- # method from this user class, we have to take care to insert mixins after that
- # class
- #
- # note that we don't plug mixins as user class parent since it causes pb
- # with some cases of entity classes inheritance.
- mixins.insert(0, cls.__bases__[0])
- mixins += cls.__bases__[1:]
- cls.__bases__ = tuple(mixins)
- cls.info('plugged %s mixins on %s', mixins, cls)
fetch_attrs = ('modification_date',)
@@ -558,7 +537,14 @@
raise NotImplementedError('comparison not implemented for %s' % self.__class__)
def __eq__(self, other):
- raise NotImplementedError('comparison not implemented for %s' % self.__class__)
+ if isinstance(self.eid, (int, long)):
+ return self.eid == other.eid
+ return self is other
+
+ def __hash__(self):
+ if isinstance(self.eid, (int, long)):
+ return self.eid
+ return super(Entity, self).__hash__()
def _cw_update_attr_cache(self, attrcache):
# if context is a repository session, don't consider dont-cache-attrs as
@@ -983,7 +969,7 @@
return value
def related(self, rtype, role='subject', limit=None, entities=False, # XXX .cw_related
- safe=False):
+ safe=False, targettypes=None):
"""returns a resultset of related entities
:param rtype:
@@ -997,10 +983,13 @@
:param safe:
if True, an empty rset/list of entities will be returned in case of
:exc:`Unauthorized`, else (the default), the exception is propagated
+ :param targettypes:
+ a tuple of target entity types to restrict the query
"""
rtype = str(rtype)
- if limit is None:
- # we cannot do much wrt cache on limited queries
+ # Caching restricted/limited results is best avoided.
+ cacheable = limit is None and targettypes is None
+ if cacheable:
cache_key = '%s_%s' % (rtype, role)
if cache_key in self._cw_related_cache:
return self._cw_related_cache[cache_key][entities]
@@ -1008,7 +997,7 @@
if entities:
return []
return self._cw.empty_rset()
- rql = self.cw_related_rql(rtype, role, limit=limit)
+ rql = self.cw_related_rql(rtype, role, limit=limit, targettypes=targettypes)
try:
rset = self._cw.execute(rql, {'x': self.eid})
except Unauthorized:
@@ -1016,9 +1005,9 @@
raise
rset = self._cw.empty_rset()
if entities:
- if limit is None:
+ if cacheable:
self.cw_set_relation_cache(rtype, role, rset)
- return self.related(rtype, role, limit, entities)
+ return self.related(rtype, role, entities=entities)
return list(rset.entities())
else:
return rset
@@ -1340,34 +1329,6 @@
def clear_all_caches(self):
return self.cw_clear_all_caches()
- @deprecated('[3.9] use entity.cw_attr_value(attr)')
- def get_value(self, name):
- return self.cw_attr_value(name)
-
- @deprecated('[3.9] use entity.cw_delete()')
- def delete(self, **kwargs):
- return self.cw_delete(**kwargs)
-
- @deprecated('[3.9] use entity.cw_attr_metadata(attr, metadata)')
- def attr_metadata(self, attr, metadata):
- return self.cw_attr_metadata(attr, metadata)
-
- @deprecated('[3.9] use entity.cw_has_perm(action)')
- def has_perm(self, action):
- return self.cw_has_perm(action)
-
- @deprecated('[3.9] use entity.cw_set_relation_cache(rtype, role, rset)')
- def set_related_cache(self, rtype, role, rset):
- self.cw_set_relation_cache(rtype, role, rset)
-
- @deprecated('[3.9] use entity.cw_clear_relation_cache(rtype, role)')
- def clear_related_cache(self, rtype=None, role=None):
- self.cw_clear_relation_cache(rtype, role)
-
- @deprecated('[3.9] use entity.cw_related_rql(rtype, [role, [targettypes]])')
- def related_rql(self, rtype, role='subject', targettypes=None):
- return self.cw_related_rql(rtype, role, targettypes)
-
@property
@deprecated('[3.10] use entity.cw_edited')
def edited_attributes(self):
diff -r cf27006ce813 -r b1e933b0e850 etwist/request.py
--- a/etwist/request.py Fri Dec 06 17:20:59 2013 +0100
+++ b/etwist/request.py Mon Dec 09 16:13:10 2013 +0100
@@ -24,15 +24,21 @@
class CubicWebTwistedRequestAdapter(CubicWebRequestBase):
+ """ from twisted .req to cubicweb .form
+ req.files are put into .form[]
+ """
def __init__(self, req, vreg, https):
self._twreq = req
super(CubicWebTwistedRequestAdapter, self).__init__(
vreg, https, req.args, headers=req.received_headers)
- for key, (name, stream) in req.files.iteritems():
- if name is None:
- self.form[key] = (name, stream)
- else:
- self.form[key] = (unicode(name, self.encoding), stream)
+ for key, name_stream_list in req.files.iteritems():
+ for name, stream in name_stream_list:
+ if name is not None:
+ name = unicode(name, self.encoding)
+ self.form.setdefault(key, []).append((name, stream))
+ # 3.16.4 backward compat
+ if len(self.form[key]) == 1:
+ self.form[key] = self.form[key][0]
self.content = self._twreq.content # stream
def http_method(self):
diff -r cf27006ce813 -r b1e933b0e850 etwist/server.py
--- a/etwist/server.py Fri Dec 06 17:20:59 2013 +0100
+++ b/etwist/server.py Mon Dec 09 16:13:10 2013 +0100
@@ -244,7 +244,6 @@
self._do_process_multipart = True
self.process()
-
@monkeypatch(http.Request)
def process_multipart(self):
if not self._do_process_multipart:
@@ -254,16 +253,17 @@
keep_blank_values=1,
strict_parsing=1)
for key in form:
- value = form[key]
- if isinstance(value, list):
- self.args[key] = [v.value for v in value]
- elif value.filename:
- if value.done != -1: # -1 is transfer has been interrupted
- self.files[key] = (value.filename, value.file)
+ values = form[key]
+ if not isinstance(values, list):
+ values = [values]
+ for value in values:
+ if value.filename:
+ if value.done != -1: # -1 is transfer has been interrupted
+ self.files.setdefault(key, []).append((value.filename, value.file))
+ else:
+ self.files.setdefault(key, []).append((None, None))
else:
- self.files[key] = (None, None)
- else:
- self.args[key] = value.value
+ self.args.setdefault(key, []).append(value.value)
from logging import getLogger
from cubicweb import set_log_methods
diff -r cf27006ce813 -r b1e933b0e850 etwist/twconfig.py
--- a/etwist/twconfig.py Fri Dec 06 17:20:59 2013 +0100
+++ b/etwist/twconfig.py Mon Dec 09 16:13:10 2013 +0100
@@ -34,9 +34,8 @@
from cubicweb.web.webconfig import WebConfiguration
-class TwistedConfiguration(WebConfiguration):
+class WebConfigurationBase(WebConfiguration):
"""web instance (in a twisted web server) client of a RQL server"""
- name = 'twisted'
options = merge_options((
# ctl configuration
@@ -107,19 +106,17 @@
return 'http://%s:%s/' % (self['host'] or getfqdn(), self['port'] or 8080)
-CONFIGURATIONS.append(TwistedConfiguration)
-
try:
from cubicweb.server.serverconfig import ServerConfiguration
- class AllInOneConfiguration(TwistedConfiguration, ServerConfiguration):
+ class AllInOneConfiguration(WebConfigurationBase, ServerConfiguration):
"""repository and web instance in the same twisted process"""
name = 'all-in-one'
- options = merge_options(TwistedConfiguration.options
+ options = merge_options(WebConfigurationBase.options
+ ServerConfiguration.options)
- cubicweb_appobject_path = TwistedConfiguration.cubicweb_appobject_path | ServerConfiguration.cubicweb_appobject_path
- cube_appobject_path = TwistedConfiguration.cube_appobject_path | ServerConfiguration.cube_appobject_path
+ cubicweb_appobject_path = WebConfigurationBase.cubicweb_appobject_path | ServerConfiguration.cubicweb_appobject_path
+ cube_appobject_path = WebConfigurationBase.cube_appobject_path | ServerConfiguration.cube_appobject_path
def pyro_enabled(self):
"""tell if pyro is activated for the in memory repository"""
return self['pyro-server']
diff -r cf27006ce813 -r b1e933b0e850 ext/rest.py
--- a/ext/rest.py Fri Dec 06 17:20:59 2013 +0100
+++ b/ext/rest.py Mon Dec 09 16:13:10 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.
@@ -29,6 +29,8 @@
* `sourcecode` (if pygments is installed), source code colorization
+* `rql-table`, create a table from a RQL query
+
"""
__docformat__ = "restructuredtext en"
@@ -40,7 +42,7 @@
from docutils import statemachine, nodes, utils, io
from docutils.core import Publisher
-from docutils.parsers.rst import Parser, states, directives
+from docutils.parsers.rst import Parser, states, directives, Directive
from docutils.parsers.rst.roles import register_canonical_role, set_classes
from logilab.mtconverter import ESC_UCAR_TABLE, ESC_CAR_TABLE, xml_escape
@@ -251,6 +253,76 @@
winclude_directive.options = {'literal': directives.flag,
'encoding': directives.encoding}
+class RQLTableDirective(Directive):
+ """rql-table directive
+
+ Example:
+
+ .. rql-table::
+ :vid: mytable
+ :headers: , , progress
+ :colvids: 2=progress
+
+ Any X,U,X WHERE X is Project, X url U
+
+ All fields but the RQL string are optionnal. The ``:headers:`` option can
+ contain empty column names.
+ """
+
+ required_arguments = 0
+ optional_arguments = 0
+ has_content= True
+ final_argument_whitespace = True
+ option_spec = {'vid': directives.unchanged,
+ 'headers': directives.unchanged,
+ 'colvids': directives.unchanged}
+
+ def run(self):
+ errid = "rql-table directive"
+ self.assert_has_content()
+ if self.arguments:
+ raise self.warning('%s does not accept arguments' % errid)
+ rql = ' '.join([l.strip() for l in self.content])
+ _cw = self.state.document.settings.context._cw
+ _cw.ensure_ro_rql(rql)
+ try:
+ rset = _cw.execute(rql)
+ except Exception as exc:
+ raise self.error("fail to execute RQL query in %s: %r" %
+ (errid, exc))
+ if not rset:
+ raise self.warning("empty result set")
+ vid = self.options.get('vid', 'table')
+ try:
+ view = _cw.vreg['views'].select(vid, _cw, rset=rset)
+ except Exception as exc:
+ raise self.error("fail to select '%s' view in %s: %r" %
+ (vid, errid, exc))
+ headers = None
+ if 'headers' in self.options:
+ headers = [h.strip() for h in self.options['headers'].split(',')]
+ while headers.count(''):
+ headers[headers.index('')] = None
+ if len(headers) != len(rset[0]):
+ raise self.error("the number of 'headers' does not match the "
+ "number of columns in %s" % errid)
+ cellvids = None
+ if 'colvids' in self.options:
+ cellvids = {}
+ for f in self.options['colvids'].split(','):
+ try:
+ idx, vid = f.strip().split('=')
+ except ValueError:
+ raise self.error("malformatted 'colvids' option in %s" %
+ errid)
+ cellvids[int(idx.strip())] = vid.strip()
+ try:
+ content = view.render(headers=headers, cellvids=cellvids)
+ except Exception as exc:
+ raise self.error("Error rendering %s (%s)" % (errid, exc))
+ return [nodes.raw('', content, format='html')]
+
+
try:
from pygments import highlight
from pygments.lexers import get_lexer_by_name
@@ -385,3 +457,4 @@
directives.register_directive('winclude', winclude_directive)
if pygments_directive is not None:
directives.register_directive('sourcecode', pygments_directive)
+ directives.register_directive('rql-table', RQLTableDirective)
diff -r cf27006ce813 -r b1e933b0e850 ext/test/data/views.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/test/data/views.py Mon Dec 09 16:13:10 2013 +0100
@@ -0,0 +1,24 @@
+# copyright 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 .
+
+
+from cubicweb.web.views import tableview
+
+class CustomRsetTableView(tableview.RsetTableView):
+ __regid__ = 'mytable'
+
diff -r cf27006ce813 -r b1e933b0e850 ext/test/unittest_rest.py
--- a/ext/test/unittest_rest.py Fri Dec 06 17:20:59 2013 +0100
+++ b/ext/test/unittest_rest.py Mon Dec 09 16:13:10 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.
@@ -82,5 +82,133 @@
out = rest_publish(context, ':bookmark:`%s`' % eid)
self.assertEqual(out, u'
\n')
+ def test_rqltable_nocontent(self):
+ context = self.context()
+ out = rest_publish(context, """.. rql-table::""")
+ self.assertIn("System Message: ERROR", out)
+ self.assertIn("Content block expected for the "rql-table" "
+ "directive; none found" , out)
+
+ def test_rqltable_norset(self):
+ context = self.context()
+ rql = "Any X WHERE X is CWUser, X firstname 'franky'"
+ out = rest_publish(
+ context, """\
+.. rql-table::
+
+ %(rql)s""" % {'rql': rql})
+ self.assertIn("System Message: WARNING", out)
+ self.assertIn("empty result set", out)
+
+ def test_rqltable_nooptions(self):
+ rql = """Any S,F,L WHERE X is CWUser, X surname S,
+ X firstname F, X login L"""
+ out = rest_publish(
+ self.context(), """\
+.. rql-table::
+
+ %(rql)s
+ """ % {'rql': rql})
+ req = self.request()
+ view = self.vreg['views'].select('table', req, rset=req.execute(rql))
+ self.assertEqual(view.render(w=None)[49:], out[49:])
+
+ def test_rqltable_vid(self):
+ rql = """Any S,F,L WHERE X is CWUser, X surname S,
+ X firstname F, X login L"""
+ vid = 'mytable'
+ out = rest_publish(
+ self.context(), """\
+.. rql-table::
+ :vid: %(vid)s
+
+ %(rql)s
+ """ % {'rql': rql, 'vid': vid})
+ req = self.request()
+ view = self.vreg['views'].select(vid, req, rset=req.execute(rql))
+ self.assertEqual(view.render(w=None)[49:], out[49:])
+ self.assertIn(vid, out[:49])
+
+ def test_rqltable_badvid(self):
+ rql = """Any S,F,L WHERE X is CWUser, X surname S,
+ X firstname F, X login L"""
+ vid = 'mytabel'
+ out = rest_publish(
+ self.context(), """\
+.. rql-table::
+ :vid: %(vid)s
+
+ %(rql)s
+ """ % {'rql': rql, 'vid': vid})
+ self.assertIn("fail to select '%s' view" % vid, out)
+
+ def test_rqltable_headers(self):
+ rql = """Any S,F,L WHERE X is CWUser, X surname S,
+ X firstname F, X login L"""
+ headers = ["nom", "prenom", "identifiant"]
+ out = rest_publish(
+ self.context(), """\
+.. rql-table::
+ :headers: %(headers)s
+
+ %(rql)s
+ """ % {'rql': rql, 'headers': ', '.join(headers)})
+ req = self.request()
+ view = self.vreg['views'].select('table', req, rset=req.execute(rql))
+ view.headers = headers
+ self.assertEqual(view.render(w=None)[49:], out[49:])
+
+ def test_rqltable_headers_missing(self):
+ rql = """Any S,F,L WHERE X is CWUser, X surname S,
+ X firstname F, X login L"""
+ headers = ["nom", "", "identifiant"]
+ out = rest_publish(
+ self.context(), """\
+.. rql-table::
+ :headers: %(headers)s
+
+ %(rql)s
+ """ % {'rql': rql, 'headers': ', '.join(headers)})
+ req = self.request()
+ view = self.vreg['views'].select('table', req, rset=req.execute(rql))
+ view.headers = [headers[0], None, headers[2]]
+ self.assertEqual(view.render(w=None)[49:], out[49:])
+
+ def test_rqltable_headers_missing_edges(self):
+ rql = """Any S,F,L WHERE X is CWUser, X surname S,
+ X firstname F, X login L"""
+ headers = [" ", "prenom", ""]
+ out = rest_publish(
+ self.context(), """\
+.. rql-table::
+ :headers: %(headers)s
+
+ %(rql)s
+ """ % {'rql': rql, 'headers': ', '.join(headers)})
+ req = self.request()
+ view = self.vreg['views'].select('table', req, rset=req.execute(rql))
+ view.headers = [None, headers[1], None]
+ self.assertEqual(view.render(w=None)[49:], out[49:])
+
+ def test_rqltable_colvids(self):
+ rql = """Any X,S,F,L WHERE X is CWUser, X surname S,
+ X firstname F, X login L"""
+ colvids = {0: "oneline"}
+ out = rest_publish(
+ self.context(), """\
+.. rql-table::
+ :colvids: %(colvids)s
+
+ %(rql)s
+ """ % {'rql': rql,
+ 'colvids': ', '.join(["%d=%s" % (k, v)
+ for k, v in colvids.iteritems()])
+ })
+ req = self.request()
+ view = self.vreg['views'].select('table', req, rset=req.execute(rql))
+ view.cellvids = colvids
+ self.assertEqual(view.render(w=None)[49:], out[49:])
+
+
if __name__ == '__main__':
unittest_main()
diff -r cf27006ce813 -r b1e933b0e850 hooks/metadata.py
--- a/hooks/metadata.py Fri Dec 06 17:20:59 2013 +0100
+++ b/hooks/metadata.py Mon Dec 09 16:13:10 2013 +0100
@@ -149,7 +149,7 @@
# entity source handling #######################################################
-class ChangeEntityUpdateCaches(hook.Operation):
+class ChangeEntitySourceUpdateCaches(hook.Operation):
oldsource = newsource = entity = None # make pylint happy
def postcommit_event(self):
@@ -221,6 +221,6 @@
'mtime': datetime.now()}
self._cw.system_sql(syssource.sqlgen.insert('entities', attrs), attrs)
# register an operation to update repository/sources caches
- ChangeEntityUpdateCaches(self._cw, entity=entity,
- oldsource=oldsource.repo_source,
- newsource=syssource)
+ ChangeEntitySourceUpdateCaches(self._cw, entity=entity,
+ oldsource=oldsource.repo_source,
+ newsource=syssource)
diff -r cf27006ce813 -r b1e933b0e850 hooks/security.py
--- a/hooks/security.py Fri Dec 06 17:20:59 2013 +0100
+++ b/hooks/security.py Mon Dec 09 16:13:10 2013 +0100
@@ -111,17 +111,11 @@
events = ('after_update_entity',)
def __call__(self):
- try:
- # check user has permission right now, if not retry at commit time
- self.entity.cw_check_perm('update')
- check_entity_attributes(self._cw, self.entity)
- except Unauthorized:
- self.entity._cw_clear_local_perm_cache('update')
- # save back editedattrs in case the entity is reedited later in the
- # same transaction, which will lead to cw_edited being
- # overwritten
- CheckEntityPermissionOp.get_instance(self._cw).add_data(
- (self.entity.eid, 'update', self.entity.cw_edited) )
+ # save back editedattrs in case the entity is reedited later in the
+ # same transaction, which will lead to cw_edited being
+ # overwritten
+ CheckEntityPermissionOp.get_instance(self._cw).add_data(
+ (self.entity.eid, 'update', self.entity.cw_edited) )
class BeforeDelEntitySecurityHook(SecurityHook):
diff -r cf27006ce813 -r b1e933b0e850 hooks/syncschema.py
--- a/hooks/syncschema.py Fri Dec 06 17:20:59 2013 +0100
+++ b/hooks/syncschema.py Mon Dec 09 16:13:10 2013 +0100
@@ -28,7 +28,7 @@
from copy import copy
from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
-from yams import buildobjs as ybo, schema2sql as y2sql
+from yams import buildobjs as ybo, schema2sql as y2sql, convert_default_value
from logilab.common.decorators import clear_cache
@@ -39,21 +39,6 @@
from cubicweb.server import hook, schemaserial as ss
from cubicweb.server.sqlutils import SQL_PREFIX
-
-TYPE_CONVERTER = { # XXX
- 'Boolean': bool,
- 'Int': int,
- 'BigInt': int,
- 'Float': float,
- 'Password': str,
- 'String': unicode,
- 'Date' : unicode,
- 'Datetime' : unicode,
- 'Time' : unicode,
- 'TZDatetime' : unicode,
- 'TZTime' : unicode,
- }
-
# core entity and relation types which can't be removed
CORE_TYPES = BASE_TYPES | SCHEMA_TYPES | META_RTYPES | set(
('CWUser', 'CWGroup','login', 'upassword', 'name', 'in_group'))
@@ -437,11 +422,11 @@
def precommit_event(self):
session = self.session
entity = self.entity
- # entity.defaultval is a string or None, but we need a correctly typed
+ # entity.defaultval is a Binary or None, but we need a correctly typed
# value
default = entity.defaultval
if default is not None:
- default = TYPE_CONVERTER[entity.otype.name](default)
+ default = default.unzpickle()
props = {'default': default,
'indexed': entity.indexed,
'fulltextindexed': entity.fulltextindexed,
@@ -493,20 +478,11 @@
# attribute is still set to False, so we've to ensure it's False
rschema.final = True
insert_rdef_on_subclasses(session, eschema, rschema, rdefdef, props)
- # set default value, using sql for performance and to avoid
- # modification_date update
- if default:
- if rdefdef.object in ('Date', 'Datetime', 'TZDatetime'):
- # XXX may may want to use creation_date
- if default == 'TODAY':
- default = syssource.dbhelper.sql_current_date()
- elif default == 'NOW':
- default = syssource.dbhelper.sql_current_timestamp()
- session.system_sql('UPDATE %s SET %s=%s'
- % (table, column, default))
- else:
- session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
- {'default': default})
+ # update existing entities with the default value of newly added attribute
+ if default is not None:
+ default = convert_default_value(self.rdefdef, default)
+ session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
+ {'default': default})
def revertprecommit_event(self):
# revert changes on in memory schema
diff -r cf27006ce813 -r b1e933b0e850 hooks/test/unittest_syncschema.py
--- a/hooks/test/unittest_syncschema.py Fri Dec 06 17:20:59 2013 +0100
+++ b/hooks/test/unittest_syncschema.py Mon Dec 09 16:13:10 2013 +0100
@@ -19,7 +19,7 @@
from logilab.common.testlib import TestCase, unittest_main
-from cubicweb import ValidationError
+from cubicweb import ValidationError, Binary
from cubicweb.schema import META_RTYPES
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.server.sqlutils import SQL_PREFIX
@@ -74,9 +74,10 @@
self.commit()
self.assertTrue(schema.has_entity('Societe2'))
self.assertTrue(schema.has_relation('concerne2'))
- attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", '
+ attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval %(default)s, '
' X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
- 'WHERE RT name "name", E name "Societe2", F name "String"')[0][0]
+ 'WHERE RT name "name", E name "Societe2", F name "String"',
+ {'default': Binary.zpickle('noname')})[0][0]
self._set_attr_perms(attreid)
concerne2_rdef_eid = self.execute(
'INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
@@ -290,8 +291,10 @@
def test_add_attribute_to_base_class(self):
- attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
- 'WHERE RT name "messageid", E name "BaseTransition", F name "String"')[0][0]
+ attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval %(default)s, '
+ 'X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
+ 'WHERE RT name "messageid", E name "BaseTransition", F name "String"',
+ {'default': Binary.zpickle('noname')})[0][0]
assert self.execute('SET X read_permission Y WHERE X eid %(x)s, Y name "managers"',
{'x': attreid})
self.commit()
diff -r cf27006ce813 -r b1e933b0e850 interfaces.py
--- a/interfaces.py Fri Dec 06 17:20:59 2013 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +0,0 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb. If not, see .
-"""Standard interfaces. Deprecated in favor of adapters.
-
-.. note::
-
- The `implements` selector used to match not only entity classes but also their
- interfaces. This will disappear in a future version. You should define an
- adapter for that interface and use `adaptable('MyIFace')` selector on appobjects
- that require that interface.
-
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab.common.interface import Interface
-
-
-# XXX deprecates in favor of IProgressAdapter
-class IProgress(Interface):
- """something that has a cost, a state and a progression"""
-
- @property
- def cost(self):
- """the total cost"""
-
- @property
- def done(self):
- """what is already done"""
-
- @property
- def todo(self):
- """what remains to be done"""
-
- def progress_info(self):
- """returns a dictionary describing progress/estimated cost of the
- version.
-
- - mandatory keys are (''estimated', 'done', 'todo')
-
- - optional keys are ('notestimated', 'notestimatedcorrected',
- 'estimatedcorrected')
-
- 'noestimated' and 'notestimatedcorrected' should default to 0
- 'estimatedcorrected' should default to 'estimated'
- """
-
- def finished(self):
- """returns True if status is finished"""
-
- def in_progress(self):
- """returns True if status is not finished"""
-
- def progress(self):
- """returns the % progress of the task item"""
-
-# XXX deprecates in favor of IMileStoneAdapter
-class IMileStone(IProgress):
- """represents an ITask's item"""
-
- parent_type = None # specify main task's type
-
- def get_main_task(self):
- """returns the main ITask entity"""
-
- def initial_prevision_date(self):
- """returns the initial expected end of the milestone"""
-
- def eta_date(self):
- """returns expected date of completion based on what remains
- to be done
- """
-
- def completion_date(self):
- """returns date on which the subtask has been completed"""
-
- def contractors(self):
- """returns the list of persons supposed to work on this task"""
-
-# XXX deprecates in favor of IEmbedableAdapter
-class IEmbedable(Interface):
- """interface for embedable entities"""
-
- def embeded_url(self):
- """embed action interface"""
-
-# XXX deprecates in favor of ICalendarViewsAdapter
-class ICalendarViews(Interface):
- """calendar views interface"""
- def matching_dates(self, begin, end):
- """
- :param begin: day considered as begin of the range (`DateTime`)
- :param end: day considered as end of the range (`DateTime`)
-
- :return:
- a list of dates (`DateTime`) in the range [`begin`, `end`] on which
- this entity apply
- """
-
-# XXX deprecates in favor of ICalendarableAdapter
-class ICalendarable(Interface):
- """interface for items that do have a begin date 'start' and an end date 'stop'
- """
-
- @property
- def start(self):
- """return start date"""
-
- @property
- def stop(self):
- """return stop state"""
-
-# XXX deprecates in favor of ICalendarableAdapter
-class ITimetableViews(Interface):
- """timetable views interface"""
- def timetable_date(self):
- """XXX explain
-
- :return: date (`DateTime`)
- """
-
-# XXX deprecates in favor of IGeocodableAdapter
-class IGeocodable(Interface):
- """interface required by geocoding views such as gmap-view"""
-
- @property
- def latitude(self):
- """returns the latitude of the entity"""
-
- @property
- def longitude(self):
- """returns the longitude of the entity"""
-
- def marker_icon(self):
- """returns the icon that should be used as the marker"""
-
-
-# XXX deprecates in favor of IEmailableAdapter
-class IFeed(Interface):
- """interface for entities with rss flux"""
-
- def rss_feed_url(self):
- """"""
-
-# XXX deprecates in favor of IDownloadableAdapter
-class IDownloadable(Interface):
- """interface for downloadable entities"""
-
- def download_url(self): # XXX not really part of this interface
- """return an url to download entity's content"""
- def download_content_type(self):
- """return MIME type of the downloadable content"""
- def download_encoding(self):
- """return encoding of the downloadable content"""
- def download_file_name(self):
- """return file name of the downloadable content"""
- def download_data(self):
- """return actual data of the downloadable content"""
-
-# XXX deprecates in favor of IPrevNextAdapter
-class IPrevNext(Interface):
- """interface for entities which can be linked to a previous and/or next
- entity
- """
-
- def next_entity(self):
- """return the 'next' entity"""
- def previous_entity(self):
- """return the 'previous' entity"""
-
-# XXX deprecates in favor of IBreadCrumbsAdapter
-class IBreadCrumbs(Interface):
-
- def breadcrumbs(self, view, recurs=False):
- pass
-
-# XXX deprecates in favor of ITreeAdapter
-class ITree(Interface):
-
- def parent(self):
- """returns the parent entity"""
-
- def children(self):
- """returns the item's children"""
-
- def children_rql(self):
- """XXX returns RQL to get children"""
-
- def iterchildren(self):
- """iterates over the item's children"""
-
- def is_leaf(self):
- """returns true if this node as no child"""
-
- def is_root(self):
- """returns true if this node has no parent"""
-
- def root(self):
- """returns the root object"""
-
diff -r cf27006ce813 -r b1e933b0e850 migration.py
--- a/migration.py Fri Dec 06 17:20:59 2013 +0100
+++ b/migration.py Mon Dec 09 16:13:10 2013 +0100
@@ -257,7 +257,7 @@
home_key = 'HOME'
if sys.platform == 'win32':
home_key = 'USERPROFILE'
- histfile = os.path.join(os.environ[home_key], ".eshellhist")
+ histfile = os.path.join(os.environ[home_key], ".cwshell_history")
try:
readline.read_history_file(histfile)
except IOError:
diff -r cf27006ce813 -r b1e933b0e850 misc/migration/3.18.0_Any.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.18.0_Any.py Mon Dec 09 16:13:10 2013 +0100
@@ -0,0 +1,83 @@
+sync_schema_props_perms('defaultval')
+
+def convert_defaultval(cwattr, default):
+ from decimal import Decimal
+ import yams
+ from cubicweb import Binary
+ if default is None:
+ return
+ atype = cwattr.to_entity[0].name
+ if atype == 'Boolean':
+ assert default in ('True', 'False'), default
+ default = default == 'True'
+ elif atype in ('Int', 'BigInt'):
+ default = int(default)
+ elif atype == 'Float':
+ default = float(default)
+ elif atype == 'Decimal':
+ default = Decimal(default)
+ elif atype in ('Date', 'Datetime', 'TZDatetime', 'Time'):
+ try:
+ # handle NOW and TODAY, keep them stored as strings
+ yams.KEYWORD_MAP[atype][default.upper()]
+ default = default.upper()
+ except KeyError:
+ # otherwise get an actual date or datetime
+ default = yams.DATE_FACTORY_MAP[atype](default)
+ else:
+ assert atype == 'String', atype
+ default = unicode(default)
+ return Binary.zpickle(default)
+
+dbh = repo.system_source.dbhelper
+driver = config.sources()['system']['db-driver']
+
+if driver == 'postgres' or driver.startswith('sqlserver'):
+
+ sql('ALTER TABLE cw_cwattribute ADD new_defaultval %s' % dbh.TYPE_MAPPING['Bytes'])
+
+ for cwattr in rql('CWAttribute X').entities():
+ olddefault = cwattr.defaultval
+ if olddefault is not None:
+ req = "UPDATE cw_cwattribute SET new_defaultval = %(val)s WHERE cw_eid = %(eid)s"
+ args = {'val': dbh.binary_value(convert_defaultval(cwattr, olddefault).getvalue()), 'eid': cwattr.eid}
+ sql(req, args, ask_confirm=False)
+
+ sql('ALTER TABLE cw_cwattribute DROP COLUMN cw_defaultval')
+ if config.sources()['system']['db-driver'] == 'postgres':
+ sql('ALTER TABLE cw_cwattribute RENAME COLUMN new_defaultval TO cw_defaultval')
+ else:
+ sql("sp_rename 'cw_cwattribute.new_defaultval', 'cw_defaultval', 'COLUMN'")
+
+elif driver == 'sqlite':
+
+ import re
+ create = sql("SELECT sql FROM sqlite_master WHERE name = 'cw_CWAttribute'")[0][0]
+ create = re.sub('cw_defaultval varchar[^,]*,', 'cw_defaultval bytea,', create, re.I)
+ create = re.sub('cw_CWAttribute', 'tmp_cw_CWAttribute', create, re.I)
+ sql(create)
+ sql("INSERT INTO tmp_cw_CWAttribute SELECT * FROM cw_CWAttribute")
+ for cwattr in rql('CWAttribute X').entities():
+ olddefault = cwattr.defaultval
+ if olddefault is None:
+ continue
+ req = "UPDATE tmp_cw_CWAttribute SET cw_defaultval = %(val)s WHERE cw_eid = %(eid)s"
+ args = {'val': dbh.binary_value(convert_defaultval(cwattr, olddefault).getvalue()),
+ 'eid': cwattr.eid}
+ sql(req, args, ask_confirm=False)
+
+ sql('DROP TABLE cw_CWAttribute')
+ sql('ALTER TABLE tmp_cw_CWAttribute RENAME TO cw_CWAttribute')
+
+else:
+ assert False, 'upgrade not supported on this database backend'
+
+# Set object type to "Bytes" for CWAttribute's "defaultval" attribute
+rql('SET X to_entity B WHERE X is CWAttribute, X from_entity Y, Y name "CWAttribute", '
+ 'X relation_type Z, Z name "defaultval", B name "Bytes"')
+
+from yams import buildobjs as ybo
+schema.add_relation_def(ybo.RelationDefinition('CWAttribute', 'defaultval', 'Bytes'))
+schema.del_relation_def('CWAttribute', 'defaultval', 'String')
+
+commit()
diff -r cf27006ce813 -r b1e933b0e850 mixins.py
--- a/mixins.py Fri Dec 06 17:20:59 2013 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,308 +0,0 @@
-# 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.
-#
-# 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 .
-"""mixins of entity/views organized somewhat in a graph or tree structure"""
-__docformat__ = "restructuredtext en"
-
-from itertools import chain
-
-from logilab.common.decorators import cached
-from logilab.common.deprecation import deprecated, class_deprecated
-
-from cubicweb.predicates import implements
-from cubicweb.interfaces import ITree
-
-
-class TreeMixIn(object):
- """base tree-mixin implementing the tree interface
-
- This mixin has to be inherited explicitly and configured using the
- tree_attribute, parent_target and children_target class attribute to
- benefit from this default implementation
- """
- __metaclass__ = class_deprecated
- __deprecation_warning__ = '[3.9] TreeMixIn is deprecated, use/override ITreeAdapter instead (%(cls)s)'
-
- tree_attribute = None
- # XXX misnamed
- parent_target = 'subject'
- children_target = 'object'
-
- def different_type_children(self, entities=True):
- """return children entities of different type as this entity.
-
- according to the `entities` parameter, return entity objects or the
- equivalent result set
- """
- res = self.related(self.tree_attribute, self.children_target,
- entities=entities)
- if entities:
- return [e for e in res if e.e_schema != self.e_schema]
- return res.filtered_rset(lambda x: x.e_schema != self.e_schema, self.cw_col)
-
- def same_type_children(self, entities=True):
- """return children entities of the same type as this entity.
-
- according to the `entities` parameter, return entity objects or the
- equivalent result set
- """
- res = self.related(self.tree_attribute, self.children_target,
- entities=entities)
- if entities:
- return [e for e in res if e.e_schema == self.e_schema]
- return res.filtered_rset(lambda x: x.e_schema is self.e_schema, self.cw_col)
-
- def iterchildren(self, _done=None):
- if _done is None:
- _done = set()
- for child in self.children():
- if child.eid in _done:
- self.error('loop in %s tree: %s', self.__regid__.lower(), child)
- continue
- yield child
- _done.add(child.eid)
-
- def prefixiter(self, _done=None):
- if _done is None:
- _done = set()
- if self.eid in _done:
- return
- _done.add(self.eid)
- yield self
- for child in self.same_type_children():
- for entity in child.prefixiter(_done):
- yield entity
-
- @cached
- def path(self):
- """returns the list of eids from the root object to this object"""
- path = []
- parent = self
- while parent:
- if parent.eid in path:
- self.error('loop in %s tree: %s', self.__regid__.lower(), parent)
- break
- path.append(parent.eid)
- try:
- # check we are not leaving the tree
- if (parent.tree_attribute != self.tree_attribute or
- parent.parent_target != self.parent_target):
- break
- parent = parent.parent()
- except AttributeError:
- break
-
- path.reverse()
- return path
-
- def iterparents(self, strict=True):
- def _uptoroot(self):
- curr = self
- while True:
- curr = curr.parent()
- if curr is None:
- break
- yield curr
- if not strict:
- return chain([self], _uptoroot(self))
- return _uptoroot(self)
-
- ## ITree interface ########################################################
- def parent(self):
- """return the parent entity if any, else None (e.g. if we are on the
- root
- """
- try:
- return self.related(self.tree_attribute, self.parent_target,
- entities=True)[0]
- except (KeyError, IndexError):
- return None
-
- def children(self, entities=True, sametype=False):
- """return children entities
-
- according to the `entities` parameter, return entity objects or the
- equivalent result set
- """
- if sametype:
- return self.same_type_children(entities)
- else:
- return self.related(self.tree_attribute, self.children_target,
- entities=entities)
-
- def children_rql(self):
- return self.cw_related_rql(self.tree_attribute, self.children_target)
-
- def is_leaf(self):
- return len(self.children()) == 0
-
- def is_root(self):
- return self.parent() is None
-
- def root(self):
- """return the root object"""
- return self._cw.entity_from_eid(self.path()[0])
-
-
-class EmailableMixIn(object):
- """base mixin providing the default get_email() method used by
- the massmailing view
-
- NOTE: The default implementation is based on the
- primary_email / use_email scheme
- """
- @deprecated("[3.9] use entity.cw_adapt_to('IEmailable').get_email()")
- def get_email(self):
- if getattr(self, 'primary_email', None):
- return self.primary_email[0].address
- if getattr(self, 'use_email', None):
- return self.use_email[0].address
- return None
-
-
-"""pluggable mixins system: plug classes registered in MI_REL_TRIGGERS on entity
-classes which have the relation described by the dict's key.
-
-NOTE: pluggable mixins can't override any method of the 'explicit' user classes tree
-(eg without plugged classes). This includes bases Entity and AnyEntity classes.
-"""
-MI_REL_TRIGGERS = {
- ('primary_email', 'subject'): EmailableMixIn,
- ('use_email', 'subject'): EmailableMixIn,
- }
-
-
-# XXX move to cubicweb.web.views.treeview once we delete usage from this file
-def _done_init(done, view, row, col):
- """handle an infinite recursion safety belt"""
- if done is None:
- done = set()
- entity = view.cw_rset.get_entity(row, col)
- if entity.eid in done:
- msg = entity._cw._('loop in %(rel)s relation (%(eid)s)') % {
- 'rel': entity.cw_adapt_to('ITree').tree_relation,
- 'eid': entity.eid
- }
- return None, msg
- done.add(entity.eid)
- return done, entity
-
-
-class TreeViewMixIn(object):
- """a recursive tree view"""
- __metaclass__ = class_deprecated
- __deprecation_warning__ = '[3.9] TreeViewMixIn is deprecated, use/override BaseTreeView instead (%(cls)s)'
-
- __regid__ = 'tree'
- __select__ = implements(ITree, warn=False)
- item_vid = 'treeitem'
-
- def call(self, done=None, **kwargs):
- if done is None:
- done = set()
- super(TreeViewMixIn, self).call(done=done, **kwargs)
-
- def cell_call(self, row, col=0, vid=None, done=None, maxlevel=None, **kwargs):
- assert maxlevel is None or maxlevel > 0
- done, entity = _done_init(done, self, row, col)
- if done is None:
- # entity is actually an error message
- self.w(u'