Flask动态输入表单,FormFieldreturns空白POST数据

Flask dynamic input form, FormField returns blank POST data

我正在开发一个 Flask 网络应用程序,它要求(内部)用户对将位于我们网络目录中的任意数量的文件进行 select 选项,以便其他一些脚本可以处理这些文件根据这些选项(与此问题无关,但如果您好奇它们是我们需要处理的 A/V 个文件)。

我遇到的问题是我似乎无法同时动态生成所需的表单字段(应用程序正在查找的服务器目录中可能有 0 - 数十个文件)并且 为我为输入对象创建的表单 class 的每个实例收集表单数据。如何将 n 表单实例硬塞进另一个表单 class 的实例??

我有一个基础 IngestForm class 和一个 ObjectForm class 来描述与每个单独对象相关的字段。我的基本怀疑是 wtforms 不能有包含其他表格的子 class...但是我可以在表格得到 POSTed 之前在各个步骤打印所有内容并查看所有内容我期望作为字典的数据,我可以看到子表单作为 wtforms 对象存在。从我的 index.html 模板中,我可以看到来自 ObjectForm 个实例的所有预期数据。但是一旦发布超级表单,返回的只是一个空白 choicesDict(见下文)和 submit 值。当我点击 Submit 时,IngestForm 实例是否会重新初始化或发生奇怪的事情?

这是我现在拥有的。我已经为选择设置了一个字典,其中每个键都是相关文件的路径,值是 ObjectForm class:

的一个实例

forms.py

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.py:

[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

的 none 之外,这里并没有真正相关

好的,所以我以一种真正的 hack-y 方式解决了这个问题,但无论如何。我在 this 示例之后使用了一个 jinja2 宏,并在我的表单模板构造字段 names/ids 中对我感兴趣的文件是唯一的。

因此,对于我的网络目录中的每个文件 ['a.mov','b.mov','c.mp4'],我都创建了一个字典,如下所示:{'a.mov': SubclassObjectForm, 'b.mov': SubclassObjectForm } 并且我有一个包含该字典的 MainForm 实例字段。当我呈现表单时,jinja 宏根据需要为 <label><input> 字段创建 nameid 属性,其中包括相关文件的前缀。

例如<input name='targetObjectFilePath-movieA.mov' value='/full/path/to/file' type='hidden>.

发布表单时,只需在我看来提取相关数据即可。

我希望这对某人有所帮助!它可能不优雅或 'pro' 但它完成了我的任务。下一步...造型!

forms.py

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

[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 %}