多对多数据结构

351 阅读3分钟

19-多对多数据结构案例_哔哩哔哩_bilibili

说明

有小伙伴想让我出一期多对多的教程,这期用班级表和教师表来做演示

后端

数据模型: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,不需要重复注册

后端运行

  1. 进入后端项目目录 cd backend
  2. 在项目根目录中,复制 ./conf/env.example.py 文件为一份新的到 ./conf/env.py 下,并重命名为env.py
  3. env.py 中配置数据库信息(默认数据库为sqlite3,测试演示可忽略此步骤)
  4. 安装依赖环境 pip3 install -r requirements.txt
  5. 执行迁移命令 python3 manage.py makemigrationspython3 manage.py migrate
  6. 初始化数据 python3 manage.py init
  7. 初始化省市县数据python3 manage.py init_area
  8. 启动项目 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>

前端运行

  1. 进入前端项目目录 cd web
  2. 安装依赖 npm install --registry=https://registry.npm.taobao.org
  3. 启动服务 npm run dev