Django之ORM

Django之ORM

rainbowYao Lv3

本人理解:ORM就是使用Django框架的时候操作数据库的工具,封装了许多功能,使用的好可以带来很多便利

简介

ORM:Object Relational Mapping(关系对象映射),其中类名对应数据库中的表名;类属性对应数据库里的字段;类实例对应数据库表里的一行数据;obj.id obj.name…..类实例对象的属性

Django ORM优势:Django的ORM本质上会根据对接的数据库引擎,翻译成对应的sql语句;所有使用Django开发的项目无需关心程序底层使用的是MySQL、Oracle、sqlite….,如果数据库迁移,只需要更换Django的数据库引擎即可。




Django连接MySQL

创建数据库

Django自带的ORM是data_first类型的ORM,使用前必须先创建数据库,且需要注意设置数据的字符编码

1
create database <name> default character set utf8 collate utf8_general_ci;

修改settings文件中设置

修改项目文件夹中的setting.py,连接MySQL数据库(Django默认使用的是sqllite数据库)

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql'
'NAME': '' #数据库名字
'HOST': '' #安装mysql的主机
'PORT': '' #端口
'USER': '' #mysql用户名
'PASSWORD': '' #mysql密码
}
}

修改Django默认连接MySQL的方式

修改项目文件夹中的__init__.py,使Django默认连接MySQL(需先安装环境npm install pymysql

1
2
3
import pymysql

pymysql.install_as_MySQLdb()

settings文件注册APP

新建app可以使用Django指令python manage.py startapp <app_name>
随后在settings中注册这一app,

1
2
3
4
5
INSTALLED_APPS = [
'django.contrib.admin',
...
'application.user', # 这里从根目录往下寻找apps.py
]

models中创建表

下一部分大部分中会具体讲述app文件夹中models.py怎么写,下面给出一个简化版的例子

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

class Cafeteria(models.Model):
name = models.CharField(max_length=50, verbose_name='食堂名称', unique=True)
address = models.CharField(max_length=255, verbose_name='食堂地址', blank=True)
image = models.ImageField(upload_to='cafeteria/', default='cafeteria/default.png', verbose_name='食堂图片')

def __str__(self):
return self.name

class Meta:
verbose_name = '食堂'
verbose_name_plural = verbose_name

数据迁移

在models.py中定义完模型后,需要将这些 Python 类转换为数据库中的表结构。这个过程称为数据迁移。以下是进行数据迁移的具体步骤和命令:

1
2
3
python manage.py makemigrations    #用于生成迁移文件,这些文件记录了模型的变化
python manage.py migrate #将这些变化应用到数据库中,实际执行表结构的创建或修改
python manage.py migrate --fake #只记录变化,而不在数据库中执行实际的操作

MySQL可视化

我选择的是jetbrains家的DataGrip,具体教程可看使用DataGrip连接MySQL数据库




models.py创建表

ORM字段介绍

  1. 字符串类

以下在数据库中本质都是字符串数据类型,此类字段只是在Django自带的admin中生效

models.CharField

更多点击查看 EmailField(CharField)
IPAddressField(Field)
URLField(CharField)
SlugField(CharField)
UUIDField(Field)
FilePathField(Field)
FileField(Field)
ImageField(FileField)
CommaSeparatedIntegerField(CharField)
  • models.CharField 对应的是MySQL的varchar数据类型
  • char 和 varchar的区别 :
    1. char和varchar的共同点是存储数据的长度,不能超过max_length限制,
    2. 不同点是varchar根据数据实际长度存储,char按指定max_length()存储数据;所有前者更节省硬盘空间
  1. 时间字段
1
2
data = models.DateTimeField(null=True)
date = models.DateField()
  1. 数字字段
1
2
3
num = models.IntegerField()
num = models.FloatField() 浮点
price = models.DecimalField(max_digits=8,decimal_places=3) #精确浮点
  1. 枚举字段
1
2
3
4
5
6
choice=(
(1,'男人'),
(2,'女人'),
(3,'其他')
)
lover=models.IntegerField(choices=choice) #枚举类型

在数据库存储枚举类型,比外键有什么优势?

  1. 无需连表查询性能低,省硬盘空间(选项不固定时用外键)
  2. 在model文件里不能动态增加(选项一成不变用Django的choice)
  1. 关系字段
1
2
3
4
author = models.ForeignKey(Author, on_delete=models.CASCADE)
# 一对多关系。on_delete参数定义了当关联的Author对象被删除时,相关的Book对象应如何处理。
related_field = models.ManyToManyField(RelatedModel)
# 多对多关系,它会在数据库中创建一个中间表来存储关联关系。

字段参数介绍

  1. 数据库级别生效
点击查看,内容有些多 AutoField(Field)
- int自增列,必须填入参数 primary_key=True
BigAutoField(AutoField)
- bigint自增列,必须填入参数 primary_key=True

注:当model中如果没有自增列,则自动会创建一个列名为id的列
SmallIntegerField(IntegerField):
- 小整数 -32768 ~ 32767

PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正小整数 0 ~ 32767

IntegerField(Field)
- 整数列(有符号的) -2147483648 ~ 2147483647

PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正整数 0 ~ 2147483647

BigIntegerField(IntegerField):
- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807

自定义无符号整数字段

class UnsignedIntegerField(models.IntegerField):
def db_type(self, connection):
return 'integer UNSIGNED'

PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',

BooleanField(Field)
- 布尔值类型

NullBooleanField(Field):
- 可以为空的布尔值

CharField(Field)
- 字符类型
- 必须提供max_length参数,max_length表示字符长度

TextField(Field)
- 文本类型

EmailField(CharField):
- 字符串类型,Django Admin以及ModelForm中提供验证机制

IPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

GenericIPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
- 参数:
protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both"

URLField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证 URL

SlugField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

CommaSeparatedIntegerField(CharField)
- 字符串类型,格式必须为逗号分割的数字

UUIDField(Field)
- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

FilePathField(Field)
- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
- 参数:
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹

FileField(Field)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage

ImageField(FileField)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
width_field=None, 上传图片的高度保存的数据库字段名(字符串)
height_field=None 上传图片的宽度保存的数据库字段名(字符串)

DateTimeField(DateField)
- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

DateField(DateTimeCheckMixin, Field)
- 日期格式 YYYY-MM-DD

TimeField(DateTimeCheckMixin, Field)
- 时间格式 HH:MM[:ss[.uuuuuu]]

DurationField(Field)
- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

FloatField(Field)
- 浮点型

DecimalField(Field)
- 10进制小数
- 参数:
max_digits,小数总长度
decimal_places,小数位长度

BinaryField(Field)
- 二进制类型
  1. Django admin级别生效
点击查看 blank (是否为空)
editable=False 是否允许编辑

help_text="提示信息"提示信息
choices=choice 提供下拉框
error_messages="错误信息" 错误信息

validators 自定义错误验证(列表类型),从而定制想要的验证规则



ORM单表操作

QuerySet数据类型

QuerySet特点:可迭代的、可切片、惰性计算和缓存机制

点击查看 books=models.Book.objects.all()[:10] #切片
book= models.Book.objects.all()[6] #索引
for obj in books: #迭代
print(obj.title)

books=models.Book.objects.all()
# 惰性机制--->等于一个生成器,不应用books不会执行任何SQL操作
# query_set缓存机制1次数据库查询结果query_set都会对应一块缓存,再次使用该query_set时,不会发生新的SQL操作
# 这样减小了频繁操作数据库给数据库带来的压力

models.Publish.objects.all().iterator()
# 但是有时候取出来的数据量太大会撑爆缓存,可以使用迭代器解决这个问题

增删改查

表.objects.create()obj=类(属性=XX) obj.save(),多对多关系处理外键时有add()、clear()、remove()

obj.delete(),同时还有级联删除的情况on_delete=models.CASCADE

update()save()

objects.all()objects.filter(id=2, price=100),这两个都会返回QuerySet对象集合 [对象1、对象2、…. ]

objects.get(id=2) ,这个返回单个对象,没有找到或有多个都会报错

后续还会有values方法与跨表查询


双下划线操作

属性后面搭配双下划线与特定的字段可以方便查询

内容较多,点击查看 获取个数
- models.book.objects.filter(price=17).count()

大于,小于
- objects.filter(id__gt=1) # 获取id大于1的值
- objects.filter(id__gte=1) # 获取id大于等于1的值
- objects.filter(id__lt=10) # 获取id小于10的值
- objects.filter(id__lte=10) # 获取id小于等于10的值
- objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值

in
- objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据
- objects.exclude(id__in=[11, 22, 33]) # not in

isnull
- objects.filter(pub_date__isnull=True)

contains
- objects.filter(name__contains="ven")
- objects.filter(name__icontains="ven") # icontains大小写不敏感
- objects.exclude(name__icontains="ven")

range
- objects.filter(id__range=[1, 2])

其他类似
- startswith,istartswith, endswith, iendswith

order by
- objects.filter(name='seven').order_by('id') # 生序
- objects.filter(name='seven').order_by('-id') # 降序

正则匹配
- objects.get(title__regex=r'^(An?|The) +')
- objects.get(title__iregex=r'^(an?|the) +') # 不区分大小写

date
- objects.filter(pub_date__date=datetime.date(2005, 1, 1))
- objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))

year
- objects.filter(pub_date__year=2005)
- objects.filter(pub_date__year__gte=2005)

month
- objects.filter(pub_date__month=12)
- objects.filter(pub_date__month__gte=6)

day
- objects.filter(pub_date__day=3)
- objects.filter(pub_date__day__gte=3)

week_day
- objects.filter(pub_date__week_day=2)
- objects.filter(pub_date__week_day__gte=2)

hour
- objects.filter(timestamp__hour=23)
- objects.filter(time__hour=5)
- objects.filter(timestamp__hour__gte=12)

minute
- objects.filter(timestamp__minute=29)
- objects.filter(time__minute=46)
- objects.filter(timestamp__minute__gte=29)

second
- objects.filter(timestamp__second=31)
- objects.filter(time__second=2)
- objects.filter(timestamp__second__gte=31)

values作用

values() 用于查询特定字段数据并返回字典格式,适合在需要对查询结果进行进一步处理或在不需要整个对象时使用。优点是优化查询性能,简化对结果的处理。

  1. 提取特定字段
1
2
3
4
5
books = Book.objects.values('title', 'price')

# books: [ {'title': 'Book 1', 'price': 29.99},
# {'title': 'Book 2', 'price': 39.99},
# {'title': 'Book 3', 'price': 19.99}, ]
  1. 结合annotate()使用
1
2
3
4
5
6
from django.db.models import Count

author_books = Author.objects.values('name').annotate(book_count=Count('book'))

# author_books: [ {'name': 'Author 1', 'book_count': 5},
# {'name': 'Author 2', 'book_count': 8}, ]
  1. 跨表查询(连表查询)

使用values()时,可以通过双下划线 (__) 访问相关联表的字段

1
2
3
4
books = Book.objects.values('title', 'author__name')

# books: [ {'title': 'Book 1', 'author__name': 'Author 1'},
# {'title': 'Book 2', 'author__name': 'Author 2'}, ]
  1. values_list() 的扩展

返回元组列表

1
2
3
titles = Book.objects.values_list('title', flat=True)

# titles: ['Book 1', 'Book 2', 'Book 3']



ORM连表操作

连表操作通常涉及三种关系,可以将这些操作分为正向查找和反向查找两种方式

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

正向查找

从包含外键的模型出发,查询关联的其他模型数据

ForeignKey 字段在哪个表,哪个表就可以通过 外键字段__关联表字段 进行正向连表操作

1
2
3
4
5
6
7
8
9
10
11
# 一对多正向查找
user = UserInfo.objects.first()
user_type = user.user_type.name # 通过 user_type 外键查找 UserType 表的 name 字段

# 多对多正向查找
student = Student.objects.first()
courses = student.courses.all() # 获取该学生所有的课程

# 一对一正向查找
profile = UserProfile.objects.first()
username = profile.user.username # 通过 user 查找 User 表的 username 字段

反向查找

从没有外键的模型出发,查询关联的模型数据。反向查找依赖于模型中自动生成的反向管理器

反向查找可以通过 小写表名_set(例如 userinfo_set)或者通过自定义的 related_name 进行查找,跨表查询的时候可应用小写表名_字段

1
2
3
4
5
6
7
8
9
10
11
# 一对多反向查找
user_type = UserType.objects.first()
users = user_type.userinfo_set.all() # 获取所有属于该 UserType 的用户

# 多对多反向查找
course = Course.objects.first()
students = course.student_set.all() # 获取选了该课程的所有学生

# 一对一反向查找
user = User.objects.first()
profile = user.userprofile # 如果设置了 related_name,直接用 related_name 访问

跨表查询

使用 values()values_list() 结合外键字段进行跨表查询

1
2
3
4
5
# 正向跨表查询
UserInfo.objects.values('id', 'user_type__name') # 获取用户信息的ID和用户类型的名称

# 反向跨表查询
UserType.objects.values('id', 'title', 'userinfo__user') # 获取用户类型ID、名称和该类型的所有用户名

多对多关系操作

多对多关系通常涉及第三张关系表,在Django中,使用 ManyToManyField 可以自动处理这个关系表,同时也可以自定义关系表,通过through字段表明

下面是一个案例:假设我们有两个模型 BoyGirl,以及一个表示他们关系的多对多关系。

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

class Girl(models.Model):
name = models.CharField(max_length=32)

class Boy(models.Model):
name = models.CharField(max_length=32)
girls = models.ManyToManyField(Girl, through='Relationship') # 通过 Relationship 表管理多对多关系

class Relationship(models.Model):
boy = models.ForeignKey(Boy, on_delete=models.CASCADE)
girl = models.ForeignKey(Girl, on_delete=models.CASCADE)
date_started = models.DateField() # 关系开始的日期
description = models.CharField(max_length=255, null=True, blank=True) # 关系描述

下面是查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
# 正向查找:通过 Boy 查询他所有的 Girl 朋友
boy = Boy.objects.first()
girls = boy.girls.all() # 获取该男生所有的女生朋友

# 反向查找:通过 Girl 查询她的所有 Boy 朋友
girl = Girl.objects.first()
boys = girl.boy_set.all() # 获取该女生的所有男生朋友

# 使用中间表查找
girl = Girl.objects.first()
relationships = girl.relationship_set.all() # 获取该女生所有的 Relationship 记录
for rel in relationships:
print(f"{rel.girl.name}{rel.boy.name}{rel.date_started} 开始了关系")

反向查找的自定义名称

使用 related_namerelated_query_name 自定义反向查找的名称,避免使用默认的 小写表名_set

1
2
3
4
5
6
class UserInfo(models.Model):
user_type = models.ForeignKey(UserType, related_name='users', on_delete=models.CASCADE)

# 反向查找
user_type = UserType.objects.first()
users = user_type.users.all() # 使用 related_name 进行反向查找



ORM查询性能

普通查询

普通查询存在N+1次查询问题,即在查询一组对象时,先执行一次主查询,然后在后续的循环中对每个对象的外键字段再次执行单独的查询

1
2
3
obj_list = models.Love.objects.all() 
for row in obj_list: # for循环10次发送10次数据库查询请求
print(row.b.name)
  • 这段代码中,Love 表的所有记录首先被查询出来。
  • 在遍历 obj_list 时,每访问一个对象的 b 外键字段(例如 row.b.name),都会触发一次查询操作,导致多次数据库查询。

select_related 适用于一对一和一对多的外键关系。它通过在查询时执行 SQL JOIN 操作,将相关表的数据一次性查询出来,从而避免了后续的多次查询。

1
2
3
obj_list = models.Love.objects.all().select_related('b')
for row in obj_list:
print(row.b.name)
  • select_related('b') 会在查询时通过 SQL JOIN 操作将 Love 表和 b 表的数据一起查询出来。
  • 在遍历 obj_list 时,外键字段 b 的数据已经包含在查询结果中,无需再次查询数据库。
  • 适用场景:数据量较小,且关联查询的表不多的情况下,select_related 可以显著减少查询次数,提升性能。

prefetch_related 适用于多对多和一对多的外键关系。它通过先分别查询主表和外键表的数据,再在 Python 层面进行数据组合,从而避免了数据库的复杂 JOIN 操作。

1
2
3
obj_list = models.Love.objects.all().prefetch_related('b')
for row in obj_list:
print(row.b.name)
  • prefetch_related('b') 会分别查询 Love 表和 b 表的数据,然后在 Python 层面将它们组合在一起。
  • 虽然 prefetch_related 也会多次查询数据库,但它避免了 SQL JOIN 带来的性能开销,因此在处理大数据量和复杂关联关系时表现更好。
  • 适用场景:数据量大,外键关系复杂的情况下,prefetch_related 更高效。

update和 save性能比较

直接给结论:update() 的性能优于 save()update() 直接生成并执行 SQL 语句,只更新指定的字段,而 save() 会查询整个对象,并在更新时提交所有字段。因此,在需要更新单个或少量字段时,update() 更为高效。




分组和聚合查询

annotate() 分组函数

1
2
3
4
5
6
7
8
9
10
11
# 查看每一位作者出过的书中最贵的一本(按作者名分组values(),然后annotate分别取每人出过的书价格最高的)
ret = models.Book.objects.values('author__name').annotate(Max('price'))
# ret: [ {'author__name': '吴承恩', 'price__max': Decimal('234.000')},
# {'author__name': '吕不韦', 'price__max': Decimal('234.000')},
# {'author__name': '姜子牙', 'price__max': Decimal('123.000')},

#查看每个出版社出版的最便宜的一本书
ret = models.Book.objects.values('publish__name').annotate(Min('price'))
# ret: [ {'publish__name': '北大出版社', 'price__min': Decimal('67.000')},
# {'publish__name': '山西出版社', 'price__min': Decimal('34.000')},
# {'publish__name': '河北出版社', 'price__min': Decimal('123.000')},

aggregate() 聚合函数

通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.db.models import Avg,Sum,Max,Min

# 求书籍的平均价
ret = models.Book.objects.all().aggregate(Avg('price'))
# {'price__avg': 145.23076923076923}

# 参与西游记著作的作者中最老的一位作者
ret = models.Book.objects.filter(title__icontains='西游记').values('author__age').aggregate(Max('author__age'))
# {'author__age__max': 518}

# 查看根哥出过得书中价格最贵一本
ret = models.Author.objects.filter(name__contains='根').values('book__price').aggregate(Max('book__price'))
# {'book__price__max': Decimal('234.000')}



F查询、Q查询

F查询

F()可以获取对象中的字段的属性(列),并对其进行操作

1
2
3
from django.db.models import F,Q

models.Book.objects.all().update(price=F('price')+1) #对图书馆里的每一本书的价格上调1块钱

Q查询

如果多个查询条件涉及到逻辑使用 fifter并用,隔开可以表示与,但没法表示或非的关系

Q()可以使ORM的fifter()方法支持多个查询条件,使用逻辑关系(&、|、~)包含、组合到一起进行多条件查询

1
2
3
4
5
6
from django.db.models import F,Q

book = models.Book.objects.filter(title__icontains='伟',author__name__contains='伟').values('title')
book = models.Book.objects.filter(Q(title__icontains='伟') & Q(author__name__contains='伟')).values('title') #与上述方法等价

book = models.Book.objects.filter(Q(author__name__contains='伟') & ~Q(title__icontains='伟')).values('title')



ContentType表

ContentType 是 Django 内置的一个表,用于记录项目中所有已注册的模型。每个模型都有一个对应的 ContentType 实例,其中包括模型所在的应用名称和模型名称。

INSTALLED_APPS 中,django.contrib.contenttypes 是 Django 自动创建和维护 ContentType 表的关键应用。

1
2
3
4
5
6
7
8
9
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes', # 负责 ContentType 表
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
]

动态获取模型类

通过 ContentType,可以动态获取某个模型类,这在需要对不同模型进行通用处理时非常有用。

1
2
3
4
5
6
7
8
from django.contrib.contenttypes.models import ContentType

def test(request):
c = ContentType.objects.get(app_label='app01', model='boy')
print(c) # 输出模型名称 'boy'
model_class = c.model_class()
print(model_class) # 输出 'app01.models.Boy' 类
return HttpResponse('OK')

复杂的外键关系处理

有时候,一个模型可能需要关联到不同的模型,例如,一个优惠券模型可能需要关联到课程模型中的某一门课程或学位课程中的某一门课程。在这种情况下,ContentType 和通用外键 (GenericForeignKey) 可以帮助我们实现这种灵活的关联。

如下示例:优惠券与课程的关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey

class DegreeCourse(models.Model):
name = models.CharField(max_length=128, unique=True)

class Course(models.Model):
name = models.CharField(max_length=128, unique=True)

class Coupon(models.Model):
name = models.CharField(max_length=64, verbose_name="活动名称")
brief = models.TextField(blank=True, null=True, verbose_name="优惠券介绍")
content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(blank=True, null=True)
content_object = GenericForeignKey('content_type', 'object_id')
  • Coupon 模型中的 content_type 字段记录了关联模型的 ContentTypeobject_id 记录了具体对象的 ID。
  • content_object 是一个通用外键,允许你通过 content_typeobject_id 访问关联的对象。

GenericRelation 的使用

GenericRelation 用于在关联模型上定义反向关系,使得你可以通过主模型查询到所有关联的对象。

延续上面示例:查询当前课程的所有优惠券

1
2
3
4
5
6
7
8
9
from django.contrib.contenttypes.fields import GenericRelation

class DegreeCourse(models.Model):
name = models.CharField(max_length=128, unique=True)
coupons = GenericRelation('Coupon')

# 查询
d1 = DegreeCourse.objects.get(id=1)
print(d1.coupons.all()) # 查询与该课程关联的所有优惠券

创建关联对象

创建一个与特定课程关联的优惠券

1
2
3
4
5
6
7
8
# 方法1:手动指定 ContentType 和 object_id 
d1 = DegreeCourse.objects.get(id=1)
c1 = ContentType.objects.get(app_label='app01', model='degreecourse')
Coupon.objects.create(name='优惠券', brief='100', content_type=c1, object_id=d1.id)

# 方法2:使用 GenericForeignKey
d1 = DegreeCourse.objects.get(id=1)
Coupon.objects.create(name='优惠券', brief='100', content_object=d1)

总结

  • ContentType 表:用于动态记录和获取 Django 中的模型类。
  • **GenericForeignKey**:允许一个模型与多个模型建立通用的外键关系。
  • **GenericRelation**:在主模型中定义反向关系,方便查询关联对象。
  • 应用场景:非常适合处理复杂的外键关系,尤其是在模型间需要动态关联或模型数量较多的情况下。



补充

order_by() 排序查询

1
2
# 按 id 降序排序
course_list = models.Course.objects.order_by('-id')

exclude() 排除查询

1
2
# 排除 course_type 为 2 的课程
course_list = models.Course.objects.exclude(course_type=2)

按时间查询

Django ORM 支持通过时间字段进行数据查询,前提是模型中的时间字段是 DateTimeFieldDateField,且存储的数据类型为 Python 的 datetime 对象。

注意

  • 如果查询时间时出现警告,可以在 settings.py 中设置 USE_TZ = True
  • 如果时间格式显示为英文,可以在 settings.py 中设置 LANGUAGE_CODE = 'zh-Hans'

比如大于某个时间点的数据可以用双下划线的__gt,更多操作可以看前面双下划线部分

1
2
3
4
5
6
import datetime

now = datetime.datetime.now()
start = now - datetime.timedelta(hours=23, minutes=59, seconds=59)
# 查询前一天的数据
obj = models.TaskLog.objects.filter(date__gt=start)
  • 标题: Django之ORM
  • 作者: rainbowYao
  • 创建于 : 2024-08-17 01:27:30
  • 更新于 : 2024-09-18 09:22:03
  • 链接: https://redefine.ohevan.com/2024/08/17/Django之ORM/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。