学生管理系统是针对学校的大量业务处理工作而开发的管理软件,主要用于学生信息和学生分数的管理。总体任务,是实现学生信息管理关系的科学化、系统化、规范化和自动化,方便教务人员使用计算机对学生各种信息进行日常管理,如查询、修改、增加、删除等,此外还涉及学生自主查询成绩等功能。
学生管理系统包含3个角色,分别是后台管理员、老师和学生。所以该系统应该具备以下功能:
1.后台管理员拥有本系统的最高权限,可以创建分组、设定权限、管理老师信息等。
2.老师具备登陆后台功能,并且可以修改原始密码。
3.老师具备管理学生功能,可以对学生信息进行增删改查。
4.老师具备管理成绩功能,可以对学生成绩进行增删改查。
5.老师具备批量上传功能,可以批量上传学生信息,批量上传成绩信息等。
5.学生具备查看成绩功能,可以查看所有参加考试的成绩。
系统分为前台后台。前台主要用于学生帐户查询成绩,后台系统主要用于老师账户上传学生信息和成绩信息,以及管理员账户添加老师信息和设置权限组。
在前台登录页面,学生账户通过学号和密码等登录成功后,进入前台首页,首页主要展示该学生的所有成绩信息。单机某门课程考试选项,即可查看该门课程成绩信息。
在后台登陆页面,老师账户通过邮箱密码登陆后,即可进入老师管理页面。
在后台登陆页面,管理员团账户通过邮箱密码登陆成功后即可进入管理员管理页面。
本系统的软件开发及运行环境如下:
操作系统:Windows7及以上、linux
数据库和驱动;MySQL+PyMySQL
开发IDE:pycharm
开发框架:Django3 + Bootstrap + jQuery
浏览器:Chrome浏览器
本项目使用的主要命令
python manager.py makemigrations # 生成数据库迁移脚本
python manager.py migrate # 根据makemigrations命令生成的脚本,创建或修改数据库表结构
python manager.py migrate migrate_name # 回滚到指定迁移版本
python manager.py collectstaic # 生成静态资源目录,根据settings.py中的STATIC_ROOT设置
python manager.py shell # 打开django解释器,引入项目包
python manager.py dbshell # 打开django数据库连接,执行原生SQL命令
python manager.py startproject # 创建一个Django项目
python manager.py startapp # 常见一个app
python manager.py createsuperuser # 创建一个管理器超级用户,使用django.contrib.auth认证
python manager.py runserver # 创建开发服务器
本系统使用Mysql数据库来存储数据,数据库名为student_system,共包含14张数据表,其中auth和django为前缀的表哦都是django框架自动创建的表,其余为用户需要创建的数据表。
studen_system数据库中的数据表对应的中文表名及主要作用:
django框架自带的ORM可以满足绝大多数数据库开发的需求,在没有达到一定的数量级时,完全不需要担心ORM为项目带来的瓶颈。下面是学生管理系统使用ORM管理一个学生模块的数据模型:关键代码
class Student(CreateUpdateMixin):
student_num = models.CharField(max_length=10, unique=True, verbose_name='学号')
name = models.CharField(max_length=20, help_text='name/姓名', verbose_name='姓名')
gender = models.CharField(max_length=32, choices=(('male', '男'),
('female', '女')), default='男', help_text='gender/性别',
verbose_name='性别')
phone = models.CharField(max_length=11, help_text="phone/联系电话", verbose_name='联系电话')
birthday = models.DateField(verbose_name="出生日期")
# user表一对一关联
user = models.OneToOneField(User, on_delete=models.CASCADE)
# teacher表以对多关联
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE) # 设置外键
def __str__(self):
return self.name
def teacher_name(self):
"""
获取老师名称
"""
self.verbose_name = '老师名称'
return self.teacher.name
teacher_name.short_description = '老师名称'
def class_name(self):
"""
获取班级名称
"""
return self.teacher.class_name
class_name.short_description = '班级名称'
class Meta:
db_table = "student"
verbose_name_plural = "学生信息"
verbose_name = "学生信息"
在上述代码中,使用model.OneToOneField实现student表和auth_user表的一对一管,使用models.ForeignKey实现student和teacher的一对多关联。此外,数据表也存在其他表之间的关系:
使用django命令创建项目时,会生成一个与项目同名的全局配置文件目录
将manage.py修改如下:
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
创建config包,将asgi,settings,urls,wsgi文件移入该包,并修改settings
ROOT_URLCONF = "config.urls"
......
WSGI_APPLICATION = 'config.wsgi.application'
config/settings.py文件是项目的配置文件,需要进行以下配置。
(1)创建应用:
diango-admin startapp 应用名称
本项目主要创建四个应用:teacher、student、score、uploadfile。创建完成后,将名称写入settings文件的INSTALLED_APPS中。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'teacher',
'student',
'score',
'uploadfile'
]
(2)配置时区:
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-Hans'
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
(3)配置mysql数据库:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'student_system',
'USER': 'root',
'PASSWORD': '********'
}
}
(4)自定义配置:
# 文件上传路径
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')
# 设置初始密码
TEACHRE_INIT_PASSWORD = 't123456' # 老师账号的初始密码
STUDENT_INIT_PASSWORD = '123456' # 学生账号的初始密码
# 没有登录时跳转的URL
LOGIN_URL = '/login/'
# 兼容Bootstrap Alert 样式
from django.contrib.messages import constants as message_constants
MESSAGE_TAGS = {message_constants.DEBUG: 'debug',
message_constants.INFO: 'info',
message_constants.SUCCESS: 'success',
message_constants.WARNING: 'warning',
message_constants.ERROR: 'danger',}
学号是一个学生在学校的唯一标识,所以在前台登录页面,需要学生填写学号及密码进行登录。对于学生填写的学号和密码信息要分别进行验证。例如:学好是否为空、长度范围是否满足、学好是否存在、学号密码是否匹配。
实现学生登录功能的步骤如下。
为了更加方便地验证表单,可以继承form.Form类来验证表单,在stuent目录下创建forms.py文件,创建StudentLoginForm类,并对学号和密码设置验证规则。
from django import forms
from django.contrib.auth.models import User
class StudentLoginForm(forms.Form):
student_num = forms.CharField(
lable='学号',
required=True,
max_length=11,
widget=forms.TextInput(attrs={
'class': "form-control mb-0",
'placeholder': '请输入学号'
}),
error_messages={
'required': "学号不能为空",
"max_length": '长度不能超过50个字符',
}
)
password = forms.CharField(
label='密码',
required=True,
min_length=6,
max_length=50,
widget=forms.TextInput(attrs={
'class': "form-control mb-0",
'placeholder': '请输入密码'
}),
error_messages={
'required': "学号不能为空",
"max_length": '长度不能超过50个字符',
"min_length": '长度不能少于6个字符',
}
)
# 二次验证函数的名字是固定写法,以clean_开头,后面跟上字段的变量
def clean_student_num(self):
# 通过了validators的验证后,在进行二次验证
student_num = self.cleaned_data['student_num']
try:
User.objects.get(username=student_num) # 使用student_num获取django用户
except User.DoesNotExist:
raise forms.ValidationError("学号不存在", 'invalid')
else:
return student_num
上述代码中,定义了一个以"clean_" + 属性名的方法clean_student_mum(),它是Django Form中的特殊方法,用于对该属性进行验证。clean_student_mum()方法就是用于都student_num属性进行验证,
如果不存在,就会抛出异常
在templates文件夹中创建login.html模板文件,关键代码如下:
{% extends "base.html" %}
{% block title %}登录页面{% endblock %}
{% block content %}
<div class="main-content container">
<div class="inner-content">
<div class="xxdj-report border zycs_text">
<div class="course-report">
<h1>学生登录h1>
<div class="wid775 div-course">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
<strong>{{ message }}strong>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×span>
button>
div>
{% endfor %}
{% endif %}
<form class="mt-4" action="" method="post">
{% csrf_token %}
<div class="form-group">
<label>{{form.student_num.label}}label>
{{form.student_num}}
{{form.student_num.errors}}
div>
<div class="form-group">
<label>{{form.password.label}}label>
{{form.password}}
{{form.password.errors}}
div>
<div class="d-inline-block w-100">
<button type="submit" class="btn btn-primary float-right">登录button>
div>
form>
div>
div>
div>
div>
div>
{% endblock %}
在上述代码中,使用extends继承父模板,然后替换掉block中内容。在html页面中,{{form.student_num}}是页面的学好元素,{{form.student_num.errors}}是页面中检测学号的错误信息。
在student/views.py中创建一个基于类的视图StudentLoginView(),并创建get()和post()请求,用于显示登陆页面和提交登陆表单,关键代码如下:
class StudentLoginView(View):
"""
学生登录页表单
"""
def get(self, request):
"""
显示登录页面
"""
return render(request, 'login.html', {'form': StudentLoginForm()}) # 渲染模板
def post(self, request):
"""
提交登录页面表单
"""
form = StudentLoginForm(request.POST) # 接收Form表单
# 验证表单
if form.is_valid():
student_num = request.POST['student_num'] # 获取学号
password = request.POST['password'] # 获取密码
user = authenticate(request, username=student_num, password=password) # 授权校验
if user is not None: # 校验成功,获得返回用户信息
login(request, user) # 登录用户,设置登录session
request.session['uid'] = user.id # 设置用户名的session
request.session['username'] = user.student.name # 设置用户名的session
request.session['student_num'] = user.student.student_num # 设置用户名的session
return HttpResponseRedirect('/')
else:
messages.add_message(request, messages.ERROR, '用户名和密码不匹配') # 提示错误信息
return render(request, 'login.html', {'form': form}) # 渲染模板
上述代码,get()方法比较简单,主要用于渲染登陆页面。在post()方法中,首先使用表单类接受表单数据,然后调用is_valid()函数来验证用户提交的表单数据是否满足StudentLoginForm类中设置的验证规则。如果不能满足验证规则,则在登陆页面中输出设置的错误信息。如果满足验证规则,则使用authenticate()函数判断学号和密码是否匹配。如果不匹配没提示错误信息;如果匹配,则将图画信息保存到session中。
在浏览器输入127.0.0.1:8000/login进入登录页面
登陆成功后,网站顶部导航栏会显示该学生的姓名。滑动鼠标到姓名位置,将显示注销和修改密码菜单,单击注销即可退出登录。使用django内置的logout()函数可以快速实现退出功能,成功后,页面跳转到accounts/login在settings.py文件中可以通过设置LOGIN_URL参数更改跳转路径:
LOGIN_URL = "/login/"
在student/views.py文件中,定义logout()函数来实现用户退出功能,关键代码如下
from django.contrib.auth import authenticate, login,logout as django_logout
def logout(request):
"""
退出登录
:param request:
:return:
"""
django_logout(request) # 清楚response的cookie和django_session中的记录
return HttpResponseRedirect("/login")
在上述代码中,由于视图函数logout()与模块下的logout重名,所以引入时设置别名,当调用django_logout()函数时,会清楚response对象cookie和django_session中的记录,从而实现退出登陆的功能。
学生登录成功后,页面将跳转至成绩列表页。该页面需要学生登录后才能访问。此时可以使用django提供的装饰器函数login_required()来判断是否登陆成功。如果学生已经登陆,此函数返回True,否则返回False。通常情况下,需要在settings.py文件中设置LOGIN_URL参数,代码如下:
LOGIN_URL = "/login/"
成绩列表页会展示该学生所有考试成绩列表信息,包括考试名称。成绩上传时间、所在班级以及老师名。由于student表和score表是一对多的管理,所以可以通过学生信息直接获取该学生的所有成绩信息,然后再模板中遍历每一个成绩信息即可。
在student/views.py中创建index()函数,关键代码如下:
@login_required
def index(request):
"""
首页
:param request:
:return:
"""
student_num = request.session.get("student_num", ) # 获取当前学生学号
student = Student.objects.get(student_num=student_num) # 根据学号查询学生信息
scores = student.score_set.all() # 获取该学生的所有分数
return render(request, 'index.html', {'scores': scores})
在templates下创建index.html文件
{% if not scores %}
<div style="text-align:center">
暂无成绩信息!
</div>
{% else %}
{% for score in scores %}
<li style="border: 1px solid #ccc;border-radius: 10px;margin:10px;">
<a href="{% url 'score' score_id=score.id %}">
<span class="start_time"><b>{{ score.created_at|date:"Y-m" }}</b><i>{{ score.created_at|date:"d" }}</i></span>
<h1 title="{{ score.title }}">
{{ score.title }}
</h1>
<p>班级:{{score.student.teacher.class_name}}</p>
<p style="padding:5px 0">老师:{{score.student.teacher.name}}</p>
</a>
</li>
{% endfor %}
{% endif %}
单机每一科成绩,即可根据成绩id进入该科成绩的详情页,在详情页需要显示学生名、考试名称和学生成绩等内容,如果成绩id不存在,则进入404错误页面,在student/views.py目录中创建score()函数,关键代码如下:
@login_required
def score(request, score_id):
"""
成绩详情
:param request:
:param score_id:
:return:
"""
try:
score= Score.object.get(id=score_id)
except:
return render(request,'404.html', {"errmsg":'数据异常'})
return render(request,'score.html',{'score':score})
同时创建score表结构:
from django.db import models
from utils.base_models import CreateUpdateMixin
from student.models import Student
class Score(CreateUpdateMixin):
title = models.CharField(max_length=20,help_text='title/考试名称',verbose_name='考试名称')
score = models.DecimalField(max_digits=5,decimal_places=2,help_text='score/分数',verbose_name='分数')
student = models.ForeignKey(Student, on_delete=models.CASCADE,verbose_name='学生姓名') # 设置外键
def student_name(self):
"""
获取学生姓名
"""
self.verbose_name = '学生姓名'
return self.student.name
student_name.short_description = '学生姓名'
def student_num(self):
"""
获取学号
"""
self.verbose_name = '学号'
return self.student.student_num
student_num.short_description = '学号'
class Meta:
db_table = "score"
verbose_name_plural = "成绩信息"
verbose_name = "成绩信息"
管理员具有网站的最高权限,在本项目中只为其设计管理老师信息和设置权限功能。创建管理员的命令如下:
python manage.py createsuperuser
为了实现管理员具有后台管理老师信息的功能,需要在teacher应用中编写teacher/admin.py文件对后台老师模块进行设置。创建TeacherAdmin类,继承admin.ModelAdmin父类。在TeacherAdmin类中定义相应的类属性,如list_display用于配置展示列表的字段;list_filter用于配置过滤查询字段;search_fields用于配置搜索字段等。配置完成后,需要使用admin.site.register(Teacher, TeacherAdmin)将Teacher模型类绑定到TeacherAdmin管理后台。关键代码如下:
from django.contrib import admin
from teacher.models import Teacher
from django.contrib.auth.models import User
from django.conf import settings
from django.contrib.auth.hashers import make_password
# Register your models here.
class TeacherAdmin(admin.ModelAdmin):
# 配置展示列表,在Teacher板块下方列表展示
list_display = ('name', 'email', 'class_name', 'gender', 'phone')
# 配置过滤查询字段,在Teacher板块右侧显示过滤框
list_filter = ('class_name', 'name')
# 配置可以搜索的字段,在Teacher板块下方显示搜索框
search_fields = (['class_name', 'name'])
# 定义后台列表显示时每页显示的数量
list_per_page = 30
# 定义列表显示的顺序
ordering = ('-create_at',)
# 显示字段
fieldsets = (
(None, {
'fields': ('name', 'email', 'class_name', 'gender', 'phone')
}),
)
def save_model(self, request, obj, form, change):
user = User.objects.create(
email=request.POST.get("email"), # 获取邮箱
username=request.POST.get("email"), # 防止重名,用Email作为用户登陆名
password=make_password(settings.TEACHER_INIT_PASSWORD), # 密码加密
is_staff=1 # 郧西作为管理员登陆后台
)
obj.tid_obj.user_id = user.id # 获取新增用户的id,作为tid和user_id
super().save_model(request, obj, form, change)
return
def delete_queryset(self, request, queryset):
"""
删除多条数据
同时删除user表中数据
由于使用的是批量删除,所以需要遍历delete_queryset 中的 queryset
:param request:
:param queryset:
:return:
"""
for obj in queryset:
obj.user.delete()
super().delete_model(request, obj)
return
def delete_model(self, request, obj):
"""
删除单条记录
同时删除user表中数据
:param request:
:param obj:
:return:
"""
super().delete_model(request, obj)
if obj.user:
obj.user.delete()
return
# 设置后台页面头部显示内容和页面标题
admin.site.site_header = "学生管理系统"
admin.site.site_site = "学生管理系统"
# 绑定Teacher模型到TeacherAdmin管理后台
admin.site.register(Teacher, TeacherAdmin)
上述代码中,定义save_model()方法用来覆盖父类的save_model()方法,是先保存老师信息的同时添加到auth_user表。定义delete_queryset()方法用来覆盖父类的delete_queryset()方法,实现批量删除老师信息的同时,删除auth_user表中的用户信息。定义delete_model()方法覆盖父类方法,实现删除单个用户信息的同时,删除auth_user表中的用户信息。
在网站后台首页,单机“老师信息”即可进入老师管理页面,
由于teacher表和auth_user表是一对一的关系,添加老师后,也会在auth_user表中和新增一条用户信息。为防止老师重名,将teacher表的邮箱作为auth_user表的用户名,并且设置初始密码为setting.py文件中settings.TEACHER_INIT_PASSWORD的值。
此外,由于设置了级联删除,当删除teacher表的数据以后,auth_user表中对应的记录也会一并删除,反之亦然。
添加完老师信息以后,需要魏老师用户设置权限,该权限包括管理学生信息、管理考试成绩信息、批量导入学生信息和成绩信息等。
(1)单机后台首页中的“组”进入权限组管理,单机“增加组”按钮进入权限组设置,然后添加组名称,并选中该族所具备的权限:
单机“用户”进入用户列表,单击选择老师邮箱,进入用户详情页面,在"可用组"列表中将“老师”添加到右侧的“选中的组”列表中:
在auth_user表中,老师用户的is_staff字段值为1,所以老师用户可以通过用户名和密码登录后台。
登陆成功以后,该用户就具备老师用户组的的权限,可以管理学生信息、管理成绩信息和批量上传以及修改密码,
为了实现老师在后台管理学生信息的功能,需要在student应用中编写student/admin.py文件对后台学生模块进行设置。创建StudentAdmin类,继承admin.ModelAdmin父类。StudentAdmin类的实现与TeacherAdmin类似:
from django.contrib import admin
from student.models import Student
from django.contrib.auth.models import User
from django.conf import settings
from django.contrib.auth.hashers import make_password
# Register your models here.
class StudentAdmin(admin.ModelAdmin):
"""
创建StudentAdmin类,继承自admin.ModelAdmin
"""
# 配置展示列表,在user板块下方列表展示
list_display = ('student_num', 'name', 'class_name', 'teacher_name', 'gender', 'birthday')
# 配置过滤查询字段,在User板块右侧显示过滤框
list_filter = ('name', 'student_name')
# 配置可以搜索的字段,在User板块下方显示搜索框
search_fields = (['name', 'student_name'])
readonly_fields = ("teacher",) # 设置只读字段,不允许更改
ordering = ('-created_at',) # 定义列表现实的顺序,符号表示降序
# 显示字段
fieldsets = (
(None, {
'fields': ('student_num', 'name', 'gender', 'phone', 'birthday')
}),
)
def save_model(self, request, obj, form, change):
"""
添加student表时,同时添加到user表
由于需要和teacher表级联,所以自动获取当前登陆的老师id作为teacher_id
:param request:
:param obj:
:param form:
:param change:
:return:
"""
if not change:
user = User.objects.create(
username=request.POST.get("student_num"), # 学号作为用户登陆名
password=make_password(settings.STUDENT_INIT_PASSWORD),
)
obj.user_id = user.id # 获取新增用户的id
obj.teacher_id = request.user.id # 获取当前老师的id
super().save_model(request, obj, form, change) # 调用父类保存方法
return
def delete_queryset(self, request, queryset):
"""
删除多条数据
同时删除user表中数据
由于使用的是批量删除,所以需要遍历delete_queryset 中的 queryset
:param request:
:param queryset:
:return:
"""
for obj in queryset:
obj.user.delete()
super().delete_model(request, obj)
return
def delete_model(self, request, obj):
"""
删除单条记录
同时删除user表中数据
:param request:
:param obj:
:return:
"""
super().delete_model(request, obj)
if obj.user:
obj.user.delete()
return
# 绑定Student模型到StudentAdmin管理后台
admin.site.register(Student, StudentAdmin)
上述代码中,由于teacher表和student表是一对多关系,所以在auth_user表中设置了teacher_id字段,
该字段的值即为当前登录的老师用户id。
老师账号登陆管理后台后,但学生信息右侧的”增加“按钮,即可添加学生信息
单击”保存“按钮,页面会跳转到学列表页,并显示学生所在的”班级名称“和”老师姓名“。因为student表中没有这两个字段,所以需要在student/model.py中编写如下代码
def teacher_name(self):
"""
获取老师名称
"""
self.verbose_name = '老师名称'
return self.teacher.name
teacher_name.short_description = '老师名称'
def class_name(self):
"""
获取班级名称
"""
return self.teacher.class_name
class_name.short_description = '班级名称'
学生信息列表如图所示:
为了实现老师在后台管理成绩信息的功能,需要在score应用中编写score/admin.py文件对后台成绩模块进行设置。创建ScoreAdmin类,继承自admin.ModelAdmin父类。score/admin.py:
from django.contrib import admin
from score.models import Score
class ScoreAdmin(admin.ModelAdmin):
"""
创建ScoreAdmin类,继承自admin.ModelAdmin
"""
# 配置展示列表,在Score板块下方列表显示
list_display = ('title', "student_num", 'student', 'score')
# 配置过滤查询字段,在Score板块右侧显示过滤框
list_filter = ('title', 'student')
# 配置可以搜索的字段,在Score板块下方显示搜索框
# student是外键,管理student类,这里使用双下划线+属性名的方式搜索
search_fields = (['title', 'student__name', 'student__student_num'])
ordering = ('-created_at',) # 定义列表显示的顺序,负号为降序
fieldsets = (
(None, {
'fields': ('title', 'student', 'score')
}),
)
# 绑定Scope模型到ScoreAdmin管理后台
admin.site.register(Score, ScoreAdmin)
student和score是一对多的关系,所以在添加学生信息时需要选择学生姓名:
添加完成信息后,页面没有跳转到成绩列表页。在该页面中可以根据考试名称、学生姓名以及学号进行查询。由于score表中没有学生姓名和学号字段,所以不能直接在search_fields属性中设置,但是可以通过score表中的student外键关联到student表,使用双下划线+属性名的方式设置,代码如下:
search_field = (["title","student__name","student__student_num"])
当学生信息和成绩信息较多时,使用手动上传的方式会非常繁琐,并且难以保证上传的准确率。此时老师用户可以使用批量上传的方式来结局这个问题。那么数据从哪里获得呢?通常情况下,可以将学生信息和成绩信息先保存到Excel文件中,然后在后台导入Excel文件,将Excel数据存储到Mysql数据库中,从而实现批量上传的目的。
学生信息:
uploadfile用于实现批量上传功能,具体步骤如下
设置允许上传的文件后缀为xls或xlsx,并且定义上传的文件内容是学生信息或成绩信息,关键代码如下
from django.db import models
from utils.base_models import CreateUpdateMixin
from django.core import validators
from teacher.models import Teacher
TYPE_CHOICES = (
(1, '学生信息'),
(2, '成绩信息'),
)
class FileUpload(CreateUpdateMixin):
file_name = models.FileField(
validators=[validators.FileExtensionValidator(['xls', 'xlsx'], message='必须为xls或xlsx文件')],
help_text='file_type/上传文件名', verbose_name='上传文件名')
file_type = models.IntegerField(choices=TYPE_CHOICES, default=1, help_text='file_type/文件类型', verbose_name='文件类型')
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE) # 设置外键
class Meta:
db_table = "file"
verbose_name_plural = "上传文件"
verbose_name = "上传文件"
在上传文件页面,如果上传其他格式文件(jpg等),单击提交,将提示”必须为xls或xlsx文件“错误信息,如图所示:
接受上传的Excel文件,然后使用openpyxl库读取Excel数据,并写入数据库,关键代码如下
class FileUploadAdmin(admin.ModelAdmin):
# 配置展示列表,在User版块下的列表展示
list_display = ('file_name',)
# 设置只读字段,不允许更改
readonly_fields = ('teacher',)
def save_model(self, request, obj, form, change):
obj.teacher_id = request.user.id # 获取当前老师的ID
# 调用父类方法保存
super().save_model(request, obj, form, change)
# 拼接目录
file_path = os.path.join(settings.MEDIA_ROOT, obj.file_name.name)
if request.POST['file_type'] == '1': # 上传学生信息
repetition = self.upload_student(file_path, request.user.id)
elif request.POST['file_type'] == '2': # 上传成绩信息
repetition = self.upload_score(file_path)
# 提示重复数据条数
if repetition:
messages.add_message(request, messages.INFO, f'过滤{repetition}条重复数据')
return
上述代码中,如果上传的是学生信息内容,则调用upload_student()方法。upload_student()方法中使用openpyxl模块读取Excel中的每一行学生信息。首先判断该学号的用户是否存在,如果已经存在,则不添加学生信息;如果不存在,则把该学生信息写入auth_user表,并且使用bulk_create()方法批量添加到student表中,upload_student()方法代码如下:
def upload_student(self, file_path, teacher_id):
wb = openpyxl.load_workbook(file_path) # 打开excel
ws = wb.active # 选中第一个sheet
rows = ws.max_row # 获取行数
columns = ws.max_column # 获取列数
student_list = []
repetition = 0
# 从第2行开始遍历每行
for row in ws.iter_rows(min_row=2, min_col=1, max_row=rows, max_col=columns):
data = [i.value for i in row] # 获取每一行数据
# 去除重复数据
if Student.objects.filter(student_num=data[0]).exists():
repetition += 1
continue
# 写入User表
user = User(
username=data[0], # 以学号作为用户名,防止重复
password=make_password(settings.STUDENT_INIT_PASSWORD),
)
user.save() # 存入数据库
# 写入student表
student = Student(
student_num=data[0],
name=data[1].strip(), # 去除空格
gender='male' if data[2] == "男" else "femal",
phone=data[4],
birthday=data[3],
user_id=user.id,
teacher_id=teacher_id
)
student_list.append(student)
Student.objects.bulk_create(student_list) # 批量加入Student表
return repetition
批量添加学生信息运行效果图如图所示:
如果上传的是成绩信息内容,则调用upload_score()方法。upload_score()方法中使用,openpyxl模块读取Excel中的每一行成绩信息。首先判断学号是否存在,如果不存在,咋不天界学生成绩信息;如果存在,则获取该用户id,然后查找score表中是否还有该学生的成绩信息,并且使用bulk_create()方法批量添加到score表中。upload_score()方法的关键代码如下:
def upload_score(self, file_path):
wb = openpyxl.load_workbook(file_path) # 打开excel
ws = wb.active # 选中第一个sheet
rows = ws.max_row # 获取行数
columns = ws.max_column # 获取列数
score_list = []
repetition = 0
# 从第2行开始遍历每行
for row in ws.iter_rows(min_row=2, min_col=1, max_row=rows, max_col=columns):
data = [i.value for i in row] # 获取每一行数据
# 查找student表,获取student_id
try:
student = Student.objects.get(student_num=data[1])
except:
continue
# 去除重复数据
if Score.objects.filter(title=data[0], student_id=student.id).exists():
repetition += 1
continue
# 写入student表
score = Score(
title=data[0], # 标题
student_id=student.id, # 学生id
score=data[-1] # 学生分数
)
score_list.append(score)
Score.objects.bulk_create(score_list) # 批量加入Score表
return repetition
admin.site.register(FileUpload, FileUploadAdmin)
效果图: