Best way to add fields on the fly to TW Forms?

9 views
Skip to first unread message

John Lorance

unread,
Aug 13, 2009, 10:03:40 PM8/13/09
to TurboGears
I'm converting a TG1 app with TG1 style widgets and am finding that a
code pattern implemented to add widgets on the fly in TG1 doesn't work
in TG2/TW. Basically, in an existing form's update_params method,
new fields were being added to a fieldset based upon parameters of the
request/data in model. This strategy doesn't seem to work in TW. So,
my questions is what is the best way to create a form that adds
widgets dynamically?

So far it seems that I need to instantiate a new form widget object
for each request and pass it a fields parameter that is the set of
fields needed for the given request. For example,

myfields = [TextField("a"), TextField("b")]... etc
myform = MyForm("formname", fields=myfields)
myform.display(**params) # pass non-field items in display.

Is this the best approach, or is there a better way that can more
mimic what was possible with TG1 widgets?

Diez B. Roggisch

unread,
Aug 14, 2009, 4:04:04 AM8/14/09
to turbo...@googlegroups.com
John Lorance schrieb:

If this worked with TG1 it was pure luck that you didn't run into
concurrency issues. TW tries to lock-down widgets after instantiation
time. This obviously has it's limits, but in this case it caught the
"abuse".

By modifying widgets like that at runtime, you modified a single
instance which was used to render concurrent requests. If the load is
not to heavy, that works. If it ramps up, errors will creep in when two
requests modify the widget before it is rendered.

So, the answer is: it isn't possible, and it wasn't really a good idea
in TG1 as well :)

Now the question is: what kind of dynamic alterations did you actually
need? Can you please give an example of that, then we can think about a
conformant solution.

Diez

John L.

unread,
Aug 14, 2009, 5:27:49 PM8/14/09
to TurboGears
Diez,
Thanks so much for the answer, I don't feel so crazy now :) "Its not
possible" and the original TG1 code is abusing the underlying classes/
library is what I thought. So what I am trying to do is this in a
couple of different areas:

1. Present a user profile form that has a variable number of fields.
Data is "progressively" collected about a user elsewhere so sometimes
there is a reason not to show a user a form field for data not yet
collected.

2. Another use of this is that I have what is the equivalent to a
custom search form. However, the controls/fields on this form will
vary depending on where they are in the site. So, as an example where
this specifically happens is in generating a series of checkbox lists
grouped by "categories" where each category is its own checkboxlist
widget with its own options.

So, I figure the solution here would be just to create a new form
widget per request that initializes the fields it needs. Is there
another route? It just seems like creating variable field forms
shouldn't be so difficult; unless I'm not understanding something here
and just need to arrive at the, doh! moment! with TW.

Here's a simplified pseudo code of what I am thinking...

-- In root.py
def subject(self, subject_id, *args, **kwargs):
fields = someMethodToDetermineFormFields(subject_id)
# ^^^ returns a list of field widgets based upon data related to the
subject_id

child_args = someMethodToDetermineChildArgs(subject_id)
# ^^^ Returns a nested dict to be passed to the form for childargs.

myform = SubjectForm("myform", fields=fields,
child_args=child_args)
# ^^^ Now based upon the subject data, different child fields will
need to be rendered.

return dict(myform...and other items to the template)

See any gotchas with this approach?

Diez B. Roggisch

unread,
Aug 16, 2009, 6:07:13 PM8/16/09
to turbo...@googlegroups.com
> 2. Another use of this is that I have what is the equivalent to a
> custom search form. However, the controls/fields on this form will
> vary depending on where they are in the site. So, as an example where
> this specifically happens is in generating a series of checkbox lists
> grouped by "categories" where each category is its own checkboxlist
> widget with its own options.


The below works for me - albeit I admit it is not really obvious.

And I haven't tested this with validation:

--------------------
import tw.api as tw
import tw.forms as twf

# takes a fields-parameter with one widget as element.

class ToggleWidget(twf.ContainerMixin, twf.FormField):

params = dict(enabled="Is the child widget enabled")

enabled = True

engine_name = "genshi"

template = """<div py:strip="True"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/" py:if="enabled"
>${display_child(fields[0])}</div>"""


form = twf.ListForm("form", fields=[
ToggleWidget("names", fields=
[twf.CheckBoxList("name", options=["Peter", "Paul",
"Mary"])]
),
ToggleWidget("pets", fields=
[twf.CheckBoxList("name", options=["Lassie", "Flipper"])]
),
])


print form.display(child_args=dict(names=dict(enabled=True,

child_args=dict(name=dict(options=["Foo", "Bar"]))),
pets=dict(enabled=False)
)
)
---------------------


A similar approach might work with your use-case #1


> So, I figure the solution here would be just to create a new form
> widget per request that initializes the fields it needs. Is there
> another route? It just seems like creating variable field forms
> shouldn't be so difficult; unless I'm not understanding something here
> and just need to arrive at the, doh! moment! with TW.


It will cause a memory-leak. This is because to fix a bug I had to
create a global registry that will hold a reference to every widget that
is created.

Instead, I would recommend that you create a widget that simply renders
the whole form. And that is parametrized by the values you need.


I will try and dig deeper into tw.forms to see if there isn't a better
option, but currently the best I can offer is the above widget.

Diez


Reply all
Reply to author
Forward
0 new messages