Django ORM框架中的表关系
为了说清楚问题,我们设计一个crm系统,包含五张表:
1.tb_student 学生表
2.tb_student_detail 学生详情表
3.tb_salesman 课程顾问表
4.tb_course 课程表
5.tb_entry 报名表
表关系和字段如下图:
接下来,根据这几个表我们来看在django中如何编写对应的模型,以及在数据库层面的处理。
多对一
在django中要表达多对一的关系需要使用django.db.models.ForeignKeyField
字段。上图中,报名表和学生表,课程表,课程顾问表是多对一的关系,模型代码如下:
from django.db import models
class Student(models.Model): # 必须继承
name = models.CharField('姓名', max_length=20, help_text='姓名')
age = models.SmallIntegerField('年龄', null=True, blank=True, help_text='年龄')
sex = models.SmallIntegerField('性别', default=1, help_text='性别')
qq = models.CharField('qq号码', max_length=20, null=True, blank=True, unique=True, help_text='qq号码')
phone = models.CharField('手机号码', max_length=20, null=True, blank=True, unique=True, help_text='手机号码')
c_time = models.DateTimeField('创建时间', auto_now_add=True)
def __str__(self):
return self.name
class Meta:
db_table = 'tb_student' # 设置创建表示的表名
verbose_name = '学生信息'
verbose_name_plural = verbose_name # django admin中显示模型的说明
class Salesman(models.Model):
# GroupChoice = [
# ('电销', '电销'),
# ('网销', '网销'),
# ('班主任', '班主任'),
# ]
class GroupChoice(models.TextChoices):
A = '电销', '电销'
B = '网销', '网销'
C = '班主任', '班主任'
name = models.CharField('姓名', max_length=24, help_text='姓名')
age = models.SmallIntegerField('年龄', null=True, blank=True, help_text='年龄')
sex = models.SmallIntegerField('性别', default=1, help_text='性别')
group = models.CharField('销售组', help_text='销售组', max_length=24, choices=GroupChoice.choices, default=GroupChoice.A )
# group = models.CharField('销售组', help_text='销售组', max_length=24, choices=GroupChoice, default='电销')
def __str__(self):
return self.name
class Meta:
db_table = 'tb_salesman'
verbose_name = '课程顾问表'
verbose_name_plural = verbose_name
class Course(models.Model):
name = models.CharField('课程名称', max_length=24, help_text='课程名称', unique=True)
price = models.IntegerField('价格', help_text='课程价格')
period = models.SmallIntegerField('课时', help_text='课时,以小时为单位')
def __str__(self):
return self.name
class Meta:
db_table = 'tb_course'
verbose_name = '课程表'
verbose_name_plural = verbose_name
class Entry(models.Model):
student = models.ForeignKey(Student, verbose_name='学生', help_text='报名学生', on_delete=models.PROTECT)
salesman = models.ForeignKey('Salesman', verbose_name='课程顾问', help_text='课程顾问', on_delete=models.PROTECT)
course = models.ForeignKey(Course, verbose_name='课程', help_text='报名课程', on_delete=models.PROTECT, db_constraint=False)
c_time = models.DateTimeField('报名时间', auto_now_add=True, help_text='报名时间')
def __str__(self):
return '{}-{}'.format(self.student.name, self.salesman.name)
class Meta:
db_table = 'tb_entry'
verbose_name = '报名表'
verbose_name_plural = verbose_name
定义ForeignKeyField
字段时有如下注意事项
- 一般外键字段定义在多的一方
- 外键字段的第一个参数是一个位置参数,就是要关联的模型,可以是模型类本身,也可是字符串形式的导入路径(当引用其他应用的模型,和引入后定义的模型时很有用)
- 在数据库层面,django会在字段名的后面附件
_id
来创建数据库列名。例如上面例子中的Entry
模型的数据库表将有一个student_id
列,然后为这个列创建一个外键约束,被引用的表为tb_student
,被引用的字段为id
.
- 注意:有时候为了效率,在数据库不会创建外键,而是通过代码逻辑来保证数据的完整性。在django中可以通过
ForeignKey
字段中指定db_constraint=False
来控制不创建外键约束。所以上图中没有course_id
的外键。
级联操作
当一个由ForeignKey
引用的对象被删除时,django将模拟on_delete
参数指定的SQl约束行为。
注意是模拟,在数据库层面创建的外键的级联操作是restrict
。
on_delete的可能值有:
- CASCADE
- 级联删除
- PROTECT
- 通过引发
ProtectedErro
防止删除被引用字段
- 通过引发
- RESTRICT
- 通过引发
RestrictErro
防止删除被引用字段
- 通过引发
- SET_NULL
- 设置外键为空,只有当null=true才可以
ForeignKey字段必须指定on_delete。
一对一
在django中要表达一对一的关系需要使用django.db.models.OneToOneField
字段,概念上类似于ForeignKey
与unique=True
的组合。
在crm中,学生详情表与学生表就是一个一对一的关系,创建模型如下:
class StudentDetail(models.Model):
STATION_CHOICES = [
('功能测试工程师', '功能测试工程师'),
('自动化测试工程师', '自动化测试工程师'),
('测试开发工程师', '测试开发工程师'),
('测试组长', '测试组长'),
('测试经理', '测试经理'),
]
class SalaryChoice(models.TextChoices):
FIRST = '5000以下', '5000以下'
SECOND = '5000-10000', '5000-10000'
THIRD = '10000-15000', '10000-15000'
FOURTH = '15000-20000', '15000-20000'
FIFTH = '20000以上', '20000以上'
student = models.OneToOneField(Student, verbose_name='学生', on_delete=models.CASCADE, help_text='学生')
city = models.CharField('所在城市', max_length=24, help_text='所在城市', null=True, blank=True)
company = models.CharField('任职公司', max_length=48, help_text='任职公司', null=True, blank=True)
station = models.CharField('岗位', max_length=24, help_text='岗位', choices=STATION_CHOICES, default='功能测试工程师' )
salary = models.CharField('薪资', max_length=24, help_text='薪资区间', choices=SalaryChoice.choices, default=SalaryChoice.FIRST)
def __str__(self):
return self.student.name
class Meta:
db_table = 'tb_student_detail'
verbose_name = '学生详情表'
verbose_name_plural = verbose_name
多对多
在django中要表达多对多的关系需要使用django.db.models.ManyToManyField
字段,例如Pizza
含有多种Topping(配料)
,一种配料也可能存在于多个pizza中,每个pizza含有多种topping的关系,可以用下面的模型来表示:
class Topping(models.Model):
name = models.CharField('名称', max_length=24)
class Pizza(models.Model):
name = models.CharField('名称', max_length=24)
toppings = models.ManyToManyField(Topping)
定义ManyToManyField
字段时有如下注意事项
- 建议设置多对多字段名为一个复数名词,表示所要管理的模型对象的集合。
- 多以多对多关联的两个模型,可以在任何一个模型中添加多对多字段,但是只能选择一个模型设置,即不能在两个模型里都添加。
- 一般来讲,应该把多对多字段放到需要在表单中编辑的对象里。跟业务相关,具体情况具体对待。
- 在数据库层面,django会自动创建一张中间表来表示多对多的关系。默认情况下,这个表名是使用多对多字段的名字和包含它的模型名生成(上面的例子,会生成
pizza_toppins
),然后包含两个字段,分别是以两个关系模型的名字和_id
组成(pizza_id,topping_id),并创建外键引用对应的表的id。
自定义中间表
当表示多对多关系的中间表需要包含其他字段的时候,需要自定义中间表,然后再定义多对多字段的时候,通过through
参数指定第三张表。
例如crm中的学生表和课程表的关系,通过报名表来表达,其中还包含了销售,创建时间字段。注意:创建学生,或者是创建课程的时候,都不需要去编辑彼此,这个时候建立多对多字段,主要是为了查询方便。然后通过课程查包名的学生表业务上可能用的更多,所以把多对多的字段定义在课程表中,代码如下:
class Course(models.Model):
name = models.CharField('课程名称', max_length=24, help_text='课程名称', unique=True)
price = models.IntegerField('价格', help_text='课程价格')
period = models.SmallIntegerField('课时', help_text='课时,以小时为单位')
students = models.ManyToManyField(Student, through='Entry', verbose_name='学生', help_text='包名课程的学生')
def __str__(self):
return self.name
class Meta:
db_table = 'tb_course'
verbose_name = '课程表'
verbose_name_plural = verbose_name
欢迎来到testingpai.com!
注册 关于