from variabledecode import NestedVariables import validators, schema, htmlview from validators import Invalid, toPython, fromPython nested = NestedVariables() stripName = validators.StripField('_formName_') class _GenericState(object): pass _HTTPState = _GenericState() _HTTPState.protocol = 'http' class FormProcessor(object): """ FormProcessor takes a schema, and given a normal HTTP submission dictionary will process and return the value. See `processForm()` for more. """ def __init__(self, schema, name='form'): """ The form name (default ``form``) is for use when more than one form may be submitted at the same URL. A special hidden variable (``_formName__``) holds the form value. If the form hasn't yet been submitted, or another form is submitted, then no processing will occur. """ self.schema = schema self.name = name self.submitButtons = [] self.suppressingButtons = [] self._initSchema(self.schema, []) def _initSchema(self, validator, names): realValidator = validators.adaptValidator(validator) view = htmlview.adaptHTMLView(validator) if view and isinstance(view, htmlview.SubmitButton): if view.suppressValidation: self.suppressingButtons.append((validator, names[:])) else: self.submitButtons.append((validator, names[:])) elif isinstance(realValidator, schema.Schema): for name, sub in realValidator.fields.items(): names.append(name) self._initSchema(sub, names) names.pop() elif isinstance(realValidator, validators.ForEach): for sub in realValidator.validators: self._initSchema(sub, names) def processForm(self, input, state=None): """ Processes the form, given the `input` dictionary. This dictionary should be of the style returned by `cgi`, where there are string keys and string values, with lists for values if a key shows up more than once. The return value is a tuple, ``(result, data)``. ``result`` may be True (form submitted successfully), False (form not submitted, or submitted with errors), or ``"partial"``, which means that the form was submitted, but a `SubmitButton` with `suppressValidation` was hit. The data depends on the result: * If True, then the data is the processed result. In the case of a `SubmitButton` with `methodToInvoke` set, the value will be the return value of the associated method. (@@: does it?) * If ``"partial"`` the data will be the value returned by the buttons `methodToInvoke`, or just the plain data (input). * If False and no form was submitted, None will be returned. * If False and an invalid form was submitted, a dictionary of the errors will be returned. """ if state is None: state = _HTTPState httpRequest = input input = nested.toPython(input, state) try: name, input = stripName.toPython(input, state) except Invalid: name = None if name != self.name: return False, None returnedResult = None for button, names in self.suppressingButtons: found, indexes = self.buttonPushed(names, input) if found: returnedResult = htmlview.adaptHTMLView(button).execute(state, indexes, input, httpRequest) if returnedResult is not None: return "partial", returnedResult try: result = toPython(self.schema, input) except Invalid, exc: errors = self.recursiveApply(str, exc) return False, errors returnedResult = result for button, names in self.submitButtons: found, indexes = self.buttonPushed(names, result) if found: returnedResult = htmlview.adaptHTMLView(button).execute(state, indexes, returnedResult, httpRequest) return True, returnedResult def buttonPushed(self, names, valueDict): if isinstance(valueDict, list): for i in range(len(valueDict)): found, indexes = self.buttonPushed(names, valueDict[i]) if found: indexes = [i] + indexes return True, indexes return False, None try: left = valueDict[names[0]] except KeyError: return False, None if len(names) == 1: return left, [] else: return self.buttonPushed(names[1:], left) def recursiveApply(self, func, exc): if isinstance(exc, str): return exc elif isinstance(exc, list): return [self.recursiveApply(func, e) for e in exc] elif isinstance(exc, dict): d = {} for key, value in exc.items(): d[key] = self.recursiveApply(func, value) return d elif isinstance(exc, Exception): try: exc.errorDict exc.errorList except AttributeError: return func(exc) if exc.errorDict is not None: return self.recursiveApply(func, exc.errorDict) elif exc.errorList is not None: return self.recursiveApply(func, exc.errorList) else: return func(exc) def renderForm(self, defaults=None, options=None, httpRequest=None, action=None, state=None, errors=None): """ Returns the string that renders this form. """ if state is None: state = _HTTPState view = htmlview.Form(schema=self.schema, action=action, formName=self.name) return view.html(httpRequest, defaults=defaults, options=options, errors=errors, state=state)