Re: [web2py] Working with custom form

2,920 views
Skip to first unread message

Richard Vézina

unread,
Jul 4, 2012, 12:12:51 PM7/4/12
to web...@googlegroups.com
Hmm!

You seem to not use the web2py view tamplate system... You don't need your custom form is you just create a register.html file in the views/YOURCONTROLLER.

All you have to write in your register.html file is {{=form}} and web2py will generate the html code of the form and render a html page if you go to the url : http://127.0.0.1/yourapp/yourcontroller/register...


Then, if you want to use the web2py customing form tool it as easy as it is explained in the book since each of the field contained in the form are broken into single pieces... So you can change the order of the field at the view level for example.

Richard



On Tue, Jul 3, 2012 at 4:18 PM, Chris <bhp...@gmail.com> wrote:

I've just started with web2py, so this might be obvious but I struggle to see how it works:

I have a controller action which returns a form:

def register():
   
return dict(form=auth.register())

I also have a view 'register.html' for this action, which is self-contained as it does NOT use any of the web2py frontend .css or .js stuff. All the HTML, CSS and JS are already done for the form too, e.g.

...
<form method="post" class="h5form">
   
<div class="own-labels">
       
<label>First name<sup>*</sup></label>
       
<input type="text" name="firstName" class="span3" placeholder="enter text…" required="required" tabindex="0">
       
<label>Last name<sup>*</sup></label>
       
<input type="text" name="lastName" class="span3" placeholder="enter text…" required="required" tabindex="1">
       
<label>Email<sup>*</sup></label>
       
<input type="email" name="email" class="span3" placeholder="enter text…" required="required" tabindex="3">
   
</div>
    
<div class="pull-left">
       <input type="submit" class="btn pull-right" style="margin-top: 15px;" value="Register">
    </div>
</form>
...

So now, how do I link the returned 'form' variables to the right fields in this pre-defined view? In the manual, it briefly mentions the use of

{{=form.custom.begin}}
...
{{=form.custom.end}}

but it is not very clear to me how should I use it in my case. Is this only and right approach for implementing custom forms whose views are already fully defined in HTML, CSS and JS? Thank you.  

Massimo Di Pierro

unread,
Jul 4, 2012, 1:23:05 PM7/4/12
to web...@googlegroups.com
Given this

def register():
   
return dict(form=auth.register())


you view just needs:

{{=form}}

...
<form method="post" class="h5form">
   
<div class="own-labels">
       
<label>First name<sup>*</sup></label>
       
<input type="text" name="firstName" class="span3" placeholder="enter text…" required="required" tabindex="0">
       
<label>Last name<sup>*</sup></label>
       
<input type="text" name="lastName" class="span3" placeholder="enter text…" required="required" tabindex="1">
       
<label>Email<sup>*</sup></label>
       
<input type="email" name="email" class="span3" placeholder="enter text…" required="required" tabindex="3">
   
</div>
    
<div class="pull-left">
       <input type="submit" class="btn pull-right" style="margin-top: 15px;" value="Register">
    </div>
</form>
...

But you need to replace <form...> with {{=form.custom.begin}}, </form> with {{=form.custom.end} and you need input field names corresponding to the column names in the db.auth_user table. If you use the custom form you must also find your own way to display form.errors

Pystar

unread,
Jul 4, 2012, 3:55:06 PM7/4/12
to web...@googlegroups.com
Hi Chris, 
I think Max just beat me in posting the solution. Thats the way I implemented by custom forms. Have the controller send the form to the view like

def controller():
    return dict(form=form)

in your view just do 

{{=form.custom.begin}}
#in between the opening and closing tags put in your normal html code and make sure the element names corresponds to the column names as defined in your database. web2py will handle the rest automatically. This is the most flexible way while still making use of web2py's validators.
{{=form.custom.end}}

If you refuse to use web2py's css and js files, remember that response.flash and session.flash wont work and also your validators also wont work. so you would have to handle form validation yourself.
I hope this helps?

On Tuesday, July 3, 2012 9:18:44 PM UTC+1, Chris wrote:

I've just started with web2py, so this might be obvious but I struggle to see how it works:

I have a controller action which returns a form:

def register():
   
return dict(form=auth.register())

I also have a view 'register.html' for this action, which is self-contained as it does NOT use any of the web2py frontend .css or .js stuff. All the HTML, CSS and JS are already done for the form too, e.g.

...
<form method="post" class="h5form">
   
<div class="own-labels">
       
<label>First name<sup>*</sup></label>
       
<input type="text" name="firstName" class="span3" placeholder="enter text…" required="required" tabindex="0">
       
<label>Last name<sup>*</sup></label>
       
<input type="text" name="lastName" class="span3" placeholder="enter text…" required="required" tabindex="1">
       
<label>Email<sup>*</sup></label>
       
<input type="email" name="email" class="span3" placeholder="enter text…" required="required" tabindex="3">
   
</div>
    
<div class="pull-left">
       <input type="submit" class="btn pull-right" style="margin-top: 15px;" value="Register">
    </div>
</form>
...

Anthony

unread,
Jul 4, 2012, 5:25:18 PM7/4/12
to web...@googlegroups.com
If you refuse to use web2py's css and js files, remember that response.flash and session.flash wont work

Well, the flash message won't display, but it's easy to add some very minimal JS code to get it to display.
 
and also your validators also wont work. so you would have to handle form validation yourself.

The validators will work fine without web2py's CSS and JS files. However, when displaying form errors, you won't get the nice slidedown effect without the JS (i.e., the errors will simply appear when the page is initially rendered rather than sliding down after page render).

Anthony

Kenny Chung

unread,
Jul 5, 2012, 11:09:43 AM7/5/12
to web...@googlegroups.com

When I used customform for login, it won't give me any errors to be shown whenever the password was invalid or email is not existing. I put it on jquery mobile website. Any idea?

Anthony

unread,
Jul 5, 2012, 11:20:21 AM7/5/12
to web...@googlegroups.com

When I used customform for login, it won't give me any errors to be shown whenever the password was invalid or email is not existing. I put it on jquery mobile website. Any idea?

Just to clarify, if you let web2py serialize the form widgets (i.e., using {{=form}} or {{=form.custom.widget.field}}), the errors will display (even without using the web2py CSS and JS files). However, if you use completely custom HTML to display the widgets, then you have to handle displaying the errors yourself. The errors can be found in form.errors (i.e., form.errors.myfield contains the error message for the "myfield" field if there is one).

Anthony

Chris

unread,
Jul 7, 2012, 4:55:59 PM7/7/12
to web...@googlegroups.com

{{=form.custom.begin}}
#in between the opening and closing tags put in your normal html code and make sure the element names corresponds to the column names as defined in your database. web2py will handle the rest automatically. This is the most flexible way while still making use of web2py's validators.
{{=form.custom.end}}

Thanks for all the useful info. I have two additional questions:

1. I want to utilize 'layout.html' to some extent such that it stores some common css files for the rest of the view pages. Kinda like a shared base template, and I want to add page-specific css (or js) files on individual html view page. Is it as simple as including:

{{extend layout.html}}
<head>
   <link href="css/page_specific_css_1.css" rel="stylesheet">
   <link href="css/page_specific_css_2.css" rel="stylesheet">
</head>

in the <head> section of a html view page? By doing this, do we append the above two css files to the list of the files in response.files (which is already set in 'layout.html')

2. I also want to enable Captcha support in web2py on my custom view. If I adopt the approach:

{{=form.custom.begin}}
# my custom html code here
{{=form.custom.end}}

How exactly should I add Captcha to the view?

Thanks a lot!


 

Anthony

unread,
Jul 7, 2012, 11:48:05 PM7/7/12
to web...@googlegroups.com
1. I want to utilize 'layout.html' to some extent such that it stores some common css files for the rest of the view pages. Kinda like a shared base template, and I want to add page-specific css (or js) files on individual html view page. Is it as simple as including:

{{extend layout.html}}
<head>
   <link href="css/page_specific_css_1.css" rel="stylesheet">
   <link href="css/page_specific_css_2.css" rel="stylesheet">
</head>

in the <head> section of a html view page? By doing this, do we append the above two css files to the list of the files in response.files (which is already set in 'layout.html')

No, because the layout already includes a <head> section. The content of the view gets inserted in the layout.html file at the point of the {{include}}, which presumably is not in the head. Instead, you have a couple options. First, you could add the files to response.files before extending the layout:

{{response.files.extend([URL('static', 'css/css_1.css'), URL('static', 'css/css_2.css')])}}
{{extend 'layout.html'}}

Then, if your layout includes web2py_ajax.html, it will link those css files in the head. Alternatively, you could create a block in the head of layout.html, and then customize the content of that block in your page view -- see http://web2py.com/books/default/chapter/29/5#Blocks-in-views.

Anthony 

Chris

unread,
Jul 8, 2012, 10:00:58 AM7/8/12
to

No, because the layout already includes a <head> section. The content of the view gets inserted in the layout.html file at the point of the {{include}}, which presumably is not in the head.

The layout also include a <body> section, does this mean that in my custom view I shouldn't have another <body> section such that I should just write all the custom html content without <body></body> tags?

Instead, you have a couple options. First, you could add the files to response.files before extending the layout: 
{{response.files.extend([URL('static', 'css/css_1.css'), URL('static', 'css/css_2.css')])}}
{{extend 'layout.html'}}

Then, if your layout includes web2py_ajax.html, it will link those css files in the head. Alternatively, you could create a block in the head of layout.html, and then customize the content of that block in your page view -- see http://web2py.com/books/default/chapter/29/5#Blocks-in-views.

Another thought, can I add page-specific files for the view in the corresponding controller action, e.g.

def myaction():
   response
.files.append(URL('static','css/page_specific_css_1.css'))
   
response.files.append(URL('static','css/page_specific_css_2.css'))
   
   
# rest of the action
   
...

Would this be enough? If so, how does this compare with the two options you've proposed below?

Regarding my other question about Recaptcha support, how should I embed and customize it in my custom view? Thanks very much!
 

Anthony

unread,
Jul 8, 2012, 10:35:23 AM7/8/12
to web...@googlegroups.com


The layout also include a <body> section, does this mean that in my custom view I shouldn't have another <body> section such that I should just write all the custom html content without <body></body> tags?

Right, you don't need a body tag in the view either. In layout.html, there should be an {{include}} -- that gets replaced with the entire contents of the view that extends the layout. See the /views/default/index.html view of the "welcome" app for an example.
 
Another thought, can I add page-specific files for the view in the corresponding controller action, e.g.

def myaction():
   response
.files.append(URL('static','css/page_specific_css_1.css'))
   
response.files.append(URL('static','css/page_specific_css_2.css'))
   
   
# rest of the action
   
...

Would this be enough? If so, how does this compare with the two options you've proposed below?

Yes, that's fine too. Just a matter of preference. Since the CSS relates to the presentation, some would argue references to it belongs in the views.
 
Regarding my other question about Recaptcha support, how should I embed and customize it in my custom view? Thanks very much!

I don't use reCAPTCHA, so don't have any special insights. Have you read the book section? Looks like it shouldn't be too difficult to add to a form.

Anthony

Chris

unread,
Jul 8, 2012, 12:50:26 PM7/8/12
to web...@googlegroups.com

Right, you don't need a body tag in the view either. In layout.html, there should be an {{include}} -- that gets replaced with the entire contents of the view that extends the layout.

Cool. But is there way to configure the <body> tag of a specific view page that extends the 'layout.html'? For example, for my custom view, I want its body tag to be like:

<body class="my_css_class">

Is it possible to configure such thing? Thanks!

Anthony

unread,
Jul 8, 2012, 2:22:25 PM7/8/12
to web...@googlegroups.com
Cool. But is there way to configure the <body> tag of a specific view page that extends the 'layout.html'? For example, for my custom view, I want its body tag to be like:

<body class="my_css_class">

Is it possible to configure such thing? Thanks!

<body class="{{=body_class}}">

and in the view:

{{body_class = 'my_css_class'}}
{{extend 'layout.html'}}

You could also do it with a block.

Anthony

Chris

unread,
Jul 8, 2012, 7:24:24 PM7/8/12
to

Now I have the custom page (e.g. register) displays correctly, however, when I submit the form nothing happens - the form just clears itself with no error msg or anything.

My 'register' action in controller is:

def register():
   
return dict(form=auth.register())

My 'layout.html' is as follows:

<!DOCTYPE html>
<html class="no-js" lang="{{=T.accepted_language or 'en'}}">
<head>
  <meta charset="utf-8" />
  <title>{{=response.title or request.application}}</title>
  <meta name="application-name" content="{{=request.application}}" />

  <!-- for Google -->
  <meta name="google-site-verification" content="my_code" />
  <!--  Mobile Viewport Fix
 device-width: Occupy full width of the screen in its current orientation
 initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
 user-scalable = yes allows the user to zoom in -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">

  <!-- All JavaScript at the bottom, except for Modernizr which enables 
  HTML5 elements & feature detects -->
  <script src="{{=URL('static','js/modernizr.custom.js')}}"></script>

  <!-- include stylesheets -->
  {{
    response.files.append(URL('static','css/bootstrap.min.css'))
    response.files.append(URL('static','css/bootstrap-responsive.min.css')) 
  }}

  {{include 'web2py_ajax.html'}}

</head>
<body class="{{=body_class}}">
  {{include}}
  {{if response.google_analytics_id:}}<script type="text/javascript"> var _gaq = _gaq || []; _gaq.push(['_setAccount', '{{=response.google_analytics_id}}']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script> {{pass}}
</body>
</html>

And my 'register.html' view is:

{{body_class = 'own'}}
{{
    response.files.append(URL('static', 'css/jquery.h5form-2.4.1.css'))
    response.files.append(URL('static', 'css/own.css')) 
}}
{{extend 'layout.html'}}
<div class="container own-container">
    <h1>Sign up</h1>
    <h5>Create Your Account</h5>
    <div class="row">
        <div class="span12">
            <form action="" enctype="multipart/form-data" method="post" class="own-form h5form">
                <div class="own-labels">
                    <label>First name<sup>*</sup></label>
                    <input type="text" name="first_name" class="span3" placeholder="enter first name ..." required="required" tabindex="0">
                    <label>Last name<sup>*</sup></label>
                    <input type="text" name="last_name" class="span3" placeholder="enter last name ..." required="required" tabindex="1">
                    <label>Email<sup>*</sup></label>
                    <input type="email" name="email" class="span3" placeholder="enter email ..." required="required" tabindex="2">
                    <label>Password<sup>*</sup></label>
                    <input type="password" name="password" class="span3" placeholder="enter password ..." required="required" tabindex="3">
                    <label>Re-password<sup>*</sup></label>
                    <input type="password" name="password" class="span3" placeholder="enter password again ..." required="required" tabindex="4">
                </div>
                <div class="pull-left">
                    <label class="checkbox">
                        <input type="checkbox" tabindex="5">I acknowledge that I have read and accept <a href="#">the Terms and Conditions</a>
                    </label>
                    <input type="submit" class="btn pull-right" style="margin-top: 15px;" value="Register">
                </div>
            </form>
        </div>
    </div>
</div>
<script src="{{=URL('static', 'js/jquery-1.7.2_min.js')}}"></script>
<script src="{{=URL('static', 'js/jquery.h5form-2.4.1.min.js')}}"></script>
<script src="{{=URL('static', 'js/own.js')}}"></script>

As told by people earlier, in the form above, I match the 'name' attribute of each input field with the corresponding column name of the auth_user table. What is missing here?

Anthony

unread,
Jul 8, 2012, 8:13:26 PM7/8/12
to web...@googlegroups.com
As was mentioned earlier, if you're going to create a custom form like that, in place of </form>, you need to include {{=form.custom.end}}. The reason is that web2py forms include two hidden fields, _formname and _formkey, and form.custom.end will add those hidden fields. They are used to protect against double form submission and CSRF attacks. See http://web2py.com/books/default/chapter/29/7#Hidden-fields and http://web2py.com/books/default/chapter/29/7#Custom-forms.

Anthony
            <form action="" enctype="multipart/form-data" method="post" class="own-form h5form">
                <div class="own-labels">
                    <label>First name<sup>*</sup></label>
                    <input type="text" name="first_name" class="span3" placeholder="enter first name ..." required="required" tabindex="0">
                    <label>Last name<sup>*</sup></label>
                    <input type="text" name="last_name" class="span3" placeholder="enter last name ..." required="required" tabindex="1">
                    <label>Email<sup>*</sup></label>
                    <input type="email" name="email" class="span3" placeholder="enter email ..." required="required" tabindex="2">
                    <label>Password<sup>*</sup></label>
                    <input type="password" name="password" class="span3" placeholder="enter password ..." required="required" tabindex="3">
                    <label>Re-password<sup>*</sup></label>
                    <input type="password" name="password" class="span3" placeholder="enter password again ..." required="required" tabindex="4">
                </div>
                <div class="pull-left">
                    <label class="checkbox">
                        <input type="checkbox" tabindex="5">I acknowledge that I have read and accept <a href="#">the Terms and Conditions</a>
                    </label>
                    <input type="submit" class="btn pull-right" style="margin-top: 15px;" value="Register">
                </div>
            </form>
        </div>
    </div>
</div>
<script src="{{=URL('static', 'js/jquery-1.7.2_min.js')}}"></script>
<script src="{{=URL('static', 'js/jquery.h5form-2.4.1.min.js')}}"></script>
<script src="{{=URL('static', 'js/own.js')}}"></script>

Chris

unread,
Jul 8, 2012, 8:44:23 PM7/8/12
to web...@googlegroups.com

Oops, I thought I needed either matching <form ...></form> or {{=form.custom.begin}}{{=form.custom.end}}.

By using {{=form.custom.end}}, the forms seems to submit now, but this strange exception comes up:

Traceback (most recent call last):
  File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/restricted.py", line 205, in restricted
   
exec ccode in environment
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/applications/turklab/controllers/account.py", line 85, in <module>
  File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/globals.py", line 173, in <lambda>
    self._caller = lambda f: f()
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/applications/turklab/controllers/account.py", line 24, in register
    return dict(form=auth.register())
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/tools.py", line 1929, in register
    onvalidation=onvalidation,hideerror=self.settings.hideerror):
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/sqlhtml.py", line 1089, in accepts
    hideerror=hideerror,
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/html.py", line 1841, in accepts
    status = self._traverse(status,hideerror)
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/html.py", line 781, in _traverse
    newstatus = c._traverse(status,hideerror) and newstatus
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/html.py", line 781, in _traverse
    newstatus = c._traverse(status,hideerror) and newstatus
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/html.py", line 781, in _traverse
    newstatus = c._traverse(status,hideerror) and newstatus
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/html.py", line 781, in _traverse
    newstatus = c._traverse(status,hideerror) and newstatus
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/html.py", line 788, in _traverse
    newstatus = self._validate()
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/html.py", line 1606, in _validate
    (value, errors) = validator(value)
 
File "/Users/chris/Package/envs/.virtualenvs/capp/web2py/gluon/validators.py", line 2590, in __call__
    all_upper = re.findall("[A-Z]", value)
 
File "/Users/chris/Package/envs/.virtualenvs/capp/lib/python2.7/re.py", line 177, in findall
    return _compile(pattern, flags).findall(string)
TypeError: expected string or buffer

line 24 of account.py is simply:

return dict(form=auth.register())

and line 85 of account.py is a blank line. Any idea where the error come from? Is it related to the custom html I am using? Thanks again!

Massimo Di Pierro

unread,
Jul 8, 2012, 9:58:05 PM7/8/12
to web...@googlegroups.com
which web2py version? Do you have a custom auth_user table? Looks like something wrong with the list of validators for a field containing the IS_UPPER validator. 

Chris

unread,
Jul 9, 2012, 5:47:39 AM7/9/12
to web...@googlegroups.com

which web2py version? Do you have a custom auth_user table? Looks like something wrong with the list of validators for a field containing the IS_UPPER validator.

This is on 1.99.7 stable. I do have two extra field to the auth_user table, and use a custom name instead of 'auth_user', but such customization should be fairly minimal. I just searched for the use of 'IS_UPPER' validator in my code, but didn't find any. The only thing related to it is the use of
 
IS_STRONG(min=7, special=0, upper=0, number=0)

but as you can see, there is no restriction on upper case characters.

I wonder if it has anything to do the javascript form (jquery.h5form-2.4.1.min.js) I have in the custom view, as it does some validation to the fields as well. Thank you for looking into this.

Chris

unread,
Jul 13, 2012, 12:06:29 PM7/13/12
to web...@googlegroups.com

Any further suggestion to what causes the error I'm seeing? Thanks!

Massimo Di Pierro

unread,
Jul 13, 2012, 12:51:00 PM7/13/12
to web...@googlegroups.com
I really need to see the models where you set IS_STRONG.

Somehow the value passed to the validator is not a string so the validator chokes. This can be fixed in IS_STRONG.__call__ by setting value = str(value) but I would like to understand why it is happening. 

Chris

unread,
Jul 13, 2012, 4:35:12 PM7/13/12
to web...@googlegroups.com

I really need to see the models where you set IS_STRONG.
Somehow the value passed to the validator is not a string so the validator chokes. This can be fixed in IS_STRONG.__call__ by setting value = str(value) but I would like to understand why it is happening. 

Sure, this is the relevant line of code:

db.user_login.password.requires = [IS_NOT_EMPTY(error_message='Password needed!'), IS_STRONG(min=7, special=0, upper=0, number=0), CRYPT()]

I am confirm that I am testing the form using a 7-character password. Thanks!

Massimo Di Pierro

unread,
Jul 13, 2012, 7:09:07 PM7/13/12
to web...@googlegroups.com
I applied your line to auth_user:

db.auth_user.password.requires = [IS_NOT_EMPTY(error_message='Password needed!'),IS_STRONG(min=7, special=0, upper=0, number=0), CRYPT()]

and I cannot reproduce your problem. :-(

There is something else in the logic that is passing a non-string to IS_STRONG()(value).
Reply all
Reply to author
Forward
0 new messages