Source

formhelpers / formhelpers / lib / mako_forms.py

Full commit
import logging
import formencode
from formencode import variabledecode
from formhelpers.lib.base import render
from pylons.decorators import PylonsFormEncodeState
from pylons.decorators import decorator
from formencode.variabledecode import variable_decode

import re
from pylons import tmpl_context

log = logging.getLogger(__name__)

def validate(name, schema, input_controller=None, post_only=True, on_get=False):
    """Validate input using FormEncode.
    
    Given a form name and FormSchema object, this decorator will
    validate the form, and set the fully validated result
    dictionary on the controller as ``self.form_result``.
    
    In all cases, the original dictionary of parameters is placed
    on the ``c`` request context using the given name as the
    attribute name.  If form validation was successful, this will
    be the result of the ``from_python()`` method of the given 
    ``FormSchema``, else it will be the original ``request.params``
    dict.   The form tags in ``form_tags.mako`` are designed to
    read from this dictionary to get their display values, and the
    name given to the ``<%form>`` tag in that library should match
    the name given here.
    
    Validation errors are placed in a dictionary at
    ``self.form_errors`` as well as ``c.form_errors`` - this is the
    validation dictionary returned from the ``FormSchema``.  When 
    validation fails, the ``self.form_result`` dictionary is
    not available.  The ``form_errors`` dictionary is available in all cases
    even if validation succeeds, and can be directly manipulated
    by the controller to place error messages which are generated
    by the controller method itself.
    
    An ``input controller`` method is usually specified, which is
    a reference to a local controller method which will be called
    if validation fails, instead of the decorated function.  
    If validation succeeds, a callable 
    is placed at ``self.on_error()`` which when called with no 
    arguments will invoke this "input controller".  This allows the
    controller method to perform additional validation and redirect
    control to the "error" page (typically after it places messages
    within the ``form_errors`` dictionary).
      
    This function is a focused and enhanced version of Pylons' decorators.validate,
    intended to be used with form controls that read from the request
    as well as the ``c.form_errors`` dictionary for reporting errors.

    """
    state = PylonsFormEncodeState
        
    def wrapper(func, self, *args, **kwargs):
        """Decorator Wrapper function"""
        request = self._py_object.request
        errors = {}
        
        # switch on GET/POST options
        if not on_get and request.environ['REQUEST_METHOD'] == 'GET':
            return func(self, *args, **kwargs)
            
        if post_only:
            params = request.POST
        else:
            params = request.params
        
        params = params.mixed()
        decoded = params
        
        try:
            self.form_result = schema.to_python(decoded, state)
            setattr(tmpl_context, name, schema.from_python(self.form_result, state))
            
        except formencode.Invalid, e:
            errors = e.unpack_errors(variable_decode)
            setattr(tmpl_context, name, decoded)
        
        self.form_errors = tmpl_context.form_errors = errors
        self.on_error = lambda: input_controller(self, *args, **kwargs)
        if errors:
            if not input_controller:
                return func(self, *args, **kwargs)
            else:
                return input_controller(self, *args, **kwargs)

        return func(self, *args, **kwargs)
    return decorator(wrapper)

tag_regexp = re.compile(r'<(\/)?%(\w+):(\w+)\s*(.*?)(\/)?>', re.S)
attr_regexp = re.compile(r"\s*(\w+)\s*=\s*(?:(?<!\\)'(.*?)(?<!\\)'|(?<!\\)\"(.*?)(?<!\\)\")")
expr_regexp = re.compile(r'\${(.+?)}')
def process_tags(source):
    """Convert tags of the form <nsname:funcname attrs> into a <%call> tag.
    
    This is a quick regexp approach that can be replaced with a full blown XML parsing
    approach, if desired.
    
    """
    def process_exprs(t):
        m = re.match(r'^\${(.+?)}$', t)
        if m:
            return m.group(1)
            
        att = []
        def replace_expr(m):
            att.append(m.group(1))
            return "%s"
        
        t = expr_regexp.sub(replace_expr, t)
        if att:
            return "'%s' %% (%s)" % (t.replace("'", r"\'"), ",".join(att))
        else:
            return "'%s'" % t.replace("'", r"\'")
        
    def cvt(match):
        if bool(match.group(1)):
            return "</%call>"
            
        ns = match.group(2)
        fname = match.group(3)
        attrs = match.group(4)

        attrs = dict([(key, process_exprs(val1 or val2)) for key, val1, val2 in attr_regexp.findall(attrs)])
        args = attrs.pop("args", "")

        attrs = ",".join(["%s=%s" % (key, value) for key, value in attrs.iteritems()])
        
        if bool(match.group(5)):
            return """<%%call expr="%s.%s(%s)" args=%s/>""" % (ns, fname, attrs, args)
        else:
            return """<%%call expr="%s.%s(%s)" args=%s>""" % (ns, fname, attrs, args)
    return tag_regexp.sub(cvt, source)