Django视频网站毕设指南:从数据模型到部署答辩全流程 做毕设的时候十个里有六七个都想做视频网站。我在做这个基于Django的在线视频电影网站时最大的感受是网上能下载的“毕设全套源码”其实不少但真正能让你顺利跑通、讲清楚、答得上答辩老师问题的并不多。很多项目要么是半成品注释乱写迁移文件缺失要么文档和代码对不上随便改一个小功能就牵一发动全身。这篇文章我想从一个完整项目的角度把设计思路、数据模型、功能实现、调试排查、文档整理到答辩准备的整条链路拆开讲尤其是一些源码里看不出来的坑。文章适合两类人一类是正在选型、准备自己做毕设的学生另一类是已经拿到了源码但改不懂、跑不通、传不上视频的同学。我会尽量把每个关键选择背后的理由讲清楚而不是只给结论。1. 项目立项与技术选型为什么视频网站选了Django1.1 毕设场景下“在线视频电影网站”到底在做什么很多人一听到“视频网站”就脑补成爱奇艺、B站那种大规模系统实际上毕设场景下的视频网站业务复杂度并不高核心就两块用户前台和管理后台。前台面对普通用户提供注册登录、浏览电影列表、查看详情、在线播放、搜索、评论、收藏、排行榜这些功能。后台面对管理员提供电影信息录入、视频文件上传、分类管理、用户管理、评论审核等功能。真正让这个项目“看起来有难度”的地方不在业务逻辑而在三个点上视频文件的存储与访问、视频播放器的集成、以及数据模型之间关系的设计。这三个点恰好也是答辩老师最关注的部分。理解了这一点你就知道为什么很多毕设选“视频网站”而不是“电商系统”——视频类项目天然自带一套完整的文件上传、展示、权限控制的链路写起来有内容讲起来有深度。1.2 Django、Flask、Spring Boot怎么选我见过不少同学在框架选择上纠结很久。这里先给结论如果你不是对Java特别熟或者学校没有明确要求必须用Spring家族毕设选题为视频网站时Django是性价比最高的选择没有之一。对比项DjangoFlaskSpring Boot上手成本低约定优于配置最低但需要自己拼装组件高概念多、配置复杂自带ORM有且功能完整无需装SQLAlchemy有但JPA学习曲线陡自带Admin后台有开箱即用无无用户认证内置完整需扩展需引入Spring Security中文学习资料非常多多多但偏企业级毕设答辩友好度高模型和视图逻辑直观中代码自由但结构靠自己中低环境问题多我最看重的是Django自带的Admin后台和用户认证体系。视频网站的管理员需要录入电影、上传视频文件这些操作如果用纯手写页面工作量大且容易出错。Django Admin可以让你在十分钟内搭出一个能用的内容管理后台你只需要把精力放在前台页面和核心功能上。还有一个容易被忽略的优势是Django的ORM写起来非常接近Python语法调试的时候可以直接在shell里跑查询语句验证结果这在答辩现场演示时很加分。1.3 模块划分与角色权限一个合格的视频网站项目通常分成三个角色、五大模块。三个角色分别是未登录游客、普通用户、管理员。游客只能浏览首页和详情页登录用户可以评论、收藏、查看播放记录管理员负责内容管理。权限控制不需要做得特别复杂Django自带的login_required装饰器和is_staff字段就能覆盖绝大多数场景。五大模块我建议这样切用户模块注册/登录/个人信息、电影模块列表/详情/播放/搜索排序、互动模块评论/收藏/评分、管理模块后台增删改查、统计模块点击量统计/排行榜。这个划分不是说代码里必须建五个app而是逻辑上要清晰。你答辩的时候被问到“项目结构是怎样的”能按这个维度讲老师会觉得你思路清楚。如果代码工程本身也按模块拆成几个app那就更好了。2. 数据库设计与核心模型2.1 用户模型不要直接改内置User表视频网站肯定需要用户系统。Django自带一套User模型包含用户名、密码、邮箱、权限等字段毕设阶段完全够用。问题在于很多初学者拿到源码后发现别人改了User表结构加了一个“昵称”或者“头像”字段然后自己照抄时发现怎么都迁移不了。这里推荐最稳妥的方案不动内置User另外建一个Profile模型用OneToOneField关联到User。代码大概是这样的from django.contrib.auth.models import User from django.db import models class Profile(models.Model): # 与内置User一对一关联 user models.OneToOneField(User, on_deletemodels.CASCADE, related_nameprofile) nickname models.CharField(昵称, max_length50, blankTrue) avatar models.ImageField(头像, upload_toavatar/%Y/%m/, blankTrue) bio models.TextField(个人简介, blankTrue) def __str__(self): return self.nickname or self.user.username这样做的核心原因有三个。第一升级Django版本时不会因为自定义User字段产生各种兼容问题。第二官方文档对OneToOne扩展的写法非常成熟网上资料多不会卡住。第三答辩时你可以解释“遵循了单一职责原则把业务扩展字段和认证核心字段解耦”这比“我改了User表”听起来专业得多。如果你已经拿到了改了User表的源码也别慌后面第5章会讲怎么迁回来。2.2 电影信息模型状态字段要留够电影/视频表是整个网站的核心字段设计直接决定了后续开发顺不顺利。我建议最少包含这些字段标题、封面图、视频文件、分类、导演、主演、简介、地区、语言、上映年份、点击量、是否上线、创建时间、更新时间。from django.db import models class Category(models.Model): name models.CharField(分类名称, max_length50) slug models.SlugField(别名, max_length50, uniqueTrue) class Meta: verbose_name 电影分类 verbose_name_plural verbose_name def __str__(self): return self.name class Movie(models.Model): STATUS_CHOICES ( (1, 已上线), (0, 已下架), ) title models.CharField(电影标题, max_length200) cover models.ImageField(封面图, upload_tocover/%Y/%m/, blankTrue) video models.FileField(视频文件, upload_tovideo/%Y/%m/, blankTrue) category models.ForeignKey(Category, on_deletemodels.SET_NULL, nullTrue, related_namemovies) director models.CharField(导演, max_length100, blankTrue) actors models.CharField(主演, max_length300, blankTrue) description models.TextField(剧情简介, blankTrue) region models.CharField(地区, max_length50, blankTrue) language models.CharField(语言, max_length50, blankTrue) year models.CharField(上映年份, max_length20, blankTrue) views models.PositiveIntegerField(点击量, default0) status models.SmallIntegerField(状态, choicesSTATUS_CHOICES, default1) created_at models.DateTimeField(创建时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue) class Meta: ordering [-created_at] verbose_name 电影 verbose_name_plural verbose_name def __str__(self): return self.title有几个细节值得展开说。第一分类字段用ForeignKey外键而不是直接存一个字符串这样后台管理时可以做成下拉框统计数据时也能按分类聚合。第二视频文件用FileField而不是URLField方便管理员直接在后台上传本地视频文件如果你打算放外部视频链接可以额外加一个video_url字段两者兼容。第三views字段用来做“点击量”每次访问详情页时加1虽然简单但是支撑排行榜功能的唯一数据来源。第四status状态字段用来下架敏感或问题影片比“直接删除记录”更安全这个设计在答辩时是加分项。2.3 评论、收藏、播放记录与评分互动模块建模视频网站不能只有“看”还得有互动。评论、收藏、播放记录是三个最常见的互动模型。class Review(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namereviews) movie models.ForeignKey(Movie, on_deletemodels.CASCADE, related_namereviews) content models.TextField(评论内容) rating models.SmallIntegerField(评分, default5) created_at models.DateTimeField(评论时间, auto_now_addTrue) class Meta: ordering [-created_at] unique_together (user, movie) # 可选一个用户对一部电影只评一次 class Favorite(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namefavorites) movie models.ForeignKey(Movie, on_deletemodels.CASCADE, related_namefavorites) created_at models.DateTimeField(收藏时间, auto_now_addTrue) class Meta: unique_together (user, movie) class PlayRecord(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameplay_records) movie models.ForeignKey(Movie, on_deletemodels.CASCADE, related_nameplay_records) play_time models.DateTimeField(播放时间, auto_now_addTrue) class Meta: ordering [-play_time]设计这三个模型时有几个常见的坑要避开。一是unique_together的使用加上之后能有效防止重复评论、重复收藏但也要注意如果业务上允许用户对同一个视频多次评价比如改评分那这个约束就不该加。大多数毕设场景下一个用户对一个电影只评一次更符合直觉所以我建议保留。二是on_delete行为的选择。User被删除时他的评论、收藏应该一并删除用CASCADE很合理。但Movie被删除时用户的历史播放记录也会被删这在业务上可以接受。如果某天你想做“播放记录不随视频删除而消失”再把on_delete改成SET_NULL并为movie字段加上nullTrue即可。三是播放记录模型不要设计得太复杂。有些同学想记录“看到第几分钟”就会加current_second字段、duration字段甚至做一个专门记录进度的模型。毕设阶段没有必要增加复杂度不说还容易在前端播放器对接时出问题。PlayRecord足够让你的“最近观看”功能正常运转。2.4 数据关系梳理与设计原则把上面几个模型画成关系图的话大概是这样的User与Movie通过Favorite、Review、PlayRecord产生多对多关系Category与Movie是一对多关系。User与Movie之间本身不直接关联都通过中间表进行。我强烈建议你在写代码之前先画出实体关系图即使只用纸笔画一遍也有价值。因为这个项目的数据关系并不复杂但答辩老师非常喜欢问“数据库为什么这样设计”。你在画图的时候就会发现为什么要有PlayRecord因为它能把“用户的观看行为”记录成一条条数据而不是每次重新猜测用户看过什么。为什么Review把评分字段放在评论里而不是单独建一个评分表因为一个用户对一部电影最多一条评论、一个评分合并存储查询效率最高还天然避免了“评论和评分不同步”的问题。另外一个设计原则是能用编码字段解决的不要建新表。比如电影状态用0/1两个整数就行地区、语言一开始用CharField存字符串等后期需要按地区筛选了再考虑升级成外键表。视频网站毕设项目最大的忌讳就是模型数量膨胀——每个模型都意味着后台要管理、页面要展示、测试要覆盖维护成本随模型数量指数上升。3. 核心功能实现细节3.1 视频文件上传MEDIA配置与文件校验视频文件上传是视频网站项目的第一个拦路虎。很多人部署好项目后在后台添加电影、选了本地视频文件上传到一半就报错或者上传成功后页面死活播放不了大多数问题都出在MEDIA配置。Django处理上传文件有三个关键配置MEDIA_URL、MEDIA_ROOT和模型里的upload_to。我的settings.py里是这样配的import os # 媒体文件访问URL前缀 MEDIA_URL /media/ # 媒体文件存放的绝对路径 MEDIA_ROOT os.path.join(BASE_DIR, media)然后在主项目的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)很多源码跑不起来就是少了最后这一段。没有它/media/video/xxx.mp4这个地址会直接404视频自然播放不了。除此之外我建议在后台上传时对视频文件做一道校验。Django的FileField不会自动限制文件类型和大小如果管理员误传了一个PDF前端播放器会直接黑屏。通过重写MovieForm的clean_video方法可以实现简单校验from django import forms from .models import Movie class MovieForm(forms.ModelForm): class Meta: model Movie fields __all__ def clean_video(self): video self.cleaned_data.get(video) if video: ext video.name.split(.)[-1].lower() if ext not in [mp4, webm, ogg, mov]: raise forms.ValidationError(不支持该视频格式请上传 mp4/webm/ogg/mov 文件) if video.size 500 * 1024 * 1024: raise forms.ValidationError(视频文件过大请压缩后上传) return video这里需要特别提醒视频文件体积是毕设项目里最容易忽略的问题。我见过太多同学把一个2GB的电影文件直接传上去结果后台卡死、浏览器加载缓慢、磁盘爆满。建议把测试视频压缩到100MB以内转成H.264编码的MP4格式这是浏览器兼容性最好的组合。压缩工具可以用格式工厂或ffmpeg命令很简单ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset fast output_small.mp4这段命令的作用是把视频重新编码-crf 28控制画质损失数值越大文件越小28是画质和体积比较平衡的点。3.2 视频播放页与播放器集成详情页是用户最常访问的页面也是整个项目能不能“看着像个视频网站”的关键。播放页的逻辑其实不复杂根据URL里的电影ID查出电影记录渲染模板把视频地址传给播放器。from django.shortcuts import render, get_object_or_404 from django.db.models import F from .models import Movie def movie_detail(request, pk): movie get_object_or_404(Movie, pkpk, status1) # 点击量 1使用F表达式避免并发问题 Movie.objects.filter(pkpk).update(viewsF(views) 1) # 相关电影推荐 related_movies Movie.objects.filter(categorymovie.category, status1)\ .exclude(pkmovie.pk)[:4] return render(request, movie/detail.html, { movie: movie, related_movies: related_movies, })这里我用了get_object_or_404它会在电影不存在时自动返回404页面省去了手写异常处理的麻烦。点击量更新用F(views) 1而不是movie.views 1这是因为直接在视图里movie.views 1然后再save()在高并发下会产生竞态问题虽然毕设流量不大但答辩老师问起来你能回答出来就是亮点。模板里播放器部分我的建议是不要自己手写HTML5 video的播放条直接用开源的video.js。原因有三第一video.js自带进度条、音量、全屏、倍速控制外观专业第二通过>link href{% static videojs/video-js.min.css %} relstylesheet ... video idmy-video classvideo-js vjs-big-play-centered controls preloadauto width100% height400 >from django.db.models import Q from django.core.paginator import Paginator from .models import Movie, Category def movie_list(request): movies Movie.objects.filter(status1) keyword request.GET.get(q, ) category_id request.GET.get(category, ) sort request.GET.get(sort, new) if keyword: # 在标题和简介中模糊搜索 movies movies.filter( Q(title__icontainskeyword) | Q(description__icontainskeyword) ) if category_id: movies movies.filter(category_idcategory_id) if sort hot: movies movies.order_by(-views, -created_at) elif sort old: movies movies.order_by(created_at) else: movies movies.order_by(-created_at) paginator Paginator(movies, 12) # 每页显示12条 page_number request.GET.get(page) page_obj paginator.get_page(page_number) categories Category.objects.all() return render(request, movie/list.html, { page_obj: page_obj, categories: categories, keyword: keyword, sort: sort, category_id: category_id, })这里有几个细节值得注意。Q(title__icontainskeyword)中的icontains表示不区分大小写的模糊匹配Django会把它翻译成SQL的LIKE %keyword%。两个Q对象用|连接表示OR关系这是实现“标题或简介都能搜到”的关键。分页使用Paginator时第二页、第三页的URL链接上要保留原有的搜索参数模板里要这样拼a href?q{{ keyword }}category{{ category_id }}sort{{ sort }}page{{ page_obj.next_page_number }}下一页/a如果不带上这些参数翻页后搜索条件就丢了这在演示时非常尴尬属于细节问题但很影响体验。3.4 登录注册与会话控制用户系统方面Django帮我们省了很多事。注册视图只需要创建一个UserCreationForm登录用内置的LoginView登出用LogoutView。from django.contrib.auth.forms import UserCreationForm from django.contrib.auth import login from django.shortcuts import render, redirect def register(request): if request.method POST: form UserCreationForm(request.POST) if form.is_valid(): user form.save() login(request, user) # 注册成功后自动登录 return redirect(movie:list) else: form UserCreationForm() return render(request, registration/register.html, {form: form})需要登录才能访问的接口在视图函数上加login_required装饰器就行。评论、收藏、播放记录这三个功能都需要登录所以在对应视图上统一加上from django.contrib.auth.decorators import login_required login_required def add_favorite(request, pk): ...还有一个低成本的加分功能在导航栏根据用户登录状态显示“登录/注册”或“欢迎用户名”。实现方法是在模板中用{{ user.is_authenticated }}判断。Django会把当前登录用户自动注入到模板上下文不需要自己传。顺便提一句这个模板变量在视图里写request.user.is_authenticated也是一样的效果。3.5 Admin后台把管理员的体验做顺Django Admin是整个项目里性价比最高的部分。你只需要在admin.py中注册模型就能获得一套完整的管理界面。但如果只是简单注册管理后台会比较简陋电影列表页直接显示一堆文件名搜索也不方便。我建议花十分钟把Admin配置优化一下。from django.contrib import admin from .models import Movie, Category, Review, Favorite, PlayRecord admin.register(Movie) class MovieAdmin(admin.ModelAdmin): list_display (title, category, year, views, status, created_at) list_filter (category, status, year) search_fields (title, director, actors) list_per_page 10 ordering (-created_at,) # 让封面图缩略图直接显示在列表页 def cover_preview(self, obj): if obj.cover: return fimg src{obj.cover.url} stylewidth:60px;height:80px;object-fit:cover; / return - cover_preview.short_description 封面 cover_preview.allow_tags True list_display (cover_preview, title, category, year, views, status, created_at)配置好之后管理员的日常工作就是选分类、填标题、上传封面、上传视频文件、点保存。这个后台在答辩时现场演示特别有用评委一眼就能看出你有一个完整的内容管理系统而不是只有前台页面。4. 项目调试、文档与远程演示的实践经验4.1 本地开发调试用对工具能省一半时间写毕设的过程中调试占掉的时间远比写代码多。我推荐在开发阶段就装上django-debug-toolbar它能直接在页面上展示本次请求用了哪几条SQL、执行时间是多少、模板渲染耗时多少。安装步骤很简单pip install django-debug-toolbar然后在settings.py里把debug_toolbar加到INSTALLED_APPS把debug_toolbar.middleware.DebugToolbarMiddleware加到MIDDLEWARE再配置一下内部IPINTERNAL_IPS [ 127.0.0.1, ]最后在主urls.py中挂路由。装好之后你打开任何一个页面侧边会多出一个调试面板点进去能看到SQL查询列表。比如列表页如果返回100条电影而页面只显示12条你会看到ORM执行了两条SQL一条查数据、一条查总数这是正常的但如果看到N1条SQL说明查询优化出了问题这时就要用select_related或prefetch_related处理。除了Debug Toolbar我还习惯用日志定位问题。在settings.py里配置一个最简单的文件日志万一网页直接报500你能在日志里看到完整的堆栈信息import os LOGGING { version: 1, disable_existing_loggers: False, handlers: { file: { level: ERROR, class: logging.FileHandler, filename: os.path.join(BASE_DIR, logs/error.log), }, }, loggers: { django: { handlers: [file], level: ERROR, propagate: True, }, }, }注意要手动创建logs目录否则会报错。4.2 远程调试给别人演示项目时怎么连线毕设项目中“远程调试”出现的频率很高因为很多同学找的源码提供方或指导者并不在同一个城市需要远程协助解决问题。就我自己的经验远程调试常见的有三种方式适用场景各不相同。第一种是云服务器部署。把自己的项目部署到一台云服务器上对方通过公网IP访问你的网站。这种方式最正规也最适合答辩演示——评委打开浏览器就能看不用你抱着笔记本。部署方案推荐nginx gunicorn django这是最经典的组合。关键步骤是在服务器上安装Python环境和nginx用pip install gunicorn安装Gunicorn然后运行gunicorn 项目名.wsgi:application --bind 0.0.0.0:8000再用nginx反向代理到80端口。这样配置后外网用户就能通过服务器的IP直接访问。第二种是内网穿透工具。如果只是临时给对方看一眼效果不想买服务器可以用内网穿透工具把本地启动的Django服务映射到一个公网临时域名上。这种方式适合开发过程中的快速演示但稳定性一般不适合正式答辩。第三种是远程桌面共享。对方通过远程桌面工具连接到你的电脑直接看你的IDE和浏览器。这种方式最适合“手把手调代码”但前提是双方网络条件都得好。不管用哪种方式远程调试前我都建议把项目跑在固定的环境里。用requirements.txt锁定依赖版本这是最基础也是最重要的。生成方式很简单pip freeze requirements.txt另外部署到云服务器后最容易遇到的问题有两个一是ALLOWED_HOSTS没有配置服务器的IPDjango会拒绝请求二是静态文件和媒体文件的路径不对。这两个问题我会在下一章详细讲排查方法。4.3 项目文档怎么写才规范“全套源码文档”中的文档很多是应付事的。但如果你是自己做毕设文档写到什么程度直接影响答辩成绩。一个标准的毕设文档通常包含这五个部分需求分析说明用户角色、功能需求、非功能需求比如“系统需要支撑多少并发”“响应时间要求”等。 系统设计包含技术架构、数据库设计附ER图、模块划分、关键接口说明。 功能实现每个核心功能模块的截图和关键代码说明注意不要大段贴代码而是讲清楚“为什么这么实现”。 测试报告测试环境、测试用例表、测试结果截图。至少要有用户注册登录、电影搜索、视频播放、后台管理这几个主要流程的测试用例。 部署说明环境要求、安装步骤、启动命令、注意事项。写文档时有一个技巧用截图代替大段文字描述。比如你写“视频播放功能”与其花500字描述播放器长什么样不如贴一张播放页截图再在图下写三行说明效果翻倍。另外文档中的代码要和上传的源码保持一致我见过太多“文档是一套代码、工程里是另一套代码”的翻车案例答辩时老师稍微一翻就能看出来。5. 常见问题排查与避坑清单5.1 数据库迁移报错顺序和一致性数据库迁移是毕设项目翻车率最高的环节。常见的报错有两类一是“No migrations to apply”明明执行了python manage.py migrate却提示没有迁移文件二是迁移到一半报错说某个字段已经存在。第一类问题多半是因为migrations目录里的迁移文件被删了或者你用的是别人拷过来的源码但db.sqlite3数据库文件没拷全。解决办法是删掉原来的数据库文件清空migrations目录中除了__init__.py之外的文件然后重新执行python manage.py makemigrations python manage.py migrate第二类问题经常发生在“在别人的源码上动手改字段名”时。比如原来的模型里有个video_url字段你改成了video但旧的迁移文件还在就会产生冲突。处理方式是如果项目还没上线可以删库重新迁移如果数据库里已经有重要测试数据则用python manage.py makemigrations --merge合并冲突迁移再逐个修正。5.2 视频上传后预览404或500“上传成功了但播放不了”是视频网站最常见的故障之一原因按出现频率排序是MEDIA路由没配置、MEDIA_ROOT路径错了、nginx没有配置媒体目录代理、视频文件本身损坏。排查顺序建议从浏览器F12网络面板开始。如果/media/video/xxx.mp4请求返回404去urls.py检查有没有加static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)如果是在服务器上部署再检查nginx的location /media/配置是否指向了正确的目录。此外注意文件权限服务器上nginx进程通常以www-data用户运行如果媒体目录的读权限不够一样会404或403。5.3 中文乱码与编码问题中文乱码主要在三个地方出现网页显示乱码、后台录入的汉字变成问号、导出数据乱码。网页显示乱码先检查HTML文件头部有没有meta charsetutf-8再检查数据库连接配置。如果用的是MySQL建库时一定要指定utf8mb4字符集CREATE DATABASE mydb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;不然的话存emoji表情或者冷门汉字时会直接报错甚至丢字。另外在settings.py中配置MySQL时加一行DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: mydb, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }如果后台录入英文正常、中文变问号十有八九是数据库连接字符集不对优先检查这一项。5.4 静态文件加载失败样式全没了页面HTML正常渲染但CSS、JS全部加载不了网址是/static/xxx.css返回404或403。这个问题的根源在于Django的静态文件机制开发模式下需要django.contrib.staticfiles这个app部署模式下需要通过collectstatic把所有静态文件集中到一个目录再交给nginx处理。开发模式下检查settings.py中STATIC_URL /static/是否正确模板中是否使用了{% load static %}和{% static css/style.css %}。部署模式下执行python manage.py collectstatic然后确保nginx配置了location /static/ { alias /你的项目路径/static/; }还有一个很容易漏的点STATICFILES_DIRS用于指定额外的静态文件目录如果你把bootstrap、video.js这些第三方库放在了项目根目录的static文件夹下一定要在settings.py里加STATICFILES_DIRS [ os.path.join(BASE_DIR, static), ]5.5 部署后显示“DisallowedHost”或白屏部署到云服务器后打开首页显示“Bad Request (400)”的几乎都是ALLOWED_HOSTS问题。把服务器的公网IP和域名加进去就行ALLOWED_HOSTS [你的服务器IP, yourdomain.com, localhost, 127.0.0.1]如果部署后访问是500页面则优先看日志。执行gunicorn时加上--error-logfile参数或者看之前配置的日志文件。常见的500原因包括数据库没迁移、缺少环境变量、DEBUGFalse后静态文件没收集、媒体文件路径不存在。排查顺序建议是先看日志确认是哪个模块报错然后逐项排查。还有一个我踩过的坑本地开发时DEBUGTrue一切正常部署到服务器把DEBUG改成False后图片和视频的访问全部404了。原因是Django在DEBUGFalse模式下不会再帮我们提供媒体文件服务必须交给nginx代理。nginx的配置要放在server块里location /media/ { alias /你的项目路径/media/; }6. 二次开发与答辩加分项6.1 低成本高回报的三个扩展点如果基础功能已经完成想在答辩前再添几个亮点我推荐三个实现成本低、但演示效果好的扩展点。第一个是观看历史。在用户中心增加“最近观看”列表从PlayRecord表取当前用户最近的10条记录按时间倒序。你能顺理成章地解释“通过记录用户行为产生了个性化推荐的数据基础”。代码量很小就是一次ORM查询加一个模板页面但对整个项目的完整性提升很大。第二个是排行榜。首页放一个“热门电影Top10”用Movie.objects.order_by(-views)[:10]取出点击量最高的十部电影再用一个简单的循环渲染成序号列表。如果想让榜单实时性强一点可以用Django的cache框架设置缓存每隔10分钟刷新一次减少数据库压力。答辩时可以顺便讲一下“排行榜模块采用了缓存策略”这类表述在评委那里是很加分的。第三个是AJAX局部刷新。收藏按钮不刷新整个页面通过fetch或者jQuery的ajax请求后台接口返回JSON数据后在原按钮上更新状态。这会让你的项目明显比同组同学“高级”一截。Django端的接口写法也很简单from django.http import JsonResponse from django.contrib.auth.decorators import login_required from .models import Movie, Favorite login_required def toggle_favorite(request, pk): movie get_object_or_404(Movie, pkpk) favorite, created Favorite.objects.get_or_create( userrequest.user, moviemovie ) if not created: favorite.delete() return JsonResponse({status: unfavorited}) return JsonResponse({status: favorited})6.2 答辩前需要提前准备的问题答辩环节说白了就是“你做了什么东西为什么这么做遇到问题怎么解决”。我把评委最常问的问题整理成了一张清单你可以对着自检一遍为什么选择Django而不用Flask数据库中的表之间是什么关系为什么这样设计视频文件存在哪里怎么保证访问速度评论和收藏功能是如何防止重复提交的如果访问量大了你这个系统哪里会最先出问题怎么优化项目的安全方面做了哪些考虑比如SQL注入、XSS、CSRF。整个项目中遇到最大的困难是什么怎么解决的对于最后一个问题不要回答“没遇到什么困难”这会让评委觉得你没有深度思考。你可以讲一个真实的调试案例比如视频上传后播放404、查了半天发现是MEDIA路由没配或者评论刷屏问题最后用unique_together解决。一个真实、完整、有解决思路的踩坑故事比你说“我做了很多功能”更有说服力。再说一下csrf问题Django默认开启了CSRF保护模板中的表单记得加{% csrf_token %}AJAX提交时在请求头中带上X-CSRFToken。这是评委非常喜欢问的安全点你可以提前把原理理清楚跨站请求伪造的本质是“服务器无法确认请求是否来自用户本人的浏览器”而CSRF Token机制通过校验随机令牌来确认请求来源可信。6.3 拿到别人的源码后应该怎么处理如果你最终选择的是网上下载的“毕设全套源码”我建议你按照下面这个顺序处理能省下大量踩坑时间。第一步别急着跑。先看README或文档里的环境要求确认Python版本和Django版本。Python 3.10和Django 4.2是比较稳妥的组合如果源码用的Django 2.x在Python 3.10以上版本大概率跑不起来。第二步创建独立的虚拟环境安装依赖python -m venv venv venv\Scripts\activate # Windows # 或者 source venv/bin/activate # Linux/Mac pip install -r requirements.txt第三步检查数据库配置。很多源码默认用SQLite直接就能跑如果写的是MySQL需要你本地建库改好用户名密码。第四步执行迁移命令启动开发服务器python manage.py migrate python manage.py runserver碰到报错不要慌先看最后两行错误信息对照本章前面的排查清单定位问题。等你跑起来之后再做一件事把整个项目的URL路由从头到尾看一遍搞清楚哪个URL对应哪个视图、哪个模板。这样你在答辩时才能回答“整个项目是怎么串起来的”。我个人在实际操作中的体会是网上的源码更像是一个“半成品”它最大的价值在于帮你快速搭建了工程结构和功能雏形但它不会告诉你每个设计背后的原因。真正把项目消化成自己的是需要你亲手改一遍、跑一遍、错一遍、修一遍的。不要抱着“下载完就能交差”的心态那样到了答辩现场老师随便问一个细节你都会卡壳。如果能踏踏实实把模型设计、视图流程、媒体配置、部署逻辑这几条线捋清楚这个项目不仅是拿得出手的毕设更是你简历里一个完整的Web开发项目案例。最后再分享一个小技巧把你修复过的每一个报错记录下来整理成自己的笔记这是答辩时最有底气的素材。