--- a/__pkginfo__.py Fri Mar 26 16:15:16 2010 +0100
+++ b/__pkginfo__.py Fri Mar 26 16:15:41 2010 +0100
@@ -7,7 +7,7 @@
modname = distname = "cubicweb"
-numversion = (3, 7, 1)
+numversion = (3, 7, 2)
version = '.'.join(str(num) for num in numversion)
description = "a repository of entities / relations for knowledge management"
--- a/_exceptions.py Fri Mar 26 16:15:16 2010 +0100
+++ b/_exceptions.py Fri Mar 26 16:15:41 2010 +0100
@@ -45,21 +45,19 @@
class ConnectionError(RepositoryError):
"""raised when a bad connection id is given or when an attempt to establish
- a connection failed"""
+ a connection failed
+ """
class AuthenticationError(ConnectionError):
- """raised when a bad connection id is given or when an attempt to establish
- a connection failed
+ """raised when when an attempt to establish a connection failed do to wrong
+ connection information (login / password or other authentication token)
"""
def __init__(self, *args, **kwargs):
super(AuthenticationError, self).__init__(*args)
self.__dict__.update(kwargs)
class BadConnectionId(ConnectionError):
- """raised when a bad connection id is given or when an attempt to establish
- a connection failed"""
-
-BadSessionId = BadConnectionId # XXX bw compat for pyro connections
+ """raised when a bad connection id is given"""
class UnknownEid(RepositoryError):
"""the eid is not defined in the system tables"""
--- a/appobject.py Fri Mar 26 16:15:16 2010 +0100
+++ b/appobject.py Fri Mar 26 16:15:41 2010 +0100
@@ -258,7 +258,7 @@
except AttributeError:
pdefs = getattr(cls, 'cw_property_defs', {})
else:
- warn('property_defs is deprecated, use cw_property_defs in %s'
+ warn('[3.6] property_defs is deprecated, use cw_property_defs in %s'
% cls, DeprecationWarning)
for propid, pdef in pdefs.items():
pdef = pdef.copy() # may be shared
--- a/cwconfig.py Fri Mar 26 16:15:16 2010 +0100
+++ b/cwconfig.py Fri Mar 26 16:15:41 2010 +0100
@@ -687,8 +687,6 @@
# for some commands (creation...) we don't want to initialize gettext
set_language = True
- # set this to true to avoid false error message while creating an instance
- creating = False
# set this to true to allow somethings which would'nt be possible
repairing = False
--- a/cwctl.py Fri Mar 26 16:15:16 2010 +0100
+++ b/cwctl.py Fri Mar 26 16:15:41 2010 +0100
@@ -312,7 +312,6 @@
cubes = splitstrip(pop_arg(args, 1))
appid = pop_arg(args)
# get the configuration and helper
- cwcfg.creating = True
config = cwcfg.config_for(appid, configname)
config.set_language = False
cubes = config.expand_cubes(cubes)
@@ -861,20 +860,11 @@
def i18ninstance_instance(appid):
"""recompile instance's messages catalogs"""
config = cwcfg.config_for(appid)
- try:
- config.bootstrap_cubes()
- except IOError, ex:
- import errno
- if ex.errno != errno.ENOENT:
- raise
- # bootstrap_cubes files doesn't exist
- # notify this is not a regular start
- config.repairing = True
- # create an in-memory repository, will call config.init_cubes()
- config.repository()
- except AttributeError:
+ config.quick_start = True # notify this is not a regular start
+ repo = config.repository()
+ if config._cubes is None:
# web only config
- config.init_cubes(config.repository().get_cubes())
+ config.init_cubes(repo.get_cubes())
errors = config.i18ncompile()
if errors:
print '\n'.join(errors)
--- a/cwvreg.py Fri Mar 26 16:15:16 2010 +0100
+++ b/cwvreg.py Fri Mar 26 16:15:41 2010 +0100
@@ -21,6 +21,9 @@
from cubicweb.vregistry import VRegistry, Registry, class_regid
from cubicweb.rtags import RTAGS
+def clear_rtag_objects():
+ for rtag in RTAGS:
+ rtag.clear()
def use_interfaces(obj):
"""return interfaces used by the given object by searching for implements
@@ -262,10 +265,7 @@
if self.config.mode != 'test':
# don't clear rtags during test, this may cause breakage with
# manually imported appobject modules
- @onevent('before-registry-reload')
- def clear_rtag_objects():
- for rtag in RTAGS:
- rtag.clear()
+ CW_EVENT_MANAGER.bind('before-registry-reload', clear_rtag_objects)
def setdefault(self, regid):
try:
--- a/dbapi.py Fri Mar 26 16:15:16 2010 +0100
+++ b/dbapi.py Fri Mar 26 16:15:41 2010 +0100
@@ -393,13 +393,13 @@
pass
def check(self):
- """raise `BadSessionId` if the connection is no more valid"""
+ """raise `BadConnectionId` if the connection is no more valid"""
if self._closed is not None:
raise ProgrammingError('Closed connection')
self._repo.check_session(self.sessionid)
def set_session_props(self, **props):
- """raise `BadSessionId` if the connection is no more valid"""
+ """raise `BadConnectionId` if the connection is no more valid"""
if self._closed is not None:
raise ProgrammingError('Closed connection')
self._repo.set_session_props(self.sessionid, props)
--- a/debian/changelog Fri Mar 26 16:15:16 2010 +0100
+++ b/debian/changelog Fri Mar 26 16:15:41 2010 +0100
@@ -1,3 +1,9 @@
+cubicweb (3.7.2-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 26 Mar 2010 15:53:01 +0100
+
cubicweb (3.7.1-1) unstable; urgency=low
* new upstream release
--- a/devtools/__init__.py Fri Mar 26 16:15:16 2010 +0100
+++ b/devtools/__init__.py Fri Mar 26 16:15:41 2010 +0100
@@ -81,7 +81,6 @@
mode = 'test'
set_language = False
read_instance_schema = False
- bootstrap_schema = False
init_repository = True
options = cwconfig.merge_options(ServerConfiguration.options + (
('anonymous-user',
--- a/devtools/testlib.py Fri Mar 26 16:15:16 2010 +0100
+++ b/devtools/testlib.py Fri Mar 26 16:15:41 2010 +0100
@@ -251,7 +251,14 @@
def setUp(self):
pause_tracing()
- self._init_repo()
+ previous_failure = self.__class__.__dict__.get('_repo_init_failed')
+ if previous_failure is not None:
+ self.skip('repository is not initialised: %r' % previous_failure)
+ try:
+ self._init_repo()
+ except Exception, ex:
+ self.__class__._repo_init_failed = ex
+ raise
resume_tracing()
self.setup_database()
self.commit()
@@ -495,7 +502,8 @@
else:
cleanup = lambda p: (p[0], unquote(p[1]))
params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p)
- path = path[len(req.base_url()):]
+ if path.startswith(req.base_url()): # may be relative
+ path = path[len(req.base_url()):]
return path, params
else:
self.fail('expected a Redirect exception')
--- a/entities/schemaobjs.py Fri Mar 26 16:15:16 2010 +0100
+++ b/entities/schemaobjs.py Fri Mar 26 16:15:41 2010 +0100
@@ -9,6 +9,8 @@
from logilab.common.decorators import cached
+from yams.schema import role_name
+
from cubicweb import ValidationError
from cubicweb.schema import ERQLExpression, RRQLExpression
@@ -64,13 +66,14 @@
for rdef in self.reverse_relation_type:
card = rdef.cardinality[0]
if not card in '?1':
+ qname = role_name('inlined', 'subject')
rtype = self.name
stype = rdef.stype
otype = rdef.otype
msg = self._cw._("can't set inlined=%(inlined)s, "
"%(stype)s %(rtype)s %(otype)s "
"has cardinality=%(card)s")
- raise ValidationError(self.eid, {'inlined': msg % locals()})
+ raise ValidationError(self.eid, {qname: msg % locals()})
def db_key_name(self):
"""XXX goa specific"""
--- a/entities/test/unittest_wfobjs.py Fri Mar 26 16:15:16 2010 +0100
+++ b/entities/test/unittest_wfobjs.py Fri Mar 26 16:15:41 2010 +0100
@@ -39,7 +39,7 @@
self.commit()
wf.add_state(u'foo')
ex = self.assertRaises(ValidationError, self.commit)
- self.assertEquals(ex.errors, {'name': 'workflow already have a state of that name'})
+ self.assertEquals(ex.errors, {'name-subject': 'workflow already have a state of that name'})
# no pb if not in the same workflow
wf2 = add_wf(self, 'Company')
foo = wf2.add_state(u'foo', initial=True)
@@ -49,7 +49,7 @@
self.commit()
bar.set_attributes(name=u'foo')
ex = self.assertRaises(ValidationError, self.commit)
- self.assertEquals(ex.errors, {'name': 'workflow already have a state of that name'})
+ self.assertEquals(ex.errors, {'name-subject': 'workflow already have a state of that name'})
def test_duplicated_transition(self):
wf = add_wf(self, 'Company')
@@ -58,7 +58,7 @@
wf.add_transition(u'baz', (foo,), bar, ('managers',))
wf.add_transition(u'baz', (bar,), foo)
ex = self.assertRaises(ValidationError, self.commit)
- self.assertEquals(ex.errors, {'name': 'workflow already have a transition of that name'})
+ self.assertEquals(ex.errors, {'name-subject': 'workflow already have a transition of that name'})
# no pb if not in the same workflow
wf2 = add_wf(self, 'Company')
foo = wf.add_state(u'foo', initial=True)
@@ -70,7 +70,7 @@
self.commit()
biz.set_attributes(name=u'baz')
ex = self.assertRaises(ValidationError, self.commit)
- self.assertEquals(ex.errors, {'name': 'workflow already have a transition of that name'})
+ self.assertEquals(ex.errors, {'name-subject': 'workflow already have a transition of that name'})
class WorkflowTC(CubicWebTC):
@@ -132,7 +132,7 @@
ex = self.assertRaises(ValidationError, self.session.execute,
'SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
{'x': self.user().eid, 's': s.eid}, 'x')
- self.assertEquals(ex.errors, {'in_state': "state doesn't belong to entity's workflow. "
+ self.assertEquals(ex.errors, {'in_state-subject': "state doesn't belong to entity's workflow. "
"You may want to set a custom workflow for this entity first."})
def test_fire_transition(self):
@@ -175,7 +175,7 @@
member = req.entity_from_eid(self.member.eid)
ex = self.assertRaises(ValidationError,
member.fire_transition, 'deactivate')
- self.assertEquals(ex.errors, {'by_transition': "transition may not be fired"})
+ self.assertEquals(ex.errors, {'by_transition-subject': "transition may not be fired"})
cnx.close()
cnx = self.login('member')
req = self.request()
@@ -184,7 +184,7 @@
cnx.commit()
ex = self.assertRaises(ValidationError,
member.fire_transition, 'activate')
- self.assertEquals(ex.errors, {'by_transition': "transition may not be fired"})
+ self.assertEquals(ex.errors, {'by_transition-subject': "transition may not be fired"})
def test_fire_transition_owned_by(self):
self.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
@@ -257,7 +257,7 @@
# subworkflow input transition
ex = self.assertRaises(ValidationError,
self.group.change_state, swfstate1, u'gadget')
- self.assertEquals(ex.errors, {'to_state': "state doesn't belong to entity's workflow"})
+ self.assertEquals(ex.errors, {'to_state-subject': "state doesn't belong to entity's workflow"})
self.rollback()
# force back to state1
self.group.change_state('state1', u'gadget')
@@ -293,7 +293,7 @@
mwf.add_wftransition(u'swftr1', swf, state1,
[(swfstate2, state2), (swfstate2, state3)])
ex = self.assertRaises(ValidationError, self.commit)
- self.assertEquals(ex.errors, {'subworkflow_exit': u"can't have multiple exits on the same state"})
+ self.assertEquals(ex.errors, {'subworkflow_exit-subject': u"can't have multiple exits on the same state"})
def test_swf_fire_in_a_row(self):
# sub-workflow
@@ -406,7 +406,7 @@
self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
{'wf': wf.eid, 'x': self.member.eid})
ex = self.assertRaises(ValidationError, self.commit)
- self.assertEquals(ex.errors, {'custom_workflow': u'workflow has no initial state'})
+ self.assertEquals(ex.errors, {'custom_workflow-subject': u'workflow has no initial state'})
def test_custom_wf_bad_etype(self):
"""try to set a custom workflow which doesn't apply to entity type"""
@@ -415,7 +415,7 @@
self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
{'wf': wf.eid, 'x': self.member.eid}, 'x')
ex = self.assertRaises(ValidationError, self.commit)
- self.assertEquals(ex.errors, {'custom_workflow': 'workflow isn\'t a workflow for this type'})
+ self.assertEquals(ex.errors, {'custom_workflow-subject': 'workflow isn\'t a workflow for this type'})
def test_del_custom_wf(self):
"""member in some state shared by the new workflow, nothing has to be
@@ -535,7 +535,7 @@
user = cnx.user(self.session)
ex = self.assertRaises(ValidationError,
user.fire_transition, 'activate')
- self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+ self.assertEquals(self._cleanup_msg(ex.errors['by_transition-subject']),
u"transition isn't allowed from")
cnx.close()
@@ -544,7 +544,7 @@
user = cnx.user(self.session)
ex = self.assertRaises(ValidationError,
user.fire_transition, 'dummy')
- self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+ self.assertEquals(self._cleanup_msg(ex.errors['by_transition-subject']),
u"transition isn't allowed from")
cnx.close()
@@ -557,7 +557,7 @@
session.set_pool()
ex = self.assertRaises(ValidationError,
user.fire_transition, 'deactivate')
- self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
+ self.assertEquals(self._cleanup_msg(ex.errors['by_transition-subject']),
u"transition isn't allowed from")
# get back now
user.fire_transition('activate')
--- a/goa/goaconfig.py Fri Mar 26 16:15:16 2010 +0100
+++ b/goa/goaconfig.py Fri Mar 26 16:15:41 2010 +0100
@@ -86,7 +86,7 @@
cube_appobject_path = WebConfiguration.cube_appobject_path | ServerConfiguration.cube_appobject_path
# use file system schema
- bootstrap_schema = read_instance_schema = False
+ read_instance_schema = False
# schema is not persistent, don't load schema hooks (unavailable)
schema_hooks = False
# no user workflow for now
--- a/hooks/integrity.py Fri Mar 26 16:15:16 2010 +0100
+++ b/hooks/integrity.py Fri Mar 26 16:15:41 2010 +0100
@@ -10,6 +10,8 @@
from threading import Lock
+from yams.schema import role_name
+
from cubicweb import ValidationError
from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
from cubicweb.selectors import implements
@@ -73,7 +75,8 @@
_ = self.session._
msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid}
- raise ValidationError(self.eid, {self.rtype: msg})
+ qname = role_name(self.rtype, self.role)
+ raise ValidationError(self.eid, {qname: msg})
def commit_event(self):
pass
@@ -84,12 +87,14 @@
class _CheckSRelationOp(_CheckRequiredRelationOperation):
"""check required subject relation"""
+ role = 'subject'
def _rql(self):
return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
class _CheckORelationOp(_CheckRequiredRelationOperation):
"""check required object relation"""
+ role = 'object'
def _rql(self):
return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
@@ -225,7 +230,8 @@
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')
- raise ValidationError(entity.eid, {attr: msg % val})
+ qname = role_name(attr, 'subject')
+ raise ValidationError(entity.eid, {qname: msg % val})
class DontRemoveOwnersGroupHook(IntegrityHook):
@@ -237,12 +243,16 @@
def __call__(self):
if self.event == 'before_delete_entity' and self.entity.name == 'owners':
- raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
- elif self.event == 'before_update_entity' and 'name' in self.entity.edited_attributes:
+ msg = self._cw._('can\'t be deleted')
+ raise ValidationError(self.entity.eid, {None: msg})
+ elif self.event == 'before_update_entity' and \
+ 'name' in self.entity.edited_attributes:
newname = self.entity.pop('name')
oldname = self.entity.name
if oldname == 'owners' and newname != oldname:
- raise ValidationError(self.entity.eid, {'name': self._cw._('can\'t be changed')})
+ qname = role_name('name', 'subject')
+ msg = self._cw._('can\'t be changed')
+ raise ValidationError(self.entity.eid, {qname: msg})
self.entity['name'] = newname
--- a/hooks/syncsession.py Fri Mar 26 16:15:16 2010 +0100
+++ b/hooks/syncsession.py Fri Mar 26 16:15:41 2010 +0100
@@ -7,6 +7,7 @@
"""
__docformat__ = "restructuredtext en"
+from yams.schema import role_name
from cubicweb import UnknownProperty, ValidationError, BadConnectionId
from cubicweb.selectors import implements
from cubicweb.server import hook
@@ -147,11 +148,13 @@
try:
value = session.vreg.typed_value(key, value)
except UnknownProperty:
+ qname = role_name('pkey', 'subject')
raise ValidationError(self.entity.eid,
- {'pkey': session._('unknown property key')})
+ {qname: session._('unknown property key')})
except ValueError, ex:
+ qname = role_name('value', 'subject')
raise ValidationError(self.entity.eid,
- {'value': session._(str(ex))})
+ {qname: session._(str(ex))})
if not session.user.matching_groups('managers'):
session.add_relation(self.entity.eid, 'for_user', session.user.eid)
else:
@@ -174,7 +177,8 @@
except UnknownProperty:
return
except ValueError, ex:
- raise ValidationError(entity.eid, {'value': session._(str(ex))})
+ qname = role_name('value', 'subject')
+ raise ValidationError(entity.eid, {qname: session._(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,
@@ -214,8 +218,10 @@
key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
{'x': eidfrom}, 'x')[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,
- {'for_user': session._("site-wide property can't be set for user")})
+ {qname: msg})
for session_ in get_user_sessions(session.repo, self.eidto):
_ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
key=key, value=value)
--- a/hooks/test/unittest_hooks.py Fri Mar 26 16:15:16 2010 +0100
+++ b/hooks/test/unittest_hooks.py Fri Mar 26 16:15:41 2010 +0100
@@ -102,9 +102,9 @@
'WHERE FE name "CWUser", RT name "in_group", TE name "String"')[0][0]
self.execute('SET X read_permission Y WHERE X eid %(x)s, Y name "managers"',
{'x': releid}, 'x')
- ex = self.assertRaises(ValidationError,
- self.commit)
- self.assertEquals(ex.errors, {'to_entity': 'RQLConstraint O final FALSE failed'})
+ ex = self.assertRaises(ValidationError, self.commit)
+ self.assertEquals(ex.errors,
+ {'to_entity-object': 'RQLConstraint O final FALSE failed'})
def test_html_tidy_hook(self):
req = self.request()
@@ -217,23 +217,23 @@
def test_unexistant_eproperty(self):
ex = self.assertRaises(ValidationError,
self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
- self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
+ self.assertEquals(ex.errors, {'pkey-subject': 'unknown property key'})
ex = self.assertRaises(ValidationError,
self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
- self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
+ self.assertEquals(ex.errors, {'pkey-subject': 'unknown property key'})
def test_site_wide_eproperty(self):
ex = self.assertRaises(ValidationError,
self.execute, 'INSERT CWProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
- self.assertEquals(ex.errors, {'for_user': "site-wide property can't be set for user"})
+ self.assertEquals(ex.errors, {'for_user-subject': "site-wide property can't be set for user"})
def test_bad_type_eproperty(self):
ex = self.assertRaises(ValidationError,
self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop", X for_user U')
- self.assertEquals(ex.errors, {'value': u'unauthorized value'})
+ self.assertEquals(ex.errors, {'value-subject': u'unauthorized value'})
ex = self.assertRaises(ValidationError,
self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop"')
- self.assertEquals(ex.errors, {'value': u'unauthorized value'})
+ self.assertEquals(ex.errors, {'value-subject': u'unauthorized value'})
class SchemaHooksTC(CubicWebTC):
@@ -253,7 +253,7 @@
self.execute('INSERT CWUser X: X login "admin"')
except ValidationError, ex:
self.assertIsInstance(ex.entity, int)
- self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
+ self.assertEquals(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
if __name__ == '__main__':
--- a/hooks/workflow.py Fri Mar 26 16:15:16 2010 +0100
+++ b/hooks/workflow.py Fri Mar 26 16:15:41 2010 +0100
@@ -9,6 +9,8 @@
from datetime import datetime
+from yams.schema import role_name
+
from cubicweb import RepositoryError, ValidationError
from cubicweb.interfaces import IWorkflowable
from cubicweb.selectors import implements
@@ -73,8 +75,9 @@
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, {'custom_workflow': msg})
+ raise ValidationError(entity.eid, {qname: msg})
if mainwf.state_by_eid(entity.current_state.eid):
# nothing to do
return
@@ -97,8 +100,9 @@
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, {'subworkflow_exit': msg})
+ raise ValidationError(self.treid, {qname: msg})
outputs.add(ep.subwf_state.eid)
@@ -112,6 +116,7 @@
wftr = forentity.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})
tostate = wftr.get_exit_point(forentity, trinfo['to_state'])
@@ -166,8 +171,9 @@
try:
foreid = entity['wf_info_for']
except KeyError:
+ qname = role_name('wf_info_for', 'subject')
msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {'wf_info_for': msg})
+ raise ValidationError(entity.eid, {qname: msg})
forentity = session.entity_from_eid(foreid)
# then check it has a workflow set, unless we're in the process of changing
# entity's workflow
@@ -195,41 +201,47 @@
# 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, {'by_transition': msg})
+ raise ValidationError(entity.eid, {qname: msg})
deststateeid = entity.get('to_state')
if not deststateeid:
+ qname = role_name('by_transition', 'subject')
msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {'by_transition': msg})
+ raise ValidationError(entity.eid, {qname: 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, {'to_state': msg})
+ raise ValidationError(entity.eid, {qname: 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')
if tr is None:
msg = session._("transition doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {'by_transition': msg})
+ raise ValidationError(entity.eid, {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, {'by_transition': msg})
+ raise ValidationError(entity.eid, {qname: msg})
if not tr.may_be_fired(foreid):
msg = session._("transition may not be fired")
- raise ValidationError(entity.eid, {'by_transition': msg})
+ raise ValidationError(entity.eid, {qname: msg})
if entity.get('to_state'):
deststateeid = entity['to_state']
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, {'by_transition': msg})
+ raise ValidationError(entity.eid, {qname: 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, {'to_state': msg})
+ raise ValidationError(entity.eid, {qname: msg})
else:
deststateeid = tr.destination(forentity).eid
# everything is ok, add missing information on the trinfo entity
@@ -279,12 +291,14 @@
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, {'in_state': msg})
+ raise ValidationError(self.eidfrom, {qname: msg})
if entity.current_workflow and wf.eid != entity.current_workflow.eid:
+ qname = role_name('in_state', 'subject')
msg = session._("state doesn't belong to entity's current workflow")
- raise ValidationError(self.eidfrom, {'in_state': msg})
+ raise ValidationError(self.eidfrom, {qname: msg})
class SetModificationDateOnStateChange(WorkflowHook):
--- a/i18n/en.po Fri Mar 26 16:15:16 2010 +0100
+++ b/i18n/en.po Fri Mar 26 16:15:41 2010 +0100
@@ -2514,6 +2514,9 @@
msgid "incontext"
msgstr "in-context"
+msgid "incorrect captcha value"
+msgstr ""
+
#, python-format
msgid "incorrect value (%(value)s) for type \"%(type)s\""
msgstr ""
@@ -3721,6 +3724,9 @@
msgid "ui.time-format"
msgstr "time format"
+msgid "unable to check captcha, please try again"
+msgstr ""
+
msgid "unaccessible"
msgstr ""
--- a/i18n/es.po Fri Mar 26 16:15:16 2010 +0100
+++ b/i18n/es.po Fri Mar 26 16:15:41 2010 +0100
@@ -2572,6 +2572,9 @@
msgid "incontext"
msgstr "En el contexto"
+msgid "incorrect captcha value"
+msgstr ""
+
#, python-format
msgid "incorrect value (%(value)s) for type \"%(type)s\""
msgstr "valor %(value)s incorrecto para el tipo \"%(type)s\""
@@ -3802,6 +3805,9 @@
msgid "ui.time-format"
msgstr ""
+msgid "unable to check captcha, please try again"
+msgstr ""
+
msgid "unaccessible"
msgstr "inaccesible"
--- a/i18n/fr.po Fri Mar 26 16:15:16 2010 +0100
+++ b/i18n/fr.po Fri Mar 26 16:15:41 2010 +0100
@@ -320,31 +320,32 @@
"Can't restore %(role)s relation %(rtype)s to entity %(eid)s which is already "
"linked using this relation."
msgstr ""
-"Ne peut restaurer la relation %(role)s %(rtype)s vers l'entité %(eid)s qui est "
-"déja lié à une autre entité par cette relation."
+"Ne peut restaurer la relation %(role)s %(rtype)s vers l'entité %(eid)s qui "
+"est déja lié à une autre entité par cette relation."
#, python-format
msgid ""
"Can't restore relation %(rtype)s between %(subj)s and %(obj)s, that relation "
"does not exists anymore in the schema."
msgstr ""
-"Ne peut restaurer la relation %(rtype)s entre %(subj)s et %(obj)s, "
-"cette relation n'existe plus dans le schéma."
+"Ne peut restaurer la relation %(rtype)s entre %(subj)s et %(obj)s, cette "
+"relation n'existe plus dans le schéma."
#, python-format
msgid ""
"Can't restore relation %(rtype)s of entity %(eid)s, this relation does not "
"exists anymore in the schema."
msgstr ""
-"Ne peut restaurer la relation %(rtype)s de l'entité %(eid)s, cette relation"
-"n'existe plus dans le schéma"
+"Ne peut restaurer la relation %(rtype)s de l'entité %(eid)s, cette "
+"relationn'existe plus dans le schéma"
#, python-format
msgid ""
"Can't restore relation %(rtype)s, %(role)s entity %(eid)s doesn't exist "
"anymore."
msgstr ""
-"Ne peut restaurer la relation %(rtype)s, l'entité %(role)s %(eid)s n'existe plus."
+"Ne peut restaurer la relation %(rtype)s, l'entité %(role)s %(eid)s n'existe "
+"plus."
msgid "Date"
msgstr "Date"
@@ -2604,6 +2605,9 @@
msgid "incontext"
msgstr "dans le contexte"
+msgid "incorrect captcha value"
+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\""
@@ -3838,6 +3842,9 @@
msgid "ui.time-format"
msgstr "format de l'heure"
+msgid "unable to check captcha, please try again"
+msgstr "impossible de vérifier le captcha, veuillez réessayer"
+
msgid "unaccessible"
msgstr "inaccessible"
--- a/schema.py Fri Mar 26 16:15:16 2010 +0100
+++ b/schema.py Fri Mar 26 16:15:41 2010 +0100
@@ -21,7 +21,7 @@
from yams import BadSchemaDefinition, buildobjs as ybo
from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \
- RelationDefinitionSchema, PermissionMixIn
+ RelationDefinitionSchema, PermissionMixIn, role_name
from yams.constraints import BaseConstraint, FormatConstraint
from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
obsolete as yobsolete, cleanup_sys_modules)
@@ -682,17 +682,22 @@
# XXX at this point if both or neither of S and O are in mainvar we
# dunno if the validation error `occured` on eidfrom or eidto (from
# user interface point of view)
+ #
+ # possible enhancement: check entity being created, it's probably
+ # the main eid unless this is a composite relation
if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
maineid = eidfrom
+ qname = role_name(rtype, 'subject')
else:
maineid = eidto
+ qname = role_name(rtype, 'object')
if self.msg:
msg = session._(self.msg)
else:
msg = '%(constraint)s %(restriction)s failed' % {
'constraint': session._(self.type()),
'restriction': self.restriction}
- raise ValidationError(maineid, {rtype: msg})
+ raise ValidationError(maineid, {qname: msg})
def exec_query(self, session, eidfrom, eidto):
if eidto is None:
--- a/selectors.py Fri Mar 26 16:15:16 2010 +0100
+++ b/selectors.py Fri Mar 26 16:15:41 2010 +0100
@@ -1078,6 +1078,24 @@
return 1
return 0
+class is_in_state(score_entity):
+ """return 1 if entity is in one of the states given as argument list
+
+ you should use this instead of your own score_entity x: x.state == 'bla'
+ selector to avoid some gotchas:
+
+ * possible views gives a fake entity with no state
+ * you must use the latest tr info, not entity.state for repository side
+ checking of the current state
+ """
+ def __init__(self, *states):
+ def score(entity, states=set(states)):
+ try:
+ return entity.latest_trinfo().new_state.name in states
+ except AttributeError:
+ return None
+ super(is_in_state, self).__init__(score)
+
## deprecated stuff ############################################################
--- a/server/__init__.py Fri Mar 26 16:15:16 2010 +0100
+++ b/server/__init__.py Fri Mar 26 16:15:41 2010 +0100
@@ -115,11 +115,7 @@
from cubicweb.server.sqlutils import sqlexec, sqlschema, sqldropschema
# configuration to avoid db schema loading and user'state checking
# on connection
- read_instance_schema = config.read_instance_schema
- bootstrap_schema = config.bootstrap_schema
- config.read_instance_schema = False
config.creating = True
- config.bootstrap_schema = True
config.consider_user_state = False
config.set_language = False
# only enable the system source at initialization time + admin which is not
@@ -202,8 +198,6 @@
repo.shutdown()
# restore initial configuration
config.creating = False
- config.read_instance_schema = read_instance_schema
- config.bootstrap_schema = bootstrap_schema
config.consider_user_state = True
config.set_language = True
print '-> database for instance %s initialized.' % config.appid
--- a/server/hook.py Fri Mar 26 16:15:16 2010 +0100
+++ b/server/hook.py Fri Mar 26 16:15:41 2010 +0100
@@ -19,8 +19,11 @@
Relation (eg before_add_relation, after_add_relation, before_delete_relation,
after_delete_relation) all have `eidfrom`, `rtype`, `eidto` attributes.
-Server start/stop hooks (eg server_startup, server_shutdown) have a `repo`
-attribute, but *their `_cw` attribute is None*.
+Server start/maintenance/stop hooks (eg server_startup, server_maintenance,
+server_shutdown) have a `repo` attribute, but *their `_cw` attribute is None*.
+The `server_startup` is called on regular startup, while `server_maintenance`
+is called on cubicweb-ctl upgrade or shell commands. `server_shutdown` is
+called anyway.
Backup/restore hooks (eg server_backup, server_restore) have a `repo` and a
`timestamp` attributes, but *their `_cw` attribute is None*.
@@ -57,7 +60,7 @@
RELATIONS_HOOKS = set(('before_add_relation', 'after_add_relation' ,
'before_delete_relation','after_delete_relation'))
SYSTEM_HOOKS = set(('server_backup', 'server_restore',
- 'server_startup', 'server_shutdown',
+ 'server_startup', 'server_maintenance', 'server_shutdown',
'session_open', 'session_close'))
ALL_HOOKS = ENTITIES_HOOKS | RELATIONS_HOOKS | SYSTEM_HOOKS
--- a/server/migractions.py Fri Mar 26 16:15:16 2010 +0100
+++ b/server/migractions.py Fri Mar 26 16:15:41 2010 +0100
@@ -59,7 +59,6 @@
def __init__(self, config, schema, interactive=True,
repo=None, cnx=None, verbosity=1, connect=True):
MigrationHelper.__init__(self, config, interactive, verbosity)
- # no config on shell to a remote instance
if not interactive:
assert cnx
assert repo
@@ -67,11 +66,13 @@
assert repo
self._cnx = cnx
self.repo = repo
- if config is not None:
- self.session.data['rebuild-infered'] = False
elif connect:
self.repo_connect()
- if not schema:
+ # no config on shell to a remote instance
+ if config is not None and (cnx or connect):
+ self.session.data['rebuild-infered'] = False
+ self.repo.hm.call_hooks('server_maintenance', repo=self.repo)
+ if not schema and not getattr(config, 'quick_start', False):
schema = config.load_schema(expand_cubes=True)
self.fs_schema = schema
self._synchronized = set()
--- a/server/repository.py Fri Mar 26 16:15:16 2010 +0100
+++ b/server/repository.py Fri Mar 26 16:15:41 2010 +0100
@@ -31,6 +31,7 @@
from logilab.common import flatten
from yams import BadSchemaDefinition
+from yams.schema import role_name
from rql import RQLSyntaxError
from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP,
@@ -164,7 +165,7 @@
if config.open_connections_pools:
self.open_connections_pools()
- def _boostrap_hook_registry(self):
+ def _bootstrap_hook_registry(self):
"""called during bootstrap since we need the metadata hooks"""
hooksdirectory = join(CW_SOFTWARE_ROOT, 'hooks')
self.vreg.init_registration([hooksdirectory])
@@ -175,12 +176,15 @@
config = self.config
self._available_pools = Queue.Queue()
self._available_pools.put_nowait(pool.ConnectionsPool(self.sources))
- if config.read_instance_schema:
- # normal start: load the instance schema from the database
- self.fill_schema()
- elif config.bootstrap_schema:
- # usually during repository creation
- self.warning("set fs instance'schema as bootstrap schema")
+ if config.quick_start:
+ # quick start, usually only to get a minimal repository to get cubes
+ # information (eg dump/restore/
+ config._cubes = ()
+ self.set_schema(config.load_schema(), resetvreg=False)
+ config['connections-pool-size'] = 1
+ config._cubes = None
+ elif config.creating:
+ # repository creation
config.bootstrap_cubes()
self.set_schema(config.load_schema(), resetvreg=False)
# need to load the Any and CWUser entity types
@@ -188,8 +192,11 @@
self.vreg.init_registration([etdirectory])
for modname in ('__init__', 'authobjs', 'wfobjs'):
self.vreg.load_file(join(etdirectory, '%s.py' % modname),
- 'cubicweb.entities.%s' % modname)
- self._boostrap_hook_registry()
+ 'cubicweb.entities.%s' % modname)
+ self._bootstrap_hook_registry()
+ elif config.read_instance_schema:
+ # 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")
@@ -218,7 +225,10 @@
self.pools.append(pool.ConnectionsPool(self.sources))
self._available_pools.put_nowait(self.pools[-1])
self._shutting_down = False
- self.hm = self.vreg['hooks']
+ if config.quick_start:
+ config.init_cubes(self.get_cubes())
+ else:
+ self.hm = self.vreg['hooks']
# internals ###############################################################
@@ -267,7 +277,8 @@
self.set_schema(appschema)
def start_looping_tasks(self):
- if not (self.config.creating or self.config.repairing):
+ if not (self.config.creating or self.config.repairing
+ or self.config.quick_start):
# call instance level initialisation hooks
self.hm.call_hooks('server_startup', repo=self)
# register a task to cleanup expired session
@@ -335,7 +346,8 @@
self.info('waiting thread %s...', thread.name)
thread.join()
self.info('thread %s finished', thread.name)
- if not (self.config.creating or self.config.repairing):
+ if not (self.config.creating or self.config.repairing
+ or self.config.quick_start):
self.hm.call_hooks('server_shutdown', repo=self)
self.close_sessions()
while not self._available_pools.empty():
@@ -447,6 +459,7 @@
"""
versions = self.get_versions(not (self.config.creating
or self.config.repairing
+ or self.config.quick_start
or self.config.mode == 'test'))
cubes = list(versions)
cubes.remove('cubicweb')
@@ -506,12 +519,13 @@
finally:
session.close()
+ # XXX protect this method: anonymous should be allowed and registration
+ # plugged
def register_user(self, login, password, email=None, **kwargs):
"""check a user with the given login exists, if not create it with the
given password. This method is designed to be used for anonymous
registration on public web site.
"""
- # XXX should not be called from web interface
session = self.internal_session()
# for consistency, keep same error as unique check hook (although not required)
errmsg = session._('the value "%s" is already used, use another one')
@@ -519,7 +533,8 @@
if (session.execute('CWUser X WHERE X login %(login)s', {'login': login})
or session.execute('CWUser X WHERE X use_email C, C address %(login)s',
{'login': login})):
- raise ValidationError(None, {'login': errmsg % login})
+ qname = role_name('login', 'subject')
+ raise ValidationError(None, {qname: errmsg % login})
# we have to create the user
user = self.vreg['etypes'].etype_class('CWUser')(session, None)
if isinstance(password, unicode):
@@ -534,9 +549,11 @@
if email or '@' in login:
d = {'login': login, 'email': email or login}
if session.execute('EmailAddress X WHERE X address %(email)s', d):
- raise ValidationError(None, {'address': errmsg % d['email']})
+ qname = role_name('address', 'subject')
+ raise ValidationError(None, {qname: errmsg % d['email']})
session.execute('INSERT EmailAddress X: X address %(email)s, '
- 'U primary_email X, U use_email X WHERE U login %(login)s', d)
+ 'U primary_email X, U use_email X '
+ 'WHERE U login %(login)s', d)
session.commit()
finally:
session.close()
@@ -610,7 +627,7 @@
session.reset_pool()
def check_session(self, sessionid):
- """raise `BadSessionId` if the connection is no more valid"""
+ """raise `BadConnectionId` if the connection is no more valid"""
self._get_session(sessionid, setpool=False)
def get_shared_data(self, sessionid, key, default=None, pop=False):
--- a/server/serverconfig.py Fri Mar 26 16:15:16 2010 +0100
+++ b/server/serverconfig.py Fri Mar 26 16:15:41 2010 +0100
@@ -194,8 +194,11 @@
# read the schema from the database
read_instance_schema = True
- bootstrap_schema = True
-
+ # set to true while creating an instance
+ creating = False
+ # set this to true to get a minimal repository, for instance to get cubes
+ # information on commands such as i18ninstance, db-restore, etc...
+ quick_start = False
# check user's state at login time
consider_user_state = True
--- a/server/serverctl.py Fri Mar 26 16:15:16 2010 +0100
+++ b/server/serverctl.py Fri Mar 26 16:15:41 2010 +0100
@@ -389,7 +389,7 @@
get_connection(
system['db-driver'], database=system['db-name'],
host=system.get('db-host'), port=system.get('db-port'),
- user=system.get('db-user'), password=system.get('db-password'),
+ user=system.get('db-user'), password=system.get('db-password'),
**extra)
except Exception, ex:
raise ConfigurationError(
@@ -572,17 +572,16 @@
def _local_dump(appid, output):
config = ServerConfiguration.config_for(appid)
- # schema=1 to avoid unnecessary schema loading
- mih = config.migration_handler(connect=False, schema=1, verbosity=1)
+ config.quick_start = True
+ mih = config.migration_handler(connect=False, verbosity=1)
mih.backup_database(output, askconfirm=False)
mih.shutdown()
def _local_restore(appid, backupfile, drop, systemonly=True):
config = ServerConfiguration.config_for(appid)
config.verbosity = 1 # else we won't be asked for confirmation on problems
- config.repairing = 1 # don't check versions
- # schema=1 to avoid unnecessary schema loading
- mih = config.migration_handler(connect=False, schema=1, verbosity=1)
+ config.quick_start = True
+ mih = config.migration_handler(connect=False, verbosity=1)
mih.restore_database(backupfile, drop, systemonly, askconfirm=False)
repo = mih.repo_connect()
# version of the database
--- a/server/session.py Fri Mar 26 16:15:16 2010 +0100
+++ b/server/session.py Fri Mar 26 16:15:41 2010 +0100
@@ -553,7 +553,7 @@
def _touch(self):
"""update latest session usage timestamp and reset mode to read"""
self.timestamp = time()
- self.local_perm_cache.clear()
+ self.local_perm_cache.clear() # XXX simply move in transaction_data, no?
self._threaddata.mode = self.default_mode
# shared data handling ###################################################
--- a/server/test/unittest_hook.py Fri Mar 26 16:15:16 2010 +0100
+++ b/server/test/unittest_hook.py Fri Mar 26 16:15:41 2010 +0100
@@ -92,19 +92,19 @@
class _Hook(hook.Hook):
events = ('before_add_entiti',)
ex = self.assertRaises(Exception, self.o.register, _Hook)
- self.assertEquals(str(ex), 'bad event before_add_entiti on unittest_hook._Hook')
+ self.assertEquals(str(ex), 'bad event before_add_entiti on %s._Hook' % __name__)
def test_register_bad_hook2(self):
class _Hook(hook.Hook):
events = None
ex = self.assertRaises(Exception, self.o.register, _Hook)
- self.assertEquals(str(ex), 'bad .events attribute None on unittest_hook._Hook')
+ self.assertEquals(str(ex), 'bad .events attribute None on %s._Hook' % __name__)
def test_register_bad_hook3(self):
class _Hook(hook.Hook):
events = 'before_add_entity'
ex = self.assertRaises(Exception, self.o.register, _Hook)
- self.assertEquals(str(ex), 'bad event b on unittest_hook._Hook')
+ self.assertEquals(str(ex), 'bad event b on %s._Hook' % __name__)
def test_call_hook(self):
self.o.register(AddAnyHook)
--- a/server/test/unittest_ldapuser.py Fri Mar 26 16:15:16 2010 +0100
+++ b/server/test/unittest_ldapuser.py Fri Mar 26 16:15:41 2010 +0100
@@ -15,7 +15,7 @@
from cubicweb.server.sources.ldapuser import *
-if socket.gethostbyname('ldap1').startswith('172'):
+if '17.1' in socket.gethostbyname('ldap1'):
SYT = 'syt'
ADIM = 'adim'
else:
--- a/web/captcha.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/captcha.py Fri Mar 26 16:15:41 2010 +0100
@@ -17,7 +17,7 @@
from time import time
from cubicweb import tags
-from cubicweb.web import formwidgets as fw
+from cubicweb.web import ProcessFormError, formwidgets as fw
def pil_captcha(text, fontfile, fontsize):
@@ -63,7 +63,22 @@
class CaptchaWidget(fw.TextInput):
def render(self, form, field, renderer=None):
# t=int(time()*100) to make sure img is not cached
- src = form._cw.build_url('view', vid='captcha', t=int(time()*100))
+ src = form._cw.build_url('view', vid='captcha', t=int(time()*100),
+ captchakey=field.input_name(form))
img = tags.img(src=src, alt=u'captcha')
img = u'<div class="captcha">%s</div>' % img
return img + super(CaptchaWidget, self).render(form, field, renderer)
+
+ def process_field_data(self, form, field):
+ captcha = form._cw.get_session_data(field.input_name(form), None,
+ pop=True)
+ val = super(CaptchaWidget, self).process_field_data(form, field)
+ if val is None:
+ return val # required will be checked by field
+ if captcha is None:
+ msg = form._cw._('unable to check captcha, please try again')
+ raise ProcessFormError(msg)
+ elif val.lower() != captcha.lower():
+ msg = form._cw._('incorrect captcha value')
+ raise ProcessFormError(msg)
+ return val
--- a/web/controller.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/controller.py Fri Mar 26 16:15:41 2010 +0100
@@ -136,6 +136,9 @@
params.update(newparams)
newparams = params
elif self._edited_entity:
+ # clear caches in case some attribute participating to the rest path
+ # has been modified
+ self._edited_entity.clear_all_caches()
path = self._edited_entity.rest_path()
else:
path = 'view'
--- a/web/data/cubicweb.edition.js Fri Mar 26 16:15:16 2010 +0100
+++ b/web/data/cubicweb.edition.js Fri Mar 26 16:15:41 2010 +0100
@@ -322,7 +322,7 @@
function _clearPreviousErrors(formid) {
jQuery('#' + formid + 'ErrorMessage').remove();
- jQuery('#' + formid + ' span.error').remove();
+ jQuery('#' + formid + ' span.errorMsg').remove();
jQuery('#' + formid + ' .error').removeClass('error');
}
@@ -334,6 +334,7 @@
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') {
@@ -341,7 +342,7 @@
firsterrfield = 'err-' + fieldid;
}
addElementClass(field, 'error');
- var span = SPAN({'id': 'err-' + fieldid, 'class': "error"}, errmsg);
+ var span = SPAN({'id': 'err-' + fieldid, 'class': "errorMsg"}, errmsg);
field.before(span);
found = true;
break;
--- a/web/data/cubicweb.form.css Fri Mar 26 16:15:16 2010 +0100
+++ b/web/data/cubicweb.form.css Fri Mar 26 16:15:41 2010 +0100
@@ -192,11 +192,14 @@
background-color: #eeedd9;
}
-input.error {
+.error input { /* error added by the form renderer */
+ background: transparent url("error.png") 100% 50% no-repeat;
+}
+input.error { /* error added by javascript */
background: transparent url("error.png") 100% 50% no-repeat;
}
-span.error {
+span.errorMsg {
display: block;
font-weight: bold;
color: #ed0d0d;
--- a/web/formfields.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/formfields.py Fri Mar 26 16:15:41 2010 +0100
@@ -15,7 +15,7 @@
from logilab.mtconverter import xml_escape
from logilab.common.date import ustrftime
-from yams.schema import KNOWN_METAATTRIBUTES
+from yams.schema import KNOWN_METAATTRIBUTES, role_name
from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
FormatConstraint)
@@ -214,7 +214,7 @@
def role_name(self):
"""return <field.name>-<field.role> if role is specified, else field.name"""
if self.role is not None:
- return '%s-%s' % (self.name, self.role)
+ return role_name(self.name, self.role)
return self.name
def dom_id(self, form, suffix=None):
@@ -381,7 +381,10 @@
for field in self.actual_fields(form):
if field is self:
try:
- yield field, field.process_form_value(form)
+ value = field.process_form_value(form)
+ if value is None and field.required:
+ raise ProcessFormError(form._cw._("required field"))
+ yield field, value
except UnmodifiedField:
continue
else:
--- a/web/test/unittest_application.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/test/unittest_application.py Fri Mar 26 16:15:41 2010 +0100
@@ -186,36 +186,66 @@
self.assertEquals(values['eid'], eid)
error = forminfo['error']
self.assertEquals(error.entity, user.eid)
- self.assertEquals(error.errors['login'], 'required attribute')
+ self.assertEquals(error.errors['login-subject'], 'required field')
- def test_validation_error_dont_loose_subentity_data(self):
+ def test_validation_error_dont_loose_subentity_data_ctrl(self):
"""test creation of two linked entities
+
+ error occurs on the web controller
"""
req = self.request()
- form = {'eid': ['X', 'Y'], '__maineid': 'X',
- '__type:X': 'CWUser', '_cw_edited_fields:X': 'login-subject,surname-subject',
- # missing required field
- 'login-subject:X': u'',
- 'surname-subject:X': u'Mr Ouaoua',
- # but email address is set
- '__type:Y': 'EmailAddress', '_cw_edited_fields:Y': 'address-subject,alias-subject,use_email-object',
- 'address-subject:Y': u'bougloup@logilab.fr',
- 'alias-subject:Y': u'',
- 'use_email-object:Y': 'X',
- # necessary to get validation error handling
- '__errorurl': 'view?vid=edition...',
- }
- req.form = form
- # monkey patch edited_eid to ensure both entities are edited, not only X
- req.edited_eids = lambda : ('Y', 'X')
+ # set Y before X to ensure both entities are edited, not only X
+ req.form = {'eid': ['Y', 'X'], '__maineid': 'X',
+ '__type:X': 'CWUser', '_cw_edited_fields:X': 'login-subject',
+ # missing required field
+ 'login-subject:X': u'',
+ # but email address is set
+ '__type:Y': 'EmailAddress', '_cw_edited_fields:Y': 'address-subject',
+ 'address-subject:Y': u'bougloup@logilab.fr',
+ 'use_email-object:Y': 'X',
+ # necessary to get validation error handling
+ '__errorurl': 'view?vid=edition...',
+ }
path, params = self.expect_redirect(lambda x: self.app_publish(x, 'edit'), req)
forminfo = req.get_session_data('view?vid=edition...')
self.assertEquals(set(forminfo['eidmap']), set('XY'))
+ self.assertEquals(forminfo['eidmap']['X'], None)
+ self.assertIsInstance(forminfo['eidmap']['Y'], int)
+ self.assertEquals(forminfo['error'].entity, 'X')
+ self.assertEquals(forminfo['error'].errors,
+ {'login-subject': 'required field'})
+ self.assertEquals(forminfo['values'], req.form)
+
+
+ def test_validation_error_dont_loose_subentity_data_repo(self):
+ """test creation of two linked entities
+
+ error occurs on the repository
+ """
+ req = self.request()
+ # set Y before X to ensure both entities are edited, not only X
+ req.form = {'eid': ['Y', 'X'], '__maineid': 'X',
+ '__type:X': 'CWUser', '_cw_edited_fields:X': 'login-subject,upassword-subject',
+ # already existent user
+ 'login-subject:X': u'admin',
+ 'upassword-subject:X': u'admin', 'upassword-subject-confirm:X': u'admin',
+ '__type:Y': 'EmailAddress', '_cw_edited_fields:Y': 'address-subject',
+ 'address-subject:Y': u'bougloup@logilab.fr',
+ 'use_email-object:Y': 'X',
+ # necessary to get validation error handling
+ '__errorurl': 'view?vid=edition...',
+ }
+ path, params = self.expect_redirect(lambda x: self.app_publish(x, 'edit'), req)
+ forminfo = req.get_session_data('view?vid=edition...')
+ self.assertEquals(set(forminfo['eidmap']), set('XY'))
+ self.assertIsInstance(forminfo['eidmap']['X'], int)
+ self.assertIsInstance(forminfo['eidmap']['Y'], int)
self.assertEquals(forminfo['error'].entity, forminfo['eidmap']['X'])
- self.assertEquals(forminfo['error'].errors, {'login': 'required attribute',
- 'upassword': 'required attribute'})
- self.assertEquals(forminfo['values'], form)
+ self.assertEquals(forminfo['error'].errors,
+ {'login-subject': u'the value "admin" is already used, use another one'})
+ self.assertEquals(forminfo['values'], req.form)
+
def _test_cleaned(self, kwargs, injected, cleaned):
req = self.request(**kwargs)
--- a/web/test/unittest_pdf.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/test/unittest_pdf.py Fri Mar 26 16:15:41 2010 +0100
@@ -36,3 +36,8 @@
self.assertEquals( len(output), len(reference) )
# cut begin & end 'cause they contain variyng data
self.assertTextEquals(output[150:1500], reference[150:1500])
+
+if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
+ unittest_main()
+
--- a/web/test/unittest_views_basecontrollers.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/test/unittest_views_basecontrollers.py Fri Mar 26 16:15:41 2010 +0100
@@ -51,7 +51,7 @@
'upassword-subject-confirm:X': u'toto',
}
ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
- self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
+ self.assertEquals(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
def test_user_editing_itself(self):
"""checking that a manager user can edit itself
@@ -219,7 +219,7 @@
'described_by_test-subject:X': u(feid),
}
ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
- self.assertEquals(ex.errors, {'amount': 'value [0;100] constraint failed for value -10'})
+ self.assertEquals(ex.errors, {'amount-subject': 'value [0;100] constraint failed for value -10'})
req = self.request()
req.form = {'eid': ['X'],
'__type:X': 'Salesterm',
@@ -228,7 +228,7 @@
'described_by_test-subject:X': u(feid),
}
ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
- self.assertEquals(ex.errors, {'amount': 'value [0;100] constraint failed for value 110'})
+ self.assertEquals(ex.errors, {'amount-subject': 'value [0;100] constraint failed for value 110'})
req = self.request()
req.form = {'eid': ['X'],
'__type:X': 'Salesterm',
@@ -430,7 +430,7 @@
'use_email-object:Y': 'X',
}
ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
- self.assertEquals(ex.errors, {'address': u'required attribute'})
+ self.assertEquals(ex.errors, {'address-subject': u'required field'})
def test_nonregr_copy(self):
user = self.user()
--- a/web/views/editcontroller.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/views/editcontroller.py Fri Mar 26 16:15:41 2010 +0100
@@ -17,6 +17,11 @@
from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit, ProcessFormError
from cubicweb.web.views import basecontrollers, autoform
+def valerror_eid(eid):
+ try:
+ return typed_eid(eid)
+ except (ValueError, TypeError):
+ return eid
class RqlQuery(object):
def __init__(self):
@@ -110,7 +115,7 @@
self._cw.remove_pending_operations()
if self.errors:
errors = dict((f.name, unicode(ex)) for f, ex in self.errors)
- raise ValidationError(form.get('__maineid'), errors)
+ raise ValidationError(valerror_eid(form.get('__maineid')), errors)
def _insert_entity(self, etype, eid, rqlquery):
rql = rqlquery.insert_query(etype)
@@ -166,7 +171,7 @@
self.handle_formfield(form, field, rqlquery)
if self.errors:
errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
- raise ValidationError(entity.eid, errors)
+ raise ValidationError(valerror_eid(entity.eid), errors)
if eid is None: # creation or copy
entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery)
elif rqlquery.edited: # edition of an existant entity
--- a/web/views/editviews.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/views/editviews.py Fri Mar 26 16:15:41 2010 +0100
@@ -15,7 +15,7 @@
from cubicweb.view import EntityView, StartupView
from cubicweb.selectors import (one_line_rset, non_final_entity,
match_search_state)
-from cubicweb.web import httpcache, captcha
+from cubicweb.web import httpcache
from cubicweb.web.views import baseviews, linksearch_select_url
@@ -95,17 +95,23 @@
else:
super(EditableFinalView, self).cell_call(row, col, props)
-
-class CaptchaView(StartupView):
- __regid__ = 'captcha'
+try:
+ from cubicweb.web import captcha
+except ImportError:
+ # PIL not installed
+ pass
+else:
+ class CaptchaView(StartupView):
+ __regid__ = 'captcha'
- http_cache_manager = httpcache.NoHTTPCacheManager
- binary = True
- templatable = False
- content_type = 'image/jpg'
+ http_cache_manager = httpcache.NoHTTPCacheManager
+ binary = True
+ templatable = False
+ content_type = 'image/jpg'
- def call(self):
- text, data = captcha.captcha(self._cw.vreg.config['captcha-font-file'],
- self._cw.vreg.config['captcha-font-size'])
- self._cw.set_session_data('captcha', text)
- self.w(data.read())
+ def call(self):
+ text, data = captcha.captcha(self._cw.vreg.config['captcha-font-file'],
+ self._cw.vreg.config['captcha-font-size'])
+ key = self._cw.form.get('captchakey', 'captcha')
+ self._cw.set_session_data(key, text)
+ self.w(data.read())
--- a/web/views/formrenderers.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/views/formrenderers.py Fri Mar 26 16:15:41 2010 +0100
@@ -224,6 +224,8 @@
w(u' class="error"')
w(u'>')
w(field.render(form, self))
+ if error:
+ self.render_error(w, error)
if self.display_help:
w(self.render_help(form, field))
w(u'</td></tr>')
@@ -241,7 +243,7 @@
def render_error(self, w, err):
"""return validation error for widget's field, if any"""
- w(u'<span class="error">%s</span>' % err)
+ w(u'<span class="errorMsg">%s</span>' % err)
--- a/web/views/iprogress.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/views/iprogress.py Fri Mar 26 16:15:41 2010 +0100
@@ -261,8 +261,7 @@
self._cw.html_headers.add_onload('draw_progressbar("canvas%s", %i, %i, %i, "%s");' %
(cid,
int(100.*done/maxi), int(100.*(done+todo)/maxi),
- int(100.*budget/maxi), color),
- jsoncall=self._cw.json_request)
+ int(100.*budget/maxi), color))
self.w(u'%s<br/>'
u'<canvas class="progressbar" id="canvas%s" width="100" height="10"></canvas>'
% (short_title.replace(' ',' '), cid))
--- a/web/views/plots.py Fri Mar 26 16:15:16 2010 +0100
+++ b/web/views/plots.py Fri Mar 26 16:15:41 2010 +0100
@@ -115,8 +115,7 @@
{'plotdefs': '\n'.join(plotdefs),
'figid': figid,
'plotdata': ','.join(plotdata),
- 'mode': self.timemode and "'time'" or 'null'},
- jsoncall=req.json_request)
+ 'mode': self.timemode and "'time'" or 'null'})
class PlotView(baseviews.AnyRsetView):