立项
用django-vue-admin快速实现学生成绩管理项目
最近发现一个比较好用的框架 django-vue-admin 前后端分离的后台管理系统,后端采用的是Python-django,前端采用的是Vue,刚好完美契合我的技术栈,用最经典的学员管理系统来学习下这套系统的便捷性,并且给自己刷一点经验值
设计思路
学员管理系统是毕设,培训机构经常用来练手的经典项目,我没上过大学,也没上个培训机构,按自己的理解做一套这个系统,该系统主要有5张主表,班级表,学科表,老师表,学员表,成绩表,还有一张辅助表,班级_学科_老师 表,个人想到的几点注意:
- 一个学生只能在一个班级,所以班级表和学生表要做一对多关联
- 因为学科表,班级表,老师表,是相互多对多,想要三个表多对多太过于复杂,所以这里采用辅助表完成
- 一个班级的一个学科只能是一个老师
实现功能
老师登录可以看到自己所授课的所有班级,所授的学科,并且可以看到学生每次考试的分数,以及平均分 学生登录可以看到自己所有学科的老师,以及每次考试的分数,并根据不同学科做一个雷达图
数据模型
班级管理
后端项目
创建APP
在指定目录下创建目录
- 首先创建apps目录,然后在apps目录下,新建一个和模块同名的文件夹,比如
student - 建完目录,执行命令:
python manage.py startapp student ./apps/student - 修改
./apps/student/apps.py文件中的内容:name = 'apps.student' - 使用startapp创建的app没有序列化器,筛选器,路由这几个文件,需要手动自己创建
数据模型:models.py
说明
继承
CoreModel自带一些标准字段 如果需要开启软删除,则需要在继承SoftDeleteModelclass TableStructure(CoreModel,SoftDeleteModel): ...
示例
from django.db import models
# Create your models here.
from django.db import models
from dvadmin.utils.models import CoreModel
table_prefix = 'apps_'
class ClassModel(CoreModel):
name = models.CharField(max_length=10, unique=True, verbose_name="班级名称")
def __str__(self):
return self.name
class Meta:
db_table = table_prefix + "class"
verbose_name = "班级表"
verbose_name_plural = verbose_name
ordering = ("id",)
序列化器:serializers.py
说明
继承
CustomModelSerializer增加了一些功能,每个模型分三个序列化器
- 查询:序列化查询需要的字段
- 新增,修改:对新增,修改的数据进行反序列化
- 导入,导出:对导入导出做一些额外的功能
示例
from apps.student.models import ClassModel
from dvadmin.utils.serializers import CustomModelSerializer
class ClassSerializer(CustomModelSerializer):
"""
班级查询序列化
"""
class Meta:
model = ClassModel
fields = "__all__"
class ClassCreateUpdateSerializer(CustomModelSerializer):
"""
班级新增,修改序列化
"""
class Meta:
model = ClassModel
fields = "__all__"
class ClassImportSerializer(CustomModelSerializer):
"""
班级导入,导出序列化
"""
class Meta:
model = ClassModel
fields = "__all__"
筛选器:filter.py
说明
可选项,框架自带筛选,在views中添加 filter_fields 和 search_fields 字段,但如果有一些特殊的筛选,比如时间区间,就需要自定义筛选器 在Django中,filter_fields和search_fields是用于REST框架中的过滤和搜索功能的选项。 filter_fields是一个列表,它定义了模型中哪些字段可以用于精确过滤(例如,过滤所有具有给定标题的文章)。这些字段通常是模型中具有基本匹配功能的字段,例如字符串或整数。 search_fields也是一个列表,它定义了模型中哪些字段可以用于全文搜索(例如,在标题或正文中搜索文章)。这些字段通常是文本字段或文本索引字段,例如CharField或TextField。 因此,filter_fields和search_fields之间的主要区别是它们使用的算法和匹配方式不同。filter_fields使用的匹配算法通常是精确匹配,而search_fields使用的匹配算法通常是模糊匹配。
示例
import django_filters
from .models import ClassModel
class ClassFilter(django_filters.rest_framework.FilterSet):
start_create_time = django_filters.DateFilter(field_name='create_datetime', lookup_expr='gte', help_text="创建开始时间")
end_create_time = django_filters.DateFilter(field_name='create_datetime', lookup_expr='lte', help_text="创建结束时间")
class Meta:
model = ClassModel
fields = ['start_create_time', 'end_create_time']
视图:views.py
说明
主要编写业务逻辑的地方,继承CustomModelViewSet,封装了多个方法
| 请求方法 | 路由 | 函数 | 功能 |
|---|---|---|---|
| GET | api/student/class/ | list | 查询列表 |
| POST | api/student/class/ | create | 新增数据 |
| GET | api/student/class/{id} | retrieve | 单例详情 |
| PUT | api/student/class/{id} | update | 全量修改一般用这个 |
| PATCH | api/student/class/{id} | partial_update | 部分修改 |
| DELETE | api/student/class/{id} | destroy | 删除 |
| DELETE | api/student/class/multiple_delete/ | multiple_delete | 批量删除 |
| GET | api/student/class/import_data/ | import_data | 获取导入新增模板 |
| GET | api/student/class/update_template/ | update_template | 获取导入更新模板 |
| POST | api/student/class/import_data/ | import_data | 导入数据 |
以上方法如果不够,则可以自己写视图函数,并添加 @action 装饰器自动以函数名作为路由匹配 例:
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])@action 是 Django Rest Framework 中视图集的装饰器,用于添加额外的自定义方法或修改默认方法的行为,同时允许指定该方法所支持的 HTTP 请求方法。
- methods: 参数用于指定该自定义方法所支持的 HTTP 请求方法。在上面的代码中,methods=["GET"] 表示该自定义方法仅支持 GET 请求,也就是只允许在该视图上进行 GET 请求。
- detail: 参数用于指定该自定义方法是否为详情方法,即该方法是否接收 pk 参数或 slug 参数等用于查找对象详情的参数。在上面的代码中,detail=False 表示该自定义方法是列表方法,不需要额外的参数来查找对象。
- permission_classes: 参数用于指定该自定义方法所需的权限类。在上面的代码中,permission_classes=[IsAuthenticated] 表示该自定义方法需要验证请求的用户是否已经通过身份验证才能访问。只有通过身份验证的用户才拥有访问该方法的权限。
综上, 装饰器表示添加一个列表方法,请求方法为 GET,需要用户已经通过身份验证才能访问。同时,继承该视图集的子类会继承该装饰器的参数。
示例
# Create your views here.
from rest_framework.decorators import action
from apps.student.filter import ClassFilter
from apps.student.models import ClassModel
from apps.student.serializers import ClassSerializer, ClassCreateUpdateSerializer, ClassImportSerializer
from dvadmin.utils.json_response import SuccessResponse
from dvadmin.utils.viewset import CustomModelViewSet
class ClassViewSet(CustomModelViewSet):
"""
班级表视图类
"""
queryset = ClassModel.objects.all()
serializer_class = ClassSerializer
create_serializer_class = ClassCreateUpdateSerializer
update_serializer_class = ClassCreateUpdateSerializer
# filter_class = ClassFilter # 可选项
# 导入
import_serializer_class = ClassImportSerializer
import_field_dict = {
"name": "班级名称",
"description": "描述",
}
# 导出
export_serializer_class = ClassImportSerializer
export_field_label = {
"name": "班级名称",
"description": "描述",
"creator": "创建人",
"create_datetime": "创建时间",
"modifier": "修改人",
"update_datetime": "修改时间",
}
@action(methods=["GET"], detail=False)
def quest_work(self, request, *args, **kwargs):
"""
自定义额外的视图函数
"""
return SuccessResponse(data=[], msg="自定义的视图函数")
路由:urls.py
说明
前端访问的API接口
示例
from django.urls import path
from rest_framework.routers import SimpleRouter
from apps.student.views import ClassViewSet
router = SimpleRouter()
router.register("class", ClassViewSet)
urlpatterns = [
# path('TableStructure/quest_work/', TableStructureViewSet.as_view({'post': 'quest_work', })),
# 可以使用action 装饰器
]
urlpatterns += router.urls
注册
APP注册
在
application/settings.py文件中注册创建的app应用
# application/settings.py
INSTALLED_APPS = [
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django_comment_migrate",
...,
'apps.student' # 注册学员管理模块APP
]
路由注册
在
application/urls.py注册路由
# application/urls.py
urlpatterns = (
[
...,
path("api/system/", include("dvadmin.system.urls")),
...,
path("api/student/", include("apps.student.urls")),
]
...
)
启动后端项目
- 生成迁移文件:
python3 manage.py makemigrations - 迁移数据库:
python3 manage.py migrate - 启动后端:
python3 manage.py runserver 0.0.0.0:8000 - 后端API地址:
http://127.0.0.1:8000/ - 后端项目的目录结构:
前端项目
在views新建APPS文件夹,在APPS文件夹下新建student文件夹
接口文件:api.js
说明
编写API请求函数的文件,自带函数:
- GetList:列表查询
- GetObj:单例查询
- AddObj:新增
- UpdateObj:修改
- DelObj:删除
- BatchDel:批量删除
- ExportData:导出
示例
/*
* @文件介绍: 班级管理接口
*/
import { request, downloadFile } from '@/api/service';
export const urlPrefix = '/api/student/class/';
/**
* 列表查询
* @param {*} query
* @returns
*/
export function GetList(query) {
return request({
url: urlPrefix,
method: 'get',
params: { ...query },
});
}
/**
* 单例查询
* @param {*} id
* @returns
*/
export function GetObj(id) {
return request({
url: urlPrefix + id + '/',
method: 'get',
});
}
/**
* 新增
* @param {*} obj
* @returns
*/
export function AddObj(obj) {
return request({
url: urlPrefix,
method: 'post',
data: obj,
});
}
/**
* 修改
* @param {*} obj
* @returns
*/
export function UpdateObj(obj) {
return request({
url: urlPrefix + obj.id + '/',
method: 'put',
data: obj,
});
}
/**
* 删除
* @param {*} id
* @returns
*/
export function DelObj(id) {
return request({
url: urlPrefix + id + '/',
method: 'delete',
data: { id },
});
}
/**
* 批量删除
* @param {*} keys
* @returns
*/
export function BatchDel(keys) {
return request({
url: urlPrefix + 'multiple_delete/',
method: 'delete',
data: { keys },
});
}
/**
* 导出
* @param params
*/
export function ExportData(params) {
return downloadFile({
url: urlPrefix + 'export_data/',
params: params,
method: 'get',
});
}
配置文件:crud.js
说明
CRUD配置项文件,参考配置:d2-crud-plus配置
示例
/*
* @文件介绍: 班级管理配置文件
*/
export const crudOptions = (vm) => {
return {
pageOptions: {
compact: true,
},
options: {
height: '100%',
rowKey: 'id',
},
formOptions: {
defaultSpan: 24, // 默认的表单 span
},
pageOptions: {
export: { local: false }, // 服务器导出
},
viewOptions: {
componentType: 'row',
},
selectionRow: {
align: 'center',
width: 40,
},
columns: [
{
title: '班级名称',
key: 'name',
width: 90,
search: {
disabled: false,
},
form: {
component: {
clearable: true,
},
},
},
].concat(
vm.commonEndColumns({
description: { showTable: true },
create_datetime: { showTable: false },
update_datetime: { showTable: false },
})
),
};
};
页面文件:index.vue
说明
vue页面文件
示例
<!--
* @文件介绍:班级管理
-->
<template>
<d2-container :class="{ 'page-compact': crud.pageOptions.compact }">
<template slot="header">班级管理</template>
<d2-crud-x ref="d2Crud" v-bind="_crudProps" v-on="_crudListeners">
<div slot="header">
<crud-search ref="search" :options="crud.searchOptions" @submit="handleSearch" />
<el-button-group>
<el-button size="small" v-permission="'Create'" type="primary" @click="addRow"> <i class="el-icon-plus" /> 新增 </el-button>
<importExcel :api="api" v-permission="'Import'">导入 </importExcel>
</el-button-group>
<crud-toolbar v-bind="_crudToolbarProps" v-on="_crudToolbarListeners" />
</div>
<span slot="PaginationPrefixSlot" class="prefix">
<el-button
class="square"
size="mini"
title="批量删除"
@click="batchDelete"
icon="el-icon-delete"
:disabled="!multipleSelection || multipleSelection.length == 0"
/>
</span>
</d2-crud-x>
</d2-container>
</template>
<script>
import * as api from './api';
import { crudOptions } from './crud';
import { d2CrudPlus } from 'd2-crud-plus';
export default {
name: 'class',
mixins: [d2CrudPlus.crud],
data() {
return {
api: api.urlPrefix,
};
},
methods: {
getCrudOptions() {
return crudOptions(this);
},
pageRequest(query) {
return api.GetList(query);
},
addRequest(row) {
return api.AddObj(row);
},
updateRequest(row) {
return api.UpdateObj(row);
},
delRequest(row) {
return api.DelObj(row.id);
},
batchDelRequest(ids) {
return api.BatchDel(ids);
},
doServerExport(context) {
return api.ExportData({ ...context.search });
},
},
};
</script>
启动前端项目
- 安装依赖:
npm install --registry=https://registry.npm.taobao.org - 启动服务:
npm run dev - 前端项目地址:
http://localhost:8080 - 前端目录结构: