Django4.2 学习 - 模型进阶 Model(五)

1.mysql 的相关配置

1.1 驱动安装

1
pip install mysqlclient

1.2 修改 settings.py 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
#
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django7', # 数据库名称
'USER': 'root', # 数据库用户名
'PASSWORD': 'pico123456', # 数据库密码
'HOST': '127.0.0.1', # 数据库主机
'PORT': '3306', # 数据库端口
}
}

1.3 在 mysql 中创建相应的数据库

1
create database django7;

1.4 测试是否连接成功

  • 做一次数据迁移测试是否连接成功。
1
python manage.py makemigrations
1
python manage.py migrate
  • 成功在 mysql 数据库中表,则说明连接成功。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying sessions.0001_initial... OK

2. 多表之间的三种关系

2.1 一对一关系(1:1)

  • 一个用户只有一个身份证号,一个身份证号只对应一个用户

user表

userid name idcard
001 张三 101

身份证表

idcard address date
101 北京 2025-01-01

2.2 一对多(1:N)

  • 一个班有多一个学生
  • 一个学生只属于一个班
  • 注意外键只能写在数据多的一边

班级表

classid cname adress
111 一班 1 楼 1 教室
112 二班 2 楼 2 教室

学生表

stuid sname classid (外键)
1 张三 111
2 李四 111
3 王五 112
4 赵六 112

2.3 多对多 (N:M)

  • 1 个用户可以收藏多个电影
  • 1 个电影可以被多个用户收藏
  • 1:N :M:1

用户表

userid uname age
101 张三 33
102 李四 44
103 王五 55
104 赵六 66

收藏表(中间表)

collectid userid movieid
1 101 1001
2 101 1002
3 102 1003
4 102 1004
5 103 1001

电影表

movieid mname date
1001 电影 1 2023
1002 电影 2 2025
1003 电影 3 2024
1004 电影 4 2022

3. 一对多关系

models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from django.db import models
# 班级表
class Classes(models.Model):
name = models.CharField(max_length=100)

def __str__(self):
return self.name

# 学生表
class Student(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
# 外键,记得不用去加上id,级联删除
# classes = models.ForeignKey(Classes, on_delete=models.CASCADE)
# ForeignKey,将字段定义在“多的一面”
classes = models.ForeignKey(Classes, on_delete=models.SET_DEFAULT, default=11)
# 注意,修改 on_delete 参数时,需要需要重新同步数据库

# models.CASCADE 表示删除时,级联删除
# models.PROTECT 表示删除时,保护删除(删除时会抛出异常 ProtectedError at /delete/classes/)如果没有级联关系,则可以直接删除
# models.SET_NULL,null=True 表示删除时,设置为null,这里需要加上null=True,否则会抛出异常
# models.SET_DEFAULT 表示删除时,设置为默认值
# models.DO_NOTHING 表示删除时,不做任何操作
# models.SET_AUTO 表示删除时,设置为自动生成的值
  • Django 模型关系中的 on_delete 参数有以下 7 种常用模式,以下是它们的典型使用场景说明:

​ 1️⃣CASCADE(级联删除)

1
on_delete=models.CASCADE

​ 当主表记录被删除时,自动删除所有关联的从表记录(适用于强关联关系,如「订单 - 订单明细」)

​ 2️⃣PROTECT(保护删除)

1
on_delete=models.PROTECT

​ 阻止删除主表记录(会抛出 ProtectedError),直到所有关联的从表记录被手动删除(适用于关键数据保护)

​ 3️⃣SET_NULL(设空值)

1
on_delete=models.SET_NULL

​ 当主表记录被删除时: 必须设置参数 **null=True**

1
classes = models.ForeignKey(Classes, models.SET_NULL,null=True)

​ 4️⃣SET_DEFAULT(设默认值)

1
classes = models.ForeignKey(Classes, on_delete=models.SET_DEFAULT, default=2)

​ 注意需要确保默认值在数据库中存在

​ 5️⃣SET ()(自定义处理)

1
on_delete=models.SET(get_default_class)

​ 可指定函数返回处理值:

1
2
def get_default_class():
return Classes.objects.get_or_create(name='未分配')[0]

​ 6️⃣DO_NOTHING(不干预)

1
on_delete=models.DO_NOTHING

​ 需要手动处理数据库级联关系(通常配合数据库自身的外键约束使用)

​ 7️⃣RESTRICT(限制删除)

1
on_delete=models.RESTRICT

​ 与 PROTECT 类似但更智能:仅当存在直接关联的从表记录时阻止删除,允许通过中间表的间接关联被删除

  • 典型应用场景建议:

​ 1️⃣用户评论 → 使用 CASCADE(用户注销后删除所有评论)

​ 2️⃣商品分类 → 使用 PROTECT(防止误删有商品的分类)

​ 3️⃣可选关联项 → 使用 SET_NULL(如文章备用分类)

​ 4️⃣系统默认项 → 使用 SET_DEFAULT(如默认用户组)

  • 生成迁移文件
1
python manage.py makemigrations
  • 执行迁移
1
python manage.py migrate
  • 执行迁移后,班级表的表结构如下:
image-20250224230504557
1
2
3
4
5
6
# 表名=应用名_类名小写
create table One2Many_classes
(
id bigint auto_increment primary key,
name varchar(100) not null
);
  • 执行迁移后,学生表的表结构如下:
image-20250224230604785
  • 这里需要注意的是,生成的外键是 **classes_id , 而不是classes **
1
2
3
4
5
6
7
8
9
10
11
create table One2Many_student
(
id bigint auto_increment primary key,
name varchar(100) not null,
age int not null,
classes_id bigint not null,
constraint One2Many_student_classes_id_2306c407_fk_One2Many_classes_id
# 这里需要注意的是,生成的外键是 classes_id
foreign key (classes_id) references One2Many_classes (id)
);

3.1 增

按照 2 的表结构,新增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def add_classes(request):
classes = ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级', '初一', '初二', '初三', '高一', '高二','高三']

for c in classes:
c = Classes.objects.create(name=c)

return HttpResponse('添加班级成功')


def add_students(request):
# 方法一:使用 classes_id 字段,传递一个班级id
# 获取所有班级id, 结果为一个元组列表<QuerySet [(1,), (2,), (3,), (4,),...(10,), (11,), (12,)]>
class_id_list = Classes.objects.values_list('id')
print(class_id_list)
# 将元组列表转换为列表
class_id_choice_list = [id[0] for id in class_id_list]
print(class_id_choice_list)

# 使用 choice 函数随机选择一个班级id,传递给 classes_id 字段
for i in range(1, 101):
Student.objects.create(name='学生' + str(i), age='10', classes_id=choice(class_id_choice_list))

return HttpResponse('添加学生成功')


def add_students2(request):
# 方法二:使用 classes 字段, 传递一个班级的实例

# 获取所有班级实例
class_list = Classes.objects.all()

print(class_list[0])
print(type[class_list[0]])

for i in range(101, 201):
# 随机选择一个班级实例
selected_class = choice(class_list)
# 创建学生实例并关联班级实例,这里的 classes 字段是外键,所以可以直接传递班级实例
Student.objects.create(name='学生' + str(i), age='10', classes=selected_class)

return HttpResponse('添加学生成功')

3.2 删

  • 这里删除时,注意 **on_delete** 中的参数
  • 修改 on_delete 参数时,需要生成迁移文件,执行迁移。

views.py

1
2
3
4
5
6
7
8
9
10
# 删除班级
def delete_classes(request):
Classes.objects.filter(name='四年级').delete()
# 删除学生,和单表相同
return HttpResponse('删除年级成功')

# 删除学生
def delete_students(request):
Student.objects.filter(id=4).delete()
return HttpResponse('删除学生成功')

3.3 改

  • 和普通模式一样,不会影响到级联表

views.py

1
2
3
4
5
6
7
8
9
10
def update_classes(request):
# 单表修改
Classes.objects.filter(id=7).update(name='七年级')
return HttpResponse('修改班级成功')


def update_students(request):
# 单表修改
Student.objects.filter(id=4).update(age='88')
return HttpResponse('修改学生成功')

3.4 查

3.4.1 正向

  • 正向:外键在哪个表里,外键则这张表去操作,就是正向

  • 通过外键字段直接获取关联对象,通过学生查询到班级

views.py

1
2
3
4
5
6
7
8
9
10
def query_classes(request):
# 正向查找(从 Student 找 Classes)
# 查找id=4学生的对象
s1 = Student.objects.get(id=4)

# 正向访问:通过外键字段直接获取关联对象
c1 = s1.classes
print(c1.name)

return HttpResponse('查询班级成功')

3.4.2 反向 1

  • 反向:外键不在的表里,使用这张表去操作,就是反向

  • 使用 **student_set**(关联对象的小写类名_set), 通过班级查询到学生

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
def query_students(request):
# 反向查找(从 Classes 找 Student),get获取的是对象
c1 = Classes.objects.get(id=8)

print(c1.name)
# 反向访问:通过关联对象的小写类名_set 来获取关联对象的 QuerySet,
ss1 = c1.student_set.all()

print(c1.name, '的学生有:')
for s in ss1:
print(s.name)

return HttpResponse('查询学生成功')

3.4.3 反向 2

  • 使用 **related_name**(管理器对象), 通过班级查询到学生

models.py

1
2
3
4
5
6
7
8
class Student(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
# 外键,记得不用去加上id,级联删除
# classes = models.ForeignKey(Classes, on_delete=models.CASCADE)

# 使用related_name 的 studnet
classes = models.ForeignKey(Classes, on_delete=models.CASCADE, related_name='students')

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def query_students2(request):
# 反向查找(从 Classes 找 Student)
c1 = Classes.objects.get(id=8)

print(c1.name)
# 反向访问:通过related_name 来获取关联对象的 QuerySet,这里的 students 就是 models.py定义的related_name='students'
# 相当于给反向 1 中的student_set重新命名为自己的名字
ss1 = c1.students.all()

print(c1.name, '的学生有:')
for s in ss1:
print(s.name)

return HttpResponse('查询学生成功')

3.3.4 反向 3

  • 查询时传入对象、双下划线 + 属性
1
2
3
4
5
6
7
8
9
10
11
def query_students3(request):
# 直接传入对象
# students = Student.objects.filter(classes=Classes.objects.get(name='一年级'))
# print(students.all())

# 直接传入双下划线+属性
students = Student.objects.filter(classes__id='2') # 相当于传入的是 classes的 id 属性, 注意这里的双下划线,这个也输入反向查询
# students = Student.objects.filter(classes__name='一年级') # 相当于传入的是 classes的 name 属性
print(students)

return HttpResponse('查询学生3成功')

4. 多对多关系

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

models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 用户表
class Users(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()

def __str__(self):
return self.name

# 电影表
class Movies(models.Model):
name = models.CharField(max_length=100)
year = models.IntegerField()
# 这条也可以写在用户表中
users = models.ManyToManyField(Users)

def __str__(self):
return self.name
  • 生成迁移文件,执行迁移
1
2
python manage.py makemigrations
python manage.py migrate

4.1 增

4.1.1 单表直接添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 单表直接添加
def add_movies(request):
for i in range(1, 20):
Movies.objects.create(name='电影' + str(i), year=i + 2000)

return HttpResponse('添加电影成功')

# 单表直接添加
def add_users(request):
for i in range(1, 20):
Users.objects.create(name='用户' + str(i), age=i + 18)
return HttpResponse('添加用户成功')


# 使用
def add_movie_user2(request):
user6 = Users.objects.get(id=6)
user7 = Users.objects.get(id=7)
user8 = Users.objects.get(id=8)

movie4 = Movies.objects.get(id=4)

# 方法二:使用set 收藏电影
user6.movies_set.add(movie4)
user7.movies_set.add(movie4)
user8.movies_set.add(movie4)

return HttpResponse('用户收藏电影成功')

4.1.2 使用 add 添加关联关系表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def add_movie_user(request):
user1 = Users.objects.get(id=1)
user2 = Users.objects.get(id=2)
user3 = Users.objects.get(id=3)
user4 = Users.objects.get(id=4)
user5 = Users.objects.get(id=5)

movie1 = Movies.objects.get(id=1)
movie2 = Movies.objects.get(id=2)
movie3 = Movies.objects.get(id=3)

# 方法一:直接添加
movie1.users.add(user1, user2)
movie2.users.add(user1, user3)
movie3.users.add(user1, user4)

movie1.users.add(user5)
movie2.users.add(user5)
movie3.users.add(user5)

4.1.3 使用_set 方法添加关联关系表

1
2
3
4
5
6
7
8
9
10
11
12
13
def add_movie_user2(request):
user6 = Users.objects.get(id=6)
user7 = Users.objects.get(id=7)
user8 = Users.objects.get(id=8)

movie4 = Movies.objects.get(id=4)

# 方法二:使用set 收藏电影
user6.movies_set.add(movie4)
user7.movies_set.add(movie4)
user8.movies_set.add(movie4)

return HttpResponse('用户收藏电影成功')

4.1.4 使用 related_name 添加关联关系表

1
# 先过,后续再补

4.2 删

4.2.1 单表删除

  • 和一对多类似,删除每一张单表中的数据,会级联删除级联表 中的数据。
1
2
3
4
5
6
7
8
9
10
11
12
def delete_movies(request):
movie19 = Movies.objects.get(id=19)
movie19.delete()

return HttpResponse('删除电影成功')


def delete_users(request):
user19 = Users.objects.get(id=19)
user19.delete()

return HttpResponse('删除用户成功')

4.2.2 关联表删除

  • 只删除关联关系表中的数据,使用 remove, 如果使用 delete, 则会删除用户中用户,然后级联删除关联中的表
1
2
3
4
5
6
7
8
def delete_related(request):
movie7 = Movies.objects.get(id=8)
user7 = Users.objects.get(id=8)

# 只删除关联关系表中的数据,使用remove,如果使用 delete,则会删除用户中用户,然后级联删除关联中的表
movie7.users.remove(user7)

return HttpResponse('删除相关数据成功')

4.3 改

  • 一般关联关系直接删除,不用修改

4.4 查

  • 和一对多基本相同
1
2
3
4
5
6
7
8
9
10
11
12
def query_movies_users(request):
# 正向查询,收藏 8 号电影的所有用户
movie8 = Movies.objects.get(id=8)
users_all = movie8.users.all()
print(users_all)

# 反向查询,8号用户收藏的所有电影
user8 = Users.objects.get(id=8)
user8_movies = user8.movies_set.all()
print(user8_movies)

return HttpResponse('查询电影成功')

5. 一对一关系

  • 一对一关系不是数据库的一个连表操作,是 Django 独有的一个连表操作,一对一关系相当于一个特殊的一对多关系,只是相对于一对多添加了 **unique=True**

models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

from django.db import models


# Create your models here.
class IdCard(models.Model):
IdCard_number = models.IntegerField(unique=True)
address = models.CharField(max_length=100)

# def __str__(self):
# return self.IdCard_number


class Person(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField(default=18)
id_card = models.OneToOneField(IdCard, on_delete=models.CASCADE)

# def __str__(self):
# return self.name

5.1 增、删、改

  • 同 1 对多

5.2 查

views.py

  • 需要注意的是,无论是正向还是反向获取的都是一个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

def get_id_card(request):
# 显式写法,
# p1 = Person.objects.get(id=2)
# 推荐写法,优先使用 pk 参数:保持代码统一性,即使未来修改主键字段名称也不需要改动查询逻辑
p1 = Person.objects.get(pk=2)

print(p1.id_card.IdCard_number)
# 这里的p1.id_card是一个对象,不是一个字符串
print(type(p1.id_card))
print(p1.id_card.address)

return HttpResponse('查询成功')


def get_person(request):
id_card3 = IdCard.objects.get(pk=3)
print(id_card3.person)
# 这里的id_card3.person是一个对象,不是一个字符串
print(type(id_card3.person))
print(id_card3.person.name)

return HttpResponse('查询成功')

6. 相关参考

B 站视频