Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/pylons/pylons/decorators/__init__.py
0001"""Pylons Decorators: ``jsonify``, ``validate``, REST, and Cache decorators"""
0002import logging
0003import sys
0004import warnings
0005
0006import formencode
0007import formencode.variabledecode as variabledecode
0008import simplejson
0009from decorator import decorator
0010from formencode import htmlfill
0011from paste.util.multidict import UnicodeMultiDict
0012
0013import pylons
0014
0015__all__ = ['jsonify', 'validate']
0016
0017log = logging.getLogger(__name__)
0018
0019def jsonify(func, *args, **kwargs):
0020    """Action decorator that formats output for JSON
0021
0022    Given a function that will return content, this decorator will
0023    turn the result into JSON, with a content-type of 'text/javascript'
0024    and output it.
0025    """
0026    pylons.response.headers['Content-Type'] = 'text/javascript'
0027    data = func(*args, **kwargs)
0028    if isinstance(data, list):
0029        msg = "JSON responses with Array envelopes are susceptible to "                 "cross-site data leak attacks, see "                 "http://pylonshq.com/warnings/JSONArray"
0032        warnings.warn(msg, Warning, 2)
0033        log.warning(msg)
0034    log.debug("Returning JSON wrapped action output")
0035    return simplejson.dumps(data)
0036jsonify = decorator(jsonify)
0037
0038def validate(schema=None, validators=None, form=None, variable_decode=False,
0039             dict_char='.', list_char='-', post_only=True, state=None,
0040             **htmlfill_kwargs):
0041    """Validate input either for a FormEncode schema, or individual validators
0042
0043    Given a form schema or dict of validators, validate will attempt to
0044    validate the schema or validator list.
0045
0046    If validation was succesfull, the valid result dict will be saved
0047    as ``self.form_result``. Otherwise, the action will be re-run as if it was
0048    a GET, and the output will be filled by FormEncode's htmlfill to fill in
0049    the form field errors.
0050
0051    If you'd like validate to also check GET (query) variables (**not** GET
0052    requests!) during its validation, set the ``post_only`` keyword argument
0053    to False.
0054
0055    .. warning::
0056        ``post_only`` applies to *where* the arguments to be validated come
0057        from. It does *not* restrict the form to only working with post, merely
0058        only checking POST vars.
0059
0060    Example:
0061
0062    .. code-block:: Python
0063
0064        class SomeController(BaseController):
0065
0066            def create(self, id):
0067                return render('/myform.mako')
0068
0069            @validate(schema=model.forms.myshema(), form='create')
0070            def update(self, id):
0071                # Do something with self.form_result
0072                pass
0073    """
0074    def wrapper(func, self, *args, **kwargs):
0075        """Decorator Wrapper function"""
0076        request = pylons.request._current_obj()
0077        errors = {}
0078        if post_only:
0079            params = request.POST
0080        else:
0081            params = request.params
0082        is_unicode_params = isinstance(params, UnicodeMultiDict)
0083        params = params.mixed()
0084        if variable_decode:
0085            log.debug("Running variable_decode on params")
0086            decoded = variabledecode.variable_decode(params, dict_char,
0087                                                     list_char)
0088        else:
0089            decoded = params
0090
0091        if schema:
0092            log.debug("Validating against a schema")
0093            try:
0094                self.form_result = schema.to_python(decoded, state)
0095            except formencode.Invalid, e:
0096                errors = e.unpack_errors(variable_decode, dict_char, list_char)
0097        if validators:
0098            log.debug("Validating against provided validators")
0099            if isinstance(validators, dict):
0100                if not hasattr(self, 'form_result'):
0101                    self.form_result = {}
0102                for field, validator in validators.iteritems():
0103                    try:
0104                        self.form_result[field] =                               validator.to_python(decoded.get(field), state)
0106                    except formencode.Invalid, error:
0107                        errors[field] = error
0108        if errors:
0109            log.debug("Errors found in validation, parsing form with htmlfill "
0110                      "for errors")
0111            request.environ['REQUEST_METHOD'] = 'GET'
0112            pylons.c.form_errors = errors
0113
0114            # If there's no form supplied, just continue with the current
0115            # function call.
0116            if not form:
0117                return func(self, *args, **kwargs)
0118
0119            request.environ['pylons.routes_dict']['action'] = form
0120            response = self._dispatch_call()
0121            # XXX: Legacy WSGIResponse support
0122            legacy_response = False
0123            if hasattr(response, 'wsgi_response'):
0124                form_content = ''.join(response.content)
0125                legacy_response = True
0126            else:
0127                form_content = response
0128                response = pylons.response._current_obj()
0129
0130            # Ensure htmlfill can safely combine the form_content, params and
0131            # errors variables (that they're all of the same string type)
0132            if not is_unicode_params:
0133                log.debug("Raw string form params: ensuring the '%s' form and "
0134                          "FormEncode errors are converted to raw strings for "
0135                          "htmlfill", form)
0136                encoding = determine_response_charset(response)
0137
0138                # WSGIResponse's content may (unlikely) be unicode
0139                if isinstance(form_content, unicode):
0140                    form_content = form_content.encode(encoding,
0141                                                       response.errors)
0142
0143                # FormEncode>=0.7 errors are unicode (due to being localized
0144                # via ugettext). Convert any of the possible formencode
0145                # unpack_errors formats to contain raw strings
0146                errors = encode_formencode_errors(errors, encoding,
0147                                                  response.errors)
0148            elif not isinstance(form_content, unicode):
0149                log.debug("Unicode form params: ensuring the '%s' form is "
0150                          "converted to unicode for htmlfill", form)
0151                encoding = determine_response_charset(response)
0152                form_content = form_content.decode(encoding)
0153
0154            form_content = htmlfill.render(form_content, defaults=params,
0155                                           errors=errors, **htmlfill_kwargs)
0156            if legacy_response:
0157                # Let the Controller merge the legacy response
0158                response.content = form_content
0159                return response
0160            else:
0161                return form_content
0162        return func(self, *args, **kwargs)
0163    return decorator(wrapper)
0164
0165def determine_response_charset(response):
0166    """Determine the charset of the specified Response object, returning the
0167    default system encoding when none is set"""
0168    charset = response.determine_charset()
0169    if charset is None:
0170        charset = sys.getdefaultencoding()
0171    log.debug("Determined result charset to be: %s", charset)
0172    return charset
0173
0174def encode_formencode_errors(errors, encoding, encoding_errors='strict'):
0175    """Encode any unicode values contained in a FormEncode errors dict to raw
0176    strings of the specified encoding"""
0177    if errors is None or isinstance(errors, str):
0178        # None or Just incase this is FormEncode<=0.7
0179        pass
0180    elif isinstance(errors, unicode):
0181        errors = errors.encode(encoding, encoding_errors)
0182    elif isinstance(errors, dict):
0183        for key, value in errors.iteritems():
0184            errors[key] = encode_formencode_errors(value, encoding,
0185                                                   encoding_errors)
0186    else:
0187        # Fallback to an iterable (a list)
0188        errors = [encode_formencode_errors(error, encoding, encoding_errors)                         for error in errors]
0190    return errors

Top