custom forms - a suggestion

8 views
Skip to first unread message

billf

unread,
Oct 14, 2008, 6:58:04 AM10/14/08
to web2py Web Framework
My objective is to be able to hand-code my own view containing a form
that I can layout exactly as I want but retain the benefits of
accepts() re validation and db updating.

I found 2 obstacles with web2py out-of-the box:
1) In my view (I have less than 1 week of python), I did not have easy
access to the latest version of a field: defaults could be in
form.record.my_field (but not for a new record) but if the user has
input data then it is in form.vars.my_field. I wanted a consistent
source and the only place is form.components but extracting the values
in the view seems difficult due to the nested nature of components.

2) For dropdown lists, the value is not enough. I want the whole
<select><options> etc including which option is selected.

If I can achieve the above there are a couple of other requirements:
3) For completeness, support for the session/formkey mechanism and any
other hidden fields that may be specified.

4) If I display the whole control (as in 2) above), I don't want any
pre-formatted errors. I can get the necessary info from form.errors.

With my solution, the usage is as follows:
- in a controller, after the SQLFORM(db.recipe) or FORM(whatever) is
created add form.customview=True

- in a view, after the <form> tag for your custom form add {{=form}} -
this will create a Storage called 'latest' in FORM with a 'value' and
a 'component' for each fieldname and return the following:
<!-- custom view -->
<input value="130911852535" type="hidden" name="_formkey" />
<input value="recipe" type="hidden" name="_formname" />
... plus any other hidden INPUTS specified in the form

- if you just want the latest value for a fieldname 'name' then code
something like
<input type="text" name="name" value="{{=form.latest.name['value']}}"/
>

- if you just want the whole control then code something like
{{=form.latest.name['component']}} to get
<input type="text" class="string" name="name" value="Cornish Pasty"
id="recipe_name" />"

- or, where the field has IS_IN_SET, code
{{=form.latest.type['component']}} to get
<select class="string" name="type" id="recipe_type"><option
value="A">accompaniment</option><option value="C">component</
option><option value="R" selected="selected">recipe</option></select>

So, assuming anyone's still interested, what changes did I make to the
web2py code? Well, it's all in html.py and makes use of a new Storage
attribute called 'latest' added to FORM if customview is present and
True:

a) replace the existing FORM.xml() method with the following:
def xml(self):
if self.customview and self.customview==True:
self._latestdata()
# return hidden INPUTs in response to {{=form}}
hidden=''
field=self.latest['hidden']
for component in field['component']:
hidden+=component.xml()+'\n'
return '<!-- custom view -->\n'+hidden
else:
# exactly the same as old xml() except hidden logic in
common method _hiddendata([])
newform=FORM(*self.components,**self.attributes)
self._hiddendata(newform.components)
return DIV.xml(newform)
# common method for creating/storing formkey and creating hidden
INPUTs - taken from original xml()
def _hiddendata(self, components):
if self.session!=None:
_formkey='_formkey[%s]' % self.formname
key=self.session[_formkey]=str(random.random())[2:]

components.append(INPUT(_type='hidden',_name='_formkey',_value=key))
if self.formname!=None:
components.append(INPUT(_type='hidden',_name='_formname',\
_value=self.formname))
if self.attributes.has_key('hidden'):
hidden=self['hidden']
for key,value in hidden.items():

components.append(INPUT(_type='hidden',_name=key,_value=value))
# create the Storage to hold value and component for each
fieldname + one for 'hidden' INPUTs
def _latestdata(self):
self.latest=Storage()
components=[]
self._hiddendata(components)

self.latest['hidden']={'value':'hidden','component':components}
for component in self.components:
component._latestdata(self.latest)

a) add the following to DIV to find the required component and store
in latest Storage
def _latestdata(self, latest):
# the following code checks whether the component can have
user input and
# if true stores the value and component using the latestitem
helper
# if false iterates through child components
if self.attributes.has_key('_value') and\
self.attributes.has_key('_name'):

latest[self.attributes['_name']]=self.latestitem(self.attributes['_value'],self)
elif isinstance(self,SELECT) and\
self.attributes.has_key('_name'):

latest[self.attributes['_name']]=self.latestitem(None,self)
elif isinstance(self,TEXTAREA) and\
self.attributes.has_key('value') and\
self.attributes.has_key('_name'):
value=self.attributes['value']
if value==None: value=''

latest[self.attributes['_name']]=self.latestitem(value,self)
elif self.components:
for c in self.components:
if hasattr(c,'_latestdata'): c._latestdata(latest)
# store value and component ensuring any errors are ignored
def latestitem(self, value, component):
component.errors=None
return {'value':value,'component':component}

That's it - it all seems to work a treat. All feedback gratefully
received (this is where Massimo tells me it could all have been done
with 2 lines of python and a regex) :-)

Bill

mdipierro

unread,
Oct 14, 2008, 10:15:22 AM10/14/08
to web2py Web Framework
give me some time to digest this. could email it to me again. the
indentation is messed up.

billf

unread,
Oct 15, 2008, 4:29:25 AM10/15/08
to web2py Web Framework
I have noted one problem with my suggestion. As coded, a standard,
non-custom form throws an error with customview not defined. I
thought that

if self.customview and self.customview=True:

in FORM.xml() would catch the absence of customview but doesn't seem
to(?) so I have changed it to

if not self.customview: self.customview=False
if self.customview==True:

Bill

billf

unread,
Oct 19, 2008, 3:27:17 PM10/19/08
to web2py Web Framework
Massimo

Did you get a chance to look at my modifications to html.py?

FYI I have looked at creating a solution outside of the core web2y
files either as a totally separate class or extending SQLFORM.

The former is based on the idea of extracting the necessary form
values from the standard SQLFORM or FORM into a new class and passing
that to the view but I am having problems supporting the session/
formkey logic as the formkey is calculated when {{=form}} in the view
is processed and that would include the standard form in the view
which is contrary to the whole purpose.

The latter is made more difficult (impossible?) with the addition of
t2 as the t2 methods use SQLFORM so any subclass would be ignored as
far as I can see.

So back to my html.py suggestion - any thoughts?

mdipierro

unread,
Oct 19, 2008, 6:33:37 PM10/19/08
to web2py Web Framework
I have not had the time but I do like your second solution. You can do

SQLFORM.myfunc=lambda self: myfunc(self)

Anyway, If I am not mistaken you only need myfunc in views hence I am
not sure you need it to be a method of SQLFORM, it could simply be a
normal function that acts on the form.

Massimo

billf

unread,
Oct 19, 2008, 7:12:12 PM10/19/08
to web2py Web Framework
This is where my lack of knowledge of python lets me down - I just
didn't know that you could override a method like that. I will have a
play.

billf

unread,
Oct 20, 2008, 8:05:15 AM10/20/08
to web2py Web Framework
I have come up with a solution that requires no changes to web2py
code. I have written out an explanation at
http://www.wellbehavedsystems.co.uk/web2py/examples/custom_forms.html

For simplicity, I have just put the code in the db.py file but it
could be in a module. Any suggestions for improvements gratefully
received.

mdipierro

unread,
Oct 20, 2008, 8:47:13 AM10/20/08
to web2py Web Framework
Anyway Bill,

I think there is room for including this in web2py. Your patch is in
the queue...

Thank you.

Massimo

On Oct 20, 7:05 am, billf <billferr...@blueyonder.co.uk> wrote:
> I have come up with a solution that requires no changes to web2py
> code. I have written out an explanation athttp://www.wellbehavedsystems.co.uk/web2py/examples/custom_forms.html
> ...
>
> read more »
Reply all
Reply to author
Forward
0 new messages