Back to index

plone3  3.1.7
FormController.py
Go to the documentation of this file.
00001 from __future__ import nested_scopes
00002 import os
00003 from zope.interface import implements
00004 
00005 
00006 from AccessControl import ClassSecurityInfo
00007 import Globals
00008 from OFS.ObjectManager import bad_id
00009 from StructuredText.StructuredText import HTML
00010 from ZPublisher.Publish import call_object, missing_name, dont_publish_class
00011 from ZPublisher.mapply import mapply
00012 from Products.CMFFormController import GLOBALS as fc_globals
00013 from Products.CMFCore.utils import registerToolInterface
00014 from Products.CMFCore.utils import getToolByName, UniqueObject, SimpleItemWithProperties
00015 from Products.CMFCore.permissions import ManagePortal
00016 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00017 
00018 from Products.CMFFormController.ControllerState import ControllerState
00019 from Products.CMFFormController.interfaces import IFormControllerTool
00020 from FormAction import FormActionType, FormActionKey, FormAction, FormActionContainer
00021 from FormValidator import FormValidatorKey, FormValidator, FormValidatorContainer
00022 from ValidationError import ValidationError
00023 
00024 _marker = []
00025 form_action_types = {}
00026 
00027 def registerFormAction(id, factory, description=''):
00028     form_action_types[id] = FormActionType(id, factory, description)
00029 
00030 
00031 class FormController(UniqueObject, SimpleItemWithProperties):
00032     """ """
00033 
00034     security = ClassSecurityInfo()
00035 
00036     id = 'portal_form_controller'
00037     title = 'Manages form validation and post-validation actions'
00038     meta_type= 'Form Controller Tool'
00039 
00040     implements(IFormControllerTool)
00041 
00042     manage_options = ( ({'label':'Overview', 'action':'manage_overview'},
00043                         {'label':'Documentation', 'action':'manage_docs'},
00044                         {'label': 'Validation', 'action': 'manage_formValidatorsForm'},
00045                         {'label': 'Actions', 'action': 'manage_formActionsForm'},
00046                         {'label': 'Purge', 'action': 'manage_purgeForm'},) +
00047                        SimpleItemWithProperties.manage_options)
00048 
00049     security.declareProtected(ManagePortal, 'manage_overview')
00050     manage_overview = PageTemplateFile(os.path.join('www','manage_overview'), globals())
00051     manage_overview.__name__ = 'manage_overview'
00052     manage_overview._need__name__ = 0
00053 
00054     security.declareProtected(ManagePortal, 'manage_docs')
00055     manage_docs = PageTemplateFile(os.path.join('www','manage_docs'), globals())
00056     manage_docs.__name__ = 'manage_docs'
00057 
00058     security.declareProtected(ManagePortal, 'manage_formActionsForm')
00059     manage_formActionsForm = PageTemplateFile(os.path.join('www','manage_formActionsForm'), globals())
00060     manage_formActionsForm.__name__ = 'manage_formActionsForm'
00061 
00062     security.declareProtected(ManagePortal, 'manage_formValidatorsForm')
00063     manage_formValidatorsForm = PageTemplateFile(os.path.join('www','manage_formValidatorsForm'), globals())
00064     manage_formValidatorsForm.__name__ = 'manage_formValidatorsForm'
00065 
00066     security.declareProtected(ManagePortal, 'manage_purgeForm')
00067     manage_purgeForm = PageTemplateFile(os.path.join('www','manage_purgeForm'), globals())
00068     manage_purgeForm.__name__ = 'manage_purgeForm'
00069 
00070     # some aliases
00071     security.declareProtected(ManagePortal, 'manage_main')
00072     manage_main = manage_overview
00073 
00074     security.declareProtected(ManagePortal, 'index_html')
00075     index_html = None
00076 
00077     wwwpath = os.path.join(Globals.package_home(fc_globals), 'www')
00078     f = open(os.path.join(wwwpath, 'docs.stx'), 'r')
00079     _docs = f.read()
00080     f.close()
00081     _docs = HTML(_docs)
00082 
00083 
00084     def __init__(self):
00085         self.actions = FormActionContainer()
00086         self.validators = FormValidatorContainer()
00087 
00088 
00089     security.declarePublic('docs')
00090     def docs(self):
00091         """Returns FormController docs formatted as HTML"""
00092         return self._docs
00093 
00094 
00095     def view(self, REQUEST, RESPONSE):
00096         """Invokes the default view."""
00097         return self.__call__(REQUEST, RESPONSE)
00098 
00099 
00100     def __call__(self, REQUEST, RESPONSE):
00101         """Invokes the default view."""
00102         if RESPONSE is not None:
00103             RESPONSE.redirect('%s/manage_main' % self.absolute_url())
00104 
00105 
00106     def _checkId(self, id):
00107         """See if an id is valid CMF/Plone id"""
00108         portal = getToolByName(self, 'portal_url').getPortalObject()
00109         if not id:
00110             return 'Empty id'
00111         s = bad_id(id)
00112         if s:
00113             return '\'%s\' is not a valid id' % (id)
00114         # extra checks for Plone sites
00115         if portal.__class__.__name__ == 'PloneSite':
00116             if hasattr(portal, 'portal_properties') and \
00117                 hasattr(portal.portal_properties, 'site_properties') and \
00118                 hasattr(portal.portal_properties.site_properties, 'invalid_ids'):
00119                 if id in portal.portal_properties.site_properties.invalid_ids:
00120                     return '\'%s\' is a reserved id' % (id)
00121 
00122 
00123     # Web-accessible methods
00124     security.declareProtected(ManagePortal, 'listActionTypes')
00125     def listActionTypes(self):
00126         """Return a list of available action types."""
00127         keys = form_action_types.keys()
00128         keys.sort()
00129         action_types = []
00130         for k in keys:
00131             action_types.append(form_action_types.get(k))
00132         return action_types
00133 
00134 
00135     def validActionTypes(self):
00136         return form_action_types.keys()
00137 
00138 
00139     security.declareProtected(ManagePortal, 'listContextTypes')
00140     def listContextTypes(self):
00141         """Return list of possible types for template context objects"""
00142         types_tool = getToolByName(self, 'portal_types')
00143         return types_tool.listContentTypes()
00144 
00145 
00146     security.declareProtected(ManagePortal, 'listFormValidators')
00147     def listFormValidators(self, override=None, **kwargs):
00148         """Return a list of existing validators.  Validators can be filtered by
00149            specifying required attributes via kwargs"""
00150         return self.validators.getFiltered(**kwargs)
00151 
00152 
00153     security.declareProtected(ManagePortal, 'listFormActions')
00154     def listFormActions(self, override=None, **kwargs):
00155         """Return a list of existing actions.  Actions can be filtered by
00156            specifying required attributes via kwargs"""
00157         return self.actions.getFiltered(**kwargs)
00158 
00159 
00160     security.declareProtected(ManagePortal, 'manage_editFormValidators')
00161     def manage_editFormValidators(self, REQUEST):
00162         """Process form validator edit form"""
00163         self._editFormValidators(self.validators, REQUEST)
00164         return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm')
00165 
00166 
00167     security.declarePrivate('_editFormValidators')
00168     def _editFormValidators(self, container, REQUEST):
00169         for k in REQUEST.form.keys():
00170             if k.startswith('old_object_id_'):
00171                 n = k[len('old_object_id_'):]
00172                 old_object_id = REQUEST.form.get('old_object_id_'+n)
00173                 old_context_type = REQUEST.form.get('old_context_type_'+n)
00174                 old_button = REQUEST.form.get('old_button_'+n)
00175                 container.delete(FormValidatorKey(old_object_id, old_context_type, old_button, self))
00176                 object_id = REQUEST.form.get('object_id_'+n)
00177                 context_type = REQUEST.form.get('context_type_'+n)
00178                 button = REQUEST.form.get('button_'+n)
00179                 validators = REQUEST.form.get('validators_'+n)
00180                 container.set(FormValidator(object_id, context_type, button, validators, self))
00181 
00182 
00183     # Method for programmatically adding validators
00184     security.declareProtected(ManagePortal, 'addFormValidators')
00185     def addFormValidators(self, object_id, context_type, button, validators):
00186         self.validators.set(FormValidator(object_id, context_type, button, validators, self))
00187 
00188 
00189     security.declareProtected(ManagePortal, 'manage_addFormValidators')
00190     def manage_addFormValidators(self, REQUEST):
00191         """Process form validator add form"""
00192         self._addFormValidators(self.validators, REQUEST)
00193         return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm')
00194 
00195 
00196     security.declarePrivate('_addFormValidators')
00197     def _addFormValidators(self, container, REQUEST):
00198         object_id = REQUEST.form.get('new_object_id')
00199         context_type = REQUEST.form.get('new_context_type')
00200         button = REQUEST.form.get('new_button')
00201         validators = REQUEST.form.get('new_validators')
00202         container.set(FormValidator(object_id, context_type, button, validators, self))
00203 
00204 
00205     security.declareProtected(ManagePortal, 'manage_delFormValidators')
00206     def manage_delFormValidators(self, REQUEST):
00207         """Process form validator delete form"""
00208         self._delFormValidators(self.validators, REQUEST)
00209         return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm')
00210 
00211 
00212     security.declarePrivate('_delFormValidators')
00213     def _delFormValidators(self, container, REQUEST):
00214         for k in REQUEST.form.keys():
00215             if k.startswith('del_id_'):
00216                 n = k[len('del_id_'):]
00217                 old_object_id = REQUEST.form.get('old_object_id_'+n)
00218                 old_context_type = REQUEST.form.get('old_context_type_'+n)
00219                 old_button = REQUEST.form.get('old_button_'+n)
00220                 container.delete(FormValidatorKey(old_object_id, old_context_type, old_button, self))
00221 
00222 
00223     security.declareProtected(ManagePortal, 'manage_editFormActions')
00224     def manage_editFormActions(self, REQUEST):
00225         """Process form action edit form"""
00226         self._editFormActions(self.actions, REQUEST)
00227         return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm')
00228 
00229 
00230     security.declarePrivate('_editFormActions')
00231     def _editFormActions(self, container, REQUEST):
00232         for k in REQUEST.form.keys():
00233             if k.startswith('old_object_id_'):
00234                 n = k[len('old_object_id_'):]
00235                 old_object_id = REQUEST.form.get('old_object_id_'+n)
00236                 old_status = REQUEST.form.get('old_status_'+n)
00237                 old_context_type = REQUEST.form.get('old_context_type_'+n)
00238                 old_button = REQUEST.form.get('old_button_'+n)
00239                 container.delete(FormActionKey(old_object_id, old_status, old_context_type, old_button, self))
00240                 object_id = REQUEST.form.get('object_id_'+n)
00241                 status = REQUEST.form.get('status_'+n)
00242                 context_type = REQUEST.form.get('context_type_'+n)
00243                 button = REQUEST.form.get('button_'+n)
00244                 action_type = REQUEST.form.get('action_type_'+n)
00245                 action_arg = REQUEST.form.get('action_arg_'+n)
00246                 container.set(FormAction(object_id, status, context_type, button, action_type, action_arg, self))
00247 
00248 
00249     # Method for programmatically adding actions
00250     security.declareProtected(ManagePortal, 'addFormAction')
00251     def addFormAction(self, object_id, status, context_type, button, action_type, action_arg):
00252         self.actions.set(FormAction(object_id, status, context_type, button, action_type, action_arg, self))
00253 
00254 
00255     security.declareProtected(ManagePortal, 'manage_addFormAction')
00256     def manage_addFormAction(self, REQUEST):
00257         """Process form action add form"""
00258         self._addFormAction(self.actions, REQUEST)
00259         return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm')
00260 
00261 
00262     security.declarePrivate('_addFormAction')
00263     def _addFormAction(self, container, REQUEST):
00264         object_id = REQUEST.form.get('new_object_id')
00265         status = REQUEST.form.get('new_status').strip()
00266         context_type = REQUEST.form.get('new_context_type').strip()
00267         button = REQUEST.form.get('new_button').strip()
00268         action_type = REQUEST.form.get('new_action_type').strip()
00269         action_arg = REQUEST.form.get('new_action_arg').strip()
00270         container.set(FormAction(object_id, status, context_type, button, action_type, action_arg, self))
00271 
00272 
00273     security.declareProtected(ManagePortal, 'manage_delFormActions')
00274     def manage_delFormActions(self, REQUEST):
00275         """Process form action delete form"""
00276         self._delFormActions(self.actions, REQUEST)
00277         return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm')
00278 
00279 
00280     security.declarePrivate('_delFormActions')
00281     def _delFormActions(self, container, REQUEST):
00282         for k in REQUEST.form.keys():
00283             if k.startswith('del_id_'):
00284                 n = k[len('del_id_'):]
00285                 old_object_id = REQUEST.form.get('old_object_id_'+n)
00286                 old_status = REQUEST.form.get('old_status_'+n)
00287                 old_context_type = REQUEST.form.get('old_context_type_'+n)
00288                 old_button = REQUEST.form.get('old_button_'+n)
00289                 container.delete(FormActionKey(old_object_id, old_status, old_context_type, old_button, self))
00290 
00291 
00292     def getValidators(self, id, context_type, button):
00293         return self.validators.match(id, context_type, button)
00294 
00295 
00296     def getAction(self, id, status, context_type, button):
00297         return self.actions.match(id, status, context_type, button)
00298 
00299 
00300     def getState(self, obj, is_validator):
00301         id = obj.id
00302         controller_state = self.REQUEST.get('controller_state', None)
00303         # The variable 'env' is a dictionary that is passed
00304         # along using record variables on forms so that you can keep
00305         # some state between different forms.
00306         env = self.REQUEST.get('form_env', {})
00307         # Make sure controller_state is something generated by us, not something submitted via POST or GET
00308         if controller_state and getattr(controller_state, '__class__', None) != ControllerState:
00309             controller_state = None
00310 
00311         if not is_validator:
00312             # Construct a new controller state object or clear out the existing
00313             # one unless we are in a validator script.
00314             if controller_state is None:
00315                 # XXX - errors={} shouldn't need to be set here, but without it
00316                 # I encountered what looks like a weird Zope caching bug.
00317                 # To reproduce, install CMFFormControllerDemo, go to portal_skins
00318                 # then to CMFFormControllerDemo, then click on test_form and
00319                 # click the Test tab.  Submit the form with an empty text box.
00320                 # Now back up to the ZMI and click the Test tab again.  If
00321                 # errors={} is left out in the line below, ControllerState.__init__()
00322                 # gets called with errors set to a non-empty value (more
00323                 # precisely, it is set to the value that was in REQUEST.controller_state.
00324                 # Yikes!
00325                 controller_state = ControllerState(errors={})
00326             else:
00327                 # clear out values we don't want to carry over from previous states.
00328                 controller_state.setStatus('success')
00329                 controller_state.setNextAction(None)
00330                 #controller_state.setButton(None)
00331             controller_state.set(id=id, context=obj.getParentNode())
00332         else:
00333             if controller_state is None:
00334                 raise ValueError, 'No controller state available.  ' + \
00335                     'This commonly occurs when a ControllerValidator (.vpy) ' + \
00336                     'script is invoked via the validation mechanism in the ' + \
00337                     'portal_form tool.  If you are using a package designed to ' + \
00338                     'be used with portal_form, you are probably inadvertently ' + \
00339                     'invoking a validator designed for use with CMFFormController (e.g. validate_id).  ' + \
00340                     'If you are using a package designed to be used with CMFFormController, you probably ' + \
00341                     'have a "portal_form" in your URL that needs to be removed.'
00342         controller_state._setValidating(is_validator)
00343         # Pass environment along, with care so we don't override
00344         # existing variables.
00345         for k, v in env.items():
00346             controller_state.kwargs.setdefault(k, v)
00347         self.REQUEST.set('controller_state', controller_state)
00348         return controller_state
00349 
00350 
00351     def validate(self, controller_state, REQUEST, validators, argdict=None):
00352         if argdict is None:
00353             args = REQUEST.args
00354             kwargs = REQUEST
00355         else:
00356             args = ()
00357             if REQUEST is None:
00358                 kwargs = argdict
00359             else:
00360                 kwargs = {}
00361                 for k in REQUEST.keys():
00362                     if k in ('SESSION',):
00363                         continue
00364                     kwargs[k] = REQUEST[k]
00365                 kwargs.update(argdict)
00366         context = controller_state.getContext()
00367         if validators is None:
00368             REQUEST.set('controller_state', controller_state)
00369             return controller_state
00370         for v in validators:
00371             REQUEST.set('controller_state', controller_state)
00372             if controller_state.hasValidated(v):
00373                 continue
00374             try:
00375                 # make sure validator exists
00376                 obj = context.restrictedTraverse(v, default=None)
00377                 if obj is None:
00378                     raise ValueError, 'Unable to find validator %s\n' % str(v)
00379                 if not getattr(obj, 'is_validator', 1):
00380                     raise ValueError, '%s is not a CMFFormController validator' % str(v)
00381                 REQUEST = controller_state.getContext().REQUEST
00382                 controller_state = mapply(obj, args, kwargs,
00383                                           call_object, 1, missing_name, dont_publish_class,
00384                                           REQUEST, bind=1)
00385                 if controller_state is None or getattr(controller_state, '__class__', None) != ControllerState:
00386                     raise ValueError, 'Validator %s did not return the state object' % str(v)
00387                 controller_state._addValidator(v)
00388             except ValidationError, e:
00389                 # if a validator raises a ValidatorException, execution of
00390                 # validators is halted and the controller_state is set to
00391                 # the controller_state embedded in the exception
00392                 controller_state = e.controller_state
00393                 state_class = getattr(controller_state, '__class__', None)
00394                 if state_class != ControllerState:
00395                     raise Exception, 'Bad ValidationError state (type = %s)' % str(state_class)
00396                 break
00397             state_class = getattr(controller_state, '__class__', None)
00398             if state_class != ControllerState:
00399                 raise Exception, 'Bad validator return type from validator %s (%s)' % (str(v), str(state_class))
00400             REQUEST.set('controller_state', controller_state)
00401 
00402         REQUEST.set('controller_state', controller_state)
00403         return controller_state
00404 
00405 
00406     security.declarePublic('writableDefaults')
00407     def writableDefaults(self):
00408         """Can default actions and validators be modified?"""
00409         return 0
00410 
00411 
00412     def _getTypeName(self, obj):
00413         type_name = getattr(obj, '__class__', None)
00414         if type_name:
00415             type_name = getattr(type_name, '__name__', None)
00416         return type_name
00417 
00418 
00419     security.declareProtected(ManagePortal, 'manage_purge')
00420     def manage_purge(self, REQUEST):
00421         """TTW interface for purge"""
00422         text = self._purge()
00423         # might want to do a redirect here instead
00424         return text
00425 
00426 
00427     security.declareProtected(ManagePortal, '_purge')
00428     def _purge(self):
00429         """Remove actions and validators for ids that no longer correspond to
00430         objects in the portal"""
00431         actions = self.actions.getFiltered()
00432         action_dict = {}
00433         for a in actions:
00434             action_dict[a.getObjectId()] = 1
00435         validators = self.validators.getFiltered()
00436         validator_dict = {}
00437         for v in validators:
00438             validator_dict[v.getObjectId()] = 1
00439 
00440         def fn(obj, dict):
00441             dict[obj.getId()] = 0
00442         meta_types = ['Controller Page Template', 'Controller Page Template (File)', 'Filesystem Controller Page Template', 'Controller Python Script', 'Filesystem Controller Python Script']
00443         portal = getToolByName(self, 'portal_url').getPortalObject()
00444         catalog = getToolByName(self, 'portal_catalog')
00445         result = catalog.ZopeFindAndApply(portal, obj_metatypes=meta_types, search_sub=1, result=[])
00446         for (path, r) in result:
00447             if action_dict.has_key(r.getId()):
00448                 del action_dict[r.getId()]
00449             if validator_dict.has_key(r.getId()):
00450                 del validator_dict[r.getId()]
00451 
00452         n_actions = 0
00453         for k in action_dict.keys():
00454             to_be_deleted = self.actions.getFiltered(object_id=k)
00455             n_actions += len(to_be_deleted)
00456             for a in to_be_deleted:
00457                 self.actions.delete(a.getKey())
00458         catalog.ZopeFindAndApply(portal, meta_types, search_sub=1, apply_func=lambda o: fn(o, validator_dict))
00459         n_validators = 0
00460         for k in validator_dict.keys():
00461             to_be_deleted = self.validators.getFiltered(object_id=k)
00462             n_validators += len(to_be_deleted)
00463             for v in to_be_deleted:
00464                 self.validators.delete(v.getKey())
00465         return '%d action overrides deleted, %d validator overrides deleted' % (n_actions, n_validators)
00466 
00467 Globals.InitializeClass(FormController)