十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Flask框架的电影管理系统源码解析与重构实践

基于Flask框架的电影管理系统源码解析与重构实践 简介基于Flask框架的电影管理系统完整源码适合Python Web初中级学习者以及毕业设计、课程设计使用者参考。系统围绕电影信息管理、用户、订单、影院等核心功能展开代码按模型、路由、工具、中间件与配置分层组织便于理解Flask项目的目录结构和开发流程。资源包共55个文件以47个Python源文件为主辅以配置文件、Shell脚本、Dockerfile、说明文档等整体约102KB轻量易用。已有293人学习下载可借此快速搭建基础管理系统并深入学习接口设计、JWT鉴权、数据模型定义及部署脚本等实践细节。1. 只靠 Flask 就能撑起一套电影管理系统吗为内部社团做一套电影管理系统最常见诉求就是能浏览片单、点开看简介、登录后打分留言管理员能录入电影。这类项目很多人第一反应是上 Django Admin但拿到 Flask 框架源码更轻改起来快数据库也直观。网上流传的“基于Flask框架的电影管理系统源码”大多长得很像问题是多数人 clone 下来跑不起来因为把配置、路由、模型揉在同一个文件里。这篇按可复现的方式拆开重构从建目录到接口验证把每个参数为什么这么设讲清楚。2. 项目骨架把 Flask 源码从单文件拆成工厂模式很多人拿到 Flask 框架源码第一反应是看 app.py其实项目组织方式才决定系统能不能维护。单文件 demo 适合跑通流程不适合管用户、电影、评分。下面用工厂模式和蓝图重排常见目录。2.1 为什么先用工厂模式而不是直接实例化 appFlask 里app Flask(__name__)写在模块顶层会让测试和配置切换变难。工厂模式create_app()允许你传入不同的配置对象比如开发、测试、上线配置。这个系统不算大用工厂不是过度设计而是给后续加接口和替换数据库留出口。# 从 run.py 启动配置外置业务按模型/表单/蓝图分层 project_root/ ├── run.py # 真正执行 python run.py ├── config.py # 开发、测试、生产配置类 ├── requirements.txt └── app/ ├── __init__.py # create_app 入口 ├── models/ │ ├── __init__.py │ └── cinema.py # 数据库模型 ├── forms/ │ ├── __init__.py │ └── movie_forms.py ├── blueprints/ │ ├── main.py # 公开页面 │ ├── auth.py # 登录注册 │ └── admin.py # 后台管理 ├── templates/ └── static/这个目录几乎是很多 Flask 电影管理系统源码的常规布局。拆开后你可以单独替换模型或表单类不用碰视图函数。测试时也能用另一个数据库配置启动同一个 app。2.1.1 create_app 里的固定动作在app/__init__.py中至少要完成四件事初始化 SQLAlchemy、初始化 CSRF 防护、初始化登录扩展、注册蓝图。from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_wtf import CSRFProtect from flask_login import LoginManager db SQLAlchemy() login_manager LoginManager() csrf CSRFProtect() def create_app(config_class): app Flask(__name__) app.config.from_object(config_class) # 初始化扩展扩展与 app 解耦 db.init_app(app) csrf.init_app(app) login_manager.init_app(app) from .blueprints.main import main_bp from .blueprints.auth import auth_bp from .blueprints.admin import admin_bp app.register_blueprint(main_bp) # 无前缀 app.register_blueprint(auth_bp, url_prefix/auth) # /auth/login app.register_blueprint(admin_bp, url_prefix/admin) # /admin/movie/new return app逻辑说明db.init_app(app)把数据库连接绑定到当前 appcsrf.init_app(app)保证所有 POST 请求都要带令牌这是很多电影管理系统源码容易忽略的部分。蓝图注册后main_bp负责公开页面auth_bp管登录注册admin_bp管后台录入。参数说明config_class必须包含SQLALCHEMY_DATABASE_URIurl_prefix一旦指定所有相关路由的前缀都会改变比如auth_bp.route(/login)实际访问路径是/auth/login。2.2 配置类与依赖锁版本不要用pip install flask一把梭项目能跑别人复现就难。requirements.txt至少要把 Flask 与 SQLAlchemy 的兼容范围写出来常见的一套如下# 先安装依赖再跑项目使用兼容的次版本范围 Flask2.0,3 Flask-SQLAlchemy3.0,4 Flask-WTF1.0 Flask-Login0.6 python-dotenv1.0逻辑说明Flask 2.x 与 Flask-SQLAlchemy 3.x 的 API 兼容性已经稳定Flask-WTF 提供 CSRF 和表单渲染能力python-dotenv 负责读取.env中的密钥。如果你拿到的源码还写着SQLALCHEMY_TRACK_MODIFICATIONS True建议改成 False避免每个请求触发信号产生额外内存开销。config.py可以写成这样import os from dotenv import load_dotenv basedir os.path.abspath(os.path.dirname(__file__)) load_dotenv() class Config: SECRET_KEY os.getenv(SECRET_KEY, dev-only-key) SQLALCHEMY_DATABASE_URI sqlite:/// os.path.join(basedir, movies.db) SQLALCHEMY_TRACK_MODIFICATIONS False表格列出必须关注的配置项配置项值影响SECRET_KEY至少 32 位随机串签名 session 和 CSRF token不能公开SQLALCHEMY_DATABASE_URIsqlite:///movies.db开发用 SQLite上线可换 mysqlpymysql://...SQLALCHEMY_TRACK_MODIFICATIONSFalse减少内存开销避免警告影响调试这套配置切到生产环境时只要新建ProductionConfig并覆盖SQLALCHEMY_DATABASE_URI即可不用改业务代码。3. 数据库模型和表单电影管理系统源码里的安全底线管理系统少不了用户、电影、评分但很多源码把所有字段塞进一张表。Flask 框架下标准做法是 SQLAlchemy ORM下面按“后台添加电影、用户评分、管理员审核”三步设计。3.1 三张核心表的关系与字段取舍一个电影管理系统最核心的关系是 User、Movie、Rating。评分表同时引用用户和电影多对多关系要拆成关联模型。下面是一个可直接使用的模型文件。from datetime import datetime from app import db, login_manager from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) class User(UserMixin, db.Model): id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(64), uniqueTrue, nullableFalse) password_hash db.Column(db.String(256), nullableFalse) is_admin db.Column(db.Boolean, defaultFalse) created_at db.Column(db.DateTime, defaultdatetime.utcnow) # 关联用户评分backref 供 Rating 对象反查用户 ratings db.relationship(Rating, backrefuser, lazyTrue) def set_password(self, password): self.password_hash generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) class Movie(db.Model): id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(120), indexTrue, nullableFalse) director db.Column(db.String(80)) year db.Column(db.Integer) description db.Column(db.Text) cover_url db.Column(db.String(500)) avg_rating db.Column(db.Numeric(3, 2), default0) created_at db.Column(db.DateTime, defaultdatetime.utcnow) class Rating(db.Model): id db.Column(db.Integer, primary_keyTrue) user_id db.Column(db.Integer, db.ForeignKey(user.id), nullableFalse) movie_id db.Column(db.Integer, db.ForeignKey(movie.id), nullableFalse) score db.Column(db.Integer, nullableFalse) comment db.Column(db.Text) created_at db.Column(db.DateTime, defaultdatetime.utcnow) # 同一用户对同一部电影只能有一条评分 __table_args__ (db.UniqueConstraint(user_id, movie_id, nameuniq_user_movie),)逻辑说明Rating表上的唯一约束是防止重复评分的关键视图层可以提示“你已经评过分”数据库层还要兜底。avg_rating冗余存储平均分虽然不满足第三范式但列表页不需要每次都去聚合评分表响应速度会快很多。参数说明db.Numeric(3, 2)表示最大三位数、两位小数平均分最多 9.99计算时要先round()再落库lazyTrue适合从用户对象查看评分记录如果列表页要同时反查电影信息可改成lazyselectin减少 N1 查询。表格补充表唯一约束外键用途userusername无登录后台movie不设唯一约束无允许同名翻拍按年份区分ratinguser_id movie_iduser, movie保存评分与评论3.2 用 Flask-WTF 收表单而不是手撸 request.form老源码喜欢在路由里request.form.get(title)字段多时校验逻辑会散落在各处。改成 Flask-WTF 的FlaskForm字段错误能统一回显也天然带 CSRF token。from flask_wtf import FlaskForm from wtforms import StringField, IntegerField, TextAreaField, SubmitField from wtforms.validators import DataRequired, Length, NumberRange class MovieForm(FlaskForm): # 表单字段与校验规则 title StringField(电影名, validators[DataRequired(), Length(max120)]) year IntegerField(年份, validators[NumberRange(min1888, max2100)]) director StringField(导演, validators[Length(max80)]) description TextAreaField(简介, validators[Length(max2000)]) submit SubmitField(保存)在视图里使用from app.forms.movie_forms import MovieForm def movie_create(): form MovieForm() if form.validate_on_submit(): movie Movie( titleform.title.data, yearform.year.data, directorform.director.data, descriptionform.description.data ) db.session.add(movie) db.session.commit() return render_template(movie_form.html, formform)逻辑说明validate_on_submit()内部先检查请求方法是不是 POST再校验 CSRF token 与字段规则。模板里必须渲染{{ form.hidden_tag() }}否则提交时 CSRF 直接失败。用 FlaskForm 之后不需要手动if request.method POST。参数说明NumberRange(min1888, max2100)的 min 写成 1888是因为最早的电影成型于 1888 年前后不设成 1900 能兼容早期默片入库。3.3 密码哈希与登录扩展的细节不要存明文密码。Flask-Login 只负责维护 session不管密码加密密码哈希改用 Werkzeug 的generate_password_hash。这个方法返回的字符串自带盐比较时用check_password_hash逐个校验。generate_password_hash(password, methodpbkdf2:sha256, salt_length8)计算成本适中不需要自己实现加盐逻辑。登录视图里不要直接比对password form.password.data正确的是user.check_password(form.password.data)。还必须在create_app或配置里指定login_manager.login_view auth.login否则未登录访问login_required页面会返回 401 而不是跳转。这几个点处理不好管理系统一上线就会出现两个问题用户注册后无法登录或者 session 在重启后莫名失效。4. 电影管理系统路由与后台权限列表、搜索和评分功能的实现模型搭好后下一步是铺路由。这一章对应常见源码里的业务核心列表页能搜能翻详情页能提交评分后台路由只有管理员能进。4.1 用 query 参数和分页让列表页能搜能翻电影列表页不应该一次查询所有记录。Flask-SQLAlchemy 的paginate比手写 limit/offset 更简洁也方便在前端生成分页链接。from flask import Blueprint, render_template, request from app.models.cinema import Movie main_bp Blueprint(main, __name__) main_bp.route(/) def index(): # 从查询字符串中取参数typeint 会在非法值时返回默认值 page request.args.get(page, 1, typeint) per_page min(request.args.get(per_page, 12, typeint), 48) keyword request.args.get(q, ).strip() query Movie.query if keyword: query query.filter(Movie.title.contains(keyword) | Movie.director.contains(keyword)) movies query.order_by(Movie.year.desc()).paginate(pagepage, per_pageper_page, error_outFalse) return render_template(index.html, moviesmovies, keywordkeyword)逻辑说明request.args.get(page, 1, typeint)这个typeint很关键用户传?pageabc时 Flask 内部会抛 404而不是 TypeError。error_outFalse让超出范围的页码返回空列表而不是报错前端可以显示“没有更多内容”。参数说明per_page设上限 48防止有人传 10000 把数据库拖垮Movie.title.contains(keyword)在 SQLite 中会翻译成LIKE %keyword%数据量小够用生产环境换ILIKE或 MySQLLOCATE。表格列表页参数映射。功能URL 参数说明分页page当前页从 1 开始页容量per_page建议 12 ~ 24搜索q匹配片名或导演排序sortyear按年份倒序可按需求扩展4.2 评分提交与事务完整性评分写入同时要更新电影平均分两个操作必须放在一个事务里。下面这段使用 SQLAlchemy 聚合函数。from sqlalchemy import func from flask_login import login_required, current_user from flask import flash, redirect, url_for, request main_bp.route(/movie/int:movie_id/rate, methods[POST]) login_required def rate_movie(movie_id): movie Movie.query.get_or_404(movie_id) # 表单传 score转换失败时返回 None score request.form.get(score, typeint) if score is None or not (1 score 10): flash(评分必须在 1 到 10 分之间) return redirect(url_for(movie_detail, movie_idmovie_id)) rating Rating.query.filter_by(user_idcurrent_user.id, movie_idmovie_id).first() try: if rating: rating.score score else: rating Rating(user_idcurrent_user.id, movie_idmovie_id, scorescore) db.session.add(rating) # 重新计算平均分并写回冗余字段 avg db.session.query(func.avg(Rating.score)).filter(Rating.movie_id movie_id).scalar() movie.avg_rating round(avg, 2) db.session.commit() except Exception: db.session.rollback() flash(评分失败请重试) return redirect(url_for(movie_detail, movie_idmovie_id))逻辑说明先用filter_by(user_id, movie_id)查旧评分有就更新没有就新增。然后func.avg重新聚合最后写回 movie 表。try/except里的rollback()很重要一旦 SQLite 写失败而没回滚后续请求提交会一直报database is locked。参数说明request.form.get(score, typeint)的注意点在于Werkzeug 在转换失败时会返回默认值。这里没写默认值所以非法值会得到None靠后面的if score is None拦下来。4.3 后台管理权限与 CSRF 陷阱后台入口通常挂在/admin下常见错误是只隐藏前端按钮后端路由没有任何保护。更可靠的做法是自定义一个admin_required装饰器。from functools import wraps from flask_login import current_user from flask import redirect, url_for, request, render_template def admin_required(func): wraps(func) def wrapper(*args, **kwargs): # 先判断是否登录登录后再判断是否管理员 if not current_user.is_authenticated: return redirect(url_for(auth.login, nextrequest.path)) if not current_user.is_admin: return render_template(errors/403.html), 403 return func(*args, **kwargs) return wrapper使用示例from app.decorators import admin_required admin_bp.route(/movie/new, methods[GET, POST]) admin_required def create_movie(): form MovieForm() if form.validate_on_submit(): pass return render_template(movie_form.html, formform)逻辑说明装饰器从下往上执行时admin_required会先检查权限再进入视图函数。权限不足时返回 403 页面而不是重定向到首页管理员才能分清“没登录”和“没有权限”。表格后台权限矩阵。操作普通用户管理员超级管理员浏览电影是是是添加/编辑电影否是是删除用户否否是修改管理员权限否否是要特别强调 CSRF如果某个路由直接用request.form而不走 FlaskForm必须确保模板里有input typehidden namecsrf_token value{{ csrf_token() }}。Flask-WTF 的全局 CSRFProtect 会拦截所有不带 token 的 POST这个细节不处理功能会表现为“提交后页面刷新数据没保存”。5. 用测试客户端做冒烟验证并避开三个 Flask 源码常见坑最后这部分不展开全量测试只给你一个能守住基本盘的最小方案再指出源码阅读时最容易改错的三处。5.1 用 pytest 与 test_client 验证核心页面不用启动真实服务器Flask 自带的test_client可以模拟请求# 测试最小回归首页可达管理员页未登录时跳转 def test_index_ok(client): resp client.get(/) assert resp.status_code 200 def test_admin_redirect(client): resp client.get(/admin/) assert resp.status_code 302参数说明clientfixture 可以通过app.test_client()得到如果你的登录依赖 session可以用client.session_transaction()往 session 里写用户 id再测管理员页面。5.2 三个容易踩的坑第一SECRET_KEY写死且不轮换。部署后每次修改代码重启session 签名失效用户会被登出。正确做法是从环境变量读取且每个环境使用不同的 key。第二SQLite 并发写入。如果使用 gunicorn 多 worker 跑 SQLite会出现database is locked。电影管理系统并发通常不高可以限制 worker 为 1或者把数据库换成 PostgreSQL。第三模板变量名不一致。很多源码在路由里传formform模板里写成{{ movie_form.hidden_tag() }}导致表单不显示且无法提交。统一模板变量名比在路由里不断rename更省事。表格验证命令。场景命令预期启动开发服务flask --app run.py run --debug访问 127.0.0.1:5000跑测试pytest -q全部通过查看路由表flask --app run.py routes列出所有 URL 与视图函数提交前建议跑一遍测试和路由表确认没有暴露多余的调试接口。本文还有配套的精品资源点击获取
返回列表