我正在开发一个Flask webapp,要求(内部)用户为我们网络上的目录中的任意数量的文件选择选项,以便其他一些脚本可以根据这些选项处理文件(与此无关)问题,但如果你很好奇他们是我们需要处理的A / V文件)。
我遇到的问题是我似乎无法动态生成所需的表单字段(应用程序正在查看的服务器目录中可能有0个 - 几十个文件)并收集每个实例的表单数据我为输入对象创建的表单类。你怎么把n
表单实例变成另一个表单类的实例?
我有一个基础IngestForm
类和一个ObjectForm
类,它描述了与每个单独对象有关的字段。我的基本怀疑是wtforms
不能有一个包含其他形式的子类...但是我可以在表格获得POST
ed之前在各个步骤打印出所有内容并查看我期望作为dict的所有数据,我可以看到子形式有wtforms
对象。从我的index.html
模板,我可以看到来自ObjectForm
实例的所有预期数据。但是一旦发布超级形式,返回的所有内容都是空白的choicesDict
(见下文)和submit
值。当我击中IngestForm
时,Submit
实例是否重新初始化或奇怪?
这就是我现在拥有的。我已经为选项设置了一个dict,其中每个键都是相关文件的路径,值是ObjectForm
类的一个实例:
forms.朋友
class ObjectForm(FlaskForm):
"""
Fields for an individual object
"""
targetFilePath = wtforms.HiddenField('targetObjectPath')
option1 = wtforms.BooleanField('Option1?')
option2 = wtforms.BooleanField("Option2?")
# etc.
class IngestForm(FlaskForm):
'''
General input form
'''
choicesDict = {}
# also tried just targetObject = wtforms.FormField(ObjectForm)
submit = wtforms.SubmitField('Submit')
routes.朋友:
[import relevant stuff]
@app.route('/index',methods=['GET','POST'])
def index():
# GET A DICT OF PATHS AND BASENAMES TO PROCESS {'fullPath':'basename'}
objects = listObjects.list_objects()
class OneObject(forms.ObjectForm):
pass
choices = {}
for path,_object in objects.items():
choices[path] = OneObject(targetPath=path,targetBase=_object)
# also tried setattr(forms.IngestForm,'choicesDict',choices)
form = forms.IngestForm()
form.choicesDict = choices
if form.is_submitted():
return redirect(url_for('status'))
return render_template(
'index.html',title='Index',objects=objects,form=form
)
@app.route('/status',methods=['GET','POST'])
def status():
# DO THE STUFF
ingest.html模板:
{% block content %}
<h1>Here are files to ingest:</h1>
<form action="{{url_for('status')}}" method='POST'>
<div>
{% for item,vals in form.choicesDict.items() %}
<p>{{vals.targetBase.data}}</p>
<p>{{vals.option1.label}}{{vals.option1()}}</p>
<p>{{vals.option2.label}} {{vals.option3()}}</p>
<p>{{vals.etc.label}}{{vals.etc()}}</p>
{%endfor%}
</div>
{{form.submit()}}
</form>
{% endblock %}
status.html模板只接受POST数据。这里并不真实相关,只是说我可以看到它没有得到choicesDict
好的,所以我以一种非常黑客的方式解决了这个问题,但无论如何。我在this示例之后使用了jinja2宏,并在我的表单模板中构造了我感兴趣的文件所特有的字段名称/ ID。
因此,对于我的网络目录中的每个文件['a.mov','b.mov','c.mp4']
,我创建了一个像这样的字典:{'a.mov': SubclassObjectForm, 'b.mov': SubclassObjectForm }
,我有一个包含这个字典的MainForm
实例字段。当我渲染表单时,jinja宏根据需要为name
和id
字段创建<label>
和<input>
属性,包括相关文件的前缀。
例如<input name='targetObjectFilePath-movieA.mov' value='/full/path/to/file' type='hidden>
。
当表单获得POST时,只需在我的视图中提取相关的数据位即可。
我希望这可以帮助别人!它可能不是优雅或'专业',但它完成了我的任务。下一步......造型!
forms.朋友
class ObjectForm(FlaskForm):
"""
Fields for an individual object
"""
targetFilePath = wtforms.HiddenField('targetObjectPath')
targetBase = wtforms.HiddenField('targetObjectBasename')
option1 = wtforms.BooleanField('Option1?')
option2 = wtforms.BooleanField("Option2?")
# etc.
class IngestForm(FlaskForm):
'''
General input form
'''
choicesDict = wtforms.HiddenField(default='no choices')
submit = wtforms.SubmitField('Submit')
routes.朋友
[import relevant stuff]
@app.route('/index',methods=['GET','POST'])
def index():
# GET A DICT OF PATHS AND BASENAMES TO PROCESS {'fullPath':'basename'}
objects = listObjects.list_objects()
class OneObject(forms.ObjectForm):
pass
choices = {}
for path,_object in objects.items():
choices[path] = OneObject(targetPath=path,targetBase=_object)
form = forms.IngestForm()
form.choicesDict = choices
return render_template(
'index.html',title='Index',form=form
)
@app.route('/status',methods=['GET','POST'])
def status():
data = request.form.to_dict(flat=False)
# DO THE STUFF
的index.html
{% import "macros.html" as macros %}
{% extends "base.html" %}
{% block content %}
<h1>Here are files to ingest:</h1>
<form action="{{ url_for('status') }}" method='POST'>
{{ form.hidden_tag() }}
{{ form.csrf_token }}
{# iterate over the form dict with subforms included: #}
{% for item,vals in form.choicesDict.items() %}
<div>
{# iterate over subform fields and include target file basename #}
{% for field in vals %}
{{macros.render_field(field,vals.targetBase.data)}}
{% endfor %}
</div>
{%endfor%}
{{form.submit()}}
</form>
{% endblock %}
macros.html
{% macro render_field(field, uniqueName) %}
<p>
{# I only care about 2 kinds of data: filenames/paths and boolean options. #}
{% if field.type == 'BooleanField' %}
<label for="{{ field.id }}-{{ uniqueName }}">{{ field.label.text }}</label>
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="checkbox" default=""></input>
{# render hidden input for full path for later processing #}
{% elif field.name == 'targetPath' %}
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="hidden" value="{{ field.data }}"/>
{# use basename for local id purposes and display value as label for users #}
{% elif field.name == 'targetBase' %}
<label for="{{ field.id }}-{{ uniqueName }}">{{ uniqueName }}</label>
<input name="{{ field.id }}-{{ uniqueName }}" id="{{ field.id }}-{{ uniqueName }}" type="hidden" value="{{ field.data }}"/>
{% endif %}
</p>
{% endmacro %}