繁体   English   中英

Python Flask WTForms:动态 SelectField 返回“无效选择”

[英]Python Flask WTForms: Dynamic SelectField returning “Not a valid choice”

首先,我知道这里有很多类似的问题。 我看过大部分,如果不是全部的话,它们都没有帮助指导我找到解决方案。 以下问题是最相似的,但我的“动态”实现与他们的有点不同(更多内容见下文): Flask / Python / WTForms 验证和动态设置 SelectField 选项

简而言之:

我有一个表单,用于从我构建的网络监控工具请求报告。 该工具跟踪各种无线网络的所有不同类型的统计信息。 这是 class 定义的形式。 我的动态字段是 ssidFilter selectField。

class RequestReportForm(FlaskForm):
    startDate = DateField('Start Date', validators=[DataRequired(), validate_startDate])
    startTime = TimeField('Start Time', format='%H:%M', validators=[DataRequired()])
    endDate = DateField('End Date', format='%Y-%m-%d', validators=[DataRequired(), validate_endDate])
    endTime = TimeField('End Time', format='%H:%M', validators=[DataRequired(), validate_allDates])
    ssidFilter = SelectField('SSID', default=('All', 'All'))
    reportType = SelectField('Report Type', validators = [DataRequired()], choices=[
                                                                ('rssi', 'RSSI vs. Time'), 
                                                                ('snr', 'SNR vs. Time'),
                                                                ('ClientCount', 'Client Count vs. Time'),

    ])
    selectLocation = SelectField('Locations', validators = [DataRequired()], choices=[ 
                                                                ('All','All'),
                                                                ('mainLobby', 'Main Lobby'), 
                                                                ('level1', 'Level 1'), 
                                                                ('level2', 'Level 2'),
                                                                ])
    submit = SubmitField('Generate Report')

我已经实现了 Javascript 以获取用户输入的 startDate 和 endDate 字段,并通过在我的应用程序中“获取”另一个 flask 路由来在我的数据库上运行查询,以返回在他们输入的日期范围。 这是那条路线:

@app.route('/updateSSIDs/<startDate>/<endDate>', methods=['GET'])
def updateSSIDs(startDate, endDate):
    startDate = datetime.strptime(startDate, '%Y-%m-%d')
    endDate = datetime.strptime(endDate, '%Y-%m-%d')
    # Get a list of unique SSIDs that we have data for between the start and end dates selected on the form.
    SSIDs = getSSIDs(startDate, endDate)
    SSIDArray = []
    for ssid_tuple in SSIDs:
        ssidObj = {}
        ssidObj['id'] = ssid_tuple[0]
        ssidObj['ssid'] = ssid_tuple[0]
        SSIDArray.append(ssidObj)
    return jsonify({'SSIDs' : SSIDArray})

变量SSIDArray在 jsonify 之前看起来像这样:

[{'id': 'Example Network 1', 'ssid': 'Example Network 1'}, {'id': 'Staff', 'ssid': 'Staff'}, ... ]

这是我实例化表单的方式:

@app.route('/requestReport', methods=['GET', 'POST'])
def requestReport():
    form = RequestReportForm()
    form.ssidFilter.choices = getSSIDs(datetime.now(), datetime.now())

    if form.validate_on_submit():
        print("Valid form data:")
        print(form.data)
        flash(f'Received request for report from {form.startDate.data} at {form.startTime.data} through {form.endDate.data} at {form.endTime.data}', 'success')
        startDate = form.startDate.data
        startTime = form.startTime.data
        endDate = form.endDate.data
        endTime = form.endTime.data

        reportType = form.reportType.data
        locations = form.selectLocation.data
        ssid = form.ssidFilter.data

        # Put requested times into datetime objects
        startDateTime = datetime(startDate.year, startDate.month, startDate.day, startTime.hour, startTime.minute)
        endDateTime = datetime(endDate.year, endDate.month, endDate.day, endTime.hour, endTime.minute)

        # Generate report and redirect client to report.
        reportParameters = rpt.prepareReport_single(startDateTime, endDateTime, reportType, locations, ssid)        
        report = rpt.buildReport_singleLocation(reportParameters)       
        report = Markup(report)
        return render_template('viewReport.html', value=report)

请注意,我在这里通过调用相同的form.ssidFilter.choices function 来填充我的动态字段getSSIDs ,它响应了我的 Javascript 获取调用,但我在datetime.now()中传递了开始日期和结束日期。 这是为了最初向用户显示当前正在使用的无线网络列表,但一旦他们更改日期,列表将更新为一组不同的网络。

这就是问题所在:如何设置可接受的选择列表 ( form.ssidFilter.choices ) 以包含在客户输入报告日期后返回的网络列表?

我正在探索的可能解决方案:

  • 在选择日期时重新加载页面以使用动态数据实例化新表单。

  • 首先保留所有可用选项的巨大列表,然后当用户更改表单上的日期时,这些选项将通过 JS 动态过滤。

哦,如果选择的 SSID 恰好是form.ssidFilter.choices = getSSIDs(datetime.now(), datetime.now())语句中的列表中的 SSID,则表单可以正常工作。 此问题仅在选择最初不在选择列表中的项目时发生(这很有意义 - 我只是不知道如何解决)。

感谢您的时间。

编辑/解决方案:

感谢@SuperShoot 的回答,我得以完成这项工作。 对我来说,关键是让 Flask 路由区分 HTTP 请求的类型 - GET 或 POST。 由于我知道 GET 方法仅用于检索表单,而 POST 方法仅用于提交填写的表单,因此我可以从用户那里提取 startDate 和 endDate 选择,运行查询以获取数据,然后更新我的表格 class 中的choices字段。

正如@SuperShoot 也提到的,我必须做一些额外的验证,但我做的有点不同。 由于我的 JavaScript 代码在修改结束日期后立即从我的 Flask 应用程序调用单独的路由,因此表单没有责任验证所选日期。 我在其他 Flask 路线中实现了一些验证。

这是我修改后的 Flask requestReport路由:

@app.route('/requestReport', methods=['GET', 'POST'])
def requestReport():
    form = RequestReportForm()
    form.ssidFilter.choices = getSSIDs(datetime.now(), datetime.now())

    if request.method == 'POST':
        startDate = datetime(form.startDate.data.year, form.startDate.data.month, form.startDate.data.day)
        endDate = datetime(form.endDate.data.year, form.endDate.data.month, form.endDate.data.day)
        # Update acceptable choices for the SSIDs on the form if the form is submitted.
        form.ssidFilter.choices = getSSIDs(startDate, endDate)

    if form.validate_on_submit():
        flash(f'Received request for report from {form.startDate.data} at {form.startTime.data} through {form.endDate.data} at {form.endTime.data}', 'success')
        startDate = form.startDate.data
        startTime = form.startTime.data
        endDate = form.endDate.data
        endTime = form.endTime.data

        reportType = form.reportType.data
        locations = form.selectLocation.data
        ssid = form.ssidFilter.data

        # Put requested times into datetime objects
        startDateTime = datetime(startDate.year, startDate.month, startDate.day, startTime.hour, startTime.minute)
        endDateTime = datetime(endDate.year, endDate.month, endDate.day, endTime.hour, endTime.minute)

        # Generate report and redirect client to report.
        reportParameters = rpt.prepareReport_single(startDateTime, endDateTime, reportType, locations, ssid)        
        report = rpt.buildReport_singleLocation(reportParameters)       
        report = Markup(report)
        return render_template('viewReport.html', value=report)

    else:
        return render_template('requestReport.html', title='Report Request', form=form)

这是我更新updateSSIDs路由,当表单的结束日期更改时,它通过 Javascript 调用:

@app.route('/updateSSIDs/<startDate>/<endDate>', methods=['GET'])
def updateSSIDs(startDate, endDate):

    startDate = datetime.strptime(startDate, '%Y-%m-%d')
    endDate = datetime.strptime(endDate, '%Y-%m-%d')

    # Validate startDate and endDate
    emptyDataSet = {'SSIDs' : {'id ': 'All', 'ssid' : 'All'}}
    if startDate > endDate:
        return jsonify(emptyDataSet)

    if startDate >= datetime.now():
        return jsonify(emptyDataSet)

    if startDate.year not in range(2019, 2029) or endDate.year not in range(2019, 2029):
        return jsonify(emptyDataSet)

    # Get a list of unique SSIDs that we have data for between the start and end dates selected on the form.
    SSIDs = getSSIDs(startDate, endDate)
    SSIDArray = []
    for ssid_tuple in SSIDs:
        ssidObj = {}
        ssidObj['id'] = ssid_tuple[0]
        ssidObj['ssid'] = ssid_tuple[0]

        SSIDArray.append(ssidObj)
    return jsonify({'SSIDs' : SSIDArray})

在尝试通过getSSIDs从数据库中检索数据之前,这条路线会进行一些基本检查,以确保提交的日期并非完全荒谬,但我在getSSIDs function 中进行了一些更彻底的验证。

您可以根据路由是否处理 GET 或 POST 请求以不同方式实例化表单:

@app.route('/requestReport', methods=['GET', 'POST'])
def requestReport():
    form = RequestReportForm()
    if request.method == "GET":
        start = end = datetime.now()
    else:
        # validate start and end dates here first?
        start, end = form.startDate.data, form.endDate.data
    form.ssidFilter.choices = getSSIDs(start, end)
    ...

...尽管在 POST 情况下,它使用的是开始日期和结束日期,然后才得到验证。 因此,一种选择是首先在“POST”条件处理(我在其中放置评论)中内联验证它们,或者另一种选择是覆盖RequestReportForm上的.validate()方法。

这是Form.validate()的文档字符串:

        """
        Validates the form by calling `validate` on each field.
        :param extra_validators:
            If provided, is a dict mapping field names to a sequence of
            callables which will be passed as extra validators to the field's
            `validate` method.
        Returns `True` if no errors occur.
        """

一个可能的实现是:

class RequestReportForm(FlaskForm):
    ...

    def validate(self, *args, **kwargs):
        """Ensure ssidFilter field choices match input startDate and endDate"""
        if not (self.startDate.validate(self) and self.endDate.validate(self)):
            return False
        self.ssidFilter.choices = getSSIDs(self.startDate.data, self.endDate.data)
        return super().validate(*args, **kwargs)

FlaskForm.validate_on_submit()首先检查表单是否提交,然后会调用自定义的.validate()方法。 该方法首先确保开始和结束日期是有效的,并在最终委托验证备份 MRO 之前使用它们来填充ssidFilter的预期可能值。

我还没有运行这段代码,所以如果有任何错误请告诉我,但希望我能很好地理解这个想法,如果它适合的话,你可以使用它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM