在Flask/Python/SQLAlchemy中实现二维评分表单的正确数据存储(解决重复存储首个评分值问题)
在Flask/Python/SQLAlchemy中实现二维评分表单的正确数据存储(解决重复存储首个评分值问题)
嘿,我一眼就看出问题出在哪了——你现在的所有评分输入框都绑定到了同一个form.scores字段上!浏览器提交表单时,同名的输入只会返回第一个值,所以后端循环存储的时候自然全是重复的第一个输入值。咱们一步步来解决这个问题:
核心问题拆解
你的二维评分表需要每个单元格对应一个独立的表单字段,这样后端才能区分不同位置的评分值。下面提供两种可行的方案,你可以根据自己的习惯选择:
方案一:动态生成命名字段(直观好调试)
这种方法会为每个评分单元格创建一个唯一命名的表单字段,比如score_1_1对应选项1、准则1的评分,score_2_3对应选项2、准则3的评分,非常直观。
1. 重新定义表单类
修改你的ScoresForm,在初始化时根据选项数和准则数动态生成字段:
from flask_wtf import FlaskForm from wtforms import IntegerField, SubmitField from wtforms.validators import DataRequired class ScoresForm(FlaskForm): def __init__(self, num_options, num_criteria, *args, **kwargs): super().__init__(*args, **kwargs) # 为每个评分单元格创建唯一字段 for option_idx in range(num_options): for crit_idx in range(num_criteria): # 字段名格式:score_{选项序号}_{准则序号}(从1开始) field_name = f"score_{option_idx + 1}_{crit_idx + 1}" setattr(self, field_name, IntegerField(validators=[DataRequired()])) submit = SubmitField("Save Scores")
2. 修改视图函数
在创建表单时传入选项数和准则数,提交时循环读取每个字段的值:
@app.route('/scoring', methods=['GET', 'POST']) def scoring(): # 先获取之前的选项和准则数据(从数据库或session中取,这里示例从表中查询) option_list = Option.query.all() criteria_list = Criteria.query.all() num_options = len(option_list) num_criteria = len(criteria_list) all_option_ids = [opt.id for opt in option_list] all_criteria_ids = [crit.id for crit in criteria_list] # 传入数量创建表单 form = ScoresForm(num_options=num_options, num_criteria=num_criteria) # 注意:如果是多用户/多案例场景,别直接全删!要加过滤条件,比如where case_id=xxx db.session.query(Scores).delete() if form.validate_on_submit(): for opt_idx, option_id in enumerate(all_option_ids): for crit_idx, criteria_id in enumerate(all_criteria_ids): # 根据字段名获取对应的值 field_name = f"score_{opt_idx + 1}_{crit_idx + 1}" score_value = getattr(form, field_name).data # 存入数据库 db.session.add(Scores(option_id=option_id, criteria_id=criteria_id, scores=score_value)) db.session.commit() flash('Scores Saved!') return redirect(url_for('scoring')) return render_template('scoring.html', form=form, options=option_list, criteria=criteria_list)
3. 调整HTML模板
渲染时对应每个动态生成的字段:
<table class="table"> <thead> <tr> <th></th> {% for crit in criteria %} <th>{{ crit.criteria_name }}</th> {% endfor %} </tr> </thead> <tbody> {% for opt_idx, opt in enumerate(options) %} <tr> <td>{{ opt.option_name }}</td> {% for crit_idx, crit in enumerate(criteria) %} <td> {{ form[f"score_{opt_idx + 1}_{crit_idx + 1}"](class="form-input") }} </td> {% endfor %} </tr> {% endfor %} </tbody> </table> {{ form.submit(class="btn btn-primary mt-3") }}
方案二:使用FieldList嵌套(结构化更强)
如果喜欢更结构化的方式,可以用WTForms的FieldList嵌套,外层是选项行,内层是每行的准则评分:
1. 重新定义表单类
from flask_wtf import FlaskForm from wtforms import IntegerField, SubmitField, FieldList from wtforms.validators import DataRequired class ScoresForm(FlaskForm): def __init__(self, num_options, num_criteria, *args, **kwargs): super().__init__(*args, **kwargs) # 外层FieldList对应每个选项行,内层FieldList对应该行的所有准则评分 self.scores = FieldList( FieldList(IntegerField(validators=[DataRequired()]), min_entries=num_criteria), min_entries=num_options ) submit = SubmitField("Save Scores")
2. 修改视图函数提交逻辑
if form.validate_on_submit(): for opt_idx, option_id in enumerate(all_option_ids): # 获取当前选项行的所有评分 row_scores = form.scores.data[opt_idx] for crit_idx, criteria_id in enumerate(all_criteria_ids): score_value = row_scores[crit_idx] db.session.add(Scores(option_id=option_id, criteria_id=criteria_id, scores=score_value)) db.session.commit() flash('Scores Saved!') return redirect(url_for('scoring'))
3. 调整HTML模板
<table class="table"> <thead> <tr> <th></th> {% for crit in criteria %} <th>{{ crit.criteria_name }}</th> {% endfor %} </tr> </thead> <tbody> {% for opt_idx, opt in enumerate(options) %} <tr> <td>{{ opt.option_name }}</td> {% for crit_idx, crit in enumerate(criteria) %} <td> {{ form.scores[opt_idx][crit_idx](class="form-input") }} </td> {% endfor %} </tr> {% endfor %} </tbody> </table> {{ form.submit(class="btn btn-primary mt-3") }}
额外注意事项
- 数据隔离:如果你的应用支持多用户或多项目,不要直接
db.session.query(Scores).delete(),要加上过滤条件(比如Scores.case_id == current_case.id),避免误删其他数据。 - 数值校验:可以给
IntegerField加额外校验,比如NumberRange(min=0, max=100),限制评分范围。 - 会话存储:如果选项数和准则数是用户临时输入的,可以存在Flask的
session里,避免每次从数据库查询。
备注:内容来源于stack exchange,提问作者Scott
相关产品推荐
相关产品推荐

