Поле wtforms, созданное с помощью setattr(), теряет свойства

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

Большинство веб-форм в моем приложении статичны и создаются с помощью wtforms:

    class CritiqueForm(Form):

        rating = IntegerField('Rating')
        comment = TextAreaField('Comments')
        submit = SubmitField('Save Critique')

Когда я так явно выражаюсь, я могу получить ожидаемые результаты, используя CritiqueForm() в представлении и передаче объекта формы для отображения в шаблоне.

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

Когда я искал способы справиться с этим, я нашел возможное решение для dezza ( динамические формы из элементов переменной длины: wtforms), создав метод класса в форме, который я мог бы затем вызвать до создания экземпляра формы, которую я хочу визуализировать. Как в:

    class CritiqueForm(Form):

        rating = IntegerField('Rating')
        comment = TextAreaField('Comments')
        submit = SubmitField('Save Critique')

        @classmethod
        def append_slider(cls, name, label):
            setattr(cls, name, IntegerField(label))
            return cls

где 'append_slider' всегда IntegerField с этикеткой, которую я предоставляю. Это работает достаточно, чтобы я мог заполнить ползунки критериев в представлении, как в:

    @app.route('/critique/<url_id>/edit', methods=['GET', 'POST'])
    def edit_critique(url_id):
        from app.models import RecordModel
        from app.models.forms import CritiqueForm

        record = RecordModel.get_object_by_url_id(url_id)
        if not record: abort(404)

        # build editing form
        ratings = list()
        for i, criterium in enumerate(record.criteria):
            CritiqueForm.append_slider('rating_' + str(i+1),criterium.name)
            ratings.append('form.rating_' + str(i+1))
        form = CritiqueForm(request.form)

        # Process valid POST
        if request.method=='POST' and form.validate():
           # Process the submitted form and show updated read-only record        
            return render_template('critique.html')

        # Display edit form
        return render_template('edit_critique.html',
            form=form,
            ratings=ratings,
            )

ratings Список построен, чтобы дать шаблону простой способ ссылки на динамические поля:

    {% for rating_field in ratings %}
        {{ render_slider_field(rating_field, label_visible=True, default_value=0) }} 
    {% endfor %}

где render_slider_field макрос, который превращает IntegerField в слайдер

С form.rating- целочисленное поле, явно определенное в CritiqueForm- проблем нет, и слайдер генерируется с меткой, как и ожидалось. Однако с динамическими целочисленными полями я не могу ссылаться на label значение в целочисленном поле. Последняя часть трассировки стека выглядит так:

    File "/home/vagrant/msp/app/templates/edit_critique.html", line 41, in block "content"
    {{ render_slider_field(rating_field, label_visible=True, default_value=0) }}

    File "/home/vagrant/msp/app/templates/common/form_macros.html", line 49, in template
    {% set label = kwargs.pop('label', field.label.text) %}

    File "/home/vagrant/.virtualenvs/msp/lib/python2.7/site-packages/jinja2/environment.py", line 397, in getattr
    return getattr(obj, attribute)

    UndefinedError: 'str object' has no attribute 'label'

Путем некоторой отладки я подтвердил, что ни одно из ожидаемых свойств поля (например, name, short_name, id ...) не отображается. Когда пыль оседает, я просто хочу это:

        CritiqueForm.append_slider('rating', 'Rating')

чтобы быть эквивалентным этому:

        rating = IntegerField('Rating')

Ограничивает ли метод setattr() то, какая информация может быть включена в форму, или я просто неправильно инициализирую или ссылаюсь на свойства поля?

РЕДАКТИРОВАТЬ: два изменения позволили удалить мои непосредственные блокировщики.

1) Я неправильно ссылался на поле формы в шаблоне. Параметры поля (например, метка) появились там, где ожидалось это изменение:

    {% for rating_field in ratings %}
            {{ render_slider_field(form[rating_field], label_visible=True, default_value=0) }} 
    {% endfor %}

где я заменяю строку rating_field с form[rating_field],

2) Для решения проблемы динамического изменения базового класса из представления, новый класс формы ThisForm() создан, чтобы расширить мою базу CritiqueForm, а затем выполняется динамическое добавление:

    class ThisForm(CritiqueForm):
        pass

    # build criteria form fields
    ratings = list()
    for i, criterium in enumerate(record.criteria):
        setattr(ThisForm, 'rating_' + str(i+1), IntegerField(criterium.name))
        ratings.append('rating_' + str(i+1))

    form = ThisForm(request.form)

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

1 ответ

setattr(obj, name, value) это очень точный эквивалент obj.name = value - оба являются синтаксическим сахаром для obj.__setattr__(name, value) - так что ваша проблема не с "некоторым ограничением" setattr() но сначала с тем, как wtform.Form работает. Если вы посмотрите на исходный код, вы увидите, что есть намного больше, чтобы заставить поля и форму работать вместе, чем просто объявить поля как атрибуты класса (задействована магия метакласса...). Итак, вам придется пройтись по исходному коду, чтобы узнать, как динамически добавлять поля в форму.

Кроме того, ваш код пытается установить новые поля в самом классе. Это большое НЕТ НЕТ в многопроцессорной / многопоточной / длительной среде процессов с одновременным доступом - каждый запрос изменяет класс формы (общий на уровне процесса), беспорядочно добавляя или переопределяя поля. Может показаться, что он работает на однопроцессорном однопоточном сервере разработки с одним и тем же пользователем, но будет работать с самыми непредсказуемыми ошибками или (что еще хуже) неправильными результатами.

Итак, что вы хотите выяснить, так это на самом деле, как динамически добавлять поля в экземпляр формы - или, в качестве альтернативы, как динамически создавать новый временный класс формы (что на самом деле совсем не сложно - помните, что классы Python также являются объектами),

Другие вопросы по тегам