问题
I need to generate my fields in the constructor of the form since the number of fields needed can vary. I think my current solution is the problem. I get an exception when I try to expand the form in my template saying
AttributeError: 'UnboundField' object has no attribute 'call'
What is wrong with this code?
class DriverTemplateSchedueForm(Form):
def __init__(self, per_day=30, **kwargs):
self.per_day = per_day
ages = model.Agency.query.all()
ages = [(a.id, a.name) for a in ages]
self.days = [[[]] * per_day] * 7
for d in range(7):
for i in range(per_day):
lbl = 'item_' + str(d) + '_' + str(i)
self.__dict__[lbl] = SelectField(lbl, choices=ages)
self.days[d][i] = self.__dict__[lbl]
for day in self.days:
print(day)
Form.__init__(self, **kwargs)
回答1:
The fix
You need to add the fields to your class not your instance:
def driver_template_schedue_form(ages, per_day=30, **kwargs):
"""Dynamically creates a driver's schedule form"""
# First we create the base form
# Note that we are not adding any fields to it yet
class DriverTemplateScheduleForm(Form):
pass
# Then we iterate over our ranges
# and create a select field for each
# item_{d}_{i} in the set, setting each field
# *on our **class**.
for d in range(7):
for i in range(per_day):
label = 'item_{:d}_{:d}'.format(d, i)
field = SelectField(label, choices=ages)
setattr(DriverTemplateScheduleForm, label, field)
# Finally, we return the *instance* of the class
# We could also use a dictionary comprehension and then use
# `type` instead, if that seemed clearer. That is:
# type('DriverTemplateScheduleForm', Form, our_fields)(**kwargs)
return DriverTemplateScheduleForm(**kwargs)
Why doesn't adding fields to self
work?
WTForms uses meta-classes to register forms and fields together and preserve order. *Field
instances are created unbound, added to the Form class' _unbound_fields attribute and are bound to the class instance when the class is being constructed by the meta-class.
By the time DriverTemplateScheduleForm.__init__
is run, _unbound_fields
has already been populated. You can push your fields into self._unbound_fields
and things would also just work, but that's making use of a private API and so may break later.
回答2:
The answer about meta-class is right, but in case if you really need this (like I do):
class SomeForm(Form):
def __init__(self, *args, **kwargs):
for name in kwargs.keys():
if name.startswith('PREFIX_'):
field = HiddenField()
setattr(self, name, field)
self._unbound_fields = self._unbound_fields + [[name, field]]
super(SomeForm, self).__init__(*args, **kwargs)
Note that we're not modifying _unbound_fields
, not to have this fields in form class next time.
来源:https://stackoverflow.com/questions/31160781/wtforms-generate-fields-in-constructor