--- a/cubicweb.spec Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb.spec Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/__init__.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/__pkginfo__.py Fri Jan 27 16:26:09 2017 +0100
@@ -27,8 +27,8 @@
modname = distname = "cubicweb"
-numversion = (3, 24, 4)
-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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/cwconfig.py Fri Jan 27 16:26:09 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):
@@ -778,57 +808,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 +1308,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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/cwctl.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/cwvreg.py Fri Jan 27 16:26:09 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/massive_store.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/dataimport/massive_store.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/dataimport/test/test_massive_store.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/devtools/devctl.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/devtools/fake.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/devtools/test/unittest_devctl.py Fri Jan 27 16:26:09 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]
@@ -51,8 +51,7 @@
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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/devtools/testlib.py Fri Jan 27 16:26:09 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/entity.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/entity.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/etwist/http.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/etwist/request.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/etwist/server.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/etwist/service.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/hooks/test/unittest_security.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/pyramid/bwcompat.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/pyramid/core.py Fri Jan 27 16:26:09 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:
--- a/cubicweb/pyramid/pyramidctl.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/pyramid/pyramidctl.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/pyramid/test/__init__.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/pyramid/test/test_bw_request.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/pyramid/test/test_login.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/req.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/rqlrewrite.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/rset.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/rtags.py Fri Jan 27 16:26:09 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,12 @@
# function given as __init__ argument and kept for bw compat
_init = _initfunc = None
- def __init__(self):
+ def __init__(self, parent=None):
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 +113,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 +135,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)
+ copied.__module__ = module
+ copied.__select__ = select
+ return copied
+
def tag_attribute(self, key, *args, **kwargs):
key = list(key)
key.append('*')
@@ -141,8 +168,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 +183,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 +207,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 +222,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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/schema.py Fri Jan 27 16:26:09 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/session.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/server/session.py Fri Jan 27 16:26:09 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
@@ -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/test/unittest_checkintegrity.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/server/test/unittest_checkintegrity.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/server/test/unittest_migractions.py Fri Jan 27 16:26:09 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 Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/server/utils.py Fri Jan 27 16:26:09 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/sobjects/notification.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/sobjects/notification.py Fri Jan 27 16:26:09 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/test/data/views.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/test/data/views.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/test/data_schemareader/schema.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/test/unittest_cubes.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/test/unittest_cwconfig.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/test/unittest_entity.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/test/unittest_req.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/test/unittest_rqlrewrite.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/test/unittest_rset.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/test/unittest_rtags.py Fri Jan 27 16:26:09 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,128 @@
#
# 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
+import unittest
+
from cubicweb.rtags import RelationTags, RelationTagsSet, RelationTagsDict
-class RelationTagsTC(TestCase):
+
+class RelationTagsTC(unittest.TestCase):
+
+ def setUp(self):
+ self.rtags = RelationTags()
+ 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(unittest.TestCase):
+
+ def setUp(self):
+ self.rtags = RelationTagsSet()
+ 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(unittest.TestCase):
- 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 setUp(self):
+ self.rtags = RelationTagsDict()
+ 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_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'),
+ {})
- 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'})
+ 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)
+
+ 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'})
+
if __name__ == '__main__':
- unittest_main()
+ unittest.main()
--- a/cubicweb/test/unittest_schema.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/test/unittest_schema.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/application.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/data/cubicweb.edition.js Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/formfields.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/formwidgets.py Fri Jan 27 16:26:09 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):
@@ -1000,8 +989,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 +999,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 +1046,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 +1074,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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/request.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/test/unittest_application.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/test/unittest_form.py Fri Jan 27 16:26:09 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('data-subject:%(eid)s-advanced')" 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('data-subject:%(eid)s-advanced')" 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="" />
 
<span class="emphasis">confirm password</span>''' % {'eid': req.user.eid})
--- a/cubicweb/web/test/unittest_formwidgets.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/test/unittest_formwidgets.py Fri Jan 27 16:26:09 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" /> '
'python >> others</label>')
--- a/cubicweb/web/test/unittest_http_headers.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/test/unittest_http_headers.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/test/unittest_reledit.py Fri Jan 27 16:26:09 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('title-subject-%(eid)s')" 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('title-subject-%(eid)s')" 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('long_desc-subject-%(eid)s')" 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('long_desc-subject-%(eid)s')" 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('manager-subject-%(eid)s')" 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('manager-subject-%(eid)s')" 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/test/unittest_request.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/test/unittest_uicfg.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/test/unittest_views_basecontrollers.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/test/unittest_views_baseviews.py Fri Jan 27 16:26:09 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'<script></script>')
+
+ 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'<script></script></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'<script></script></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'<script></script></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=""><script></script></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'<script></script></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'<script></script></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=""><script></script></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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/test/unittest_views_forms.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/actions.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/autoform.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/basecomponents.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/basetemplates.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/boxes.py Fri Jan 27 16:26:09 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/cwproperties.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/cwproperties.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/editcontroller.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/forms.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/staticcontrollers.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/uicfg.py Fri Jan 27 16:26:09 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,6 +90,7 @@
section = 'sideboxes'
self.tag_relation((sschema, rschema, oschema, role), section)
+
primaryview_section = PrimaryViewSectionRelationTags()
@@ -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,6 +484,7 @@
for attr in attrs:
self.edit_as_attr(self, etype, attr, formtype='muledit')
+
autoform_section = AutoformSectionRelationTags()
@@ -510,6 +504,7 @@
"""
self._tag_etype_attr(etype, attr, '*', field)
+
autoform_field = AutoformFieldTags()
@@ -571,6 +566,7 @@
class AutoFormPermissionsOverrides(RelationTagsSet):
__regid__ = 'autoform_permissions_overrides'
+
autoform_permissions_overrides = AutoFormPermissionsOverrides()
@@ -626,12 +622,13 @@
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()
@@ -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,16 +676,17 @@
: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()
-
def registration_callback(vreg):
vreg.register_all(globals().values(), __name__)
indexview_etype_section.init(vreg.schema)
--- a/cubicweb/web/views/workflow.py Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/views/workflow.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/web/webconfig.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/wsgi/request.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/cubicweb/wsgi/test/unittest_wsgi.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/debian/control Fri Jan 27 16:26:09 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/book/admin/instance-config.rst Fri Jan 27 13:41:24 2017 +0100
+++ b/doc/book/admin/instance-config.rst Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/doc/book/devweb/edition/dissection.rst Fri Jan 27 16:26:09 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> </th><td> </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('__action_apply', 'button_apply', 'entityForm')"
- 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('__action_cancel', 'button_cancel', 'entityForm')"
- 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/doc/book/devweb/request.rst Fri Jan 27 16:26:09 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/flake8-ok-files.txt Fri Jan 27 13:41:24 2017 +0100
+++ b/flake8-ok-files.txt Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/requirements/dev.txt Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/setup.cfg Fri Jan 27 16:26:09 2017 +0100
@@ -1,3 +1,6 @@
+[bdist_wheel]
+universal = 1
+
[check-manifest]
ignore =
debian
--- a/setup.py Fri Jan 27 13:41:24 2017 +0100
+++ b/setup.py Fri Jan 27 16:26:09 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 Fri Jan 27 13:41:24 2017 +0100
+++ b/tox.ini Fri Jan 27 16:26:09 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