一、写这篇博客的目的
主要记录:
- 如何通过django去创建【一对多】的表关系;
- 以及如何通过【一对多】的表关系去走对应的完整业务操作;
- 通过修改django里的哪些代码配置,使当【一】表的数据被删除后,对应【多】表的数据会有对应的哪些变化?
假设有一张数据表A和一张数据表B,那么表A和表B只可能存在这样的四种表关系的其中一种:
- 表A,和表B,是独立的;
- 表A,和表B,是一对多关系,表B里有个表字段的值存储的是表A的主键值;
- 表A,和表B,是一对一关系,表B里有个表字段的值存储的是表A的主键值;
- 表A,和表B,是多对多关系,有个中间表的两个表字段分别存储表A的主键值和表B的主键值;
二、【一对多】表关系对应的业务例子
我们在现实生活里会遇到很多一对多的场景。
比如一个最常见的例子是:我们填写银行卡信息的时候,会从提交字段【银行】的列表下拉框里选择对应的银行信息;
一般会建两张数据表,一张表放银行的信息,一张表放银行卡的信息;
每个银行可以对应多张银行卡,每张银行卡只能属于一家银行;
那么银行和银行卡就是一对多的关系,反之,银行卡和银行就是多对一的关系;
完整操作流程可以看接下来的内容;
三、完整操作流程
1、第一步:在【helloworld/hello/models.py】里新增模型类
class Bank(models.Model):
# 银行信息
bank_name = models.CharField(max_length=50, verbose_name="银行名称")
city = models.CharField(max_length=30, verbose_name="所在城市")
point = models.CharField(max_length=60, verbose_name="所在网点")
class Meta:
verbose_name_plural = "银行"
def __str__(self):
return self.bank_name
class CardInfo(models.Model):
# 银行卡信息
card_id = models.CharField(max_length=30, verbose_name="卡号")
card_name = models.CharField(max_length=10, verbose_name="姓名")
card_info = models.ForeignKey(to=Bank, on_delete=models.CASCADE, verbose_name="选择银行")
class Meta:
verbose_name_plural = '银行卡信息'
def __str__(self):
return self.card_id
2、第二步:在【helloworld/hello/admin.py】里注册模型类
# 银行信息
class ControlBank(admin.ModelAdmin):
list_display = ["id", "bank_name", "city", "point"]
# 银行卡信息
class ControlCardInfo(admin.ModelAdmin):
list_display = ["id", "card_id", "card_name", "card_info"]
admin.site.register(models.Bank, ControlBank)
admin.site.register(models.CardInfo, ControlCardInfo)
3、第三步:在【helloworld/】根目录下执行生成数据表的相关语句
执行的相关语句和顺序是:
3.1、首先,先执行语句【python manage.py makemigrations】
3.2、最后,再执行语句【python manage.py migrate】
相关结果如下:
会生成两张对应的数据表
4、第四步:重启服务
5、第五步:成功登陆admin管理后台
6、第六步:在后台新增2个银行信息
7、第七步:在后台新增1个银行卡信息
四、相关知识点
1、类ForeignKey里的两个必填参数对应入参值的简单分析
细节:
从类ForeignKey的类名,可以直观知道单词ForeignKey的中文含义是【外键】;
1.1、第一步:看类ForeignKey源码里的【__init__】方法里的内容
class ForeignKey(ForeignObject):
"""
Provide a many-to-one relation by adding a column to the local model
to hold the remote value.
By default ForeignKey will target the pk of the remote model but this
behavior can be changed by using the ``to_field`` argument.
"""
descriptor_class = ForeignKeyDeferredAttribute
# Field flags
many_to_many = False
many_to_one = True
one_to_many = False
one_to_one = False
rel_class = ManyToOneRel
empty_strings_allowed = False
default_error_messages = {
'invalid': _('%(model)s instance with %(field)s %(value)r does not exist.')
}
description = _("Foreign Key (type determined by related field)")
def __init__(self, to, on_delete, related_name=None, related_query_name=None,
limit_choices_to=None, parent_link=False, to_field=None,
db_constraint=True, **kwargs):
try:
to._meta.model_name
except AttributeError:
assert isinstance(to, str), (
"%s(%r) is invalid. First parameter to ForeignKey must be "
"either a model, a model name, or the string %r" % (
self.__class__.__name__, to,
RECURSIVE_RELATIONSHIP_CONSTANT,
)
)
else:
# For backwards compatibility purposes, we need to *try* and set
# the to_field during FK construction. It won't be guaranteed to
# be correct until contribute_to_class is called. Refs #12190.
to_field = to_field or (to._meta.pk and to._meta.pk.name)
if not callable(on_delete):
raise TypeError('on_delete must be callable.')
kwargs['rel'] = self.rel_class(
self, to, to_field,
related_name=related_name,
related_query_name=related_query_name,
limit_choices_to=limit_choices_to,
parent_link=parent_link,
on_delete=on_delete,
)
kwargs.setdefault('db_index', True)
super().__init__(
to,
on_delete,
from_fields=[RECURSIVE_RELATIONSHIP_CONSTANT],
to_fields=[to_field],
**kwargs,
)
self.db_constraint = db_constraint
截取这部分源码:
def __init__(self, to, on_delete, related_name=None, related_query_name=None,
limit_choices_to=None, parent_link=False, to_field=None,
db_constraint=True, **kwargs):
从这部分源码可以简单看出来:类ForeignKey被实例化时,必须给两个必填参数【to】和【on_delete】分别赋值,其他入参的入参值一般情况下保持默认值即可;
2、第二步:结合实际业务代码,对必填参数【to】的参数值和必填参数【on_delete】的参数值做分析
实际业务代码如下:
class Bank(models.Model):
'''银行信息'''
bank_name = models.CharField(max_length=50,verbose_name="银行名称")
city = models.CharField(max_length=30,verbose_name="所在城市")
point = models.CharField(max_length=60,verbose_name="所在网点")
class Meta:
verbose_name_plural = "银行"
def __str__(self):
return self.bank_name
class CardInfo(models.Model):
'''银行卡信息'''
card_id = models.CharField(max_length=30, verbose_name="卡号")
card_name = models.CharField(max_length=10, verbose_name="姓名")
card_info = models.ForeignKey(to=Bank,on_delete=models.CASCADE,verbose_name="选择银行",)
class Meta:
verbose_name_plural = '银行卡信息'
def __str__(self):
return self.card_id
截取这部分业务代码:
card_info = models.ForeignKey(to=Bank,on_delete=models.CASCADE,verbose_name="选择银行",)
3、对必填参数【to】的参数值的理解
参数【to】的含义是:关联到对应的一张表(这张表在一对多表关系里是为一的表);
参数【to】的参数值:必须只能是这个【一对多表关系里的为多的】模型类对应的【一对多表关系里的为一的】模型类的模型类名;
4、对必填参数【on_delete】的参数值的理解
参数【on_delete】的含义是:通过类ForeignKey关联起来的表关系为一对多的两张表,当为一的表的表数据被物理删除后,在为多的表的表里跟这些被物理删除的表数据关联的表数据会发生对应的变化;
参数【on_delete】的参数值必须只能是这6种参数值里的其中一种:
models.CASCADE
models.PROTECT
models.SET_NULL
models.SET_DEFAULT
moels.SET(value)
models.DO_NOTHING
细节:
- 当参数【on_delete】的值为【models.SET_NULL】:类ForeignKey里的入参null的入参值必须设置为【True】;
- 当参数【on_delete】的值为【models.SET_DEFAULT】:类ForeignKey里的入参default的入参值必填且入参值只能为正整数;
- 当参数【on_delete】的值为【models.SET(value)】:方法set()里的入参value的入参值必填且入参值只能为正整数;
- 参数【on_delete】的值为【models.CASCADE】,是常被使用的场景;
假设bank表现在有这样一条数据(我们把这条数据命名为数据A,方便后续相关文案的描述):
假设cardinfo表现在有这样两条数据(我们把这两条数据分别命名为数据B和数据C,方便后续相关文案的描述):
每种参数值的具体作用分别如下:
models.CASCADE:在admin管理后台,当数据A被物理删除后,跟数据A有关联的数据B和数据C也都会被物理删除;
models.PROTECT:在admin管理后台,当数据A要被物理删除时会提示先物理删除数据B和数据C后才能物理删除数据A,接着当物理删除数据B和数据C成功后再尝试物理删除数据A时就能马上成功物理删除数据A;
models.SET_NULL:在admin管理后台,当数据A被物理删除后,跟数据A有关联的数据B和数据C的表字段【card_info_id】的值都会由【25】变为【Null】;
models.SET_DEFAULT:在admin管理后台,当数据A被物理删除后,跟数据A有关联的数据B和数据C的表字段【card_info_id】的值都会由【25】变为类ForeignKey里的入参default的入参值(入参default的入参值只能为正整数);
moels.SET:在admin管理后台,当数据A被物理删除后,跟数据A有关联的数据B和数据C的表字段【card_info_id】的值都会由【25】变为方法set()里的入参value的入参值(入参value的入参值只能为正整数);
models.DO_NOTHING:在admin管理后台,当数据A被物理删除后,跟数据A有关联的数据B和数据C的每个表字段的表字段值都保持不变(即数据A被物理删除后,数据B和数据C完全不受影响);
这6种参数值对应的具体业务代码如下(我都已在本地调试通过):
card_info = models.ForeignKey(to=Bank,on_delete=models.CASCADE,verbose_name="选择银行",)
card_info = models.ForeignKey(to=Bank,on_delete=models.PROTECT,verbose_name="选择银行",)
card_info = models.ForeignKey(to=Bank,on_delete=models.SET_NULL,verbose_name="选择银行",null=True)
card_info = models.ForeignKey(to=Bank,on_delete=models.SET_DEFAULT,verbose_name="选择银行",default=999999999)
card_info = models.ForeignKey(to=Bank,on_delete=models.SET(value=7777777),verbose_name="选择银行",)
card_info = models.ForeignKey(to=Bank,on_delete=models.DO_NOTHING,verbose_name="选择银行",)