张同学拿着U盘跑到实验室找我,说毕设选题批下来了,导师给的方向是django学生宿舍管理系统。我问他准备做成什么样,他说"就是能增删改查吧"。我当时就意识到,这题如果只停在增删改查,答辩时基本是送人头。宿舍管理系统这个题目看着不起眼,但它几乎是毕业设计里最典型的"麻雀虽小、五脏俱全"型项目——有用户体系、有权限管理、有核心业务流、还有实时消息推送的扩展空间,做扎实了,一个系统能同时展示Django后端、数据库设计、WebSocket、缓存、部署等一堆知识点。
这篇文章我就拿这个题目当例子,完整拆一遍从选题逻辑到技术实现、再到答辩准备的整个链路。内容全部基于Django项目实战经验,适合计算机专业大四学生、以及想用Django做管理类系统的初学者参考。我尽量把"导师没写进任务书"的那些细节也说清楚。
1. 为什么宿舍管理系统是"看着简单实则很有料"的毕设题目
很多学生一听"宿舍管理"四个字,第一反应是"都做烂了"。但毕业设计的评分逻辑从来不是题目本身多新鲜,而是你在题目范围内展示了多少工程能力。宿舍管理系统这个业务场景,恰恰能把Django的多数核心能力串起来。
1.1 业务角色天然适配Django权限体系
宿舍管理系统的用户不是单一角色。至少要分四类:学生、宿管员、辅导员、系统管理员。学生要看自己的住宿信息、发起报修、登记访客;宿管员要处理入住退宿、审核报修、录入水电费;辅导员需要查看所带班级的住宿分布;系统管理员负责维护楼栋、房间、床位基础数据。这个角色划分几乎就是为Django自带的认证和授权体系量身定做的。
用Django原生的django.contrib.auth做登录注册和Session管理,再用Group加Permission实现细粒度权限控制。热搜词里提到的"django rbac",在这个项目里落地的办法很简单:不需要裸写RBAC表,Django内置的Group和Permission本来就是一套标准的RBAC实现。
1.2 核心业务流程足够撑起"系统设计"的深度
宿舍分配不是随便放个"入住"按钮就结束。真的做设计时,你要考虑:怎么校验房间当前空余床位?怎么防止同时操作导致一床多住?退宿后床位如何释放?调宿怎么保留历史记录?报修从提交到派单到完工再到评价,状态怎么流转?水电费是按房间周期出账还是实时累计?
这些业务逻辑落到代码里,就是一连串"如果…那么…否则…"的判断,再加上事务处理、状态机设计、唯一约束。导师看一眼你的表结构,就知道你有没有认真做过需求分析。
1.3 技术栈选型和扩展性决定答辩的高度
一个纯Django+模板的宿舍管理系统能过,一个加了WebSocket实时通知、Redis缓存、Celery定时任务、甚至小程序端的宿舍管理系统,答辩分数完全不在一个档次。热搜词里那个"django websocket实现后台有数据前端推送",在宿舍管理场景里有特别自然的落地点:报修状态变了要立刻推给报修学生,新公告发布了要推送给整栋楼,这些场景用WebSocket比轮询优雅得多。
所以选型上我的建议是:框架锁定Django 4.x LTS,数据库直接用MySQL,缓存和消息推送用Redis,实时通信用Django Channels。前端不引入太重的东西,Admin后台用Django原生,面向学生的前台用Bootstrap模板即可——除非你想额外展示前后端分离的能力,那可以把前台改用Vue + DRF。
提示:毕设的核心是"你掌握多少",不是"你用了多新的版本"。选型不要追新到文档都不全的版本,Django 4.2 LTS在写这篇文章时仍然是稳定可靠的选择。
2. 数据库设计这一步做扎实,后面所有模块都顺
宿舍管理系统的表结构,我建议按照"基础数据—人员关系—业务流水—权限与通知"四层来组织。这一节我把核心模型直接写出来,并解释每个字段的设计理由,因为在毕设答辩里"为什么这么建表"是高频问题。
2.1 核心表结构与字段设计的思路
基础数据层是楼栋、房间、床位三张表。楼栋表字段很简单:楼栋编号、名称、楼层数、管理员外键。房间表的关键点在于床位容量bed_count和已住人数occupied_count,这两个字段要分开存,因为已住人数是要频繁查询的统计值。床位表单独建一张,理由是床位状态(空闲/入住/停用)需要被独立维护,未来如果做"按床选宿"的自动分配,没床位表是没法实现的。
人员关系层的核心表如下,代码可以自己照着写:
from django.db import models from django.contrib.auth.models import User class Student(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name="关联账号") student_no = models.CharField(max_length=20, unique=True, verbose_name="学号") name = models.CharField(max_length=50, verbose_name="姓名") major = models.CharField(max_length=100, verbose_name="专业") grade = models.CharField(max_length=10, verbose_name="年级") phone = models.CharField(max_length=11, verbose_name="手机号") class Dormitory(models.Model): building = models.ForeignKey(Building, on_delete=models.PROTECT, verbose_name="楼栋") room_no = models.CharField(max_length=10, verbose_name="房间号") bed_count = models.PositiveIntegerField(verbose_name="床位数") occupied_count = models.PositiveIntegerField(default=0, verbose_name="已住人数") class Meta: unique_together = ('building', 'room_no') class Bed(models.Model): STATUS_CHOICES = ( ('empty', '空闲'), ('occupied', '已入住'), ('disabled', '停用'), ) dormitory = models.ForeignKey(Dormitory, on_delete=models.CASCADE, verbose_name="所属房间") bed_no = models.CharField(max_length=10, verbose_name="床位编号") status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='empty') class OccupancyRecord(models.Model): student = models.ForeignKey(Student, on_delete=models.CASCADE, verbose_name="学生") bed = models.ForeignKey(Bed, on_delete=models.CASCADE, verbose_name="床位") check_in_time = models.DateTimeField(auto_now_add=True, verbose_name="入住时间") check_out_time = models.DateTimeField(null=True, blank=True, verbose_name="退宿时间")几个容易被新手忽视的细节:
on_delete参数一定要想清楚。学生账号被删除时关联的Student表是CASCADE,这个没问题;但Room被删除时应该PROTECT,否则会把历史入住记录一起带没。这是热搜词里"django执行查询-删除对象"被问最多的点,后面我有专门一节讲。入住不是直接往
Student表写个宿舍字段,而是通过OccupancyRecord中间表关联。这样调宿、退宿、毕业清退全部走"流水"逻辑,历史可追溯。把student直接绑死在Bed上是新手最常犯的设计错误。occupied_count是冗余字段,用空间换查询速度。每次入住退宿操作时在事务里同时更新它,保证不出现"房间写满但床位还空着"的数据不一致。
2.2 报修、水电、访客、公告这些业务表的与众不同之处
报修表是一个典型的状态机。字段建议这样设计:report_no(单号)、student、room、category(水/电/家具/设备)、description、status(待处理/已派单/维修中/已完成/已评价)、created_at、handled_at、handler。状态流转不要裸写字符串比较,Django模型里可以定义常量加方法:
class RepairOrder(models.Model): STATUS_PENDING = 'pending' STATUS_DISPATCHED = 'dispatched' STATUS_PROCESSING = 'processing' STATUS_FINISHED = 'finished' STATUS_EVALUATED = 'evaluated' STATUS_CHOICES = ( (STATUS_PENDING, '待处理'), (STATUS_DISPATCHED, '已派单'), (STATUS_PROCESSING, '维修中'), (STATUS_FINISHED, '已完成'), (STATUS_EVALUATED, '已评价'), ) @classmethod def valid_transitions(cls, current_status): return { cls.STATUS_PENDING: [cls.STATUS_DISPATCHED], cls.STATUS_DISPATCHED: [cls.STATUS_PROCESSING], cls.STATUS_PROCESSING: [cls.STATUS_FINISHED], cls.STATUS_FINISHED: [cls.STATUS_EVALUATED], }.get(current_status, [])这么做的好处是状态流转的合法性收敛在一处,前端下拉框的可选状态、后端校验逻辑、接口文档都可以共用这一份定义。答辩时导师一眼就能看出你有软件工程意识。
水电费表要按"房间+计费周期"唯一:room、year_month、water_usage(用水量)、electric_usage、water_fee、electric_fee、status(待缴/已缴)。DecimalField而不是FloatField存金额,max_digits=8, decimal_places=2,这是做财务数据的基本素养。
访客登记表就比较简单:student、visitor_name、visitor_phone、reason、visit_in_time、visit_out_time。一个学生一天最多登记几条,可以做unique_together约束。
2.3 用Django自带RBAC,而不是重复造轮子
热搜词"django rbac"背后的需求,在毕设场景里最简单的实现就是三板斧:
- 用
django.contrib.auth自带的User存账号。 - 分别在
Student和DormitoryAdmin模型里用OneToOneField扩展用户画像。 - 用
Group建立学生组、宿管员组、辅导员组,给组分配Permission。
我自己在项目里一般这样建权限矩阵:视图函数上不写死@login_required,而是用@permission_required('repair.view_repairorder')这种声明式校验。Django会自动为每个模型生成add_、change_、delete_、view_四类权限码,直接可用。
如果你的系统要做更细的数据级权限——比如辅导员只能看自己班级的学生——那就在查询条件里加过滤,不要试图用权限框架去解决数据隔离问题。权限管"能不能访问这个模块",数据过滤管"能看到哪些行",两件事别混在一起。模板里用{% if perms.repair.add_repairorder %}控制按钮显隐,这能大幅提升人机交互的合理性。
3. 核心业务逻辑的实现:从分配宿舍到报修全流程
这个系统的代码量不算大,但真正值钱的是几处容易写错或者容易写得"太低级"的模块。我把宿舍分配、报修流程、水电费生成三个典型的业务逻辑拆开讲,顺便带出Django ORM的进阶用法,因为这些内容在答辩演示时最能体现你的工程水平。
3.1 宿舍分配算法:手动分配、自动分配与并发安全
宿舍分配是系统里最核心的操作。手动分配的逻辑是:宿管员选择学生、选择楼栋、选择房间,系统自动列出空闲床位,提交后生成入住流水。这一步的关键不是界面上怎么下拉,而是后端怎么校验。
from django.db import transaction from django.core.exceptions import ValidationError @transaction.atomic def assign_bed(student, bed): # 用 select_for_update 锁住房间记录,防止并发下两个请求同时选到同一床位 room = Dormitory.objects.select_for_update().get(pk=bed.dormitory_id) if bed.status != 'empty': raise ValidationError("该床位已被占用") if room.occupied_count >= room.bed_count: raise ValidationError("该房间已住满") bed.status = 'occupied' bed.save() room.occupied_count += 1 room.save() OccupancyRecord.objects.create(student=student, bed=bed)很多新手写到这里,直接用Bed.objects.filter(status='empty').filter(dormitory__id=room_id)查出空床位,然后入库,完全不做校验,也不做锁。这样在单人操作用没问题,但毕设答辩时只要导师问一句"两个宿管员同时点分配,会不会一床分给两个人?"就答不上来。select_for_update是MySQL InnoDB的行锁,配合@transaction.atomic事务,才叫完整的并发安全处理。
自动分配的逻辑也不难:按性别、年级、专业做粗筛,然后遍历楼栋房间床位,找到第一个空位。排序规则可以加权重,比如同班优先相邻房间,这个用order_by加extra就能实现。自动分配的价值在于演示时能快速批量生成几百条测试数据,你不用手动一条条录。
退宿逻辑同样要注意:退宿时不要物理删除OccupancyRecord,而是把check_out_time写上当前时间,再把Bed.status改成empty、Room.occupied_count减一。为什么?因为"是否住过这个宿舍"是学生履历的一部分,物理删除以后什么都查不到。这个思路我沿用在所有管理系统的历史记录模块里。
注意:入住和退宿涉及至少两张表的字段变更,必须包在
transaction.atomic()里。如果中途抛异常,要保证床位状态和流水记录一起回滚,否则会出现"床占了但流水没建"的脏数据。
3.2 报修流程:一个状态机的Django实现
报修模块开发时,最重要的不是怎么建表,而是怎么让"状态变化"和"历史记录"联动。我做的方案是:每次状态变更时,除了更新RepairOrder.status,还要写入一张RepairLog表,记录操作人、旧状态、新状态、操作时间、备注。这相当于给报修流程加了审计日志,答辩时可以直接展示"这条报修从提交到评价的完整时间线",效果比干巴巴讲代码好得多。
嵌套写法可以让状态流转集中在视图层:
def transition_repair_status(request, order_id, target_status): order = RepairOrder.objects.get(pk=order_id) if target_status not in RepairOrder.valid_transitions(order.status): return JsonResponse({'error': '非法的状态流转'}, status=400) # 记录日志 RepairLog.objects.create( order=order, operator=request.user, old_status=order.status, new_status=target_status, remark=request.POST.get('remark', '') ) order.status = target_status order.save()这里有个经验:状态机的合法性校验永远不要写在前端。前端下拉框只是"友好提示",后端必须卡一次。HTML里的<option>可以被绕过,Postman直接发数组请求就能把状态从"待处理"改成"已完成"。所有的控制逻辑都以服务端为准。
报修完成后还要自动发消息给学生,这就是WebSocket上场的场景。我放在下一节单独讲,因为它牵扯到Django Channels的完整配置,是不少新手项目最大的加分项,也是最容易踩坑的地方。
3.3 水电费的定时生成:一次性说清"管理命令"
水电费不用实时生成,通常是每个月1号由宿管员录入本月的用水用电度数,系统自动算出费用。这里用到Django的管理命令(python manage.py自定义命令),创建dormitory/management/commands/generate_water_elec_fee.py,遍历所有房间,读取每月的表底和本月表底,差值乘以单价,生成待缴费记录。
from django.core.management.base import BaseCommand from datetime import date class Command(BaseCommand): help = '生成本月水电费账单' def handle(self, *args, **options): year, month = date.today().year, date.today().month for room in Dormitory.objects.all(): WaterElectricFee.objects.get_or_create( room=room, year_month=f"{year}-{month:02d}", defaults={'water_fee': 0, 'electric_fee': 0} ) self.stdout.write(self.style.SUCCESS('账单生成完成'))get_or_create的妙处在于幂等:重复执行不会生成重复账单,这对定时任务非常关键。你可以把这个命令挂到Cron里,也可以手动在Admin后台执行。毕设里一般不要求你真上Celery,但如果你想让项目显得更完整,加一个Celery Beat定时任务调用这个命令,完全是加分项。
3.4 WebSocket实时推送:Channels在宿舍管理里的落地
热搜词里"python django websocket实现后台有数据前端推送"说得就是这个能力。毕设项目里有实时推送功能非常提气质,宿舍管理场景里最典型的是两种应用:一是报修状态变化,前端页面能实时弹出"你的报修已完成";二是新公告发布,所有在线学生能实时收到。
我用Django Channels来实现,核心步骤记录如下:
- 安装依赖:
pip install channels channels-redis daphne settings.py里注册daphne并加ASGI_APPLICATION = 'config.asgi.application',Channel Layer用Redis。- 建立
consumers.py,定义消费者处理WebSocket消息。 - 路由配置:WebSocket的
ws://路径单独走ASGI协议。 - 前端页面上写JavaScript,
new WebSocket()连接后端,收到消息后DOM更新。
一个最简单的消费者大概长这样:
import json from channels.generic.websocket import AsyncWebsocketConsumer class RepairNoticeConsumer(AsyncWebsocketConsumer): async def connect(self): self.room_group_name = self.scope['url_route']['kwargs']['student_id'] await self.channel_layer.group_add(self.room_group_name, self.channel_name) await self.accept() async def disconnect(self, close_code): await self.channel_layer.group_discard(self.room_group_name, self.channel_name) async def repair_status_message(self, event): await self.send(text_data=json.dumps({ 'message': event['message'], 'order_id': event['order_id'], 'status': event['status'] }))业务侧要推消息时,用channel_layer.group_send往学生的个人分组里发。
新手最容易在这几个地方卡住:
daphne启动和runserver是两套进程。channels4.x里必须用daphne -b 0.0.0.0 -p 8000 config.asgi:application启动,如果还在用manage.py runserver,WebSocket路径不会进到消费者。- Redis配置写错会导致消息发不出去,报
Server doesn't exist。本地开发如果不用Redis,可以临时把Channel Layer换成InMemoryChannelLayer,先跑通业务再补Redis。 - 前端要处理断线重连,
onclose里加个定时器重新连接,否则手机切后台再切回来,WebSocket已经断了还不知道。
我在实际项目里验证过:这个功能做完,演示的时候把修报的状态在后台改一下,学生端页面立刻弹出提示,视觉效果比任何PPT截图都来得震撼。这算是毕设答辩里"五分钟征服评委"的经典操作。
4. 从开发到上线:那些导师不会写进任务书里的踩坑点
这一节全是实战里趟出来的问题。我用表格和排查链路把它们列出来,每一条都对应真实场景。尤其是热搜词里提到的"vscode写img标签 在django的static文件中显示不了",这个坑几乎每个新手都会踩。
4.1 静态文件404:为什么img标签在Django里显示不出来
现象很典型:前端模板里写了<img src="{% static 'img/logo.png' %}">,本地DEBUG模式下打开,图片裂了;浏览器控制台显示404。新手第一反应是路径写错了,但其实80%的情况是配置问题。
排查链路如下:
- 先确认
settings.py里的STATIC_URL = '/static/'存在。 - 确认你的静态文件放在app下面的
static/app_name/目录,而项目级公共资源放在BASE_DIR / 'static'。 - 项目级目录要加
STATICFILES_DIRS配置:
STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / 'static']- 模板文件顶部要先写
{% load static %},再写{% static %}标签。忘了load static是新手最常犯的错,页面不报错,但图片就是不显示。 - 部署模式下(
DEBUG=False),Django不直接服务静态文件,需要先执行python manage.py collectstatic把所有静态文件收集到STATIC_ROOT,然后由Nginx代理静态目录。
我见过一个学生调了两天,最后发现他只是把static文件夹放在了项目根目录,但STATICFILES_DIRS没加,Django根本不知道去那里找文件。所以看到404,第一眼不要怀疑路径,先看配置。
4.2 数据库迁移与删除操作:migrations和on_delete的连锁反应
热搜词"django执行查询-删除对象"实际上对应的是两个场景:一是ORM的QuerySet.delete(),二是模型删除时的级联行为。
先说QuerySet.delete(),注意它返回的是一个元组(删除总数, 按模型分组的计数明细)。如果你执行Request.objects.filter(status='closed').delete(),它不会返回被删对象列表,也不会触发模型的save()方法,只会在所有外键级联的所有相关表上执行删除。想在删除前记录日志,要自己先循环查出来再逐条处理。
再讲on_delete的选型。新手写外键时习惯照抄models.CASCADE,但这在业务系统里是灾难。宿舍管理系统的楼栋表如果被删了,房间不该跟着删,应该PROTECT;学生账号如果被删了,历史入住记录不该被删,应该SET_NULL并把外键设为null=True。我给自己定了一个规则:凡是"商业单据"性质的表,外键一律不级联删除,宁可SET_NULL留空,也不能把交易流水删掉。这个细节在答辩时讲成"我为了保证数据完整性,所以拒绝了级联删除",是能直接兑现成印象分的。
还有一类经典踩坑:改了模型字段类型后migrate报错。比如把某字段从CharField改成IntegerField,表里已有全中文数据,迁移必然失败。解决办法是先手动清理数据,或者按migrate的提示加一个default值。毕设项目一般数据量不大,但教训是:开发阶段要频繁调整模型字段时,不要拖到数据库里塞满了数据再改结构,想好字段再动手,比什么技巧都好用。
4.3 登录、CSRF和时区:三个不起眼但高发的问题
宿舍管理系统登录不了,最常见的原因是密码校验用的make_password和check_password流程出错,还有用户用User.objects.create()而不是create_user()存账号。后者不会自动加密密码,导致所有用户密码都是明文,输入正确也登不进去,因为Django默认的密码解析器不同意。
我的建议是注册接口统一用User.objects.create_user(username=..., password=...),它会自动调用set_password(),把明文密码做PBKDF2哈希。你可以在代码里显式调user.set_password(request.POST['password'])再user.save(),效果一样。
CSRF的问题集中出现在新手用Postman测试POST接口时。开发时如果确实不想每次处理CSRF,可以在测试接口上临时加@csrf_exempt装饰器,但我强调这只是开发期便利,正式代码里所有POST请求都应该通过Django的CSRF校验。实践方案是前端模板里用{% csrf_token %},AJAX请求时在请求头加X-CSRFToken。这个知识在答辩时被问到的概率极高。
时区问题比较隐蔽。Django默认时区是UTC,如果你不改成TIME_ZONE = 'Asia/Shanghai'并且设置USE_TZ = False,那么所有datetime字段在数据库里都是UTC时间。宿舍系统的打卡、报修时间、水电计费周期全都会差8小时。我的建议是毕设项目在settings.py里设置TIME_ZONE = 'Asia/Shanghai',然后USE_TZ = False,这样所有时间都是本地时间,业务逻辑好理解。如果你想顺带展示"国际化最佳实践",那可以保持USE_TZ = True,但所有展示前都要做时区转换。后者更正规,但明显增加心智负担,毕设阶段推荐前者。
5. 答辩演示策略与项目扩展方向
代码写完只是毕设的一半,另一半是让导师在有限时间里看到你的工作量。宿舍管理系统不是新鲜项目,想拿高分,演示顺序和话术需要设计。
5.1 演示流程这样设计,十分钟讲出三十分钟的效果
我的建议是严格按照"入口展示→核心操作→实时通知→数据可视化"的四段式来做,顺序不要颠倒。
先走登录页,说明不使用第三方登录、而是基于Django认证体系的自研登录注册,顺带提一下密码是PBKDF2哈希存储的——这句话一说,导师就知道你懂安全。接着以管理员身份进入后台,展示楼栋房间床位的数据维护界面,现场新建一栋楼,创建一个房间,演示为什么床位表要单独一张。然后切换到宿管员视角,现场展示自动分配功能:选10个学生,一次性分配宿舍,后台可以看到占用率上升。这一步是展示"业务算法的完整闭环"。
接下来是报修流程。创建一个报修单,后台改状态,打开前台页面,WebSocket消息实时弹出。这里推荐提前开好两个浏览器窗口,一个学生端一个管理端,同屏演示,视觉冲击力最强。你甚至可以准备一段小脚本:管理员点"派单"的一瞬间,学生端弹出"维修师傅正在赶来的路上"。
最后打开一个简单的数据统计页,展示每个楼栋的入住率、报修工单的月度趋势。用Chart.js画两个图表就够,不用上太重的前端可视化框架。整个演示控制在十分钟以内,重点讲"我为什么会这么设计"而不是"我用了什么函数"。
5.2 导师最爱问的五个问题,提前把答案准备好
答辩导师通常不问你怎么敲代码,而是问设计决策。根据我多年的观察,宿舍管理系统这个题,以下五个问题出现频率最高:
- "加一个床位后,为什么已住人数不会自动更新?" 答案是:
occupied_count是冗余计数,所有入住/退宿操作在事务中同时更新它,这是为了查询时的性能,以一致性换取速度。 - "如果两个管理员同时操作分配,怎么办?" 答案是:
select_for_update加事务,锁定房间记录,保证串行化。 - "为什么学生对报修单不能直接删除?" 答案是:删除会导致统计表、通知记录失去关联,历史追踪断裂,所以只允许状态流转,不允许物理删除。
- "WebSocket消息如果断了怎么办?" 答案是:前端做断线重连,消息持久化存到数据库,重连后主动拉取未读消息,WebSocket只做实时触达,不做消息存储。
- "系统数据量变大后,哪个查询会慢?" 答案是:报修列表如果没加
select_related('student', 'room')做联表预取,会出现N+1查询。这个问题一旦回答上来,导师马上知道你真的写过代码。
5.3 让项目再上一个台阶的三个扩展方向
如果时间充裕,我建议在基础功能之外挑一个方向加深,不要贪多。方向一是对接小程序端,学生微信小程序查宿舍、报修、扫码开门,Django后端出REST API(DRF),权限和现有系统无缝复用。方向二是把公告模块升级为"分组广播",不同年级、不同楼栋看到不同公告,本质是给通知模型加一个recipient_group外键,再用Channels按组推送。方向三是加一张宿舍评分表,宿管员按周对宿舍卫生评分,学生端能看到自己的评分变化趋势,这个模块虽然简单,但能展示你对"宿舍管理"整个业务的理解深度。
我个人最推荐小程序方向,因为现在毕设几乎必谈"移动端"。但要注意,如果选了小程序,数据库设计和后端API的健壮性要求会提升,项目工作量也要多留两到三周,别高估自己写前端的时间。
宿舍管理系统这个题目,说到底考的是"一个管理系统该有的底子和边界"。底子是用户、权限、数据模型、业务流程这套基础设施,边界是不能什么都往里塞、也不能把核心业务做得太浅。把它做完,你收获的不仅是一个能过查重的系统,Django从认证到ORM、从视图到消息推送,这些能力可以顺延到任何Web开发项目里。我个人的经验是,毕设阶段"先把一个完整的闭环跑起来"胜过"十个半成品模块堆在一起",做完这个项目,你就有了一份真正属于自己的、可以拿出来讲的干净作品。