如何用WTForms渲染我的选择字段?

时间:2011-12-11 10:58:27

标签: python forms web-applications jinja2 wtforms

我有一个选择字段,其中某些元素已淡化和禁用,我想用WTForms渲染:

<select name="cg" id="cat" class="search_category">
<option value='' >{% trans %}All{% endtrans %}</option>  
<option value='' style='background-color:#dcdcc3' id='cat1'  disabled="disabled">-- {% trans %}VEHICLES{% endtrans %} --</option>
<option value='2'  {% if "2" == cg %} selected="selected" {% endif %} id='cat2' >{% trans %}Cars{% endtrans %}</option>
<option value='3' {% if "3" == cg %} selected="selected" {% endif %}   id='cat3' >{% trans %}Motorcycles{% endtrans %}</option>
<option value='4' {% if "4" == cg %} selected="selected" {% endif %}   id='cat4' >{% trans %}Accessories &amp; Parts{% endtrans %}</option>
...

我有一个工作的表单类,我开始实现本地化的类别变量,但我不知道如何制作使得淡化(background-color:#dcdcc3)和禁用属性的窗口小部件(?)选项元素:

class AdForm(Form):
    my_choices = [('1', _('VEHICLES')), ('2', _('Cars')), ('3', _('Bicycles'))]
    name = TextField(_('Name'), [validators.Required(message=_('Name is required'))], widget=MyTextInput())
    title = TextField(_('title'), [validators.Required(message=_('Subject is required'))], widget=MyTextInput())
    text = TextAreaField(_('Text'),[validators.Required(message=_('Text is required'))], widget=MyTextArea())
    phonenumber = TextField(_('Phone number'))
    phoneview = BooleanField(_('Display phone number on site'))
    price = TextField(_('Price'),[validators.Regexp('\d', message=_('This is not an integer number, please see the example and try again')),validators.Optional()] )
    password = PasswordField(_('Password'),[validators.Optional()], widget=PasswordInput())
    email = TextField(_('Email'), [validators.Required(message=_('Email is required')), validators.Email(message=_('Your email is invalid'))], widget=MyTextInput())
    category = SelectField(choices = my_choices, default = '1')

    def validate_name(form, field):
        if len(field.data) > 50:
            raise ValidationError(_('Name must be less than 50 characters'))

    def validate_email(form, field):
        if len(field.data) > 60:
            raise ValidationError(_('Email must be less than 60 characters'))

    def validate_price(form, field):
        if len(field.data) > 8:
            raise ValidationError(_('Price must be less than 9 integers'))

我可以使用上面的变量类别来渲染类别的选择。我还想启用特殊渲染,即禁用元素和褪色背景。你能告诉我应该怎么做吗?

谢谢

更新

当从答案中尝试添加禁用属性的解决方案时,我收到以下错误消息:

Trace:

Traceback (most recent call last):
  File "/media/Lexar/montao/lib/webapp2/webapp2.py", line 545, in dispatch
    return method(*args, **kwargs)
  File "/media/Lexar/montao/montaoproject/i18n.py", line 438, in get
    current_user=self.current_user,
  File "/media/Lexar/montao/montaoproject/main.py", line 469, in render_jinja
    self.response.out.write(template.render(data))
  File "/media/Lexar/montao/montaoproject/jinja2/environment.py", line 894, in render
    return self.environment.handle_exception(exc_info, True)
  File "/media/Lexar/montao/montaoproject/templates/insert_jinja.html", line 221, in top-level template code
    {{ form.category|safe }}
ValueError: need more than 2 values to unpack

我试过的代码是:

from wtforms.widgets import html_params
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))))


class SelectFieldWithDisable(SelectField):
    widget = SelectWithDisable()

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


class AdForm(Form):
    my_choices = [('1', _('VEHICLES')), ('2', _('Cars')), ('3', _('Motorcycles'))]
    nouser = HiddenField(_('No user'))
    name = TextField(_('Name'), [validators.Required(message=_('Name is required'))], widget=MyTextInput())
    title = TextField(_('Subject'), [validators.Required(message=_('Subject is required'))], widget=MyTextInput())
    text = TextAreaField(_('Text'),[validators.Required(message=_('Text is required'))], widget=MyTextArea())
    phonenumber = TextField(_('Phone number'))
    phoneview = BooleanField(_('Display phone number on site'))
    price = TextField(_('Price'),[validators.Regexp('\d', message=_('This is not an integer number, please see the example and try again')),validators.Optional()] )
    password = PasswordField(_('Password'),validators=[RequiredIf('nouser', message=_('Password is required'))], widget=MyPasswordInput())
    email = TextField(_('Email'), [validators.Required(message=_('Email is required')), validators.Email(message=_('Your email is invalid'))], widget=MyTextInput())
    category = SelectFieldWithDisable(choices = my_choices)

    def validate_name(form, field):
        if len(field.data) > 50:
            raise ValidationError(_('Name must be less than 50 characters'))

    def validate_email(form, field):
        if len(field.data) > 60:
            raise ValidationError(_('Email must be less than 60 characters'))

    def validate_price(form, field):
        if len(field.data) > 8:
            raise ValidationError(_('Price must be less than 9 integers'))

我想我必须在某处设置'禁用'属性,但在哪里?

更新2

这比我想象的要复杂。还有一个solution suggested on the wtforms mailing list,但我也无法解决这个问题(关于无效语法的一些微不足道的错误,而且无法从wtforms导入ecscape,所以我采取的操作是从hg存储库更新我的wtforms,如果有的话重要的是在那里改变了。

从这里的答案我得到Need more than 2 values to unpackValueError: too many values to unpack所以我似乎无法做到正确。在我的模板中,我正在尝试渲染的是

{{ form.category }} 

我的表单类是

class AdForm(Form):
    my_choices = [('1', _('VEHICLES'), False, True), ('2', _('Cars'), False, False), ('3', _('Motorcycles'), False, False)]

    ...
    category = SelectFieldWithDisable(choices = my_choices)

添加了我从这里获得的课程:

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))))


class SelectFieldWithDisable(SelectField):
    widget = SelectWithDisable()

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

2 个答案:

答案 0 :(得分:4)

编辑:

如果要始终在禁用某些选项的情况下渲染字段,则必须创建自己的自定义窗口小部件和字段以提供给渲染器。

当前渲染器在其选择元组中只有三个选项:(value, name, selected)

您需要修改它以接受第四个可选元素:禁用。

基于wtforms.widget中的Select类:

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))))

然后根据wtforms.fields中的代码,继承已存在的SelectField

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)

注意:这不是测试,甚至没有运行PYTHON代码,但是非常快速地从WTFORMS中提出问题和基本代码。但它应该给你足够的先发制人和前一个完全控制场的答案。

使用CSS和JavaScript控制页面上的渲染元素。

在你使用的任何模板渲染系统中(我使用flask,jinja和wtforms)你渲染你的元素并在渲染时提供id或class属性。 (我只是打印form.select_field_variable_name

然后生成一个CSS文件来控制样式并使用JavaScript来控制某些元素的自定义禁用等。

编辑:

如果你有:

<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>

您可以使用以下颜色应用背景颜色:

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

您启用/禁用:

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

Etc等等。

使用WTForms小部件(如所有HTML元素)渲染元素后,您应该使用CSS和JavaScript设置和控制元素的任何动态部分

答案 1 :(得分:1)

很久以后,我已经进入并想出了如何使@tkone的答案工作中的wtform部分。我将添加一个答案,因为它不适合评论。另外,我尝试使用SelectMultipleField执行此操作,因此我的字段类继承而不是SelectField

首先是小部件类:

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))))

这里唯一的导入更改是我在forms.py的顶部有from wtforms import widgets所以我使用widgets.HTMLString等来引用小部件。我还在这里添加了一个大小参数,或许最好在其他地方实现,只需将元素的大小设置为元素的数量或15,以较低者为准。如果我以其他方式开始使用这个小部件的话,我已经把它放在if self.multiple里面以提醒自己重新检查大小的东西。

现在是字段类:

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

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

这是所有重要变化的地方。首先如前所述,该字段继承自SelectMultipleField类,因此我将widget = True参数添加到窗口小部件声明中。最后,我从iter_choices方法(self.coerce(value) == self.data)中删除了最后一个元素。我不确定应该做什么,但在我的情况下,它总是将整数与列表进行比较并返回False,并导致

  

ValueError:要解压缩的值太多

  

需要超过x值才能解压缩

错误OP正在看到。如果它返回有价值的东西,只需将该额外变量添加到widget类的调用方法中的for语句。

然后,当我定义选项时,我只需要将每个项目的选择元组设置为(value, label, selected, disabled),其中选择和禁用是布尔值,指示是否应分别选择和禁用项目。

我希望能帮助某些人像我在某个时候一样迷失方向。