Step 02: Form Handling

The previous step was the smallest possible step into a Colander schema and Deform form based on that schema. Let’s take another very small step, but show processing the form.

Goals

  • More validation
  • Handle form submission

Objectives

  • Process the submitted form by detecting a POST
  • Show the 3-way code path: GET vs. valid POST vs. invalid POST
  • Display the validated values

Steps

  1. $ cd ../../forms_schema; mkdir step02; cd step02

  2. (Unchanged) Copy the following into step02/application.py:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    from wsgiref.simple_server import make_server
    
    from pyramid.config import Configurator
    
    
    def main():
        config = Configurator()
        config.scan("views")
        config.add_static_view('deform_static', 'deform:static')
        app = config.make_wsgi_app()
        return app
    
    
    if __name__ == '__main__':
        app = main()
        server = make_server(host='0.0.0.0', port=8080, app=app)
        server.serve_forever()
    
  3. Copy the following into step02/views.py:

     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
    from pyramid.view import view_config
    
    import colander
    from deform import Form
    from deform import ValidationFailure
    
    class Person(colander.MappingSchema):
        name = colander.SchemaNode(colander.String())
        shoe_size = colander.SchemaNode(
            colander.Integer(),
            missing = 0,
        )
    
    class ProjectorViews(object):
        def __init__(self, request):
            self.request = request
    
        @view_config(renderer="templates/site_view.pt")
        def site_view(self):
            schema = Person()
            myform = Form(schema, buttons=('submit',))
    
            if 'submit' in self.request.POST:
                controls = self.request.POST.items()
                try:
                    appstruct = myform.validate(controls)
                except ValidationFailure, e:
                    return {'form':e.render(), 'values': False}
                # Process the valid form data, do some work
                values = {
                    "name": appstruct['name'],
                    "shoe_size": appstruct['shoe_size'],
                    }
                return {"form": myform.render(), "values": values}
    
            # We are a GET not a POST
            return {"form": myform.render(), "values": None}
    
  4. Copy the following into step02/templates/site_view.pt:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Projector</title>
        <link rel="stylesheet" href="/deform_static/css/form.css"
              type="text/css"
                />
        <script type="text/javascript"
                src="/deform_static/scripts/jquery-1.4.2.min.js"></script>
        <script type="text/javascript"
                src="/deform_static/scripts/deform.js"></script>
    </head>
    <body>
    <h2>Hello Form</h2>
    
    <div tal:content="structure form">form</div>
    <p tal:condition="values">Valid form values: ${values.name} and
    ${values.shoe_size}.</p>
    </body>
    </html>
    
  5. Copy the following into step02/tests.py:

     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
    import unittest
    
    from pyramid.testing import DummyRequest
    
    class ProjectorViewsUnitTests(unittest.TestCase):
    
        def _makeOne(self, request):
            from views import ProjectorViews
            inst = ProjectorViews(request)
            return inst
    
        def test_site_view(self):
            request = DummyRequest()
            inst = self._makeOne(request)
            result = inst.site_view()
            self.assertTrue('form' in result.keys())
    
    class ProjectorFunctionalTests(unittest.TestCase):
        def setUp(self):
            from application import main
            app = main()
            from webtest import TestApp
            self.testapp = TestApp(app)
    
        def test_GET(self):
            # Get the form
            res = self.testapp.get('/', status=200)
            self.assertTrue('Hello Form' in res.body)
    
  6. $ nosetests should report running 2 tests.

  7. $ python application.py

  8. Open http://127.0.0.1:8080/ in your browser.

Extra Credit

  1. If the form was successfully submitted, display a status message at the top of the screen.

Analysis

In this step we see the explicit branching in a self-posting form. This is important: it’s useful to have a common pattern to see which code path goes where, especially on a large project with lots of forms.

In many cases, this self-posting form isn’t going to return itself on success. Instead, it will redirect using pyramid.httpexceptions.HTTPFound to the newly-created resource.

Discussion

  • Impact of a form library on test writing

Table Of Contents