Python Flask WTForms:动态 SelectField 返回 "Not a valid choice"
Python Flask WTForms: Dynamic SelectField returning "Not a valid choice"
首先,我知道这里有很多类似的问题。我已经查看了大部分(如果不是全部的话),其中 none 帮助指导我找到了解决方案。下面的问题是最相似的,但我对 "dynamic" 的实现与他们的有点不同(更多内容见下文):
简而言之:
我有一个表格,用于从我构建的网络监控工具中请求报告。该工具跟踪各种无线网络的所有不同类型的统计数据。下面是表格 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 以获取用户输入的开始日期和结束日期字段,并且 运行 通过 "fetch" 在我的应用程序中使用另一个 flask 路由对我的数据库进行查询return 在他们输入的日期范围内使用的所有无线网络 (SSID) 的列表。这是那条路线:
@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)
请注意,我正在通过调用响应我的 Javascript 获取调用的相同 getSSIDs
函数填充我的动态字段 form.ssidFilter.choices
,但我传入 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
从数据库中检索数据之前提交的日期不是完全荒谬的,但我在 [=20] 中做了一些更彻底的验证=]函数。
您可以根据路由处理的是 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()
方法。 20=]
这是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()
方法。该方法首先确保开始和结束日期有效,并使用它们填充 ssidFilter
的预期可能值,然后最终委托验证备份 MRO。
我没有 运行 这个代码,所以如果有任何错误请告诉我,但希望我已经很好地理解了这个想法,如果合适的话,你可以 运行 使用它。
首先,我知道这里有很多类似的问题。我已经查看了大部分(如果不是全部的话),其中 none 帮助指导我找到了解决方案。下面的问题是最相似的,但我对 "dynamic" 的实现与他们的有点不同(更多内容见下文):
简而言之:
我有一个表格,用于从我构建的网络监控工具中请求报告。该工具跟踪各种无线网络的所有不同类型的统计数据。下面是表格 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 以获取用户输入的开始日期和结束日期字段,并且 运行 通过 "fetch" 在我的应用程序中使用另一个 flask 路由对我的数据库进行查询return 在他们输入的日期范围内使用的所有无线网络 (SSID) 的列表。这是那条路线:
@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)
请注意,我正在通过调用响应我的 Javascript 获取调用的相同 getSSIDs
函数填充我的动态字段 form.ssidFilter.choices
,但我传入 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
从数据库中检索数据之前提交的日期不是完全荒谬的,但我在 [=20] 中做了一些更彻底的验证=]函数。
您可以根据路由处理的是 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()
方法。 20=]
这是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()
方法。该方法首先确保开始和结束日期有效,并使用它们填充 ssidFilter
的预期可能值,然后最终委托验证备份 MRO。
我没有 运行 这个代码,所以如果有任何错误请告诉我,但希望我已经很好地理解了这个想法,如果合适的话,你可以 运行 使用它。