Race-Inventorytools 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/MANIFEST.in +5 -0
  2. {race_inventorytools-0.1.0/src/Race_Inventorytools.egg-info → race_inventorytools-0.2.0}/PKG-INFO +2 -2
  3. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/README.md +1 -1
  4. race_inventorytools-0.2.0/app.py +351 -0
  5. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/pyproject.toml +1 -1
  6. race_inventorytools-0.2.0/scripts/download_dependencies.ps1 +15 -0
  7. race_inventorytools-0.2.0/scripts/install_offline.ps1 +8 -0
  8. race_inventorytools-0.2.0/scripts/upload_pypi.ps1 +13 -0
  9. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0/src/Race_Inventorytools.egg-info}/PKG-INFO +2 -2
  10. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/src/Race_Inventorytools.egg-info/SOURCES.txt +6 -0
  11. race_inventorytools-0.2.0/src/flask_crud_competition/app1.py +98 -0
  12. race_inventorytools-0.2.0/src/flask_crud_competition/demo.py +31 -0
  13. {race_inventorytools-0.1.0/src/flask_crud_competition → race_inventorytools-0.2.0}/app1.py +0 -0
  14. {race_inventorytools-0.1.0/src/flask_crud_competition → race_inventorytools-0.2.0}/demo.py +0 -0
  15. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/requirements.txt +0 -0
  16. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/setup.cfg +0 -0
  17. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/src/Race_Inventorytools.egg-info/dependency_links.txt +0 -0
  18. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/src/Race_Inventorytools.egg-info/entry_points.txt +0 -0
  19. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/src/Race_Inventorytools.egg-info/requires.txt +0 -0
  20. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/src/Race_Inventorytools.egg-info/top_level.txt +0 -0
  21. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/src/flask_crud_competition/__init__.py +0 -0
  22. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/src/flask_crud_competition/__main__.py +0 -0
  23. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/src/flask_crud_competition/app.py +0 -0
  24. {race_inventorytools-0.1.0 → race_inventorytools-0.2.0}/src/flask_crud_competition/templates/index.html +0 -0
@@ -1,3 +1,8 @@
1
1
  include README.md
2
2
  include requirements.txt
3
+ include app.py
4
+ include app1.py
5
+ include demo.py
6
+ graft scripts
7
+ recursive-include src *.py *.html
3
8
  recursive-include src/flask_crud_competition/templates *.html
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Race-Inventorytools
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A Flask-Admin inventory CRUD demo application.
5
5
  Author: Competition Demo Team
6
6
  Keywords: flask,crud,inventory,admin,sqlite
@@ -23,7 +23,7 @@ Requires-Dist: Flask-Babel==4.0.0
23
23
 
24
24
  # Race Inventory CRUD Demo
25
25
 
26
- 这是一个用于演示的 Flask-Admin 库存 CRUD 应用,包含产品管理和出入库流水管理。
26
+ Flask-Admin快速构建应用工具
27
27
 
28
28
  ## 安装运行
29
29
 
@@ -1,6 +1,6 @@
1
1
  # Race Inventory CRUD Demo
2
2
 
3
- 这是一个用于演示的 Flask-Admin 库存 CRUD 应用,包含产品管理和出入库流水管理。
3
+ Flask-Admin快速构建应用工具
4
4
 
5
5
  ## 安装运行
6
6
 
@@ -0,0 +1,351 @@
1
+ from datetime import datetime
2
+
3
+ from flask import Flask, flash
4
+ from flask_babel import Babel
5
+ from flask_sqlalchemy import SQLAlchemy
6
+ from flask_admin import Admin, AdminIndexView
7
+ from flask_admin.contrib.sqla import ModelView
8
+ from flask_admin.form import SecureForm
9
+ from wtforms import validators
10
+
11
+ # ===================== 初始化Flask与数据库 =====================
12
+ app = Flask(__name__)
13
+ # 密钥必须配置,表单校验、flash提示依赖
14
+ app.config["SECRET_KEY"] = "race_demo_2026_secret_key_123456"
15
+ app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data.db"
16
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
17
+ # Flask-Admin / WTForms 中文化
18
+ app.config["BABEL_DEFAULT_LOCALE"] = "zh_Hans_CN"
19
+ app.config["BABEL_DEFAULT_TIMEZONE"] = "Asia/Shanghai"
20
+
21
+ db = SQLAlchemy(app)
22
+ # 固定使用简体中文,后台按钮/提示全部中文化
23
+ babel = Babel(app, locale_selector=lambda: "zh_Hans_CN")
24
+
25
+ # ===================== 数据库模型(产品表) =====================
26
+ class Product(db.Model):
27
+ __tablename__ = "product"
28
+ id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment="主键ID")
29
+ code = db.Column(db.String(30), unique=True, nullable=False, comment="产品编号(唯一)")
30
+ name = db.Column(db.String(100), nullable=False, comment="产品名称")
31
+ count = db.Column(db.Integer, nullable=False, comment="库存数量")
32
+ price = db.Column(db.Float, nullable=False, comment="单价")
33
+ remark = db.Column(db.Text, comment="备注信息")
34
+ stock_records = db.relationship(
35
+ "StockRecord",
36
+ back_populates="product",
37
+ cascade="all, delete-orphan"
38
+ )
39
+
40
+ def __repr__(self):
41
+ return f"{self.code} - {self.name}"
42
+
43
+
44
+ # ===================== 数据库模型(出入库流水表) =====================
45
+ class StockRecord(db.Model):
46
+ __tablename__ = "stock_record"
47
+ id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment="主键ID")
48
+ product_id = db.Column(db.Integer, db.ForeignKey("product.id"), nullable=False, comment="产品ID")
49
+ type = db.Column(db.String(10), nullable=False, comment="操作类型")
50
+ quantity = db.Column(db.Integer, nullable=False, comment="出入库数量")
51
+ before_count = db.Column(db.Integer, nullable=False, default=0, comment="操作前库存")
52
+ after_count = db.Column(db.Integer, nullable=False, default=0, comment="操作后库存")
53
+ operator = db.Column(db.String(50), nullable=False, default="管理员", comment="操作人")
54
+ created_at = db.Column(db.DateTime, nullable=False, default=datetime.now, comment="操作时间")
55
+ remark = db.Column(db.Text, comment="备注信息")
56
+ product = db.relationship("Product", back_populates="stock_records")
57
+
58
+ def __repr__(self):
59
+ product_name = self.product.name if self.product else "未知产品"
60
+ return f"{product_name} {self.type} {self.quantity}"
61
+
62
+ # ===================== 自定义Admin视图 + 表单校验核心 =====================
63
+ class HiddenAdminIndexView(AdminIndexView):
64
+ def is_visible(self):
65
+ return False
66
+
67
+
68
+ class ProductAdmin(ModelView):
69
+ # 开启CSRF安全表单
70
+ form_base_class = SecureForm
71
+
72
+ # 列表页弹窗新增/编辑,不跳转新页面
73
+ create_modal = True
74
+ edit_modal = True
75
+
76
+ # 补充中文标题(Flask-Babel 覆盖 Create/Save/Delete 等按钮)
77
+ create_modal_title = "新建产品"
78
+ edit_modal_title = "编辑产品"
79
+ details_modal_title = "详情"
80
+
81
+ # 页面展示配置
82
+ column_list = ["id", "code", "name", "count", "price", "remark"] # 列表显示字段
83
+ column_searchable_list = ["code", "name"] # 可搜索字段
84
+ column_sortable_list = ["id", "count", "price"] # 可排序
85
+ column_labels = {
86
+ "id": "序号",
87
+ "code": "产品编号",
88
+ "name": "产品名称",
89
+ "count": "库存数量",
90
+
91
+ "price": "单价(元)",
92
+ "remark": "备注"
93
+ }
94
+
95
+ # 表单字段顺序
96
+ form_columns = ["code", "name", "count", "price", "remark"]
97
+
98
+ # 全局基础校验规则
99
+ form_args = {
100
+ "code": {
101
+ "validators": [
102
+ validators.DataRequired(message="产品编号不能为空"),
103
+ validators.Length(min=3, max=30, message="编号长度3~30位")
104
+ ]
105
+ },
106
+ "name": {
107
+ "validators": [
108
+ validators.DataRequired(message="产品名称不能为空"),
109
+ validators.Length(min=2, max=100, message="名称长度2~100位")
110
+ ]
111
+ },
112
+ "count": {
113
+ "validators": [validators.InputRequired(message="库存不能为空")]
114
+ },
115
+ "price": {
116
+ "validators": [validators.DataRequired(message="价格不能为空")]
117
+ }
118
+ }
119
+
120
+ # 【重点:自定义业务校验】仅新增/编辑执行;删除表单无业务字段
121
+ def validate_form(self, form):
122
+ # 删除确认表单(DeleteForm)只有 id/csrf,没有 count/code 等字段
123
+ if not hasattr(form, "code") or not hasattr(form, "count") or not hasattr(form, "price"):
124
+ return super().validate_form(form)
125
+
126
+ # 1. 校验库存不能小于0,出库后允许库存归零
127
+ if form.count.data is not None and form.count.data < 0:
128
+ flash("库存数量不能小于0!", "error")
129
+ return False
130
+
131
+ # 2. 校验价格必须大于0
132
+ if form.price.data is not None and form.price.data <= 0:
133
+ flash("商品单价必须大于0!", "error")
134
+ return False
135
+
136
+ # 3. 校验产品编号唯一性(区分新增和编辑)
137
+ code = (form.code.data or "").strip()
138
+ if not code:
139
+ return super().validate_form(form)
140
+
141
+ # 获取当前编辑对象(编辑时存在,新增为None)
142
+ model_id = form._obj.id if getattr(form, "_obj", None) else None
143
+ exist = Product.query.filter_by(code=code).first()
144
+
145
+ # 新增:编号已存在
146
+ if model_id is None and exist:
147
+ flash(f"编号 {code} 已存在,不可重复!", "error")
148
+ return False
149
+ # 编辑:修改为其他已存在编号
150
+ if model_id is not None and exist and exist.id != model_id:
151
+ flash(f"编号 {code} 已被其他产品占用!", "error")
152
+ return False
153
+
154
+ return super().validate_form(form)
155
+
156
+
157
+ # ===================== 自定义Admin视图(出入库管理) =====================
158
+ class StockRecordAdmin(ModelView):
159
+ form_base_class = SecureForm
160
+
161
+ create_modal = True
162
+ edit_modal = True
163
+ can_view_details = True
164
+
165
+ create_modal_title = "新建出入库记录"
166
+ edit_modal_title = "编辑出入库记录"
167
+ details_modal_title = "出入库详情"
168
+
169
+ column_list = [
170
+ "id",
171
+ "product",
172
+ "type",
173
+ "quantity",
174
+ "before_count",
175
+ "after_count",
176
+ "operator",
177
+ "created_at",
178
+ "remark"
179
+ ]
180
+ column_searchable_list = ["operator", "remark"]
181
+ column_sortable_list = ["id", "type", "quantity", "before_count", "after_count", "created_at"]
182
+ column_filters = ["type", "operator", "created_at"]
183
+ column_default_sort = ("id", True)
184
+ column_labels = {
185
+ "id": "序号",
186
+ "product": "产品",
187
+ "type": "类型",
188
+ "quantity": "数量",
189
+ "before_count": "操作前库存",
190
+ "after_count": "操作后库存",
191
+ "operator": "操作人",
192
+ "created_at": "操作时间",
193
+ "remark": "备注"
194
+ }
195
+ column_formatters = {
196
+ "created_at": lambda view, context, model, name: (
197
+ model.created_at.strftime("%Y-%m-%d %H:%M:%S") if model.created_at else ""
198
+ )
199
+ }
200
+
201
+ form_columns = ["product", "type", "quantity", "operator", "remark"]
202
+ form_choices = {
203
+ "type": [("入库", "入库"), ("出库", "出库")]
204
+ }
205
+ form_args = {
206
+ "product": {
207
+ "validators": [validators.DataRequired(message="产品不能为空")]
208
+ },
209
+ "type": {
210
+ "validators": [
211
+ validators.DataRequired(message="类型不能为空"),
212
+ validators.AnyOf(["入库", "出库"], message="类型只能选择入库或出库")
213
+ ]
214
+ },
215
+ "quantity": {
216
+ "validators": [validators.DataRequired(message="数量不能为空")]
217
+ },
218
+ "operator": {
219
+ "validators": [
220
+ validators.DataRequired(message="操作人不能为空"),
221
+ validators.Length(min=2, max=50, message="操作人长度2~50位")
222
+ ]
223
+ }
224
+ }
225
+
226
+ @staticmethod
227
+ def _stock_effect(record_type, quantity):
228
+ return quantity if record_type == "入库" else -quantity
229
+
230
+ def _get_old_stock_info(self, form):
231
+ old_model = getattr(form, "_obj", None)
232
+ if not old_model or not old_model.id:
233
+ return None
234
+ return {
235
+ "product_id": old_model.product_id,
236
+ "type": old_model.type,
237
+ "quantity": old_model.quantity
238
+ }
239
+
240
+ def _preview_stock_change(self, product, record_type, quantity, old_info=None):
241
+ old_effect = 0
242
+ if old_info and old_info["product_id"] == product.id:
243
+ old_effect = self._stock_effect(old_info["type"], old_info["quantity"])
244
+
245
+ before_count = (product.count or 0) - old_effect
246
+ after_count = before_count + self._stock_effect(record_type, quantity)
247
+ return before_count, after_count
248
+
249
+ def _preview_old_product_after_rollback(self, old_info, new_product_id):
250
+ if not old_info or old_info["product_id"] == new_product_id:
251
+ return None, None
252
+
253
+ old_product = Product.query.get(old_info["product_id"])
254
+ if not old_product:
255
+ return None, None
256
+
257
+ old_effect = self._stock_effect(old_info["type"], old_info["quantity"])
258
+ return old_product, (old_product.count or 0) - old_effect
259
+
260
+ def validate_form(self, form):
261
+ if not super().validate_form(form):
262
+ return False
263
+
264
+ if not hasattr(form, "product") or not hasattr(form, "type") or not hasattr(form, "quantity"):
265
+ return True
266
+
267
+ product = form.product.data
268
+ record_type = form.type.data
269
+ quantity = form.quantity.data
270
+ old_info = self._get_old_stock_info(form)
271
+ form._old_stock_info = old_info
272
+
273
+ if not product:
274
+ flash("请选择产品!", "error")
275
+ return False
276
+
277
+ if quantity is None or quantity <= 0:
278
+ flash("出入库数量必须大于0!", "error")
279
+ return False
280
+
281
+ old_product, old_product_after = self._preview_old_product_after_rollback(old_info, product.id)
282
+ if old_product and old_product_after < 0:
283
+ flash(f"调整后 {old_product.name} 的库存会小于0,请先调整相关出库记录!", "error")
284
+ return False
285
+
286
+ before_count, after_count = self._preview_stock_change(product, record_type, quantity, old_info)
287
+ if after_count < 0:
288
+ flash(f"库存不足,当前可出库数量为 {before_count}!", "error")
289
+ return False
290
+
291
+ return True
292
+
293
+ def on_model_change(self, form, model, is_created):
294
+ old_info = getattr(form, "_old_stock_info", None)
295
+ product = model.product
296
+ before_count, after_count = self._preview_stock_change(
297
+ product,
298
+ model.type,
299
+ model.quantity,
300
+ old_info
301
+ )
302
+
303
+ if old_info and old_info["product_id"] != product.id:
304
+ old_product = Product.query.get(old_info["product_id"])
305
+ if old_product:
306
+ old_product.count = (old_product.count or 0) - self._stock_effect(
307
+ old_info["type"],
308
+ old_info["quantity"]
309
+ )
310
+
311
+ model.before_count = before_count
312
+ model.after_count = after_count
313
+ product.count = after_count
314
+
315
+ return super().on_model_change(form, model, is_created)
316
+
317
+ def delete_model(self, model):
318
+ product = model.product
319
+ after_count = (product.count or 0) - self._stock_effect(model.type, model.quantity)
320
+ if after_count < 0:
321
+ flash("删除该入库记录会导致库存小于0,请先调整相关出库记录!", "error")
322
+ return False
323
+
324
+ try:
325
+ product.count = after_count
326
+ self.session.delete(model)
327
+ self.session.commit()
328
+ flash("出入库记录删除成功,库存已同步回滚。", "success")
329
+ return True
330
+ except Exception:
331
+ self.session.rollback()
332
+ raise
333
+
334
+ # ===================== 初始化后台管理 =====================
335
+ admin = Admin(
336
+ app,
337
+ name="管理后台",
338
+ template_mode="bootstrap3",
339
+ index_view=HiddenAdminIndexView(url="/admin", endpoint="admin")
340
+ )
341
+ admin.add_view(ProductAdmin(Product, db.session, name="产品管理", category="库存管理"))
342
+ admin.add_view(StockRecordAdmin(StockRecord, db.session, name="出入库管理", category="库存管理"))
343
+
344
+ # ===================== 创建数据库表(程序启动自动执行) =====================
345
+ with app.app_context():
346
+ db.create_all()
347
+ print("数据库表初始化完成,访问 http://127.0.0.1:5000/admin 使用后台")
348
+
349
+ # ===================== 启动入口 =====================
350
+ if __name__ == "__main__":
351
+ app.run(debug=True, host="127.0.0.1", port=5000)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "Race-Inventorytools"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "A Flask-Admin inventory CRUD demo application."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,15 @@
1
+ param(
2
+ [string]$OutputDir = "wheelhouse",
3
+ [string]$Python = "python"
4
+ )
5
+
6
+ $ErrorActionPreference = "Stop"
7
+
8
+ New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
9
+ & $Python -m pip download --dest $OutputDir -r requirements.txt
10
+
11
+ if (Test-Path "dist") {
12
+ Copy-Item -Path "dist\*.whl" -Destination $OutputDir -Force -ErrorAction SilentlyContinue
13
+ }
14
+
15
+ Write-Host "Dependencies are ready in $OutputDir"
@@ -0,0 +1,8 @@
1
+ param(
2
+ [string]$Wheelhouse = "wheelhouse",
3
+ [string]$Python = "python"
4
+ )
5
+
6
+ $ErrorActionPreference = "Stop"
7
+
8
+ & $Python -m pip install --no-index --find-links $Wheelhouse Race-Inventorytools
@@ -0,0 +1,13 @@
1
+ param(
2
+ [string]$Repository = "pypi",
3
+ [string]$Python = "python"
4
+ )
5
+
6
+ $ErrorActionPreference = "Stop"
7
+
8
+ if (-not (Test-Path "dist")) {
9
+ throw "dist directory not found. Run: python -m build"
10
+ }
11
+
12
+ & $Python -m twine check dist\*
13
+ & $Python -m twine upload --repository $Repository dist\*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Race-Inventorytools
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A Flask-Admin inventory CRUD demo application.
5
5
  Author: Competition Demo Team
6
6
  Keywords: flask,crud,inventory,admin,sqlite
@@ -23,7 +23,7 @@ Requires-Dist: Flask-Babel==4.0.0
23
23
 
24
24
  # Race Inventory CRUD Demo
25
25
 
26
- 这是一个用于演示的 Flask-Admin 库存 CRUD 应用,包含产品管理和出入库流水管理。
26
+ Flask-Admin快速构建应用工具
27
27
 
28
28
  ## 安装运行
29
29
 
@@ -1,7 +1,13 @@
1
1
  MANIFEST.in
2
2
  README.md
3
+ app.py
4
+ app1.py
5
+ demo.py
3
6
  pyproject.toml
4
7
  requirements.txt
8
+ scripts/download_dependencies.ps1
9
+ scripts/install_offline.ps1
10
+ scripts/upload_pypi.ps1
5
11
  src/Race_Inventorytools.egg-info/PKG-INFO
6
12
  src/Race_Inventorytools.egg-info/SOURCES.txt
7
13
  src/Race_Inventorytools.egg-info/dependency_links.txt
@@ -0,0 +1,98 @@
1
+ # 1. 先写 Flask 基础结构:app = Flask(__name__)
2
+ # 2. 再写数据库:数据库文件名、建表函数 init_db()
3
+ # 3. 再写数据库操作函数:查、新增、修改、删除
4
+ # 4. 前端页面已拆分到 templates/index.html
5
+ # 5. 最后写路由:/、/edit/<id>、/save、/del/<id>
6
+
7
+ # 可以记成一句话:先数据表,再操作函数,再页面,最后接路由。
8
+
9
+ from flask import Flask, render_template, request, redirect, url_for # 从 Flask 导入需要用到的工具:创建网站、渲染模板、读取表单、跳转页面、生成路由地址
10
+ import sqlite3 # 导入 Python 自带的 sqlite3 模块,用来操作本地 SQLite 数据库文件
11
+
12
+ app = Flask(__name__) # 创建一个 Flask 应用对象,__name__ 表示当前这个 Python 文件
13
+ DB_FILE = "user.db" # 定义数据库文件名,后面所有数据库操作都连接这个文件
14
+
15
+
16
+ # 初始化数据表:程序启动时先保证 user 表存在
17
+ def init_db(): # 定义一个函数,专门负责创建数据库表
18
+ conn = sqlite3.connect(DB_FILE) # 连接数据库;如果 user.db 不存在,SQLite 会自动创建
19
+ c = conn.cursor() # 创建游标对象,游标可以执行 SQL 语句
20
+ c.execute('''CREATE TABLE IF NOT EXISTS user
21
+ (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, phone TEXT)''') # 如果 user 表不存在,就创建一个包含 id、name、phone 的表
22
+ conn.commit() # 提交更改,让建表操作真正保存到数据库文件
23
+ conn.close() # 关闭数据库连接,避免资源一直占用
24
+
25
+
26
+ # 查询全部用户:给首页表格使用
27
+ def get_all_users(): # 定义查询函数,返回数据库里所有用户数据
28
+ conn = sqlite3.connect(DB_FILE) # 打开数据库连接
29
+ data = conn.execute("SELECT * FROM user").fetchall() # 执行查询语句,并用 fetchall 取出全部结果
30
+ conn.close() # 查询完成后关闭连接
31
+ return data # 把查询到的数据返回给调用者,格式类似 [(1, "张三", "138..."), ...]
32
+
33
+
34
+ # 新增用户:把表单里的姓名和电话插入数据库
35
+ def add_user(name, phone): # 定义新增函数,需要传入 name 和 phone
36
+ conn = sqlite3.connect(DB_FILE) # 打开数据库连接
37
+ conn.execute("INSERT INTO user(name,phone) VALUES (?,?)", (name, phone)) # 用占位符 ? 插入数据,避免直接拼接字符串
38
+ conn.commit() # 提交事务,让新增的数据真正写入数据库
39
+ conn.close() # 关闭数据库连接
40
+
41
+
42
+ # 修改用户:根据 id 更新姓名和电话
43
+ def edit_user(uid, name, phone): # 定义修改函数,uid 是要修改的用户编号
44
+ conn = sqlite3.connect(DB_FILE) # 打开数据库连接
45
+ conn.execute("UPDATE user SET name=?,phone=? WHERE id=?", (name, phone, uid)) # 按 id 找到对应用户,并更新 name 和 phone
46
+ conn.commit() # 提交事务,让修改结果保存
47
+ conn.close() # 关闭数据库连接
48
+
49
+
50
+ # 删除用户:根据 id 删除一条记录
51
+ def del_user(uid): # 定义删除函数,uid 是要删除的用户编号
52
+ conn = sqlite3.connect(DB_FILE) # 打开数据库连接
53
+ conn.execute("DELETE FROM user WHERE id=?", (uid,)) # 删除 id 等于 uid 的用户;单个参数也要写成元组,所以这里有逗号
54
+ conn.commit() # 提交事务,让删除操作生效
55
+ conn.close() # 关闭数据库连接
56
+
57
+
58
+ # 首页路由:展示全部数据
59
+ @app.route('/') # 当浏览器访问 / 时,Flask 会执行下面的 index 函数
60
+ def index(): # 定义首页处理函数
61
+ return render_template("index.html", list_data=get_all_users(), edit_row=None) # 渲染前端模板,把用户列表传进去;首页没有编辑对象,所以 edit_row 是 None
62
+
63
+
64
+ # 编辑路由:点击编辑按钮后,回填对应用户的数据到表单
65
+ @app.route('/edit/<int:uid>') # <int:uid> 表示从网址里接收一个整数参数,例如 /edit/3
66
+ def edit(uid): # uid 会自动接收网址里的用户 id
67
+ rows = get_all_users() # 先查询全部用户,表格还要继续展示这些数据
68
+ target = None # 先假设没有找到要编辑的那一行
69
+ for r in rows: # 遍历所有用户记录
70
+ if r[0] == uid: # r[0] 是用户 id,如果等于网址里的 uid,说明找到了
71
+ target = r # 把这条记录保存到 target,用来回填表单
72
+ break # 找到后就停止循环,没必要继续找
73
+ return render_template("index.html", list_data=rows, edit_row=target) # 渲染同一个页面,但把 target 传给 edit_row,实现表单回填
74
+
75
+
76
+ # 保存路由:新增和修改共用这一个接口
77
+ @app.route('/save', methods=["POST"]) # 只有 POST 请求才能访问这个地址,因为它负责接收表单提交
78
+ def save(): # 定义保存处理函数
79
+ uid = request.form.get("id") # 从表单中取隐藏字段 id;有 id 表示编辑,没有 id 表示新增
80
+ name = request.form["name"] # 从表单中取姓名;因为姓名 required,所以这里直接用 [] 读取
81
+ phone = request.form["phone"] # 从表单中取电话
82
+ if uid: # 如果 uid 有值,说明这次提交是修改已有用户
83
+ edit_user(uid, name, phone) # 调用修改函数,按 id 更新数据
84
+ else: # 如果 uid 为空,说明这次提交是新增用户
85
+ add_user(name, phone) # 调用新增函数,插入一条新数据
86
+ return redirect(url_for("index")) # 保存完成后跳转回首页,避免刷新页面重复提交表单
87
+
88
+
89
+ # 删除路由:根据网址里的 id 删除用户
90
+ @app.route('/del/<int:uid>') # 访问 /del/用户id 时,执行下面的 delete 函数
91
+ def delete(uid): # uid 自动接收网址里的用户 id
92
+ del_user(uid) # 调用删除函数,从数据库删除对应用户
93
+ return redirect(url_for("index")) # 删除后跳转回首页,重新显示最新列表
94
+
95
+
96
+ if __name__ == '__main__': # 只有直接运行这个文件时,下面的代码才会执行;被别的文件导入时不会执行
97
+ init_db() # 启动网站前先初始化数据库表,确保 user 表存在
98
+ app.run(debug=True) # 启动 Flask 开发服务器;debug=True 方便开发时看到错误并自动重载
@@ -0,0 +1,31 @@
1
+ from flask import Flask
2
+ from flask_admin import Admin
3
+ from flask_admin.contrib.sqla import ModelView
4
+ from flask_sqlalchemy import SQLAlchemy
5
+
6
+ app = Flask(__name__)
7
+ app.config['SECRET_KEY'] = 'race_demo_2026_secret_key_123456'
8
+ # 数据库配置
9
+ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///demo.db'
10
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
11
+
12
+ db = SQLAlchemy(app)
13
+
14
+ class User(db.Model):
15
+ id = db.Column(db.Integer, primary_key=True)
16
+ name = db.Column(db.String(128))
17
+
18
+ # 首页路由
19
+ @app.route('/')
20
+ def index():
21
+ return '<h3>首页</h3><p>后台管理地址:<a href="/admin">/admin</a></p>'
22
+
23
+ admin = Admin(app, name='My Admin', template_mode='bootstrap3')
24
+ admin.add_view(ModelView(User, db.session))
25
+
26
+ # 创建数据表
27
+ with app.app_context():
28
+ db.create_all()
29
+
30
+ if __name__ == '__main__':
31
+ app.run(debug=True)