Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Changes between Version 12 and Version 13 of SimpleWidgetForm

02/13/06 22:42:14 (13 years ago)
Karl Guertin

reworked tutorial


  • SimpleWidgetForm

    v12 v13  
    1 = Simple Widget Form = 
    3 Level: Beginner 
    6 Version 1.0 - Michael Schneider 
    7     created - 12/22/05  0.9 SVN version 356 
    8     Updated 02/13/2006 by Diwaker Gupta 
    10 Widgets are in a state of flux.  This is a journal of 
    11 creating my first widget form in turbogears. 
    13 Please feel free to edit this page to improve the 
    14 code, or add user instructions. 
    16 == Goal == 
    18 Create a simple application that demonstrates a form widget. 
    21 == Pages == 
    23 http://localhost:8080/  - the main form page 
    24  * time created 
    25  * form with two elements and a submit button 
    26    * name field - text string 
    27    * age field  - integer string 
    29 http://localhost:8080/processForm - page form is submitted to for 
    30 processing 
    31  * validate form 
    32  * forward to http://localhost:8080/youDidIt (if form data is valid) 
    33  * redisplay form with suggestions (if form data is invalid) 
    35 http://localhost:8080/youDidIt - very simple page to indicate you are 
    36 at the end of the journey 
    38 == project setup == 
    40 create a new project with tg-admin 
     1'''Note:''' Check the bottom of the page for the example project. 
    43 tg-admin quickstart 
    44 .... mywidgets 
     9This is a TurboGears (http://www.turbogears.org) project. It can be 
     10started by running the start-formstutorial.py script. 
     12This tutorial is designed to introduce TurboGears 0.8 users to the new 
     13widgets, error handling, and decorators for Turbogears 0.9. It is current as 
     14of revision 767 (Feb 13, 2006) and should be valid through 0.9 final. 
     16This application accepts and displays comments. Unlike most TurboGears 
     17projects, this application uses an in-memory in a global ``comments`` 
     18object rather than in a database. The comments are listed on the index page 
     19and a link at the bottom of the page directs the user to add a comment. 
     21The comments form itself should requrie little explanation, but it requires 
     22both a name and an email address to be entered. If the name or email fields 
     23are missing, the form is redisplayed along with a message next to the 
     24appropriate field. 
     26Successful form entry adds the comment to the global comments variable and 
     27displays a success message on the index page. 
     29Starting the Project 
     32As in 0.8, tg-admin quickstart creates a new project. This project was created 
     33with the name ``FormsTutorial``. 
     35The first thing users upgrading from 0.8 should notice is that the ``dev.cfg`` 
     36and ``prod.cfg`` files have been replaced with ``devcfg.py`` and 
     37``prodcfg.py``.  Obviously, these new files are python files with all that 
     38implies. You can now programmatically determine settings, but you are advised 
     39to keep your code side-effect free, as there is no guarantee as to how many 
     40times the file will be evaluated. 
     42All configuration that was previously duplicated between dev and prod 
     43has been moved to ``formstutorial/config.py``. Sections are now marked with 
     44the path() directive and paths are created using the absfile() directive 
     45(which uses sys.path.join under the hood so directory separators are as 
     46expected). See the end of ``formstutorial/config.py`` for examples. 
     48Some features of the ``formstutorial/config.py`` file to note (but which will 
     49not be discussed): 
     51* You can now change the templating engine basis by installing a new 
     52  templating plugin. Control which is active by setting the 
     53  ``tg.defaultview`` setting. 
     54* TurboGears now requires you to explicitly state that an exposed method can 
     55  be formatted as json. You can restore the 0.8 behavior by setting the 
     56  ``tg.allow_json`` variable to true. 
     57* TurboGears now comes with Visit Tracking and an Identity framework. Both 
     58  are enabled by default. 
     61Starting from the index() 
     64Open up ``formstutorial/controllers.py`` in your favorite text editor. Ignore 
     65the first part and skip down to the ``Root`` class. The first major change 
     66here is the new decorators. 
     68In earlier versions of 0.9 svn, the ``@expose`` decorator both published the 
     69decorated method and provided form validation. This was deemed to be 
     70inflexible, and in 0.9 ``@expose`` provides publishing related functions 
     71(template, engine, allow_json, etc) while the new ``@validate`` decorator 
     72dictates which form (keep reading, forms are coming) to use for input 
     75You may notice that templates passed to ``@expose`` in this example don't 
     76start with "formstutorial". This is the new relative import feature in Kid. 
     77Naming your templates this way simplifies project renaming. 
     79Take a quick glance at ``formstutorial/templates/index.kid``. The main feature 
     80to notice here is the use of ``std.url`` for the link. If your project was 
     81mounted at ``http://blah/foo/``, ``std.url`` will take the '/foo/' into 
     82account and send your users to '/foo/add' rather than the incorrect '/add'. 
     84We've exhausted the interesting bits of index, now lets move on to ``add``. 
     86Intro to Forms and Widgets 
     88The ``formstutorial/templates/form.kid`` is a completely generic form 
     89displaying template. It takes an optional title (default is 'Forms Tutorial') 
     90along with a form and an action. Setting your form template up this way makes 
     91for easy prototyping and I prefer it to creating a new template for each form. 
     93All the action on this page is being handled by the new widgets system. 
     94Widgets provide a convenient way to bundle appearance and behavior and make 
     95form creation really easy. 
     97Widgets are split up into three general categories: Forms, Fields, and 
     98Fastdata. Forms act as containers for Fields providing a layout (Table or 
     99List) and as a result are responsible for labels and error display. Fields are 
     100fairly self explanatory, you can get an overview of what widgets are available 
     101by checking out the widgets browser in the new turbogears toolbox. Fastdata is 
     102a grid that autogenerates forms based on the SQLObject column type. It's 
     103useful for prototyping, but we won't be using it in this tutorial. 
     105You must have your Fields in a Form in order to use the widgets framework:: 
     107  example_form = widgets.TableForm( 
     108        fields=[widgets.TextField(name="test",label="Example")], 
     109        submit_text="Submit Me") 
     111This proto-form needs to be put on the page by calling it with an action 
     112argument in your template:: 
     114  ${example_form(action="sample")} 
     116Which will produce the following output:: 
     118    <FORM ACTION="sample" NAME="form" METHOD="post"> 
     119        <TABLE BORDER="0"> 
     120            <TR> 
     121                <TD> 
     122                    <LABEL CLASS="fieldlabel" FOR="test">Example</LABEL> 
     123                </TD> 
     124                <TD> 
     125                    <INPUT CLASS="textfield" TYPE="text" ID="test" NAME="test"> 
     126                </TD> 
     127            </TR> 
     128            <TR> 
     129                <TD> </TD> 
     130                <TD> 
     131                <INPUT TYPE="submit" CLASS="submitbutton" VALUE="Submit Me"> 
     132                </TD> 
     133            </TR> 
     134        </TABLE> 
     135    </FORM> 
     137The form strips off the submit field so that you don't have to deal with it. 
     138If you do want to manage the submit button, add a ``widget.Submit`` to your 
     139form. The default label is the capitalized widget name. Create a custom label 
     140using the ``label`` argument. 
     142Conversion from a python value to a string displayed as part of the form is 
     143handed by a FormEncode validator, which is attached to the widget using the 
     144named ``validator`` argument. More on validation later. 
     146The function/constructor form syntax is a bit unweildy and doesn't look nice 
     147on the screen, so a declarative syntax is provided and is used in this 
     148project. To take advantage of this syntax, simply subclass 
     149``widgets.WidgetsDeclaration``. Widgets declared this way will automatically 
     150have their name set to the attribute name but are otherwise exactly as they 
     151would be if you created them as shown above. When instantiated, the widgets 
     152wind up as a simple python list. This means that you can do list-like things 
     153when you put it in a form, such as adding additional widgets:: 
     155  comment_form_2 = widgets.TableForm( 
     156      fields=CommentFields().append( 
     157          widgets.TextField(name='added') 
     158      ), 
     159      submit_text="Submit Tweaked Form" 
     160  ) 
     164The ``comment_form`` has a validator on each of the first two fields. It's 
     165fairly obvious that the first field (``validators.NotEmpty``)is required, but 
     166the second field (``validators.Email``) is required as well. Adding a 
     167validator to the field generally makes that field required, but you can get 
     168around this by passing an ``if_empty="default value"`` argument to the 
     169validator's constructor. Note that validators don't need to be instantiated 
     170unless you're passing in arguments. 
     171Each of the form's validators are rolled up into a form-wide 
     172``FormEncode.Schema``, which is used to check the contents for validity by the 
     173``@validate`` method decorator. 
     175Methods previously had to check for ``cherrypy.form_errors`` and re-dispatch 
     176based on the result. The new ``@error_handler`` decorator makes handling this 
     177error quite a bit simpler. The first argument to the decorator is the error 
     178handling method. In the example, we're re-using ``add`` so that the form will 
     179be re-displayed if errors occur. A simple addition would be to display a 
     180message to the user indicating there was a problem:: 
     182  @expose(template=".templates.form") 
     183  def add(self,tg_errors=None): 
     184      if tg_errors: 
     185          flash("There was a problem with the form!") 
     186      return dict( 
     187          form=comment_form, 
     188          action='save', 
     189          title='New Comment' 
     190      ) 
     192In addition to redirecting the error, the ``@error_handler`` decorator will 
     193look for a parameter named 'tg_errors' and will put error information into 
     194that parameter. The decorator also has super RuleDispatch powers allowing you 
     195to redirect errors based on arbitrarily complex conditions, but that is beyond 
     196the scope of this tutorial. 
     198Form Processing 
     200As part of validation, the values of the form will be converted to the 
     201appropriate python objects (this example is all strings, but if you used an 
     202``Int`` validator, for example, you'd get a python integer rather than a 
     203string containing an integer). If you list the field names out 
     204as attributes in the decorated function, as in the example, the values get put 
     205into the appropriate attributes. If, on the other hand, you put a keyword 
     206argument parameter, e.g.:: 
     208  @expose() 
     209  @validate(form=comment_form) 
     210  @error_handler(add) 
     211  def save(self, **data): 
     212      comments.add(data['name'], 
     213                   data['email'], 
     214                   data.get('comment','No Comment.')) 
     215      #... 
     217Then you get the data as a dictionary. The use of .get() above is needed for 
     218the last two because those attributes aren't guaranteed to exist by the 
     219validators, while the first two are. 
     221The ``flash`` method displays a notice on the next page a user visits (and 
     222only on the first page). 
     224Back to the Index 
     226And we conclude the tutorial where we began, back at the index. 
     228TurboGears 0.9 comes jam-packed with new features and this tutorial only 
     229covers the most basic and immediate changes. You'll probably want to explore 
     230identity, the toolbox, and widgets in greater detail. 
     232See you on the mailing list. 
     234  --Karl 
     236Note: The files for this example are courtesy of Michele Cella, but I've tweaked them 
     237a bit! 
    47 == Create controller.py == 
    49  * my_form - variable that contains instance of TableForm widget 
    50  * index function - serves http://localhost:8080/ 
    51     * display the form with default data 
    52  * processForm - serves http://localhost:8080/processForm  
    53     * process form data 
    54         * if invalid - redisplay form, with invalid data marked 
    55         * if valid - forward to http://localhost:8080/youDidIt 
    56  * youDidIt - serves http://localhost:8080/youDidIt 
    57     * simple page that indicates form data is correct 
    59 {{{ 
    60 import time 
    61 import cherrypy 
    62 import turbogears 
    63 import turbogears.widgets as W 
    64 import turbogears.validators as V 
    65 from turbogears import controllers 
    67 # I created my form widget here 
    68 my_form = W.TableForm([ W.TextField("name", default=None, validator=V.String()), 
    69                         W.TextField("age", default=0, validator=V.Int())]) 
    71 class Root(controllers.RootController): 
    73    @turbogears.expose( template="mywidgets.templates.myFormPage") 
    74    def index( self, name='', age='', submit=''): 
    75       return dict(message="", now=time.ctime(), my_form= my_form) 
    78    @turbogears.expose( template="mywidgets.templates.myFormPage", 
    79                        inputform= my_form) 
    80    def processForm( self, name='', age='', submit=''): 
    81        # Check for validation errors 
    82        if cherrypy.request.form_errors: 
    83            return dict(message="", now=time.ctime(), my_form= my_form) 
    85        # At this point, you have valid form data. 
    86        # you would normally stash form info in 
    87        # your model here 
    88        raise cherrypy.HTTPRedirect(turbogears.url("/youDidIt")) 
    90    @turbogears.expose() 
    91    def youDidIt(self): 
    92        return " Your form had valid data, good job!!!!!!" 
    93 }}} 
    95 == Create your Kid Template == 
    97 {{{ 
    98 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    99 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
    100 <html xmlns="http://www.w3.org/1999/xhtml" 
    101 xmlns:py="http://purl.org/kid/ns#" 
    102    py:extends="'master.kid'"> 
    104 <head> 
    105    <meta content="text/html; charset=UTF-8" http-equiv="content-type" 
    106 py:replace="''"/> 
    107    <title>Welcome to TurboGears</title> 
    108 </head> 
    110 <body> 
    111    <!-- grab the value of now from the dictionary returned by the 
    112         controller method that called this template --> 
    113    <p>Form now <span py:replace="now">now</span>.</p> 
    115    <h2>Let's start with a Simple Form</h2> 
    117    <!--  The next line inserts the instance of our form in 
    118          my_form into the kid template. 
    120          when submit is pressed, the form will be submitted to 
    121          /processForm --> 
    123    <span py:replace="my_form.insert(action='processForm')" /> 
    124 </body> 
    125 </html> 
    127 }}}