How to make Flask-WTFoms update labels dynamically

2019-02-27 01:15发布

I use WTForms to define form for data filtering it is defined like this (My goal is to have user specified labels for BooleanFields set, I let each user to name labels for fields and I save name of fields to Google Datastore):

class MainFilterForm(FlaskForm):
    """
    Represents main filter form.
    """

    start_date = pendulum.parse(
        str(pendulum.today().year)
        + str(pendulum.today().month)
        + '01')
    end_date = pendulum.today()
    calendar_colors_descriptions = CalendarColorsDescription(
        users.get_current_user().user_id()
        ).get_colors_description()
    search_query = StringField(
        'Search',
        [
            validators.Length(min=1, max=128),
            validators.optional()],
        default=None)
    start_date = DateField(
        'Start date',
        [validators.required()],
        format='%Y-%m-%d',
        default=start_date)
    end_date = DateField(
        'End date',
        [validators.required()],
        format='%Y-%m-%d',
        default=end_date)
    i_am_owner = BooleanField(
        'I am owner',
        default=False)
    include_all_day_events = BooleanField(
        'Include all day events',
        default=False)
    selected_colors_calendar_color = BooleanField(
        calendar_colors_descriptions[0],
        default=True)
    selected_colors_color1 = BooleanField(
        calendar_colors_descriptions[1],
        default=True)
    selected_colors_color2 = BooleanField(
        calendar_colors_descriptions[2],
        default=True)
    selected_colors_color3 = BooleanField(
        calendar_colors_descriptions[3],
        default=True)
    selected_colors_color4 = BooleanField(
        calendar_colors_descriptions[4],
        default=True)
    selected_colors_color5 = BooleanField(
        calendar_colors_descriptions[5],
        default=True)
    selected_colors_color6 = BooleanField(
        calendar_colors_descriptions[6],
        default=True)
    selected_colors_color7 = BooleanField(
        calendar_colors_descriptions[7],
        default=True)
    selected_colors_color8 = BooleanField(
        calendar_colors_descriptions[8],
        default=True)
    selected_colors_color9 = BooleanField(
        calendar_colors_descriptions[9],
        default=True)
    selected_colors_color10 = BooleanField(
        calendar_colors_descriptions[10],
        default=True)
    selected_colors_color11 = BooleanField(
        calendar_colors_descriptions[11],
        default=True)

CalendarColorsDescription class returns list of strings which represents desired labels for Boolean fields (these values are stored in Google Datastore).

This form is displayed on dashboard home page rendered by Jinja2 and Flask (only relevant part of Flask class is pasted here):

@APP.route('/dashboard', methods=('GET', 'POST'))
def dashboard():
    """
    Main page handler, shows stats dashboard.
    """

    form = MainFilterForm()
    calendar_events = get_events(
        calendar_service,
        form.search_query.data,
        form.start_date.data,
        form.end_date.data,
        form.i_am_owner.data,
        form.include_all_day_events.data,
        form.selected_colors_calendar_color.data,
        form.selected_colors_color1.data,
        form.selected_colors_color2.data,
        form.selected_colors_color3.data,
        form.selected_colors_color4.data,
        form.selected_colors_color5.data,
        form.selected_colors_color6.data,
        form.selected_colors_color7.data,
        form.selected_colors_color8.data,
        form.selected_colors_color9.data,
        form.selected_colors_color10.data,
        form.selected_colors_color11.data)
    return flask.render_template(
        'dashboard.html',
        calendar_events=calendar_events,
        form=form)

On first run all labels are properly set and displayed. But when I change values in Datastore (via another form), values in form labels are never updated they stays the same, unless I restart webserver.

I tried to put "debug" print to different parts of program and output the class which reads data from Datastore, and output is always valid and in sync with expected values. It seems to me (and for me it is total magic), that

form = MainFilterForm()

is executed only once at first HTTP request (as i tried to put "debug" print to MainFilterForm definition as well, but this print was shown only at first HTTP request).

I tried to set labels manually with:

form.selected_colors_calendar_color.label = calendar_colors_descriptions[0]

after line:

form = MainFilterForm()

But I got error "TypeError: 'str' object is not callable" from, i believe, Jinja2.

1条回答
在下西门庆
2楼-- · 2019-02-27 01:51

The approach that you have taken, calendar_colors_descriptions is assigned in the body of your form class.

This means that it is only evaluated once - when the forms module is first imported - and so the field label values are fixed until the server is restarted. In effect, the label values are part of the class definition, and so common across all instances of the class.

This example code is similar to yours;

import random
import wtforms


def get_labels(labels=None):
        if labels is None:
            labels = ['red', 'amber', 'green']
        # Simulate data changes by shuffling the list.
        random.shuffle(labels)
        return labels


class StaticLabelForm(wtforms.Form):

    # labels is set when the class is compiled at import time.
    labels = get_labels()

    foo = wtforms.BooleanField(labels[0], default=True)
    bar = wtforms.BooleanField(labels[1], default=True)
    baz = wtforms.BooleanField(labels[2], default=True)

Each time we instantiate a new StaticLabelForm, the labels are always the same, because the get_labels function is only called once.

>>> static1 = StaticLabelForm()
>>> for field in static1: print(field.label, field)
... 
<label for="foo">amber</label> <input checked id="foo" name="foo" type="checkbox" value="y">
<label for="bar">green</label> <input checked id="bar" name="bar" type="checkbox" value="y">
<label for="baz">red</label> <input checked id="baz" name="baz" type="checkbox" value="y">

>>> static2 = StaticLabelForm()
>>> for field in static2: print(field.label, field)
... 
<label for="foo">amber</label> <input checked id="foo" name="foo" type="checkbox" value="y">
<label for="bar">green</label> <input checked id="bar" name="bar" type="checkbox" value="y">
<label for="baz">red</label> <input checked id="baz" name="baz" type="checkbox" value="y">

We can fix this by passing the label values to the form's __init__ method, and setting them on the fields inside the __init__ method.

class DynamicLabelForm(wtforms.Form):

    # Don't set the labels here
    foo = wtforms.BooleanField(default=True)
    bar = wtforms.BooleanField(default=True)
    baz = wtforms.BooleanField(default=True)

    def __init__(self, labels=None, **kwargs):
        super().__init__(**kwargs)
        # super(DynamicLabelForm, self).__init__(**kwargs) for python2!
        if labels is None:
            labels = ['red', 'amber', 'green']
        self['foo'].label = wtforms.Label(self['foo'].id, labels[0])
        self['bar'].label = wtforms.Label(self['bar'].id, labels[1])
        self['baz'].label = wtforms.Label(self['baz'].id, labels[2])

Now the labels are reset on each new form:

>>> dynamic1 = DynamicLabelForm(labels=get_labels())
>>> for field in dynamic1: print(field.label, field)
... 
<label for="foo">amber</label> <input checked id="foo" name="foo" type="checkbox" value="y">
<label for="bar">red</label> <input checked id="bar" name="bar" type="checkbox" value="y">
<label for="baz">green</label> <input checked id="baz" name="baz" type="checkbox" value="y">

>>> dynamic2 = DynamicLabelForm(labels=get_labels())
>>> for field in dynamic2: print(field.label, field)
... 
<label for="foo">amber</label> <input checked id="foo" name="foo" type="checkbox" value="y">
<label for="bar">green</label> <input checked id="bar" name="bar" type="checkbox" value="y">
<label for="baz">red</label> <input checked id="baz" name="baz" type="checkbox" value="y">
查看更多
登录 后发表回答