How to render my select field with WTForms?

天大地大妈咪最大 提交于 2019-12-03 16:49:48

EDIT:

If you want to always render the field with certain options disabled you'll have to create your own custom widget and field to provide to the renderer.

The current renderer only takes three options in it's choice tuple: (value, name, selected).

You'll need to modify that to accept a fourth optional element: disabled.

Based on the Select class in wtforms.widget:

class SelectWithDisable(object):
    """
    Renders a select field.

    If `multiple` is True, then the `size` property should be specified on
    rendering to make the field useful.

    The field must provide an `iter_choices()` method which the widget will
    call on rendering; this method must yield tuples of 
    `(value, label, selected, disabled)`.
    """
    def __init__(self, multiple=False):
        self.multiple = multiple

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        if self.multiple:
            kwargs['multiple'] = 'multiple'
        html = [u'<select %s>' % html_params(name=field.name, **kwargs)]
        for val, label, selected, disabled in field.iter_choices():
            html.append(self.render_option(val, label, selected, disabled))
        html.append(u'</select>')
        return HTMLString(u''.join(html))

    @classmethod
    def render_option(cls, value, label, selected, disabled):
        options = {'value': value}
        if selected:
            options['selected'] = u'selected'
        if disabled:
            options['disabled'] = u'disabled'
        return HTMLString(u'<option %s>%s</option>' % (html_params(**options), escape(unicode(label))))

And then based on the code in wtforms.fields, subclass the SelectField that already exists

class SelectFieldWithDisable(SelectFiel):
    widget = widgets.SelectWithDisable()

    def iter_choices(self):
        for value, label, selected, disabled in self.choices:
            yield (value, label, selected, disabled, self.coerce(value) == self.data)

NOTE: THIS IS NOT TESTED NOR EVEN RUN PYTHON CODE BUT A VERY QUICK HACK GIVEN THE QUESTION AND THE UNDERLYING CODE FROM WTFORMS. But it should give you enough of a head start along with the previous answer to control the field entirely.

Use CSS and JavaScript to control the rendered element on the page.

In whatever template rendering system your using (I'm using flask, jinja and wtforms) you render your elemen and provide an id or class attribute when you render it. (I'm just printing form.select_field_variable_name)

Then generate a CSS file to control your styling and use JavaScript to control custom disabling of certain elements, etc.

EDIT:

If you've got:

<select id=selector>
    <option id=value1 value=1>Bananas</option>
    <option id=value2 value=2>Corn</option>
    <option id=value3 value=3>Lolcats</option>
</select>

You can apply a background color with:

<style>
#selector {background-color: #beef99}
</style>

And you enable/disable with:

<script>
option = document.getElementById('value3')
option.disabled = true
</script>

Etc, etc etc.

Once you've got your element rendered using WTForms widgets, like all HTML elements, you should style and control any dynamic parts of the element with CSS and JavaScript

Long after the fact, I've gone in and figured out how to make the wtform part of @tkone's answer work. I'll add an answer to this since it won't fit in a comment. Additionally, I was trying to do this with a SelectMultipleField so my field class is inheriting from that instead of SelectField

First the widget class:

class SelectWithDisable(object):
    """
    Renders a select field.

    If `multiple` is True, then the `size` property should be specified on
    rendering to make the field useful.

    The field must provide an `iter_choices()` method which the widget will
    call on rendering; this method must yield tuples of
    `(value, label, selected, disabled)`.
    """
    def __init__(self, multiple=False):
        self.multiple = multiple

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        if self.multiple:
            kwargs['multiple'] = 'multiple'
            kwargs['size'] = len(field.choices) if len(field.choices) < 15 else 15
        html = [u'<select %s>' % widgets.html_params(name=field.name, **kwargs)]
        for val, label, selected, disabled, coerced_value in field.iter_choices():
            html.append(self.render_option(val, label, selected, disabled))
        html.append(u'</select>')
        return widgets.HTMLString(u''.join(html))

    @classmethod
    def render_option(cls, value, label, selected, disabled):
        options = {'value': value}
        if selected:
            options['selected'] = u'selected'
        if disabled:
            options['disabled'] = u'disabled'
        return widgets.HTMLString(u'<option %s>%s</option>' % (widgets.html_params(**options), escape(unicode(label))))

The only change of import here is that I have from wtforms import widgets at the top of my forms.py so I refer to the widgets using widgets.HTMLString, etc. I also added a size argument in here, that perhaps would better be implemented somewhere else, that just sets the size of the element to the number of elements or 15, whichever is lower. I've stuck that inside the if self.multiple to remind myself to go reexamine the size thing if I start using this widget in other ways.

Now the field class:

class SelectMultipleFieldWithDisable(SelectMultipleField):
    widget = SelectWithDisable(multiple=True)

    def iter_choices(self):
        for value, label, selected, disabled in self.choices:
            yield (value, label, selected, disabled)

This is where all the important changes were made. First as mentioned earlier, the field is inheriting from the SelectMultipleField class, so I add the multiple=True argument to the widget declaration. Finally, I remove the last element from the iter_choices method (self.coerce(value) == self.data). I'm not really sure what that was supposed to do, but in my case it always compared an integer to a list and returned False, and resulted in the

ValueError: Too many values to unpack

and

Need more than x values to unpack

error OP was seeing. If it is returning something valuable, just add that extra variable to the for statement in the call method of the widget class.

Then when I'm defining the choices I just need to set the choices tuple for each item to be (value, label, selected, disabled) where selected and disabled are boolean values indicating whether the item should be selected and disabled respectively.

I hope that helps someone as lost as I was at some point down the line.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!