如何用Flask将MySQL查询结果传递至HTML,复用表单加载产品数据
嘿,看起来你已经搞定了产品创建的表单,现在想复用这个模板来加载已有产品的组件、追踪更换后的新序列号对吧?结合Flask和MySQL,我给你梳理一套可行的实现方案,咱们一步步来:
1. 先把数据库结构捋顺
首先得确保你的MySQL表结构能支撑这个需求——毕竟要关联产品和组件,还要追踪更换记录对吧?假设你已经有products和components表,要是还没加追踪更换的功能,建议补个历史表,这样后续溯源更方便:
-- 产品主表(你应该已经有类似结构了) CREATE TABLE products ( product_id INT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(100) NOT NULL, -- 其他产品属性,比如型号、创建时间等 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 组件表,关联产品ID,序列号设为唯一避免重复 CREATE TABLE components ( component_id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, serial_number VARCHAR(50) UNIQUE NOT NULL, hardware_version VARCHAR(20) NOT NULL, FOREIGN KEY (product_id) REFERENCES products(product_id) ); -- 组件更换历史表,记录每次更换的旧/新序列号 CREATE TABLE component_history ( history_id INT PRIMARY KEY AUTO_INCREMENT, component_id INT NOT NULL, old_serial_number VARCHAR(50) NOT NULL, new_serial_number VARCHAR(50) NOT NULL, change_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (component_id) REFERENCES components(component_id) );
2. Flask后端的核心逻辑
2.1 加载已有产品和组件的路由
写一个路由,根据产品ID从数据库拉取产品主信息和关联的所有组件,然后传到你复用的表单模板里:
from flask import Flask, render_template, request, redirect, url_for import mysql.connector from mysql.connector import Error app = Flask(__name__) # 数据库配置建议用环境变量,别硬编码在代码里! db_config = { 'host': 'localhost', 'database': 'product_tracking', 'user': 'your_db_user', 'password': 'your_db_pwd' } def get_db_conn(): try: conn = mysql.connector.connect(**db_config) return conn except Error as e: print(f"数据库连接炸了: {e}") return None @app.route('/product/<int:product_id>/edit', methods=['GET']) def edit_product(product_id): conn = get_db_conn() if not conn: return "数据库连接失败", 500 cursor = conn.cursor(dictionary=True) # 拉取产品主信息 cursor.execute("SELECT * FROM products WHERE product_id = %s", (product_id,)) product = cursor.fetchone() if not product: return "找不到这个产品哦", 404 # 拉取该产品下的所有组件 cursor.execute("SELECT * FROM components WHERE product_id = %s", (product_id,)) components = cursor.fetchall() cursor.close() conn.close() # 把数据传到表单模板,复用创建页面的布局 return render_template('product_form.html', product=product, components=components)
2.2 处理组件更新/更换的路由
再写一个POST路由,接收表单提交的数据,处理组件的序列号更换(或新增组件),同时记录更换历史:
@app.route('/product/<int:product_id>/update', methods=['POST']) def update_product(product_id): conn = get_db_conn() if not conn: return "数据库连接失败", 500 cursor = conn.cursor() try: # 先更新产品主信息(如果表单里有需要修改的产品字段) product_name = request.form.get('product_name') cursor.execute("UPDATE products SET product_name = %s WHERE product_id = %s", (product_name, product_id)) # 处理组件数据:假设表单用component_id_0、component_serial_0这种命名方式 component_count = int(request.form.get('component_count', 0)) for i in range(component_count): component_id = request.form.get(f'component_id_{i}') new_serial = request.form.get(f'component_serial_{i}') new_version = request.form.get(f'component_version_{i}') if component_id: # 已有组件,检查序列号是否变更 cursor.execute("SELECT serial_number FROM components WHERE component_id = %s", (component_id,)) old_serial = cursor.fetchone()[0] if old_serial != new_serial: # 序列号变了,先记录历史 cursor.execute("INSERT INTO component_history (component_id, old_serial_number, new_serial_number) VALUES (%s, %s, %s)", (component_id, old_serial, new_serial)) # 更新组件的序列号和版本 cursor.execute("UPDATE components SET serial_number = %s, hardware_version = %s WHERE component_id = %s", (new_serial, new_version, component_id)) else: # 只有版本变更,直接更新 cursor.execute("UPDATE components SET hardware_version = %s WHERE component_id = %s", (new_version, component_id)) else: # 没有component_id,说明是新增组件 cursor.execute("INSERT INTO components (product_id, serial_number, hardware_version) VALUES (%s, %s, %s)", (product_id, new_serial, new_version)) conn.commit() except Error as e: conn.rollback() return f"更新出错了: {e}", 500 finally: cursor.close() conn.close() return redirect(url_for('edit_product', product_id=product_id))
3. 复用表单模板(product_form.html)
把你原来的创建表单改成支持编辑的模式,核心是判断是否传入了product和components数据,来切换创建/编辑状态:
<form method="POST" action="{% if product %}{{ url_for('update_product', product_id=product.product_id) }}{% else %}{{ url_for('create_product') }}{% endif %}"> <!-- 产品主信息字段 --> <div class="form-group"> <label class="control-label col-md-3">产品名称</label> <div class="col-md-9"> <input type="text" name="product_name" class="form-control" value="{{ product.product_name if product else '' }}" required> </div> </div> <!-- 组件区域 --> <div id="components-container"> <h4>组件信息</h4> <!-- 编辑状态下,循环渲染已有组件 --> {% if components %} {% for component in components %} <div class="component-row"> <input type="hidden" name="component_id_{{ loop.index0 }}" value="{{ component.component_id }}"> <div class="form-group"> <label class="control-label col-md-3">组件序列号</label> <div class="col-md-9"> <input type="text" name="component_serial_{{ loop.index0 }}" class="form-control" value="{{ component.serial_number }}" required> </div> </div> <div class="form-group"> <label class="control-label col-md-3">硬件版本</label> <div class="col-md-9"> <input type="text" name="component_version_{{ loop.index0 }}" class="form-control" value="{{ component.hardware_version }}" required> </div> </div> <button type="button" class="btn btn-danger remove-component">删除组件</button> </div> {% endfor %} {% endif %} <!-- 新增组件按钮 --> <button type="button" class="btn btn-primary" id="add-component">添加新组件</button> <!-- 隐藏字段记录组件数量,后端用来循环处理 --> <input type="hidden" name="component_count" id="component-count" value="{{ components|length if components else 0 }}"> </div> <button type="submit" class="btn btn-success mt-3">{% if product %}更新产品{% else %}创建产品{% endif %}</button> </form> <!-- 动态添加/删除组件的JS --> <script> // 添加组件逻辑 document.getElementById('add-component').addEventListener('click', function() { const container = document.getElementById('components-container'); const count = parseInt(document.getElementById('component-count').value); const newRow = document.createElement('div'); newRow.className = 'component-row mb-2'; newRow.innerHTML = ` <input type="hidden" name="component_id_${count}" value=""> <div class="form-group row"> <label class="control-label col-md-3">组件序列号</label> <div class="col-md-9"> <input type="text" name="component_serial_${count}" class="form-control" required> </div> </div> <div class="form-group row"> <label class="control-label col-md-3">硬件版本</label> <div class="col-md-9"> <input type="text" name="component_version_${count}" class="form-control" required> </div> </div> <button type="button" class="btn btn-danger remove-component">删除组件</button> `; container.insertBefore(newRow, document.getElementById('add-component')); document.getElementById('component-count').value = count + 1; // 给新的删除按钮绑定事件 newRow.querySelector('.remove-component').addEventListener('click', function() { newRow.remove(); document.getElementById('component-count').value = document.querySelectorAll('.component-row').length; }); }); // 已有组件的删除逻辑 document.querySelectorAll('.remove-component').forEach(btn => { btn.addEventListener('click', function() { this.parentElement.remove(); document.getElementById('component-count').value = document.querySelectorAll('.component-row').length; }); }); </script>
几个关键注意事项
- 事务处理:上面的更新路由里加了
try-except和事务回滚,避免部分更新失败导致数据不一致,这个很重要! - SQL注入防护:全程用参数化查询(
%s占位符),别直接拼SQL字符串,安全第一。 - 序列号唯一性:数据库里已经给
serial_number加了UNIQUE约束,后端要捕获重复序列号的异常,给用户友好提示。 - 生产环境优化:数据库配置别硬编码,用环境变量或者配置文件;可以加个缓存来减少数据库查询次数。
这样你就能完美复用原来的表单模板,实现加载已有产品组件、追踪更换记录的功能啦!
内容的提问来源于stack exchange,提问作者Colin Roberts




