Nicolas Lehuen
nicolas at lehuen.com
Wed Oct 20 03:11:38 EDT 2004
> -----Message d'origine----- > De : mod_python-bounces at modpython.org > [mailto:mod_python-bounces at modpython.org] De la part de > Graham Dumpleton > Envoyé : mercredi 20 octobre 2004 00:46 > À : Johannes Erdfelt > Cc : mod_python at modpython.org > Objet : Re: [mod_python] vampire form validation > ... > I certainly think something to assist in form validation > would be a good thing and I am sure it is something a few > people have probably tackled before. Thus will be interested > to here how other people have approached it as well. Amongst > all the different variants, we can come up with a nice > general approach that suits a lot of needs. > > -- > Graham Dumpleton (grahamd at dscpl.com.au) Like many, we have implemented a form handling module which we interfaced to mod_python. The module contains a Form class, which contains a series of fields. Fields are instances of subclass of the Field class, which differ based on their visual behaviour. Form and Fields are a mix of view and controller, views being mostly implemented using our own templating engine, and the model is a pretty simple dict-based model. Field classes are : - Label (not really a field, it's a read-only field) - Text (a simple text field) - TextArea - CheckBox - CheckList (a series of checkboxes representing a subset of a list in the model) - Radio and Select (two different rendering of a choice between differents items) - Submit Each field has a name, a label. It has a default controller behaviour, which can be customized. For example, a Text field transform HTTP fields into string in the model. If you want integers in the model, you just instanciate the field like this : Form(( Text('foobar',required=True,transform=IntegerTransform(),default_value=42), Submit('ok'), )) Form loading, validation and processing are implemented in the Form class, so if you want to customize them, you just subclass Form. The default implementation of Form.validate() calls Field.validate() for each field of the form, which ensures that each field is OK (e.g. for the Text field above, it checks that the field is filled and can be transformed into an integer). Forms are always posted at the same URI they are obtained (i.e. the ACTION attribute is automatically set to req.unparsed_uri). We use parameters from the query string to pass information, on top of what is POSTed. Here is a sample usage : ### index.py from tcc.online.forms import * from tcc.template import html_template class MyForm(Form): def __init__(self): Form.__init__(self,( Text('last_name',required=True), Text('first_name'), Radio('gender',((0,'Male'),(1,'Female')),required=True), CheckList('interests',self.load_interests), Text('children',transform=IntegerTransform()), Text('phone',transform=PhoneNumber()), )) def load_interests(self,req,form): # anywhere a list of tuples is required (in CheckList, Radio or Select) # it is possible to put a callable which returns a sequence or iterator of tuples. # here we could dynamically load interests from a database, based on parameters in the request return ( ('babysitting','Babysitting'), ('sports','Sports'), ) def load(self,req): # here we can load data from a database into the model # so that the form is pre-filled pass def validate(self,req): if Form.validate(self,req): # basic validation performed # we can make cross-field validation if req.fields['children']>0 and 'babysitting' not in req.fields['interests']: req.errors['interests']='If you have children, you must be interested in babysitting' return False return True else: return False def process(self,req): # here we can update the database etc. # the default behaviour is to render a table of the fields return Form.process(self,req) def render_page(self,req): # here we can customize how the form is rendered # Usually the template is in a file and cached in memory return html_template[''' <html><head><title>Sample</title></head><body> <p>Hello, world !</p> ${form.control} </body></html> '''](req=req,form=self) handler = MyForm() ### end of index.py As you can see, the form instance is a callable object, so that is can directly be a mod_python.publisher handler. In its default behaviour, the Form class handle all the tricky validation process, i.e. : 1) If the form is rendered "from scratch" (i.e. it has not been posted by the user), the load() method is called to initialize it. 2) If the form is posted, the validate() method is called. If it returns True, the form is validated and the process() method is called. 3) If the form is not posted or not validated, the form is rendered thanks to the template it has been given (default templates are used if no templates were given). This framework has the advantage of being scalable, i.e. if you want to write yourself the HTML code and use the Form class only for validation, you can. If you want to control the way the controls are disposed within the form, without handling the form itself, you can (just pass a template to the form). On the other hand, you can do everything with the Form class and never see any HTML code. Last, but not least, forms are embeddable within templates, so you can have many forms in a single page. This was a pretty quick description, there are many tricks when implementing this. What we thought was going to be a 100 lines module ended as a 400 lines module. Form handling is a pretty involving matter ; I don't know if you can really find a one-size-fits-all answer to the problem. Maybe it's more an application-framework thing than a server-framework thing. Regards, Nicolas
|