简介:这是一份基于Django框架的视频点播后台管理系统设计源码,面向Python Web入门及进阶开发者、毕业设计或课程设计人群,旨在解决视频点播内容高效管理与安全传输的问题。系统基于流行的Django框架开发,遵循MVC架构模式,通过ORM操作数据库,实现了视频信息增删改查、用户权限控制、视频上传与分组管理等功能;前端采用响应式设计,可兼容PC与移动端,同时具备防范常见网络攻击的安全考虑。资源共包含43个文件,压缩包仅1022KB。其中16个Python源码文件承载核心业务逻辑与视图处理,12个字节码文件用于运行时缓存并保护源码,7个XML配置文件负责数据库连接和应用参数,1份SQL文件可直接导入数据表结构;另有4张图片素材、2个文本说明及1个IDE配置等辅助内容。整体目录划分清晰,模型、迁移、配置与静态资源分离,便于根据目录结构快速定位和修改代码。目前已有281人学习下载。研读本套源码,能够系统掌握Django后台管理界面定制、ORM模型设计、用户认证与权限控制、项目分模块管理等实战技能;项目本身模块化程度高,扩展难度低,既适合作为个人学习Django框架的完整案例,也可为企业视频点播后台的快速搭建提供设计参考。

1. 做视频后台,难的不是播放器,而是内容和权限的组织

做视频点播后台的人都知道,播放器只是最外面一层,真正拉开工作量的是视频怎么分组、谁有上传权限、不同格式的视频怎么在上传之后被正确管理。这套基于 Django 框架的视频点播后台管理系统,把视频信息存储、检索、更新,以及用户、权限、分组的管理全部收进模型层和 Django 自带 admin 后台里,数据库初始化脚本、迁移记录和源码放在同一个包里。源码包含 14 个 Python 文件、12 个 pyc 字节码、7 个 XML 配置和一份 video.sql,覆盖了内容管理类后台最常见的几个模块。它适合有一定 Python 基础、正在改造内容管理系统或者想学习 Django admin 落地方式的开发者。

2. 拆解源码包:从文件分工看懂 Django 后台的工程结构

2.1 upload.zip 解压后,每一类文件承担的角色

解开 upload.zip,目录结构并不复杂。back_admin 这个名字同时承担了 Django 项目目录和业务应用目录的角色,在中小型项目里很常见,虽然不够「标准」,但能显著减少目录层级,适合快速交付。所有核心逻辑集中在 back_admin 下的 models.py、admin.py、views.py、urls.py、settings.py 里,uploads 和 avatar 目录则是运行时上传文件的落盘位置。

文件/目录 角色 重点内容
back_admin/models.py 数据模型 视频、分组表的字段定义
back_admin/admin.py 模型注册 后台列表页与编辑页的呈现方式
back_admin/views.py 业务视图 上传、播放、权限过滤逻辑
back_admin/migrations/ 迁移记录 0001_initial 到 0004 的建表轨迹
video.sql 数据库基准数据 建库后可直接导入
uploads/2019/11 上传资源 封面图等运行时文件
manage.py 管理入口 启动、建表、建超级用户

用命令解压和查看目录层级:

unzip upload.zip -d video_admin
cd video_admin
find . -maxdepth 3 -type d | sort

unzip 的 -d 参数指定解压目标目录,避免文件散落;find 用 -maxdepth 3 限制深度,只看前两层结构,就能快速定位 settings.py、migrations 这些关键位置。注意 .idea 和 pycache 这类目录通常不应该提交到工程里,这份源码明显是从本地项目整体打包的,保留它们反而能看出原开发环境的一些信息,比如 pycache 下的 cpython-37.pyc 就表明原作者运行的是 Python 3.7。

2.2 settings.py 里需要改的三个配置

拿到源码第一件事,不是急着运行,而是把配置过一遍。这份源码配套的是 MySQL 数据库,因为项目里带了一份 video.sql,这种结构通常意味着数据库结构已经固定,代码里的 DATABASES 配置需要和 SQL 文件里的库名、账号保持一致:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "video",
        "USER": "root",
        "PASSWORD": "your_password",
        "HOST": "127.0.0.1",
        "PORT": "3306",
    }
}

MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "uploads")

ENGINE 指定 MySQL 驱动,Django 会通过它生成对应方言的 SQL;NAME 必须和导入 video.sql 时创建的数据库同名,否则执行查询会报 OperationalError: Unknown database 'video' 。MEDIA_ROOT 指向 uploads 目录,所有上传的视频封面和临时资源都会落到这个路径下,部署时这个目录需要单独配置读写权限。如果本地没有 MySQL,也可以临时改成 SQLite,但要注意字段类型差异,ImageField、CharField 这类字段没有影响,DateTimeField 的存储格式则略有不同。

2.3 从迁移文件反推建表过程

migrations 目录下保留着 0001_initial.py、0002_auto_20191113_1603.py、0003_auto_20191113_1613.py、0004_auto_20191113_1618.py 四个文件。从命名时间可以看出,视频相关表在 2019 年 11 月 13 日下午短短十几分钟内经历了四轮调整。迁移文件是 Django 对表结构变更的版本记录,不要直接修改已生成的迁移,而是通过命令生成新的增量:

python manage.py makemigrations
python manage.py migrate
python manage.py startapp video_comment

makemigrations 只把模型变更写入迁移文件,不真正操作数据库;migrate 才会执行 DDL 建表或加字段。startapp 用于创建新的业务模块,如果要在这个项目上加评论管理、公告管理,这就是标准入口,生成的新 app 需要手动加入 settings.py 的 INSTALLED_APPS。查看某个迁移文件里的具体操作,可以直接用编辑器打开,里面是依赖关系和 operations 列表,字段变更、建索引、加外键都以代码形式保存在那儿。

3. 模型与 admin 注册:后台管理页面是怎么长出来的

3.1 视频表、分组表、用户表的关系设计

Django 后台能自动生成增删改查,前提是先把模型定义好,并注册到 admin。视频点播场景里最重要的三个模型是 Video、Category 和 Django 内置的 User。Category 做内容分组,Video 通过外键关联 Category,再用 uploader 外键关联 User。这样设计的直接好处是:分组改名时只需更新一处,所有视频自动同步。

from django.db import models
from django.contrib.auth.models import User

class Category(models.Model):
    name = models.CharField(max_length=64, unique=True)
    sort = models.IntegerField(default=0, help_text="越小越靠前")

    def __str__(self):
        return self.name

class Video(models.Model):
    title = models.CharField(max_length=200)
    category = models.ForeignKey(Category, on_delete=models.PROTECT, null=True, blank=True)
    video_url = models.CharField(max_length=255)
    cover = models.ImageField(upload_to="uploads/%Y/%m", null=True, blank=True)
    uploader = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
    play_count = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

这段代码是视频点播后台最常见的模型骨架,标题、分组、地址、封面、上传人各占一个字段。

字段 类型 参数含义
title CharField(200) 视频标题,列表页默认展示列
category ForeignKey 关联分组,PROTECT 禁止删除被引用的分组
video_url CharField(255) 视频文件路径或外链
cover ImageField 封面图,按年/月自动分目录
play_count PositiveIntegerField 播放次数,默认 0
created_at DateTimeField(auto_now_add=True) 写入时自动取当前时间

on_delete 参数值得细说。PROTECT 表示存在外键引用时阻止删除分组,避免「分组删了,视频还挂在空分组上」;SET_NULL 表示用户被删除后视频记录保留,uploader 置空。auto_now_add 只在 create 时赋值,编辑时不会更新,适合记录创建时间。

3.2 ORM 查询与删除:不要到处写原生 SQL

日常业务里,视频列表、搜索、批量删除都用 ORM 完成。Django 的 QuerySet 是懒加载的,真正执行 SQL 是在迭代或取值的时候,这个特性可以用来做分页和条件拼装:

hot_videos = Video.objects.filter(play_count__gte=100).order_by("-play_count")[:10]
Video.objects.filter(category__name="教程").delete()

filter 返回 QuerySet,可以继续链式调用;play_count__gte 是「大于等于」的字段查询,order_by("-play_count") 按播放量倒序。delete() 是批量删除,返回一个字典,例如 {'back_admin.Video': 3} ,表示删掉了 3 条视频。如果视频存在外键引用,delete 会级联处理相关对象,删除前最好先做 count 确认:

Video.objects.filter(id__in=[3, 5, 7]).count()

还有一个经常被忽略的查询场景:按分组统计视频数。用 annotate 配合聚合函数:

from django.db.models import Count
categories = Category.objects.annotate(video_count=Count("video")).filter(video_count__gt=0)
for c in categories:
    print(c.name, c.video_count)

annotate 给每个 Category 动态挂上一个 video_count 字段,用 video_count__gt=0 过滤掉空分组。这套写法能直接用在后台统计页,不需要额外写 SQL,也避免了拼接字符串带来的注入风险。

3.3 admin.py 注册:两行代码换一个管理后台

把模型注册到 admin 后,Django 会自动生成列表页、编辑页、删除确认页。对于 Category 这种结构简单的字典表,一行注册就够了;Video 这种核心业务表,建议用 ModelAdmin 定制:

from django.contrib import admin
from .models import Video, Category

@admin.register(Video)
class VideoAdmin(admin.ModelAdmin):
    list_display = ["title", "category", "uploader", "play_count", "created_at"]
    list_filter = ["category"]
    search_fields = ["title"]

admin.site.register(Category)

注册后的后台直接可用:列表页默认显示 list_display 里的字段,右侧出现 category 筛选,顶部出现 title 搜索框。Category 用 admin.site.register 注册,走的是默认展示方式,显示的是 str 的返回值。这一层写好后,视图层只需要负责业务判断,基础增删改查全部交给 admin,这正是 Django 适合做内容管理后台的核心原因。

4. 环境搭建:把 video.sql 导入 MySQL,跑通整个项目

4.1 建库、导入、确认字符集

拿到的 video.sql 是数据库基准脚本,包含建表语句和初始数据。第一步是创建同名数据库并导入,注意指定字符集,否则导入时遇到中文和 emoji 会乱码或报错。

mysql -u root -p
CREATE DATABASE video DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE video;
SOURCE /path/to/video.sql;
show tables;

utf8mb4 是 MySQL 对完整 Unicode 的支持,视频标题里出现生僻字或 emoji 都不会出问题。SOURCE 命令后面跟绝对路径或相对路径都可以,但 Windows 下不要把路径写成 C:\path\video.sql ,反斜杠会被转义。导入后执行 show tables,确认核心表都创建成功。这里可以核对表数量和 models.py 的迁移记录是否对得上,如果 SQL 里表比迁移文件多,通常说明项目后续手工改过库。

4.2 迁移文件与已有数据库的冲突处理

一个高频坑:先导入了 video.sql,再运行 migrate,报错说表已存在。原因是 Django 的迁移文件里记录了建表操作,migrate 会尝试执行一遍,而 SQL 脚本已经把这些表建出来了。此时要用 --fake-initial 标记:

python manage.py migrate --fake-initial

--fake-initial 的作用是检测到表已存在时,不再执行建表语句,直接把迁移记录写入 django_migrations 表。之后再做字段调整,就可以正常 makemigrations 和 migrate。如果表结构被手工改过,和 models.py 不一致,migrate 会报字段缺失,先手工对齐或重新生成迁移再同步。任何时候动库之前,备份都不会多余:

mysqldump -u root -p video > video_backup.sql

4.3 Python 3.7 字节码与 mysqlclient 的兼容坑

源码包里带了一堆 cpython-37.pyc,这是 Python 3.7 的字节码,Django 会在首次导入时优先使用,但字节码和 Python 版本强绑定,换解释器版本后会自动失效重新编译,直接删除 pycache 目录也没有影响。

驱动安装是最常见的一个坎。MySQL 配 Django,正常做法是装 mysqlclient:

pip install mysqlclient

在 macOS 或 Linux 上经常遇到编译错误,报错大多是 mysql_config not found ,需要先装 MySQL 开发头文件。如果不想折腾编译环境,常见的替代方案是 pymysql,在 settings.py 顶部加两行:

import pymysql
pymysql.install_as_MySQLdb()

这样 Django 通过 MySQLdb 名称引用到的实际是 pymysql。要注意 pymysql 的版本和 Django 版本兼容性,Django 3.2 以后对 mysqlclient 版本有要求,先 pip list 确认已装版本。跑通后访问 /admin 出现登录页,说明数据库连接、迁移记录、静态文件都正常。

4.4 创建超级用户并启动开发服务器

python manage.py createsuperuser
python manage.py runserver 0.0.0.0:8000

runserver 的 0.0.0.0 让服务监听所有网卡,局域网内其他机器可以通过 IP 访问,方便联调。浏览器打开 http://127.0.0.1:8000/admin,输入刚创建的账号。如果页面样式丢失,优先检查 DEBUG 是否开启;如果登录提示数据库错误,回到 4.2 查看迁移状态。

5. 视频上传、分组管理与多格式点播的落地细节

5.1 上传目录与分年月存储策略

uploads/2019/11 这种路径在源码里出现,说明封面和上传资源按时间分目录存放。这个策略在文件数量变大后非常重要:单个目录文件超过几千个,文件系统遍历速度就会明显下降。Django 视图里处理上传的常见做法是:

import os
from django.conf import settings
from django.http import HttpResponse

def upload_video(request):
    if request.method == "POST" and request.FILES.get("video_file"):
        f = request.FILES["video_file"]
        save_path = os.path.join(settings.MEDIA_ROOT, f.name)
        with open(save_path, "wb") as dest:
            for chunk in f.chunks():
                dest.write(chunk)
        return HttpResponse("ok")
    return HttpResponse("POST with video_file")

注意这里用的是 chunks() 而不是 read()。上传大文件时,read() 会把整个文件读进内存,多用户同时上传时服务很容易被打满;chunks() 按块写入磁盘,内存占用稳定。保存文件后还需要创建 Video 记录,把 path 写入 video_url 字段,这样才能在后台列表里看到并管理。MEDIA_ROOT 配置在前面 2.2 已经提到,生产环境一般把 uploads 挂到独立磁盘或对象存储。

5.2 多格式点播:响应头决定播放还是下载

视频点播的响应处理和普通下载不同。mp4、webm、m3u8 的 content_type 都不一样,StreamingHttpResponse 可以逐块返回大文件,配合 Content-Disposition 控制是内嵌播放还是强制下载:

from django.http import StreamingHttpResponse

def stream_video(request, video_id):
    video = Video.objects.get(id=video_id)
    file_path = video.video_url
    def chunk_file():
        with open(file_path, "rb") as f:
            while True:
                block = f.read(1024 * 1024)
                if not block:
                    break
                yield block
    resp = StreamingHttpResponse(chunk_file(), content_type="video/mp4")
    resp["Content-Disposition"] = f'inline; filename="{video.title}.mp4"'
    return resp

StreamingHttpResponse 接收的是可迭代对象,生成器逐段产出数据,避免大文件整体加载进内存。content_type 用于告知浏览器这是视频流,浏览器会用内置播放器解析;Content-Disposition 的 inline 表示页面内播放,attachment 则会让浏览器下载文件。这一版是教学写法,生产环境如果希望拖动进度条时响应秒开,优先用 Django 的 FileResponse,它对 HTTP Range 请求有内置支持,客户端可以按字节区间拉取。移动端点播场景,常见做法是转出 m3u8 切片再交给前端播放器加载。

5.3 分组管理与权限过滤的查询写法

用户管理和权限控制是视频后台的安全底线。Django 的权限系统会给每个模型生成 add、change、delete、view 四种权限,判断当前用户能否查看视频,常见写法是:

from django.shortcuts import get_object_or_404

def video_detail(request, video_id):
    video = get_object_or_404(Video, id=video_id)
    if request.user.has_perm("back_admin.view_video"):
        return render(request, "video_detail.html", {"video": video})
    return HttpResponseForbidden("无权限查看")

has_perm 的参数格式是 app_label.codename ,back_admin 是 app 名,view_video 是 Django 为 Video 模型自动生成的权限码。更细的业务分组,比如「运营一组只看自己上传的视频」,可以再叠加条件:

queryset = Video.objects.all()
if not request.user.is_superuser:
    queryset = queryset.filter(uploader=request.user)

超级用户跳过过滤,普通用户只看自己上传的内容。需要注意别把业务分组和 Django auth 的 Group 混用,auth 的 Group 负责权限授予,业务上的分组用 Category 或自定义字段即可。

5.4 模板层如何消费这些数据

Django 模板负责展示,视图只需要把 QuerySet 或 model 实例传给模板。列表页常见的片段是:

{% for video in videos %}
    <div class="video-item">
        <a href="{% url 'video_detail' video.id %}">{{ video.title }}</a>
        <span>{{ video.category.name }}</span>
        <span>{{ video.play_count }} 次播放</span>
    </div>
{% empty %}
    <p>暂无视频</p>
{% endfor %}

{% url %} 标签通过视图名称反向生成链接,比硬编码路径更稳妥,视图改名或路径调整时不需要改模板。模板里访问外键字段用点号链式调用,category.name 会自动触发 ORM 获取关联对象。这个片段不涉及复杂 JS,符合本轮设计的后端管理定位。

6. admin 后台列表页优化:把默认页面改成业务操作台

6.1 用 list_display、list_editable 与 list_filter 做减法

Django admin 默认列表只显示模型 str ,对操作人员来说信息量太少。调整 ModelAdmin 是优化成本最低的方案:

from django.contrib import admin
from .models import Video

@admin.register(Video)
class VideoAdmin(admin.ModelAdmin):
    list_display = ["id", "title", "category", "uploader", "play_count", "created_at"]
    list_editable = ["play_count"]
    list_filter = ["category", "uploader"]
    search_fields = ["title", "video_url"]
    list_per_page = 20
    readonly_fields = ["created_at"]

这段配置能带来几个直接变化:id 列方便和数据表对照;play_count 进入 list_editable 后,不用点进详情页就能修改展示数值;list_filter 在右侧渲染筛选面板;search_fields 决定顶部搜索框匹配哪些字段。readonly_fields 用于保护创建时间这类不允许运营改动的字段。

6.2 用 list_select_related 消除后台 N+1 查询

后台列表渲染一行就要查询一次外键,当列表页展示 category 和 uploader 时,默认会有 N+1 次查询。数据量上千后页面会明显变慢,解决办法是让 Django 用 JOIN 把关联表提前查出来:

class VideoAdmin(admin.ModelAdmin):
    list_select_related = ["category", "uploader"]

list_select_related 是 ModelAdmin 的内置属性,添加后列表查询会一次性带出外键对象。这一步不需要改视图代码,后台响应速度的提升却是实打实的。如果再用 django-debug-toolbar 的 SQL 面板核对,查询数量会从 N 次降为 1 次。刷新 /admin/video/video/ 页面,列表加载、筛选和搜索的响应速度会立刻有感知。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

邀请您加入社区

更多推荐