I had a great idea:
@validate(model_form)
@error_handler()
@expose(template='kid:myproject.templates.new')
def new(self, id, tg_errors=None, **kw):
"""Create new records in model"""
if tg_errors:
# Ask until there is still something missing
return dict(record = defaults, form = model_form)
else:
# We have everything: save it
i = Item(**kw)
flash("Item was successfully created.")
raise redirect("../show/%d" % i.id)
It was perfect: one simple method, simple error handling, nice helpful messages all around. Except, check boxes and select fields would not get the default values while all other fields would.
After two hours searching and cursing and tracing things into widget code, I
found this bit in InputWidget.adjust_value
:
# there are some input fields that when nothing is checked/selected
# instead of sending a nice name="" are totally missing from
# input_values, this little workaround let's us manage them nicely
# without interfering with other types of fields, we need this to
# keep track of their empty status otherwise if the form is going to be
# redisplayed for some errors they end up to use their defaults values
# instead of being empty since FE doesn't validate a failing Schema.
# posterity note: this is also why we need if_missing=None in
# validators.Schema, see ticket #696.
So, what is happening here is that since check boxes and option fields don't have a nice behaviour when unselected, turbogears has to work around it. So in order to detect the difference between "I selected 'None'" and "I didn't select anything", it reasons that if the input has been validated, then the user has made some selections, so it defaults to "The user selected 'None'". If the input has not been validated, then we're showing the form for the first time, then a missing value means "Use the default provided".
Since I was doing the validation all the time, this meant that Checkboxes and Select fields would never use the default values.
Hence, if you use those fields then you necessarily need two different controller methods, one to present the form and one to save it:
@expose(template='kid:myproject.templates.new')
def new(self, id, **kw):
"""Create new records in model"""
return dict(record = defaults(), form = model_form)
@validate(model_form)
@error_handler(new)
@expose()
def savenew(self, id, **kw):
"""Create new records in model"""
i = Item(**kw)
flash("Item was successfully created.")
raise redirect("../show/%d"%i.id)
If someone else stumbles on the same problem, I hope they'll find this post and they won't have to spend another two awful hours tracking it down again.