简介:这是一个基于Django开发的在线学习平台完整项目,附带详细设计文档,面向计算机相关专业的学生、教师及企业开发者,可用于毕业设计、课程设计、初期项目演示,也可作为从理论走向工程实践的学习案例。压缩包内共收录783个文件,核心代码由97个Python文件构成,后端逻辑清晰可读;前端部分包含118个HTML页面、182个JavaScript脚本与54个CSS样式表,覆盖页面结构、交互效果与视觉样式;另有超过200张PNG/JPG图片素材、12个GIF动画,以及PO/MO多语言文件和SQLite数据库文件,整体压缩包仅5.33MB,下载和部署都很轻量。项目代码已经过运行测试,并获导师指导认可,评审答辩分达到95分,可直接套用或二次开发,例如扩展课程管理、用户认证、在线测验、学习记录等模块;配套文档中还包括环境配置、功能模块说明和数据库结构介绍,方便快速上手和排错维护。目前该资源已有56人学习下载,对于需要搭建在线教育平台原型或学习Django完整项目的读者来说,是一份性价比很高的参考资料。
1. 基于Django的在线学习平台:资料齐全的项目包,到底能让你少走多少弯路
拿到一个标注“资料齐全+详细文档”的Django在线学习平台项目包,很多人的第一反应是解压、跑起来、看效果。但真上手才发现,绝大多数时间不是花在写代码上,而是花在“为什么数据库连不上”“为什么图片全挂”“为什么登录后又被踢回登录页”这类问题上。这个项目之所以值得折腾,是因为它把在线教育场景里最常用的一套东西都做了:课程分类、章节管理、视频播放、用户注册登录、后台维护。你能直接看到Django的MTV分层怎么落进真实业务,也能摸清楚ORM、认证、Admin这些核心模块的配合方式。适合刚学完Django基础想找完整项目练手的人,也适合要做课程设计或快速搭教学站点的开发者。
2. 拆开压缩包看门道:目录结构、MTV分层和数据模型设计
2.1 先看懂Django项目的标准目录,别急着点运行
Django项目实战新手最容易犯的毛病,就是一解压就python manage.py runserver,结果屏幕上飘着红字却不知道去哪改。正确顺序是先花十分钟浏览目录结构。一个标准的Django项目包,通常由“项目配置目录”和“应用目录”两部分组成。
online_learning/ ├── manage.py # 入口脚本,所有命令都从它进 ├── requirements.txt # 依赖清单,pip安装全靠它 ├── db.sqlite3 # SQLite数据库文件(如果作者提供的话) ├── media/ # 用户上传的图片、视频等媒体文件 ├── static/ # 项目级静态资源(CSS/JS/图片) ├── templates/ # HTML模板存放处 ├── config/ # 项目配置目录 │ ├── settings.py # 核心配置文件 │ ├── urls.py # 全局路由入口 │ ├── wsgi.py # 上线部署时用 │ └── asgi.py # 异步/WebSocket入口 └── apps/ ├── users/ # 用户应用(注册、登录、个人信息) ├── courses/ # 课程应用(课程、章节、视频) └── orders/ # 订单应用(购买、支付记录)拿 Djangoproject 来说,manage.py 是所有命令的执行入口,比如createsuperuser、migrate都由它转发给django-admin。config/settings.py 里写着数据库配置、app注册列表和静态文件指向,后面改配置基本都是动它。apps 下按业务拆成多个子应用,是 Django 官方推荐的模块化做法——用户、课程、订单各管各的,互不纠缠。
我在本地复现这类项目时,一般第一件事就是打开 settings.py 看 INSTALLED_APPS。这个列表里注册了哪些应用,直接决定你跑migrate时会建出哪些数据表。如果项目里明明有课程模块但你没把它注册进去,那后台管理页面里就永远看不到课程表。
2.2 课程、章节、视频的数据模型是怎么设计的
在线学习平台的核心数据链路很清晰:一门课程包含多个章节,一个章节下面挂多节课(视频)。在 Django 里,这种“一对多”关系靠外键实现。项目包里的 models.py 通常会长这样:
# apps/courses/models.py from django.db import models from django.utils import timezone class Course(models.Model): """课程模型:存放课程的基础信息""" title = models.CharField(max_length=200, verbose_name='课程名称') intro = models.TextField(verbose_name='课程简介') cover = models.ImageField(upload_to='covers/', verbose_name='封面图') created_at = models.DateTimeField(default=timezone.now, verbose_name='创建时间') class Meta: verbose_name = '课程' verbose_name_plural = verbose_name def __str__(self): return self.title class Chapter(models.Model): """章节模型:通过外键关联到课程""" course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='chapters', verbose_name='所属课程') name = models.CharField(max_length=100, verbose_name='章节名称') order = models.PositiveIntegerField(default=0, verbose_name='排序权重') class Meta: ordering = ['order'] verbose_name = '章节' class Lesson(models.Model): """课时模型:挂在章节下面,可以是视频或图文""" chapter = models.ForeignKey(Chapter, on_delete=models.CASCADE, related_name='lessons', verbose_name='所属章节') title = models.CharField(max_length=200, verbose_name='课时标题') video_url = models.URLField(blank=True, verbose_name='视频链接') content = models.TextField(blank=True, verbose_name='图文内容') duration = models.PositiveIntegerField(default=0, verbose_name='视频时长(秒)')这段代码里值得注意的参数有三个。on_delete=models.CASCADE表示删除课程时,它下面的所有章节和课时会被一并清掉,这是最常用的级联策略,省得你手动清理脏数据。related_name='chapters'给外键起了一个反向查询名,这样在模板里可以直接写course.chapters.all()拿到该课程下的所有章节,非常顺手。ordering = ['order']让章节列表默认按 order 字段升序排列,避免手动排序的麻烦。
如果你拿到的项目里用的是VideoField或者直接把视频文件传到 media 目录,也属正常。很多教学项目为了降低托管成本,会用video_url存外部视频链接,这个字段设计上的取舍,后面二次开发时要注意。
2.3 用户体系与学习进度:Django自带的User够用吗
在线学习平台逃不掉用户系统。常见做法是优先用 Django 内置的django.contrib.auth,它已经封装好了用户名密码校验、session 管理、登录状态判断这三大件。你不需要自己写登录逻辑,只要在 urls.py 里引入LoginView和LogoutView就能跑通基本流程。
# config/urls.py from django.contrib.auth import views as auth_views from django.urls import path urlpatterns = [ path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'), path('logout/', auth_views.LogoutView.as_view(), name='logout'), ]只用 Auth 模块能解决“能不能登录”,却解决不了“这个用户学到哪了”。所以学习进度得自己建模型来记。设计思路很简单:记录用户、课时、以及看到第几秒。
# apps/learning/models.py from django.contrib.auth.models import User from django.db import models from apps.courses.models import Lesson class LearningProgress(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户') lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, verbose_name='课时') progress = models.FloatField(default=0, verbose_name='播放进度(秒)') completed = models.BooleanField(default=False, verbose_name='是否学完') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') class Meta: unique_together = ('user', 'lesson')unique_together是这里最关键的约束——它保证同一个用户对同一节课最多只有一条进度记录,后续更新进度时执行update_or_create即可,不会像没加约束时那样越攒越多。updated_at用auto_now会在每次保存时自动写入当前时间,省得你手动维护时间戳。
3. 用Python 3在本地把项目跑起来:从安装依赖到启动服务
3.1 创建虚拟环境并安装依赖:requirements.txt的完整用法
跑 Django 项目第一步不是装 Django,而是建虚拟环境。用 venv 隔离环境是最稳的做法,千万别图省事全局安装,否则不同项目间的依赖版本会互相打架。
# 创建并激活虚拟环境(Python 3.10/3.11均可) python3 -m venv venv source venv/bin/activate # 安装项目依赖 pip install -r requirements.txtpython3 -m venv venv会在当前目录生成一个名叫 venv 的虚拟环境目录,里面是独立的Python解释器和pip。source venv/bin/activate激活后,终端前缀会变成(venv),这时候你执行pip install装的所有包都只进这个环境,不会污染系统Python。
关于 requirements.txt:如果里面用的是 Django 3.x,建议直接升级到 4.x 或 5.x 再跑,老版本的 Django 和新的 Python 3.11+ 之间偶尔会有兼容报错。装完依赖后先执行python -c "import django; print(django.get_version())"确认版本,能少踩很多莫名其妙的坑。
3.2 修改settings.py里的三项配置:数据库、App注册和静态文件
拿到项目包后,settings.py 通常需要手工调整。最常改的就是三处:SECRET_KEY(临时用无所谓)、DEBUG(本地调试开True)、ALLOWED_HOSTS(本地填localhost或127.0.0.1)。但还有两个容易忽略的点:数据库配置和静态文件路径。
# config/settings.py # 本地调试用SQLite,零配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # 确保你的业务app都在这里注册 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 第三方和业务应用 'apps.users', 'apps.courses', 'apps.orders', 'apps.learning', ] # 静态文件和媒体文件指向 STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / 'static'] MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media'我在多个项目里发现一个高频翻车点:拿到别人的项目后,直接把 INSTALLED_APPS 原样保留,结果对方代码里引用了某个没注册的app,启动服务后访问页面就报TemplateDoesNotExist或者NoReverseMatch。所以每次迁移项目,我都会逐项核对一遍 INSTALLED_APPS 和代码里的实际 import。SQLite 的好处是零配置,适合先跑通;如果你需要体验 MySQL 的生产配置,后面第6章会专门提。
3.3 数据迁移与初始化:makemigrations、migrate和fixture导入
模型写好后,要让数据库里有对应的表结构,得跑两行命令。第一行生成迁移文件,第二行真正执行建表。
# 先生成迁移脚本(自动扫描models.py的变化) python manage.py makemigrations # 再执行迁移,让数据库结构生效 python manage.py migrate # 查看迁移执行是否正常 python manage.py showmigrationsmakemigrations做的事情是把 models.py 里的类转换成一段 Python 脚本文件,存在各 app 的 migrations 目录下,这段脚本描述“要建哪些表、有哪些字段、外键指向谁”。migrate则是把脚本翻译成真实执行的 SQL,在数据库里建表。新手容易混淆二者,记住一句话:makemigrations 是写剧本,migrate 才是登台演出。
如果项目包里有 .json 格式的初始数据文件(比如 courses_data.json),导入课程数据用 loaddata:
python manage.py loaddata courses_data.jsonloaddata 会把文件里的课程、章节、课时数据按模型关系写进数据库。常见的失败原因是 JSON 里的外键 ID 和你本地数据库里的 ID 对不上——比如文件里写着 course_id: 1,但你数据库里已经有条 ID 为1的数据了,于是报冲突。解决办法是导入前清空对应表,或者直接删掉 db.sqlite3 重新迁移,保证一个干净状态。
3.4 创建超级用户并登录后台,验证核心页面
课程数据导入后,创建管理员账号:
python manage.py createsuperuser按提示输入用户名、邮箱(可空)、密码。密码要求8位以上且不要太简单,否则会拒绝。创建完毕后,启动服务:
python manage.py runserver 0.0.0.0:8000浏览器打开http://127.0.0.1:8000看首页课程列表,再登进http://127.0.0.1:8000/admin用刚才的超级用户账号登录,打开后台数据管理页面。这里要注意一点:打开 admin 后会看到很多内置的表,比如用户、组、权限,这些是 Django 自己生成的,不要动;你只检查自己业务 app 下面是否有课程、章节、课时这些数据表,如果有且能编辑,说明整个项目已经没有大问题了。
4. 业务模块串起来的核心链路:认证、课程列表与学习记录
4.1 登录注册不重复造轮子:Django内置认证模块怎么接
一个在线学习平台,最常见的用户动作是:注册、登录、浏览课程、听课、查看进度。Django 的django.contrib.auth已经解决了前两件事,你需要的只是在模板里加两个表单字段,以及控制哪些页面必须登录才能访问。
# apps/users/views.py from django.contrib.auth.forms import UserCreationForm from django.shortcuts import render, redirect def register(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): form.save() return redirect('login') else: form = UserCreationForm() return render(request, 'users/register.html', {'form': form})UserCreationForm是 Django 内置的注册表单,自带密码一致性校验和简单强度检查。保存用户后直接重定向到登录页,这种写法在项目里最常见。你需要理解的核心点是:Django 的 session 机制——用户登录成功后,服务器把user_id写进 session,后续请求通过中间件自动识别身份。所以你在视图里写request.user.is_authenticated就能判断当前是否已登录,不需要自己维护 token 或 cookie。
4.2 课程列表与详情页:用 QuerySet 把数据查出来再传给模板
课程列表页的常见实现方式有两种:函数视图(FBV)和类视图(CBV)。在线学习项目里两者都很常见,但 CBV 更简洁:
# apps/courses/views.py from django.views.generic import ListView from apps.courses.models import Course class CourseListView(ListView): model = Course template_name = 'courses/course_list.html' context_object_name = 'courses' paginate_by = 12 def get_queryset(self): # 只返回有章节的课程,过滤掉空壳 return Course.objects.filter(chapters__isnull=False).distinct()paginate_by = 12是每页显示12门课,超过自动分页,模板里使用page_obj这个变量渲染翻页按钮。get_queryset里我做了过滤:chapters__isnull=False表示“关联的章节不为空”,这样没挂任何内容的新建课程不会出现在前台列表,避免用户点开看到空白页。
filter里用双下划线跨表查询是 Django ORM 最常用的技巧,它翻译成 SQL 是LEFT JOIN courses_chapter ON ... WHERE courses_chapter.id IS NOT NULL。如果你对性能有更高要求,可以加.prefetch_related('chapters')减少 N+1 查询,课程多了之后这个优化特别明显。
4.3 学习进度记录:update_or_create的用法与边界条件
用户播放视频时,前端通过 Ajax 定时把“当前播放到第几秒”发给后端。后端视图要做的不是新建记录,而是更新已有记录——这正是update_or_create的典型场景。
# apps/learning/views.py import json from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from apps.learning.models import LearningProgress @csrf_exempt @require_POST def update_progress(request, lesson_id): user = request.user if not user.is_authenticated: return JsonResponse({'status': 'not_login'}, status=401) data = json.loads(request.body) progress = data.get('progress', 0) completed = progress > 95 # 播放超过95%视为学完 obj, created = LearningProgress.objects.update_or_create( user=user, lesson_id=lesson_id, defaults={'progress': progress, 'completed': completed} ) return JsonResponse({'status': 'ok', 'created': created})update_or_create第一个参数是查询条件,defaults是更新时要写入的字段。执行逻辑是先按 user 和 lesson_id 查,有则更新 defaults 里的值,没有则新建一条。created返回布尔值,告诉你刚才到底是插入了还是更新了。这个函数适合“只允许一条记录”的业务约束,配合模型里的unique_together双保险,不会出现数据爆炸。
这里有两个值得注意的边界。第一,@csrf_exempt是给 Ajax POST 请求临时关掉 CSRF 校验的,正式项目里不建议全局这么干,更稳妥的是在 JavaScript 请求头里带上X-CSRFToken。第二,判断“学完”的标准因项目而异,有些平台按百分比,有些按连续观看时长,参数阈值需要在文档里写清楚,否则验收时会扯皮。
4.4 后台管理:把课程、章节都挂进Admin后台
Django Admin 是这类项目里提效最明显的部分。开发者只要在 admin.py 里注册模型,就能获得增删改查页面、搜索、筛选和分页功能,不需要手写一张后台界面。
# apps/courses/admin.py from django.contrib import admin from apps.courses.models import Course, Chapter, Lesson class LessonInline(admin.TabularInline): model = Lesson extra = 1 @admin.register(Course) class CourseAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'created_at') search_fields = ('title', 'intro') list_filter = ('created_at',) @admin.register(Chapter) class ChapterAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'course', 'order') list_filter = ('course',)LessonInline用在 Chapter 后台里,实现“在章节页面直接添加课时”的效果,省去逐个查找关联的麻烦。extra = 1控制新增行数,页面上会多显示一行空表单。list_display决定列表页展示哪些列,超出的字段只能在详情页看;list_filter生成筛选条件,适合按课程名快速定位。如果你要复现一个在线教育后台,这个配置是最小可用版本,后续加富文本编辑、图片上传都在这套框架里扩展。
5. 新手最容易翻车的5个坑:数据库报错、静态文件404和登录失效
5.1 安装依赖报错:mysqlclient和gcc编译失败
现象:执行pip install -r requirements.txt时,在 mysqlclient 或者 uWSGI 附近报error: command 'gcc' failed with exit status 1。
原因:requirements.txt 里写了 MySQL 相关的底层包,这类包是 C 扩展,安装时需要在系统层面编一次。Windows 上还经常缺 Visual C++ Build Tools,Linux 上缺 python3-dev 头文件,于是编译直接中断。
解决:两步处理。第一步,先确认自己是否真的需要 MySQL——如果只是想本地跑通,编辑器打开 requirements.txt,把mysqlclient这一行注释掉,改用 SQLite 即可。第二步,确认要 MySQL,Linux 下先执行sudo apt-get install python3-dev default-libmysqlclient-dev build-essential,Windows 可以去官网装 Microsoft C++ Build Tools,装完再重跑 pip。
5.2 migrate时提示“table already exists”或外键约束失败
现象:执行python manage.py migrate时报django.db.utils.OperationalError: table "xxx" already exists,或者直接报外键约束失败。
原因:项目包里带了现成的 db.sqlite3 数据库文件,里面已经有表结构;而你本地又跑了 migrate 想再建一次,自然冲突。还有一种情況是作者改了模型但没清空旧迁移记录。
解决:本地复现时直接删掉 db.sqlite3 再重新迁移,这是最干净的方案。如果不想丢数据,可以执行python manage.py migrate appname zero回滚该应用的迁移记录,然后重新迁移。但作为学习复现,删库重跑的成本最低,反正导入 fixture 就能恢复课程数据。
5.3 页面能打开但图片、CSS全挂:静态文件404
现象:首页文字能显示,但样式表、图片、视频封面全打不开,浏览器 F12 控制台一堆 404。
原因:Django 在开发模式下不会自动收集项目静态文件到公共目录。如果项目的 settings.py 里设置了STATIC_ROOT但你没执行收集命令,或者模板里用的是{% static %}标签而 STATICFILES_DIRS 没配对,就出现这种“裸奔”页面。
解决:先确认 settings.py 里DEBUG = True,开发模式下 Django 会用内置服务器读 STATICFILES_DIRS。再看目录是否存在,如果不存在就创建。最后执行:
python manage.py collectstatic --noinputcollectstatic会把所有 app 和 STATICFILES_DIRS 里的静态文件复制到 STATIC_ROOT 指向的目录。--noinput跳过交互询问,方便自动化。执行完刷新页面看是否恢复。这一步在生产部署时也是必须的,下面第6章还会碰到。
5.4 中文内容变成乱码或问号
现象:后台输入课程名称“数据结构”,保存后再打开显示“???”。
原因:SQLite 的字符集默认是 UTF-8,一般不会乱码。更常见的是项目本身用了旧版 MySQL 且表结构是 latin1 字符集,或者模板文件头部没有声明charset=utf-8。另外,settings.py 里LANGUAGE_CODE如果写成en-us,某些字符显示也会别扭。
解决:三步排查。第一步看 settings.py:
LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_TZ = True第二步,如果用的是 MySQL,检查数据库字符集:
ALTER DATABASE online_learning CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;第三步,确认模板 HTML 文件头部有<meta charset="utf-8">。utf8mb4 比 utf8 多支持四字节字符(如 emoji),推荐直接用它。
5.5 登录成功后又被踢回登录页:CSRF和重定向问题
现象:输入正确的用户名密码,点登录,页面刷新后仍然停在登录页,或者跳转到了/accounts/profile/这类 404 页面。
原因:两个高频原因。一是登录页表单里没有写{% csrf_token %},POST 请求被 Django 的 CSRF 中间件拦截,登录接口根本没有执行。二是 settings.py 里没设置LOGIN_REDIRECT_URL,Django 默认跳转到/accounts/profile/,这个路径不存在。
解决:检查模板表单,确保有:
<form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">登录</button> </form>然后在 settings.py 末尾补上:
LOGIN_URL = '/login/' LOGIN_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/'LOGIN_REDIRECT_URL是登录成功后的默认去向,这里指向首页;LOGIN_URL是访问受保护页面时自动跳转的登录地址;LOGOUT_REDIRECT_URL是退出后回哪。这三个参数在任何一个 Django 项目里都是必配项,项目文档里如果没写,二次开发时一定要自己补。
6. 从本地跑通到部署上线:把Django服务搬到生产环境的三个关键动作
先用runserver跑通只算完成了 40% 的工作。runserver 是 Django 自带的开发服务器,单进程且不抗压,真上线必须换 WSGI 服务器,并处理好静态文件托管和敏感信息保护。
第一步,改配置。生产环境的 settings.py 里要把 DEBUG 置为 False,ALLOWED_HOSTS 填服务器域名或 IP,SECRET_KEY 别写在代码里,用环境变量读取:
import os DEBUG = False ALLOWED_HOSTS = ['your-domain.com'] SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY') STATIC_ROOT = BASE_DIR / 'staticfiles'第二步,收集静态文件并跑迁移,然后测试 WSGI 是否正常响应:
python manage.py collectstatic --noinput python manage.py migrate gunicorn config.wsgi:application --bind 0.0.0.0:8000gunicorn config.wsgi:application的格式是“配置目录名:应用对象名”,config 对应你项目的配置目录,wsgi 是里面的 wsgi.py 文件名,application 是文件里的 WSGI 应用变量。如果你用的是 Windows 服务器,gunicorn 不支持,改用 waitress 做替代品,启动命令是waitress-serve --port=8000 config.wsgi:application。
第三步,用 Nginx 反向代理并把静态文件交给 Nginx 托管。Nginx 收到用户请求后,如果路径以/static/开头就直接返回文件,不走 Django;其余请求转发给 gunicorn。这一步直接决定首屏加载速度——让 Django 处理静态文件,在高并发下是灾难。
真实项目里,我吃过一次亏:DEBUG 关掉后忘了执行 collectstatic,结果线上页面全裸,样式全丢,用户投诉像看纯文本站。后来养成习惯,每次部署流程里把collectstatic排在migrate前面,谁也别想跳过。这类“资料齐全”的项目包,最常见的隐藏缺口就是没把部署阶段的配置差异写清楚,文档里只讲了本地跑通,你补上这一块,才算是真正把项目用起来了。
如果你要在部署时把 SQLite 换成 MySQL,注意两点:一是DATABASES配置里 ENGINE 改成django.db.backends.mysql,加上 NAME、USER、PASSWORD、HOST 四个键;二是旧数据导到 MySQL 前操作思路和迁移初始数据一致,执行python manage.py dumpdata > backup.json备份,切库后再python manage.py loaddata backup.json。但要注意可能触发 json 里外键 ID 冲突,换库这种事,宁可重跑 fixture 也别折腾数据迁移,省下的时间够再写两个页面了。希望这套落地路径能帮到你。
本文还有配套的精品资源,点击获取