WTForm: FieldList with SelectField, how do I render?

disappearedng picture disappearedng · Jun 19, 2014 · Viewed 10.1k times · Source

I have this order form which allows my users to create an order. An order consists of multiple tuples of (producetype, quantity). Producetype should be rendered in a <select> form while quantity can just be an input. The choices of producetype should be dynamically added because that could change. Currently, I've written this in bare html

enter image description here

I would like to use WTForm for this because WTForm really simplifies my code. However, I am unable to do so:

Code:

class OrderEntryForm(Form):
  quantity = IntegerField('Quantity',
                          [validators.Required(), validators.NumberRange(min=1)])
  # we will be dynamically adding choices
  producetype = SelectField('Produce',
                            [validators.Required()],
                            choices=[])

class OrderForm(Form):
  name = TextField('Crop', [validators.Length(min=3, max=60)])
  due_date = DateField('Due Date', [validators.required()])
  order_entries = FieldList(FormField(OrderEntryForm))

I have the following questions:

  1. How can I dynamically add choices to the order_entries field of the OrderForm?
  2. If I have an order, order = { name:hello, due_date:2014-06-18, order_entries:[ {producetype_id: 1, quantity: 2}, {producetype_id: 3, quantity: 4}] }, how can populate my OrderForm with the right OrderEntryForm values?

My code is available here: https://gist.github.com/vicngtor/f8c8f0519dbd6b3b6110

Answer

nsfyn55 picture nsfyn55 · Jun 19, 2014

How can I dynamically add choices to the order_entries field of the OrderForm

This depends on what you mean

If you mean you want to add lines items to the form after render. You have to use DOM manipulation to add these on the client side. WTForms has a naming convention for addressing indices on form fields. Its just name="<form-field)name>-<index>". If you add a name using javascript that follows this convention WTForms will know how to handle it on the backend.

If you mean you want to have a dynamic choice list then you can just iterate over the FieldList in the form after its instantiated. You can assign choices any iterable of 2-tuples. In the example below I am assigning them directly but they could just as easily have been retrieved from storage.

order_form = OrderForm()
for sub_form in order_form.order_entries:
    sub_form.producetype.choices = [('2', 'apples'), ('2', 'oranges')]

how can populate my OrderForm with the right OrderEntryForm values?

You can just bind objects directly to the form using the obj keyword argument. WTForms is smart enough to dynamically build the form from the object's attributes.

from wtforms import Form, IntegerField, SelectField, TextField, FieldList, FormField
from wtforms import validators
from collections import namedtuple

OrderEntry = namedtuple('OrderEntry', ['quantity', 'producetype'])
Order = namedtuple('Order', ['name', 'order_entries'])

class OrderEntryForm(Form):
  quantity = IntegerField('Quantity',
                          [validators.Required(), validators.NumberRange(min=1)])
  # we will be dynamically adding choices
  producetype = SelectField('Produce',
                            [validators.Required()],
                            choices=[
                                (1, 'carrots'),
                                (2, 'turnips'),
                            ])

class OrderForm(Form):
  name = TextField('Crop', [validators.Length(min=3, max=60)])
  order_entries = FieldList(FormField(OrderEntryForm))

# Test Print of just the OrderEntryForm
o_form = OrderEntryForm()
print o_form.producetype()

# Create a test order
order_entry_1 = OrderEntry(4, 1)
order_entry_2 = OrderEntry(2, 2)

order = Order('My First Order', [order_entry_1, order_entry_2])

order_form = OrderForm(obj=order)

print order_form.name
print order_form.order_entries

The above example creates a sample Order and supplies it to the obj keyword. On render this will generate the following(unstyled):

enter image description here