form.py: first draft up now

16 views
Skip to first unread message

Aaron Swartz

unread,
Jan 22, 2006, 3:04:06 PM1/22/06
to we...@googlegroups.com
The first draft of my form library is now up:

http://webpy.org/form.py

I'd love to hear your thoughts.

Sample usage:

import web, form

myform = form.Form(
form.Textbox("boe"),
form.Textbox("bax",
form.notnull,
form.Validator('Must be more than 5', lambda x:int(x)>5)),
form.Textarea('moe'),
form.Checkbox('curly'),
form.Dropdown('french', ['mustard', 'fries', 'wine']))

class index:
def GET(self):
form = myform()
web.render('formtest.html')

def POST(self):
form = myform()
if not form.validates(): return web.render('formtest.html')
print "when libraries compete, you win!"


And sample formtest.html:

<style type="text/css">
form p { display: table}
form p label { display: table-row; }
form p label strong.label { text-align: right; color: gray;
font-weight: normal; text-transform: lowercase; }
form p label strong.label:after { content: ":"}
form .wrong { color: red; font-weight: bold; }
form p label * { display: table-cell; padding: .3em; vertical-align: middle;}
</style>

<form name="main" method="post">
#filter Filter
#if not $form.valid#<p class="wrong">Try again, cowpoke:</p>#end if#
$form.render()
#filter WebSafe
</form>

peter

unread,
Jan 22, 2006, 5:43:27 PM1/22/06
to web.py
Using the template code example (which works )

http://groups.google.com/group/webpy/browse_thread/thread/d8c9f2adb704f951

and adding 'import form' returns errors (see below).
Have you updated web.py? Or could it be my setup?

------ apache debug: tail -f /var/log/httpd/error_log ------
PythonHandler wsgiref.modpython_gateway::handler: Traceback (most
recent call last):

File "/usr/lib/python2.4/site-packages/mod_python/apache.py", line 299,
in HandlerDispatch\n result = object(req)

PythonHandler wsgiref.modpython_gateway::handler: File
"/usr/lib/python2.4/site-packages/wsgiref/modpython_gateway.py", line
176, in handler\n module = __import__(modname, globals(), locals(),
[''])

PythonHandler wsgiref.modpython_gateway::handler: File
"/var/www/html/wbForm.py", line 4, in ?\n import form

PythonHandler wsgiref.modpython_gateway::handler: File
"/var/www/html/form.py", line 112, in ?\n notnull =
Validator("Required", bool)

PythonHandler wsgiref.modpython_gateway::handler: File
"/var/www/html/form.py", line 107, in __init__\n def __init__(self,
msg, test, jstest=None): web.autoassign()

PythonHandler wsgiref.modpython_gateway::handler: File
"/var/www/html/web.py", line 51, in autoassign\n self =
locals['self']

PythonHandler wsgiref.modpython_gateway::handler: KeyError: 'self'
------ apache debug: tail -f /var/log/httpd/error_log ------

You can find code/debug image at .....
http://www.flickr.com/photos/bootload/89893809/
http://www.flickr.com/photos/bootload/89893808/

Aaron Swartz

unread,
Jan 22, 2006, 6:05:44 PM1/22/06
to we...@googlegroups.com
Oh, good point. I think it's a web.py bug. I got it to work by
changing web.py a couple lines in where it says:

def autoassign():
locals = sys._getframe(2).f_locals

Just change that 2 to a 1 and it should work.

Also, I should say that form.d returns a dictionary of the form,
suitable for use with something like web.insert or web.update. And I
just uploaded a version where you can call
form.validates(storageobject) and it'll fill in the form.

peter

unread,
Jan 22, 2006, 8:33:48 PM1/22/06
to web.py
>just change that 2 to a 1 and it should work.
Correct. I can now run using 'import form'.

So in web.py in function, autoassign approx L 49, make the following
changes:
FROM: local = sys._getframe(2).f_locals
TO: local = sys._getframe(1).f_locals

------ web.py snippet ------
49 def autoassign():
50 # locals = sys._getframe(2).f_locals
51 locals = sys._getframe(1).f_locals
51 self = locals['self']
52 for (k, v) in locals.iteritems():
53 if k == 'self': continue
54 setattr(self, k, v)
------ web.py -----

I'll post back when I get it to run with the code.

Regs PR

peter

unread,
Jan 23, 2006, 5:44:52 AM1/23/06
to web.py
Hi Aaron,

heres the best I can do. Below is the most amount of code I
can run before apache barfs. So to recap,

*import form is OK
*myform code works
*failure is on 'class Index'

Description:
------------
*remember this code is for apache2, mod_python2, python241 etc running
web.py v0125.
*apache log reports error in and around web.py, L 380
-on 'def handle(mapping, fvars=None):'
*after the result (match) is made & returning the 'tocall' func
-return tocall(*[urllib.unquote(x) for x in args])

Error reported:
---------------
*Traceback (most recent call last):\n
-File "/usr/lib/python2.4/site-packages/wsgiref/handlers.py", line 92,
in run\n
-self.result = application(self.environ, self.start_response)\n
-File "/var/www/html/web.py", line 896, in wsgifunc\n
- func()\n
-File "/var/www/html/web.py", line 888, in <lambda>\n
-func = lambda: handle(inp, fvars)\n
-File "/var/www/html/web.py", line 380, in handle\n
- return tocall(*[urllib.unquote(x) for x in args])\n
-TypeError: GET() takes exactly 1 argument (2 given)\n
*view the code/debug shots at flickr
http://www.flickr.com/photos/bootload/90145641/
http://www.flickr.com/photos/bootload/90148217/


What should have happened:
--------------------------
*GET function should have returned 1 arg (bug in web.py)
or 2 args (bug in sample app).


Questions
---------
Is the bug in the supplied test code? Or is it in web.py?
Not really sure what the 'def handle' method is doing.

Regs PR


------ wbForm.py ------
#!/usr/bin/env python
import os
import web
import form


myform = form.Form(
form.Textbox("boe"),
form.Textbox("bax",
form.notnull,
form.Validator('Must be more than 5', lambda x:int(x)>5)),
form.Textarea('moe'),
form.Checkbox('curly'),
form.Dropdown('french', ['mustard', 'fries', 'wine']))

url = ('/(.*)', 'Index')
class Index:
def GET(self):
web.debug("Index.GET")
def POST(self):
web.debug("Index.POST")
main = web.wsgifunc(web.webpyfunc(url))
------ wbForm.py -----

Filip Salomonsson

unread,
Jan 23, 2006, 7:24:30 AM1/23/06
to we...@googlegroups.com
On 1/23/06, peter <goon...@netspace.net.au> wrote:
> url = ('/(.*)', 'Index')
> class Index:
> def GET(self):

Your GET method should take an extra argument (which will be the part
of the URL that matches the "(.*)" part of your url pattern). Or you
could just remove the parentheses.
--
filip salomonsson

Filip Salomonsson

unread,
Jan 23, 2006, 12:48:01 PM1/23/06
to we...@googlegroups.com
On 1/22/06, Aaron Swartz <m...@aaronsw.com> wrote:
>
> I'd love to hear your thoughts.

1) How about only printing notes if form.valid is False?

2) How about having the Input constructor accept optional **kwargs (or
perhaps just a few named ones), to be rendered as html attributes?
That would make it easy to, say, style required fields with a CSS rule
and form.Textbox("bax", class="required").
--
filip salomonsson

peter

unread,
Jan 23, 2006, 3:25:07 PM1/23/06
to web.py
>Your GET method should take an extra argument (which will be the part
>of the URL that matches the "(.*)" part of your url pattern). Or you
>could just remove the parentheses.

Thanks filip you have identified the problem.

Aaron Swartz

unread,
Jan 23, 2006, 3:31:15 PM1/23/06
to we...@googlegroups.com
> 1) How about only printing notes if form.valid is False?

Shouldn't the user get some indication of what they need to enter? If
you want to, you could easily turn this off with a stylesheet.

> 2) How about having the Input constructor accept optional **kwargs (or
> perhaps just a few named ones), to be rendered as html attributes?
> That would make it easy to, say, style required fields with a CSS rule
> and form.Textbox("bax", class="required").

Yep, quite right.

Filip Salomonsson

unread,
Jan 23, 2006, 4:04:35 PM1/23/06
to we...@googlegroups.com
On 1/23/06, Aaron Swartz <m...@aaronsw.com> wrote:
>
> > 1) How about only printing notes if form.valid is False?
>
> Shouldn't the user get some indication of what they need to enter?

In most cases, I think the label should suffice until the user
actually enters invalid data. For example, a text field labeled "email
address" _and_ a note saying something like "must be a valid email
address" seems a bit pushy. Until a user enters "none" or
"f...@bar.baz" or...
--
filip salomonsson

peter

unread,
Jan 23, 2006, 5:53:55 PM1/23/06
to web.py
Hi Aaron,

*I've just got some of the form working. Just a fragment.
*the generated html itself is correct but not valid [0]
-meaning output may not render as you expect
-output also requires prettyprint.
*Browsers are picky with things like spaces.
*Humans are picky about layout.

I suggest that:
*optional python bindings for tidy [1] (micro tidy - python) [2] be
implemented within the Form.render method allowing:
-detection & correction of invalid or ill-formatted output.
-pretty print


Reference
[0]
Code generated is ....
------ test.py ------
myform = form.Form(form.Textbox("boe"))
------ test.py ------

output generated is ...
------ verbatum html ------


<form name="main" method="post">

<p><label><strong class='label'>boe</strong> <input type="text"
name="boe" /><span id="note_boe"></span><br /></label>
</form>
------ verbatum html ------

and I hand edited the output so it could be rendered properly in
firefox.
------ corrected html ------


<form name="main" method="post">

<p>
<label>
<strong class='label'>boe</strong>
<input type="text" name="boe" />
<span id="note_boe"></span><br />
</label>
</p>
</form
------- corrected html ------

[1] tidy, 'html validation lib':
http://tidy.sourceforge.net/
[Accessed Tuesday, 24 January 2006]

[2] µTidylib, 'TidyLib Python wrapper':
http://utidylib.berlios.de/
[Accessed Tuesday, 24 January 2006]

peter

unread,
Jan 23, 2006, 11:18:53 PM1/23/06
to web.py
Well its not the html thats causing the problem (though its not pretty
printed & has missing elements. Its the stylesheet. Commenting out the
following line in form.html

*form p label * { display: table-cell; padding: .3em; vertical-align:
middle;}

allows the textbox to be viewed. Whats this style sheet line for?

Regs PR

Message has been deleted

Kamal Mustafa

unread,
Jan 28, 2006, 7:06:40 PM1/28/06
to we...@googlegroups.com
well ... I know this is a little bit late but only today I got some
time to take a look at the form library.

> And sample formtest.html:
>
> <style type="text/css">
> form p { display: table}
> form p label { display: table-row; }
> form p label strong.label { text-align: right; color: gray;
> font-weight: normal; text-transform: lowercase; }
> form p label strong.label:after { content: ":"}
> form .wrong { color: red; font-weight: bold; }
> form p label * { display: table-cell; padding: .3em; vertical-align: middle;}
> </style>

is it only me or it's a known bug that display:table would not work on
firefox ? I'm using Firefox 1.0.6 and the form input boxes/textarea
were not displayed. I know it's there since hovering my cursor would
display the autocomplete lists. It work in Konqueror anyway. I'm
unwilling to use other kind of rendering such as '<lable for="name">'
since using 'display: table' look much better to me.

> <form name="main" method="post">
> #filter Filter
> #if not $form.valid#<p class="wrong">Try again, cowpoke:</p>#end if#
> $form.render()
> #filter WebSafe
> </form>
>

I think it would be better if could specify all the form attributes
(name, action etc) using the API rather than having to manually
specify it in template. I know this is a draft so I hope aaron already
have some thought about it.

this draft look's good to me. quite simple yet able to do the task.
with my totally lack understanding of python code I think I can slowly
go through it, thus gave some feel good feeling in mine. looking
forward for the final implementation.
--
kamal, www.k4ml.com

peter

unread,
Jan 28, 2006, 8:11:02 PM1/28/06
to web.py
Try commenting out the style sheet line in formtest.html:

> form p label * { display: table-cell; padding: .3em;
vertical-align: middle;}

Pretty sure I had this problem. Check (
http://www.flickr.com/photos/bootload/90408507/ ) here to see if this
is what your form looks like using only the first textbox in the
supplied code.

#my quick test to see if textbox generated


myform = form.Form(form.Textbox("boe"))

The before & after shot is simply the last line in the style sheet (one
above) commented out. Why the style fails I'm note sure. I just wanted
to get it running.

Regs PR

PS Nice wiki code you put up.

Kamal Mustafa

unread,
Jan 28, 2006, 10:09:02 PM1/28/06
to we...@googlegroups.com
I change the stylesheet a little bit. now it works.

form label strong,span,textarea {


display: table-cell;
padding: .3em;
vertical-align: middle;
}

I specify each elements inside label instead of using the universal
selector *. also left out <select> to prevent it from being rendered
as table cell. the <p></p> surrounding the form also need to be
removed.

def render(self):
out = ''
#out += '<p>'
for i in self.inputs:
out += "<label><strong class='label'>%s</strong> " % i.name
out += i.render()
out += '<span id="note_%s">%s</span><br /></label>\n' %
(i.name, self.rendernotes(i.notes()))
#out += '</p>'
return out

--
kamal, www.k4ml.com

peter

unread,
Jan 29, 2006, 1:03:20 AM1/29/06
to web.py
Beaut. I'll apply this.

Regs PR

Kamal Mustafa

unread,
Jan 29, 2006, 3:44:29 AM1/29/06
to we...@googlegroups.com
please correct me if I'm wrong but I couldn't find a way to set the
input value once you get the Form object. This is useful for example
to set a hidden value based on input from GET. so I add a function
where you can pass the input name and the value to set in form.py

class Form:
....
def inputvalue(self, name, value):
for input in self.inputs:
if input.name == name:
input.value = value

and a snippet of my code to show why I need to set the input value:-

class Node(Page):
def GET(self, nid):
self.page.form = myform()
self.page.form.inputvalue('nid', nid)
self.page.node = self.get_node(nid)
self.page.comment = self.get_comment(nid)
self.render()

def POST(self):
input = web.input()
self.page.form = myform()
self.page.node = self.get_node(input.nid)
self.page.comment = self.get_comment(input.nid)
if not self.page.form.validates(): return self.render()


print "when libraries compete, you win!"

any comments ?

--
kamal, www.k4ml.com

GB

unread,
Jan 29, 2006, 1:55:57 PM1/29/06
to web.py
Been messing around a bit with form.py -- some quick notes.

* I'm not sure I'll use render/notes, pretty much just care about
the validation -- I'll keep the errors messages, actual form xhtml
and explanatory text in the template. Course the good thing is
there's nothing forcing me to use it.

* I ended up wanted to keep lists of inputs separate from the
form itself [ and resusing them across forms ] -- to help with
that I changed the Form constructor to flatten the args:

form.py -->

def flatten(seq):
rlst = []
for x in seq:
if type(x) is list or type(x) is tuple:
for val in x: rlst.append(val)
else:
rlst.append(x)
return rlst

class Form:
def __init__(self, *inputs):
self.inputs = flatten(inputs)
self.valid = True

<---

* As I wasn't using notes, what I wanted was a quick
way to get a mapping of errors to input names that I
could iterate over in the template, so I also added an
errors method.

form.py -->

class Form:
...

def errors(self):
errs = {}
for i in self.inputs:
errnotes = [msg[0] for msg in i.notes() if msg[1] is False]
if len(errnotes) > 0:
errs[i.name] = errnotes
return errs

<--

All in all, I like how form handling has started out -- thanks
for putting it up.

Gary

Harry Fuecks

unread,
Jan 30, 2006, 6:17:31 AM1/30/06
to web.py
By way of irritating musing here have a (probably unformed) RESTish
angle.

Thinking in terms of that simple wiki example
(http://www.sitepoint.com/blogs/2006/01/06/a-simple-wiki-with-webpy/)
what I was aiming for was to have the "WikiPage" class / resource
ignorant of forms, while having a "WikiEditor" resource responsible for
setting up the forms to POST to WikiPage.

Of course a wiki doesn't generally have complex form requirements so
may be it's a bad example to begin with but the basic caveat I'm trying
to apply is "absolutely no verbs in URLs" e.g

- http://ex.org/page?action=edit is bad

- http://ex.org/pages/results?term=searchword rather than
http://ex.org/pages?search=searchword etc.

To make that happen thinking along the lines that the form renderer is
itself a resource with a name.

Assuming there is some value in doing things this way, could form.py be
tuned in that direction?

One particular thing which spring to mind there is validation _rules_
probably need to be re-usable in a way that's independent from form
rendering.

Have some general (not RESTish) comments which I'll add seperately some
other time.

Reply all
Reply to author
Forward
0 new messages