高校自习室预约系统(Python+Django)设计与实现
·
一、系统文档
1. 需求分析
1.1 功能需求
| 角色 | 核心功能 | 具体描述 |
|---|---|---|
| 学生 | 账号管理 | 注册、登录、密码重置(绑定校园邮箱) |
| 学生 | 自习室查询 | 按楼层、座位类型(靠窗/靠电源)筛选,查看座位占用状态 |
| 学生 | 预约操作 | 选择日期、时间段(1小时/2小时)预约座位,预约后30分钟未到自动释放 |
| 学生 | 预约管理 | 查看个人预约记录,取消未开始的预约 |
| 管理员 | 自习室管理 | 添加/删除自习室,编辑座位信息(编号、状态、是否有电源) |
| 管理员 | 预约审核 | 处理异常预约(如重复预约),手动释放长期占用座位 |
| 管理员 | 数据统计 | 查看每日/每周预约量、座位使用率报表 |
1.2 非功能需求
- 性能:单页面加载时间≤2秒,支持500人同时在线操作
- 安全:学生账号与校园统一身份认证关联,密码加密存储(MD5)
- 兼容性:支持Chrome、Edge等主流浏览器,适配电脑端/平板端
2. 系统设计
2.1 技术栈
- 后端:Python 3.9 + Django 4.2(MVT架构)
- 前端:HTML + CSS(Bootstrap 5) + JavaScript(JQuery)
- 数据库:MySQL 8.0
- 部署:本地开发(Python manage.py runserver)
2.2 数据库设计(ER图核心实体)
- User(用户表):id(主键)、username(学号)、password(加密密码)、email(校园邮箱)、role(角色:student/admin)
- Classroom(自习室表):id(主键)、floor(楼层)、room_num(房间号)、total_seats(总座位数)
- Seat(座位表):id(主键)、classroom_id(外键关联自习室)、seat_num(座位号)、has_power(是否有电源:0/1)、status(状态:0-空闲/1-占用)
- Reservation(预约表):id(主键)、user_id(外键关联用户)、seat_id(外键关联座位)、reserve_date(预约日期)、start_time(开始时间)、end_time(结束时间)、is_valid(是否有效:0-已取消/1-有效)
2.3 系统架构
高校自习室预约系统
├── 前端层(用户界面)
│ ├── 学生端(登录/预约/查询)
│ └── 管理员端(管理/统计)
├── 后端层(Django核心)
│ ├── 视图层(views.py:处理请求逻辑)
│ ├── 模型层(models.py:数据库映射)
│ ├── 路由层(urls.py:URL分发)
│ └── 表单层(forms.py:数据验证)
└── 数据层(MySQL)
└── 4张核心数据表(User/Classroom/Seat/Reservation)
3. 测试与部署
3.1 功能测试用例
| 测试模块 | 测试步骤 | 预期结果 |
|---|---|---|
| 座位预约 | 1. 学生登录 2. 选择3楼自习室、靠窗有电源座位 3. 预约次日14:00-16:00 |
预约成功,座位状态变为“占用”,个人中心显示预约记录 |
| 预约取消 | 1. 进入“我的预约” 2. 选择未开始的预约 3. 点击“取消预约” |
预约状态变为“已取消”,座位状态恢复“空闲” |
3.2 部署步骤
- 安装依赖:
pip install django==4.2 mysqlclient==2.2.0 - 配置数据库:修改
settings.py中DATABASES参数(数据库名、用户名、密码) - 数据迁移:
python manage.py makemigrations→python manage.py migrate - 创建管理员:
python manage.py createsuperuser(设置账号密码) - 启动系统:
python manage.py runserver 0.0.0.0:8000 - 访问地址:学生端
http://localhost:8000/student/,管理员端http://localhost:8000/admin/
二、核心代码实现
1. 模型层(models.py)
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
# 自定义用户管理器
class UserManager(BaseUserManager):
def create_user(self, username, email, password=None):
if not email:
raise ValueError("必须填写校园邮箱")
user = self.model(
username=username,
email=self.normalize_email(email)
)
user.set_password(password) # 自动加密密码
user.save(using=self._db)
return user
def create_superuser(self, username, email, password):
user = self.create_user(
username=username,
email=email,
password=password
)
user.role = 'admin' # 管理员角色
user.save(using=self._db)
return user
# 用户表
class User(AbstractBaseUser):
ROLE_CHOICES = (
('student', '学生'),
('admin', '管理员'),
)
username = models.CharField(max_length=20, unique=True, verbose_name="学号")
email = models.EmailField(max_length=50, unique=True, verbose_name="校园邮箱")
role = models.CharField(max_length=10, choices=ROLE_CHOICES, default='student', verbose_name="角色")
is_active = models.BooleanField(default=True, verbose_name="是否激活")
objects = UserManager()
USERNAME_FIELD = 'username' # 登录字段(学号)
REQUIRED_FIELDS = ['email'] # 创建用户必填字段
def __str__(self):
return self.username
# 自习室表
class Classroom(models.Model):
floor = models.IntegerField(verbose_name="楼层")
room_num = models.CharField(max_length=10, verbose_name="房间号")
total_seats = models.IntegerField(verbose_name="总座位数")
def __str__(self):
return f"{self.floor}楼-{self.room_num}室"
# 座位表
class Seat(models.Model):
classroom = models.ForeignKey(Classroom, on_delete=models.CASCADE, related_name="seats", verbose_name="所属自习室")
seat_num = models.CharField(max_length=10, verbose_name="座位号")
has_power = models.BooleanField(default=False, verbose_name="是否有电源")
status = models.BooleanField(default=False, verbose_name="状态(0-空闲/1-占用)")
def __str__(self):
return f"{self.classroom}-{self.seat_num}号"
# 预约表
class Reservation(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="reservations", verbose_name="预约用户")
seat = models.ForeignKey(Seat, on_delete=models.CASCADE, related_name="reservations", verbose_name="预约座位")
reserve_date = models.DateField(verbose_name="预约日期")
start_time = models.TimeField(verbose_name="开始时间")
end_time = models.TimeField(verbose_name="结束时间")
is_valid = models.BooleanField(default=True, verbose_name="是否有效")
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
def __str__(self):
return f"{self.user.username}-{self.seat}-{self.reserve_date}"
2. 视图层(学生端预约功能,views.py)
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import authenticate, login
from django.contrib import messages
from .models import User, Classroom, Seat, Reservation
from .forms import ReservationForm
from datetime import date, datetime
# 学生登录
def student_login(request):
if request.method == "POST":
username = request.POST.get("username")
password = request.POST.get("password")
user = authenticate(request, username=username, password=password)
if user and user.role == "student":
login(request, user)
return redirect("student:seat_list") # 跳转至座位列表页
else:
messages.error(request, "学号或密码错误,或非学生账号")
return render(request, "student/login.html")
# 座位列表查询
def seat_list(request):
if not request.user.is_authenticated or request.user.role != "student":
return redirect("student:login")
# 筛选条件:楼层、是否有电源
floor = request.GET.get("floor")
has_power = request.GET.get("has_power")
seats = Seat.objects.all()
if floor:
seats = seats.filter(classroom__floor=floor)
if has_power in ["0", "1"]:
seats = seats.filter(has_power=bool(int(has_power)))
return render(request, "student/seat_list.html", {
"seats": seats,
"floors": Classroom.objects.values_list("floor", flat=True).distinct() # 所有楼层选项
})
# 座位预约
def reserve_seat(request, seat_id):
if not request.user.is_authenticated or request.user.role != "student":
return redirect("student:login")
seat = get_object_or_404(Seat, id=seat_id)
today = date.today()
if request.method == "POST":
form = ReservationForm(request.POST)
if form.is_valid():
reserve_date = form.cleaned_data["reserve_date"]
start_time = form.cleaned_data["start_time"]
end_time = form.cleaned_data["end_time"]
# 校验:预约日期不能小于今天,开始时间小于结束时间
if reserve_date < today:
messages.error(request, "预约日期不能小于今天")
return render(request, "student/reserve.html", {"form": form, "seat": seat})
if start_time >= end_time:
messages.error(request, "开始时间必须早于结束时间")
return render(request, "student/reserve.html", {"form": form, "seat": seat})
# 校验:该时间段座位是否已被预约
existing_reserve = Reservation.objects.filter(
seat=seat,
reserve_date=reserve_date,
is_valid=True,
start_time__lt=end_time, # 避免时间重叠
end_time__gt=start_time
).exists()
if existing_reserve:
messages.error(request, "该时间段座位已被预约,请选择其他时间")
return render(request, "student/reserve.html", {"form": form, "seat": seat})
# 创建预约记录
Reservation.objects.create(
user=request.user,
seat=seat,
reserve_date=reserve_date,
start_time=start_time,
end_time=end_time
)
# 更新座位状态为占用
seat.status = True
seat.save()
messages.success(request, "预约成功!请在预约时间前30分钟到达")
return redirect("student:reserve_record")
else:
form = ReservationForm(initial={"reserve_date": today}) # 默认预约今天
return render(request, "student/reserve.html", {"form": form, "seat": seat})
# 个人预约记录
def reserve_record(request):
if not request.user.is_authenticated or request.user.role != "student":
return redirect("student:login")
# 只显示当前用户的有效预约,按预约日期排序
records = Reservation.objects.filter(
user=request.user,
is_valid=True
).order_by("reserve_date", "start_time")
return render(request, "student/reserve_record.html", {"records": records})
# 取消预约
def cancel_reserve(request, record_id):
if not request.user.is_authenticated or request.user.role != "student":
return redirect("student:login")
record = get_object_or_404(Reservation, id=record_id, user=request.user)
# 校验:只能取消未开始的预约
now = datetime.now()
reserve_datetime = datetime.combine(record.reserve_date, record.start_time)
if reserve_datetime < now:
messages.error(request, "只能取消未开始的预约")
return redirect("student:reserve_record")
# 取消预约:更新状态
record.is_valid = False
record.save()
# 恢复座位空闲状态(需确认该座位无其他有效预约)
seat = record.seat
has_other_reserve = Reservation.objects.filter(
seat=seat,
is_valid=True,
reserve_date__gte=date.today()
).exists()
if not has_other_reserve:
seat.status = False
seat.save()
messages.success(request, "预约已取消")
return redirect("student:reserve_record")
3. 前端页面(座位列表页,seat_list.html)
{% extends "student/base.html" %}
{% block content %}
<div class="container mt-4">
<h2>自习室座位查询</h2>
<!-- 筛选表单 -->
<form method="get" class="row g-3 mt-3">
<div class="col-md-4">
<label class="form-label">楼层</label>
<select name="floor" class="form-select" onchange="this.form.submit()">
<option value="">全部楼层</option>
{% for floor in floors %}
<option value="{{ floor }}" {% if request.GET.floor == floor|stringformat:"i" %}selected{% endif %}>
{{ floor }}楼
</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label">座位类型</label>
<select name="has_power" class="form-select" onchange="this.form.submit()">
<option value="">全部类型</option>
<option value="1" {% if request.GET.has_power == "1" %}selected{% endif %}>有电源</option>
<option value="0" {% if request.GET.has_power == "0" %}selected{% endif %}>无电源</option>
</select>
</div>
</form>
<!-- 座位列表 -->
<div class="row mt-4">
{% for seat in seats %}
<div class="col-md-3 mb-3">
<div class="card {% if seat.status %}bg-secondary text-white{% else %}bg-light{% endif %}">
<div class="card-body">
<h5 class="card-title">{{ seat }}</h5>
<p class="card-text">
状态:{% if seat.status %}已占用{% else %}空闲{% endif %}<br>
电源:{% if seat.has_power %}有{% else %}无{% endif %}
</p>
{% if not seat.status %}
<a href="{% url 'student:reserve_seat' seat.id %}" class="btn btn-primary">立即预约</a>
{% endif %}
</div>
</div>
</div>
{% empty %}
<div class="col-12">暂无符合条件的座位</div>
{% endfor %}
</div>
</div>
{% endblock %}
4. 表单验证(forms.py)
from django import forms
from .models import Reservation
from datetime import date
class ReservationForm(forms.ModelForm):
class Meta:
model = Reservation
fields = ["reserve_date", "start_time", "end_time"]
widgets = {
"reserve_date": forms.DateInput(attrs={"type": "date", "class": "form-control"}),
"start_time": forms.TimeInput(attrs={"type": "time", "class": "form-control"}),
"end_time": forms.TimeInput(attrs={"type": "time", "class": "form-control"}),
}
# 自定义验证:预约日期不能超过7天(最多预约下周)
def clean_reserve_date(self):
reserve_date = self.cleaned_data.get("reserve_date")
if reserve_date > date.today():
delta = reserve_date - date.today()
if delta.days > 7:
raise forms.ValidationError("最多只能预约7天内的座位")
return reserve_date
三、系统功能演示说明
- 学生登录:输入学号(如“2023001”)和密码,登录后进入座位查询页;
- 座位筛选:选择“3楼”和“有电源”,筛选出符合条件的空闲座位;
- 预约操作:点击“立即预约”,选择日期(如今天)、时间段(14:00-16:00),提交后完成预约;
- 记录管理:在“我的预约”中查看已预约记录,若需取消
更多推荐




所有评论(0)