学生成绩管理项目-班级模块

714 阅读7分钟

立项

django-vue-admin快速实现学生成绩管理项目

哔哩哔哩视频

最近发现一个比较好用的框架 django-vue-admin 前后端分离的后台管理系统,后端采用的是Python-django,前端采用的是Vue,刚好完美契合我的技术栈,用最经典的学员管理系统来学习下这套系统的便捷性,并且给自己刷一点经验值

设计思路

学员管理系统是毕设,培训机构经常用来练手的经典项目,我没上过大学,也没上个培训机构,按自己的理解做一套这个系统,该系统主要有5张主表,班级表,学科表,老师表,学员表,成绩表,还有一张辅助表,班级_学科_老师 表,个人想到的几点注意:

  • 一个学生只能在一个班级,所以班级表和学生表要做一对多关联
  • 因为学科表,班级表,老师表,是相互多对多,想要三个表多对多太过于复杂,所以这里采用辅助表完成
  • 一个班级的一个学科只能是一个老师

实现功能

老师登录可以看到自己所授课的所有班级,所授的学科,并且可以看到学生每次考试的分数,以及平均分 学生登录可以看到自己所有学科的老师,以及每次考试的分数,并根据不同学科做一个雷达图

数据模型 image.png

班级管理

后端项目

创建APP

在指定目录下创建目录

  1. 首先创建apps目录,然后在apps目录下,新建一个和模块同名的文件夹,比如student
  2. 建完目录,执行命令:python manage.py startapp student ./apps/student
  3. 修改./apps/student/apps.py文件中的内容:name = 'apps.student'
  4. 使用startapp创建的app没有序列化器,筛选器,路由这几个文件,需要手动自己创建

数据模型:models.py

说明

继承 CoreModel 自带一些标准字段 如果需要开启软删除,则需要在继承 SoftDeleteModel class 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,封装了多个方法

请求方法路由函数功能
GETapi/student/class/list查询列表
POSTapi/student/class/create新增数据
GETapi/student/class/{id}retrieve单例详情
PUTapi/student/class/{id}update全量修改一般用这个
PATCHapi/student/class/{id}partial_update部分修改
DELETEapi/student/class/{id}destroy删除
DELETEapi/student/class/multiple_delete/multiple_delete批量删除
GETapi/student/class/import_data/import_data获取导入新增模板
GETapi/student/class/update_template/update_template获取导入更新模板
POSTapi/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/
  • 后端项目的目录结构:

image.png


前端项目

在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
  • 前端目录结构:

image.png