基于 Django 信号机制实现类似触发器的效果_触发器


基于 Django 信号机制实现类似触发器的效果_django_02

我们都知道,在关系数据库中,为了保证数据完整性,我们都会使用一个叫做触发器的玩意。今天我就基于Django信号机制实现类似触发器的效果,在此之前我先简单介绍一下触发器。

基于 Django 信号机制实现类似触发器的效果_触发器_03



触发器



触发器(trigger)是数据库提供给程序员和数据分析员来保证数据完整性的一种方法,它是与表事件相关的特殊的存储过程,它的执行不是由程序调用,也不是手工启动,而是由事件来触发,比如当对一个表进行操作(insert,delete,update)时就会激活它执行。触发器经常用于加强数据的完整性约束和业务规则等。

一般情况下,常用的触发器总共有 6 种——增加数据之前的触发器、增加数据之后的触发器、删除数据之前的触发器、删除数据之后的触发器、修改数据之前的触发器、修改数据之后的触发器。



触发器的优点


触发器可通过数据库中的相关表实现级联更改,不过,通过级联引用完整性约束可以更有效地执行这些更改。触发器可以强制比用 CHECK 约束定义的约束更为复杂的约束。与 CHECK 约束不同,触发器可以引用其它表中的列。例如,触发器可以使用另一个表中的 SELECT 比较插入或更新的数据,以及执行其它操作,如修改数据或显示用户定义错误信息。触发器也可以评估数据修改前后的表状态,并根据其差异采取对策。一个表中的多个同类触发器(INSERT、UPDATE 或 DELETE)允许采取多个不同的对策以响应同一个修改语句。



慎用触发器


触发器功能强大,轻松可靠地实现许多复杂的功能,为什么又要慎用呢。触发器本身没有过错,但由于我们的滥用会造成数据库及应用程序的维护困难。在数据库操作中,我们可以通过关系、触发器、存储过程、应用程序等来实现数据操作…… 同时规则、约束、缺省值也是保证数据完整性的重要保障。如果我们对触发器过分的依赖,势必影响数据库的结构,同时增加了维护的复杂程度。


Django 信号机制



Django 包含一个当事件发生在这个框架内的其他地方有助于多个应用模块获得通知的“信号调度器”。当一些事件发生时,允许一个发送方给一群接收方发送信号,Django 内置的信号机制有很多,具体的大家可以参考:

​https://docs.djangoproject.com/zh-hans/3.0/topics/signals/。​

下面我们就通过创建一个基于 Django 的学生信息管理系统来通过信号机制实现类似触发器的效果。


学生信息管理系统



我主要是为了给大家演示效果,系统弄的尽量简单,数据库总共两个表:

  1. 学生表:ID、姓名、班级
  2. 班级表:ID、名称、学生人数

其中的关系很简单:一个班级对应多个学生,一个学生只对应一个班级。

我们主要实现学生和班级这两块数据的增删改查,但其中需要注意以下 4 点:

  1. 班级表中的学生人数字段默认为 0,不可以随意更改
  2. 删除班级表中的一条数据的时候,其对应的学生表中的所有数据都要删除
  3. 增加一条学生数据的时候,需要在对应的班级表中把学生数量+1
  4. 删除一条学生数据的时候,需要在对应的班级表中把学生数量-1



新建项目


新建项目可以参考文档用命令,也可以直接用 PyCharm,我这里直接使用 PyCharm 来创建 Django 项目。点击 Create New Project,如图所示。

基于 Django 信号机制实现类似触发器的效果_django_04

然后跳转到如图所示的界面,点击 Django,并设置项目路径和解释器,完成之后点击 CREATE。

基于 Django 信号机制实现类似触发器的效果_数据_05

等待一会之后项目就创建完成了,点击左下角的 Terminal,如图所示。

基于 Django 信号机制实现类似触发器的效果_django_06

然后输入命令 python manage.py startapp student 并回车,如图所示。

基于 Django 信号机制实现类似触发器的效果_数据_07

我们会发现项目内多出来一个叫做 student 的目录,如图所示。

基于 Django 信号机制实现类似触发器的效果_数据_08



定义模型


我们接下来就去 models.py 定义模型,代码如下:

from django.db import models


# Create your models here.
class Grade(models.Model):
name = models.CharField(max_length=255, verbose_name='名称')
student_number = models.PositiveSmallIntegerField(default=0, editable=False, verbose_name='学生人数')

class Meta:
verbose_name = verbose_name_plural = '班级'

def __str__(self):
return self.name


class Student(models.Model):
name = models.CharField(max_length=255, verbose_name='姓名')
grade = models.ForeignKey(Grade, models.CASCADE, verbose_name='班级')

class Meta:
verbose_name = verbose_name_plural = '学生'

def __str__(self):
return self.name

这段代码除了“增加一条学生数据的时候,对应班级表中学生人数字段+1”和“删除一条学生数据的时候,对应班级表中学生人数字段-1”这两个要求之外的其他要求都实现了。



实现类似触发器的效果


接下来就来着重讲解这两个需求的实现,实现这两个需求大家很容易想到触发器,但是触发器最好还是慎用,我这里直接使用信号来代替触发器。信号主要位于 django.db.models.signals 模块中,其中有 pre_save(增加数据之前)、post_save(增加数据之后)、pre_delete(删除数据之前)、post_delete(删除数据之后)……



增加数据的触发器


我们先来实现增加数据的触发器,增加数据我们可以使用 pre_save 或者 post_save 信号,在这里我用 pre_save 来实现,代码如下:

# noinspection PyUnusedLocal
@receiver(pre_save, sender=Student)
def pre_save_student(sender, instance, **kwargs):
grade = instance.grade
grade.student_number += 1
grade.save()



删除数据的触发器


接下来我们就来实现删除数据的触发器,删除数据我们可以通过 pre_delete 或者 post_delete 来实现。在这里我用 pre_delete 来实现,代码如下:

# noinspection PyUnusedLocal
@receiver(pre_delete, sender=Student)
def pre_delete_student(sender, instance, **kwargs):
grade = instance.grade
grade.student_number -= 1
grade.save()

接下来给出 models.py 的完整代码:

from django.db import models
from django.db.models.signals import pre_save, pre_delete
from django.dispatch import receiver


# Create your models here.
class Grade(models.Model):
name = models.CharField(max_length=255, verbose_name='名称')
student_number = models.PositiveSmallIntegerField(default=0, editable=False, verbose_name='学生人数')

class Meta:
verbose_name = verbose_name_plural = '班级'

def __str__(self):
return self.name


class Student(models.Model):
name = models.CharField(max_length=255, verbose_name='姓名')
grade = models.ForeignKey(Grade, models.CASCADE, verbose_name='班级')

class Meta:
verbose_name = verbose_name_plural = '学生'

def __str__(self):
return self.name


# noinspection PyUnusedLocal
@receiver(pre_save, sender=Student)
def pre_save_student(sender, instance, **kwargs):
grade = instance.grade
grade.student_number += 1
grade.save()


# noinspection PyUnusedLocal
@receiver(pre_delete, sender=Student)
def pre_delete_student(sender, instance, **kwargs):
grade = instance.grade
grade.student_number -= 1
grade.save()



配置项目


配置项目我们首先激活模型,在 settings.py 中的 INSTALLED_APPS 列表中增加一个名为'student.apps.StudentConfig'的元素,如图所示。

基于 Django 信号机制实现类似触发器的效果_django_09

激活模型之后我们就需要进行数据库迁移了。在之前的 Terminal 里面执行按顺序执行下面两个命令:

python manage.py makemigrations

python manage.py migrate

如图所示。

基于 Django 信号机制实现类似触发器的效果_触发器_10

接下来就是创建一个管理员账号,在 Terminal 下执行命令python manage.py createsuperuser,然后按照提示输入信息就行了。

然后点击 PyCharm 右上角的类似于播放的按钮运行程序。

基于 Django 信号机制实现类似触发器的效果_触发器_11

接下来打开浏览器,地址栏输入:http://127.0.0.1:8000/admin,会发现进入了一个登录页面,用之前创建的管理员账号登录,登陆之后我们会发现并没有学生和班级。我们还要去 admin.py 中注册模型,注册代码如下:

from django.contrib import admin
from.models import Grade, Student
# Register your models here.
admin.site.register(Grade)
admin.site.register(Student)

注册完成之后像之前一样重新运行项目,登陆之后如图所示。

基于 Django 信号机制实现类似触发器的效果_触发器_12

我们可以发现学生和班级已经有了,为了测试我们先增加一个班级,增加完成之后查看数据库可以发现数据库中确实有这条记录,如图所示。

基于 Django 信号机制实现类似触发器的效果_django_13

接下来我们增加一个学生,增加学生之后,我们看一下班级表中对应的学生人数字段是不是从 0 变成了 1,如图所示。

基于 Django 信号机制实现类似触发器的效果_数据_14

班级表中的学生人数字段确实从 0 变成了 1,所以说增加数据的触发器测试通过。

接下来我们测试一下删除数据的触发器,我们就把之前增加的学生数据删掉,看看班级表中对应的学生人数字段是不是又变回到 0,删除之后刷新数据库的结果如图所示。

基于 Django 信号机制实现类似触发器的效果_数据_15

班级表中的学生人数字段确实从 1 变成了 0,所以说删除数据的触发器测试通过。


总结



不管是增加数据的触发器还是删除数据的触发器,我们都可以通过 Django 信号机制来实现类似的效果,比在数据库中定义触发器简单太多了,而且大幅度地降低了数据库的维护成本。

今天的文章有不懂的可以后台回复“加群”,备注:小陈学Python,不备注可是会被拒绝的哦~!



基于 Django 信号机制实现类似触发器的效果_django_16

基于 Django 信号机制实现类似触发器的效果_django_17





基于 Django 信号机制实现类似触发器的效果_触发器_18