SingleSelectField of WidgetsList not properly updated

1 view
Skip to first unread message

Cecil Westerhof

unread,
May 12, 2008, 1:06:32 PM5/12/08
to TurboGears
I have the following functions:
#####
def getOptions(selectLabel, table):
return [(-1, selectLabel)] +\
[(entry.id, entry) for entry in table.select()]

def getSingleSelectField(label, name, table, validator = None):
selectLabel = 'Selecteer %s' % (label)
return SingleSelectField(label = label,
name = name,
options = getOptions(selectLabel, table),
validator = validator)
#####

and the folowing class:
#####
class DogFields(WidgetsList):
def getOwners(self):
return getSingleSelectField('Eigenaar',
'owner',
Owner)

def refresh(self):
self.owner = self.getOwners()
log.info(self.owner)

name = TextField(label = 'Naam *',
validator = validators.NotEmpty)
description = TextField(label = 'Omschrijving *',
validator = validators.NotEmpty)
sex = RadioButtonList(label = "Geslacht",
validator = validators.NotEmpty,
options =
Dog.sqlmeta.columns['sex'].enumValues,
default =
Dog.sqlmeta.columns['sex'].default)
isNeutered = CheckBox(label = "Is geholpen",
default = False)
mayOnMeadow = CheckBox(label = "Mag op de wei",
default = True)
needSupervisor = CheckBox(label = "Op wei onder toezicht",
default = False)
mayHike = CheckBox(label = "Mag mee worden gewandeld",
default = False)
morningFood = TextField(label = 'Ochtendvoer')
afternoonFood = TextField(label = 'Middagvoer')
eveningFood = TextField(label = 'Avondvoer')
notes = TextArea(label = 'Opmerkingen')
owner = getOwners(None)
#####

For one reason or another the owner field keeps the value of the first
time the class is used.
When I make a new owner and call the form with this class again, like:
#####
def getForm(table):
if( table == 'Dog' ):
fields = DogFields()
elif( table == 'Owner' ):
fields = OwnerFields()
elif( table == 'Reservation' ):
fields = ReservationFields()
else:
log.error('Got a non existing table: %s' % (table))
return None
fields.refresh()
return TableForm(fields = fields, method = 'POST')
#####

and in the controller:
@expose(template='kid:tblog.templates.feedingList.form')
def newRecord(self, table, **kw):
form = getForm(table)
if table == 'Dog':
log.info(form.fields.owner)
return dict(form = form,
page = 'new',
params = kw,
table = table,
values = kw)

I see in the log that the new owner is in the option list that is used
in refresh, but in the select in the form it is not. The same in
newRecord. The log.info(form.fields.owner) displays the new owner. But
the new owner is only displayed in the form after restarting the
server.
What is happening here?

--
Cecil Westerhof

Cecil Westerhof

unread,
May 12, 2008, 5:50:34 PM5/12/08
to TurboGears
2008/5/12 Cecil Westerhof <cldwes...@gmail.com>:

I understood that it is best to use the declarative form as above. But
that did not work in my case.
I made the following function:
#####
def getDogFields():
return [TextField(label = 'Naam *',
name = 'name',
validator = validators.NotEmpty),


TextField(label = 'Omschrijving *',

name = 'description',
validator = validators.NotEmpty),
RadioButtonList(label = "Geslacht",
name = 'sex',


validator = validators.NotEmpty,
options = Dog.sqlmeta.columns['sex'].enumValues,

default = Dog.sqlmeta.columns['sex'].default),


CheckBox(label = "Is geholpen",

name = 'isNeutered',
default = False),


CheckBox(label = "Mag op de wei",

name = 'mayOnMeadow',
default = True),


CheckBox(label = "Op wei onder toezicht",

name = 'needSupervisor',
default = False),


CheckBox(label = "Mag mee worden gewandeld",

name = 'mayHike',
default = False),
TextField(label = 'Ochtendvoer',
name = 'morningFood'),
TextField(label = 'Middagvoer',
name = 'afternoonFood'),
TextField(label = 'Avondvoer',
name = 'eveningFood'),
TextArea(label = 'Opmerkingen',
name = 'notes'),
getSingleSelectField('Eigenaar', 'owner', Owner),
]
#####

And changed in the function getForm


if( table == 'Dog' ):
fields = DogFields()

to


if( table == 'Dog' ):

fields = getDogFields()

And now the form works as it is supposed to do.
Is there a way to work with the declarative form when using dynamic
data, or should I always do it like I am now doing things?

--
Cecil Westerhof

Diez B. Roggisch

unread,
May 12, 2008, 6:23:49 PM5/12/08
to turbo...@googlegroups.com
Cecil Westerhof schrieb:

That's a tough one. Because you go so much against everything widgets are..

Widgets are supposed to be static. The above violates that in more ways
than one.

Instead of what you do, something like this should be done

def get_owners():
# return a list of owners based on some criteria
# use e.g. cherrypy.request to store that in a thread-safe manner


class DogFields(WidgetsList):
...
# note the callable!
owner = SingleSelectField(options=get_owners)


dog_form = TableForm(fields=DogFields())
owner_form = TableForm(fields=OwnerFields()
...

def get_form(table):
# did you hear of data driven programming?
return {'Dog' : dog_form, 'Owner' : owner_form}[table]


Do you get the idea?


DON'T change widgets at runtime. At the most create them - but to be
honest: I didn't see any reason so far in my projects to do so, because
ultimately whatever you want to do with a form must be stored in a DB or
such, thus resulting in something that should be declarable static.
Variances only include 1:n-relations, which formencode and the widgets
can deal with as well.

Diez

Cecil Westerhof

unread,
May 12, 2008, 9:19:43 PM5/12/08
to turbo...@googlegroups.com
2008/5/13 Diez B. Roggisch <de...@web.de>:

> That's a tough one. Because you go so much against everything widgets are..

I am still learning.


> Widgets are supposed to be static. The above violates that in more ways
> than one.
>
> Instead of what you do, something like this should be done
>
> def get_owners():
> # return a list of owners based on some criteria
> # use e.g. cherrypy.request to store that in a thread-safe manner
>
>
> class DogFields(WidgetsList):
> ...
> # note the callable!
> owner = SingleSelectField(options=get_owners)

This what I did not know. That is what was needed. :-}


> def get_form(table):
> # did you hear of data driven programming?
> return {'Dog' : dog_form, 'Owner' : owner_form}[table]

This was new for me. Quit neat I must say.
I did put it in a try-block, in case table has an illegal value.


> Do you get the idea?

Yes, it was quit enlighting. I am using the declarative form again.

I only have one minor problem left. The other fields are filled
correctly for an edit. But for the owner field I have to set the
selected value with some javascript at the client side, because it is
not set. Is there a way to get the selected value rightly set?

--
Cecil Westerhof

Diez B. Roggisch

unread,
May 13, 2008, 5:43:45 AM5/13/08
to turbo...@googlegroups.com
>
> I only have one minor problem left. The other fields are filled
> correctly for an edit. But for the owner field I have to set the
> selected value with some javascript at the client side, because it is
> not set. Is there a way to get the selected value rightly set?


Yes - you can directly set values on forms by passing a proper dictionary.
Unfortunately I have to lookup that myself each time - but searching this
group will enlighten you even further.

Diez

Cecil Westerhof

unread,
May 13, 2008, 7:20:57 AM5/13/08
to turbo...@googlegroups.com
2008/5/13 Diez B. Roggisch <de...@web.de>:

I found the solution. It was something completly different as where I
was searching. And now I know the solution, it is very logical. ;-}

In my model I have:
owner = ForeignKey("Owner")

In my WidgetsList class I had:
owner = SingleSelectField(label = ownerLabel,
options = getOwners)
but that should be:
ownerID = SingleSelectField(label = ownerLabel,
options = getOwners)

I'll change the other definitions also and then I can get rid of the
ugly javascript I used for setting the good select.

Maybe good to document this somewhere? To prevent other people
stepping into this trap.

--
Cecil Westerhof

Diez B. Roggisch

unread,
May 13, 2008, 10:00:29 AM5/13/08
to turbo...@googlegroups.com

I'm not sure, but to me that looks buttugly. If you want that, instead create
a ModelValidator attached to SingleSelectField that will lookup a certain
owner-instance. I did that for myself, and maybe I'll get around to implement
that for TG11. But that depends on somebody testing/implementing it with SA,
as I solely use SO.

Diez

Cecil Westerhof

unread,
May 14, 2008, 5:18:57 AM5/14/08
to turbo...@googlegroups.com
2008/5/13 Diez B. Roggisch <de...@web.de>:
> I'm not sure, but to me that looks buttugly. If you want that, instead create
> a ModelValidator attached to SingleSelectField that will lookup a certain
> owner-instance.

I think that what I am doing is correct, but maybe I was not clear
enough. So let me explain. Hopefully you agree that what I am doing is
okay, otherwise I have to improve my code. ;-}

I have the following class:
#####
ownerLabel = 'Eigenaar'
class DogFields(WidgetsList):
def getOwners():
return getOptions(ownerLabel, Owner)

name = TextField(label = 'Naam *',
validator = validators.NotEmpty)
description = TextField(label = 'Omschrijving *',
validator = validators.NotEmpty)
sex = RadioButtonList(label = "Geslacht",
validator = validators.NotEmpty,
options =
Dog.sqlmeta.columns['sex'].enumValues,
default =
Dog.sqlmeta.columns['sex'].default)
isNeutered = CheckBox(label = "Is geholpen",
default = False)
mayOnMeadow = CheckBox(label = "Mag op de wei",
default = True)
needSupervisor = CheckBox(label = "Op wei onder toezicht",
default = False)
mayHike = CheckBox(label = "Mag mee worden gewandeld",
default = False)
morningFood = TextField(label = 'Ochtendvoer')
afternoonFood = TextField(label = 'Middagvoer')
eveningFood = TextField(label = 'Avondvoer')
notes = TextArea(label = 'Opmerkingen')

ownerID = SingleSelectField(label = ownerLabel,
options = getOwners)

#####

I also have the following functions:
#####
def cmpOptions(a, b):
temp1 = a[1].lower()
temp2 = b[1].lower()
if temp1 < temp2:
return -1
if temp1 > temp2:
return 1
return 0

def getOptions(label, table):
options = [(entry.id, str(entry)) for entry in table.select()]
options.sort(cmpOptions)
return [(-1, 'Selecteer %s' % (label))] + options
#####

And in the controller I have
#####
@expose(template='kid:tblog.templates.feedingList.form')
def editRecord(self, table, id, **kw):
form = getForm(table)
record = None
values = dict()
try:
if table == 'Dog':
record = Dog.get(int(id))
elif table == 'Owner':
record = Owner.get(int(id))
elif table == 'Reservation':
record = Reservation.get(int(id))
except:
log.error('editRecord: error getting record (%s, %s)' % (table, id))
flash = "Not valid edit"
return dict(form = form,
page = 'edit',
record = record,
table = table,)
#####

What is wrong with that? I use getOwners because I want the selectbox
be populated with all the owners, so the right owner can be assigned
to the dog.

By the way, in the save function I have:
#####
if kw['ownerID'] == -1:
kw['ownerID'] = None
#####

in case there is no owner selected. (This allowed.)

--
Cecil Westerhof

Diez B. Roggisch

unread,
May 14, 2008, 1:25:20 PM5/14/08
to turbo...@googlegroups.com
Cecil Westerhof schrieb:

Using a global or class-dictionary here as central registry could save
you a lot of typing - as shown in the last answer. And at *least* use
Int-validators for integers.


> except:
> log.error('editRecord: error getting record (%s, %s)' % (table, id))
> flash = "Not valid edit"
> return dict(form = form,
> page = 'edit',
> record = record,
> table = table,)
> #####
>
> What is wrong with that? I use getOwners because I want the selectbox
> be populated with all the owners, so the right owner can be assigned
> to the dog.
>
> By the way, in the save function I have:
> #####
> if kw['ownerID'] == -1:
> kw['ownerID'] = None
> #####
>
> in case there is no owner selected. (This allowed.)

If it works, it's not "wrong" - but ugly :) Using a self-written
Validator that converts an id to the instance of e.g. Owner, and the
other way round would allow you to

- not append "ID" to columns
- deal with objects instead of ids

I can't find the validator I wrote for that myself right now - but it's
easy: just subclass TgFancyValidator, like this (untested):


class ModelValidator(TgFancyValidator):

messages = {
'badFormat': _('Invalid number format'),
'empty': _('Empty values not allowed'),
}

def __init__(self, model_class, allow_empty=None, *args, **kw):
if allow_empty is not None:
warnings.warn("Use not_empty instead of allow_empty",
DeprecationWarning, 2)
not_empty = not allow_empty
kw["not_empty"] = not_empty
super(Money, self).__init__(*args, **kw)
self._mc = model_class

def _to_python(self, value, state):
try:
return self._mc.get(int(value))
except:
# return e.g. None for anything that doesn't work out
return None

def _from_python(self, value, state):
""" returns a string using the correct grouping """
if value is not None:
return value.id
else:
return -1


Then use that like this:

owner = SingleSelectField(options=getOwners,
validator=ModelValidator(Owener)


Diez

Reply all
Reply to author
Forward
0 new messages