--- a/.hgtags Tue Mar 30 14:32:03 2010 +0200
+++ b/.hgtags Wed Mar 31 15:39:09 2010 +0200
@@ -115,3 +115,5 @@
f9fce56d6a0c2bc6c4b497b66039a8bbbbdc8074 cubicweb-debian-version-3.6.3-1
d010f749c21d55cd85c5feb442b9cf816282953c cubicweb-version-3.7.2
8fda29a6c2191ba3cc59242c17b28b34127c75fa cubicweb-debian-version-3.7.2-1
+768beb8e15f15e079f8ee6cfc35125e12b19e140 cubicweb-version-3.7.3
+44c7bf90df71dd562e5a7be5ced3019da603d24f cubicweb-debian-version-3.7.3-1
--- a/MANIFEST.in Tue Mar 30 14:32:03 2010 +0200
+++ b/MANIFEST.in Wed Mar 31 15:39:09 2010 +0200
@@ -15,7 +15,7 @@
recursive-include etwist *.xml *.html
recursive-include i18n *.pot *.po
-recursive-include schemas *.py *.sql.*
+recursive-include schemas *.py *.sql
recursive-include entities/test/data *
recursive-include sobjects/test/data *
--- a/__pkginfo__.py Tue Mar 30 14:32:03 2010 +0200
+++ b/__pkginfo__.py Wed Mar 31 15:39:09 2010 +0200
@@ -7,7 +7,7 @@
modname = distname = "cubicweb"
-numversion = (3, 7, 2)
+numversion = (3, 7, 3)
version = '.'.join(str(num) for num in numversion)
description = "a repository of entities / relations for knowledge management"
@@ -95,8 +95,9 @@
[join(_data_dir, fname) for fname in listdir(_data_dir)
if not isdir(join(_data_dir, fname))]],
[join('share', 'cubicweb', 'cubes', 'shared', 'data', 'timeline'),
- [join(_data_dir, 'timeline', fname)
- for fname in listdir(join(_data_dir, 'timeline'))]],
+ [join(data_dir, 'timeline', fname) for fname in listdir(join(data_dir, 'timeline'))]],
+ [join('share', 'cubicweb', 'cubes', 'shared', 'data', 'images'),
+ [join(data_dir, 'images', fname) for fname in listdir(join(data_dir, 'images'))]],
[join('share', 'cubicweb', 'cubes', 'shared', 'wdoc'),
[join(_wdoc_dir, fname) for fname in listdir(_wdoc_dir)
if not isdir(join(_wdoc_dir, fname))]],
--- a/appobject.py Tue Mar 30 14:32:03 2010 +0200
+++ b/appobject.py Wed Mar 31 15:39:09 2010 +0200
@@ -14,6 +14,7 @@
from warnings import warn
from logilab.common.deprecation import deprecated
+from logilab.common.decorators import classproperty
from logilab.common.logging_ext import set_log_methods
@@ -245,6 +246,12 @@
__regid__ = None
__select__ = yes()
+ @classproperty
+ def __registries__(cls):
+ if cls.__registry__ is None:
+ return ()
+ return (cls.__registry__,)
+
@classmethod
def __registered__(cls, registry):
"""called by the registry when the appobject has been registered.
--- a/cwconfig.py Tue Mar 30 14:32:03 2010 +0200
+++ b/cwconfig.py Wed Mar 31 15:39:09 2010 +0200
@@ -337,7 +337,7 @@
def available_cubes(cls):
cubes = set()
for directory in cls.cubes_search_path():
- if not os.path.exists(directory):
+ if not exists(directory):
cls.error('unexistant directory in cubes search path: %s'
% directory)
continue
--- a/cwctl.py Tue Mar 30 14:32:03 2010 +0200
+++ b/cwctl.py Wed Mar 31 15:39:09 2010 +0200
@@ -725,7 +725,7 @@
# handle i18n upgrade:
# * install new languages
# * recompile catalogs
- # in the first componant given
+ # XXX search available language in the first cube given
from cubicweb import i18n
templdir = cwcfg.cube_dir(config.cubes()[0])
langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
@@ -740,7 +740,12 @@
print
print '-> instance migrated.'
if not (CWDEV or self.config.nostartstop):
- StartInstanceCommand().start_instance(appid)
+ # restart instance through fork to get a proper environment, avoid
+ # uicfg pb (and probably gettext catalogs, to check...)
+ forkcmd = '%s start %s' % (sys.argv[0], appid)
+ status = system(forkcmd)
+ if status:
+ print '%s exited with status %s' % (forkcmd, status)
print
--- a/dataimport.py Tue Mar 30 14:32:03 2010 +0200
+++ b/dataimport.py Wed Mar 31 15:39:09 2010 +0200
@@ -498,7 +498,7 @@
err = func(buckets)
if err:
self.errors[title] = (help, err)
- self.store.commit()
+ txuuid = self.store.commit()
self._print_stats()
if self.errors:
if self.askerror == 2 or (self.askerror and confirm('Display errors ?')):
@@ -506,7 +506,8 @@
for errkey, error in self.errors.items():
self.tell("\n%s (%s): %d\n" % (error[0], errkey, len(error[1])))
self.tell(pformat(sorted(error[1])))
-
+ if txuuid is not None:
+ print 'transaction id:', txuuid
def _print_stats(self):
nberrors = sum(len(err[1]) for err in self.errors.values())
self.tell('\nImport statistics: %i entities, %i types, %i relations and %i errors'
--- a/dbapi.py Tue Mar 30 14:32:03 2010 +0200
+++ b/dbapi.py Wed Mar 31 15:39:09 2010 +0200
@@ -367,6 +367,16 @@
return '<Connection %s (anonymous)>' % self.sessionid
return '<Connection %s>' % self.sessionid
+ def __enter__(self):
+ return self.cursor()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if exc_type is None:
+ self.commit()
+ else:
+ self.rollback()
+ return False #propagate the exception
+
def request(self):
return DBAPIRequest(self.vreg, self)
--- a/debian/changelog Tue Mar 30 14:32:03 2010 +0200
+++ b/debian/changelog Wed Mar 31 15:39:09 2010 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.7.3-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 31 Mar 2010 14:55:21 +0200
+
cubicweb (3.7.2-1) unstable; urgency=low
* new upstream release
--- a/devtools/testlib.py Tue Mar 30 14:32:03 2010 +0200
+++ b/devtools/testlib.py Wed Mar 31 15:39:09 2010 +0200
@@ -14,6 +14,7 @@
import re
from urllib import unquote
from math import log
+from contextlib import contextmanager
import simplejson
@@ -357,6 +358,15 @@
def entity(self, rql, args=None, eidkey=None, req=None):
return self.execute(rql, args, eidkey, req=req).get_entity(0, 0)
+ @contextmanager
+ def temporary_appobjects(self, *appobjects):
+ self.vreg._loadedmods.setdefault(self.__module__, {})
+ for obj in appobjects:
+ self.vreg.register(obj)
+ yield
+ for obj in appobjects:
+ self.vreg.unregister(obj)
+
# vregistry inspection utilities ###########################################
def pviews(self, req, rset):
--- a/entity.py Tue Mar 30 14:32:03 2010 +0200
+++ b/entity.py Wed Mar 31 15:39:09 2010 +0200
@@ -298,6 +298,12 @@
self.edited_attributes.remove(attr)
return value
+ def update(self, values):
+ """override update to update self.edited_attributes. See `__setitem__`
+ """
+ for attr, value in values.items():
+ self[attr] = value # use self.__setitem__ implementation
+
def rql_set_value(self, attr, value):
"""call by rql execution plan when some attribute is modified
@@ -874,17 +880,21 @@
# raw edition utilities ###################################################
def set_attributes(self, **kwargs):
+ _check_cw_unsafe(kwargs)
assert kwargs
- _check_cw_unsafe(kwargs)
+ assert self._is_saved
relations = []
for key in kwargs:
relations.append('X %s %%(%s)s' % (key, key))
- # update current local object
- self.update(kwargs)
# and now update the database
kwargs['x'] = self.eid
self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
kwargs, 'x')
+ kwargs.pop('x')
+ # update current local object _after_ the rql query to avoid
+ # interferences between the query execution itself and the
+ # edited_attributes / skip_security_attributes machinery
+ self.update(kwargs)
def set_relations(self, **kwargs):
"""add relations to the given object. To set a relation where this entity
--- a/etwist/service.py Tue Mar 30 14:32:03 2010 +0200
+++ b/etwist/service.py Wed Mar 31 15:39:09 2010 +0200
@@ -17,7 +17,6 @@
os.environ['CW_INSTANCES_DIR'] = r'C:\etc\cubicweb.d'
os.environ['USERNAME'] = 'cubicweb'
-
class CWService(object, win32serviceutil.ServiceFramework):
_svc_name_ = None
_svc_display_name_ = None
@@ -25,7 +24,6 @@
def __init__(self, *args, **kwargs):
win32serviceutil.ServiceFramework.__init__(self, *args, **kwargs)
- self._stop_event = win32event.CreateEvent(None, 0, 0, None)
cwcfg.load_cwctl_plugins()
set_log_methods(CubicWebRootResource, logger)
server.parsePOSTData = parsePOSTData
@@ -33,8 +31,8 @@
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
logger.info('stopping %s service' % self.instance)
- win32event.SetEvent(self._stop_event)
- self.ReportServiceStatus(win32service.SERVICE_STOPPED)
+ reactor.stop()
+ self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
def SvcDoRun(self):
self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
@@ -57,5 +55,4 @@
except Exception, e:
logger.error('service %s stopped (cause: %s)' % (self.instance, e))
logger.exception('what happened ...')
- self.SvcStop()
-
+ self.ReportServiceStatus(win32service.SERVICE_STOPPED)
--- a/hooks/__init__.py Tue Mar 30 14:32:03 2010 +0200
+++ b/hooks/__init__.py Wed Mar 31 15:39:09 2010 +0200
@@ -24,7 +24,7 @@
session = repo.internal_session()
try:
session.system_sql(
- 'DELETE FROM transaction WHERE tx_time < %(time)s',
+ 'DELETE FROM transactions WHERE tx_time < %(time)s',
{'time': mindate})
# cleanup deleted entities
session.system_sql(
--- a/i18n/en.po Tue Mar 30 14:32:03 2010 +0200
+++ b/i18n/en.po Wed Mar 31 15:39:09 2010 +0200
@@ -332,6 +332,18 @@
"anymore."
msgstr ""
+#, python-format
+msgid ""
+"Can't undo addition of relation %(rtype)s from %(subj)s to %(obj)s, doesn't "
+"exist anymore"
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't undo creation of entity %(eid)s of type %(etype)s, type no more "
+"supported"
+msgstr ""
+
msgid "Date"
msgstr "Date"
@@ -3358,6 +3370,9 @@
msgid "some errors occured:"
msgstr ""
+msgid "some later transaction(s) touch entity, undo them first"
+msgstr ""
+
msgid "sorry, the server is unable to handle this query"
msgstr ""
--- a/i18n/es.po Tue Mar 30 14:32:03 2010 +0200
+++ b/i18n/es.po Wed Mar 31 15:39:09 2010 +0200
@@ -340,6 +340,18 @@
"anymore."
msgstr ""
+#, python-format
+msgid ""
+"Can't undo addition of relation %(rtype)s from %(subj)s to %(obj)s, doesn't "
+"exist anymore"
+msgstr ""
+
+#, python-format
+msgid ""
+"Can't undo creation of entity %(eid)s of type %(etype)s, type no more "
+"supported"
+msgstr ""
+
msgid "Date"
msgstr "Fecha"
@@ -3439,6 +3451,9 @@
msgid "some errors occured:"
msgstr ""
+msgid "some later transaction(s) touch entity, undo them first"
+msgstr ""
+
msgid "sorry, the server is unable to handle this query"
msgstr "lo sentimos, el servidor no puede manejar esta consulta"
--- a/i18n/fr.po Tue Mar 30 14:32:03 2010 +0200
+++ b/i18n/fr.po Wed Mar 31 15:39:09 2010 +0200
@@ -347,6 +347,22 @@
"Ne peut restaurer la relation %(rtype)s, l'entité %(role)s %(eid)s n'existe "
"plus."
+#, python-format
+msgid ""
+"Can't undo addition of relation %(rtype)s from %(subj)s to %(obj)s, doesn't "
+"exist anymore"
+msgstr ""
+"Ne peut annuler l'ajout de relation %(rtype)s de %(subj)s vers %(obj)s, "
+"cette relation n'existe plus"
+
+#, python-format
+msgid ""
+"Can't undo creation of entity %(eid)s of type %(etype)s, type no more "
+"supported"
+msgstr ""
+"Ne peut annuler la création de l'entité %(eid)s de type %(etype)s, ce type "
+"n'existe plus"
+
msgid "Date"
msgstr "Date"
@@ -3471,6 +3487,10 @@
msgid "some errors occured:"
msgstr "des erreurs sont survenues"
+msgid "some later transaction(s) touch entity, undo them first"
+msgstr ""
+"des transactions plus récentes modifient cette entité, annulez les d'abord"
+
msgid "sorry, the server is unable to handle this query"
msgstr "désolé, le serveur ne peut traiter cette requête"
--- a/selectors.py Tue Mar 30 14:32:03 2010 +0200
+++ b/selectors.py Wed Mar 31 15:39:09 2010 +0200
@@ -58,26 +58,29 @@
# helpers for debugging selectors
SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
-TRACED_OIDS = ()
+TRACED_OIDS = None
+
+def _trace_selector(cls, ret):
+ # /!\ lltrace decorates pure function or __call__ method, this
+ # means argument order may be different
+ if isinstance(cls, Selector):
+ selname = str(cls)
+ vobj = args[0]
+ else:
+ selname = selector.__name__
+ vobj = cls
+ if TRACED_OIDS == 'all' or class_regid(vobj) in TRACED_OIDS:
+ #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
+ print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__)
def lltrace(selector):
# don't wrap selectors if not in development mode
if CubicWebConfiguration.mode == 'system': # XXX config.debug
return selector
def traced(cls, *args, **kwargs):
- # /!\ lltrace decorates pure function or __call__ method, this
- # means argument order may be different
- if isinstance(cls, Selector):
- selname = str(cls)
- vobj = args[0]
- else:
- selname = selector.__name__
- vobj = cls
- oid = class_regid(vobj)
ret = selector(cls, *args, **kwargs)
- if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
- #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
- print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__)
+ if TRACED_OIDS is not None:
+ _trace_selector(cls, ret)
return ret
traced.__name__ = selector.__name__
traced.__doc__ = selector.__doc__
@@ -108,7 +111,7 @@
def __exit__(self, exctype, exc, traceback):
global TRACED_OIDS
- TRACED_OIDS = ()
+ TRACED_OIDS = None
return traceback is None
--- a/server/__init__.py Tue Mar 30 14:32:03 2010 +0200
+++ b/server/__init__.py Wed Mar 31 15:39:09 2010 +0200
@@ -210,8 +210,11 @@
paths = [p for p in config.cubes_path() + [config.apphome]
if exists(join(p, 'migration'))]
# deactivate every hooks but those responsible to set metadata
- # so, NO INTEGRITY CHECKS are done, to have quicker db creation
- with hooks_control(session, session.HOOKS_DENY_ALL, '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'):
# execute cubicweb's pre<event> script
mhandler.exec_event_script('pre%s' % event)
# execute cubes pre<event> script if any
--- a/server/hook.py Tue Mar 30 14:32:03 2010 +0200
+++ b/server/hook.py Wed Mar 31 15:39:09 2010 +0200
@@ -48,6 +48,7 @@
from logilab.common.deprecation import deprecated
from logilab.common.logging_ext import set_log_methods
+from cubicweb import RegistryNotFound
from cubicweb.cwvreg import CWRegistry, VRegistry
from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector,
implements)
@@ -66,15 +67,14 @@
class HooksRegistry(CWRegistry):
+ def initialization_completed(self):
+ for appobjects in self.values():
+ for cls in appobjects:
+ if not cls.enabled:
+ warn('[3.6] %s: enabled is deprecated' % cls)
+ self.unregister(cls)
def register(self, obj, **kwargs):
- try:
- iter(obj.events)
- except AttributeError:
- raise
- except:
- raise Exception('bad .events attribute %s on %s.%s' % (
- obj.events, obj.__module__, obj.__name__))
for event in obj.events:
if event not in ALL_HOOKS:
raise Exception('bad event %s on %s.%s' % (
@@ -96,7 +96,19 @@
for hook in hooks:
hook()
-VRegistry.REGISTRY_FACTORY['hooks'] = HooksRegistry
+class HooksManager(object):
+ def __init__(self, vreg):
+ self.vreg = vreg
+
+ def call_hooks(self, event, session=None, **kwargs):
+ try:
+ self.vreg['%s_hooks' % event].call_hooks(event, session, **kwargs)
+ except RegistryNotFound:
+ pass # no hooks for this event
+
+
+for event in ALL_HOOKS:
+ VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry
_MARKER = object()
def entity_oldnewvalue(entity, attr):
@@ -116,21 +128,6 @@
@objectify_selector
@lltrace
-def _bw_is_enabled(cls, req, **kwargs):
- if cls.enabled:
- return 1
- warn('[3.6] %s: enabled is deprecated' % cls)
- return 0
-
-@objectify_selector
-@lltrace
-def match_event(cls, req, **kwargs):
- if kwargs.get('event') in cls.events:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
def enabled_category(cls, req, **kwargs):
if req is None:
return True # XXX how to deactivate server startup / shutdown event
@@ -190,11 +187,11 @@
return 1
return 0
+
# base class for hook ##########################################################
class Hook(AppObject):
- __registry__ = 'hooks'
- __select__ = match_event() & enabled_category() & _bw_is_enabled()
+ __select__ = enabled_category()
# set this in derivated classes
events = None
category = None
@@ -203,6 +200,16 @@
enabled = True
@classproperty
+ def __registries__(cls):
+ try:
+ return ['%s_hooks' % ev for ev in cls.events]
+ except AttributeError:
+ raise
+ except TypeError:
+ raise Exception('bad .events attribute %s on %s.%s' % (
+ cls.events, cls.__module__, cls.__name__))
+
+ @classproperty
def __regid__(cls):
warn('[3.6] %s.%s: please specify an id for your hook'
% (cls.__module__, cls.__name__), DeprecationWarning)
--- a/server/repository.py Tue Mar 30 14:32:03 2010 +0200
+++ b/server/repository.py Wed Mar 31 15:39:09 2010 +0200
@@ -24,7 +24,6 @@
from os.path import join
from datetime import datetime
from time import time, localtime, strftime
-#from pickle import dumps
from logilab.common.decorators import cached
from logilab.common.compat import any
@@ -38,7 +37,7 @@
UnknownEid, AuthenticationError, ExecutionError,
ETypeNotSupportedBySources, MultiSourcesError,
BadConnectionId, Unauthorized, ValidationError,
- typed_eid, onevent)
+ RepositoryError, typed_eid, onevent)
from cubicweb import cwvreg, schema, server
from cubicweb.server import utils, hook, pool, querier, sources
from cubicweb.server.session import Session, InternalSession, InternalManager, \
@@ -59,7 +58,7 @@
# XXX we should imo rely on the orm to first fetch existing entity if any
# then delete it.
if session.is_internal_session \
- or not session.is_hook_category_activated('integrity'):
+ or not session.is_hook_category_activated('activeintegrity'):
return
card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
# one may be tented to check for neweids but this may cause more than one
@@ -153,10 +152,14 @@
self._available_pools.put_nowait(pool.ConnectionsPool(self.sources))
if config.quick_start:
# quick start, usually only to get a minimal repository to get cubes
- # information (eg dump/restore/
+ # information (eg dump/restore/...)
config._cubes = ()
- self.set_schema(config.load_schema(), resetvreg=False)
+ # only load hooks and entity classes in the registry
+ 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
@@ -202,8 +205,7 @@
self._shutting_down = False
if config.quick_start:
config.init_cubes(self.get_cubes())
- else:
- self.hm = self.vreg['hooks']
+ self.hm = hook.HooksManager(self.vreg)
# internals ###############################################################
@@ -1064,62 +1066,68 @@
if server.DEBUG & server.DBG_REPO:
print 'UPDATE entity', entity.__regid__, entity.eid, \
dict(entity), edited_attributes
- entity.edited_attributes = edited_attributes
- if session.is_hook_category_activated('integrity'):
- entity.check()
+ hm = self.hm
eschema = entity.e_schema
session.set_entity_cache(entity)
- only_inline_rels, need_fti_update = True, False
- relations = []
- for attr in edited_attributes:
- if attr == 'eid':
- continue
- rschema = eschema.subjrels[attr]
- if rschema.final:
- if getattr(eschema.rdef(attr), 'fulltextindexed', False):
- need_fti_update = True
- only_inline_rels = False
- else:
- # inlined relation
- previous_value = entity.related(attr) or None
- if previous_value is not None:
- previous_value = previous_value[0][0] # got a result set
- if previous_value == entity[attr]:
- previous_value = None
+ orig_edited_attributes = getattr(entity, 'edited_attributes', None)
+ entity.edited_attributes = edited_attributes
+ try:
+ if session.is_hook_category_activated('integrity'):
+ entity.check()
+ only_inline_rels, need_fti_update = True, False
+ relations = []
+ for attr in list(edited_attributes):
+ if attr == 'eid':
+ continue
+ rschema = eschema.subjrels[attr]
+ if rschema.final:
+ if getattr(eschema.rdef(attr), 'fulltextindexed', False):
+ need_fti_update = True
+ only_inline_rels = False
+ else:
+ # inlined relation
+ previous_value = entity.related(attr) or None
+ if previous_value is not None:
+ previous_value = previous_value[0][0] # got a result set
+ if previous_value == entity[attr]:
+ previous_value = None
+ else:
+ hm.call_hooks('before_delete_relation', session,
+ eidfrom=entity.eid, rtype=attr,
+ eidto=previous_value)
+ relations.append((attr, entity[attr], previous_value))
+ source = self.source_from_eid(entity.eid, session)
+ if source.should_call_hooks:
+ # call hooks for inlined relations
+ for attr, value, _ in relations:
+ hm.call_hooks('before_add_relation', session,
+ eidfrom=entity.eid, rtype=attr, eidto=value)
+ if not only_inline_rels:
+ hm.call_hooks('before_update_entity', session, entity=entity)
+ source.update_entity(session, entity)
+ self.system_source.update_info(session, entity, need_fti_update)
+ if source.should_call_hooks:
+ if not only_inline_rels:
+ hm.call_hooks('after_update_entity', session, entity=entity)
+ for attr, value, prevvalue in relations:
+ # if the relation is already cached, update existant cache
+ relcache = entity.relation_cached(attr, 'subject')
+ if prevvalue is not None:
+ hm.call_hooks('after_delete_relation', session,
+ eidfrom=entity.eid, rtype=attr, eidto=prevvalue)
+ if relcache is not None:
+ session.update_rel_cache_del(entity.eid, attr, prevvalue)
+ del_existing_rel_if_needed(session, entity.eid, attr, value)
+ if relcache is not None:
+ session.update_rel_cache_add(entity.eid, attr, value)
else:
- self.hm.call_hooks('before_delete_relation', session,
- eidfrom=entity.eid, rtype=attr,
- eidto=previous_value)
- relations.append((attr, entity[attr], previous_value))
- source = self.source_from_eid(entity.eid, session)
- if source.should_call_hooks:
- # call hooks for inlined relations
- for attr, value, _ in relations:
- self.hm.call_hooks('before_add_relation', session,
- eidfrom=entity.eid, rtype=attr, eidto=value)
- if not only_inline_rels:
- self.hm.call_hooks('before_update_entity', session, entity=entity)
- source.update_entity(session, entity)
- self.system_source.update_info(session, entity, need_fti_update)
- if source.should_call_hooks:
- if not only_inline_rels:
- self.hm.call_hooks('after_update_entity', session, entity=entity)
- for attr, value, prevvalue in relations:
- # if the relation is already cached, update existant cache
- relcache = entity.relation_cached(attr, 'subject')
- if prevvalue is not None:
- self.hm.call_hooks('after_delete_relation', session,
- eidfrom=entity.eid, rtype=attr, eidto=prevvalue)
- if relcache is not None:
- session.update_rel_cache_del(entity.eid, attr, prevvalue)
- del_existing_rel_if_needed(session, entity.eid, attr, value)
- if relcache is not None:
- session.update_rel_cache_add(entity.eid, attr, value)
- else:
- entity.set_related_cache(attr, 'subject',
- session.eid_rset(value))
- self.hm.call_hooks('after_add_relation', session,
- eidfrom=entity.eid, rtype=attr, eidto=value)
+ entity.set_related_cache(attr, 'subject',
+ session.eid_rset(value))
+ hm.call_hooks('after_add_relation', session,
+ eidfrom=entity.eid, rtype=attr, eidto=value)
+ finally:
+ if orig_edited_attributes is not None:
+ entity.edited_attributes = orig_edited_attributes
def glob_delete_entity(self, session, eid):
"""delete an entity and all related entities from the repository"""
--- a/server/session.py Tue Mar 30 14:32:03 2010 +0200
+++ b/server/session.py Wed Mar 31 15:39:09 2010 +0200
@@ -278,8 +278,13 @@
"""return a sql cursor on the system database"""
if not sql.split(None, 1)[0].upper() == 'SELECT':
self.mode = 'write'
- return self.pool.source('system').doexec(self, sql, args,
- rollback=rollback_on_failure)
+ source = self.pool.source('system')
+ try:
+ return source.doexec(self, sql, args, rollback=rollback_on_failure)
+ except (source.OperationalError, source.InterfaceError):
+ source.warning("trying to reconnect")
+ self.pool.reconnect(self)
+ return source.doexec(self, sql, args, rollback=rollback_on_failure)
def set_language(self, language):
"""i18n configuration for translation"""
--- a/server/sources/native.py Tue Mar 30 14:32:03 2010 +0200
+++ b/server/sources/native.py Wed Mar 31 15:39:09 2010 +0200
@@ -410,7 +410,7 @@
cursor = self.doexec(session, sql, args)
except (self.OperationalError, self.InterfaceError):
# FIXME: better detection of deconnection pb
- self.info("request failed '%s' ... retry with a new cursor", sql)
+ self.warning("trying to reconnect")
session.pool.reconnect(self)
cursor = self.doexec(session, sql, args)
results = self.process_result(cursor, cbs)
@@ -1031,8 +1031,8 @@
entity = self.repo.vreg['etypes'].etype_class(etype)(session)
except Exception:
return [session._(
- "Can't undo creation of entity %s of type %s, type "
- "no more supported" % (eid, etype))]
+ "Can't undo creation of entity %(eid)s of type %(etype)s, type "
+ "no more supported" % {'eid': eid, 'etype': etype})]
entity.set_eid(eid)
# for proper eid/type cache update
hook.set_operation(session, 'pendingeids', eid,
@@ -1075,8 +1075,8 @@
cu = self.doexec(session, sql)
if cu.fetchone() is None:
errors.append(session._(
- "Can't undo addition of relation %s from %s to %s, doesn't "
- "exist anymore" % (rtype, subj, obj)))
+ "Can't undo addition of relation %(rtype)s from %(subj)s to"
+ " %(obj)s, doesn't exist anymore" % locals()))
if not errors:
self.repo.hm.call_hooks('before_delete_relation', session,
eidfrom=subj, rtype=rtype, eidto=obj)
--- a/server/test/unittest_hook.py Tue Mar 30 14:32:03 2010 +0200
+++ b/server/test/unittest_hook.py Wed Mar 31 15:39:09 2010 +0200
@@ -81,7 +81,7 @@
raise HookCalled()
-class HooksManagerTC(TestCase):
+class HooksRegistryTC(TestCase):
def setUp(self):
""" called before each test from this class """
@@ -115,7 +115,6 @@
is_hook_activated=lambda x, cls: cls.category not in dis)
self.assertRaises(HookCalled,
self.o.call_hooks, 'before_add_entity', cw)
- self.o.call_hooks('before_delete_entity', cw) # nothing to call
dis.add('cat1')
self.o.call_hooks('before_add_entity', cw) # disabled hooks category, not called
dis.remove('cat1')
--- a/server/test/unittest_repository.py Tue Mar 30 14:32:03 2010 +0200
+++ b/server/test/unittest_repository.py Wed Mar 31 15:39:09 2010 +0200
@@ -6,6 +6,9 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+from __future__ import with_statement
+
+from __future__ import with_statement
import os
import sys
@@ -20,12 +23,14 @@
from cubicweb import (BadConnectionId, RepositoryError, ValidationError,
UnknownEid, AuthenticationError)
+from cubicweb.selectors import implements
from cubicweb.schema import CubicWebSchema, RQLConstraint
from cubicweb.dbapi import connect, multiple_connections_unfix
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.repotest import tuplify
from cubicweb.server import repository, hook
from cubicweb.server.sqlutils import SQL_PREFIX
+from cubicweb.server.hook import Hook
from cubicweb.server.sources import native
# start name server anyway, process will fail if already running
@@ -272,8 +277,10 @@
from logilab.common import pyro_ext
pyro_ext._DAEMONS.clear()
+
def _pyro_client(self, done):
- cnx = connect(self.repo.config.appid, u'admin', password='gingkow')
+ cnx = connect(self.repo.config.appid, u'admin', password='gingkow',
+ initlog=False) # don't reset logging configuration
try:
# check we can get the schema
schema = cnx.get_schema()
@@ -283,7 +290,7 @@
cnx.close()
done.append(True)
finally:
- # connect monkey path some method by default, remove them
+ # connect monkey patch some method by default, remove them
multiple_connections_unfix()
def test_internal_api(self):
@@ -357,6 +364,42 @@
self.assertEquals(rset.rows[0][0], p2.eid)
+ def test_set_attributes_in_before_update(self):
+ # local hook
+ class DummyBeforeHook(Hook):
+ __regid__ = 'dummy-before-hook'
+ __select__ = Hook.__select__ & implements('EmailAddress')
+ events = ('before_update_entity',)
+ def __call__(self):
+ # safety belt: avoid potential infinite recursion if the test
+ # fails (i.e. RuntimeError not raised)
+ 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')
+ 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')
+ rset = self.execute('Any A,AA WHERE X eid %(x)s, X address A, X alias AA',
+ {'x': addr.eid})
+ self.assertEquals(rset.rows, [[u'a@b.com', u'foo']])
+
+ def test_set_attributes_in_before_add(self):
+ # local hook
+ class DummyBeforeHook(Hook):
+ __regid__ = 'dummy-before-hook'
+ __select__ = Hook.__select__ & implements('EmailAddress')
+ events = ('before_add_entity',)
+ def __call__(self):
+ # set_attributes is forbidden within before_add_entity()
+ self.entity.set_attributes(alias=u'foo')
+ with self.temporary_appobjects(DummyBeforeHook):
+ req = self.request()
+ self.assertRaises(RepositoryError, req.create_entity,
+ 'EmailAddress', address=u'a@b.fr')
+
+
class DataHelpersTC(CubicWebTC):
def test_create_eid(self):
@@ -402,6 +445,7 @@
class FTITC(CubicWebTC):
def test_reindex_and_modified_since(self):
+ self.repo.system_source.multisources_etypes.add('Personne')
eidp = self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"')[0][0]
self.commit()
ts = datetime.now()
@@ -484,7 +528,6 @@
"""
def setUp(self):
CubicWebTC.setUp(self)
- self.hm = self.repo.hm
CALLED[:] = ()
def _after_relation_hook(self, pool, fromeid, rtype, toeid):
@@ -500,20 +543,20 @@
def __call__(self):
CALLED.append((self.event, self.eidfrom, self.rtype, self.eidto))
- self.hm.register(EcritParHook)
- eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
- eidn = self.execute('INSERT Note X: X type "T"')[0][0]
- self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
- self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
- ('after_add_relation', eidn, 'ecrit_par', eidp)])
- CALLED[:] = ()
- self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
- self.assertEquals(CALLED, [('before_delete_relation', eidn, 'ecrit_par', eidp),
- ('after_delete_relation', eidn, 'ecrit_par', eidp)])
- CALLED[:] = ()
- eidn = self.execute('INSERT Note N: N ecrit_par P WHERE P nom "toto"')[0][0]
- self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
- ('after_add_relation', eidn, 'ecrit_par', eidp)])
+ with self.temporary_appobjects(EcritParHook):
+ eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
+ eidn = self.execute('INSERT Note X: X type "T"')[0][0]
+ self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
+ self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
+ ('after_add_relation', eidn, 'ecrit_par', eidp)])
+ CALLED[:] = ()
+ self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
+ self.assertEquals(CALLED, [('before_delete_relation', eidn, 'ecrit_par', eidp),
+ ('after_delete_relation', eidn, 'ecrit_par', eidp)])
+ CALLED[:] = ()
+ eidn = self.execute('INSERT Note N: N ecrit_par P WHERE P nom "toto"')[0][0]
+ self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
+ ('after_add_relation', eidn, 'ecrit_par', eidp)])
if __name__ == '__main__':
--- a/server/test/unittest_storage.py Tue Mar 30 14:32:03 2010 +0200
+++ b/server/test/unittest_storage.py Wed Mar 31 15:39:09 2010 +0200
@@ -6,6 +6,8 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+from __future__ import with_statement
+
from logilab.common.testlib import unittest_main
from cubicweb.devtools.testlib import CubicWebTC
@@ -93,14 +95,8 @@
self.assertEquals(fspath.getvalue(), '/the/path')
def test_source_storage_transparency(self):
- self.vreg._loadedmods[__name__] = {}
- self.vreg.register(DummyBeforeHook)
- self.vreg.register(DummyAfterHook)
- try:
+ with self.temporary_appobjects(DummyBeforeHook, DummyAfterHook):
self.create_file()
- finally:
- self.vreg.unregister(DummyBeforeHook)
- self.vreg.unregister(DummyAfterHook)
def test_source_mapped_attribute_error_cases(self):
ex = self.assertRaises(QueryError, self.execute,
--- a/server/test/unittest_undo.py Tue Mar 30 14:32:03 2010 +0200
+++ b/server/test/unittest_undo.py Wed Mar 31 15:39:09 2010 +0200
@@ -64,7 +64,7 @@
self.assertEquals(a4.eid_to, self.toto.in_group[0].eid)
self.assertEquals(a4.order, 4)
for i, rtype in ((1, 'owned_by'), (2, 'owned_by'),
- (4, 'created_by'), (5, 'in_state')):
+ (4, 'in_state'), (5, 'created_by')):
a = actions[i]
self.assertEquals(a.action, 'A')
self.assertEquals(a.eid_from, self.toto.eid)
--- a/vregistry.py Tue Mar 30 14:32:03 2010 +0200
+++ b/vregistry.py Wed Mar 31 15:39:09 2010 +0200
@@ -82,6 +82,11 @@
return cls.id
return cls.__regid__
+def class_registries(cls, registryname):
+ if registryname:
+ return (registryname,)
+ return cls.__registries__
+
class Registry(dict):
@@ -205,25 +210,25 @@
if len(args) > 1:
warn('[3.5] only the request param can not be named when calling select*',
DeprecationWarning, stacklevel=3)
- score, winners = 0, []
+ score, winners = 0, None
for appobject in appobjects:
appobjectscore = appobject.__select__(appobject, *args, **kwargs)
if appobjectscore > score:
score, winners = appobjectscore, [appobject]
elif appobjectscore > 0 and appobjectscore == score:
winners.append(appobject)
- if not winners:
+ if winners is None:
raise NoSelectableObject('args: %s\nkwargs: %s %s'
% (args, kwargs.keys(),
[repr(v) for v in appobjects]))
if len(winners) > 1:
+ # log in production environement, error while debugging
if self.config.debugmode:
- self.error('select ambiguity, args: %s\nkwargs: %s %s',
- args, kwargs.keys(), [repr(v) for v in winners])
- else:
raise Exception('select ambiguity, args: %s\nkwargs: %s %s'
% (args, kwargs.keys(),
[repr(v) for v in winners]))
+ self.error('select ambiguity, args: %s\nkwargs: %s %s',
+ args, kwargs.keys(), [repr(v) for v in winners])
# return the result of calling the appobject
return winners[0](*args, **kwargs)
@@ -318,31 +323,33 @@
if obj.__module__ != modname or obj in butclasses:
continue
oid = class_regid(obj)
- registryname = obj.__registry__
except AttributeError:
continue
if oid and not '__abstract__' in obj.__dict__:
- self.register(obj, registryname)
+ self.register(obj, oid=oid)
def register(self, obj, registryname=None, oid=None, clear=False):
"""base method to add an object in the registry"""
assert not '__abstract__' in obj.__dict__
- registryname = registryname or obj.__registry__
- registry = self.setdefault(registryname)
- registry.register(obj, oid=oid, clear=clear)
try:
vname = obj.__name__
except AttributeError:
+ # XXX may occurs?
vname = obj.__class__.__name__
- self.debug('registered appobject %s in registry %s with id %s',
- vname, registryname, oid or class_regid(obj))
+ for registryname in class_registries(obj, registryname):
+ registry = self.setdefault(registryname)
+ registry.register(obj, oid=oid, clear=clear)
+ self.debug('registered appobject %s in registry %s with id %s',
+ vname, registryname, oid or class_regid(obj))
self._loadedmods[obj.__module__][classid(obj)] = obj
def unregister(self, obj, registryname=None):
- self[registryname or obj.__registry__].unregister(obj)
+ for registryname in class_registries(obj, registryname):
+ self[registryname].unregister(obj)
def register_and_replace(self, obj, replaced, registryname=None):
- self[registryname or obj.__registry__].register_and_replace(obj, replaced)
+ for registryname in class_registries(obj, registryname):
+ self[registryname].register_and_replace(obj, replaced)
# initialization methods ###################################################
@@ -451,7 +458,7 @@
self._load_ancestors_then_object(modname, parent)
if (appobjectcls.__dict__.get('__abstract__')
or appobjectcls.__name__[0] == '_'
- or not appobjectcls.__registry__
+ or not appobjectcls.__registries__
or not class_regid(appobjectcls)):
return
try:
--- a/web/data/cubicweb.edition.js Tue Mar 30 14:32:03 2010 +0200
+++ b/web/data/cubicweb.edition.js Wed Mar 31 15:39:09 2010 +0200
@@ -331,26 +331,30 @@
var firsterrfield = null;
for (fieldname in errors) {
var errmsg = errors[fieldname];
- var fieldid = fieldname + ':' + eid;
- var suffixes = ['', '-subject', '-object'];
- var found = false;
- // XXX remove suffixes at some point
- for (var i=0, length=suffixes.length; i<length;i++) {
- var field = jqNode(fieldname + suffixes[i] + ':' + eid);
- if (field && getNodeAttribute(field, 'type') != 'hidden') {
- if ( !firsterrfield ) {
- firsterrfield = 'err-' + fieldid;
+ if (!fieldname) {
+ globalerrors.push(errmsg);
+ } else {
+ var fieldid = fieldname + ':' + eid;
+ var suffixes = ['', '-subject', '-object'];
+ var found = false;
+ // XXX remove suffixes at some point
+ for (var i=0, length=suffixes.length; i<length;i++) {
+ var field = jqNode(fieldname + suffixes[i] + ':' + eid);
+ if (field && getNodeAttribute(field, 'type') != 'hidden') {
+ if ( !firsterrfield ) {
+ firsterrfield = 'err-' + fieldid;
+ }
+ addElementClass(field, 'error');
+ var span = SPAN({'id': 'err-' + fieldid, 'class': "errorMsg"}, errmsg);
+ field.before(span);
+ found = true;
+ break;
}
- addElementClass(field, 'error');
- var span = SPAN({'id': 'err-' + fieldid, 'class': "errorMsg"}, errmsg);
- field.before(span);
- found = true;
- break;
}
- }
- if (!found) {
- firsterrfield = formid;
- globalerrors.push(_(fieldname) + ' : ' + errmsg);
+ if (!found) {
+ firsterrfield = formid;
+ globalerrors.push(_(fieldname) + ' : ' + errmsg);
+ }
}
}
if (globalerrors.length) {
--- a/web/data/cubicweb.facets.js Tue Mar 30 14:32:03 2010 +0200
+++ b/web/data/cubicweb.facets.js Wed Mar 31 15:39:09 2010 +0200
@@ -47,10 +47,11 @@
var rql = result[0];
var $bkLink = jQuery('#facetBkLink');
if ($bkLink.length) {
- var bkUrl = $bkLink.attr('cubicweb:target') + '&path=view?rql=' + rql;
+ var bkPath = 'view?rql=' + escape(rql);
if (vid) {
- bkUrl += '&vid=' + vid;
+ bkPath += '&vid=' + escape(vid);
}
+ var bkUrl = $bkLink.attr('cubicweb:target') + '&path=' + escape(bkPath);
$bkLink.attr('href', bkUrl);
}
var toupdate = result[1];
--- a/web/formfields.py Tue Mar 30 14:32:03 2010 +0200
+++ b/web/formfields.py Wed Mar 31 15:39:09 2010 +0200
@@ -867,7 +867,7 @@
eids.add(typed_eid)
return eids
-
+# XXX use cases where we don't actually want a better widget?
class CompoundField(Field):
def __init__(self, fields, *args, **kwargs):
super(CompoundField, self).__init__(*args, **kwargs)
@@ -877,7 +877,11 @@
return self.fields
def actual_fields(self, form):
- return [self] + list(self.fields)
+ # don't add [self] to actual fields, compound field is usually kinda
+ # virtual, all interesting values are in subfield. Skipping it may avoid
+ # error when processed by the editcontroller : it may be marked as required
+ # while it has no value, hence generating a false error.
+ return list(self.fields)
_AFF_KWARGS = uicfg.autoform_field_kwargs
--- a/web/formwidgets.py Tue Mar 30 14:32:03 2010 +0200
+++ b/web/formwidgets.py Wed Mar 31 15:39:09 2010 +0200
@@ -783,6 +783,7 @@
attrs['id'] = field.dom_id(form, 'fqs')
if self.settabindex:
attrs['tabindex'] = req.next_tabindex()
+ attrs.setdefault('cols', 60)
attrs.setdefault('onkeyup', 'autogrow(this)')
inputs += [tags.textarea(fqs, name=fqsqname, **attrs),
u'</td></tr></table>']
--- a/web/views/basecontrollers.py Tue Mar 30 14:32:03 2010 +0200
+++ b/web/views/basecontrollers.py Wed Mar 31 15:39:09 2010 +0200
@@ -282,8 +282,6 @@
except RemoteCallFailed:
raise
except Exception, ex:
- import traceback
- traceback.print_exc()
self.exception('an exception occured while calling js_%s(%s): %s',
fname, args, ex)
raise RemoteCallFailed(repr(ex))
@@ -392,7 +390,18 @@
else: # we receive unicode keys which is not supported by the **syntax
extraargs = dict((str(key), value)
for key, value in extraargs.items())
- comp = self._cw.vreg[registry].select(compid, self._cw, rset=rset, **extraargs)
+ # XXX while it sounds good, addition of the try/except below cause pb:
+ # when filtering using facets return an empty rset, the edition box
+ # isn't anymore selectable, as expected. The pb is that with the
+ # try/except below, we see a "an error occured" message in the ui, while
+ # we don't see it without it. Proper fix would probably be to deal with
+ # this by allowing facet handling code to tell to js_component that such
+ # error is expected and should'nt be reported.
+ #try:
+ comp = self._cw.vreg[registry].select(compid, self._cw, rset=rset,
+ **extraargs)
+ #except NoSelectableObject:
+ # raise RemoteCallFailed('unselectable')
extraargs = extraargs or {}
stream = comp.set_stream()
comp.render(**extraargs)
--- a/web/views/bookmark.py Tue Mar 30 14:32:03 2010 +0200
+++ b/web/views/bookmark.py Wed Mar 31 15:39:09 2010 +0200
@@ -113,7 +113,7 @@
# we can't edit shared bookmarks we don't own
bookmarksrql = 'Bookmark B WHERE B bookmarked_by U, B owned_by U, U eid %(x)s'
erset = req.execute(bookmarksrql, {'x': ueid}, 'x',
- build_descr=False)
+ build_descr=False)
bookmarksrql %= {'x': ueid}
if erset:
url = self._cw.build_url(vid='muledit', rql=bookmarksrql)
--- a/web/views/facets.py Tue Mar 30 14:32:03 2010 +0200
+++ b/web/views/facets.py Wed Mar 31 15:39:09 2010 +0200
@@ -105,7 +105,10 @@
def display_bookmark_link(self, rset):
eschema = self._cw.vreg.schema.eschema('Bookmark')
if eschema.has_perm(self._cw, 'add'):
- bk_path = 'view?rql=%s' % rset.printable_rql()
+ bk_path = 'rql=%s' % self._cw.url_quote(rset.printable_rql())
+ if self._cw.form.get('vid'):
+ bk_path += '&vid=%s' % self._cw.url_quote(self._cw.form['vid'])
+ bk_path = 'view?' + bk_path
bk_title = self._cw._('my custom search')
linkto = 'bookmarked_by:%s:subject' % self._cw.user.eid
bk_add_url = self._cw.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)