05 Django 模型进阶

一、配置 MySQL

安装 MySQL

安装 MySQL 驱动

使用 mysqlclient

pip install mysqlclient

UbuntuLinux 系统中可能需要先安装 apt install libmysqld-dev

Django 中配置和使用 mysql 数据库

settings.py 中配置:

DATABASES = {
	"default": {
		"ENGINE": "django.db.backends.mysql",
		"NAME": "mydb",
		"UESR": "root",
		"PASSWORD": "123456",
		"HOST": "127.0.0.1",
		"PORT": "3306",
	}
}

二、多模块关联关系

多个模块(型)关联 的分类:

一对多关系

举例说明(一对一,多对多类似)。一个班级可以有多个学生,一个学生只能属于一个班级

class Grade(models.Model):
	name = models.CharField(max_length=20)

class Student(models.Model):
	name = models.CharField(max_length=20)
	grade = models.ForeignKey(Grade)

对象使用说明:

正向(在 Student 这边,即有 grade 属性的这边): 一对多中的多端

反向(在 Grade 这边):一对多中的一端

filter()get() 等操作中的使用:

正向(在 Student 这边,即有 grade 属性的这边):

Student.objects.filter(属性__name="1"),如:Student.objects.filter(grade__name="1")

反向(在 Grade 这边):

Grade.objects.filter(类名小写__id=7),如:Grade.objects.filter(student__id=7)

三、Model 连表结构

一对多: models.ForeignKey(其他表)
多对多: models.ManyToManyField(其他表)
一对一: models.OneToOneField(其他表)

应用场景

一对多。当一张表中创建一行数据时,有一个单选的下拉框(可以被重置选择)

例如:创建用户信息时,需要选择一个用户类型(普通用户,金牌用户,铂金用户)。

多对多。在某表中创建一行数据时,有一个可以多选的下拉框(猫眼,淘票票,格拉瓦电影

例如:创建用户信息时,需要为用户指定多个爱好

一对一。在某表中创建一行数据时,有一个单选的下拉框(下拉框中的内容被用过一次就消失了)

例如:有个身份证表,有个 person 表。每个人只能有一张身份证,一张身份证也只能对应一个人

四、多模型增删改查

1. 一对多

有以下两个模型,用户模型和用户类型模型。

一个用户只能有一个用户类型,而多个用户可以是同一个用户类型。也就是一个用户类型对多个用户,是 1 对 多 关系。外键在多端(用户模型)中指定

# models.py 
class UserType(models.Model):
	name = models.CharField(max_length=30)


class User(models.Model):
	name = models.CharField(max_length=30)
	age = models.IntegerField(default=18)

	# 外键
	user_type = models.ForeignKey(to=UserType, on_delete=models.CASCADE)


user = User.objects.get(id=2)

# 使用 . 可以取出 user 对象中的所有属性(包括那个外键 user_type)及`外键id`(user_type_id)
user.name
user.age
user.user_type
# 上面三个很好理解
user.user_type_id  # 这个也不难,django 直接给的,拿来用就行

user.user_type.name
user.user_type.id
# 上面两个也不难理解。user.user_type 其实就是 UserType 的实列,换句话说就是,user.user_type 是 UserType 的一个对象。那么,取出 user.user_type 的属性就是 user.user_type.name, user.user_type.id


utype = UserType.objects.get(id=2)

utype.id
utype.name
# 上面两个很好理解呀

外键使用 models.ForeignKey() 指定,第一个参数 to 是指外键是谁(一对多中一端的类名),第二个参数 on_delete 是删除数据方式。

on_delete 确定删除数据方式。因为两个表关联,任意一张表删除一条数据(记录),另一张关联的表中对应的数据是删还是不删?所以,需要 on_delete 来确定。

on_delete 的值

多模型增删改查之增

一对多关系的增加数据操作,必需先增加一端表的数据。

# views.py
# 给 UserType 添加数据
def add_user_type(request):

	user_types = ["青铜", "白银", "黄金", "钻石", "大师", "王者"]
	for name in user_types:
		UserType.objects.create(name=name)

	return HttpResponse("用户类型添加成功!")


# 给 User 添加数据
def add_user(request):
	for i in range(11, 30):
		User.objects.create(name=f"张三-{i}", 
							age=i, 
							user_type_id=i % 6 + 1)  # 1

def add_user_2(request):
	for i in range(11, 30):
		User.objects.create(name=f"李四-{i}",
							age=i+100, 
							user_type=UserType.objects.get(pk=i % 6 + 1))  # 2

对应路由不写了

先执行 add_user_type 对应的路由,再执行 add_useradd_user_2 对应的路由。

代码中 # 1# 2 的区别

user_type_iduser_type。定义 User 模型时(就是定义在 modelsl.py 中的 User 类),除了有 nameage 两个属性外,还有一个外键。所以,创建 User 对象时(呃~, 就是创建 User 表中的数据时),除了要给两个属性的值,还要给外键赋值(这也是为什么要先创建 UserType 表的原因,没有这个表,这个外键指向谁?)。

user_type_id赋的值是 UserType 表中一条记录(就是一行数据)的 id 字段(定义 UserType 类时,虽然没有写 id 属性,但 django 默认自动添加)的值,是一个整数,这里是 i % 6 + 1

user_type 赋的值是 UserType 表中的一条记录。获得一条记录的方法有多种,但结果必需是唯一,不然一个用户多个用户类型就不合适了吧。这里用的是 UserType.objects.get(pk=i % 6 + 1)

关于那个 6,是一端表中的数据个数。外键是要指向一端表中的一条记录,所以值不能大于这个个数。能不能小于?还没试过。

为什么要加 1 ? UserType 表中有 6 条数据,id 列从 16 。模 6 运算的结果是 05,所以要加 1

多模型增删改查之删

删除数据要注意定义模型时多端表中外键的 on_delete 参数的值,一般设为 models.PROTECT

多模型增删改查之改

没什么说的

多模型增删改查之查

正向查询,使用多端表(或定义外键的表)进行查询的方式称为正向查询。因为多端表中定义的外键指向了一端表,所以正向查询很简单。

user = User.objects.get(id=2)  # .get() 的结果只能是一个值
users = User.objects.filter(age=100)
users = User.obejcts.filter(name="张三")  # .filetr() 的结果是一个集合
# 用除外键的类属性进行查询

正向查询还可以使用 外键__外键属性名 的方式进行查找。

给定用户类型比如 钻石,查找属于该用户类型的所有用户。User 中定义的外键是 UserType,那么可以使用这种方式查找:User.objects.filter(user_type),呃~, user_type 该等于啥? user_type 是个 UserType 类,不能直接等于其属性名的值(用户类型:钻石),肿么办?Django 给了双下划线(__)运算。user_type 后跟 __ 可以直接指向 UserType 的属性,即 User.objects.filter(user_type__name="钻石") 即可得到所有 钻石 级的用户。

.filter() 外,.get() 等操作也可以使用 外键__外键属性名 进行查询。

反向查询,使用一端表(或没有定义外键的表)进行查询的方式称为反向查询。从一对多关联的两个表中的模型中看,因为一端表的定义模型中仅有自己的属性,所以不能查到关联的多端表中的数据,比如给定一个用户类型,使用一端表查找所有是该用户类型的所有用户,找不了一点。很幸运,Django 会帮我们完成。

当两个表建立一对多关联时,Django 会在一端建立一个叫做 多端类名全小写_set反向多对一关联管理器对象,使用这个管理器对象可以反向查到指向该一端数据的一对多关联表多端数据。

utype = UserType.objects.get(id=2)

type(utype.user_set)
# <class 
# 'django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.
# <locals>.RelatedManager'>
# 就是 reverse_many_to_one_manager RelatedManager

utype.user_set.all() 
# 这就是个 QuerySet 了。

还是以 UserUserType 这两模型为例,解释一下 user_set。其实 user_set 这个反向多对一关联管理器与 objects 这个管理器是一样的,只不过 objects 管理器是当前模型的对象管理器,指向所有当前模型的对象,可以用 all()get()filter()查找符合条件的结果集;而 user_set 管理器是与当前模型关联的多端模型的对象管理器,指向与当前模型关联的模型的所有对象,同样也可以用all()get()filter()查找符合条件的结果集。

Pay me Attention

我要变形了。 上面代码中,是先得到了 UserTpye 的对象 utype,再用的 utype.user_set,相当于 Djangoutype 加了个 user_set 属性,而 UserType 类没有 user_set 属性,所以 不能使用 UserType.user_set 进行查询

反向多对一关联管理器对象(多端类名全小写_set)可以在定义外键时使用用 related_name 参数指定,如 user_type = models.ForeignKey(to=UserType, on_delete=models.PROTECT, related_name="users")。此时,user_set,不能使用,即 utype.user_set.all()不能使用, 只能使用 users 进行反向查询,即 utype.users.all()

反向查询中的双下划线(__)运算

正向查询中,因为 User 模型中定义的外键指向了 UserType,所以可以使用 User.objects.filter(user_type__name="钻石") 进行查询。而反向查询时,UserType 模型却没有指向 User 的键。如果给的是 User 的属性,需要在 UserType 表中进行查询 User 对象,该怎么查?

比如:查询年龄是 1000 岁的用户的用户类型是什么 ?

当然,先把 1000 岁的所有用户都找到,再取出这些用户的数据类型并去重也可以实现,但太麻烦了。

users = User.object.filter(age=1000)
utype_names = {}  # 字典可以完成去重操作
for user in users:
	utpyes.append(user.user_type.name)


for name in type_names:
	print(utype.name)

同样 Django 也帮我们实现了简单的查询方式。可以使用 类名全小写__类属性 的方式进行查询

utypes = UserType.objects.filter(user__age=100)

for utype in utypes
	print(utype.name)

这种方式进行反向查询时,定义外键时,不能指定 related_name 关键字。

2. 多对多

针对多对多关系,django 会自动创建第三张表(中间表),也可以通过 through 参数指定第三张表。

用户和组是典型的多对多关系

class Group(models.Model):
	name = models.CharField(max_length=20)

class User(models.Model):
	name = models.CharField(max_length=64)
	password = models.CharField(max_length=100)

	groups = models.ManyToMany(Group)  # 没有 on_delete 参数

	def __str__(self):
		return self.name

增删改查之

先分别创建 user 和 group,再使用 add 关联

u = User(name='aa', password='123')
s.save()

g = Group(name="g5")
g.save()

# 将 aa 加入到 g5 组中。正向操作,在有外键的表中操作。 
u.groups.add(g)  
# 将 aa 加入到 g5 组的用户集合中。向向操作,在无外键的表中操作 。
g.user_set.add(u)  # 通过 Manager 对象使用 add() 方法关联

增删改查之

和一对多类似,删除 usergroup 会级联删除 user_groups 表(第三张表或叫中间表)中的关联数据。

# 删除 id=9 的用户
User.objects.filter(id=9).delete()

# 删除 id=9 的电影
Movie.objects.filter(id=9).delete()

# 删除中间表
user = User.objects.get(name="张三")
user.movies.filter(name="阿凡达3").delete()  # 将 电影阿凡达3 从 张三 的收藏电影集合中删除,张三与阿凡达的对应关系随之删除,中间表中就没有这两者之间的记录

增删改查之

和一对多类似,只修改当前表

增删改查之

正向:查询 id=2 的用户所在的所有组

u = User.objects.get(id=2)

u.groups.all()

反向:查询 id=1 的组中包含的所有用户

g = Group.objects.get(id=1)
g.user_set.all()

3. 一对一关联

一对一不是数据库的一个连表操作,而是 Django 独有的连表操作。一对一关系相当于是特殊的一对多关系,只是相当于加入了 unique=Ture

典型一对一关系:人与身份证

一个人只能有一张身份证,一张身份证对应一个人。

class IDCard(models.Model):
	idnum = models.IntegerField()

	def __str__(self):
		return str(self.idnum)

class Person(models.Model):
	name = models.CharField(max_length=20)
	age = models.IntegerField(default=18)  
	sex = models.BooleanField(default=True)

	# 一对一关系
	idcard = models.OneToOneField(IDCard)

	def __str__(self):
		return self.name

增删改查之增删改

与一对多是类似的,略

增删改查之

与一对多关系类似,但有一个小小的区别。

查找某个用户的身份证信息

person = Person.objects.get(pk=1)
person.idcard

因为 idcardperson 的类属性,又因为是 一对一关系,所以 idcard 是一个对象,而不是对象集合,所以找到了,泰裤辣!!!!找到了一个确定的 IDCard 类对象,就可以用 . 操作取出该对象的所有属性。

person.idcard.idnum

查找身份证对应的用户

idcard = IDCard.objects.get(pk=1)
idcard.person

idcard.person 中的 person 就像是一对多关系中反向查找的 反向多对一关联管理器对象多端类名全小写_set)。 不过,这里是一对一关系,所以最后的查询结果是一个确定的 Person 类对象,所以,又可以用. 操作取出这个 Person 类对象的所有属性。

idcard.person.name
idcard.person.age
idcard.person.sex

一对一关系查询与一对多关系查询的不同之处就是反向查询时的那个 关联管理器对象

一对多关系反向查询时的关联管理器对象是 多端类名全小写_set (换句话说是 有外键类的类名全小写_set),之所以加了个 _set,是因为一对多关系反向查询出来的结果是个对象集合;而一对一关系反向查询时的关联管理器对象是 有外键类的类名全小写 ,没有加 _set,是因为一对一关系反向查询出来的结果是一个确定的对象