You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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") }}

额外注意事项

  1. 数据隔离:如果你的应用支持多用户或多项目,不要直接db.session.query(Scores).delete(),要加上过滤条件(比如Scores.case_id == current_case.id),避免误删其他数据。
  2. 数值校验:可以给IntegerField加额外校验,比如NumberRange(min=0, max=100),限制评分范围。
  3. 会话存储:如果选项数和准则数是用户临时输入的,可以存在Flask的session里,避免每次从数据库查询。

备注:内容来源于stack exchange,提问作者Scott

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 18:14:50