提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
初始Django之宿舍管理系统(ORM入门篇)
项目部分功能展示:
**
管理员登录:
**
**
宿管登录:
**
**
学生登录:
**
前言
- 宿舍管理系统是一种能够提高学生宿舍管理效率的工具。随着大学生群体的不断增长,宿舍管理工作变得越来越复杂,传统的手工管理方式已经无法满足需求。为了提高宿舍管理的便利性和效率,引入计算机技术成为了必然选择。
- Django是一种基于Python语言开发的高性能Web开发框架,它的设计理念是简洁、优雅、高效。它提供了丰富的功能和强大的开发工具,使得开发人员能够快速构建出稳定可靠的Web应用程序。
- 本文将介绍如何使用Django开发一个宿舍管理系统。宿舍管理系统主要包括学生信息管理、入住管理、楼栋和房间管理、报修管理等功能。(最简单方式无from表单验证)
提示:以下是本篇文章正文内容,下面案例可供参考
一、宿舍管理系统需求分析
所需技术包:
asgiref==3.8.1
Django==5.0.7
et-xmlfile==1.1.0
openpyxl==3.1.5
PyMySQL==1.1.1
sqlparse==0.5.1
tzdata==2024.1
设置权限管理:系统管理员可以设置不同用户的权限,限制其对系统功能的访问和操作。
1.学生信息管理:
学生信息录入: 包括学生姓名、学号、性别、班级,宿舍号,并自动保存学号后六位为密码等信息。
学生信息查询: 根据学号等关键字进行查询,并显示学生的详细信息。
学生信息导入: 使用openpyxl读取并导入本地的.xlsx文件。
学生信息导出: 使用openpyxl创建一个新的Excel工作簿并将数据写入其中,最后保存至本地。
2.宿舍信息管理:
楼栋信息: 楼栋编号、楼栋名称、楼栋地址等。可以实现楼栋信息的增加、修改、删除和查询功能。
宿管信息: 宿管楼栋、宿管姓名、宿管电话等。 可以实现宿管信息的增加、修改、删除和查询功能。
宿舍信息: 楼栋编号、宿管编号、宿舍容量人数、宿舍已住人数等。可以实现宿舍信息的增加、修改、删除和查询功能。
可以根据学生信息绑定的宿舍自动更新已住人数,如超过容量则不允许更换学生寝室。
入住统计:系统可以生成宿舍入住统计报表。
3.维修信息管理:
报修管理:学生可以提交宿舍报修请求,管理员可以查看并处理报修请求。
维修信息: 申请人、申请宿舍(学生登陆时自动绑定宿舍不可更改)、故障信息、申请时间等信息进行增加、修改、删除和查询功能。
4.访客信息管理
访客基本信息管理: 记录和管理访客的基本信息,包括姓名、联系方式等。
访问记录管理: 记录和管理访客的访问记录,包括访问时间、访问地点等信息
5.宿舍通知管理
允许管理员和宿管发布、修改、删除通知。通知应包括标题、内容、发布时间等信息。
二、系统设计与实现
1.ORM中Model设计
在ORM(Object-Relational Mapping)中,处理外键关系(如一对多、一对一、多对多)是非常重要的。这些关系帮助我们在数据库中维护数据的一致性和完整性。以下是在ORM中定义和使用这些关系的基本写法和用法,以Python的Django ORM为例(因为Django的ORM非常流行且易于理解):
一对多(ForeignKey):
一对多关系是最常见的数据库关系之一。在Django中,你可以通过在模型中使用ForeignKey字段来定义一对多关系。
一对一(OneToOneField):
一对一关系用于表示两个模型之间的严格一对一映射。在Django中,你可以使用OneToOneField来定义这种关系。
多对多(ManyToManyField)
多对多关系表示两个模型之间可以存在多个关联。在Django中,你可以使用ManyToManyField来定义这种关系。
(1). 宿舍信息模型代码如下(示例):
from django.db import models
from django.contrib.auth.hashers import make_password, check_password
from django.core.validators import RegexValidator, MinLengthValidator
class Building(models.Model):
building_name = models.CharField(max_length=50, verbose_name='楼宇名字')
remarks = models.TextField(verbose_name='楼宇备注')
def __str__(self):
return self.building_name
class Dormitory(models.Model):
dorm_num = models.CharField(max_length=10, unique=True, verbose_name='宿舍号')
# 这里是外键 一对多的关系 一个楼宇 对应多个宿舍
building = models.ForeignKey('Building', on_delete=models.CASCADE, verbose_name='楼宇')
floor = models.IntegerField(verbose_name='楼层')
capacity = models.PositiveIntegerField(verbose_name='容量', default=4)
current_occupancy = models.PositiveIntegerField(verbose_name='当前已住人数', default=0)
remarks = models.TextField(verbose_name='备注')
class Meta:
ordering=['building']
def __str__(self):
return self.dorm_num
def add_student(self):
if self.current_occupancy < self.capacity:
self.current_occupancy += 1
self.save()
else:
print("宿舍已满,无法新增学生")
def remove_student(self):
if self.current_occupancy > 0:
self.current_occupancy -= 1
self.save()
else:
print("宿舍为空,无法移除学生")
class DormitoryManager(models.Model):
GENDER_CHOICES = [
('M', '男'),
('F', '女'),
]
manager_name = models.CharField(max_length=50, verbose_name="宿管姓名")
manager_tel = models.CharField(
max_length=15,
verbose_name='宿管电话',
validators=[RegexValidator(r'^\d{10,15}$', '请输入有效的电话号码')],
unique=True,
)
manager_password = models.CharField(
max_length=128,
verbose_name="宿管密码",
validators=[MinLengthValidator(6)]
)
# 这里也是一对多的关系 !!!
building = models.ForeignKey(Building, on_delete=models.CASCADE, verbose_name='管理楼宇')
def save(self, *args, **kwargs):
if not self.pk and not self.manager_password:
# 设置初始密码并加密
self.manager_password = make_password(self.manager_tel[-6:])
elif self.manager_password and not self.manager_password.startswith('pbkdf2_sha256$'):
# 如果密码没有加密,确保再次加密(防止更新时明文存储)
self.manager_password = make_password(self.manager_password)
super(DormitoryManager, self).save(*args, **kwargs)
def __str__(self):
return self.manager_name
def check_password(self, raw_password):
return check_password(raw_password, self.manager_password)
(2). 学生信息模型代码如下(示例):
from django.db import models
from django.core.validators import RegexValidator, MinLengthValidator
from django.contrib.auth.hashers import make_password
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from dorm.models import Dormitory
class Student(models.Model):
GENDER_CHOICES = [
('M', '男'),
('F', '女'),
]
STATUS_CHOICES = [
('active', '激活'),
('inactive', '未激活'),
]
student_name = models.CharField(max_length=50, verbose_name="学生姓名")
student_grade = models.CharField(max_length=10, verbose_name="学生年级")
student_num = models.CharField(max_length=20, unique=True, verbose_name='学生学号')
student_sex = models.CharField(max_length=1, choices=GENDER_CHOICES, verbose_name="学生性别")
student_age = models.IntegerField(verbose_name="学生年龄")
student_tel = models.CharField(
max_length=15,
verbose_name='学生电话',
validators=[RegexValidator(r'^\d{10,15}$', '请输入有效的电话号码')]
)
student_password = models.CharField(
max_length=128,
verbose_name="学生密码",
validators=[MinLengthValidator(6)]
)
student_status = models.CharField(max_length=10,default='active', choices=STATUS_CHOICES, verbose_name="学生状态")
student_last_time = models.DateTimeField(verbose_name="学生上次登录时间", null=True, blank=True)
# 一对多的关系 一个宿舍对应多个学生
dormitory = models.ForeignKey(Dormitory, on_delete=models.CASCADE, related_name='students', verbose_name='宿舍')
def save(self, *args, **kwargs):
if not self.pk and not self.student_password:
# 设置初始密码为学号后六位并加密
self.student_password = make_password(self.student_num[-6:])
elif self.student_password and not self.student_password.startswith('pbkdf2_sha256$'):
# 如果密码没有加密,确保再次加密(防止更新时明文存储)
self.student_password = make_password(self.student_password)
# 获取之前的宿舍
old_dormitory = None
if self.pk:
old_dormitory = Student.objects.get(pk=self.pk).dormitory
# 如果宿舍发生了变化,更新对应宿舍的入住人数
if old_dormitory and old_dormitory != self.dormitory:
old_dormitory.remove_student()
if self.dormitory:
self.dormitory.add_student()
super(Student, self).save(*args, **kwargs)
def __str__(self):
return self.student_name
@receiver(post_save, sender=Student)
def update_dormitory_on_student_save(sender, instance, **kwargs):
"""当学生对象被创建时,更新宿舍的当前已住人数。"""
if kwargs.get('created', False):
dormitory = instance.dormitory
dormitory.add_student()
@receiver(post_delete, sender=Student)
def update_dormitory_on_student_delete(sender, instance, **kwargs):
"""当学生对象被删除时,更新宿舍的当前已住人数。"""
dormitory = instance.dormitory
dormitory.remove_student()
(3). 维修信息模型代码如下(示例):
from django.db import models
from dorm.models import Dormitory
from students.models import Student
from django.utils import timezone
class MaintenanceRequest(models.Model):
STATUS_CHOICES = [
('pending', '待完成'),
('completed', '已完成'),
]
student_name = models.CharField(max_length=255,default='student',verbose_name='申请人')
dormitory = models.ForeignKey(Dormitory, on_delete=models.CASCADE, verbose_name='申请宿舍')
issue = models.TextField(verbose_name='故障信息')
remarks = models.TextField(blank=True, null=True, verbose_name='备注')
repairman = models.CharField(max_length=10, default='待指派', verbose_name='维修人员')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='申请时间')
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='pending', verbose_name='申请状态')
completed_at = models.DateTimeField(null=True, blank=True, verbose_name='完成时间')
def __str__(self):
return f'{self.student_name} - {self.dormitory.dorm_num} - {self.get_status_display()}'
(4). 访客信息模型代码如下(示例):
from django.db import models
from django.db import models
from django.core.validators import RegexValidator
from dorm.models import Dormitory
class Visitor(models.Model):
GENDER_CHOICES = [
('M', '男'),
('F', '女'),
]
visitor_name = models.CharField(max_length=50, verbose_name="访客姓名")
visitor_tel = models.CharField(
max_length=11,
verbose_name='访客电话',
validators=[RegexValidator(r'^\d{10,11}$', '请输入有效的电话号码')]
)
visitor_gender = models.CharField(max_length=1, choices=GENDER_CHOICES, verbose_name="访客性别")
visit_date = models.DateTimeField(verbose_name="到访时间")
visit_purpose = models.TextField(verbose_name="到访目的")
dormitory = models.ForeignKey(Dormitory, on_delete=models.CASCADE, verbose_name="宿舍")
remarks = models.TextField(blank=True, null=True, verbose_name="备注")
def __str__(self):
return f'{self.visitor_name} - {self.visit_date.strftime("%Y-%m-%d %H:%M:%S")}'
(5). 通知信息模型代码如下(示例):
class Notification(models.Model):
title = models.CharField(max_length=100, verbose_name="通知标题")
message = models.TextField(verbose_name="通知内容")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
def __str__(self):
return self.title
class Meta:
verbose_name = "通知"
verbose_name_plural = verbose_name
ordering = ['-created_at']
2.ORM基础增删改查示例
使用ORM进行数据库操作
一旦你定义了模型及其关系,你就可以使用Django的ORM来执行数据库操作了。例如,查询、创建、更新和删除记录。
# 增
创建一个新的作者
author = Author.objects.create(name="John Doe")
创建一个新的书,并将其与作者关联
book = Book.objects.create(title="Django Book", author=author)
为书添加额外的作者(多对多关系)
another_author = Author.objects.create(name="Jane Doe")
book.authors.add(another_author)
# 查
获取所有记录
all_objs = MyModel.objects.all()
使用过滤条件获取记录
filtered_objs = MyModel.objects.filter(field1='value1')
获取单个对象(注意:这会抛出异常如果找到多个或没有对象)
single_obj = MyModel.objects.get(id=1)
获取第一个匹配的对象(如果没有找到则返回None)
first_obj = MyModel.objects.filter(field1='value1').first()
# 改
# 检索对象并更新
obj = MyModel.objects.get(id=1)
obj.field1 = 'new_value1'
obj.save()
# 删
# 删除单个对象
obj = MyModel.objects.get(id=1)
obj.delete()
# 或者删除查询集中的所有对象
MyModel.objects.filter(field1='value1').delete()
# 或者使用update()方法更新查询集中的所有对象
MyModel.objects.filter(field1='value1').update(field2='new_value2')
下面用通知管理的增删改查做示例来进行展示ORM基础的操作:
消息通知应用中部分views.py:
# 展示消息通知列表
def Notificationlist(request):
search_query = request.GET.get('search', '') # 获取搜索关键词
notifications = Notification.objects.all()
if search_query:
notifications = notifications.filter(title__icontains=search_query) # 按标题搜索
# 分页设置,每页显示7条通知
paginator = Paginator(notifications, 7)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'news/Notificationlist.html', {
'page_obj': page_obj,
'search_query': search_query, # 将搜索关键词传递给模板
})
# 增
def news_create_view(request):
if request.method == 'POST':
# request.post[] 获取表单提交 组件name为【'name'】里的值
title = request.POST['title']
message = request.POST['message']
Notification.objects.create(title=title, message=message)
messages.success(request, '新闻已成功添加!')
# 重新定向到列表页
return redirect('Notificationlist')
return redirect('Notificationlist')
# 改 通过传递的id进行查询展示修改
def news_update_view(request, pk):
news = get_object_or_404(Notification, pk=pk)
if request.method == 'POST':
news.title = request.POST['title']
news.message = request.POST['message']
news.save()
messages.success(request, '新闻已成功更新!')
return redirect('Notificationlist')
else:
return render(request, 'news/newsupdate.html', {'news': news})
# 删 通过传入的id进行删除
def news_delete_view(request, pk):
try:
news = get_object_or_404(Notification, pk=pk)
news.delete()
messages.success(request, '新闻已成功删除!')
return redirect('Notificationlist')
except:
messages.error(request, '新闻删除失败!')
return redirect('Notificationlist')
前端模板Notificationlist.html:
{% extends 'base.html' %}
{% block content %}
<div class="right">
<div class="top-section">
<h1>新闻管理</h1>
<div class="actions">
<!-- 搜索表单 -->
<form method="get" action="{% url 'Notificationlist' %}" class="d-inline">
<input type="text" id="search" name="search" value="{{ search }}" placeholder="搜索标题..."/>
<button type="submit" class="btn btn-primary">搜索</button>
</form>
<!-- 新增新闻按钮 -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal"
data-bs-target="#addNewsModal">
新增新闻
</button>
</div>
</div>
<!-- 新闻表格 -->
<div class="table-section">
<form id="batchDeleteForm">
{% csrf_token %}
<table id="building-table">
<thead>
<tr>
<th>标题</th>
<th>内容</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for news in page_obj %}
<tr>
<td>{{ news.title }}</td>
<td>{{ news.message|truncatewords:20 }}</td>
<td>{{ news.created_at }}</td>
<td>
<a class="btn btn-warning" href="{% url 'news_update_view' news.id %}">
编辑
</a>
<a href="{% url 'news_delete_view' news.id %}">
<button type="button" class="btn btn-danger">
删除
</button>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<br>
</form>
</div>
<!-- 分页控件 -->
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1{% if search_query %}&search={{ search_query }}{% endif %}">« 第一页</a>
<a href="?page=
{{ page_obj.previous_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}">上一页</a>
{% else %}
<span class="disabled">第一页</span>
<span class="disabled">上一页</span>
{% endif %}
</span>
<span class="current">页码 {{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</span>
<span class="step-links">
{% if page_obj.has_next %}
<a href="?page=
{{ page_obj.next_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}">下一页</a>
<a href="?page=
{{ page_obj.paginator.num_pages }}{% if search_query %}&search={{ search_query }}{% endif %}">最后一页 »</a>
{% else %}
<span class="disabled">下一页</span>
<span class="disabled">最后一页</span>
{% endif %}
</span>
</div>
</div>
<!-- 新增新闻模态框 -->
<div class="modal fade" id="addNewsModal" tabindex="-1" aria-labelledby="addNewsModalTitle"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addNewsModalTitle">新增新闻</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="post" action="{% url 'news_create_view' %}">
{% csrf_token %}
<div class="mb-3">
<label for="news-title" class="form-label">新闻标题:</label>
<input type="text" class="form-control" id="news-title" name="title" required>
</div>
<div class="mb-3">
<label for="news-message" class="form-label">新闻内容:</label>
<textarea class="form-control" id="news-message" name="message" rows="5"
required></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% load static %}
<script src="{% static 'js/showMessgae.js' %}"></script>
<script src="{% static 'plugins/toastify/toastify.js' %}"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
{% if messages %}
{% for message in messages %}
var type = '{{ message.tags }}';
var text = '{{ message }}';
if (type === 'success') {
showSueccess(text);
} else if (type === 'error') {
showError(text);
}
{% endfor %}
{% endif %}
});
</script>
{% endblock %}
通过上面的展示可以知道:
查询数据库:
Notification.objects.all():获取 Notification 模型的所有对象。
notifications.filter(title__icontains=search_query):使用 filter 方法根据标题包含的关键字进行过滤。
创建对象:
Notification.objects.create(title=title, message=message):创建一个新的 Notification 对象并保存到数据库。
更新对象:
news.title = request.POST['title'] 和 news.message = request.POST['message']:更新对象的属性。
news.save():保存更新到数据库。
删除对象:
news.delete():删除一个对象。
分页:
Paginator 类用于分页显示查询结果。
get_object_or_404 是 Django 提供的一个函数,用于从数据库中获取一个对象。
get_object_or_404(Notification, pk=pk)
pk=pk 是一个参数,pk 代表主键(Primary Key),是数据库中唯一标识一个记录的字段。这里的 pk=pk 意味着函数将使用 URL 中传递的 pk 参数来查找对应的 Notification 对象。
后端ORM与前端模板交互的简要解释:
数据传递:
后端视图(如Notificationlist)使用ORM从数据库中获取Notification对象,并将它们传递给前端模板。
模板通过循环{% for news in page_obj %}来遍历这些对象,并显示它们的标题、内容和创建时间。
分页:
后端使用Django的Paginator类对查询结果进行分页。
模板中显示了分页控件,允许用户浏览不同的页面。分页链接包含了查询参数,以保持搜索状态或页面状态。
搜索和过滤:
后端视图接收搜索查询参数,并使用ORM的filter方法来过滤标题中包含搜索词的Notification对象。
模板中的搜索表单允许用户输入搜索词,并提交查询以更新页面内容。
创建、更新和删除:
后端视图提供了创建、更新和删除Notification对象的逻辑。
模板中的表单和按钮允许用户触发这些操作。例如,点击“新增新闻”按钮会打开一个模态框,其中包含用于创建新新闻的表单。提交这个表单会调用后端的news_create_view视图。
消息显示:
后端使用Django的messages框架来发送成功或错误消息。
模板中的JavaScript代码监听DOMContentLoaded事件,以在页面加载时显示这些消息。
总结
ORM(Object-Relational Mapping,对象关系映射)是一种编程技术,用于将数据库中的数据映射到编程语言中的对象。ORM 的主要目的是简化数据库操作,使得开发者可以用更加面向对象的方式来处理数据库,而不是直接使用 SQL 语句。
安装和配置:
需要选择一个适合你的编程语言和数据库的 ORM 框架。例如,Python 中的Django ORM,Java 中的 JavaScript等。
安装 ORM 库并配置数据库连接。
定义模型:
在 ORM 中,模型(Model)通常是一个类,它代表了数据库中的一个表。
模型的每个属性对应表中的一列。
可以在模型中定义方法,实现业务逻辑。
数据库操作:
创建:ORM 允许你创建新的数据库记录,只需实例化模型并保存。
读取:可以简单地通过模型查询数据库中的记录。
更新:修改模型实例的属性,然后保存,即可更新数据库中的记录。
删除:删除模型实例,对应的数据库记录也会被删除。
查询语言:
ORM 提供了一套丰富的查询语言(或API),允许你构造复杂的查询。
你可以使用链式调用、条件表达式等方式来构建查询。
关系管理:
ORM 支持定义模型之间的关系,如一对一、一对多、多对多等。
可以通过模型之间的关系来方便地查询关联数据。
事务管理:
ORM 提供了事务管理的功能,允许你控制数据库事务的开始、提交和回滚。
迁移和版本控制:
一些 ORM 框架支持数据库迁移功能,允许你修改模型后,自动更新数据库结构。
性能优化:
ORM 通常会提供缓存、懒加载等机制来优化性能。
开发者也可以编写原生 SQL 语句,以应对复杂的查询需求,提高性能。
使用 ORM 可以显著提高开发效率,减少直接操作数据库时可能出现的错误。然而,它也可能会引入一些性能开销,特别是在处理大量数据时。因此,在选择是否使用 ORM 时,需要根据项目的具体需求和预期的性能目标来做出决定。