Django视频点播网站系统:架构设计、核心实现与答辩指南
发布时间:2026/10/21 0:57:57 锦皓数字建站

简介基于Python与Django框架实现的视频点播网站系统是一套面向毕业设计或课程实践的完整可运行项目覆盖在线教育、视频分享等常见应用场景。项目前后端功能齐备前端支持视频列表展示、播放详情、用户评论与个人中心后端提供视频管理、评论审核、用户权限及反馈处理便于理解Django MTV架构、ORM操作、内置认证与Admin后台的实战用法。压缩包共128个文件以48个html页面模板、40个py逻辑文件、20个js脚本为主辅以css样式表与png/jpg图片素材整体仅3.23MB结构清晰、可直接部署运行。目前已有244人学习浏览适合需要快速上手Django全栈开发、完成毕设或巩固Web开发技能的读者。通过该项目可掌握从数据库设计到前后端联调的关键流程并获得一套可用于扩展二次开发的代码基础。1. 为什么毕业设计选视频点播系统而不是“管理系统”三次毕业设计开题下来有个感受很强烈做后台管理系统的同学最后都在演示点按钮做视频点播网站系统的同学却在讲用户上传、播放器取流、播放量累加、分类推荐老师提问的方向完全不在一个层次。视频点播本身覆盖文件存储、媒体播放、权限控制、后台管理等多条链路展示维度多技术边界清楚适合在有限时间内交付。这套基于 Python Django 开发的视频点播网站系统代码和数据库文件都是现成的虚拟环境建好、依赖装上就能跑。适合两类人一类是 Django 基础还没吃透需要一套能运行的完整代码保底另一类是已经会写简单视图但想在存储设计、播放器接入和答辩扩展点上做文章的。下面先从项目骨架开始拆。2. 项目骨架与数据模型先把三张核心表讲透2.1 MVT 分层与视频站点的模块划分Django 做视频点播系统最容易出现的问题不是代码写不出来而是所有逻辑堆在 views.py 里出问题以后不知道去哪个文件排查。这个项目的结构走的是标准 Django MVT 路线根 urls.py 统一路由分发把 admin、用户、视频三个入口拆开video 这个 app 承载核心业务models.py 定义数据结构views.py 写列表、详情、搜索、上传处理的视图函数用户系统直接复用 Django 自带的 auth 模块注册、登录、改密码不重复造轮子后台管理通过继承 admin.ModelAdmin 配置列表字段和搜索框前端直接用服务端模板渲染页面不额外引入复杂前端框架。这样分层有个明显好处功能出问题时定位面很小。比如播放页打开 404先查 video/urls.py 和 video/views.py 里的视频详情视图再看模板文件名是否拼错后台传不了视频查 admin.py 里 ModelAdmin 注册项和字段权限就行。为什么这个场景选 Django 而不是 Flask核心原因是 Django 自带 Admin 管理和数据模型迁移机制。视频点播这种“后台要管理、前台要展示”的双端项目Admin 直接省掉了一整套后台管理页的重复开发量。Flask 要做出同样的后台效果需要额外接入 Flask-Admin 和 SQLAlchemy自己补的代码多依赖链长了跑不起来时排查也费劲。另外 Django ORM 在写跨表查询时更顺比如按分类名筛视频一条 category__name 就能拿到分类的中文名不用手动写 join。2.2 数据模型设计Video、Category、User 三张表的关键字段数据表是整个系统最核心的地基。这套下载包里虽然已经带了初始化好的数据库文件但只有读懂表结构后面加功能、改逻辑才动得了。下面这段模型代码就是这套系统最常见的表结构# video/models.py视频点播系统的三张核心表 from django.db import models class Category(models.Model): name models.CharField(分类名称, max_length50) sort models.IntegerField(排序权重, default0) class Meta: verbose_name 视频分类 verbose_name_plural verbose_name ordering [sort, id] def __str__(self): return self.name class Video(models.Model): title models.CharField(视频标题, max_length200) category models.ForeignKey( Category, on_deletemodels.PROTECT, verbose_name所属分类 ) cover models.ImageField(封面图, upload_tocover/%Y/%m/) video_file models.FileField(视频文件, upload_tovideos/%Y/%m/) duration models.CharField(视频时长, max_length20, blankTrue) desc models.TextField(视频简介, blankTrue) view_count models.IntegerField(播放量, default0) is_published models.BooleanField(是否上架, defaultTrue) created_at models.DateTimeField(发布时间, auto_now_addTrue) class Meta: verbose_name 视频 verbose_name_plural verbose_name ordering [-view_count] def __str__(self): return self.title这里有两个字段细节建议特别注意。第一个是 category 外键的 on_delete 参数。很多人习惯写 models.CASCADE意思是分类被删掉这个分类下面所有视频全部跟着删除。毕设阶段如果误点了删除分类视频数据整批消失找都找不回来。用 PROTECT 的话Django 会在删分类时主动拦截并报错逼你先手动把该分类下的视频转移或下架这个约束放着就是一道保险。第二个是 cover 和 video_file 的 upload_to 参数。这里没有简单写 upload_tocover而是带上了 %Y/%m 子目录Django 会按上传年份和月份自动创建目录。这样处理有两个直接收益一是文件名即使重复也不会互相覆盖二是后台和播放页拿到的是相对路径迁移项目时目录结构清晰。对应的后台注册代码一般长这样逻辑简单但必不可少# video/admin.py把视频和分类注册进 Django 后台 from django.contrib import admin from .models import Video, Category admin.register(Video) class VideoAdmin(admin.ModelAdmin): list_display (id, title, category, view_count, is_published) list_filter (category, is_published) search_fields (title, desc) admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display (id, name, sort)list_display 决定后台列表展示哪些列list_filter 在右侧生成分类和上下架的筛选项search_fields 开启标题和简介的关键词搜索。这三个参数是管理后台最常用的配置答辩时如果老师追问“后台是怎么实现的”直接从这个注册类入手讲就行。2.3 字段的坑ImageField 和 FileField 的落盘规则上传文件有两条路。第一种是直接把文件对象存到模型字段里Django 自动按 upload_to 的规则拼接目录第二种是先用 FileSystemStorage 手动保存再把返回路径写到数据库。这套项目的视频上传走的是第一种也是最推荐的方式。用 FileSystemStorage 手动处理还容易出 bug最常见的是保存时没处理文件重名同名学生视频传两次后一个把前一个覆盖掉。另一个容易踩的是 MEDIA_ROOT 和 STATIC_ROOT 混为一谈。MEDIA_ROOT 是用户上传的视频、封面存放的物理目录STATIC_ROOT 是项目自带的 CSS、JS、图片在部署时收集到一起的目录。两个目录必须分开配置否则 collectstatic 时会把用户上传的视频也一起复制走实际部署时会拖慢构建流程。3. 从环境到界面一倍速跑通本地全流程3.1 Python 环境与依赖安装虚拟环境无论如何都要建打开下载包第一件事不是直接双击 manage.py而是先建一套干净的 Python 虚拟环境。很多拿到代码就说跑不起来的人十有八九是在全局 Python 环境里直接装依赖某次升级把 Django 版本改掉了项目就跟着坏了。# 在项目根目录创建虚拟环境 python -m venv venv # 激活虚拟环境Windows 使用下面的命令 venv\Scripts\activate # macOS / Linux 使用下面的命令 source venv/bin/activate激活之后终端提示符前面会出现 (venv) 字样之后的 pip 和 python 命令都会落在虚拟环境里不会污染全局环境。接着安装依赖# 安装项目运行所需的依赖 pip install -r requirements.txtrequirements.txt 里一般会包含 django、pillow 这两个核心依赖。pillow 是 ImageField 处理封面图的底层库漏装的话执行数据库迁移时会直接报 ModuleNotFoundError。如果下载包里没有 requirements.txt可以用 pip install django pillow 手动补上版本不用太新Django 3.2 到 4.2 之间都可以太新的版本某些第三方库还没跟上。安装完成后验证环境是否正常用 python -c import django; print(django.get_version())能输出版本号说明 Django 已经就位。3.2 数据库迁移、初始数据导入和管理员账号这套系统包了一个现成的 db.sqlite3 文件。能不能直接用它分两种情况如果你不对代码做任何改动直接跳过迁移跑服务器是可以的但一旦你改了模型字段、增删了表就要重新处理迁移。保守做法是先把原始数据库备份一份再按下面的流程从头迁移一次# 在虚拟环境激活状态下执行 python manage.py makemigrations video python manage.py migratemakemigrations 会根据 models.py 的变化生成一个新的迁移文件migrate 会把迁移真正应用到数据库。第一次跑如果提示“No changes detected”说明模型没有变化数据库已经是最新的不用紧张。如果系统带了初始分类和演示视频数据通常会有一个 data.json 文件迁移后执行# 把初始分类与视频数据导入数据库 python manage.py loaddata data.jsonloaddata 会按 JSON 里记录的模型和字段逐一回填。导入报错最常见的原因是数据库里已经有重复的主键 id可以先清空 video_video 和 video_category 两张表再导入。接着创建后台管理员# 交互式创建管理员账号 python manage.py createsuperuser按提示输入用户名、邮箱、密码。这里特别提醒密码不要太复杂答辩现场输入时容易出错我一般设成 Admin123456 这种兼顾大小写和数字还能记住的密码演示完再改。3.3 启动开发服务器访问首页和管理后台数据库准备好以后在项目根目录执行python manage.py runserver看到“Starting development server at http://127.0.0.1:8000/”就是启动成功。浏览器打开首页应该能看到视频列表和分类导航打开 /admin 路径用刚创建的管理员账号登录能看到视频、分类的管理入口。如果首页能开但所有图片和视频都不显示这是 media 路由没有配置。开发环境下需要在根 urls.py 里加上一行# urls.py开发环境下的媒体文件访问路由 from django.conf import settings from django.conf.urls.static import static urlpatterns [ # ... 项目已有路由 ] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)这行代码只应该在 DEBUGTrue 时生效否则公网上所有人都能访问你的上传目录是个安全隐患。部署后要通过 Django 部署文档里的正规静态文件服务方案来替代。4. 核心功能实现上传、播放、搜索与用户逻辑4.1 视频上传格式校验、大小限制与落盘处理视频上传是这套系统里最值得讲的模块。很多毕设项目只做后台表单上传不管文件格式结果用户传一个 .txt 改名成 .mp4 也能入库。正规做法是在视图里做校验至少拦掉扩展名明显不对的文件# video/views.py视频发布处理视图 import os from django.shortcuts import render, redirect from django.contrib import messages from .models import Video, Category ALLOWED_EXT [.mp4, .avi, .mkv, .mov] MAX_SIZE_MB 500 def publish_video(request): if request.method POST: title request.POST.get(title) category_id request.POST.get(category_id) desc request.POST.get(desc, ) cover request.FILES.get(cover) video_file request.FILES.get(video_file) if not title or not video_file: messages.error(request, 标题和视频文件为必填项) return render(request, publish.html, { categories: Category.objects.all() }) ext os.path.splitext(video_file.name)[1].lower() if ext not in ALLOWED_EXT: messages.error(request, 仅支持 mp4 / avi / mkv / mov 格式) return render(request, publish.html, { categories: Category.objects.all() }) if video_file.size MAX_SIZE_MB * 1024 * 1024: messages.error(request, 文件大小不能超过 500MB) return render(request, publish.html, { categories: Category.objects.all() }) Video.objects.create( titletitle, category_idcategory_id, covercover, video_filevideo_file, descdesc, ) return redirect(video_list) return render(request, publish.html, { categories: Category.objects.all() })这段逻辑里要注意三点。第一ext os.path.splitext(video_file.name)[1].lower() 这段是在切出文件名的后缀并转成小写避免用户传 .MP4 时被误判成不支持的格式。第二video_file.size 是 Django 上传文件对象自带的属性单位是字节所以 MAX_SIZE_MB 要乘以 1024 再乘以 1024这里漏一处就相当于没限制。第三Video.objects.create 传 category_id 而不是 category 对象能少一次查询Django 在 create 时会自动处理外键的 id 赋值。真实项目里格式校验不能只看扩展名。扩展名是 mp4 的文件内部也可能是 H.265 编码浏览器播放不了。这个后面避坑章节会展开讲这里先记住一个结论扩展名校验只是第一道门真正的兼容性要靠转码解决。4.2 播放页设计HTML5 video 标签与模板变量传递播放页是整套系统里用户唯一能直观感受到的模块。这里的核心是把 Video 对象的文件地址正确交给前端播放器。最简单的方案是直接用 HTML5 原生 video 标签不引入第三方播放器库减少依赖!-- video/templates/video/detail.html -- {% extends base.html %} {% block content %} div classplayer-wrapper video controls preloadmetadata poster{{ video.cover.url }} classvideo-player source src{{ video.video_file.url }} typevideo/mp4 抱歉您的浏览器不支持 HTML5 视频播放。 /video /div div classvideo-meta h1{{ video.title }}/h1 p分类{{ video.category.name }} | 播放量{{ video.view_count }}/p p{{ video.desc }}/p /div {% endblock %}模板里的 {{ video.cover.url }} 和 {{ video.video_file.url }} 是 FileField 和 ImageField 自带的 url 属性Django 会把 MEDIA_URL 和文件路径拼接成完整可访问的地址。这里最容易犯的错误是直接写 {{ video.video_file.path }}path 返回的是服务器物理绝对路径浏览器拿到这个路径是访问不了的必须用 url。preloadmetadata 这个属性值得说一下。它告诉浏览器先只加载视频的元数据比如时长和封面信息用户真正点击播放时才拉取视频内容。如果一个页面加载大量视频数据会直接把本地带宽打满加上这个属性可以明显加快列表页和详情页的响应速度。前端如果想做更丰富的控制栏比如倍速、画中画、进度缩略图可以在此基础上叠加 video.js 之类的播放器库但毕设阶段原生 video 已经能覆盖绝大多数演示需求。4.3 播放量统计和搜索筛选QuerySet 的三个常用写法播放量统计看起来简单但实现位置不同效果也完全不一样。有些版本把播放量递增写在 JavaScript 里页面一加载就发请求用户只是快速滑过去播放量照样加一。更合理的做法是在详情视图里递增保证用户真正打开了播放页才算一次# video/views.py播放量递增与相似推荐逻辑 from django.shortcuts import render, get_object_or_404 from .models import Video def video_detail(request, video_id): video get_object_or_404(Video, pkvideo_id, is_publishedTrue) video.view_count 1 video.save(update_fields[view_count]) similar Video.objects.filter( categoryvideo.category, is_publishedTrue ).exclude(pkvideo.pk)[:6] return render(request, video/detail.html, { video: video, similar: similar, })这里的 save(update_fields[view_count]) 是个被忽略的小优化。如果不指定 update_fieldsDjango 会把整条记录的所有字段重新写一遍包括可能很大的 desc 文本字段指定后只更新 view_count 一个字段SQL 语句更短并发更新时的锁粒度也更小。搜索和分类筛选功能通常会放在同一个视图里用 request.GET 接收参数# video/views.py搜索与分类筛选逻辑 from django.shortcuts import render from .models import Video, Category def search(request): keyword request.GET.get(q, ).strip() category_id request.GET.get(category_id, ) videos Video.objects.filter(is_publishedTrue) if keyword: videos videos.filter(title__icontainskeyword) if category_id: videos videos.filter(category_idcategory_id) videos videos.order_by(-created_at)[:60] return render(request, video/search.html, { videos: videos, keyword: keyword, categories: Category.objects.all(), })title__icontains 是 Django ORM 里的“包含且不区分大小写”查询。它有两个好处一是能匹配标题中任意位置的关键词二是默认忽略英文大小写搜索“django”和“Django”结果一致。keyword 先做 .strip() 处理是为了去掉用户输入的首尾空格避免搜索时因为多打了一个空格而查不到结果。一个反向的坑如果 keyword 为空videos.filter(title__icontains) 会把所有视频都捞出来。所以代码里用 if keyword 包了一层没有关键词时只按分类过滤或直接返回最新列表。这属于最常见的权限集合顺序先拿到基础 QuerySet再按条件逐层追加筛选。5. 避坑记录跑通这个 Django 视频站常见的 5 个坎5.1 首页能打开但视频和封面全部 404现象runserver 启动成功首页 HTML 正常但所有封面图和小视频链接点击后返回 404控制台能看到大量 /media/ 开头的请求失败。原因settings.py 里没有配置 MEDIA_URL 和 MEDIA_ROOT或者配置了但没有在 urls.py 中增加媒体访问路由。开发服务器默认只处理 Django 应用本身的路由不会自动映射 MEDIA_ROOT 目录。解决在 settings.py 里补上 MEDIA_URL /media/ 和 MEDIA_ROOT os.path.join(BASE_DIR, media)再按前面 3.3 节的方法在根 urls.py 中用 static() 函数把路由挂进去。改完重启服务器不要只刷新页面Django 修改路由后必须重新加载。5.2 迁移数据库时报 duplicate table 或已存在表现象用项目自带的 db.sqlite3 文件直接跑 python manage.py migrate提示某些表已经存在或者反过来迁移已经记录为已执行但访问页面时提示 no such table。原因项目自带的数据库文件和当前代码模型不一致。别人打包时可能用的是旧版本模型迁移历史记录还在但实际表结构和当前代码对不上。sqlite3 文件的表结构是写死在二进制文件里的Django 只认迁移记录不会自动帮你重建缺失表。解决把原始 db.sqlite3 备份到项目外删除原文件重新执行 makemigrations、migrate、createsuperuser再用 loaddata 导入数据。从那以后我每次拿到新项目的数据库文件第一件事就是把原始文件备份好再删掉重来绝不直接拿别人数据库凑合跑。5.3 页面没有任何样式像纯文本浏览器现象视频列表和详情页能打开也能看到文字和图片但所有 CSS 样式完全没生效页面排版全部错乱。原因开发环境下没有配置静态文件路由或 STATICFILES_DIRS 指向的目录和实际模板里加载 css/js 的路径对不上。Django 默认只按 app 内的 static/ 目录查找如果项目把静态文件放在根目录的 static/ 下必须显式配置 STATICFILES_DIRS。解决在 settings.py 中指定 STATICFILES_DIRS [os.path.join(BASE_DIR, static)]并确认模板里 {% load static %} 标签在最顶部且 {% static xxx.css %} 里的路径和文件实际位置一致。改完重启 runserver 再看。5.4 上传的 mp4 视频在播放器里黑屏现象后台能成功上传视频封面正常点击播放后只有进度条画面一直黑屏控制台也没有明显报错。原因视频编码不兼容。很多手机或剪辑软件输出的 mp4 是 H.265/HEVC 编码而浏览器对 HEVC 的支持非常有限大多只认 H.264 编码的 mp4。扩展名是 .mp4 只能保证容器格式不能保证内部编码。解决用 FFmpeg 把视频转码成 H.264 加 AAC 编码# 使用 FFmpeg 把不兼容编码转成 H.264 AAC ffmpeg -i input.mp4 -vcodec h264 -acodec aac output.mp4转码后重新上传再播放。如果项目内已经有大量视频可以在后台做一层预处理上传时用 subprocess 调用 FFmpeg 自动转码这个功能答辩时提出来也比较加分。5.5 中文乱码或显示成正方形现象后台分类名称、视频标题只要是中文的地方全部乱码或者页面显示很多问号和小方块。原因模板文件保存时不是 UTF-8 编码或者 HTML 中缺少 charset 声明。很多编辑器默认保存成 GBK浏览器按 UTF-8 解析时中文就变成乱码。Django 默认输出编码是 UTF-8但模板文件本身的编码不受 Django 控制。解决用编辑器把出问题的模板文件另存为 UTF-8 without BOM并确保每个模板的 head 里都有 。另外 name 字段在 settings.py 中也可以补一行 DEFAULT_CHARSET utf-8双保险。6. 答辩前必做的一件事把系统从“能跑”变成“能演示”6.1 五分钟演示脚本先定好流程再上台答辩现场最容易出的状况不是代码不会写而是演示顺序混乱。我总结了一套比较顺的流程先打开首页展示视频列表和分类导航让老师看到整体结构点开一个视频播放展示播放器、封面、简介和播放量切到搜索框输入一个关键词验证筛选和搜索再打开 /admin 登录进入后台展示视频管理列表最后新建一条测试视频回到前台确认它出现在列表里。这套流程一共五分钟每个动作对应一个技术点老师问什么都能接得上。6.2 用 Django TestCase 做一次回归验证答辩前我在本地跑一套冒烟测试确保修改代码后核心页面没有变坏。Django 自带测试客户端不需要额外装工具# video/tests.py验证播放详情页和搜索页可访问 from django.test import TestCase from django.urls import reverse class VideoPageTests(TestCase): def test_detail_page_ok(self): resp self.client.get(reverse(video_detail, args[1])) self.assertEqual(resp.status_code, 200) def test_search_page_ok(self): resp self.client.get(reverse(search)) self.assertEqual(resp.status_code, 200)执行 python manage.py test 之后两个用例全绿再上台。这个小环节能帮你拦掉很多低级错误比如路由写错、模板加载失败这类一改代码就翻车的问题。测试用例不用写太多覆盖打开首页、视频详情、搜索页、登录页四条主链路就够了。6.3 数据准备演示数据宁多勿乱分类下放上 3 到 5 条视频视频列表空荡荡的页面在答辩时会让项目效果大打折扣。上传前先规划好分类比如按“热门电影、纪录片、编程公开课”三个方向每个分类下放 3 到 5 条视频封面图统一尺寸让列表页视觉整齐一些。时长最好是几十秒到两分钟的短视频完整长视频加载慢演示时等缓冲反而尴尬。答辩前一天我会强制自己把整条链路从头走一遍建虚拟环境、装依赖、清数据库、重新迁移、导入数据、启动服务器、测试用例、按脚本演示。从那以后我每次拿到新的毕设代码都先按这条路完整跑一遍再动任何代码省掉了无数次临时翻车。希望这篇文章能帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。