简介:基于Django框架的视频点播网站完整源码包,主要面向计算机、数学、电子信息等专业的学生,适合用作课程设计、期末大作业或毕业设计参考资料。项目实现了视频播放、收藏、后台管理等核心功能,页面与后端逻辑均打包在内,下载后可直接配置运行,便于对照源码理解Django的MTV开发模式和路由分发机制。资源包共178个文件,包含49个Python源码文件、47个HTML页面模板、40个pyc编译文件,以及JavaScript脚本、CSS样式表、图片和演示视频等,压缩包大小约14.56MB。当前已有125人学习下载。这份源码的价值在于结构清晰:视频列表、详情播放、后台编辑、个人中心等典型模块都分别对应独立的模板与视图逻辑,样式表与脚本文件也展示了前端界面和交互效果的构建过程。对于希望快速掌握Django项目组织方式、并在此基础上二次开发或完成课程作业的学习者来说,是一份便于拆解和参考的完整示例。
1. 先别急着装播放器:Django 做视频点播到底在做什么
把这份源码解压后,我第一反应是目录里没有 video.js、没有 ckplayer,只有 base.html、index.html、detail.html、profile.html、video_edit.html 和 style.css、semantic.custom.css、admin.css、reset.css、dropload.css。这个结构反而让人放心:视频点播网站的核心不在播放器外壳,而在 Django 的路由分配、ORM 建表、用户收藏关系、后台管理、静态资源托管这一整套 MTV 流程。它适合做课程设计、期末大作业和毕设底子,也适合刚学完 Django 想见完整项目的人。下面我按拆这类源码的顺序,把它跑通、讲透,并指出哪些地方容易被当成“现成功能”而忽略。
2. 项目骨架与数据模型:URL 路由、Video 表和 Favorite 表怎么联动
2.1 从 base.html 看 Django 模板继承与静态资源组织
Django 模板和前端模板不太一样,它靠{% extends %}和{% block %}完成页面复用。资源里所有 HTML 页面几乎都会继承 base.html,打开 base.html 能看到 reset.css、style.css、semantic.custom.css 都被统一引入。常见做法是再把自定义页面里的独有样式放进{% block extra_css %},避免每个页面重复引一堆 link。
{% load static %} <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}视频点播{% endblock %}</title> <link rel="stylesheet" href="{% static 'css/reset.css' %}"> <link rel="stylesheet" href="{% static 'css/style.css' %}"> <link rel="stylesheet" href="{% static 'css/semantic.custom.css' %}"> {% block extra_css %}{% endblock %} </head> <body> {% block content %}{% endblock %} </body> </html>{% static %}标签最终会拼上 settings.py 里的STATIC_URL,默认是/static/。所以 index.html、detail.html 里引用的{% static 'css/xxx.css' %}都能被 Django 的开发服务器直接解析。这里有个容易踩的坑:如果项目后面接了 Nginx,Django 只负责生成页面,/static/ 下的文件会交给 Nginx 统一处理,否则每次请求都会打回 Python 进程,并发一高页面就会卡。
2.2 视频与收藏的 ORM 模型设计
一个视频点播网站的数据模型不需要设计得特别复杂,核心就是视频表、分类表、收藏表。收藏关系必须把登录用户和视频关联起来,并且不能让同一用户对同一视频重复收藏。这是 Django 模型层最值得看的部分。
from django.conf import settings from django.db import models class Category(models.Model): name = models.CharField(max_length=30, unique=True) class Video(models.Model): title = models.CharField(max_length=120) description = models.TextField(blank=True) video_file = models.FileField(upload_to='videos/%Y/%m/') cover = models.ImageField(upload_to='covers/%Y/%m/', blank=True) category = models.ForeignKey(Category, null=True, on_delete=models.SET_NULL) views_count = models.PositiveIntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title class Favorite(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='favorites') video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='favorite_set') created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('user', 'video')video_file里的upload_to='videos/%Y/%m/'是按年月建子目录,避免所有视频堆在一个目录里;cover用 ImageField,需要安装 pillow,否则迁移时会报错。on_delete=models.CASCADE表示用户或视频删除后,收藏记录同步删除,不会留下脏数据。unique_together是数据库层面做唯一约束,比只在业务代码里判重更可靠。
| 字段 | 类型 | 说明 |
|---|---|---|
| title | CharField | 视频标题,长度 120 |
| video_file | FileField | 视频文件,按年月存储 |
| cover | ImageField | 封面图,可留空 |
| category | ForeignKey | 分类,分类删了不删视频 |
| views_count | PositiveIntegerField | 播放数,只增不减 |
| Favorite.user | ForeignKey | 关联 Django 自带 User |
| Favorite.video | ForeignKey | 关联视频,收藏目标 |
2.3 路由映射与视图分层:列表页、播放页、个人中心怎么分
拆解这个项目时,我最先看的是 urls.py,因为页面之间怎么跳转都在这里定死。路由用path()而不是旧式正则,匹配参数直接写int:pk,比正则清晰很多。
from django.urls import path from . import views app_name = 'video' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('video/<int:pk>/', views.VideoDetailView.as_view(), name='detail'), path('profile/', views.ProfileView.as_view(), name='profile'), path('video/edit/<int:pk>/', views.video_edit, name='video_edit'), ]app_name = 'video'是命名空间,模板里引用时写{% url 'video:detail' video.id %}。这个项目里 index.html 负责列表展示,detail.html 是播放页,profile.html 是当前登录用户的收藏列表,video_edit.html 承担后台编辑视频的入口。用类视图的好处是ListView自带分页,DetailView自带 404 逻辑,不用每个视图都手写 try except。
3. 视频播放与收藏接口:从 HTML5 video 标签到 Ajax 请求
3.1 播放页怎么把视频文件安全地交给模板
Django 的播放页不需要自己写流媒体服务器,只需要在DetailView里把视频对象取出来,模板里用 HTML5<video>标签引地址。播放量统计不要在__str__或者模板里做,要在视图层用F()表达式做原子更新,不然并发点击时会丢数据。
from django.db.models import F from django.views.generic import DetailView from .models import Video class VideoDetailView(DetailView): model = Video template_name = 'detail.html' context_object_name = 'video' def get_object(self, queryset=None): video = super().get_object(queryset) Video.objects.filter(pk=video.pk).update(views_count=F('views_count') + 1) return video模板中播放器部分:
<video controls preload="metadata" poster="{{ video.cover.url }}"> <source src="{{ video.video_file.url }}" type="video/mp4"> </video>video.cover.url和video.video_file.url是 FileField 根据 MEDIA_URL 拼出来的完整地址。开发环境必须手动把媒体路由挂到 urls.py 下:
from django.conf import settings from django.conf.urls.static import static urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)这里有一个需要想清楚的边界:static()只适合开发调试,生产环境不会由 Django 直接返回视频文件,而是由 Nginx 托管 /media/ 目录。如果视频需要登录后才能看,还要再加一层权限判断,不能直接把文件地址暴露出去。多数毕设项目保留普通播放方式就够用。
3.2 收藏与取消收藏:Ajax POST、CSRF 与 JSON 响应
收藏功能用整页刷新也能做,但用户体验差。比较标准的做法是点击按钮后发一个 POST 请求,后端返回 JSON,前端根据favorited字段切换按钮样式。这个接口需要同时处理“收藏”和“取消收藏”,所以用切换逻辑而不是create。
import json from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.views.decorators.http import require_POST from .models import Video, Favorite @login_required @require_POST def toggle_favorite(request): data = json.loads(request.body) video_id = data.get('video_id') video = Video.objects.filter(pk=video_id).first() if not video: return JsonResponse({'code': 404, 'message': '视频不存在'}, status=404) favorite, created = Favorite.objects.get_or_create(user=request.user, video=video) if not created: favorite.delete() return JsonResponse({'code': 0, 'favorited': False}) return JsonResponse({'code': 0, 'favorited': True})@require_POST限制了只能通过 POST 访问,避免用户用地址栏直接 GET 误删收藏数据。get_or_create返回一个布尔值,created=True表示新建成功,created=False表示之前已经收藏过,此时执行删除。前端 fetch 时要带上 CSRF token,否则 Django 会返回 403。
fetch('/favorite/toggle/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, body: JSON.stringify({ video_id: videoId }) })getCookie('csrftoken')是从 cookie 里读取 csrftoken 的通用函数,Django 的 CsrfViewMiddleware 校验的就是这个值。你需要确保模板里有{% csrf_token %}或在渲染时写入了这个 cookie。
3.3 profile.html 里把收藏列表查出来
个人中心页最忌讳的做法是直接在模板里写user.favorite_set.all。这样每渲染一个收藏项,Django 都要再查一次关联视频的 title、cover,形成 N+1 查询。资源里的 profile.html 差不多就是个人中心加收藏清单的组合页,正确做法是使用select_related预取外键。
from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import ListView from .models import Favorite class ProfileView(LoginRequiredMixin, ListView): template_name = 'profile.html' context_object_name = 'favorites' paginate_by = 6 def get_queryset(self): return Favorite.objects.filter(user=self.request.user)\ .select_related('video')\ .order_by('-created_at')LoginRequiredMixin会在未登录时跳到登录页。select_related('video')生成的 SQL 会带 JOIN,一次性把 Favorite 和 Video 查出来。模板里遍历 favorites 时直接访问favorite.video.title就不会触发额外查询。如果你发现这个页面响应慢,先看数据库 query 数量,而不是急着上缓存。
4. 后台管理实战:自定义 Django admin 与视频录入
4.1 注册模型与 list_display 快速生成后台
Django 自带的 admin 已经能覆盖视频点播网站的后台管理需求,关键是怎么把列表页调成能用的状态。默认注册模型后,后台只显示一个对象名称,完全没法做内容运营。加一张VideoAdmin把常用字段铺开是第一步。
from django.contrib import admin from .models import Video, Favorite, Category @admin.register(Video) class VideoAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'category', 'views_count', 'created_at') list_filter = ('category', 'created_at') search_fields = ('title', 'description') list_per_page = 20 readonly_fields = ('views_count', 'created_at')list_display决定后台列表显示哪些列,list_filter是右侧筛选器,search_fields会生成搜索框,list_per_page控制每页数量。readonly_fields把播放统计和创建时间设为只读,避免手滑改掉数据。这样视频点播网站的后台管理就已经具备“内容录入、筛选、搜索”能力了。
4.2 admin.css 与 video_edit.html:两条定制路线
资源里同时存在 admin.css 和 video_edit.html,这其实是后台管理的两条路线。admin.css 是给 Django admin 换肤的,常见做法是在模型 admin 里通过 Media 指定额外样式:
class VideoAdmin(admin.ModelAdmin): class Media: css = { 'all': ('admin/css/admin.css',) }把 admin.css 放进某个 app 的 static/admin/css/ 目录下,后台页面就会加载它覆盖默认样式。另一条路线是video_edit.html这种自定义表单页,在 Django admin 之外写一个视频编辑视图,模板里放自己的表单控件,提交后手动保存 Video 记录。它的好处是可以定制字段布局、加前端校验,坏处是要重复处理权限、表单验证和错误回显。我一般建议:课程设计和大部分内部工具直接用 admin,只有对外给运营团队用才考虑自定义表单页。
4.3 后台关联收藏数据,用 TabularInline 看用户行为
收藏记录一般不需要在后台手动新增,但管理员可能想看看某个视频被哪些用户收藏了。把 Favorite 作为内联插到 VideoAdmin 里就能实现。
class FavoriteInline(admin.TabularInline): model = Favorite extra = 0 readonly_fields = ('user', 'video', 'created_at') def has_add_permission(self, request, obj=None): return FalseTabularInline 以表格形式展示关联记录,extra = 0表示不额外显示空行。has_add_permission禁用手动新增,因为收藏行为只能由用户在前端触发。这种限制也让后台管理更贴近真实业务,而不是一个脱离功能的 CRUD 面板。
5. 前端整合:dropload 下拉加载、index.html 列表渲染与分页
5.1 dropload 是什么?为什么需要 dropload.css
首页视频列表一多,直接全量渲染会拖慢首屏。这个资源里出现的 dropload.css 是配合 dropload.js 实现下拉加载的配套样式,控制加载提示动画的位置和转圈效果。dropload 和 Django 的分页方式是天然搭配:前端滚动到底部时请求下一页 JSON,后端返回当前页数据,前端拼接 HTML。
$('#video-list').dropload({ scrollArea: window, loadDownFn: function (me) { $.ajax({ url: '/api/videos/?page=' + nextPage, dataType: 'json', success: function (data) { if (data.has_next) { appendVideoCards(data.items); nextPage += 1; me.resetload(); } else { me.lock(); me.noData(); } }, error: function () { me.resetload(); } }); } });scrollArea指定滚动容器,默认是 window。loadDownFn是触底回调,me.resetload()必须在下一次请求前调用,否则插件会处于加载中状态;me.lock()用来在数据全部加载后锁住下拉,配合me.noData()显示“没有更多了”。dropload.css 里定义的.dropload-load、.dropload-noData是加载状态和底部状态的样式类,自己改布局时优先调整这两个类。
5.2 给 dropload 写一个 JSON 分页视图
首页第一次渲染可以用 Django 模板,但下拉加载必须有一个返回 JSON 的接口。这里不适合直接套ListView,因为它默认返回 HTML。写一个独立视图更清晰:
from django.core.paginator import Paginator from django.http import JsonResponse from django.views.decorators.http import require_GET from .models import Video @require_GET def video_api(request): page_num = int(request.GET.get('page', 1)) video_list = Video.objects.all().order_by('-created_at') paginator = Paginator(video_list, 8) page_obj = paginator.get_page(page_num) items = [] for video in page_obj: items.append({ 'id': video.id, 'title': video.title, 'cover': video.cover.url if video.cover else '', 'views_count': video.views_count, 'url': reverse('video:detail', args=[video.id]) }) return JsonResponse({ 'page': page_obj.number, 'has_next': page_obj.has_next(), 'items': items })get_page即使传入超出范围的页码也不会抛异常,会返回最后一页。has_next决定前端是否继续加载。reverse在这里生成详情页链接,前端拿到后直接location.href或拼到卡片上。注意这个接口没有做登录限制,因为在首页列表播放量展示不需要登录。
5.3 style.css 与 semantic.custom.css 的分工
拿到这种前后端不分离的 Django 项目,CSS 文件命名和职责要分清。reset.css 重置浏览器默认 margin 和 padding,保证各浏览器下页面基准一致;style.css 是项目主体样式,负责布局、间距、卡片和导航栏;semantic.custom.css 是对 Semantic UI 风格的定制补丁,里面一般放图标、按钮、标签等组件的微调。
改界面时我习惯把自定义样式额外放在一个 custom.css 里,而不是直接改 semantic.custom.css。因为后者的类名往往关联多个页面,一删一改就会牵动 detail.html、profile.html 的显示效果。从这个资源的页面数量看,风格统一靠的是这套 CSS 分层,而不是内联 style,这对后续扩展页面非常重要。
6. 从源码到在线:本地复现、数据库迁移与 Django 部署排错
6.1 必做的 settings 环境配置
拿到源码第一件事,不是立刻runserver,而是先在项目根目录建虚拟环境,装依赖。ImageViewField 用到了 pillow,视频文件本身不需要额外库,但封面如果使用 ImageField 就必须装。
python -m venv venv source venv/bin/activate pip install django pillow python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver如果 zip 里提供了 requirements.txt,就改用pip install -r requirements.txt。settings.py 里至少要确认MEDIA_ROOT、MEDIA_URL、STATIC_URL三个配置存在,否则视频上传后访问不到文件。
6.2 启动前用一条命令检查部署风险
Django 自带一个检查部署风险的命令,很多人不知道:
python manage.py check --deploy它会输出一堆安全警告,包括 SECRET_KEY 是否硬编码、DEBUG 是否开启、ALLOWED_HOSTS 是否为空。热词里经常能看到的“Python Django 搭建 Web 项目”踩坑,大部分都能在这一步暴露出来。生产环境还要执行collectstatic把 admin 和自定义静态文件收集到指定目录:
python manage.py collectstatic --dry-run--dry-run只显示计划复制的文件,不真正复制。确认无误后去掉这个参数执行一次,否则关闭 DEBUG 后后台管理页会完全没有样式。
6.3 典型报错与排查路径
| 现象 | 优先排查 | 常见原因 |
|---|---|---|
| 视频地址 404 无法播放 | MEDIA_URL 是否挂载到 urls | 忘记拼接 static() 或 production 未指向媒体目录 |
| admin 页面无样式 | collectstatic 是否执行 | DEBUG=False 后未收集静态文件 |
| 收藏按钮提示 403 | CSRF token 是否传递 | fetch 请求没有带 X-CSRFToken 头 |
| 收藏接口返回 302 而不是 JSON | 用户是否已登录 | LoginRequired 拦截 Ajax 请求,会被重定向到登录页 |
| dropload 一直转圈 | jQuery 和 dropload 引入顺序 | dropload.js 必须在 jQuery 之后加载 |
收藏接口返回 302 这个问题最容易忽略。你需要在 Ajax 里判断response.redirected或后端返回 401 而不是自动跳转。我一般会在@login_required之外包一层 JSON 判断,让未登录用户收到明确提示。
最后一个实用技巧:开发环境里把媒体和静态路径都交给 Django 处理没问题,但一旦把项目切到正式环境,不要用 runserver,而是用 gunicorn 启动,Nginx 单独托管 /static/ 和 /media/。启动前先跑一遍python manage.py check --deploy和collectstatic --dry-run,这两个命令能拦下大部分部署上线问题。
本文还有配套的精品资源,点击获取