69

When attempting to set the default value of a SelectField with WTForms, I pass in value to the 'default' parameter like so.

class TestForm(Form):
  test_field = SelectField("Test: ", choices=[(1, "Abc"), (2, "Def")], default=2)

I have also tried the following.

class TestForm(Form):
  test_field = SelectField("Test: ", choices=[(1, "Abc"), (2, "Def")], default=(2, "Def"))

Neither set the default selected field to "Def." This works for other kinds of Fields such as TextField. How do you set the default value for a SelectField?'

jan
  • 2,263
  • 5
  • 19
  • 22

9 Answers9

50

I believe this problem is caused by the Field's data attribute overriding the default with something that WTForms doesn't understand (e.g. a DB model object -- it expects an int). This would happen if you have populated your form in the constructor like so:

form = PostForm(obj=post)

the solution is to manually set the data attribute after the form has been populated:

form = PostForm(obj=post)
form.category.data = (post.category.id
                      if page.category
                      else 0) # I make 0 my default
Elliott
  • 2,479
  • 2
  • 21
  • 13
  • 9
    When populating the choices dynamically from the view at runtime, this is the correct way to make it have one choice selected by default. Since Google brings a lot of people to this question, this should probably be the accepted answer. – Aaron D Apr 30 '15 at 04:40
  • 3
    Well I noticed that when I change the default option with another one from the list in the view and submit the form the default option is submitted instead of the new one. How this problem can be fixed? – nikitz Feb 16 '17 at 19:43
  • 1
    This doesn't seem to work any longer. When I update the field, the original choices=choicesfunction() gets called anyway. So updating the field doesn't has an effect. ** UPDATE ** form.category.choices = [(),()] worked for me – Karl Jul 30 '19 at 20:53
45

Flask-WTF 0.14.2 user here. So this answer is for anyone who have similar problem with me.

Basically, none of the previous solutions function properly with form.validate_on_submit().

Setting form.test_field.data will indeed change the default value when the page is loaded, but the data stays the same after validate_on_submit (user changes in browser has no effect).

Setting form.test_field.default then call form.process() also changes the value when the page is loaded, but validate_on_submit will fail.

Here is the new way to do it:

class TestForm(Form):
    test_field = SelectField("Test", choices=[(0, "test0"), (1, "test1")])

@app.route("/test")
def view_function():
    form = TestForm(test_field=1)
    if form.validate_on_submit():
        ...
MBT
  • 21,733
  • 19
  • 84
  • 102
greedy52
  • 1,345
  • 9
  • 8
  • 1
    Yes. This is what works. To say it a little differently: To have a particular option selected in a selectlist, the option has to be loaded into the form when it is first instantiated. Likewise to have multiple options selected in a multipleselectlist the options have to be loaded into the form when it is first instantiated. The syntax for setting the selectlist varies depending on what type it is: 1) selectlist example: form = MyUserForm(roles=1) #the first option is preselected 2) selectmultiplelist example: form = MyUserForm(roles=[1,3]) #the first and third option are preselected – Luke Feb 22 '20 at 21:32
  • 1
    Works for me also! Flask-WTF == 0.14.3 – Dragos Vasile Jul 23 '20 at 12:43
41

The first way you posted is correct, and it works for me. The only explanation for it not working can be that you are running an older version of WTForms, it worked for me on 1.0.1

sp.
  • 1,336
  • 11
  • 7
20

There are a few ways to do this. Your first code snippet is indeed correct.

If you want to do this in a View dynamically though, you can also do:

form = TestForm()
form.test_field.default = some_default_id
form.process()
getup8
  • 6,949
  • 1
  • 27
  • 31
13

This is a choices settings with SelectField when you use an int, it works like this:

test_select = SelectField("Test", coerce=int, choices=[(0, "test0"), (1, "test1")], default=1)

or:

class TestForm(Form):
    test_select = SelectField("Test", coerce=int)

@app.route("/test")
def view_function():
    form = TestForm()
    form.test_select.choices = [(0, "test0"), (1, "test1")]
    form.test_select.default = 1
    form.process()
Liu Yue
  • 141
  • 1
  • 3
  • 1
    Have you tested the second variant? calling ` form.test_select.default = 1` does not change anything on my system. – Robert Sep 08 '16 at 12:55
  • The coerce=int when you're using integer IDs is an important setting. Was setting default on two forms and one worked, the other didn't. Turns out one ID was a string and the other was an integer. Coerce fixed the problem. – rgacote Sep 25 '18 at 19:04
  • The second works fine, but there is something you have to edit. The correct syntax is `form.test_select.process([])`. The process method requires an iterate argument. Version I tested is 2.3.1. – crbroflovski Jan 28 '21 at 19:10
3

In case you're using flask_wtf and would like to set a default value for different SelectFields nested in a FieldList like this

from flask_wtf import FlaskForm


class FormWithSelect(FlaskForm):
    my_select_field = SelectField(validators=[DataRequired()], coerce=int)


class FormWithMultipleSelects(FlaskForm):
    select_fields = FieldList(FormField(FormWithSelect), min_entries=1)
    submit = SubmitField(label="save all the select field values")

the solution is to overwrite data, not default as you would expect.

def generate_my_form(my_data, overwrite_data=True):
    form = FormWithMultipleSelects(select_fields=[{
        "my_select_field": {
             "name": d["name"],
             # ...
        }
    } for d in my_data]))
    for n range(len(form.select_fields)):
        form.select_fields[n].my_select_field.choices = [(1,'foo'), (2,'bar')]
        # I wan't 'bar' to be the option selected by default
        # form.select_fields[n].my_select_field.default = 2  # has no effect!
        if overwrite_data:
            form.select_fields[n].my_select_field.data = 2 #  works

Basically this solution is similar to Elliots answer above; I just wanted to provide a solution for the more complex case of a FieldList. nikitz mentioned in a comment, that there is a side effect: form.validate_on_submit() does not work if you're overwriting data! You can build a simple workaround where to disable the overwriting of data when calling validate_on_submit().

 form_for_validation = generate_my_form(my_data, False)
 if form_for_validation.validate_on_submit():
      # do something with form_for_validation.data
 else:
     form_with_default_data = generate_my_form(my_data, True)
     render_template(..., form=form_with_default_data)

It's not super elegant, but it works.

PS: My reputation is too low to directly comment on the correct answer by Elliot.

koks der drache
  • 1,398
  • 1
  • 16
  • 33
  • 1
    Two thirds of this post sound like a question, rather than an answer. Could you write the part after "The solution was..." at the beginning, and then provide the background afterwards if you think that it will be helpful? – Roland Weber Jan 29 '19 at 11:35
1
Python Flask SelectField choices 

    # method 1 - routes.py
     form.proj_cat_id.default    = table1.proj_cat_id
     form.process()  # for preset 

    # method 2 - routes.py
    form.proj_cat_id.process_formdata([table1.proj_cat_id])



# method 3  - routes.py
      proj_cat_id_default    = table1.proj_cat_id
      return render_template('abc.html',proj_cat_id_default=proj_cat_id_default)

# method 3 - abc.html
        {% set z = form.proj_cat_id.process_data(proj_cat_id_default) %}
        {{ form.proj_cat_id(class="form-control form-control-lg") }}
0

I have some problem with dynamic set default selectfield thati think can be useful. the model was like this:

class State(db.Model):
    __tablename__ = 'states'
    id = db.Column(db.Integer, primary_key=True)   
    name = db.Column(db.String(128), nullable=False)


class City(db.Model):
    __tablename__ = 'cities'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64))
    state_id = db.Column(db.Integer, db.ForeignKey('states.id'))   
    state = db.relationship(State, backref='state')


class User(UserMixin, db.Model):
    ...
    city_id = db.Column(db.Integer, db.ForeignKey('cities.id'))
    city = db.relationship('City', backref='city')

the form was like this:

class UserEditForm(FlaskForm):
    ...
    state = SelectField("state", coerce=int)
    city = SelectField("city", coerce=int)
    ....

  def __init__(self, state_id, *args, **kwargs):
       super(UserEditForm, self).__init__(*args, **kwargs)
       self.state.choices =  [(state.id, state.name)
                        for state in State.query.order_by('name').all()]
       self.city.choices = [(city.id, city.name)
                            for city in   City.query.filter_by(state_id=state_id).order_by('name').all()]   

and the view was like this:

@dashboard.route('/useredit', methods=['GET', 'POST'])
@login_required
def useredit():
    ....
    user = current_user._get_current_object()       
    form = OrganEditForm(state_id=user.city.state_id, state=user.city.state_id, city=user.city_id)
     ....

it works good and set Selectfield default value correctly. but i changed the code like this:

in view:

@dashboard.route('/useredit', methods=['GET', 'POST'])
    @login_required
    def useredit():
        ....
        user = current_user._get_current_object()       
        form = OrganEditForm(obj=user)
         ....

in forms

def __init__(self, *args, **kwargs):
       super(UserEditForm, self).__init__(*args, **kwargs)
       self.state.choices =  [(state.id, state.name)
                        for state in State.query.order_by('name').all()]
       self.city.choices = [(city.id, city.name)
                            for city in   City.query.filter_by(state_id=kwargs['obj'].city.state_id).order_by('name').all()] 

but this time the city default value doesn't set. i changed form like this:

def __init__(self, *args, **kwargs):
           kwargs['city'] = kwargs['obj'].city_id
           super(UserEditForm, self).__init__(*args, **kwargs)
           self.state.choices =  [(state.id, state.name)
                            for state in State.query.order_by('name').all()]
           self.city.choices = [(city.id, city.name)
                                for city in   City.query.filter_by(state_id=kwargs['obj'].city.state_id).order_by('name').all()] 

but it didn't work. I tried many solution and in the last I changed the city variable name to usercity:

usercity= SelectField("city", coerce=int)

and kwargs['city'] = kwargs['obj'].city_id to kwargs['usercity'] = kwargs['obj'].city_id. affter that it worked correctly.

the problem was that when I set obj=user, default value for city selectfield read from kwargs['obj'].city that I defined in user model.

Tavakoli
  • 1,303
  • 3
  • 18
  • 36
0

I had the same issue. In my case the Selectfields didn't update on my html. Stringfields and Datefields weren't affected.

{{ wtf.form_field(form.my_field, class="form-control", values=some_values_from_sql[8]) }}

My render_template is like:

render_template('my.html', form=form, some_values_from_sql=[x for x in sql_data[0]])

After changing the code to:

new_choice = [x for x in sql_data[0]]
form.my_field.default = new_choice[8]
form.my_field.process([])

And my html file:

{{ wtf.form_field(form.my_field, class="form-control") }}

Everything works fine now

Chris
  • 1
  • 1