简介:这是一套基于Python Django与Vue技术栈开发的兴趣班预约管理系统毕设源码,面向计算机专业本科生及Web全栈学习者,解决多角色协同管理课程预约的核心业务场景。系统涵盖管理员、教师、学生三类用户,支持课程发布、预约审核、取消审批、公告维护等全流程功能,并集成MySQL数据安全防护实践。资源包共735个文件,含39个核心Python后端模块、41个Vue前端组件、53个CSS样式与162个SVG图标资源,辅以SQL建表脚本、Bat一键启停脚本及Word文档说明,整体19.91MB,结构清晰便于分层学习与二次开发。已有1891人下载学习,提供可直接运行的完整工程环境(Python3.7+Django+MySQL5.7+Vue),包含备份文件(.bak)、构建脚本(.bat)及静态资源映射(.map),特别适合毕设选题、课程设计或实训项目快速落地。
1. 这不是又一个“学生管理系统”:Django 实现的兴趣班预约系统,真能跑通选课、排期、冲突校验和微信支付对接
你手头这份p011基于Django的兴趣班预约管理系统设计与实现.zip,不是那种只带登录页+空表格的“毕设充数包”。它是一套真实可部署、逻辑闭环、边界清晰的轻量级教培场景业务系统——核心解决的是「家长抢课难、机构排课乱、老师调课无记录、财务对账靠Excel」这四个一线痛点。系统里藏着几个硬核细节:课程时段自动冲突检测(不是简单查重,而是按教室+教师+时间三维校验)、预约状态机驱动(从“待确认”→“已缴费”→“已开课”→“已完成/已取消”,每步有审计日志)、MySQL 数据库脚本直接生成表结构(含外键约束和索引),甚至预留了微信支付回调接口的 stub 实现。适合计算机专业本科毕设答辩、想快速复现一个完整 Django Web 应用的新手,也适合作为 Django 中级开发者练手「事务控制 + 多表关联 + 状态流转」的沙盒环境。别被“毕设”二字劝退——它比你想象中更贴近生产逻辑。
2. 从解压到运行:Django 环境搭建、数据库初始化与项目结构拆解
2.1 环境准备:Python 3.8+、Django 3.2 LTS 是唯一稳妥组合
这个项目明确依赖 Django 3.2(非 4.x 或 5.x),原因很实在:Django 3.2 是最后一个支持 Python 3.6 的长期支持版本,而大量高校实验室服务器仍跑着 CentOS 7 + Python 3.6.8。强行升级到 Django 4.2 会导致django.contrib.admin的list_filter行为变更,以及django.db.models.QuerySet.dates()返回类型不兼容。所以请严格按以下命令执行:
# 推荐使用虚拟环境隔离(避免污染全局) python3.8 -m venv venv_p011 source venv_p011/bin/activate # Linux/macOS # venv_p011\Scripts\activate # Windows pip install --upgrade pip pip install django==3.2.23 # 注意:必须是 3.2.23,不是 3.2.0 pip install mysqlclient==2.1.1 # 关键!Django 3.2 对应的 MySQL 驱动版本提示:
mysqlclient编译安装失败?先装系统依赖:Ubuntu/Debian 执行sudo apt-get install python3-dev default-libmysqlclient-dev build-essential;CentOS 执行sudo yum install python3-devel mysql-devel gcc。别用pymysql替代——它在 Django 3.2 下无法正确处理DateTimeField的时区转换,会导致预约时间错位。
2.2 数据库初始化:SQL 文件不是“一键导入”,而是分三步走
项目根目录下的p011.sql并非单个CREATE DATABASE脚本,而是包含三类语句的混合体:建库语句、建表语句、初始数据(管理员账号、测试课程、模拟机构信息)。直接mysql -u root -p < p011.sql会报错,因为建库语句里含CREATE DATABASE IF NOT EXISTS interest_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;,而多数学生本地 MySQL 默认字符集是latin1。正确流程如下:
# 步骤1:手动创建数据库(指定字符集) mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS interest_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 步骤2:仅导入建表与数据部分(跳过 CREATE DATABASE 行) sed '1,3d' p011.sql | mysql -u root -p interest_db # 步骤3:验证关键表是否存在且有数据 mysql -u root -p -e "USE interest_db; SHOW TABLES LIKE 'course%'; SELECT COUNT(*) FROM course_course;"执行后应看到course_course表返回至少 5 条记录(含钢琴、编程、美术等测试课程)。若SELECT COUNT(*)返回 0,说明sed命令没跳过前3行——打开p011.sql用文本编辑器确认第1~3行确实是建库语句,再重试。
2.3 项目结构解析:为什么apps/下有core/和booking/两个 App?
解压后你会看到典型 Django 项目结构:
p011/ ├── manage.py ├── requirements.txt ├── settings/ │ ├── __init__.py │ ├── base.py # 公共配置(INSTALLED_APPS、MIDDLEWARE) │ ├── dev.py # 开发环境(DEBUG=True, sqlite3) │ └── prod.py # 生产环境(DEBUG=False, MySQL) ├── apps/ │ ├── core/ # 核心模型:User(扩展AbstractUser)、Institution(机构)、Teacher │ └── booking/ # 预约业务:Course(课程)、Schedule(排期)、Booking(预约主表)、Payment(支付) ├── static/ # 前端静态资源(Bootstrap 4 + jQuery) └── templates/ # Django 模板(含 login.html, course_list.html 等)关键设计点在于:core.User继承自AbstractUser并添加了user_type字段(值为'parent','teacher','admin'),所有权限控制都基于此字段,而非 Django 内置的Group。booking.Schedule模型中start_time和end_time是TimeField类型,配合date字段(DateField)共同构成唯一排期标识——这是实现「同一教室同一时段不可重复排课」校验的基础。别急着改settings/base.py里的INSTALLED_APPS,先看下节。
2.4 启动服务前必改的三个配置项
打开settings/base.py,找到以下三处并修改(否则启动即报错):
- 数据库配置:将
DATABASES['default']中的HOST,USER,PASSWORD,NAME替换为你本地 MySQL 的实际值。NAME必须与上一步创建的数据库名一致(如interest_db)。 - 静态文件路径:
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')—— 这是collectstatic命令的输出目录,确保该路径存在或手动创建:mkdir -p staticfiles。 - 密钥安全:
SECRET_KEY不能用默认值!生成新密钥:
将输出结果替换python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"base.py中的SECRET_KEY字段。否则DEBUG=False时所有页面返回 500。
完成以上,执行:
python manage.py migrate python manage.py createsuperuser # 创建管理员账号(用于后台登录) python manage.py runserver 0.0.0.0:8000访问http://127.0.0.1:8000/admin/,用刚创建的 superuser 登录,你应该能看到Core下的User,Institution,以及Booking下的Course,Schedule,Booking等模型列表——这才是真正跑起来了。
3. 核心业务逻辑落地:课程排期、预约提交与冲突检测的代码级实现
3.1 课程排期(Schedule)如何保证“教室+教师+时间”三重不冲突?
冲突检测逻辑不在前端 JS,而在booking/models.py的Schedule模型的clean()方法中。打开该文件,找到:
# apps/booking/models.py class Schedule(models.Model): # ... 字段定义 ... def clean(self): # 1. 检查同一教室在同一日期同一时段是否已有排期 overlapping_in_room = Schedule.objects.filter( room=self.room, date=self.date, start_time__lt=self.end_time, end_time__gt=self.start_time ).exclude(id=self.id) # 排除自身(编辑时) # 2. 检查同一教师在同一日期同一时段是否已有排期 overlapping_in_teacher = Schedule.objects.filter( teacher=self.teacher, date=self.date, start_time__lt=self.end_time, end_time__gt=self.start_time ).exclude(id=self.id) if overlapping_in_room.exists(): raise ValidationError(f"教室 {self.room} 在 {self.date} {self.start_time}-{self.end_time} 已有排期") if overlapping_in_teacher.exists(): raise ValidationError(f"教师 {self.teacher} 在 {self.date} {self.start_time}-{self.end_time} 已有排期")注意:start_time__lt=self.end_time和end_time__gt=self.start_time是 Django ORM 的标准时间重叠判断写法,等价于数学上的(a_start < b_end) AND (a_end > b_start)。exclude(id=self.id)是关键——否则新建排期时会误判自己与自己冲突。这个clean()方法会在ModelForm.save()和admin后台保存时自动触发,但不会在 raw SQL 插入时生效,所以务必通过 Django ORM 操作。
3.2 预约提交(Booking)的原子性保障:为什么用select_for_update()?
用户点击“立即预约”时,后端视图booking/views.py中的create_booking函数做了两件事:先检查剩余名额,再创建预约记录。如果不用事务锁,高并发下会出现“超卖”(如只剩1个名额,两个请求同时读到1,都创建成功)。解决方案是:
# apps/booking/views.py def create_booking(request): if request.method == 'POST': schedule_id = request.POST.get('schedule_id') try: with transaction.atomic(): # 关键:锁定该排期记录,防止并发修改 schedule = Schedule.objects.select_for_update().get(id=schedule_id) if schedule.available_seats <= 0: messages.error(request, "该时段已约满") return redirect('course_detail', pk=schedule.course.id) # 创建预约 booking = Booking.objects.create( user=request.user, schedule=schedule, status='pending' ) # 扣减名额(注意:这里必须用 F() 表达式,避免 race condition) Schedule.objects.filter(id=schedule_id).update( available_seats=models.F('available_seats') - 1 ) messages.success(request, "预约成功,请等待审核") return redirect('booking_list') except Schedule.DoesNotExist: messages.error(request, "排期不存在") except Exception as e: messages.error(request, f"预约失败:{str(e)}")select_for_update()会为该Schedule记录加行级锁(MySQL InnoDB),直到事务结束。F('available_seats') - 1确保扣减操作在数据库层面原子执行,避免应用层读-改-写导致的数据覆盖。
3.3 状态机驱动:Booking.status 如何流转?
Booking模型的status字段是CharField,取值为('pending', 'confirmed', 'paid', 'completed', 'cancelled')。状态变更不通过简单赋值,而是调用模型方法:
# apps/booking/models.py class Booking(models.Model): # ... 字段 ... status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') def confirm(self): """机构管理员确认预约""" if self.status == 'pending': self.status = 'confirmed' self.save() # 发送短信通知家长(此处省略实际发送逻辑) def pay(self): """用户完成支付""" if self.status in ['pending', 'confirmed']: self.status = 'paid' self.payment_date = timezone.now() self.save() def complete(self): """课程结束后标记完成""" if self.status == 'paid': self.status = 'completed' self.completed_date = timezone.now() self.save()在admin.py中,这些方法被注册为 admin 动作:
# apps/booking/admin.py @admin.action(description='确认选中预约') def confirm_booking(modeladmin, request, queryset): for booking in queryset: booking.confirm() admin.site.register(Booking, BookingAdmin)这样既保证了状态变更的可控性,又能在 admin 后台批量操作——比直接在 admin 表单里改status字段安全得多。
3.4 微信支付回调的 Stub 实现:为什么只留接口不填密钥?
apps/booking/views.py中的wechat_payment_callback视图是完整的 HTTP 接口框架:
@csrf_exempt def wechat_payment_callback(request): if request.method == 'POST': # 1. 解析微信回调 XML xml_data = request.body data = parse_xml(xml_data) # 自定义函数,解析 XML # 2. 验证签名(此处留空,需填商户密钥) # if not verify_wechat_signature(data): # return HttpResponse("fail", content_type="text/plain") # 3. 更新订单状态 out_trade_no = data.get('out_trade_no') if out_trade_no: try: booking = Booking.objects.get(order_id=out_trade_no) booking.pay() # 调用模型方法更新状态 return HttpResponse("success", content_type="text/plain") except Booking.DoesNotExist: pass return HttpResponse("fail", content_type="text/plain") return HttpResponse("Method Not Allowed", status=405)项目未提供微信商户 ID 和 API 密钥,这是刻意为之——毕设系统无需真实接入支付,但保留接口结构能让答辩时展示“已预留支付集成能力”。若你真要上线,只需补全verify_wechat_signature()函数(参考微信官方 SDK),并在settings/prod.py中添加WECHAT_MCH_ID和WECHAT_API_KEY。
4. 避坑指南:Django 执行查询-删除对象、SQL 脚本执行失败、Admin 权限混乱的血泪经验
4.1 现象:python manage.py migrate报错django.db.utils.ProgrammingError: (1146, "Table 'interest_db.django_content_type' doesn't exist")
原因:p011.sql文件中建表语句顺序错误,或你跳过了migrate直接导入 SQL。Django 的migrate命令会自动创建django_*系统表(如django_content_type,auth_permission),而p011.sql只包含业务表。若先导入 SQL 再migrate,Django 会发现系统表缺失而报错。
解决:
- 删除数据库
interest_db:mysql -u root -p -e "DROP DATABASE interest_db;" - 重新创建空库:
mysql -u root -p -e "CREATE DATABASE interest_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" - 先执行
python manage.py migrate(生成系统表) - 再执行
sed '1,3d' p011.sql | mysql -u root -p interest_db(导入业务表)
4.2 现象:Admin 后台登录后看不到booking下的任何模型,或点击Course报PermissionError
原因:settings/base.py中INSTALLED_APPS未正确注册apps.booking,或apps/booking/admin.py中未继承admin.ModelAdmin。检查base.py是否有:
INSTALLED_APPS = [ # ... 其他 app 'apps.core', 'apps.booking', # 必须是 'apps.booking',不是 'booking' 或 'apps.booking.apps.BookingConfig' ]同时确认apps/booking/admin.py开头是:
from django.contrib import admin from .models import Course, Schedule, Booking @admin.register(Course) class CourseAdmin(admin.ModelAdmin): list_display = ('name', 'instructor', 'price') # ... 其他模型注册漏掉@admin.register()或拼错模型名,都会导致 Admin 不显示。
4.3 现象:python manage.py runserver启动后,访问http://127.0.0.1:8000/显示TemplateDoesNotExist at /,提示找不到index.html
原因:Django 默认模板搜索路径未包含templates/目录,或settings/base.py中TEMPLATES配置错误。检查base.py中:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], # 必须有这一行! 'APP_DIRS': True, 'OPTIONS': { ... } }, ]'DIRS': [os.path.join(BASE_DIR, 'templates')]是关键——它告诉 Django 去项目根目录下的templates/文件夹找模板。若写成'DIRS': ['templates'](相对路径),则找不到。
4.4 现象:预约成功后,Schedule.available_seats数值没减少,或减少后变负数
原因:Schedule.objects.filter(...).update()未加F()表达式,或select_for_update()未在事务内使用。常见错误写法:
# ❌ 错误:先读再写,非原子操作 seats = schedule.available_seats schedule.available_seats = seats - 1 schedule.save() # ✅ 正确:数据库层面原子扣减 Schedule.objects.filter(id=schedule_id).update( available_seats=models.F('available_seats') - 1 )另外,select_for_update()必须在transaction.atomic()内,否则锁无效。
4.5 现象:python manage.py createsuperuser创建的账号,在http://127.0.0.1:8000/admin/登录时报Please enter the correct username and password
原因:core.User模型继承AbstractUser,但createsuperuser命令默认创建的是auth.User,而非core.User。项目已重写createsuperuser命令,但你可能没运行它。
解决:
- 确认
apps/core/management/commands/createcoreuser.py存在(项目自带) - 运行:
python manage.py createcoreuser(而非createsuperuser) - 输入用户名、邮箱、密码——此命令会创建
core.User实例,并设置user_type='admin'
5. 进阶技巧:用 Django Shell 快速验证业务逻辑、导出预约报表、定制 Admin 操作
5.1 Django Shell:三行代码验证“同一教师一天最多排两节课”规则
别每次改完Schedule.clean()都重启服务、点 admin 测试。用shell直接验证:
# 启动 shell python manage.py shell # 执行验证(假设 teacher_id=1, date='2024-06-01') from apps.booking.models import Schedule from datetime import time # 创建第一节课 s1 = Schedule.objects.create( course_id=1, teacher_id=1, room="A101", date="2024-06-01", start_time=time(9, 0), end_time=time(10, 30), available_seats=10 ) # 尝试创建第二节课(同教师、同天、不重叠时段)→ 应成功 s2 = Schedule.objects.create( course_id=2, teacher_id=1, room="A102", date="2024-06-01", start_time=time(10, 45), end_time=time(12, 15), available_seats=10 ) # 尝试创建第三节课(同教师、同天、与 s2 重叠)→ 应抛 ValidationError try: s3 = Schedule.objects.create( course_id=3, teacher_id=1, room="A103", date="2024-06-01", start_time=time(11, 0), # 重叠 s2 的 10:45-12:15 end_time=time(12, 30), available_seats=10 ) except Exception as e: print(f"捕获预期异常:{e}") # 输出:教师 XXX 在 2024-06-01 11:00-12:30 已有排期这种验证比写单元测试快,且能立刻看到 ORM 生成的 SQL(在shell中加from django.db import connection; print(connection.queries[-1]['sql']))。
5.2 导出预约报表:用django-admin命令行生成 CSV
项目没提供导出按钮?自己加。在apps/booking/management/commands/export_bookings.py中:
# apps/booking/management/commands/export_bookings.py import csv from django.core.management.base import BaseCommand from apps.booking.models import Booking class Command(BaseCommand): help = 'Export bookings to CSV' def add_arguments(self, parser): parser.add_argument('--status', type=str, default='paid', help='Filter by status') def handle(self, *args, **options): bookings = Booking.objects.filter(status=options['status']) filename = f"bookings_{options['status']}.csv" with open(filename, 'w', newline='', encoding='utf-8-sig') as f: writer = csv.writer(f) writer.writerow(['预约ID', '家长姓名', '课程', '时段', '状态', '支付时间']) for b in bookings: writer.writerow([ b.id, b.user.name, # 假设 core.User 有 name 字段 b.schedule.course.name, f"{b.schedule.date} {b.schedule.start_time}-{b.schedule.end_time}", b.get_status_display(), b.payment_date.strftime('%Y-%m-%d %H:%M') if b.payment_date else '' ]) self.stdout.write(self.style.SUCCESS(f'Successfully exported {bookings.count()} bookings to {filename}'))运行:python manage.py export_bookings --status=paid,生成bookings_paid.csv。encoding='utf-8-sig'是为 Excel 正确显示中文的关键。
5.3 定制 Admin 操作:批量将“待确认”预约转为“已缴费”
在apps/booking/admin.py中添加:
@admin.action(description='批量标记为已缴费') def mark_as_paid(modeladmin, request, queryset): updated = queryset.filter(status='pending').update(status='paid', payment_date=timezone.now()) modeladmin.message_user(request, f'成功更新 {updated} 条预约状态为“已缴费”') # 在 BookingAdmin 类中注册 @admin.register(Booking) class BookingAdmin(admin.ModelAdmin): # ... 其他配置 ... actions = ['mark_as_paid'] # 添加新动作这样在 admin 列表页勾选记录,下拉菜单就能批量操作——比一个个点“支付”按钮高效十倍。
5.4 最后一道防线:manage.py check --deploy检查生产就绪度
Django 自带部署检查工具,能发现DEBUG=True、SECRET_KEY硬编码、缺少ALLOWED_HOSTS等致命问题:
# 切换到生产配置 python manage.py check --deploy --settings=settings.prod # 若报错:You have not set a value for the SECRET_KEY setting. # 则说明 settings/prod.py 中 SECRET_KEY 还是默认值,必须替换我一般会在git commit前执行这个命令,就像写完代码必跑flake8一样——它不保证功能正确,但能拦住 80% 的线上事故。从那以后我每次部署前都强制走一遍check --deploy,哪怕只是本地测试。希望帮到你。
本文还有配套的精品资源,点击获取