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>
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/
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.
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
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 -----
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
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
Thanks filip you have identified the problem.
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.
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
*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]
*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
> 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
> 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.
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
Regs PR
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
* 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
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.