Django4.2 学习 - 模型 Model(四)

Django 的模型(Model)是框架的核心组件之一,用于定义数据结构并与数据库进行交互。

1. 模型的作用

  • 定义数据库表的结构(字段、关联关系等)。
  • 通过 Django ORM(对象关系映射)操作数据库,无需直接编写 SQL。
  • 支持数据验证、业务逻辑封装(如保存前后的操作)。

2. 创建模型

在 Django 应用的 models.py 中定义模型类:

1
2
3
4
5
6
7
8
9
10
11
from django.db import models

class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
publish_date = models.DateField()
price = models.DecimalField(max_digits=5, decimal_places=2)
is_published = models.BooleanField(default=True)

def __str__(self):
return self.title

3. 常用字段类型

3.1 字符串类字段

  • CharField

    • 作用:存储段文本(如标题、名称)
    • 必选参数:max_length
    • 🌰:
    1
    title = models.CharField(max_length=200)
  • TextField

    • 作用:存储长文本(如文章内容)
    • CharField 的区别:无需 max_length,适合大段文本。
    • 🌰:
    1
    content = models.TextField()
  • EmailField

    • 作用:存储邮箱地址,自动验证格式。
    • 底层实现:继承自 CharField,但添加了邮箱格式验证。
    • 🌰:
    1
    email = models.EmailField()
  • URLField

    • 作用:存储 URL,自动验证格式。
    • 🌰:
    1
    website = models.URLField()

3.2 数值类字段

  • IntegerField

    • 作用:存储整数。
    • 🌰:
    1
    age = models.IntegerField()
  • FloatField

    • 作用:存储浮点数。
    • 🌰:
    1
    rating = models.FloatField()
  • DecimalField

    • 作用:存储精确小数(适用于金融数据)。
    • 必选参数
      • max_digits:总位数(包括小数点后的位数)。
      • decimal_places:小数点后的位数。
    • 🌰:
    1
    price = models.DecimalField(max_digits=5, decimal_places=2)

3.3 日期和时间类字段

  • DateField

    • 作用:存储日期(年月日)。
    • 常用参数
      • auto_now:每次保存时自动更新为当前日期。
      • auto_now_add:仅在创建时设置为当前日期。
    • 🌰:
    1
    publish_date = models.DateField(auto_now_add=True)
  • DateTimeField

    • 作用:存储日期和时间。
    • ** 参数同 DateField**。
    • 🌰:
    1
    created_at = models.DateTimeField(auto_now_add=True)

3.4 布尔类字段

  • BooleanField

    • 作用:存储布尔值(True/False)。

    • 注意:默认值为 False,可通过 default=True 修改。

    • 🌰:

    1
    is_published = models.BooleanField(default=True)

3.5 文件类字段

  • FileField

    • 作用:存储文件路径(如上传的文件)。
    • 常用参数
      • upload_to:文件上传目录(如 "documents/")。
    • 🌰:
    1
    document = models.FileField(upload_to="documents/")
  • ImageField

    • 作用:存储图片路径,继承自 FileField,额外验证图片格式。
    • 依赖:需安装 Pillow 库。
    • 🌰:
    1
    photo = models.ImageField(upload_to="photos/")

3.6 关联类字段

4. 常用字段参数

以下参数适用于大多数字段类型:

4.1 通用参数

  • null

    • 作用:是否允许数据库存储 NULL 值。
    • 🌰null=True(默认为 False)。
  • blank

    • 作用:是否允许表单验证为空(与 null 无关)。
    • 🌰blank=True(默认为 False)。
  • default

    • 作用:字段的默认值。
    • 🌰default="draft"
  • unique

    • 作用:是否要求字段值唯一。
    • 🌰unique=True
  • choices

    • 作用:限制字段值为预定义选项。
    • 🌰
    1
    2
    3
    4
    5
    STATUS_CHOICES = [
    ('D', 'Draft'), # 数据库存储的是
    ('P', 'Published'),
    ]
    status = models.CharField(max_length=1, choices=STATUS_CHOICES)
  • verbose_name

    • 作用:字段的人类可读名称(用于 Admin 后台)。
    • 示例verbose_name="出版日期"
  • help_text

    • 作用:字段的提示文本(用于 Admin 后台)。
    • 示例help_text="请输入书籍标题"
  • db_index

    • 作用:为字段创建数据库索引,加速查询。
    • 示例db_index=True

4.2 关联字段专用参数

  • **on_delete**(用于 ForeignKeyOneToOneField
    • 作用:定义关联对象删除时的行为。
    • 常用选项
      • models.CASCADE:级联删除。
      • models.PROTECT:阻止删除关联对象。
      • models.SET_NULL:将字段设为 NULL(需 null=True)。
      • models.SET_DEFAULT:设为默认值(需 default 参数)。

5. 数据库迁移

5.1 生成迁移文件

1
python manage.py makemigrations

5.2 执行迁移

1
python manage.py migrate

5.3 查看 SQL 语句

1
python manage.py sqlmigrate <app_name> <migration_number>

6. 操作模型

以下是 Django 模型增删改查(CRUD)操作:

6.1 增

方法 1:

方法 1 和方法 2 都是先实例化对象,然后再保存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# views.py
def add(request):
# 方式一:
message = 'add success'
try:
t = testModels6()
t.name = '测试增加1'
t.sex = '男'
t.age = '21'
t.save() # 同步到数据库表中,显示保存
except Exception as e:
print(e)
message = 'add error'

return HttpResponse(message)

方法 2:

1
2
3
4
5
6
7
8
9
10
11
12
# views.py
def add(request):

message = 'add success'
try:
t = testModels6(name='测试增加方法二', sex='男', age='22')
t.save() # 同步到数据库表中,显示保存
except Exception as e:
print(e)
message = 'add error'

return HttpResponse(message)

方法 3:

使用 create() 直接创建并保存到数据库。

1
2
3
4
5
6
7
8
9
10
# views.py
def add(request):

message = 'add success'
try:
# 直接创建并保存到数据库
testModels6.objects.create(name='测试增加方法三', sex='男', age='23')
except:
message = 'add fail'
return HttpResponse(message)

方法 4:

使用 get_or_create() 创建,与 create() 不同,get_or_create() 有返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
# views.py
def add(request):

message = 'add success'
try:
# 直接创建并保存到数据库,z
ret = testModels6.objects.get_or_create(name='测试增加方法四-5', sex='女', age=' 44')
print(ret) # (<testModels6: 测试增加方法四>, True) 数据不存在时,返回
# (<testModels6: 测试增加方法四>, False) 数据已存在时,返回
except:
message = 'add fail'
return HttpResponse(message)

方法 5:

  • 使用 bulk_create() 批量插入多条数据(减少数据库查询次数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# views.py

def add(request):
models6 = [
testModels6(name='测试增加方法四-000', age=10, sex='1'),
testModels6(name='测试增加方法四-001', age=20, sex='2'),
testModels6(name='测试增加方法四-002', age=30, sex='2'),
testModels6(name='测试增加方法四-003', age=40, sex='1'),
testModels6(name='测试增加方法四-004', age=50, sex='2'),
]
try:
testModels6.objects.bulk_create(models6)

except Exception as e:
print(e)
return HttpResponse('添加失败')

return HttpResponse('添加成功')
  • 循环添加多条
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# views.py

def add(request):
user_list = [
{'name': '测试增加方法四-01', 'sex': '女', 'age': '41'},
{'name': '测试增加方法四-02', 'sex': '女', 'age': '42'},
{'name': '测试增加方法四-03', 'sex': '女', 'age': '43'},
{'name': '测试增加方法四-04', 'sex': '女', 'age': '44'},
{'name': '测试增加方法四-05', 'sex': '女', 'age': '44'},
]

for user in user_list:
ret = testModels6.objects.get_or_create(name=user['name'], sex=user['sex'], age=user['age'])
return HttpResponse('添加成功')

6.2 删

  • 删除单条
1
2
3
4
5
6
7
8
9
10
11
12
13
# views.py

def delete_user(request):
try:
# 删除单条
t = testModels6.objects.get(name='测试增加方法四-5')
t.delete()

except Exception as e:
print(e)
return HttpResponse('删除失败')

return HttpResponse('删除成功')
  • 批量删除
1
2
3
4
5
6
7
8
9
10
11
12
# views.py

def delete_user(request):
try:
# 根据条件批量删除:找出age大于59的记录,然后删除
testModels6.objects.filter(age__gt=59).delete()

except Exception as e:
print(e)
return HttpResponse('删除失败')

return HttpResponse('删除成功')
  • 删除所有
1
2
3
4
5
6
7
8
9
10
11
12
13
# views.py

def delete_user(request):
try:
# 删除所有
testModels6.objects.all().delete()
# testModels6.objects.filter().delete() 这样也是可以的

except Exception as e:
print(e)
return HttpResponse('删除失败')

return HttpResponse('删除成功')

6.3 改

  • 更新单条
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# views.py

def update_user(request):
try:
# 普通方法:
user1 = testModels6.objects.get(name='测试增加方法四-05')
user1.age = 123
user1.save() # 触发 save() 方法(会更新 auto_now 字段)


except Exception as e:
print(e)
return HttpResponse('更新失败')

return HttpResponse('更新成功')
  • 提高效率减少数据操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# views.py

def update_user(request):
try:
user2 = testModels6.objects.get(name='测试增加方法四-08')
user2.age = 111
# save()方法更新时会更新所有字段,如果只想更新某个字段,减少数据操作,可以这样
user2.save(update_fields=['age']) # 指定需要更新的字段,提高更新的效率

except Exception as e:
print(e)
return HttpResponse('更新失败')

return HttpResponse('更新成功')

  • 批量更新
1
2
3
4
5
6
7
8
9
10
11
12
# views.py

def update_user(request):
try:
# 直接更新(不触发 save() 方法和信号)
testModels6.objects.filter(sex='2').update(age=18)

except Exception as e:
print(e)
return HttpResponse('更新失败')

return HttpResponse('更新成功')

6.4 查

  • 获取单条数据

    • get():精确匹配(找不到或找到多个会抛异常)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def get_user(request):

    try:
    m6 = testModels6.objects.get(name='测试增加方法四-05')
    print(m6.name)
    print(m6.age)
    print(m6.sex)

    except testModels6.DoesNotExist as e:
    # 处理不存在的情况
    print(e)
    except testModels6.MultipleObjectsReturned as e:
    # 处理多个对象返回的情况
    print(e)
    return HttpResponse('获取成功')
    • **first()last()**:获取第一条或最后一条
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def get_user(request):

    try:
    m_first = testModels6.objects.first()
    m_last = testModels6.objects.last()
    print(m_first)
    print(m_last)

    except Exception as e:
    print(e)
    return HttpResponse('获取失败')

    return HttpResponse('获取成功')
  • 过滤数据

    • **filter()**:条件查询
    1
    users = testModels6.objects.filter(age=18)
    • **exclude()**:排除条件
    1
    users2 = testModels6.objects.exclude(age=18)
  • 统计

    • **count()**:统计查询集中对象个数
    1
    num = testModels6.objects.count()
  • 获取全部数据

    • **all()**: 获取全部数据
    1
    all_data = testModels6.objects.all()
  • 获取指定列的值

    • **values()**:获取指定列的值,可以传多个参数,返回字典列表(保存了字段名和对应的值)
    1
    2
    3
    4
    5
    users = testModels6.objects.all().values('name', 'age')
    print(users)
    """
    <QuerySet [{'name': '测试增加方法四-01', 'age': 18}, {'name': '测试增加方法四-02', 'age': 18}, {'name': '测试增加方法四-03', 'age': 18}, {'name': '测试增加方法四-04', 'age': 18}, {'name': '测试增加方法四-05', 'age': 18}, {'name': '测试增加方法四-07', 'age': 18}, {'name': '测试增加方法四-08', 'age': 18}, {'name': '测试增加方法四-09', 'age': 18}, {'name': '测试增加方法四-10', 'age': 18}, {'name': '测试增加方法四-11', 'age': 18}, {'name': '测试增加方法四'age': 18}, {'name': '测试增加方法四-14', 'age': 18}, {'name': '测试增加方法四-15', 'age': 18}, {'name': '测试增加方法四-16', 'age': 18}, {'name': '测试增加方法四-17', 'age': 18}, {'name': '测试增加方法四-18', 'age': 18}, {'name': '测试增加方法四-20', 'age': 18}, '...(remaining elements truncated)...']>
    """
    • **values_list()**:获取指定列的值,可以传多个参数,返回元组列表(只保存了值)
    1
    2
    3
    4
    5
    users2 = testModels6.objects.all().values_list('name','age')
    print(users2)
    '''
    <QuerySet [('测试增加方法四-01', 18), ('测试增加方法四-02', 18), ('测试增加方法四-03', 18), ('测试增加方法四-04', 18), ('测试增加方法四-05', 18), ('测试增加方法四-06', 18), ('测试增加方法四-07', 18), ('测试增加方法四-08', 18), 8), ('测试增加方法四-12', 18), ('测试增加方法四-13', 18), ('测试增加方法四-14', 18), ('测试增加方法四-15', 18), ('测试增加方法四-16', 18), ('测试增加方法四-17', 18), ('测试增加方法四-18', 18), ('测试增加方法四-19', 18), ('测试增加方法四-20', 18), '...(remaining elements truncated)...']>
    '''
  • 判断是否存在

    • exists()
    1
    2
    user = testModels6.objects.filter(name='测试增加方法四-0511').exists()
    print(user)
  • 查找参数相关

    1. __ltLess Than

      • 表示 “小于”。
      • 示例:age__lt=18 表示筛选出 年龄小于 18 的记录。
      1
      user = testModels6.objects.filter(age__lt=18)
    2. __gtGreater Than

      • 表示 “大于”。
      • 示例:gt__lt=18 表示筛选出年龄大于 18 的记录。
      1
      user = testModels6.objects.filter(gt__lt=18)
    3. __lteLess Than or Equal to

      • 表示 “小于等于”。
      • 示例:age__lte=18` 表示筛选出年龄小于等于 18 的记录。
      1
      users = testModels6.objects.filter(age__lte=18)
    4. __gteGreater Than or Equal to

      • 表示 “大于等于”。
      • 示例:age__gte=18 表示筛选出年龄大于等于 18 的记录。
      1
      users = testModels6.objects.filter(age__gte=18)
    5. **__in**:

      • 表示 “在某个列表中”。
      • 示例:age__in=[1, 2, 3] 表示筛选出 ID 在列表 [1, 2, 3]` 中的记录。
      1
      2
      3
      4
      # 在列表
      users = testModels6.objects.filter(age__in=[111, 222])
      # 元组也行
      users = testModels6.objects.filter(age__in=(111, 222))
    6. **__isnull**:

      • 表示 “是否为 null 值”。
      • 示例:age__isnull 表示筛选出年龄中是 null 的记录。
      1
      users = testModels6.objects.filter(age__isnull=True)
    7. **__range**:

      • 表示:字段值在某个范围内的记录,有点像 between... and..
      • 示例:age__range 表示筛选出年龄是 18~40(包含 18,包含 40)的记录
      1
      users = testModels6.objects.filter(age__range=(18,40))
    8. **__contains**:

      • 表示 “包含某个子字符串”,区分大小写。
      • 示例:name__contains="Alice" 表示筛选出名字中包含 “Ali” 的记录。
      1
      users = testModels6.objects.filter(name__contains='Ali')
    9. **__icontains**:

      • 表示 “包含某个子字符串”,不区分大小写。
      • 示例:name__icontains="Alice" 表示筛选出名字中包含 “ali” 的记录。
      1
      users = testModels6.objects.filter(name__icontains='ali')
    10. **__regex**:

      • 表示 “正则表达式匹配”。
      • 示例:name__regex="^A.*" 表示筛选出名字以大写字母 “A” 开头的记录。
      1
      users = testModels6.objects.filter(name__regex='^A.*')
    11. **__iregex**:

      • 表示 “不区分大小写的正则表达式匹配”。
      • 示例:name__iregex="^a.*" 表示筛选出名字以字母 “a” 开头(不区分大小写)的记录。
      1
      2
      3
      4
      # 以下 2 种格式查询结果相同
      users = testModels6.objects.filter(name__iregex='^A.*')

      users = testModels6.objects.filter(name__iregex='^a.*')
    12. **__startswith**:

      • 表示 “以某个字符串开头”。
      • 示例:name__startswith="A" 表示筛选出名字以 “A” 开头的记录。
      1
      users = testModels6.objects.filter(name__startswith='A')
    13. **__istartswith**:

      • 表示 “不区分大小写的以某个字符串开头”。
      • 示例:name__istartswith="a" 表示筛选出名字以 “a” 开头的记录(不区分大小写)。
      1
      2
      users = testModels6.objects.filter(name__istartswith='a')
      users = testModels6.objects.filter(name__istartswith='A')
    14. **__endswith**:

      • 表示 “以某个字符串结尾”。
      • 示例:name__endswith="a" 表示筛选出名字以 “a” 结尾的记录。
      1
      users = testModels6.objects.filter(name__endswith='a')
    15. **__iendswith**:

      • 表示 “不区分大小写的以某个字符串结尾”。
      • 示例:name__iendswith="smith" 表示筛选出名字以 “Smith” 结尾的记录(不区分大小写)。
      1
      2
      users = testModels6.objects.filter(name__iendswith='a')  # sqlite中不生效,需要改配置
      users = testModels6.objects.filter(name__iendswith='A')
    16. **__exact**:

      • 表示 “精确等于”。
      • 示例:name__exact="Alice" 表示筛选出名字精确等于 “Alice” 的记录。
      • 注意:在 Django 中,__exact 是默认的查找方式,如果不写查找参数,默认就是精确匹配。
      1
      users = testModels6.objects.filter(name__exact='Alice test name')
    17. **__iexact**:

      • 表示 “不区分大小写的精确等于”。
      • 示例:name__iexact="alice" 表示筛选出名字精确等于 “Alice” 的记录(不区分大小写)。
      1
      users = testModels6.objects.filter(name__exact='alice test name')
    18. **__search**:

      • 表示 “全文搜索”。
      • 示例:name__search="Alice" 表示筛选出名字中包含 “Alice” 的记录(通常用于全文搜索,具体行为可能依赖于数据库后端,PostgreSQL 数据库)。
      1
      2
      # sqlite 不支持, postgresql 支持,mysql还没试应该也不支持
      users = testModels6.objects.filter(name__search='alice test name')
  • 排序和去重

    • **order_by()**:排序

      1
      2
      3
      4
      5
      6
      # 按照年龄正序排
      users = testModels6.objects.all().order_by('age')
      # 按照年龄倒序排
      users = testModels6.objects.all().order_by('-age')
      # 先按照年龄正序排,如果年龄相同,则按照 id 倒序排
      users = testModels6.objects.all().order_by('age', '-id')
    • **distinct()**:去重

      1
      2
      # 按照年龄去重
      users = testModels6.objects.values('age').distinct()
  • 聚合与分组

    • **Max()**:求最大值
    1
    max_age = testModels6.objects.aggregate(Max('age'))
    • **Min()**:求最小值
    1
    min_age = testModels6.objects.aggregate(Min('age'))
    • **Sum()**:求和
    1
    sum_age = testModels6.objects.aggregate(Sum('age'))
    • **Avg()**:求最大值
    1
    avg_age = testModels6.objects.aggregate(Avg('age'))

6.5 分页

  • 手动分页

    思路:

    pageNum:页码

    pageSize:每页条数

    页码 每页显示 下标范围 切片范围
    1 1~10 0~9 0:10
    2 11~20 10~19 10:20
    3 21~30 20~29 20:30
    4 (4-1)*10+1~(4-1)*10 (4-1)*10 ~ 4*10-1 (4-1)*10 : 4*10
    pageNum (pageNum-1)*pageSize+1~(pageNum-1)*pageSize (pageNum-1)*pageSize ~ pageNum*pageSize-1 (pageNum-1)*pageSize : pageNum*pageSize

    views.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def manual_pagination(request, pageNum):
    # 页码
    pageNum = int(pageNum)
    all_data = testModels7.objects.all()
    # 总数据条数
    total = all_data.count()

    # 每页显示的数据条数
    pageSize = 10
    # 总页码数,ceil 向上取整
    totalPage = math.ceil(total / pageSize)

    # all_data = all_data[pageNum * pageSize:(pageNum + 1) * pageSize]
    all_data = all_data[(pageNum - 1) * pageSize: pageNum * pageSize]

    return render(request, 'manualPagination.html', {'all_data': all_data, 'totalPage': totalPage + 1})

    urls.py

    1
    path('manualPagination/<int:pageNum>', views.manual_pagination, name='manualPagination'),

    manualPagination.html

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h2>手动分页</h2>
    <hr>
    <ul>
    {% for item in all_data %}
    <li>id:{{ item.id }}--名字:{{ item.name }}--年龄:{{ item.age }}--性别:{{ item.sex }} </li>
    {% endfor %}
    </ul>
    <hr>
    {# django 模板 #}
    {#<a href="{% url 'manualPagination' pageNum=1 %}">第一页</a>#}

    {# Jinja2 模板 #}
    {#<a href="{{ url('manualPagination', kwargs={'pageNum': 1}) }}">第1页</a>#}
    {% for page in range(1,totalPage) %}

    <a href="{{ url('manualPagination', kwargs={'pageNum': page}) }}"> 第{{ page }}页</a>

    {% endfor %}
    </body>
    </html>
  • Django分页器

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def django_pagination(request, pageNum):
all_data = testModels7.objects.all()
# 实例化分页对象,第一个参数是要分页的数据,第二个参数是每页显示的数据条数
paginator = Paginator(all_data, 10)

# 获取指定页码的数据
page_data = paginator.page(pageNum)

page_data = page_data.object_list
print(page_data)

# 获取总页码数, 这里提前处理一下
totalPage = range(1, paginator.num_pages + 1)

return render(request, 'djangoPagination.html', {'page_data': page_data, 'totalPage': totalPage})

urls.py

1
path('djangoPagination/<int:pageNum>', views.django_pagination, name='djangoPagination'),

djangoPagination.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>django 分页器</h2>
<hr>
<ul>
{% for item in page_data %}
<li>id:{{ item.id }}--名字:{{ item.name }}--年龄:{{ item.age }}--性别:{{ item.sex }} </li>
{% endfor %}
</ul>
<hr>

{% for page in totalPage %}
<a href="{{ url('djangoPagination', kwargs={'pageNum': page}) }}"> 第{{ page }}页</a>
{% endfor %}

</body>
</html>

7. 相关参考