Django ORM基础

参考
Django学习笔记4——Django ORM系统
刘江的博客教程
django 外键查询 一对多 通过对象查询和通过filter values 双下划线查询

一、ORM介绍

1、ORM概念

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。 
  
简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。 
  
ORM在业务逻辑层和数据库层之间充当了桥梁的作用。

2、ORM由来

字母O起源于”对象”(Object),而R则来自于”关系”(Relational)。几乎所有的软件开发过程中都会涉及到对象和关系数据库。在用户层面和业务逻辑层面,我们是面向对象的。当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系数据库中。 
  
按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多SQL语句用来增加、读取、修改、删除相关数据,而这些代码通常都是重复的。

3、ORM的优势

ORM解决的主要问题是对象和关系的映射。它通常把一个类和一个表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段。 
  
ORM提供了对数据库的映射,不用直接编写SQL代码,只需像操作对象一样从数据库操作数据。让软件开发人员专注于业务逻辑的处理,提高了开发效率。

4、ORM的劣势

ORM的缺点是会在一定程度上牺牲程序的执行效率。 
  
比较坑的是,ORM用多了SQL语句就不会写了,关系数据库相关技能退化…

5、ORM总结

ORM只是一种工具,工具确实能解决一些重复,简单的劳动。这是不可否认的。但我们不能指望某个工具能一劳永逸地解决所有问题,一些特殊问题还是需要特殊处理的。但是在整个软件开发过程中需要特殊处理的情况应该都是很少的,否则所谓的工具也就失去了它存在的意义。

二、ORM创建表

1、在mysql中手动创建mysql数据库

create databases mysite;

2、在Django项目使用MySQL数据库

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',  #连接数据库的类型
        'NAME': 'mysite',                         #数据库名
        'HOST':'127.0.0.1',                       #数据库主机
        'PORT':3306,
        "USER":'root',
        'PASSWORD':'123456',
    }
}

3、在Django项目与settings.py文件的init.py文件中写如下代码,告诉Django使用pymysql模块连接MySQL数据库

import pymysql
pymysql.install_as_MySQLdb()

4、Model
在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表。

基本情况:

  • 每个模型都是一个Python类,它是django.db.models.Model的子类。
  • 模型的每个属性都代表一个数据库字段。

Django ORM基础_第1张图片

5、创建app及注册的两种方法。
app中想要使用orm,必须先确认当前app已经注册,所以这里提一下

(1)、在创建项目的时候,直接创建app,这种方法默认已经注册了app

#pycharm会自动注册app,即 INSTALLED_APPS 中
'app01.apps.App01Config'

(2)、在terminate命令行创建: python manage.py startapp app01(app名字),亦可:pycharm菜单栏中 Tools --> run manage.py task ,然后在terminate输入命令 startapp app01(app名字),这种方法没自动注册app,需要在配置文件的 settings.py 中的 INSTALLED_APPS 中,添加

'app01'   # app名字
或者
'app01.apps.App01Config'

6、示例

(1)下面这个例子定义了一个 Person 模型,包含 first_name 和 last_name。在app下的models.py文件中,first_name 和 last_name 是模型的字段。每个字段被指定为一个类属性,每个属性映射到一个数据库列。

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

上面的 Person 模型将会像这样创建一个数据库表:

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

(2)一些说明:

  • 表myapp_person的名称是自动生成的,如果你要自定义表名,需要在model的Meta类中指定 db_table 参数,强烈建议使用小写表名,特别是使用MySQL作为后端数据库时
  • id字段是自动添加的,如果你想要指定自定义主键,只需在其中一个字段中指定 primary_key=True 即可。如果Django发现你已经明确地设置了Field.primary_key,它将不会添加自动ID列。
  • Django会根据配置文件中指定的数据库后端类型来生成相应的SQL语句,Django支持MySQL5.5及更高版本。
  • 通常,我们会将模型编写在其所属app下的models.py文件中,没有特别需求时,请坚持这个原则,不要自己给自己添加麻烦。

(3)创建了模型之后,在使用它之前,你需要先在settings文件中的INSTALLED_APPS 处,注册models.py文件所在的myapp。如果是创建项目时创建的app可能已经自动注册了,如果是手动创建的需要手动设置一下注册。

INSTALLED_APPS = [
#...
'myapp',
#...
]

(4)然后执行

python manage.py makemigrations  # 记录models的变化,将变更记录记录到 对应APP下的migrations
python manage.py migrate ## 翻译成SQL语句,去数据库执行

查看数据库可以看到创建成功了
Django ORM基础_第2张图片

7、元数据
作用:修改数据库表的默认的名称

数据库表的默认名称为 : 应用名_模型名
例:Book应用中定义BookInfo模型类,在数据库中表名为:Book_bookinfo
在模型类中定义元类Meta,用于设置元信息,使用db_table自定义表的名字

 # 书籍信息模型
 class BookInfo(models.Model):
     name = models.CharField(max_length=20) #图书名称

     class Meta: #元信息类
         db_table = 'bookinfo' #自定义表的名字

三、常用字段类型

1、字段类型的作用

  • 决定数据库中对应列的数据类型(例如:INTEGER, VARCHAR, TEXT)
  • HTML中对应的表单标签的类型,例如
  • 在admin后台和自动生成的表单中最小的数据验证需求

Django内置了许多字段类型,它们都位于django.db.models中,例如models.CharField。这些类型基本满足需求,如果还不够,你也可以自定义字段。

2、内置字段
下表列出了所有Django内置的字段类型,但不包括关系字段类型(字段名采用驼峰命名法,初学者请一定要注意)

类型 说明
AutoField 一个自动增加的整数类型字段。通常你不需要自己编写它,Django会自动帮你添加字段:id = models.AutoField(primary_key=True),这是一个自增字段,从1开始计数。如果你非要自己设置主键,那么请务必将字段设置为primary_key=True。Django在一个模型中只允许有一个自增字段,并且该字段必须为主键!
BigAutoField (1.10新增)64位整数类型自增字段,数字范围更大,从1到9223372036854775807
BigIntegerField 64位整数字段(看清楚,非自增),类似IntegerField ,-9223372036854775808 到9223372036854775807。在Django的模板表单里体现为一个textinput标签。
BinaryField 二进制数据类型。使用受限,少用。
BooleanField 布尔值类型。默认值是None。在HTML表单中体现为CheckboxInput标签。如果要接收null值,请使用NullBooleanField。
CharField 字符串类型。必须接收一个max_length参数,表示字符串长度不能超过该值。默认的表单标签是input text。最常用的filed,没有之一!
CommaSeparatedIntegerField 逗号分隔的整数类型。必须接收一个max_length参数。常用于表示较大的金额数目,例如1,000,000元。
DateField class DateField(auto_now=False, auto_now_add=False, **options)日期类型。一个Python中的datetime.date的实例。在HTML中表现为TextInput标签。在admin后台中,Django会帮你自动添加一个JS的日历表和一个“Today”快捷方式,以及附加的日期合法性验证。两个重要参数:(参数互斥,不能共存) auto_now:每当对象被保存时将字段设为当前日期,常用于保存最后修改时间。auto_now_add:每当对象被创建时,设为当前日期,常用于保存创建日期(注意,它是不可修改的)。设置上面两个参数就相当于给field添加了editable=False和blank=True属性。如果想具有修改属性,请用default参数。例子:pub_time = models.DateField(auto_now_add=True),自动添加发布时间。
DateTimeField 日期时间类型。Python的datetime.datetime的实例。与DateField相比就是多了小时、分和秒的显示,其它功能、参数、用法、默认值等等都一样。
DecimalField 固定精度的十进制小数。相当于Python的Decimal实例,必须提供两个指定的参数!参数max_digits:最大的位数,必须大于或等于小数点位数 。decimal_places:小数点位数,精度。 当localize=False时,它在HTML表现为NumberInput标签,否则是text类型。例子:储存最大不超过999,带有2位小数位精度的数,定义如下:models.DecimalField(…, max_digits=5, decimal_places=2)。
DurationField 持续时间类型。存储一定期间的时间长度。类似Python中的timedelta。在不同的数据库实现中有不同的表示方法。常用于进行时间之间的加减运算。但是小心了,这里有坑,PostgreSQL等数据库之间有兼容性问题!
EmailField 邮箱类型,默认max_length最大长度254位。使用这个字段的好处是,可以使用DJango内置的EmailValidator进行邮箱地址合法性验证。
FileField class FileField(upload_to=None, max_length=100, **options)上传文件类型,后面单独介绍。
FilePathField 文件路径类型,后面单独介绍
FloatField 浮点数类型,参考整数类型
ImageField 图像类型,后面单独介绍。
IntegerField 整数类型,最常用的字段之一。取值范围-2147483648到2147483647。在HTML中表现为NumberInput标签。
GenericIPAddressField class GenericIPAddressField(protocol=‘both’, unpack_ipv4=False, **options)[source],IPV4或者IPV6地址,字符串形式,例如192.0.2.30或者2a02:42fe::4在HTML中表现为TextInput标签。参数protocol默认值为‘both’,可选‘IPv4’或者‘IPv6’,表示你的IP地址类型。
NullBooleanField 类似布尔字段,只不过额外允许NULL作为选项之一。
PositiveIntegerField 正整数字段,包含0,最大2147483647。
PositiveSmallIntegerField 较小的正整数字段,从0到32767。
SlugField slug 是一个新闻行业的术语。一个slug就是一个某种东西的简短标签,包含字母、数字、下划线或者连接线,通常用于URLs中。可以设置max_length参数,默认为50。
SmallIntegerField 小整数,包含-32768到32767。
TextField 大量文本内容,在HTML中表现为Textarea标签,最常用的字段类型之一!如果你为它设置一个max_length参数,那么在前端页面中会受到输入字符数量限制,然而在模型和数据库层面却不受影响。只有CharField才能同时作用于两者。
TimeField 时间字段,Python中datetime.time的实例。接收同DateField一样的参数,只作用于小时、分和秒。
URLField 一个用于保存URL地址的字符串类型,默认最大长度200。
UUIDField 用于保存通用唯一识别码(Universally Unique Identifier)的字段。使用Python的UUID类。在PostgreSQL数据库中保存为uuid类型,其它数据库中为char(32)。这个字段是自增主键的最佳替代品,后面有例子展示。

四、多对一外键(ForeignKey)

1、多对一的关系,通常被称为外键。外键字段类的定义如下:

class ForeignKey(to, on_delete, **options)[source]

外键需要两个位置参数,一个是关联的模型,另一个是on_delete选项。
实际上,在目前版本中,on_delete选项也可以不设置,但Django极力反对如此,因此在Django2.0版本后,该选项会设置为必填。并且外键要定义在‘多’的一方!

2、示例

from django.db import models

#汽车类
class Car(models.Model):
    manufacturer = models.ForeignKey(
        'Manufacturer',
        on_delete=models.CASCADE,
    )
    # ...

#工厂类
class Manufacturer(models.Model):
    # ...
    pass

上面的例子中,每辆车都会有一个生产工厂,一个工厂可以生产N辆车,于是用一个外键字段manufacturer表示,并放在Car模型中。注意,此manufacturer非彼Manufacturer模型类,它是一个字段的名称。在Django的模型定义中,经常出现类似的英文单词大小写不同,一定要注意区分!

3、关联的对象在另外一个app中

如果要关联的对象在另外一个app中,可以显式的指出。下例假设Manufacturer模型存在于production这个app中,则Car模型的定义如下:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',      # 关键在这里!!
        on_delete=models.CASCADE,
    )

4、递归自己关联自己的的外键
如果要创建一个递归的外键,也就是自己关联自己的的外键,使用下面的方法:

models.ForeignKey('self', on_delete=models.CASCADE)

核心在于‘self’这个引用。什么时候需要自己引用自己的外键呢?典型的例子就是评论系统!一条评论可以被很多人继续评论,如下所示:

class Comment(models.Model):
    title = models.CharField(max_length=128)
    text = models.TextField()
    parent_comment = models.ForeignKey('self', on_delete=models.CASCADE)

注意上面的外键字段定义的是父评论,而不是子评论。为什么呢?因为外键要放在‘多’的一方!

5、外键名称

在实际的数据库后台,Django会为每一个外键添加_id后缀,并以此创建数据表里的一列。在上面的工厂与车的例子中,Car模型对应的数据表中,会有一列叫做manufacturer_id。但实际上,在Django代码中你不需要使用这个列名,除非你书写原生的SQL语句,一般我们都直接使用字段名manufacturer。

像上面示例中car表中,外键为manufacturer_id,但在orm中直接使用(汽车类示例.manufacturer)可以调用对应的工厂类实例。
Django ORM基础_第3张图片

关系字段的定义还有个小坑。在后面我们会讲到的verbose_name参数用于设置字段的别名。很多情况下,为了方便,我们都会设置这么个值,并且作为字段的第一位置参数。但是对于关系字段,其第一位置参数永远是关系对象,不能是verbose_name,一定要注意!

6、参数说明:
外键还有一些重要的参数,说明如下:

(1)on_delete
注意:这个参数在Django2.0之后,不可以省略了,需要显式的指定!这也是除了路由编写方式外,Django2和Django1.x最大的不同点之一!

当一个被外键关联的对象被删除时,Django将模仿on_delete参数定义的SQL约束执行相应操作。比如,你有一个可为空的外键,并且你想让它在关联的对象被删除时,自动设为null,可以如下定义:

user = models.ForeignKey(
    User,
    on_delete=models.SET_NULL,
    blank=True,
    null=True,
)

该参数可选的值都内置在django.db.models中,包括:

CASCADE:模拟SQL语言中的ON DELETE CASCADE约束,将定义有外键的模型对象同时删除!(该操作为当前Django版本的默认操作!)
PROTECT:阻止上面的删除操作,但是弹出ProtectedError异常
SET_NULL:将外键字段设为null,只有当字段设置了null=True时,方可使用该值。
SET_DEFAULT:将外键字段设为默认值。只有当字段设置了default参数时,方可使用。
DO_NOTHING:什么也不做。
SET():设置为一个传递给SET()的值或者一个回调函数的返回值。注意大小写。

(2)limit_choices_to
我不用ModelForm所以就记一下就好了,该参数用于限制外键所能关联的对象,只能用于Django的ModelForm(Django的表单模块)和admin后台,对其它场合无限制功能。其值可以是一个字典、Q对象或者一个返回字典或Q对象的函数调用,如下例所示:

staff_member = models.ForeignKey(
    User,
    on_delete=models.CASCADE,
    limit_choices_to={'is_staff': True},
)

这样定义,则ModelForm的staff_member字段列表中,只会出现那些is_staff=True的Users对象,这一功能对于admin后台非常有用。
可以参考下面的方式,使用函数调用:

def limit_pub_date_choices():
    return {'pub_date__lte': datetime.date.utcnow()}

# ...
limit_choices_to = limit_pub_date_choices
# ...

(3)related_name
用于关联对象反向引用模型的名称。以前面车和工厂的例子解释,就是从工厂反向关联到车的关系名称。

通常情况下,这个参数我们可以不设置,Django会默认以模型的小写加上_set作为反向关联名,比如对于工厂就是car_set,可以使用(工厂类实例.car_set.all())获取工厂生产的所有汽车类示例。如果你觉得car_set还不够直观,可以如下定义:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',      
        on_delete=models.CASCADE,
        related_name='car_producted_by_this_manufacturer',  # 看这里!!
    )

也许我定义了一个蹩脚的词,但表达的意思很清楚。以后从工厂对象反向关联到它所生产的汽车,就可以使用maufacturer.car_producted_by_this_manufacturer了。

如果你不想为外键设置一个反向关联名称,可以将这个参数设置为“+”或者以“+”结尾,如下所示:

user = models.ForeignKey(
    User,
    on_delete=models.CASCADE,
    related_name='+',
)

(4)related_query_name
反向关联查询名。用于从目标模型反向过滤模型对象的名称。
也就是说将(related_query_name.属性)作为被关联表中的一个字段来进行filter查询,例如这里的tag_name,这里的name指的是tag的,使用tag的名称来搜索文章,虽然文章中并没有tag_name这个字段。

class Tag(models.Model):
    article = models.ForeignKey(
        Article,
        on_delete=models.CASCADE,
        related_name="tags",
        related_query_name="tag",       # 注意这一行
    )
    name = models.CharField(max_length=255)

# 现在可以使用‘tag’作为查询名了,通过related_query_name查询tag名为important的文章
Article.objects.filter(tag__name="important")

(5)to_field
默认情况下,外键都是关联到被关联对象的主键上(一般为id)。如果指定这个参数,可以关联到指定的字段上,但是该字段必须具有unique=True属性,也就是具有唯一属性。

(6)db_constraint
默认情况下,这个参数被设为True,表示遵循数据库约束,这也是大多数情况下你的选择。如果设为False,那么将无法保证数据的完整性和合法性。在下面的场景中,你可能需要将它设置为False:

  • 有历史遗留的不合法数据,没办法的选择
  • 你正在分割数据表

当它为False,并且你试图访问一个不存在的关系对象时,会抛出DoesNotExist 异常。

(7)swappable
控制迁移框架的动作,如果当前外键指向一个可交换的模型。使用场景非常稀少,通常请将该参数保持默认的True。

五、多对多(ManyToManyField)

1、定义
多对多关系在数据库中也是非常常见的关系类型。比如一本书可以有好几个作者,一个作者也可以写好几本书。多对多的字段可以定义在任何的一方,请尽量定义在符合人们思维习惯的一方,但不要同时都定义。

class ManyToManyField(to, **options)[source]

多对多关系需要一个位置参数:关联的对象模型。它的用法和外键多对一基本类似。

在数据库后台,Django实际上会额外创建一张用于体现多对多关系的中间表。默认情况下,该表的名称是“多对多字段名+关联对象模型名+一个独一无二的哈希码”,例如‘author_books_9cdf4’,当然你也可以通过db_table选项,自定义表名。

2、例子
一本书可以有多个作者,每个作者也可以有多本书,这里使用多对多

# 书
class Book(models.Model):
    id = models.AutoField(primary_key=True)  # 自增id主键
    title = models.CharField(max_length=30)  # 书名

# 作者
class Author(models.Model):
    id = models.AutoField(primary_key=True)  # 自增id主键
    name = models.CharField(max_length=32)  # 作者名字
    books = models.ManyToManyField(to='Book')  # 只是ORM层面建立的一个多对多关系,不是作者表的一个字段

然后创建,可以看看到有个中间关系表 author_books,列为author_id 和 book_id
Django ORM基础_第4张图片
3、参数说明

(1)related_name
参考外键的相同参数。

(2)related_query_name
参考外键的相同参数。

(3)limit_choices_to
参考外键的相同参数。但是对于使用through参数自定义中间表的多对多字段无效。

(4)symmetrical
默认情况下,Django中的多对多关系是对称的。看下面的例子:

from django.db import models

class Person(models.Model):
    friends = models.ManyToManyField("self")

Django认为,如果我是你的朋友,那么你也是我的朋友,这是一种对称关系,Django不会为Person模型添加person_set属性用于反向关联。如果你不想使用这种对称关系,可以将symmetrical设置为False,这将强制Django为反向关联添加描述符。

(5)through(定义中间表)
如果你想自定义多对多关系的那张额外的关联表,可以使用这个参数!参数的值为一个中间模型。

最常见的使用场景是你需要为多对多关系添加额外的数据,比如两个人建立QQ好友的时间。

通常情况下,这张表在数据库内的结构是这个样子的:

中间表的id列....模型对象的id列.....被关联对象的id列
# 各行数据

如果自定义中间表并添加时间字段,则在数据库内的表结构如下:

中间表的id列....模型对象的id列.....被关联对象的id列.....时间对象列
# 各行数据

看下面的例子:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=50)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(
        Person,
        through='Membership',       ## 自定义中间表
        through_fields=('group', 'person'),
    )

class Membership(models.Model):  # 这就是具体的中间表模型
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    inviter = models.ForeignKey(
        Person,
        on_delete=models.CASCADE,
        related_name="membership_invites",
    )
    invite_reason = models.CharField(max_length=64)

上面的代码中,通过class Membership(models.Model)定义了一个新的模型,用来保存Person和Group模型的多对多关系,并且同时增加了‘邀请人’和‘邀请原因’的字段。

through参数在某些使用场景中是必须的,至关重要,请务必掌握!

(6)through_fields
接着上面的例子。Membership模型中包含两个关联Person的外键,Django无法确定到底使用哪个作为和Group关联的对象。所以,在这个例子中,必须显式的指定through_fields参数,用于定义关系。

through_fields参数接收一个二元元组(‘field1’, ‘field2’),field1是指向定义有多对多关系的模型的外键字段的名称,这里是Membership中的‘group’字段(注意大小写),另外一个则是指向目标模型的外键字段的名称,这里是Membership中的‘person’,而不是‘inviter’。

再通俗的说,就是through_fields参数指定从中间表模型Membership中选择哪两个字段,作为关系连接字段。

(7)db_table
设置中间表的名称。不指定的话,则使用默认值。

(8)db_constraint
参考外键的相同参数。

(9)swappable
参考外键的相同参数。

ManyToManyField多对多字段不支持Django内置的validators验证功能。
null参数对ManyToManyField多对多字段无效!设置null=True毫无意义。

六、多对多中间表详解

1、例子

(1)在Group模型中,通过members字段,以ManyToMany方式与Person模型建立了关系。

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name


class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person)

    def __str__(self):
        return self.name

(2)让我们到数据库内看一下实际的内容,Django为我们创建了三张数据表,其中的app1是应用名。
在这里插入图片描述
(3)然后我在数据库中添加了下面的Person对象:
Django ORM基础_第5张图片
再添加下面的Group对象:
Django ORM基础_第6张图片
让我们来看看,中间表是个什么样子的:
Django ORM基础_第7张图片
首先有一列id,这是Django默认添加的,没什么好说的。然后是Group和Person的id列,这是默认情况下,Django关联两张表的方式。如果你要设置关联的列,可以使用to_field参数。

可见在中间表中,并不是将两张表的数据都保存在一起,而是通过id的关联进行映射。

2、自定义中间表
一般情况,普通的多对多已经够用,无需自己创建第三张关系表。但是某些情况可能更复杂一点,比如如果你想保存某个人加入某个分组的时间呢?想保存进组的原因呢?

Django提供了一个through参数,用于指定中间模型,你可以将类似进组时间,邀请原因等其他字段放在这个中间模型内。例子如下:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)
    def __str__(self): 
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')
    def __str__(self): 
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()        # 进组时间
    invite_reason = models.CharField(max_length=64)  # 邀请原因

在中间表中,我们至少要编写两个外键字段,分别指向关联的两个模型。在本例中就是‘Person’和‘group’。 这里,我们额外增加了‘date_joined’字段,用于保存人员进组的时间,‘invite_reason’字段用于保存邀请进组的原因。

下面我们依然在数据库中实际查看一下(应用名为app2):
在这里插入图片描述
注意中间表的名字已经变成“app2_membership”了。
Person和Group没有变化。
Django ORM基础_第8张图片
Django ORM基础_第9张图片
但是中间表就截然不同了!它完美的保存了我们需要的内容
Django ORM基础_第10张图片

3、使用中间表

针对上面的中间表,下面是一些使用例子(以欧洲著名的甲壳虫乐队成员为例):

>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,date_joined=date(1962, 8, 16),invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>

与普通的多对多不一样,使用自定义中间表的多对多不能使用add(), create(),remove(),和set()方法来创建、删除关系

>>> # 无效
>>> beatles.members.add(john)
>>> # 无效
>>> beatles.members.create(name="George Harrison")
>>> # 无效
>>> beatles.members.set([john, paul, ringo, george])

为什么?因为上面的方法无法提供加入时间、邀请原因等中间模型需要的字段内容。唯一的办法只能是通过创建中间模型的实例来创建这种类型的多对多关联。但是,clear()方法是有效的,它能清空所有的多对多关系。

>>> # 甲壳虫乐队解散了
>>> beatles.members.clear()
>>> # 删除了中间模型的对象
>>> Membership.objects.all()
<QuerySet []>

一旦你通过创建中间模型实例的方法建立了多对多的关联,你立刻就可以像普通的多对多那样进行查询操作

# 查找组内有Paul这个人的所有的组(以Paul开头的名字)
>>> Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<Group: The Beatles>]>

可以使用中间模型的属性进行查询:

# 查找甲壳虫乐队中加入日期在1961年1月1日之后的成员
>>> Person.objects.filter(
... group__name='The Beatles',
... membership__date_joined__gt=date(1961,1,1))
<QuerySet [<Person: Ringo Starr]>

对于中间表,有一点要注意(在前面章节已经介绍过,再次重申一下),默认情况下,中间模型只能包含一个指向源模型的外键关系,上面例子中,也就是在Membership中只能有Person和Group外键关系各一个,不能多。否则,你必须显式的通过ManyToManyField.through_fields参数指定关联的对象。参考下面的例子:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=50)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(
    Person,
    through='Membership',
    through_fields=('group', 'person'),
    )

class Membership(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    inviter = models.ForeignKey(
    Person,
    on_delete=models.CASCADE,
    related_name="membership_invites",
    )
    invite_reason = models.CharField(max_length=64)

七、一对一(OneToOneField)

1、一对一关系类型的定义如下:

class OneToOneField(to, on_delete, parent_link=False, **options)[source]

从概念上讲,一对一关系非常类似具有unique=True属性的外键关系,但是反向关联对象只有一个。这种关系类型多数用于当一个模型需要从别的模型扩展而来的情况。比如,Django自带auth模块的User用户表,如果你想在自己的项目里创建用户模型,又想方便的使用Django的认证功能,那么一个比较好的方案就是在你的用户模型里,使用一对一关系,添加一个与auth模块User模型的关联字段。

该关系的第一位置参数为关联的模型,其用法和前面的多对一外键一样。

如果你没有给一对一关系设置related_name参数,Django将使用当前模型的小写名作为默认值。

2、看下面的例子:

from django.conf import settings
from django.db import models

# 两个字段都使用一对一关联到了Django内置的auth模块中的User模型
class MySpecialUser(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )
    supervisor = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='supervisor_of',
    )

这样下来,你的User模型将拥有下面的属性:

>>> user = User.objects.get(pk=1)
>>> hasattr(user, 'myspecialuser')
True
>>> hasattr(user, 'supervisor_of')
True

OneToOneField一对一关系拥有和多对一外键关系一样的额外可选参数,只是多了一个parent_link参数。

3、跨模块的模型:

有时候,我们关联的模型并不在当前模型的文件内,没关系,就像我们导入第三方库一样的从别的模块内导入进来就好,如下例所示:

from django.db import models
from geography.models import ZipCode

class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(
        ZipCode,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

八、字段的参数

所有的模型字段都可以接收一定数量的参数,比如CharField至少需要一个max_length参数。下面的这些参数是所有字段都可以使用的,并且是可选的。

1、null
该值为True时,Django在数据库用NULL保存空值。默认值为False。对于保存字符串类型数据的字段,请尽量避免将此参数设为True,那样会导致两种‘没有数据’的情况,一种是NULL,另一种是‘空字符串’。

2、blank
True时,字段可以为空。默认False。和null参数不同的是,null是纯数据库层面的,而blank是验证相关的,它与表单验证是否允许输入框内为空有关,与数据库无关。所以要小心一个null为False,blank为True的字段接收到一个空值可能会出bug或异常。

3、choices
用于页面上的选择框标签,需要先提供一个二维的二元元组,第一个元素表示存在数据库内真实的值,第二个表示页面上显示的具体内容。在浏览器页面上将显示第二个元素的值。例如:

    YEAR_IN_SCHOOL_CHOICES = (
        ('FR', 'Freshman'),
        ('SO', 'Sophomore'),
        ('JR', 'Junior'),
        ('SR', 'Senior'),
        ('GR', 'Graduate'),
    )

一般来说,最好将选项定义在类里,并取一个直观的名字,如下所示:

from django.db import models

class Student(models.Model):
    FRESHMAN = 'FR'
    SOPHOMORE = 'SO'
    JUNIOR = 'JR'
    SENIOR = 'SR'
    YEAR_IN_SCHOOL_CHOICES = (
        (FRESHMAN, 'Freshman'),
        (SOPHOMORE, 'Sophomore'),
        (JUNIOR, 'Junior'),
        (SENIOR, 'Senior'),
    )
    year_in_school = models.CharField(
        max_length=2,
        choices=YEAR_IN_SCHOOL_CHOICES,
        default=FRESHMAN,
    )

    def is_upperclass(self):
        return self.year_in_school in (self.JUNIOR, self.SENIOR)

要获取一个choices的第二元素的值,可以使用get_FOO_display()方法,其中的FOO用字段名代替。对于下面的例子:

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
    ('S', 'Small'),
    ('M', 'Medium'),
    ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)

使用方法:

>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
db_column

该参数用于定义当前字段在数据表内的列名。如果未指定,Django将使用字段名作为列名。

4、db_index
该参数接收布尔值。如果为True,数据库将为该字段创建索引。

5、db_tablespace
用于字段索引的数据库表空间的名字,前提是当前字段设置了索引。默认值为工程的DEFAULT_INDEX_TABLESPACE设置。如果使用的数据库不支持表空间,该参数会被忽略。

6、default
字段的默认值,可以是值或者一个可调用对象。如果是可调用对象,那么每次创建新对象时都会调用。设置的默认值不能是一个可变对象,比如列表、集合等等。lambda匿名函数也不可用于default的调用对象,因为匿名函数不能被migrations序列化。

注意:在某种原因不明的情况下将default设置为None,可能会引发intergyerror:not null constraint failed,即非空约束失败异常,导致python manage.py migrate失败,此时可将None改为False或其它的值,只要不是None就行。

7、editable
如果设为False,那么当前字段将不会在admin后台或者其它的ModelForm表单中显示,同时还会被模型验证功能跳过。参数默认值为True。

8、error_messages
用于自定义错误信息。参数接收字典类型的值。字典的键可以是null、 blank、 invalid、 invalid_choice、 unique和unique_for_date其中的一个。

9、help_text
额外显示在表单部件上的帮助文本。使用时请注意转义为纯文本,防止脚本攻击。

10、primary_key
如果你没有给模型的任何字段设置这个参数为True,Django将自动创建一个AutoField自增字段,名为‘id’,并设置为主键。也就是id = models.AutoField(primary_key=True)。

如果你为某个字段设置了primary_key=True,则当前字段变为主键,并关闭Django自动生成id主键的功能。

primary_key=True隐含null=False和unique=True的意思。一个模型中只能有一个主键字段!

另外,主键字段不可修改,如果你给某个对象的主键赋个新值实际上是创建一个新对象,并不会修改原来的对象。

from django.db import models
class Fruit(models.Model):
    name = models.CharField(max_length=100, primary_key=True)
>>> fruit = Fruit.objects.create(name='Apple')
>>> fruit.name = 'Pear'
>>> fruit.save()
>>> Fruit.objects.values_list('name', flat=True)
['Apple', 'Pear']
unique

设为True时,在整个数据表内该字段的数据不可重复。

注意:

  • 对于ManyToManyField和OneToOneField关系类型,该参数无效。
  • 当unique=True时,db_index参数无须设置,因为unqiue隐含了索引。
  • 自1.11版本后,unique参数可以用于FileField字段。

11、unique_for_date
日期唯一。可能不太好理解。举个栗子,如果你有一个名叫title的字段,并设置了参数unique_for_date=“pub_date”,那么Django将不允许有两个模型对象具备同样的title和pub_date。有点类似联合约束。

12、unique_for_month
同上,只是月份唯一。

13、unique_for_year
同上,只是年份唯一。

14、verbose_name
为字段设置一个人类可读,更加直观的别名。

对于每一个字段类型,除了ForeignKey、ManyToManyField和OneToOneField这三个特殊的关系类型,其第一可选位置参数都是verbose_name。如果没指定这个参数,Django会利用字段的属性名自动创建它,并将下划线转换为空格。

下面这个例子的verbose name是"person’s first name":

first_name = models.CharField("person's first name", max_length=30)

下面这个例子的verbose name是"first name":

first_name = models.CharField(max_length=30)

对于外键、多对多和一对一字字段,由于第一个参数需要用来指定关联的模型,因此必须用关键字参数verbose_name来明确指定。如下:

poll = models.ForeignKey(
    Poll,
    on_delete=models.CASCADE,
    verbose_name="the related poll",
    )
sites = models.ManyToManyField(Site, verbose_name="list of sites")
    place = models.OneToOneField(
    Place,
    on_delete=models.CASCADE,
    verbose_name="related place",
)

另外,你无须大写verbose_name的首字母,Django自动为你完成这一工作。

15、validators
运行在该字段上的验证器的列表。

九、Django 执行脚本使用ORM

1、Django在命令行下执行的脚本使用ORM

如果你想通过自己创建的python文件在django项目中使用django的models,那么就需要在脚本开头添加一些设置,如下

import os,django,sys

#对于模块和自己写的脚本不在同一个目录下,在脚本开头加sys.path.append('引用模块的地址')
#这里如果你的脚本已经在根目录下的话则不用设置,否则需要使用sys.path.append()从脚本位置定位到项目根目录下,或者直接使用绝对路径
#不然会在Linux下执行脚本会报ModuleNotFoundError: No module named 'xxx'的错误,在window下使用pycharm不会,可能pycharm会帮我们配了吧。。
#例如我的脚本在根目录下的某个子文件夹里,则使用sys.path.append('../') 
sys.path.append('根目录下') 

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project_name.settings")# project_name 项目名称这里需要改成你自己的项目名
django.setup()

#从app中导入模型类,然后便可以操作了
from app01.models import Log
logs = Log.objects.all()
print(logs)

2、本节的下面内容基于如下的一个博客应用模型,我这里在我的app01.models下添加模型

#博客类
class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):              # __unicode__ on Python 2
        return self.name

#作者类
class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):              # __unicode__ on Python 2
        return self.name

#博客信息类
class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)     #多对一Blog博客外键,每一个信息类实例对应唯一一个博客类实例,而一篇博客可能有多个信息类实例
    authors = models.ManyToManyField(Author)  #多对多关系,每个博客信息可能对应多个作者,而每个作者也可以有多篇的博客信息

    headline = models.CharField(max_length=255)   #标题
    body_text = models.TextField()           #内容
    pub_date = models.DateField()            #发布时间
    mod_date = models.DateField()            #修改时间
    n_comments = models.IntegerField()       #评论数
    n_pingbacks = models.IntegerField()      #引用数
    rating = models.IntegerField()           #浏览量

    def __str__(self):              # __unicode__ on Python 2
        return self.headline

然后执行命令创建表

python manage.py makemigrations  # 记录models的变化,将变更记录记录到 对应APP下的migrations
python manage.py migrate ## 翻译成SQL语句,去数据库执行

十、创建对象和保存对象

1、在app01下建ormtest.py一个脚本

#设置django环境,后面的代码就不放这部分了
import os,django,sys
sys.path.append('../')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")# project_name 项目名称这里需要改成你自己的项目名
django.setup()

from app01.models import Blog
b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
b.save()

执行脚本后可以看到有创建的对象
Django ORM基础_第11张图片

如果想要一行代码完成上面的操作,请使用create()方法,它可以省略save的步骤:

b = Blog.objects.create(name='Beatles Blog2', tagline='All the latest Beatles news.')

执行后也有创建的对象
在这里插入图片描述
2、保存对象
使用save()方法,保存对数据库内已有对象的修改。例如我这里获取Blog类主键为1的对象,然后修改name

blog_obj=Blog.objects.get(pk=1)
blog_obj.name="New name"
blog_obj.save()

修改成功
Django ORM基础_第12张图片

在后台,这会运行一条SQL的UPDATE语句。如果你不显式地调用save()方法,Django不会立刻将该操作反映到数据库中。

3、创建和保存外键

保存一个外键字段和保存普通字段没什么区别,只是要注意值的类型要正确。
(1)如下例子,我这里先创建了一个Entry类的实例,创建的时候使用 blog=blog_obj1,直接将获取的实例给entry类的blog属性

from  app01.models import Entry,Author,Blog
import datetime

datetoday=datetime.date.today()
#获取blog类主键为1的实例
blog_obj1=Blog.objects.get(pk=1)
#创建entry实例
entry_obj=Entry.objects.create(blog=blog_obj1,headline="标题",body_text="内容",pub_date=datetoday,mod_date=datetoday,n_comments=1,n_pingbacks=1,rating=2)

执行脚本后查看,可以看到这里的外键在数据库中实际上是blog_id,指向blog表中的主键,而在orm中我们不用管是哪个字段,创建的时候直接使用(blog=blog实例),在修改的时候使用entry实例.blog=blog实例,orm中外键基于类实例来创建修改。
在这里插入图片描述

(2)修改保存外键,这里获取id=1的entry实例和主键为2的blog实例,修改entry实例的blog外键

from  app01.models import Entry,Author,Blog
blog_obj2=Blog.objects.get(pk=2)
entry_obj=Entry.objects.get(id=1)
entry_obj.blog=blog_obj2
entry_obj.save()

运行脚本后查看可以看到,blog_id这个外键所指向的blog主键变为2了
在这里插入图片描述

4、创建和保存多对多字段
多对多字段的保存稍微有点区别,需要调用一个add()方法,而不是直接给属性赋值,但它不需要调用save方法。因为多对多字段其实是另外建了一个关系表,例如这里的例子中。如下例所示:

from app01.models import Author
#创建一个作者实例
joe = Author.objects.create(name="Joe")
#获取entry信息示例
entry_obj=Entry.objects.get(id=1)
#使用add设置多对多字段
entry_obj.authors.add(joe)

执行脚本后查看表
在这里插入图片描述

在一行语句内,可以同时添加多个对象到多对多的字段,如下所示:

#创建作者实例
author_john = Author.objects.create(name="John")
author_paul = Author.objects.create(name="Paul")
author_george = Author.objects.create(name="George")
author_ringo = Author.objects.create(name="Ringo")

#获取entry实例
entry_obj=Entry.objects.get(id=1)
#添加多对多关系
entry_obj.authors.add(author_john,author_paul,author_george,author_ringo)

执行脚本后查看
Django ORM基础_第13张图片
如果你指定或添加了错误类型的对象,Django会抛出异常。

十一、检索对象

  • 想要从数据库内检索对象,你需要基于模型类,通过管理器(Manager)构造一个查询结果集(QuerySet)。

  • 每个QuerySet代表一些数据库对象的集合。它可以包含零个、一个或多个过滤器(filters)。Filters缩小查询结果的范围。在SQL语法中,一个QuerySet相当于一个SELECT语句,而filter则相当于WHERE或者LIMIT一类的子句。

  • 通过模型的Manager获得QuerySet,每个模型至少具有一个Manager,默认情况下,它被称作objects,可以通过模型类直接调用它,但不能通过模型类的实例调用它,以此实现“表级别”操作和“记录级别”操作的强制分离。如下所示:

>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances."

1.、检索所有对象

使用all()方法,可以获取某张表的所有记录。

#获取结果集对象,结果集里面放着类实例
all_entries=Entry.objects.all()
#遍历所有实例,通过(类实例.属性)获取具体属性
for entry_obj in all_entries:
    print(entry_obj.headline)
    print(entry_obj.body_text)
    print(entry_obj.pub_date)
    print(entry_obj.mod_date)

执行结果
Django ORM基础_第14张图片
2、过滤对象

(1)有两个方法可以用来过滤QuerySet的结果,分别是:

  • filter(**kwargs):返回一个根据指定参数查询出来的QuerySet
  • exclude(**kwargs):返回除了根据指定参数查询出来结果的QuerySet,其中,**kwargs参数的格式必须是Django设置的一些字段格式。

例如:

Entry.objects.filter(pub_date__year=2020)

它等同于:

Entry.objects.all().filter(pub_date__year=2020)

(2)链式过滤
filter和exclude的结果依然是个QuerySet,因此它可以继续被filter和exclude,这就形成了链式过滤:

>>> Entry.objects.filter(
...     headline__startswith='What'
... ).exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(
...     pub_date__gte=datetime(2005, 1, 30)
... )

这里需要注意的是,当在进行跨关系的链式过滤时,结果可能和你想象的不一样,参考下面的跨多值关系查询

  • 被过滤的QuerySets都是唯一的:

每一次过滤,你都会获得一个全新的QuerySet,它和之前的QuerySet没有任何关系,可以完全独立的被保存,使用和重用。例如:

q1 = Entry.objects.filter(headline__startswith="What")
q2 = q1.exclude(pub_date__gte=datetime.date.today())
q3 = q1.filter(pub_date__gte=datetime.date.today())

例子中的q2和q3虽然由q1得来,是q1的子集,但是都是独立自主存在的。同样q1也不会受到q2和q3的影响。

  • QuerySets都是懒惰的:

一个创建QuerySets的动作不会立刻导致任何的数据库行为。你可以不断地进行filter动作一整天,Django不会运行任何实际的数据库查询动作,直到QuerySets被提交(evaluated)。

简而言之就是,只有碰到某些特定的操作,Django才会将所有的操作体现到数据库内,否则它们只是保存在内存和Django的层面中。这是一种提高数据库查询效率,减少操作次数的优化设计。看下面的例子:

q = Entry.objects.filter(headline__startswith="What")
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains="food")
print(q)

上面的例子,看起来执行了3次数据库访问,实际上只是在print语句时才执行1次访问。通常情况,QuerySets的检索不会立刻执行实际的数据库查询操作,直到出现类似print的请求,也就是所谓的evaluated。

3、检索单一对象

(1)简介
filter方法始终返回的是QuerySets,那怕只有一个对象符合过滤条件,返回的也是包含一个对象的QuerySets,这是一个集合类型对象,你可以简单的理解为Python列表,可迭代可循环可索引。

如果你确定你的检索只会获得一个对象,那么你可以使用Manager的get()方法来直接返回这个对象。可以直接获得类实例对象,然后通过(类实例.属性)获取具体属性,在get方法中你可以使用任何filter方法中的查询参数,用法也是一模一样。

one_entry = Entry.objects.get(pk=1)
print(one_entry.headline)

(2)注意

使用get()方法和使用filter()方法然后通过[0]的方式分片,有着不同的地方。看似两者都是获取单一对象。但是,如果在查询时没有匹配到对象,那么get()方法将抛出DoesNotExist异常。这个异常是模型类的一个属性,在上面的例子中,如果不存在主键为1的Entry对象,那么Django将抛出Entry.DoesNotExist异常。

类似地,在使用get()方法查询时,如果结果超过1个,则会抛出MultipleObjectsReturned异常,这个异常也是模型类的一个属性。

所以:get()方法要慎用!

4、其它QuerySet方法
大多数情况下,需要从数据库中查找对象时,使用all()、 get()、filter() 和exclude()就行。针对QuerySet的方法还有很多,都是一些相对高级的用法。

5、QuerySet使用限制

(1)使用类似Python对列表进行切片的方法可以对QuerySet进行范围取值。它相当于SQL语句中的LIMIT和OFFSET子句。参考下面的例子:

Entry.objects.all()[:5]      # 返回前5个对象
Entry.objects.all()[5:10]    # 返回第6个到第10个对象

注意:不支持负索引!例如 Entry.objects.all()[-1]是不允许的

(2)通常情况,切片操作会返回一个新的QuerySet,并且不会被立刻执行。但是有一个例外,那就是指定步长的时候,查询操作会立刻在数据库内执行,如下:

Entry.objects.all()[:10:2]

(3)若要获取单一的对象而不是一个列表(例如,SELECT foo FROM bar LIMIT 1),可以简单地使用索引而不是切片。例如,下面的语句返回数据库中根据标题排序后的第一条Entry:

Entry.objects.order_by('headline')[0]

它相当于:

Entry.objects.order_by('headline')[0:1].get()

(4)注意:如果没有匹配到对象,那么第一种方法会抛出IndexError异常,而第二种方式会抛出DoesNotExist异常。

也就是说在使用get和切片的时候,要注意查询结果的元素个数。

6.、字段查询

(1)字段查询其实就是filter()、exclude()和get()等方法的关键字参数。 其基本格式是:field__lookuptype=value,注意其中是双下划线。 例如:

Entry.objects.filter(pub_date__lte='2006-01-01')
#相当于:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

其中的字段必须是模型中定义的字段之一。但是有一个例外,那就是ForeignKey字段,你可以为其添加一个“_id”后缀(单下划线)。这种情况下键值是外键模型的主键原生值。例如:

>>> Entry.objects.filter(blog_id=4)

如果你传递了一个非法的键值,查询函数会抛出TypeError异常。

(2)Django的数据库API支持20多种查询类型,下面介绍一些常用的:

  • exact:默认类型。如果你不提供查询类型,或者关键字参数不包含一个双下划线,那么查询类型就是这个默认的exact。
>>> Entry.objects.get(headline__exact="Cat bites dog")
# 相当于
# SELECT ... WHERE headline = 'Cat bites dog';
# 下面两个相当
>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)         # __exact is implied
  • iexact:不区分大小写。
>>> Blog.objects.get(name__iexact="beatles blog")
# 匹配"Beatles Blog", "beatles blog",甚至"BeAtlES blOG".
  • contains:表示包含的意思!大小写敏感!
Entry.objects.get(headline__contains='Lennon')
# 相当于
# SELECT ... WHERE headline LIKE '%Lennon%';
# 匹配'Today Lennon honored',但不匹配'today lennon honored'
  • icontains:contains的大小写不敏感模式。

  • startswith和endswith:以什么开头和以什么结尾。大小写敏感!

  • istartswith和iendswith:是不区分大小写的模式。

7、跨越关系查询

(1)Django提供了强大并且直观的方式解决跨越关联的查询,它在后台自动执行包含JOIN的SQL语句。要跨越某个关联,只需使用关联的模型字段名称,并使用双下划线分隔,直至你想要的字段(可以链式跨越,无限跨度)。例如:

# 返回所有Blog的name为'Beatles Blog'的Entry对象
# 一定要注意,返回的是Entry对象,而不是Blog对象。
# objects前面用的是哪个class,返回的就是哪个class的对象。
Entry.objects.filter(blog__name='Beatles Blog')

反之亦然,如果要引用一个反向关联,只需要使用模型的小写名!

# 获取所有的Blog对象,前提是它所关联的Entry的headline包含'Lennon'
>>> Blog.objects.filter(entry__headline__contains='Lennon')

(2) 如果你在多级关联中进行过滤而且其中某个中间模型没有满足过滤条件的值,Django将把它当做一个空的(所有的值都为NULL)但是合法的对象,不会抛出任何异常或错误。例如,在下面的过滤器中:

Blog.objects.filter(entry__authors__name='Lennon')

如果Entry中没有关联任何的author,那么它将当作其没有name,而不会因为没有author 引发一个错误。通常,这是比较符合逻辑的处理方式。唯一可能让你困惑的是当你使用isnull的时候:

Blog.objects.filter(entry__authors__name__isnull=True)

这将返回Blog对象,它关联的entry对象的author字段的name字段为空,以及Entry对象的author字段为空。如果你不需要后者,你可以这样写:

Blog.objects.filter(entry__authors__isnull=False,entry__authors__name__isnull=True)

(3)跨越多值的关系查询尽量使用Q查询
最基本的filter和exclude的关键字参数只有一个,这种情况很好理解。但是当关键字参数有多个,且是跨越外键或者多对多的情况下,那么就比较复杂,让人迷惑了。

(1)我们看下面的例子:

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

这是一个跨外键、两个过滤参数的查询。此时我们理解两个参数之间属于-与“and”的关系,也就是说,过滤出来的BLog对象对应的entry对象必须同时满足上面两个条件。这点很好理解。也就是说上面要求至少有一个entry同时满足两个条件。

(2)但是,看下面的用法:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

把两个参数拆开,放在两个filter调用里面,按照我们前面说过的链式过滤,这个结果应该和上面的例子一样。可实际上,它不一样,Django在这种情况下,将两个filter之间的关系设计为-或“or”,这真是让人头疼。

多对多关系下的多值查询和外键foreignkey的情况一样。

(3)但是,更头疼的来了,exclude的策略设计的又和filter不一样!

Blog.objects.exclude(entry__headline__contains='Lennon',entry__pub_date__year=2008,)

这会排除headline中包含“Lennon”的Entry和在2008年发布的Entry,中间是一个-和“or”的关系!
那么要排除同时满足上面两个条件的对象,该怎么办呢?看下面:

Blog.objects.exclude(
entry=Entry.objects.filter(
    headline__contains='Lennon',
    pub_date__year=2008,
),
)

(有没有很坑爹的感觉?所以,建议在碰到跨关系的多值查询时,尽量使用Q查询)

8、使用F表达式引用模型的字段

可以看 Django Q和F查询

到目前为止的例子中,我们都是将模型字段与常量进行比较。但是,如果你想将模型的一个字段与同一个模型的另外一个字段进行比较该怎么办?使用Django提供的F表达式!

(1)例如,为了查找comments数目多于pingbacks数目的Entry,可以构造一个F()对象来引用pingback数目,并在查询中使用该F()对象:

from django.db.models import F
Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

(2)Django支持对F()对象进行加、减、乘、除、取模以及幂运算等算术操作。两个操作数可以是常数和其它F()对象。例如查找comments数目比pingbacks两倍还要多的Entry,我们可以这么写:

 Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

(3)为了查询rating比pingback和comment数目总和要小的Entry,我们可以这么写:

Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

(4)你还可以在F()中使用双下划线来进行跨表查询。例如,查询author的名字与blog名字相同的Entry:

 Entry.objects.filter(authors__name=F('blog__name'))

(5)对于date和date/time字段,还可以加或减去一个timedelta对象。下面的例子将返回发布时间超过3天后被修改的所有Entry:

from datetime import timedelta
Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

(6)F()对象还支持.bitand()、.bitor()、.bitrightshift()和.bitleftshift()4种位操作,例如:

F('somefield').bitand(16)

(7)对数据库中的所有的商品,在原价格的基础上涨价10元

from django.db.models import F
from app01.models import Book
Book.objects.update(price=F("price")+20)  # 对于book表中每本书的价格都在原价格的基础上增加20元

9、 主键的快捷查询方式:pk
pk就是primary key的缩写。通常情况下,一个模型的主键为“id”,所以下面三个语句的效果一样:

Blog.objects.get(id__exact=14) # Explicit form
Blog.objects.get(id=14) # __exact is implied
Blog.objects.get(pk=14) # pk implies id__exact

可以联合其他类型的参数:

# Get blogs entries with id 1, 4 and 7
Blog.objects.filter(pk__in=[1,4,7])
# Get all blog entries with id > 14
Blog.objects.filter(pk__gt=14)

可以跨表操作:

Entry.objects.filter(blog__id__exact=3) 
Entry.objects.filter(blog__id=3) 
Entry.objects.filter(blog__pk=3)

10、在LIKE语句中转义百分符号和下划线
在原生SQL语句中%符号有特殊的作用。Django帮你自动转义了百分符号和下划线,你可以和普通字符一样使用它们,如下所示:

>>> Entry.objects.filter(headline__contains='%')
# 它和下面的一样
# SELECT ... WHERE headline LIKE '%\%%';

11、缓存与查询集

  • 每个QuerySet都包含一个缓存,用于减少对数据库的实际操作。理解这个概念,有助于你提高查询效率。

  • 对于新创建的QuerySet,它的缓存是空的。当QuerySet第一次被提交后,数据库执行实际的查询操作,Django会把查询的结果保存在QuerySet的缓存内,随后的对于该QuerySet的提交将重用这个缓存的数据。

(1)要想高效的利用查询结果,降低数据库负载,你必须善于利用缓存。看下面的例子,这会造成2次实际的数据库操作,加倍数据库的负载,同时由于时间差的问题,可能在两次操作之间数据被删除或修改或添加,导致脏数据的问题:

print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()])

(2)为了避免上面的问题,好的使用方式如下,这只产生一次实际的查询操作,并且保持了数据的一致性

queryset = Entry.objects.all()
print([p.headline for p in queryset]) # 提交查询
print([p.pub_date for p in queryset]) # 重用查询缓存

(3)有一些操作不会缓存QuerySet,例如切片和索引。这就导致这些操作没有缓存可用,每次都会执行实际的数据库查询操作。例如:

queryset = Entry.objects.all()
print(queryset[5]) # 查询数据库
print(queryset[5]) # 再次查询数据库

(4)但是,如果已经遍历过整个QuerySet,那么就相当于缓存过,后续的操作则会使用缓存,例如:

queryset = Entry.objects.all()
[entry for entry in queryset] # 查询数据库
print(queryset[5]) # 使用缓存
print(queryset[5]) # 使用缓存

(5)下面的这些操作都将遍历QuerySet并建立缓存:

[entry for entry in queryset]
bool(queryset)
entry in queryset
list(queryset)

注意:简单的打印QuerySet并不会建立缓存,因为__repr__()调用只返回全部查询集的一个切片。

四、使用Q对象进行复杂查询

详细解释可以看Django中F查询及Q查询应用

普通filter函数里的条件都是“and”逻辑,如果你想实现“or”逻辑怎么办?用Q查询!

(1)Q来自django.db.models.Q,用于封装关键字参数的集合,可以作为关键字参数用于filter、exclude和get等函数。 例如:

from django.db.models import Q
Q(question__startswith='What')

(2)可以使用“&”或者“|”或“~”来组合Q对象,分别表示与或非逻辑。它将返回一个新的Q对象。

Q(question__startswith='Who')|Q(question__startswith='What')
# 这相当于:
WHERE question LIKE 'Who%' OR question LIKE 'What%'

(3)你也可以这么使用,默认情况下,以逗号分隔的都表示AND关系:

Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),Q(question__startswith='Who')
)
# 它相当于
# SELECT * from polls WHERE question LIKE 'Who%' AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

(4)当关键字参数和Q对象组合使用时,Q对象必须放在前面,如下例子:

Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),question__startswith='Who',)

如果关键字参数放在Q对象的前面,则会报错。

(5)Q查询可以组合使用 “&”, “|” 操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象,Q对象可以用 “~” 操作符放在前面表示否定,也可允许否定与不否定形式的组合。Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面。

from django.db.models import Q
 
print(Book.objects.filter(Q(id=3))[0])  # 因为获取的结果是一个QuerySet,所以使用下标的方式获取结果
print(Book.objects.filter(Q(id=3)|Q(title="Go"))[0])  # 查询id=3或者标题是“Go”的书
print(Book.objects.filter(Q(price__gte=70)&Q(title__startswith="J")))  # 查询价格大于等于70并且标题是“J”开头的书
print(Book.objects.filter(Q(title__startswith="J") & ~Q(id=3)))  # 查询标题是“J”开头并且id不是3的书
print(Book.objects.filter(Q(price=70)|Q(title="Python"), publication_date="2017-09-26"))  # Q对象可以与关键字参数查询一起使用,必须把普通关键字查询放到Q对象查询的后面

(6)也可以通过将Q对象实例化来然后增加各个条件之间的关系,而且这种写法用在你不知道用户到底会传入多少个参数的时候很方便,即当参数为变量时就很好用了,可以遍历条件然后append进Q对象实例中。例如下面将Q对象实例化,添加条件后再进行查询。

from django.db.models import Q
 
con = Q()
q1 = Q()
q1.connector = "AND"
q1.children.append(("email", "[email protected]"))
q1.children.append(("password", "abc123"))
 
q2 = Q()
q2.connector = "AND"
q2.children.append(("username", "abc"))
q2.children.append(("password", "xyz123"))
 
con.add(q1, "OR")
con.add(q2, "OR")
 
obj = models.UserInfo.objects.filter(con).first()
 
# 查询[email protected]和password=abc123 或者 username=abc和password=xyz123的用户信息

十一、比较对象

要比较两个模型实例,只需要使用python提供的双等号比较符就可以了。在后台,其实比较的是两个实例的主键的值。下面两种方法是等同的:

some_entry == other_entry
some_entry.id == other_entry.id

如果模型的主键不叫做“id”也没关系,后台总是会使用正确的主键名字进行比较,例如,如果一个模型的主键的名字是“name”,那么下面是相等的:

some_obj == other_obj
some_obj.name == other_obj.name

十二、删除对象

(1)删除对象使用的是对象的delete()方法。该方法将返回被删除对象的总数量和一个字典,字典包含了每种被删除对象的类型和该类型的数量。如下所示:

e.delete()
(1, {'weblog.Entry': 1})

(2)也可以批量删除。每个QuerySet都有一个delete()方法,它能删除该QuerySet的所有成员。例如:

Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

(3)需要注意的是,有可能不是每一个对象的delete方法都被执行。如果你改写了delete方法,为了确保对象被删除,你必须手动迭代QuerySet进行逐一删除操作。

(4)当Django删除一个对象时,它默认使用SQL的ON DELETE CASCADE约束,也就是说,任何有外键指向要删除对象的对象将一起被删除。例如:

b = Blog.objects.get(pk=1)
# 下面的动作将删除该条Blog和所有的它关联的Entry对象
b.delete()

这种级联的行为可以通过的ForeignKey的on_delete参数自定义

(5)注意,delete()是唯一没有在管理器上暴露出来的方法。这是刻意设计的一个安全机制,用来防止你意外地请求类似Entry.objects.delete()的动作,而不慎删除了所有的条目。如果你确实想删除所有的对象,你必须明确地请求一个完全的查询集,像下面这样:

Entry.objects.all().delete()

十三、复制模型实例

PS:这部分没太看懂,等需要用到的时候再研究下吧

1、虽然没有内置的方法用于复制模型的实例,但还是很容易创建一个新的实例并将原实例的所有字段都拷贝过来。最简单的方法是将原实例的pk设置为None,这会创建一个新的实例copy。示例如下:

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
#
blog.pk = None
blog.save() # blog.pk == 2

2、但是在使用继承的时候,情况会变得复杂,如果有下面一个Blog的子类:

class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3

基于继承的工作机制,你必须同时将pk和id设为None:

django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4

3、对于外键和多对多关系,更需要进一步处理。例如,Entry有一个ManyToManyField到Author。 复制条目后,您必须为新条目设置多对多关系,像下面这样:

entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors.set(old_authors)

4、对于OneToOneField,还要复制相关对象并将其分配给新对象的字段,以避免违反一对一唯一约束。 例如,假设entry已经如上所述重复:

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail.entry = entry
detail.save()

十四、批量更新对象

1、使用update()方法可以批量为QuerySet中所有的对象进行更新操作。

# 更新所有2007年发布的entry的headline
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

2、只可以对普通字段和ForeignKey字段使用这个方法。若要更新一个普通字段,只需提供一个新的常数值。若要更新ForeignKey字段,需设置新值为你想指向的新模型实例。例如:

b = Blog.objects.get(pk=1)
# 修改所有的Entry,让他们都属于b
Entry.objects.all().update(blog=b)

3、update方法会被立刻执行,并返回操作匹配到的行的数目(有可能不等于要更新的行的数量,因为有些行可能已经有这个新值了)。唯一的约束是:只能访问一张数据库表。你可以根据关系字段进行过滤,但你只能更新模型主表的字段。例如:

b = Blog.objects.get(pk=1)
# Update all the headlines belonging to this Blog.
Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same')

4、要注意的是update()方法会直接转换成一个SQL语句,并立刻批量执行。它不会运行模型的save()方法,或者产生pre_save或post_save信号(调用save()方法产生)或者服从auto_now字段选项。如果你想保存QuerySet中的每个条目并确保每个实例的save()方法都被调用,你不需要使用任何特殊的函数来处理。只需要迭代它们并调用save()方法:

for item in my_queryset:
    item.save()

5、update方法可以配合F表达式。这对于批量更新同一模型中某个字段特别有用。例如增加Blog中每个Entry的pingback个数:

Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

6、然而,与filter和exclude子句中的F()对象不同,在update中你不可以使用F()对象进行跨表操作,你只可以引用正在更新的模型的字段。如果你尝试使用F()对象引入另外一张表的字段,将抛出FieldError异常:

# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))

十五、关系的对象

利用本节一开始的模型,一个Entry对象e可以通过blog属性e.blog获取关联的Blog对象。反过来,Blog对象b可以通过entry_set属性b.entry_set.all()访问与它关联的所有Entry对象。

一对多(外键)

1、正向查询:

(1)直接通过圆点加属性,访问外键对象

e = Entry.objects.get(id=2)
e.blog # 返回关联的Blog对象

(2)要注意的是,对外键的修改,必须调用save方法进行保存,例如:

e = Entry.objects.get(id=2)
e.blog = some_blog
e.save()

(3)如果一个外键字段设置有null=True属性,那么可以通过给该字段赋值为None的方法移除外键值:

e = Entry.objects.get(id=2)
e.blog = None
e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"

(4)在第一次对一个外键关系进行正向访问的时候,关系对象会被缓存。随后对同样外键关系对象的访问会使用这个缓存,例如:

e = Entry.objects.get(id=2)
print(e.blog)  # 访问数据库,获取实际数据
print(e.blog)  # 不会访问数据库,直接使用缓存的版本

(5)请注意QuerySet的select_related()方法会递归地预填充所有的一对多关系到缓存中。例如:

e = Entry.objects.select_related().get(id=2)
print(e.blog)  # 不会访问数据库,直接使用缓存
print(e.blog)  # 不会访问数据库,直接使用缓存

2、反向查询

(1)如果一个模型有ForeignKey,那么该ForeignKey所指向的外键模型的实例可以通过一个管理器进行反向查询,返回源模型的所有实例。默认情况下,这个管理器的名字为FOO_set,其中FOO是源模型的小写名称。该管理器返回的查询集可以用前面提到的方式进行过滤和操作。

b = Blog.objects.get(id=1) 

# Returns all Entry objects related to Blog.
b.entry_set.all() 

# b.entry_set is a Manager that returns QuerySets.
b.entry_set.filter(headline__contains='Lennon')

b.entry_set.count()

(2)你可以在ForeignKey字段的定义中,通过设置related_name来重写FOO_set的名字。举例说明,如果你修改Entry模型blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name=’entries’),那么上面的例子会变成下面的样子:

b = Blog.objects.get(id=1)

# Returns all Entry objects related to Blog.
b.entries.all() 
# b.entries is a Manager that returns QuerySets.
b.entries.filter(headline__contains='Lennon')

b.entries.count()

3、使用自定义的反向管理器

ps:没看懂,用到的时候再研究下

默认情况下,用于反向关联的RelatedManager是该模型默认管理器的子类。如果你想为一个查询指定一个不同的管理器,你可以使用下面的语法:

from django.db import models

class Entry(models.Model):
    #...
    objects = models.Manager()  # 默认管理器
    entries = EntryManager()    # 自定义管理器

b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()

当然,指定的自定义反向管理器也可以调用它的自定义方法:

b.entry_set(manager='entries').is_published()

4、处理关联对象的其它方法

(1)除了在前面定义的QuerySet方法之外,ForeignKey管理器还有其它方法用于处理关联的对象集合。下面是每个方法的概括。

  • add(obj1, obj2, …):添加指定的模型对象到关联的对象集中。

  • create(**kwargs):创建一个新的对象,将它保存并放在关联的对象集中。返回新创建的对象。

  • remove(obj1, obj2, …):从关联的对象集中删除指定的模型对象。

  • clear():清空关联的对象集。

  • set(objs):重置关联的对象集。

(2)若要一次性给关联的对象集赋值,使用set()方法,并给它赋值一个可迭代的对象集合或者一个主键值的列表。例如:

b = Blog.objects.get(id=1)
b.entry_set.set([e1, e2])

在这个例子中,e1和e2可以是完整的Entry实例,也可以是整数的主键值。

(3)如果clear()方法可用,那么在将可迭代对象中的成员添加到集合中之前,将从entry_set中删除所有已经存在的对象。如果clear()方法不可用,那么将直接添加可迭代对象中的成员而不会删除所有已存在的对象。

这节中的每个反向操作都将立即在数据库内执行。所有的增加、创建和删除操作也将立刻自动地保存到数据库内。

多对多

多对多关系的两端都会自动获得访问
另一端的API。这些API的工作方式与前面提到的“反向”一对多关系的用法一样。

唯一的区别在于属性的名称:定义ManyToManyField的模型使用该字段的属性名称,而“反向”模型使用源模型的小写名称加上’_set’ (和一对多关系一样)。

e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')
#
a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.

与外键字段中一样,在多对多的字段中也可以指定related_name名。

(注:在一个模型中,如果存在多个外键或多对多的关系指向同一个外部模型,必须给他们分别加上不同的related_name,用于反向查询)

一对一

一对一非常类似多对一关系,可以简单的通过模型的属性访问关联的模型。

class EntryDetail(models.Model):
    entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
    details = models.TextField()

ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.

不同之处在于反向查询的时候。一对一关系中的关联模型同样具有一个管理器对象,但是该管理器表示一个单一的对象而不是对象的集合:

e = Entry.objects.get(id=2)
e.entrydetail # 返回关联的EntryDetail对象

如果没有对象赋值给这个关系,Django将抛出一个DoesNotExist异常。 可以给反向关联进行赋值,方法和正向的关联一样:

e.entrydetail = ed

反向关联是如一些ORM框架需要你在关系的两端都进行定义。Django的开发者认为这违反了DRY (Don’t Repeat Yourself)原则,所以在Django中你只需要在一端进行定义。

那么这是怎么实现的呢?因为在关联的模型类没有被加载之前,一个模型类根本不知道有哪些类和它关联。

答案在app registry!在Django启动的时候,它会导入所有INSTALLED_APPS中的应用和每个应用中的模型模块。每创建一个新的模型时,Django会自动添加反向的关系到所有关联的模型。如果关联的模型还没有导入,Django将保存关联的记录并在关联的模型导入时添加这些关系。

由于这个原因,将模型所在的应用都定义在INSTALLED_APPS的应用列表中就显得特别重要。否则,反向关联将不能正确工作。何实现的?

通过关联对象进行查询

涉及关联对象的查询与正常值的字段查询遵循同样的规则。当你指定查询需要匹配的值时,你可以使用一个对象实例或者对象的主键值。

例如,如果你有一个id=5的Blog对象b,下面的三个查询将是完全一样的:

Entry.objects.filter(blog=b) # 使用对象实例
Entry.objects.filter(blog=b.id) # 使用实例的id
Entry.objects.filter(blog=5) # 直接使用id

十六、查询集API

1、QuerySet何时被提交
在内部,创建、过滤、切片和传递一个QuerySet不会真实操作数据库,在你对查询集提交之前,不会发生任何实际的数据库操作。

可以使用下列方法对QuerySet提交查询操作:

(1)迭代
QuerySet是可迭代的,在首次迭代查询集时执行实际的数据库查询。 例如, 下面的语句会将数据库中所有Entry的headline打印出来:

for e in Entry.objects.all():
    print(e.headline)

(2)切片:如果使用切片的”step“参数,Django 将执行数据库查询并返回一个列表。

(3)Pickling/缓存

(4)repr()

(5)len():当你对QuerySet调用len()时, 将提交数据库操作。

(5)list():对QuerySet调用list()将强制提交操作entry_list = list(Entry.objects.all())

(6)bool() 测试布尔值,像这样:

if Entry.objects.filter(headline="Test"):
   print("There is at least one Entry with the headline Test")

注:如果你需要知道是否存在至少一条记录(而不需要真实的对象),使用exists() 将更加高效。

2、QuerySet

下面是对于QuerySet的正式定义:

class QuerySet(model=None, query=None, using=None)[source]

QuerySet类具有两个公有属性用于内省:

  • ordered:如果QuerySet是排好序的则为True,否则为False。
  • db:如果现在执行,则返回使用的数据库。

3、返回新QuerySets的API
以下的方法都将返回一个新的QuerySets。

方法名 解释
filter() 过滤查询对象。
exclude() 排除满足条件的对象
annotate() 使用聚合函数
order_by() 对查询集进行排序
reverse() 反向排序
distinct() 对查询集去重
values() 返回包含对象具体值的字典的QuerySet
values_list() 与values()类似,只是返回的是元组而不是字典。
dates() 根据日期获取查询集
datetimes() 根据时间获取查询集
none() 创建空的查询集
all() 获取所有的对象
union() 并集
intersection() 交集
difference() 差集
select_related() 附带查询关联对象
prefetch_related() 预先查询
extra() 附加SQL查询
defer() 不加载指定字段
only() 只加载指定的字段
using() 选择数据库
select_for_update() 锁住选择的对象,直到事务结束。
raw() 接收一个原始的SQL查询

QuerySet常用方法

1、filter()
filter(**kwargs)

返回满足查询参数的对象集合。

查找的参数(**kwargs)应该满足下文字段查找中的格式。多个参数之间是和AND的关系。

2、exclude()
exclude(**kwargs)

返回一个新的QuerySet,它包含不满足给定的查找参数的对象。

查找的参数(**kwargs)应该满足下文字段查找中的格式。多个参数通过AND连接,然后所有的内容放入NOT() 中。

下面的示例排除所有pub_date晚于2005-1-3且headline为“Hello” 的记录:

Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')

下面的示例排除所有pub_date晚于2005-1-3或者headline 为“Hello” 的记录:

Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello')

3.、annotate()
annotate(args, *kwargs)

使用提供的聚合表达式查询对象。

表达式可以是简单的值、对模型(或任何关联模型)上的字段的引用或者聚合表达式(平均值、总和等)。

annotate()的每个参数都是一个annotation,它将添加到返回的QuerySet每个对象中。

关键字参数指定的Annotation将使用关键字作为Annotation 的别名。 匿名参数的别名将基于聚合函数的名称和模型的字段生成。 只有引用单个字段的聚合表达式才可以使用匿名参数。 其它所有形式都必须用关键字参数。

例如,如果正在操作一个Blog列表,你可能想知道每个Blog有多少Entry:

from django.db.models import Count
q = Blog.objects.annotate(Count('entry'))
# The name of the first blog
>>> q[0].name
'Blogasaurus'
# The number of entries on the first blog
>>> q[0].entry__count
42

Blog模型本身没有定义entry__count属性,但是通过使用一个关键字参数来指定聚合函数,可以控制Annotation的名称:

>>> q = Blog.objects.annotate(number_of_entries=Count('entry'))
# The number of entries on the first blog, using the name provided
>>> q[0].number_of_entries
42

4、order_by()

order_by(*fields)

(1)默认情况下,根据模型的Meta类中的ordering属性对QuerySet中的对象进行排序

Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')

上面的结果将按照pub_date降序排序,然后再按照headline升序排序。"-pub_date"前面的负号表示降序顺序。 升序是默认的。

(2)要随机排序,使用"?",如下所示:

Entry.objects.order_by('?')

注:order_by(’?’)可能耗费资源且很慢,这取决于使用的数据库。

(3)若要按照另外一个模型中的字段排序,可以使用查询关联模型的语法。即通过字段的名称后面跟两个下划线(__),再加上新模型中的字段的名称,直到希望连接的模型。 像这样:

Entry.objects.order_by('blog__name', 'headline')

(4)如果排序的字段与另外一个模型关联,Django将使用关联的模型的默认排序,或者如果没有指定Meta.ordering将通过关联的模型的主键排序。 例如,因为Blog模型没有指定默认的排序:

Entry.objects.order_by('blog')

与以下相同:

Entry.objects.order_by('blog__id')

(5)如果Blog设置了ordering = [‘name’],那么第一个QuerySet将等同于:

Entry.objects.order_by('blog__name')

(6)还可以通过调用表达式的desc()或者asc()方法

Entry.objects.order_by(Coalesce('summary', 'headline').desc())

(7)考虑下面的情况,指定一个多值字段来排序(例如,一个ManyToManyField 字段或者ForeignKey 字段的反向关联):

class Event(Model):
   parent = models.ForeignKey(
       'self',
       on_delete=models.CASCADE,
       related_name='children',
   )
   date = models.DateField()

Event.objects.order_by('children__date')

在这里,每个Event可能有多个排序数据;具有多个children的每个Event将被多次返回到order_by()创建的新的QuerySet中。 换句话说,用order_by()方法对QuerySet对象进行操作会返回一个扩大版的新QuerySet对象。因此,使用多值字段对结果进行排序时要格外小心。

(8)没有方法指定排序是否考虑大小写。 对于大小写的敏感性,Django将根据数据库中的排序方式排序结果。
可以通过Lower将一个字段转换为小写来排序,它将达到大小写一致的排序:

Entry.objects.order_by(Lower('headline').desc())

(9)可以通过检查QuerySet.ordered属性来知道查询是否是排序的。

每个order_by()都将清除前面的任何排序。 例如下面的查询将按照pub_date排序,而不是headline:

Entry.objects.order_by('headline').order_by('pub_date')

5、reverse()

反向排序QuerySet中返回的元素。 第二次调用reverse()将恢复到原有的排序。

如要获取QuerySet中最后五个元素,可以这样做:

my_queryset.reverse()[:5]

这与Python直接使用负索引有点不一样。 Django不支持负索引,只能曲线救国

6、distinct()

distinct(*fields)

去除查询结果中重复的行。

默认情况下,QuerySet不会去除重复的行。当查询跨越多张表的数据时,QuerySet可能得到重复的结果,这时候可以使用distinct()进行去重。

7、values()

values(fields, *expressions)

返回一个包含数据的字典的queryset,而不是模型实例。

每个字典表示一个对象,键对应于模型对象的属性名称。

(1)下面的例子将values() 与普通的模型对象进行比较:

# 列表中包含的是Blog对象
>>> Blog.objects.filter(name__startswith='Beatles')
<QuerySet [<Blog: Beatles Blog>]>
# 列表中包含的是数据字典
>>> Blog.objects.filter(name__startswith='Beatles').values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>

(2)该方法接收可选的位置参数*fields,它指定values()应该限制哪些字段。如果指定字段,每个字典将只包含指定的字段的键/值。如果没有指定字段,每个字典将包含数据库表中所有字段的键和值。

例如:

>>> Blog.objects.values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
>>> Blog.objects.values('id', 'name')
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>

(3)values()方法还有关键字参数**expressions,这些参数将传递给annotate():

>>> from django.db.models.functions import Lower
>>> Blog.objects.values(lower_name=Lower('name'))
<QuerySet [{'lower_name': 'beatles blog'}]>

在values()子句中的聚合应用于相同values()子句中的其他参数之前。 如果需要按另一个值分组,请将其添加到较早的values()子句中。 像这样:

>>> from django.db.models import Count
>>> Blog.objects.values('author', entries=Count('entry'))
<QuerySet [{'author': 1, 'entries': 20}, {'author': 1, 'entries': 13}]>
>>> Blog.objects.values('author').annotate(entries=Count('entry'))
<QuerySet [{'author': 1, 'entries': 33}]>

(4)注意:

如果你有一个字段foo是一个ForeignKey,默认的foo_id参数返回的字典中将有一个叫做foo 的键,因为这是保存实际值的那个隐藏的模型属性的名称。 当调用foo_id并传递字段的名称,传递foo 或values()都可以,得到的结果是相同的。像这样:

>>> Entry.objects.values()
<QuerySet [{'blog_id': 1, 'headline': 'First Entry', ...}, ...]>
>>> Entry.objects.values('blog')
<QuerySet [{'blog': 1}, ...]>
>>> Entry.objects.values('blog_id')
<QuerySet [{'blog_id': 1}, ...]>

(5)当values()与distinct()一起使用时,注意排序可能影响最终的结果。

(6)如果values()子句位于extra()调用之后,extra()中的select参数定义的字段必须显式包含在values()调用中。 values( 调用后面的extra( 调用将忽略选择的额外的字段。

(7)在values()之后调用only()和defer()不太合理,所以将引发一个NotImplementedError。

(8)可以通过ManyToManyField、ForeignKey 和 OneToOneFiel 属性反向引用关联的模型的字段:

>>> Blog.objects.values('name', 'entry__headline')
<QuerySet [{'name': 'My blog', 'entry__headline': 'An entry'},
     {'name': 'My blog', 'entry__headline': 'Another entry'}, ...]>

8、values_list()
ps:感觉一般都用不到呀。。

values_list(*fields, flat=False)

(1)与values()类似,只是在迭代时返回的是元组而不是字典。每个元组包含传递给values_list()调用的相应字段或表达式的值,因此第一个项目是第一个字段等。 像这样:

>>> Entry.objects.values_list('id', 'headline')
<QuerySet [(1, 'First entry'), ...]>
>>> from django.db.models.functions import Lower
>>> Entry.objects.values_list('id', Lower('headline'))
<QuerySet [(1, 'first entry'), ...]>

(2)如果只传递一个字段,还可以传递flat参数。 如果为True,它表示返回的结果为单个值而不是元组。 如下所示:

>>> Entry.objects.values_list('id').order_by('id')
<QuerySet[(1,), (2,), (3,), ...]>
>>> Entry.objects.values_list('id', flat=True).order_by('id')
<QuerySet [1, 2, 3, ...]>

如果有多个字段,传递flat将发生错误。

(3)如果不传递任何值给values_list(),它将返回模型中的所有字段,以在模型中定义的顺序。

(4)常见的情况是获取某个模型实例的特定字段值。可以使用values_list(),然后调用get():

>>> Entry.objects.values_list('headline', flat=True).get(pk=1)
'First entry'

values()和values_list()都用于特定情况下的优化:检索数据子集,而无需创建模型实例。

(5)注意通过ManyToManyField进行查询时的行为:

>>> Author.objects.values_list('name', 'entry__headline')
<QuerySet [('Noam Chomsky', 'Impressions of Gaza'),
 ('George Orwell', 'Why Socialists Do Not Believe in Fun'),
 ('George Orwell', 'In Defence of English Cooking'),
 ('Don Quixote', None)]>

类似地,当查询反向外键时,对于没有任何作者的条目,返回None。

>>> Entry.objects.values_list('authors')
<QuerySet [('Noam Chomsky',), ('George Orwell',), (None,)]>

9、dates()

dates(field, kind, order=‘ASC’)

返回一个QuerySet,表示QuerySet内容中特定类型的所有可用日期的datetime.date对象列表。

field参数是模型的DateField的名称。 kind参数应为"year",“month"或"day”。 结果列表中的每个datetime.date对象被截取为给定的类型。

  • “year” 返回对应该field的所有不同年份值的列表。

  • "month"返回字段的所有不同年/月值的列表。

  • "day"返回字段的所有不同年/月/日值的列表。

  • order参数默认为’ASC’,或者’DESC’。 它指定如何排序结果。

例子:

>>> Entry.objects.dates('pub_date', 'year')
[datetime.date(2005, 1, 1)]
>>> Entry.objects.dates('pub_date', 'month')
[datetime.date(2005, 2, 1), datetime.date(2005, 3, 1)]
>>> Entry.objects.dates('pub_date', 'day')
[datetime.date(2005, 2, 20), datetime.date(2005, 3, 20)]
>>> Entry.objects.dates('pub_date', 'day', order='DESC')
[datetime.date(2005, 3, 20), datetime.date(2005, 2, 20)]
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
[datetime.date(2005, 3, 20)]

10、datetimes()
datetimes(field_name, kind, order=‘ASC’, tzinfo=None)

返回QuerySet,为datetime.datetime对象的列表,表示QuerySet内容中特定种类的所有可用日期。

field_name应为模型的DateTimeField的名称。

kind参数应为"hour",“minute”,“month”,“year”,“second"或"day”。

结果列表中的每个datetime.datetime对象被截取到给定的类型。

order参数默认为’ASC’,或者’DESC’。 它指定如何排序结果。

tzinfo参数定义在截取之前将数据时间转换到的时区。

11、none()
none()

调用none()将创建一个不返回任何对象的查询集,并且在访问结果时不会执行任何查询。

例子:

>>> Entry.objects.none()
<QuerySet []>
>>> from django.db.models.query import EmptyQuerySet
>>> isinstance(Entry.objects.none(), EmptyQuerySet)
True

12、all()

all()

返回当前QuerySet(或QuerySet子类)的副本。通常用于获取全部QuerySet对象。

13、union()
union(*other_qs, all=False)

Django中的新功能1.11。也就是集合中并集的概念!

使用SQL的UNION运算符组合两个或更多个QuerySet的结果。例如:

qs1.union(qs2, qs3)

默认情况下,UNION操作符仅选择不同的值。 要允许重复值,请使用all=True参数。

14、intersection()
intersection(*other_qs)

Django中的新功能1.11。也就是集合中交集的概念!

使用SQL的INTERSECT运算符返回两个或更多个QuerySet的共有元素。例如:

qs1.intersection(qs2, qs3)

15、difference()
difference(*other_qs)

Django中的新功能1.11。也就是集合中差集的概念!

使用SQL的EXCEPT运算符只保留QuerySet中的元素,但不保留其他QuerySet中的元素。例如:

qs1.difference(qs2, qs3)

16、select_related()

select_related(*fields)

沿着外键关系查询关联的对象的数据。这会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要再次数据库查询。

(1)下面的例子解释了普通查询和select_related()查询的区别。 下面是一个标准的查询:

# 访问数据库。
e = Entry.objects.get(id=5)
# 再次访问数据库以得到关联的Blog对象。
b = e.blog

下面是一个select_related查询:

# 访问数据库。
e = Entry.objects.select_related('blog').get(id=5)
# 不会访问数据库,因为e.blog已经在前面的查询中获得了。
b = e.blog

(2)select_related()可用于objects任何的查询集:

from django.utils import timezone

# Find all the blogs with entries scheduled to be published in the future.
blogs = set()

for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
    # 没有select_related(),下面的语句将为每次循环迭代生成一个数据库查询,以获得每个entry关联的blog。
    blogs.add(e.blog)

(3)filter()和select_related()的顺序不重要。 下面的查询集是等同的:

Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())

(4)可以沿着外键查询。 如果有以下模型:

from django.db import models

class City(models.Model):
    # ...
    pass

class Person(models.Model):
    # ...
    hometown = models.ForeignKey(
        City,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

class Book(models.Model):
    # ...
    author = models.ForeignKey(Person, on_delete=models.CASCADE)

调用Book.objects.select_related(‘author__hometown’).get(id=4)将缓存相关的Person 和相关的City:

b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author         # Doesn't hit the database.
c = p.hometown       # Doesn't hit the database.
b = Book.objects.get(id=4) # No select_related() in this example.
p = b.author         # Hits the database.
c = p.hometown       # Hits the database.

在传递给select_related()的字段中,可以使用任何ForeignKey和OneToOneField。

在传递给select_related的字段中,还可以反向引用OneToOneField。也就是说,可以回溯到定义OneToOneField 的字段。 此时,可以使用关联对象字段的related_name,而不要指定字段的名称。

17、prefetch_related()
prefetch_related(*lookups)

在单个批处理中自动检索每个指定查找的相关对象。

与select_related类似,但是策略是完全不同的。

假设有这些模型:

from django.db import models

class Topping(models.Model):
    name = models.CharField(max_length=30)

class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping)

    def __str__(self):              # __unicode__ on Python 2
        return "%s (%s)" % (
            self.name,
            ", ".join(topping.name for topping in self.toppings.all()),
        )

并运行:

>>> Pizza.objects.all()
["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...

问题是每次QuerySet要求Pizza.objects.all()查询数据库,因此self.toppings.all()将在Pizza Pizza.str()中的每个项目的Toppings表上运行查询。

可以使用prefetch_related减少为只有两个查询:

>>> Pizza.objects.all().prefetch_related('toppings')

这意味着现在每次self.toppings.all()被调用,不会再去数据库查找,而是在一个预取的QuerySet缓存中查找。

还可以使用正常连接语法来执行相关字段的相关字段。 假设在上面的例子中增加一个额外的模型

class Restaurant(models.Model):
    pizzas = models.ManyToManyField(Pizza, related_name='restaurants')
    best_pizza = models.ForeignKey(Pizza, related_name='championed_by')

以下是合法的:

>>> Restaurant.objects.prefetch_related('pizzas__toppings')

这将预取所有属于餐厅的比萨饼,和所有属于那些比萨饼的配料。 这将导致总共3个查询 - 一个用于餐馆,一个用于比萨饼,一个用于配料。

>>> Restaurant.objects.prefetch_related('best_pizza__toppings')

这将获取最好的比萨饼和每个餐厅最好的披萨的所有配料。 这将在3个表中查询 - 一个为餐厅,一个为“最佳比萨饼”,一个为一个为配料。

当然,也可以使用best_pizza来获取select_related关系,以将查询数减少为2:

>>> Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')

18、extra()
extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

有些情况下,Django的查询语法难以简单的表达复杂的WHERE子句,对于这种情况,可以在extra()生成的SQL从句中注入新子句。使用这种方法作为最后的手段,这是一个旧的API,在将来的某个时候可能被弃用。仅当无法使用其他查询方法表达查询时才使用它。

例如:

>>> qs.extra(
...     select={'val': "select col from sometable where othercol = %s"},
...     select_params=(someparam,),
... )

相当于:

>>> qs.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))

19、defer()

defer(*fields)

在一些复杂的数据建模情况下,模型可能包含大量字段,其中一些可能包含大尺寸数据(例如文本字段),将它们转换为Python对象需要花费很大的代价。

当最初获取数据时不知道是否需要这些特定字段的情况下,如果正在使用查询集的结果,可以告诉Django不要从数据库中检索它们。

通过传递字段名称到defer()实现不加载:

Entry.objects.defer("headline", "body")

具有延迟加载字段的查询集仍将返回模型实例。

每个延迟字段将在你访问该字段时从数据库中检索(每次只检索一个,而不是一次检索所有的延迟字段)。

可以多次调用defer()。 每个调用都向延迟集添加新字段:

# 延迟body和headline两个字段。
Entry.objects.defer("body").filter(rating=5).defer("headline")

字段添加到延迟集的顺序无关紧要。对已经延迟的字段名称再次defer()没有问题(该字段仍将被延迟)。

可以使用标准的双下划线符号来分隔关联的字段,从而加载关联模型中的字段:

Blog.objects.select_related().defer("entry__headline", "entry__body")

如果要清除延迟字段集,将None作为参数传递到defer():

# 立即加载所有的字段。
my_queryset.defer(None)

defer()方法(及其兄弟,only())仅适用于高级用例,它们提供了数据加载的优化方法。

20、only()
only(*fields)

only()方法与defer()相反。

如果有一个模型几乎所有的字段需要延迟,使用only()指定补充的字段集可以使代码更简单。

假设有一个包含字段biography、age和name的模型。 以下两个查询集是相同的,就延迟字段而言:

Person.objects.defer("age", "biography")
Person.objects.only("name")

每当你调用only()时,它将替换立即加载的字段集。因此,对only()的连续调用的结果是只有最后一次调用的字段被考虑:

# This will defer all fields except the headline.
Entry.objects.only("body", "rating").only("headline")

由于defer()以递增方式动作(向延迟列表中添加字段),因此你可以结合only()和defer()调用:

# Final result is that everything except "headline" is deferred.
Entry.objects.only("headline", "body").defer("body")
# Final result loads headline and body immediately (only() replaces any
# existing set of fields).
Entry.objects.defer("body").only("headline", "body")

当对具有延迟字段的实例调用save()时,仅保存加载的字段。

21、using()
using(alias)

如果正在使用多个数据库,这个方法用于指定在哪个数据库上查询QuerySet。方法的唯一参数是数据库的别名,定义在DATABASES。

例如:

# queries the database with the 'default' alias.
>>> Entry.objects.all()
# queries the database with the 'backup' alias
>>> Entry.objects.using('backup')

22、select_for_update()

select_for_update(nowait=False, skip_locked=False)

返回一个锁住行直到事务结束的查询集,如果数据库支持,它将生成一个SELECT … FOR UPDATE语句。

例如:

entries = Entry.objects.select_for_update().filter(author=request.user)

所有匹配的行将被锁定,直到事务结束。这意味着可以通过锁防止数据被其它事务修改。

一般情况下如果其他事务锁定了相关行,那么本查询将被阻塞,直到锁被释放。使用select_for_update(nowait=True)将使查询不阻塞。如果其它事务持有冲突的锁,那么查询将引发DatabaseError异常。也可以使用select_for_update(skip_locked=True)忽略锁定的行。nowait和skip_locked是互斥的。

目前,postgresql,oracle和mysql数据库后端支持select_for_update()。但是,MySQL不支持nowait和skip_locked参数。

  1. raw()
    raw(raw_query, params=None, translations=None)

接收一个原始的SQL查询,执行它并返回一个django.db.models.query.RawQuerySet实例。

这个RawQuerySet实例可以迭代,就像普通的QuerySet一样。

终于看完了。。。还有一些看不太懂的地方,等以后有用到在详细研究下吧

你可能感兴趣的:(Django)