Flask 使用 SelectField 为列表中的每个项目创建表单
Flask create form with SelectField for every item in a list
我是 Flask 的新手,我正在尝试做一些需要一些技能的东西,我在搜索 SO 时找不到答案。
所以,概念是...我有一个动态生成的歌曲列表。基本上我们不知道会有多少。歌曲存储在列表中的列表中,如下所示:
[[id,artist_name,track_name],[id,artist_name,track_name],[id,artist_name,track_name] etc]
我想为列表中的每个列表创建一个带有 SelectField 的表单,以便用户可以为列表中的每首歌曲打分。
传递列表项的路由如下所示:
@app.route('/submitlist', methods=['GET', 'POST'])
def submitlist():
form = forms.Playlist()
if request.method == 'POST':
if form.validate():
song_data = [[id,artist_name,track_name],[id,artist_name,track_name]...]
session['thesongs'] = song_data
return redirect(url_for('songs'))
return render_template('songs.html', form=form)
接收列表的路由如下所示:
@app.route('/songs', methods=['GET', 'POST'])
def songs():
form = forms.SongsRated()
if request.method == 'POST':
data = form.rating.data
session['results'] = data
return redirect(url_for('results'))
return render_template('songs.html', thesongs=session['thesongs'], form=form)
我无法弄清楚 SongsRated 的形式应该是什么 return SelectFields 的动态数量。我还应该能够收集 return 值并确定哪个 SelectField 值属于列表中的哪个项目(歌曲)。
最后我想做一些验证,因为我只希望用户能够对 10 首歌曲(无论多少)进行评分,并且分数应该都是唯一的 (1-10)。
如果没有解释清楚,我很抱歉。
提前致谢。
您可以在 this 教程中找到有关如何在视图中动态创建表单的说明。
基于此,我给你写了以下示例。
为列表中的每个项目创建一个带有 select 字段的表单。如果提交了表单,它将为您提供所有指定排名的条目的 ID 和 selected 值。标识基于字段名称,因为它包含条目的原始 ID。
双 select 离子被 custom validator.
阻止
此外,JavaScript 可以防止双 selection 发生。为此,每个 SelectField 都添加了 change-events 的侦听器,这会禁用所有其他 selected 排名,或者如果 selected 排名不同,则再次启用它。
烧瓶 (app.py)
from flask import (
Flask,
render_template,
request
)
from flask_wtf import FlaskForm
from wtforms import SelectField
from wtforms.validators import (
NumberRange,
ValidationError
)
LIMIT = 10 # <- HERE!!!
app = Flask(__name__)
app.secret_key = 'your secret here'
def validate_rating(form, field):
if field.data:
# Check if a ranking was selected twice.
for _f in form:
if _f != field and _f.data == field.data:
raise ValidationError('A rating can only be given once.')
else:
# Check whether the number of ratings corresponds to the number of songs
# or the maximum limit (10).
count,length = 0,0
for _f in form:
if _f.name.startswith('track'):
length += 1
count += int(not (_f.data == '' or _f.data == 0))
limit = max([0, min([LIMIT, length])])
if count != limit:
raise ValidationError(f'You should give up to {limit} ratings.')
class SongsRated(FlaskForm):
pass
songs = [(i, f'Unknown Artist {i}', f'Untitled Track {i}') for i in range(1,16)]
@app.route('/', methods=['GET', 'POST'])
def index():
# Generate the actual form.
class F(SongsRated):
pass
for id,artist,track in songs:
field = SelectField(
f'{artist} - {track}',
[
NumberRange(min=1, max=LIMIT),
validate_rating
],
choices=list(range(LIMIT + 1)),
coerce=int
)
setattr(F, f'track-{id}', field)
# Create an instance of the form.
form = F(request.form)
# Once the form has been received and the entry is valid, ...
if form.validate_on_submit():
# ... inquire about the rankings awarded.
id_values = [
(int(f.name[6:]), int(f.data)) for f in form \
if f.name.startswith('track') and f.data
]
return render_template('index.html', **locals())
HTML (templates/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
</head>
<body>
<form method="post">
{{ form.csrf_token }}
{% for field in form -%}
{% if field.name.startswith('track') -%}
<div>
{{ field.label() }}
{{ field() }}
{% if field.errors -%}
<ul>
{% for error in field.errors -%}
<li>{{ error }}</li>
{% endfor -%}
</ul>
{% endif -%}
</div>
{% endif -%}
{% endfor -%}
<input type="submit" />
</form>
{% if id_values -%}
<output>{{ id_values }}</output>
{% endif -%}
<script type="text/javascript">
/* This script is optional and not strictly required. */
(() => {
const btn = document.querySelector('input[type="submit"]');
const elems = document.querySelectorAll('select[name^="track-"]');
const temp = Array.from(elems, elem => elem.value);
const count = temp.filter(val => !(val == '' || val == 0)).length;
const limit = Math.max(0, Math.min(10, elems.length)); // <- HERE!!!
btn.disabled = count != limit;
elems.forEach(elem => {
// Initialize the previous selection.
elem.value && (elem.dataset.prev = elem.value);
Array.from(elem.options).forEach(opt => {
opt.disabled = opt.value
&& opt.value != elem.value
&& temp.includes(opt.value);
});
// Register event listeners.
elem.addEventListener('change', evt => {
// Enable and disable based on the selection made.
const val = evt.target.value
const prev = evt.target.dataset.prev;
elems.forEach(sel => {
if (sel != evt.target) {
Array.from(sel.options).forEach(opt => {
if (opt.value == val && !(val == '' || val == 0)) {
opt.disabled = true;
} else if (opt.value == prev) {
opt.disabled = false;
}
});
}
});
evt.target.dataset.prev = val;
const cnt = Array.from(elems)
.filter(elem => !(elem.value == '' || elem.value == 0))
.length;
btn.disabled = cnt != limit;
});
});
})()
</script>
</body>
</html>
我是 Flask 的新手,我正在尝试做一些需要一些技能的东西,我在搜索 SO 时找不到答案。
所以,概念是...我有一个动态生成的歌曲列表。基本上我们不知道会有多少。歌曲存储在列表中的列表中,如下所示:
[[id,artist_name,track_name],[id,artist_name,track_name],[id,artist_name,track_name] etc]
我想为列表中的每个列表创建一个带有 SelectField 的表单,以便用户可以为列表中的每首歌曲打分。
传递列表项的路由如下所示:
@app.route('/submitlist', methods=['GET', 'POST'])
def submitlist():
form = forms.Playlist()
if request.method == 'POST':
if form.validate():
song_data = [[id,artist_name,track_name],[id,artist_name,track_name]...]
session['thesongs'] = song_data
return redirect(url_for('songs'))
return render_template('songs.html', form=form)
接收列表的路由如下所示:
@app.route('/songs', methods=['GET', 'POST'])
def songs():
form = forms.SongsRated()
if request.method == 'POST':
data = form.rating.data
session['results'] = data
return redirect(url_for('results'))
return render_template('songs.html', thesongs=session['thesongs'], form=form)
我无法弄清楚 SongsRated 的形式应该是什么 return SelectFields 的动态数量。我还应该能够收集 return 值并确定哪个 SelectField 值属于列表中的哪个项目(歌曲)。
最后我想做一些验证,因为我只希望用户能够对 10 首歌曲(无论多少)进行评分,并且分数应该都是唯一的 (1-10)。
如果没有解释清楚,我很抱歉。
提前致谢。
您可以在 this 教程中找到有关如何在视图中动态创建表单的说明。
基于此,我给你写了以下示例。
为列表中的每个项目创建一个带有 select 字段的表单。如果提交了表单,它将为您提供所有指定排名的条目的 ID 和 selected 值。标识基于字段名称,因为它包含条目的原始 ID。
双 select 离子被 custom validator.
阻止
此外,JavaScript 可以防止双 selection 发生。为此,每个 SelectField 都添加了 change-events 的侦听器,这会禁用所有其他 selected 排名,或者如果 selected 排名不同,则再次启用它。
烧瓶 (app.py)
from flask import (
Flask,
render_template,
request
)
from flask_wtf import FlaskForm
from wtforms import SelectField
from wtforms.validators import (
NumberRange,
ValidationError
)
LIMIT = 10 # <- HERE!!!
app = Flask(__name__)
app.secret_key = 'your secret here'
def validate_rating(form, field):
if field.data:
# Check if a ranking was selected twice.
for _f in form:
if _f != field and _f.data == field.data:
raise ValidationError('A rating can only be given once.')
else:
# Check whether the number of ratings corresponds to the number of songs
# or the maximum limit (10).
count,length = 0,0
for _f in form:
if _f.name.startswith('track'):
length += 1
count += int(not (_f.data == '' or _f.data == 0))
limit = max([0, min([LIMIT, length])])
if count != limit:
raise ValidationError(f'You should give up to {limit} ratings.')
class SongsRated(FlaskForm):
pass
songs = [(i, f'Unknown Artist {i}', f'Untitled Track {i}') for i in range(1,16)]
@app.route('/', methods=['GET', 'POST'])
def index():
# Generate the actual form.
class F(SongsRated):
pass
for id,artist,track in songs:
field = SelectField(
f'{artist} - {track}',
[
NumberRange(min=1, max=LIMIT),
validate_rating
],
choices=list(range(LIMIT + 1)),
coerce=int
)
setattr(F, f'track-{id}', field)
# Create an instance of the form.
form = F(request.form)
# Once the form has been received and the entry is valid, ...
if form.validate_on_submit():
# ... inquire about the rankings awarded.
id_values = [
(int(f.name[6:]), int(f.data)) for f in form \
if f.name.startswith('track') and f.data
]
return render_template('index.html', **locals())
HTML (templates/index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Index</title>
</head>
<body>
<form method="post">
{{ form.csrf_token }}
{% for field in form -%}
{% if field.name.startswith('track') -%}
<div>
{{ field.label() }}
{{ field() }}
{% if field.errors -%}
<ul>
{% for error in field.errors -%}
<li>{{ error }}</li>
{% endfor -%}
</ul>
{% endif -%}
</div>
{% endif -%}
{% endfor -%}
<input type="submit" />
</form>
{% if id_values -%}
<output>{{ id_values }}</output>
{% endif -%}
<script type="text/javascript">
/* This script is optional and not strictly required. */
(() => {
const btn = document.querySelector('input[type="submit"]');
const elems = document.querySelectorAll('select[name^="track-"]');
const temp = Array.from(elems, elem => elem.value);
const count = temp.filter(val => !(val == '' || val == 0)).length;
const limit = Math.max(0, Math.min(10, elems.length)); // <- HERE!!!
btn.disabled = count != limit;
elems.forEach(elem => {
// Initialize the previous selection.
elem.value && (elem.dataset.prev = elem.value);
Array.from(elem.options).forEach(opt => {
opt.disabled = opt.value
&& opt.value != elem.value
&& temp.includes(opt.value);
});
// Register event listeners.
elem.addEventListener('change', evt => {
// Enable and disable based on the selection made.
const val = evt.target.value
const prev = evt.target.dataset.prev;
elems.forEach(sel => {
if (sel != evt.target) {
Array.from(sel.options).forEach(opt => {
if (opt.value == val && !(val == '' || val == 0)) {
opt.disabled = true;
} else if (opt.value == prev) {
opt.disabled = false;
}
});
}
});
evt.target.dataset.prev = val;
const cnt = Array.from(elems)
.filter(elem => !(elem.value == '' || elem.value == 0))
.length;
btn.disabled = cnt != limit;
});
});
})()
</script>
</body>
</html>