Я довольно новичок в мире Python (планирую сделать переключение и покинуть CurlyBracesCamelCaseWorld), и я работаю над простым приложением go get exp во всем начальном стеке (база данных, сервер, обработка html-страниц и ресурсов) и т. д.).
Пока все хорошо, темпы развития не перестают меня удивлять, а количество ресурсов вызывает у меня головокружительную боль.
Но я столкнулся с проблемой, от которой просто не могу пройти.
Что я пытаюсь сделать сейчас:
- Иметь главную wtform со статическим набором полей
- Добавить динамически генерируемый список подформ - предварительно заполненный значениями и пользовательскими метками
В моем сценарии приложений пользователь сможет указать список вопросов, на которые нужно ответить - так что будет форма с общими сведениями + динамический список, который я могу использовать для сохранения данных в базе данных при отправке.
У меня есть полный список вопросов здесь
1) Основная проблема в том, что я не могу прочитать данные подчиненных форм в .validate () при отправке формы
2) Другое дело, что я не могу заставить метки отображать пользовательские значения, которые я хочу установить динамически
3) Мне нужно больше читать об обработке csfr в подчиненных формах и о том, как обойти это также
4) И последнее - как проверить подчиненные формы - для обязательных полей, длины и т. Д.
1 & 2 - моя главная проблема сейчас, и у меня есть ощущение, что проблемы имеют одну и ту же первопричину
Мои интуитивные ощущения говорят мне, что идентификатор сломанного элемента имеет смысл («контент» для каждого поля строки подчиненной формы вместо индексированных «записей-0-контент» - которые я вижу при отправке)
Мне не удалось найти полный пример того, как это сделать, и я изо всех сил пытаюсь соединить собранные мной фрагменты. Я подготовил простой код, как Python, так и шаблон jinja2, готовый к запуску, чтобы продемонстрировать проблемы. Я с удовольствием выложу полный рабочий код после того, как разберусь с ним и убью, чтобы найти именно это ..
Итак, сервер ->
from flask import Flask, redirect, url_for, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, FieldList, FormField, SubmitField, HiddenField, Label
from wtforms.validators import DataRequired
app = Flask(__name__, template_folder='flaskblog/templates')
app.config['SECRET_KEY'] = 'SECRET_KEY-SECRET_KEY-SECRET_KEY'
# subforms
class SubForm(FlaskForm):
# how to handle hidden id that I can use to properly commit that on submit?
# entry_type_id = HiddenField()
# validators for subforms don't work, but that's something I'll try to address later
content = StringField(validators=[DataRequired()])
# I use custom __init__ set custom label for the field - or rather I try, as it doesn't work..
def __init__(self, custom_label=None, *args, **kwargs):
# not sure if safe - even just for the subform! #
# Without that, I get 'TypeError: argument of type 'CSRFTokenField' is not iterable' on main_form.validate_on_submit()
kwargs['csrf_enabled'] = False
FlaskForm.__init__(self, *args, **kwargs)
if custom_label is not None:
self.content.label = Label(self.content.id, custom_label)
print(f'INIT // id: [{self.content.id}] // content.data: [{self.content.data}] // label: [{self.content.label.text}]')
# main forms
class MainForm(FlaskForm):
title = StringField('title')
entries = FieldList(FormField(SubForm))
submit = SubmitField('Post')
@app.route("/test", methods=['GET', 'POST'])
def test_route():
# the main form
main_form = MainForm(title='title')
# sub forms, created before validate_on_submit()
sub_form_1 = SubForm(content='Default answer 1', custom_label='Question 1')
sub_form_2 = SubForm(content='Default answer 2', custom_label='Question 2')
main_form.entries.append_entry(sub_form_1)
main_form.entries.append_entry(sub_form_2)
if main_form.validate_on_submit():
for entry in main_form.entries.entries:
print(f'LOOP // id: [{entry.content.id}] // content.data: [{entry.content.data}] // label: [{entry.content.label.text}]')
return redirect(url_for('test_route'))
print(f'INSTANCE_1 // id: [{sub_form_1.content.id}] // content.data: [{sub_form_1.content.data}] // label: [{sub_form_1.content.label.text}]')
print(f'INSTANCE_2 // id: [{sub_form_2.content.id}] // content.data: [{sub_form_2.content.data}] // label: [{sub_form_2.content.label.text}]')
return render_template('test_form.html', title='Test Form', main_form=main_form, legend='Test Form')
if __name__ == '__main__':
app.run(debug=True)
И HTML-шаблон ->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title> Confused -.-' </title>
</head>
<body>
<div class="content-section">
<form action="" method="post">
{{ main_form.hidden_tag() }}
{{ main_form.title.label(class="form-control-label") }}: {{ main_form.title(class="form-control form-control-lg") }}
{% for entry_line in main_form.entries %}
<div class="form-group">
{{ entry_line.content.label(class="form-control-label") }}
{{ entry_line.content.data(class="form-control form-control-lg") }}
</div>
{% endfor %}
{# For the main form I use main_form.title(), main_form.submit(), etc - without .data(). #}
{# If I try to do main_form.title.data() I get the ex that I can't call on 'str' #}
{# But, for entry_lines, if I don't add .data() and just use entry_line.content() #}
{# I can see the input field, but it's prepopulated with HTML for that input instead of the value (that I see in that html) #}
<div class="form-group">
{{ main_form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
</body>
</html>
Отладка по GET:
INIT // id: [content] // content.data: [Default answer 1] // label: [Question 1]
INIT // id: [content] // content.data: [Default answer 2] // label: [Question 2]
INSTANCE_1 // id: [content] // content.data: [Default answer 1] // label: [Question 1]
INSTANCE_2 // id: [content] // content.data: [Default answer 2] // label: [Question 2]
Отладка по почте:
INIT // id: [content] // content.data: [my ans 1] // label: [Question 1]
INIT // id: [content] // content.data: [my ans 1] // label: [Question 2]
LOOP // id: [entries-0-content] // content.data: [<input id="content" name="content" type="text" value="my ans 1">] // label: [Content]
LOOP // id: [entries-1-content] // content.data: [<input id="content" name="content" type="text" value="my ans 1">] // label: [Content]
INIT // id: [content] // content.data: [Default answer 1] // label: [Question 1]
INIT // id: [content] // content.data: [Default answer 2] // label: [Question 2]
INSTANCE_1 // id: [content] // content.data: [Default answer 1] // label: [Question 1]
INSTANCE_2 // id: [content] // content.data: [Default answer 2] // label: [Question 2]
Очевидно, что существует некоторая проблема с идентификаторами (2x контент против записей-0-контент), и я получаю первый результат дважды .. (value = "my ans 1")
Мне бы хотелось иметь возможность создавать полный список форм на основе списка вопросов (я просто использую статический 2 здесь), устанавливать пользовательские метки для каждой подчиненной формы, а затем получать данные на сервере, чтобы я мог сделайте там остальную часть работы.
После этого я могу бороться с проверкой и самостоятельно, но наличие рабочих лесов кажется правильным первым шагом. Я провожу много времени в поисках, но чувствую, что бегаю по кругу.
И, конечно, - если вы считаете, что мои предположения о том, как достичь того, чего я хочу достичь, неверны, и я должен использовать это, - дайте мне знать. Я хочу написать правильную вещь, а не только то, что работает.
Вставьте ссылки, если вы предпочитаете
РЕДАКТИРОВАТЬ - рабочий код
Благодаря @Nick K9!
from flask import Flask, redirect, url_for, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, FieldList, FormField, SubmitField, HiddenField, Label
from wtforms.validators import DataRequired
app = Flask(__name__, template_folder='flaskblog/templates')
app.config['SECRET_KEY'] = 'SECRET_KEY-SECRET_KEY-SECRET_KEY'
subform_datasource = {
0: {'question': 'Question 1', 'answare': 'Answare 1'},
1: {'question': 'Question 2', 'answare': 'Answare 2'}
}
# subforms
class SubForm(FlaskForm):
entry_type_id = HiddenField()
content = StringField(validators=[DataRequired()])
# main forms
class MainForm(FlaskForm):
title = StringField('title')
entries = FieldList(FormField(SubForm))
submit = SubmitField('Post')
@app.route("/test", methods=['GET', 'POST'])
def test_route():
main_form = MainForm()
if main_form.validate_on_submit():
for entry in main_form.entries.entries:
entry_message = (
f'POST // wtform id: [{entry.content.id}] '
f' // entry_type_id id: [{entry.entry_type_id.data}]'
f' // content.data: [{entry.content.data}]'
f' // label: [{entry.content.label.text}]'
)
print(str(entry_message))
return redirect(url_for('test_route'))
elif request.method == 'GET':
# You can indeed set the default values, but you need to pass the dict, not the SubForm instance!
for key, subform in subform_datasource.items():
main_form.entries.append_entry({'content': subform['answare'], 'entry_type_id': key})
# Moved out from the constructor - on subform failed validation labels reset to the default value 'Content'
# I guess that matching what was send to the form does not cast back the labels but creates the fresh instances with just the value
# What, of course, makes sense - it's an edge case, no point in affecting performance for everyone
for entry in main_form.entries.entries:
entry.content.label.text = subform_datasource[entry.entry_type_id.data]['question']
return render_template('test_form.html', title='Test Form', main_form=main_form, legend='Test Form')
if __name__ == '__main__':
app.run(debug=True)
2 ссылки, которые также помогли мне