Merge 3.24.5 into default branch
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 03 Feb 2017 13:37:32 +0100
changeset 11947 3c58ea2fd745
parent 11946 8de62610cea2 (diff)
parent 11941 f96f77a190f2 (current diff)
child 11950 68766861debe
Merge 3.24.5 into default branch
cubicweb.spec
cubicweb/__pkginfo__.py
--- a/cubicweb.spec	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb.spec	Fri Feb 03 13:37:32 2017 +0100
@@ -27,7 +27,7 @@
 Requires:       %{python}-rql >= 0.34.0
 Requires:       %{python}-yams >= 0.44.0
 Requires:       %{python}-logilab-database >= 1.15.0
-Requires:       %{python}-passlib < 2.0
+Requires:       %{python}-passlib => 1.7.0
 Requires:       %{python}-lxml
 Requires:       %{python}-twisted-web < 16.0.0
 Requires:       %{python}-markdown
--- a/cubicweb/__init__.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/__init__.py	Fri Feb 03 13:37:32 2017 +0100
@@ -26,6 +26,7 @@
 import pickle
 import pkgutil
 import sys
+import types
 import warnings
 import zlib
 
@@ -294,7 +295,9 @@
             sys.meta_path.append(self)
 
     def find_module(self, fullname, path=None):
-        if fullname.startswith('cubes.'):
+        if fullname == 'cubes':
+            return self
+        elif fullname.startswith('cubes.'):
             modname = 'cubicweb_' + fullname.split('.', 1)[1]
             try:
                 modinfo = imp.find_module(modname)
@@ -302,3 +305,9 @@
                 return None
             else:
                 return pkgutil.ImpLoader(fullname, *modinfo)
+
+    def load_module(self, fullname):
+        if fullname != 'cubes':
+            raise ImportError('No module named {0}'.format(fullname))
+        mod = sys.modules[fullname] = types.ModuleType(fullname, doc='CubicWeb cubes')
+        return mod
--- a/cubicweb/__pkginfo__.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/__pkginfo__.py	Fri Feb 03 13:37:32 2017 +0100
@@ -27,8 +27,8 @@
 
 modname = distname = "cubicweb"
 
-numversion = (3, 24, 5)
-version = '.'.join(str(num) for num in numversion)
+numversion = (3, 25, 0)
+version = '.'.join(str(num) for num in numversion) + '.dev0'
 
 description = "a repository of entities / relations for knowledge management"
 author = "Logilab"
--- a/cubicweb/cwconfig.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/cwconfig.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -177,16 +177,15 @@
 
    Directory where pid files will be written
 """
+
 from __future__ import print_function
 
-
-
 import importlib
 import logging
 import logging.config
 import os
-from os.path import (exists, join, expanduser, abspath, normpath, realpath,
-                     basename, isdir, dirname, splitext)
+from os.path import (exists, join, expanduser, abspath, normpath,
+                     basename, isdir, dirname, splitext, realpath)
 import pkgutil
 import pkg_resources
 import re
@@ -194,7 +193,7 @@
 import stat
 import sys
 from threading import Lock
-from warnings import warn, filterwarnings
+from warnings import filterwarnings
 
 from six import text_type
 
@@ -221,6 +220,7 @@
     except IndexError:
         raise ConfigurationError('no such config %r (check it exists with "cubicweb-ctl list")' % name)
 
+
 def possible_configurations(directory):
     """return a list of installed configurations in a directory
     according to \*-ctl files
@@ -228,6 +228,7 @@
     return [name for name in ('repository', 'all-in-one')
             if exists(join(directory, '%s.conf' % name))]
 
+
 def guess_configuration(directory):
     """try to guess the configuration to use for a directory. If multiple
     configurations are found, ConfigurationError is raised
@@ -238,6 +239,7 @@
                                  % (directory, modes))
     return modes[0]
 
+
 def _find_prefix(start_path=None):
     """Return the prefix path of CubicWeb installation.
 
@@ -274,6 +276,40 @@
     return cube
 
 
+def _expand_modname(modname):
+    """expand modules names `modname` if exists by walking non package submodules
+    and yield (submodname, filepath) including `modname` itself
+
+    If the file ends with .pyc or .pyo (python bytecode) also check that the
+    corresponding source .py file exists before yielding.
+    """
+    try:
+        loader = pkgutil.find_loader(modname)
+    except ImportError:
+        return
+    if not loader:
+        return
+
+    def check_source_file(filepath):
+        if filepath[-4:] in ('.pyc', '.pyo'):
+            if not exists(filepath[:-1]):
+                return False
+        return True
+
+    filepath = loader.get_filename()
+    if not check_source_file(filepath):
+        return
+    yield modname, filepath
+    if loader.is_package(modname):
+        path = dirname(filepath)
+        for subloader, subname, ispkg in pkgutil.walk_packages([path]):
+            # ignore subpackages (historical behavior)
+            if not ispkg:
+                filepath = subloader.find_module(subname).get_filename()
+                if check_source_file(filepath):
+                    yield modname + '.' + subname, filepath
+
+
 # persistent options definition
 PERSISTENT_OPTIONS = (
     ('encoding',
@@ -667,14 +703,8 @@
         """update python path if necessary"""
         from cubicweb import _CubesImporter
         _CubesImporter.install()
-        cubes_parent_dir = normpath(join(cls.CUBES_DIR, '..'))
-        if not cubes_parent_dir in sys.path:
-            sys.path.insert(0, cubes_parent_dir)
-        try:
-            import cubes
-            cubes.__path__ = cls.cubes_search_path()
-        except ImportError:
-            return # cubes dir doesn't exists
+        import cubes
+        cubes.__path__ = cls.cubes_search_path()
 
     @classmethod
     def load_available_configs(cls):
@@ -692,7 +722,9 @@
                        'devtools.devctl', 'pyramid.pyramidctl'):
             try:
                 __import__('cubicweb.%s' % ctlmod)
-            except ImportError:
+            except ImportError as exc:
+                cls.warning('failed to load cubicweb-ctl plugin %s (%s)',
+                            ctlmod, exc)
                 continue
             cls.info('loaded cubicweb-ctl plugin %s', ctlmod)
         for cube in cls.available_cubes():
@@ -778,57 +810,26 @@
         # configure simpleTal logger
         logging.getLogger('simpleTAL').setLevel(logging.ERROR)
 
-    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.
+    def schema_modnames(self):
+        modnames = []
+        for name in ('bootstrap', 'base', 'workflow', 'Bookmark'):
+            modnames.append(('cubicweb', 'cubicweb.schemas.' + name))
+        for cube in reversed(self.cubes()):
+            for modname, filepath in _expand_modname('cubes.{0}.schema'.format(cube)):
+                modnames.append((cube, modname))
+        if self.apphome:
+            apphome = realpath(self.apphome)
+            for modname, filepath in _expand_modname('schema'):
+                if realpath(filepath).startswith(apphome):
+                    modnames.append(('data', modname))
+        return modnames
+
+    def appobjects_modnames(self):
+        """return a list of modules 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, cubes=()):
@@ -1309,21 +1310,49 @@
             try:
                 tr = translation('cubicweb', path, languages=[language])
                 self.translations[language] = (tr.ugettext, tr.upgettext)
-            except (ImportError, AttributeError, IOError):
+            except IOError:
                 if self.mode != 'test':
                     # in test contexts, data/i18n does not exist, hence
                     # logging will only pollute the logs
                     self.exception('localisation support error for language %s',
                                    language)
 
-    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_appobjects_path(templpath)
+    @staticmethod
+    def _sorted_appobjects(appobjects):
+        appobjects = sorted(appobjects)
+        try:
+            index = appobjects.index('entities')
+        except ValueError:
+            pass
+        else:
+            # put entities first
+            appobjects.insert(0, appobjects.pop(index))
+        return appobjects
+
+    def appobjects_cube_modnames(self, cube):
+        modnames = []
+        cube_submodnames = self._sorted_appobjects(self.cube_appobject_path)
+        for name in cube_submodnames:
+            for modname, filepath in _expand_modname('.'.join(['cubes', cube, name])):
+                modnames.append(modname)
+        return modnames
+
+    def appobjects_modnames(self):
+        modnames = []
+        for name in self._sorted_appobjects(self.cubicweb_appobject_path):
+            for modname, filepath in _expand_modname('cubicweb.' + name):
+                modnames.append(modname)
+        for cube in reversed(self.cubes()):
+            modnames.extend(self.appobjects_cube_modnames(cube))
+        if self.apphome:
+            cube_submodnames = self._sorted_appobjects(self.cube_appobject_path)
+            apphome = realpath(self.apphome)
+            for name in cube_submodnames:
+                for modname, filepath in _expand_modname(name):
+                    # ensure file is in apphome
+                    if realpath(filepath).startswith(apphome):
+                        modnames.append(modname)
+        return modnames
 
     def set_sources_mode(self, sources):
         if not 'all' in sources:
--- a/cubicweb/cwctl.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/cwctl.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -20,15 +20,13 @@
 """
 from __future__ import print_function
 
-
-
 # *ctl module should limit the number of import to be imported as quickly as
 # possible (for cubicweb-ctl reactivity, necessary for instance for usable bash
 # completion). So import locally in command helpers.
 import sys
 from warnings import warn, filterwarnings
 from os import remove, listdir, system, pathsep
-from os.path import exists, join, isfile, isdir, dirname, abspath
+from os.path import exists, join, isdir, dirname, abspath
 
 try:
     from os import kill, getpgid
@@ -43,7 +41,7 @@
 from logilab.common.clcommands import CommandLine
 from logilab.common.shellutils import ASK
 from logilab.common.configuration import merge_options
-from logilab.common.deprecation import deprecated
+from logilab.common.decorators import clear_cache
 
 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CWDEV, CONFIGURATIONS
@@ -54,6 +52,7 @@
 CWCTL = CommandLine('cubicweb-ctl', 'The CubicWeb swiss-knife.',
                     version=version, check_duplicated_command=False)
 
+
 def wait_process_end(pid, maxtry=10, waittime=1):
     """wait for a process to actually die"""
     import signal
@@ -62,19 +61,21 @@
     while nbtry < maxtry:
         try:
             kill(pid, signal.SIGUSR1)
-        except (OSError, AttributeError): # XXX win32
+        except (OSError, AttributeError):  # XXX win32
             break
         nbtry += 1
         sleep(waittime)
     else:
         raise ExecutionError('can\'t kill process %s' % pid)
 
+
 def list_instances(regdir):
     if isdir(regdir):
         return sorted(idir for idir in listdir(regdir) if isdir(join(regdir, idir)))
     else:
         return []
 
+
 def detect_available_modes(templdir):
     modes = []
     for fname in ('schema', 'schema.py'):
@@ -103,13 +104,6 @@
         )
     actionverb = None
 
-    @deprecated('[3.22] startorder is not used any more')
-    def ordered_instances(self):
-        """return list of known instances
-        """
-        regdir = cwcfg.instances_dir()
-        return list_instances(regdir)
-
     def run(self, args):
         """run the <command>_method on each argument (a list of instance
         identifiers)
@@ -756,6 +750,7 @@
             with mih.cnx:
                 with mih.cnx.security_enabled(False, False):
                     mih.migrate(vcconf, reversed(toupgrade), self.config)
+            clear_cache(config, 'instance_md5_version')
         else:
             print('-> no data migration needed for instance %s.' % appid)
         # rewrite main configuration file
--- a/cubicweb/cwvreg.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/cwvreg.py	Fri Feb 03 13:37:32 2017 +0100
@@ -29,7 +29,7 @@
 
 from logilab.common.decorators import cached, clear_cache
 from logilab.common.deprecation import class_deprecated
-from logilab.common.modutils import cleanup_sys_modules
+from logilab.common.modutils import clean_sys_modules
 from logilab.common.registry import RegistryStore, Registry, ObjectNotFound, RegistryNotFound
 
 from rql import RQLHelper
@@ -417,7 +417,7 @@
         """set instance'schema and load application objects"""
         self._set_schema(schema)
         # now we can load application's web objects
-        self.reload(self.config.appobjects_path(), force_reload=False)
+        self.reload(self.config.appobjects_modnames(), force_reload=False)
         # map lowered entity type names to their actual name
         self.case_insensitive_etypes = {}
         for eschema in self.schema.entities():
@@ -426,13 +426,28 @@
             clear_cache(eschema, 'ordered_relations')
             clear_cache(eschema, 'meta_attributes')
 
+    def is_reload_needed(self, modnames):
+        """overriden to handle modules names instead of directories"""
+        lastmodifs = self._lastmodifs
+        for modname in modnames:
+            if modname not in sys.modules:
+                # new module to load
+                return True
+            filepath = sys.modules[modname].__file__
+            if filepath.endswith('.py'):
+                mdate = self._mdate(filepath)
+                if filepath not in lastmodifs or lastmodifs[filepath] < mdate:
+                    self.info('File %s changed since last visit', filepath)
+                    return True
+        return False
+
     def reload_if_needed(self):
-        path = self.config.appobjects_path()
-        if self.is_reload_needed(path):
-            self.reload(path)
+        modnames = self.config.appobjects_modnames()
+        if self.is_reload_needed(modnames):
+            self.reload(modnames)
 
-    def _cleanup_sys_modules(self, path):
-        """Remove submodules of `directories` from `sys.modules` and cleanup
+    def _cleanup_sys_modules(self, modnames):
+        """Remove modules and submodules of `modnames` from `sys.modules` and cleanup
         CW_EVENT_MANAGER accordingly.
 
         We take care to properly remove obsolete registry callbacks.
@@ -446,18 +461,18 @@
                 # for non-function callable, we do nothing interesting
                 module = getattr(func, '__module__', None)
                 caches[id(callback)] = module
-        deleted_modules = set(cleanup_sys_modules(path))
+        deleted_modules = set(clean_sys_modules(modnames))
         for callbacklist in callbackdata:
             for callback in callbacklist[:]:
                 module = caches[id(callback)]
                 if module and module in deleted_modules:
                     callbacklist.remove(callback)
 
-    def reload(self, path, force_reload=True):
+    def reload(self, modnames, force_reload=True):
         """modification detected, reset and reload the vreg"""
         CW_EVENT_MANAGER.emit('before-registry-reload')
         if force_reload:
-            self._cleanup_sys_modules(path)
+            self._cleanup_sys_modules(modnames)
             cubes = self.config.cubes()
             # if the fs code use some cubes not yet registered into the instance
             # we should cleanup sys.modules for those as well to avoid potential
@@ -465,9 +480,9 @@
             cfg = self.config
             for cube in cfg.expand_cubes(cubes, with_recommends=True):
                 if not cube in cubes:
-                    cpath = cfg.build_appobjects_cube_path([cfg.cube_dir(cube)])
-                    self._cleanup_sys_modules(cpath)
-        self.register_objects(path)
+                    cube_modnames = cfg.appobjects_cube_modnames(cube)
+                    self._cleanup_sys_modules(cube_modnames)
+        self.register_modnames(modnames)
         CW_EVENT_MANAGER.emit('after-registry-reload')
 
     def load_file(self, filepath, modname):
--- a/cubicweb/dataimport/importer.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/dataimport/importer.py	Fri Feb 03 13:37:32 2017 +0100
@@ -265,7 +265,7 @@
     :param extid2eid: optional {extid: eid} dictionary giving information on existing entities. It
         will be completed during import. You may want to use :func:`cwuri2eid` to build it.
 
-    :param existing_relation: optional {rtype: set((subj eid, obj eid))} mapping giving information
+    :param existing_relations: optional {rtype: set((subj eid, obj eid))} mapping giving information
         on existing relations of a given type. You may want to use :class:`RelationMapping` to build
         it.
 
--- a/cubicweb/dataimport/massive_store.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/dataimport/massive_store.py	Fri Feb 03 13:37:32 2017 +0100
@@ -72,7 +72,6 @@
 
         self.uuid = text_type(uuid4()).replace('-', '')
         self.slave_mode = slave_mode
-        self.eids_seq_range = eids_seq_range
         if metagen is None:
             metagen = stores.MetadataGenerator(cnx)
         self.metagen = metagen
@@ -81,7 +80,7 @@
         self.sql = cnx.system_sql
         self.schema = cnx.vreg.schema
         self.default_values = get_default_values(self.schema)
-        self.get_next_eid = lambda g=self._get_eid_gen(): next(g)
+        self.get_next_eid = lambda g=self._get_eid_gen(eids_seq_range): next(g)
         self._source_dbhelper = cnx.repo.system_source.dbhelper
         self._dbh = PGHelper(cnx)
 
@@ -89,13 +88,13 @@
         self._data_relations = defaultdict(list)
         self._initialized = {}
 
-    def _get_eid_gen(self):
+    def _get_eid_gen(self, eids_seq_range):
         """ Function getting the next eid. This is done by preselecting
         a given number of eids from the 'entities_id_seq', and then
         storing them"""
         while True:
-            last_eid = self._cnx.repo.system_source.create_eid(self._cnx, self.eids_seq_range)
-            for eid in range(last_eid - self.eids_seq_range + 1, last_eid + 1):
+            last_eid = self._cnx.repo.system_source.create_eid(self._cnx, eids_seq_range)
+            for eid in range(last_eid - eids_seq_range + 1, last_eid + 1):
                 yield eid
 
     # master/slaves specific API
--- a/cubicweb/dataimport/test/test_massive_store.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/dataimport/test/test_massive_store.py	Fri Feb 03 13:37:32 2017 +0100
@@ -16,8 +16,6 @@
 # with this program. If not, see <http://www.gnu.org/licenses/>.
 """Massive store test case"""
 
-import itertools
-
 from cubicweb.devtools import testlib, PostgresApptestConfiguration
 from cubicweb.devtools import startpgcluster, stoppgcluster
 from cubicweb.dataimport import ucsvreader, stores
--- a/cubicweb/devtools/devctl.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/devtools/devctl.py	Fri Feb 03 13:37:32 2017 +0100
@@ -37,6 +37,7 @@
 from six.moves import input
 
 from logilab.common import STD_BLACKLIST
+from logilab.common.modutils import clean_sys_modules
 from logilab.common.fileutils import ensure_fs_mode
 from logilab.common.shellutils import find
 
@@ -100,24 +101,6 @@
         return None
 
 
-def cleanup_sys_modules(config):
-    # cleanup sys.modules, required when we're updating multiple cubes
-    appobjects_path = config.appobjects_path()
-    for name, mod in list(sys.modules.items()):
-        if mod is None:
-            # duh ? logilab.common.os for instance
-            del sys.modules[name]
-            continue
-        if not hasattr(mod, '__file__'):
-            continue
-        if mod.__file__ is None:
-            # odd/rare but real
-            continue
-        for path in appobjects_path:
-            if mod.__file__.startswith(path):
-                del sys.modules[name]
-                break
-
 def generate_schema_pot(w, cubedir=None):
     """generate a pot file with schema specific i18n messages
 
@@ -136,7 +119,7 @@
     else:
         config = DevConfiguration()
         cube = libconfig = None
-    cleanup_sys_modules(config)
+    clean_sys_modules(config.appobjects_modnames())
     schema = config.load_schema(remove_unused_rtypes=False)
     vreg = CWRegistryStore(config)
     # set_schema triggers objects registrations
@@ -161,7 +144,7 @@
         # (cubicweb incl.)
         from cubicweb.cwvreg import CWRegistryStore
         libschema = libconfig.load_schema(remove_unused_rtypes=False)
-        cleanup_sys_modules(libconfig)
+        clean_sys_modules(libconfig.appobjects_modnames())
         libvreg = CWRegistryStore(libconfig)
         libvreg.set_schema(libschema) # trigger objects registration
         libafss = libvreg['uicfg']['autoform_section']
--- a/cubicweb/devtools/fake.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/devtools/fake.py	Fri Feb 03 13:37:32 2017 +0100
@@ -34,7 +34,6 @@
 class FakeConfig(dict, BaseApptestConfiguration):
     translations = {}
     uiprops = {}
-    https_uiprops = {}
     apphome = None
     debugmode = False
     def __init__(self, appid='data', apphome=None, cubes=()):
@@ -46,7 +45,6 @@
         self['base-url'] = BASE_URL
         self['rql-cache-size'] = 3000
         self.datadir_url = BASE_URL + 'data/'
-        self.https_datadir_url = (BASE_URL + 'data/').replace('http://', 'https://')
 
     def cubes(self, expand=False):
         return self._cubes
@@ -69,7 +67,6 @@
     def __init__(self, *args, **kwargs):
         if not (args or 'vreg' in kwargs):
             kwargs['vreg'] = FakeCWRegistryStore(FakeConfig(), initlog=False)
-        kwargs['https'] = False
         self._http_method = kwargs.pop('method', 'GET')
         self._url = kwargs.pop('url', None)
         if self._url is None:
--- a/cubicweb/devtools/test/unittest_devctl.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/devtools/test/unittest_devctl.py	Fri Feb 03 13:37:32 2017 +0100
@@ -20,11 +20,11 @@
 import os
 import os.path as osp
 import sys
-import tempfile
-import shutil
 from subprocess import Popen, PIPE, STDOUT, check_output
 from unittest import TestCase
 
+from cubicweb.devtools.testlib import TemporaryDirectory
+
 
 def newcube(directory, name):
     cmd = ['cubicweb-ctl', 'newcube', '--directory', directory, name]
@@ -47,12 +47,11 @@
         expected_project_content = ['setup.py', 'test', 'MANIFEST.in',
                                     'cubicweb_foo',
                                     'cubicweb-foo.spec', 'debian', 'README',
-                                    'tox.ini']
+                                    'tox.ini', 'development.ini']
         expected_package_content = ['i18n', 'hooks.py', 'views.py',
                                     'migration', 'entities.py', 'schema.py',
                                     '__init__.py', 'data', '__pkginfo__.py']
-        tmpdir = tempfile.mkdtemp(prefix="temp-cwctl-newcube")
-        try:
+        with TemporaryDirectory(prefix="temp-cwctl-newcube") as tmpdir:
             retcode, stdout = newcube(tmpdir, 'foo')
             self.assertEqual(retcode, 0, msg=to_unicode(stdout))
             project_dir = osp.join(tmpdir, 'cubicweb-foo')
@@ -61,27 +60,21 @@
             package_content = os.listdir(package_dir)
             self.assertItemsEqual(project_content, expected_project_content)
             self.assertItemsEqual(package_content, expected_package_content)
-        finally:
-            shutil.rmtree(tmpdir, ignore_errors=True)
 
     def test_flake8(self):
         """Ensure newcube built from skeleton is flake8-compliant"""
-        tmpdir = tempfile.mkdtemp(prefix="temp-cwctl-newcube-flake8")
-        try:
+        with TemporaryDirectory(prefix="temp-cwctl-newcube-flake8") as tmpdir:
             newcube(tmpdir, 'foo')
             cmd = [sys.executable, '-m', 'flake8',
                    osp.join(tmpdir, 'cubicweb-foo', 'cubicweb_foo')]
             proc = Popen(cmd, stdout=PIPE, stderr=STDOUT)
             retcode = proc.wait()
-        finally:
-            shutil.rmtree(tmpdir, ignore_errors=True)
         self.assertEqual(retcode, 0,
                          msg=to_unicode(proc.stdout.read()))
 
     def test_newcube_sdist(self):
         """Ensure sdist can be built from a new cube"""
-        tmpdir = tempfile.mkdtemp(prefix="temp-cwctl-newcube-sdist")
-        try:
+        with TemporaryDirectory(prefix="temp-cwctl-newcube-sdist") as tmpdir:
             newcube(tmpdir, 'foo')
             projectdir = osp.join(tmpdir, 'cubicweb-foo')
             cmd = [sys.executable, 'setup.py', 'sdist']
@@ -91,13 +84,10 @@
             self.assertEqual(retcode, 0, stdout)
             distfpath = osp.join(projectdir, 'dist', 'cubicweb-foo-0.1.0.tar.gz')
             self.assertTrue(osp.isfile(distfpath))
-        finally:
-            shutil.rmtree(tmpdir, ignore_errors=True)
 
     def test_newcube_install(self):
         """Ensure a new cube can be installed"""
-        tmpdir = tempfile.mkdtemp(prefix="temp-cwctl-newcube-install")
-        try:
+        with TemporaryDirectory(prefix="temp-cwctl-newcube-install") as tmpdir:
             newcube(tmpdir, 'foo')
             projectdir = osp.join(tmpdir, 'cubicweb-foo')
             env = os.environ.copy()
@@ -120,8 +110,6 @@
             self.assertItemsEqual(pkgcontent,
                                   [b'schema.py', b'entities.py', b'hooks.py', b'__init__.py',
                                    b'__pkginfo__.py', b'views.py'])
-        finally:
-            shutil.rmtree(tmpdir, ignore_errors=True)
 
 
 if __name__ == '__main__':
--- a/cubicweb/devtools/testlib.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/devtools/testlib.py	Fri Feb 03 13:37:32 2017 +0100
@@ -679,7 +679,8 @@
     def list_boxes_for(self, rset):
         """returns the list of boxes that can be applied on `rset`"""
         req = rset.req
-        for box in self.vreg['ctxcomponents'].possible_objects(req, rset=rset):
+        for box in self.vreg['ctxcomponents'].possible_objects(req, rset=rset,
+                                                               view=None):
             yield box
 
     def list_startup_views(self):
--- a/cubicweb/entities/__init__.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/entities/__init__.py	Fri Feb 03 13:37:32 2017 +0100
@@ -98,57 +98,15 @@
 
     # meta data api ###########################################################
 
-    def dc_title(self):
-        """return a suitable *unicode* title for this entity"""
-        for rschema, attrschema in self.e_schema.attribute_definitions():
-            if rschema.meta:
-                continue
-            value = self.cw_attr_value(rschema.type)
-            if value is not None:
-                # make the value printable (dates, floats, bytes, etc.)
-                return self.printable_value(rschema.type, value, attrschema.type,
-                                            format='text/plain')
-        return u'%s #%s' % (self.dc_type(), self.eid)
-
-    def dc_long_title(self):
-        """return a more detailled title for this entity"""
-        return self.dc_title()
-
-    def dc_description(self, format='text/plain'):
-        """return a suitable description for this entity"""
-        if 'description' in self.e_schema.subjrels:
-            return self.printable_value('description', format=format)
-        return u''
-
-    def dc_authors(self):
-        """return a suitable description for the author(s) of the entity"""
-        try:
-            return ', '.join(u.name() for u in self.owned_by)
-        except Unauthorized:
-            return u''
-
-    def dc_creator(self):
-        """return a suitable description for the creator of the entity"""
-        if self.creator:
-            return self.creator.name()
-        return u''
-
-    def dc_date(self, date_format=None):# XXX default to ISO 8601 ?
-        """return latest modification date of this entity"""
-        return self._cw.format_date(self.modification_date, date_format=date_format)
-
-    def dc_type(self, form=''):
-        """return the display name for the type of this entity (translated)"""
-        return self.e_schema.display_name(self._cw, form)
-
-    def dc_language(self):
-        """return language used by this entity (translated)"""
-        # check if entities has internationalizable attributes
-        # XXX one is enough or check if all String attributes are internationalizable?
-        for rschema, attrschema in self.e_schema.attribute_definitions():
-            if rschema.rdef(self.e_schema, attrschema).internationalizable:
-                return self._cw._(self._cw.user.property_value('ui.language'))
-        return self._cw._(self._cw.vreg.property_value('ui.language'))
+    def __getattr__(self, name):
+        prefix = 'dc_'
+        if name.startswith(prefix):
+            # Proxy to IDublinCore adapter for bw compat.
+            adapted = self.cw_adapt_to('IDublinCore')
+            method = name[len(prefix):]
+            if hasattr(adapted, method):
+                return getattr(adapted, method)
+        raise AttributeError(name)
 
     @property
     def creator(self):
--- a/cubicweb/entities/adapters.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/entities/adapters.py	Fri Feb 03 13:37:32 2017 +0100
@@ -25,10 +25,71 @@
 from logilab.mtconverter import TransformError
 from logilab.common.decorators import cached
 
-from cubicweb import ValidationError, view, ViolatedConstraint, UniqueTogetherError
+from cubicweb import (Unauthorized, ValidationError, view, ViolatedConstraint,
+                      UniqueTogetherError)
 from cubicweb.predicates import is_instance, relation_possible, match_exception
 
 
+class IDublinCoreAdapter(view.EntityAdapter):
+    __regid__ = 'IDublinCore'
+    __select__ = is_instance('Any')
+
+    def title(self):
+        """Return a suitable *unicode* title for entity"""
+        entity = self.entity
+        for rschema, attrschema in entity.e_schema.attribute_definitions():
+            if rschema.meta:
+                continue
+            value = entity.cw_attr_value(rschema.type)
+            if value is not None:
+                # make the value printable (dates, floats, bytes, etc.)
+                return entity.printable_value(
+                    rschema.type, value, attrschema.type, format='text/plain')
+        return u'%s #%s' % (self.type(), entity.eid)
+
+    def long_title(self):
+        """Return a more detailled title for entity"""
+        return self.title()
+
+    def description(self, format='text/plain'):
+        """Return a suitable description for entity"""
+        if 'description' in self.entity.e_schema.subjrels:
+            return self.entity.printable_value('description', format=format)
+        return u''
+
+    def authors(self):
+        """Return a suitable description for the author(s) of the entity"""
+        try:
+            return u', '.join(u.name() for u in self.entity.owned_by)
+        except Unauthorized:
+            return u''
+
+    def creator(self):
+        """Return a suitable description for the creator of the entity"""
+        if self.entity.creator:
+            return self.entity.creator.name()
+        return u''
+
+    def date(self, date_format=None):  # XXX default to ISO 8601 ?
+        """Return latest modification date of entity"""
+        return self._cw.format_date(self.entity.modification_date,
+                                    date_format=date_format)
+
+    def type(self, form=''):
+        """Return the display name for the type of entity (translated)"""
+        return self.entity.e_schema.display_name(self._cw, form)
+
+    def language(self):
+        """Return language used by this entity (translated)"""
+        eschema = self.entity.e_schema
+        # check if entities has internationalizable attributes
+        # XXX one is enough or check if all String attributes are internationalizable?
+        for rschema, attrschema in eschema.attribute_definitions():
+            if rschema.rdef(eschema, attrschema).internationalizable:
+                return self._cw._(self._cw.user.property_value('ui.language'))
+        return self._cw._(self._cw.vreg.property_value('ui.language'))
+
+
 class IEmailableAdapter(view.EntityAdapter):
     __regid__ = 'IEmailable'
     __select__ = relation_possible('primary_email') | relation_possible('use_email')
--- a/cubicweb/entity.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/entity.py	Fri Feb 03 13:37:32 2017 +0100
@@ -985,11 +985,10 @@
             if not safe:
                 raise
             rset = self._cw.empty_rset()
+        if cacheable:
+            self.cw_set_relation_cache(rtype, role, rset)
         if entities:
-            if cacheable:
-                self.cw_set_relation_cache(rtype, role, rset)
-                return self.related(rtype, role, entities=entities)
-            return list(rset.entities())
+            return tuple(rset.entities())
         else:
             return rset
 
@@ -1251,7 +1250,7 @@
     def cw_set_relation_cache(self, rtype, role, rset):
         """set cached values for the given relation"""
         if rset:
-            related = list(rset.entities(0))
+            related = tuple(rset.entities(0))
             rschema = self._cw.vreg.schema.rschema(rtype)
             if role == 'subject':
                 rcard = rschema.rdef(self.e_schema, related[0].e_schema).cardinality[1]
--- a/cubicweb/etwist/http.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/etwist/http.py	Fri Feb 03 13:37:32 2017 +0100
@@ -8,6 +8,7 @@
 
 
 
+
 class HTTPResponse(object):
     """An object representing an HTTP Response to be sent to the client.
     """
@@ -29,9 +30,14 @@
         # add content-length if not present
         if (self._headers_out.getHeader('content-length') is None
             and self._stream is not None):
-           self._twreq.setHeader('content-length', len(self._stream))
+            self._twreq.setHeader('content-length', len(self._stream))
 
     def _finalize(self):
+        # cw_failed is set on errors such as "connection aborted by client". In
+        # such cases, req.finish() was already called and calling it a twice
+        # would crash
+        if getattr(self._twreq, 'cw_failed', False):
+            return
         # we must set code before writing anything, else it's too late
         if self._code is not None:
             self._twreq.setResponseCode(self._code)
--- a/cubicweb/etwist/request.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/etwist/request.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,7 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """Twisted request handler for CubicWeb"""
 
-
-
+from six import text_type
 
 from cubicweb.web.request import CubicWebRequestBase
 
@@ -27,19 +26,19 @@
     """ from twisted .req to cubicweb .form
     req.files are put into .form[<filefield>]
     """
-    def __init__(self, req, vreg, https):
+    def __init__(self, req, vreg):
         self._twreq = req
         super(CubicWebTwistedRequestAdapter, self).__init__(
-            vreg, https, req.args, headers=req.received_headers)
+            vreg, req.args, headers=req.received_headers)
         for key, name_stream_list in req.files.items():
             for name, stream in name_stream_list:
                 if name is not None:
-                    name = unicode(name, self.encoding)
+                    name = text_type(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
+        self.content = self._twreq.content  # stream
 
     def http_method(self):
         """returns 'POST', 'GET', 'HEAD', etc."""
@@ -53,7 +52,7 @@
         :param includeparams:
            boolean indicating if GET form parameters should be kept in the path
         """
-        path = self._twreq.uri[1:] # remove the root '/'
+        path = self._twreq.uri[1:]  # remove the root '/'
         if not includeparams:
             path = path.split('?', 1)[0]
         return path
--- a/cubicweb/etwist/server.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/etwist/server.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,14 +17,11 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """twisted server for CubicWeb web instances"""
 
-
 import sys
-import select
 import traceback
 import threading
 from cgi import FieldStorage, parse_header
-
-from six.moves.urllib.parse import urlsplit, urlunsplit
+from functools import partial
 
 from cubicweb.statsd_logger import statsd_timeit
 
@@ -44,6 +41,7 @@
 from cubicweb.etwist.request import CubicWebTwistedRequestAdapter
 from cubicweb.etwist.http import HTTPResponse
 
+
 def start_task(interval, func):
     lc = task.LoopingCall(func)
     # wait until interval has expired to actually start the task, else we have
@@ -59,7 +57,6 @@
         # checks done before daemonization (eg versions consistency)
         self.appli = CubicWebPublisher(repo, config)
         self.base_url = config['base-url']
-        self.https_url = config['https-url']
         global MAX_POST_LENGTH
         MAX_POST_LENGTH = config['max-post-length']
 
@@ -92,13 +89,20 @@
         """Indicate which resource to use to process down the URL's path"""
         return self
 
+    def on_request_finished_ko(self, request, reason):
+        # annotate the twisted request so that we're able later to check for
+        # failure without having to dig into request's internal attributes such
+        # as _disconnected
+        request.cw_failed = True
+        self.warning('request finished abnormally: %s', reason)
+
     def render(self, request):
         """Render a page from the root resource"""
+        finish_deferred = request.notifyFinish()
+        finish_deferred.addErrback(partial(self.on_request_finished_ko, request))
         # reload modified files in debug mode
         if self.config.debugmode:
             self.config.uiprops.reload_if_needed()
-            if self.https_url:
-                self.config.https_uiprops.reload_if_needed()
             self.appli.vreg.reload_if_needed()
         if self.config['profile']: # default profiler don't trace threads
             return self.render_request(request)
@@ -123,18 +127,11 @@
     def _render_request(self, request):
         origpath = request.path
         host = request.host
-        # dual http/https access handling: expect a rewrite rule to prepend
-        # 'https' to the path to detect https access
-        https = False
-        if origpath.split('/', 2)[1] == 'https':
-            origpath = origpath[6:]
-            request.uri = request.uri[6:]
-            https = True
         if self.url_rewriter is not None:
             # XXX should occur before authentication?
             path = self.url_rewriter.rewrite(host, origpath, request)
             request.uri.replace(origpath, path, 1)
-        req = CubicWebTwistedRequestAdapter(request, self.appli.vreg, https)
+        req = CubicWebTwistedRequestAdapter(request, self.appli.vreg)
         try:
             ### Try to generate the actual request content
             content = self.appli.handle_request(req)
--- a/cubicweb/etwist/service.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/etwist/service.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -38,15 +38,17 @@
 from cubicweb import set_log_methods
 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg
 
+
 def _check_env(env):
     env_vars = ('CW_INSTANCES_DIR', 'CW_INSTANCES_DATA_DIR', 'CW_RUNTIME_DIR')
     for var in env_vars:
         if var not in env:
-            raise Exception('The environment variables %s must be set.' % \
+            raise Exception('The environment variables %s must be set.' %
                             ', '.join(env_vars))
     if not env.get('USERNAME'):
         env['USERNAME'] = 'cubicweb'
 
+
 class CWService(object, win32serviceutil.ServiceFramework):
     _svc_name_ = None
     _svc_display_name_ = None
@@ -80,8 +82,7 @@
             config.debugmode = False
             logger.info('starting cubicweb instance %s ', self.instance)
             config.info('clear ui caches')
-            for cachedir in ('uicache', 'uicachehttps'):
-                rm(join(config.appdatahome, cachedir, '*'))
+            rm(join(config.appdatahome, 'uicache', '*'))
             root_resource = CubicWebRootResource(config, config.repository())
             website = server.Site(root_resource)
             # serve it via standard HTTP on port set in the configuration
--- a/cubicweb/hooks/test/unittest_security.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/hooks/test/unittest_security.py	Fri Feb 03 13:37:32 2017 +0100
@@ -51,6 +51,7 @@
                     cnx.commit()
                     self.assertEqual(email.sender[0].eid, self.add_eid)
 
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/cubicweb/pyramid/bwcompat.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/pyramid/bwcompat.py	Fri Feb 03 13:37:32 2017 +0100
@@ -53,10 +53,6 @@
         CubicWebPublisher.core_handle do
         """
 
-        # XXX The main handler of CW forbid anonymous https connections
-        # I guess we can drop this "feature" but in doubt I leave this comment
-        # so we don't forget about it. (cdevienne)
-
         req = request.cw_request
         vreg = request.registry['cubicweb.registry']
 
@@ -170,10 +166,6 @@
         self.cwhandler = registry['cubicweb.handler']
 
     def __call__(self, request):
-        if request.path.startswith('/https/'):
-            request.environ['PATH_INFO'] = request.environ['PATH_INFO'][6:]
-            assert not request.path.startswith('/https/')
-            request.scheme = 'https'
         try:
             response = self.handler(request)
         except httpexceptions.HTTPNotFound:
--- a/cubicweb/pyramid/core.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/pyramid/core.py	Fri Feb 03 13:37:32 2017 +0100
@@ -126,12 +126,11 @@
         self.path = request.upath_info
 
         vreg = request.registry['cubicweb.registry']
-        https = request.scheme == 'https'
 
         post = request.params.mixed()
         headers_in = request.headers
 
-        super(CubicWebPyramidRequest, self).__init__(vreg, https, post,
+        super(CubicWebPyramidRequest, self).__init__(vreg, post,
                                                      headers=headers_in)
 
         self.content = request.body_file_seekable
@@ -157,9 +156,6 @@
             else:
                 self.form[param] = val
 
-    def is_secure(self):
-        return self._request.scheme == 'https'
-
     def relative_path(self, includeparams=True):
         path = self._request.path_info[1:]
         if includeparams and self._request.query_string:
@@ -213,7 +209,7 @@
 
     :param request: A pyramid request
     :param vid: A CubicWeb view id
-    :param **kwargs: Keyword arguments to select and instanciate the view
+    :param kwargs: Keyword arguments to select and instanciate the view
     :returns: The rendered view content
     """
     vreg = request.registry['cubicweb.registry']
--- a/cubicweb/pyramid/pyramidctl.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/pyramid/pyramidctl.py	Fri Feb 03 13:37:32 2017 +0100
@@ -15,7 +15,7 @@
 import threading
 import subprocess
 
-from cubicweb import BadCommandUsage, ExecutionError
+from cubicweb import ExecutionError
 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg
 from cubicweb.cwctl import CWCTL, InstanceCommand, init_cmdline_log_threshold
 from cubicweb.pyramid import wsgi_application_from_cwconfig
@@ -99,15 +99,6 @@
     def info(self, msg):
         print('INFO - %s' % msg)
 
-    def ordered_instances(self):
-        instances = super(PyramidStartHandler, self).ordered_instances()
-        if (self['debug-mode'] or self['debug'] or self['reload']) \
-                and len(instances) > 1:
-            raise BadCommandUsage(
-                '--debug-mode, --debug and --reload can be used on a single '
-                'instance only')
-        return instances
-
     def quote_first_command_arg(self, arg):
         """
         There's a bug in Windows when running an executable that's
--- a/cubicweb/pyramid/test/__init__.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/pyramid/test/__init__.py	Fri Feb 03 13:37:32 2017 +0100
@@ -11,10 +11,7 @@
     @classmethod
     def init_config(cls, config):
         super(PyramidCWTest, cls).init_config(config)
-        config.global_set_option('https-url', 'https://localhost.local/')
         config.global_set_option('anonymous-user', 'anon')
-        config.https_uiprops = None
-        config.https_datadir_url = None
 
     def setUp(self):
         # Skip CubicWebTestTC setUp
--- a/cubicweb/pyramid/test/test_bw_request.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/pyramid/test/test_bw_request.py	Fri Feb 03 13:37:32 2017 +0100
@@ -33,24 +33,6 @@
 
         self.assertEqual(b'some content', req.content.read())
 
-    def test_http_scheme(self):
-        req = CubicWebPyramidRequest(
-            self.make_request('/', {
-                'wsgi.url_scheme': 'http'}))
-
-        self.assertFalse(req.https)
-
-    def test_https_scheme(self):
-        req = CubicWebPyramidRequest(
-            self.make_request('/', {
-                'wsgi.url_scheme': 'https'}))
-
-        self.assertTrue(req.https)
-
-    def test_https_prefix(self):
-        r = self.webapp.get('/https/')
-        self.assertIn('https://', r.text)
-
     def test_big_content(self):
         content = b'x' * 100001
 
--- a/cubicweb/pyramid/test/test_login.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/pyramid/test/test_login.py	Fri Feb 03 13:37:32 2017 +0100
@@ -3,6 +3,7 @@
 
 from cubicweb.pyramid.test import PyramidCWTest
 
+
 class LoginTestLangUrlPrefix(PyramidCWTest):
 
     @classmethod
@@ -19,7 +20,6 @@
         self.assertEqual(res.status_int, 303)
 
 
-
 class LoginTest(PyramidCWTest):
 
     def test_login_form(self):
--- a/cubicweb/req.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/req.py	Fri Feb 03 13:37:32 2017 +0100
@@ -278,9 +278,6 @@
         parameters. Values are automatically URL quoted, and the
         publishing method to use may be specified or will be guessed.
 
-        if ``__secure__`` argument is True, the request will try to build a
-        https url.
-
         raises :exc:`ValueError` if None is found in arguments
         """
         # use *args since we don't want first argument to be "anonymous" to
@@ -295,8 +292,10 @@
         #     not try to process it and directly call req.build_url()
         base_url = kwargs.pop('base_url', None)
         if base_url is None:
-            secure = kwargs.pop('__secure__', None)
-            base_url = self.base_url(secure=secure)
+            if kwargs.pop('__secure__', None) is not None:
+                warn('[3.25] __secure__ argument is deprecated',
+                     DeprecationWarning, stacklevel=2)
+            base_url = self.base_url()
         path = self.build_url_path(method, kwargs)
         if not kwargs:
             return u'%s%s' % (base_url, path)
@@ -327,9 +326,9 @@
         necessary encoding / decoding. Also it's designed to quote each
         part of a url path and so the '/' character will be encoded as well.
         """
-        if PY2 and isinstance(value, unicode):
+        if PY2 and isinstance(value, text_type):
             quoted = urlquote(value.encode(self.encoding), safe=safe)
-            return unicode(quoted, self.encoding)
+            return text_type(quoted, self.encoding)
         return urlquote(str(value), safe=safe)
 
     def url_unquote(self, quoted):
@@ -340,12 +339,12 @@
         """
         if PY3:
             return urlunquote(quoted)
-        if isinstance(quoted, unicode):
+        if isinstance(quoted, text_type):
             quoted = quoted.encode(self.encoding)
         try:
-            return unicode(urlunquote(quoted), self.encoding)
+            return text_type(urlunquote(quoted), self.encoding)
         except UnicodeDecodeError:  # might occurs on manually typed URLs
-            return unicode(urlunquote(quoted), 'iso-8859-1')
+            return text_type(urlunquote(quoted), 'iso-8859-1')
 
     def url_parse_qsl(self, querystring):
         """return a list of (key, val) found in the url quoted query string"""
@@ -353,13 +352,13 @@
             for key, val in parse_qsl(querystring):
                 yield key, val
             return
-        if isinstance(querystring, unicode):
+        if isinstance(querystring, text_type):
             querystring = querystring.encode(self.encoding)
         for key, val in parse_qsl(querystring):
             try:
-                yield unicode(key, self.encoding), unicode(val, self.encoding)
+                yield text_type(key, self.encoding), text_type(val, self.encoding)
             except UnicodeDecodeError:  # might occurs on manually typed URLs
-                yield unicode(key, 'iso-8859-1'), unicode(val, 'iso-8859-1')
+                yield text_type(key, 'iso-8859-1'), text_type(val, 'iso-8859-1')
 
     def rebuild_url(self, url, **newparams):
         """return the given url with newparams inserted. If any new params
@@ -367,7 +366,7 @@
 
         newparams may only be mono-valued.
         """
-        if PY2 and isinstance(url, unicode):
+        if PY2 and isinstance(url, text_type):
             url = url.encode(self.encoding)
         schema, netloc, path, query, fragment = urlsplit(url)
         query = parse_qs(query)
@@ -439,7 +438,7 @@
             as_string = formatters[attrtype]
         except KeyError:
             self.error('given bad attrtype %s', attrtype)
-            return unicode(value)
+            return text_type(value)
         return as_string(value, self, props, displaytime)
 
     def format_date(self, date, date_format=None, time=False):
@@ -502,13 +501,12 @@
             raise ValueError(self._('can\'t parse %(value)r (expected %(format)s)')
                              % {'value': value, 'format': format})
 
-    def _base_url(self, secure=None):
-        if secure:
-            return self.vreg.config.get('https-url') or self.vreg.config['base-url']
-        return self.vreg.config['base-url']
-
-    def base_url(self, secure=None):
-        """return the root url of the instance
-        """
-        url = self._base_url(secure=secure)
+    def base_url(self, **kwargs):
+        """Return the root url of the instance."""
+        secure = kwargs.pop('secure', None)
+        if secure is not None:
+            warn('[3.25] secure argument is deprecated', DeprecationWarning, stacklevel=2)
+        if kwargs:
+            raise TypeError('base_url got unexpected keyword arguments %s' % ', '.join(kwargs))
+        url = self.vreg.config['base-url']
         return url if url is None else url.rstrip('/') + '/'
--- a/cubicweb/rqlrewrite.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/rqlrewrite.py	Fri Feb 03 13:37:32 2017 +0100
@@ -580,6 +580,7 @@
                     done.add(rel)
                     rschema = get_rschema(rel.r_type)
                     if rschema.final or rschema.inlined:
+                        subselect_vrefs = []
                         rel.children[0].name = varname # XXX explain why
                         subselect.add_restriction(rel.copy(subselect))
                         for vref in rel.children[1].iget_nodes(n.VariableRef):
@@ -592,6 +593,7 @@
                                     "least uninline %s" % rel.r_type)
                             subselect.append_selected(vref.copy(subselect))
                             aliases.append(vref.name)
+                            subselect_vrefs.append(vref)
                         self.select.remove_node(rel)
                         # when some inlined relation has to be copied in the
                         # subquery and that relation is optional, we need to
@@ -602,14 +604,15 @@
                         # also, if some attributes or inlined relation of the
                         # object variable are accessed, we need to get all those
                         # from the subquery as well
-                        if vref.name not in done and rschema.inlined:
-                            # we can use vref here define in above for loop
-                            ostinfo = vref.variable.stinfo
-                            for orel in iter_relations(ostinfo):
-                                orschema = get_rschema(orel.r_type)
-                                if orschema.final or orschema.inlined:
-                                    todo.append( (vref.name, ostinfo) )
-                                    break
+                        for vref in subselect_vrefs:
+                            if vref.name not in done and rschema.inlined:
+                                # we can use vref here define in above for loop
+                                ostinfo = vref.variable.stinfo
+                                for orel in iter_relations(ostinfo):
+                                    orschema = get_rschema(orel.r_type)
+                                    if orschema.final or orschema.inlined:
+                                        todo.append( (vref.name, ostinfo) )
+                                        break
             if need_null_test:
                 snippetrqlst = n.Or(
                     n.make_relation(subselect.get_variable(selectvar), 'is',
--- a/cubicweb/rset.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/rset.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 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,7 @@
 
 from warnings import warn
 
-from six import PY3
+from six import PY3, text_type
 from six.moves import range
 
 from logilab.common import nullobject
@@ -73,7 +73,7 @@
         # set by the cursor which returned this resultset
         self.req = None
         # actions cache
-        self._rsetactions = None
+        self._actions_cache = None
 
     def __str__(self):
         if not self.rows:
@@ -94,25 +94,26 @@
 
         if not self.description:
             return pattern % (self.rql, len(self.rows),
-                                                     '\n'.join(str(r) for r in rows))
+                              '\n'.join(str(r) for r in rows))
         return pattern % (self.rql, len(self.rows),
-                                                 '\n'.join('%s (%s)' % (r, d)
-                                                           for r, d in zip(rows, self.description)))
+                          '\n'.join('%s (%s)' % (r, d)
+                                    for r, d in zip(rows, self.description)))
 
     def possible_actions(self, **kwargs):
-        if self._rsetactions is None:
-            self._rsetactions = {}
-        if kwargs:
-            key = tuple(sorted(kwargs.items()))
-        else:
-            key = None
-        try:
-            return self._rsetactions[key]
-        except KeyError:
+        """Return possible actions on this result set. Should always be called with the same
+        arguments so it may be computed only once.
+        """
+        key = tuple(sorted(kwargs.items()))
+        if self._actions_cache is None:
             actions = self.req.vreg['actions'].poss_visible_objects(
                 self.req, rset=self, **kwargs)
-            self._rsetactions[key] = actions
+            self._actions_cache = (key, actions)
             return actions
+        else:
+            assert key == self._actions_cache[0], \
+                'unexpected new arguments for possible actions (%s vs %s)' % (
+                    key, self._actions_cache[0])
+            return self._actions_cache[1]
 
     def __len__(self):
         """returns the result set's size"""
@@ -120,7 +121,7 @@
 
     def __getitem__(self, i):
         """returns the ith element of the result set"""
-        return self.rows[i] #ResultSetRow(self.rows[i])
+        return self.rows[i]
 
     def __iter__(self):
         """Returns an iterator over rows"""
@@ -132,7 +133,7 @@
         # at least rql could be fixed now that we have union and sub-queries
         # but I tend to think that since we have that, we should not need this
         # method anymore (syt)
-        rset = ResultSet(self.rows+rset.rows, self.rql, self.args,
+        rset = ResultSet(self.rows + rset.rows, self.rql, self.args,
                          self.description + rset.description)
         rset.req = self.req
         return rset
@@ -163,7 +164,7 @@
         rset = self.copy(rows, descr)
         for row, desc in zip(self.rows, self.description):
             nrow, ndesc = transformcb(row, desc)
-            if ndesc: # transformcb returns None for ndesc to skip that row
+            if ndesc:  # transformcb returns None for ndesc to skip that row
                 rows.append(nrow)
                 descr.append(ndesc)
         rset.rowcount = len(rows)
@@ -192,7 +193,6 @@
         rset.rowcount = len(rows)
         return rset
 
-
     def sorted_rset(self, keyfunc, reverse=False, col=0):
         """sorts the result set according to a given keyfunc
 
@@ -308,7 +308,7 @@
             newselect = stmts.Select()
             newselect.limit = limit
             newselect.offset = offset
-            aliases = [nodes.VariableRef(newselect.get_variable(chr(65+i), i))
+            aliases = [nodes.VariableRef(newselect.get_variable(chr(65 + i), i))
                        for i in range(len(rqlst.children[0].selection))]
             for vref in aliases:
                 newselect.append_selected(nodes.VariableRef(vref.variable))
@@ -336,7 +336,7 @@
 
         :rtype: `ResultSet`
         """
-        stop = limit+offset
+        stop = limit + offset
         rows = self.rows[offset:stop]
         descr = self.description[offset:stop]
         if inplace:
@@ -375,11 +375,11 @@
             return rqlstr
         # sounds like we get encoded or unicode string due to a bug in as_string
         if not encoded:
-            if isinstance(rqlstr, unicode):
+            if isinstance(rqlstr, text_type):
                 return rqlstr
-            return unicode(rqlstr, encoding)
+            return text_type(rqlstr, encoding)
         else:
-            if isinstance(rqlstr, unicode):
+            if isinstance(rqlstr, text_type):
                 return rqlstr.encode(encoding)
             return rqlstr
 
@@ -592,7 +592,7 @@
             if row != last:
                 if last is not None:
                     result[-1][1] = i - 1
-                result.append( [i, None, row] )
+                result.append([i, None, row])
                 last = row
         if last is not None:
             result[-1][1] = i
@@ -665,7 +665,7 @@
                 try:
                     entity = self.get_entity(row, index)
                     return entity, rel.r_type
-                except NotAnEntity as exc:
+                except NotAnEntity:
                     return None, None
         return None, None
 
@@ -683,12 +683,14 @@
                 return rhs.eval(self.args)
         return None
 
+
 def _get_variable(term):
     # XXX rewritten const
     # use iget_nodes for (hack) case where we have things like MAX(V)
     for vref in term.iget_nodes(nodes.VariableRef):
         return vref.variable
 
+
 def attr_desc_iterator(select, selectidx, rootidx):
     """return an iterator on a list of 2-uple (index, attr_relation)
     localizing attribute relations of the main variable in a result's row
--- a/cubicweb/rtags.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/rtags.py	Fri Feb 03 13:37:32 2017 +0100
@@ -38,16 +38,26 @@
 
 
 import logging
-from warnings import warn
 
 from six import string_types
 
 from logilab.common.logging_ext import set_log_methods
 from logilab.common.registry import RegistrableInstance, yes
 
+
 def _ensure_str_key(key):
     return tuple(str(k) for k in key)
 
+
+def rtags_chain(rtag):
+    """Return the rtags chain, starting from the given one, and going back through each parent rtag
+    up to the root (i.e. which as no parent).
+    """
+    while rtag is not None:
+        yield rtag
+        rtag = rtag._parent
+
+
 class RegistrableRtags(RegistrableInstance):
     __registry__ = 'uicfg'
     __select__ = yes()
@@ -67,8 +77,13 @@
     # function given as __init__ argument and kept for bw compat
     _init = _initfunc = None
 
-    def __init__(self):
+    def __init__(self, parent=None, __module__=None):
+        super(RelationTags, self).__init__(__module__)
         self._tagdefs = {}
+        self._parent = parent
+        if parent is not None:
+            assert parent.__class__ is self.__class__, \
+                'inconsistent class for parent rtag {0}'.format(parent)
 
     def __repr__(self):
         # find a way to have more infos but keep it readable
@@ -99,12 +114,12 @@
         if check:
             for (stype, rtype, otype, tagged), value in list(self._tagdefs.items()):
                 for ertype in (stype, rtype, otype):
-                    if ertype != '*' and not ertype in schema:
+                    if ertype != '*' and ertype not in schema:
                         self.warning('removing rtag %s: %s, %s undefined in schema',
                                      (stype, rtype, otype, tagged), value, ertype)
                         self.del_rtag(stype, rtype, otype, tagged)
                         break
-        if self._init is not None:
+        if self._parent is None and self._init is not None:
             self.apply(schema, self._init)
 
     def apply(self, schema, func):
@@ -121,6 +136,19 @@
 
     # rtag declaration api ####################################################
 
+    def derive(self, module, select):
+        """Return a derivated of this relation tag, associated to given module and selector.
+
+        This derivated will hold a set of specific rules but delegate to its "parent" relation tags
+        for unfound keys.
+
+        >>> class_afs = uicfg.autoform_section.derive(__name__, is_instance('Class'))
+        """
+        copied = self.__class__(self, __module__=__name__)
+        copied.__module__ = module
+        copied.__select__ = select
+        return copied
+
     def tag_attribute(self, key, *args, **kwargs):
         key = list(key)
         key.append('*')
@@ -141,8 +169,8 @@
         assert len(key) == 4, 'bad key: %s' % list(key)
         if self._allowed_values is not None:
             assert tag in self._allowed_values, \
-                   '%r is not an allowed tag (should be in %s)' % (
-                tag, self._allowed_values)
+                '%r is not an allowed tag (should be in %s)' % (
+                    tag, self._allowed_values)
         self._tagdefs[_ensure_str_key(key)] = tag
         return tag
 
@@ -156,18 +184,21 @@
         else:
             self.tag_object_of((desttype, attr, etype), *args, **kwargs)
 
-
     # rtag runtime api ########################################################
 
     def del_rtag(self, *key):
         del self._tagdefs[key]
 
     def get(self, *key):
+        """Return value for the given key, by looking from the most specific key to the more
+        generic (using '*' wildcards). For each key, look into this rtag and its parent rtags.
+        """
         for key in reversed(self._get_keys(*key)):
-            try:
-                return self._tagdefs[key]
-            except KeyError:
-                continue
+            for rtag in rtags_chain(self):
+                try:
+                    return rtag._tagdefs[key]
+                except KeyError:
+                    continue
         return None
 
     def etype_get(self, etype, rtype, role, ttype='*'):
@@ -177,7 +208,7 @@
 
     # 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
+    info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None
 
 
 class RelationTagsSet(RelationTags):
@@ -192,17 +223,23 @@
         return rtags
 
     def get(self, stype, rtype, otype, tagged):
+        """Return value for the given key, which is an union of the values found from the most
+        specific key to the more generic (using '*' wildcards). For each key, look into this rtag
+        and its parent rtags.
+        """
         rtags = self.tag_container_cls()
         for key in self._get_keys(stype, rtype, otype, tagged):
-            try:
-                rtags.update(self._tagdefs[key])
-            except KeyError:
-                continue
+            for rtag in rtags_chain(self):
+                try:
+                    rtags.update(rtag._tagdefs[key])
+                    break
+                except KeyError:
+                    continue
         return rtags
 
 
 class RelationTagsDict(RelationTagsSet):
-    """This class associates a set of tags to each key."""
+    """This class associates a dictionary to each key."""
     tag_container_cls = dict
 
     def tag_relation(self, key, tag):
--- a/cubicweb/schema.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/schema.py	Fri Feb 03 13:37:32 2017 +0100
@@ -19,6 +19,7 @@
 
 from __future__ import print_function
 
+import pkgutil
 import re
 from os.path import join, basename
 from hashlib import md5
@@ -325,6 +326,7 @@
                 keyarg = None
             rqlst.recover()
             return rql, found, keyarg
+        rqlst.where = nodes.Exists(rqlst.where)
         return rqlst.as_string(), None, None
 
     def _check(self, _cw, **kwargs):
@@ -347,7 +349,9 @@
         if keyarg is None:
             kwargs.setdefault('u', _cw.user.eid)
             try:
-                rset = _cw.execute(rql, kwargs, build_descr=True)
+                # ensure security is disabled
+                with getattr(_cw, 'cnx', _cw).security_enabled(read=False):
+                    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:
@@ -1366,19 +1370,12 @@
     """
     schemacls = CubicWebSchema
 
-    def load(self, config, path=(), **kwargs):
+    def load(self, config, modnames=(['cubicweb', 'cubicweb.schemas.bootstrap'],), **kwargs):
         """return a Schema instance from the schema definition read
         from <directory>
         """
         return super(BootstrapSchemaLoader, self).load(
-            path, config.appid, register_base_types=False, **kwargs)
-
-    def _load_definition_files(self, cubes=None):
-        # bootstraping, ignore cubes
-        filepath = join(cubicweb.CW_SOFTWARE_ROOT, 'schemas', 'bootstrap.py')
-        self.info('loading %s', filepath)
-        with tempattr(ybo, 'PACKAGE', 'cubicweb'):  # though we don't care here
-            self.handle_file(filepath)
+            modnames, name=config.appid, register_base_types=False, **kwargs)
 
     def unhandled_file(self, filepath):
         """called when a file without handler associated has been found"""
@@ -1399,30 +1396,12 @@
         from <directory>
         """
         self.info('loading %s schemas', ', '.join(config.cubes()))
-        self.extrapath = config.extrapath
-        if config.apphome:
-            path = tuple(reversed([config.apphome] + config.cubes_path()))
-        else:
-            path = tuple(reversed(config.cubes_path()))
         try:
-            return super(CubicWebSchemaLoader, self).load(config, path=path, **kwargs)
+            return super(CubicWebSchemaLoader, self).load(config, config.schema_modnames(), **kwargs)
         finally:
             # we've to cleanup modules imported from cubicweb.schemas as well
             cleanup_sys_modules([join(cubicweb.CW_SOFTWARE_ROOT, 'schemas')])
 
-    def _load_definition_files(self, cubes):
-        for filepath in (join(cubicweb.CW_SOFTWARE_ROOT, 'schemas', 'bootstrap.py'),
-                         join(cubicweb.CW_SOFTWARE_ROOT, 'schemas', 'base.py'),
-                         join(cubicweb.CW_SOFTWARE_ROOT, 'schemas', 'workflow.py'),
-                         join(cubicweb.CW_SOFTWARE_ROOT, 'schemas', 'Bookmark.py')):
-            self.info('loading %s', filepath)
-            with tempattr(ybo, 'PACKAGE', 'cubicweb'):
-                self.handle_file(filepath)
-        for cube in cubes:
-            for filepath in self.get_schema_files(cube):
-                with tempattr(ybo, 'PACKAGE', basename(cube)):
-                    self.handle_file(filepath)
-
     # 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
--- a/cubicweb/server/repository.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/server/repository.py	Fri Feb 03 13:37:32 2017 +0100
@@ -150,6 +150,47 @@
         pass
 
 
+class _CnxSetPool(object):
+
+    def __init__(self, source, size):
+        self._cnxsets = []
+        self._queue = queue.Queue()
+        for i in range(size):
+            cnxset = source.wrapped_connection()
+            self._cnxsets.append(cnxset)
+            self._queue.put_nowait(cnxset)
+        super(_CnxSetPool, self).__init__()
+
+    def qsize(self):
+        return self._queue.qsize()
+
+    def get(self):
+        try:
+            return self._queue.get(True, timeout=5)
+        except queue.Empty:
+            raise Exception('no connections set available after 5 secs, probably either a '
+                            'bug in code (too many uncommited/rolled back '
+                            'connections) or too much load on the server (in '
+                            'which case you can try to set a bigger '
+                            'connections pool size)')
+
+    def release(self, cnxset):
+        self._queue.put_nowait(cnxset)
+
+    def __iter__(self):
+        for cnxset in self._cnxsets:
+            yield cnxset
+
+    def close(self):
+        q = self._queue
+        while not q.empty():
+            cnxset = q.get_nowait()
+            try:
+                cnxset.close(True)
+            except Exception:
+                self.exception('error while closing %s' % cnxset)
+
+
 class Repository(object):
     """a repository provides access to a set of persistent storages for
     entities and relations
@@ -208,10 +249,9 @@
         # reload configuration from file and could reset a manually set pool
         # size.
         pool_size = config['connections-pool-size']
-        self._cnxsets_pool = queue.Queue()
-        # 0. init a cnxset that will be used to fetch bootstrap information from
+        # 0. init a cnxset of size 1 that will be used to fetch bootstrap information from
         #    the database
-        self._cnxsets_pool.put_nowait(self.system_source.wrapped_connection())
+        self.cnxsets = _CnxSetPool(self.system_source, 1)
         # 1. set used cubes
         if config.creating or not config.read_instance_schema:
             config.bootstrap_cubes()
@@ -259,12 +299,8 @@
                 self.vreg.init_properties(self.properties())
         # 4. close initialization connection set and reopen fresh ones for
         #    proper initialization
-        self._get_cnxset().close(True)
-        # list of available cnxsets (can't iterate on a Queue)
-        self.cnxsets = []
-        for i in range(pool_size):
-            self.cnxsets.append(self.system_source.wrapped_connection())
-            self._cnxsets_pool.put_nowait(self.cnxsets[-1])
+        self.cnxsets.close()
+        self.cnxsets = _CnxSetPool(self.system_source, pool_size)
 
     # internals ###############################################################
 
@@ -396,19 +432,6 @@
         """start function in a separated thread"""
         utils.RepoThread(func, self._running_threads).start()
 
-    def _get_cnxset(self):
-        try:
-            return self._cnxsets_pool.get(True, timeout=5)
-        except queue.Empty:
-            raise Exception('no connections set available after 5 secs, probably either a '
-                            'bug in code (too many uncommited/rolled back '
-                            'connections) or too much load on the server (in '
-                            'which case you can try to set a bigger '
-                            'connections pool size)')
-
-    def _free_cnxset(self, cnxset):
-        self._cnxsets_pool.put_nowait(cnxset)
-
     def shutdown(self):
         """called on server stop event to properly close opened sessions and
         connections
@@ -430,13 +453,7 @@
             thread.join()
             self.info('thread %s finished', thread.getName())
         self.close_sessions()
-        while not self._cnxsets_pool.empty():
-            cnxset = self._cnxsets_pool.get_nowait()
-            try:
-                cnxset.close(True)
-            except Exception:
-                self.exception('error while closing %s' % cnxset)
-                continue
+        self.cnxsets.close()
         hits, misses = self.querier.cache_hit, self.querier.cache_miss
         try:
             self.info('rql st cache hit/miss: %s/%s (%s%% hits)', hits, misses,
--- a/cubicweb/server/session.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/server/session.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -111,27 +111,19 @@
         assert mode in (HOOKS_ALLOW_ALL, HOOKS_DENY_ALL)
         self.cnx = cnx
         self.mode = mode
-        self.categories = categories
-        self.oldmode = None
-        self.changes = ()
+        self.categories = set(categories)
+        self.old_mode = None
+        self.old_categories = None
 
     def __enter__(self):
-        self.oldmode = self.cnx.hooks_mode
-        self.cnx.hooks_mode = self.mode
-        if self.mode is HOOKS_DENY_ALL:
-            self.changes = self.cnx.enable_hook_categories(*self.categories)
-        else:
-            self.changes = self.cnx.disable_hook_categories(*self.categories)
+        self.old_mode = self.cnx._hooks_mode
+        self.old_categories = self.cnx._hooks_categories
+        self.cnx._hooks_mode = self.mode
+        self.cnx._hooks_categories = self.categories
 
     def __exit__(self, exctype, exc, traceback):
-        try:
-            if self.categories:
-                if self.mode is HOOKS_DENY_ALL:
-                    self.cnx.disable_hook_categories(*self.categories)
-                else:
-                    self.cnx.enable_hook_categories(*self.categories)
-        finally:
-            self.cnx.hooks_mode = self.oldmode
+        self.cnx._hooks_mode = self.old_mode
+        self.cnx._hooks_categories = self.old_categories
 
 
 @deprecated('[3.17] use <object>.security_enabled instead')
@@ -240,13 +232,8 @@
 
     Hooks controls:
 
-      :attr:`hooks_mode`, may be either `HOOKS_ALLOW_ALL` or `HOOKS_DENY_ALL`.
-
-      :attr:`enabled_hook_cats`, when :attr:`hooks_mode` is
-      `HOOKS_DENY_ALL`, this set contains hooks categories that are enabled.
-
-      :attr:`disabled_hook_cats`, when :attr:`hooks_mode` is
-      `HOOKS_ALLOW_ALL`, this set contains hooks categories that are disabled.
+    .. automethod:: cubicweb.server.session.Connection.deny_all_hooks_but
+    .. automethod:: cubicweb.server.session.Connection.allow_all_hooks_but
 
     Security level Management:
 
@@ -285,9 +272,13 @@
         self.commit_state = None
 
         # hook control attribute
-        self.hooks_mode = HOOKS_ALLOW_ALL
-        self.disabled_hook_cats = set()
-        self.enabled_hook_cats = set()
+        # `_hooks_mode`, may be either `HOOKS_ALLOW_ALL` or `HOOKS_DENY_ALL`.
+        self._hooks_mode = HOOKS_ALLOW_ALL
+        # `_hooks_categories`, when :attr:`_hooks_mode` is `HOOKS_DENY_ALL`,
+        # this set contains hooks categories that are enabled ;
+        # when :attr:`_hooks_mode` is `HOOKS_ALLOW_ALL`, it contains hooks
+        # categories that are disabled.
+        self._hooks_categories = set()
         self.pruned_hooks_cache = {}
 
         # security control attributes
@@ -385,7 +376,7 @@
     def __enter__(self):
         assert not self._open
         self._open = True
-        self.cnxset = self.repo._get_cnxset()
+        self.cnxset = self.repo.cnxsets.get()
         if self.lang is None:
             self.set_language(self.user.prefered_language())
         return self
@@ -395,7 +386,7 @@
         self.rollback()
         self._open = False
         self.cnxset.cnxset_freed()
-        self.repo._free_cnxset(self.cnxset)
+        self.repo.cnxsets.release(self.cnxset)
         self.cnxset = None
 
     @contextmanager
@@ -506,14 +497,11 @@
         return self.transaction_data.get('ecache', {}).values()
 
     @_open_only
-    def drop_entity_cache(self, eid=None):
-        """drop entity from the cache
-
-        If eid is None, the whole cache is dropped"""
-        if eid is None:
-            self.transaction_data.pop('ecache', None)
-        else:
-            del self.transaction_data['ecache'][eid]
+    def drop_entity_cache(self):
+        """Drop the whole entity cache."""
+        for entity in self.cached_entities():
+            entity.cw_clear_all_caches()
+        self.transaction_data.pop('ecache', None)
 
     # relations handling #######################################################
 
@@ -679,60 +667,26 @@
 
     @_open_only
     def allow_all_hooks_but(self, *categories):
+        """Context manager to enable all hooks but those in the given
+        categories.
+        """
         return _hooks_control(self, HOOKS_ALLOW_ALL, *categories)
 
     @_open_only
     def deny_all_hooks_but(self, *categories):
-        return _hooks_control(self, HOOKS_DENY_ALL, *categories)
-
-    @_open_only
-    def disable_hook_categories(self, *categories):
-        """disable the given hook categories:
-
-        - on HOOKS_DENY_ALL mode, ensure those categories are not enabled
-        - on HOOKS_ALLOW_ALL mode, ensure those categories are disabled
+        """Context manager to disable all hooks but those in the given
+        categories.
         """
-        changes = set()
-        self.pruned_hooks_cache.clear()
-        categories = set(categories)
-        if self.hooks_mode is HOOKS_DENY_ALL:
-            enabledcats = self.enabled_hook_cats
-            changes = enabledcats & categories
-            enabledcats -= changes  # changes is small hence faster
-        else:
-            disabledcats = self.disabled_hook_cats
-            changes = categories - disabledcats
-            disabledcats |= changes  # changes is small hence faster
-        return tuple(changes)
-
-    @_open_only
-    def enable_hook_categories(self, *categories):
-        """enable the given hook categories:
-
-        - on HOOKS_DENY_ALL mode, ensure those categories are enabled
-        - on HOOKS_ALLOW_ALL mode, ensure those categories are not disabled
-        """
-        changes = set()
-        self.pruned_hooks_cache.clear()
-        categories = set(categories)
-        if self.hooks_mode is HOOKS_DENY_ALL:
-            enabledcats = self.enabled_hook_cats
-            changes = categories - enabledcats
-            enabledcats |= changes  # changes is small hence faster
-        else:
-            disabledcats = self.disabled_hook_cats
-            changes = disabledcats & categories
-            disabledcats -= changes  # changes is small hence faster
-        return tuple(changes)
+        return _hooks_control(self, HOOKS_DENY_ALL, *categories)
 
     @_open_only
     def is_hook_category_activated(self, category):
         """return a boolean telling if the given category is currently activated
         or not
         """
-        if self.hooks_mode is HOOKS_DENY_ALL:
-            return category in self.enabled_hook_cats
-        return category not in self.disabled_hook_cats
+        if self._hooks_mode is HOOKS_DENY_ALL:
+            return category in self._hooks_categories
+        return category not in self._hooks_categories
 
     @_open_only
     def is_hook_activated(self, hook):
--- a/cubicweb/server/sources/native.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/server/sources/native.py	Fri Feb 03 13:37:32 2017 +0100
@@ -380,7 +380,7 @@
         # check full text index availibility
         if self.do_fti:
             if cnxset is None:
-                _cnxset = self.repo._get_cnxset()
+                _cnxset = self.repo.cnxsets.get()
             else:
                 _cnxset = cnxset
             if not self.dbhelper.has_fti_table(_cnxset.cu):
@@ -389,7 +389,7 @@
                 self.do_fti = False
             if cnxset is None:
                 _cnxset.cnxset_freed()
-                self.repo._free_cnxset(_cnxset)
+                self.repo.cnxsets.release(_cnxset)
 
     def backup(self, backupfile, confirm, format='native'):
         """method called to create a backup of the source's data"""
--- a/cubicweb/server/test/unittest_checkintegrity.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/server/test/unittest_checkintegrity.py	Fri Feb 03 13:37:32 2017 +0100
@@ -25,9 +25,9 @@
 else:
     from io import StringIO
 
-from cubicweb import devtools
-from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.server.checkintegrity import check, check_indexes, reindex_entities
+from cubicweb import devtools  # noqa: E402
+from cubicweb.devtools.testlib import CubicWebTC  # noqa: E402
+from cubicweb.server.checkintegrity import check, check_indexes, reindex_entities  # noqa: E402
 
 
 class CheckIntegrityTC(unittest.TestCase):
--- a/cubicweb/server/test/unittest_migractions.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/server/test/unittest_migractions.py	Fri Feb 03 13:37:32 2017 +0100
@@ -19,6 +19,7 @@
 
 import os
 import os.path as osp
+import sys
 from datetime import date
 from contextlib import contextmanager
 import tempfile
@@ -77,13 +78,25 @@
         # we have to read schema from the database to get eid for schema entities
         self.repo.set_schema(self.repo.deserialize_schema(), resetvreg=False)
         # hack to read the schema from data/migrschema
-        config = self.config
-        config.appid = osp.join(self.appid, 'migratedapp')
-        config._apphome = osp.join(HERE, config.appid)
-        global migrschema
-        migrschema = config.load_schema()
-        config.appid = self.appid
-        config._apphome = osp.join(HERE, self.appid)
+
+        @contextmanager
+        def temp_app(config, appid, apphome):
+            old = config.apphome, config.appid
+            sys.path.remove(old[0])
+            sys.path.insert(0, apphome)
+            config._apphome, config.appid = apphome, appid
+            try:
+                yield config
+            finally:
+                sys.path.remove(apphome)
+                sys.path.insert(0, old[0])
+                config._apphome, config.appid = old
+
+        appid = osp.join(self.appid, 'migratedapp')
+        apphome = osp.join(HERE, appid)
+        with temp_app(self.config, appid, apphome) as config:
+            global migrschema
+            migrschema = config.load_schema()
 
     def setUp(self):
         self.configcls.cls_adjust_sys_path()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/server/test/unittest_session.py	Fri Feb 03 13:37:32 2017 +0100
@@ -0,0 +1,54 @@
+# copyright 2017 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/>.
+
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.server import session
+
+
+class HooksControlTC(CubicWebTC):
+
+    def test_hooks_control(self):
+        with self.admin_access.repo_cnx() as cnx:
+            self.assertEqual(cnx._hooks_mode, session.HOOKS_ALLOW_ALL)
+            self.assertEqual(cnx._hooks_categories, set())
+
+            with cnx.deny_all_hooks_but('metadata'):
+                self.assertEqual(cnx._hooks_mode, session.HOOKS_DENY_ALL)
+                self.assertEqual(cnx._hooks_categories, set(['metadata']))
+
+                with cnx.deny_all_hooks_but():
+                    self.assertEqual(cnx._hooks_categories, set())
+                self.assertEqual(cnx._hooks_categories, set(['metadata']))
+
+                with cnx.deny_all_hooks_but('integrity'):
+                    self.assertEqual(cnx._hooks_categories, set(['integrity']))
+                self.assertEqual(cnx._hooks_categories, set(['metadata']))
+
+                with cnx.allow_all_hooks_but('integrity'):
+                    self.assertEqual(cnx._hooks_mode, session.HOOKS_ALLOW_ALL)
+                    self.assertEqual(cnx._hooks_categories, set(['integrity']))
+                self.assertEqual(cnx._hooks_mode, session.HOOKS_DENY_ALL)
+                self.assertEqual(cnx._hooks_categories, set(['metadata']))
+
+            self.assertEqual(cnx._hooks_mode, session.HOOKS_ALLOW_ALL)
+            self.assertEqual(cnx._hooks_categories, set())
+
+
+if __name__ == '__main__':
+    import unittest
+    unittest.main()
--- a/cubicweb/server/utils.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/server/utils.py	Fri Feb 03 13:37:32 2017 +0100
@@ -66,7 +66,7 @@
     """return the encrypted password using the given salt or a generated one
     """
     if salt is None:
-        return _CRYPTO_CTX.encrypt(passwd).encode('ascii')
+        return _CRYPTO_CTX.hash(passwd).encode('ascii')
     # empty hash, accept any password for backwards compat
     if salt == '':
         return salt
--- a/cubicweb/skeleton/MANIFEST.in.tmpl	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/skeleton/MANIFEST.in.tmpl	Fri Feb 03 13:37:32 2017 +0100
@@ -5,6 +5,6 @@
 recursive-include cubicweb_%(cubename)s/i18n *.po
 recursive-include cubicweb_%(cubename)s/wdoc *
 recursive-include test/data bootstrap_cubes *.py
-include tox.ini
+include *.ini
 recursive-include debian changelog compat control copyright rules
 include cubicweb-%(cubename)s.spec
--- a/cubicweb/skeleton/cubicweb_CUBENAME/__init__.py.tmpl	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/skeleton/cubicweb_CUBENAME/__init__.py.tmpl	Fri Feb 03 13:37:32 2017 +0100
@@ -2,3 +2,12 @@
 
 %(longdesc)s
 """
+
+
+def pyramid_main(global_config, **settings):
+    """Return a Pyramid WSGI application bound to a CubicWeb repository."""
+    from pyramid.config import Configurator
+    config = Configurator(settings=settings)
+    config.include('cubicweb.pyramid')
+    config.scan()
+    return config.make_wsgi_app()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/skeleton/development.ini.tmpl	Fri Feb 03 13:37:32 2017 +0100
@@ -0,0 +1,76 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
+[app:main]
+use = egg:%(distname)s
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+    pyramid_debugtoolbar
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+##
+# CubicWeb instance settings
+# http://cubicweb.readthedocs.io/en/latest/book/pyramid/settings/
+##
+cubicweb.instance = %%(instance)s
+cubicweb.bwcompat = True
+cubicweb.debug = True
+# cubicweb.auth.authtkt.persistent.secret =
+cubicweb.auth.authtkt.persistent.secure = False
+# cubicweb.auth.authtkt.session.secret =
+cubicweb.auth.authtkt.session.secure = False
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+listen = 127.0.0.1:6543 [::1]:6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+###
+
+[loggers]
+keys = root, cubicweb_%(cubename)s, cubicweb
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_cubicweb]
+level = INFO
+handlers = console
+qualname = cubicweb
+
+[logger_cubicweb_%(cubename)s]
+level = DEBUG
+handlers = console
+qualname = cubicweb_%(cubename)s
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %%(asctime)s %%(levelname)-5.5s [%%(name)s:%%(lineno)s][%%(threadName)s] %%(message)s
--- a/cubicweb/skeleton/setup.py.tmpl	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/skeleton/setup.py.tmpl	Fri Feb 03 13:37:32 2017 +0100
@@ -77,6 +77,9 @@
         'cubicweb.cubes': [
             '%(cubename)s=cubicweb_%(cubename)s',
         ],
+        'paste.app_factory': [
+            'main=cubicweb_%(cubename)s:pyramid_main',
+        ],
     },
     zip_safe=False,
 )
--- a/cubicweb/sobjects/notification.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/sobjects/notification.py	Fri Feb 03 13:37:32 2017 +0100
@@ -187,7 +187,7 @@
         kwargs.update({'user': self.user_data['login'],
                        'eid': entity.eid,
                        'etype': entity.dc_type(),
-                       'url': entity.absolute_url(__secure__=True),
+                       'url': entity.absolute_url(),
                        'title': entity.dc_long_title(),})
         return kwargs
 
--- a/cubicweb/sobjects/services.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/sobjects/services.py	Fri Feb 03 13:37:32 2017 +0100
@@ -54,7 +54,7 @@
         results['nb_active_threads'] = threading.activeCount()
         looping_tasks = repo._tasks_manager._looping_tasks
         results['looping_tasks'] = [(t.name, t.interval) for t in looping_tasks]
-        results['available_cnxsets'] = repo._cnxsets_pool.qsize()
+        results['available_cnxsets'] = repo.cnxsets.qsize()
         results['threads'] = [t.name for t in threading.enumerate()]
         return results
 
--- a/cubicweb/test/data/views.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/test/data/views.py	Fri Feb 03 13:37:32 2017 +0100
@@ -15,12 +15,13 @@
 #
 # 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.views import xmlrss
-xmlrss.RSSIconBox.visible = True
-
 
 from cubicweb.predicates import match_user_groups
 from cubicweb.server import Service
+from cubicweb.web.views import xmlrss
+
+
+xmlrss.RSSIconBox.visible = True
 
 
 class TestService(Service):
--- a/cubicweb/test/data_schemareader/schema.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/test/data_schemareader/schema.py	Fri Feb 03 13:37:32 2017 +0100
@@ -11,6 +11,7 @@
         'CWSource', inlined=True, cardinality='1*', composite='object',
         __permissions__=RELATION_MANAGERS_PERMISSIONS)
 
+
 cw_for_source = CWSourceSchemaConfig.get_relation('cw_for_source')
 cw_for_source.__permissions__ = {'read': ('managers', 'users'),
                                  'add': ('managers',),
--- a/cubicweb/test/unittest_cubes.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/test/unittest_cubes.py	Fri Feb 03 13:37:32 2017 +0100
@@ -20,33 +20,31 @@
 from contextlib import contextmanager
 import os
 from os import path
-import shutil
 import sys
-import tempfile
 import unittest
 
 from six import PY2
 
 from cubicweb import _CubesImporter
 from cubicweb.cwconfig import CubicWebConfiguration
+from cubicweb.devtools.testlib import TemporaryDirectory
 
 
 @contextmanager
 def temp_cube():
-    tempdir = tempfile.mkdtemp()
-    try:
-        libdir = path.join(tempdir, 'libpython')
-        cubedir = path.join(libdir, 'cubicweb_foo')
-        os.makedirs(cubedir)
-        with open(path.join(cubedir, '__init__.py'), 'w') as f:
-            f.write('"""cubicweb_foo application package"""')
-        with open(path.join(cubedir, 'bar.py'), 'w') as f:
-            f.write('baz = 1')
-        sys.path.append(libdir)
-        yield cubedir
-    finally:
-        shutil.rmtree(tempdir)
-        sys.path.remove(libdir)
+    with TemporaryDirectory() as tempdir:
+        try:
+            libdir = path.join(tempdir, 'libpython')
+            cubedir = path.join(libdir, 'cubicweb_foo')
+            os.makedirs(cubedir)
+            with open(path.join(cubedir, '__init__.py'), 'w') as f:
+                f.write('"""cubicweb_foo application package"""')
+            with open(path.join(cubedir, 'bar.py'), 'w') as f:
+                f.write('baz = 1')
+            sys.path.append(libdir)
+            yield cubedir
+        finally:
+            sys.path.remove(libdir)
 
 
 class CubesImporterTC(unittest.TestCase):
--- a/cubicweb/test/unittest_cwconfig.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/test/unittest_cwconfig.py	Fri Feb 03 13:37:32 2017 +0100
@@ -17,8 +17,12 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb.cwconfig unit tests"""
 
+import contextlib
+import compileall
+import functools
 import sys
 import os
+import pkgutil
 from os.path import dirname, join, abspath
 from pkg_resources import EntryPoint, Distribution
 import unittest
@@ -31,7 +35,8 @@
 
 from cubicweb.devtools import ApptestConfiguration
 from cubicweb.devtools.testlib import BaseTestCase, TemporaryDirectory
-from cubicweb.cwconfig import _find_prefix
+from cubicweb.cwconfig import (
+    CubicWebConfiguration, _find_prefix, _expand_modname)
 
 
 def unabsolutize(path):
@@ -44,6 +49,50 @@
     raise Exception('duh? %s' % path)
 
 
+def templibdir(func):
+    """create a temporary directory and insert it in sys.path"""
+    @functools.wraps(func)
+    def wrapper(*args, **kwargs):
+        with TemporaryDirectory() as libdir:
+            sys.path.insert(0, libdir)
+            try:
+                args = args + (libdir,)
+                return func(*args, **kwargs)
+            finally:
+                sys.path.remove(libdir)
+    return wrapper
+
+
+def create_filepath(filepath):
+    filedir = dirname(filepath)
+    if not os.path.exists(filedir):
+        os.makedirs(filedir)
+    with open(filepath, 'a'):
+        pass
+
+
+@contextlib.contextmanager
+def temp_config(appid, instance_dir, cubes_dir, cubes):
+    """context manager that create a config object with specified appid,
+    instance_dir, cubes_dir and cubes"""
+    cls = CubicWebConfiguration
+    old = (cls._INSTANCES_DIR, cls.CUBES_DIR, cls.CUBES_PATH,
+           sys.path[:], sys.meta_path[:])
+    old_modules = set(sys.modules)
+    try:
+        cls._INSTANCES_DIR, cls.CUBES_DIR, cls.CUBES_PATH = (
+            instance_dir, cubes_dir, [])
+        config = cls(appid)
+        config._cubes = cubes
+        config.adjust_sys_path()
+        yield config
+    finally:
+        (cls._INSTANCES_DIR, cls.CUBES_DIR, cls.CUBES_PATH,
+         sys.path[:], sys.meta_path[:]) = old
+        for module in set(sys.modules) - old_modules:
+            del sys.modules[module]
+
+
 class CubicWebConfigurationTC(BaseTestCase):
 
     @classmethod
@@ -129,17 +178,6 @@
         self.assertEqual(self.config.expand_cubes(('email', 'comment')),
                           ['email', 'comment', 'file'])
 
-    def test_appobjects_path(self):
-        path = [unabsolutize(p) for p in self.config.appobjects_path()]
-        self.assertEqual(path[0], 'entities')
-        self.assertCountEqual(path[1:4], ['web/views', 'sobjects', 'hooks'])
-        self.assertEqual(path[4], 'file/entities')
-        self.assertCountEqual(path[5:7],
-                              ['file/views.py', 'file/hooks'])
-        self.assertEqual(path[7], 'email/entities.py')
-        self.assertCountEqual(path[8:10],
-                              ['email/views', 'email/hooks.py'])
-        self.assertEqual(path[10:], ['test/data/entities.py', 'test/data/views.py'])
 
     def test_init_cubes_ignore_pyramid_cube(self):
         warning_msg = 'cubicweb-pyramid got integrated into CubicWeb'
@@ -203,7 +241,8 @@
         from cubes import mycube
         self.assertEqual(mycube.__path__, [join(self.custom_cubes_dir, 'mycube')])
         # file cube should be overriden by the one found in data/cubes
-        if sys.modules.pop('cubes.file', None) and PY3:
+        sys.modules.pop('cubes.file')
+        if hasattr(cubes, 'file'):
             del cubes.file
         from cubes import file
         self.assertEqual(file.__path__, [join(self.custom_cubes_dir, 'file')])
@@ -331,5 +370,150 @@
                 os.environ['VIRTUAL_ENV'] = venv
 
 
+class ModnamesTC(unittest.TestCase):
+
+    @templibdir
+    def test_expand_modnames(self, libdir):
+        tempdir = join(libdir, 'lib')
+        filepaths = [
+            join(tempdir, '__init__.py'),
+            join(tempdir, 'a.py'),
+            join(tempdir, 'b.py'),
+            join(tempdir, 'c.py'),
+            join(tempdir, 'b', '__init__.py'),
+            join(tempdir, 'b', 'a.py'),
+            join(tempdir, 'b', 'c.py'),
+            join(tempdir, 'b', 'd', '__init__.py'),
+            join(tempdir, 'e', 'e.py'),
+        ]
+        for filepath in filepaths:
+            create_filepath(filepath)
+        # not importable
+        self.assertEqual(list(_expand_modname('isnotimportable')), [])
+        # not a python package
+        self.assertEqual(list(_expand_modname('lib.e')), [])
+        self.assertEqual(list(_expand_modname('lib.a')), [
+            ('lib.a', join(tempdir, 'a.py')),
+        ])
+        # lib.b.d (subpackage) not to be imported
+        self.assertEqual(list(_expand_modname('lib.b')), [
+            ('lib.b', join(tempdir, 'b', '__init__.py')),
+            ('lib.b.a', join(tempdir, 'b', 'a.py')),
+            ('lib.b.c', join(tempdir, 'b', 'c.py')),
+        ])
+        self.assertEqual(list(_expand_modname('lib')), [
+            ('lib', join(tempdir, '__init__.py')),
+            ('lib.a', join(tempdir, 'a.py')),
+            ('lib.c', join(tempdir, 'c.py')),
+        ])
+        for source in (
+            join(tempdir, 'c.py'),
+            join(tempdir, 'b', 'c.py'),
+        ):
+            if not PY3:
+                # ensure pyc file exists.
+                # Doesn't required for PY3 since it create __pycache__
+                # directory and will not import if source file doesn't
+                # exists.
+                compileall.compile_file(source, force=True)
+                self.assertTrue(os.path.exists(source + 'c'))
+            # remove source file
+            os.remove(source)
+        self.assertEqual(list(_expand_modname('lib.c')), [])
+        self.assertEqual(list(_expand_modname('lib.b')), [
+            ('lib.b', join(tempdir, 'b', '__init__.py')),
+            ('lib.b.a', join(tempdir, 'b', 'a.py')),
+        ])
+        self.assertEqual(list(_expand_modname('lib')), [
+            ('lib', join(tempdir, '__init__.py')),
+            ('lib.a', join(tempdir, 'a.py')),
+        ])
+
+    @templibdir
+    def test_schema_modnames(self, libdir):
+        for filepath in (
+            join(libdir, 'schema.py'),
+            join(libdir, 'cubicweb_foo', '__init__.py'),
+            join(libdir, 'cubicweb_foo', 'schema', '__init__.py'),
+            join(libdir, 'cubicweb_foo', 'schema', 'a.py'),
+            join(libdir, 'cubicweb_foo', 'schema', 'b.py'),
+            join(libdir, 'cubes', '__init__.py'),
+            join(libdir, 'cubes', 'bar', '__init__.py'),
+            join(libdir, 'cubes', 'bar', 'schema.py'),
+            join(libdir, '_instance_dir', 'data1', 'schema.py'),
+            join(libdir, '_instance_dir', 'data2', 'noschema.py'),
+        ):
+            create_filepath(filepath)
+        expected = [
+            ('cubicweb', 'cubicweb.schemas.bootstrap'),
+            ('cubicweb', 'cubicweb.schemas.base'),
+            ('cubicweb', 'cubicweb.schemas.workflow'),
+            ('cubicweb', 'cubicweb.schemas.Bookmark'),
+            ('bar', 'cubes.bar.schema'),
+            ('foo', 'cubes.foo.schema'),
+            ('foo', 'cubes.foo.schema.a'),
+            ('foo', 'cubes.foo.schema.b'),
+        ]
+        # app has schema file
+        instance_dir, cubes_dir = (
+            join(libdir, '_instance_dir'), join(libdir, 'cubes'))
+        with temp_config('data1', instance_dir, cubes_dir,
+                         ('foo', 'bar')) as config:
+            self.assertEqual(pkgutil.find_loader('schema').get_filename(),
+                             join(libdir, '_instance_dir',
+                                  'data1', 'schema.py'))
+            self.assertEqual(config.schema_modnames(),
+                             expected + [('data', 'schema')])
+        # app doesn't have schema file
+        with temp_config('data2', instance_dir, cubes_dir,
+                         ('foo', 'bar')) as config:
+            self.assertEqual(pkgutil.find_loader('schema').get_filename(),
+                             join(libdir, 'schema.py'))
+            self.assertEqual(config.schema_modnames(), expected)
+
+    @templibdir
+    def test_appobjects_modnames(self, libdir):
+        for filepath in (
+            join(libdir, 'entities.py'),
+            join(libdir, 'cubicweb_foo', '__init__.py'),
+            join(libdir, 'cubicweb_foo', 'entities', '__init__.py'),
+            join(libdir, 'cubicweb_foo', 'entities', 'a.py'),
+            join(libdir, 'cubicweb_foo', 'hooks.py'),
+            join(libdir, 'cubes', '__init__.py'),
+            join(libdir, 'cubes', 'bar', '__init__.py'),
+            join(libdir, 'cubes', 'bar', 'hooks.py'),
+            join(libdir, '_instance_dir', 'data1', 'entities.py'),
+            join(libdir, '_instance_dir', 'data2', 'hooks.py'),
+        ):
+            create_filepath(filepath)
+        instance_dir, cubes_dir = (
+            join(libdir, '_instance_dir'), join(libdir, 'cubes'))
+        expected = [
+            'cubicweb.entities',
+            'cubicweb.entities.adapters',
+            'cubicweb.entities.authobjs',
+            'cubicweb.entities.lib',
+            'cubicweb.entities.schemaobjs',
+            'cubicweb.entities.sources',
+            'cubicweb.entities.wfobjs',
+            'cubes.bar.hooks',
+            'cubes.foo.entities',
+            'cubes.foo.entities.a',
+            'cubes.foo.hooks',
+        ]
+        # data1 has entities
+        with temp_config('data1', instance_dir, cubes_dir,
+                         ('foo', 'bar')) as config:
+            config.cube_appobject_path = set(['entities', 'hooks'])
+            self.assertEqual(config.appobjects_modnames(),
+                             expected + ['entities'])
+        # data2 has hooks
+        with temp_config('data2', instance_dir, cubes_dir,
+                         ('foo', 'bar')) as config:
+            config.cube_appobject_path = set(['entities', 'hooks'])
+            self.assertEqual(config.appobjects_modnames(),
+                             expected + ['hooks'])
+
+
 if __name__ == '__main__':
     unittest.main()
--- a/cubicweb/test/unittest_entity.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/test/unittest_entity.py	Fri Feb 03 13:37:32 2017 +0100
@@ -182,10 +182,15 @@
                 req.create_entity('Tag', name=tag)
             req.execute('SET X tags Y WHERE X is Tag, Y is Personne')
             self.assertEqual(len(p.related('tags', 'object', limit=2)), 2)
+            self.assertFalse(p.cw_relation_cached('tags', 'object'))
             self.assertEqual(len(p.related('tags', 'object')), 4)
+            self.assertTrue(p.cw_relation_cached('tags', 'object'))
             p.cw_clear_all_caches()
+            self.assertFalse(p.cw_relation_cached('tags', 'object'))
             self.assertEqual(len(p.related('tags', 'object', entities=True, limit=2)), 2)
+            self.assertFalse(p.cw_relation_cached('tags', 'object'))
             self.assertEqual(len(p.related('tags', 'object', entities=True)), 4)
+            self.assertTrue(p.cw_relation_cached('tags', 'object'))
 
     def test_related_targettypes(self):
         with self.admin_access.web_request() as req:
--- a/cubicweb/test/unittest_req.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/test/unittest_req.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -19,25 +19,25 @@
 from logilab.common.testlib import TestCase, unittest_main
 from cubicweb import ObjectNotFound
 from cubicweb.req import RequestSessionBase, FindEntityError
-from cubicweb.devtools import ApptestConfiguration
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb import Unauthorized
 
+
 class RequestTC(TestCase):
     def test_rebuild_url(self):
         rebuild_url = RequestSessionBase(None).rebuild_url
         self.assertEqual(rebuild_url('http://logilab.fr?__message=pouet', __message='hop'),
-                          'http://logilab.fr?__message=hop')
+                         'http://logilab.fr?__message=hop')
         self.assertEqual(rebuild_url('http://logilab.fr', __message='hop'),
-                          'http://logilab.fr?__message=hop')
+                         'http://logilab.fr?__message=hop')
         self.assertEqual(rebuild_url('http://logilab.fr?vid=index', __message='hop'),
-                          'http://logilab.fr?__message=hop&vid=index')
+                         'http://logilab.fr?__message=hop&vid=index')
 
     def test_build_url(self):
         req = RequestSessionBase(None)
-        req.from_controller = lambda : 'view'
+        req.from_controller = lambda: 'view'
         req.relative_path = lambda includeparams=True: None
-        req.base_url = lambda secure=None: 'http://testing.fr/cubicweb/'
+        req.base_url = lambda: 'http://testing.fr/cubicweb/'
         self.assertEqual(req.build_url(), u'http://testing.fr/cubicweb/view')
         self.assertEqual(req.build_url(None), u'http://testing.fr/cubicweb/view')
         self.assertEqual(req.build_url('one'), u'http://testing.fr/cubicweb/one')
@@ -49,8 +49,10 @@
         req = RequestSessionBase(None)
         self.assertEqual(req.ensure_ro_rql('Any X WHERE X is CWUser'), None)
         self.assertEqual(req.ensure_ro_rql('  Any X WHERE X is CWUser  '), None)
-        self.assertRaises(Unauthorized, req.ensure_ro_rql, 'SET X login "toto" WHERE X is CWUser')
-        self.assertRaises(Unauthorized, req.ensure_ro_rql, '   SET X login "toto" WHERE X is CWUser   ')
+        self.assertRaises(Unauthorized, req.ensure_ro_rql,
+                          'SET X login "toto" WHERE X is CWUser')
+        self.assertRaises(Unauthorized, req.ensure_ro_rql,
+                          '   SET X login "toto" WHERE X is CWUser   ')
 
 
 class RequestCWTC(CubicWebTC):
@@ -59,11 +61,15 @@
         base_url = self.config['base-url']
         with self.admin_access.repo_cnx() as session:
             self.assertEqual(session.base_url(), base_url)
-            assert 'https-url' not in self.config
-            self.assertEqual(session.base_url(secure=True), base_url)
-            secure_base_url = base_url.replace('http', 'https')
-            self.config.global_set_option('https-url', secure_base_url)
-            self.assertEqual(session.base_url(secure=True), secure_base_url)
+
+    def test_secure_deprecated(self):
+        with self.admin_access.cnx() as cnx:
+            with self.assertWarns(DeprecationWarning):
+                cnx.base_url(secure=True)
+            with self.assertRaises(TypeError):
+                cnx.base_url(thing=42)
+            with self.assertWarns(DeprecationWarning):
+                cnx.build_url('ah', __secure__='whatever')
 
     def test_view_catch_ex(self):
         with self.admin_access.web_request() as req:
--- a/cubicweb/test/unittest_rqlrewrite.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/test/unittest_rqlrewrite.py	Fri Feb 03 13:37:32 2017 +0100
@@ -215,6 +215,12 @@
         self.assertEqual(rqlst.as_string(),
                          u'Any A,AR,X,CD WHERE A concerne X?, A ref AR, A eid %(a)s WITH X,CD BEING (Any X,CD WHERE X creation_date CD, EXISTS(X created_by B), B eid %(A)s, X is IN(Division, Note, Societe))')
 
+    def test_ambiguous_optional_same_exprs_constant(self):
+        rqlst = parse(u'Any A,AR,X WHERE A concerne X?, A ref AR, A eid %(a)s, X creation_date TODAY')
+        rewrite(rqlst, {('X', 'X'): ('X created_by U',),}, {'a': 3})
+        self.assertEqual(rqlst.as_string(),
+                         u'Any A,AR,X WHERE A concerne X?, A ref AR, A eid %(a)s WITH X BEING (Any X WHERE X creation_date TODAY, EXISTS(X created_by B), B eid %(A)s, X is IN(Division, Note, Societe))')
+
     def test_optional_var_inlined(self):
         c1 = ('X require_permission P')
         c2 = ('X inlined_card O, O require_permission P')
--- a/cubicweb/test/unittest_rset.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/test/unittest_rset.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,5 +1,5 @@
 # coding: utf-8
-# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -44,12 +44,12 @@
     def test_relations_description(self):
         """tests relations_description() function"""
         queries = {
-            'Any U,L,M where U is CWUser, U login L, U mail M' : [(1, 'login', 'subject'), (2, 'mail', 'subject')],
-            'Any U,L,M where U is CWUser, L is Foo, U mail M' : [(2, 'mail', 'subject')],
-            'Any C,P where C is Company, C employs P' : [(1, 'employs', 'subject')],
-            'Any C,P where C is Company, P employed_by P' : [],
-            'Any C where C is Company, C employs P' : [],
-            }
+            'Any U,L,M where U is CWUser, U login L, U mail M': [(1, 'login', 'subject'), (2, 'mail', 'subject')],
+            'Any U,L,M where U is CWUser, L is Foo, U mail M': [(2, 'mail', 'subject')],
+            'Any C,P where C is Company, C employs P': [(1, 'employs', 'subject')],
+            'Any C,P where C is Company, P employed_by P': [],
+            'Any C where C is Company, C employs P': [],
+        }
         for rql, relations in queries.items():
             result = list(attr_desc_iterator(parse(rql).children[0], 0, 0))
             self.assertEqual((rql, result), (rql, relations))
@@ -57,9 +57,10 @@
     def test_relations_description_indexed(self):
         """tests relations_description() function"""
         queries = {
-            'Any C,U,P,L,M where C is Company, C employs P, U is CWUser, U login L, U mail M' :
-            {0: [(2,'employs', 'subject')], 1: [(3,'login', 'subject'), (4,'mail', 'subject')]},
-            }
+            'Any C,U,P,L,M where C is Company, C employs P, U is CWUser, U login L, U mail M':
+            {0: [(2, 'employs', 'subject')],
+             1: [(3, 'login', 'subject'), (4, 'mail', 'subject')]},
+        }
         for rql, results in queries.items():
             for idx, relations in results.items():
                 result = list(attr_desc_iterator(parse(rql).children[0], idx, idx))
@@ -118,17 +119,6 @@
             #                  '%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):
@@ -155,8 +145,6 @@
 
     def test_limit_2(self):
         with self.admin_access.web_request() as req:
-            # drop user from cache for the sake of this test
-            req.drop_entity_cache(req.user.eid)
             rs = req.execute('Any E,U WHERE E is CWEType, E created_by U')
             # get entity on row 9. This will fill its created_by relation cache,
             # with cwuser on row 9 as well
@@ -284,7 +272,7 @@
     def test_get_entity_simple(self):
         with self.admin_access.web_request() as req:
             req.create_entity('CWUser', login=u'adim', upassword='adim',
-                                         surname=u'di mascio', firstname=u'adrien')
+                              surname=u'di mascio', firstname=u'adrien')
             req.drop_entity_cache()
             e = req.execute('Any X,T WHERE X login "adim", X surname T').get_entity(0, 0)
             self.assertEqual(e.cw_attr_cache['surname'], 'di mascio')
@@ -575,6 +563,13 @@
                               '(Any X,N WHERE X is CWGroup, X name N)'
                               ')')
 
+    def test_possible_actions_cache(self):
+        with self.admin_access.web_request() as req:
+            rset = req.execute('Any D, COUNT(U) GROUPBY D WHERE U is CWUser, U creation_date D')
+            rset.possible_actions(argument='Value')
+            self.assertRaises(AssertionError, rset.possible_actions, argument='OtherValue')
+            self.assertRaises(AssertionError, rset.possible_actions, other_argument='Value')
+
     def test_count_users_by_date(self):
         with self.admin_access.web_request() as req:
             rset = req.execute('Any D, COUNT(U) GROUPBY D WHERE U is CWUser, U creation_date D')
--- a/cubicweb/test/unittest_rtags.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/test/unittest_rtags.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -15,81 +15,140 @@
 #
 # 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 logilab.common.testlib import TestCase, unittest_main
+from cubicweb.devtools.testlib import BaseTestCase
 from cubicweb.rtags import RelationTags, RelationTagsSet, RelationTagsDict
 
-class RelationTagsTC(TestCase):
+
+class RelationTagsTC(BaseTestCase):
+
+    def setUp(self):
+        self.rtags = RelationTags(__module__=__name__)
+        self.rtags.tag_subject_of(('Societe', 'travaille', '*'), 'primary')
+        self.rtags.tag_subject_of(('*', 'evaluee', '*'), 'secondary')
+        self.rtags.tag_object_of(('*', 'tags', '*'), 'generated')
 
-    def test_rtags_expansion(self):
-        rtags = RelationTags()
-        rtags.tag_subject_of(('Societe', 'travaille', '*'), 'primary')
-        rtags.tag_subject_of(('*', 'evaluee', '*'), 'secondary')
-        rtags.tag_object_of(('*', 'tags', '*'), 'generated')
-        self.assertEqual(rtags.get('Note', 'evaluee', '*', 'subject'),
-                          'secondary')
-        self.assertEqual(rtags.get('Societe', 'travaille', '*', 'subject'),
-                          'primary')
-        self.assertEqual(rtags.get('Note', 'travaille', '*', 'subject'),
-                          None)
-        self.assertEqual(rtags.get('Note', 'tags', '*', 'subject'),
-                          None)
-        self.assertEqual(rtags.get('*', 'tags', 'Note', 'object'),
-                          'generated')
-        self.assertEqual(rtags.get('Tag', 'tags', '*', 'object'),
-                          'generated')
+    def test_expansion(self):
+        self.assertEqual(self.rtags.get('Note', 'evaluee', '*', 'subject'),
+                         'secondary')
+        self.assertEqual(self.rtags.get('Societe', 'travaille', '*', 'subject'),
+                         'primary')
+        self.assertEqual(self.rtags.get('Note', 'travaille', '*', 'subject'),
+                         None)
+        self.assertEqual(self.rtags.get('Note', 'tags', '*', 'subject'),
+                         None)
+        self.assertEqual(self.rtags.get('*', 'tags', 'Note', 'object'),
+                         'generated')
+        self.assertEqual(self.rtags.get('Tag', 'tags', '*', 'object'),
+                         'generated')
 
-#         self.assertEqual(rtags.rtag('evaluee', 'Note', 'subject'), set(('secondary', 'link')))
-#         self.assertEqual(rtags.is_inlined('evaluee', 'Note', 'subject'), False)
-#         self.assertEqual(rtags.rtag('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
-#         self.assertEqual(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
-#         self.assertEqual(rtags.rtag('ecrit_par', 'Note', 'object'), set(('inlineview', 'link')))
-#         self.assertEqual(rtags.is_inlined('ecrit_par', 'Note', 'object'), True)
-#         class Personne2(Personne):
-#             id = 'Personne'
-#             __rtags__ = {
-#                 ('evaluee', 'Note', 'subject') : set(('inlineview',)),
-#                 }
-#         self.vreg.register(Personne2)
-#         rtags = Personne2.rtags
-#         self.assertEqual(rtags.rtag('evaluee', 'Note', 'subject'), set(('inlineview', 'link')))
-#         self.assertEqual(rtags.is_inlined('evaluee', 'Note', 'subject'), True)
-#         self.assertEqual(rtags.rtag('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
-#         self.assertEqual(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
+    def test_expansion_with_parent(self):
+        derived_rtags = self.rtags.derive(__name__, None)
+        derived_rtags.tag_subject_of(('Societe', 'travaille', '*'), 'secondary')
+        derived_rtags.tag_subject_of(('Note', 'evaluee', '*'), 'primary')
+        self.rtags.tag_object_of(('*', 'tags', '*'), 'hidden')
+
+        self.assertEqual(derived_rtags.get('Note', 'evaluee', '*', 'subject'),
+                         'primary')
+        self.assertEqual(derived_rtags.get('Societe', 'evaluee', '*', 'subject'),
+                         'secondary')
+        self.assertEqual(derived_rtags.get('Societe', 'travaille', '*', 'subject'),
+                         'secondary')
+        self.assertEqual(derived_rtags.get('Note', 'travaille', '*', 'subject'),
+                         None)
+        self.assertEqual(derived_rtags.get('*', 'tags', 'Note', 'object'),
+                         'hidden')
 
 
-    def test_rtagset_expansion(self):
-        rtags = RelationTagsSet()
-        rtags.tag_subject_of(('Societe', 'travaille', '*'), 'primary')
-        rtags.tag_subject_of(('*', 'travaille', '*'), 'secondary')
-        self.assertEqual(rtags.get('Societe', 'travaille', '*', 'subject'),
-                          set(('primary', 'secondary')))
-        self.assertEqual(rtags.get('Note', 'travaille', '*', 'subject'),
-                          set(('secondary',)))
-        self.assertEqual(rtags.get('Note', 'tags', "*", 'subject'),
-                          set())
+class RelationTagsSetTC(BaseTestCase):
+
+    def setUp(self):
+        self.rtags = RelationTagsSet(__module__=__name__)
+        self.rtags.tag_subject_of(('Societe', 'travaille', '*'), 'primary')
+        self.rtags.tag_subject_of(('*', 'travaille', '*'), 'secondary')
+
+    def test_expansion(self):
+        self.assertEqual(self.rtags.get('Societe', 'travaille', '*', 'subject'),
+                         set(('primary', 'secondary')))
+        self.assertEqual(self.rtags.get('Note', 'travaille', '*', 'subject'),
+                         set(('secondary',)))
+        self.assertEqual(self.rtags.get('Note', 'tags', "*", 'subject'),
+                         set())
+
+    def test_expansion_with_parent(self):
+        derived_rtags = self.rtags.derive(__name__, None)
+        derived_rtags.tag_subject_of(('Societe', 'travaille', '*'), 'derived_primary')
+        self.assertEqual(derived_rtags.get('Societe', 'travaille', '*', 'subject'),
+                         set(('derived_primary', 'secondary')))
+        self.assertEqual(derived_rtags.get('Note', 'travaille', '*', 'subject'),
+                         set(('secondary',)))
+
+        derived_rtags.tag_subject_of(('*', 'travaille', '*'), 'derived_secondary')
+        self.assertEqual(derived_rtags.get('Societe', 'travaille', '*', 'subject'),
+                         set(('derived_primary', 'derived_secondary')))
+        self.assertEqual(derived_rtags.get('Note', 'travaille', '*', 'subject'),
+                         set(('derived_secondary',)))
+
+        self.assertEqual(derived_rtags.get('Note', 'tags', "*", 'subject'),
+                         set())
+
+
+class RelationTagsDictTC(BaseTestCase):
+
+    def setUp(self):
+        self.rtags = RelationTagsDict(__module__=__name__)
+        self.rtags.tag_subject_of(('Societe', 'travaille', '*'),
+                                  {'key1': 'val1', 'key2': 'val1'})
+        self.rtags.tag_subject_of(('*', 'travaille', '*'),
+                                  {'key1': 'val0', 'key3': 'val0'})
+        self.rtags.tag_subject_of(('Societe', 'travaille', '*'),
+                                  {'key2': 'val2'})
 
-    def test_rtagdict_expansion(self):
-        rtags = RelationTagsDict()
-        rtags.tag_subject_of(('Societe', 'travaille', '*'),
-                             {'key1': 'val1', 'key2': 'val1'})
-        rtags.tag_subject_of(('*', 'travaille', '*'),
-                             {'key1': 'val0', 'key3': 'val0'})
-        rtags.tag_subject_of(('Societe', 'travaille', '*'),
-                             {'key2': 'val2'})
-        self.assertEqual(rtags.get('Societe', 'travaille', '*', 'subject'),
-                          {'key1': 'val1', 'key2': 'val2', 'key3': 'val0'})
-        self.assertEqual(rtags.get('Note', 'travaille', '*', 'subject'),
-                          {'key1': 'val0', 'key3': 'val0'})
-        self.assertEqual(rtags.get('Note', 'tags', "*", 'subject'),
-                          {})
+    def test_expansion(self):
+        self.assertEqual(self.rtags.get('Societe', 'travaille', '*', 'subject'),
+                         {'key1': 'val1', 'key2': 'val2', 'key3': 'val0'})
+        self.assertEqual(self.rtags.get('Note', 'travaille', '*', 'subject'),
+                         {'key1': 'val0', 'key3': 'val0'})
+        self.assertEqual(self.rtags.get('Note', 'tags', "*", 'subject'),
+                         {})
+
+        self.rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key1', 'val4')
+        self.rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key4', 'val4')
+        self.assertEqual(self.rtags.get('Societe', 'travaille', '*', 'subject'),
+                         {'key1': 'val1', 'key2': 'val2', 'key3': 'val0', 'key4': 'val4'})
+
+    def test_expansion_with_parent(self):
+        derived_rtags = self.rtags.derive(__name__, None)
 
-        rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key1', 'val4')
-        rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key4', 'val4')
-        self.assertEqual(rtags.get('Societe', 'travaille', '*', 'subject'),
-                          {'key1': 'val1', 'key2': 'val2', 'key3': 'val0', 'key4': 'val4'})
+        derived_rtags.tag_subject_of(('Societe', 'travaille', '*'),
+                                     {'key0': 'val0'})
+        self.assertEqual(derived_rtags.get('Societe', 'travaille', '*', 'subject'),
+                         {'key0': 'val0', 'key1': 'val0', 'key3': 'val0'})
+        self.assertEqual(derived_rtags.get('Note', 'travaille', '*', 'subject'),
+                         {'key1': 'val0', 'key3': 'val0'})
+        self.assertEqual(derived_rtags.get('Note', 'tags', "*", 'subject'),
+                         {})
+
+        derived_rtags.tag_subject_of(('*', 'travaille', '*'),
+                                     {'key0': 'val00', 'key4': 'val4'})
+        self.assertEqual(derived_rtags.get('Societe', 'travaille', '*', 'subject'),
+                         {'key0': 'val0', 'key4': 'val4'})
+        self.assertEqual(derived_rtags.get('Note', 'travaille', '*', 'subject'),
+                         {'key0': 'val00', 'key4': 'val4'})
+
+
+class DeprecatedInstanceWithoutModule(BaseTestCase):
+
+    def test_deprecated_instance_without_module(self):
+        class SubRelationTags(RelationTags):
+            pass
+        with self.assertWarnsRegex(
+            DeprecationWarning,
+            'instantiate SubRelationTags with __module__=__name__',
+        ):
+            SubRelationTags()
+
 
 if __name__ == '__main__':
-    unittest_main()
+    import unittest
+    unittest.main()
--- a/cubicweb/test/unittest_schema.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/test/unittest_schema.py	Fri Feb 03 13:37:32 2017 +0100
@@ -17,7 +17,7 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """unit tests for module cubicweb.schema"""
 
-from os.path import join, dirname
+from os.path import join, dirname, splitext
 
 from logilab.common.testlib import TestCase, unittest_main
 
@@ -402,7 +402,8 @@
         self.loader.post_build_callbacks = []
 
     def _test(self, schemafile, msg):
-        self.loader.handle_file(join(DATADIR, schemafile))
+        self.loader.handle_file(join(DATADIR, schemafile),
+                                splitext(schemafile)[0])
         sch = self.loader.schemacls('toto')
         with self.assertRaises(BadSchemaDefinition) as cm:
             fill_schema(sch, self.loader.defined, False)
@@ -575,5 +576,32 @@
                                          for r, role in schema[etype].composite_rdef_roles]))
 
 
+class CWShemaTC(CubicWebTC):
+
+    def test_transform_has_permission_match(self):
+        with self.admin_access.repo_cnx() as cnx:
+            eschema = cnx.vreg.schema['EmailAddress']
+            rql_exprs = eschema.get_rqlexprs('update')
+            self.assertEqual(len(rql_exprs), 1)
+            self.assertEqual(rql_exprs[0].expression,
+                             'P use_email X, U has_update_permission P')
+            rql, found, keyarg = rql_exprs[0].transform_has_permission()
+            self.assertEqual(rql, 'Any X,P WHERE P use_email X, X eid %(x)s')
+            self.assertEqual(found, [(u'update', 1)])
+            self.assertEqual(keyarg, None)
+
+    def test_transform_has_permission_no_match(self):
+        with self.admin_access.repo_cnx() as cnx:
+            eschema = cnx.vreg.schema['EmailAddress']
+            rql_exprs = eschema.get_rqlexprs('read')
+            self.assertEqual(len(rql_exprs), 1)
+            self.assertEqual(rql_exprs[0].expression,
+                             'U use_email X')
+            rql, found, keyarg = rql_exprs[0].transform_has_permission()
+            self.assertEqual(rql, 'Any X WHERE EXISTS(U use_email X, X eid %(x)s, U eid %(u)s)')
+            self.assertEqual(found, None)
+            self.assertEqual(keyarg, None)
+
+
 if __name__ == '__main__':
     unittest_main()
--- a/cubicweb/web/application.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/application.py	Fri Feb 03 13:37:32 2017 +0100
@@ -125,8 +125,6 @@
         """return a string giving the name of the cookie used to store the
         session identifier.
         """
-        if req.https:
-            return '__%s_https_session' % self.vreg.config.appid
         return '__%s_session' % self.vreg.config.appid
 
     def get_session(self, req):
@@ -158,7 +156,7 @@
     def open_session(self, req):
         session = self.session_manager.open_session(req)
         sessioncookie = self.session_cookie(req)
-        secure = req.https and req.base_url().startswith('https://')
+        secure = req.base_url().startswith('https://')
         req.set_cookie(sessioncookie, session.sessionid,
                        maxage=None, secure=secure, httponly=True)
         if not session.anonymous_session:
@@ -252,15 +250,18 @@
             return set_cnx
 
         req.set_cnx = wrap_set_cnx(req.set_cnx)
+        tstart, cstart = time(), clock()
         try:
             return self.main_handle_request(req)
         finally:
             cnx = req.cnx
-            if cnx:
+            if cnx and cnx.executed_queries:
                 with self._logfile_lock:
+                    tend, cend = time(), clock()
                     try:
                         result = ['\n' + '*' * 80]
-                        result.append(req.url())
+                        result.append('%s -- (%.3f sec, %.3f CPU sec)' % (
+                            req.url(), tend - tstart, cend - cstart))
                         result += ['%s %s -- (%.3f sec, %.3f CPU sec)' % q
                                    for q in cnx.executed_queries]
                         cnx.executed_queries = []
@@ -331,27 +332,20 @@
             content = self.redirect_handler(req, ex)
         # Wrong, absent or Reseted credential
         except AuthenticationError:
-            # If there is an https url configured and
-            # the request does not use https, redirect to login form
-            https_url = self.vreg.config['https-url']
-            if https_url and req.base_url() != https_url:
-                req.status_out = http_client.SEE_OTHER
-                req.headers_out.setHeader('location', https_url + 'login')
+            # We assume here that in http auth mode the user *May* provide
+            # Authentification Credential if asked kindly.
+            if self.vreg.config['auth-mode'] == 'http':
+                req.status_out = http_client.UNAUTHORIZED
+            # In the other case (coky auth) we assume that there is no way
+            # for the user to provide them...
+            # XXX But WHY ?
             else:
-                # We assume here that in http auth mode the user *May* provide
-                # Authentification Credential if asked kindly.
-                if self.vreg.config['auth-mode'] == 'http':
-                    req.status_out = http_client.UNAUTHORIZED
-                # In the other case (coky auth) we assume that there is no way
-                # for the user to provide them...
-                # XXX But WHY ?
-                else:
-                    req.status_out = http_client.FORBIDDEN
-                # If previous error handling already generated a custom content
-                # do not overwrite it. This is used by LogOut Except
-                # XXX ensure we don't actually serve content
-                if not content:
-                    content = self.need_login_content(req)
+                req.status_out = http_client.FORBIDDEN
+            # If previous error handling already generated a custom content
+            # do not overwrite it. This is used by LogOut Except
+            # XXX ensure we don't actually serve content
+            if not content:
+                content = self.need_login_content(req)
         assert isinstance(content, binary_type)
         return content
 
--- a/cubicweb/web/data/cubicweb.edition.js	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/data/cubicweb.edition.js	Fri Feb 03 13:37:32 2017 +0100
@@ -9,7 +9,7 @@
 
 //============= Eproperty form functions =====================================//
 /**
- * .. function:: setPropValueWidget(varname, tabindex)
+ * .. function:: setPropValueWidget(varname)
  *
  * called on CWProperty key selection:
  * - get the selected value
@@ -17,16 +17,15 @@
  * - fill associated div with the returned html
  *
  * * `varname`, the name of the variable as used in the original creation form
- * * `tabindex`, the tabindex that should be set on the widget
  */
 
-function setPropValueWidget(varname, tabindex) {
+function setPropValueWidget(varname) {
     var key = firstSelected(document.getElementById('pkey-subject:' + varname));
     if (key) {
         var args = {
             fname: 'prop_widget',
             pageid: pageid,
-            arg: $.map([key.value, varname, tabindex], JSON.stringify)
+            arg: $.map([key.value, varname], JSON.stringify)
         };
         cw.jqNode('div:value-subject:' + varname).loadxhtml(AJAX_BASE_URL, args, 'post');
     }
--- a/cubicweb/web/formfields.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/formfields.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -111,6 +111,7 @@
     result += sorted(partresult)
     return result
 
+
 _MARKER = nullobject()
 
 
@@ -361,7 +362,6 @@
             if callable(self.value):
                 return self.value(form, self)
             return self.value
-        formattr = '%s_%s_default' % (self.role, self.name)
         if self.eidparam and self.role is not None:
             if form._cw.vreg.schema.rschema(self.name).final:
                 return form.edited_entity.e_schema.default(self.name)
@@ -1237,8 +1237,19 @@
         kwargs.setdefault('label', (eschema.type, rschema.type))
     kwargs.setdefault('help', rdef.description)
     if rschema.final:
-        fieldclass = FIELDS[targetschema]
-        if fieldclass is StringField:
+        fieldclass = kwargs.pop('fieldclass', FIELDS[targetschema])
+        if issubclass(fieldclass, 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')
+                    kwargs['%s_field' % metadata] = guess_field(eschema, metaschema,
+                                                                req=req, **metakwargs)
+        elif issubclass(fieldclass, StringField):
             if eschema.has_metadata(rschema, 'format'):
                 # use RichTextField instead of StringField if the attribute has
                 # a "format" metadata. But getting information from constraints
@@ -1255,18 +1266,6 @@
             for cstr in rdef.constraints:
                 if isinstance(cstr, SizeConstraint) and cstr.max is not None:
                     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')
-                    kwargs['%s_field' % metadata] = guess_field(eschema, metaschema,
-                                                                req=req, **metakwargs)
         return fieldclass(**kwargs)
     return RelationField.fromcardinality(card, **kwargs)
 
--- a/cubicweb/web/formwidgets.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/formwidgets.py	Fri Feb 03 13:37:32 2017 +0100
@@ -125,9 +125,6 @@
     :attr:`setdomid`
        flag telling if HTML DOM identifier should be set on input.
 
-    :attr:`settabindex`
-       flag telling if HTML tabindex attribute of inputs should be set.
-
     :attr:`suffix`
        string to use a suffix when generating input, to ease usage as a
        sub-widgets (eg widget used by another widget)
@@ -157,21 +154,17 @@
     needs_js = ()
     needs_css = ()
     setdomid = True
-    settabindex = True
     suffix = None
     # does this widget expect a vocabulary
     vocabulary_widget = False
 
-    def __init__(self, attrs=None, setdomid=None, settabindex=None, suffix=None):
+    def __init__(self, attrs=None, setdomid=None, suffix=None):
         if attrs is None:
             attrs = {}
         self.attrs = attrs
         if setdomid is not None:
             # override class's default value
             self.setdomid = setdomid
-        if settabindex is not None:
-            # override class's default value
-            self.settabindex = settabindex
         if suffix is not None:
             self.suffix = suffix
 
@@ -202,14 +195,11 @@
 
     def attributes(self, form, field):
         """Return HTML attributes for the widget, automatically setting DOM
-        identifier and tabindex when desired (see :attr:`setdomid` and
-        :attr:`settabindex` attributes)
+        identifier when desired (see :attr:`setdomid` attribute)
         """
         attrs = dict(self.attrs)
         if self.setdomid:
             attrs['id'] = field.dom_id(form, self.suffix)
-        if self.settabindex and 'tabindex' not in attrs:
-            attrs['tabindex'] = form._cw.next_tabindex()
         if 'placeholder' in attrs:
             attrs['placeholder'] = form._cw._(attrs['placeholder'])
         return attrs
@@ -386,7 +376,6 @@
     """
     type = 'hidden'
     setdomid = False  # by default, don't set id attribute on hidden input
-    settabindex = False
 
 
 class ButtonInput(Input):
@@ -682,9 +671,12 @@
     choose a date anterior(/posterior) to this DatePicker.
 
     example:
-    start and end are two JQueryDatePicker and start must always be before end
+
+    start and end are two JQueryDatePicker and start must always be before end::
+
         affk.set_field_kwargs(etype, 'start_date', widget=JQueryDatePicker(min_of='end_date'))
         affk.set_field_kwargs(etype, 'end_date', widget=JQueryDatePicker(max_of='start_date'))
+
     That way, on change of end(/start) value a new max(/min) will be set for start(/end)
     The invalid dates will be gray colored in the datepicker
     """
@@ -1000,8 +992,6 @@
         attrs = dict(self.attrs)
         if self.setdomid:
             attrs['id'] = field.dom_id(form)
-        if self.settabindex and 'tabindex' not in attrs:
-            attrs['tabindex'] = req.next_tabindex()
         # ensure something is rendered
         inputs = [u'<table><tr><th>',
                   req._('i18n_bookmark_url_path'),
@@ -1012,8 +1002,6 @@
                   u'</th><td>']
         if self.setdomid:
             attrs['id'] = field.dom_id(form, 'fqs')
-        if self.settabindex:
-            attrs['tabindex'] = req.next_tabindex()
         attrs.setdefault('cols', 60)
         attrs.setdefault('onkeyup', 'autogrow(this)')
         inputs += [tags.textarea(fqs, name=fqsqname, **attrs),
@@ -1061,9 +1049,9 @@
     css_class = 'validateButton'
 
     def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None,
-                 setdomid=None, settabindex=None,
+                 setdomid=None,
                  name='', value='', onclick=None, cwaction=None):
-        super(Button, self).__init__(attrs, setdomid, settabindex)
+        super(Button, self).__init__(attrs, setdomid)
         if isinstance(label, tuple):
             self.label = label[0]
             self.icon = label[1]
@@ -1089,8 +1077,6 @@
             attrs['name'] = self.name
             if self.setdomid:
                 attrs['id'] = self.name
-        if self.settabindex and 'tabindex' not in attrs:
-            attrs['tabindex'] = form._cw.next_tabindex()
         if self.icon:
             img = tags.img(src=form._cw.uiprops[self.icon], alt=self.icon)
         else:
--- a/cubicweb/web/request.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/request.py	Fri Feb 03 13:37:32 2017 +0100
@@ -105,28 +105,19 @@
     """
     ajax_request = False # to be set to True by ajax controllers
 
-    def __init__(self, vreg, https=False, form=None, headers=None):
+    def __init__(self, vreg, form=None, headers=None):
         """
         :vreg: Vregistry,
-        :https: boolean, s this a https request
         :form: Forms value
         :headers: dict, request header
         """
         super(_CubicWebRequestBase, self).__init__(vreg)
-        #: (Boolean) Is this an https request.
-        self.https = https
-        #: User interface property (vary with https) (see :ref:`uiprops`)
+        #: User interface property (see :ref:`uiprops`)
         self.uiprops = None
-        #: url for serving datadir (vary with https) (see :ref:`resources`)
+        #: url for serving datadir (see :ref:`resources`)
         self.datadir_url = None
-        if https and vreg.config.https_uiprops is not None:
-            self.uiprops = vreg.config.https_uiprops
-        else:
-            self.uiprops = vreg.config.uiprops
-        if https and vreg.config.https_datadir_url is not None:
-            self.datadir_url = vreg.config.https_datadir_url
-        else:
-            self.datadir_url = vreg.config.datadir_url
+        self.uiprops = vreg.config.uiprops
+        self.datadir_url = vreg.config.datadir_url
         #: enable UStringIO's write tracing
         self.tracehtml = False
         if vreg.config.debugmode:
@@ -179,22 +170,6 @@
         self.ajax_request = value
     json_request = property(_get_json_request, _set_json_request)
 
-    def _base_url(self, secure=None):
-        """return the root url of the instance
-
-        secure = False -> base-url
-        secure = None  -> https-url if req.https
-        secure = True  -> https if it exist
-        """
-        if secure is None:
-            secure = self.https
-        base_url = None
-        if secure:
-            base_url = self.vreg.config.get('https-url')
-        if base_url is None:
-            base_url = super(_CubicWebRequestBase, self)._base_url()
-        return base_url
-
     @property
     def authmode(self):
         """Authentification mode of the instance
@@ -215,13 +190,6 @@
         """
         return self.set_varmaker()
 
-    def next_tabindex(self):
-        nextfunc = self.get_page_data('nexttabfunc')
-        if nextfunc is None:
-            nextfunc = Counter(1)
-            self.set_page_data('nexttabfunc', nextfunc)
-        return nextfunc()
-
     def set_varmaker(self):
         varmaker = self.get_page_data('rql_varmaker')
         if varmaker is None:
@@ -959,7 +927,7 @@
     cnx = None
     session = None
 
-    def __init__(self, vreg, https=False, form=None, headers={}):
+    def __init__(self, vreg, form=None, headers={}):
         """"""
         self.vreg = vreg
         try:
@@ -967,8 +935,7 @@
             self.translations = vreg.config.translations
         except AttributeError:
             self.translations = {}
-        super(ConnectionCubicWebRequestBase, self).__init__(vreg, https=https,
-                                                       form=form, headers=headers)
+        super(ConnectionCubicWebRequestBase, self).__init__(vreg, form=form, headers=headers)
         self.session = _MockAnonymousSession()
         self.cnx = self.user = _NeedAuthAccessMock()
 
@@ -1016,11 +983,8 @@
     def cached_entities(self):
         return self.transaction_data.get('req_ecache', {}).values()
 
-    def drop_entity_cache(self, eid=None):
-        if eid is None:
-            self.transaction_data.pop('req_ecache', None)
-        else:
-            del self.transaction_data['req_ecache'][eid]
+    def drop_entity_cache(self):
+        self.transaction_data.pop('req_ecache', None)
 
 
 CubicWebRequestBase = ConnectionCubicWebRequestBase
--- a/cubicweb/web/test/unittest_application.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/test/unittest_application.py	Fri Feb 03 13:37:32 2017 +0100
@@ -503,7 +503,7 @@
             self.assertTrue(cnx.find('Directory', eid=subd.eid))
             self.assertTrue(cnx.find('Filesystem', eid=fs.eid))
             self.assertEqual(cnx.find('Directory', eid=subd.eid).one().parent,
-                             [topd,])
+                             (topd,))
 
     def test_subject_mixed_composite_subentity_removal_2(self):
         """Editcontroller: detaching several subentities respects each rdef's
@@ -542,7 +542,7 @@
             self.assertTrue(cnx.find('Directory', eid=subd.eid))
             self.assertTrue(cnx.find('Filesystem', eid=fs.eid))
             self.assertEqual(cnx.find('Directory', eid=subd.eid).one().parent,
-                             [topd,])
+                             (topd,))
 
     def test_object_mixed_composite_subentity_removal_2(self):
         """Editcontroller: detaching several subentities respects each rdef's
--- a/cubicweb/web/test/unittest_form.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/test/unittest_form.py	Fri Feb 03 13:37:32 2017 +0100
@@ -200,20 +200,20 @@
     def test_richtextfield_1(self):
         with self.admin_access.web_request() as req:
             req.use_fckeditor = lambda: False
-            self._test_richtextfield(req, '''<select id="description_format-subject:%(eid)s" name="description_format-subject:%(eid)s" size="1" style="display: block" tabindex="1">
+            self._test_richtextfield(req, '''<select id="description_format-subject:%(eid)s" name="description_format-subject:%(eid)s" size="1" style="display: block">
 ''' + ('<option value="text/cubicweb-page-template">text/cubicweb-page-template</option>\n'
 if HAS_TAL else '') +
 '''<option selected="selected" value="text/html">text/html</option>
 <option value="text/markdown">text/markdown</option>
 <option value="text/plain">text/plain</option>
 <option value="text/rest">text/rest</option>
-</select><textarea cols="80" id="description-subject:%(eid)s" name="description-subject:%(eid)s" onkeyup="autogrow(this)" rows="2" tabindex="2"></textarea>''')
+</select><textarea cols="80" id="description-subject:%(eid)s" name="description-subject:%(eid)s" onkeyup="autogrow(this)" rows="2"></textarea>''')
 
 
     def test_richtextfield_2(self):
         with self.admin_access.web_request() as req:
             req.use_fckeditor = lambda: True
-            self._test_richtextfield(req, '<input name="description_format-subject:%(eid)s" type="hidden" value="text/html" /><textarea cols="80" cubicweb:type="wysiwyg" id="description-subject:%(eid)s" name="description-subject:%(eid)s" onkeyup="autogrow(this)" rows="2" tabindex="1"></textarea>')
+            self._test_richtextfield(req, '<input name="description_format-subject:%(eid)s" type="hidden" value="text/html" /><textarea cols="80" cubicweb:type="wysiwyg" id="description-subject:%(eid)s" name="description-subject:%(eid)s" onkeyup="autogrow(this)" rows="2"></textarea>')
 
 
     def test_filefield(self):
@@ -229,11 +229,11 @@
                                      data=Binary(b'new widgets system'))
             form = FFForm(req, redirect_path='perdu.com', entity=file)
             self.assertMultiLineEqual(self._render_entity_field(req, 'data', form),
-                              '''<input id="data-subject:%(eid)s" name="data-subject:%(eid)s" tabindex="1" type="file" value="" />
+                              '''<input id="data-subject:%(eid)s" name="data-subject:%(eid)s" type="file" value="" />
 <a href="javascript: toggleVisibility(&#39;data-subject:%(eid)s-advanced&#39;)" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
 <div id="data-subject:%(eid)s-advanced" class="hidden">
-<label for="data_format-subject:%(eid)s">data_format</label><input id="data_format-subject:%(eid)s" maxlength="50" name="data_format-subject:%(eid)s" size="45" tabindex="2" type="text" value="text/plain" /><br/>
-<label for="data_encoding-subject:%(eid)s">data_encoding</label><input id="data_encoding-subject:%(eid)s" maxlength="20" name="data_encoding-subject:%(eid)s" size="20" tabindex="3" type="text" value="UTF-8" /><br/>
+<label for="data_format-subject:%(eid)s">data_format</label><input id="data_format-subject:%(eid)s" maxlength="50" name="data_format-subject:%(eid)s" size="45" type="text" value="text/plain" /><br/>
+<label for="data_encoding-subject:%(eid)s">data_encoding</label><input id="data_encoding-subject:%(eid)s" maxlength="20" name="data_encoding-subject:%(eid)s" size="20" type="text" value="UTF-8" /><br/>
 </div>
 <br/>
 <input name="data-subject__detach:%(eid)s" type="checkbox" />
@@ -253,17 +253,17 @@
                                      data=Binary(b'new widgets system'))
             form = EFFForm(req, redirect_path='perdu.com', entity=file)
             self.assertMultiLineEqual(self._render_entity_field(req, 'data', form),
-                              '''<input id="data-subject:%(eid)s" name="data-subject:%(eid)s" tabindex="1" type="file" value="" />
+                              '''<input id="data-subject:%(eid)s" name="data-subject:%(eid)s" type="file" value="" />
 <a href="javascript: toggleVisibility(&#39;data-subject:%(eid)s-advanced&#39;)" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
 <div id="data-subject:%(eid)s-advanced" class="hidden">
-<label for="data_format-subject:%(eid)s">data_format</label><input id="data_format-subject:%(eid)s" maxlength="50" name="data_format-subject:%(eid)s" size="45" tabindex="2" type="text" value="text/plain" /><br/>
-<label for="data_encoding-subject:%(eid)s">data_encoding</label><input id="data_encoding-subject:%(eid)s" maxlength="20" name="data_encoding-subject:%(eid)s" size="20" tabindex="3" type="text" value="UTF-8" /><br/>
+<label for="data_format-subject:%(eid)s">data_format</label><input id="data_format-subject:%(eid)s" maxlength="50" name="data_format-subject:%(eid)s" size="45" type="text" value="text/plain" /><br/>
+<label for="data_encoding-subject:%(eid)s">data_encoding</label><input id="data_encoding-subject:%(eid)s" maxlength="20" name="data_encoding-subject:%(eid)s" size="20" type="text" value="UTF-8" /><br/>
 </div>
 <br/>
 <input name="data-subject__detach:%(eid)s" type="checkbox" />
 detach attached file
 <p><b>You can either submit a new file using the browse button above, or choose to remove already uploaded file by checking the "detach attached file" check-box, or edit file content online with the widget below.</b></p>
-<textarea cols="80" name="data-subject:%(eid)s" onkeyup="autogrow(this)" rows="3" tabindex="4">new widgets system</textarea>''' % {'eid': file.eid})
+<textarea cols="80" name="data-subject:%(eid)s" onkeyup="autogrow(this)" rows="3">new widgets system</textarea>''' % {'eid': file.eid})
 
     def _modified_tzdatenaiss(self, eid, date_and_time_str=None):
         ctx = {}
@@ -303,9 +303,9 @@
         with self.admin_access.web_request() as req:
             form = PFForm(req, redirect_path='perdu.com', entity=req.user)
             self.assertMultiLineEqual(self._render_entity_field(req, 'upassword', form),
-                                  '''<input id="upassword-subject:%(eid)s" name="upassword-subject:%(eid)s" tabindex="1" type="password" value="" />
+                                  '''<input id="upassword-subject:%(eid)s" name="upassword-subject:%(eid)s" type="password" value="" />
 <br/>
-<input name="upassword-subject-confirm:%(eid)s" tabindex="1" type="password" value="" />
+<input name="upassword-subject-confirm:%(eid)s" type="password" value="" />
 &#160;
 <span class="emphasis">confirm password</span>''' % {'eid': req.user.eid})
 
--- a/cubicweb/web/test/unittest_formwidgets.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/test/unittest_formwidgets.py	Fri Feb 03 13:37:32 2017 +0100
@@ -38,7 +38,7 @@
     def test_bitselect_widget(self):
         field = formfields.guess_field(self.schema['CWAttribute'], self.schema['ordernum'])
         field.choices = [('un', '1',), ('deux', '2',)]
-        widget = formwidgets.BitSelect(settabindex=False)
+        widget = formwidgets.BitSelect()
         req = fake.FakeRequest(form={'ordernum-subject:A': ['1', '2']})
         form = mock(_cw=req, formvalues={}, edited_entity=mock(eid='A'),
                     form_previous_values=())
@@ -62,7 +62,7 @@
             field = form.field_by_name('bool')
             widget = field.widget
             self.assertMultiLineEqual(widget._render(form, field, None),
-                '<label><input id="bool" name="bool" tabindex="1" '
+                '<label><input id="bool" name="bool" '
                 'type="checkbox" value="1" />&#160;'
                 'python &gt;&gt; others</label>')
 
--- a/cubicweb/web/test/unittest_http_headers.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/test/unittest_http_headers.py	Fri Feb 03 13:37:32 2017 +0100
@@ -13,6 +13,7 @@
         with self.assertRaises(ValueError):
             http_headers.generateTrueFalse('any value')
 
+
 if __name__ == '__main__':
     from unittest import main
     main()
--- a/cubicweb/web/test/unittest_reledit.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/test/unittest_reledit.py	Fri Feb 03 13:37:32 2017 +0100
@@ -73,13 +73,13 @@
 <table class="">
 <tr class="title_subject_row">
 <td>
-<input id="title-subject:%(eid)s" maxlength="32" name="title-subject:%(eid)s" size="32" tabindex="1" type="text" value="cubicweb-world-domination" />
+<input id="title-subject:%(eid)s" maxlength="32" name="title-subject:%(eid)s" size="32" type="text" value="cubicweb-world-domination" />
 </td></tr>
 </table></fieldset>
 <table class="buttonbar">
 <tr>
-<td><button class="validateButton" tabindex="2" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
-<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;title-subject-%(eid)s&#39;)" tabindex="3" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
+<td><button class="validateButton" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
+<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;title-subject-%(eid)s&#39;)" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
 </tr></table>
 </fieldset>
 <iframe width="0px" height="0px" src="javascript: void(0);" name="eformframe" id="eformframe"></iframe>
@@ -108,23 +108,23 @@
 <tr class="title_subject_row">
 <th class="labelCol"><label class="required" for="title-subject:A">title</label></th>
 <td>
-<input id="title-subject:A" maxlength="50" name="title-subject:A" size="45" tabindex="4" type="text" value="" />
+<input id="title-subject:A" maxlength="50" name="title-subject:A" size="45" type="text" value="" />
 </td></tr>
 <tr class="description_subject_row">
 <th class="labelCol"><label for="description-subject:A">description</label></th>
 <td>
-<input name="description_format-subject:A" type="hidden" value="text/html" /><textarea cols="80" cubicweb:type="wysiwyg" id="description-subject:A" name="description-subject:A" onkeyup="autogrow(this)" rows="2" tabindex="5"></textarea>
+<input name="description_format-subject:A" type="hidden" value="text/html" /><textarea cols="80" cubicweb:type="wysiwyg" id="description-subject:A" name="description-subject:A" onkeyup="autogrow(this)" rows="2"></textarea>
 </td></tr>
 <tr class="rss_url_subject_row">
 <th class="labelCol"><label for="rss_url-subject:A">rss_url</label></th>
 <td>
-<input id="rss_url-subject:A" maxlength="128" name="rss_url-subject:A" size="45" tabindex="6" type="text" value="" />
+<input id="rss_url-subject:A" maxlength="128" name="rss_url-subject:A" size="45" type="text" value="" />
 </td></tr>
 </table></fieldset>
 <table class="buttonbar">
 <tr>
-<td><button class="validateButton" tabindex="7" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
-<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;long_desc-subject-%(eid)s&#39;)" tabindex="8" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
+<td><button class="validateButton" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
+<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;long_desc-subject-%(eid)s&#39;)" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
 </tr></table>
 </fieldset>
 <iframe width="0px" height="0px" src="javascript: void(0);" name="eformframe" id="eformframe"></iframe>
@@ -152,7 +152,7 @@
 <table class="">
 <tr class="manager_subject_row">
 <td>
-<select id="manager-subject:%(eid)s" name="manager-subject:%(eid)s" size="1" tabindex="9">
+<select id="manager-subject:%(eid)s" name="manager-subject:%(eid)s" size="1">
 <option value="__cubicweb_internal_field__"></option>
 <option value="%(toto)s">Toto</option>
 </select>
@@ -160,8 +160,8 @@
 </table></fieldset>
 <table class="buttonbar">
 <tr>
-<td><button class="validateButton" tabindex="10" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
-<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;manager-subject-%(eid)s&#39;)" tabindex="11" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
+<td><button class="validateButton" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
+<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel(&#39;manager-subject-%(eid)s&#39;)" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
 </tr></table>
 </fieldset>
 <iframe width="0px" height="0px" src="javascript: void(0);" name="eformframe" id="eformframe"></iframe>
--- a/cubicweb/web/test/unittest_request.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/test/unittest_request.py	Fri Feb 03 13:37:32 2017 +0100
@@ -71,28 +71,13 @@
 
 class WebRequestTC(unittest.TestCase):
 
-    def test_base_url(self):
-        dummy_vreg = FakeCWRegistryStore(FakeConfig(), initlog=False)
-        dummy_vreg.config['base-url'] = 'http://babar.com/'
-        dummy_vreg.config['https-url'] = 'https://toto.com/'
-
-        req = CubicWebRequestBase(dummy_vreg, https=False)
-        self.assertEqual('http://babar.com/', req.base_url())
-        self.assertEqual('http://babar.com/', req.base_url(False))
-        self.assertEqual('https://toto.com/', req.base_url(True))
-
-        req = CubicWebRequestBase(dummy_vreg, https=True)
-        self.assertEqual('https://toto.com/', req.base_url())
-        self.assertEqual('http://babar.com/', req.base_url(False))
-        self.assertEqual('https://toto.com/', req.base_url(True))
-
     def test_negotiated_language(self):
         vreg = FakeCWRegistryStore(FakeConfig(), initlog=False)
         vreg.config.translations = {'fr': (None, None), 'en': (None, None)}
         headers = {
             'Accept-Language': 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3',
         }
-        req = CubicWebRequestBase(vreg, https=False, headers=headers)
+        req = CubicWebRequestBase(vreg, headers=headers)
         self.assertEqual(req.negotiated_language(), 'fr')
 
     def test_build_url_language_from_url(self):
@@ -100,7 +85,7 @@
         vreg.config['base-url'] = 'http://testing.fr/cubicweb/'
         vreg.config['language-mode'] = 'url-prefix'
         vreg.config.translations['fr'] = text_type, text_type
-        req = CubicWebRequestBase(vreg, https=False)
+        req = CubicWebRequestBase(vreg)
         # Override from_controller to avoid getting into relative_path method,
         # which is not implemented in CubicWebRequestBase.
         req.from_controller = lambda : 'not view'
--- a/cubicweb/web/test/unittest_uicfg.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/test/unittest_uicfg.py	Fri Feb 03 13:37:32 2017 +0100
@@ -16,6 +16,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/>.
 import copy
+import warnings
+
 from logilab.common.testlib import tag
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web import uihelper, formwidgets as fwdgs
@@ -70,7 +72,10 @@
     def test_uihelper_set_fields_order(self):
         afk_get = uicfg.autoform_field_kwargs.get
         self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {})
-        uihelper.set_fields_order('CWUser', ('login', 'firstname', 'surname'))
+        with warnings.catch_warnings(record=True) as w:
+            uihelper.set_fields_order('CWUser', ('login', 'firstname', 'surname'))
+            self.assertEqual(len(w), 1)
+            self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
         self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {'order': 1})
 
     @tag('uicfg', 'order', 'func')
@@ -86,7 +91,10 @@
         afk_get = uicfg.autoform_field_kwargs.get
         self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {})
         wdg = fwdgs.TextInput({'size': 30})
-        uihelper.set_field_kwargs('CWUser', 'firstname', widget=wdg)
+        with warnings.catch_warnings(record=True) as w:
+            uihelper.set_field_kwargs('CWUser', 'firstname', widget=wdg)
+            self.assertEqual(len(w), 1)
+            self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
         self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {'widget': wdg})
 
     @tag('uihelper', 'hidden', 'func')
@@ -95,11 +103,17 @@
         section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject')
         self.assertCountEqual(section_conf, ['main_attributes', 'muledit_attributes'])
         # hide field in main form
-        uihelper.hide_fields('CWUser', ('login', 'in_group'))
+        with warnings.catch_warnings(record=True) as w:
+            uihelper.hide_fields('CWUser', ('login', 'in_group'))
+            self.assertEqual(len(w), 1)
+            self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
         section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject')
         self.assertCountEqual(section_conf, ['main_hidden', 'muledit_attributes'])
         # hide field in muledit form
-        uihelper.hide_fields('CWUser', ('login', 'in_group'), formtype='muledit')
+        with warnings.catch_warnings(record=True) as w:
+            uihelper.hide_fields('CWUser', ('login', 'in_group'), formtype='muledit')
+            self.assertEqual(len(w), 1)
+            self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
         section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject')
         self.assertCountEqual(section_conf, ['main_hidden', 'muledit_hidden'])
 
--- a/cubicweb/web/test/unittest_views_basecontrollers.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/test/unittest_views_basecontrollers.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -27,22 +27,20 @@
 from logilab.common.testlib import unittest_main
 from logilab.common.decorators import monkeypatch
 
-from cubicweb import Binary, NoSelectableObject, ValidationError, AuthenticationError
+from cubicweb import Binary, NoSelectableObject, ValidationError, transaction as tx
 from cubicweb.schema import RRQLExpression
+from cubicweb.predicates import is_instance
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.devtools.webtest import CubicWebTestTC
 from cubicweb.devtools.httptest import CubicWebServerTC
 from cubicweb.utils import json_dumps
 from cubicweb.uilib import rql_for_eid
-from cubicweb.web import Redirect, RemoteCallFailed, http_headers
-import cubicweb.server.session
-from cubicweb.server.session import Connection
+from cubicweb.web import Redirect, RemoteCallFailed, http_headers, formfields as ff
 from cubicweb.web.views.autoform import get_pending_inserts, get_pending_deletes
 from cubicweb.web.views.basecontrollers import JSonController, xhtmlize, jsonize
 from cubicweb.web.views.ajaxcontroller import ajaxfunc, AjaxFunction
-import cubicweb.transaction as tx
+from cubicweb.server.session import Connection
 from cubicweb.server.hook import Hook, Operation
-from cubicweb.predicates import is_instance
 
 
 class ViewControllerTC(CubicWebTestTC):
@@ -617,6 +615,58 @@
             finally:
                 blogentry.__class__.cw_skip_copy_for = []
 
+    def test_avoid_multiple_process_posted(self):
+        # test that when some entity is being created and data include non-inlined relations, the
+        # values for this relation are stored for later usage, without calling twice field's
+        # process_form method, which may be unexpected for custom fields
+
+        orig_process_posted = ff.RelationField.process_posted
+
+        def count_process_posted(self, form):
+            res = list(orig_process_posted(self, form))
+            nb_process_posted_calls[0] += 1
+            return res
+
+        ff.RelationField.process_posted = count_process_posted
+
+        try:
+            with self.admin_access.web_request() as req:
+                gueid = req.execute('CWGroup G WHERE G name "users"')[0][0]
+                req.form = {
+                    'eid': 'X',
+                    '__type:X': 'CWUser',
+                    '_cw_entity_fields:X': 'login-subject,upassword-subject,in_group-subject',
+                    'login-subject:X': u'adim',
+                    'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
+                    'in_group-subject:X': repr(gueid),
+                }
+                nb_process_posted_calls = [0]
+                self.expect_redirect_handle_request(req, 'edit')
+                self.assertEqual(nb_process_posted_calls[0], 1)
+                user = req.find('CWUser', login=u'adim').one()
+                self.assertEqual(set(g.eid for g in user.in_group), set([gueid]))
+                req.form = {
+                    'eid': ['X', 'Y'],
+                    '__type:X': 'CWUser',
+                    '_cw_entity_fields:X': 'login-subject,upassword-subject,in_group-subject',
+                    'login-subject:X': u'dlax',
+                    'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
+                    'in_group-subject:X': repr(gueid),
+
+                    '__type:Y': 'EmailAddress',
+                    '_cw_entity_fields:Y': 'address-subject,use_email-object',
+                    'address-subject:Y': u'dlax@cw.org',
+                    'use_email-object:Y': 'X',
+                }
+                nb_process_posted_calls = [0]
+                self.expect_redirect_handle_request(req, 'edit')
+                self.assertEqual(nb_process_posted_calls[0], 3)  # 3 = 1 (in_group) + 2 (use_email)
+                user = req.find('CWUser', login=u'dlax').one()
+                self.assertEqual(set(e.address for e in user.use_email), set(['dlax@cw.org']))
+
+        finally:
+            ff.RelationField.process_posted = orig_process_posted
+
     def test_nonregr_eetype_etype_editing(self):
         """non-regression test checking that a manager user can edit a CWEType entity
         """
@@ -669,7 +719,6 @@
             self.assertEqual(e.title, '"13:03:40"')
             self.assertEqual(e.content, '"13:03:43"')
 
-
     def test_nonregr_multiple_empty_email_addr(self):
         with self.admin_access.web_request() as req:
             gueid = req.execute('CWGroup G WHERE G name "users"')[0][0]
--- a/cubicweb/web/test/unittest_views_baseviews.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/test/unittest_views_baseviews.py	Fri Feb 03 13:37:32 2017 +0100
@@ -156,5 +156,138 @@
                                       b'<head>'],
                                      source_lines[:3])
 
+class BaseViewsTC(CubicWebTC):
+
+    def test_null(self):
+        with self.admin_access.web_request() as req:
+            rset = req.execute('Any X WHERE X login "admin"')
+            result = req.view('null', rset)
+            self.assertEqual(result, u'')
+
+    def test_final(self):
+        with self.admin_access.web_request() as req:
+            rset = req.execute('Any "<script></script>"')
+            result = req.view('final', rset)
+            self.assertEqual(result, u'&lt;script&gt;&lt;/script&gt;')
+
+    def test_incontext(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            result = entity.view('incontext')
+            expected = (u'<a href="http://testing.fr/cubicweb/%d" title="">'
+                        u'&lt;script&gt;&lt;/script&gt;</a>' % entity.eid)
+            self.assertEqual(result, expected)
+
+    def test_outofcontext(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            result = entity.view('outofcontext')
+            expect = (u'<a href="http://testing.fr/cubicweb/%d" title="">'
+                      u'&lt;script&gt;&lt;/script&gt;</a>' % entity.eid)
+            self.assertEqual(result, expect)
+
+    def test_outofcontext(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            result = entity.view('oneline')
+            expect = (u'<a href="http://testing.fr/cubicweb/%d" title="">'
+                      u'&lt;script&gt;&lt;/script&gt;</a>' % entity.eid)
+            self.assertEqual(result, expect)
+
+    def test_text(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            result = entity.view('text')
+            self.assertEqual(result, u'<script></script>')
+
+    def test_textincontext(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            result = entity.view('textincontext')
+            self.assertEqual(result, u'<script></script>')
+
+    def test_textoutofcontext(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            result = entity.view('textoutofcontext')
+            self.assertEqual(result, u'<script></script>')
+
+    def test_list(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            rset = req.execute('Any X WHERE X is CWUser')
+            result = req.view('list', rset)
+            expected = u'''<ul class="section">
+<li><a href="http://testing.fr/cubicweb/%d" title="">&lt;script&gt;&lt;/script&gt;</a></li>
+<li><a href="http://testing.fr/cubicweb/cwuser/admin" title="">admin</a></li>
+<li><a href="http://testing.fr/cubicweb/cwuser/anon" title="">anon</a></li>
+</ul>
+''' % entity.eid
+            self.assertEqual(result, expected)
+
+    def test_simplelist(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            rset = req.execute('Any X WHERE X is CWUser')
+            result = req.view('simplelist', rset)
+            expected = (
+                u'<div class="section">'
+                u'<a href="http://testing.fr/cubicweb/%d" title="">'
+                u'&lt;script&gt;&lt;/script&gt;</a></div>'
+                u'<div class="section">'
+                u'<a href="http://testing.fr/cubicweb/cwuser/admin" title="">admin</a></div>'
+                u'<div class="section">'
+                u'<a href="http://testing.fr/cubicweb/cwuser/anon" title="">anon</a></div>'
+                % entity.eid
+            )
+            self.assertEqual(result, expected)
+
+    def test_sameetypelist(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            rset = req.execute('Any X WHERE X is CWUser')
+            result = req.view('sameetypelist', rset)
+            expected = (
+                u'<h1>CWUser_plural</h1>'
+                u'<div class="section">'
+                u'<a href="http://testing.fr/cubicweb/%d" title="">'
+                u'&lt;script&gt;&lt;/script&gt;</a></div>'
+                u'<div class="section">'
+                u'<a href="http://testing.fr/cubicweb/cwuser/admin" title="">admin</a></div>'
+                u'<div class="section">'
+                u'<a href="http://testing.fr/cubicweb/cwuser/anon" title="">anon</a></div>'
+                % entity.eid
+            )
+            self.assertEqual(expected, result)
+
+    def test_sameetypelist(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            rset = req.execute('Any X WHERE X is CWUser')
+            result = req.view('csv', rset)
+            expected = (
+                u'<a href="http://testing.fr/cubicweb/%d" title="">&lt;script&gt;&lt;/script&gt;</a>, '
+                u'<a href="http://testing.fr/cubicweb/cwuser/admin" title="">admin</a>, '
+                u'<a href="http://testing.fr/cubicweb/cwuser/anon" title="">anon</a>'
+                % entity.eid
+            )
+            self.assertEqual(result, expected)
+
+    def test_metadata(self):
+        with self.admin_access.web_request() as req:
+            entity = req.create_entity('CWUser', login=u'<script></script>', upassword=u'toto')
+            entity.cw_set(creation_date=u'2000-01-01 00:00:00')
+            entity.cw_set(modification_date=u'2015-01-01 00:00:00')
+            result = entity.view('metadata')
+            expected = (
+                u'<div>CWUser #%d - <span>latest update on</span>'
+                u' <span class="value">2015/01/01</span>,'
+                u' <span>created on</span>'
+                u' <span class="value">2000/01/01</span></div>'
+                % entity.eid
+            )
+            self.assertEqual(result, expected)
+
+
 if __name__ == '__main__':
     unittest_main()
--- a/cubicweb/web/test/unittest_views_forms.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/test/unittest_views_forms.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2014-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -20,6 +20,8 @@
 
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web.views.autoform import InlinedFormField
+from cubicweb.web.views.forms import EntityFieldsForm
+
 
 class InlinedFormTC(CubicWebTC):
 
@@ -68,7 +70,21 @@
                                                   InlinedFormField(view=formview)])
                 self.assertTrue(formview._get_removejs())
 
+    def test_field_by_name_consider_aff(self):
+        class MyField(object):
+            def __init__(self, *args, **kwargs):
+                pass
+
+        EntityFieldsForm.uicfg_aff.tag_attribute(('CWUser', 'firstname'), MyField)
+        try:
+            with self.admin_access.web_request() as req:
+                form = req.vreg['forms'].select('base', req, entity=req.user)
+                self.assertIsInstance(form.field_by_name('firstname', 'subject', req.user.e_schema),
+                                      MyField)
+        finally:
+            EntityFieldsForm.uicfg_aff.del_rtag('CWUser', 'firstname', '*', 'subject')
+
 
 if __name__ == '__main__':
-    from logilab.common.testlib import unittest_main
-    unittest_main()
+    import unittest
+    unittest.main()
--- a/cubicweb/web/views/actions.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/actions.py	Fri Feb 03 13:37:32 2017 +0100
@@ -48,7 +48,7 @@
         # display action anyway
         form = entity._cw.vreg['forms'].select('edition', entity._cw,
                                                entity=entity, mainform=False)
-        for dummy in form.editable_relations():
+        for dummy in form.iter_editable_relations():
             return 1
         for dummy in form.inlined_form_views():
             return 1
--- a/cubicweb/web/views/autoform.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/autoform.py	Fri Feb 03 13:37:32 2017 +0100
@@ -565,9 +565,9 @@
                 w(u'</tr>')
         w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
         w(u'<th class="labelCol">')
-        w(u'<select id="relationSelector_%s" tabindex="%s" '
+        w(u'<select id="relationSelector_%s" '
           'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
-          % (eid, req.next_tabindex(), xml_escape(json_dumps(eid))))
+          % (eid, xml_escape(json_dumps(eid))))
         w(u'<option value="">%s</option>' % _('select a relation'))
         for i18nrtype, rschema, role in field.relations:
             # more entities to link to
@@ -861,24 +861,25 @@
         """return a list of (relation schema, role) to edit for the entity"""
         if self.display_fields is not None:
             schema = self._cw.vreg.schema
-            return [(schema[rtype], role) for rtype, role in self.display_fields]
+            for rtype, role in self.display_fields:
+                yield (schema[rtype], role)
         if self.edited_entity.has_eid() and not self.edited_entity.cw_has_perm('update'):
-            return []
+            return
         action = 'update' if self.edited_entity.has_eid() else 'add'
-        return [(rtype, role) for rtype, _, role in self._relations_by_section(
-            'attributes', action, strict)]
+        for rtype, _, role in self._relations_by_section('attributes', action, strict):
+            yield (rtype, role)
 
     def editable_relations(self):
         """return a sorted list of (relation's label, relation'schema, role) for
         relations in the 'relations' section
         """
-        result = []
-        for rschema, _, role in self._relations_by_section('relations',
-                                                           strict=True):
-            result.append( (rschema.display_name(self.edited_entity._cw, role,
-                                                 self.edited_entity.cw_etype),
-                            rschema, role) )
-        return sorted(result)
+        return sorted(self.iter_editable_relations())
+
+    def iter_editable_relations(self):
+        for rschema, _, role in self._relations_by_section('relations', strict=True):
+            yield (rschema.display_name(self.edited_entity._cw, role,
+                                        self.edited_entity.cw_etype),
+                   rschema, role)
 
     def inlined_relations(self):
         """return a list of (relation schema, target schemas, role) matching
@@ -889,10 +890,8 @@
     # inlined forms control ####################################################
 
     def inlined_form_views(self):
-        """compute and return list of inlined form views (hosting the inlined
-        form object)
+        """Yield inlined form views (hosting the inlined form object)
         """
-        allformviews = []
         entity = self.edited_entity
         for rschema, ttypes, role in self.inlined_relations():
             # show inline forms only if there's one possible target type
@@ -904,11 +903,15 @@
                 continue
             tschema = ttypes[0]
             ttype = tschema.type
-            formviews = list(self.inline_edition_form_view(rschema, ttype, role))
+            existing = bool(entity.related(rschema, role)) if entity.has_eid() else False
+            for formview in self.inline_edition_form_view(rschema, ttype, role):
+                yield formview
+                existing = True
             card = rschema.role_rdef(entity.e_schema, ttype, role).role_cardinality(role)
-            existing = entity.related(rschema, role) if entity.has_eid() else formviews
             if self.should_display_inline_creation_form(rschema, existing, card):
-                formviews += self.inline_creation_form_view(rschema, ttype, role)
+                for formview in self.inline_creation_form_view(rschema, ttype, role):
+                    yield formview
+                    existing = True
             # we can create more than one related entity, we thus display a link
             # to add new related entities
             if self.must_display_add_new_relation_link(rschema, role, tschema,
@@ -918,9 +921,7 @@
                     etype=ttype, rtype=rschema, role=role, card=card,
                     peid=self.edited_entity.eid,
                     petype=self.edited_entity.e_schema, pform=self)
-                formviews.append(addnewlink)
-            allformviews += formviews
-        return allformviews
+                yield addnewlink
 
     def should_display_inline_creation_form(self, rschema, existing, card):
         """return true if a creation form should be inlined
--- a/cubicweb/web/views/basecomponents.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/basecomponents.py	Fri Feb 03 13:37:32 2017 +0100
@@ -66,9 +66,9 @@
             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" />
+<input type="text" id="rql" name="rql" value="%s"  title="%s" accesskey="q" class="searchField" />
 ''' % (not self.cw_propval('visible') and 'hidden' or '',
-       req.build_url('view'), xml_escape(rql), req._('full text or RQL query'), req.next_tabindex()))
+       req.build_url('view'), xml_escape(rql), req._('full text or RQL query')))
         if req.search_state[0] != 'normal':
             self.w(u'<input type="hidden" name="__mode" value="%s"/>'
                    % ':'.join(req.search_state[1]))
@@ -169,7 +169,8 @@
     def render(self, w):
         # display useractions and siteactions
         self._cw.add_css('cubicweb.pictograms.css')
-        actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset)
+        actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset,
+                                                            view=self.cw_extra_kwargs['view'])
         box = MenuWidget('', 'userActionsBox', _class='', islist=False)
         menu = PopupBoxMenu(self._cw.user.login, isitem=False, link_class='icon-user')
         box.append(menu)
--- a/cubicweb/web/views/basetemplates.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/basetemplates.py	Fri Feb 03 13:37:32 2017 +0100
@@ -18,20 +18,20 @@
 """default templates for CubicWeb web client"""
 
 
-from cubicweb import _
-
 from logilab.mtconverter import xml_escape
 from logilab.common.deprecation import class_renamed
 from logilab.common.registry import objectify_predicate
 from logilab.common.decorators import classproperty
 
-from cubicweb.predicates import match_kwargs, no_cnx, anonymous_user
+from cubicweb import _
+from cubicweb.predicates import match_kwargs, anonymous_user
 from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW, StartupView
 from cubicweb.utils import UStringIO
 from cubicweb.schema import display_name
-from cubicweb.web import component, formfields as ff, formwidgets as fw
+from cubicweb.web import formfields as ff, formwidgets as fw
 from cubicweb.web.views import forms
 
+
 # main templates ##############################################################
 
 class LogInOutTemplate(MainTemplate):
@@ -92,6 +92,7 @@
     if req.form.get('__modal', None):
         return 1
 
+
 @objectify_predicate
 def templatable_view(cls, req, rset, *args, **kwargs):
     view = kwargs.pop('view', None)
@@ -176,7 +177,6 @@
 
     def template_html_header(self, content_type, page_title, additional_headers=()):
         w = self.whead
-        lang = self._cw.lang
         self.write_doctype()
         self._cw.html_headers.define_var('BASE_URL', self._cw.base_url())
         self._cw.html_headers.define_var('DATA_URL', self._cw.datadir_url)
@@ -208,14 +208,13 @@
         self.w(u'</td>\n')
         self.nav_column(view, 'right')
         self.w(u'</tr></table></div>\n')
-        self.wview('footer', rset=self.cw_rset)
+        self.wview('footer', rset=self.cw_rset, view=view)
         self.w(u'</body>')
 
     def nav_column(self, view, context):
         boxes = list(self._cw.vreg['ctxcomponents'].poss_visible_objects(
             self._cw, rset=self.cw_rset, view=view, context=context))
         if boxes:
-            getlayout = self._cw.vreg['components'].select
             self.w(u'<td id="navColumn%s"><div class="navboxes">\n' % context.capitalize())
             for box in boxes:
                 box.render(w=self.w, view=view)
@@ -248,7 +247,6 @@
 
     def template_header(self, content_type, view=None, page_title='', additional_headers=()):
         w = self.whead
-        lang = self._cw.lang
         self.write_doctype()
         w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
           % (content_type, self._cw.encoding))
@@ -269,7 +267,6 @@
         page_title = page_title or view.page_title()
         additional_headers = additional_headers or view.html_headers()
         whead = self.whead
-        lang = self._cw.lang
         self.write_doctype()
         whead(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
               % (content_type, self._cw.encoding))
@@ -337,10 +334,10 @@
 
     def alternates(self):
         urlgetter = self._cw.vreg['components'].select_or_none('rss_feed_url',
-                                                           self._cw, rset=self.cw_rset)
+                                                               self._cw, rset=self.cw_rset)
         if urlgetter is not None:
             self.whead(u'<link rel="alternate" type="application/rss+xml" title="RSS feed" href="%s"/>\n'
-                       %  xml_escape(urlgetter.feed_url()))
+                       % xml_escape(urlgetter.feed_url()))
 
 
 class HTMLPageHeader(View):
@@ -398,7 +395,8 @@
 
     def footer_content(self):
         actions = self._cw.vreg['actions'].possible_actions(self._cw,
-                                                            rset=self.cw_rset)
+                                                            rset=self.cw_rset,
+                                                            view=self.cw_extra_kwargs['view'])
         footeractions = actions.get('footer', ())
         for i, action in enumerate(footeractions):
             self.w(u'<a href="%s">%s</a>' % (action.url(),
@@ -406,6 +404,7 @@
             if i < (len(footeractions) - 1):
                 self.w(u' | ')
 
+
 class HTMLContentHeader(View):
     """default html page content header:
     * include message component if selectable for this request
@@ -439,6 +438,7 @@
                 comp.render(w=self.w, view=view)
             self.w(u'</div>')
 
+
 class BaseLogForm(forms.FieldsForm):
     """Abstract Base login form to be used by any login form
     """
@@ -461,7 +461,7 @@
                         fw.ResetButton(label=_('cancel'),
                                        attrs={'class': 'loginButton',
                                               'onclick': onclick}),]
-        ## Can't shortcut next access because __dict__ is a "dictproxy" which 
+        ## Can't shortcut next access because __dict__ is a "dictproxy" which
         ## does not support items assignement.
         # cls.__dict__['form_buttons'] = form_buttons
         return form_buttons
@@ -474,9 +474,10 @@
             url_args = {}
             if target and target != '/':
                 url_args['postlogin_path'] = target
-            return self._cw.build_url('login', __secure__=True, **url_args)
+            return self._cw.build_url('login', **url_args)
         return super(BaseLogForm, self).form_action()
 
+
 class LogForm(BaseLogForm):
     """Simple login form that send username and password
     """
@@ -488,7 +489,7 @@
     __password = ff.StringField('__password', label=_('password'),
                                 widget=fw.PasswordSingleInput({'class': 'data'}))
 
-    onclick_args =  ('popupLoginBox', '__login')
+    onclick_args = ('popupLoginBox', '__login')
 
 
 class LogFormView(View):
@@ -531,4 +532,5 @@
         form.render(w=self.w, table_class='', display_progress_div=False)
         cw.html_headers.add_onload('jQuery("#__login:visible").focus()')
 
+
 LogFormTemplate = class_renamed('LogFormTemplate', LogFormView)
--- a/cubicweb/web/views/boxes.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/boxes.py	Fri Feb 03 13:37:32 2017 +0100
@@ -67,7 +67,7 @@
         self._menus_by_id = {}
         # build list of actions
         actions = self._cw.vreg['actions'].possible_actions(self._cw, self.cw_rset,
-                                                            **self.cw_extra_kwargs)
+                                                            view=self.cw_extra_kwargs['view'])
         other_menu = self._get_menu('moreactions', _('more actions'))
         for category, defaultmenu in (('mainactions', self),
                                       ('moreactions', other_menu),
@@ -138,11 +138,11 @@
     order = 0
     formdef = u"""<form action="%(action)s">
 <table id="%(id)s"><tr><td>
-<input class="norql" type="text" accesskey="q" tabindex="%(tabindex1)s" title="search text" value="%(value)s" name="rql" />
+<input class="norql" type="text" accesskey="q" title="search text" value="%(value)s" name="rql" />
 <input type="hidden" name="__fromsearchbox" value="1" />
 <input type="hidden" name="subvid" value="tsearch" />
 </td><td>
-<input tabindex="%(tabindex2)s" type="submit" class="rqlsubmit" value="" />
+<input type="submit" class="rqlsubmit" value="" />
  </td></tr></table>
  </form>"""
 
@@ -155,13 +155,10 @@
             rql = self._cw.form.get('rql', '')
         else:
             rql = ''
-        tabidx1 = self._cw.next_tabindex()
-        tabidx2 = self._cw.next_tabindex()
         w(self.formdef % {'action': self._cw.build_url('view'),
                           'value': xml_escape(rql),
-                          'id': self.cw_extra_kwargs.get('domid', 'tsearch'),
-                          'tabindex1': tabidx1,
-                          'tabindex2': tabidx2})
+                          'id': self.cw_extra_kwargs.get('domid', 'tsearch')
+                          })
 
 
 # boxes disabled by default ###################################################
--- a/cubicweb/web/views/calendar.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/calendar.py	Fri Feb 03 13:37:32 2017 +0100
@@ -41,12 +41,17 @@
                _('november'), _('december')
                )
 
+ICAL_EVENT = "event"
+ICAL_TODO = "todo"
 
 class ICalendarableAdapter(EntityAdapter):
     __needs_bw_compat__ = True
     __regid__ = 'ICalendarable'
     __abstract__ = True
 
+    # component type
+    component = ICAL_EVENT
+
     @property
     def start(self):
         """return start date"""
@@ -64,7 +69,7 @@
     from vobject import iCalendar
 
     class iCalView(EntityView):
-        """A calendar view that generates a iCalendar file (RFC 2445)
+        """A calendar view that generates a iCalendar file (RFC 5545)
 
         Does apply to ICalendarable compatible entities
         """
@@ -79,14 +84,21 @@
             ical = iCalendar()
             for i in range(len(self.cw_rset.rows)):
                 task = self.cw_rset.complete_entity(i, 0)
-                event = ical.add('vevent')
-                event.add('summary').value = task.dc_title()
-                event.add('description').value = task.dc_description()
-                icalendarable = task.cw_adapt_to('ICalendarable')
-                if icalendarable.start:
-                    event.add('dtstart').value = icalendarable.start
-                if icalendarable.stop:
-                    event.add('dtend').value = icalendarable.stop
+                ical_task = task.cw_adapt_to('ICalendarable')
+                if ical_task.component == ICAL_TODO:
+                    elt = ical.add('vtodo')
+                    stop_kw = "due"
+                else:
+                    elt = ical.add('vevent')
+                    stop_kw = "dtend"
+                elt.add('uid').value = task.absolute_url() # unique stable id
+                elt.add('url').value = task.absolute_url()
+                elt.add('summary').value = task.dc_title()
+                elt.add('description').value = task.dc_description()
+                if ical_task.start:
+                    elt.add('dtstart').value = ical_task.start
+                if ical_task.stop:
+                    elt.add(stop_kw).value = ical_task.stop
 
             buff = ical.serialize()
             if not isinstance(buff, unicode):
--- a/cubicweb/web/views/cwproperties.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/cwproperties.py	Fri Feb 03 13:37:32 2017 +0100
@@ -315,9 +315,8 @@
     def render(self, form, renderer):
         wdg = self.get_widget(form)
         # pylint: disable=E1101
-        wdg.attrs['tabindex'] = form._cw.next_tabindex()
-        wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % (
-            form.edited_entity.eid, form._cw.next_tabindex())
+        wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s')" % (
+            form.edited_entity.eid)
         return wdg.render(form, self, renderer)
 
     def vocabulary(self, form):
@@ -335,10 +334,8 @@
     """
     widget = PlaceHolderWidget
 
-    def render(self, form, renderer=None, tabindex=None):
+    def render(self, form, renderer=None):
         wdg = self.get_widget(form)
-        if tabindex is not None:
-            wdg.attrs['tabindex'] = tabindex
         return wdg.render(form, self, renderer)
 
     def form_init(self, form):
@@ -422,7 +419,7 @@
 
 
 @ajaxfunc(output_type='xhtml')
-def prop_widget(self, propkey, varname, tabindex=None):
+def prop_widget(self, propkey, varname):
     """specific method for CWProperty handling"""
     entity = self._cw.vreg['etypes'].etype_class('CWProperty')(self._cw)
     entity.eid = varname
@@ -431,7 +428,7 @@
     form.build_context()
     vfield = form.field_by_name('value', 'subject')
     renderer = formrenderers.FormRenderer(self._cw)
-    return vfield.render(form, renderer, tabindex=tabindex) \
+    return vfield.render(form, renderer) \
            + renderer.render_help(form, vfield)
 
 _afs = uicfg.autoform_section
--- a/cubicweb/web/views/editcontroller.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/editcontroller.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -17,8 +17,6 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """The edit controller, automatically handling entity form submitting"""
 
-
-
 from warnings import warn
 from collections import defaultdict
 
@@ -26,16 +24,14 @@
 
 from six import text_type
 
-from logilab.common.deprecation import deprecated
 from logilab.common.graph import ordered_nodes
 
 from rql.utils import rqlvar_maker
 
-from cubicweb import _, Binary, ValidationError, UnknownEid
+from cubicweb import _, ValidationError, UnknownEid
 from cubicweb.view import EntityAdapter
 from cubicweb.predicates import is_instance
-from cubicweb.web import (INTERNAL_FIELD_VALUE, RequestError, NothingToEdit,
-                          ProcessFormError)
+from cubicweb.web import RequestError, NothingToEdit, ProcessFormError
 from cubicweb.web.views import basecontrollers, autoform
 
 
@@ -74,6 +70,7 @@
     except (ValueError, TypeError):
         return eid
 
+
 class RqlQuery(object):
     def __init__(self):
         self.edited = []
@@ -190,15 +187,16 @@
             req.transaction_data['__maineid'] = form['__maineid']
         # no specific action, generic edition
         self._to_create = req.data['eidmap'] = {}
-        # those two data variables are used to handle relation from/to entities
+        # those three data variables are used to handle relation from/to entities
         # which doesn't exist at time where the entity is edited and that
         # deserves special treatment
         req.data['pending_inlined'] = defaultdict(set)
         req.data['pending_others'] = set()
         req.data['pending_composite_delete'] = set()
+        req.data['pending_values'] = dict()
         try:
             for formparams in self._ordered_formparams():
-                eid = self.edit_entity(formparams)
+                self.edit_entity(formparams)
         except (RequestError, NothingToEdit) as ex:
             if '__linkto' in req.form and 'eid' in req.form:
                 self.execute_linkto()
@@ -208,10 +206,20 @@
         # treated now (pop to ensure there are no attempt to add new ones)
         pending_inlined = req.data.pop('pending_inlined')
         assert not pending_inlined, pending_inlined
+        pending_values = req.data.pop('pending_values')
         # handle all other remaining relations now
         while req.data['pending_others']:
             form_, field = req.data['pending_others'].pop()
-            self.handle_formfield(form_, field)
+            # attempt to retrieve values and original values if they have already gone through
+            # handle_formfield (may not if there has been some not yet known eid at the first
+            # processing round). In the later case we've to go back through handle_formfield.
+            try:
+                values, origvalues = pending_values.pop((form_, field))
+            except KeyError:
+                self.handle_formfield(form_, field)
+            else:
+                self.handle_relation(form_, field, values, origvalues)
+        assert not pending_values, 'unexpected remaining pending values %s' % pending_values
         del req.data['pending_others']
         # then execute rql to set all relations
         for querydef in self.relations_rql:
@@ -236,7 +244,7 @@
             neweid = entity.eid
         except ValidationError as ex:
             self._to_create[eid] = ex.entity
-            if self._cw.ajax_request: # XXX (syt) why?
+            if self._cw.ajax_request:  # XXX (syt) why?
                 ex.entity = eid
             raise
         self._to_create[eid] = neweid
@@ -268,7 +276,7 @@
         form = req.vreg['forms'].select(formid, req, entity=entity)
         eid = form.actual_eid(entity.eid)
         editedfields = formparams['_cw_entity_fields']
-        form.formvalues = {} # init fields value cache
+        form.formvalues = {}  # init fields value cache
         for field in form.iter_modified_fields(editedfields, entity):
             self.handle_formfield(form, field, rqlquery)
         # if there are some inlined field which were waiting for this entity's
@@ -279,9 +287,9 @@
             if self.errors:
                 errors = dict((f.role_name(), text_type(ex)) for f, ex in self.errors)
                 raise ValidationError(valerror_eid(entity.eid), errors)
-            if eid is None: # creation or copy
+            if eid is None:  # creation or copy
                 entity.eid = eid = self._insert_entity(etype, formparams['eid'], rqlquery)
-            elif rqlquery.edited: # edition of an existant entity
+            elif rqlquery.edited:  # edition of an existant entity
                 self.check_concurrent_edition(formparams, eid)
                 self._update_entity(eid, rqlquery)
         else:
@@ -294,7 +302,7 @@
             autoform.delete_relations(req, todelete)
         if '__cloned_eid' in formparams:
             entity.copy_relations(int(formparams['__cloned_eid']))
-        if is_main_entity: # only execute linkto for the main entity
+        if is_main_entity:  # only execute linkto for the main entity
             self.execute_linkto(entity.eid)
         return eid
 
@@ -303,10 +311,9 @@
         eschema = entity.e_schema
         try:
             for field, value in field.process_posted(form):
-                if not (
-                    (field.role == 'subject' and field.name in eschema.subjrels)
-                    or
-                    (field.role == 'object' and field.name in eschema.objrels)):
+                if not ((field.role == 'subject' and field.name in eschema.subjrels)
+                        or
+                        (field.role == 'object' and field.name in eschema.objrels)):
                     continue
 
                 rschema = self._cw.vreg.schema.rschema(field.name)
@@ -315,11 +322,11 @@
                     continue
 
                 if entity.has_eid():
-                    origvalues = set(data[0] for data in entity.related(field.name, field.role).rows)
+                    origvalues = set(row[0] for row in entity.related(field.name, field.role).rows)
                 else:
                     origvalues = set()
                 if value is None or value == origvalues:
-                    continue # not edited / not modified / to do later
+                    continue  # not edited / not modified / to do later
 
                 unlinked_eids = origvalues - value
 
@@ -333,7 +340,8 @@
                 elif form.edited_entity.has_eid():
                     self.handle_relation(form, field, value, origvalues)
                 else:
-                    form._cw.data['pending_others'].add( (form, field) )
+                    form._cw.data['pending_others'].add((form, field))
+                    form._cw.data['pending_values'][(form, field)] = (value, origvalues)
 
         except ProcessFormError as exc:
             self.errors.append((field, exc))
@@ -387,15 +395,10 @@
     def handle_relation(self, form, field, values, origvalues):
         """handle edition for the (rschema, x) relation of the given entity
         """
-        etype = form.edited_entity.e_schema
         rschema = self._cw.vreg.schema.rschema(field.name)
         if field.role == 'subject':
-            desttype = rschema.objects(etype)[0]
-            card = rschema.rdef(etype, desttype).cardinality[0]
             subjvar, objvar = 'X', 'Y'
         else:
-            desttype = rschema.subjects(etype)[0]
-            card = rschema.rdef(desttype, etype).cardinality[1]
             subjvar, objvar = 'Y', 'X'
         eid = form.edited_entity.eid
         if field.role == 'object' or not rschema.inlined or not values:
@@ -419,7 +422,7 @@
         for eid, etype in eidtypes:
             entity = self._cw.entity_from_eid(eid, etype)
             path, params = entity.cw_adapt_to('IEditControl').after_deletion_path()
-            redirect_info.add( (path, tuple(params.items())) )
+            redirect_info.add((path, tuple(params.items())))
             entity.cw_delete()
         if len(redirect_info) > 1:
             # In the face of ambiguity, refuse the temptation to guess.
@@ -431,7 +434,6 @@
         else:
             self._cw.set_message(self._cw._('entity deleted'))
 
-
     def check_concurrent_edition(self, formparams, eid):
         req = self._cw
         try:
@@ -446,7 +448,7 @@
             msg = _("Entity %(eid)s has changed since you started to edit it."
                     " Reload the page and reapply your changes.")
             # ... this is why we pass the formats' dict as a third argument.
-            raise ValidationError(eid, {None: msg}, {'eid' : eid})
+            raise ValidationError(eid, {None: msg}, {'eid': eid})
 
     def _action_apply(self):
         self._default_publish()
--- a/cubicweb/web/views/forms.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/forms.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -42,9 +42,6 @@
 but you'll use this one rarely.
 """
 
-
-
-
 import time
 import inspect
 
@@ -177,8 +174,10 @@
             return self._onsubmit
         except AttributeError:
             return "return freezeFormButtons('%(domid)s');" % dictattr(self)
+
     def _set_onsubmit(self, value):
         self._onsubmit = value
+
     onsubmit = property(_get_onsubmit, _set_onsubmit)
 
     def add_media(self):
@@ -210,6 +209,7 @@
             rset=self.cw_rset, row=self.cw_row, col=self.cw_col or 0)
 
     formvalues = None
+
     def build_context(self, formvalues=None):
         """build form context values (the .context attribute which is a
         dictionary with field instance as key associated to a dictionary
@@ -217,7 +217,7 @@
         a string).
         """
         if self.formvalues is not None:
-            return # already built
+            return  # already built
         self.formvalues = formvalues or {}
         # use a copy in case fields are modified while context is built (eg
         # __linkto handling for instance)
@@ -239,6 +239,7 @@
                             eidparam=True)
 
     _default_form_action_path = 'edit'
+
     def form_action(self):
         action = self.action
         if action is None:
@@ -256,7 +257,7 @@
                 editedfields = self._cw.form['_cw_fields']
             except KeyError:
                 raise RequestError(self._cw._('no edited fields specified'))
-        entityform = entity and len(inspect.getargspec(self.field_by_name)) == 4 # XXX
+        entityform = entity and len(inspect.getargspec(self.field_by_name)) == 4  # XXX
         for editedfield in splitstrip(editedfields):
             try:
                 name, role = editedfield.split('-')
@@ -275,7 +276,7 @@
         will return a dictionary with field names as key and typed value as
         associated value.
         """
-        with tempattr(self, 'formvalues', {}): # init fields value cache
+        with tempattr(self, 'formvalues', {}):  # init fields value cache
             errors = []
             processed = {}
             for field in self.iter_modified_fields():
@@ -317,16 +318,16 @@
             rschema = eschema.schema.rschema(name)
             # XXX use a sample target type. Document this.
             tschemas = rschema.targets(eschema, role)
-            fieldcls = cls_or_self.uicfg_aff.etype_get(
+            fieldclass = 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)
+            if fieldclass:
+                if not isinstance(fieldclass, type):
+                    return fieldclass  # already an instance
+                kwargs['fieldclass'] = fieldclass
             if isinstance(cls_or_self, type):
                 req = None
             else:
@@ -441,7 +442,7 @@
     def actual_eid(self, eid):
         # should be either an int (existant entity) or a variable (to be
         # created entity)
-        assert eid or eid == 0, repr(eid) # 0 is a valid eid
+        assert eid or eid == 0, repr(eid)  # 0 is a valid eid
         try:
             return int(eid)
         except ValueError:
@@ -470,8 +471,8 @@
 
     def build_context(self, formvalues=None):
         super(CompositeFormMixIn, self).build_context(formvalues)
-        for form in self.forms:
-            form.build_context(formvalues)
+        for form_ in self.forms:
+            form_.build_context(formvalues)
 
 
 class CompositeForm(CompositeFormMixIn, FieldsForm):
@@ -479,5 +480,6 @@
     at once.
     """
 
+
 class CompositeEntityForm(CompositeFormMixIn, EntityFieldsForm):
-    pass # XXX why is this class necessary?
+    pass  # XXX why is this class necessary?
--- a/cubicweb/web/views/staticcontrollers.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/staticcontrollers.py	Fri Feb 03 13:37:32 2017 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -39,7 +39,6 @@
 from cubicweb.web.views.urlrewrite import URLRewriter
 
 
-
 class StaticFileController(Controller):
     """an abtract class to serve static file
 
@@ -49,7 +48,7 @@
 
     def max_age(self, path):
         """max cache TTL"""
-        return 60*60*24*7
+        return 60 * 60 * 24 * 7
 
     def static_file(self, path):
         """Return full content of a static file.
@@ -81,7 +80,6 @@
         self._cw.set_header('last-modified', generateDateTime(os.stat(path).st_mtime))
         if self._cw.is_client_cache_valid():
             return ''
-        # XXX elif uri.startswith('/https/'): uri = uri[6:]
         mimetype, encoding = mimetypes.guess_type(path)
         if mimetype is None:
             mimetype = 'application/octet-stream'
@@ -226,11 +224,7 @@
     __regid__ = 'fckeditor'
 
     def publish(self, rset=None):
-        config = self._cw.vreg.config
-        if self._cw.https:
-            uiprops = config.https_uiprops
-        else:
-            uiprops = config.uiprops
+        uiprops = self._cw.vreg.config.uiprops
         relpath = self.relpath
         if relpath.startswith('fckeditor/'):
             relpath = relpath[len('fckeditor/'):]
@@ -248,9 +242,11 @@
         relpath = self.relpath[len(self.__regid__) + 1:]
         return self.static_file(osp.join(staticdir, relpath))
 
+
 STATIC_CONTROLLERS = [DataController, FCKEditorController,
                       StaticDirectoryController]
 
+
 class StaticControlerRewriter(URLRewriter):
     """a quick and dirty rewritter in charge of server static file.
 
@@ -267,6 +263,5 @@
         else:
             self.debug("not a static file uri: %s", uri)
             raise KeyError(uri)
-        relpath = self._cw.relative_path(includeparams=False)
         self._cw.form['static_relative_path'] = self._cw.relative_path(includeparams=True)
         return ctrl.__regid__, None
--- a/cubicweb/web/views/uicfg.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/uicfg.py	Fri Feb 03 13:37:32 2017 +0100
@@ -54,9 +54,6 @@
    uicfg.actionbox_appearsin_addmenu.tag_object_of(('*', 'entry_of', 'Blog'), True)
 """
 
-
-from warnings import warn
-
 from six import string_types
 
 from cubicweb import neg_role
@@ -93,7 +90,8 @@
                     section = 'sideboxes'
             self.tag_relation((sschema, rschema, oschema, role), section)
 
-primaryview_section = PrimaryViewSectionRelationTags()
+
+primaryview_section = PrimaryViewSectionRelationTags(__module__=__name__)
 
 
 class DisplayCtrlRelationTags(NoTargetRelationTagsDict):
@@ -144,7 +142,7 @@
                 self.tag_object_of(('*', rtype, etype), {'order': index})
 
 
-primaryview_display_ctrl = DisplayCtrlRelationTags()
+primaryview_display_ctrl = DisplayCtrlRelationTags(__module__=__name__)
 
 
 # index view configuration ####################################################
@@ -155,7 +153,7 @@
 # * 'hidden'
 # * 'subobject' (not displayed by default)
 
-class InitializableDict(dict): # XXX not a rtag. Turn into an appobject?
+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)
@@ -174,12 +172,13 @@
             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 ##################################
@@ -191,6 +190,7 @@
         result[formtype] = section
     return result
 
+
 def _card_and_comp(sschema, rschema, oschema, role):
     rdef = rschema.rdef(sschema, oschema)
     if role == 'subject':
@@ -201,6 +201,7 @@
         composed = not rschema.final and rdef.composite == 'subject'
     return card, composed
 
+
 class AutoformSectionRelationTags(RelationTagsSet):
     """autoform relations'section"""
     __regid__ = 'autoform_section'
@@ -220,12 +221,7 @@
         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) )
+        key = _ensure_str_key((sschema, rschema, oschema, role))
         self._tagdefs[key] = formsections
 
     def _initfunc_step2(self, sschema, rschema, oschema, role):
@@ -242,31 +238,26 @@
             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):
+        if 'main' not in sectdict:
+            if 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:
+                    if 'muledit' not in sectdict:
                         sectdict['muledit'] = 'attributes'
                 elif rschema.final:
                     sectdict['main'] = 'attributes'
                 else:
                     sectdict['main'] = 'relations'
-        if not 'muledit' in sectdict:
+        if 'muledit' not 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:
+        if 'inlined' not in sectdict:
             sectdict['inlined'] = sectdict['main']
         # recompute formsections and set it to avoid recomputing
         for formtype, section in sectdict.items():
@@ -278,11 +269,11 @@
                 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)
+            '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)
+            '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
@@ -303,8 +294,8 @@
                 section, value = tag.split('_', 1)
                 rtags[section] = value
         cls = self.tag_container_cls
-        rtags = cls('_'.join([section,value])
-                    for section,value in rtags.items())
+        rtags = cls('_'.join([section, value])
+                    for section, value in rtags.items())
         return rtags
 
     def get(self, *key):
@@ -320,9 +311,10 @@
           bool telling if having local role is enough (strict = False) or not
         """
         tag = '%s_%s' % (formtype, section)
-        eschema  = entity.e_schema
+        eschema = entity.e_schema
         cw = entity._cw
-        permsoverrides = cw.vreg['uicfg'].select('autoform_permissions_overrides', cw, entity=entity)
+        permsoverrides = cw.vreg['uicfg'].select('autoform_permissions_overrides', cw,
+                                                 entity=entity)
         if entity.has_eid():
             eid = entity.eid
         else:
@@ -339,7 +331,7 @@
             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):
+                if tag not in self.etype_get(eschema, rschema, role, tschema):
                     continue
                 rdef = rschema.role_rdef(eschema, tschema, role)
                 if rschema.final:
@@ -361,7 +353,8 @@
             # 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):
+            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':
@@ -491,7 +484,8 @@
         for attr in attrs:
             self.edit_as_attr(self, etype, attr, formtype='muledit')
 
-autoform_section = AutoformSectionRelationTags()
+
+autoform_section = AutoformSectionRelationTags(__module__=__name__)
 
 
 # relations'field class
@@ -510,7 +504,8 @@
         """
         self._tag_etype_attr(etype, attr, '*', field)
 
-autoform_field = AutoformFieldTags()
+
+autoform_field = AutoformFieldTags(__module__=__name__)
 
 
 # relations'field explicit kwargs (given to field's __init__)
@@ -562,7 +557,7 @@
         self._tag_etype_attr(etype, attr, '*', kwargs)
 
 
-autoform_field_kwargs = AutoformFieldKwargsTags()
+autoform_field_kwargs = AutoformFieldKwargsTags(__module__=__name__)
 
 
 # set of tags of the form <action>_on_new on relations. <action> is a
@@ -571,7 +566,8 @@
 class AutoFormPermissionsOverrides(RelationTagsSet):
     __regid__ = 'autoform_permissions_overrides'
 
-autoform_permissions_overrides = AutoFormPermissionsOverrides()
+
+autoform_permissions_overrides = AutoFormPermissionsOverrides(__module__=__name__)
 
 
 class ReleditTags(NoTargetRelationTagsDict):
@@ -626,13 +622,14 @@
                 edittarget = 'related' if composite else 'rtype'
                 self.tag_relation((sschema, rschema, oschema, role),
                                   {'edit_target': edittarget})
-        if not 'novalue_include_rtype' in values:
+        if 'novalue_include_rtype' not 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()
+
+reledit_ctrl = ReleditTags(__module__=__name__)
 
 
 # boxes.EditBox configuration #################################################
@@ -666,7 +663,8 @@
 
         :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))
+        :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)
 
@@ -678,14 +676,15 @@
 
         :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))
+        :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()
 
+actionbox_appearsin_addmenu = ActionBoxUicfg(__module__=__name__)
 
 
 def registration_callback(vreg):
--- a/cubicweb/web/views/workflow.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/views/workflow.py	Fri Feb 03 13:37:32 2017 +0100
@@ -152,7 +152,8 @@
         else:
             sel += ',WF'
             headers = (_('from_state'), _('to_state'), _('comment'), _('date'))
-        rql = '%s %s, X eid %%(x)s' % (sel, rql)
+        sel += ',FSN,TSN,CF'
+        rql = '%s %s, FS name FSN, TS name TSN, WF comment_format CF, X eid %%(x)s' % (sel, rql)
         try:
             rset = self._cw.execute(rql, {'x': eid})
         except Unauthorized:
--- a/cubicweb/web/webconfig.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/web/webconfig.py	Fri Feb 03 13:37:32 2017 +0100
@@ -82,7 +82,7 @@
     """the WebConfiguration is a singleton object handling instance's
     configuration and preferences
     """
-    cubicweb_appobject_path = CubicWebConfiguration.cubicweb_appobject_path | set([join('web', 'views')])
+    cubicweb_appobject_path = CubicWebConfiguration.cubicweb_appobject_path | set(['web.views'])
     cube_appobject_path = CubicWebConfiguration.cube_appobject_path | set(['views'])
 
     options = merge_options(CubicWebConfiguration.options + (
@@ -113,19 +113,6 @@
           'group': 'web', 'level': 3,
           }),
         # web configuration
-        ('https-url',
-         {'type' : 'string',
-          'default': None,
-          'help': 'web server root url on https. By specifying this option your '\
-          'site can be available as an http and https site. Authenticated users '\
-          'will in this case be authenticated and once done navigate through the '\
-          'https site. IMPORTANTE NOTE: to do this work, you should have your '\
-          'apache redirection include "https" as base url path so cubicweb can '\
-          'differentiate between http vs https access. For instance: \n'\
-          'RewriteRule ^/demo/(.*) http://127.0.0.1:8080/https/$1 [L,P]\n'\
-          'where the cubicweb web server is listening on port 8080.',
-          'group': 'main', 'level': 3,
-          }),
         ('datadir-url',
          {'type': 'string', 'default': None,
           'help': ('base url for static data, if different from "${base-url}/data/".  '
@@ -261,9 +248,7 @@
     def __init__(self, *args, **kwargs):
         super(WebConfiguration, self).__init__(*args, **kwargs)
         self.uiprops = None
-        self.https_uiprops = None
         self.datadir_url = None
-        self.https_datadir_url = None
 
     def fckeditor_installed(self):
         if self.uiprops is None:
@@ -382,16 +367,8 @@
                 self.datadir_url += '/'
             if self.mode != 'test':
                 self.datadir_url += '%s/' % self.instance_md5_version()
-            self.https_datadir_url = self.datadir_url
             return
-        httpsurl = self['https-url']
         data_relpath = self.data_relpath()
-        if httpsurl:
-            if httpsurl[-1] != '/':
-                httpsurl += '/'
-                if not self.repairing:
-                    self.global_set_option('https-url', httpsurl)
-            self.https_datadir_url = httpsurl + data_relpath
         self.datadir_url = baseurl + data_relpath
 
     def data_relpath(self):
@@ -409,14 +386,6 @@
             data=lambda x: self.datadir_url + x,
             datadir_url=self.datadir_url[:-1])
         self._init_uiprops(self.uiprops)
-        if self['https-url']:
-            cachedir = join(self.appdatahome, 'uicachehttps')
-            self.check_writeable_uid_directory(cachedir)
-            self.https_uiprops = PropertySheet(
-                cachedir,
-                data=lambda x: self.https_datadir_url + x,
-                datadir_url=self.https_datadir_url[:-1])
-            self._init_uiprops(self.https_uiprops)
 
     def _init_uiprops(self, uiprops):
         libuiprops = join(self.shared_dir(), 'data', 'uiprops.py')
--- a/cubicweb/wsgi/request.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/wsgi/request.py	Fri Feb 03 13:37:32 2017 +0100
@@ -69,15 +69,10 @@
                           if k.startswith('HTTP_'))
         if 'CONTENT_TYPE' in environ:
             headers_in['Content-Type'] = environ['CONTENT_TYPE']
-        https = self.is_secure()
-        if self.path.startswith('/https/'):
-            self.path = self.path[6:]
-            self.environ['PATH_INFO'] = self.path
-            https = True
 
         post, files = self.get_posted_data()
 
-        super(CubicWebWsgiRequest, self).__init__(vreg, https, post,
+        super(CubicWebWsgiRequest, self).__init__(vreg, post,
                                                   headers= headers_in)
         self.content = environ['wsgi.input']
         if files is not None:
@@ -121,9 +116,6 @@
 
     ## wsgi request helpers ###################################################
 
-    def is_secure(self):
-        return self.environ['wsgi.url_scheme'] == 'https'
-
     def get_posted_data(self):
         # The WSGI spec says 'QUERY_STRING' may be absent.
         post = parse_qs(self.environ.get('QUERY_STRING', ''))
--- a/cubicweb/wsgi/test/unittest_wsgi.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/cubicweb/wsgi/test/unittest_wsgi.py	Fri Feb 03 13:37:32 2017 +0100
@@ -27,30 +27,6 @@
 
         self.assertEqual(b'some content', req.content.read())
 
-    def test_http_scheme(self):
-        r = webtest.app.TestRequest.blank('/', {
-            'wsgi.url_scheme': 'http'})
-
-        req = CubicWebWsgiRequest(r.environ, self.vreg)
-
-        self.assertFalse(req.https)
-
-    def test_https_scheme(self):
-        r = webtest.app.TestRequest.blank('/', {
-            'wsgi.url_scheme': 'https'})
-
-        req = CubicWebWsgiRequest(r.environ, self.vreg)
-
-        self.assertTrue(req.https)
-
-    def test_https_prefix(self):
-        r = webtest.app.TestRequest.blank('/https/', {
-            'wsgi.url_scheme': 'http'})
-
-        req = CubicWebWsgiRequest(r.environ, self.vreg)
-
-        self.assertTrue(req.https)
-
     def test_big_content(self):
         content = b'x'*100001
         r = webtest.app.TestRequest.blank('/', {
--- a/debian/control	Tue Jan 31 11:06:28 2017 +0100
+++ b/debian/control	Fri Feb 03 13:37:32 2017 +0100
@@ -25,7 +25,7 @@
  python-pyramid,
  python-pyramid-multiauth,
  python-waitress,
- python-passlib (<< 2.0),
+ python-passlib (>= 1.7.0),
  python-wsgicors,
  sphinx-common,
 Standards-Version: 3.9.6
--- a/doc/api/pyramid/authplugin.rst	Tue Jan 31 11:06:28 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-.. _authplugin_module:
-
-:mod:`cubicweb.pyramid.authplugin`
-----------------------------------
-
-.. automodule:: cubicweb.pyramid.authplugin
-
-    .. autoclass:: DirectAuthentifier
-        :show-inheritance:
-        :members:
--- a/doc/api/pyramid/tools.rst	Tue Jan 31 11:06:28 2017 +0100
+++ b/doc/api/pyramid/tools.rst	Fri Feb 03 13:37:32 2017 +0100
@@ -1,7 +1,7 @@
 .. _tools_module:
 
 :mod:`cubicweb.pyramid.tools`
-----------------------------
+-----------------------------
 
 .. automodule:: cubicweb.pyramid.tools
 
--- a/doc/book/admin/instance-config.rst	Tue Jan 31 11:06:28 2017 +0100
+++ b/doc/book/admin/instance-config.rst	Fri Feb 03 13:37:32 2017 +0100
@@ -42,12 +42,9 @@
 :`main.base-url`:
     url base site to be used to generate the urls of web pages
 
-Https configuration
-```````````````````
-It is possible to make a site accessible for anonymous http connections
-and https for authenticated users. This requires to
-use apache (for example) for redirection and the variable `main.https-url`
-of configuration file.
+Apache configuration
+````````````````````
+It is possible to use apache (for example) as proxy.
 
 For this to work you have to activate the following apache modules :
 
@@ -62,9 +59,8 @@
 
 :Example:
 
-   For an apache redirection of a site accessible via `http://localhost/demo`
-   and `https://localhost/demo` and actually running on port 8080, it
-   takes to the http:::
+   For an apache redirection of a site accessible via `http://localhost/demo` while cubicweb is
+   actually running on port 8080:::
 
      ProxyPreserveHost On
      RewriteEngine On
@@ -72,24 +68,11 @@
      RewriteRule ^/demo$ /demo/
      RewriteRule ^/demo/(.*) http://127.0.0.1:8080/$1 [L,P]
 
-   and for the https:::
-
-     ProxyPreserveHost On
-     RewriteEngine On
-     RewriteCond %{REQUEST_URI} ^/ demo
-     RewriteRule ^/demo$/demo/
-     RewriteRule ^/demo/(.*) http://127.0.0.1:8080/https/$1 [L,P]
-
 
    and we will file in the all-in-one.conf of the instance:::
 
      base-url = http://localhost/demo
-     https-url = https://localhost/demo
 
-Notice that if you simply want a site accessible through https, not *both* http
-and https, simply set `base-url` to the https url and the first section into your
-apache configuration (as you would have to do for an http configuration with an
-apache front-end).
 
 Setting up the web client
 -------------------------
--- a/doc/book/devweb/edition/dissection.rst	Tue Jan 31 11:06:28 2017 +0100
+++ b/doc/book/devweb/edition/dissection.rst	Fri Feb 03 13:37:32 2017 +0100
@@ -107,14 +107,14 @@
             <th class="labelCol"><label class="required" for="title-subject:763">title</label></th>
             <td>
               <input id="title-subject:763" maxlength="128" name="title-subject:763" size="45"
-                     tabindex="1" type="text" value="let us write more doc" />
+                     type="text" value="let us write more doc" />
             </td>
           </tr>
           ... (description field omitted) ...
           <tr class="priority_subject_row">
             <th class="labelCol"><label class="required" for="priority-subject:763">priority</label></th>
             <td>
-              <select id="priority-subject:763" name="priority-subject:763" size="1" tabindex="4">
+              <select id="priority-subject:763" name="priority-subject:763" size="1">
                 <option value="important">important</option>
                 <option selected="selected" value="normal">normal</option>
                 <option value="minor">minor</option>
@@ -126,7 +126,7 @@
           <tr class="concerns_subject_row">
             <th class="labelCol"><label class="required" for="concerns-subject:763">concerns</label></th>
             <td>
-              <select id="concerns-subject:763" name="concerns-subject:763" size="1" tabindex="6">
+              <select id="concerns-subject:763" name="concerns-subject:763" size="1">
                 <option selected="selected" value="760">Foo</option>
               </select>
             </td>
@@ -134,7 +134,7 @@
           <tr class="done_in_subject_row">
             <th class="labelCol"><label for="done_in-subject:763">done in</label></th>
             <td>
-              <select id="done_in-subject:763" name="done_in-subject:763" size="1" tabindex="7">
+              <select id="done_in-subject:763" name="done_in-subject:763" size="1">
                 <option value="__cubicweb_internal_field__"></option>
                 <option selected="selected" value="761">Foo 0.1.0</option>
                 <option value="762">Foo 0.2.0</option>
@@ -180,7 +180,7 @@
                 <tr><th>&#160;</th><td>&#160;</td></tr>
                 <tr id="relationSelectorRow_763" class="separator">
                   <th class="labelCol">
-                    <select id="relationSelector_763" tabindex="8"
+                    <select id="relationSelector_763"
                             onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,763);">
                       <option value="">select a relation</option>
                       <option value="appeared_in_subject">appeared in</option>
@@ -228,7 +228,7 @@
         <tbody>
           <tr>
             <td align="center">
-              <button class="validateButton" tabindex="9" type="submit" value="validate">
+              <button class="validateButton" type="submit" value="validate">
                 <img alt="OK_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/ok.png" />
                 validate
               </button>
@@ -236,13 +236,13 @@
             <td style="align: right; width: 50%;">
               <button class="validateButton"
                       onclick="postForm(&#39;__action_apply&#39;, &#39;button_apply&#39;, &#39;entityForm&#39;)"
-                      tabindex="10" type="button" value="apply">
+                      type="button" value="apply">
                 <img alt="APPLY_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/plus.png" />
                 apply
               </button>
               <button class="validateButton"
                       onclick="postForm(&#39;__action_cancel&#39;, &#39;button_cancel&#39;, &#39;entityForm&#39;)"
-                      tabindex="11" type="button" value="cancel">
+                      type="button" value="cancel">
                 <img alt="CANCEL_ICON" src="http://myapp/datafd8b5d92771209ede1018a8d5da46a37/cancel.png" />
                 cancel
               </button>
--- a/doc/book/devweb/request.rst	Tue Jan 31 11:06:28 2017 +0100
+++ b/doc/book/devweb/request.rst	Fri Feb 03 13:37:32 2017 +0100
@@ -48,8 +48,6 @@
   * etype_rset
   * `form`, dictionary containing the values of a web form
   * `encoding`, character encoding to use in the response
-  * `next_tabindex()`: returns a monotonically growing integer used to
-    build the html tab index of forms
 
 * `HTTP`
 
--- a/doc/book/pyramid/quickstart.rst	Tue Jan 31 11:06:28 2017 +0100
+++ b/doc/book/pyramid/quickstart.rst	Fri Feb 03 13:37:32 2017 +0100
@@ -57,3 +57,17 @@
 
 -   Configure the base-url and https-url in all-in-one.conf to match the ones
     of the pyramid configuration (this is a temporary limitation).
+
+
+Usage with pserve
+-----------------
+
+To run a Pyramid application using pserve_:
+
+::
+
+    pserve /path/to/development.ini instance=<appid>
+
+
+.. _pserve: \
+    http://docs.pylonsproject.org/projects/pyramid/en/latest/pscripts/pserve.html
--- a/doc/index.rst	Tue Jan 31 11:06:28 2017 +0100
+++ b/doc/index.rst	Fri Feb 03 13:37:32 2017 +0100
@@ -108,7 +108,7 @@
 .. toctree::
     :maxdepth: 1
 
-    js_api/index
+    book/en/devweb/js_api/index
 
 Developpers
 ~~~~~~~~~~~
--- a/flake8-ok-files.txt	Tue Jan 31 11:06:28 2017 +0100
+++ b/flake8-ok-files.txt	Fri Feb 03 13:37:32 2017 +0100
@@ -1,3 +1,5 @@
+cubicweb/__init__.py
+cubicweb/__main__.py
 cubicweb/dataimport/csv.py
 cubicweb/dataimport/importer.py
 cubicweb/dataimport/massive_store.py
@@ -20,14 +22,16 @@
 cubicweb/entities/adapters.py
 cubicweb/entities/test/unittest_base.py
 cubicweb/etwist/__init__.py
+cubicweb/etwist/request.py
+cubicweb/etwist/service.py
 cubicweb/ext/__init__.py
 cubicweb/hooks/test/data/hooks.py
-cubicweb/hooks/test/unittest_notification.py
+cubicweb/hooks/test/unittest_notificationhooks.py
 cubicweb/hooks/test/unittest_security.py
 cubicweb/hooks/test/unittest_syncsession.py
-cubicweb/__init__.py
-cubicweb/__main__.py
 cubicweb/pylintext.py
+cubicweb/rset.py
+cubicweb/rtags.py
 cubicweb/server/repository.py
 cubicweb/server/rqlannotation.py
 cubicweb/server/schema2sql.py
@@ -43,27 +47,27 @@
 cubicweb/server/test/data-schema2sql/__init__.py
 cubicweb/server/test/unittest_checkintegrity.py
 cubicweb/server/test/unittest_ldapsource.py
+cubicweb/server/test/unittest_session.py
 cubicweb/server/test/unittest_rqlannotation.py
-cubicweb/skeleton/test/pytestconf.py
 cubicweb/sobjects/test/unittest_notification.py
 cubicweb/sobjects/test/unittest_register_user.py
 cubicweb/sobjects/textparsers.py
-cubicweb/test/data/cubes/comment/__init__.py
-cubicweb/test/data/cubes/comment/__pkginfo__.py
-cubicweb/test/data/cubes/email/entities.py
-cubicweb/test/data/cubes/email/hooks.py
-cubicweb/test/data/cubes/email/__init__.py
-cubicweb/test/data/cubes/email/__pkginfo__.py
-cubicweb/test/data/cubes/email/views/__init__.py
-cubicweb/test/data/cubes/file/entities/__init__.py
-cubicweb/test/data/cubes/file/hooks/__init__.py
-cubicweb/test/data/cubes/file/__init__.py
-cubicweb/test/data/cubes/file/__pkginfo__.py
-cubicweb/test/data/cubes/file/views.py
-cubicweb/test/data/cubes/forge/__init__.py
-cubicweb/test/data/cubes/forge/__pkginfo__.py
-cubicweb/test/data/cubes/mycube/__init__.py
-cubicweb/test/data/cubes/mycube/__pkginfo__.py
+cubicweb/test/data/libpython/cubicweb_comment/__init__.py
+cubicweb/test/data/libpython/cubicweb_comment/__pkginfo__.py
+cubicweb/test/data/libpython/cubicweb_email/entities.py
+cubicweb/test/data/libpython/cubicweb_email/hooks.py
+cubicweb/test/data/libpython/cubicweb_email/__init__.py
+cubicweb/test/data/libpython/cubicweb_email/__pkginfo__.py
+cubicweb/test/data/libpython/cubicweb_email/views/__init__.py
+cubicweb/test/data/libpython/cubicweb_file/entities/__init__.py
+cubicweb/test/data/libpython/cubicweb_file/hooks/__init__.py
+cubicweb/test/data/libpython/cubicweb_file/__init__.py
+cubicweb/test/data/libpython/cubicweb_file/__pkginfo__.py
+cubicweb/test/data/libpython/cubicweb_file/views.py
+cubicweb/test/data/libpython/cubicweb_forge/__init__.py
+cubicweb/test/data/libpython/cubicweb_forge/__pkginfo__.py
+cubicweb/test/data/libpython/cubicweb_mycube/__init__.py
+cubicweb/test/data/libpython/cubicweb_mycube/__pkginfo__.py
 cubicweb/test/data/migration/0.1.0_common.py
 cubicweb/test/data/migration/0.1.0_repository.py
 cubicweb/test/data_schemareader/schema.py
@@ -72,17 +76,21 @@
 cubicweb/test/unittest_binary.py
 cubicweb/test/unittest_mail.py
 cubicweb/test/unittest_repoapi.py
+cubicweb/test/unittest_req.py
+cubicweb/test/unittest_rtags.py
 cubicweb/test/unittest_schema.py
 cubicweb/test/unittest_toolsutils.py
-cubicweb/test/unittest_utils.py
 cubicweb/web/formwidgets.py
 cubicweb/web/test/data/entities.py
 cubicweb/web/test/unittest_http_headers.py
 cubicweb/web/test/unittest_views_basetemplates.py
 cubicweb/web/test/unittest_views_cwsources.py
 cubicweb/web/test/unittest_views_json.py
+cubicweb/web/views/editcontroller.py
 cubicweb/web/views/json.py
 cubicweb/web/views/searchrestriction.py
+cubicweb/web/views/staticcontrollers.py
+cubicweb/web/views/uicfg.py
 cubicweb/xy.py
 cubicweb/pyramid/auth.py
 cubicweb/pyramid/bwcompat.py
--- a/requirements/dev.txt	Tue Jan 31 11:06:28 2017 +0100
+++ b/requirements/dev.txt	Fri Feb 03 13:37:32 2017 +0100
@@ -1,1 +1,3 @@
 pytest
+http://hg.logilab.org/master/logilab/common/archive/default.tar.bz2#egg=logilab-common
+http://hg.logilab.org/master/yams/archive/default.tar.bz2#egg=yams
--- a/setup.cfg	Tue Jan 31 11:06:28 2017 +0100
+++ b/setup.cfg	Fri Feb 03 13:37:32 2017 +0100
@@ -1,3 +1,6 @@
+[bdist_wheel]
+universal = 1
+
 [check-manifest]
 ignore =
   debian
--- a/setup.py	Tue Jan 31 11:06:28 2017 +0100
+++ b/setup.py	Fri Feb 03 13:37:32 2017 +0100
@@ -149,40 +149,16 @@
                 dest = join(self.install_dir, src)
                 export(src, dest, verbose=self.verbose)
 
-# write required share/cubicweb/cubes/__init__.py
-class MyInstallData(install_data.install_data):
-    """A class That manages data files installation"""
-    def run(self):
-        """overridden from install_data class"""
-        install_data.install_data.run(self)
-        path = join(self.install_dir, 'share', 'cubicweb', 'cubes', '__init__.py')
-        ini = open(path, 'w')
-        ini.write('# Cubicweb cubes directory\n')
-        ini.close()
-
-
-class CWDevelop(develop.develop):
-    """Custom "develop" command warning about (legacy) cubes directory not
-    installed.
-    """
-
-    def run(self):
-        cubespath = join(sys.prefix, 'share', 'cubicweb', 'cubes')
-        self.warn('develop command does not install (legacy) cubes directory (%s)'
-                  % cubespath)
-        return develop.develop.run(self)
-
 
 # re-enable copying data files in sys.prefix
-# overwrite MyInstallData to use sys.prefix instead of the egg directory
-MyInstallMoreData = MyInstallData
-class MyInstallData(MyInstallMoreData): # pylint: disable=E0102
+# overwrite install_data to use sys.prefix instead of the egg directory
+class MyInstallData(install_data.install_data):
     """A class that manages data files installation"""
     def run(self):
         _old_install_dir = self.install_dir
         if self.install_dir.endswith('egg'):
             self.install_dir = sys.prefix
-        MyInstallMoreData.run(self)
+        install_data.install_data.run(self)
         self.install_dir = _old_install_dir
 try:
     import setuptools.command.easy_install # only if easy_install available
@@ -223,7 +199,7 @@
         'yams >= 0.44.0',
         'lxml',
         'logilab-database >= 1.15.0',
-        'passlib < 2.0',
+        'passlib >= 1.7.0',
         'pytz',
         'Markdown',
         'unittest2 >= 0.7.0',
@@ -263,7 +239,6 @@
     cmdclass={
         'install_lib': MyInstallLib,
         'install_data': MyInstallData,
-        'develop': CWDevelop,
     },
     zip_safe=False,
 )
--- a/tox.ini	Tue Jan 31 11:06:28 2017 +0100
+++ b/tox.ini	Fri Feb 03 13:37:32 2017 +0100
@@ -5,8 +5,6 @@
 
 [testenv]
 sitepackages = True
-whitelist_externals =
-  /usr/bin/touch
 deps =
   -r{toxinidir}/requirements/dev.txt
   py27: backports.tempfile
@@ -14,7 +12,6 @@
   server: -r{toxinidir}/requirements/test-server.txt
   web: -r{toxinidir}/requirements/test-web.txt
 commands =
-  py34: touch {envdir}/share/cubicweb/cubes/__init__.py
   misc: {envpython} -m pip install --upgrade --no-deps --quiet git+git://github.com/logilab/yapps@master#egg=yapps
   misc: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/test {toxinidir}/cubicweb/dataimport/test {toxinidir}/cubicweb/devtools/test {toxinidir}/cubicweb/entities/test {toxinidir}/cubicweb/ext/test {toxinidir}/cubicweb/hooks/test {toxinidir}/cubicweb/sobjects/test {toxinidir}/cubicweb/wsgi/test {toxinidir}/cubicweb/pyramid/test
   py27-misc: {envpython} -m pytest {posargs} {toxinidir}/cubicweb/etwist/test
@@ -53,7 +50,7 @@
 format = pylint
 ignore = W503
 max-line-length = 100
-exclude = setup.py,doc/*,cubicweb/misc/*,cubicweb/test/*,cubicweb/*/test/*,.tox/*
+exclude = doc/*,.tox/*
 
 
 # vim: wrap sts=2 sw=2