6. 模型层¶
模型层是在视图和数据库之间的内容,根本任务是屏蔽了数据库的繁琐细节, 让程序员可以没有数据库的知识就能直接使用Django.
一般定义的文件位置在project_name/app_name/models.py
文件内。
- 负责跟数据库访问操作,数据的定义等
- 每个模型是django.models.Model的子类, 可以认为是对应一张数据库table
- Django提供一套自动生成的用于数据库访问的API
- models.Type 负责定义字段类型,每个字段可以具有一定约束条件,且与数据库字段对应
为什么要用模型?
Model是MVC框架中重要的一部分,主要负责程序中用于处理数据逻辑的部分。
通常模型对象负责在数据库中存取数据。
模型层实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,
通过简单的配置就可以轻松更换数据库。
6.1. ORM¶
- 模型与数据库的关系:
- 模型(Model)负责业务对象和数据库的关系映射(ORM)
- ORM是“对象-关系-映射”的简称,主要任务是:
- 根据对象的类型生成表结构
- 将对象、列表的操作,转换为sql语句
- 将sql查询到的结果转换为对象、列表
ORM最根本目的是让习惯OOP的程序员能够不懂SQL就能利用OOP来编程,在ORM中, 可以粗略把对应关系认为:
- OOP的类对应SQL的表
- OOP的属性(一般是一个描述字段的类)对应SQL的表中的字段
- OOP的属性的属性对应SQL表字段的约束条件
- OOP的函数对应SQL的操作, 比如增删查改
Django利用ORM实现了模型层。
6.2. Django中数据库的基本使用¶
Django有一个默认数据库sqlite3, 所以如果不配置数据库django也能运行,但是默认数据库基本不能 在生产环境中使用。
调试阶段可以直接利用django默认数据库sqlite3,等到调试完成可以无缝切换到mysql等其他关系型 数据库。
配置Mysql数据库
#Ubuntu中MySql的安装 sudo apt-get install mysql-server sudo apt install mysql-client sudo apt install libmysqlclient-dev
在当前python环境中安装 pymysql
pip install pymysql #在mysql中创建数据库 #此处需要注意的是一定要指定编码,否则可能对中文不太友好 create databases mydb default charset=utf8
在Django项目中配置数据库
Django中使用数据库需要对数据库进行简单的配置,一般需要配置两处:setttings和初始化文件。修改settings.py文件中的DATABASE配置项
DATABASES = { #此处删除了默认数据库,其他数据库也可以并存 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mydb',#选择数据库的名,请确认你的mysql中有这个库 'USER': 'root', 'PASSWORD': '123456', 'HOST': 'localhost', 'PORT': '3306', } }
告诉Django在接下来的mysql操作中使用pymysql
#打开mysite/__init__.py,写入以下代码导入pymysql: import pymysql pymysql.install_as_MySQLdb()
6.3. 初识模型:试验项目v4_model¶
创建v4_model项目和tuling应用app
在setting中设置INSTALLED_APPS: tuling
在
tuling/models.py
中添加模型类Studentfrom django.db import models # Create your models here. class Student(models.Model): name = models.CharField(max_length=20) age = models.IntegerField(default=18) addr = models.CharField(max_length=100)
迁移数据库:
迁移数据库分两步:迁移准备和迁移实施。#迁移准备 python manage.py makemigrations #迁移 python manage.py migrate
使用模型
模型创建并迁移成功后就可以使用了,此时会看到相应的目录下生成了迁移文件tuling/migrations/..
注意: 有时候迁移出错,特别是更改后,可以把生成的数据库和迁移文件都删除再重新生成迁移
或者: python manage.py makemigrations appname 用来指定某个特定的app进行迁移
6.4. 定义模型¶
本章主要讲述如何定义一个模型类。
- 在模型中定义属性,会生成表中的字段。
- django会为表增加自动增长的主键列,每个模型只能有一个主键列。
- 如果使用选项设置某属性为主键列后,则django不会再生成默认的主键列
属性命名限制:
- 不能是python的保留关键字
- 由于django的查询方式使用连续下划线,所以命名中不允许使用连续的下划线
6.4.1. 定义属性¶
定义属性时,需要字段类型,即每个属性的类型,智能书哟models下定义好的属性类型,或者你自己定义自己的属性类型。
字段类型被定义在django.db.models.fields
目录下,为了方便使用,被导入到django.db.models
中
使用方式:
- 导入from django.db import models
- 通过models.Field创建字段类型的对象,赋值给属性
例如下面案例, 三个属性类型都是models.Field
字类:
from django.db import models
# Create your models here.
class Student(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField(default=18)
addr = models.CharField(max_length=100)
6.4.2. 常用属性类型¶
属性类型XXXField都在文件django\db\models\fields\__init__.py
中定义,必要时可以查阅代码。
必要到时候,如果系统定义字段不能满足需求,可以自行定义字段属性。
每个字段类型都是Field的子类, Django可以根据字段类型确定以下信息:
- 数据库中的类型
- 渲染表单上用的HTML部件
- 最低限度的验证需求
常用的Field类型如下:
AutoField:一个根据实际ID自动增长的IntegerField,通常不指定
如果不指定,一个主键字段将自动添加到模型中BooleanField:true/false 字段,此字段的默认表单控制是CheckboxInput
NullBooleanField:支持null、true、false三种值
CharField(max_length=字符长度):字符串,默认的表单样式是 TextInput
TextField:大文本字段,一般超过4000使用,默认的表单控件是Textarea
IntegerField:整数
DecimalField(max_digits=None, decimal_places=None):
- 使用python的Decimal实例表示的十进制浮点数
- DecimalField.max_digits:位数总数
- DecimalField.decimal_places:小数点后的数字位数
FloatField:用Python的float实例来表示的浮点数
DateField[auto_now=False, auto_now_add=False]):
- 使用Python的datetime.date实例表示的日期
- 参数DateField.auto_now:每次保存对象时,自动设置该字段为当前时间,用于”最后一次修改”的时间戳,它总是使用当前日期,默认为false
- 参数DateField.auto_now_add:当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false
- auto_now_add, auto_now, and default 这些设置是相互排斥的,他们之间的任何组合将会发生错误的结果
- 该字段默认对应的表单控件是一个TextInput. 在管理员站点添加了一个JavaScript写的日历控件,和一个“Today”的快捷按钮,包含了一个额外的invalid_date错误消息键
TimeField:使用Python的datetime.time实例表示的时间,参数同DateField
DateTimeField:使用Python的datetime.datetime实例表示的日期和时间,参数同DateField
FileField:一个上传文件的字段
ImageField:
- 继承了FileField的所有属性和方法
- 对上传的对象进行校验,确保它是个有效的image
除ForeignKey,ManyToManyField,OneToOneField成员外, 其余定义第一个参数位置是verbose_name, 否则需要第二个参数位置用verbose_name指定
first_name = models.CharField("person's first name", max_length=30) poll = models.ForeignKey(Poll, verbose_name="the related poll") sites = models.ManyToManyField(Site, verbose_name="list of sites")
6.4.3. 字段选项¶
通过字段选项,可以实现对字段的约束。数据库表或者字段在创建的时候有很多约束,用来对数据 进行校验。
使用是通过在字段对象定义的时候通过关键字参数指定。
常用的字段控制选项:
null:如果为True,Django 将空值以NULL存储到数据库中,默认值是 False
db_column:字段的名称,如果未指定,则使用属性的名称
db_index:若值为 True, 则在表中会为此字段创建索引
default:默认值
primary_key:若为 True, 则该字段会成为模型的主键字段
unique:如果为 True, 这个字段在表中必须有唯一值
blank: 是否允许字段值为空,默认False
null: 是否将DB中空值保存为NULL,默认是False
choices: 由二元相组成的可选迭代对象,用来给字段提供选项.例如:
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)
6.4.4. 元选项¶
在模型类中定义类Meta,用于设置元信息。 一般用于对数据库表整个表的属性进行定义。
常见的元信息如下:
db_table:定义数据表名称,推荐使用小写字母,
数据表的默认名称:<app_name>_<model_name>ordering:对象的默认排序字段,获取对象的列表时使用,接收属性构成的列表
class BookInfo(models.Model): ... class Meta(): ordering = ['id'] #字符串前加-表示倒序,不加-表示正序 class BookInfo(models.Model): ... class Meta(): ordering = ['-id']
排序会增加数据库的开销
6.5. 模型的关系¶
本章探讨模型与模型的关系,常见关系数据库模型关系分为三种:
- 一对一(1:1, OneToOne):
- 一个有一个,即 has one: OneToOneField
- 用一访问一: 对象.模型类小写(例如: hasband.wife)
- 一对多(1:N, OneToMany):
- 多个属于一个,即 belong to: ForeignKey,多个属于一个
- 一个有很多个,即 has many: lots of A belong to B 与 B has many A, 在建立 ForeignKey 时,另一个表会自动建立对应的关系
- 用一访问多: 对象.模型类小写_set(例如:course.student_set)
- 多对多(N:N, ManyToMany): 一个既有很多个,又属于很多个,即has many and belong to: ManyToManyField, 同样只能在一个model类中说明,关联表会自动建立。
下面是相应的示例代码,用来演示三种对应关系, 其中一个地址(Place)只能 开一个餐馆(Restaurant), 一个餐馆可以有多个服务员(Waiter):
试验代码本人用的是Django shell
, 为了便于演示,具体源码参考项目v4_model:
6.5.1. OneToOne¶
一对一定义方式是在任一个类中使用OneToOneField定义。
```
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self):
return "%s the place" % self.name
class Restaurant(models.Model):
# Place:Restaurant=1:1
pobject = models.OneToOneField(Place, primary_key=True)
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.place.name
class Waiter(models.Model):
#Restaurant:Waiter=1:N
restaurant = models.ForeignKey(Restaurant)
name = models.CharField(max_length=50)
def __str__(self):
return "%s the waiter at %s" % (self.name, self.restaurant)
```
一对一关系中,访问OneToOne关系定义方可通过定义的变量访问,对方会自动 有个定义方同名的属性,可以直接使用。
访问方法伪代码如下, 以上面Place和Restaurant为例:
#保存
p = Place()
r = Restaurant()
r.pobject = p
p.save()
r.save()
#访问
#Place中自动添加一个一对一关系中另一方小写名字的属性,通过这个属性可以访问对方
#Restaurant访问Place通过自己定义的属性pobject就可以
print(p.restaurant)
print(r.pobject )
6.5.2. OneToMany¶
多对一使用django.db.models.ForeignKey定义,字段定义放在多的一边。
添加关系一般在多方,多方可以直接使用定义关系的变量。一方自动有
一个形如 partername_set
的变量名,此变量可以直接使用查询方法,例如
all, get等。
下面案例中,一个制造商可以生产很多汽车,在多的一端,即Car类中说明即可。
```
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer)
```
在项目v4_model中,我们定义模型类如下,试验代码以下面模型类为准:
class Users(models.Model):
username = models.CharField(max_length=20, null=True, blank=True, verbose_name=u'用户名')
class Meta:
db_table = 'Users'
#用户详情
class UserInfo(models.Model):
# 在用户详情表中,关联用户表,让两个表的数据产生联系
# 第一个参数:是被关联的模型名称
# 第二个参数:当user用户表中的一条数据被删除的时候,与之对应的详情表数据也会被删除
uid = models.OneToOneField(Users, on_delete=models.CASCADE)
address = models.CharField(max_length=100, null=True)
class Meta:
db_table = 'UserInfo'
# 班级
class Class(models.Model):
id = models.AutoField(primary_key=True)
classname = models.CharField(max_length=32)
class Meta:
db_table = 'Class'
def __str__(self):
return self.classname
# 学员
class Stu(models.Model):
id = models.AutoField(primary_key=True)
sname = models.CharField(max_length=32)
# 一对多
cid = models.ForeignKey(to="Class", to_field="id", on_delete= models.CASCADE)
class Meta:
db_table = 'Stu'
def __str__(self):
return self.sname
# 讲师信息表
class Teacher(models.Model):
id = models.AutoField(primary_key=True)
tname = models.CharField(max_length=32)
cid = models.ManyToManyField(to="Class")
def __str__(self):
return self.tname
参考以上模型,测试案例如下,具体代码请参考项目源码, 以下代码使用shell测试:
In [12]: c = Class(classname="北图01班")
In [13]: c.save()
In [14]: c = Class(classname="北图02班")
In [15]: c.save()
In [16]: s = Stu(sname="刘大拿")
In [17]: s.cid = c
In [18]: s.save()
In [19]: s = Stu(sname="刘二拿")
In [20]: s.cid = c
In [22]: s.save()
In [23]: s = Stu(sname="刘二拿")
IIn [24]: s.sname = "刘三拿"
In [25]: s.cid = c
In [26]: s.save()
In [27]: c = Class.objects.all()[1]
In [29]: c.classname
Out[29]: '北图02班'
In [34]: for s in Stu.objects.all():
...: print(s.sname + " --- " + s.cid.classname)
刘大拿 --- 北图02班
刘二拿 --- 北图02班
刘三拿 --- 北图02班
In [35]: c
Out[35]: <Class: 北图02班>
In [40]: for s in c.stu_set.all():
...: print(c.classname + " --- " + s.sname)
...:
北图02班 --- 刘大拿
北图02班 --- 刘二拿
北图02班 --- 刘三拿
6.5.3. ManyToMany:¶
多对多定义可以在任一端采用ManyToMany定义,属性名称一般采用复数形式。
```
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
```
多对多关系的访问一方通过定义的变量访问,对方自动增加一个xxx_set
的变量,
可以使用查询方法访问。
多对多的案例展示如下所示,具体参加源码:
# c是Class的实列
In [51]: t = Teacher(tname="刘大拿")
#需要注意的是,必须先save后才能add,否则报错,顺序很重要
In [54]: t.save()
In [55]: t.cid.add(c)
In [56]: t.save()
In [57]: c.classname
Out[57]: '北图py02'
In [59]: c1 = Class.objects.get(classname="北图py01")
In [60]: c2 = c
# 添加一个联系
In [61]: t.cid.add(c1)
In [63]: t.cid.all()
Out[63]: <QuerySet [<Class: 北图py01>, <Class: 北图py02>]>
In [64]: c1.teacher_set.all()
Out[64]: <QuerySet [<Teacher: 刘大拿>]>
In [65]: t2 = Teacher(tname="刘二拿")
In [66]: t2.save()
In [67]: t2.cid.add(c1)
In [68]: t2.cid.add(c2)
In [69]: t3 = Teacher(tname="刘d三拿")
In [71]: t3.save()
In [72]: c1.teacher_set.add(t3)
In [73]: c1.teacher_set.all()
Out[73]: <QuerySet [<Teacher: 刘大拿>, <Teacher: 刘二拿>, <Teacher: 刘d三拿>]>
In [74]: t3.delete()
Out[74]: (2, {'tuling.Teacher_cid': 1, 'tuling.Teacher': 1})
#从另一端访问,teacher_set是自动添加的属性
In [75]: c1.teacher_set.all()
Out[75]: <QuerySet [<Teacher: 刘大拿>, <Teacher: 刘二拿>]>
6.6. 模型高级知识¶
本章讨论模型的一些属性或者实例相关的函数和功能。
6.6.1. 模型的Manager属性(管理器)¶
模型的作用是跟数据库打交道,我们前面主要展示了模型的建立等,但模型究竟如何 和数据库交互依然没解释清楚,其实可以把模型看作是一个数据描述集合,具体跟 数据库的操作,比如增删查改等,是通过一个Manager类的实例作为代理人来实行的。
所以,操作类函数一般都在Manager的实例里,它是模型类的一个类属性。
Manager,是数据库查询操作的接口,用于从数据库获取示例
如果没有自定义Manager,则默认名称是objects
只能通过模型类访问,不能通过模型实例访问
可以自行定义这个管理器类,常见于两种情况
- 向管理器类添加额外的方法
- 修改管理器返回的原始查询集合,重写 get_queryset()方法
示例:
class StudentManager(models.Manager): def get_queryset(self): return super(StudentManager, self).get_queryset().filter(name="test") class StudentInfo(models.Model): ... #此处自己定义了一个管理器,当然这个管理器是继承自Manager student_test = StudentManager()
Manager用于表范围的事物,属于”静态类方法”
6.6.2. 模型的方法¶
模型当然也可以带有自己的方法,但一般此类方法都是跟模型的 实例相关的内容,如下例,通过定义一些函数,在模型的 实例上执行:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
elif self.birth_date < datetime.date(1965, 1, 1):
return "Baby boomer"
else:
return "Post-boomer"
def _get_full_name(self):
"Returns the person's full name."
return '%s %s' % (self.first_name, self.last_name)
full_name = property(_get_full_name)
6.6.3. 重写预定义的模型方法¶
必要的时候,也可以对模型预定义的方法进行重写,常见于对模型 执行预定义的方法的时候需要增加功能的时候,优点类似于类的属性(property)的需求 场景。
代码示例:
from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() # 本例中,每次实例保存的时候我们都需要加点私货, # 注意写法, 对于正常的保存功能,完全可以调用父类的方法 def save(self, *args, **kwargs): do_something() # Call the "real" save() method. super(Blog, self).save(*args, **kwargs) do_something_else()
6.6.4. 模型的继承¶
模型的诞生是为了OOP精神? 那么继承是不能少的,需要注意的是,模型的继承 最终也要反映到数据库的表的继承上。
模型基础主要分三类:
- 抽象继承
- 多表继承
- 代理继承
下面分别解释。
6.6.4.1. 抽象继承¶
抽象类的诞生,纯属是为了被继承, 子类只使用父类类持有信息,父类不会被单独使用。
此类继承当然不会产生父类相应的数据库表。
父类中属性
abstract
需要abstract=True
不生成任何数据表
子类继承Meta后,abstract=False,可以手动更改
示例代码如下:
from django.db import models class CommonInfo(models.Model): # ... class Meta: # 抽象类 abstract = True ordering = ['name'] class Student(CommonInfo): # ... class Meta(CommonInfo.Meta): db_table = 'student_info'
related_name 问题:
名称中包含 ‘%(app_label)s’和 ‘%(class)s’
代码示例:
from django.db import models class Base(models.Model): m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related") class Meta: abstract = True class ChildA(Base): pass class ChildB(Base): pass
6.6.5. 多表继承¶
子类继承父类后,子类会拥有父类的属性,但在数据库中父类 和子类不是一个表格,通过一个OneToOne的关系进行链接, 此时字类可以自由访问包含父类的所有属性。
示例如下:
from django.db import models class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) class Restaurant(Place): serves_hot_dogs = models.BooleanField(default=False) serves_pizza = models.BooleanField(default=False)
查询的时候可以通过子类的小写形式得到其对象
>>> Place.objects.filter(name="Bob's Cafe") >>> Restaurant.objects.filter(name="Bob's Cafe") >>> p = Place.objects.get(id=12) #如果p同时也是个Restaurant类,则可以通过p.restaurant访问 #否则会引发异常 >>> p.restaurant <Restaurant: ...>
一般情况子类不继承父类的Meta,在受限情况下,可有特例
6.6.6. 代理继承¶
代理继承是对原始模型的一个代理,可以创建,删除,更新代理model的实例, 不同之处在于,可以在代理model中改变默认的排序设置 和默认的manager,不会对原始model产生影响
示例如下:
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) class MyPerson(Person): class Meta: #代理继承 proxy = True def do_something(self): # ... pass
MyPerson 和Person操作同一个数据表
6.7. 模型层查询¶
本章讲述模型的相关具体操作,主要通过Manager的实例来操作。
实验代码:
from django.db import 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=50) email = models.EmailField() def __str__(self): # __unicode__ on Python 2 return self.name class Entry(models.Model): blog = models.ForeignKey(Blog) headline = models.CharField(max_length=255) body_text = models.TextField() pub_date = models.DateField() mod_date = models.DateField() authors = models.ManyToManyField(Author) n_comments = models.IntegerField() n_pingbacks = models.IntegerField() rating = models.IntegerField() def __str__(self): # __unicode__ on Python 2 return self.headline
save: 保存, 无返回值,直接执行insert操作
ForeignKey类型的变量:
直接对ForeignKey赋值即可
代码如下
>>> from blog.models import Entry >>> entry = Entry.objects.get(pk=1) >>> cheese_blog = Blog.objects.get(name="Cheddar Talk") >>> entry.blog = cheese_blog >>> entry.save()
ManyToMany属性的处理:
需要用到add函数来添加关联关系的记录
代码示例:
>>> from blog.models import Author >>> joe = Author.objects.create(name="Joe") >>> entry.authors.add(joe)
为了在一条语句中,向ManyToManyField添加多条记录,可以在调用add()方法时传入多个参数,像这样:
>>> john = Author.objects.create(name="John") >>> paul = Author.objects.create(name="Paul") >>> george = Author.objects.create(name="George") >>> ringo = Author.objects.create(name="Ringo") >>> entry.authors.add(john, paul, george, ringo)
6.7.1. 管理器¶
管理器就是ClassName.objects
, 用来对数据库表进行具体操作。
管理器只可以通过模型的类访问,而不可以通过模型的实例访问,
目的是为了强制区分表级别
的操作和记录级别
的操作。
返回结果是复数的,一般是一个QuerySet结果,单数的一般是一个object。
- 查询集(QuerySet):
表示从数据库中取出来的对象的集合。它可以含有零个、一个或者多个过滤器。
6.7.2. 过滤器¶
过滤器就是返回查询集的方法,即结果是QuerySet。
- all: 所有结果
- filter: 过滤
- exclude:不包括
- order_by:排序
- values:一个对象构成一个字典,然后构成一个列表返回
返回单个值的函数:
- get:
- 返回单个满足条件的对象
- 如果未找到会引发”模型类.DoesNotExist”异常
- 如果多条被返回,会引发”模型类.MultipleObjectsReturned”异常
- count:返回当前查询的总条数
- first:返回第一个对象
- last:返回最后一个对象
- exists():判断查询集中是否有数据,如果有则返回True
6.7.3. 查询集¶
查询集具有一些特性,在此做一个总结:
链式过滤:
如果查询集的筛选结果本身还是查询集,所以可以将筛选语句链接在一起。
代码如下:
>>> Entry.objects.filter( ... headline__startswith='What' ... ).exclude( ... pub_date__gte=datetime.date.today() ... ).filter( ... pub_date__gte=datetime(2005, 1, 30) ... )
每次过滤后结果相互独立不受彼此影响,如下q1,q2,q3相互独立:
>>> 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())
查询集的惰性问题:
下面代码只有在执行print(q)
语句才会真实查询数据库>>> 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)
查询集可以使用切片操作,
同样惰性,不会引发真实查询
Entry.objects.all()[:5]
但如果指定step则真实查询
Entry.objects.all()[:10:2]
不支持负索引,比如
Entry.objects.all()[-1]
get获取一个单一的对象
- get和
filter()[0]
有区别- get如果查询不到会引发DoesNotExist异常
- get如果查询结果多余1会引发MultipleObjectsReturned异常
- get和
6.7.4. 查询¶
SQL在查询的时候往往有很多条件限定,我们ORM本意是做一个完全的从OOP到 SQL的映射,所以带条件的查询是少不了的。
6.7.4.1. 查询参数¶
如果使用查询参数,待条件的查询使用方法如下pramater__querypara
, 比如要查询
年龄(age)小于18岁的人, 则age__lt=18
。
常见的带条件的查询如下:
exact: 精准等于
>>> Entry.objects.get(headline__exact="Man bites dog")
iexact: 大小写不敏感
>>> Blog.objects.get(name__iexact="beatles blog")
contains: 大小写敏感的包含关系
Entry.objects.get(headline__contains='Lennon') 大体翻译成 SELECT ... WHERE headline LIKE '%Lennon%';
icontains
istartwith
iendwith
gt: 大于
get: 大于等于
lt: 小鱼
lte: 小于等于
range: 范围
year: 年份
isnull:
6.7.4.2. 跨关系查询¶
跨关联关系的查询, 即查询之前定义的的表关系, 比如OneToOne, OneToMany之类的关系。
下面这个例子获取所有Blog 的name 为’Beatles Blog’ 的Entry 对象
# Blog:Entry = 1:N >>> Entry.objects.filter(blog__name='Beatles Blog')
还可以反向工作。若要引用一个“反向”的关系,只需要使用该模型的小写的名称
>>> Blog.objects.filter(entry__headline__contains='Lennon')
6.7.4.3. F表达式¶
F表达式用来表示同一模型中不同字段的比较。
使用的是需要先导入F查询:
from djaong.db.models import F
比如:
查找comments 数目多于pingbacks 的Entry
>>> from django.db.models import F >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django 支持对F() 对象使用加法、减法、乘法、除法、取模以及幂计算等算术操作
查找comments 数目比pingbacks 两倍还要多的Entry
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
查询rating 比pingback 和comment 数目总和要小的Entry
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
在 F 对象中使用双下划线标记来跨越关联关系。 带有双下划线的 F 对象将引入任何需要的join 操作以访问关联的对象
要获取author 的名字与blog 名字相同的Entry
>>> Entry.objects.filter(authors__name=F('blog__name'))
对于date 和date/time 字段,你可以给它们加上或减去一个timedelta 对象。
>>> from datetime import timedelta >>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
F() 对象支持.bitand() 和.bitor() 两种位操作
>>> F('somefield').bitand(16)
6.7.4.4. 查询快捷方式pk¶
Django 提供一个查询快捷方式pk ,它表示“primary key” 的意思。
在Blog 模型示例中,主键是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
pk 的使用不仅限于__exact 查询, 任何查询类型都可以与pk 结合来完成一个模型上对主键的查询
# 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)
6.7.4.5. 转义LIKE语句中的百分号和下划线¶
SQL语句中的百分号和下划线自动被转义:
>>> Entry.objects.filter(headline__contains='%')
上面可以转化成下面语句的SQL
SELECT ... WHERE headline LIKE '%\%%';
6.7.4.6. Q查询¶
filter() 等方法中的关键字参数查询都是一起进行“AND” 的
Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
下面的语句产生一个Q 对象,表示两个”question__startswith” 查询的“OR”
Q(question__startswith='Who') | Q(question__startswith='What') 它等同于一下SQL Where语句: WHERE question LIKE 'Who%' OR question LIKE 'What%'
可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。
同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询Q(question__startswith='Who') | ~Q(pub_date__year=2005)
每个接受关键字参数的查询函数(例如filter()、exclude()、get())都可以传递一个或多个Q 对象作为位置(不带名的)参数。如果一个查询函数有多个Q 对象参数,这些参数的逻辑关系为“AND”
Poll.objects.get( Q(question__startswith='Who'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) )
6.7.4.7. 比较对象¶
为了比较两个模型实例,只需要使用标准的Python 比较操作符,即双等于符号:==。
在后台,它会比较两个模型主键的值。
如果模型的主键不叫id,也没有问题。比较将始终使用主键,无论它叫什么
6.7.4.8. 删除对象¶
删除对象使用delete函数。
- 无返回值
- 是唯一没有在管理器 上暴露出来的查询集方法
- 当Django 删除一个对象时,它默认使用SQL ON DELETE CASCADE 约束
6.7.4.9. 拷贝模型实例¶
虽然没有内建的方法用于拷贝模型实例,但还是很容易创建一个新的实例并让它的所有字段都拷贝过来。
只需要将pk 设置为None
blog = Blog(name='My blog', tagline='Blogging is easy') blog.save() # blog.pk == 1 blog.pk = None blog.save() # blog.pk == 2
如果你用继承,那么会复杂,由于继承的工作方式,你必须设置pk 和 id 都为None
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 django_blog.pk = None django_blog.id = None django_blog.save() # django_blog.pk == 4
这个过程不会拷贝关联的对象。如果你想拷贝关联关系,你必须编写一些更多的代码
entry = Entry.objects.all()[0] # some previous entry old_authors = entry.authors.all() entry.pk = None entry.save() entry.authors = old_authors # saves new many2many relations
6.7.4.10. 一次更新多个对象¶
有时你想为一个查询集中所有对象的某个字段都设置一个特定的值,then update()
# Update all the headlines with pub_date in 2007. Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
若要更新ForeignKey 字段,需设置新的值为你想指向的新的模型实例
>>> b = Blog.objects.get(pk=1) # Change every Entry so that it belongs to this Blog. >>> Entry.objects.all().update(blog=b)
6.7.4.11. 关联的对象的查询¶
- 个Entry 对象e 可以通过blog 属性e.blog 获取关联的Blog 对象
- Django 还会创建API 用于访问关联关系的另一头, 即从关联的模型访问定义关联关系的模型。
例如,Blog 对象b 可以通过entry_set 属性 b.entry_set.all()访问与它关联的所有Entry 对象
6.7.4.11.1. 一对多关系¶
前向查询
如果一个模型具有ForeignKey,那么该模型的实例将可以通过属性访问关联的(外部)对象
>>> e = Entry.objects.get(id=2) >>> e.blog # Returns the related Blog object.
你可以通过外键属性获取和设置。和你预期的一样,对外键的修改不会保存到数据库中直至你调用save()
>>> e = Entry.objects.get(id=2) >>> e.blog = some_blog >>> e.save()
如果ForeignKey 字段有null=True 设置(即它允许NULL 值),你可以分配None 来删除对应的关联性
>>> e = Entry.objects.get(id=2) >>> e.blog = None >>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
一对多关联关系的前向访问在第一次访问关联的对象时被缓存。以后对同一个对象的外键的访问都使用缓存
>>> e = Entry.objects.get(id=2) >>> print(e.blog) # Hits the database to retrieve the associated Blog. >>> print(e.blog) # Doesn't hit the database; uses cached version.
select_related() 查询集方法递归地预填充所有的一对多关系到缓存中
>>> e = Entry.objects.select_related().get(id=2) >>> print(e.blog) # Doesn't hit the database; uses cached version. >>> print(e.blog) # Doesn't hit the database; uses cached version.
反向查询
如果模型I有一个ForeignKey,那么该ForeignKey 所指的模型II实例可以通过一个管理器返回前面有ForeignKey的模型I的所有实例。默认情况下,这个管理器的名字为foo_set,其中foo 是源模型的小写名称。该管理器返回的查询集可以用上一节提到的方式进行过滤和操作
>>> b = Blog.objects.get(id=1) >>> b.entry_set.all() # Returns all Entry objects related to Blog. # b.entry_set is a Manager that returns QuerySets. >>> b.entry_set.filter(headline__contains='Lennon') >>> b.entry_set.count()
你可以在ForeignKey 定义时设置related_name 参数来覆盖foo_set 的名称。例如,如果Entry 模型改成blog = ForeignKey(Blog, related_name=’entries’),那么上面的示例代码应该改成这样
>>> b = Blog.objects.get(id=2) >>> b.entries.all() # Returns all Entry objects related to Blog. # b.entries is a Manager that returns QuerySets. >>> b.entries.filter(headline__contains='Lennon') >>> b.entries.count()
6.7.4.11.2. 处理关联对象的其它方法¶
- add(obj1, obj2, …): 添加一指定的模型对象到关联的对象集中。
- remove(obj1, obj2, …): 从关联的对象集中删除指定的模型对象
- lear(): 从关联的对象集中删除所有的对象
6.7.4.11.3. 多对多关系¶
对多关系的两端都会自动获得访问另一端的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.
类似ForeignKey,ManyToManyField 可以指定related_name
6.7.4.11.4. 一对一关系¶
- 如果你在模型中定义一个OneToOneField,该模型的实例将可以通过该模型的一个简单属性访问关联的模型
- 例如:
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
- 在“反向”查询中有所不同。一对一关系中的关联模型同样具有一个管理器对象,但是该管理器表示一个单一的对象而不是对象的集合
e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object
6.8. models API¶
本章主要记录一些Django的API常见的问题。
save发生了什么
- 发出一个pre-save 信号: 发送一个django.db.models.signals.pre_save 信号, 以允许监听该信号的函数完成一些自定义的动作
- 预处理数据: 如果需要,对对象的每个字段进行自动转换。
- 准备数据库数据: 要求每个字段提供的当前值是能够写入到数据库中的类型。
- 据到数据库中: 将预处理过、准备好的数据组织成一个SQL 语句用于插入数据库。
- 出一个post-save 信号: 发送一个django.db.models.signals.post_save 信号,以允许监听听信号的函数完成一些自定义的动作。
Django如何分辨是Update还是Insert
- 一个求值为True 的值(例如,非None 值或非空字符串),Django 将执行UPDATE。
- 如果对象的主键属性没有设置或者UPDATE 没有更新任何记录,Django 将执行INSERT。
强制使用Insert或者Update
- 可以传递force_insert=True 或 force_update=True 参数给save() 方法。
其他模型实例方法
unicode(): python3废弃
str: 对对象调用str的时候调用
get_FOO_display: 对于每个具有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=2, choices=SHIRT_SIZES) >>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() >>> p.shirt_size 'L' >>> p.get_shirt_size_display() 'Large'