
简介Python电影系统源码是一套基于Django框架的完整Web应用面向希望系统学习Python Web开发的初中级开发者覆盖电影信息展示、用户购票、在线评论等典型业务场景。压缩包共79个文件大小约905KB其中43个Python源码对应Django项目的模型、视图、URL配置、表单与认证等核心模块5个HTML模板负责页面渲染6个JavaScript和5个CSS文件实现前端交互样式10张PNG图片用于界面展示和流程说明整体目录结构清晰便于按模块阅读。项目还附带部署相关配置可对接Gunicorn/uWSGI等WSGI服务器。目前已有224人浏览学习过该资源。源码充分展示了Django的模型-视图-模板分层设计、ORM数据库操作、URL路由、用户认证与权限、表单处理、CSRF/XSS安全防护等关键知识点并覆盖从开发到上线的常见环节能帮助开发者快速理解实际项目的完整结构与运行流程是提升Django开发能力的高价值参考资料。1. 为什么这份 Python 电影系统源码值得拆开看拿到Python电影系统源码.zip时很多人的第一反应是直接解压然后python manage.py runserver。这个 djangoFilmSystem 项目真正值钱的地方在于它不是单文件 Demo而是把用户注册登录、图片验证码、在线帮助、后台电影管理拆到了不同的 Django app 里目录结构接近公司里真实维护的工程。对刚学完 Python 语法的人来说可以从里面看到 WSGI 应用从入口到数据库的完整链路对写过几年后端的开发者也能从settings.py、中间件和模板布局里对比自己的项目组织方式。这篇文章直接从解压和启动开始把目录结构、模型层、视图层、路由与模板串起来讲最后落到验证码和生产环境里最容易踩的坑上。2. 先从 manage.py 入手djangoFilmSystem 的目录结构与启动参数2.1 工程目录settings 与 apps 怎么分层一个标准的 Django 工程由一个 project 和多个 app 组成。根目录下的manage.py是命令行入口所有启动、迁移、创建管理员的操作都靠它分发djangoFilmSystem/是项目配置包存放settings.py、urls.py、wsgi.py和asgi.pyapps/把业务模块收拢成子应用templates/放页面模板static/放 CSS、JS 和图片utils/里是验证码生成和统一返回码这类被多个 app 复用的公共代码。dbs/目录则是 SQLite 数据库文件的常见存放位置。我一般建议拿到 zip 后不要急着启动先按下面的目录树对照一遍确认每个目录的职责djangoFilmSystem-main/ ├── manage.py # 命令行入口启动、迁移、创建管理员 ├── djangoFilmSystem/ # 项目配置包 │ ├── settings.py │ ├── urls.py │ ├── asgi.py │ └── wsgi.py ├── apps/ # 业务子应用 │ ├── users/ # 用户注册、登录、信息维护 │ ├── verifications/ # 图片/短信验证码 │ ├── online_help/ # 在线帮助、公告 │ └── administer/ # 后台管理电影、排片、票务 ├── templates/ │ ├── index.html │ └── register.html ├── static/ │ ├── css/ js/ images/ │ └── auth.js message.js photo.js ├── utils/ │ ├── captcha/ # 验证码生成工具 │ └── res_code.py # 统一返回码 ├── dbs/ # 可选的 SQLite 数据库 ├── 数据格式demo.md └── README.md这个结构里最容易忽略的是djangoFilmSystem同时是包名也是 WSGI application 的导入路径。gunicorn djangoFilmSystem.wsgi:application能启动就是因为djangoFilmSystem/wsgi.py中定义了application get_wsgi_application()。如果你把整个项目文件夹改名只要包名也跟着变wsgi.py和settings.py里所有djangoFilmSystem.settings的引用都要同步修改否则启动时会报ModuleNotFoundError。2.2 初始化虚拟环境Python 版本、Django 版本和依赖安装准备环境的第一步是先建虚拟环境不要让这个项目的依赖和系统 Python 混在一起。常规操作如下cd djangoFilmSystem-main python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install django pip install pillow python manage.py checkpython -m venv venv会在当前目录生成一个独立的 Python 环境激活之后安装的包只作用于这个项目。pip install django安装框架本体pillow是验证码模块绘制图片时最常见的依赖如果utils/captcha用的是纯算法生成那么pillow可以去掉。python manage.py check只检查配置和依赖不启动服务拿到项目第一次跑时报错时可以先用它来确认是否是依赖缺失。依赖装完还需要初始化数据库再启动。执行顺序一般是python manage.py makemigrations python manage.py migrate python manage.py runserver 0.0.0.0:8000makemigrations从models.py中读取模型变更生成迁移文件migrate把迁移同步到数据库。第一次跑如果没执行这两步页面会报no such table。runserver 0.0.0.0:8000的关键是0.0.0.0它让服务监听所有网卡虚拟机和局域网内其他机器都能访问只想本机调试时直接runserver即可。默认端口是 8000如果被占用改成runserver 8001。2.3 settings.py 中第一次跑必须改的参数启动后如果看到DisallowedHost报错需要先改ALLOWED_HOSTS。这个项目的settings.py里有几个参数是本地开发阶段必须确认的配置项作用本地建议说明ALLOWED_HOSTS允许访问的域名/主机[*]空数组时只能处理 localhost局域网 IP 会被拒绝INSTALLED_APPS注册的应用列表保留apps.users等漏注册会出现模板找不到或迁移表不生成DATABASES数据库连接默认 sqlite3确认dbs目录存在且可写TIME_ZONE时区Asia/Shanghai不设置的话排片时间会偏差 8 小时LANGUAGE_CODE界面语言zh-hans配合USE_TZTrue使用时注意时间格式化INSTALLED_APPS里如果漏掉了自定义 appmakemigrations不会生成对应表模板里的{% url %}也会因为路由没加载而失效。如果migrate时报table already exists不要直接删源文件里的数据库先把dbs/*.sqlite3备份后移除再重新执行迁移。数据格式demo.md里通常记录了接口的返回格式可以按它检查代码里的字段命名是否一致。3. 模型层与 ORM打开电影系统的数据表结构和关联查询3.1 业务模型用户、电影、场次与订单是怎么关联的Django 的模型是数据库表的抽象层每个模型类对应一张表模型字段对应表字段。这个电影系统虽然把业务拆成了四个 app但电影、场次、订单这几张核心表最终还是通过外键关联。简单梳理一下Film是电影主表Screening记录某部电影在什么时间、哪个影厅放映Order记录哪个用户购买了哪个座位。下面这段代码是常见的模型实现参考和源码中apps/administer/models.py的写法基本一致# apps/administer/models.py from django.db import models from django.contrib.auth.models import User class Film(models.Model): name models.CharField(电影名, max_length100) director models.CharField(导演, max_length50) release_date models.DateField(上映日期) status models.CharField(状态, max_length10, choices[(coming, 未上映), (on, 上映中), (off, 已下映)], defaulton) created_at models.DateTimeField(创建时间, auto_now_addTrue) class Meta: db_table film ordering [-release_date] class Screening(models.Model): film models.ForeignKey(Film, on_deletemodels.CASCADE, related_namescreenings) hall models.CharField(影厅, max_length20) start_time models.DateTimeField(开场时间) class Order(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) screening models.ForeignKey(Screening, on_deletemodels.CASCADE) seat models.CharField(座位, max_length10) created_at models.DateTimeField(下单时间, auto_now_addTrue)每个模型对应数据库一张表db_table显式指定表名不写时默认是app名_小写模型名。on_deletemodels.CASCADE表示主记录删除时关联数据也删除订单这类需要留痕的场景更推荐on_deletePROTECT防止误删历史数据。related_namescreenings定义了反向查询名可以用film.screenings.all()取某部电影的全部场次而不需要自己去Screening表里再 filter 一次。3.2 常用 ORM 查询与聚合统计想知道数据库里有哪些正在上映的电影不需要打开dbs下的 SQLite 文件直接在 Django shell 里操作更直观python manage.py shellfrom datetime import date from django.db.models import Count from apps.administer.models import Film, Screening now date.today() on_films Film.objects.filter(statuson, release_date__ltenow) for film in on_films: print(film.id, film.name, film.director) future Screening.objects.filter(start_time__gt2025-01-01 00:00:00) print(list(future.values(film__name).annotate(totalCount(id)).order_by(-total)))filter(statuson, release_date__ltenow)表示状态是上映中且上映日期小于等于今天这是一个典型的范围过滤。values(film__name)通过双下划线跨外键取出电影名字段等价于 SQL 里的GROUP BY film.nameannotate(totalCount(id))给每个分组统计数量order_by(-total)按数量倒序。Screening.objects.filter(start_time__gt...)里的__gt是大于条件类似的还有__lt、__gte、__lte。如果只要单条记录用get_object_or_404而不是filter(...).first()这样记录不存在时能直接返回 404 页面。3.3 迁移顺序与模型字段修改的常见坑模型改字段之后必须重新生成迁移不然数据库不知道新结构。开发阶段最常撞到的几个报错处理思路如下报错信息原因解决方法no such table: film没有执行 migrate或迁移到了另一个数据库先python manage.py makemigrations再migratetable film already exists数据库文件残留旧表与迁移记录不一致备份后删除dbs/*.sqlite3重新 migrateColumn cannot be null新增字段未给null或默认值迁移时选择提供 default或修改模型字段加default...relation does not existPostgreSQL 下 schema 名不匹配检查数据库用户是否有对应 schema 权限这里要提醒一句不要轻易删掉dbs目录里的 SQLite 文件。如果里面有历史测试数据删完所有迁移记录要从头跑createsuperuser创建的账号也会丢失。稳妥的办法是先把数据格式demo.md里的表结构说明读完确认没有可用数据后再重置。4. URL 路由、视图和模板渲染电影列表如何从数据库渲染成 HTML 页面4.1 路由分发项目 URLs 和 app 内 URLs 的两层设计Django 的 URL 配置不是只能堆在djangoFilmSystem/urls.py里。项目里常见的做法是把子应用的路由拆到各自的urls.py再用include拼到主路由上。主路由会像下面这样# djangoFilmSystem/urls.py from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(apps.administer.urls)), path(user/, include(apps.users.urls)), path(verify/, include(apps.verifications.urls)), ]子应用里再定义自己的路由# apps/administer/urls.py from django.urls import path from . import views urlpatterns [ path(, views.index, nameindex), path(movie/int:film_id/, views.movie_detail, namemovie_detail), path(order/, views.create_order, namecreate_order), ]path(movie/int:film_id/)是 Django 2.0 以后的路由语法尖括号里写转换器int和变量名film_idURL 里匹配到的数字会作为film_id参数传给视图函数。name是 URL 的别名模板里可以用{% url movie_detail film.pk %}生成真实地址避免把 URL 硬编码到页面里。include的路径apps.administer.urls对应根目录apps/administer/urls.py。如果多个 app 里出现同名路由别名反向解析会拿到第一个匹配所以建议在urls.py里声明app_name administer再写{% url administer:movie_detail film.id %}。4.2 视图使用函数视图处理请求的典型写法视图承担的是“接收请求、查数据库、渲染模板”这三件事常见实现有函数视图与类视图两种。源码里如果用的是函数视图大概会长这样# apps/administer/views.py from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import login_required from .models import Film def index(request): film_list Film.objects.filter(statuson) return render(request, index.html, {films: film_list}) login_required def movie_detail(request, film_id): film get_object_or_404(Film, pkfilm_id) return render(request, detail.html, {film: film}) login_required def create_order(request): if request.method POST: # 按表单提交的 screening_id 和 seat 创建订单 pass return render(request, order.html)render(request, template, context)先把模板和 context 合并成 HTML再返回HttpResponsecontext 是字典模板里用{{ films }}访问。pk是主键的通用别名get_object_or_404(Film, pkfilm_id)在找不到时抛 404不需要自己写 try/except。login_required是内置装饰器未登录用户会被重定向到settings.LOGIN_URL默认是/accounts/login/。如果这个项目没配置登录页可以在settings.py里显式改成LOGIN_URL /user/login让它指向apps/users下面的登录视图。4.3 模板语法与静态资源让 index.html 正常显示电影列表templates/index.html放在项目根目录是因为settings.TEMPLATES的DIRS里注册了BASE_DIR / templates。页面里展示的电影数据来自视图传过来的films写法是 Django 模板语言不是原生 Python{% load static %} !DOCTYPE html html langzh-hans head meta charsetutf-8 link relstylesheet href{% static css/style.css %} /head body ul {% for film in films %} li a href{% url movie_detail film.id %}{{ film.name }}/a span{{ film.director }}/span /li {% empty %} li当前没有上映中的电影/li {% endfor %} /ul form methodpost action{% url create_order %} {% csrf_token %} input typetext namescreening_id input typetext nameseat button typesubmit购买/button /form script src{% static js/auth.js %}/script /body /html{% load static %}写在文件顶部{% static css/style.css %}会拼接STATIC_URL输出最终 URL。{% for %}配合{% empty %}能处理空列表比在视图里做判断更直观。{% csrf_token %}会输出一个隐藏的csrfmiddlewaretoken输入框Django 的CsrfViewMiddleware在接收 POST 时会校验这个 token模板里漏写的话表单提交会直接返回 403 Forbidden这是新手最容易撞到的错误之一。4.4 模板表单与用户注册从 register.html 到 Django 用户认证注册页register.html的表单提交后会走到apps/users这个应用。源码为了保持演示简洁常采用下面的处理方式from django.contrib.auth.models import User from django.shortcuts import render, redirect def register(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) if not username or not password: return render(request, register.html, {error: 用户名和密码不能为空}) if User.objects.filter(usernameusername).exists(): return render(request, register.html, {error: 用户已存在}) User.objects.create_user(usernameusername, passwordpassword) return redirect(/) return render(request, register.html, {})User.objects.create_user()是 Django 创建用户的正确方式它会把明文密码做哈希再写入数据库而不是把密码存成明文。如果换成User.objects.create(username..., password...)密码就是明文字符串之后login_required和authenticate()永远都验不过。request.POST.get()返回表单字段的值没填时返回None所以要先判空。实际项目中更推荐用 Django Form 或ModelForm它能自动完成字段绑定、类型校验和错误回显代码量不会比手写多。模板中经常出现的标签语法按使用频率整理如下模板语法作用典型场景{{ variable }}输出变量输出用户名、电影名{% for %}/{% empty %}循环列表空值兜底渲染电影列表{% url name arg %}反向解析 URL生成详情页链接{% csrf_token %}输出防跨站令牌所有 POST 表单{% static path %}拼接静态文件地址加载 CSS、JS模板里不支持写复杂表达式和函数调用需要提前计算的数据一律放到视图 context 中模板只负责展示和简单分支这样后期维护时定位问题更快。5. 验证码闭环与生产部署避坑把 DEBUGFalse 之前的准备做好5.1 verifications 与 utils/captcha 的协作方式验证码在这个项目里不是一个只画图的独立工具它分成两层utils/captcha负责生成图片和答案apps/verifications负责把答案写入 session并把图片作为 HTTP 响应返回。生成部分的常见实现如下# utils/captcha/captcha.py import random from PIL import Image, ImageDraw def generate_captcha(): text .join(random.sample(abcdefghjkmnpqrstuvwxyz23456789, 4)) image Image.new(RGB, (120, 40), (240, 240, 240)) draw ImageDraw.Draw(image) for i, ch in enumerate(text): draw.text((10 i * 25, 8), ch, fill(40, 40, 40)) return image, text字符集里故意没放i、l、o、0、1能显著降低人工读错的概率同时减少验证码被自动识别成功的机会。draw.text的坐标用索引乘以固定步长图片宽度改变时这个坐标步长也要同步调整。校验时最容易被忽略的是大小写和一次性校验。正确逻辑是在生成时把text存进request.session校验时取出并立刻删除def captcha_check(request): user_input request.POST.get(captcha, ).lower() saved request.session.get(captcha_text, ) if not saved or user_input ! saved: return render(request, register.html, {error: 验证码错误}) request.session.pop(captcha_text) # 验证通过后继续执行用户名密码校验request.session.get从 session 里取出生成时保存的答案取出来后立刻pop让同一个验证码只能用一次。如果验证码校验后没有失效攻击者可以反复重放这个请求。建议在 session 里再存一个生成时间超过 60 秒就强制失效。5.2 本地和生产环境静态文件的差异开发阶段runserver会自动处理静态文件一旦上线把DEBUG设为FalseDjango 默认就不再提供静态资源。所以需要执行collectstatic把 app 和项目里的静态文件统一复制到STATIC_ROOTpython manage.py collectstatic --noinput pip install gunicorn gunicorn djangoFilmSystem.wsgi:application -b 0.0.0.0:8000collectstatic之后不会自动配上 URL还需要 Nginx 的location /static/ { alias /static目录/; }或whitenoise中间件。gunicorn的导入路径djangoFilmSystem.wsgi:application要和项目包名保持一致如果目录改名这里也要同步改。线上环境样式全丢时先看STATIC_URL和STATIC_ROOT是否指向同一个目录再检查 Nginx alias 里有没有写错路径。本文还有配套的精品资源点击获取