一、写这篇博客的目的
主要记录:
- 如何通过django去创建【多对多】的表关系;
- 以及如何通过【多对多】的表关系去走对应的完整业务操作;
- 通过修改django里的哪些代码配置,使当一张【多】表的数据被删除后,对应另外一张【多】表的数据和中间表的数据会有对应的哪些变化?
二、【多对多】表关系对应的业务例子
我们在现实生活里会遇到很多多对多的场景。
比如一个最常见的例子是:一本书可以有多个作者,一个作者也可以写多本书,书和作者就是多对多的关系;
一般会建三张数据表,一张表放书的信息,一张表放作者的信息,一张中间表一般有三个表字段且三个表字段分别存放书的主键值和作者的主键值和中间表本身的主键值;
完整操作流程可以看接下来的内容;
三、完整操作流程
1、第一步:在【helloworld/hello/models.py】里新增模型类
from django.db import models
# 作者
class Auther(models.Model):
name = models.CharField(max_length=10, verbose_name="作者")
mail = models.CharField(max_length=30, verbose_name="邮箱")
city = models.CharField(max_length=10, verbose_name="城市")
class Meta:
verbose_name_plural = '作者'
def __str__(self):
return self.name
class Book(models.Model):
book_name = models.CharField(max_length=50, verbose_name="书名")
auther_of_be_associated = models.ManyToManyField(to=Auther)
class Meta:
verbose_name_plural = '书籍'
def __str__(self):
return self.book_name
2、第二步:在【helloworld/hello/admin.py】里注册模型类
from django.contrib import admin
# Register your models here.
from hello import models
admin.site.site_title = "系统后台"
admin.site.site_header = "lucas的项目管理系统"
admin.site.index_title = "后台主页"
# 类名可以随意
class ControlPerson(admin.ModelAdmin):
# 自定义hello_person表在admin管理后台的数据列表展示页面里展示哪几个表字段内容,需要重写属性list_display
list_display = ('id', "name", "age") # 重写属性list_display,来设置展示的表字段
search_fields = ("name",) # 重写属性search_fields,把表字段name的值当做搜索条件
admin.site.register(models.Person, ControlPerson)
admin.site.register(models.User)
class ControllerRole(admin.ModelAdmin):
list_display = ('role_name', 'role_level', 'role_intro')
search_fields = ('role_name',)
admin.site.register(models.Role, ControllerRole)
class ControllerArticle(admin.ModelAdmin):
list_display = ("title", "author", "body", "create_time", "update_time")
# 细节:如果该search_fields里只有一个值,一定要加个逗号【,】,否则系统会报错会认为search_fields的值是一个字符串而不是一个元祖;
search_fields = ("title",)
admin.site.register(models.Article, ControllerArticle)
class ControlAnimal(admin.ModelAdmin):
list_display = ("id", "name", "age", "create_time")
list_display_links = ("id", "name",)
list_filter = ("name", "create_time")
list_per_page = 11
list_editable = ("age",) # 注意:表字段id因为是主键且由于主键不允许被编辑,所以表字段id不能添加到 list_editable里面,否则会报错;
search_fields = ("name", "age")
date_hierarchy = "create_time"
ordering = ("-create_time",)
admin.site.register(models.Animal, ControlAnimal)
# 银行信息
class ControlBank(admin.ModelAdmin):
list_display = ["id", "bank_name", "city", "point"]
# 纵向展示
# class MoreInfo(admin.StackedInline):
# model = models.Card_of_Expand_information
# 横向展示
class MoreInfo(admin.TabularInline):
model = models.Card_of_Expand_information
# 银行卡信息
class ControlCardInfo(admin.ModelAdmin):
list_display = ["id", "card_id", "card_name", "card_info"]
# 在银行卡基本信息详情页面里显示银行卡拓展信息
inlines = [MoreInfo]
admin.site.register(models.Bank, ControlBank)
admin.site.register(models.CardInfo, ControlCardInfo)
# 作者信息
class ControlAuthor(admin.ModelAdmin):
list_display = ["id", "name", "city", "mail"]
admin.site.register(models.Auther, ControlAuthor)
# 书信息
class ControlBook(admin.ModelAdmin):
# 属性list_display里的索引值为1对应的【'AssociatedAuthor'】,这是我们自定义的要展示在列表页面的列表字段(该列表字段不是表里的表字段),且这必须是这个类ControlBook里的我们手工创建的方法名【被关联的作者】;
list_display = ["book_name", 'AssociatedAuthor']
# 第一步:定义一个方法(方法名任意,方法名为中文也可以只是不规范),比如方法名为【AssociatedAuthor】;
# 第二步:在方法【被关联的作者】里,首先调用模型类Book提供的方法【all()】即通过【obj.auther_of_selected.all()】,获取到book表里的一条书籍数据关联的auther表里的0到多条的完整的作者数据(每条作者数据都包含了每个表字段和每个表字段对应的表字段值);
# 细节:【obj.auther_of_selected.all()】里的【auther_of_selected】是模型类Book里的一个属性且这个属性的属性值必须是类ManyToManyField被实例化后生成的对象;
# 第三步:在方法【被关联的作者】里,接着用for遍历获取每条作者数据里的表字段name的表字段值并用list类提供的append方法用一个空列表来存储这些表字段值,然后返回这个列表;
def AssociatedAuthor(self, obj):
# 第一种写法,官方推荐写法,语法最简洁,因为只需要一行代码;
# return [a.name for a in obj.auther_of_selected.all()]
# 第二种写法,个人根据第一种写法写的比较简单易懂的写法,语法不简洁,因为需要多行代码;
emptyList = []
for a in obj.auther_of_be_associated.all():
emptyList.append(a.name)
return emptyList
# list_display = ["book_name",'show_all_auther_of_be_associated' ]
# def show_all_auther_of_be_associated(self,obj):
# # return [a.name for a in obj.auther_of_selected.all()]
# emptyList = []
# for a in obj.auther_of_be_associated.all():
# emptyList.append(a.name)
# return emptyList
admin.site.register(models.Book, ControlBook)
3、第三步:在【helloworld/】根目录下执行生成数据表的相关语句
执行的相关语句和顺序是:
a、首先,先执行语句【python manage.py makemigrations】
b、最后,再执行语句【python manage.py migrate】
相关结果如下:
会生成三张对应的数据表,其中会包含一张中间表;
4、第四步:重启服务
5、第五步:成功登陆admin管理后台
6、第六步:在后台新增2个作者信息
7、第七步:在后台新增1本书籍信息
四、相关知识点
1、类ManyToManyField里的一个必填参数对应入参值的简单分析
细节:
从类ManyToManyField的类名,可以直观知道单词ManyToManyField的中文含义是【多对多】;
1.1、第一步:看类ManyToManyField源码里的【__init__】方法里的内容
class ManyToManyField(RelatedField):
"""
Provide a many-to-many relation by using an intermediary model that
holds two ForeignKey fields pointed at the two sides of the relation.
Unless a ``through`` model was provided, ManyToManyField will use the
create_many_to_many_intermediary_model factory to automatically generate
the intermediary model.
"""
# Field flags
many_to_many = True
many_to_one = False
one_to_many = False
one_to_one = False
rel_class = ManyToManyRel
description = _("Many-to-many relationship")
def __init__(self, to, related_name=None, related_query_name=None,
limit_choices_to=None, symmetrical=None, through=None,
through_fields=None, db_constraint=True, db_table=None,
swappable=True, **kwargs):
try:
to._meta
except AttributeError:
assert isinstance(to, str), (
"%s(%r) is invalid. First parameter to ManyToManyField must be "
"either a model, a model name, or the string %r" %
(self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
)
if symmetrical is None:
symmetrical = (to == RECURSIVE_RELATIONSHIP_CONSTANT)
if through is not None:
assert db_table is None, (
"Cannot specify a db_table if an intermediary model is used."
)
kwargs['rel'] = self.rel_class(
self, to,
related_name=related_name,
related_query_name=related_query_name,
limit_choices_to=limit_choices_to,
symmetrical=symmetrical,
through=through,
through_fields=through_fields,
db_constraint=db_constraint,
)
self.has_null_arg = 'null' in kwargs
super().__init__(**kwargs)
self.db_table = db_table
self.swappable = swappable
截取这部分源码:
def __init__(self, to, related_name=None, related_query_name=None,
limit_choices_to=None, symmetrical=None, through=None,
through_fields=None, db_constraint=True, db_table=None,
swappable=True, **kwargs):
从这部分源码可以简单看出来:类ManyToManyField被实例化时,必须给一个必填参数【to】赋值,其他入参的入参值一般情况下保持默认值即可;
1.2、第二步:结合实际业务代码,对必填参数【to】的参数值做分析
实际业务代码如下:
# 作者
class Auther(models.Model):
name = models.CharField(max_length=10, verbose_name="作者")
mail = models.CharField(max_length=30, verbose_name="邮箱")
city = models.CharField(max_length=10, verbose_name="城市")
class Meta:
verbose_name_plural = '作者'
def __str__(self):
return self.name
# 书籍
class Book(models.Model):
book_name = models.CharField(max_length=50, verbose_name="书名")
auther_of_be_associated = models.ManyToManyField(to=Auther)
class Meta:
verbose_name_plural = '书籍'
def __str__(self):
return self.book_name
截取这部分业务代码:
auther_of_be_associated = models.ManyToManyField(to=Auther)
1.3、对必填参数【to】的参数值的理解
参数【to】的含义是:关联到对应的一张表(这张表在多对多表关系里是两张表里被关联的那张表);
参数【to】的参数值:必须只能是这个【多对多表关系里的】两个模型类里的其中一个被关联的模型类的模型类名;
2、当一张【多】表的数据被删除后,对应另外一张【多】表的数据和中间表的数据会有对应的哪些变化的操作记录
假设作者表现在有这样两条数据(我们把这两条数据分别命名为数据A和数据B,方便后续相关文案的描述):
假设书籍表现在有这样两条数据(我们把这两条数据分别命名为数据C和数据D,方便后续相关文案的描述):
假设作者表和书籍表共同对应的中间表现在有这样四条数据(我们把这四条数据分别命名为数据E1和数据E2和数据E3和数据E4,方便后续相关文案的描述):
细节:
中间表这四条数据的含义是:书籍1的作者是:作者1和作者2、书籍2的作者是:作者1和作者2;
数据都造好了,接着我们验证这些删除场景(我都已在本地调试通过):
a、当从admin管理后台,从作者列表里删除了【作者1】,那么:作者表里的数据A会被删掉、中间表里的数据E1和数据E3会被删掉;
b、当从admin管理后台,从作者列表里删除了【书籍1】,那么:书籍表里的数据C会被删掉、中间表里的数据E1和数据E2会被删掉;