Поле 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 также являются объектами),