form fields customization, and complex validation

May 1, 2010

making a field value readable but not writable

INPUT(_name="username", _value=session.username, _readonly=ON)), 

in SQLFORM this’s done by adding :”writable=False” to the field in database table definition :

db.define_table('city', Field('name', writable=False, default='LO.A'))

or

db.define_table('city', Field('name', default='LO.A'))
db.city.name.writable = False

To customize forms as you widh, you may do the following:

form=SQLFORM(....)
    form.accepts(...)

and in view

<form>
{{for table in form.components:}}
   {{for tr in table.components:}}
      {{for td in tr.components:}}
         {{for item in td.componets:}}
            {{=item}}
         {{pass}}
      {{pass}}
   {{pass}}
{{pass}}
</form> 

The customization may be adding a new field in the view
WARNING: when adding new fields after form is accepted, validation on these fields won’t work at all.


  • Web2py forms can be treated as list of lists, you can create a form and print it (to exactly know its depth) and try to insert fields in it before accepting it.

    Some body can argue about the benefit of this, but it’s really very important sometimes.
    Ex:
    Suppose for example you have a table in an old database that’s used to create forms, but in a certain situation [only one situation] you need another field in the form, and since it’s not a good idea to change your db table by adding a new field in it, you’ll have in the function that creates and returns the special form to insert field before accepting it.
    you may also wish to add a field for a table that is not supposed to have extra fields like db.auth_user may be to determine the category of the user when adding a new user.

    Adding the field from within the view as shown above will work but without the validation, you can freely add a field fom within the view file if validation on it is not important.

    form = SQLFORM(db.auth_user, submit_button=T("Save"))
    # can't put this in the view since form needs to be accepted b4 returning to view
            form[0].insert(-1, TR(LABEL(T(category:')),
                                 INPUT(_id='no_table_category',
                                       _name='user_category',
                                       requires=[]
                                )))
    

    Why adding the following line ?

    _id='no_table_categoryt'
    

    Because web2py convention when create forms is to give them the id:
    ‘_no_table_%’field_name and this’s important just in case you want to add more customization on the field using javascript or add some extra style to it using css and we should follow this style on creating our custom fields.


  • How to check for form errors, how to make enforce validation to fail on afield ?

    form = SQLFORM(....)
    if form.accepts(...):
       #option 1
    elif form.errors:
          #option 2
    else:
          pass
    

    In option 2 you can do either of the following

    a) rewrite error messages:

        if form.errors.user_name: form.errors.user_name='oops!'
     

    b) copy error messages in a dictionary :

        import copy
        myerrors=copy.copy(form.errors)
     

    c) clear errors so that they are not displayed:

        form.errors.clear() 
     

    In the :

    else:
        pass
    

    This means :

    Don’t display any message, this is important for the form not to display the error flash message when entering the page.
    To make it clear, you’ve 3 situations : form accepted, form not accepted [form.errors] exists and form not accepted and there’s no form.errors [when you first navigate to page containing the form].
    Without this you’ll end up always with a flash message [‘Not accepted’] even you’ve already entered the page and without submitting the form.


    What if you’re doing more complex validation that has to be performed upon submitting data ?

  • We know that any code inside accepts() function is actually executing after accepting data and put it on database (in case of SQLFORM)
    so what to do?
  • It’s easy you need to specify another function to make an extra validation upon submitting the form data

    #In model :
    db.define_table('country', Field('name'))
    
    #In controller:
    def validate_city(form):
        if form.vars.name != 'LOA':
            form.errors.name = T('only LOA is allowed')
    
    def index():
       form = SQLFORM(db.city)
       if form.accepts(request.vars, session, onvalidation=validate_city):
           response.flash = T('city added')
       return dict(form=form)    
    

    You can even do :

    test():
         form=FORM(
                   LABEL("Username:",INPUT(_name="user_name",
                                    requires=IS_NOT_EMPTY
    (error_message="test"))),
                                  INPUT(_type='submit'))
    
    form.components[0] is the LABEL field
    form.components[0].components[0] is "Username:"
    form.components[0].components[1] is the INPUT field
    form.components[0].components[1].attributes['requires'] #is the  
    IS_NOT_EMPTY object.
    
    #Technically you can do:
    form.components[0].components[1].attributes
    ['requires'].error_message="oops!"
    but this would work only before accepts .