class ControllerBase: """Common functions for objects controlled by portal_form_controller""" security = ClassSecurityInfo() security.declareObjectProtected(View) security.declareProtected(ManagePortal, 'manage_formActionsForm') manage_formActionsForm = PageTemplateFile('www/manage_formActionsForm', globals()) manage_formActionsForm.__name__ = 'manage_formActionsForm' security.declareProtected(ManagePortal, 'manage_formValidatorsForm') manage_formValidatorsForm = PageTemplateFile('www/manage_formValidatorsForm', globals()) manage_formValidatorsForm.__name__ = 'manage_formValidatorsForm' def _updateActions(self, container, old_id, new_id, move): """Copy action overrides stored in portal_form_controller from one object id to another""" actions = container.getFiltered(object_id=old_id) for a in actions: # if not container.match(new_id, a.getStatus(), a.getContextType(), a.getButton()): container.set(FormAction(new_id, a.getStatus(), a.getContextType(), a.getButton(), a.getActionType(), a.getActionArg())) if move: for a in actions: container.delete(a.getKey()) def _updateValidators(self, container, old_id, new_id, move): """Copy validator overrides stored in portal_form_controller from one object id to another""" validators = container.getFiltered(object_id=old_id) for v in validators: # if not container.match(new_id, v.getContextType(), v.getButton()): container.set(FormValidator(new_id, v.getContextType(), v.getButton(), v.getValidators())) if move: for v in validators: container.delete(v.getKey()) def _base_notifyOfCopyTo(self, container, op=0): self._old_id = self.getId() if op==0: # copy self._cloned_object_path = self.getPhysicalPath() def _fixup_old_ids(self, old_id): fc = getToolByName(self, 'portal_form_controller') id = self.getId() if old_id != id: if hasattr(aq_base(self), 'actions'): self._updateActions(self.actions, old_id, id, move=1) # swap the ids for the default actions self._updateActions(fc.actions, old_id, id, move=0) # copy the overrides if hasattr(aq_base(self), 'validators'): self._updateValidators(self.validators, old_id, id, move=1) # swap the ids for the default validators self._updateValidators(fc.validators, old_id, id, move=0) # copy the overrides def _base_manage_afterAdd(self, object, container): old_id = getattr(self, '_old_id', None) if old_id: self._fixup_old_ids(old_id) delattr(self, '_old_id') def _base_manage_afterClone(self, object): # clean up the old object cloned_object_path = getattr(self, '_cloned_object_path') cloned_object = self.getPhysicalRoot().unrestrictedTraverse(cloned_object_path) delattr(cloned_object, '_old_id') delattr(cloned_object, '_cloned_object_path') # clean up the new object delattr(self, '_cloned_object_path') security.declareProtected(ManagePortal, 'listActionTypes') def listActionTypes(self): """Return a list of available action types.""" return getToolByName(self, 'portal_form_controller').listActionTypes() security.declareProtected(ManagePortal, 'listFormValidators') def listFormValidators(self, override, **kwargs): """Return a list of existing validators. Validators can be filtered by specifying required attributes via kwargs""" controller = getToolByName(self, 'portal_form_controller') if override: return controller.validators.getFiltered(**kwargs) else: return self.validators.getFiltered(**kwargs) security.declareProtected(ManagePortal, 'listFormActions') def listFormActions(self, override, **kwargs): """Return a list of existing actions. Actions can be filtered by specifying required attributes via kwargs""" controller = getToolByName(self, 'portal_form_controller') if override: return controller.actions.getFiltered(**kwargs) else: return self.actions.getFiltered(**kwargs) security.declareProtected(ManagePortal, 'listContextTypes') def listContextTypes(self): """Return list of possible types for template context objects""" return getToolByName(self, 'portal_form_controller').listContextTypes() security.declareProtected(ManagePortal, 'manage_editFormValidators') def manage_editFormValidators(self, REQUEST): """Process form validator edit form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.validators else: container = self.validators controller._editFormValidators(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm') security.declareProtected(ManagePortal, 'manage_addFormValidators') def manage_addFormValidators(self, REQUEST): """Process form validator add form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.validators else: container = self.validators controller._addFormValidators(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm') security.declareProtected(ManagePortal, 'manage_delFormValidators') def manage_delFormValidators(self, REQUEST): """Process form validator delete form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.validators else: container = self.validators controller._delFormValidators(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm') security.declareProtected(ManagePortal, 'manage_editFormActions') def manage_editFormActions(self, REQUEST): """Process form action edit form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.actions else: container = self.actions controller._editFormActions(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm') security.declareProtected(ManagePortal, 'manage_addFormAction') def manage_addFormAction(self, REQUEST): """Process form action add form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.actions else: container = self.actions controller._addFormAction(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm') security.declareProtected(ManagePortal, 'manage_delFormActions') def manage_delFormActions(self, REQUEST): """Process form action delete form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.actions else: container = self.actions controller._delFormActions(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm') def getNext(self, controller_state, REQUEST): id = self.getId() status = controller_state.getStatus() context = controller_state.getContext() context_base = aq_base(context) context_type = getattr(context_base, 'portal_type', None) if context_type is None: context_type = getattr(context_base, '__class__', None) if context_type: context_type = getattr(context_type, '__name__', None) button = controller_state.getButton() controller = getToolByName(aq_inner(self), 'portal_form_controller') next_action = None try: next_action = controller.getAction(id, status, context_type, button) except ValueError: pass if next_action is None: try: if getattr(context_base, 'formcontroller_actions', None) is not None: next_action = context.formcontroller_actions.match(id, status, context_type, button) except ValueError: pass if next_action is None: try: next_action = self.actions.match(id, status, context_type, button) except ValueError: pass if next_action is None: next_action = controller_state.getNextAction() if next_action is None: # default for failure is to traverse to the form if status == 'failure': next_action=FormAction(id, status, ANY_CONTEXT, ANY_BUTTON, 'traverse_to', 'string:%s' % id, controller) if next_action is None: metadata_actions = [str(a) for a in self.actions.getFiltered(object_id=id)] zmi_actions = [str(a) for a in controller.actions.getFiltered(object_id=id)] raise ValueError, 'No next action found for %s.%s.%s.%s\nMetadata actions:\n%s\n\nZMI actions:\n%s\n' % \ (id, status, context_type, button, '\n'.join(metadata_actions), '\n'.join(zmi_actions)) REQUEST.set('controller_state', controller_state) return next_action.getAction()(controller_state) def getButton(self, controller_state, REQUEST): buttons = [] for k in REQUEST.form.keys(): if k.startswith('form.button.'): buttons.append(k) if buttons: # Clicking on an image button results in 2 button variables in REQUEST.form # (maybe 3),namely form.button.button_name.x, form.button.button_name.y, and # possibly form.button.button_name (not for IE, though) # If we see more than one key with the button prefix, try to handle sensibly. if len(buttons) > 1: buttons.sort(lambda x, y: cmp(len(x), len(y))) if buttons[0].endswith('.x') or buttons[0].endswith('.y'): buttons[0] = buttons[0][:-2] button = buttons[0][len('form.button.'):] controller_state.setButton(button) return controller_state def getValidators(self, controller_state, REQUEST): controller = getToolByName(self, 'portal_form_controller') context = controller_state.getContext() context_type = controller._getTypeName(context) button = controller_state.getButton() validators = None try: validators = controller.validators.match(self.id, context_type, button) if validators is not None: return validators except ValueError: pass try: if hasattr(aq_base(context), 'formcontroller_validators'): validators = context.formcontroller_validators.match(self.id, context_type, button) if validators is not None: return validators except ValueError: pass try: validators = self.validators.match(self.id, context_type, button) if validators is not None: return validators except ValueError: pass return FormValidator(self.id, ANY_CONTEXT, ANY_BUTTON, []) def _read_action_metadata(self, id, filepath): self.actions = FormActionContainer() metadata = FSMetadata(filepath) cfg = CMFConfigParser() if os.path.exists(filepath + '.metadata'): cfg.read(filepath + '.metadata') _buttons_for_status = {} actions = metadata._getSectionDict(cfg, 'actions') if actions is None: actions = {} for (k, v) in actions.items(): # action.STATUS.CONTEXT_TYPE.BUTTON = ACTION_TYPE:ACTION_ARG component = k.split('.') while len(component) < 4: component.append('') if component[0] != 'action': raise ValueError, '%s: Format for .metadata actions is action.STATUS.CONTEXT_TYPE.BUTTON = ACTION_TYPE:ACTION_ARG (not %s)' % (filepath, k) act = v.split(':',1) while len(act) < 2: act.append('') context_type = component[2] self.actions.set(FormAction(id, component[1], component[2], component[3], act[0], act[1])) status_key = str(component[1])+'.'+str(context_type) if _buttons_for_status.has_key(status_key): _buttons_for_status[status_key].append(component[3]) else: _buttons_for_status[status_key] = [component[3]] for (k, v) in _buttons_for_status.items(): if v and not '' in v: sk = k.split('.') status = sk[0] content_type = sk[1] if not status: status = 'ANY' if not content_type: content_type = 'ANY' log('%s: No default action specified for status %s, content type %s. Users of IE can submit pages using the return key, resulting in no button in the REQUEST. Please specify a default action for this case.' % (str(filepath), status, content_type)) def _read_validator_metadata(self, id, filepath): self.validators = FormValidatorContainer() metadata = FSMetadata(filepath) cfg = CMFConfigParser() if os.path.exists(filepath + '.metadata'): cfg.read(filepath + '.metadata') _buttons_for_status = {} validators = metadata._getSectionDict(cfg, 'validators') if validators is None: validators = {} for (k, v) in validators.items(): # validators.CONTEXT_TYPE.BUTTON = LIST component = k.split('.') while len(component) < 3: component.append('') if component[0] != 'validators': raise ValueError, '%s: Format for .metadata validators is validators.CONTEXT_TYPE.BUTTON = LIST (not %s)' % (filepath, k) context_type = component[1] self.validators.set(FormValidator(id, component[1], component[2], v)) status_key = str(context_type) if _buttons_for_status.has_key(status_key): _buttons_for_status[status_key].append(component[2]) else: _buttons_for_status[status_key] = [component[2]] for (k, v) in _buttons_for_status.items(): if v and not '' in v: content_type = k if not content_type: content_type = 'ANY' log('%s: No default validators specified for content type %s. Users of IE can submit pages using the return key, resulting in no button in the REQUEST. Please specify default validators for this case.' % (str(filepath), content_type)) security.declarePublic('writableDefaults') def writableDefaults(self): """Can default actions and validators be modified?""" return 1
class FormController(UniqueObject, SimpleItemWithProperties): """ """ security = ClassSecurityInfo() id = 'portal_form_controller' title = 'Manages form validation and post-validation actions' meta_type = 'Form Controller Tool' manage_options = (( { 'label': 'Overview', 'action': 'manage_overview' }, { 'label': 'Documentation', 'action': 'manage_docs' }, { 'label': 'Validation', 'action': 'manage_formValidatorsForm' }, { 'label': 'Actions', 'action': 'manage_formActionsForm' }, { 'label': 'Purge', 'action': 'manage_purgeForm' }, ) + SimpleItemWithProperties.manage_options) security.declareProtected(ManagePortal, 'manage_overview') manage_overview = PageTemplateFile(os.path.join('www', 'manage_overview'), globals()) manage_overview.__name__ = 'manage_overview' manage_overview._need__name__ = 0 security.declareProtected(ManagePortal, 'manage_docs') manage_docs = PageTemplateFile(os.path.join('www', 'manage_docs'), globals()) manage_docs.__name__ = 'manage_docs' security.declareProtected(ManagePortal, 'manage_formActionsForm') manage_formActionsForm = PageTemplateFile( os.path.join('www', 'manage_formActionsForm'), globals()) manage_formActionsForm.__name__ = 'manage_formActionsForm' security.declareProtected(ManagePortal, 'manage_formValidatorsForm') manage_formValidatorsForm = PageTemplateFile( os.path.join('www', 'manage_formValidatorsForm'), globals()) manage_formValidatorsForm.__name__ = 'manage_formValidatorsForm' security.declareProtected(ManagePortal, 'manage_purgeForm') manage_purgeForm = PageTemplateFile( os.path.join('www', 'manage_purgeForm'), globals()) manage_purgeForm.__name__ = 'manage_purgeForm' # some aliases security.declareProtected(ManagePortal, 'manage_main') manage_main = manage_overview security.declareProtected(ManagePortal, 'index_html') index_html = None wwwpath = os.path.join(package_home(fc_globals), 'www') f = open(os.path.join(wwwpath, 'docs.stx'), 'r') _docs = f.read() f.close() _docs = stx2html(_docs) def __init__(self): self.actions = FormActionContainer() self.validators = FormValidatorContainer() security.declarePublic('docs') def docs(self): """Returns FormController docs formatted as HTML""" return self._docs def view(self, REQUEST, RESPONSE): """Invokes the default view.""" return self.__call__(REQUEST, RESPONSE) def __call__(self, REQUEST, RESPONSE): """Invokes the default view.""" if RESPONSE is not None: RESPONSE.redirect('%s/manage_main' % self.absolute_url()) def _checkId(self, id): """See if an id is valid CMF/Plone id""" portal = getToolByName(self, 'portal_url').getPortalObject() if not id: return 'Empty id' s = bad_id(id) if s: return '\'%s\' is not a valid id' % (id) # extra checks for Plone sites if portal.__class__.__name__ == 'PloneSite': props = getToolByName(portal, 'portal_properties', None) if props is not None: if hasattr(props, 'site_properties') and \ hasattr(props.site_properties, 'invalid_ids'): if id in props.site_properties.invalid_ids: return '\'%s\' is a reserved id' % (id) # Web-accessible methods security.declareProtected(ManagePortal, 'listActionTypes') def listActionTypes(self): """Return a list of available action types.""" keys = form_action_types.keys() keys.sort() action_types = [] for k in keys: action_types.append(form_action_types.get(k)) return action_types def validActionTypes(self): return form_action_types.keys() security.declareProtected(ManagePortal, 'listContextTypes') def listContextTypes(self): """Return list of possible types for template context objects""" types_tool = getToolByName(self, 'portal_types') return types_tool.listContentTypes() security.declareProtected(ManagePortal, 'listFormValidators') def listFormValidators(self, override=None, **kwargs): """Return a list of existing validators. Validators can be filtered by specifying required attributes via kwargs""" return self.validators.getFiltered(**kwargs) security.declareProtected(ManagePortal, 'listFormActions') def listFormActions(self, override=None, **kwargs): """Return a list of existing actions. Actions can be filtered by specifying required attributes via kwargs""" return self.actions.getFiltered(**kwargs) security.declareProtected(ManagePortal, 'manage_editFormValidators') def manage_editFormValidators(self, REQUEST): """Process form validator edit form""" self._editFormValidators(self.validators, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formValidatorsForm') security.declarePrivate('_editFormValidators') def _editFormValidators(self, container, REQUEST): for k in REQUEST.form.keys(): if k.startswith('old_object_id_'): n = k[len('old_object_id_'):] old_object_id = REQUEST.form.get('old_object_id_' + n) old_context_type = REQUEST.form.get('old_context_type_' + n) old_button = REQUEST.form.get('old_button_' + n) container.delete( FormValidatorKey(old_object_id, old_context_type, old_button, self)) object_id = REQUEST.form.get('object_id_' + n) context_type = REQUEST.form.get('context_type_' + n) button = REQUEST.form.get('button_' + n) validators = REQUEST.form.get('validators_' + n) container.set( FormValidator(object_id, context_type, button, validators, self)) # Method for programmatically adding validators security.declareProtected(ManagePortal, 'addFormValidators') def addFormValidators(self, object_id, context_type, button, validators): self.validators.set( FormValidator(object_id, context_type, button, validators, self)) security.declareProtected(ManagePortal, 'manage_addFormValidators') def manage_addFormValidators(self, REQUEST): """Process form validator add form""" self._addFormValidators(self.validators, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formValidatorsForm') security.declarePrivate('_addFormValidators') def _addFormValidators(self, container, REQUEST): object_id = REQUEST.form.get('new_object_id') context_type = REQUEST.form.get('new_context_type') button = REQUEST.form.get('new_button') validators = REQUEST.form.get('new_validators') container.set( FormValidator(object_id, context_type, button, validators, self)) security.declareProtected(ManagePortal, 'manage_delFormValidators') def manage_delFormValidators(self, REQUEST): """Process form validator delete form""" self._delFormValidators(self.validators, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formValidatorsForm') security.declarePrivate('_delFormValidators') def _delFormValidators(self, container, REQUEST): for k in REQUEST.form.keys(): if k.startswith('del_id_'): n = k[len('del_id_'):] old_object_id = REQUEST.form.get('old_object_id_' + n) old_context_type = REQUEST.form.get('old_context_type_' + n) old_button = REQUEST.form.get('old_button_' + n) container.delete( FormValidatorKey(old_object_id, old_context_type, old_button, self)) security.declareProtected(ManagePortal, 'manage_editFormActions') def manage_editFormActions(self, REQUEST): """Process form action edit form""" self._editFormActions(self.actions, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formActionsForm') security.declarePrivate('_editFormActions') def _editFormActions(self, container, REQUEST): for k in REQUEST.form.keys(): if k.startswith('old_object_id_'): n = k[len('old_object_id_'):] old_object_id = REQUEST.form.get('old_object_id_' + n) old_status = REQUEST.form.get('old_status_' + n) old_context_type = REQUEST.form.get('old_context_type_' + n) old_button = REQUEST.form.get('old_button_' + n) container.delete( FormActionKey(old_object_id, old_status, old_context_type, old_button, self)) object_id = REQUEST.form.get('object_id_' + n) status = REQUEST.form.get('status_' + n) context_type = REQUEST.form.get('context_type_' + n) button = REQUEST.form.get('button_' + n) action_type = REQUEST.form.get('action_type_' + n) action_arg = REQUEST.form.get('action_arg_' + n) container.set( FormAction(object_id, status, context_type, button, action_type, action_arg, self)) # Method for programmatically adding actions security.declareProtected(ManagePortal, 'addFormAction') def addFormAction(self, object_id, status, context_type, button, action_type, action_arg): self.actions.set( FormAction(object_id, status, context_type, button, action_type, action_arg, self)) security.declareProtected(ManagePortal, 'manage_addFormAction') def manage_addFormAction(self, REQUEST): """Process form action add form""" self._addFormAction(self.actions, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formActionsForm') security.declarePrivate('_addFormAction') def _addFormAction(self, container, REQUEST): object_id = REQUEST.form.get('new_object_id') status = REQUEST.form.get('new_status').strip() context_type = REQUEST.form.get('new_context_type').strip() button = REQUEST.form.get('new_button').strip() action_type = REQUEST.form.get('new_action_type').strip() action_arg = REQUEST.form.get('new_action_arg').strip() container.set( FormAction(object_id, status, context_type, button, action_type, action_arg, self)) security.declareProtected(ManagePortal, 'manage_delFormActions') def manage_delFormActions(self, REQUEST): """Process form action delete form""" self._delFormActions(self.actions, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formActionsForm') security.declarePrivate('_delFormActions') def _delFormActions(self, container, REQUEST): for k in REQUEST.form.keys(): if k.startswith('del_id_'): n = k[len('del_id_'):] old_object_id = REQUEST.form.get('old_object_id_' + n) old_status = REQUEST.form.get('old_status_' + n) old_context_type = REQUEST.form.get('old_context_type_' + n) old_button = REQUEST.form.get('old_button_' + n) container.delete( FormActionKey(old_object_id, old_status, old_context_type, old_button, self)) def getValidators(self, id, context_type, button): return self.validators.match(id, context_type, button) def getAction(self, id, status, context_type, button): return self.actions.match(id, status, context_type, button) def getState(self, obj, is_validator): id = obj.id controller_state = self.REQUEST.get('controller_state', None) # The variable 'env' is a dictionary that is passed # along using record variables on forms so that you can keep # some state between different forms. env = self.REQUEST.get('form_env', {}) # Make sure controller_state is something generated by us, not something submitted via POST or GET if controller_state and getattr(controller_state, '__class__', None) != ControllerState: controller_state = None if not is_validator: # Construct a new controller state object or clear out the existing # one unless we are in a validator script. if controller_state is None: # XXX - errors={} shouldn't need to be set here, but without it # I encountered what looks like a weird Zope caching bug. # To reproduce, install CMFFormControllerDemo, go to portal_skins # then to CMFFormControllerDemo, then click on test_form and # click the Test tab. Submit the form with an empty text box. # Now back up to the ZMI and click the Test tab again. If # errors={} is left out in the line below, ControllerState.__init__() # gets called with errors set to a non-empty value (more # precisely, it is set to the value that was in REQUEST.controller_state. # Yikes! controller_state = ControllerState(errors={}) else: # clear out values we don't want to carry over from previous states. controller_state.setStatus('success') controller_state.setNextAction(None) #controller_state.setButton(None) controller_state.set(id=id, context=obj.__parent__) else: if controller_state is None: raise ValueError, 'No controller state available. ' + \ 'This commonly occurs when a ControllerValidator (.vpy) ' + \ 'script is invoked via the validation mechanism in the ' + \ 'portal_form tool. If you are using a package designed to ' + \ 'be used with portal_form, you are probably inadvertently ' + \ 'invoking a validator designed for use with CMFFormController (e.g. validate_id). ' + \ 'If you are using a package designed to be used with CMFFormController, you probably ' + \ 'have a "portal_form" in your URL that needs to be removed.' controller_state._setValidating(is_validator) # Pass environment along, with care so we don't override # existing variables. for k, v in env.items(): controller_state.kwargs.setdefault(k, v) self.REQUEST.set('controller_state', controller_state) return controller_state def validate(self, controller_state, REQUEST, validators, argdict=None): if argdict is None: args = REQUEST.args kwargs = REQUEST else: args = () if REQUEST is None: kwargs = argdict else: kwargs = {} for k in REQUEST.keys(): if k in ('SESSION', ): continue kwargs[k] = REQUEST[k] kwargs.update(argdict) context = controller_state.getContext() if validators is None: REQUEST.set('controller_state', controller_state) return controller_state for v in validators: REQUEST.set('controller_state', controller_state) if controller_state.hasValidated(v): continue try: # make sure validator exists obj = context.restrictedTraverse(v, default=None) if obj is None: raise ValueError, 'Unable to find validator %s\n' % str(v) if not getattr(obj, 'is_validator', 1): raise ValueError, '%s is not a CMFFormController validator' % str( v) REQUEST = controller_state.getContext().REQUEST controller_state = mapply(obj, args, kwargs, call_object, 1, missing_name, dont_publish_class, REQUEST, bind=1) if controller_state is None or getattr( controller_state, '__class__', None) != ControllerState: raise ValueError, 'Validator %s did not return the state object' % str( v) controller_state._addValidator(v) except ValidationError, e: # if a validator raises a ValidatorException, execution of # validators is halted and the controller_state is set to # the controller_state embedded in the exception controller_state = e.controller_state state_class = getattr(controller_state, '__class__', None) if state_class != ControllerState: raise Exception, 'Bad ValidationError state (type = %s)' % str( state_class) break state_class = getattr(controller_state, '__class__', None) if state_class != ControllerState: raise Exception, 'Bad validator return type from validator %s (%s)' % ( str(v), str(state_class)) REQUEST.set('controller_state', controller_state) REQUEST.set('controller_state', controller_state) return controller_state
class ControllerBase: """Common functions for objects controlled by portal_form_controller""" security = ClassSecurityInfo() security.declareObjectProtected(View) security.declareProtected(ManagePortal, 'manage_formActionsForm') manage_formActionsForm = PageTemplateFile('www/manage_formActionsForm', globals()) manage_formActionsForm.__name__ = 'manage_formActionsForm' security.declareProtected(ManagePortal, 'manage_formValidatorsForm') manage_formValidatorsForm = PageTemplateFile( 'www/manage_formValidatorsForm', globals()) manage_formValidatorsForm.__name__ = 'manage_formValidatorsForm' def _updateActions(self, container, old_id, new_id, move): """Copy action overrides stored in portal_form_controller from one object id to another""" actions = container.getFiltered(object_id=old_id) for a in actions: # if not container.match(new_id, a.getStatus(), a.getContextType(), a.getButton()): container.set( FormAction(new_id, a.getStatus(), a.getContextType(), a.getButton(), a.getActionType(), a.getActionArg())) if move: for a in actions: container.delete(a.getKey()) def _updateValidators(self, container, old_id, new_id, move): """Copy validator overrides stored in portal_form_controller from one object id to another""" validators = container.getFiltered(object_id=old_id) for v in validators: # if not container.match(new_id, v.getContextType(), v.getButton()): container.set( FormValidator(new_id, v.getContextType(), v.getButton(), v.getValidators())) if move: for v in validators: container.delete(v.getKey()) def _base_notifyOfCopyTo(self, container, op=0): self._old_id = self.getId() if op == 0: # copy self._cloned_object_path = self.getPhysicalPath() def _fixup_old_ids(self, old_id): fc = getToolByName(self, 'portal_form_controller') id = self.getId() if old_id != id: if hasattr(aq_base(self), 'actions'): self._updateActions( self.actions, old_id, id, move=1) # swap the ids for the default actions self._updateActions(fc.actions, old_id, id, move=0) # copy the overrides if hasattr(aq_base(self), 'validators'): self._updateValidators( self.validators, old_id, id, move=1) # swap the ids for the default validators self._updateValidators(fc.validators, old_id, id, move=0) # copy the overrides def _base_manage_afterAdd(self, object, container): old_id = getattr(self, '_old_id', None) if old_id: self._fixup_old_ids(old_id) delattr(self, '_old_id') def _base_manage_afterClone(self, object): # clean up the old object cloned_object_path = getattr(self, '_cloned_object_path') cloned_object = self.getPhysicalRoot().unrestrictedTraverse( cloned_object_path) delattr(cloned_object, '_old_id') delattr(cloned_object, '_cloned_object_path') # clean up the new object delattr(self, '_cloned_object_path') security.declareProtected(ManagePortal, 'listActionTypes') def listActionTypes(self): """Return a list of available action types.""" return getToolByName(self, 'portal_form_controller').listActionTypes() security.declareProtected(ManagePortal, 'listFormValidators') def listFormValidators(self, override, **kwargs): """Return a list of existing validators. Validators can be filtered by specifying required attributes via kwargs""" controller = getToolByName(self, 'portal_form_controller') if override: return controller.validators.getFiltered(**kwargs) else: return self.validators.getFiltered(**kwargs) security.declareProtected(ManagePortal, 'listFormActions') def listFormActions(self, override, **kwargs): """Return a list of existing actions. Actions can be filtered by specifying required attributes via kwargs""" controller = getToolByName(self, 'portal_form_controller') if override: return controller.actions.getFiltered(**kwargs) else: return self.actions.getFiltered(**kwargs) security.declareProtected(ManagePortal, 'listContextTypes') def listContextTypes(self): """Return list of possible types for template context objects""" return getToolByName(self, 'portal_form_controller').listContextTypes() security.declareProtected(ManagePortal, 'manage_editFormValidators') def manage_editFormValidators(self, REQUEST): """Process form validator edit form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.validators else: container = self.validators controller._editFormValidators(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formValidatorsForm') security.declareProtected(ManagePortal, 'manage_addFormValidators') def manage_addFormValidators(self, REQUEST): """Process form validator add form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.validators else: container = self.validators controller._addFormValidators(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formValidatorsForm') security.declareProtected(ManagePortal, 'manage_delFormValidators') def manage_delFormValidators(self, REQUEST): """Process form validator delete form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.validators else: container = self.validators controller._delFormValidators(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formValidatorsForm') security.declareProtected(ManagePortal, 'manage_editFormActions') def manage_editFormActions(self, REQUEST): """Process form action edit form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.actions else: container = self.actions controller._editFormActions(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formActionsForm') security.declareProtected(ManagePortal, 'manage_addFormAction') def manage_addFormAction(self, REQUEST): """Process form action add form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.actions else: container = self.actions controller._addFormAction(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formActionsForm') security.declareProtected(ManagePortal, 'manage_delFormActions') def manage_delFormActions(self, REQUEST): """Process form action delete form""" controller = getToolByName(self, 'portal_form_controller') if REQUEST.form.get('override', 0): container = controller.actions else: container = self.actions controller._delFormActions(container, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_formActionsForm') def getNext(self, controller_state, REQUEST): id = self.getId() status = controller_state.getStatus() context = controller_state.getContext() context_base = aq_base(context) context_type = getattr(context_base, 'portal_type', None) if context_type is None: context_type = getattr(context_base, '__class__', None) if context_type: context_type = getattr(context_type, '__name__', None) button = controller_state.getButton() controller = getToolByName(aq_inner(self), 'portal_form_controller') next_action = None try: next_action = controller.getAction(id, status, context_type, button) except ValueError: pass if next_action is None: try: if getattr(context_base, 'formcontroller_actions', None) is not None: next_action = context.formcontroller_actions.match( id, status, context_type, button) except ValueError: pass if next_action is None: try: next_action = self.actions.match(id, status, context_type, button) except ValueError: pass if next_action is None: next_action = controller_state.getNextAction() if next_action is None: # default for failure is to traverse to the form if status == 'failure': next_action = FormAction(id, status, ANY_CONTEXT, ANY_BUTTON, 'traverse_to', 'string:%s' % id, controller) if next_action is None: metadata_actions = [ str(a) for a in self.actions.getFiltered(object_id=id) ] zmi_actions = [ str(a) for a in controller.actions.getFiltered( object_id=id) ] raise ValueError, 'No next action found for %s.%s.%s.%s\nMetadata actions:\n%s\n\nZMI actions:\n%s\n' % \ (id, status, context_type, button, '\n'.join(metadata_actions), '\n'.join(zmi_actions)) REQUEST.set('controller_state', controller_state) return next_action.getAction()(controller_state) def getButton(self, controller_state, REQUEST): buttons = [] for k in REQUEST.form.keys(): if k.startswith('form.button.'): buttons.append(k) if buttons: # Clicking on an image button results in 2 button variables in REQUEST.form # (maybe 3),namely form.button.button_name.x, form.button.button_name.y, and # possibly form.button.button_name (not for IE, though) # If we see more than one key with the button prefix, try to handle sensibly. if len(buttons) > 1: buttons.sort(lambda x, y: cmp(len(x), len(y))) if buttons[0].endswith('.x') or buttons[0].endswith('.y'): buttons[0] = buttons[0][:-2] button = buttons[0][len('form.button.'):] controller_state.setButton(button) return controller_state def getValidators(self, controller_state, REQUEST): controller = getToolByName(self, 'portal_form_controller') context = controller_state.getContext() context_type = controller._getTypeName(context) button = controller_state.getButton() validators = None try: validators = controller.validators.match(self.id, context_type, button) if validators is not None: return validators except ValueError: pass try: if hasattr(aq_base(context), 'formcontroller_validators'): validators = context.formcontroller_validators.match( self.id, context_type, button) if validators is not None: return validators except ValueError: pass try: validators = self.validators.match(self.id, context_type, button) if validators is not None: return validators except ValueError: pass return FormValidator(self.id, ANY_CONTEXT, ANY_BUTTON, []) def _read_action_metadata(self, id, filepath): self.actions = FormActionContainer() metadata = FSMetadata(filepath) cfg = CMFConfigParser() if os.path.exists(filepath + '.metadata'): cfg.read(filepath + '.metadata') _buttons_for_status = {} actions = metadata._getSectionDict(cfg, 'actions') if actions is None: actions = {} for (k, v) in actions.items(): # action.STATUS.CONTEXT_TYPE.BUTTON = ACTION_TYPE:ACTION_ARG component = k.split('.') while len(component) < 4: component.append('') if component[0] != 'action': raise ValueError, '%s: Format for .metadata actions is action.STATUS.CONTEXT_TYPE.BUTTON = ACTION_TYPE:ACTION_ARG (not %s)' % ( filepath, k) act = v.split(':', 1) while len(act) < 2: act.append('') context_type = component[2] self.actions.set( FormAction(id, component[1], component[2], component[3], act[0], act[1])) status_key = str(component[1]) + '.' + str(context_type) if _buttons_for_status.has_key(status_key): _buttons_for_status[status_key].append(component[3]) else: _buttons_for_status[status_key] = [component[3]] for (k, v) in _buttons_for_status.items(): if v and not '' in v: sk = k.split('.') status = sk[0] content_type = sk[1] if not status: status = 'ANY' if not content_type: content_type = 'ANY' log('%s: No default action specified for status %s, content type %s. Users of IE can submit pages using the return key, resulting in no button in the REQUEST. Please specify a default action for this case.' % (str(filepath), status, content_type), log_level=logging.DEBUG) def _read_validator_metadata(self, id, filepath): self.validators = FormValidatorContainer() metadata = FSMetadata(filepath) cfg = CMFConfigParser() if os.path.exists(filepath + '.metadata'): cfg.read(filepath + '.metadata') _buttons_for_status = {} validators = metadata._getSectionDict(cfg, 'validators') if validators is None: validators = {} for (k, v) in validators.items(): # validators.CONTEXT_TYPE.BUTTON = LIST component = k.split('.') while len(component) < 3: component.append('') if component[0] != 'validators': raise ValueError, '%s: Format for .metadata validators is validators.CONTEXT_TYPE.BUTTON = LIST (not %s)' % ( filepath, k) context_type = component[1] self.validators.set( FormValidator(id, component[1], component[2], v)) status_key = str(context_type) if _buttons_for_status.has_key(status_key): _buttons_for_status[status_key].append(component[2]) else: _buttons_for_status[status_key] = [component[2]] for (k, v) in _buttons_for_status.items(): if v and not '' in v: content_type = k if not content_type: content_type = 'ANY' log('%s: No default validators specified for content type %s. Users of IE can submit pages using the return key, resulting in no button in the REQUEST. Please specify default validators for this case.' % (str(filepath), content_type), log_level=logging.DEBUG) security.declarePublic('writableDefaults') def writableDefaults(self): """Can default actions and validators be modified?""" return 1
class FormController(UniqueObject, SimpleItemWithProperties): """ """ security = ClassSecurityInfo() id = 'portal_form_controller' title = 'Manages form validation and post-validation actions' meta_type= 'Form Controller Tool' implements(IFormControllerTool) manage_options = ( ({'label':'Overview', 'action':'manage_overview'}, {'label':'Documentation', 'action':'manage_docs'}, {'label': 'Validation', 'action': 'manage_formValidatorsForm'}, {'label': 'Actions', 'action': 'manage_formActionsForm'}, {'label': 'Purge', 'action': 'manage_purgeForm'},) + SimpleItemWithProperties.manage_options) security.declareProtected(ManagePortal, 'manage_overview') manage_overview = PageTemplateFile(os.path.join('www','manage_overview'), globals()) manage_overview.__name__ = 'manage_overview' manage_overview._need__name__ = 0 security.declareProtected(ManagePortal, 'manage_docs') manage_docs = PageTemplateFile(os.path.join('www','manage_docs'), globals()) manage_docs.__name__ = 'manage_docs' security.declareProtected(ManagePortal, 'manage_formActionsForm') manage_formActionsForm = PageTemplateFile(os.path.join('www','manage_formActionsForm'), globals()) manage_formActionsForm.__name__ = 'manage_formActionsForm' security.declareProtected(ManagePortal, 'manage_formValidatorsForm') manage_formValidatorsForm = PageTemplateFile(os.path.join('www','manage_formValidatorsForm'), globals()) manage_formValidatorsForm.__name__ = 'manage_formValidatorsForm' security.declareProtected(ManagePortal, 'manage_purgeForm') manage_purgeForm = PageTemplateFile(os.path.join('www','manage_purgeForm'), globals()) manage_purgeForm.__name__ = 'manage_purgeForm' # some aliases security.declareProtected(ManagePortal, 'manage_main') manage_main = manage_overview security.declareProtected(ManagePortal, 'index_html') index_html = None wwwpath = os.path.join(Globals.package_home(fc_globals), 'www') f = open(os.path.join(wwwpath, 'docs.stx'), 'r') _docs = f.read() f.close() _docs = HTML(_docs) def __init__(self): self.actions = FormActionContainer() self.validators = FormValidatorContainer() security.declarePublic('docs') def docs(self): """Returns FormController docs formatted as HTML""" return self._docs def view(self, REQUEST, RESPONSE): """Invokes the default view.""" return self.__call__(REQUEST, RESPONSE) def __call__(self, REQUEST, RESPONSE): """Invokes the default view.""" if RESPONSE is not None: RESPONSE.redirect('%s/manage_main' % self.absolute_url()) def _checkId(self, id): """See if an id is valid CMF/Plone id""" portal = getToolByName(self, 'portal_url').getPortalObject() if not id: return 'Empty id' s = bad_id(id) if s: return '\'%s\' is not a valid id' % (id) # extra checks for Plone sites if portal.__class__.__name__ == 'PloneSite': if hasattr(portal, 'portal_properties') and \ hasattr(portal.portal_properties, 'site_properties') and \ hasattr(portal.portal_properties.site_properties, 'invalid_ids'): if id in portal.portal_properties.site_properties.invalid_ids: return '\'%s\' is a reserved id' % (id) # Web-accessible methods security.declareProtected(ManagePortal, 'listActionTypes') def listActionTypes(self): """Return a list of available action types.""" keys = form_action_types.keys() keys.sort() action_types = [] for k in keys: action_types.append(form_action_types.get(k)) return action_types def validActionTypes(self): return form_action_types.keys() security.declareProtected(ManagePortal, 'listContextTypes') def listContextTypes(self): """Return list of possible types for template context objects""" types_tool = getToolByName(self, 'portal_types') return types_tool.listContentTypes() security.declareProtected(ManagePortal, 'listFormValidators') def listFormValidators(self, override=None, **kwargs): """Return a list of existing validators. Validators can be filtered by specifying required attributes via kwargs""" return self.validators.getFiltered(**kwargs) security.declareProtected(ManagePortal, 'listFormActions') def listFormActions(self, override=None, **kwargs): """Return a list of existing actions. Actions can be filtered by specifying required attributes via kwargs""" return self.actions.getFiltered(**kwargs) security.declareProtected(ManagePortal, 'manage_editFormValidators') def manage_editFormValidators(self, REQUEST): """Process form validator edit form""" self._editFormValidators(self.validators, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm') security.declarePrivate('_editFormValidators') def _editFormValidators(self, container, REQUEST): for k in REQUEST.form.keys(): if k.startswith('old_object_id_'): n = k[len('old_object_id_'):] old_object_id = REQUEST.form.get('old_object_id_'+n) old_context_type = REQUEST.form.get('old_context_type_'+n) old_button = REQUEST.form.get('old_button_'+n) container.delete(FormValidatorKey(old_object_id, old_context_type, old_button, self)) object_id = REQUEST.form.get('object_id_'+n) context_type = REQUEST.form.get('context_type_'+n) button = REQUEST.form.get('button_'+n) validators = REQUEST.form.get('validators_'+n) container.set(FormValidator(object_id, context_type, button, validators, self)) # Method for programmatically adding validators security.declareProtected(ManagePortal, 'addFormValidators') def addFormValidators(self, object_id, context_type, button, validators): self.validators.set(FormValidator(object_id, context_type, button, validators, self)) security.declareProtected(ManagePortal, 'manage_addFormValidators') def manage_addFormValidators(self, REQUEST): """Process form validator add form""" self._addFormValidators(self.validators, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm') security.declarePrivate('_addFormValidators') def _addFormValidators(self, container, REQUEST): object_id = REQUEST.form.get('new_object_id') context_type = REQUEST.form.get('new_context_type') button = REQUEST.form.get('new_button') validators = REQUEST.form.get('new_validators') container.set(FormValidator(object_id, context_type, button, validators, self)) security.declareProtected(ManagePortal, 'manage_delFormValidators') def manage_delFormValidators(self, REQUEST): """Process form validator delete form""" self._delFormValidators(self.validators, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm') security.declarePrivate('_delFormValidators') def _delFormValidators(self, container, REQUEST): for k in REQUEST.form.keys(): if k.startswith('del_id_'): n = k[len('del_id_'):] old_object_id = REQUEST.form.get('old_object_id_'+n) old_context_type = REQUEST.form.get('old_context_type_'+n) old_button = REQUEST.form.get('old_button_'+n) container.delete(FormValidatorKey(old_object_id, old_context_type, old_button, self)) security.declareProtected(ManagePortal, 'manage_editFormActions') def manage_editFormActions(self, REQUEST): """Process form action edit form""" self._editFormActions(self.actions, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm') security.declarePrivate('_editFormActions') def _editFormActions(self, container, REQUEST): for k in REQUEST.form.keys(): if k.startswith('old_object_id_'): n = k[len('old_object_id_'):] old_object_id = REQUEST.form.get('old_object_id_'+n) old_status = REQUEST.form.get('old_status_'+n) old_context_type = REQUEST.form.get('old_context_type_'+n) old_button = REQUEST.form.get('old_button_'+n) container.delete(FormActionKey(old_object_id, old_status, old_context_type, old_button, self)) object_id = REQUEST.form.get('object_id_'+n) status = REQUEST.form.get('status_'+n) context_type = REQUEST.form.get('context_type_'+n) button = REQUEST.form.get('button_'+n) action_type = REQUEST.form.get('action_type_'+n) action_arg = REQUEST.form.get('action_arg_'+n) container.set(FormAction(object_id, status, context_type, button, action_type, action_arg, self)) # Method for programmatically adding actions security.declareProtected(ManagePortal, 'addFormAction') def addFormAction(self, object_id, status, context_type, button, action_type, action_arg): self.actions.set(FormAction(object_id, status, context_type, button, action_type, action_arg, self)) security.declareProtected(ManagePortal, 'manage_addFormAction') def manage_addFormAction(self, REQUEST): """Process form action add form""" self._addFormAction(self.actions, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm') security.declarePrivate('_addFormAction') def _addFormAction(self, container, REQUEST): object_id = REQUEST.form.get('new_object_id') status = REQUEST.form.get('new_status').strip() context_type = REQUEST.form.get('new_context_type').strip() button = REQUEST.form.get('new_button').strip() action_type = REQUEST.form.get('new_action_type').strip() action_arg = REQUEST.form.get('new_action_arg').strip() container.set(FormAction(object_id, status, context_type, button, action_type, action_arg, self)) security.declareProtected(ManagePortal, 'manage_delFormActions') def manage_delFormActions(self, REQUEST): """Process form action delete form""" self._delFormActions(self.actions, REQUEST) return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm') security.declarePrivate('_delFormActions') def _delFormActions(self, container, REQUEST): for k in REQUEST.form.keys(): if k.startswith('del_id_'): n = k[len('del_id_'):] old_object_id = REQUEST.form.get('old_object_id_'+n) old_status = REQUEST.form.get('old_status_'+n) old_context_type = REQUEST.form.get('old_context_type_'+n) old_button = REQUEST.form.get('old_button_'+n) container.delete(FormActionKey(old_object_id, old_status, old_context_type, old_button, self)) def getValidators(self, id, context_type, button): return self.validators.match(id, context_type, button) def getAction(self, id, status, context_type, button): return self.actions.match(id, status, context_type, button) def getState(self, obj, is_validator): id = obj.id controller_state = self.REQUEST.get('controller_state', None) # The variable 'env' is a dictionary that is passed # along using record variables on forms so that you can keep # some state between different forms. env = self.REQUEST.get('form_env', {}) # Make sure controller_state is something generated by us, not something submitted via POST or GET if controller_state and getattr(controller_state, '__class__', None) != ControllerState: controller_state = None if not is_validator: # Construct a new controller state object or clear out the existing # one unless we are in a validator script. if controller_state is None: # XXX - errors={} shouldn't need to be set here, but without it # I encountered what looks like a weird Zope caching bug. # To reproduce, install CMFFormControllerDemo, go to portal_skins # then to CMFFormControllerDemo, then click on test_form and # click the Test tab. Submit the form with an empty text box. # Now back up to the ZMI and click the Test tab again. If # errors={} is left out in the line below, ControllerState.__init__() # gets called with errors set to a non-empty value (more # precisely, it is set to the value that was in REQUEST.controller_state. # Yikes! controller_state = ControllerState(errors={}) else: # clear out values we don't want to carry over from previous states. controller_state.setStatus('success') controller_state.setNextAction(None) #controller_state.setButton(None) controller_state.set(id=id, context=obj.getParentNode()) else: if controller_state is None: raise ValueError, 'No controller state available. ' + \ 'This commonly occurs when a ControllerValidator (.vpy) ' + \ 'script is invoked via the validation mechanism in the ' + \ 'portal_form tool. If you are using a package designed to ' + \ 'be used with portal_form, you are probably inadvertently ' + \ 'invoking a validator designed for use with CMFFormController (e.g. validate_id). ' + \ 'If you are using a package designed to be used with CMFFormController, you probably ' + \ 'have a "portal_form" in your URL that needs to be removed.' controller_state._setValidating(is_validator) # Pass environment along, with care so we don't override # existing variables. for k, v in env.items(): controller_state.kwargs.setdefault(k, v) self.REQUEST.set('controller_state', controller_state) return controller_state def validate(self, controller_state, REQUEST, validators, argdict=None): if argdict is None: args = REQUEST.args kwargs = REQUEST else: args = () if REQUEST is None: kwargs = argdict else: kwargs = {} for k in REQUEST.keys(): if k in ('SESSION',): continue kwargs[k] = REQUEST[k] kwargs.update(argdict) context = controller_state.getContext() if validators is None: REQUEST.set('controller_state', controller_state) return controller_state for v in validators: REQUEST.set('controller_state', controller_state) if controller_state.hasValidated(v): continue try: # make sure validator exists obj = context.restrictedTraverse(v, default=None) if obj is None: raise ValueError, 'Unable to find validator %s\n' % str(v) if not getattr(obj, 'is_validator', 1): raise ValueError, '%s is not a CMFFormController validator' % str(v) REQUEST = controller_state.getContext().REQUEST controller_state = mapply(obj, args, kwargs, call_object, 1, missing_name, dont_publish_class, REQUEST, bind=1) if controller_state is None or getattr(controller_state, '__class__', None) != ControllerState: raise ValueError, 'Validator %s did not return the state object' % str(v) controller_state._addValidator(v) except ValidationError, e: # if a validator raises a ValidatorException, execution of # validators is halted and the controller_state is set to # the controller_state embedded in the exception controller_state = e.controller_state state_class = getattr(controller_state, '__class__', None) if state_class != ControllerState: raise Exception, 'Bad ValidationError state (type = %s)' % str(state_class) break state_class = getattr(controller_state, '__class__', None) if state_class != ControllerState: raise Exception, 'Bad validator return type from validator %s (%s)' % (str(v), str(state_class)) REQUEST.set('controller_state', controller_state) REQUEST.set('controller_state', controller_state) return controller_state