summaryrefslogtreecommitdiff
path: root/docs/source/validation_n_errors.rst
blob: 49f55cfb8c7635095515f06b78f6a79833f584ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
.. _validation_n_errors:

Validation and Error Handling
=============================
Pecan provides a variety of tools to help you handle common form validation and
error handling activities, like:

* Validating the presence of submitted form contents with a schema.
* Transforming strings from form submissions into useful Python objects.
* Simplifying the process of re-displaying form values and associated error messages inline.

Rather than re-inventing the wheel, Pecan uses `FormEncode <http://formencode.org/>`_ for schemas and form validation.

Writing and Applying Schemas
------------------------------
Here's a simple example of a schema and how to apply it to a controller method using
Pecan's ``expose`` decorator::

    from pecan import expose
    from formencode import Schema, validators as v

    class SimpleSchema(Schema):    
        username = v.String(not_empty=True)
        password = v.String(not_empty=True)
        
    class LoginController(object):

        @expose(schema=SimpleSchema)
        def login(self, **kw):
            if authenticate(
              kw['username'],
              kw['password']
            ):
              set_cookie()
            return dict()

Validating JSON Content
------------------------------
In addition to simple form arguments, Pecan also makes it easy to validate JSON request bodies.
Often, especially in AJAX requests, the request content is encoded as JSON in the request body.
Pecan's validation can handle the decoding for you and apply schema validation to the decoded
data structure::

    from pecan import expose
    from formencode import Schema, validators as v
    from myproject.lib import authenticate

    class JSONSchema(Schema):
        """
        This schema would decode a JSON request body
        that looked like:
        
        {
          'username' : 'pecan',
          'password' : 'dotpy
        }
        
        """
        username = v.String(not_empty=True)
        password = v.String(not_empty=True)
    
    class LoginController(object):

        @expose(json_schema=JSONSchema)
        def login(self, **kw):
            authenticate(
              kw['username'],
              kw['password']
            )
            return dict()

Handling Schema Failures
------------------------------
When schema validation fails, the validation errors from FormEncode are applied to Pecan's
request object (``pecan.request.pecan``) as a dictionary.
The key where the actual errors go is ``validation_errors`` and this can be
inspected by your controller methods to react to errors appropiately::

    from pecan import expose, request
    from myproject.schemas import SimpleSchema

    class LoginController(object):

        @expose(schema=SimpleSchema)
        def login(self, **kw):
            if request.pecan['validation_errors']:
                pass # Don't Panic!
            return dict()

Error Handlers and Template Filling
------------------------------
When schema validation fails, Pecan allows you to redirect to another controller internally
for error handling via the `error_handler` keyword argument to ``@expose()``.
This is especially useful when used in combination with generic
controller methods::

  from pecan import request, expose
  from formencode import Schema, validators as v

  class ProfileSchema(Schema):    
      name = v.String(not_empty=True)
      email = v.String(not_empty=True)

  class ProfileController(object):
  
      @expose(generic=True)
      def index(self):
          pass
          
      @index.when(method="GET", template='profile.html')
      def index_get(self):
          """
          This method will be called to render the original template.
          It will also be used for generating a form pre-filled with values
          when schema failures occur.
          """
          return dict()
          
      @index.when(method="POST", schema=ProfileSchema(), error_handler=lambda: request.path)
      def index_post(self, **kw):
          """
          This method will do something with POST arguments.
          If the schema validation fails, an internal redirect will
          cause the `profile.html` template to be rendered via the
          ``index_get`` method.
          """
          
          name = kw.get('name')
          email = ke.get('email')
          
          redirect('/profile')
          
In this example, when form validation errors occur (for example, the email provided is invalid),
Pecan will handle pre-filling the form values in ``profile.html`` for you.  Additionally, inline
errors will be appended to the template using FormEncode's ``htmlfill``.

Bypassing ``htmlfill``
------------------------------
Sometimes you want certain fields in your templates to be ignored (i.e., not pre-filled) by ``htmlfill``.
A perfect use case for this is password and hidden input fields.  The default Pecan template namespace
includes a built-in function, ``static``, which allows you to enforce a static value for form fields,
preventing ``htmlfill`` from filling in submitted form variables::

    <form method="POST">
      <dl>
        <dt>Username:</dt>
          <dd><input type="text" name="username" /></dd>
        <dt>Password:</dt>        
          <dd><input type="password" name="password" value="${static('password', '')}" /></dd>
        <input type="hidden" name="ticket" value="${static('ticket', 'RANDOM_PER_REQUEST_VALUE')}" />
      </dl>
      <button>Login</button>
    </form>

Working with ``variabledecode``
------------------------------
Pecan also lets you take advantage of FormEncode's ``variabledecode`` for transforming flat HTML form
submissions into nested structures::

    from pecan import expose
    from myproject import SimpleSchema

    class ProfileController(object):

        @expose(schema=SimpleSchema(), variable_decode=True)
        def index(self):
            return dict()