# HG changeset patch # User Sylvain Thénault # Date 1269616541 -3600 # Node ID bf8a53a11b6d24bc13f3dad7a8407283aae4ba35 # Parent ed048e317eae751d3a6191cad42470f137c9bf87# Parent d010f749c21d55cd85c5feb442b9cf816282953c backport stable diff -r ed048e317eae -r bf8a53a11b6d __pkginfo__.py --- 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" diff -r ed048e317eae -r bf8a53a11b6d _exceptions.py --- 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""" diff -r ed048e317eae -r bf8a53a11b6d appobject.py --- 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 diff -r ed048e317eae -r bf8a53a11b6d cwconfig.py --- 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 diff -r ed048e317eae -r bf8a53a11b6d cwctl.py --- 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) diff -r ed048e317eae -r bf8a53a11b6d cwvreg.py --- 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: diff -r ed048e317eae -r bf8a53a11b6d dbapi.py --- 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) diff -r ed048e317eae -r bf8a53a11b6d debian/changelog --- 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 Fri, 26 Mar 2010 15:53:01 +0100 + cubicweb (3.7.1-1) unstable; urgency=low * new upstream release diff -r ed048e317eae -r bf8a53a11b6d devtools/__init__.py --- 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', diff -r ed048e317eae -r bf8a53a11b6d devtools/testlib.py --- 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') diff -r ed048e317eae -r bf8a53a11b6d entities/schemaobjs.py --- 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""" diff -r ed048e317eae -r bf8a53a11b6d entities/test/unittest_wfobjs.py --- 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') diff -r ed048e317eae -r bf8a53a11b6d goa/goaconfig.py --- 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 diff -r ed048e317eae -r bf8a53a11b6d hooks/integrity.py --- 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 diff -r ed048e317eae -r bf8a53a11b6d hooks/syncsession.py --- 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) diff -r ed048e317eae -r bf8a53a11b6d hooks/test/unittest_hooks.py --- 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__': diff -r ed048e317eae -r bf8a53a11b6d hooks/workflow.py --- 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): diff -r ed048e317eae -r bf8a53a11b6d i18n/en.po --- 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 "" diff -r ed048e317eae -r bf8a53a11b6d i18n/es.po --- 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" diff -r ed048e317eae -r bf8a53a11b6d i18n/fr.po --- 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" diff -r ed048e317eae -r bf8a53a11b6d schema.py --- 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: diff -r ed048e317eae -r bf8a53a11b6d selectors.py --- 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 ############################################################ diff -r ed048e317eae -r bf8a53a11b6d server/__init__.py --- 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 diff -r ed048e317eae -r bf8a53a11b6d server/hook.py --- 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 diff -r ed048e317eae -r bf8a53a11b6d server/migractions.py --- 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() diff -r ed048e317eae -r bf8a53a11b6d server/repository.py --- 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): diff -r ed048e317eae -r bf8a53a11b6d server/serverconfig.py --- 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 diff -r ed048e317eae -r bf8a53a11b6d server/serverctl.py --- 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 diff -r ed048e317eae -r bf8a53a11b6d server/session.py --- 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 ################################################### diff -r ed048e317eae -r bf8a53a11b6d server/test/unittest_hook.py --- 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) diff -r ed048e317eae -r bf8a53a11b6d server/test/unittest_ldapuser.py --- 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: diff -r ed048e317eae -r bf8a53a11b6d web/captcha.py --- 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'
%s
' % 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 diff -r ed048e317eae -r bf8a53a11b6d web/controller.py --- 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' diff -r ed048e317eae -r bf8a53a11b6d web/data/cubicweb.edition.js --- 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- 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: diff -r ed048e317eae -r bf8a53a11b6d web/test/unittest_application.py --- 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) diff -r ed048e317eae -r bf8a53a11b6d web/test/unittest_pdf.py --- 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() + diff -r ed048e317eae -r bf8a53a11b6d web/test/unittest_views_basecontrollers.py --- 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() diff -r ed048e317eae -r bf8a53a11b6d web/views/editcontroller.py --- 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 diff -r ed048e317eae -r bf8a53a11b6d web/views/editviews.py --- 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()) diff -r ed048e317eae -r bf8a53a11b6d web/views/formrenderers.py --- 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'') @@ -241,7 +243,7 @@ def render_error(self, w, err): """return validation error for widget's field, if any""" - w(u'%s' % err) + w(u'%s' % err) diff -r ed048e317eae -r bf8a53a11b6d web/views/iprogress.py --- 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
' u'' % (short_title.replace(' ',' '), cid)) diff -r ed048e317eae -r bf8a53a11b6d web/views/plots.py --- 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):