Как сделать мое поле выбора с помощью WTForms? - PullRequest
4 голосов
/ 11 декабря 2011

У меня есть поле выбора, в котором некоторые элементы исчезли и отключены, которые я хотел бы визуализировать с помощью 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) и отключенные атрибуты в элементе option :

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

Я могу использовать переменную категорию сверху, чтобы сделать выбор для категорий. Я также хочу включить специальный рендеринг, т.е. отключенные элементы и блеклый фон. Можете ли вы сказать мне, как я должен делать?

Спасибо

Обновление

При попытке решения из ответа добавить атрибут disabled, я получаю следующее сообщение об ошибке:

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

Полагаю, я должен где-нибудь установить атрибут 'disabled', но где?

Обновление 2

Это было сложнее, чем я думал. Было также предложено решение, предложенное в списке рассылки wtforms , но я также не смог заставить его работать (какая-то тривиальная ошибка, связанная с неверным синтаксисом и невозможностью импортировать ecscape из wtforms, поэтому выполняемое мною действие обновлялось мой wtforms из репозитория hg, если там что-то изменилось.

Из ответа здесь я получаю Need more than 2 values to unpack или ValueError: 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 ]

4 голосов
/ 11 декабря 2011

РЕДАКТИРОВАТЬ:

Если вы хотите всегда отображать поле с отключенными определенными параметрами, вам придется создать собственный настраиваемый виджет и поле для предоставления средству визуализации.

Текущий рендерер принимает только три опции в своем выборе: (value, name, selected).

Вам нужно будет изменить это, чтобы принять четвертый необязательный элемент: отключен.

На основе класса Select в 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))))

И затем, основываясь на коде в 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)

ПРИМЕЧАНИЕ: ЭТО НЕ ПРОВЕРЕНО НИКОГДА НЕ ЗАПУСКАЕТСЯ КОД ПИТОНА, НО ОЧЕНЬ БЫСТРЫЙ ХАК ПОЛУЧИЛ ВОПРОС И ПОДТВЕРЖДАЮЩИЙСЯ КОД ОТ WTFORMS. Но это должно дать вам достаточное преимущество и предыдущий ответ, чтобы полностью контролировать поле.

Используйте CSS и JavaScript для управления отображаемым элементом на странице.

В любой используемой вами системе рендеринга шаблонов (я использую флешку, jinja и wtforms) вы рендерите свой элемент и предоставляете идентификатор или атрибут класса при его рендеринге. (Я просто печатаю form.select_field_variable_name)

Затем сгенерируйте файл CSS для управления вашим стилем и используйте JavaScript для управления пользовательским отключением определенных элементов и т. Д.

EDIT:

Если у вас есть:

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

и т. Д. И т. Д.

После того как ваш элемент визуализирован с использованием виджетов WTForms, как и всех элементов HTML, вы должны стилизовать и контролировать любые динамические части элемента с помощью CSS и JavaScript

1 голос
/ 28 июня 2016

Вскоре после этого я вошел и выяснил, как сделать wtform частью ответа @ tkone.Я добавлю ответ на это, так как он не помещается в комментарии.Кроме того, я пытался сделать это с 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))))

Единственное изменениеимпорт заключается в том, что у меня есть from wtforms import widgets в верхней части моего forms.py, поэтому я обращаюсь к виджетам, используя 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, поэтому я добавляю аргумент multiple = True к объявлению виджета.Наконец, я удаляю последний элемент из метода iter_choices (self.coerce(value) == self.data).Я не совсем уверен, что это должно было сделать, но в моем случае он всегда сравнивал целое число со списком и возвращал False, и в результате

ValueError: слишком много значений для распаковки

и

Требуется больше чем x значений для распаковки

Ошибка OP обнаружил.Если он возвращает что-то ценное, просто добавьте эту дополнительную переменную в оператор for в методе call класса виджета.

Затем, когда я определяю варианты, мне просто нужно установитькортеж выбора для каждого элемента, равный (value, label, selected, disabled), где выбран и отключен, является логическим значением, указывающим, должен ли элемент быть выбран и отключен соответственно.

Я надеюсь, что это поможет кому-то потерянному так же, как я был в какой-то момент внизлиния.

...