简介:基于Python Django框架开发的学生宿舍管理系统,配套完整数据库脚本,面向高校计算机专业学生以及正在准备课程设计、期末大作业的开发者。项目已获导师指导并通过,最终以97分高分结题,核心业务功能、依赖环境与初始数据均已配置妥当,解压后即可直接部署运行,无需二次修改。资源包共862个文件,整体大小约30.65MB,涵盖84个Python后端逻辑文件、103个前端组件、30个HTML页面、多份SQL初始化脚本,同时包含容器部署配置、服务运行参数、接口文档组件与富文本编辑组件等,前后端结构分明,方便对照理解Django框架的项目分层、接口设计与数据表关联。目录组织清晰,宿舍管理、学生信息、楼栋分配等模块均可快速定位,既能作为可运行的高分项目使用,也能帮助读者梳理完整的Web开发实现思路。目前已有247人学习下载,适合需要拿高分Django全栈项目范例进行参考或二次开发的同学。
1. 学生宿舍管理系统这道课程设计,难点不在 CRUD
很多人的“宿舍管理系统”就是几张表配上增删改查,交上去能跑,答辩时却经不起三个问题:空床位怎么统计、分配为什么不均衡、删数据时关联记录怎么办。这套基于 Python 和 Django 的课程设计之所以能拿高分,不是因为界面多漂亮,而是把宿舍管理中真正烦人的需求落在了数据模型和 ORM 上,并且把“有源码有数据库”做成了可交付的状态,而不是临时拼凑的演示稿。
这篇博文不是给你一段贴上去就能跑的运行说明,而是按一个完整课程设计的做法拆开讲:宿舍楼的分层、床位分配、报修流程、统计查询、Admin 后台定制、性能排查,以及答辩验收前怎么把数据造得够漂亮。目录规模我会按下述结构组织,只讲你实际能用到的部分。
2. Django 宿舍管理系统的数据模型:先想清楚表,再谈源码
2.1 宿舍楼的层级关系怎么建模
一个宿舍管理系统的核心无非是“楼栋 - 楼层 - 房间 - 床位 - 学生 - 入住记录”。很多课程设计把房间和床位直接混在同一张表里,结果调整床位时要改好几处,答辩时被追问就只能支支吾吾。正确做法是把层级拆开,至少分成宿舍楼、宿舍房间、学生、入住记录四张核心表。
这里的建模是有讲究的:宿舍楼和房间是一对多关系,房间和学生是一对多关系,但“学生入住”这件事本身应该单独用一条记录表示,因为同一个学生可能在学期中换宿舍,历史记录要保留。下面是一个常见的 models.py 结构,注释已经标清了每个字段的作用:
from django.db import models from django.contrib.auth.models import User class DormBuilding(models.Model): name = models.CharField('楼栋名称', max_length=50, unique=True) address = models.CharField('楼栋地址', max_length=100, blank=True) floor_count = models.PositiveSmallIntegerField('楼层数', default=6) def __str__(self): return self.name class DormRoom(models.Model): building = models.ForeignKey(DormBuilding, on_delete=models.CASCADE, verbose_name='所属楼栋') room_number = models.CharField('房间号', max_length=20) capacity = models.PositiveSmallIntegerField('可住人数', default=4) # 床位总数 gender = models.CharField('性别限制', max_length=1, choices=[('M', '男'), ('F', '女')]) class Meta: unique_together = ('building', 'room_number') # 同一楼栋房号唯一上面 DormRoom 里没有单独建床位表,因为对大多数课程设计来说,房间容量就是床位总数,入住记录里的人数就是已占数。如果你要支持“床号”维度,再加一张 Bed 表即可,但这会让代码量翻倍,分数不一定涨,先把层级讲清楚更重要。
2.2 models.py 里最容易加分的字段设计
接下来是学生和入住记录。学生表不建议直接继承 Django 的 AbstractUser,那样会把用户系统和学生信息耦合,等你要批量导入 Excel 时会很难受。常见做法是单独写一个 Student 模型,用 OneToOneField 关联到 User 作为登录账号,不关联也能跑,看你要不要做登录。
class Student(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True) student_no = models.CharField('学号', max_length=20, unique=True) name = models.CharField('姓名', max_length=30) department = models.CharField('学院', max_length=50) major = models.CharField('专业', max_length=50) phone = models.CharField('联系电话', max_length=11, blank=True) def __str__(self): return f'{self.student_no} {self.name}' class CheckInRecord(models.Model): student = models.ForeignKey(Student, on_delete=models.PROTECT, verbose_name='学生') room = models.ForeignKey(DormRoom, on_delete=models.PROTECT, verbose_name='宿舍房间') checkin_date = models.DateField('入住日期', auto_now_add=True) checkout_date = models.DateField('退宿日期', null=True, blank=True) is_active = models.BooleanField('是否当前有效', default=True)字段设计里最容易被忽略的是is_active。筛选某一时刻住在某房间的人,不要去统计 CheckInRecord 记录条数,而是直接过滤is_active=True。on_delete=models.PROTECT也有讲究:如果学生有入住记录,就不允许直接删学生,这比默认的 CASCADE 更符合教务现实——学生如果换过宿舍,删除学生会导致历史记录消失,答辩时这是加分项。
2.3 从模型到数据库迁移的命令和交付物规范
模型写完之后,下面这套命令是从零开始最标准的流程:
# 在项目根目录执行,创建 Django 项目和应用 django-admin startproject dormitory manage.py startapp dormitory_app # 在 settings.py 的 INSTALLED_APPS 里加入 'dormitory_app' 后执行迁移 python manage.py makemigrations dormitory_app python manage.py migrate # 创建管理员账号,用于进入 admin 后台 python manage.py createsuperuser迁移执行后,数据库里会自动生成十几张表,包括 Django 自带的 session、auth 相关表,以及我们上面定义的四张业务表。课程设计里的“数据库”指的不是一张孤立的 .sql 文件,而是这套迁移文件加上一个能从头复现环境的完整工程。我一般建议把db.sqlite3保留一份,同时把migrations/目录里的迁移文件完整提交,这样才能算真正的“源码+数据库”。
3. 宿舍分配与组合查询:把 ORM 用出“业务逻辑”感
3.1 贪心分配:按学院和班级优先凑满楼层
宿舍分配是整个系统里最能体现“业务逻辑”的地方,也是答辩时最能展示编程思维的部分。最常见的需求是:同学院、同专业的学生优先住同一区域,尽量安排在同一栋楼,不要出现一个学院的学生分散在七八个楼的情况。这本质上是一个带约束的分组问题。
下面我用一个贪心算法来实现:优先把同一专业的学生分到同一房间,房间满了再进下一间,专业内的人不够填满一个房间时就跨专业凑满。这个算法不追求全局最优,但速度快、实现直观、演示效果好。
from itertools import groupby from django.db.models import Count def assign_rooms(room_batch, student_batch): """ 贪心分配:先按专业分组,专业内顺序分配,一个房间装满后开下一个 room_batch: 指定一组宿舍房间 QuerySet,需预排序 student_batch: 待分配学生 QuerySet,按专业排好序 返回分配结果列表 """ result = [] room_iter = iter(room_batch) room = next(room_iter) current_count = 0 for student in student_batch: # 房间已满则换下一间 if current_count >= room.capacity: room = next(room_iter) current_count = 0 # 分配记录 CheckInRecord.objects.create(student=student, room=room, is_active=True) result.append((student, room)) current_count += 1 return result调用前要把学生按专业排序,并且只把尚未分配的学生传进来。这里几行代码就是完整的分配逻辑,不依赖复杂的第三方库。缺陷也很明显:如果某个专业人数特别多,后面的专业会离得很远,但课程设计的场景下这个复杂度够了。如果你想拿分更高,可以加一句说明“这是贪心的解,换成运筹学里的二分图匹配可以做全局最优”,老师就会知道你有扩展思维。
3.2 aggregate + Case/When 统计宿舍空位
宿舍系统最常做的统计是“哪些楼还有空位”“每栋楼入住率是多少”。新手最容易写成这样:把所有房间全部取出来,在 Python 里循环计算。
# 不推荐:循环里发 SQL,房间数量一多就慢 rooms = DormRoom.objects.all() for room in rooms: used = CheckInRecord.objects.filter(room=room, is_active=True).count()如果房间只有几十间,这样写没感觉;但如果一个校区有几千间宿舍,每间房一条 count SQL,就是几千次数据库往返。正确写法是使用 Django 的 ORM 聚合函数,一次性查出每个房间的已住人数和空床位数:
from django.db.models import Count, Case, When, IntegerField, Sum room_stats = DormRoom.objects.annotate( used_count=Count('checkinrecord', filter=Q(checkinrecord__is_active=True)), empty_count=Count('checkinrecord') * 0 # 占位,下面用 capacity 减 )上面这种 Count 写法有 bug,因为 Count 会去重,当房间没有任何入住记录时,Count 结果恒为 1。更好的写法是直接数入住表再关联:
room_stats = DormRoom.objects.annotate( used_count=Count( 'checkinrecord', filter=Q(checkinrecord__is_active=True) ) ).annotate( empty_count=F('capacity') - F('used_count') ).filter(empty_count__gt=0)这里用到了Count的 filter 参数,以及F表达式完成 SQL 层的减法计算。最终empty_count大于零的就是还有空位的房间,再把它聚合到楼栋维度就能看到每栋楼的入住情况。
3.3 删除操作的坑:Queryset.delete() 与 instance.delete()
Django 的删除看起来只是一个调用,但里面有两个答辩高频坑。第一个坑是批量删除不触发模型的delete()方法,无论你重写这个方法来清理日志还是回写状态,Queryset.delete()都会直接绕过去,走 SQL 级删除。第二个坑是默认的级联删除:删掉一栋楼会导致该楼所有房间、入住记录全部被 CASCADE,一旦误操作数据就全没了。
# 正确:删除单个学生时判断是否有入住记录 def safe_delete_student(student_id): student = Student.objects.get(pk=student_id) has_record = CheckInRecord.objects.filter(student=student).exists() if has_record: raise ValueError('该学生存在入住记录,请先办理退宿再进行删除') student.delete()推荐在 admin 后台里把delete_selected动作重写,或者干脆在模型里用on_delete=PROTECT挡一层。装配了 PROTECT 之后,删除操作会抛出 ProtectedError,需要你显式去处理,这比让数据悄悄消失要安全得多。
4. 视图、路由与 Admin 后台:让课程设计“看起来像产品”
4.1 FBV 还是 CBV,课程设计怎么选
宿舍管理系统的页面不多,无非是学生列表、房间列表、入住登记、报修列表。常见做法是用 FBV,函数视图,因为逻辑直白,你读起来不绕。答辩时老师问“你这个代码是怎么走的”,FBV 能从头到尾讲清楚,而 CBV 一上来就是 get/post/queryset 的继承链,你极可能把自己讲晕。
不过为了显得你不只会写过程式,可以在用到表单校验的接口上用一点 DRF 序列化,或者把查询接口做成 JSON 返回,让前端用简单模板渲染。下面是一个典型的房间查询视图:
from django.http import JsonResponse from django.contrib.auth.decorators import login_required from django.db.models import Count, Q @login_required def room_list_api(request): building_id = request.GET.get('building_id', '') rooms = DormRoom.objects.filter(building_id=building_id) if building_id else DormRoom.objects.all() rooms = rooms.annotate( used=Count('checkinrecord', filter=Q(checkinrecord__is_active=True)) ).values('id', 'room_number', 'capacity', 'used') result = list(rooms) for item in result: item['empty'] = item['capacity'] - item['used'] return JsonResponse({'code': 0, 'data': result})这个视图做了一件事:允许前端按楼栋 ID 过滤房间列表,并在数据库查询阶段就算出已住人数,一次 SQL 返回所有房间的空位信息。参数说明:building_id是查询参数,不传就查全部;Count上加了 filter 条件,只统计有效入住记录。返回值里的empty是前台直接可以用于展示的字段。
4.2 Admin 定制:list_filter/search_fields/readonly_fields 的组合
Django Admin 是课程设计里的“隐藏分系统”。如果你什么都不改,后台就是一个原始的数据管理页面,老师看了无感;但稍微配置一下,就能体现出你对 admin 机制的理解。最值得改的是 DormRoomAdmin 和 StudentAdmin:
from django.contrib import admin from .models import Student, DormRoom @admin.register(DormRoom) class DormRoomAdmin(admin.ModelAdmin): list_display = ('building', 'room_number', 'capacity', 'gender', 'current_count') list_filter = ('building', 'gender') search_fields = ('room_number',) list_per_page = 20 def current_count(self, obj): return obj.checkinrecord_set.filter(is_active=True).count() current_count.short_description = '当前入住人数' @admin.register(Student) class StudentAdmin(admin.ModelAdmin): list_display = ('student_no', 'name', 'department', 'major', 'phone') search_fields = ('student_no', 'name', 'major') list_filter = ('department',)list_display里混了一个方法是可以的,它会在每一行实时计算入住人数。如果宿舍数量多,这里会有 N+1 查询问题,后面我会给出优化方案,但至少从功能上是完整的。search_fields可以传多个字段,Django 会自动拼接 Q 对象做 LIKE 查询。
4.3 把报修流程做成 Admin 可直接处理的工作台
报修是宿舍管理系统中最能体现“流程感”的功能。大多数课程设计只做报修单的增删改查,状态字段只有“已提交/已处理”。要加分,就把状态变成更真实的生命周期:
| 状态键 | 状态名 | 说明 |
|---|---|---|
| submitted | 已提交 | 学生填写维修单,刚入库 |
| processing | 处理中 | 维修人员接单 |
| finished | 已完成 | 处理完,等待学生确认 |
| rejected | 已驳回 | 填单有误或不在维修范围 |
在 Admin 中,你可以用list_editable直接把状态变成下拉框,在列表页就能改,不用进详情页。再把actions注册一个批量完成操作,这样“一次性把多张维修单标记为已处理”只需要勾选、下拉、点执行。
from django.contrib import admin from .models import RepairOrder @admin.action(description='将选中报修单标记为已完成') def mark_as_done(modeladmin, request, queryset): queryset.update(status='finished', finish_time=timezone.now()) @admin.register(RepairOrder) class RepairOrderAdmin(admin.ModelAdmin): list_display = ('order_no', 'student', 'room', 'type', 'status', 'create_time') list_filter = ('status', 'type') list_editable = ('status',) actions = (mark_as_done,)这里绕开了 model 的 save 方法,直接执行queryset.update,好处是只发一条 UPDATE SQL,缺点是不会走 save 的自动逻辑,所以在finish_time上必须手动设置。Django 后台定制到这里,基本就达到了“课程设计里能展示的性价比最高操作”,你只需要在答辩时现场演示一次批量更新即可。
5. 查询性能与部署排错:答辩时被问最多的几个问题
5.1 N+1 查询与 django-debug-toolbar 的定位方法
当你的宿舍列表页显示每一行都要查一次入住人数时,N+1 查询就会出现。第一次 QuerySet 取列表算 1,之后逐行 count 又是 N 次。肉眼感知不出来的原因是数据量小,但老师很可能会问“如果宿舍有 1 万间怎么办”。正确答法是用django-debug-toolbar抓出重复 SQL,再做查询优化。
pip install django-debug-toolbar安装后在settings.py里加入debug_toolbar到 INSTALLED_APPS,在urls.py里挂载路由。打开宿舍列表页后,你会在页面右侧看到 SQL 面板,里面每条 SQL 会出现多次一模一样的语句,这就是 N+1 的证据。
5.2 select_related 与 prefetch_related 该怎么选
N+1 的解决方案取决于外键关系的方向。如果查询入口在 DormRoom 表,需要展示其关联的 building 信息,这是正向外键,用select_related就够了:
rooms = DormRoom.objects.select_related('building').annotate(...)如果要从房间查入住记录,或者从学生查多张入住记录,这是反向关联且结果集是多条,需要用prefetch_related做预处理,避免逐条去查子表。对于上面的 Admincurrent_count计算方法,我的优化方式是直接预取聚合值,而不在模板里反复 count:
from django.db.models import Count, Q rooms = DormRoom.objects.annotate( used_count=Count('checkinrecord', filter=Q(checkinrecord__is_active=True)) ) for room in rooms: print(room.used_count)这里用一个带条件和 SQL 别名的小技巧,把“当前有效入住人数”作为一个虚拟字段挂在每条记录上,模板直接取room.used_count。数据量越大,这种方式与逐条 count 的差距越明显。答辩时把这两段代码并排摆出来讲,基本就没有死角。
5.3 迁移后 admin 样式丢失、时区、ALLOWED_HOSTS 与宝塔部署
Django 课程设计里最容易翻车的是部署环节。本地跑得好好的,传到服务器后 Admin 页面没 CSS,排错半天发现是STATIC_ROOT没有配置。常见做法是在settings.py里这样处理:
STATIC_URL = '/static/' STATIC_ROOT = BASE_DIR / 'staticfiles' # 部署时执行 collectstatic 收集到这里的目录在服务器上执行python manage.py collectstatic --noinput,然后在 Nginx 配置里把/static/指向这个绝对路径。宝塔面板部署 Django 时,需要在网站设置里选择 Python 项目,指定入口文件wsgi.py和项目根目录,特别注意的是settings.py中的DEBUG = False以及ALLOWED_HOSTS必须填上域名或服务器 IP,否则会直接抛 DisallowedHost 错误。
时间字段也有一个经典坑:如果不设置USE_TZ = False,数据库里存的是带时区的 UTC 时间,你在 Admin 里看到的时间会比北京时间少 8 小时。我的做法是开发时保持默认,展示时统一用模板过滤器localtime,这样逻辑上更标准,不会被老师挑出“时区是什么”的问题。
6. 答辩验收前的最后一步:造数据、测指标、写报告
6.1 用 bulk_create 造一批逼真的验收数据
答辩演示最怕数据太少,点开空空的列表毫无说服力。手动录入几百条不现实,应该用bulk_create批量生成。下面这段代码可以放在一个临时脚本里,跑一次就能造出 300 名学生和 80 间宿舍的入住数据:
from django.utils import timezone from datetime import timedelta import random students = [] for i in range(300): students.append(Student( student_no=f'2025{i:04d}', name=f'学生{i}', department=random.choice(['信息学院', '机械学院']), major=random.choice(['软件工程', '计算机科学', '自动化']), )) Student.objects.bulk_create(students, batch_size=100)批量写入的要点是batch_size,它不是 MySQL 显式批量代替逐条插入,而是控制每批插入多少条,防止一次拼接的 SQL 过长。配合这个脚本,你还可以写一个循环,把学生按专业顺序往房间里塞,整个演示数据十分钟就能造完。
6.2 指标口径与报告写法
课程设计报告里必然有“系统功能”和“数据库设计”两章,但很多人的报告只是一张张截图堆砌。更容易拿分的做法是写清楚统计口径,比如“入住率 = 当前有效入住记录数 / 宿舍总床位数”,再选择三个指标做可视化:宿舍楼入住率、各学院已分配学生人数、报修完成率。只画直方图和饼图即可,不要为了炫技去用花哨的图表。
6.3 演示动线的最后安排
答辩演示的顺序建议不要从登录页开始,而是直接打开后台宿舍楼列表,先展示 filter 筛选和搜索,再切换到分配脚本演示一次自动分配,最后打开 debug_toolbar 的 SQL 面板说一句“这里是预加载优化的效果”。如果你能做到以上任意一步,至少在完成度上已经超过了一般的课程设计作品。
本文还有配套的精品资源,点击获取