第02章 模型层
models
- 模型介绍
- 字段
- 索引
- 元类选项
- 元类模型
模型介绍
一般来说,每个模型映射到一个数据库表ORM
- 每个模型都是一个python类,这些类继承
django.db.models.Model
- 模型类的每个属性都相当于一个数据库字段
- 综上,django给你一个自动生成访问数据库的API
快速上手
定义一个模型
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
上面模型映射到的数据库表
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
说明:
- 表名中是自动从模型元数据中派生出来,但可以被改写。
- 默认为
$APP_NAME_$CLASS_NAME.lower()
- 默认为
- 默认自动添加主键
id
字段,也可以自定义。
使用模型
定义了模型后,需要告诉Django你准备使用这些模型(映射模型)。修改项目设置文件中的INSTALLED_APP
,在这个设置中添加包含你models.py
文件的应用模块名
INSTALLED_APPS = [
#...
'myapp',
#...
]
添加应用模块后,记得做数据库迁移:python manage.py migrate
基本操作
def index(request):
#1.使/用oRM添加一条数据到数据库中
#book=Book(name='西游记",author='吴承恩',price=100)
#book.save()
#2.查询
#2.1.根据主键进行查找
#primary key
#book=Book.objects.get(pk=2)
#print(book)
#2.2.根据其他条件进行查找
#books=Book.objects.filter(name='三国演义).first()
#print(books)
#3.刷除数据#book=Book.objects.get(pk=1)
#book.delete()
#4.修改数据
book=Book.objects.get(pk=2)
book.price=200 book.save()
return HttpResponse("图书添加成功!")
字段
模型中最重要的部分就是定义字段,模型(类)由类属性定义字段。
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
字段类型
每个字段由一个适当的Field
类实例定义,Django会使用字段类型做一些事情
- 用于定义数据库保存的列数据类型
- 用于渲染HTML组件表单域
- django后台用来做验证
常用字段
常用字段 | 说明 |
---|---|
AutoField() |
自增长字段,映射到DB中的int,一般不需要使用 |
BigAutoField() |
同上,64位 |
BooleanField() |
在模型层面是True/False,在DB中是 tinyint类型。如果没有指定,默认为None |
NullBooleanField() |
|
CharField(max_length=30) |
使用varchar(254)保存;如果>254字符,就不建议使用了 |
TextField() |
DB中是longtext |
InTegerField() |
整形。-2147483648~2147483647 |
BigIntegerField() |
大整形。-9223372036854775808~9223372036854775807 |
SmallIntegerField() |
小整形。-32768~32767 |
PositiveIntegerField() |
正整形,无符号。0~2147483647 |
PositiveSmallIntegerField() |
正小整形 0~32767 |
EmailField() |
类似CharField。varchar(254),用于支持ModelForm表单操作,在数据库中没有其他意义 |
FileField() |
用来存储文件 |
ImageField() |
用来存储图片,继承自FieField,自动验证上传的对象是否是有效图片。为了方便查询,带两个可选参数。 |
- 依赖pillow库
- 在DB中使用varchar(100)保存, |
| | 浮点类型,DB中是float类型 |
|DateField()
| 日期类型,在python中是datetime.date
,在DB中也是date,可以记录年月日。可以传递以下几个参数:
auto_now
:在每次这个数据保存时,使用当半的时间。比如作为一个记录修改日期的字段,可以将这个属性设置为Trueauto_now_add
:在数据第一次添加进到的时候,使用当前的时间。比如作为一个记录第一次入库的字段,可以将这个属性设置为True |
|DateTimeField()
| 类似DateField(),还可以存储时间。DB中是 datetime类型,这个字段也支持上面字段的两个参数。 |
|TimeField()
| 时间类型。在DB上是time类型,在python中是datetime.time
|
|UUIDField()
| 只能存储UUID字符串
uuid是一个32位的全球唯一的字符串,一般用来做pk |
|URLField()
| |
官方地址: https://docs.djangoproject.com/zh-hans/2.0/ref/models/fields/#imagefield
关于时间
官方参考地址: https://docs.python.org/3/library/datetime.html
naive time
和 aware time
navie time
: 幼稚时间(不带时区)aware time
:成熟时间(带时区)
# naive time 幼稚时间、天真时间 - 指不带时区的
naive date: "2017-09-29"
naive time: "15:00:00"
naive datetime: "2017-09-29T15:00:00"
# 指定时区(或者偏移量)来表示特定时间
datetime in UTC: "2017-09-29T15:00:00Z"
datetime with timezone offset: "2017-09-29T15:00:00+0300"
datetime with timezone: datetime="2017-09-29T15:00:00", timezone=
- datetime with timezone: datetime="2017-09-29T15:00:00", timezone=
http://kean.github.io/post/naive-date-time
astimezone()
将一个时区的时间转换为另外一个时区的时间。这个方法只能被aware
类型的时间调用。不能被naive
类型的时间调用。
replace()
:
可以将一个时间的某些属性进行更改。
django.utils.timezone.now()
会根据settings.py
中是否设置了USE_TZ=True
获取当前的时间。如果设置了,那么就获取一个aware
类型的UTC
时间。如果没有设置,那么就会获取一个navie
类型的时间。
django.utils.timezone.localtime
方法:
会根据setting.py
中的TIME_ZONE
来将一个aware
类型的时间转换为TIME_ZONE
指定时区的
naive
和aware
介绍以及在 django 中的用法:
https://docs.djangoproject.com/en/2.0/topics/i18n/timezones/
什么是UUID
UUID,是Universally Unique Identifier的缩写,UUID出现的目的,是为了让分布式系统可以不借助中心节点,就可以生成UUID来标识一些唯一的信息;
GUID,是Globally Unique Identifier的缩写,跟UUID是同一个东西,只是来源于微软。
规范定义
UUID来自于IETF发布的一个规范:A Universally Unique IDentifier (UUID) URN Namespace。
UUID来源于OSF的DCE规范,也就是RFC4122的前身
GUID来源于微软,注意RFC4122的作者之一是微软员工
UUID = 128bit_number
uuid = 123e4567-e89b-12d3-a456-426655440000
UUID版本
标准格式
UUID
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
- M版本号,
- N只能是8/9/a/b
| | 说明 |
| —- | —- |
| UUID v1 | 基于时间+MAC (缺点暴露了MAC) |
| UUID v2 | 基于时间+DEC |
| UUID v3 | MD5(namespace, str) 为了向后兼容 |
| UUID v4 | 基于随机数 |
| UUID v3 | SHA1(namespace, str) |
字段示例代码
# image字段参数
ImageField(upload_to=None,height_field=None,Width_field=None,max_length=100, **options)
字段选项
每个字段可指定特定的选项,全部字段也有一组通用的选项。以下介绍常用选项
options | 说明 |
---|---|
null | 布尔,默认False;指定数据库中的NULL(纯粹与DB相关) |
- 在使用基于字段串的字段时(CharField和TextField),官方不推荐使用此选项,也就是保持默认;默认值False。
- 如果想要在表单验证的时候允许这个字符串为空,那么建议使用blank=True。
- 如果你的字段是BooleanField,那么对应的可空的字段则为NullBooleanField
总结:不能为空,置“”空字符串;可以为空,置null |
| blank | 布尔,默认False;验证表单是否允许为空(仅与验证相关) |
| default | 默认值,可以为一个值/可调用对象,每次创建新对象时调用;但是不支持 lambda表达式。并且不支持列表/字典/集合。保存到DB之前,取一个默认值 |
| help_text | 用于表单组件 form,即使没有在表单上使用,也可用于文档 |
| db_column | 字段在数据库中的名字,如果没有设置,默认使用模型中属性的名字。 |
| primary_key | 用于指定数据库主键。如果没有指定,django会自动添加一个IntegerField字段用作主键 |
| unique | 用于指定唯一键,常用于手机号/邮箱 |
| choices | 指定2元组的一个列表/元组 |
关于 null 选项:
布尔,默认False;指定数据库中的NULL(纯粹与DB相关)
在使用字段串相关的字段时(CharField和TextField),官方不推荐使用此选项,也就是保持默认;默认值False。因为django在处理字符串字段时,即使 null=False
,如果你没有给这个Field传递任何值,那么django也会使用一个“”空字符串作为默认值存储进去。 因此如果再使用null=True
,django会产生两种空值的情形(NULL或者空字符串)。
避免在基于字符串的字段(如CharField和TextField)上使用null。
如果基于字符串的字段为null = True,则表示它有两个可能的值为“无数据”:
- NULL
- 空字符串。
如果想要在表单验证的时候允许这个字符串为空,那么建议使用blank=True。
如果你的Field是BooleanField,那么对应的可空的字段则为NullBooleanField
总结:
不能为空,置“”空字符串;可以为空,置null
选项示例代码
# 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)
# 第一个元素保存在数据库,第二个元素用于表单form组件显示。
# 可以使用模型实例的 get_Model_display 方法获取显示值
# > p = Person(name="Fred", shirt_size="L")
# > p.save()
# > p.shirt_size
# 'L'
# > p.get_shirt_size_display()
# 'Large'
以上为常用字段选项,官网地址https://docs.djangoproject.com/zh-hans/2.0/ref/models/fields/
关于字段名字
除了ForeignKey/ManyToManyField/OneToOneField
外,每个字段使用可选的第一个位置参数定义字段名称
# 指定名称 "person's first name"
first_name = models.CharField("person's first name", max_length=30)
# 使用字段属性名做为默认名称 "first name"
# 使用默认名称时,属性字段中的下划线会转换为空格
first_name = models.CharField(max_length=30)
使用默认名称时,属性字段中的下划线会转换为空格
django自带用户模型
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser
class UserProfile(AbstractUser):
"""
用户
"""
name = models.CharField(max_length=30, null=True, blank=True, verbose_name="姓名")
birthday = models.DateField(null=True, blank=True, verbose_name="出生年月")
mobile = models.CharField(max_length=11,verbose_name="电话")
gender = models.CharField(max_length=6, choices=(("male", "男"), ("female", "女")), default="female", verbose_name="性别")
email = models.CharField(max_length=100, null=True, blank=True,verbose_name="邮箱" )
class Meta:
verbose_name = "用户"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class VerifyCode(models.Model):
"""
短信验证码
"""
code = models.CharField(max_1ength=10, verbose_name="验证码")
mobile = models.CharField(max_length=11, verbose_name="电话")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "短信验证码"
verbose_name_plural = verbose_name
def __str__(self):
return self.code
自定义用户模型替换系统用户模型
# setting.py
# AUTH_USER_MODEL = 'myapp.MyUser'
AUTH_USER_MODEL = 'users.UserProfile'
# 上面的值表示Django应用的名称(必须位于INSTALLLED_APPS中)和你想使用的User模型的名称
Meta选项
功能:对于一些模型(表)级别的配置。
我们可以在模型中定义一个类,叫做Meta
。然后在这个类中添加一些类属性来控制模型的作用。比如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称。那么我们可以在Meta类中添加一个db_table
的属性。
示例代码如下:
class Book(mode1s.Mode1):
name=models.CharField(max_lengthe=20,null=False)
desc=mode1s.CharField(max_fength=100, name='description',db_column='description1')
class Meta:
db_table='book_model'
ordering = ['pub_data']
# ordering = ['create_time', 'id']
# ordering = ['-create_time', 'id']
常用属性
选项 | 说明 |
---|---|
abstract |
|
app_label |
如果这个APP是在INSTALLED_APPS 之外定义的,就必须声明 |
base_manager_name |
b |
db_table |
指定DB表名 |
模型映射到数据库中的表名。如果没有指定这个参数,那么在映射的时候将会使用模型名来作为默认的表名。 为了节省时间,DJANGO自动从模型类的名称和包含它的app中派生出表名。默认为:应用名_模型名 Bookbookstore_book |
|
db_tablespace |
定义DB表空间名称。 |
表空间:是DB的逻辑划分,一个表空间只能属于一个数据库。所有的数据库对象都存放在指定的表空间中。但主要存放的是表,所以称为表空间 | |
get_latest_by |
一个字段,或者字段列表,通常是DateField/DataTimeField/IntegerField |
ordering |
设置在提取数据的排序方式。后面章节会讲到如何查找数据。 |
indexes |
指定模型上定义的索引列表 |
indexes=[models.Index(fields='last_name', 'first_name']) |
|
verbose_name |
对象的人类可读名称 |
verbose_name_plural |
对象的复数名 |
参考地址: https://docs.djangoproject.com/zh-hans/2.0/ref/models/options/
外键和表关系
外键
在mySQL中,表有两种引擎,一种是ImnoDB,另外一种是myisam。如果使用的是ImnoDB引擎,是支持外键约束的。外键的存在使得ORM框架在处理表关系的时候异常的强大。因此这里我们首先来介绍下外键在Django中的使用。
类定义为class ForeignKey(to, on_delete,**options)
- 第一个参数是引用的是哪个模型,
- 第二个参数是在使用外键引用的模型数据被除了,这个字段该如何处理,比如有CASCADE、SET_NULL等。
数据库自动为ForeignKey创建索引。可以通过db_index=False禁用此功能。
数据库表示
django创建外键名称时,自动追加_id
命名;如果要显示指定外键列名称必须使用db_column
指定。
这里以一个实际案例来说明。比如有一个User和一个Article两个模型。一个User可以发表多篇文章,一个Article只能有一个Author,并且通过外键进行引用。那么相关的示例代码如下:
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
# on_delete 必须传递
author = models.ForeignKey("User", on_delete=models.CSCADE)
以上使用Foreignkey
来定义模型之间的关系。即在article的实例中可以通过author
属性来操作对应的user模型。这样使用起来非常的方便。示例代码如下:
article=Article(title='abc', content='123')
author = User(username='张三', password='111111')
article.author = author
article.save()
django 处理外键方式:自动在数据库中生成定义外键时的
属性名_ID
外键引用关系。
完整的外键示例
role = models.ForeignKey(to="roles.Role", to_field="role_code", default=settings.ROLE_CODE, on_delete=models.DO_NOTHING, db_column='role_code',
verbose_name="角色")
- to : apps.models 或者 models
- to_field : 模型上的字段
- db_column: 指定保存时数据库字段名
- on_delete : 指定外键删除时动作
外键的基本使用
# models.py
from django.db import models
class Category(models.Model):
name=models.CharField(max_length=100)
class Article(models.Model):
title=models.CharField(max_length=100)
content=models.TextField()
category=models.Foreignkey(to="Category",on_delete=models.CASCADE)
可选参数,下面介绍较为常用的几个:
- to_field: 设置关联到主表的字段,例如
role = models.ForeignKey(to="roles.Role", to_field="role_code", default=1,
on_delete=models.DO_NOTHING,
verbose_name="角色")
注意:关联的字段的内容必须是不重复的。在默认情况下,Django关联到的字段是主表的主键(成为主键的要求之一就是不重复)
- related_name: 自定义一个名称,通常是复数形式,用于返回查询
当一张子表里,多个foreignkey指向同一个主表,related_name必须设置```python
ModelA
model_b = ForeignKeyField(to=ModelB, related_name=’model_as’)
访问ModelA与ModelB实例相关的实例
model_b_instance.model_as.all()
3. related_query_name: 用于django 查询集,它允许您过滤外键相关字段的反向关系。通常使用单数形式
```python
# ModelA
model_b = ForeignKeyField(to=ModelB, related_query_name='model_a')
# 将使您可以model_a用作查询集中的查找参数
ModelB.objects.filter(model_a=whatever)
官方文档:没有必要指定两者(或者其中之一),related_name和related_query_name。Django框架设置具有合理的默认值。
视图
# 以下代码报错
def index(request):
article = Article(title='abc', content='111')
category = Category(name="最新文章")
article.category = category
# 异常: save() prohibited to prevent data loss due to unsaved related objet 'category'
article.save()
# 修正
def index(request):
category = Category(name="最新文章")
category.save()
article = Article(title='abc', content='111')
article.category = category
article.save()
不同app之间的外键引用
class Article(models.Model):
title=models.CharField(max_length=100)
content=models.TextField()
# 不同app之间的外键引用,使用 appname.modelname
category=models.Foreignkey("blog.Category",on_delete=models.CASCADE)
外键引用自己本身的模型
class Comment(models.Model):
content=models.TextField()
origin_comment=models.Foreignkey("self",on_delete=models.CASCADE, null=True)
#或者
#origin_comment=models.Foreignkey("Comment",on_delete=models.CASCADE, null=True)
外键的删除操作
如果一个模型使用了外键。那么在对方那个模型被删除后,该进行什么样的操作。可以通过on_delete来指定。可以指定的类型有
选项 | 说明 |
---|---|
models.CASCATE |
级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。 |
models.PROTECT |
受保护。即只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据。 |
models.SET_NULL |
设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空。 |
models.SET_DEFAULT |
设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是要指定这个字段一个默认值。 |
models.SET() |
如果外键的那条数据被除了。那么将会获取SET函数中的值来作为这个外键的值.SET函数可以接收一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去。 |
models.DO_NOTHING |
不采取任何行为。一切全看数据库级别的约束。 |
举例
class Article(models.Model):
title=models.CharField(max_length=100)
content=models.TextField()
# 使用 SET_NULL 时,需要设置null
category = models.ForeignKey("Category", on_delete=models.SET_NULL, null=True)
# 使用 SET_DEFAULT,需要设置default
category = models.ForeignKey("Category", on_delete=models.SET_DEFAULT, null=True, default=Category.objects.get(pk=4))
# 使用SET()
category = models.ForeignKey("Category", on_delete=models.SET(Category.objects.get(pk=4)), null=True)
注意: 以上这些选择只是django级别的,数据库级别依旧是RESTRICT
表关系
表之间的关系都是通过外键来进行关联的。而表之间的关系,无非就三种关系:
- 一对一
- 一对多(多对一)
- 多对多
一对多
ForeignKey
- 应用场景:如文章和作者之间的关系,一个文章只能有一个作者,但是一个作者可以写多篇文章,这是典型的多对一关系。
- 实现方式:一对多或者多对一,都是通过
ForeignKey
来实现的。还是以文章和作者的案例进行讲解。
ForeignKey(othermodel, on_delete, **option)
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey("User",on_delete=models.CASCADE)
那么以后在给Article
对象指定author
,就可以使用以下代码来完成:
article = Article(title='abc',content='123')
author = User(username='xiongda',password='111111')
# 要先保存到数据库中
author.save()
article.author = author
article.save()
并且以后如果想要获取某个用户下所有的文章,可以通过article_set
来实现。示例代码如下:
user = User.objects.first()
# 获取第一个用户写的所有文章
articles = user.article_set.all()
for article in articles:
print(article)
一对一
OneToOneField(someModel)
可以理解为ForeignKey(SomeModel, unique=True)
两者的反向查询是有区别的:
- ForeginKey 反向查询返回的是一个列表
- OneToOneField 反向查询返回的是一个模型实例(因为一对一关系)
The Definitive Guide to Django http://djangobook.com/
OneToOneField 关系类似于 ForeignKey(unique=True),但是“reverse”时一边直接返回的是单个实例。
ForeignKey()的“reverse”返回的是一个 QuerySet
特点:
- 应用场景:比如一个用户表和一个用户信息表。在实际网站中,可能需要保存用户的许多信息,但是有些信息是不经常用的。如果把所有信息都存放到一张表中可能会影响查询效率,因此可以把用户的一些不常用的信息存放到另外一张表中我们叫做
UserExtension
。但是用户表User
和用户信息表UserExtension
就是典型的一对一了。 - 实现方式:
Django
为一对一提供了一个专门的Field
叫做OneToOneField
来实现一对一操作。示例代码如下:
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)
class UserExtension(models.Model):
birthday = models.DateTimeField(null=True)
school = models.CharField(blank=True,max_length=50)
user = models.OneToOneField("User", on_delete=models.CASCADE)
在UserExtension
模型上增加了一个一对一的关系映射。其实数据库底层是在UserExtension
这个表上增加了一个UNIQUE INDEX user_id
外键 ,来和user
表进行关联且保证一对一。
CREATE TABLE `user_status` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`realname_status` INT(11) NULL DEFAULT NULL,
`phone_status` INT(11) NULL DEFAULT NULL,
`qq_status` INT(11) NULL DEFAULT NULL,
`mail_status` INT(11) NULL DEFAULT NULL,
`education_status` INT(11) NULL DEFAULT NULL,
`user_id` INT(11) NOT NULL,
PRIMARY KEY (`id`),
# 参考
UNIQUE INDEX `user_id` (`user_id`),
CONSTRAINT `user_status_user_id_dd266ed5_fk_user_info_id` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=3
;
多对多
ManyToManyField
- 应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。
- 实现方式:
Django
为这种多对多的实现提供了专门的Field
。叫做ManyToManyField
。还是拿文章和标签为例进行讲解。示例代码如下:
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
tags = models.ManyToManyField("Tag",related_name="articles")
class Tag(models.Model):
name = models.CharField(max_length=50)
在数据库层面,实际上Django
是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到article
和tag
两张表的主键。
related_name和related_query_name
related_name
还是以User
和Article
为例来进行说明。如果一个article
想要访问对应的作者,那么可以通过author
来进行访问。但是如果有一个user
对象,想要通过这个user
对象获取所有的文章,该如何做呢?这时候可以通过user.article_set
来访问,这个名字的规律是模型名字小写_set
。示例代码如下:
user = User.objects.get(name='张三')
user.article_set.all()
如果不想使用模型名字小写_set
的方式,想要使用其他的名字,那么可以在定义模型的时候指定related_name
。示例代码如下:
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
# 传递related_name参数,以后在方向引用的时候使用articles进行访问
author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles')
以后在方向引用的时候。使用articles
可以访问到这个作者的文章模型。示例代码如下:
user = User.objects.get(name='张三')
user.articles.all()
如果不想使用反向引用,那么可以指定related_name='+'
。示例代码如下:
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
# 传递related_name参数,以后在方向引用的时候使用articles进行访问
author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='+')
以后将不能通过user.article_set
来访问文章模型了。
related_query_name
在查找数据的时候,可以使用filter
进行过滤。使用filter
过滤的时候,不仅仅可以指定本模型上的某个属性要满足什么条件,还可以指定相关联的模型满足什么属性。比如现在想要获取写过标题为abc
的所有用户,那么可以这样写:
users = User.objects.filter(article__title='abc')
如果你设置了related_name
为articles
,因为反转的过滤器的名字将使用related_name
的名字,那么上例代码将改成如下:
users = User.objects.filter(articles__title='abc')
可以通过related_query_name
将查询的反转名字修改成其他的名字。比如article
。示例代码如下:
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
# 传递related_name参数,以后在反向引用的时候使用articles进行访问
author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles',related_query_name='article')
那么在做反向过滤查找的时候就可以使用以下代码:
users = User.objects.filter(article__title='abc')
模型实例参考
官方文档:https://docs.djangoproject.com/zh-hans/2.0/ref/models/instances/
创建对象
class Model(**kwargs)
keyword arguments 需要是定义的模型上的字段fields,注意:实例化不会访问数据库,调用**save()**
时才会
验证对象
验证对象涉及三个步骤
- 验证模型字段
Model.clean_fields()
- 验证整个模型
Model.clean()
- 验证字段唯一性
Model.validate_unique()
当调用 Model.full_clean()
时,所有以上三个步骤都会执行
Model.full_clean(exclude=None, validate_unique=True)[source]
此方法按此顺序调用Model.clean_fields() -> Model.clean() -> Model.validate_unique()
模型的操作
在ORM
框架中,所有模型相关的操作,比如添加/删除等。其实都是映射到数据库中一条数据的操作。因此模型操作也就是数据库表中数据的操作。
查询
查询是数据库操作中一个非常重要的技术。查询一般就是使用filter/exclude/get
三个方法实现。我们可以在调用这些方法的时候传递不同的参数来实现查询需求。在ORM层面,这些查询条件都是使用field + __ +condition
的方式来使用的。以下将那些常用的查询条件来一一解释。
filter 返回的是 QuerySet对象
get 返回的是模型对象
查询条件
查询条件 | 代码 | 说明 |
---|---|---|
exact |
Article.objects.get(id__exact=14) |
精确查询,底层SQL使用= 号 |
iexact |
Article.objects.get(title__iexact="hello word") |
同上,忽略大小写;底层SQL使用的是like |
contains |
Article.objects.filter(title__contains='hello') |
包含判断(大小写敏感);底层是LIKE BINARY %hello% |
icontains |
Article.objects.filter(title__icontains='hello') |
同上,忽略大小写;底层LIKE %hello% |
in |
Article.objects.filter(id__in=[1, 2, 3]) |
指定的值是否在指定的容器中,容器可以为list/tuple或者任何一个可迭代对象,包括QuerySet对象 ;底层是 where id in (1, 2 3) |
gt |
Article.objects.filter(id__gt=2) |
> |
gte |
>= |
|
lt |
< |
|
lte |
<= |
|
startswith |
开始;底层LIKE BINARY hello% |
|
istartswith |
开始,忽略大小写;底层LIKE hello% |
|
endswith |
结尾;底层LIKE BINARY %hello |
|
iendswith |
结尾,忽略大小写;底层LIKE %hello |
|
range |
Article.objects.filter(pub_date__range=(start_date, end_date)) |
是否在给定的区间中 |
date |
Article.objects.filter(pub_date__date=date(2018, 3,29)) |
针对date或者datetime 类型字段。可以指定date的范围来过滤,且支持链接调用 |
year |
Article.objects.filter(pub_date__year=2018 |
|
Article.objects.filter(pub_date__year_gte=2018 |
按年份查询 | |
month |
按月份查询 | |
day |
按日查询 | |
week_day |
按星期查询,2-6周一到周五,7周六,1周日(django1.11+) | |
time |
Article.objects.filter(pub_date__time=datetime.time(12,12,12) |
|
Article.objects.filter(pub_date__time__range=(stime, etime) |
按时间查询 | |
isnull |
Article.objects.filter(pub_date__isnull=False |
是否为空 |
regex |
Article.objects.filter(pub_date__regex=r"^hello" |
按正则查询 |
iregex |
Article.objects.filter(pub_date__iregex=r"^hello" |
同上,忽略大小写 |
说明:
QuerySet.query
为ORM最终翻译的SQL语句
query
只能用在QuerySet
对象上
exact
和iecact
就是等号与LIKE的区别,大部分情况下相同,
定义外键时,可以使用related_query_name
来指定反向引用的名字
反向查询是将模型名字小写化。比如article__in
。可以通过related_query_name
来指定自己的方式,而不使用默认的方式。
反向引用是将模型名字小写化,然后再加上_set
,比如article_set
,可以通过related_name
来指定自己的方式,而不使用默认的方式。
原时间与UTC时间的转换,可以使用from django.utils.timezone import make_aware
函数
默认情况下MySQL
的表中没有存储时区相关的信息。因此我们需要下载一些时区表的文件http://dev.mysql.com/downloads/timezones.html
,然后添加到MySQL
的配置路径中。如果你用的是:
- windows:
C:\ProgramData\MySQL\MySQL Server\5.7\Data\mysql
中覆盖 - linux:
$ mysql_tzinfo_to_sql /usr/share/zoneinfo |mysql -D mysql -u root -p
将时区文件更新到mysql
按时间**time**
查询时可以使用**range**
指定查询范围,官方文档指定的**between**
无法使用
根据关联的表进行查询:
categories = Category.objects.filter(article__title__contains("hello"))
聚合函数
分类汇总
如果你用原生SQL,则可以使用聚合函数 aggregate 来提供数据。比如提取某个商品销售的数量,那么可以使用Count
,如果想要知道商品销售的平均价格,那么可以使用Avg
模块地址:from django.db.models import Avg
聚合函数 | 代码 | 说明 |
---|---|---|
Avg |
Book.objects.aggregate(Avg('price')) |
|
Book.objects.aggregate(my_avg=Avg('price')) |
返回dict;可以使用关键字参数指定名字 | |
Count |
Author.objects.aggregate(count=Count('email', distinct=True)) |
distinct 去重 |
Max |
Author.objects.aggregate(Max('age'),Main('age')) |
|
Min |
Author.objects.aggregate(Max('age'),Main('age')) |
|
Sum |
Book.objects.annotate(total=Sum("bookstore__price")).values("name", "total") |
给Book表在查询时添加一个字段total,数据来源BookStore模型的price的总和。values方法是只提取name和total两个字段的值。 |
说明:
- aggregate:返回
dict
类型;使用聚合函数后的字段和值,不会做分组(group by) - annotate:返回
QuerySet
;在原来模型字段的基础上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型的主键进行分组(group by)
F
表达式
F表达式是用来优化ORM操作数据库的。
比如我们要将公司所有员工的薪水都增加1000元,如果按照正常的流程,应该是先从数据库中提取所有的员工工资到Python内存中,然后使用Python代码在员工工资的基础上增加1000元,最后再保存到数据库中。这里面涉及的流程就是,首先从数据库中提取数据到Python内存中,然后在Python内存中做完运算,之后再保存到数据库中。示例代码如下:
employees = Employee.objects.all()
for employee in employees:
employee.salary += 1000
employee.save()
而我们的F表达式
可以优化这个流程,他可以不需要先把数据提取出来,计算完成后再保存回去,他可以直接执行SQL语句,就将员工的工资增加1000元。示例代码如下:
form django.db.models import F
Employee.object.update(salary=F("salary"+1000)
F表达式
并不会马上从数据库中获取数据,而是在生成SQL语句时,动态获取传给F表达式
的值。
比如如果想要获取作者表中,name和email相同的作者数据。如果使用F表达式
,那么需要使用如下代码来完成:
authors = Author.objects.all()
for author in authors:
if author.name == author.email:
print(author)
如果使用F表达式
,那么一行代码就可以
from django.db.models import F
author = Author.objects.filter(name=F("email"))
Q
对象
如果想要实现所有价格高于100元,并且评分达到9.0
以上的图书。那么可以:
books = Book.objects.filter(price__gte=100,rating__gte=9)
以上这个安全是一个并集查询,可以简单的通过传递多个条件进去来实现。
但是如果想要实现一些复杂的查询语句,比如要查询所有价格低于10元,或者是评分低于9分的书。那就没有办法通过传递多个条件进去实现了。这时候就需要使用Q表达式
来实现
from django.db.models import Q
boos = Book.objects.filter(Q(price__lte=10) | Q(rating__lte=9))
以上是进行或运算,当然还可以进行其他的运算,比如有&/~
等。
from django.db.models import Q
books = Book.objects.filter(Q(id=3))
books = Book.objects.filter(Q(id=3) | Q(name__contains("记")))
books = Book.objects.filter(Q(price__gte=100) & Q(name__contains("记")))
books = Book.objects.filter(Q(name__contains("记") & ~Q(id=3))
QuerySet API
在使用QuerySet
进行查询时,可以提供多种操作。比如过滤完后还要根据某个字段进行排序,那么这一系列的操作我们可以通过一个非常流畅的链式调用
进行。比如要从文章表中获取标题为123
,并且提取后要将结果根据发布的时间进行排序,那么可以使用以下方式来完成
articles = Article.objects.filter(title='123').order_by('create_time')
返回QuerySet
对象的方法
method | 代码 | 说明 |
---|---|---|
filter |
Article.objects.filter(title='123') |
过滤 |
exclude |
排除 | |
annotate |
Article.objects.annotate(author_name=F("author__name")) |
给QuerySet 中的每个对象添加一个使用查询表达式(聚合函数、F表达式、Q表达式、Func表达式等)的新字段。 |
order_by |
||
values |
指定要提取的数据字段,默认返回所有属性 | |
values_list |
同上,返回元组 | |
all |
获取ORM模型的QuerySet对象 | |
select_related |
在提取模型数据时,也提前将相联的数据提取出来 | |
prefetch_related |
||
defer |
||
only |
||
get |
||
create |
||
get_or_create |
obj, created = Person.objects.get_or_create( first_name='John', last_name='Lennon', defaults={'birthday': date(1940, 10, 9)}, ) |
返回(object, created) |
buld_create |
||
count |
||
exists |
||
distinct |
||
update |
Entry.objects.filter(id=10).update(comments_on=False) |
如果你只是更新记录而不需要对模型对象做任何事件,最有效的方法是调用update(),而不是将模型对象加载到内存中。 |
delete |
QuerySet转换为SQL的条件
生成一个QuerySet对象并不会马上转换为SQL语句去执行。
以下情况下QuerySet会被转换为SQL语句执行
- 迭代
- 使用步长做切片操作
- 调用
len函数
- 调用
list函数
- 判断:
model instances
- 实例方法
- 访问相关对象
Migrations
- 迁移介绍
- 操作参考
- 构架编辑
- 迁移
advanced
- 管理
- 祼SQL
- 事务
- Aggregation
- 搜索
- 自定义字段
- 多数据库
- 自定义查询
- 查询表达式
- 条件表达式
- 数据库函数
other
- 支持的数据库
- 传统数据库
- 提供初始数据
- 数据库优化
- PostgreSQL特定功能