On Thu, Jun 3, 2010 at 7:15 AM, pb <[email protected]> wrote:
> CODE:
> When I call validate/index?par1=xxx
> fill a non-number into the second text-field and press submit, I get
> an exception
> on the second line of index.

So what is the problem?  @validate is eating parameters so that they
no longer appear in ``requests.params`` or
``request.environ['webob._parsed_post_vars'][0]`` (whatever the latter
is)?

I did a diff of @validate between 1.0 and 0.9.6. There are some
changes but nothing that seems related. Attached is version 1 and the
diff.  (The diff does not include the other decorators or module
docstring for clarity.)  Version 1 uses ``self._py_object.request``
instead of ``pylons.request`` to avoid depending on the SOPs.
PylonsFormEncodeState is a gettext-wrapper thing.

-- 
Mike Orr <[email protected]>

-- 
You received this message because you are subscribed to the Google Groups 
"pylons-discuss" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/pylons-discuss?hl=en.

"""Pylons Decorators

Common decorators intended for use in controllers. Additional
decorators for use with controllers are in the
:mod:`~pylons.decorators.cache`, :mod:`~pylons.decorators.rest` and
:mod:`~pylons.decorators.secure` modules.

"""
import logging
import sys
import warnings

import formencode
import simplejson
from decorator import decorator
from formencode import api, htmlfill, variabledecode
from webob.multidict import UnicodeMultiDict

from pylons.decorators.util import get_pylons
from pylons.i18n import _ as pylons_gettext

__all__ = ['jsonify', 'validate']

log = logging.getLogger(__name__)

@decorator
def jsonify(func, *args, **kwargs):
    """Action decorator that formats output for JSON

    Given a function that will return content, this decorator will turn
    the result into JSON, with a content-type of 'application/json' and
    output it.
    
    """
    pylons = get_pylons(args)
    pylons.response.headers['Content-Type'] = 'application/json'
    data = func(*args, **kwargs)
    if isinstance(data, (list, tuple)):
        msg = "JSON responses with Array envelopes are susceptible to " \
              "cross-site data leak attacks, see " \
              "http://pylonshq.com/warnings/JSONArray";
        warnings.warn(msg, Warning, 2)
        log.warning(msg)
    log.debug("Returning JSON wrapped action output")
    return simplejson.dumps(data)


def validate(schema=None, validators=None, form=None, variable_decode=False,
             dict_char='.', list_char='-', post_only=True, state=None,
             on_get=False, **htmlfill_kwargs):
    """Validate input either for a FormEncode schema, or individual
    validators

    Given a form schema or dict of validators, validate will attempt to
    validate the schema or validator list.

    If validation was successful, the valid result dict will be saved
    as ``self.form_result``. Otherwise, the action will be re-run as if
    it was a GET, and the output will be filled by FormEncode's
    htmlfill to fill in the form field errors.

    ``schema``
        Refers to a FormEncode Schema object to use during validation.
    ``form``
        Method used to display the form, which will be used to get the 
        HTML representation of the form for error filling.
    ``variable_decode``
        Boolean to indicate whether FormEncode's variable decode
        function should be run on the form input before validation.
    ``dict_char``
        Passed through to FormEncode. Toggles the form field naming 
        scheme used to determine what is used to represent a dict. This
        option is only applicable when used with variable_decode=True.
    ``list_char``
        Passed through to FormEncode. Toggles the form field naming
        scheme used to determine what is used to represent a list. This
        option is only applicable when used with variable_decode=True.
    ``post_only``
        Boolean that indicates whether or not GET (query) variables
        should be included during validation.
        
        .. warning::
            ``post_only`` applies to *where* the arguments to be
            validated come from. It does *not* restrict the form to
            only working with post, merely only checking POST vars.
    ``state``
        Passed through to FormEncode for use in validators that utilize
        a state object.
    ``on_get``
        Whether to validate on GET requests. By default only POST
        requests are validated.

    Example::

        class SomeController(BaseController):

            def create(self, id):
                return render('/myform.mako')

            @validate(schema=model.forms.myshema(), form='create')
            def update(self, id):
                # Do something with self.form_result
                pass

    """
    if state is None:
        state = PylonsFormEncodeState
    def wrapper(func, self, *args, **kwargs):
        """Decorator Wrapper function"""
        request = self._py_object.request
        errors = {}
        
        # Skip the validation if on_get is False and its a GET
        if not on_get and request.environ['REQUEST_METHOD'] == 'GET':
            return func(self, *args, **kwargs)
        
        # If they want post args only, use just the post args
        if post_only:
            params = request.POST
        else:
            params = request.params
        
        params = params.mixed()
        if variable_decode:
            log.debug("Running variable_decode on params")
            decoded = variabledecode.variable_decode(params, dict_char,
                                                     list_char)
        else:
            decoded = params

        if schema:
            log.debug("Validating against a schema")
            try:
                self.form_result = schema.to_python(decoded, state)
            except formencode.Invalid, e:
                errors = e.unpack_errors(variable_decode, dict_char, list_char)
        if validators:
            log.debug("Validating against provided validators")
            if isinstance(validators, dict):
                if not hasattr(self, 'form_result'):
                    self.form_result = {}
                for field, validator in validators.iteritems():
                    try:
                        self.form_result[field] = \
                            validator.to_python(decoded.get(field), state)
                    except formencode.Invalid, error:
                        errors[field] = error
        if errors:
            log.debug("Errors found in validation, parsing form with htmlfill "
                      "for errors")
            request.environ['REQUEST_METHOD'] = 'GET'
            self._py_object.tmpl_context.form_errors = errors

            # If there's no form supplied, just continue with the current
            # function call.
            if not form:
                return func(self, *args, **kwargs)

            request.environ['pylons.routes_dict']['action'] = form
            response = self._dispatch_call()

            # If the form_content is an exception response, return it
            if hasattr(response, '_exception'):
                return response

            htmlfill_kwargs2 = htmlfill_kwargs.copy()
            htmlfill_kwargs2.setdefault('encoding', request.charset)
            return htmlfill.render(response, defaults=params, errors=errors,
                                   **htmlfill_kwargs2)
        return func(self, *args, **kwargs)
    return decorator(wrapper)


def pylons_formencode_gettext(value):
    """Translates a string ``value`` using pylons gettext first and if
    that fails, formencode gettext.
    
    This allows to "merge" localized error messages from built-in
    FormEncode's validators with application-specific validators.

    """
    trans = pylons_gettext(value)
    if trans == value:
        # translation failed, try formencode
        trans = api._stdtrans(value)
    return trans


class PylonsFormEncodeState(object):
    """A ``state`` for FormEncode validate API that includes smart
    ``_`` hook.

    The FormEncode library used by validate() decorator has some
    provision for localizing error messages. In particular, it looks
    for attribute ``_`` in the application-specific state object that
    gets passed to every ``.to_python()`` call. If it is found, the
    ``_`` is assumed to be a gettext-like function and is called to
    localize error messages.

    One complication is that FormEncode ships with localized error
    messages for standard validators so the user may want to re-use
    them instead of gathering and translating everything from scratch.
    To allow this, we pass as ``_`` a function which looks up
    translation both in application and formencode message catalogs.
    
    """
    _ = staticmethod(pylons_formencode_gettext)
--- /tmp/validate-0.9.6.py	2010-06-03 11:24:49.000000000 -0700
+++ /tmp/validate-1.0.py	2010-06-03 11:24:43.000000000 -0700
@@ -3,39 +3,61 @@
 import warnings
 
 import formencode
-import formencode.variabledecode as variabledecode
 import simplejson
 from decorator import decorator
-from formencode import htmlfill
-from paste.util.multidict import UnicodeMultiDict
+from formencode import api, htmlfill, variabledecode
+from webob.multidict import UnicodeMultiDict
+
+from pylons.decorators.util import get_pylons
+from pylons.i18n import _ as pylons_gettext
 
-import pylons
 
 def validate(schema=None, validators=None, form=None, variable_decode=False,
              dict_char='.', list_char='-', post_only=True, state=None,
-             **htmlfill_kwargs):
-    """Validate input either for a FormEncode schema, or individual validators
+             on_get=False, **htmlfill_kwargs):
+    """Validate input either for a FormEncode schema, or individual
+    validators
 
     Given a form schema or dict of validators, validate will attempt to
     validate the schema or validator list.
 
-    If validation was succesfull, the valid result dict will be saved
-    as ``self.form_result``. Otherwise, the action will be re-run as if it was
-    a GET, and the output will be filled by FormEncode's htmlfill to fill in
-    the form field errors.
-
-    If you'd like validate to also check GET (query) variables (**not** GET
-    requests!) during its validation, set the ``post_only`` keyword argument
-    to False.
-
-    .. warning::
-        ``post_only`` applies to *where* the arguments to be validated come
-        from. It does *not* restrict the form to only working with post, merely
-        only checking POST vars.
-
-    Example:
+    If validation was successful, the valid result dict will be saved
+    as ``self.form_result``. Otherwise, the action will be re-run as if
+    it was a GET, and the output will be filled by FormEncode's
+    htmlfill to fill in the form field errors.
+
+    ``schema``
+        Refers to a FormEncode Schema object to use during validation.
+    ``form``
+        Method used to display the form, which will be used to get the 
+        HTML representation of the form for error filling.
+    ``variable_decode``
+        Boolean to indicate whether FormEncode's variable decode
+        function should be run on the form input before validation.
+    ``dict_char``
+        Passed through to FormEncode. Toggles the form field naming 
+        scheme used to determine what is used to represent a dict. This
+        option is only applicable when used with variable_decode=True.
+    ``list_char``
+        Passed through to FormEncode. Toggles the form field naming
+        scheme used to determine what is used to represent a list. This
+        option is only applicable when used with variable_decode=True.
+    ``post_only``
+        Boolean that indicates whether or not GET (query) variables
+        should be included during validation.
+        
+        .. warning::
+            ``post_only`` applies to *where* the arguments to be
+            validated come from. It does *not* restrict the form to
+            only working with post, merely only checking POST vars.
+    ``state``
+        Passed through to FormEncode for use in validators that utilize
+        a state object.
+    ``on_get``
+        Whether to validate on GET requests. By default only POST
+        requests are validated.
 
-    .. code-block:: Python
+    Example::
 
         class SomeController(BaseController):
 
@@ -46,16 +68,25 @@
             def update(self, id):
                 # Do something with self.form_result
                 pass
+
     """
+    if state is None:
+        state = PylonsFormEncodeState
     def wrapper(func, self, *args, **kwargs):
         """Decorator Wrapper function"""
-        request = pylons.request._current_obj()
+        request = self._py_object.request
         errors = {}
+        
+        # Skip the validation if on_get is False and its a GET
+        if not on_get and request.environ['REQUEST_METHOD'] == 'GET':
+            return func(self, *args, **kwargs)
+        
+        # If they want post args only, use just the post args
         if post_only:
             params = request.POST
         else:
             params = request.params
-        is_unicode_params = isinstance(params, UnicodeMultiDict)
+        
         params = params.mixed()
         if variable_decode:
             log.debug("Running variable_decode on params")
@@ -85,7 +116,7 @@
             log.debug("Errors found in validation, parsing form with htmlfill "
                       "for errors")
             request.environ['REQUEST_METHOD'] = 'GET'
-            pylons.c.form_errors = errors
+            self._py_object.tmpl_context.form_errors = errors
 
             # If there's no form supplied, just continue with the current
             # function call.
@@ -94,46 +125,14 @@
 
             request.environ['pylons.routes_dict']['action'] = form
             response = self._dispatch_call()
-            # XXX: Legacy WSGIResponse support
-            legacy_response = False
-            if hasattr(response, 'wsgi_response'):
-                form_content = ''.join(response.content)
-                legacy_response = True
-            else:
-                form_content = response
-                response = pylons.response._current_obj()
-
-            # Ensure htmlfill can safely combine the form_content, params and
-            # errors variables (that they're all of the same string type)
-            if not is_unicode_params:
-                log.debug("Raw string form params: ensuring the '%s' form and "
-                          "FormEncode errors are converted to raw strings for "
-                          "htmlfill", form)
-                encoding = determine_response_charset(response)
-
-                # WSGIResponse's content may (unlikely) be unicode
-                if isinstance(form_content, unicode):
-                    form_content = form_content.encode(encoding,
-                                                       response.errors)
-
-                # FormEncode>=0.7 errors are unicode (due to being localized
-                # via ugettext). Convert any of the possible formencode
-                # unpack_errors formats to contain raw strings
-                errors = encode_formencode_errors(errors, encoding,
-                                                  response.errors)
-            elif not isinstance(form_content, unicode):
-                log.debug("Unicode form params: ensuring the '%s' form is "
-                          "converted to unicode for htmlfill", form)
-                encoding = determine_response_charset(response)
-                form_content = form_content.decode(encoding)
-
-            form_content = htmlfill.render(form_content, defaults=params,
-                                           errors=errors, **htmlfill_kwargs)
-            if legacy_response:
-                # Let the Controller merge the legacy response
-                response.content = form_content
+
+            # If the form_content is an exception response, return it
+            if hasattr(response, '_exception'):
                 return response
-            else:
-                return form_content
+
+            htmlfill_kwargs2 = htmlfill_kwargs.copy()
+            htmlfill_kwargs2.setdefault('encoding', request.charset)
+            return htmlfill.render(response, defaults=params, errors=errors,
+                                   **htmlfill_kwargs2)
         return func(self, *args, **kwargs)
     return decorator(wrapper)

Reply via email to