Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SelectField in wtforms and added <option> via javascript

I'm currently work on some project in pyramid and have problem with wtforms SelectField.

I have a 3 SelectField fields:

  • car_make (e.g., "audi")
  • car_model (e.g., "audi 80")
  • car_version (e.g., "AUDI 80 B4").

The car_make choices I can load in the view. The choices for rest of SelectFields (car_model, car_version) I will load on the client side via AJAX/javascript (I can choose car_model when car_make is selected and so on).

The problem is that when I submit the form, car_model and car_version raise 'Not valid choice' because (in SelectField.pre_validation line 431) self.choices is empty.

How can I get around this problem?

like image 786
qwetty Avatar asked Dec 31 '13 11:12

qwetty


1 Answers

What you are asking to do is have WTForms handle "cascading selects" (having the valid fields of one choice be determined by the value of another field). There really isn't a good way using the built in fields.

The SelectField in WTForms does NOT provide you with an option to say "Don't validate that the choice supplied is valid". You MUST provide choices in order for the field to validate the choice.

As shown in the docs, while you typically could fill the choices field with a static list of choices...

class PastebinEntry(Form):
    language = SelectField(u'Programming Language', choices=[('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')])

...however, since you are dynamically coming up with the options, you need to set the choices attribute after instantiating the form.

def edit_user(request, id):
    user = User.query.get(id)
    form = UserDetails(request.POST, obj=user)
    form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]

In the above sample, the choices for "group_id" is filled dynamically in what would be your Pyramid view. So, that's what you would need to do: you need to fill the choices in your view. This is how you can fix your issue with car_make (although I think in your question you said that car_make was okay).

The problem that you have, however, is that the valid choices for car_model cannot be determined, since they depend on car_make having already been parsed and validated. WTForms doesn't really handle this well (at least with SelectFields) because it assumes that all of the fields should be validated at once. In other words, in order to generate the list of valid choices for car_model, you first need to validate the value for car_make, which is not possible to easily do given how the SelectField works.

The best way I see doing this is to create a new field type that extends the SelectField type, but removes the validation:

class NonValidatingSelectField(SelectField):
    def pre_validate(self, form):
        pass

This new type overrides pre_validate, which typically does the check to determine if a choice is valid.

If you use this for car_model, you won't have the error anymore. However, this now means that your field isn't actually being validated! To fix this, you can add an in-line validator on your form...

class MyForm(Form):
    car_make = SelectField(u'Make', choices=[...])
    car_model = NonValidatingSelectField(u'Model', choices=[])

    def validate_car_model(self, field):
        choices = query_for_valid_models(self.car_make.data)
        # check that field.data is in choices...

You might need to tweak this a bit to get it to work exactly how you want, I haven't actually tested that this works.

like image 64
Mark Hildreth Avatar answered Nov 07 '22 21:11

Mark Hildreth