--- a/__init__.py Wed Jan 09 16:04:26 2013 +0100
+++ b/__init__.py Wed Jan 09 16:06:20 2013 +0100
@@ -199,3 +199,26 @@
CW_EVENT_MANAGER.bind(event, func, *args, **kwargs)
return func
return _decorator
+
+
+from yams.schema import role_name as rname
+
+def validation_error(entity, errors, substitutions=None, i18nvalues=None):
+ """easy way to retrieve a :class:`cubicweb.ValidationError` for an entity or eid.
+
+ You may also have 2-tuple as error keys, :func:`yams.role_name` will be
+ called automatically for them.
+
+ Messages in errors **should not be translated yet**, though marked for
+ internationalization. You may give an additional substition dictionary that
+ will be used for interpolation after the translation.
+ """
+ if substitutions is None:
+ # set empty dict else translation won't be done for backward
+ # compatibility reason (see ValidationError.translate method)
+ substitutions = {}
+ for key in errors.keys():
+ if isinstance(key, tuple):
+ errors[rname(*key)] = errors.pop(key)
+ return ValidationError(getattr(entity, 'eid', entity), errors,
+ substitutions, i18nvalues)
--- a/__pkginfo__.py Wed Jan 09 16:04:26 2013 +0100
+++ b/__pkginfo__.py Wed Jan 09 16:06:20 2013 +0100
@@ -43,7 +43,7 @@
'logilab-common': '>= 0.58.0',
'logilab-mtconverter': '>= 0.8.0',
'rql': '>= 0.31.2',
- 'yams': '>= 0.34.0',
+ 'yams': '>= 0.36.0',
#gettext # for xgettext, msgcat, etc...
# web dependancies
'simplejson': '>= 2.0.9',
--- a/_exceptions.py Wed Jan 09 16:04:26 2013 +0100
+++ b/_exceptions.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -19,7 +19,7 @@
__docformat__ = "restructuredtext en"
-from yams import ValidationError
+from yams import ValidationError as ValidationError
# abstract exceptions #########################################################
--- a/cwconfig.py Wed Jan 09 16:04:26 2013 +0100
+++ b/cwconfig.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -671,54 +671,6 @@
cubicweb_appobject_path = set(['entities'])
cube_appobject_path = set(['entities'])
- @classmethod
- def build_vregistry_path(cls, templpath, evobjpath=None, tvobjpath=None):
- """given a list of directories, return a list of sub files and
- directories that should be loaded by the instance objects registry.
-
- :param evobjpath:
- optional list of sub-directories (or files without the .py ext) of
- the cubicweb library that should be tested and added to the output list
- if they exists. If not give, default to `cubicweb_appobject_path` class
- attribute.
- :param tvobjpath:
- optional list of sub-directories (or files without the .py ext) of
- directories given in `templpath` that should be tested and added to
- the output list if they exists. If not give, default to
- `cube_appobject_path` class attribute.
- """
- vregpath = cls.build_vregistry_cubicweb_path(evobjpath)
- vregpath += cls.build_vregistry_cube_path(templpath, tvobjpath)
- return vregpath
-
- @classmethod
- def build_vregistry_cubicweb_path(cls, evobjpath=None):
- vregpath = []
- if evobjpath is None:
- evobjpath = cls.cubicweb_appobject_path
- # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
- # it is clearly a workaround
- for subdir in sorted(evobjpath, key=lambda x:x != 'entities'):
- path = join(CW_SOFTWARE_ROOT, subdir)
- if exists(path):
- vregpath.append(path)
- return vregpath
-
- @classmethod
- def build_vregistry_cube_path(cls, templpath, tvobjpath=None):
- vregpath = []
- if tvobjpath is None:
- tvobjpath = cls.cube_appobject_path
- for directory in templpath:
- # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
- for subdir in sorted(tvobjpath, key=lambda x:x != 'entities'):
- path = join(directory, subdir)
- if exists(path):
- vregpath.append(path)
- elif exists(path + '.py'):
- vregpath.append(path + '.py')
- return vregpath
-
def __init__(self, debugmode=False):
if debugmode:
# in python 2.7, DeprecationWarning are not shown anymore by default
@@ -766,12 +718,57 @@
# configure simpleTal logger
logging.getLogger('simpleTAL').setLevel(logging.ERROR)
- def vregistry_path(self):
+ def appobjects_path(self):
"""return a list of files or directories where the registry will look
for application objects. By default return nothing in NoApp config.
"""
return []
+ def build_appobjects_path(self, templpath, evobjpath=None, tvobjpath=None):
+ """given a list of directories, return a list of sub files and
+ directories that should be loaded by the instance objects registry.
+
+ :param evobjpath:
+ optional list of sub-directories (or files without the .py ext) of
+ the cubicweb library that should be tested and added to the output list
+ if they exists. If not give, default to `cubicweb_appobject_path` class
+ attribute.
+ :param tvobjpath:
+ optional list of sub-directories (or files without the .py ext) of
+ directories given in `templpath` that should be tested and added to
+ the output list if they exists. If not give, default to
+ `cube_appobject_path` class attribute.
+ """
+ vregpath = self.build_appobjects_cubicweb_path(evobjpath)
+ vregpath += self.build_appobjects_cube_path(templpath, tvobjpath)
+ return vregpath
+
+ def build_appobjects_cubicweb_path(self, evobjpath=None):
+ vregpath = []
+ if evobjpath is None:
+ evobjpath = self.cubicweb_appobject_path
+ # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
+ # it is clearly a workaround
+ for subdir in sorted(evobjpath, key=lambda x:x != 'entities'):
+ path = join(CW_SOFTWARE_ROOT, subdir)
+ if exists(path):
+ vregpath.append(path)
+ return vregpath
+
+ def build_appobjects_cube_path(self, templpath, tvobjpath=None):
+ vregpath = []
+ if tvobjpath is None:
+ tvobjpath = self.cube_appobject_path
+ for directory in templpath:
+ # NOTE: for the order, see http://www.cubicweb.org/ticket/2330799
+ for subdir in sorted(tvobjpath, key=lambda x:x != 'entities'):
+ path = join(directory, subdir)
+ if exists(path):
+ vregpath.append(path)
+ elif exists(path + '.py'):
+ vregpath.append(path + '.py')
+ return vregpath
+
apphome = None
def load_site_cubicweb(self, paths=None):
@@ -1177,14 +1174,14 @@
self.exception('localisation support error for language %s',
language)
- def vregistry_path(self):
+ def appobjects_path(self):
"""return a list of files or directories where the registry will look
for application objects
"""
templpath = list(reversed(self.cubes_path()))
if self.apphome: # may be unset in tests
templpath.append(self.apphome)
- return self.build_vregistry_path(templpath)
+ return self.build_appobjects_path(templpath)
def set_sources_mode(self, sources):
if not 'all' in sources:
--- a/cwvreg.py Wed Jan 09 16:04:26 2013 +0100
+++ b/cwvreg.py Wed Jan 09 16:06:20 2013 +0100
@@ -588,7 +588,7 @@
"""set instance'schema and load application objects"""
self._set_schema(schema)
# now we can load application's web objects
- self.reload(self.config.vregistry_path(), force_reload=False)
+ self.reload(self.config.appobjects_path(), force_reload=False)
# map lowered entity type names to their actual name
self.case_insensitive_etypes = {}
for eschema in self.schema.entities():
@@ -598,7 +598,7 @@
clear_cache(eschema, 'meta_attributes')
def reload_if_needed(self):
- path = self.config.vregistry_path()
+ path = self.config.appobjects_path()
if self.is_reload_needed(path):
self.reload(path)
@@ -614,7 +614,7 @@
cfg = self.config
for cube in cfg.expand_cubes(cubes, with_recommends=True):
if not cube in cubes:
- cpath = cfg.build_vregistry_cube_path([cfg.cube_dir(cube)])
+ cpath = cfg.build_appobjects_cube_path([cfg.cube_dir(cube)])
cleanup_sys_modules(cpath)
self.register_objects(path)
CW_EVENT_MANAGER.emit('after-registry-reload')
--- a/dataimport.py Wed Jan 09 16:04:26 2013 +0100
+++ b/dataimport.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -66,13 +66,18 @@
"""
__docformat__ = "restructuredtext en"
+import csv
import sys
-import csv
+import threading
import traceback
+import cPickle
import os.path as osp
-from StringIO import StringIO
+from collections import defaultdict
+from contextlib import contextmanager
from copy import copy
-from datetime import datetime
+from datetime import date, datetime
+from time import asctime
+from StringIO import StringIO
from logilab.common import shellutils, attrdict
from logilab.common.date import strptime
@@ -80,9 +85,11 @@
from logilab.common.deprecation import deprecated
from cubicweb import QueryError
+from cubicweb.utils import make_uid
from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES
+from cubicweb.server.edition import EditedEntity
+from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.utils import eschema_eid
-from cubicweb.server.edition import EditedEntity
def count_lines(stream_or_filename):
@@ -117,15 +124,33 @@
print ' %s rows imported' % rowcount
def ucsvreader(stream, encoding='utf-8', separator=',', quote='"',
- skipfirst=False):
+ skipfirst=False, ignore_errors=False):
"""A csv reader that accepts files with any encoding and outputs unicode
strings
"""
it = iter(csv.reader(stream, delimiter=separator, quotechar=quote))
- if skipfirst:
- it.next()
- for row in it:
- yield [item.decode(encoding) for item in row]
+ if not ignore_errors:
+ if skipfirst:
+ it.next()
+ for row in it:
+ yield [item.decode(encoding) for item in row]
+ else:
+ # Skip first line
+ try:
+ row = it.next()
+ except csv.Error:
+ pass
+ # Safe version, that can cope with error in CSV file
+ while True:
+ try:
+ row = it.next()
+ # End of CSV, break
+ except StopIteration:
+ break
+ # Error in CSV, ignore line and continue
+ except csv.Error:
+ continue
+ yield [item.decode(encoding) for item in row]
def callfunc_every(func, number, iterable):
"""yield items of `iterable` one by one and call function `func`
@@ -299,6 +324,149 @@
if k is not None and len(v) > 1]
+# sql generator utility functions #############################################
+
+
+def _import_statements(sql_connect, statements, nb_threads=3,
+ dump_output_dir=None,
+ support_copy_from=True, encoding='utf-8'):
+ """
+ Import a bunch of sql statements, using different threads.
+ """
+ try:
+ chunksize = (len(statements) / nb_threads) + 1
+ threads = []
+ for i in xrange(nb_threads):
+ chunks = statements[i*chunksize:(i+1)*chunksize]
+ thread = threading.Thread(target=_execmany_thread,
+ args=(sql_connect, chunks,
+ dump_output_dir,
+ support_copy_from,
+ encoding))
+ thread.start()
+ threads.append(thread)
+ for t in threads:
+ t.join()
+ except Exception:
+ print 'Error in import statements'
+
+def _execmany_thread_not_copy_from(cu, statement, data, table=None,
+ columns=None, encoding='utf-8'):
+ """ Execute thread without copy from
+ """
+ cu.executemany(statement, data)
+
+def _execmany_thread_copy_from(cu, statement, data, table,
+ columns, encoding='utf-8'):
+ """ Execute thread with copy from
+ """
+ buf = _create_copyfrom_buffer(data, columns, encoding)
+ if buf is None:
+ _execmany_thread_not_copy_from(cu, statement, data)
+ else:
+ if columns is None:
+ cu.copy_from(buf, table, null='NULL')
+ else:
+ cu.copy_from(buf, table, null='NULL', columns=columns)
+
+def _execmany_thread(sql_connect, statements, dump_output_dir=None,
+ support_copy_from=True, encoding='utf-8'):
+ """
+ Execute sql statement. If 'INSERT INTO', try to use 'COPY FROM' command,
+ or fallback to execute_many.
+ """
+ if support_copy_from:
+ execmany_func = _execmany_thread_copy_from
+ else:
+ execmany_func = _execmany_thread_not_copy_from
+ cnx = sql_connect()
+ cu = cnx.cursor()
+ try:
+ for statement, data in statements:
+ table = None
+ columns = None
+ try:
+ if not statement.startswith('INSERT INTO'):
+ cu.executemany(statement, data)
+ continue
+ table = statement.split()[2]
+ if isinstance(data[0], (tuple, list)):
+ columns = None
+ else:
+ columns = data[0].keys()
+ execmany_func(cu, statement, data, table, columns, encoding)
+ except Exception:
+ print 'unable to copy data into table %s', table
+ # Error in import statement, save data in dump_output_dir
+ if dump_output_dir is not None:
+ pdata = {'data': data, 'statement': statement,
+ 'time': asctime(), 'columns': columns}
+ filename = make_uid()
+ try:
+ with open(osp.join(dump_output_dir,
+ '%s.pickle' % filename), 'w') as fobj:
+ fobj.write(cPickle.dumps(pdata))
+ except IOError:
+ print 'ERROR while pickling in', dump_output_dir, filename+'.pickle'
+ pass
+ cnx.rollback()
+ raise
+ finally:
+ cnx.commit()
+ cu.close()
+
+def _create_copyfrom_buffer(data, columns, encoding='utf-8', replace_sep=None):
+ """
+ Create a StringIO buffer for 'COPY FROM' command.
+ Deals with Unicode, Int, Float, Date...
+ """
+ # Create a list rather than directly create a StringIO
+ # to correctly write lines separated by '\n' in a single step
+ rows = []
+ if isinstance(data[0], (tuple, list)):
+ columns = range(len(data[0]))
+ for row in data:
+ # Iterate over the different columns and the different values
+ # and try to convert them to a correct datatype.
+ # If an error is raised, do not continue.
+ formatted_row = []
+ for col in columns:
+ value = row[col]
+ if value is None:
+ value = 'NULL'
+ elif isinstance(value, (long, int, float)):
+ value = str(value)
+ elif isinstance(value, (str, unicode)):
+ # Remove separators used in string formatting
+ for _char in (u'\t', u'\r', u'\n'):
+ if _char in value:
+ # If a replace_sep is given, replace
+ # the separator instead of returning None
+ # (and thus avoid empty buffer)
+ if replace_sep:
+ value = value.replace(_char, replace_sep)
+ else:
+ return
+ value = value.replace('\\', r'\\')
+ if value is None:
+ return
+ if isinstance(value, unicode):
+ value = value.encode(encoding)
+ elif isinstance(value, (date, datetime)):
+ # Do not use strftime, as it yields issue
+ # with date < 1900
+ value = '%04d-%02d-%02d' % (value.year,
+ value.month,
+ value.day)
+ else:
+ return None
+ # We push the value to the new formatted row
+ # if the value is not None and could be converted to a string.
+ formatted_row.append(value)
+ rows.append('\t'.join(formatted_row))
+ return StringIO('\n'.join(rows))
+
+
# object stores #################################################################
class ObjectStore(object):
@@ -753,3 +921,261 @@
return self.session.user.eid
def gen_owned_by(self, entity):
return self.session.user.eid
+
+
+###########################################################################
+## SQL object store #######################################################
+###########################################################################
+class SQLGenObjectStore(NoHookRQLObjectStore):
+ """Controller of the data import process. This version is based
+ on direct insertions throught SQL command (COPY FROM or execute many).
+
+ >>> store = SQLGenObjectStore(session)
+ >>> store.create_entity('Person', ...)
+ >>> store.flush()
+ """
+
+ def __init__(self, session, dump_output_dir=None, nb_threads_statement=3):
+ """
+ Initialize a SQLGenObjectStore.
+
+ Parameters:
+
+ - session: session on the cubicweb instance
+ - dump_output_dir: a directory to dump failed statements
+ for easier recovery. Default is None (no dump).
+ - nb_threads_statement: number of threads used
+ for SQL insertion (default is 3).
+ """
+ super(SQLGenObjectStore, self).__init__(session)
+ ### hijack default source
+ self.source = SQLGenSourceWrapper(
+ self.source, session.vreg.schema,
+ dump_output_dir=dump_output_dir,
+ nb_threads_statement=nb_threads_statement)
+ ### XXX This is done in super().__init__(), but should be
+ ### redone here to link to the correct source
+ self.add_relation = self.source.add_relation
+ self.indexes_etypes = {}
+
+ def flush(self):
+ """Flush data to the database"""
+ self.source.flush()
+
+ def relate(self, subj_eid, rtype, obj_eid, subjtype=None):
+ if subj_eid is None or obj_eid is None:
+ return
+ # XXX Could subjtype be inferred ?
+ self.source.add_relation(self.session, subj_eid, rtype, obj_eid,
+ self.rschema(rtype).inlined, subjtype)
+
+ def drop_indexes(self, etype):
+ """Drop indexes for a given entity type"""
+ if etype not in self.indexes_etypes:
+ cu = self.session.cnxset['system']
+ def index_to_attr(index):
+ """turn an index name to (database) attribute name"""
+ return index.replace(etype.lower(), '').replace('idx', '').strip('_')
+ indices = [(index, index_to_attr(index))
+ for index in self.source.dbhelper.list_indices(cu, etype)
+ # Do not consider 'cw_etype_pkey' index
+ if not index.endswith('key')]
+ self.indexes_etypes[etype] = indices
+ for index, attr in self.indexes_etypes[etype]:
+ self.session.system_sql('DROP INDEX %s' % index)
+
+ def create_indexes(self, etype):
+ """Recreate indexes for a given entity type"""
+ for index, attr in self.indexes_etypes.get(etype, []):
+ sql = 'CREATE INDEX %s ON cw_%s(%s)' % (index, etype, attr)
+ self.session.system_sql(sql)
+
+
+###########################################################################
+## SQL Source #############################################################
+###########################################################################
+
+class SQLGenSourceWrapper(object):
+
+ def __init__(self, system_source, schema,
+ dump_output_dir=None, nb_threads_statement=3):
+ self.system_source = system_source
+ self._sql = threading.local()
+ # Explicitely backport attributes from system source
+ self._storage_handler = self.system_source._storage_handler
+ self.preprocess_entity = self.system_source.preprocess_entity
+ self.sqlgen = self.system_source.sqlgen
+ self.copy_based_source = self.system_source.copy_based_source
+ self.uri = self.system_source.uri
+ self.eid = self.system_source.eid
+ # Directory to write temporary files
+ self.dump_output_dir = dump_output_dir
+ # Allow to execute code with SQLite backend that does
+ # not support (yet...) copy_from
+ # XXX Should be dealt with in logilab.database
+ spcfrom = system_source.dbhelper.dbapi_module.support_copy_from
+ self.support_copy_from = spcfrom
+ self.dbencoding = system_source.dbhelper.dbencoding
+ self.nb_threads_statement = nb_threads_statement
+ # initialize thread-local data for main thread
+ self.init_thread_locals()
+ self._inlined_rtypes_cache = {}
+ self._fill_inlined_rtypes_cache(schema)
+ self.schema = schema
+ self.do_fti = False
+
+ def _fill_inlined_rtypes_cache(self, schema):
+ cache = self._inlined_rtypes_cache
+ for eschema in schema.entities():
+ for rschema in eschema.ordered_relations():
+ if rschema.inlined:
+ cache[eschema.type] = SQL_PREFIX + rschema.type
+
+ def init_thread_locals(self):
+ """initializes thread-local data"""
+ self._sql.entities = defaultdict(list)
+ self._sql.relations = {}
+ self._sql.inlined_relations = {}
+ # keep track, for each eid of the corresponding data dict
+ self._sql.eid_insertdicts = {}
+
+ def flush(self):
+ print 'starting flush'
+ _entities_sql = self._sql.entities
+ _relations_sql = self._sql.relations
+ _inlined_relations_sql = self._sql.inlined_relations
+ _insertdicts = self._sql.eid_insertdicts
+ try:
+ # try, for each inlined_relation, to find if we're also creating
+ # the host entity (i.e. the subject of the relation).
+ # In that case, simply update the insert dict and remove
+ # the need to make the
+ # UPDATE statement
+ for statement, datalist in _inlined_relations_sql.iteritems():
+ new_datalist = []
+ # for a given inlined relation,
+ # browse each couple to be inserted
+ for data in datalist:
+ keys = data.keys()
+ # For inlined relations, it exists only two case:
+ # (rtype, cw_eid) or (cw_eid, rtype)
+ if keys[0] == 'cw_eid':
+ rtype = keys[1]
+ else:
+ rtype = keys[0]
+ updated_eid = data['cw_eid']
+ if updated_eid in _insertdicts:
+ _insertdicts[updated_eid][rtype] = data[rtype]
+ else:
+ # could not find corresponding insert dict, keep the
+ # UPDATE query
+ new_datalist.append(data)
+ _inlined_relations_sql[statement] = new_datalist
+ _import_statements(self.system_source.get_connection,
+ _entities_sql.items()
+ + _relations_sql.items()
+ + _inlined_relations_sql.items(),
+ dump_output_dir=self.dump_output_dir,
+ nb_threads=self.nb_threads_statement,
+ support_copy_from=self.support_copy_from,
+ encoding=self.dbencoding)
+ except:
+ print 'failed to flush'
+ finally:
+ _entities_sql.clear()
+ _relations_sql.clear()
+ _insertdicts.clear()
+ _inlined_relations_sql.clear()
+ print 'flush done'
+
+ def add_relation(self, session, subject, rtype, object,
+ inlined=False, subjtype=None):
+ if inlined:
+ _sql = self._sql.inlined_relations
+ data = {'cw_eid': subject, SQL_PREFIX + rtype: object}
+ if subjtype is None:
+ # Try to infer it
+ targets = [t.type for t in
+ self.schema.rschema(rtype).targets()]
+ if len(targets) == 1:
+ subjtype = targets[0]
+ else:
+ raise ValueError('You should give the subject etype for '
+ 'inlined relation %s'
+ ', as it cannot be inferred' % rtype)
+ statement = self.sqlgen.update(SQL_PREFIX + subjtype,
+ data, ['cw_eid'])
+ else:
+ _sql = self._sql.relations
+ data = {'eid_from': subject, 'eid_to': object}
+ statement = self.sqlgen.insert('%s_relation' % rtype, data)
+ if statement in _sql:
+ _sql[statement].append(data)
+ else:
+ _sql[statement] = [data]
+
+ def add_entity(self, session, entity):
+ with self._storage_handler(entity, 'added'):
+ attrs = self.preprocess_entity(entity)
+ rtypes = self._inlined_rtypes_cache.get(entity.__regid__, ())
+ if isinstance(rtypes, str):
+ rtypes = (rtypes,)
+ for rtype in rtypes:
+ if rtype not in attrs:
+ attrs[rtype] = None
+ sql = self.sqlgen.insert(SQL_PREFIX + entity.__regid__, attrs)
+ self._sql.eid_insertdicts[entity.eid] = attrs
+ self._append_to_entities(sql, attrs)
+
+ def _append_to_entities(self, sql, attrs):
+ self._sql.entities[sql].append(attrs)
+
+ def _handle_insert_entity_sql(self, session, sql, attrs):
+ # We have to overwrite the source given in parameters
+ # as here, we directly use the system source
+ attrs['source'] = 'system'
+ attrs['asource'] = self.system_source.uri
+ self._append_to_entities(sql, attrs)
+
+ def _handle_is_relation_sql(self, session, sql, attrs):
+ self._append_to_entities(sql, attrs)
+
+ def _handle_is_instance_of_sql(self, session, sql, attrs):
+ self._append_to_entities(sql, attrs)
+
+ def _handle_source_relation_sql(self, session, sql, attrs):
+ self._append_to_entities(sql, attrs)
+
+ # XXX add_info is similar to the one in NativeSQLSource. It is rewritten
+ # here to correctly used the _handle_xxx of the SQLGenSourceWrapper. This
+ # part should be rewritten in a more clearly way.
+ def add_info(self, session, entity, source, extid, complete):
+ """add type and source info for an eid into the system table"""
+ # begin by inserting eid/type/source/extid into the entities table
+ if extid is not None:
+ assert isinstance(extid, str)
+ extid = b64encode(extid)
+ uri = 'system' if source.copy_based_source else source.uri
+ attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
+ 'source': uri, 'asource': source.uri, 'mtime': datetime.utcnow()}
+ self._handle_insert_entity_sql(session, self.sqlgen.insert('entities', attrs), attrs)
+ # insert core relations: is, is_instance_of and cw_source
+ try:
+ self._handle_is_relation_sql(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, eschema_eid(session, entity.e_schema)))
+ except IndexError:
+ # during schema serialization, skip
+ pass
+ else:
+ for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
+ self._handle_is_relation_sql(session,
+ 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, eschema_eid(session, eschema)))
+ if 'CWSource' in self.schema and source.eid is not None: # else, cw < 3.10
+ self._handle_is_relation_sql(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, source.eid))
+ # now we can update the full text index
+ if self.do_fti and self.need_fti_indexation(entity.__regid__):
+ if complete:
+ entity.complete(entity.e_schema.indexable_attributes())
+ self.index_entity(session, entity=entity)
--- a/dbapi.py Wed Jan 09 16:04:26 2013 +0100
+++ b/dbapi.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -80,9 +80,8 @@
class ConnectionProperties(object):
- def __init__(self, cnxtype=None, lang=None, close=True, log=False):
+ def __init__(self, cnxtype=None, close=True, log=False):
self.cnxtype = cnxtype or 'pyro'
- self.lang = lang
self.log_queries = log
self.close_on_del = close
@@ -283,6 +282,8 @@
return '<DBAPISession %r>' % self.sessionid
class DBAPIRequest(RequestSessionBase):
+ #: Request language identifier eg: 'en'
+ lang = None
def __init__(self, vreg, session=None):
super(DBAPIRequest, self).__init__(vreg)
@@ -292,9 +293,6 @@
self.translations = vreg.config.translations
except AttributeError:
self.translations = {}
- #: Request language identifier eg: 'en'
- self.lang = None
- self.set_default_language(vreg)
#: cache entities built during the request
self._eid_cache = {}
if session is not None:
@@ -304,6 +302,7 @@
# established
self.session = None
self.cnx = self.user = _NeedAuthAccessMock()
+ self.set_default_language(vreg)
def from_controller(self):
return 'view'
@@ -320,7 +319,7 @@
self.cnx = session.cnx
self.execute = session.cnx.cursor(self).execute
if user is None:
- user = self.cnx.user(self, {'lang': self.lang})
+ user = self.cnx.user(self)
if user is not None:
self.user = user
self.set_entity_cache(user)
@@ -333,19 +332,15 @@
def set_default_language(self, vreg):
try:
- self.lang = vreg.property_value('ui.language')
+ lang = vreg.property_value('ui.language')
except Exception: # property may not be registered
- self.lang = 'en'
- # use req.__ to translate a message without registering it to the catalog
+ lang = 'en'
try:
- gettext, pgettext = self.translations[self.lang]
- self._ = self.__ = gettext
- self.pgettext = pgettext
+ self.set_language(lang)
except KeyError:
# this occurs usually during test execution
self._ = self.__ = unicode
self.pgettext = lambda x, y: unicode(y)
- self.debug('request default language: %s', self.lang)
# server-side service call #################################################
@@ -606,9 +601,9 @@
# then init cubes
config.init_cubes(cubes)
# then load appobjects into the registry
- vpath = config.build_vregistry_path(reversed(config.cubes_path()),
- evobjpath=esubpath,
- tvobjpath=subpath)
+ vpath = config.build_appobjects_path(reversed(config.cubes_path()),
+ evobjpath=esubpath,
+ tvobjpath=subpath)
self.vreg.register_objects(vpath)
def use_web_compatible_requests(self, baseurl, sitetitle=None):
@@ -678,11 +673,6 @@
# session data methods #####################################################
@check_not_closed
- def set_session_props(self, **props):
- """raise `BadConnectionId` if the connection is no more valid"""
- self._repo.set_session_props(self.sessionid, props)
-
- @check_not_closed
def get_shared_data(self, key, default=None, pop=False, txdata=False):
"""return value associated to key in the session's data dictionary or
session's transaction's data if `txdata` is true.
--- a/debian/control Wed Jan 09 16:04:26 2013 +0100
+++ b/debian/control Wed Jan 09 16:06:20 2013 +0100
@@ -107,7 +107,7 @@
Package: cubicweb-common
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.58.0), python-yams (>= 0.34.0), python-rql (>= 0.31.2), python-lxml
+Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.58.0), python-yams (>= 0.36.0), python-rql (>= 0.31.2), python-lxml
Recommends: python-simpletal (>= 4.0), python-crypto
Conflicts: cubicweb-core
Replaces: cubicweb-core
--- a/devtools/__init__.py Wed Jan 09 16:04:26 2013 +0100
+++ b/devtools/__init__.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -355,7 +355,7 @@
def _restore_database(self, backup_coordinates, config):
"""Actual restore of the current database.
- Use the value tostored in db_cache as input """
+ Use the value stored in db_cache as input """
raise NotImplementedError()
def get_repo(self, startup=False):
@@ -466,7 +466,6 @@
``pre_setup_func`` to setup the database.
This function backup any database it build"""
-
if self.has_cache(test_db_id):
return #test_db_id, 'already in cache'
if test_db_id is DEFAULT_EMPTY_DB_ID:
@@ -723,7 +722,7 @@
dbfile = self.absolute_dbfile()
self._cleanup_database(dbfile)
shutil.copy(backup_coordinates, dbfile)
- repo = self.get_repo()
+ self.get_repo()
def init_test_database(self):
"""initialize a fresh sqlite databse used for testing purpose"""
--- a/devtools/devctl.py Wed Jan 09 16:04:26 2013 +0100
+++ b/devtools/devctl.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -91,7 +91,7 @@
if mod.__file__ is None:
# odd/rare but real
continue
- for path in config.vregistry_path():
+ for path in config.appobjects_path():
if mod.__file__.startswith(path):
del sys.modules[name]
break
@@ -302,10 +302,11 @@
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import globfind, find, rm
from logilab.common.modutils import get_module_files
- from cubicweb.i18n import extract_from_tal, execute
+ from cubicweb.i18n import extract_from_tal, execute2
tempdir = tempfile.mkdtemp(prefix='cw-')
cwi18ndir = WebConfiguration.i18n_lib_dir()
- print '-> extract schema messages.'
+ print '-> extract messages:',
+ print 'schema',
schemapot = osp.join(tempdir, 'schema.pot')
potfiles = [schemapot]
potfiles.append(schemapot)
@@ -314,7 +315,7 @@
schemapotstream = file(schemapot, 'w')
generate_schema_pot(schemapotstream.write, cubedir=None)
schemapotstream.close()
- print '-> extract TAL messages.'
+ print 'TAL',
tali18nfile = osp.join(tempdir, 'tali18n.py')
extract_from_tal(find(osp.join(BASEDIR, 'web'), ('.py', '.pt')),
tali18nfile)
@@ -329,26 +330,29 @@
('tal', [tali18nfile], None),
('js', jsfiles, 'java'),
]:
- cmd = 'xgettext --no-location --omit-header -k_ -o %s %s'
+ potfile = osp.join(tempdir, '%s.pot' % id)
+ cmd = ['xgettext', '--no-location', '--omit-header', '-k_']
if lang is not None:
- cmd += ' -L %s' % lang
- potfile = osp.join(tempdir, '%s.pot' % id)
- execute(cmd % (potfile, ' '.join('"%s"' % f for f in files)))
+ cmd.extend(['-L', lang])
+ cmd.extend(['-o', potfile])
+ cmd.extend(files)
+ execute2(cmd)
if osp.exists(potfile):
potfiles.append(potfile)
else:
print '-> WARNING: %s file was not generated' % potfile
print '-> merging %i .pot files' % len(potfiles)
cubicwebpot = osp.join(tempdir, 'cubicweb.pot')
- execute('msgcat -o %s %s'
- % (cubicwebpot, ' '.join('"%s"' % f for f in potfiles)))
+ cmd = ['msgcat', '-o', cubicwebpot] + potfiles
+ execute2(cmd)
print '-> merging main pot file with existing translations.'
chdir(cwi18ndir)
toedit = []
for lang in CubicWebNoAppConfiguration.cw_languages():
target = '%s.po' % lang
- execute('msgmerge -N --sort-output -o "%snew" "%s" "%s"'
- % (target, target, cubicwebpot))
+ cmd = ['msgmerge', '-N', '--sort-output', '-o',
+ target+'new', target, cubicwebpot]
+ execute2(cmd)
ensure_fs_mode(target)
shutil.move('%snew' % target, target)
toedit.append(osp.abspath(target))
@@ -382,16 +386,21 @@
def update_cubes_catalogs(cubes):
+ from subprocess import CalledProcessError
for cubedir in cubes:
if not osp.isdir(cubedir):
print '-> ignoring %s that is not a directory.' % cubedir
continue
try:
toedit = update_cube_catalogs(cubedir)
+ except CalledProcessError, exc:
+ print '\n*** error while updating catalogs for cube', cubedir
+ print 'cmd:\n%s' % exc.cmd
+ print 'stdout:\n%s\nstderr:\n%s' % exc.data
except Exception:
import traceback
traceback.print_exc()
- print '-> error while updating catalogs for cube', cubedir
+ print '*** error while updating catalogs for cube', cubedir
return False
else:
# instructions pour la suite
@@ -408,7 +417,7 @@
import tempfile
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import find, rm
- from cubicweb.i18n import extract_from_tal, execute
+ from cubicweb.i18n import extract_from_tal, execute2
cube = osp.basename(osp.normpath(cubedir))
tempdir = tempfile.mkdtemp()
print underline_title('Updating i18n catalogs for cube %s' % cube)
@@ -421,7 +430,8 @@
potfiles = [osp.join('i18n', 'static-messages.pot')]
else:
potfiles = []
- print '-> extract schema messages'
+ print '-> extracting messages:',
+ print 'schema',
schemapot = osp.join(tempdir, 'schema.pot')
potfiles.append(schemapot)
# explicit close necessary else the file may not be yet flushed when
@@ -429,50 +439,55 @@
schemapotstream = file(schemapot, 'w')
generate_schema_pot(schemapotstream.write, cubedir)
schemapotstream.close()
- print '-> extract TAL messages'
+ print 'TAL',
tali18nfile = osp.join(tempdir, 'tali18n.py')
ptfiles = find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',))
extract_from_tal(ptfiles, tali18nfile)
- print '-> extract Javascript messages'
+ print 'Javascript'
jsfiles = [jsfile for jsfile in find('.', '.js')
if osp.basename(jsfile).startswith('cub')]
if jsfiles:
tmppotfile = osp.join(tempdir, 'js.pot')
- execute('xgettext --no-location --omit-header -k_ -L java '
- '--from-code=utf-8 -o %s %s' % (tmppotfile, ' '.join(jsfiles)))
+ cmd = ['xgettext', '--no-location', '--omit-header', '-k_', '-L', 'java',
+ '--from-code=utf-8', '-o', tmppotfile] + jsfiles
+ execute2(cmd)
# no pot file created if there are no string to translate
if osp.exists(tmppotfile):
potfiles.append(tmppotfile)
- print '-> create cube-specific catalog'
+ print '-> creating cube-specific catalog'
tmppotfile = osp.join(tempdir, 'generated.pot')
cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
cubefiles.append(tali18nfile)
- execute('xgettext --no-location --omit-header -k_ -o %s %s'
- % (tmppotfile, ' '.join('"%s"' % f for f in cubefiles)))
+ cmd = ['xgettext', '--no-location', '--omit-header', '-k_', '-o', tmppotfile]
+ cmd.extend(cubefiles)
+ execute2(cmd)
if osp.exists(tmppotfile): # doesn't exists of no translation string found
potfiles.append(tmppotfile)
potfile = osp.join(tempdir, 'cube.pot')
- print '-> merging %i .pot files:' % len(potfiles)
- execute('msgcat -o %s %s' % (potfile,
- ' '.join('"%s"' % f for f in potfiles)))
+ print '-> merging %i .pot files' % len(potfiles)
+ cmd = ['msgcat', '-o', potfile]
+ cmd.extend(potfiles)
+ execute2(cmd)
if not osp.exists(potfile):
print 'no message catalog for cube', cube, 'nothing to translate'
# cleanup
rm(tempdir)
return ()
- print '-> merging main pot file with existing translations:'
+ print '-> merging main pot file with existing translations:',
chdir('i18n')
toedit = []
for lang in CubicWebNoAppConfiguration.cw_languages():
- print '-> language', lang
+ print lang,
cubepo = '%s.po' % lang
if not osp.exists(cubepo):
shutil.copy(potfile, cubepo)
else:
- execute('msgmerge -N -s -o %snew %s %s' % (cubepo, cubepo, potfile))
+ cmd = ['msgmerge','-N','-s','-o', cubepo+'new', cubepo, potfile]
+ execute2(cmd)
ensure_fs_mode(cubepo)
shutil.move('%snew' % cubepo, cubepo)
toedit.append(osp.abspath(cubepo))
+ print
# cleanup
rm(tempdir)
return toedit
--- a/devtools/fake.py Wed Jan 09 16:04:26 2013 +0100
+++ b/devtools/fake.py Wed Jan 09 16:06:20 2013 +0100
@@ -155,6 +155,12 @@
def set_entity_cache(self, entity):
pass
+ def security_enabled(self, read=False, write=False):
+ class FakeCM(object):
+ def __enter__(self): pass
+ def __exit__(self, exctype, exc, traceback): pass
+ return FakeCM()
+
# for use with enabled_security context manager
read_security = write_security = True
def init_security(self, *args):
--- a/devtools/repotest.py Wed Jan 09 16:04:26 2013 +0100
+++ b/devtools/repotest.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/devtools/testlib.py Wed Jan 09 16:04:26 2013 +0100
+++ b/devtools/testlib.py Wed Jan 09 16:06:20 2013 +0100
@@ -47,7 +47,7 @@
from cubicweb import cwconfig, dbapi, devtools, web, server
from cubicweb.sobjects import notification
from cubicweb.web import Redirect, application
-from cubicweb.server.session import Session, security_enabled
+from cubicweb.server.session import Session
from cubicweb.server.hook import SendMailOp
from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
from cubicweb.devtools import BASE_URL, fake, htmlparser, DEFAULT_EMPTY_DB_ID
@@ -445,7 +445,7 @@
finally:
self.session.set_cnxset() # ensure cnxset still set after commit
- # # server side db api #######################################################
+ # server side db api #######################################################
def sexecute(self, rql, args=None, eid_key=None):
if eid_key is not None:
@@ -1054,7 +1054,7 @@
"""this method populates the database with `how_many` entities
of each possible type. It also inserts random relations between them
"""
- with security_enabled(self.session, read=False, write=False):
+ with self.session.security_enabled(read=False, write=False):
self._auto_populate(how_many)
def _auto_populate(self, how_many):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/3.16.rst Wed Jan 09 16:06:20 2013 +0100
@@ -0,0 +1,24 @@
+What's new in CubicWeb 3.16?
+============================
+
+New functionnalities
+--------------------
+
+* Add a new dataimport store (`SQLGenObjectStore`). This store enables a fast
+ import of data (entity creation, link creation) in CubicWeb, by directly
+ flushing information in SQL. This may only be used with PostgreSQL, as it
+ requires the 'COPY FROM' command.
+
+API changes
+-----------
+
+Unintrusive API changes
+-----------------------
+
+
+
+User interface changes
+----------------------
+
+* Remove changelog view, as nor cubicweb nor known cubes/applications were properly
+ feeding related files
\ No newline at end of file
--- a/doc/book/en/annexes/faq.rst Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/annexes/faq.rst Wed Jan 09 16:06:20 2013 +0100
@@ -364,7 +364,7 @@
>>> crypted = crypt_password('joepass')
>>> rset = rql('Any U WHERE U is CWUser, U login "joe"')
>>> joe = rset.get_entity(0,0)
- >>> joe.set_attributes(upassword=Binary(crypted))
+ >>> joe.cw_set(upassword=Binary(crypted))
Please, refer to the script example is provided in the `misc/examples/chpasswd.py` file.
--- a/doc/book/en/annexes/rql/debugging.rst Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/annexes/rql/debugging.rst Wed Jan 09 16:06:20 2013 +0100
@@ -15,6 +15,8 @@
.. autodata:: cubicweb.server.DBG_SQL
.. autodata:: cubicweb.server.DBG_REPO
.. autodata:: cubicweb.server.DBG_MS
+.. autodata:: cubicweb.server.DBG_HOOKS
+.. autodata:: cubicweb.server.DBG_OPS
.. autodata:: cubicweb.server.DBG_MORE
.. autodata:: cubicweb.server.DBG_ALL
@@ -31,6 +33,14 @@
.. autofunction:: cubicweb.server.set_debug
+Another example showing how to debug hooks at a specific code site:
+
+.. sourcecode:: python
+
+ from cubicweb.server import debuged, DBG_HOOKS
+ with debugged(DBG_HOOKS):
+ person.cw_set(works_for=company)
+
Detect largest RQL queries
~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devrepo/dataimport.rst Wed Jan 09 16:06:20 2013 +0100
@@ -0,0 +1,58 @@
+. -*- coding: utf-8 -*-
+
+.. _dataimport:
+
+Dataimport
+==========
+
+*CubicWeb* is designed to manipulate huge of amount of data, and provides helper functions to do so.
+These functions insert data within different levels of the *CubicWeb* API,
+allowing different speed/security tradeoffs. Those keeping all the *CubicWeb* hooks
+and security will be slower but the possible errors in insertion
+(bad data types, integrity error, ...) will be raised.
+
+These dataimport function are provided in the file `dataimport.py`.
+
+All the stores have the following API::
+
+ >>> store = ObjectStore()
+ >>> user = store.create_entity('CWUser', login=u'johndoe')
+ >>> group = store.create_entity('CWUser', name=u'unknown')
+ >>> store.relate(user.eid, 'in_group', group.eid)
+
+
+ObjectStore
+-----------
+
+This store keeps objects in memory for *faster* validation. It may be useful
+in development mode. However, as it will not enforce the constraints of the schema,
+it may miss some problems.
+
+
+
+RQLObjectStore
+--------------
+
+This store works with an actual RQL repository, and it may be used in production mode.
+
+
+NoHookRQLObjectStore
+--------------------
+
+This store works similarly to the *RQLObjectStore* but bypasses some *CubicWeb* hooks to be faster.
+
+
+SQLGenObjectStore
+-----------------
+
+This store relies on *COPY FROM*/execute many sql commands to directly push data using SQL commands
+rather than using the whole *CubicWeb* API. For now, **it only works with PostgresSQL** as it requires
+the *COPY FROM* command.
+
+The API is similar to the other stores, but **it requires a flush** after some imports to copy data
+in the database (these flushes may be multiples through the processes, or be done only once at the
+end if there is no memory issue)::
+
+ >>> store = SQLGenObjectStore(session)
+ >>> store.create_entity('Person', ...)
+ >>> store.flush()
--- a/doc/book/en/devrepo/entityclasses/application-logic.rst Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/devrepo/entityclasses/application-logic.rst Wed Jan 09 16:06:20 2013 +0100
@@ -38,7 +38,7 @@
object was built.
Setting an attribute or relation value can be done in the context of a
-Hook/Operation, using the obj.set_relations(x=42) notation or a plain
+Hook/Operation, using the obj.cw_set(x=42) notation or a plain
RQL SET expression.
In views, it would be preferable to encapsulate the necessary logic in
--- a/doc/book/en/devrepo/entityclasses/data-as-objects.rst Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/devrepo/entityclasses/data-as-objects.rst Wed Jan 09 16:06:20 2013 +0100
@@ -16,50 +16,47 @@
`Formatting and output generation`:
-* `view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
+* :meth:`view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
(and returns an unicode string)
-* `absolute_url(*args, **kwargs)`, returns an absolute URL including the base-url
+* :meth:`absolute_url(*args, **kwargs)`, returns an absolute URL including the base-url
-* `rest_path()`, returns a relative REST URL to get the entity
+* :meth:`rest_path()`, returns a relative REST URL to get the entity
-* `printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
+* :meth:`printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
returns a string enabling the display of an attribute value in a given format
(the value is automatically recovered if necessary)
`Data handling`:
-* `as_rset()`, converts the entity into an equivalent result set simulating the
+* :meth:`as_rset()`, converts the entity into an equivalent result set simulating the
request `Any X WHERE X eid _eid_`
-* `complete(skip_bytes=True)`, executes a request that recovers at
+* :meth:`complete(skip_bytes=True)`, executes a request that recovers at
once all the missing attributes of an entity
-* `get_value(name)`, returns the value associated to the attribute name given
+* :meth:`get_value(name)`, returns the value associated to the attribute name given
in parameter
-* `related(rtype, role='subject', limit=None, entities=False)`,
+* :meth:`related(rtype, role='subject', limit=None, entities=False)`,
returns a list of entities related to the current entity by the
relation given in parameter
-* `unrelated(rtype, targettype, role='subject', limit=None)`,
+* :meth:`unrelated(rtype, targettype, role='subject', limit=None)`,
returns a result set corresponding to the entities not (yet)
related to the current entity by the relation given in parameter
and satisfying its constraints
-* `set_attributes(**kwargs)`, updates the attributes list with the corresponding
- values given named parameters
+* :meth:`cw_set(**kwargs)`, updates entity's attributes and/or relation with the
+ corresponding values given named parameters. To set a relation where this
+ entity is the object of the relation, use `reverse_<relation>` as argument
+ name. Values may be an entity, a list of entities, or None (meaning that all
+ relations of the given type from or to this object should be deleted).
-* `set_relations(**kwargs)`, add relations to the given object. To
- set a relation where this entity is the object of the relation,
- use `reverse_<relation>` as argument name. Values may be an
- entity, a list of entities, or None (meaning that all relations of
- the given type from or to this object should be deleted).
-
-* `copy_relations(ceid)`, copies the relations of the entities having the eid
+* :meth:`copy_relations(ceid)`, copies the relations of the entities having the eid
given in the parameters on the current entity
-* `delete()` allows to delete the entity
+* :meth:`cw_delete()` allows to delete the entity
The :class:`AnyEntity` class
@@ -81,40 +78,30 @@
`Standard meta-data (Dublin Core)`:
-* `dc_title()`, returns a unicode string corresponding to the
+* :meth:`dc_title()`, returns a unicode string corresponding to the
meta-data `Title` (used by default is the first non-meta attribute
of the entity schema)
-* `dc_long_title()`, same as dc_title but can return a more
+* :meth:`dc_long_title()`, same as dc_title but can return a more
detailed title
-* `dc_description(format='text/plain')`, returns a unicode string
+* :meth:`dc_description(format='text/plain')`, returns a unicode string
corresponding to the meta-data `Description` (looks for a
description attribute by default)
-* `dc_authors()`, returns a unicode string corresponding to the meta-data
+* :meth:`dc_authors()`, returns a unicode string corresponding to the meta-data
`Authors` (owners by default)
-* `dc_creator()`, returns a unicode string corresponding to the
+* :meth:`dc_creator()`, returns a unicode string corresponding to the
creator of the entity
-* `dc_date(date_format=None)`, returns a unicode string corresponding to
+* :meth:`dc_date(date_format=None)`, returns a unicode string corresponding to
the meta-data `Date` (update date by default)
-* `dc_type(form='')`, returns a string to display the entity type by
+* :meth:`dc_type(form='')`, returns a string to display the entity type by
specifying the preferred form (`plural` for a plural form)
-* `dc_language()`, returns the language used by the entity
-
-
-`Misc methods`:
-
-* `after_deletion_path`, return (path, parameters) which should be
- used as redirect information when this entity is being deleted
-
-* `pre_web_edit`, callback called by the web editcontroller when an
- entity will be created/modified, to let a chance to do some entity
- specific stuff (does nothing by default)
+* :meth:`dc_language()`, returns the language used by the entity
Inheritance
-----------
--- a/doc/book/en/devrepo/repo/hooks.rst Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/devrepo/repo/hooks.rst Wed Jan 09 16:06:20 2013 +0100
@@ -206,10 +206,11 @@
Reminder
~~~~~~~~
-You should never use the `entity.foo = 42` notation to update an
-entity. It will not do what you expect (updating the
-database). Instead, use the :meth:`set_attributes` and
-:meth:`set_relations` methods.
+You should never use the `entity.foo = 42` notation to update an entity. It will
+not do what you expect (updating the database). Instead, use the
+:meth:`~cubicweb.entity.Entity.cw_set` method or direct access to entity's
+:attr:`cw_edited` attribute if you're writing a hook for 'before_add_entity' or
+'before_update_entity' event.
How to choose between a before and an after event ?
--- a/doc/book/en/devrepo/testing.rst Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/devrepo/testing.rst Wed Jan 09 16:06:20 2013 +0100
@@ -70,13 +70,13 @@
def test_cannot_create_cycles(self):
# direct obvious cycle
- self.assertRaises(ValidationError, self.kw1.set_relations,
+ self.assertRaises(ValidationError, self.kw1.cw_set,
subkeyword_of=self.kw1)
# testing indirect cycles
kw3 = self.execute('INSERT Keyword SK: SK name "kwgroup2", SK included_in C, '
'SK subkeyword_of K WHERE C name "classif1", K eid %s'
% self.kw1.eid).get_entity(0,0)
- self.kw1.set_relations(subkeyword_of=kw3)
+ self.kw1.cw_set(subkeyword_of=kw3)
self.assertRaises(ValidationError, self.commit)
The test class defines a :meth:`setup_database` method which populates the
@@ -192,10 +192,10 @@
description=u'cubicweb is beautiful')
blog_entry_1 = req.create_entity('BlogEntry', title=u'hop',
content=u'cubicweb hop')
- blog_entry_1.set_relations(entry_of=cubicweb_blog)
+ blog_entry_1.cw_set(entry_of=cubicweb_blog)
blog_entry_2 = req.create_entity('BlogEntry', title=u'yes',
content=u'cubicweb yes')
- blog_entry_2.set_relations(entry_of=cubicweb_blog)
+ blog_entry_2.cw_set(entry_of=cubicweb_blog)
self.assertEqual(len(MAILBOX), 0)
self.commit()
self.assertEqual(len(MAILBOX), 2)
--- a/doc/book/en/devweb/index.rst Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/devweb/index.rst Wed Jan 09 16:06:20 2013 +0100
@@ -10,6 +10,7 @@
publisher
controllers
request
+ searchbar
views/index
rtags
ajax
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/searchbar.rst Wed Jan 09 16:06:20 2013 +0100
@@ -0,0 +1,41 @@
+.. _searchbar:
+
+RQL search bar
+--------------
+
+The RQL search bar is a visual component, hidden by default, the tiny *search*
+input being enough for common use cases.
+
+An autocompletion helper is provided to help you type valid queries, both
+in terms of syntax and in terms of schema validity.
+
+.. autoclass:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder
+
+
+How search is performed
++++++++++++++++++++++++
+
+You can use the *rql search bar* to either type RQL queries, plain text queries
+or standard shortcuts such as *<EntityType>* or *<EntityType> <attrname> <value>*.
+
+Ultimately, all queries are translated to rql since it's the only
+language understood on the server (data) side. To transform the user
+query into RQL, CubicWeb uses the so-called *magicsearch component*,
+defined in :mod:`cubicweb.web.views.magicsearch`, which in turn
+delegates to a number of query preprocessor that are responsible of
+interpreting the user query and generating corresponding RQL.
+
+The code of the main processor loop is easy to understand:
+
+.. sourcecode:: python
+
+ for proc in self.processors:
+ try:
+ return proc.process_query(uquery, req)
+ except (RQLSyntaxError, BadRQLQuery):
+ pass
+
+The idea is simple: for each query processor, try to translate the
+query. If it fails, try with the next processor, if it succeeds,
+we're done and the RQL query will be executed.
+
--- a/doc/book/en/tutorials/advanced/part02_security.rst Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part02_security.rst Wed Jan 09 16:06:20 2013 +0100
@@ -196,7 +196,7 @@
for eid in self.get_data():
entity = self.session.entity_from_eid(eid)
if entity.visibility == 'parent':
- entity.set_attributes(visibility=u'authenticated')
+ entity.cw_set(visibility=u'authenticated')
class SetVisibilityHook(hook.Hook):
__regid__ = 'sytweb.setvisibility'
@@ -215,7 +215,7 @@
parent = self._cw.entity_from_eid(self.eidto)
child = self._cw.entity_from_eid(self.eidfrom)
if child.visibility == 'parent':
- child.set_attributes(visibility=parent.visibility)
+ child.cw_set(visibility=parent.visibility)
Notice:
@@ -344,7 +344,7 @@
self.assertEquals(len(req.execute('Folder X')), 0) # restricted...
# may_be_read_by propagation
self.restore_connection()
- folder.set_relations(may_be_read_by=toto)
+ folder.cw_set(may_be_read_by=toto)
self.commit()
photo1.clear_all_caches()
self.failUnless(photo1.may_be_read_by)
--- a/doc/book/en/tutorials/advanced/part04_ui-base.rst Wed Jan 09 16:04:26 2013 +0100
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst Wed Jan 09 16:06:20 2013 +0100
@@ -294,6 +294,7 @@
folder in which the current file (e.g. `self.entity`) is located.
.. Note::
+
The :class:`IBreadCrumbs` interface is a `breadcrumbs` method, but the default
:class:`IBreadCrumbsAdapter` provides a default implementation for it that will look
at the value returned by its `parent_entity` method. It also provides a
@@ -331,6 +332,7 @@
navigate through the web site to see if everything is ok...
.. Note::
+
In the 'cubicweb-ctl i18ncube' command, `sytweb` refers to the **cube**, while
in the two other, it refers to the **instance** (if you can't see the
difference, reread CubicWeb's concept chapter !).
@@ -363,4 +365,4 @@
.. _`several improvments`: http://www.cubicweb.org/blogentry/1179899
.. _`3.8`: http://www.cubicweb.org/blogentry/917107
.. _`first blog of this series`: http://www.cubicweb.org/blogentry/824642
-.. _`an earlier post`: http://www.cubicweb.org/867464
\ No newline at end of file
+.. _`an earlier post`: http://www.cubicweb.org/867464
--- a/entities/authobjs.py Wed Jan 09 16:04:26 2013 +0100
+++ b/entities/authobjs.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -77,6 +77,19 @@
self._properties = dict((p.pkey, p.value) for p in self.reverse_for_user)
return self._properties
+ def prefered_language(self, language=None):
+ """return language used by this user, if explicitly defined (eg not
+ using http negociation)
+ """
+ language = language or self.property_value('ui.language')
+ vreg = self._cw.vreg
+ try:
+ vreg.config.translations[language]
+ except KeyError:
+ language = vreg.property_value('ui.language')
+ assert language in vreg.config.translations[language], language
+ return language
+
def property_value(self, key):
try:
# properties stored on the user aren't correctly typed
@@ -101,7 +114,7 @@
kwargs['for_user'] = self
self._cw.create_entity('CWProperty', **kwargs)
else:
- prop.set_attributes(value=value)
+ prop.cw_set(value=value)
def matching_groups(self, groups):
"""return the number of the given group(s) in which the user is
--- a/entities/sources.py Wed Jan 09 16:04:26 2013 +0100
+++ b/entities/sources.py Wed Jan 09 16:06:20 2013 +0100
@@ -51,7 +51,7 @@
continue
raise
cfgstr = unicode(generate_source_config(sconfig), self._cw.encoding)
- self.set_attributes(config=cfgstr)
+ self.cw_set(config=cfgstr)
class CWSource(_CWSourceCfgMixIn, AnyEntity):
@@ -181,5 +181,5 @@
def write_log(self, session, **kwargs):
if 'status' not in kwargs:
kwargs['status'] = getattr(self, '_status', u'success')
- self.set_attributes(log=u'<br/>'.join(self._logs), **kwargs)
+ self.cw_set(log=u'<br/>'.join(self._logs), **kwargs)
self._logs = []
--- a/entities/test/unittest_base.py Wed Jan 09 16:04:26 2013 +0100
+++ b/entities/test/unittest_base.py Wed Jan 09 16:06:20 2013 +0100
@@ -70,7 +70,7 @@
email1 = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"').get_entity(0, 0)
email2 = self.execute('INSERT EmailAddress X: X address "maarten@philips.com"').get_entity(0, 0)
email3 = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"').get_entity(0, 0)
- email1.set_relations(prefered_form=email2)
+ email1.cw_set(prefered_form=email2)
self.assertEqual(email1.prefered.eid, email2.eid)
self.assertEqual(email2.prefered.eid, email2.eid)
self.assertEqual(email3.prefered.eid, email3.eid)
@@ -104,10 +104,10 @@
e = self.execute('CWUser U WHERE U login "member"').get_entity(0, 0)
self.assertEqual(e.dc_title(), 'member')
self.assertEqual(e.name(), 'member')
- e.set_attributes(firstname=u'bouah')
+ e.cw_set(firstname=u'bouah')
self.assertEqual(e.dc_title(), 'member')
self.assertEqual(e.name(), u'bouah')
- e.set_attributes(surname=u'lôt')
+ e.cw_set(surname=u'lôt')
self.assertEqual(e.dc_title(), 'member')
self.assertEqual(e.name(), u'bouah lôt')
--- a/entities/test/unittest_wfobjs.py Wed Jan 09 16:04:26 2013 +0100
+++ b/entities/test/unittest_wfobjs.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -20,7 +20,6 @@
from cubicweb import ValidationError
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.server.session import security_enabled
def add_wf(self, etype, name=None, default=False):
@@ -63,7 +62,7 @@
# gnark gnark
bar = wf.add_state(u'bar')
self.commit()
- bar.set_attributes(name=u'foo')
+ bar.cw_set(name=u'foo')
with self.assertRaises(ValidationError) as cm:
self.commit()
self.assertEqual(cm.exception.errors, {'name-subject': 'workflow already have a state of that name'})
@@ -86,7 +85,7 @@
# gnark gnark
biz = wf.add_transition(u'biz', (bar,), foo)
self.commit()
- biz.set_attributes(name=u'baz')
+ biz.cw_set(name=u'baz')
with self.assertRaises(ValidationError) as cm:
self.commit()
self.assertEqual(cm.exception.errors, {'name-subject': 'workflow already have a transition of that name'})
@@ -126,8 +125,9 @@
self.assertEqual(trs[0].destination(None).name, u'deactivated')
# test a std user get no possible transition
cnx = self.login('member')
+ req = self.request()
# fetch the entity using the new session
- trs = list(cnx.user().cw_adapt_to('IWorkflowable').possible_transitions())
+ trs = list(req.user.cw_adapt_to('IWorkflowable').possible_transitions())
self.assertEqual(len(trs), 0)
cnx.close()
@@ -154,7 +154,7 @@
wf = add_wf(self, 'CWUser')
s = wf.add_state(u'foo', initial=True)
self.commit()
- with security_enabled(self.session, write=False):
+ with self.session.security_enabled(write=False):
with self.assertRaises(ValidationError) as cm:
self.session.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
{'x': self.user().eid, 's': s.eid})
@@ -173,7 +173,7 @@
def test_goback_transition(self):
req = self.request()
- wf = self.session.user.cw_adapt_to('IWorkflowable').current_workflow
+ wf = req.user.cw_adapt_to('IWorkflowable').current_workflow
asleep = wf.add_state('asleep')
wf.add_transition('rest', (wf.state_by_name('activated'),
wf.state_by_name('deactivated')),
@@ -212,7 +212,7 @@
req = self.request()
iworkflowable = req.entity_from_eid(self.member.eid).cw_adapt_to('IWorkflowable')
iworkflowable.fire_transition('deactivate')
- cnx.commit()
+ req.cu.commit()
with self.assertRaises(ValidationError) as cm:
iworkflowable.fire_transition('activate')
self.assertEqual(cm.exception.errors, {'by_transition-subject': "transition may not be fired"})
@@ -516,7 +516,7 @@
['rest'])
self.assertEqual(parse_hist(iworkflowable.workflow_history),
[('asleep', 'asleep', 'rest', None)])
- user.set_attributes(surname=u'toto') # fulfill condition
+ user.cw_set(surname=u'toto') # fulfill condition
self.commit()
iworkflowable.fire_transition('rest')
self.commit()
@@ -556,13 +556,12 @@
def setUp(self):
CubicWebTC.setUp(self)
- self.wf = self.session.user.cw_adapt_to('IWorkflowable').current_workflow
- self.session.set_cnxset()
+ req = self.request()
+ self.wf = req.user.cw_adapt_to('IWorkflowable').current_workflow
self.s_activated = self.wf.state_by_name('activated').eid
self.s_deactivated = self.wf.state_by_name('deactivated').eid
self.s_dummy = self.wf.add_state(u'dummy').eid
self.wf.add_transition(u'dummy', (self.s_deactivated,), self.s_dummy)
- req = self.request()
ueid = self.create_user(req, 'stduser', commit=False).eid
# test initial state is set
rset = self.execute('Any N WHERE S name N, X in_state S, X eid %(x)s',
@@ -623,23 +622,22 @@
cnx.close()
def test_transition_checking3(self):
- cnx = self.login('stduser')
- session = self.session
- user = cnx.user(session)
- iworkflowable = user.cw_adapt_to('IWorkflowable')
- iworkflowable.fire_transition('deactivate')
- cnx.commit()
- session.set_cnxset()
- with self.assertRaises(ValidationError) as cm:
+ with self.login('stduser') as cnx:
+ session = self.session
+ user = cnx.user(session)
+ iworkflowable = user.cw_adapt_to('IWorkflowable')
iworkflowable.fire_transition('deactivate')
- self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
- u"transition isn't allowed from")
- cnx.rollback()
- session.set_cnxset()
- # get back now
- iworkflowable.fire_transition('activate')
- cnx.commit()
- cnx.close()
+ session.commit()
+ session.set_cnxset()
+ with self.assertRaises(ValidationError) as cm:
+ iworkflowable.fire_transition('deactivate')
+ self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
+ u"transition isn't allowed from")
+ session.rollback()
+ session.set_cnxset()
+ # get back now
+ iworkflowable.fire_transition('activate')
+ session.commit()
if __name__ == '__main__':
--- a/entity.py Wed Jan 09 16:04:26 2013 +0100
+++ b/entity.py Wed Jan 09 16:06:20 2013 +0100
@@ -452,26 +452,13 @@
return mainattr, needcheck
@classmethod
- def cw_instantiate(cls, execute, **kwargs):
- """add a new entity of this given type
-
- Example (in a shell session):
-
- >>> companycls = vreg['etypes'].etype_class(('Company')
- >>> personcls = vreg['etypes'].etype_class(('Person')
- >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
- >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
- ... works_for=c)
-
- You can also set relation where the entity has 'object' role by
- prefixing the relation by 'reverse_'.
- """
- rql = 'INSERT %s X' % cls.__regid__
+ def _cw_build_entity_query(cls, kwargs):
relations = []
restrictions = set()
- pending_relations = []
+ pendingrels = []
eschema = cls.e_schema
qargs = {}
+ attrcache = {}
for attr, value in kwargs.items():
if attr.startswith('reverse_'):
attr = attr[len('reverse_'):]
@@ -487,10 +474,13 @@
value = iter(value).next()
else:
# prepare IN clause
- pending_relations.append( (attr, role, value) )
+ pendingrels.append( (attr, role, value) )
continue
if rschema.final: # attribute
relations.append('X %s %%(%s)s' % (attr, attr))
+ attrcache[attr] = value
+ elif value is None:
+ pendingrels.append( (attr, role, value) )
else:
rvar = attr.upper()
if role == 'object':
@@ -503,19 +493,51 @@
if hasattr(value, 'eid'):
value = value.eid
qargs[attr] = value
+ rql = u''
if relations:
- rql = '%s: %s' % (rql, ', '.join(relations))
+ rql += ', '.join(relations)
if restrictions:
- rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
- created = execute(rql, qargs).get_entity(0, 0)
- for attr, role, values in pending_relations:
+ rql += ' WHERE %s' % ', '.join(restrictions)
+ return rql, qargs, pendingrels, attrcache
+
+ @classmethod
+ def _cw_handle_pending_relations(cls, eid, pendingrels, execute):
+ for attr, role, values in pendingrels:
if role == 'object':
restr = 'Y %s X' % attr
else:
restr = 'X %s Y' % attr
+ if values is None:
+ execute('DELETE %s WHERE X eid %%(x)s' % restr, {'x': eid})
+ continue
execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
restr, ','.join(str(getattr(r, 'eid', r)) for r in values)),
- {'x': created.eid}, build_descr=False)
+ {'x': eid}, build_descr=False)
+
+ @classmethod
+ def cw_instantiate(cls, execute, **kwargs):
+ """add a new entity of this given type
+
+ Example (in a shell session):
+
+ >>> companycls = vreg['etypes'].etype_class(('Company')
+ >>> personcls = vreg['etypes'].etype_class(('Person')
+ >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
+ >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
+ ... works_for=c)
+
+ You can also set relations where the entity has 'object' role by
+ prefixing the relation name by 'reverse_'. Also, relation values may be
+ an entity or eid, a list of entities or eids.
+ """
+ rql, qargs, pendingrels, attrcache = cls._cw_build_entity_query(kwargs)
+ if rql:
+ rql = 'INSERT %s X: %s' % (cls.__regid__, rql)
+ else:
+ rql = 'INSERT %s X' % (cls.__regid__)
+ created = execute(rql, qargs).get_entity(0, 0)
+ created._cw_update_attr_cache(attrcache)
+ cls._cw_handle_pending_relations(created.eid, pendingrels, execute)
return created
def __init__(self, req, rset=None, row=None, col=0):
@@ -535,6 +557,24 @@
def __cmp__(self, other):
raise NotImplementedError('comparison not implemented for %s' % self.__class__)
+ def _cw_update_attr_cache(self, attrcache):
+ # if context is a repository session, don't consider dont-cache-attrs as
+ # the instance already hold modified values and loosing them could
+ # introduce severe problems
+ if self._cw.is_request:
+ for attr in self._cw.get_shared_data('%s.dont-cache-attrs' % self.eid,
+ default=(), txdata=True, pop=True):
+ attrcache.pop(attr, None)
+ self.cw_attr_cache.pop(attr, None)
+ self.cw_attr_cache.update(attrcache)
+
+ def _cw_dont_cache_attribute(self, attr):
+ """repository side method called when some attribute have been
+ transformed by a hook, hence original value should not be cached by
+ client
+ """
+ self._cw.transaction_data.setdefault('%s.dont-cache-attrs' % self.eid, set()).add(attr)
+
def __json_encode__(self):
"""custom json dumps hook to dump the entity's eid
which is not part of dict structure itself
@@ -1215,54 +1255,41 @@
# raw edition utilities ###################################################
- def set_attributes(self, **kwargs): # XXX cw_set_attributes
+ def cw_set(self, **kwargs):
+ """update this entity using given attributes / relation, working in the
+ same fashion as :meth:`cw_instantiate`.
+
+ Example (in a shell session):
+
+ >>> c = rql('Any X WHERE X is Company').get_entity(0, 0)
+ >>> p = rql('Any X WHERE X is Person').get_entity(0, 0)
+ >>> c.set(name=u'Logilab')
+ >>> p.set(firstname=u'John', lastname=u'Doe', works_for=c)
+
+ You can also set relations where the entity has 'object' role by
+ prefixing the relation name by 'reverse_'. Also, relation values may be
+ an entity or eid, a list of entities or eids, or None (meaning that all
+ relations of the given type from or to this object should be deleted).
+ """
_check_cw_unsafe(kwargs)
assert kwargs
assert self.cw_is_saved(), "should not call set_attributes while entity "\
"hasn't been saved yet"
- relations = ['X %s %%(%s)s' % (key, key) for key in kwargs]
- # and now update the database
- kwargs['x'] = self.eid
- self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
- kwargs)
- kwargs.pop('x')
+ rql, qargs, pendingrels, attrcache = self._cw_build_entity_query(kwargs)
+ if rql:
+ rql = 'SET ' + rql
+ qargs['x'] = self.eid
+ if ' WHERE ' in rql:
+ rql += ', X eid %(x)s'
+ else:
+ rql += ' WHERE X eid %(x)s'
+ self._cw.execute(rql, qargs)
# update current local object _after_ the rql query to avoid
# interferences between the query execution itself and the cw_edited /
# skip_security machinery
- self.cw_attr_cache.update(kwargs)
-
- def set_relations(self, **kwargs): # XXX cw_set_relations
- """add relations to the given object. To set a relation where this entity
- is the object of the relation, use 'reverse_'<relation> as argument name.
-
- Values may be an entity or eid, a list of entities or eids, or None
- (meaning that all relations of the given type from or to this object
- should be deleted).
- """
- # XXX update cache
- _check_cw_unsafe(kwargs)
- for attr, values in kwargs.iteritems():
- if attr.startswith('reverse_'):
- restr = 'Y %s X' % attr[len('reverse_'):]
- else:
- restr = 'X %s Y' % attr
- if values is None:
- self._cw.execute('DELETE %s WHERE X eid %%(x)s' % restr,
- {'x': self.eid})
- continue
- if not isinstance(values, (tuple, list, set, frozenset)):
- values = (values,)
- eids = []
- for val in values:
- try:
- eids.append(str(val.eid))
- except AttributeError:
- try:
- eids.append(str(typed_eid(val)))
- except (ValueError, TypeError):
- raise Exception('expected an Entity or eid, got %s' % val)
- self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
- restr, ','.join(eids)), {'x': self.eid})
+ self._cw_update_attr_cache(attrcache)
+ self._cw_handle_pending_relations(self.eid, pendingrels, self._cw.execute)
+ # XXX update relation cache
def cw_delete(self, **kwargs):
assert self.has_eid(), self.eid
@@ -1277,6 +1304,21 @@
# deprecated stuff #########################################################
+ @deprecated('[3.16] use cw_set() instead')
+ def set_attributes(self, **kwargs): # XXX cw_set_attributes
+ self.cw_set(**kwargs)
+
+ @deprecated('[3.16] use cw_set() instead')
+ def set_relations(self, **kwargs): # XXX cw_set_relations
+ """add relations to the given object. To set a relation where this entity
+ is the object of the relation, use 'reverse_'<relation> as argument name.
+
+ Values may be an entity or eid, a list of entities or eids, or None
+ (meaning that all relations of the given type from or to this object
+ should be deleted).
+ """
+ self.cw_set(**kwargs)
+
@deprecated('[3.13] use entity.cw_clear_all_caches()')
def clear_all_caches(self):
return self.cw_clear_all_caches()
--- a/ext/tal.py Wed Jan 09 16:04:26 2013 +0100
+++ b/ext/tal.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -261,7 +261,7 @@
return wrapped
def _compiled_template(self, instance):
- for fileordirectory in instance.config.vregistry_path():
+ for fileordirectory in instance.config.appobjects_path():
filepath = join(fileordirectory, self.filename)
if isdir(fileordirectory) and exists(filepath):
return compile_template_file(filepath)
--- a/hooks/bookmark.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/bookmark.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/hooks/email.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/email.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -15,9 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""hooks to ensure use_email / primary_email relations consistency
+"""hooks to ensure use_email / primary_email relations consistency"""
-"""
__docformat__ = "restructuredtext en"
from cubicweb.server import hook
--- a/hooks/integrity.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/integrity.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -20,12 +20,11 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from threading import Lock
-from yams.schema import role_name
-
-from cubicweb import ValidationError
+from cubicweb import validation_error
from cubicweb.schema import (META_RTYPES, WORKFLOW_RTYPES,
RQLConstraint, RQLUniqueConstraint)
from cubicweb.predicates import is_instance
@@ -87,11 +86,11 @@
continue
if not session.execute(self.base_rql % rtype, {'x': eid}):
etype = session.describe(eid)[0]
- _ = session._
msg = _('at least one relation %(rtype)s is required on '
'%(etype)s (%(eid)s)')
- msg %= {'rtype': _(rtype), 'etype': _(etype), 'eid': eid}
- raise ValidationError(eid, {role_name(rtype, self.role): msg})
+ raise validation_error(eid, {(rtype, self.role): msg},
+ {'rtype': rtype, 'etype': etype, 'eid': eid},
+ ['rtype', 'etype'])
class _CheckSRelationOp(_CheckRequiredRelationOperation):
@@ -231,9 +230,9 @@
rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
rset = self._cw.execute(rql, {'val': val})
if rset and rset[0][0] != entity.eid:
- msg = self._cw._('the value "%s" is already used, use another one')
- qname = role_name(attr, 'subject')
- raise ValidationError(entity.eid, {qname: msg % val})
+ msg = _('the value "%s" is already used, use another one')
+ raise validation_error(entity, {(attr, 'subject'): msg},
+ (val,))
class DontRemoveOwnersGroupHook(IntegrityHook):
@@ -246,15 +245,12 @@
def __call__(self):
entity = self.entity
if self.event == 'before_delete_entity' and entity.name == 'owners':
- msg = self._cw._('can\'t be deleted')
- raise ValidationError(entity.eid, {None: msg})
+ raise validation_error(entity, {None: _("can't be deleted")})
elif self.event == 'before_update_entity' \
and 'name' in entity.cw_edited:
oldname, newname = entity.cw_edited.oldnewvalue('name')
if oldname == 'owners' and newname != oldname:
- qname = role_name('name', 'subject')
- msg = self._cw._('can\'t be changed')
- raise ValidationError(entity.eid, {qname: msg})
+ raise validation_error(entity, {('name', 'subject'): _("can't be changed")})
class TidyHtmlFields(IntegrityHook):
--- a/hooks/metadata.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/metadata.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/hooks/syncschema.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/syncschema.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -24,6 +24,7 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from copy import copy
from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
@@ -31,7 +32,7 @@
from logilab.common.decorators import clear_cache
-from cubicweb import ValidationError
+from cubicweb import validation_error
from cubicweb.predicates import is_instance
from cubicweb.schema import (SCHEMA_TYPES, META_RTYPES, VIRTUAL_RTYPES,
CONSTRAINTS, ETYPE_NAME_MAP, display_name)
@@ -127,10 +128,9 @@
if attr in ro_attrs:
origval, newval = entity.cw_edited.oldnewvalue(attr)
if newval != origval:
- errors[attr] = session._("can't change the %s attribute") % \
- display_name(session, attr)
+ errors[attr] = _("can't change this attribute")
if errors:
- raise ValidationError(entity.eid, errors)
+ raise validation_error(entity, errors)
class _MockEntity(object): # XXX use a named tuple with python 2.6
@@ -913,7 +913,7 @@
# final entities can't be deleted, don't care about that
name = self.entity.name
if name in CORE_TYPES:
- raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+ raise validation_error(self.entity, {None: _("can't be deleted")})
# delete every entities of this type
if name not in ETYPE_NAME_MAP:
self._cw.execute('DELETE %s X' % name)
@@ -983,7 +983,7 @@
def __call__(self):
name = self.entity.name
if name in CORE_TYPES:
- raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+ raise validation_error(self.entity, {None: _("can't be deleted")})
# delete relation definitions using this relation type
self._cw.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',
{'x': self.entity.eid})
--- a/hooks/syncsession.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/syncsession.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -18,9 +18,9 @@
"""Core hooks: synchronize living session on persistent data changes"""
__docformat__ = "restructuredtext en"
+_ = unicode
-from yams.schema import role_name
-from cubicweb import UnknownProperty, ValidationError, BadConnectionId
+from cubicweb import UnknownProperty, BadConnectionId, validation_error
from cubicweb.predicates import is_instance
from cubicweb.server import hook
@@ -165,13 +165,11 @@
try:
value = session.vreg.typed_value(key, value)
except UnknownProperty:
- qname = role_name('pkey', 'subject')
- msg = session._('unknown property key %s') % key
- raise ValidationError(self.entity.eid, {qname: msg})
+ msg = _('unknown property key %s')
+ raise validation_error(self.entity, {('pkey', 'subject'): msg}, (key,))
except ValueError, ex:
- qname = role_name('value', 'subject')
- raise ValidationError(self.entity.eid,
- {qname: session._(str(ex))})
+ raise validation_error(self.entity,
+ {('value', 'subject'): str(ex)})
if not session.user.matching_groups('managers'):
session.add_relation(self.entity.eid, 'for_user', session.user.eid)
else:
@@ -196,8 +194,7 @@
except UnknownProperty:
return
except ValueError, ex:
- qname = role_name('value', 'subject')
- raise ValidationError(entity.eid, {qname: session._(str(ex))})
+ raise validation_error(entity, {('value', 'subject'): str(ex)})
if entity.for_user:
for session_ in get_user_sessions(session.repo, entity.for_user[0].eid):
_ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
@@ -237,10 +234,8 @@
key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
{'x': eidfrom})[0]
if session.vreg.property_info(key)['sitewide']:
- qname = role_name('for_user', 'subject')
- msg = session._("site-wide property can't be set for user")
- raise ValidationError(eidfrom,
- {qname: msg})
+ msg = _("site-wide property can't be set for user")
+ raise validation_error(eidfrom, {('for_user', 'subject'): msg})
for session_ in get_user_sessions(session.repo, self.eidto):
_ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
key=key, value=value)
--- a/hooks/syncsources.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/syncsources.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2010-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -17,12 +17,13 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""hooks for repository sources synchronization"""
+_ = unicode
+
from socket import gethostname
from logilab.common.decorators import clear_cache
-from yams.schema import role_name
-from cubicweb import ValidationError
+from cubicweb import validation_error
from cubicweb.predicates import is_instance
from cubicweb.server import SOURCE_TYPES, hook
@@ -46,12 +47,15 @@
try:
sourcecls = SOURCE_TYPES[self.entity.type]
except KeyError:
- msg = self._cw._('unknown source type')
- raise ValidationError(self.entity.eid,
- {role_name('type', 'subject'): msg})
- sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
- fail_if_unknown=not self._cw.vreg.config.repairing)
- SourceAddedOp(self._cw, entity=self.entity)
+ msg = _('Unknown source type')
+ raise validation_error(self.entity, {('type', 'subject'): msg})
+ # ignore creation of the system source done during database
+ # initialisation, as config for this source is in a file and handling
+ # is done separatly (no need for the operation either)
+ if self.entity.name != 'system':
+ sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
+ fail_if_unknown=not self._cw.vreg.config.repairing)
+ SourceAddedOp(self._cw, entity=self.entity)
class SourceRemovedOp(hook.Operation):
@@ -65,7 +69,8 @@
events = ('before_delete_entity',)
def __call__(self):
if self.entity.name == 'system':
- raise ValidationError(self.entity.eid, {None: 'cant remove system source'})
+ msg = _("You cannot remove the system source")
+ raise validation_error(self.entity, {None: msg})
SourceRemovedOp(self._cw, uri=self.entity.name)
@@ -116,11 +121,18 @@
__select__ = SourceHook.__select__ & is_instance('CWSource')
events = ('before_update_entity',)
def __call__(self):
- if 'config' in self.entity.cw_edited:
- SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
if 'name' in self.entity.cw_edited:
oldname, newname = self.entity.cw_edited.oldnewvalue('name')
+ if oldname == 'system':
+ msg = _("You cannot rename the system source")
+ raise validation_error(self.entity, {('name', 'subject'): msg})
SourceRenamedOp(self._cw, oldname=oldname, newname=newname)
+ if 'config' in self.entity.cw_edited:
+ if self.entity.name == 'system' and self.entity.config:
+ msg = _("Configuration of the system source goes to "
+ "the 'sources' file, not in the database")
+ raise validation_error(self.entity, {('config', 'subject'): msg})
+ SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
class SourceHostConfigUpdatedHook(SourceHook):
@@ -154,8 +166,8 @@
events = ('before_add_relation',)
def __call__(self):
if not self._cw.added_in_transaction(self.eidfrom):
- msg = self._cw._("can't change this relation")
- raise ValidationError(self.eidfrom, {self.rtype: msg})
+ msg = _("You can't change this relation")
+ raise validation_error(self.eidfrom, {self.rtype: msg})
class SourceMappingChangedOp(hook.DataOperationMixIn, hook.Operation):
--- a/hooks/test/unittest_hooks.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/test/unittest_hooks.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -67,10 +67,9 @@
entity = self.request().create_entity('Workflow', name=u'wf1',
description_format=u'text/html',
description=u'yo')
- entity.set_attributes(name=u'wf2')
+ entity.cw_set(name=u'wf2')
self.assertEqual(entity.description, u'yo')
- entity.set_attributes(description=u'R&D<p>yo')
- entity.cw_attr_cache.pop('description')
+ entity.cw_set(description=u'R&D<p>yo')
self.assertEqual(entity.description, u'R&D<p>yo</p>')
def test_metadata_cwuri(self):
@@ -166,13 +165,12 @@
self.execute, 'INSERT CWRType X: X name "in_group"')
def test_validation_unique_constraint(self):
- self.assertRaises(ValidationError,
- self.execute, 'INSERT CWUser X: X login "admin"')
- try:
+ with self.assertRaises(ValidationError) as cm:
self.execute('INSERT CWUser X: X login "admin"')
- except ValidationError, ex:
- self.assertIsInstance(ex.entity, int)
- self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
+ ex = cm.exception
+ ex.translate(unicode)
+ self.assertIsInstance(ex.entity, int)
+ self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
if __name__ == '__main__':
--- a/hooks/test/unittest_syncschema.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/test/unittest_syncschema.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -294,7 +294,7 @@
def test_change_fulltext_container(self):
req = self.request()
target = req.create_entity(u'EmailAddress', address=u'rick.roll@dance.com')
- target.set_relations(reverse_use_email=req.user)
+ target.cw_set(reverse_use_email=req.user)
self.commit()
rset = req.execute('Any X WHERE X has_text "rick.roll"')
self.assertIn(req.user.eid, [item[0] for item in rset])
--- a/hooks/test/unittest_syncsession.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/test/unittest_syncsession.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -31,9 +31,11 @@
def test_unexistant_cwproperty(self):
with self.assertRaises(ValidationError) as cm:
self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
with self.assertRaises(ValidationError) as cm:
self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
def test_site_wide_cwproperty(self):
--- a/hooks/workflow.py Wed Jan 09 16:04:26 2013 +0100
+++ b/hooks/workflow.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -18,12 +18,12 @@
"""Core hooks: workflow related hooks"""
__docformat__ = "restructuredtext en"
+_ = unicode
from datetime import datetime
-from yams.schema import role_name
-from cubicweb import RepositoryError, ValidationError
+from cubicweb import RepositoryError, validation_error
from cubicweb.predicates import is_instance, adaptable
from cubicweb.server import hook
@@ -92,9 +92,8 @@
if mainwf.eid == self.wfeid:
deststate = mainwf.initial
if not deststate:
- qname = role_name('custom_workflow', 'subject')
- msg = session._('workflow has no initial state')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('workflow has no initial state')
+ raise validation_error(entity, {('custom_workflow', 'subject'): msg})
if mainwf.state_by_eid(iworkflowable.current_state.eid):
# nothing to do
return
@@ -119,9 +118,8 @@
outputs = set()
for ep in tr.subworkflow_exit:
if ep.subwf_state.eid in outputs:
- qname = role_name('subworkflow_exit', 'subject')
- msg = self.session._("can't have multiple exits on the same state")
- raise ValidationError(self.treid, {qname: msg})
+ msg = _("can't have multiple exits on the same state")
+ raise validation_error(self.treid, {('subworkflow_exit', 'subject'): msg})
outputs.add(ep.subwf_state.eid)
@@ -137,13 +135,12 @@
wftr = iworkflowable.subworkflow_input_transition()
if wftr is None:
# inconsistency detected
- qname = role_name('to_state', 'subject')
- msg = session._("state doesn't belong to entity's current workflow")
- raise ValidationError(self.trinfo.eid, {'to_state': msg})
+ msg = _("state doesn't belong to entity's current workflow")
+ raise validation_error(self.trinfo, {('to_state', 'subject'): msg})
tostate = wftr.get_exit_point(forentity, trinfo.cw_attr_cache['to_state'])
if tostate is not None:
# reached an exit point
- msg = session._('exiting from subworkflow %s')
+ msg = _('exiting from subworkflow %s')
msg %= session._(iworkflowable.current_workflow.name)
session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
iworkflowable.change_state(tostate, msg, u'text/plain', tr=wftr)
@@ -186,9 +183,8 @@
try:
foreid = entity.cw_attr_cache['wf_info_for']
except KeyError:
- qname = role_name('wf_info_for', 'subject')
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('mandatory relation')
+ raise validation_error(entity, {('wf_info_for', 'subject'): msg})
forentity = session.entity_from_eid(foreid)
# see comment in the TrInfo entity definition
entity.cw_edited['tr_count']=len(forentity.reverse_wf_info_for)
@@ -201,13 +197,13 @@
else:
wf = iworkflowable.current_workflow
if wf is None:
- msg = session._('related entity has no workflow set')
- raise ValidationError(entity.eid, {None: msg})
+ msg = _('related entity has no workflow set')
+ raise validation_error(entity, {None: msg})
# then check it has a state set
fromstate = iworkflowable.current_state
if fromstate is None:
- msg = session._('related entity has no state')
- raise ValidationError(entity.eid, {None: msg})
+ msg = _('related entity has no state')
+ raise validation_error(entity, {None: msg})
# True if we are coming back from subworkflow
swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
cowpowers = (session.user.is_in_group('managers')
@@ -219,47 +215,42 @@
# no transition set, check user is a manager and destination state
# is specified (and valid)
if not cowpowers:
- qname = role_name('by_transition', 'subject')
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('mandatory relation')
+ raise validation_error(entity, {('by_transition', 'subject'): msg})
deststateeid = entity.cw_attr_cache.get('to_state')
if not deststateeid:
- qname = role_name('by_transition', 'subject')
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _('mandatory relation')
+ raise validation_error(entity, {('by_transition', 'subject'): msg})
deststate = wf.state_by_eid(deststateeid)
if deststate is None:
- qname = role_name('to_state', 'subject')
- msg = session._("state doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("state doesn't belong to entity's workflow")
+ raise validation_error(entity, {('to_state', 'subject'): msg})
else:
# check transition is valid and allowed, unless we're coming back
# from subworkflow
tr = session.entity_from_eid(treid)
if swtr is None:
- qname = role_name('by_transition', 'subject')
+ qname = ('by_transition', 'subject')
if tr is None:
- msg = session._("transition doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition doesn't belong to entity's workflow")
+ raise validation_error(entity, {qname: msg})
if not tr.has_input_state(fromstate):
- msg = session._("transition %(tr)s isn't allowed from %(st)s") % {
- 'tr': session._(tr.name), 'st': session._(fromstate.name)}
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition %(tr)s isn't allowed from %(st)s")
+ raise validation_error(entity, {qname: msg}, {
+ 'tr': tr.name, 'st': fromstate.name}, ['tr', 'st'])
if not tr.may_be_fired(foreid):
- msg = session._("transition may not be fired")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition may not be fired")
+ raise validation_error(entity, {qname: msg})
deststateeid = entity.cw_attr_cache.get('to_state')
if deststateeid is not None:
if not cowpowers and deststateeid != tr.destination(forentity).eid:
- qname = role_name('by_transition', 'subject')
- msg = session._("transition isn't allowed")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("transition isn't allowed")
+ raise validation_error(entity, {('by_transition', 'subject'): msg})
if swtr is None:
deststate = session.entity_from_eid(deststateeid)
if not cowpowers and deststate is None:
- qname = role_name('to_state', 'subject')
- msg = session._("state doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {qname: msg})
+ msg = _("state doesn't belong to entity's workflow")
+ raise validation_error(entity, {('to_state', 'subject'): msg})
else:
deststateeid = tr.destination(forentity).eid
# everything is ok, add missing information on the trinfo entity
@@ -307,20 +298,18 @@
iworkflowable = entity.cw_adapt_to('IWorkflowable')
mainwf = iworkflowable.main_workflow
if mainwf is None:
- msg = session._('entity has no workflow set')
- raise ValidationError(entity.eid, {None: msg})
+ msg = _('entity has no workflow set')
+ raise validation_error(entity, {None: msg})
for wf in mainwf.iter_workflows():
if wf.state_by_eid(self.eidto):
break
else:
- qname = role_name('in_state', 'subject')
- msg = session._("state doesn't belong to entity's workflow. You may "
- "want to set a custom workflow for this entity first.")
- raise ValidationError(self.eidfrom, {qname: msg})
+ msg = _("state doesn't belong to entity's workflow. You may "
+ "want to set a custom workflow for this entity first.")
+ raise validation_error(self.eidfrom, {('in_state', 'subject'): msg})
if iworkflowable.current_workflow and wf.eid != iworkflowable.current_workflow.eid:
- qname = role_name('in_state', 'subject')
- msg = session._("state doesn't belong to entity's current workflow")
- raise ValidationError(self.eidfrom, {qname: msg})
+ msg = _("state doesn't belong to entity's current workflow")
+ raise validation_error(self.eidfrom, {('in_state', 'subject'): msg})
class SetModificationDateOnStateChange(WorkflowHook):
@@ -335,7 +324,7 @@
return
entity = self._cw.entity_from_eid(self.eidfrom)
try:
- entity.set_attributes(modification_date=datetime.now())
+ entity.cw_set(modification_date=datetime.now())
except RepositoryError, ex:
# usually occurs if entity is coming from a read-only source
# (eg ldap user)
--- a/i18n.py Wed Jan 09 16:04:26 2013 +0100
+++ b/i18n.py Wed Jan 09 16:06:20 2013 +0100
@@ -54,19 +54,16 @@
w('msgid "%s"\n' % msgid[0])
w('msgstr ""\n\n')
-
-def execute(cmd):
- """display the command, execute it and raise an Exception if returned
- status != 0
- """
- from subprocess import call
- # use getcwdu as cmd may be unicode and cwd may contains non-ascii
- # characters
- print cmd.replace(os.getcwdu() + os.sep, '')
- status = call(cmd, shell=True)
- if status != 0:
- raise Exception('status = %s' % status)
-
+def execute2(args):
+ # XXX replace this with check_output in Python 2.7
+ from subprocess import Popen, PIPE, CalledProcessError
+ p = Popen(args, stdout=PIPE, stderr=PIPE)
+ out, err = p.communicate()
+ if p.returncode != 0:
+ exc = CalledProcessError(p.returncode, args[0])
+ exc.cmd = args
+ exc.data = (out, err)
+ raise exc
def available_catalogs(i18ndir=None):
if i18ndir is None:
@@ -81,6 +78,7 @@
def compile_i18n_catalogs(sourcedirs, destdir, langs):
"""generate .mo files for a set of languages into the `destdir` i18n directory
"""
+ from subprocess import CalledProcessError
from logilab.common.fileutils import ensure_fs_mode
print '-> compiling message catalogs to %s' % destdir
errors = []
@@ -93,17 +91,21 @@
mergedpo = join(destdir, '%s_merged.po' % lang)
try:
# merge instance/cubes messages catalogs with the stdlib's one
- execute('msgcat --use-first --sort-output --strict -o "%s" %s'
- % (mergedpo, ' '.join('"%s"' % f for f in pofiles)))
+ cmd = ['msgcat', '--use-first', '--sort-output', '--strict',
+ '-o', mergedpo] + pofiles
+ execute2(cmd)
# make sure the .mo file is writeable and compiles with *msgfmt*
applmo = join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
try:
ensure_fs_mode(applmo)
except OSError:
pass # suppose not exists
- execute('msgfmt "%s" -o "%s"' % (mergedpo, applmo))
- except Exception, ex:
- errors.append('while handling language %s: %s' % (lang, ex))
+ execute2(['msgfmt', mergedpo, '-o', applmo])
+ except CalledProcessError, exc:
+ errors.append(u'while handling language %s:\ncmd:\n%s\nstdout:\n%s\nstderr:\n%s\n' %
+ (lang, exc.cmd, repr(exc.data[0]), repr(exc.data[1])))
+ except Exception, exc:
+ errors.append(u'while handling language %s: %s' % (lang, exc))
try:
# clean everything
os.unlink(mergedpo)
--- a/i18n/de.po Wed Jan 09 16:04:26 2013 +0100
+++ b/i18n/de.po Wed Jan 09 16:06:20 2013 +0100
@@ -50,6 +50,14 @@
msgstr ""
#, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
msgid "%(attr)s set to %(newvalue)s"
msgstr "%(attr)s geändert in %(newvalue)s"
@@ -58,10 +66,6 @@
msgstr "%(attr)s geändert von %(oldvalue)s in %(newvalue)s"
#, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "%(cstr)s Einschränkung verletzt für Wert %(value)r"
-
-#, python-format
msgid "%(etype)s by %(author)s"
msgstr ""
@@ -74,10 +78,6 @@
msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
#, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r entspricht nicht dem regulären Ausdruck %(regexp)r"
-
-#, python-format
msgid "%d days"
msgstr "%d Tage"
@@ -427,6 +427,11 @@
msgid "Click to sort on this column"
msgstr ""
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
#, python-format
msgid "Created %(etype)s : %(entity)s"
msgstr ""
@@ -902,6 +907,9 @@
msgid "UniqueConstraint"
msgstr "eindeutige Einschränkung"
+msgid "Unknown source type"
+msgstr ""
+
msgid "Unreachable objects"
msgstr "unzugängliche Objekte"
@@ -960,6 +968,15 @@
msgid "You can use any of the following substitutions in your text"
msgstr "Sie können die folgenden Ersetzungen in Ihrem Text verwenden:"
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
msgid ""
"You have no access to this view or it can not be used to display the current "
"data."
@@ -1006,9 +1023,6 @@
msgid "abstract base class for transitions"
msgstr "abstrakte Basisklasse für Übergänge"
-msgid "action menu"
-msgstr ""
-
msgid "action(s) on this selection"
msgstr "Aktionen(en) bei dieser Auswahl"
@@ -1392,11 +1406,7 @@
msgid "can't be deleted"
msgstr "kann nicht entfernt werden"
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "Kann das Attribut %s nicht ändern."
-
-msgid "can't change this relation"
+msgid "can't change this attribute"
msgstr ""
#, python-format
@@ -2569,6 +2579,9 @@
msgid "foaf"
msgstr "FOAF"
+msgid "focus on this selection"
+msgstr ""
+
msgid "follow"
msgstr "dem Link folgen"
@@ -2859,8 +2872,8 @@
msgstr "Unzulässiger Wert für Überschrift"
#, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "Wert %(value)s ungültig für den Typ \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr ""
msgid "index this attribute's value in the plain text index"
msgstr "indizieren des Wertes dieses Attributs im Volltext-Index"
@@ -2938,8 +2951,8 @@
msgstr "Ungültige Aktion %r"
#, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "Wert %(value)s ungültig, er muss zwischen %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
msgid "is"
msgstr "vom Typ"
@@ -4174,6 +4187,9 @@
msgid "toggle check boxes"
msgstr "Kontrollkästchen umkehren"
+msgid "toggle filter"
+msgstr "filter verbergen/zeigen"
+
msgid "tr_count"
msgstr ""
@@ -4316,9 +4332,6 @@
msgid "unknown property key %s"
msgstr "Unbekannter Eigentumsschlüssel %s"
-msgid "unknown source type"
-msgstr ""
-
msgid "unknown vocabulary:"
msgstr "Unbekanntes Wörterbuch : "
@@ -4464,15 +4477,7 @@
msgstr "Wert"
#, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
msgstr ""
msgid "value associated to this key is not editable manually"
@@ -4481,11 +4486,11 @@
"werden."
#, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
msgstr ""
#, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
msgstr ""
msgid "vcard"
@@ -4630,6 +4635,12 @@
msgid "you should un-inline relation %s which is supported and may be crossed "
msgstr ""
+#~ msgid "%(cstr)s constraint failed for value %(value)r"
+#~ msgstr "%(cstr)s Einschränkung verletzt für Wert %(value)r"
+
+#~ msgid "%(value)r doesn't match the %(regexp)r regular expression"
+#~ msgstr "%(value)r entspricht nicht dem regulären Ausdruck %(regexp)r"
+
#~ msgid ""
#~ "Can't restore relation %(rtype)s of entity %(eid)s, this relation does "
#~ "not exists anymore in the schema."
@@ -4637,8 +4648,11 @@
#~ "Kann die Relation %(rtype)s der Entität %(eid)s nicht wieder herstellen, "
#~ "diese Relation existiert nicht mehr in dem Schema."
-#~ msgid "log out first"
-#~ msgstr "Melden Sie sich zuerst ab."
-
-#~ msgid "week"
-#~ msgstr "Woche"
+#~ msgid "can't change the %s attribute"
+#~ msgstr "Kann das Attribut %s nicht ändern."
+
+#~ msgid "incorrect value (%(value)s) for type \"%(type)s\""
+#~ msgstr "Wert %(value)s ungültig für den Typ \"%(type)s\""
+
+#~ msgid "invalid value %(value)s, it must be one of %(choices)s"
+#~ msgstr "Wert %(value)s ungültig, er muss zwischen %(choices)s"
--- a/i18n/en.po Wed Jan 09 16:04:26 2013 +0100
+++ b/i18n/en.po Wed Jan 09 16:06:20 2013 +0100
@@ -42,6 +42,14 @@
msgstr ""
#, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
msgid "%(attr)s set to %(newvalue)s"
msgstr ""
@@ -50,10 +58,6 @@
msgstr ""
#, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr ""
-
-#, python-format
msgid "%(etype)s by %(author)s"
msgstr ""
@@ -66,10 +70,6 @@
msgstr ""
#, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr ""
-
-#, python-format
msgid "%d days"
msgstr ""
@@ -405,6 +405,11 @@
msgid "Click to sort on this column"
msgstr ""
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
#, python-format
msgid "Created %(etype)s : %(entity)s"
msgstr ""
@@ -878,6 +883,9 @@
msgid "UniqueConstraint"
msgstr "unique constraint"
+msgid "Unknown source type"
+msgstr ""
+
msgid "Unreachable objects"
msgstr ""
@@ -929,6 +937,15 @@
msgid "You can use any of the following substitutions in your text"
msgstr ""
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
msgid ""
"You have no access to this view or it can not be used to display the current "
"data."
@@ -968,9 +985,6 @@
msgid "abstract base class for transitions"
msgstr ""
-msgid "action menu"
-msgstr ""
-
msgid "action(s) on this selection"
msgstr ""
@@ -1349,11 +1363,7 @@
msgid "can't be deleted"
msgstr ""
-#, python-format
-msgid "can't change the %s attribute"
-msgstr ""
-
-msgid "can't change this relation"
+msgid "can't change this attribute"
msgstr ""
#, python-format
@@ -2516,6 +2526,9 @@
msgid "foaf"
msgstr ""
+msgid "focus on this selection"
+msgstr ""
+
msgid "follow"
msgstr ""
@@ -2788,7 +2801,7 @@
msgstr ""
#, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
msgstr ""
msgid "index this attribute's value in the plain text index"
@@ -2865,7 +2878,7 @@
msgstr ""
#, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
msgstr ""
msgid "is"
@@ -4074,6 +4087,9 @@
msgid "toggle check boxes"
msgstr ""
+msgid "toggle filter"
+msgstr ""
+
msgid "tr_count"
msgstr "transition number"
@@ -4216,9 +4232,6 @@
msgid "unknown property key %s"
msgstr ""
-msgid "unknown source type"
-msgstr ""
-
msgid "unknown vocabulary:"
msgstr ""
@@ -4355,26 +4368,18 @@
msgstr ""
#, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
msgstr ""
msgid "value associated to this key is not editable manually"
msgstr ""
#, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
msgstr ""
#, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
msgstr ""
msgid "vcard"
--- a/i18n/es.po Wed Jan 09 16:04:26 2013 +0100
+++ b/i18n/es.po Wed Jan 09 16:06:20 2013 +0100
@@ -51,6 +51,14 @@
"\"role=subject\" o \"role=object\" debe ser especificado en las opciones"
#, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr ""
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+
+#, python-format
msgid "%(attr)s set to %(newvalue)s"
msgstr "%(attr)s modificado a %(newvalue)s"
@@ -59,10 +67,6 @@
msgstr "%(attr)s modificado de %(oldvalue)s a %(newvalue)s"
#, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "el valor %(value)r no satisface la condición %(cstr)s"
-
-#, python-format
msgid "%(etype)s by %(author)s"
msgstr "%(etype)s por %(author)s"
@@ -75,10 +79,6 @@
msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
#, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r no corresponde a la expresión regular %(regexp)r"
-
-#, python-format
msgid "%d days"
msgstr "%d días"
@@ -427,6 +427,11 @@
msgid "Click to sort on this column"
msgstr ""
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
#, python-format
msgid "Created %(etype)s : %(entity)s"
msgstr ""
@@ -905,6 +910,9 @@
msgid "UniqueConstraint"
msgstr "Restricción de Unicidad"
+msgid "Unknown source type"
+msgstr ""
+
msgid "Unreachable objects"
msgstr "Objetos inaccesibles"
@@ -965,6 +973,15 @@
"Puede realizar cualquiera de las siguientes sustituciones en el contenido de "
"su email."
+msgid "You can't change this relation"
+msgstr ""
+
+msgid "You cannot remove the system source"
+msgstr ""
+
+msgid "You cannot rename the system source"
+msgstr ""
+
msgid ""
"You have no access to this view or it can not be used to display the current "
"data."
@@ -1016,9 +1033,6 @@
msgid "abstract base class for transitions"
msgstr "Clase de base abstracta para la transiciones"
-msgid "action menu"
-msgstr ""
-
msgid "action(s) on this selection"
msgstr "Acción(es) en esta selección"
@@ -1403,12 +1417,8 @@
msgid "can't be deleted"
msgstr "No puede ser eliminado"
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "no puede modificar el atributo %s"
-
-msgid "can't change this relation"
-msgstr "no puede modificar esta relación"
+msgid "can't change this attribute"
+msgstr ""
#, python-format
msgid "can't connect to source %s, some data may be missing"
@@ -2611,6 +2621,9 @@
msgid "foaf"
msgstr "Amigo de un Amigo, FOAF"
+msgid "focus on this selection"
+msgstr ""
+
msgid "follow"
msgstr "Seguir la liga"
@@ -2901,8 +2914,8 @@
msgstr "Valor del Captcha incorrecto"
#, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr ""
msgid "index this attribute's value in the plain text index"
msgstr "Indexar el valor de este atributo en el índice de texto simple"
@@ -2981,8 +2994,8 @@
msgstr "Acción %r invalida"
#, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "Valor %(value)s incorrecto, debe estar entre %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
msgid "is"
msgstr "es"
@@ -4224,6 +4237,9 @@
msgid "toggle check boxes"
msgstr "Cambiar valor"
+msgid "toggle filter"
+msgstr "esconder/mostrar el filtro"
+
msgid "tr_count"
msgstr "n° de transición"
@@ -4366,9 +4382,6 @@
msgid "unknown property key %s"
msgstr "Clave de Propiedad desconocida: %s"
-msgid "unknown source type"
-msgstr "tipo de fuente desconocida"
-
msgid "unknown vocabulary:"
msgstr "Vocabulario desconocido: "
@@ -4514,26 +4527,18 @@
msgstr "Vampr"
#, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr ""
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
msgstr ""
msgid "value associated to this key is not editable manually"
msgstr "El valor asociado a este elemento no es editable manualmente"
#, python-format
-msgid "value should have maximum size of %s but found %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
msgstr ""
#, python-format
-msgid "value should have minimum size of %s but found %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
msgstr ""
msgid "vcard"
@@ -4681,6 +4686,12 @@
"usted debe quitar la puesta en línea de la relación %s que es aceptada y "
"puede ser cruzada"
+#~ msgid "%(cstr)s constraint failed for value %(value)r"
+#~ msgstr "el valor %(value)r no satisface la condición %(cstr)s"
+
+#~ msgid "%(value)r doesn't match the %(regexp)r regular expression"
+#~ msgstr "%(value)r no corresponde a la expresión regular %(regexp)r"
+
#~ msgid ""
#~ "Can't restore relation %(rtype)s of entity %(eid)s, this relation does "
#~ "not exists anymore in the schema."
@@ -4688,17 +4699,17 @@
#~ "No puede restaurar la relación %(rtype)s de la entidad %(eid)s, esta "
#~ "relación ya no existe en el esquema."
-#~ msgid "day"
-#~ msgstr "día"
-
-#~ msgid "log out first"
-#~ msgstr "Desconéctese primero"
-
-#~ msgid "month"
-#~ msgstr "mes"
-
-#~ msgid "today"
-#~ msgstr "hoy"
-
-#~ msgid "week"
-#~ msgstr "sem."
+#~ msgid "can't change the %s attribute"
+#~ msgstr "no puede modificar el atributo %s"
+
+#~ msgid "can't change this relation"
+#~ msgstr "no puede modificar esta relación"
+
+#~ msgid "incorrect value (%(value)s) for type \"%(type)s\""
+#~ msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
+
+#~ msgid "invalid value %(value)s, it must be one of %(choices)s"
+#~ msgstr "Valor %(value)s incorrecto, debe estar entre %(choices)s"
+
+#~ msgid "unknown source type"
+#~ msgstr "tipo de fuente desconocida"
--- a/i18n/fr.po Wed Jan 09 16:04:26 2013 +0100
+++ b/i18n/fr.po Wed Jan 09 16:06:20 2013 +0100
@@ -50,6 +50,15 @@
"\"role=subject\" ou \"role=object\" doit être specifié dans les options"
#, python-format
+msgid "%(KEY-cstr)s constraint failed for value %(KEY-value)r"
+msgstr "la valeur %(KEY-value)r ne satisfait pas la contrainte %(KEY-cstr)s"
+
+#, python-format
+msgid "%(KEY-value)r doesn't match the %(KEY-regexp)r regular expression"
+msgstr ""
+"%(KEY-value)r ne correspond pas à l'expression régulière %(KEY-regexp)r"
+
+#, python-format
msgid "%(attr)s set to %(newvalue)s"
msgstr "%(attr)s modifié à %(newvalue)s"
@@ -58,10 +67,6 @@
msgstr "%(attr)s modifié de %(oldvalue)s à %(newvalue)s"
#, python-format
-msgid "%(cstr)s constraint failed for value %(value)r"
-msgstr "la valeur %(value)r ne satisfait pas la contrainte %(cstr)s"
-
-#, python-format
msgid "%(etype)s by %(author)s"
msgstr "%(etype)s par %(author)s"
@@ -74,10 +79,6 @@
msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
#, python-format
-msgid "%(value)r doesn't match the %(regexp)r regular expression"
-msgstr "%(value)r ne correspond pas à l'expression régulière %(regexp)r"
-
-#, python-format
msgid "%d days"
msgstr "%d jours"
@@ -427,6 +428,11 @@
msgid "Click to sort on this column"
msgstr "Cliquer pour trier sur cette colonne"
+msgid ""
+"Configuration of the system source goes to the 'sources' file, not in the "
+"database"
+msgstr ""
+
#, python-format
msgid "Created %(etype)s : %(entity)s"
msgstr "Entité %(etype)s crée : %(entity)s"
@@ -907,6 +913,9 @@
msgid "UniqueConstraint"
msgstr "contrainte d'unicité"
+msgid "Unknown source type"
+msgstr "Type de source inconnue"
+
msgid "Unreachable objects"
msgstr "Objets inaccessibles"
@@ -967,6 +976,15 @@
"Vous pouvez utiliser n'importe quelle substitution parmi la liste suivante "
"dans le contenu de votre courriel."
+msgid "You can't change this relation"
+msgstr "Vous ne pouvez pas modifier cette relation"
+
+msgid "You cannot remove the system source"
+msgstr "Vous ne pouvez pas supprimer la source système"
+
+msgid "You cannot rename the system source"
+msgstr "Vous ne pouvez pas renommer la source système"
+
msgid ""
"You have no access to this view or it can not be used to display the current "
"data."
@@ -1018,9 +1036,6 @@
msgid "abstract base class for transitions"
msgstr "classe de base abstraite pour les transitions"
-msgid "action menu"
-msgstr "actions"
-
msgid "action(s) on this selection"
msgstr "action(s) sur cette sélection"
@@ -1260,7 +1275,7 @@
msgstr "anonyme"
msgid "anyrsetview"
-msgstr "vues \"tous les rset\""
+msgstr "vues pour tout rset"
msgid "april"
msgstr "avril"
@@ -1311,7 +1326,7 @@
msgstr "mauvaise valeur"
msgid "badly formatted url"
-msgstr ""
+msgstr "URL mal formattée"
msgid "base url"
msgstr "url de base"
@@ -1399,7 +1414,7 @@
msgstr "impossible d'interpréter les types d'entités :"
msgid "can only have one url"
-msgstr ""
+msgstr "ne supporte qu'une seule URL"
msgid "can't be changed"
msgstr "ne peut-être modifié"
@@ -1407,12 +1422,8 @@
msgid "can't be deleted"
msgstr "ne peut-être supprimé"
-#, python-format
-msgid "can't change the %s attribute"
-msgstr "ne peut changer l'attribut %s"
-
-msgid "can't change this relation"
-msgstr "ne peut modifier cette relation"
+msgid "can't change this attribute"
+msgstr "cet attribut ne peut pas être modifié"
#, python-format
msgid "can't connect to source %s, some data may be missing"
@@ -2621,6 +2632,9 @@
msgid "foaf"
msgstr "foaf"
+msgid "focus on this selection"
+msgstr "afficher cette sélection"
+
msgid "follow"
msgstr "suivre le lien"
@@ -2909,8 +2923,8 @@
msgstr "valeur de captcha incorrecte"
#, python-format
-msgid "incorrect value (%(value)s) for type \"%(type)s\""
-msgstr "valeur %(value)s incorrecte pour le type \"%(type)s\""
+msgid "incorrect value (%(KEY-value)r) for type \"%(KEY-type)s\""
+msgstr "la valeur %(KEY-value)s est incorrecte pour le type \"%(KEY-type)s\""
msgid "index this attribute's value in the plain text index"
msgstr "indexer la valeur de cet attribut dans l'index plein texte"
@@ -2989,8 +3003,9 @@
msgstr "action %r invalide"
#, python-format
-msgid "invalid value %(value)s, it must be one of %(choices)s"
-msgstr "valeur %(value)s incorrect, doit être parmi %(choices)s"
+msgid "invalid value %(KEY-value)s, it must be one of %(KEY-choices)s"
+msgstr ""
+"la valeur %(KEY-value)s est incorrecte, elle doit être parmi %(KEY-choices)s"
msgid "is"
msgstr "de type"
@@ -4235,7 +4250,10 @@
msgstr "à faire par"
msgid "toggle check boxes"
-msgstr "inverser les cases à cocher"
+msgstr "afficher/masquer les cases à cocher"
+
+msgid "toggle filter"
+msgstr "afficher/masquer le filtre"
msgid "tr_count"
msgstr "n° de transition"
@@ -4379,14 +4397,11 @@
msgid "unknown property key %s"
msgstr "clé de propriété inconnue : %s"
-msgid "unknown source type"
-msgstr "type de source inconnu"
-
msgid "unknown vocabulary:"
msgstr "vocabulaire inconnu : "
msgid "unsupported protocol"
-msgstr ""
+msgstr "protocole non supporté"
msgid "upassword"
msgstr "mot de passe"
@@ -4525,27 +4540,23 @@
msgstr "valeur"
#, python-format
-msgid "value %(value)s must be %(op)s %(boundary)s"
-msgstr "la valeur %(value)s doit être %(op)s %(boundary)s"
-
-#, python-format
-msgid "value %(value)s must be <= %(boundary)s"
-msgstr "la valeur %(value)s doit être <= %(boundary)s"
-
-#, python-format
-msgid "value %(value)s must be >= %(boundary)s"
-msgstr "la valeur %(value)s doit être >= %(boundary)s"
+msgid "value %(KEY-value)s must be %(KEY-op)s %(KEY-boundary)s"
+msgstr "la valeur %(KEY-value)s n'est pas %(KEY-op)s %(KEY-boundary)s"
msgid "value associated to this key is not editable manually"
msgstr "la valeur associée à cette clé n'est pas éditable manuellement"
#, python-format
-msgid "value should have maximum size of %s but found %s"
-msgstr "la taille maximum est %s mais cette valeur est de taille %s"
+msgid "value should have maximum size of %(KEY-max)s but found %(KEY-size)s"
+msgstr ""
+"la taille maximum est %(KEY-max)s mais cette valeur est de taille "
+"%(KEY-size)s"
#, python-format
-msgid "value should have minimum size of %s but found %s"
-msgstr "la taille minimum est %s mais cette valeur est de taille %s"
+msgid "value should have minimum size of %(KEY-min)s but found %(KEY-size)s"
+msgstr ""
+"la taille minimum est %(KEY-min)s mais cette valeur est de taille "
+"%(KEY-size)s"
msgid "vcard"
msgstr "vcard"
@@ -4699,6 +4710,9 @@
#~ msgid "day"
#~ msgstr "jour"
+#~ msgid "jump to selection"
+#~ msgstr "afficher cette sélection"
+
#~ msgid "log out first"
#~ msgstr "déconnecter vous d'abord"
--- a/misc/migration/3.10.0_Any.py Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/migration/3.10.0_Any.py Wed Jan 09 16:06:20 2013 +0100
@@ -34,5 +34,5 @@
for x in rql('Any X,XK WHERE X pkey XK, '
'X pkey ~= "boxes.%" OR '
'X pkey ~= "contentnavigation.%"').entities():
- x.set_attributes(pkey=u'ctxcomponents.' + x.pkey.split('.', 1)[1])
+ x.cw_set(pkey=u'ctxcomponents.' + x.pkey.split('.', 1)[1])
--- a/misc/migration/3.11.0_Any.py Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/migration/3.11.0_Any.py Wed Jan 09 16:06:20 2013 +0100
@@ -81,5 +81,5 @@
rset = session.execute('Any V WHERE X is CWProperty, X value V, X pkey %(k)s',
{'k': pkey})
timestamp = int(rset[0][0])
- sourceentity.set_attributes(latest_retrieval=datetime.fromtimestamp(timestamp))
+ sourceentity.cw_set(latest_retrieval=datetime.fromtimestamp(timestamp))
session.execute('DELETE CWProperty X WHERE X pkey %(k)s', {'k': pkey})
--- a/misc/migration/3.14.0_Any.py Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/migration/3.14.0_Any.py Wed Jan 09 16:06:20 2013 +0100
@@ -9,5 +9,5 @@
expression = rqlcstr.value
mainvars = guess_rrqlexpr_mainvars(expression)
yamscstr = CONSTRAINTS[rqlcstr.type](expression, mainvars)
- rqlcstr.set_attributes(value=yamscstr.serialize())
+ rqlcstr.cw_set(value=yamscstr.serialize())
print 'updated', rqlcstr.type, rqlcstr.value.strip()
--- a/misc/migration/3.15.0_Any.py Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/migration/3.15.0_Any.py Wed Jan 09 16:06:20 2013 +0100
@@ -4,7 +4,7 @@
config = source.dictconfig
host = config.pop('host', u'ldap')
protocol = config.pop('protocol', u'ldap')
- source.set_attributes(url=u'%s://%s' % (protocol, host))
+ source.cw_set(url=u'%s://%s' % (protocol, host))
source.update_config(skip_unknown=True, **config)
commit()
--- a/misc/migration/postcreate.py Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/migration/postcreate.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/misc/scripts/chpasswd.py Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/scripts/chpasswd.py Wed Jan 09 16:06:20 2013 +0100
@@ -42,7 +42,7 @@
crypted = crypt_password(pass1)
cwuser = rset.get_entity(0,0)
-cwuser.set_attributes(upassword=Binary(crypted))
+cwuser.cw_set(upassword=Binary(crypted))
commit()
print("password updated.")
--- a/misc/scripts/ldapuser2ldapfeed.py Wed Jan 09 16:04:26 2013 +0100
+++ b/misc/scripts/ldapuser2ldapfeed.py Wed Jan 09 16:06:20 2013 +0100
@@ -87,7 +87,7 @@
source_ent = rql('CWSource S WHERE S eid %(s)s', {'s': source.eid}).get_entity(0, 0)
-source_ent.set_attributes(type=u"ldapfeed", parser=u"ldapfeed")
+source_ent.cw_set(type=u"ldapfeed", parser=u"ldapfeed")
if raw_input('Commit ?') in 'yY':
--- a/predicates.py Wed Jan 09 16:04:26 2013 +0100
+++ b/predicates.py Wed Jan 09 16:06:20 2013 +0100
@@ -737,12 +737,16 @@
See :class:`~cubicweb.predicates.EClassPredicate` documentation for entity
class lookup / score rules according to the input context.
- .. note:: when interface is an entity class, the score will reflect class
- proximity so the most specific object will be selected.
+ .. note::
+
+ when interface is an entity class, the score will reflect class
+ proximity so the most specific object will be selected.
- .. note:: deprecated in cubicweb >= 3.9, use either
- :class:`~cubicweb.predicates.is_instance` or
- :class:`~cubicweb.predicates.adaptable`.
+ .. note::
+
+ deprecated in cubicweb >= 3.9, use either
+ :class:`~cubicweb.predicates.is_instance` or
+ :class:`~cubicweb.predicates.adaptable`.
"""
def __init__(self, *expected_ifaces, **kwargs):
--- a/req.py Wed Jan 09 16:04:26 2013 +0100
+++ b/req.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -62,6 +62,8 @@
:attribute vreg.schema: the instance's schema
:attribute vreg.config: the instance's configuration
"""
+ is_request = True # False for repository session
+
def __init__(self, vreg):
self.vreg = vreg
try:
@@ -75,6 +77,17 @@
self.local_perm_cache = {}
self._ = unicode
+ def set_language(self, lang):
+ """install i18n configuration for `lang` translation.
+
+ Raises :exc:`KeyError` if translation doesn't exist.
+ """
+ self.lang = lang
+ gettext, pgettext = self.vreg.config.translations[lang]
+ # use _cw.__ to translate a message without registering it to the catalog
+ self._ = self.__ = gettext
+ self.pgettext = pgettext
+
def get_option_value(self, option, foreid=None):
raise NotImplementedError
@@ -308,21 +321,12 @@
def user_data(self):
"""returns a dictionary with this user's information"""
userinfo = {}
- if self.is_internal_session:
- userinfo['login'] = "cubicweb"
- userinfo['name'] = "cubicweb"
- userinfo['email'] = ""
- return userinfo
user = self.user
userinfo['login'] = user.login
userinfo['name'] = user.name()
userinfo['email'] = user.cw_adapt_to('IEmailable').get_email()
return userinfo
- def is_internal_session(self):
- """overrided on the server-side"""
- return False
-
# formating methods #######################################################
def view(self, __vid, rset=None, __fallback_oid=None, __registry='views',
@@ -426,7 +430,7 @@
"""return the root url of the instance
"""
if secure:
- raise NotImplementedError()
+ return self.vreg.config.get('https-url', self.vreg.config['base-url'])
return self.vreg.config['base-url']
# abstract methods to override according to the web front-end #############
--- a/schema.py Wed Jan 09 16:04:26 2013 +0100
+++ b/schema.py Wed Jan 09 16:06:20 2013 +0100
@@ -261,30 +261,34 @@
return self.has_local_role(action) or self.has_perm(req, action)
PermissionMixIn.may_have_permission = may_have_permission
-def has_perm(self, session, action, **kwargs):
+def has_perm(self, _cw, action, **kwargs):
"""return true if the action is granted globaly or localy"""
try:
- self.check_perm(session, action, **kwargs)
+ self.check_perm(_cw, action, **kwargs)
return True
except Unauthorized:
return False
PermissionMixIn.has_perm = has_perm
-def check_perm(self, session, action, **kwargs):
- # NB: session may be a server session or a request object check user is
- # in an allowed group, if so that's enough internal sessions should
- # always stop there
+def check_perm(self, _cw, action, **kwargs):
+ # NB: _cw may be a server transaction or a request object.
+ #
+ # check user is in an allowed group, if so that's enough internal
+ # transactions should always stop there
groups = self.get_groups(action)
- if session.user.matching_groups(groups):
+ if _cw.user.matching_groups(groups):
return
# if 'owners' in allowed groups, check if the user actually owns this
# object, if so that's enough
+ #
+ # NB: give _cw to user.owns since user is not be bound to a transaction on
+ # the repository side
if 'owners' in groups and (
kwargs.get('creating')
- or ('eid' in kwargs and session.user.owns(kwargs['eid']))):
+ or ('eid' in kwargs and _cw.user.owns(kwargs['eid']))):
return
# else if there is some rql expressions, check them
- if any(rqlexpr.check(session, **kwargs)
+ if any(rqlexpr.check(_cw, **kwargs)
for rqlexpr in self.get_rqlexprs(action)):
return
raise Unauthorized(action, str(self))
@@ -467,45 +471,45 @@
return True
return False
- def has_perm(self, session, action, **kwargs):
+ def has_perm(self, _cw, action, **kwargs):
"""return true if the action is granted globaly or localy"""
if self.final:
assert not ('fromeid' in kwargs or 'toeid' in kwargs), kwargs
assert action in ('read', 'update')
if 'eid' in kwargs:
- subjtype = session.describe(kwargs['eid'])[0]
+ subjtype = _cw.describe(kwargs['eid'])[0]
else:
subjtype = objtype = None
else:
assert not 'eid' in kwargs, kwargs
assert action in ('read', 'add', 'delete')
if 'fromeid' in kwargs:
- subjtype = session.describe(kwargs['fromeid'])[0]
+ subjtype = _cw.describe(kwargs['fromeid'])[0]
elif 'frometype' in kwargs:
subjtype = kwargs.pop('frometype')
else:
subjtype = None
if 'toeid' in kwargs:
- objtype = session.describe(kwargs['toeid'])[0]
+ objtype = _cw.describe(kwargs['toeid'])[0]
elif 'toetype' in kwargs:
objtype = kwargs.pop('toetype')
else:
objtype = None
if objtype and subjtype:
- return self.rdef(subjtype, objtype).has_perm(session, action, **kwargs)
+ return self.rdef(subjtype, objtype).has_perm(_cw, action, **kwargs)
elif subjtype:
for tschema in self.targets(subjtype, 'subject'):
rdef = self.rdef(subjtype, tschema)
- if not rdef.has_perm(session, action, **kwargs):
+ if not rdef.has_perm(_cw, action, **kwargs):
return False
elif objtype:
for tschema in self.targets(objtype, 'object'):
rdef = self.rdef(tschema, objtype)
- if not rdef.has_perm(session, action, **kwargs):
+ if not rdef.has_perm(_cw, action, **kwargs):
return False
else:
for rdef in self.rdefs.itervalues():
- if not rdef.has_perm(session, action, **kwargs):
+ if not rdef.has_perm(_cw, action, **kwargs):
return False
return True
@@ -754,17 +758,17 @@
return rql, found, keyarg
return rqlst.as_string(), None, None
- def _check(self, session, **kwargs):
+ def _check(self, _cw, **kwargs):
"""return True if the rql expression is matching the given relation
between fromeid and toeid
- session may actually be a request as well
+ _cw may be a request or a server side transaction
"""
creating = kwargs.get('creating')
if not creating and self.eid is not None:
key = (self.eid, tuple(sorted(kwargs.iteritems())))
try:
- return session.local_perm_cache[key]
+ return _cw.local_perm_cache[key]
except KeyError:
pass
rql, has_perm_defs, keyarg = self.transform_has_permission()
@@ -772,50 +776,50 @@
if creating and 'X' in self.rqlst.defined_vars:
return True
if keyarg is None:
- kwargs.setdefault('u', session.user.eid)
+ kwargs.setdefault('u', _cw.user.eid)
try:
- rset = session.execute(rql, kwargs, build_descr=True)
+ rset = _cw.execute(rql, kwargs, build_descr=True)
except NotImplementedError:
self.critical('cant check rql expression, unsupported rql %s', rql)
if self.eid is not None:
- session.local_perm_cache[key] = False
+ _cw.local_perm_cache[key] = False
return False
except TypeResolverException, ex:
# some expression may not be resolvable with current kwargs
# (type conflict)
self.warning('%s: %s', rql, str(ex))
if self.eid is not None:
- session.local_perm_cache[key] = False
+ _cw.local_perm_cache[key] = False
return False
except Unauthorized, ex:
self.debug('unauthorized %s: %s', rql, str(ex))
if self.eid is not None:
- session.local_perm_cache[key] = False
+ _cw.local_perm_cache[key] = False
return False
else:
- rset = session.eid_rset(kwargs[keyarg])
+ rset = _cw.eid_rset(kwargs[keyarg])
# if no special has_*_permission relation in the rql expression, just
# check the result set contains something
if has_perm_defs is None:
if rset:
if self.eid is not None:
- session.local_perm_cache[key] = True
+ _cw.local_perm_cache[key] = True
return True
elif rset:
# check every special has_*_permission relation is satisfied
- get_eschema = session.vreg.schema.eschema
+ get_eschema = _cw.vreg.schema.eschema
try:
for eaction, col in has_perm_defs:
for i in xrange(len(rset)):
eschema = get_eschema(rset.description[i][col])
- eschema.check_perm(session, eaction, eid=rset[i][col])
+ eschema.check_perm(_cw, eaction, eid=rset[i][col])
if self.eid is not None:
- session.local_perm_cache[key] = True
+ _cw.local_perm_cache[key] = True
return True
except Unauthorized:
pass
if self.eid is not None:
- session.local_perm_cache[key] = False
+ _cw.local_perm_cache[key] = False
return False
@property
@@ -843,15 +847,15 @@
rql += ', U eid %(u)s'
return rql
- def check(self, session, eid=None, creating=False, **kwargs):
+ def check(self, _cw, eid=None, creating=False, **kwargs):
if 'X' in self.rqlst.defined_vars:
if eid is None:
if creating:
- return self._check(session, creating=True, **kwargs)
+ return self._check(_cw, creating=True, **kwargs)
return False
assert creating == False
- return self._check(session, x=eid, **kwargs)
- return self._check(session, **kwargs)
+ return self._check(_cw, x=eid, **kwargs)
+ return self._check(_cw, **kwargs)
def vargraph(rqlst):
@@ -904,7 +908,7 @@
rql += ', U eid %(u)s'
return rql
- def check(self, session, fromeid=None, toeid=None):
+ def check(self, _cw, fromeid=None, toeid=None):
kwargs = {}
if 'S' in self.rqlst.defined_vars:
if fromeid is None:
@@ -914,7 +918,7 @@
if toeid is None:
return False
kwargs['o'] = toeid
- return self._check(session, **kwargs)
+ return self._check(_cw, **kwargs)
# in yams, default 'update' perm for attributes granted to managers and owners.
@@ -1024,7 +1028,7 @@
'expression': self.expression}
raise ValidationError(maineid, {qname: msg})
- def exec_query(self, session, eidfrom, eidto):
+ def exec_query(self, _cw, eidfrom, eidto):
if eidto is None:
# checking constraint for an attribute relation
expression = 'S eid %(s)s, ' + self.expression
@@ -1034,11 +1038,11 @@
args = {'s': eidfrom, 'o': eidto}
if 'U' in self.rqlst.defined_vars:
expression = 'U eid %(u)s, ' + expression
- args['u'] = session.user.eid
+ args['u'] = _cw.user.eid
rql = 'Any %s WHERE %s' % (','.join(sorted(self.mainvars)), expression)
if self.distinct_query:
rql = 'DISTINCT ' + rql
- return session.execute(rql, args, build_descr=False)
+ return _cw.execute(rql, args, build_descr=False)
class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint):
@@ -1061,7 +1065,7 @@
"""
# XXX turns mainvars into a required argument in __init__
distinct_query = True
-
+
def match_condition(self, session, eidfrom, eidto):
return len(self.exec_query(session, eidfrom, eidto)) <= 1
--- a/server/__init__.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/__init__.py Wed Jan 09 16:06:20 2013 +0100
@@ -77,10 +77,14 @@
DBG_REPO = 4
#: multi-sources
DBG_MS = 8
+#: hooks
+DBG_HOOKS = 16
+#: operations
+DBG_OPS = 32
#: more verbosity
-DBG_MORE = 16
+DBG_MORE = 64
#: all level enabled
-DBG_ALL = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_MORE
+DBG_ALL = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_HOOKS + DBG_OPS + DBG_MORE
#: current debug mode
DEBUG = 0
@@ -165,6 +169,8 @@
# on connection
config.creating = True
config.consider_user_state = False
+ config.cubicweb_appobject_path = set(('hooks', 'entities'))
+ config.cube_appobject_path = set(('hooks', 'entities'))
# only enable the system source at initialization time
repo = Repository(config, vreg=vreg)
schema = repo.schema
@@ -224,10 +230,6 @@
config._cubes = None # avoid assertion error
repo, cnx = in_memory_repo_cnx(config, login, password=pwd)
repo.system_source.eid = ssource.eid # redo this manually
- # trigger vreg initialisation of entity classes
- config.cubicweb_appobject_path = set(('entities',))
- config.cube_appobject_path = set(('entities',))
- repo.vreg.set_schema(repo.schema)
assert len(repo.sources) == 1, repo.sources
handler = config.migration_handler(schema, interactive=False,
cnx=cnx, repo=repo)
@@ -246,20 +248,21 @@
# restore initial configuration
config.creating = False
config.consider_user_state = True
+ # (drop instance attribute to get back to class attribute)
+ del config.cubicweb_appobject_path
+ del config.cube_appobject_path
print '-> database for instance %s initialized.' % config.appid
def initialize_schema(config, schema, mhandler, event='create'):
from cubicweb.server.schemaserial import serialize_schema
- from cubicweb.server.session import hooks_control
session = mhandler.session
cubes = config.cubes()
# deactivate every hooks but those responsible to set metadata
# so, NO INTEGRITY CHECKS are done, to have quicker db creation.
# Active integrity is kept else we may pb such as two default
# workflows for one entity type.
- with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata',
- 'activeintegrity'):
+ with session.deny_all_hooks_but('metadata', 'activeintegrity'):
# execute cubicweb's pre<event> script
mhandler.cmd_exec_event_script('pre%s' % event)
# execute cubes pre<event> script if any
--- a/server/checkintegrity.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/checkintegrity.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -32,7 +32,6 @@
from cubicweb.schema import PURE_VIRTUAL_RTYPES, VIRTUAL_RTYPES
from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.session import security_enabled
def notify_fixed(fix):
if fix:
@@ -394,18 +393,18 @@
# yo, launch checks
if checks:
eids_cache = {}
- with security_enabled(session, read=False, write=False): # ensure no read security
+ with session.security_enabled(read=False, write=False): # ensure no read security
for check in checks:
check_func = globals()['check_%s' % check]
check_func(repo.schema, session, eids_cache, fix=fix)
if fix:
- cnx.commit()
+ session.commit()
else:
print
if not fix:
print 'WARNING: Diagnostic run, nothing has been corrected'
if reindex:
- cnx.rollback()
+ session.rollback()
session.set_cnxset()
reindex_entities(repo.schema, session, withpb=withpb)
- cnx.commit()
+ session.commit()
--- a/server/edition.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/edition.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -103,6 +103,8 @@
assert not self.saved, 'too late to modify edited attributes'
super(EditedEntity, self).__setitem__(attr, value)
self.entity.cw_attr_cache[attr] = value
+ # mark attribute as needing purge by the client
+ self.entity._cw_dont_cache_attribute(attr)
def oldnewvalue(self, attr):
"""returns the couple (old attr value, new attr value)
@@ -141,8 +143,7 @@
for rtype in self]
try:
entity.e_schema.check(dict_protocol_catcher(entity),
- creation=creation, _=entity._cw._,
- relations=relations)
+ creation=creation, relations=relations)
except ValidationError, ex:
ex.entity = self.entity
raise
--- a/server/hook.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/hook.py Wed Jan 09 16:06:20 2013 +0100
@@ -152,7 +152,7 @@
On those events, the entity has no `cw_edited` dictionary.
-.. note:: `self.entity.set_attributes(age=42)` will set the `age` attribute to
+.. note:: `self.entity.cw_set(age=42)` will set the `age` attribute to
42. But to do so, it will generate a rql query that will have to be processed,
hence may trigger some hooks, etc. This could lead to infinitely looping hooks.
@@ -197,14 +197,12 @@
~~~~~~~~~~~~~
It is sometimes convenient to explicitly enable or disable some hooks. For
-instance if you want to disable some integrity checking hook. This can be
+instance if you want to disable some integrity checking hook. This can be
controlled more finely through the `category` class attribute, which is a string
giving a category name. One can then uses the
-:class:`~cubicweb.server.session.hooks_control` context manager to explicitly
-enable or disable some categories.
-
-.. autoclass:: cubicweb.server.session.hooks_control
-
+:meth:`~cubicweb.server.session.Session.deny_all_hooks_but` and
+:meth:`~cubicweb.server.session.Session.allow_all_hooks_but` context managers to
+explicitly enable or disable some categories.
The existing categories are:
@@ -230,10 +228,8 @@
* ``bookmark``, bookmark entities handling hooks
-Nothing precludes one to invent new categories and use the
-:class:`~cubicweb.server.session.hooks_control` context manager to
-filter them in or out. Note that ending the transaction with commit()
-or rollback() will restore the hooks.
+Nothing precludes one to invent new categories and use existing mechanisms to
+filter them in or out.
Hooks specific predicates
@@ -264,11 +260,10 @@
from logilab.common.registry import (Predicate, NotPredicate, OrPredicate,
classid, objectify_predicate, yes)
-from cubicweb import RegistryNotFound
+from cubicweb import RegistryNotFound, server
from cubicweb.cwvreg import CWRegistry, CWRegistryStore
from cubicweb.predicates import ExpectedValuePredicate, is_instance
from cubicweb.appobject import AppObject
-from cubicweb.server.session import security_enabled
ENTITIES_HOOKS = set(('before_add_entity', 'after_add_entity',
'before_update_entity', 'after_update_entity',
@@ -326,12 +321,15 @@
pruned = self.get_pruned_hooks(session, event,
entities, eids_from_to, kwargs)
# by default, hooks are executed with security turned off
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
for _kwargs in _iter_kwargs(entities, eids_from_to, kwargs):
hooks = sorted(self.filtered_possible_objects(pruned, session, **_kwargs),
key=lambda x: x.order)
- with security_enabled(session, write=False):
+ debug = server.DEBUG & server.DBG_HOOKS
+ with session.security_enabled(write=False):
for hook in hooks:
+ if debug:
+ print event, _kwargs, hook
hook()
def get_pruned_hooks(self, session, event, entities, eids_from_to, kwargs):
--- a/server/migractions.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/migractions.py Wed Jan 09 16:06:20 2013 +0100
@@ -1321,7 +1321,7 @@
except Exception:
self.cmd_create_entity('CWProperty', pkey=unicode(pkey), value=value)
else:
- prop.set_attributes(value=value)
+ prop.cw_set(value=value)
# other data migration commands ###########################################
--- a/server/pool.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/pool.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/querier.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/querier.py Wed Jan 09 16:06:20 2013 +0100
@@ -26,21 +26,26 @@
from itertools import repeat
from logilab.common.compat import any
-from rql import RQLSyntaxError
+from rql import RQLSyntaxError, CoercionError
from rql.stmts import Union, Select
+from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
from rql.nodes import (Relation, VariableRef, Constant, SubQuery, Function,
Exists, Not)
+from yams import BASE_TYPES
from cubicweb import ValidationError, Unauthorized, QueryError, UnknownEid
-from cubicweb import server, typed_eid
+from cubicweb import Binary, server, typed_eid
from cubicweb.rset import ResultSet
-from cubicweb.utils import QueryCache
+from cubicweb.utils import QueryCache, RepeatList
from cubicweb.server.utils import cleanup_solutions
from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
from cubicweb.server.edition import EditedEntity
-from cubicweb.server.session import security_enabled
+
+
+ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
+
def empty_rset(rql, args, rqlst=None):
"""build an empty result set object"""
@@ -256,7 +261,7 @@
cached = True
else:
noinvariant = set()
- with security_enabled(self.session, read=False):
+ with self.session.security_enabled(read=False):
self._insert_security(union, noinvariant)
if key is not None:
self.session.transaction_data[key] = (union, self.args)
@@ -751,14 +756,22 @@
if build_descr:
if rqlst.TYPE == 'select':
# sample selection
- descr = session.build_description(orig_rqlst, args, results)
+ if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
+ # easy, all lines are identical
+ selected = rqlst.children[0].selection
+ solution = rqlst.children[0].solutions[0]
+ description = _make_description(selected, args, solution)
+ descr = RepeatList(len(results), tuple(description))
+ else:
+ # hard, delegate the work :o)
+ descr = manual_build_descr(session, rqlst, args, results)
elif rqlst.TYPE == 'insert':
# on insert plan, some entities may have been auto-casted,
# so compute description manually even if there is only
# one solution
basedescr = [None] * len(plan.selected)
todetermine = zip(xrange(len(plan.selected)), repeat(False))
- descr = session._build_descr(results, basedescr, todetermine)
+ descr = _build_descr(session, results, basedescr, todetermine)
# FIXME: get number of affected entities / relations on non
# selection queries ?
# return a result set object
@@ -772,3 +785,77 @@
from cubicweb import set_log_methods
LOGGER = getLogger('cubicweb.querier')
set_log_methods(QuerierHelper, LOGGER)
+
+
+def manual_build_descr(tx, rqlst, args, result):
+ """build a description for a given result by analysing each row
+
+ XXX could probably be done more efficiently during execution of query
+ """
+ # not so easy, looks for variable which changes from one solution
+ # to another
+ unstables = rqlst.get_variable_indices()
+ basedescr = []
+ todetermine = []
+ for i in xrange(len(rqlst.children[0].selection)):
+ ttype = _selection_idx_type(i, rqlst, args)
+ if ttype is None or ttype == 'Any':
+ ttype = None
+ isfinal = True
+ else:
+ isfinal = ttype in BASE_TYPES
+ if ttype is None or i in unstables:
+ basedescr.append(None)
+ todetermine.append( (i, isfinal) )
+ else:
+ basedescr.append(ttype)
+ if not todetermine:
+ return RepeatList(len(result), tuple(basedescr))
+ return _build_descr(tx, result, basedescr, todetermine)
+
+def _build_descr(tx, result, basedescription, todetermine):
+ description = []
+ etype_from_eid = tx.describe
+ todel = []
+ for i, row in enumerate(result):
+ row_descr = basedescription[:]
+ for index, isfinal in todetermine:
+ value = row[index]
+ if value is None:
+ # None value inserted by an outer join, no type
+ row_descr[index] = None
+ continue
+ if isfinal:
+ row_descr[index] = etype_from_pyobj(value)
+ else:
+ try:
+ row_descr[index] = etype_from_eid(value)[0]
+ except UnknownEid:
+ tx.error('wrong eid %s in repository, you should '
+ 'db-check the database' % value)
+ todel.append(i)
+ break
+ else:
+ description.append(tuple(row_descr))
+ for i in reversed(todel):
+ del result[i]
+ return description
+
+def _make_description(selected, args, solution):
+ """return a description for a result set"""
+ description = []
+ for term in selected:
+ description.append(term.get_type(solution, args))
+ return description
+
+def _selection_idx_type(i, rqlst, args):
+ """try to return type of term at index `i` of the rqlst's selection"""
+ for select in rqlst.children:
+ term = select.selection[i]
+ for solution in select.solutions:
+ try:
+ ttype = term.get_type(solution, args)
+ if ttype is not None:
+ return ttype
+ except CoercionError:
+ return None
--- a/server/repository.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/repository.py Wed Jan 09 16:06:20 2013 +0100
@@ -56,8 +56,7 @@
RepositoryError, UniqueTogetherError, typed_eid, onevent)
from cubicweb import cwvreg, schema, server
from cubicweb.server import ShuttingDown, utils, hook, pool, querier, sources
-from cubicweb.server.session import Session, InternalSession, InternalManager, \
- security_enabled
+from cubicweb.server.session import Session, InternalSession, InternalManager
from cubicweb.server.ssplanner import EditedEntity
NO_CACHE_RELATIONS = set( [('owned_by', 'object'),
@@ -109,12 +108,12 @@
# * we don't want read permissions to be applied but we want delete
# permission to be checked
if card[0] in '1?':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
'NOT Y eid %%(y)s' % rtype,
{'x': eidfrom, 'y': eidto})
if card[1] in '1?':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE Y eid %%(y)s, '
'NOT X eid %%(x)s' % rtype,
{'x': eidfrom, 'y': eidto})
@@ -135,7 +134,7 @@
session.update_rel_cache_add(entity.eid, attr, value)
rdef = session.rtype_eids_rdef(attr, entity.eid, value)
if rdef.cardinality[1] in '1?' and activeintegrity:
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE Y eid %%(y)s' % attr,
{'x': entity.eid, 'y': value})
return relations
@@ -218,33 +217,21 @@
# information (eg dump/restore/...)
config._cubes = ()
# only load hooks and entity classes in the registry
- config.__class__.cube_appobject_path = set(('hooks', 'entities'))
- config.__class__.cubicweb_appobject_path = set(('hooks', 'entities'))
+ config.cube_appobject_path = set(('hooks', 'entities'))
+ config.cubicweb_appobject_path = set(('hooks', 'entities'))
self.set_schema(config.load_schema())
config['connections-pool-size'] = 1
# will be reinitialized later from cubes found in the database
config._cubes = None
- elif config.creating:
- # repository creation
+ elif config.creating or not config.read_instance_schema:
+ if not config.creating:
+ # test start: use the file system schema (quicker)
+ self.warning("set fs instance'schema")
config.bootstrap_cubes()
- self.set_schema(config.load_schema(), resetvreg=False)
- # need to load the Any and CWUser entity types
- etdirectory = join(CW_SOFTWARE_ROOT, 'entities')
- self.vreg.init_registration([etdirectory])
- for modname in ('__init__', 'authobjs', 'wfobjs'):
- self.vreg.load_file(join(etdirectory, '%s.py' % modname),
- 'cubicweb.entities.%s' % modname)
- hooksdirectory = join(CW_SOFTWARE_ROOT, 'hooks')
- self.vreg.load_file(join(hooksdirectory, 'metadata.py'),
- 'cubicweb.hooks.metadata')
- elif config.read_instance_schema:
+ self.set_schema(config.load_schema())
+ else:
# normal start: load the instance schema from the database
self.fill_schema()
- else:
- # test start: use the file system schema (quicker)
- self.warning("set fs instance'schema")
- config.bootstrap_cubes()
- self.set_schema(config.load_schema())
if not config.creating:
self.init_sources_from_database()
if 'CWProperty' in self.schema:
@@ -767,10 +754,10 @@
raise `AuthenticationError` if the authentication failed
raise `ConnectionError` if we can't open a connection
"""
+ cnxprops = kwargs.pop('cnxprops', None)
# use an internal connection
with self.internal_session() as session:
# try to get a user object
- cnxprops = kwargs.pop('cnxprops', None)
user = self.authenticate_user(session, login, **kwargs)
session = Session(user, self, cnxprops)
user._cw = user.cw_rset.req = session
@@ -921,22 +908,9 @@
* update user information on each user's request (i.e. groups and
custom properties)
"""
- session = self._get_session(sessionid, setcnxset=False)
- if props is not None:
- self.set_session_props(sessionid, props)
- user = session.user
+ user = self._get_session(sessionid, setcnxset=False).user
return user.eid, user.login, user.groups, user.properties
- def set_session_props(self, sessionid, props):
- """this method should be used by client to:
- * check session id validity
- * update user information on each user's request (i.e. groups and
- custom properties)
- """
- session = self._get_session(sessionid, setcnxset=False)
- for prop, value in props.items():
- session.change_property(prop, value)
-
def undoable_transactions(self, sessionid, ueid=None, txid=None,
**actionfilters):
"""See :class:`cubicweb.dbapi.Connection.undoable_transactions`"""
@@ -1239,7 +1213,7 @@
source = self.sources_by_eid[scleanup]
# delete remaining relations: if user can delete the entity, he can
# delete all its relations without security checking
- with security_enabled(session, read=False, write=False):
+ with session.security_enabled(read=False, write=False):
eid = entity.eid
for rschema, _, role in entity.e_schema.relation_definitions():
rtype = rschema.type
@@ -1281,7 +1255,7 @@
source = self.sources_by_eid[scleanup]
# delete remaining relations: if user can delete the entity, he can
# delete all its relations without security checking
- with security_enabled(session, read=False, write=False):
+ with session.security_enabled(read=False, write=False):
in_eids = ','.join([str(_e.eid) for _e in entities])
for rschema, _, role in entities[0].e_schema.relation_definitions():
rtype = rschema.type
@@ -1567,7 +1541,7 @@
rdef = session.rtype_eids_rdef(rtype, subjeid, objeid)
card = rdef.cardinality
if card[0] in '?1':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE X eid %%(x)s, '
'NOT Y eid %%(y)s' % rtype,
{'x': subjeid, 'y': objeid})
@@ -1578,7 +1552,7 @@
continue
subjects[subjeid] = len(relations_by_rtype[rtype]) - 1
if card[1] in '?1':
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
session.execute('DELETE X %s Y WHERE Y eid %%(y)s, '
'NOT X eid %%(x)s' % rtype,
{'x': subjeid, 'y': objeid})
--- a/server/serverctl.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/serverctl.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/session.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/session.py Wed Jan 09 16:06:20 2013 +0100
@@ -30,21 +30,16 @@
from logilab.common.deprecation import deprecated
from logilab.common.textutils import unormalize
from logilab.common.registry import objectify_predicate
-from rql import CoercionError
-from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
-from yams import BASE_TYPES
-from cubicweb import Binary, UnknownEid, QueryError, schema
+from cubicweb import UnknownEid, QueryError, schema, server
from cubicweb.req import RequestSessionBase
from cubicweb.dbapi import ConnectionProperties
-from cubicweb.utils import make_uid, RepeatList
+from cubicweb.utils import make_uid
from cubicweb.rqlrewrite import RQLRewriter
from cubicweb.server import ShuttingDown
from cubicweb.server.edition import EditedEntity
-ETYPE_PYOBJ_MAP[Binary] = 'Bytes'
-
NO_UNDO_TYPES = schema.SCHEMA_TYPES.copy()
NO_UNDO_TYPES.add('CWCache')
# is / is_instance_of are usually added by sql hooks except when using
@@ -55,25 +50,6 @@
NO_UNDO_TYPES.add('cw_source')
# XXX rememberme,forgotpwd,apycot,vcsfile
-def _make_description(selected, args, solution):
- """return a description for a result set"""
- description = []
- for term in selected:
- description.append(term.get_type(solution, args))
- return description
-
-def selection_idx_type(i, rqlst, args):
- """try to return type of term at index `i` of the rqlst's selection"""
- for select in rqlst.children:
- term = select.selection[i]
- for solution in select.solutions:
- try:
- ttype = term.get_type(solution, args)
- if ttype is not None:
- return ttype
- except CoercionError:
- return None
-
@objectify_predicate
def is_user_session(cls, req, **kwargs):
"""repository side only predicate returning 1 if the session is a regular
@@ -132,6 +108,11 @@
with hooks_control(self.session, self.session.HOOKS_DENY_ALL, 'integrity'):
# ... do stuff with none but 'integrity' hooks activated
+
+ This is an internal api, you should rather use
+ :meth:`~cubicweb.server.session.Session.deny_all_hooks_but` or
+ :meth:`~cubicweb.server.session.Session.allow_all_hooks_but` session
+ methods.
"""
def __init__(self, session, mode, *categories):
self.session = session
@@ -241,7 +222,11 @@
:attr:`running_dbapi_query`, boolean flag telling if the executing query
is coming from a dbapi connection or is a query from within the repository
+
+ .. automethod:: cubicweb.server.session.deny_all_hooks_but
+ .. automethod:: cubicweb.server.session.all_all_hooks_but
"""
+ is_request = False
is_internal_session = False
def __init__(self, user, repo, cnxprops=None, _id=None):
@@ -264,7 +249,7 @@
# and the rql server
self.data = {}
# i18n initialization
- self.set_language(cnxprops.lang)
+ self.set_language(user.prefered_language())
# internals
self._tx_data = {}
self.__threaddata = threading.local()
@@ -464,28 +449,6 @@
self.cnxset.reconnect(source)
return source.doexec(self, sql, args, rollback=rollback_on_failure)
- def set_language(self, language):
- """i18n configuration for translation"""
- language = language or self.user.property_value('ui.language')
- try:
- gettext, pgettext = self.vreg.config.translations[language]
- self._ = self.__ = gettext
- self.pgettext = pgettext
- except KeyError:
- language = self.vreg.property_value('ui.language')
- try:
- gettext, pgettext = self.vreg.config.translations[language]
- self._ = self.__ = gettext
- self.pgettext = pgettext
- except KeyError:
- self._ = self.__ = unicode
- self.pgettext = lambda x, y: y
- self.lang = language
-
- def change_property(self, prop, value):
- assert prop == 'lang' # this is the only one changeable property for now
- self.set_language(value)
-
def deleted_in_transaction(self, eid):
"""return True if the entity of the given eid is being deleted in the
current transaction
@@ -509,7 +472,7 @@
DEFAULT_SECURITY = object() # evaluated to true by design
- def security_enabled(self, read=False, write=False):
+ def security_enabled(self, read=None, write=None):
return security_enabled(self, read=read, write=write)
def init_security(self, read, write):
@@ -976,16 +939,21 @@
# information:
# - processed by the precommit/commit event or not
# - if processed, is it the failed operation
+ debug = server.DEBUG & server.DBG_OPS
try:
# by default, operations are executed with security turned off
with security_enabled(self, False, False):
processed = []
self.commit_state = 'precommit'
+ if debug:
+ print self.commit_state, '*' * 20
try:
while self.pending_operations:
operation = self.pending_operations.pop(0)
operation.processed = 'precommit'
processed.append(operation)
+ if debug:
+ print operation
operation.handle_event('precommit_event')
self.pending_operations[:] = processed
self.debug('precommit session %s done', self.id)
@@ -1001,7 +969,11 @@
# instead of having to implements rollback, revertprecommit
# and revertcommit, that will be enough in mont case.
operation.failed = True
+ if debug:
+ print self.commit_state, '*' * 20
for operation in reversed(processed):
+ if debug:
+ print operation
try:
operation.handle_event('revertprecommit_event')
except BaseException:
@@ -1014,8 +986,12 @@
raise
self.cnxset.commit()
self.commit_state = 'postcommit'
+ if debug:
+ print self.commit_state, '*' * 20
while self.pending_operations:
operation = self.pending_operations.pop(0)
+ if debug:
+ print operation
operation.processed = 'postcommit'
try:
operation.handle_event('postcommit_event')
@@ -1154,71 +1130,6 @@
self._threaddata._rewriter = RQLRewriter(self)
return self._threaddata._rewriter
- def build_description(self, rqlst, args, result):
- """build a description for a given result"""
- if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
- # easy, all lines are identical
- selected = rqlst.children[0].selection
- solution = rqlst.children[0].solutions[0]
- description = _make_description(selected, args, solution)
- return RepeatList(len(result), tuple(description))
- # hard, delegate the work :o)
- return self.manual_build_descr(rqlst, args, result)
-
- def manual_build_descr(self, rqlst, args, result):
- """build a description for a given result by analysing each row
-
- XXX could probably be done more efficiently during execution of query
- """
- # not so easy, looks for variable which changes from one solution
- # to another
- unstables = rqlst.get_variable_indices()
- basedescr = []
- todetermine = []
- for i in xrange(len(rqlst.children[0].selection)):
- ttype = selection_idx_type(i, rqlst, args)
- if ttype is None or ttype == 'Any':
- ttype = None
- isfinal = True
- else:
- isfinal = ttype in BASE_TYPES
- if ttype is None or i in unstables:
- basedescr.append(None)
- todetermine.append( (i, isfinal) )
- else:
- basedescr.append(ttype)
- if not todetermine:
- return RepeatList(len(result), tuple(basedescr))
- return self._build_descr(result, basedescr, todetermine)
-
- def _build_descr(self, result, basedescription, todetermine):
- description = []
- etype_from_eid = self.describe
- todel = []
- for i, row in enumerate(result):
- row_descr = basedescription[:]
- for index, isfinal in todetermine:
- value = row[index]
- if value is None:
- # None value inserted by an outer join, no type
- row_descr[index] = None
- continue
- if isfinal:
- row_descr[index] = etype_from_pyobj(value)
- else:
- try:
- row_descr[index] = etype_from_eid(value)[0]
- except UnknownEid:
- self.error('wrong eid %s in repository, you should '
- 'db-check the database' % value)
- todel.append(i)
- break
- else:
- description.append(tuple(row_descr))
- for i in reversed(todel):
- del result[i]
- return description
-
# deprecated ###############################################################
@deprecated('[3.13] use getattr(session.rtype_eids_rdef(rtype, eidfrom, eidto), prop)')
@@ -1317,6 +1228,24 @@
return 'en'
return None
+ def prefered_language(self, language=None):
+ # mock CWUser.prefered_language, mainly for testing purpose
+ return self.property_value('ui.language')
+
+ # CWUser compat for notification ###########################################
+
+ def name(self):
+ return 'cubicweb'
+
+ class _IEmailable:
+ @staticmethod
+ def get_email():
+ return ''
+
+ def cw_adapt_to(self, iface):
+ if iface == 'IEmailable':
+ return self._IEmailable
+ return None
from logging import getLogger
from cubicweb import set_log_methods
--- a/server/sources/__init__.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/sources/__init__.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/sources/datafeed.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/sources/datafeed.py Wed Jan 09 16:06:20 2013 +0100
@@ -406,7 +406,7 @@
attrs = dict( (k, v) for k, v in attrs.iteritems()
if v != getattr(entity, k))
if attrs:
- entity.set_attributes(**attrs)
+ entity.cw_set(**attrs)
self.notify_updated(entity)
--- a/server/sources/native.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/sources/native.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -961,6 +961,14 @@
cnx.commit()
return eid
+ def _handle_is_relation_sql(self, session, sql, attrs):
+ """ Handler for specific is_relation sql that may be
+ overwritten in some stores"""
+ self.doexec(session, sql % attrs)
+
+ _handle_insert_entity_sql = doexec
+ _handle_is_instance_of_sql = _handle_source_relation_sql = _handle_is_relation_sql
+
def add_info(self, session, entity, source, extid, complete):
"""add type and source info for an eid into the system table"""
# begin by inserting eid/type/source/extid into the entities table
@@ -970,21 +978,22 @@
uri = 'system' if source.copy_based_source else source.uri
attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
'source': uri, 'asource': source.uri, 'mtime': datetime.utcnow()}
- self.doexec(session, self.sqlgen.insert('entities', attrs), attrs)
+ self._handle_insert_entity_sql(session, self.sqlgen.insert('entities', attrs), attrs)
# insert core relations: is, is_instance_of and cw_source
try:
- self.doexec(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)'
- % (entity.eid, eschema_eid(session, entity.e_schema)))
+ self._handle_is_relation_sql(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, eschema_eid(session, entity.e_schema)))
except IndexError:
# during schema serialization, skip
pass
else:
for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
- self.doexec(session, 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)'
- % (entity.eid, eschema_eid(session, eschema)))
+ self._handle_is_relation_sql(session,
+ 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, eschema_eid(session, eschema)))
if 'CWSource' in self.schema and source.eid is not None: # else, cw < 3.10
- self.doexec(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) '
- 'VALUES (%s,%s)' % (entity.eid, source.eid))
+ self._handle_is_relation_sql(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) VALUES (%s,%s)',
+ (entity.eid, source.eid))
# now we can update the full text index
if self.do_fti and self.need_fti_indexation(entity.__regid__):
if complete:
--- a/server/sources/storages.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/sources/storages.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/ssplanner.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/ssplanner.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -27,7 +27,6 @@
from cubicweb import QueryError, typed_eid
from cubicweb.schema import VIRTUAL_RTYPES
from cubicweb.rqlrewrite import add_types_restriction
-from cubicweb.server.session import security_enabled
from cubicweb.server.edition import EditedEntity
READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
@@ -87,7 +86,7 @@
# the generated select substep if not emited (eg nothing
# to be selected)
if checkread and eid not in neweids:
- with security_enabled(session, read=False):
+ with session.security_enabled(read=False):
eschema(session.describe(eid)[0]).check_perm(
session, 'read', eid=eid)
eidconsts[lhs.variable] = eid
--- a/server/test/unittest_checkintegrity.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_checkintegrity.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -29,8 +29,9 @@
handler = get_test_db_handler(TestServerConfiguration(apphome=self.datadir))
handler.build_db_cache()
self.repo, self.cnx = handler.get_repo_and_cnx()
- self.execute = self.cnx.cursor().execute
- self.session = self.repo._sessions[self.cnx.sessionid]
+ session = self.repo._get_session(self.cnx.sessionid, setcnxset=True)
+ self.session = session
+ self.execute = session.execute
sys.stderr = sys.stdout = StringIO()
def tearDown(self):
--- a/server/test/unittest_datafeed.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_datafeed.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2011-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/test/unittest_hook.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_hook.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/test/unittest_msplanner.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_msplanner.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/test/unittest_multisources.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_multisources.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/server/test/unittest_querier.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_querier.py Wed Jan 09 16:06:20 2013 +0100
@@ -29,10 +29,10 @@
from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.utils import crypt_password
from cubicweb.server.sources.native import make_schema
+from cubicweb.server.querier import manual_build_descr, _make_description
from cubicweb.devtools import get_test_db_handler, TestServerConfiguration
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
-from unittest_session import Variable
class FixedOffset(tzinfo):
def __init__(self, hours=0):
@@ -87,6 +87,30 @@
del repo, cnx
+class Variable:
+ def __init__(self, name):
+ self.name = name
+ self.children = []
+
+ def get_type(self, solution, args=None):
+ return solution[self.name]
+ def as_string(self):
+ return self.name
+
+class Function:
+ def __init__(self, name, varname):
+ self.name = name
+ self.children = [Variable(varname)]
+ def get_type(self, solution, args=None):
+ return 'Int'
+
+class MakeDescriptionTC(TestCase):
+ def test_known_values(self):
+ solution = {'A': 'Int', 'B': 'CWUser'}
+ self.assertEqual(_make_description((Function('max', 'A'), Variable('B')), {}, solution),
+ ['Int','CWUser'])
+
+
class UtilsTC(BaseQuerierTC):
setUpClass = classmethod(setUpClass)
tearDownClass = classmethod(tearDownClass)
@@ -242,6 +266,28 @@
rset = self.execute('Any %(x)s', {'x': u'str'})
self.assertEqual(rset.description[0][0], 'String')
+ def test_build_descr1(self):
+ rset = self.execute('(Any U,L WHERE U login L) UNION (Any G,N WHERE G name N, G is CWGroup)')
+ rset.req = self.session
+ orig_length = len(rset)
+ rset.rows[0][0] = 9999999
+ description = manual_build_descr(rset.req, rset.syntax_tree(), None, rset.rows)
+ self.assertEqual(len(description), orig_length - 1)
+ self.assertEqual(len(rset.rows), orig_length - 1)
+ self.assertNotEqual(rset.rows[0][0], 9999999)
+
+ def test_build_descr2(self):
+ rset = self.execute('Any X,Y WITH X,Y BEING ((Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G))')
+ for x, y in rset.description:
+ if y is not None:
+ self.assertEqual(y, 'CWGroup')
+
+ def test_build_descr3(self):
+ rset = self.execute('(Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G)')
+ for x, y in rset.description:
+ if y is not None:
+ self.assertEqual(y, 'CWGroup')
+
class QuerierTC(BaseQuerierTC):
setUpClass = classmethod(setUpClass)
--- a/server/test/unittest_repository.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_repository.py Wed Jan 09 16:06:20 2013 +0100
@@ -522,7 +522,7 @@
self.commit()
self.assertEqual(len(c.reverse_fiche), 1)
- def test_set_attributes_in_before_update(self):
+ def test_cw_set_in_before_update(self):
# local hook
class DummyBeforeHook(Hook):
__regid__ = 'dummy-before-hook'
@@ -534,31 +534,31 @@
pendings = self._cw.transaction_data.setdefault('pending', set())
if self.entity.eid not in pendings:
pendings.add(self.entity.eid)
- self.entity.set_attributes(alias=u'foo')
+ self.entity.cw_set(alias=u'foo')
with self.temporary_appobjects(DummyBeforeHook):
req = self.request()
addr = req.create_entity('EmailAddress', address=u'a@b.fr')
- addr.set_attributes(address=u'a@b.com')
+ addr.cw_set(address=u'a@b.com')
rset = self.execute('Any A,AA WHERE X eid %(x)s, X address A, X alias AA',
{'x': addr.eid})
self.assertEqual(rset.rows, [[u'a@b.com', u'foo']])
- def test_set_attributes_in_before_add(self):
+ def test_cw_set_in_before_add(self):
# local hook
class DummyBeforeHook(Hook):
__regid__ = 'dummy-before-hook'
__select__ = Hook.__select__ & is_instance('EmailAddress')
events = ('before_add_entity',)
def __call__(self):
- # set_attributes is forbidden within before_add_entity()
- self.entity.set_attributes(alias=u'foo')
+ # cw_set is forbidden within before_add_entity()
+ self.entity.cw_set(alias=u'foo')
with self.temporary_appobjects(DummyBeforeHook):
req = self.request()
# XXX will fail with python -O
self.assertRaises(AssertionError, req.create_entity,
'EmailAddress', address=u'a@b.fr')
- def test_multiple_edit_set_attributes(self):
+ def test_multiple_edit_cw_set(self):
"""make sure cw_edited doesn't get cluttered
by previous entities on multiple set
"""
@@ -664,7 +664,7 @@
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEqual(rset.rows, [])
- req.user.set_relations(use_email=toto)
+ req.user.cw_set(use_email=toto)
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEqual(rset.rows, [[req.user.eid]])
@@ -674,11 +674,11 @@
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEqual(rset.rows, [])
tutu = req.create_entity('EmailAddress', address=u'tutu@logilab.fr')
- req.user.set_relations(use_email=tutu)
+ req.user.cw_set(use_email=tutu)
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'tutu'})
self.assertEqual(rset.rows, [[req.user.eid]])
- tutu.set_attributes(address=u'hip@logilab.fr')
+ tutu.cw_set(address=u'hip@logilab.fr')
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'tutu'})
self.assertEqual(rset.rows, [])
@@ -790,7 +790,7 @@
personnes.append(p)
abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M')
for j in xrange(0, 2000, 100):
- abraham.set_relations(personne_composite=personnes[j:j+100])
+ abraham.cw_set(personne_composite=personnes[j:j+100])
t1 = time.time()
self.info('creation: %.2gs', (t1 - t0))
req.cnx.commit()
@@ -816,7 +816,7 @@
t1 = time.time()
self.info('creation: %.2gs', (t1 - t0))
for j in xrange(100, 2000, 100):
- abraham.set_relations(personne_composite=personnes[j:j+100])
+ abraham.cw_set(personne_composite=personnes[j:j+100])
t2 = time.time()
self.info('more relations: %.2gs', (t2-t1))
req.cnx.commit()
@@ -836,7 +836,7 @@
t1 = time.time()
self.info('creation: %.2gs', (t1 - t0))
for j in xrange(100, 2000, 100):
- abraham.set_relations(personne_inlined=personnes[j:j+100])
+ abraham.cw_set(personne_inlined=personnes[j:j+100])
t2 = time.time()
self.info('more relations: %.2gs', (t2-t1))
req.cnx.commit()
@@ -917,7 +917,7 @@
p1 = req.create_entity('Personne', nom=u'Vincent')
p2 = req.create_entity('Personne', nom=u'Florent')
w = req.create_entity('Affaire', ref=u'wc')
- w.set_relations(todo_by=[p1,p2])
+ w.cw_set(todo_by=[p1,p2])
w.cw_clear_all_caches()
self.commit()
self.assertEqual(len(w.todo_by), 1)
@@ -928,9 +928,9 @@
p1 = req.create_entity('Personne', nom=u'Vincent')
p2 = req.create_entity('Personne', nom=u'Florent')
w = req.create_entity('Affaire', ref=u'wc')
- w.set_relations(todo_by=p1)
+ w.cw_set(todo_by=p1)
self.commit()
- w.set_relations(todo_by=p2)
+ w.cw_set(todo_by=p2)
w.cw_clear_all_caches()
self.commit()
self.assertEqual(len(w.todo_by), 1)
--- a/server/test/unittest_security.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_security.py Wed Jan 09 16:06:20 2013 +0100
@@ -473,9 +473,9 @@
anon = cu.connection.user(self.session)
# anonymous user can only read itself
rset = cu.execute('Any L WHERE X owned_by U, U login L')
- self.assertEqual(rset.rows, [['anon']])
+ self.assertEqual([['anon']], rset.rows)
rset = cu.execute('CWUser X')
- self.assertEqual(rset.rows, [[anon.eid]])
+ self.assertEqual([[anon.eid]], rset.rows)
# anonymous user can read groups (necessary to check allowed transitions for instance)
self.assert_(cu.execute('CWGroup X'))
# should only be able to read the anonymous user, not another one
@@ -488,7 +488,7 @@
# {'x': self.user.eid})
rset = cu.execute('CWUser X WHERE X eid %(x)s', {'x': anon.eid})
- self.assertEqual(rset.rows, [[anon.eid]])
+ self.assertEqual([[anon.eid]], rset.rows)
# but can't modify it
cu.execute('SET X login "toto" WHERE X eid %(x)s', {'x': anon.eid})
self.assertRaises(Unauthorized, self.commit)
--- a/server/test/unittest_session.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_session.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -17,33 +17,7 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
from __future__ import with_statement
-from logilab.common.testlib import TestCase, unittest_main, mock_object
-
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.server.session import _make_description, hooks_control
-
-class Variable:
- def __init__(self, name):
- self.name = name
- self.children = []
-
- def get_type(self, solution, args=None):
- return solution[self.name]
- def as_string(self):
- return self.name
-
-class Function:
- def __init__(self, name, varname):
- self.name = name
- self.children = [Variable(varname)]
- def get_type(self, solution, args=None):
- return 'Int'
-
-class MakeDescriptionTC(TestCase):
- def test_known_values(self):
- solution = {'A': 'Int', 'B': 'CWUser'}
- self.assertEqual(_make_description((Function('max', 'A'), Variable('B')), {}, solution),
- ['Int','CWUser'])
class InternalSessionTC(CubicWebTC):
@@ -61,7 +35,7 @@
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set())
self.assertEqual(len(session._tx_data), 1)
- with hooks_control(session, session.HOOKS_DENY_ALL, 'metadata'):
+ with session.deny_all_hooks_but('metadata'):
self.assertEqual(session.hooks_mode, session.HOOKS_DENY_ALL)
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set(('metadata',)))
@@ -73,7 +47,7 @@
self.assertEqual(session.hooks_mode, session.HOOKS_DENY_ALL)
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set(('metadata',)))
- with hooks_control(session, session.HOOKS_ALLOW_ALL, 'integrity'):
+ with session.allow_all_hooks_but('integrity'):
self.assertEqual(session.hooks_mode, session.HOOKS_ALLOW_ALL)
self.assertEqual(session.disabled_hook_categories, set(('integrity',)))
self.assertEqual(session.enabled_hook_categories, set(('metadata',))) # not changed in such case
@@ -88,27 +62,7 @@
self.assertEqual(session.disabled_hook_categories, set())
self.assertEqual(session.enabled_hook_categories, set())
- def test_build_descr1(self):
- rset = self.execute('(Any U,L WHERE U login L) UNION (Any G,N WHERE G name N, G is CWGroup)')
- orig_length = len(rset)
- rset.rows[0][0] = 9999999
- description = self.session.build_description(rset.syntax_tree(), None, rset.rows)
- self.assertEqual(len(description), orig_length - 1)
- self.assertEqual(len(rset.rows), orig_length - 1)
- self.assertFalse(rset.rows[0][0] == 9999999)
-
- def test_build_descr2(self):
- rset = self.execute('Any X,Y WITH X,Y BEING ((Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G))')
- for x, y in rset.description:
- if y is not None:
- self.assertEqual(y, 'CWGroup')
-
- def test_build_descr3(self):
- rset = self.execute('(Any G,NULL WHERE G is CWGroup) UNION (Any U,G WHERE U in_group G)')
- for x, y in rset.description:
- if y is not None:
- self.assertEqual(y, 'CWGroup')
-
if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
unittest_main()
--- a/server/test/unittest_storage.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_storage.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -99,7 +99,7 @@
f1 = self.create_file()
self.commit()
self.assertEqual(file(expected_filepath).read(), 'the-data')
- f1.set_attributes(data=Binary('the new data'))
+ f1.cw_set(data=Binary('the new data'))
self.rollback()
self.assertEqual(file(expected_filepath).read(), 'the-data')
f1.cw_delete()
@@ -118,7 +118,7 @@
def test_bfss_fs_importing_doesnt_touch_path(self):
self.session.transaction_data['fs_importing'] = True
filepath = osp.abspath(__file__)
- f1 = self.session.create_entity('File', data=Binary(filepath),
+ f1 = self.request().create_entity('File', data=Binary(filepath),
data_format=u'text/plain', data_name=u'foo')
self.assertEqual(self.fspath(f1), filepath)
@@ -196,15 +196,18 @@
filepath = osp.abspath(__file__)
f1 = self.session.create_entity('File', data=Binary(filepath),
data_format=u'text/plain', data_name=u'foo')
- self.assertEqual(f1.data.getvalue(), file(filepath).read(),
- 'files content differ')
+ cw_value = f1.data.getvalue()
+ fs_value = file(filepath).read()
+ if cw_value != fs_value:
+ self.fail('cw value %r is different from file content' % cw_value)
+
@tag('update')
def test_bfss_update_with_existing_data(self):
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
data_format=u'text/plain', data_name=u'foo')
- # NOTE: do not use set_attributes() which would automatically
+ # NOTE: do not use cw_set() which would automatically
# update f1's local dict. We want the pure rql version to work
self.execute('SET F data %(d)s WHERE F eid %(f)s',
{'d': Binary('some other data'), 'f': f1.eid})
@@ -218,7 +221,7 @@
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
data_format=u'text/plain', data_name=u'foo.txt')
- # NOTE: do not use set_attributes() which would automatically
+ # NOTE: do not use cw_set() which would automatically
# update f1's local dict. We want the pure rql version to work
self.commit()
old_path = self.fspath(f1)
@@ -240,7 +243,7 @@
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
data_format=u'text/plain', data_name=u'foo.txt')
- # NOTE: do not use set_attributes() which would automatically
+ # NOTE: do not use cw_set() which would automatically
# update f1's local dict. We want the pure rql version to work
self.commit()
old_path = self.fspath(f1)
@@ -265,7 +268,7 @@
f = self.session.create_entity('Affaire', opt_attr=Binary('toto'))
self.session.commit()
self.session.set_cnxset()
- f.set_attributes(opt_attr=None)
+ f.cw_set(opt_attr=None)
self.session.commit()
@tag('fs_importing', 'update')
--- a/server/test/unittest_undo.py Wed Jan 09 16:04:26 2013 +0100
+++ b/server/test/unittest_undo.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -203,7 +203,7 @@
c.cw_delete()
txuuid = self.commit()
c2 = session.create_entity('Card', title=u'hip', content=u'hip')
- p.set_relations(fiche=c2)
+ p.cw_set(fiche=c2)
self.commit()
self.assertUndoTransaction(txuuid, [
"Can't restore object relation fiche to entity "
@@ -217,7 +217,7 @@
session = self.session
g = session.create_entity('CWGroup', name=u'staff')
session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
- self.toto.set_relations(in_group=g)
+ self.toto.cw_set(in_group=g)
self.commit()
self.toto.cw_delete()
txuuid = self.commit()
@@ -228,6 +228,7 @@
"%s doesn't exist anymore." % g.eid])
with self.assertRaises(ValidationError) as cm:
self.commit()
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.entity, self.toto.eid)
self.assertEqual(cm.exception.errors,
{'in_group-subject': u'at least one relation in_group is '
@@ -265,7 +266,7 @@
email = self.request().create_entity('EmailAddress', address=u'tutu@cubicweb.org')
prop = self.request().create_entity('CWProperty', pkey=u'ui.default-text-format',
value=u'text/html')
- tutu.set_relations(use_email=email, reverse_for_user=prop)
+ tutu.cw_set(use_email=email, reverse_for_user=prop)
self.commit()
with self.assertRaises(ValidationError) as cm:
self.cnx.undo_transaction(txuuid)
@@ -278,7 +279,7 @@
g = session.create_entity('CWGroup', name=u'staff')
txuuid = self.commit()
session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
- self.toto.set_relations(in_group=g)
+ self.toto.cw_set(in_group=g)
self.commit()
with self.assertRaises(ValidationError) as cm:
self.cnx.undo_transaction(txuuid)
@@ -304,7 +305,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis', fiche=c)
self.commit()
- p.set_relations(fiche=None)
+ p.cw_set(fiche=None)
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
self.commit()
@@ -319,7 +320,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis', fiche=c)
self.commit()
- p.set_relations(fiche=None)
+ p.cw_set(fiche=None)
txuuid = self.commit()
c.cw_delete()
self.commit()
@@ -339,7 +340,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis')
self.commit()
- p.set_relations(fiche=c)
+ p.cw_set(fiche=c)
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
self.commit()
@@ -354,7 +355,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis')
self.commit()
- p.set_relations(fiche=c)
+ p.cw_set(fiche=c)
txuuid = self.commit()
c.cw_delete()
self.commit()
@@ -369,7 +370,7 @@
c2 = session.create_entity('Card', title=u'hip', content=u'hip')
p = session.create_entity('Personne', nom=u'louis', fiche=c1)
self.commit()
- p.set_relations(fiche=c2)
+ p.cw_set(fiche=c2)
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
self.commit()
@@ -385,7 +386,7 @@
c2 = session.create_entity('Card', title=u'hip', content=u'hip')
p = session.create_entity('Personne', nom=u'louis', fiche=c1)
self.commit()
- p.set_relations(fiche=c2)
+ p.cw_set(fiche=c2)
txuuid = self.commit()
c1.cw_delete()
self.commit()
@@ -401,7 +402,7 @@
p = session.create_entity('Personne', nom=u'toto')
session.commit()
self.session.set_cnxset()
- p.set_attributes(nom=u'titi')
+ p.cw_set(nom=u'titi')
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
p.cw_clear_all_caches()
@@ -412,7 +413,7 @@
p = session.create_entity('Personne', nom=u'toto')
session.commit()
self.session.set_cnxset()
- p.set_attributes(nom=u'titi')
+ p.cw_set(nom=u'titi')
txuuid = self.commit()
p.cw_delete()
self.commit()
--- a/sobjects/ldapparser.py Wed Jan 09 16:04:26 2013 +0100
+++ b/sobjects/ldapparser.py Wed Jan 09 16:06:20 2013 +0100
@@ -97,7 +97,7 @@
attrs = dict( (k, v) for k, v in attrs.iteritems()
if v != getattr(entity, k))
if attrs:
- entity.set_attributes(**attrs)
+ entity.cw_set(**attrs)
self.notify_updated(entity)
def ldap2cwattrs(self, sdict, tdict=None):
@@ -131,7 +131,7 @@
if entity.__regid__ == 'EmailAddress':
return
groups = [self._get_group(n) for n in self.source.user_default_groups]
- entity.set_relations(in_group=groups)
+ entity.cw_set(in_group=groups)
self._process_email(entity, sourceparams)
def is_deleted(self, extidplus, etype, eid):
@@ -160,9 +160,9 @@
email = self.extid2entity(emailextid, 'EmailAddress',
address=emailaddr)
if entity.primary_email:
- entity.set_relations(use_email=email)
+ entity.cw_set(use_email=email)
else:
- entity.set_relations(primary_email=email)
+ entity.cw_set(primary_email=email)
elif self.sourceuris:
# pop from sourceuris anyway, else email may be removed by the
# source once import is finished
--- a/test/unittest_cwconfig.py Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_cwconfig.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -101,10 +101,10 @@
self.assertEqual(self.config.expand_cubes(('email', 'comment')),
['email', 'comment', 'file'])
- def test_vregistry_path(self):
+ def test_appobjects_path(self):
self.config.__class__.CUBES_PATH = [CUSTOM_CUBES_DIR]
self.config.adjust_sys_path()
- self.assertEqual([unabsolutize(p) for p in self.config.vregistry_path()],
+ self.assertEqual([unabsolutize(p) for p in self.config.appobjects_path()],
['entities', 'web/views', 'sobjects', 'hooks',
'file/entities', 'file/views.py', 'file/hooks',
'email/entities.py', 'email/views', 'email/hooks.py',
--- a/test/unittest_dbapi.py Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_dbapi.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/test/unittest_entity.py Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_entity.py Wed Jan 09 16:06:20 2013 +0100
@@ -701,23 +701,23 @@
self.assertEqual(card4.rest_path(), unicode(card4.eid))
- def test_set_attributes(self):
+ def test_cw_set_attributes(self):
req = self.request()
person = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien')
self.assertEqual(person.prenom, u'adrien')
self.assertEqual(person.nom, u'di mascio')
- person.set_attributes(prenom=u'sylvain', nom=u'thénault')
+ person.cw_set(prenom=u'sylvain', nom=u'thénault')
person = self.execute('Personne P').get_entity(0, 0) # XXX retreival needed ?
self.assertEqual(person.prenom, u'sylvain')
self.assertEqual(person.nom, u'thénault')
- def test_set_relations(self):
+ def test_cw_set_relations(self):
req = self.request()
person = req.create_entity('Personne', nom=u'chauvat', prenom=u'nicolas')
note = req.create_entity('Note', type=u'x')
- note.set_relations(ecrit_par=person)
+ note.cw_set(ecrit_par=person)
note = req.create_entity('Note', type=u'y')
- note.set_relations(ecrit_par=person.eid)
+ note.cw_set(ecrit_par=person.eid)
self.assertEqual(len(person.reverse_ecrit_par), 2)
def test_metainformation_and_external_absolute_url(self):
--- a/test/unittest_migration.py Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_migration.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -108,7 +108,13 @@
self.assertEqual(source['db-driver'], 'sqlite')
handler = get_test_db_handler(config)
handler.init_test_database()
-
+ handler.build_db_cache()
+ repo, cnx = handler.get_repo_and_cnx()
+ cu = cnx.cursor()
+ self.assertEqual(cu.execute('Any SN WHERE X is CWUser, X login "admin", X in_state S, S name SN').rows,
+ [['activated']])
+ cnx.close()
+ repo.shutdown()
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_rset.py Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_rset.py Wed Jan 09 16:06:20 2013 +0100
@@ -112,6 +112,17 @@
# '%stask/title/go' % baseurl)
# empty _restpath should not crash
self.compare_urls(req.build_url('view', _restpath=''), baseurl)
+ self.assertNotIn('https', req.build_url('view', vid='foo', rql='yo',
+ __secure__=True))
+ try:
+ self.config.global_set_option('https-url', 'https://testing.fr/')
+ self.assertTrue('https', req.build_url('view', vid='foo', rql='yo',
+ __secure__=True))
+ self.compare_urls(req.build_url('view', vid='foo', rql='yo',
+ __secure__=True),
+ '%sview?vid=foo&rql=yo' % req.base_url(secure=True))
+ finally:
+ self.config.global_set_option('https-url', None)
def test_build(self):
--- a/test/unittest_schema.py Wed Jan 09 16:04:26 2013 +0100
+++ b/test/unittest_schema.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -350,8 +350,8 @@
class WorkflowShemaTC(CubicWebTC):
def test_trinfo_default_format(self):
- tr = self.session.user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
- self.assertEqual(tr.comment_format, 'text/plain')
+ tr = self.request().user.cw_adapt_to('IWorkflowable').fire_transition('deactivate')
+ self.assertEqual(tr.comment_format, 'text/plain')
if __name__ == '__main__':
unittest_main()
--- a/web/application.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/application.py Wed Jan 09 16:06:20 2013 +0100
@@ -462,7 +462,6 @@
result = self.notfound_content(req)
req.status_out = ex.status
except ValidationError, ex:
- req.status_out = httplib.CONFLICT
result = self.validation_error_handler(req, ex)
except RemoteCallFailed, ex:
result = self.ajax_error_handler(req, ex)
@@ -486,7 +485,7 @@
except (AuthenticationError, LogOut):
# the rollback is handled in the finally
raise
- ### Last defence line
+ ### Last defense line
except BaseException, ex:
result = self.error_handler(req, ex, tb=True)
finally:
@@ -517,7 +516,7 @@
return ''
def validation_error_handler(self, req, ex):
- ex.errors = dict((k, v) for k, v in ex.errors.items())
+ ex.translate(req._) # translate messages using ui language
if '__errorurl' in req.form:
forminfo = {'error': ex,
'values': req.form,
@@ -532,6 +531,7 @@
req.headers_out.setHeader('location', str(location))
req.status_out = httplib.SEE_OTHER
return ''
+ req.status_out = httplib.CONFLICT
return self.error_handler(req, ex, tb=False)
def error_handler(self, req, ex, tb=False):
--- a/web/data/cubicweb.ajax.js Wed Jan 09 16:04:26 2013 +0100
+++ b/web/data/cubicweb.ajax.js Wed Jan 09 16:06:20 2013 +0100
@@ -70,7 +70,7 @@
callback.apply(null, args);
}
} catch(error) {
- this.error(this.xhr, null, error);
+ this.error(this._req, null, error);
}
},
@@ -704,7 +704,7 @@
var ajaxArgs = ['render', formparams, registry, compid];
ajaxArgs = ajaxArgs.concat(cw.utils.sliceList(arguments, 4));
var params = ajaxFuncArgs.apply(null, ajaxArgs);
- return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap');
+ return $('#'+domid).loadxhtml(AJAX_BASE_URL, params, null, 'swap', true);
}
/* ajax tabs ******************************************************************/
--- a/web/data/cubicweb.css Wed Jan 09 16:04:26 2013 +0100
+++ b/web/data/cubicweb.css Wed Jan 09 16:06:20 2013 +0100
@@ -545,6 +545,16 @@
padding-left: 2em;
}
+/* actions around tables */
+.tableactions span {
+ padding: 0 18px;
+ height: 24px;
+ background: #F8F8F8;
+ border: 1px solid #DFDFDF;
+ border-bottom: none;
+ border-radius: 4px 4px 0 0;
+}
+
/* custom boxes */
.search_box div.boxBody {
--- a/web/data/cubicweb.facets.js Wed Jan 09 16:04:26 2013 +0100
+++ b/web/data/cubicweb.facets.js Wed Jan 09 16:06:20 2013 +0100
@@ -68,6 +68,14 @@
var bkUrl = $bkLink.attr('cubicweb:target') + '&path=' + encodeURIComponent(bkPath);
$bkLink.attr('href', bkUrl);
}
+ var $focusLink = jQuery('#focusLink');
+ if ($focusLink.length) {
+ var url = baseuri()+ 'view?rql=' + encodeURIComponent(rql);
+ if (vid) {
+ url += '&vid=' + encodeURIComponent(vid);
+ }
+ $focusLink.attr('href', url);
+ }
var toupdate = result[1];
var extraparams = vidargs;
if (paginate) { extraparams['paginate'] = '1'; } // XXX in vidargs
--- a/web/data/cubicweb.old.css Wed Jan 09 16:04:26 2013 +0100
+++ b/web/data/cubicweb.old.css Wed Jan 09 16:06:20 2013 +0100
@@ -899,6 +899,16 @@
padding-left: 0.5em;
}
+/* actions around tables */
+.tableactions span {
+ padding: 0 18px;
+ height: 24px;
+ background: #F8F8F8;
+ border: 1px solid #DFDFDF;
+ border-bottom: none;
+ border-radius: 4px 4px 0 0;
+}
+
/***************************************/
/* error view (views/management.py) */
/***************************************/
--- a/web/httpcache.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/httpcache.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/request.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/request.py Wed Jan 09 16:06:20 2013 +0100
@@ -170,7 +170,6 @@
@property
def authmode(self):
"""Authentification mode of the instance
-
(see :ref:`WebServerConfig`)"""
return self.vreg.config['auth-mode']
@@ -227,14 +226,6 @@
# 3. default language
self.set_default_language(vreg)
- def set_language(self, lang):
- gettext, self.pgettext = self.translations[lang]
- self._ = self.__ = gettext
- self.lang = lang
- self.debug('request language: %s', lang)
- if self.cnx:
- self.cnx.set_session_props(lang=lang)
-
# input form parameters management ########################################
# common form parameters which should be protected against html values
@@ -366,7 +357,7 @@
def update_search_state(self):
"""update the current search state"""
searchstate = self.form.get('__mode')
- if not searchstate and self.cnx:
+ if not searchstate:
searchstate = self.session.data.get('search_state', 'normal')
self.set_search_state(searchstate)
@@ -377,8 +368,7 @@
else:
self.search_state = ('linksearch', searchstate.split(':'))
assert len(self.search_state[-1]) == 4
- if self.cnx:
- self.session.data['search_state'] = searchstate
+ self.session.data['search_state'] = searchstate
def match_search_state(self, rset):
"""when searching an entity to create a relation, return True if entities in
--- a/web/test/data/views.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/data/views.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -15,9 +15,7 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
-"""
from cubicweb.web import Redirect
from cubicweb.web.application import CubicWebPublisher
--- a/web/test/unittest_application.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_application.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/test/unittest_magicsearch.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_magicsearch.py Wed Jan 09 16:06:20 2013 +0100
@@ -230,5 +230,118 @@
self.assertEqual(rset.rql, 'Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s')
self.assertEqual(rset.args, {'text': u'utilisateur Smith'})
+
+class RQLSuggestionsBuilderTC(CubicWebTC):
+ def suggestions(self, rql):
+ req = self.request()
+ rbs = self.vreg['components'].select('rql.suggestions', req)
+ return rbs.build_suggestions(rql)
+
+ def test_no_restrictions_rql(self):
+ self.assertListEqual([], self.suggestions(''))
+ self.assertListEqual([], self.suggestions('An'))
+ self.assertListEqual([], self.suggestions('Any X'))
+ self.assertListEqual([], self.suggestions('Any X, Y'))
+
+ def test_invalid_rql(self):
+ self.assertListEqual([], self.suggestions('blabla'))
+ self.assertListEqual([], self.suggestions('Any X WHERE foo, bar'))
+
+ def test_is_rql(self):
+ self.assertListEqual(['Any X WHERE X is %s' % eschema
+ for eschema in sorted(self.vreg.schema.entities())
+ if not eschema.final],
+ self.suggestions('Any X WHERE X is'))
+
+ self.assertListEqual(['Any X WHERE X is Personne', 'Any X WHERE X is Project'],
+ self.suggestions('Any X WHERE X is P'))
+
+ self.assertListEqual(['Any X WHERE X is Personne, Y is Personne',
+ 'Any X WHERE X is Personne, Y is Project'],
+ self.suggestions('Any X WHERE X is Personne, Y is P'))
+
+
+ def test_relations_rql(self):
+ self.assertListEqual(['Any X WHERE X is Personne, X ass A',
+ 'Any X WHERE X is Personne, X datenaiss A',
+ 'Any X WHERE X is Personne, X description A',
+ 'Any X WHERE X is Personne, X fax A',
+ 'Any X WHERE X is Personne, X nom A',
+ 'Any X WHERE X is Personne, X prenom A',
+ 'Any X WHERE X is Personne, X promo A',
+ 'Any X WHERE X is Personne, X salary A',
+ 'Any X WHERE X is Personne, X sexe A',
+ 'Any X WHERE X is Personne, X tel A',
+ 'Any X WHERE X is Personne, X test A',
+ 'Any X WHERE X is Personne, X titre A',
+ 'Any X WHERE X is Personne, X travaille A',
+ 'Any X WHERE X is Personne, X web A',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X '))
+ self.assertListEqual(['Any X WHERE X is Personne, X tel A',
+ 'Any X WHERE X is Personne, X test A',
+ 'Any X WHERE X is Personne, X titre A',
+ 'Any X WHERE X is Personne, X travaille A',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X t'))
+ # try completion on selected
+ self.assertListEqual(['Any X WHERE X is Personne, Y is Societe, X tel A',
+ 'Any X WHERE X is Personne, Y is Societe, X test A',
+ 'Any X WHERE X is Personne, Y is Societe, X titre A',
+ 'Any X WHERE X is Personne, Y is Societe, X travaille Y',
+ ],
+ self.suggestions('Any X WHERE X is Personne, Y is Societe, X t'))
+ # invalid relation should not break
+ self.assertListEqual([],
+ self.suggestions('Any X WHERE X is Personne, X asdasd'))
+
+ def test_attribute_vocabulary_rql(self):
+ self.assertListEqual(['Any X WHERE X is Personne, X promo "bon"',
+ 'Any X WHERE X is Personne, X promo "pasbon"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X promo "'))
+ self.assertListEqual(['Any X WHERE X is Personne, X promo "pasbon"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X promo "p'))
+ # "bon" should be considered complete, hence no suggestion
+ self.assertListEqual([],
+ self.suggestions('Any X WHERE X is Personne, X promo "bon"'))
+ # no valid vocabulary starts with "po"
+ self.assertListEqual([],
+ self.suggestions('Any X WHERE X is Personne, X promo "po'))
+
+ def test_attribute_value_rql(self):
+ # suggestions should contain any possible value for
+ # a given attribute (limited to 10)
+ req = self.request()
+ for i in xrange(15):
+ req.create_entity('Personne', nom=u'n%s' % i, prenom=u'p%s' % i)
+ self.assertListEqual(['Any X WHERE X is Personne, X nom "n0"',
+ 'Any X WHERE X is Personne, X nom "n1"',
+ 'Any X WHERE X is Personne, X nom "n10"',
+ 'Any X WHERE X is Personne, X nom "n11"',
+ 'Any X WHERE X is Personne, X nom "n12"',
+ 'Any X WHERE X is Personne, X nom "n13"',
+ 'Any X WHERE X is Personne, X nom "n14"',
+ 'Any X WHERE X is Personne, X nom "n2"',
+ 'Any X WHERE X is Personne, X nom "n3"',
+ 'Any X WHERE X is Personne, X nom "n4"',
+ 'Any X WHERE X is Personne, X nom "n5"',
+ 'Any X WHERE X is Personne, X nom "n6"',
+ 'Any X WHERE X is Personne, X nom "n7"',
+ 'Any X WHERE X is Personne, X nom "n8"',
+ 'Any X WHERE X is Personne, X nom "n9"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X nom "'))
+ self.assertListEqual(['Any X WHERE X is Personne, X nom "n1"',
+ 'Any X WHERE X is Personne, X nom "n10"',
+ 'Any X WHERE X is Personne, X nom "n11"',
+ 'Any X WHERE X is Personne, X nom "n12"',
+ 'Any X WHERE X is Personne, X nom "n13"',
+ 'Any X WHERE X is Personne, X nom "n14"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X nom "n1'))
+
+
if __name__ == '__main__':
unittest_main()
--- a/web/test/unittest_reledit.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_reledit.py Wed Jan 09 16:06:20 2013 +0100
@@ -175,8 +175,8 @@
def setup_database(self):
super(ClickAndEditFormUICFGTC, self).setup_database()
- self.tick.set_relations(concerns=self.proj)
- self.proj.set_relations(manager=self.toto)
+ self.tick.cw_set(concerns=self.proj)
+ self.proj.cw_set(manager=self.toto)
def test_with_uicfg(self):
old_rctl = reledit_ctrl._tagdefs.copy()
--- a/web/test/unittest_urlrewrite.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_urlrewrite.py Wed Jan 09 16:06:20 2013 +0100
@@ -60,7 +60,6 @@
('/doc/images/(.+?)/?$', dict(fid='\\1', vid='wdocimages')),
('/doc/?$', dict(fid='main', vid='wdoc')),
('/doc/(.+?)/?$', dict(fid='\\1', vid='wdoc')),
- ('/changelog/?$', dict(vid='changelog')),
# now in SchemaBasedRewriter
#('/search/(.+)$', dict(rql=r'Any X WHERE X has_text "\1"')),
])
@@ -105,9 +104,9 @@
def setup_database(self):
req = self.request()
self.p1 = self.create_user(req, u'user1')
- self.p1.set_attributes(firstname=u'joe', surname=u'Dalton')
+ self.p1.cw_set(firstname=u'joe', surname=u'Dalton')
self.p2 = self.create_user(req, u'user2')
- self.p2.set_attributes(firstname=u'jack', surname=u'Dalton')
+ self.p2.cw_set(firstname=u'jack', surname=u'Dalton')
def test_rgx_action_with_transforms(self):
class TestSchemaBasedRewriter(SchemaBasedRewriter):
--- a/web/test/unittest_views_basecontrollers.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_views_basecontrollers.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -77,6 +77,7 @@
}
with self.assertRaises(ValidationError) as cm:
self.ctrl_publish(req)
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.errors, {'login-subject': 'the value "admin" is already used, use another one'})
def test_user_editing_itself(self):
@@ -249,6 +250,7 @@
}
with self.assertRaises(ValidationError) as cm:
self.ctrl_publish(req)
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.errors, {'amount-subject': 'value -10 must be >= 0'})
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
@@ -259,6 +261,7 @@
}
with self.assertRaises(ValidationError) as cm:
self.ctrl_publish(req)
+ cm.exception.translate(unicode)
self.assertEqual(cm.exception.errors, {'amount-subject': 'value 110 must be <= 100'})
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
--- a/web/test/unittest_views_basetemplates.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_views_basetemplates.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/test/unittest_views_searchrestriction.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_views_searchrestriction.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/test/unittest_viewselector.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/test/unittest_viewselector.py Wed Jan 09 16:06:20 2013 +0100
@@ -39,7 +39,6 @@
actions.LogoutAction]
SITEACTIONS = [actions.ManageAction]
FOOTERACTIONS = [wdoc.HelpAction,
- wdoc.ChangeLogAction,
wdoc.AboutAction,
actions.PoweredByAction]
MANAGEACTIONS = [actions.SiteConfigurationAction,
@@ -93,8 +92,7 @@
def test_possible_views_none_rset(self):
req = self.request()
self.assertListEqual(self.pviews(req, None),
- [('changelog', wdoc.ChangeLogView),
- ('cw.sources-management', cwsources.CWSourcesManagementView),
+ [('cw.sources-management', cwsources.CWSourcesManagementView),
('cw.users-and-groups-management', cwuser.UsersAndGroupsManagementView),
('gc', debug.GCView),
('index', startup.IndexView),
--- a/web/views/ajaxcontroller.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/ajaxcontroller.py Wed Jan 09 16:06:20 2013 +0100
@@ -28,7 +28,7 @@
functions that can be called from the javascript world.
To register a new remote function, either decorate your function
-with the :func:`cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:
+with the :func:`~cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:
.. sourcecode:: python
@@ -39,7 +39,7 @@
def list_users(self):
return [u for (u,) in self._cw.execute('Any L WHERE U login L')]
-or inherit from :class:`cubicwbe.web.views.ajaxcontroller.AjaxFunction` and
+or inherit from :class:`~cubicweb.web.views.ajaxcontroller.AjaxFunction` and
implement the ``__call__`` method:
.. sourcecode:: python
--- a/web/views/authentication.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/authentication.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/views/basecomponents.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/basecomponents.py Wed Jan 09 16:06:20 2013 +0100
@@ -59,6 +59,14 @@
# display multilines query as one line
rql = rset is not None and rset.printable_rql(encoded=False) or req.form.get('rql', '')
rql = rql.replace(u"\n", u" ")
+ rql_suggestion_comp = self._cw.vreg['components'].select_or_none('rql.suggestions', self._cw)
+ if rql_suggestion_comp is not None:
+ # enable autocomplete feature only if the rql
+ # suggestions builder is available
+ self._cw.add_css('jquery.ui.css')
+ self._cw.add_js(('cubicweb.ajax.js', 'jquery.ui.js'))
+ self._cw.add_onload('$("#rql").autocomplete({source: "%s"});'
+ % (req.build_url('json', fname='rql_suggest')))
self.w(u'''<div id="rqlinput" class="%s"><form action="%s"><fieldset>
<input type="text" id="rql" name="rql" value="%s" title="%s" tabindex="%s" accesskey="q" class="searchField" />
''' % (not self.cw_propval('visible') and 'hidden' or '',
--- a/web/views/basecontrollers.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/basecontrollers.py Wed Jan 09 16:06:20 2013 +0100
@@ -191,6 +191,7 @@
def _validation_error(req, ex):
req.cnx.rollback()
+ ex.translate(req._) # translate messages using ui language
# XXX necessary to remove existant validation error?
# imo (syt), it's not necessary
req.session.data.pop(req.form.get('__errorurl'), None)
--- a/web/views/editcontroller.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/editcontroller.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/views/facets.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/facets.py Wed Jan 09 16:06:20 2013 +0100
@@ -26,6 +26,7 @@
from logilab.common.decorators import cachedproperty
from logilab.common.registry import objectify_predicate, yes
+from cubicweb import tags
from cubicweb.predicates import (non_final_entity, multi_lines_rset,
match_context_prop, relation_possible)
from cubicweb.utils import json_dumps
@@ -234,6 +235,7 @@
vid = req.form.get('vid')
if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm(req, 'add'):
w(self.bookmark_link(rset))
+ w(self.focus_link(rset))
hiddens = {}
for param in ('subvid', 'vtitle'):
if param in req.form:
@@ -269,6 +271,9 @@
req._('bookmark this search'))
return self.bk_linkbox_template % bk_link
+ def focus_link(self, rset):
+ return self.bk_linkbox_template % tags.a(self._cw._('focus on this selection'),
+ href=self._cw.url(), id='focusLink')
class FilterTable(FacetFilterMixIn, AnyRsetView):
__regid__ = 'facet.filtertable'
--- a/web/views/magicsearch.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/magicsearch.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -15,19 +15,23 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""a query processor to handle quick search shortcuts for cubicweb"""
+"""a query processor to handle quick search shortcuts for cubicweb
+"""
__docformat__ = "restructuredtext en"
import re
from logging import getLogger
-from warnings import warn
+
+from yams.interfaces import IVocabularyConstraint
from rql import RQLSyntaxError, BadRQLQuery, parse
+from rql.utils import rqlvar_maker
from rql.nodes import Relation
from cubicweb import Unauthorized, typed_eid
from cubicweb.view import Component
+from cubicweb.web.views.ajaxcontroller import ajaxfunc
LOGGER = getLogger('cubicweb.magicsearch')
@@ -408,3 +412,247 @@
# explicitly specified processor: don't try to catch the exception
return proc.process_query(uquery)
raise BadRQLQuery(self._cw._('sorry, the server is unable to handle this query'))
+
+
+
+## RQL suggestions builder ####################################################
+class RQLSuggestionsBuilder(Component):
+ """main entry point is `build_suggestions()` which takes
+ an incomplete RQL query and returns a list of suggestions to complete
+ the query.
+
+ This component is enabled by default and is used to provide autocompletion
+ in the RQL search bar. If you don't want this feature in your application,
+ just unregister it or make it unselectable.
+
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.build_suggestions
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.etypes_suggestion_set
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.possible_etypes
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.possible_relations
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.vocabulary
+ """
+ __regid__ = 'rql.suggestions'
+
+ #: maximum number of results to fetch when suggesting attribute values
+ attr_value_limit = 20
+
+ def build_suggestions(self, user_rql):
+ """return a list of suggestions to complete `user_rql`
+
+ :param user_rql: an incomplete RQL query
+ """
+ req = self._cw
+ try:
+ if 'WHERE' not in user_rql: # don't try to complete if there's no restriction
+ return []
+ variables, restrictions = [part.strip() for part in user_rql.split('WHERE', 1)]
+ if ',' in restrictions:
+ restrictions, incomplete_part = restrictions.rsplit(',', 1)
+ user_rql = '%s WHERE %s' % (variables, restrictions)
+ else:
+ restrictions, incomplete_part = '', restrictions
+ user_rql = variables
+ select = parse(user_rql, print_errors=False).children[0]
+ req.vreg.rqlhelper.annotate(select)
+ req.vreg.solutions(req, select, {})
+ if restrictions:
+ return ['%s, %s' % (user_rql, suggestion)
+ for suggestion in self.rql_build_suggestions(select, incomplete_part)]
+ else:
+ return ['%s WHERE %s' % (user_rql, suggestion)
+ for suggestion in self.rql_build_suggestions(select, incomplete_part)]
+ except Exception, exc: # we never want to crash
+ self.debug('failed to build suggestions: %s', exc)
+ return []
+
+ ## actual completion entry points #########################################
+ def rql_build_suggestions(self, select, incomplete_part):
+ """
+ :param select: the annotated select node (rql syntax tree)
+ :param incomplete_part: the part of the rql query that needs
+ to be completed, (e.g. ``X is Pr``, ``X re``)
+ """
+ chunks = incomplete_part.split(None, 2)
+ if not chunks: # nothing to complete
+ return []
+ if len(chunks) == 1: # `incomplete` looks like "MYVAR"
+ return self._complete_rqlvar(select, *chunks)
+ elif len(chunks) == 2: # `incomplete` looks like "MYVAR some_rel"
+ return self._complete_rqlvar_and_rtype(select, *chunks)
+ elif len(chunks) == 3: # `incomplete` looks like "MYVAR some_rel something"
+ return self._complete_relation_object(select, *chunks)
+ else: # would be anything else, hard to decide what to do here
+ return []
+
+ # _complete_* methods are considered private, at least while the API
+ # isn't stabilized.
+ def _complete_rqlvar(self, select, rql_var):
+ """return suggestions for "variable only" incomplete_part
+
+ as in :
+
+ - Any X WHERE X
+ - Any X WHERE X is Project, Y
+ - etc.
+ """
+ return ['%s %s %s' % (rql_var, rtype, dest_var)
+ for rtype, dest_var in self.possible_relations(select, rql_var)]
+
+ def _complete_rqlvar_and_rtype(self, select, rql_var, user_rtype):
+ """return suggestions for "variable + rtype" incomplete_part
+
+ as in :
+
+ - Any X WHERE X is
+ - Any X WHERE X is Person, X firstn
+ - etc.
+ """
+ # special case `user_type` == 'is', return every possible type.
+ if user_rtype == 'is':
+ return self._complete_is_relation(select, rql_var)
+ else:
+ return ['%s %s %s' % (rql_var, rtype, dest_var)
+ for rtype, dest_var in self.possible_relations(select, rql_var)
+ if rtype.startswith(user_rtype)]
+
+ def _complete_relation_object(self, select, rql_var, user_rtype, user_value):
+ """return suggestions for "variable + rtype + some_incomplete_value"
+
+ as in :
+
+ - Any X WHERE X is Per
+ - Any X WHERE X is Person, X firstname "
+ - Any X WHERE X is Person, X firstname "Pa
+ - etc.
+ """
+ # special case `user_type` == 'is', return every possible type.
+ if user_rtype == 'is':
+ return self._complete_is_relation(select, rql_var, user_value)
+ elif user_value:
+ if user_value[0] in ('"', "'"):
+ # if finished string, don't suggest anything
+ if len(user_value) > 1 and user_value[-1] == user_value[0]:
+ return []
+ user_value = user_value[1:]
+ return ['%s %s "%s"' % (rql_var, user_rtype, value)
+ for value in self.vocabulary(select, rql_var,
+ user_rtype, user_value)]
+ return []
+
+ def _complete_is_relation(self, select, rql_var, prefix=''):
+ """return every possible types for rql_var
+
+ :param prefix: if specified, will only return entity types starting
+ with the specified value.
+ """
+ return ['%s is %s' % (rql_var, etype)
+ for etype in self.possible_etypes(select, rql_var, prefix)]
+
+ def etypes_suggestion_set(self):
+ """returns the list of possible entity types to suggest
+
+ The default is to return any non-final entity type available
+ in the schema.
+
+ Can be overridden for instance if an application decides
+ to restrict this list to a meaningful set of business etypes.
+ """
+ schema = self._cw.vreg.schema
+ return set(eschema.type for eschema in schema.entities() if not eschema.final)
+
+ def possible_etypes(self, select, rql_var, prefix=''):
+ """return all possible etypes for `rql_var`
+
+ The returned list will always be a subset of meth:`etypes_suggestion_set`
+
+ :param select: the annotated select node (rql syntax tree)
+ :param rql_var: the variable name for which we want to know possible types
+ :param prefix: if specified, will only return etypes starting with it
+ """
+ available_etypes = self.etypes_suggestion_set()
+ possible_etypes = set()
+ for sol in select.solutions:
+ if rql_var in sol and sol[rql_var] in available_etypes:
+ possible_etypes.add(sol[rql_var])
+ if not possible_etypes:
+ # `Any X WHERE X is Person, Y is`
+ # -> won't have a solution, need to give all etypes
+ possible_etypes = available_etypes
+ return sorted(etype for etype in possible_etypes if etype.startswith(prefix))
+
+ def possible_relations(self, select, rql_var, include_meta=False):
+ """returns a list of couple (rtype, dest_var) for each possible
+ relations with `rql_var` as subject.
+
+ ``dest_var`` will be picked among availabel variables if types match,
+ otherwise a new one will be created.
+ """
+ schema = self._cw.vreg.schema
+ relations = set()
+ untyped_dest_var = rqlvar_maker(defined=select.defined_vars).next()
+ # for each solution
+ # 1. find each possible relation
+ # 2. for each relation:
+ # 2.1. if the relation is meta, skip it
+ # 2.2. for each possible destination type, pick up possible
+ # variables for this type or use a new one
+ for sol in select.solutions:
+ etype = sol[rql_var]
+ sol_by_types = {}
+ for varname, var_etype in sol.items():
+ # don't push subject var to avoid "X relation X" suggestion
+ if varname != rql_var:
+ sol_by_types.setdefault(var_etype, []).append(varname)
+ for rschema in schema[etype].subject_relations():
+ if include_meta or not rschema.meta:
+ for dest in rschema.objects(etype):
+ for varname in sol_by_types.get(dest.type, (untyped_dest_var,)):
+ suggestion = (rschema.type, varname)
+ if suggestion not in relations:
+ relations.add(suggestion)
+ return sorted(relations)
+
+ def vocabulary(self, select, rql_var, user_rtype, rtype_incomplete_value):
+ """return acceptable vocabulary for `rql_var` + `user_rtype` in `select`
+
+ Vocabulary is either found from schema (Yams) definition or
+ directly from database.
+ """
+ schema = self._cw.vreg.schema
+ vocab = []
+ for sol in select.solutions:
+ # for each solution :
+ # - If a vocabulary constraint exists on `rql_var+user_rtype`, use it
+ # to define possible values
+ # - Otherwise, query the database to fetch available values from
+ # database (limiting results to `self.attr_value_limit`)
+ try:
+ eschema = schema.eschema(sol[rql_var])
+ rdef = eschema.rdef(user_rtype)
+ except KeyError: # unknown relation
+ continue
+ cstr = rdef.constraint_by_interface(IVocabularyConstraint)
+ if cstr is not None:
+ # a vocabulary is found, use it
+ vocab += [value for value in cstr.vocabulary()
+ if value.startswith(rtype_incomplete_value)]
+ elif rdef.final:
+ # no vocab, query database to find possible value
+ vocab_rql = 'DISTINCT Any V LIMIT %s WHERE X is %s, X %s V' % (
+ self.attr_value_limit, eschema.type, user_rtype)
+ vocab_kwargs = {}
+ if rtype_incomplete_value:
+ vocab_rql += ', X %s LIKE %%(value)s' % user_rtype
+ vocab_kwargs['value'] = '%s%%' % rtype_incomplete_value
+ vocab += [value for value, in
+ self._cw.execute(vocab_rql, vocab_kwargs)]
+ return sorted(set(vocab))
+
+
+
+@ajaxfunc(output_type='json')
+def rql_suggest(self):
+ rql_builder = self._cw.vreg['components'].select_or_none('rql.suggestions', self._cw)
+ if rql_builder:
+ return rql_builder.build_suggestions(self._cw.form['term'])
+ return []
--- a/web/views/sessions.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/sessions.py Wed Jan 09 16:06:20 2013 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/views/startup.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/startup.py Wed Jan 09 16:06:20 2013 +0100
@@ -51,7 +51,7 @@
title = _('manage')
http_cache_manager = httpcache.EtagHTTPCacheManager
add_etype_links = ()
- skip_startup_views = set( ('index', 'manage', 'schema', 'owl', 'changelog',
+ skip_startup_views = set( ('index', 'manage', 'schema', 'owl',
'systempropertiesform', 'propertiesform',
'loggedout', 'login',
'cw.users-and-groups-management', 'cw.groups-management',
--- a/web/views/tableview.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/tableview.py Wed Jan 09 16:06:20 2013 +0100
@@ -290,20 +290,17 @@
return attrs
def render_actions(self, w, actions):
- box = MenuWidget('', '', _class='tableActionsBox', islist=False)
- label = tags.span(self._cw._('action menu'))
- menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
- ident='%sActions' % self.view.domid)
- box.append(menu)
+ w(u'<div class="tableactions">')
for action in actions:
- menu.append(action)
- box.render(w=w)
- w(u'<div class="clear"></div>')
+ w(u'<span>')
+ action.render(w)
+ w(u'</span>')
+ w(u'</div>')
def show_hide_filter_actions(self, currentlydisplayed=False):
divid = self.view.domid
showhide = u';'.join(toggle_action('%s%s' % (divid, what))[11:]
- for what in ('Form', 'Show', 'Hide', 'Actions'))
+ for what in ('Form', 'Actions'))
showhide = 'javascript:' + showhide
self._cw.add_onload(u'''\
$(document).ready(function() {
@@ -313,10 +310,8 @@
$('#%(id)sShow').attr('class', 'hidden');
}
});''' % {'id': divid})
- showlabel = self._cw._('show filter form')
- hidelabel = self._cw._('hide filter form')
- return [component.Link(showhide, showlabel, id='%sShow' % divid),
- component.Link(showhide, hidelabel, id='%sHide' % divid)]
+ showlabel = self._cw._('toggle filter')
+ return [component.Link(showhide, showlabel, id='%sToggle' % divid)]
class AbstractColumnRenderer(object):
--- a/web/views/urlrewrite.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/urlrewrite.py Wed Jan 09 16:06:20 2013 +0100
@@ -109,7 +109,6 @@
(rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')),
(rgx('/doc/?'), dict(vid='wdoc', fid=r'main')),
(rgx('/doc/(.+?)/?'), dict(vid='wdoc', fid=r'\1')),
- (rgx('/changelog/?'), dict(vid='changelog')),
]
def rewrite(self, req, uri):
--- a/web/views/wdoc.py Wed Jan 09 16:04:26 2013 +0100
+++ b/web/views/wdoc.py Wed Jan 09 16:06:20 2013 +0100
@@ -205,60 +205,6 @@
self.w(open(join(resourcedir, rid)).read())
-class ChangeLogView(StartupView):
- __regid__ = 'changelog'
- title = _('What\'s new?')
- maxentries = 25
-
- def call(self):
- rid = 'ChangeLog_%s' % (self._cw.lang)
- allentries = []
- title = self._cw._(self.title)
- restdata = ['.. -*- coding: utf-8 -*-', '', title, '='*len(title), '']
- w = restdata.append
- today = date.today()
- for fpath in self._cw.vreg.config.locate_all_files(rid):
- cl = ChangeLog(fpath)
- encoding = 'utf-8'
- # additional content may be found in title
- for line in (cl.title + cl.additional_content).splitlines():
- m = CHARSET_DECL_RGX.search(line)
- if m is not None:
- encoding = m.group(1)
- continue
- elif line.startswith('.. '):
- w(unicode(line, encoding))
- for entry in cl.entries:
- if entry.date:
- edate = todate(strptime(entry.date, '%Y-%m-%d'))
- else:
- edate = today
- messages = []
- for msglines, submsgs in entry.messages:
- msgstr = unicode(' '.join(l.strip() for l in msglines), encoding)
- msgstr += u'\n\n'
- for submsglines in submsgs:
- msgstr += ' - ' + unicode(' '.join(l.strip() for l in submsglines), encoding)
- msgstr += u'\n'
- messages.append(msgstr)
- entry = (edate, messages)
- allentries.insert(bisect_right(allentries, entry), entry)
- latestdate = None
- i = 0
- for edate, messages in reversed(allentries):
- if latestdate != edate:
- fdate = self._cw.format_date(edate)
- w(u'\n%s' % fdate)
- w('~' * len(fdate))
- latestdate = edate
- for msg in messages:
- w(u'* %s' % msg)
- i += 1
- if i > self.maxentries:
- break
- w('') # blank line
- self.w(rest_publish(self, '\n'.join(restdata)))
-
class HelpAction(action.Action):
__regid__ = 'help'
@@ -271,17 +217,6 @@
def url(self):
return self._cw.build_url('doc/main')
-class ChangeLogAction(action.Action):
- __regid__ = 'changelog'
- __select__ = yes()
-
- category = 'footer'
- order = 1
- title = ChangeLogView.title
-
- def url(self):
- return self._cw.build_url('changelog')
-
class AboutAction(action.Action):
__regid__ = 'about'
--- a/web/wdoc/ChangeLog_en Wed Jan 09 16:04:26 2013 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-.. -*- coding: utf-8 -*-
-.. _`user preferences`: myprefs
-.. _here: sparql
-.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
-.. _schema: schema
-.. _OWL: http://www.w3.org/TR/owl-features/
-.. _CWSource: cwetype/CWSource
-
-2010-10-17 -- 3.10.0
-
- * facets on optional relations now propose to search to entities
- which miss the relation
-
- * box and content navigation components have been merged, so this
- simplify the configuration and you should be able to move
- boxes/components in various places of the interface
-
- * global / context dependant boxes should now be more
- distinguishable to make the interface more intuitive
-
- * upgraded jQuery and jQuery UI respectively to version 1.4.2 and
- 1.8.
-
- * data sources are now stored as CWSource_ entities in the database.
- This allows on multi-sources instance to filter search results
- according to the source entities are coming from.
-
-
-2010-06-11 -- 3.8.4
- * support full text prefix search for instances using postgres > 8.4 as database: try it
- by using search such as 'cubic*'
-
-
-2010-04-20 -- 3.8.0
- * nicer schema_ and workflow views (clickable image!)
-
- * more power to undo, though not yet complete (missing entity updates, soon available...)
-
- * pdf export functionnality moved to its own cube. If it's no more
- present on this site while you found it useful, ask you site
- administrator to install the pdfexport_ cube.
-
-
-2010-03-16 -- 3.7.0
- * experimental support for undoing of deletion. If you're not proposed to *undo*
- deletion of one or several entities, ask you site administrator to activate
- the feature.
-
-
-2010-02-10 -- 3.6.0
- * nice 'demo widget' to edit bookmark's path, e.g. a relative url, splitted
- as path and parameters and dealing nicely with url encodings. Try to
- edit your bookmarks!
-
- * hell lot of refactorings, but you should hopefuly not see that from the outside
-
-2009-09-17 -- 3.5.0
-
- * selectable workflows: authorized users may change the workflow used
- by some workflowable entities
-
-
-2009-08-07 -- 3.4.0
-
- * support for SPARQL_. Click here_ to test it.
-
- * and another step toward the semantic web: new `ExternalUri` entity type
- with its associated `same_as` relation. See
- http://www.w3.org/TR/owl-ref/#sameAs-def for more information and check
- this instance schema_ to see on which entity types it may be applied
-
- * new "view workflow" and "view history" items in the workflow
- sub-menu of the actions box
-
- * you can now edit comments of workflow transition afterward (if authorized,
- of course)
-
- * modification date of an entity is updated when its state is changed
-
-
-2009-02-26 -- 3.1.0
-
- * schema may be exported as OWL_
-
- * new ajax interface for site configuration / `user preferences`_
-
-
-2008-10-24 -- 2.99.0
- * cubicweb is now open source !
--- a/web/wdoc/ChangeLog_fr Wed Jan 09 16:04:26 2013 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-.. -*- coding: utf-8 -*-
-.. _`préférences utilisateurs`: myprefs#fieldset_ui
-.. _ici: sparql
-.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
-.. _schema: schema
-.. _OWL: http://www.w3.org/TR/owl-features/
-.. _pdfexport: http://www.cubicweb.org/project/cubicweb-pdfexport
-.. _CWSource: cwetype/CWSource
-
-2010-10-07 -- 3.10.0
-
- * les facettes sur les relations optionnelles proposent maintenant
- de filter les entité qui n'ont *pas* la relation
-
- * les boîtes et composants contextuels ont été fusionnés, permettant
- de simplifier la configuration et de placer ces nouveaux composants
- comme vous le désirez
-
- * les boîtes globales ou dépendantes du contexte sont plus
- facilement distinguable pour rendre l'interface plus intuitive
-
- * passage à jQuery 1.4.2, et jQuery UI 1.8
-
- * les sources de données sont maintenant stockées dans la base de
- données sous forme d'entités CWSource_. Cela permet sur les
- instances multi-source de filter les résultats de recherche en
- fonction de la source dont viennent les entités.
-
-
-2010-06-11 -- 3.8.4
- * support pour la recherche de préfixe pour les instances utilisant postgres > 8.4 :
- essayez en cherchant par ex. 'cubic*'
-
-2010-04-20 -- 3.8.0
-
- * amélioration des vues de schema_ et des vues de workflows
- (images clickable !)
-
- * meilleure support du "undo", mais il manque toujours le support
- sur la modification d'entité (bientôt...)
-
- * la fonctionnalité d'export d'pdf a été déplacé dans son propre
- cube. Si cette fonctionalité n'est plus disponible sur ce site et
- que vous la trouviez utile, demander à l'administrateur
- d'installer le cube pdfexport_.
-
-
-2010-03-16 -- 3.7.0
-
- * support experimental pour l'annulation ("undo") de la
- suppression. Si, après une suppression d'une ou plusieurs
- entités, on ne vous propose pas d'annuler l'opération, demander à
- l'administrateur d'activé la fonctionnalité
-
-
-2010-02-10 -- 3.6.0
-
- * nouvelle widget (de démonstration :) pour éditer le chemin des
- signets. Celui-ci, une url relative finalement, est décomposée de
- chemin et paramètres que vous pouvez éditer individuellement et
- surtout lisiblement car la gestion de l'échappement de l'url est
- géré de manière transparente
-
- * beaucoup de refactoring, mais vous ne devriez rien remarquer :)
-
-2009-09-17 -- 3.5.0
-
- * workflow sélectionnable: les utilisateurs autorisés peuvent
- changer le workflow à utilister pour les entités le supportant
-
-
-2009-08-07 -- 3.4.0
-
- * support de SPARQL_. Cliquez ici_ pour le tester.
-
- * et encore un pas vers le web sémantique : un nouveau type d'entité
- `ExternalUri` et la relation associée `same_as`. Voir
- http://www.w3.org/TR/owl-ref/#sameAs-def pour plus d'information, ainsi
- que le schema_ de cette instance pour voir à quels types d'entités cela
- s'applique.
-
- * nouveau liens "voir les états possibles" et "voir l'historique" dans le sous-menu
- workflow de la boite actions
-
- * vous pouvez dorénavant éditer les commentaires des passages de transition
- depuis l'historique, pour peu que vous ayez les droits nécessaire bien sûr
-
- * la date de modification d'une entité est mise à jour lorsque son état est changé
-
-
-2009-02-26 -- 3.1.0
-
- * le schéma peut être exporté en OWL_
-
- * nouvelle interface ajax pour la configuration du site et les `préférences utilisateurs`_
-
-
-
--- a/wsgi/handler.py Wed Jan 09 16:04:26 2013 +0100
+++ b/wsgi/handler.py Wed Jan 09 16:06:20 2013 +0100
@@ -106,8 +106,8 @@
def __init__(self, config, vreg=None):
self.appli = CubicWebPublisher(config, vreg=vreg)
self.config = config
- self.base_url = config['base-url']
- self.https_url = config['https-url']
+ self.base_url = self.config['base-url']
+ self.https_url = self.config['https-url']
self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter')
def _render(self, req):