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

Python+Flask+MySQL项目报错:jinja2 UndefinedError问题求助

问题:Jinja2中'tuple object' has no attribute 'keys'错误解决

我正在开发一个基于Python+Flask+MySQL的小型项目,包含登录、注册、表格列表、上传Excel创建表格、表格查看、CRUD操作及登出功能。查看数据库表格时出现以下错误:

Error: "jinja2.exceptions.UndefinedError: 'tuple object' has no attribute 'keys'"

我的Python代码如下:

from flask import Flask, render_template, request, redirect, url_for, session, flash
from flask_mysqldb import MySQL
from werkzeug.security import generate_password_hash, check_password_hash
import os
import pandas as pd
import pymysql


app = Flask(__name__)
app.secret_key = 'your-secret-key'

# Configure the upload folder
app.config['UPLOAD_FOLDER'] = 'uploads'

# MySQL configurations
app.config['MYSQL_HOST'] = 'localhost'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = '1234'
app.config['MYSQL_DB'] = 'userprofile'
mysql = MySQL(app)


@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        cur = mysql.connection.cursor()
        cur.execute("SELECT * FROM users WHERE username = %s", (username,))
        user = cur.fetchone()
        cur.close()

        if user and check_password_hash(user[2], password):
            session['user_id'] = user[0]
            session['username'] = user[1]
            return redirect('/tables')
        else:
            flash('Invalid username or password', 'error')
            return render_template('login.html')

    return render_template('login.html')


@app.route('/signup', methods=['GET', 'POST'])
def signup():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        hashed_password = generate_password_hash(password)

        cur = mysql.connection.cursor()
        cur.execute("INSERT INTO users (username, password) VALUES (%s, %s)", (username, hashed_password))
        mysql.connection.commit()
        cur.close()

        flash('Registration successful! Please login.', 'success')
        return redirect(url_for('login'))

    return render_template('signup.html')


@app.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('login'))


@app.route('/tables', methods=['GET'])
def tables():
    if 'user_id' not in session:
        return redirect(url_for('login'))

    cur = mysql.connection.cursor()
    cur.execute("SHOW TABLES")
    tables = [table[0] for table in cur.fetchall()]
    cur.close()

    return render_template('tables.html', tables=tables)

@app.route('/upload', methods=['POST'])
def upload():
    if 'user_id' not in session:
        return redirect(url_for('login'))

    file = request.files['file']
    if file.filename == '':
        flash('No file selected', 'error')
        return redirect(url_for('tables'))

    # Save the uploaded file to a temporary location
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
    file.save(file_path)

    # Read the Excel file and create table in the database
    try:
        df = pd.read_excel(file_path)
        table_name = os.path.splitext(file.filename)[0]

        # MySQL connection and cursor
        conn = pymysql.connect(
            host=app.config['MYSQL_HOST'],
            user=app.config['MYSQL_USER'],
            password=app.config['MYSQL_PASSWORD'],
            db=app.config['MYSQL_DB'],
            cursorclass=pymysql.cursors.DictCursor
        )
        cur = conn.cursor()

        # Drop table if exists
        cur.execute(f"DROP TABLE IF EXISTS `{table_name}`")

        # Create table
        columns = ", ".join([f"`{column}` VARCHAR(255)" for column in df.columns])
        create_table_query = f"CREATE TABLE `{table_name}` ({columns})"
        cur.execute(create_table_query)

        # Insert data
        for _, row in df.iterrows():
            values = ", ".join([f"'{str(value)}'" for value in row])
            insert_query = f"INSERT INTO `{table_name}` VALUES ({values})"
            cur.execute(insert_query)

        # Commit the changes
        conn.commit()

        flash(f'Table {table_name} created successfully', 'success')
    except Exception as e:
        flash(f'Error uploading file: {str(e)}', 'error')

    # Remove the temporary file
    os.remove(file_path)

    return redirect(url_for('tables'))


@app.route('/view_table/<table_name>', methods=['GET'])
def view_table(table_name):
    if 'user_id' not in session:
        return redirect(url_for('login'))

    cur = mysql.connection.cursor()
    cur.execute(f"SELECT * FROM {table_name}")
    rows = cur.fetchall()
    cur.close()

    return render_template('view_table.html', table_name=table_name, rows=rows)


@app.route('/edit_row/<table_name>/<int:row_id>', methods=['GET', 'POST'])
def edit_row(table_name, row_id):
    if 'user_id' not in session:
        return redirect(url_for('login'))

    cur = mysql.connection.cursor()
    cur.execute(f"SELECT * FROM {table_name} WHERE id = %s", (row_id,))
    row = cur.fetchone()

    if request.method == 'POST':
        new_values = tuple(request.form.get(f'field_{i}') for i in range(len(row)))
        query = f"UPDATE {table_name} SET {', '.join([f'field_{i} = %s' for i in range(len(row))])} WHERE id = %s"
        cur.execute(query, new_values + (row_id,))
        mysql.connection.commit()
        flash('Row updated successfully', 'success')
        return redirect(url_for('view_table', table_name=table_name))

    cur.close()
    return render_template('edit_row.html', table_name=table_name, row=row)


@app.route('/delete_row/<table_name>/<int:row_id>', methods=['GET'])
def delete_row(table_name, row_id):
    if 'user_id' not in session:
        return redirect(url_for('login'))

    cur = mysql.connection.cursor()
    cur.execute(f"DELETE FROM {table_name} WHERE id = %s", (row_id,))
    mysql.connection.commit()
    cur.close()

    flash('Row deleted successfully', 'success')
    return redirect(url_for('view_table', table_name=table_name))


if __name__ == '__main__':
    app.run(debug=True)

错误原因及修复方案

1. 核心错误原因

Flask-MySQLdb默认返回**元组(tuple)**类型的查询结果,但你的Jinja2模板中大概率在尝试用字典的方式访问数据(比如row['column_name']或row.keys()),而元组不支持这种键名访问,只能通过索引(如row[0])获取值,因此触发该错误。

2. 直接修复:使用字典游标

修改所有查询数据的函数,创建游标时指定DictCursor,让查询结果以字典形式返回,支持键名访问:

# 在view_table、edit_row、delete_row等函数中修改游标创建代码
cur = mysql.connection.cursor(pymysql.cursors.DictCursor)

注:代码中已导入pymysql,无需额外导入

3. 额外问题修复

(1)上传Excel时缺少主键

你上传Excel创建表格的代码中没有添加自增主键id,导致后续编辑、删除操作的WHERE id = %s会失效。修改创建表格的语句:

# 原创建语句
# create_table_query = f"CREATE TABLE `{table_name}` ({columns})"
# 修改后添加主键
create_table_query = f"CREATE TABLE `{table_name}` (id INT AUTO_INCREMENT PRIMARY KEY, {columns})"

(2)编辑功能的列名错误

edit_row函数中用field_{i}作为列名,但实际表格列名是Excel中的列名,需先获取列名再构造更新语句:

@app.route('/edit_row/<table_name>/<int:row_id>', methods=['GET', 'POST'])
def edit_row(table_name, row_id):
    if 'user_id' not in session:
        return redirect(url_for('login'))

    cur = mysql.connection.cursor(pymysql.cursors.DictCursor)
    # 获取表格所有列名
    cur.execute(f"DESCRIBE {table_name}")
    columns = [col['Field'] for col in cur.fetchall()]
    # 获取要编辑的行
    cur.execute(f"SELECT * FROM {table_name} WHERE id = %s", (row_id,))
    row = cur.fetchone()

    if request.method == 'POST':
        # 构造更新字段(排除id)
        update_pairs = [f"`{col}` = %s" for col in columns if col != 'id']
        new_values = tuple(request.form[col] for col in columns if col != 'id')
        query = f"UPDATE `{table_name}` SET {', '.join(update_pairs)} WHERE id = %s"
        cur.execute(query, new_values + (row_id,))
        mysql.connection.commit()
        flash('Row updated successfully', 'success')
        return redirect(url_for('view_table', table_name=table_name))

    cur.close()
    return render_template('edit_row.html', table_name=table_name, row=row, columns=columns)

对应模板edit_row.html也要改成用列名渲染:

{% for col in columns %}
  {% if col != 'id' %}
  <div class="form-group">
    <label>{{ col }}</label>
    <input type="text" name="{{ col }}" value="{{ row[col] }}" class="form-control">
  </div>
  {% endif %}
{% endfor %}

内容的提问来源于stack exchange,提问作者Abinesh S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 09:08:14