说明
有小伙伴想让我出一期多对多的教程,这期用班级表和教师表来做演示
后端
数据模型:models.py
班级表在之前已经做了,这里创建老师表,然后多对多班级表
class TeacherModel(CoreModel):
fk_class = models.ManyToManyField(ClassModel, blank=True, db_constraint=False, verbose_name="班级")
name = models.CharField(max_length=10, unique=True, verbose_name="老师姓名")
def __str__(self):
return self.name
class Meta:
db_table = table_prefix + "teacher"
verbose_name = "老师表"
verbose_name_plural = verbose_name
ordering = ("id",)
序列化器:serializers.py
class TeacherSerializer(CustomModelSerializer):
"""
老师查询序列化
"""
teacher_class = DynamicSerializerMethodField()
def get_teacher_class(self, instance, parsed_query):
fk_class = instance.fk_class.all()
serializer = ClassSerializer(
fk_class,
many=True,
parsed_query=parsed_query
)
return serializer.data
class Meta:
model = TeacherModel
fields = "__all__"
class TeacherCreateUpdateSerializer(CustomModelSerializer):
"""
老师新增,修改序列化
"""
class Meta:
model = TeacherModel
fields = "__all__"
class TeacherImportSerializer(CustomModelSerializer):
"""
老师导入,导出序列化
"""
teacher_class = DynamicSerializerMethodField()
def get_teacher_class(self, instance, parsed_query):
fk_class = instance.fk_class.all()
serializer = ClassSerializer(
fk_class,
many=True,
parsed_query=parsed_query
)
return ",".join([i["name"] for i in serializer.data])
class Meta:
model = TeacherModel
fields = "__all__"
视图:views.py
class TeacherViewSet(CustomModelViewSet):
"""
老师表视图类
"""
queryset = TeacherModel.objects.all()
serializer_class = TeacherSerializer
create_serializer_class = TeacherCreateUpdateSerializer
update_serializer_class = TeacherCreateUpdateSerializer
# 导入
import_serializer_class = TeacherImportSerializer
import_field_dict = {
"name": "老师姓名",
"fk_class": {
"title": "班级",
"choices": {"queryset": ClassModel.objects.all(), "values_name": "name"}},
"description": "描述",
}
# 导出
export_serializer_class = TeacherImportSerializer
export_field_label = {
"name": "老师姓名",
"teacher_class": "班级",
"description": "描述",
"creator": "创建人",
"create_datetime": "创建时间",
"modifier": "修改人",
"update_datetime": "修改时间",
}
路由:urls.py
from rest_framework.routers import SimpleRouter
from apps.student.views import ClassViewSet, TeacherViewSet
router = SimpleRouter()
router.register("class", ClassViewSet) # 班级表路由
router.register("teacher", TeacherViewSet) # 老师表路由
urlpatterns = [
]
urlpatterns += router.urls
注册
APP注册
在
application/settings.py文件中注册创建的app应用
# 和班级表同一个APP,不需要重复注册
路由注册
在
application/urls.py注册路由
# 和班级表同一个APP,不需要重复注册
后端运行
- 进入后端项目目录
cd backend - 在项目根目录中,复制
./conf/env.example.py文件为一份新的到./conf/env.py下,并重命名为env.py - 在
env.py中配置数据库信息(默认数据库为sqlite3,测试演示可忽略此步骤) - 安装依赖环境
pip3 install -r requirements.txt - 执行迁移命令
python3 manage.py makemigrationspython3 manage.py migrate - 初始化数据
python3 manage.py init - 初始化省市县数据
python3 manage.py init_area - 启动项目
python3 manage.py runserver 0.0.0.0:8000
前端
接口文件:api.js
/*
* @文件介绍: 老师管理接口
*/
import { request, downloadFile } from '@/api/service';
export const urlPrefix = '/api/student/teacher/';
/**
* 列表查询
* @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
/*
* @文件介绍: 老师管理配置文件
*/
import { request } from '@/api/service';
export const crudOptions = (vm) => {
return {
pageOptions: {
compact: true,
export: { local: false }, // 服务器导出
},
options: {
height: '100%',
rowKey: 'id',
},
formOptions: {
defaultSpan: 24, // 默认的表单 span
},
viewOptions: {
componentType: 'row',
},
selectionRow: {
align: 'center',
width: 40,
},
columns: [
{
title: '老师姓名',
key: 'name',
width: 90,
search: {
disabled: false,
},
form: {
rules: [
// 表单校验规则
{ required: true, message: '老师姓名必填项' },
],
component: {
clearable: true,
},
},
},
{
title: '班级',
key: 'fk_class',
search: {
disabled: true,
},
minWidth: 130,
type: 'table-selector',
dict: {
cache: false,
url: '/api/student/class/',
value: 'id', // 数据字典中value字段的属性名
label: 'name', // 数据字典中label字段的属性名
getData: (url, dict, { form, component }) => {
return request({
url: url,
params: {
page: 1,
limit: 10,
},
}).then((ret) => {
component._elProps.page = ret.data.page;
component._elProps.limit = ret.data.limit;
component._elProps.total = ret.data.total;
return ret.data.data;
});
},
},
form: {
rules: [
// 表单校验规则
{ required: true, message: '班级必填项' },
],
component: {
pagination: true,
props: { multiple: true },
elProps: {
columns: [
{
field: 'name',
title: '班级名称',
},
{
field: 'description',
title: '备注',
},
],
},
},
},
component: {
name: 'manyToMany',
valueBinding: 'teacher_class',
children: 'name',
},
},
].concat(
vm.commonEndColumns({
description: { showTable: true },
create_datetime: { showTable: false },
update_datetime: { showTable: false },
})
),
};
};
页面文件:index.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: 'teacher',
mixins: [d2CrudPlus.crud],
data() {
return {
api: api.urlPrefix,
};
},
methods: {
getCrudOptions() {
return crudOptions(this);
},
pageRequest(query) {
return api.GetList(query);
},
addRequest(row) {
console.log(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>
前端运行
- 进入前端项目目录
cd web - 安装依赖
npm install --registry=https://registry.npm.taobao.org - 启动服务
npm run dev