Django配置连接数据库
settings.py文件中数据库相关的配置
1 | DATABASES = { |
在Django中操作数据库
下面是使用原生sql语句操作
1 | # 使用django封装好的connection对象,会自动读取settings.py中数据库的配置信息 |
以上的execute
以及fetchall
方法都是Python DB API
规范中定义好的。任何使用Python
来操作MySQL
的驱动程序都应该遵循这个规范。所以不管是使用pymysql
或者是mysqlclient
或者是mysqldb
,他们的接口都是一样的。
Python DB API下规范下cursor对象常用接口:
description
:如果cursor
执行了查询的sql
代码。那么读取cursor.description
属性的时候,将返回一个列表,这个列表中装的是元组,元组中装的分别是(name,type_code,display_size,internal_size,precision,scale,null_ok)
,其中name
代表的是查找出来的数据的字段名称,其他参数暂时用处不大。rowcount
:代表的是在执行了sql
语句后受影响的行数。close
:关闭游标。关闭游标以后就再也不能使用了,否则会抛出异常。execute(sql[,parameters])
:执行某个sql
语句。如果在执行sql
语句的时候还需要传递参数,那么可以传给parameters
参数。示例代码如下:
1 | cursor.execute("select * from article where id=%s",(1,)) |
fetchone
:在执行了查询操作以后,获取第一条数据。fetchmany(size)
:在执行查询操作以后,获取多条数据。具体是多少条要看传的size
参数。如果不传size
参数,那么默认是获取第一条数据。fetchall
:获取所有满足sql
语句的数据。
ORM模型的创建和映射:
创建ORM模型:
ORM
模型一般都是放在app
的models.py
文件中。每个app
都可以拥有自己的模型。并且如果这个模型想要映射到数据库中,那么这个app
必须要放在settings.py
的INSTALLED_APP
中进行安装。以下是写一个简单的书籍ORM
模型。示例代码如下:
1 | from django.db import models |
以上便定义了一个模型。这个模型继承自django.db.models.Model
,如果这个模型想要映射到数据库中,就必须继承自这个类。这个模型以后映射到数据库中,表名是模型名称的小写形式,为book
。在这个表中,有四个字段,一个为name
,这个字段是保存的是书的名称,是varchar
类型,最长不能超过20个字符,并且不能为空。第二个字段是作者名字类型,同样也是varchar
类型,长度不能超过20个。第三个是出版时间,数据类型是datetime
类型,默认是保存这本书籍的时间。第五个是这本书的价格,是浮点类型。
还有一个字段我们没有写,就是主键id
,在django
中,如果一个模型没有定义主键,那么将会自动生成一个自动增长的int
类型的主键,并且这个主键的名字就叫做id
。
映射模型到数据库中:
将ORM
模型映射到数据库中,总结起来就是以下几步:
- 在
settings.py
中,配置好DATABASES
,做好数据库相关的配置。 - 在
app
中的models.py
中定义好模型,这个模型必须继承自django.db.models
。 - 将这个
app
添加到settings.py
的INSTALLED_APP
中。 - 在命令行终端,进入到项目所在的路径,然后执行命令
python manage.py makemigrations
来生成迁移脚本文件。 - 同样在命令行中,执行命令
python manage.py migrate
来将迁移脚本文件映射到数据库中。
ORM对数据库的基本操作
添加数据
只要使用ORM模型创建一个对象。然后再调用这个ORM模型的save
方法就可以保存了。
1 | book = Book(name='西游记',author='吴承恩',price=100) |
查找数据:
所有的查找工作都是使用模型上的objects
属性来完成的。当然也可以自定义查询对象。这部分功能会在后面讲到。
- 根据主键进行查找:使用主键进行查找。可以使用
objects.get
方法。然后传递pk=xx
的方式进行查找。示例代码如下:
1 | book = Book.objects.get(pk=2) |
- 根据其他字段进行查找:可以使用
objects.filter
方法进行查找。示例代码如下:
1 | books = Book.objects.filter(name='三国演义') |
使用filter
方法返回来的是一个QuerySet
对象。这个对象类似于列表。我们可以使用这个对象的first
方法来获取第一个值。
删除数据
首先查找到对应的数据模型。然后再执行这个模型的delete
方法即可删除。示例代码如下:
1 | book = Book.objects.get(pk=1) |
修改数据:
首先查找到对应的数据模型。然后修改这个模型上的属性的值。再执行save
方法即可修改完成。示例代码如下:
1 | book = Book.objects.get(pk=2) |
常用Field笔记:
1 | class Article(models.Model): |
navie时间和aware时间
- navie时间:不知道自己的时间表示的是哪个时区的。也就是不知道自己几斤几两。比较幼稚。
- aware时间:知道自己的时间表示的是哪个时区的。也就是比较清醒。
pytz库
专门用来处理时区的库。这个库会经常更新一些时区的数据,不需要我们担心。并且这个库在安装Django的时候会默认的安装。如果没有安装,那么可以通过pip install pytz
的方式进行安装。
astimezone方法:
将一个时区的时间转换为另外一个时区的时间。这个方法只能被aware
类型的时间调用。不能被navie
类型的时间调用。
示例代码如下:
1 | import pytz |
replace方法
可以将一个时间的某些属性进行更改。
django.utils.timezone.now方法
会根据settings.py
中是否设置了USE_TZ=True
获取当前的时间。如果设置了,那么就获取一个aware
类型的UTC
时间。如果没有设置,那么就会获取一个navie
类型的时间。
django.utils.timezone.localtime方法
会根据setting.py
中的TIME_ZONE
来将一个aware
类型的时间转换为TIME_ZONE
指定时区的时间。
html模板中,会自动转换为当前时区时间。views
中可以使用localtime
转换为当前时区
DateField:
日期类型。在Python
中是datetime.date
类型,可以记录年月日。在映射到数据库中也是date
类型。使用这个Field
可以传递以下几个参数:
auto_now
:在每次这个数据保存的时候,都使用当前的时间。比如作为一个记录修改日期的字段,可以将这个属性设置为True
。auto_now_add
:在每次数据第一次被添加进去的时候,都使用当前的时间。比如作为一个记录第一次入库的字段,可以将这个属性设置为True
。
DateTimeField:
日期时间类型,类似于DateField
。不仅仅可以存储日期,还可以存储时间。映射到数据库中是datetime
类型。这个Field
也可以使用auto_now
和auto_now_add
两个属性。
TimeField:
时间类型。在数据库中是time
类型。在Python
中是datetime.time
类型。
EmailField:
类似于CharField
。在数据库底层也是一个varchar
类型。最大长度是254个字符。
FileField:
用来存储文件的。这个请参考后面的文件上传章节部分。
ImageField:
用来存储图片文件的。这个请参考后面的图片上传章节部分。
FloatField:
浮点类型。映射到数据库中是float
类型。
IntegerField:
整形。值的区间是-2147483648——2147483647
。
BigIntegerField:
大整形。值的区间是-9223372036854775808——9223372036854775807
。
PositiveIntegerField:
正整形。值的区间是0——2147483647
。
SmallIntegerField:
小整形。值的区间是-32768——32767
。
PositiveSmallIntegerField:
正小整形。值的区间是0——32767
。
TextField:
大量的文本类型。映射到数据库中是longtext类型。
UUIDField:
只能存储uuid
格式的字符串。uuid
是一个32位的全球唯一的字符串,一般用来作为主键。
URLField:
类似于CharField
,只不过只能用来存储url
格式的字符串。并且默认的max_length
是200。
Field常用的参数
null:
如果设置为True
,Django
将会在映射表的时候指定是否为空。默认是为False
。在使用字符串相关的Field
(CharField/TextField)的时候,官方推荐尽量不要使用这个参数,也就是保持默认值False
。因为Django
在处理字符串相关的Field
的时候,即使这个Field
的null=False
,如果你没有给这个Field
传递任何值,那么Django
也会使用一个空的字符串""
来作为默认值存储进去。因此如果再使用null=True
,Django
会产生两种空值的情形(NULL或者空字符串)。如果想要在表单验证的时候允许这个字符串为空,那么建议使用blank=True
。如果你的Field
是BooleanField
,那么对应的可空的字段则为NullBooleanField
。
blank:
标识这个字段在表单验证的时候是否可以为空。默认是False
。
这个和null
是有区别的,null
是一个纯数据库级别的。而blank
是表单验证级别的。
db_column:
这个字段在数据库中的名字。如果没有设置这个参数,那么将会使用模型中属性的名字。
default:
默认值。可以为一个值,或者是一个函数,但是不支持lambda
表达式。并且不支持列表/字典/集合等可变的数据结构。
primary_key:
是否为主键。默认是False
。
unique:
在表中这个字段的值是否唯一。一般是设置手机号码/邮箱等。
模型中Meta
配置
对于一些模型级别的配置。我们可以在模型中定义一个类,叫做Meta
。然后在这个类中添加一些类属性来控制模型的作用。比如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称。那么我们可以在Meta
类中添加一个db_table
的属性。示例代码如下:
1 | class Book(models.Model): |
以下将对Meta
类中的一些常用配置进行解释。
db_table:
这个模型映射到数据库中的表名。如果没有指定这个参数,那么在映射的时候将会使用模型名来作为默认的表名。
ordering:
设置在提取数据的排序方式。后面章节会讲到如何查找数据。比如我想在查找数据的时候根据添加的时间排序,那么示例代码如下:
1 | class Book(models.Model): |
外键和表关系
外键
在MySQL
中,表有两种引擎,一种是InnoDB
,另外一种是myisam
。如果使用的是InnoDB
引擎,是支持外键约束的。外键的存在使得ORM
框架在处理表关系的时候异常的强大。因此这里我们首先来介绍下外键在Django中的使用。
类定义为class ForeignKey(to,on_delete,**options)
。第一个参数是引用的是哪个模型,第二个参数是在使用外键引用的模型数据被删除了,这个字段该如何处理,比如有CASCADE
、SET_NULL
等。这里以一个实际案例来说明。比如有一个User
和一个Article
两个模型。一个User
可以发表多篇文章,一个Article
只能有一个Author
,并且通过外键进行引用。那么相关的示例代码如下:
1 | class User(models.Model): |
以上使用ForeignKey来定义模型之间的关系。即在article的实例中可以通过author属性来操作对应的User模型。这样使用起来非常的方便。示例代码如下:
1 | article = Article(title='abc',content='123') |
为什么使用了ForeignKey
后,就能通过author
访问到对应的user
对象呢。因此在底层,Django
为Article
表添加了一个属性名_id
的字段(比如author
的字段名称是author_id
),这个字段是一个外键,记录着对应的作者的主键。以后通过article.author
访问的时候,实际上是先通过author_id
找到对应的数据,然后再提取User
表中的这条数据,形成一个模型。
如果想要引用另外一个app的模型,那么应该在传递to参数的时候,使用app.model_name
进行指定。以上例为例,如果User
和Article
不是在同一个app
中,那么在引用的时候的示例代码如下:
1 | # User模型在user这个app中 |
如果模型的外键引用的是本身自己这个模型,那么to
参数可以为self
,或者是这个模型的名字。在论坛开发中,一般评论都可以进行二级评论,即可以针对另外一个评论进行评论,那么在定义模型的时候就需要使用外键来引用自身。示例代码如下:
1 | class Comment(models.Model): |
外键删除操作
如果一个模型使用了外键。那么在对方那个模型被删掉后,该进行什么样的操作。可以通过on_delete
来指定。可以指定的类型如下:
CASCADE
:级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。PROTECT
:受保护。即只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据。SET_NULL
:设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空。SET_DEFAULT
:设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是要指定这个字段一个默认值。SET()
:如果外键的那条数据被删除了。那么将会获取SET函数中的值来作为这个外键的值。SET函数可以接收一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去。DO_NOTHING
:不采取任何行为。一切全看数据库级别的约束。
以上这些选项只是Django级别的,数据级别依旧是RESTRICT!
1 | class Article(models.Model): |
表关系笔记:
一对多:
- 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。
- 实现方式:一对多或者多对一,都是通过
ForeignKey
来实现的。还是以文章和作者的案例进行讲解。
1 | class User(models.Model): |
那么以后在给Article
对象指定author
,就可以使用以下代码来完成:
1 | article = Article(title='abc',content='123') |
并且以后如果想要获取某个用户下所有的文章,可以通过article_set
来实现。示例代码如下:
1 | user = User.objects.first() |
并且如果想要将文章添加到某个分类中。可以使用一下的方式:
1 | category = Category.objects.first() |
- 使用
bulk=False
,那么Django会自动的保存article,而不需要在添加到category之前先保存article。 - 或者是另外一种解决方式是,在添加到
category.article_set
中之前,先将article
保存到数据库中。但是如果article.category
不能为空,那么就产生一种死循环了,article没有category
不能保存,而将article添加到cateogry.artile_set
中,又需要article之前是已经存储到数据库中的。 - 如果是上面的那种需求,建议使用
bulk=False
的解决方案。
一对一:
- 在Django中一对一是通过
models.OnetToOneField
来实现的。这个OneToOneField
其实本质上就是一个外键,只不过这个外键有一个唯一约束(unique key)
,来实现一对一。 - 以后如果想要反向引用,那么是通过引用的模型的名字转换为小写的形式进行访问。比如以下模型:
1 | class FrontUser(models.Model): |
UserExtension
的对象,可以通过user
来访问到对应的user对象。并且FrontUser
对象可以使用userextension
来访问对应的UserExtension
对象。
如果不想使用Django默认的引用属性名字。那么可以在OneToOneField
中添加一个related_name
参数。示例代码如下:
1 | class FrontUser(models.Model): |
那么以后就FrontUser
的对象就可以通过extension
属性来访问到对应的UserExtension
对象。
多对多:
应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。
实现方式:
Django
为这种多对多的实现提供了专门的Field
。叫做ManyToManyField
。还是拿文章和标签为例进行讲解。示例代码如下:
1 | class Article(models.Model): |
在数据库层面,实际上Django
是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到article
和tag
两张表的主键。
1 | def many_to_many_view(request): |
查询条件笔记:
exact
exact
:在底层会被翻译成=
。
iexact
iexact
:在底层会被翻译成LIKE
。
LIKE
和=
:大部分情况下都是等价的,只有少数情况下是不等价的。exict
和iexact
:他们的区别其实就是LIKE
和=
的区别,因为exact会被翻译成=
,而iexact
会被翻译成LIKE
。- 因为
field__exact=xxx
其实等价于filed=xxx
,因此我们直接使用filed=xxx
就可以了,并且因为大部分情况exact
和iexact
又是等价的,因此我们以后直接使用field=xxx
就可以了。
QuerySet.query
QuerySet.query
:query
可以用来查看这个ORM
查询语句最终被翻译成的SQL
语句。但是query
只能被用在QuerySet
对象上,不能用在普通的ORM模型
上。因此如果你的查询语句是通过get
来获取数据的,那么就不能使用query
,因为get
返回的是满足条件的ORM
模型,而不是QuerySet
。如果你是通过filter
等其他返回QuerySet
的方法查询的,那么就可以使用query
。
contains
contains
:使用大小写敏感的判断,某个字符串是否在指定的字段中。这个判断条件会使用大小敏感,因此在被翻译成SQL
语句的时候,会使用like binary
,而like binary
就是使用大小写敏感的。
icontains
icontains
:使用大小写不敏感的判断,某个字符串是否被包含在指定的字段中。这个查询语句在被翻译成SQL
的时候,使用的是like
,而like
在MySQL
层面就是不区分大小写的。
contains和iexact区别
contains
和icontains
,在被翻译成SQL
的时候使用的是%hello%
,就是只要整个字符串中出现了hello
都能过够被找到,而iexact
没有百分号,那么意味着只有完全相等的时候才会被匹配到。
in
in
:可以直接指定某个字段的是否在某个集合中。示例代码如下:
1 | articles = Article.objects.filter(id__in=[1,2,3]) |
也可以通过其他的表的字段来判断是否在某个集合中。示例代码如下:
1 | categories = Category.objects.filter(article__id__in=[1,2,3]) |
如果要判断相关联的表的字段,那么也是通过__
来连接。并且在做关联查询的时候,不需要写models_set
,直接使用模型的名字的小写化
就可以了。比如通过分类去查找相应的文章,那么通过article__id__in
就可以了,而不是写成article_set__id__in
的形式。当然如果你不想使用默认的形式,可以在外键定义的时候传递related_query_name
来指定反向查询的名字。示例代码如下:
1 | class Category(models.Model): |
因为在cateogry
的ForeignKey
中指定了related_query_name
为articles
,因此你不能再使用article
来进行反向查询了。这时候就需要通过articles__id__in
来进行反向查询。
反向查询是将模型名字小写化。比如article__in
。可以通过related_query_name
来指定自己的方式,而不使用默认的方式。
反向引用是将模型名字小写化,然后再加上_set
,比如article_set
,可以通过related_name
来指定自己的方式,而不是用默认的方式。
并且,如果在做反向查询的时候,如果查询的字段就是模型的主键,那么可以省略掉这个字段,直接写成article__in
就可以了,不需要这个id
了。
in
不仅仅可以指定列表/元组,还可以为QuerySet
。比如要查询“文章标题中包含有hello的所有分类”,那么可以通过以下代码来实现:
1 | articles = Article.objects.filter(title__icontains='hello') |
gt
、gte
、lt
、lte
gt
、gte
、lt
、lte
:代表的是大于、大于等于、小于、小于等于的条件。示例代码如下:
1 | articles = Article.objects.filter(id__lte=3) #小于等于3 |
startswith
、istartswith
、endswith
、iendswith
startswith
、istartswith
、endswith
、iendswith
:表示以某个值开始,不区分大小写的以某个值开始、以某个值结束、不区分大小写的以某个值结束。示例代码如下:
1 | articles = Article.objects.filter(title__endswith="hello") |
时间查询条件
range
range
:可以指定一个时间段。并且时间应该标记为aware
时间,不然django会报警告。Filtering a DateTimeField with dates won’t include items on the last day. 示例代码如下:
1 | from datetime import datetime,time |
date
date
:用年月日来进行过滤。如果想要使用这个过滤条件,那么前提必须要在MySQL
中添加好那些时区文件。示例代码如下:
1 | articles = Article.objects.filter(create_time__date=datetime(year=2018,month=4,day=4)) |
year/month/day
year/month/day
:表示根据年/月/日进行查找。示例代码如下:
1 | articles = Article.objects.filter(create_time__year__gte=2018) |
week_day
week_day
:根据星期来进行查找。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。比如要查找星期三的所有文章,那么可以通过以下代码来实现:
1 | articles = Article.objects.filter(create_time__week_day=4) |
time
time
:根据分时秒来进行查找。如果要具体到秒,一般比较难匹配到,可以使用区间的方式来进行查找。区间使用range
条件。比如想要获取17时/10分/27-28秒之间的文章,那么可以通过以下代码来实现:
1 | start_time = time(hour=17,minute=10,second=27) |
regex
和iregex
regex
和iregex
:大小写敏感和大小写不敏感的正则表达式。示例代码如下:
1 | articles = Article.objects.filter(title__regex=r'^hello') |
以上代码的意思是提取所有标题以hello
字符串开头的文章。
聚合函数笔记:
- 所有的聚合函数都是放在
django.db.models
下面。 - 聚合函数不能够单独的执行,需要放在一些可以执行聚合函数的方法下面中去执行。比如
aggregate
。示例代码如下:
1 | from django.db.models import Avg |
- 聚合函数执行完成后,自动给这个聚合函数的值取个名字。取别名的规则,默认是
field+__+聚合函数名字
形成的。比如以上代码形成的名字叫做price__avg
。如果不想使用默认的名字,那么可以在使用聚合函数的时候传递关键字参数进去,参数的名字就是聚合函数执行完成的名字。实示例代码如下:
1 | result = Book.objects.aggregate(avg=Avg("price")) |
以上传递了关键字参数avg=Avg("price")
,那么以后Avg
聚合函数执行完成的名字就叫做avg
。
aggregate
aggregate
:这个方法不会返回一个QuerySet
对象,而是返回一个字典。这个字典中的key
就是聚合函数的名字,值就是聚合函数执行后的结果。
annotate
aggregate
和annotate
的相同和不同:
* 相同:这两个方法都可以执行聚合函数。
* 不同:
- aggregate
返回的是一个字典,在这个字典中存储的是这个聚合函数执行的结果。而annotate
返回的是一个QuerySet
对象,并且会在查找的模型上添加一个聚合函数的属性。
- aggregate
不会做分组,而annotate
会使用group by
子句进行分组,只有调用了group by
子句,才能对每一条数据求聚合函数的值。annotate
在原来模型字段的基础之上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型的主键进行分组(group by)。
aggregate
和annotate
方法可以在任何的QuerySet
对象上调用。因此只要是返回了QuerySet
对象,那么就可以进行链式调用。比如要获取2018年度的销售总额,那么可以先过滤年份,再求聚合函数。示例代码如下:
1 | BookOrder.objects.filter(create_time__year=2018).aggregate(total=Sum('price')) |
Count
Count
:用来求某个数据的个数。比如要求所有图书的数量,那么可以使用以下代码:
1 | result = Book.objects.aggregate(book_nums=Count("id")) |
并且Count
可以传递distinct=True
参数,用来剔除那些重复的值,只保留一个。比如要获取作者表中,不同邮箱的个数,那么这时候可以使用distinct=True
。示例代码如下:
1 | result = Author.objects.aggregate(email_nums=Count('email',distinct=True)) |
Max
和Min
Max
和Min
:求指定字段的最大值和最小值。示例代码如下:
1 | result = Author.objects.aggregate(max=Max("age"),min=Min("age")) |
Sum
Sum
:求某个字段值的总和。示例代码如下:
1 | result = BookOrder.objects.aggregate(total=Sum('price')) |
F表达式和Q表达式
F表达式
F表达式
: 动态的获取某个字段上的值。并且这个F表达式,不会真正的去数据库中查询数据,他相当于只是起一个标识的作用。比如想要将原来每本图书的价格都在原来的基础之上增加10元,那么可以使用以下代码来实现:
1 | from django.db.models import F |
1 | # 获取名字和邮箱相同的作者 |
Q表达式
Q表达式
:使用Q
表达式包裹查询条件,可以在条件之间进行多种操作。与、或、非等,从而实现一些复杂的查询操作。例子如下:
- 查找价格大于100,并且评分达到4.85以上的图书:
1 | # 不使用Q表达式的 |
- 查找价格低于100元,或者评分低于4分的图书:
1 | books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85)) |
- 获取价格大于100,并且图书名字中不包含”传“字的图书:
1 | books = Book.objects.filter(Q(price__gte=100)&~Q(name__icontains='传')) |
QuerySet API:
模型.objects:
这个对象是django.db.models.manager.Manager
的对象,这个类是一个空壳类,他上面的所有方法都是从QuerySet
这个类上面拷贝过来的。因此我们只要学会了QuerySet
,这个objects
也就知道该如何使用了。
Manager
源码解析:
1 | class_name = "BaseManagerFromQuerySet" |
filter/exclude/annotate
:过滤/排除满足条件的/给模型添加新的字段。
1 | Article.objects.exclude(title__contains='hello') |
order_by:
1 | # 根据创建的时间正序排序 |
一定要注意的一点是,多个order_by
,会把前面排序的规则给打乱,而使用后面的排序方式。比如以下代码:
1 | articles = Article.objects.order_by("create_time").order_by("author__name") |
它会根据作者的名字进行排序,而不是使用文章的创建时间。
当然,也可以在模型定义的在Meta
类中定义ordering
来指定默认的排序方式。示例代码如下:
1 | class Meta: |
还可以根据annotate
定义的字段进行排序。比如要实现图书的销量进行排序,那么示例代码如下:
1 | books = Book.objects.annotate(order_nums=Count("bookorder")).order_by("-order_nums") |
values
只想提取其中的几个字段,可以使用values
来进行指定,并且使用了values
方法后,提取出的QuerySet
中的数据类型不是模型,而是在values
方法中指定的字段和值形成的字典:
1 | books = Book.objects.values('id','name') |
提取这个模型上关联的的对象的属性,查找顺序和filter
的用法是一样的:
1 | books = Book.objects.values('id','name','author__name') |
以上会提取author
的name
字段,如果想更改字段名,可以使用关键字参数:
1 | books = Book.objects.values('id', 'name', author_name=F('author__name')) |
自定义的名字,不能和模型上本身拥有的字段一样,否则会报错。
在values
中,也可以使用聚合函数来形成一个新的字段。比如获取每本图书的销量:
1 | books = Book.objects.values('id', 'name', order_nums=Count('bookorder')) |
如果调用values
方法的时候,没有传递任何的参数,那么会获取这个模型上的所有字段以及对于的值形成的字典:
1 | books = Book.objects.values() |
那么books
中的值如下:
1 | {'id':1, 'name':'三国演义', 'pages':987, 'price':108.0, 'rating':3.9, 'author_id':3} |
values_list
和values
是一样的作用,只不过返回的QuerySet
中,装的不是字典,而是元组。示例代码如下:
1 | books = Book.objects.values_list('id', 'name') |
以上的返回结果是:
1 | (1,'三国演义') |
如果给values_list
只指定一个字段,那么我们可以指定flat=True
,这样返回的结果就不再是一个元组,而是整个字段的值。flat
只能用在只有一个字段的情况下,否则就会报错。示例代码如下:
1 | books = Book.objects.values_list('name', flat=True) |
那么以上返回的结果是:
1 | '三国演义' |
all
方法
这个简单的返回一个QuerySet
对象,这个QuerySet
对象没有经过任何的修改(比如:过滤等)。
select_related
在查找某个表的数据的时候,可以一次性把相关联的其它表的数据都提取出来。以后再访问相关表的数据的时候,不用再次查找数据库,可以节省一些开销。示例代码如下:
1 | books = Book.objects.select_related('author','publisher') |
注意:这个方法只能用在外键的关联对象上,对于那种多对多,或者是多对一的情况,不能使用它来实现,应该使用prefetch_related
来实现。
prefetch_related
与select_related
方法类似,也是在查询语句时,提前将指定的数据查找出来。只不过这个方法是用来解决多对多,或者多对一的情况。这个方法会产生两个查询语句。所以,如果在这个方法中查询使用外键关联的模型的时候,也会产生两个查询语句,因此如果查询的是外键关联的模型,建议使用select_related
方法。
在查询多对多或者多对一的关联的对象的时候,与使用模型访问传递的字符串一样。比如要获取图书上的所有订单,示例代码如下:
1 | books = Book.objects.prefetch_related('bookorder_set') |
注意:在使用prefetch_related
查找出来的bookorder_set
,建议不要再对其进行任何操作,比如filter
,不然又会产生N多查询语句。
如果确实想要对预先查找的集合进行操作,可以用django.db.models.Prefetch
完成。示例代码如下:
1 | from django.db.models import Prefetch |
defer和only
这两个方法都会返回一个QuerySet
对象,并且这个QuerySet
中装的都是模型,而不是字典。
defer
:这个方法用来告诉ORM
,在查询某个模型的时候,过滤掉某些字段。注意:使用了defer
了的字段,如果以后再使用这个字段,会重新发起一次请求。因此要谨慎操作。only
:这个方法用来告诉ORM
,在查询某个模型的时候,值提取几个字段。注意:没有加在only
中的字段,以后如果使用了,那么也会重新发起一次请求。因此要谨慎操作。
1 | books = Book.objects.only('name') |
get方法
这个方法给定的条件只能匹配到一条数据,如果匹配到多条数据,或没有匹配到任何数据,都会报错。一般用于查询主键。
1 | book = Book.objects.get(id=5) |
create方法
创建一条数据,并且将这个数据保存到数据库中,以下的代码是等价的:
1 | publisher = Publisher(name='人民邮电出版社') |
get_or_create
如果给定的条件有数据,那么就会把这个数据直接提取出来。如果给定的条件没有数据,那么就会先创建数据,然后再把数据返回回来。这个方法的返回值是一个元组,元组的第一个参数obj
是这个对象,第二个参数created
代表是否创建的,是返回True
。
1 | obj,created= Category.objects.get_or_create(title='默认分类') |
bulk_create
一次性创建多个数据。示例代码如下:
1 | publisher = Publisher.objects.bulk_create([ |
count
获取提取的数据的个数。如果想要知道总共有多少条数据,那么建议使用count
,而不是使用len(articles)
这种。因为count
在底层是使用select count(*)
来实现的,这种方式比使用len
函数更加的高效。
firtst和last
返回QuerySet
中的第一条和最后一条数据。
aggregate
使用聚合函数。
exists
判断某个条件的数据是否存在。如果要判断某个条件的元素是否存在,那么建议使用exists
,这比使用count
或者直接判断QuerySet
更有效得多。示例代码如下:
1 | if Article.objects.filter(title__contains='hello').exists(): |
distinct
去除掉那些重复的数据。这个方法如果底层数据库用的是MySQL
,那么不能传递任何的参数。比如想要提取所有销售的价格超过80元的图书,并且删掉那些重复的,那么可以使用distinct
来帮我们实现,示例代码如下:
1 | books = Book.objects.filter(bookorder__price__gte=80).distinct() |
需要注意的是,如果在distinct
之前使用了order_by
,那么因为order_by
会提取order_by
中指定的字段,因此再使用distinct
就会根据多个字段来进行唯一化,所以就不会把那些重复的数据删掉。示例代码如下:
1 | orders = BookOrder.objects.order_by("create_time").values("book_id").distinct() |
那么以上代码因为使用了order_by
,即使使用了distinct
,也会把重复的book_id
提取出来。
update
执行更新操作,在SQL
底层走的也是update
命令。比如要将所有category
为空的article
的article
字段都更新为默认的分类。示例代码如下:
1 | Article.objects.filter(category__isnull=True).update(category_id=3) |
注意这个方法走的是更新的逻辑。所以更新完成后保存到数据库中不会执行save
方法,因此不会更新auto_now
设置的字段。
delete
删除所有满足条件的数据。删除数据的时候,要注意on_delete
指定的处理方式。
1 | Author.objects.filter(id__gte=3).delete() |
切片操作
有时候我们查找数据,有可能只需要其中的一部分。那么这时候可以使用切片操作来帮我们完成。QuerySet
使用切片操作就跟列表使用切片操作是一样的。示例代码如下:
1 | books = Book.objects.all()[1:3] #1,2 |
切片操作并不是把所有数据从数据库中提取出来再做切片操作。而是在数据库层面使用LIMIT
和OFFSET
来帮我们完成。所以如果只需要取其中一部分的数据的时候,建议大家使用切片操作。
什么时候Django
会将QuerySet
转换为SQL
去执行
生成一个QuerySet
对象并不会马上转换为SQL
语句去执行。
比如我们获取Book
表下所有的图书:
1 | books = Book.objects.all() |
我们可以看到在打印connection.quries
的时候打印的是一个空的列表。说明上面的QuerySet
并没有真正的执行。
在以下情况下QuerySet
会被转换为SQL
语句执行:
- 迭代:在遍历
QuerySet
对象的时候,会首先先执行这个SQL
语句,然后再把这个结果返回进行迭代。比如以下代码就会转换为SQL
语句:
1 | for book in Book.objects.all(): |
- 使用步长做切片操作:
QuerySet
可以类似于列表一样做切片操作。做切片操作本身不会执行SQL
语句,但是如果如果在做切片操作的时候提供了步长,那么就会立马执行SQL语句。需要注意的是,做切片后不能再执行filter
方法,否则会报错。 - 调用
len
函数:调用len
函数用来获取QuerySet
中总共有多少条数据也会执行SQL
语句。 - 调用
list
函数:调用list
函数用来将一个QuerySet
对象转换为list
对象也会立马执行SQL
语句。 - 判断:如果对某个
QuerySet
进行判断,也会立马执行SQL
语句。
ORM实例
ORM模型如下:
1 | from django.db import models |
查询平均成绩大于60分的同学的id和平均成绩;
1 | rows = Student.objects.annotate(avg=Avg("score__number")).filter(avg__gte=60).values("id","avg") |
查询所有同学的id、姓名、选课的数、总成绩;
1 | rows = Student.objects.annotate(course_nums=Count("score__course"),total_score=Sum("score__number")) |
查询姓“李”的老师的个数;
1 | teacher_nums = Teacher.objects.filter(name__startswith="李").count() |
查询没学过“黄老师”课的同学的id、姓名;
1 | rows = Student.objects.exclude(score__course__teacher__name="黄老师").values('id','name') |
查询学过课程id为1和2的所有同学的id、姓名;
1 | rows = Student.objects.filter(score__course__in=[1,2]).distinct().values('id','name') |
查询学过“黄老师”所教的所有课的同学的学号、姓名;
1 | rows = Student.objects.annotate(nums=Count("score__course",filter=Q(score__course__teacher__name='黄老师'))).filter(nums=Course.objects.filter(teacher__name='黄老师').count()).values('id','name') |
查询所有课程成绩小于60分的同学的id和姓名;
1 | students = Student.objects.exclude(score__number__gt=60) |
查询没有学全所有课的同学的id、姓名;
1 | students = Student.objects.annotate(num=Count(F("score__course"))).filter(num__lt=Course.objects.count()).values('id','name') |
查询所有学生的姓名、平均分,并且按照平均分从高到低排序;
1 | students = Student.objects.annotate(avg=Avg("score__number")).order_by("-avg").values('name','avg') |
查询各科成绩的最高和最低分,以如下形式显示:课程ID,课程名称,最高分,最低分:
1 | courses = Course.objects.annotate(min=Min("score__number"),max=Max("score__number")).values("id",'name','min','max') |
查询每门课程的平均成绩,按照平均成绩进行排序;
1 | courses = Course.objects.annotate(avg=Avg("score__number")).order_by('avg').values('id','name','avg') |
统计总共有多少女生,多少男生;
1 | rows = Student.objects.aggregate(male_num=Count("gender",filter=Q(gender=1)),female_num=Count("gender",filter=Q(gender=2))) |
将“黄老师”的每一门课程都在原来的基础之上加5分;
1 | rows = Score.objects.filter(course__teacher__name='黄老师').update(number=F("number")+5) |
查询两门以上不及格的同学的id、姓名、以及不及格课程数;
1 | students = Student.objects.annotate(bad_count=Count("score__number",filter=Q(score__number__lt=60))).filter(bad_count__gte=2).values('id','name','bad_count') |
查询每门课的选课人数;
1 | courses = Course.objects.annotate(student_nums=Count("score__student")).values('id','name','student_nums') |
ORM模型迁移
命令说明
makemigrations
将模型生成迁移脚本。模型所在的app,必须放在settings.py
中的INSTALLED_APPS
中。这个命令有以下几个常用选项:
app_label
:后面可以跟一个或者多个app,那么就只会针对这几个app生成迁移脚本。如果没有任何的app_label,那么会检查INSTALLED_APPS
中所有的app下的模型,针对每一个app都生成响应的迁移脚本。--name
:给这个迁移脚本指定一个名字。--empty
:生成一个空的迁移脚本。如果你想写自己的迁移脚本,可以使用这个命令来实现一个空的文件,然后自己再在文件中写迁移脚本。
migrate
将新生成的迁移脚本。映射到数据库中。创建新的表或者修改表的结构。以下一些常用的选项:
app_label
:将某个app下的迁移脚本映射到数据库中。如果没有指定,那么会将所有在INSTALLED_APPS
中的app下的模型都映射到数据库中。app_label migrationname
:将某个app下指定名字的migration文件映射到数据库中。--fake
:可以将指定的迁移脚本名字添加到数据库中。但是并不会把迁移脚本转换为SQL语句,修改数据库中的表。--fake-initial
:将第一次生成的迁移文件版本号记录在数据库中。但并不会真正的执行迁移脚本。showmigrations
:查看某个app下的迁移文件。如果后面没有app,那么将查看INSTALLED_APPS
中所有的迁移文件。
sqlmigrate:查看某个迁移文件在映射到数据库中的时候,转换的SQL语句。
migrate原理说明
migrate做了什么事情:
- 将相关的迁移脚本翻译成SQL语句,在数据库中执行这个SQL语句。
- 如果这个SQL语句执行没有问题,那么就会将这个迁移脚本的名字记录到
django_migrations
中。
migrate怎么判断哪些迁移脚本需要执行
它会将代码项目中的迁移脚本和数据库中django_migrations
表中的迁移脚本进行对比,如果发现数据库中,没有这个迁移脚本,那么就会执行这个迁移脚本。
执行migrate命令的时候报错的解决办法
原因
执行migrate
命令会报错的原因是。数据库的django_migrations
表中的迁移版本记录和代码中的迁移脚本不一致导致的。
解决办法:
使用--fake
参数:
首先对比数据库中的迁移脚本和代码中的迁移脚本。然后找到哪个不同,之后再使用--fake
,将代码中的迁移脚本添加到django_migrations
中,但是并不会执行sql
语句。这样就可以避免每次执行migrate
的时候,都执行一些重复的迁移脚本。
终极解决方案:
如果代码中的迁移脚本和数据库中的迁移脚本实在太多,很难找到准确出错位置。那么这时候就可以使用以下终极解决方案。就是清除之前的迁移脚本,重新映射,将出问题的app下的所有模型和数据库中表保持一致。操作步骤如下:
- 将出问题的app下的所有模型,都和数据库中的表保持一致。
- 将出问题的app下的所有迁移脚本文件都删掉。再在
django_migrations
表中将出问题的app相关的迁移记录都删掉。 - 使用
makemigrations
,重新将模型生成一个迁移脚本。 - 使用
migrate --fake-initial
参数,将刚刚生成的迁移脚本,标记为已经完成(因为这些模型相对应的表,其实都已经在数据库中存在了,不需要重复执行了)。 - 之后可以正常映射了。
根据已有的表自动生成模型
首先需要在
settings.py
中配置好数据库相关信息通过
python manage.py inspectdb > models.py
,将表转换为模型后的代码,输出到models.py
文件。如果只是想要转换一个表为模型。那么可以指定表的名字python manage.py inspectdb article_article > models.py
修正模型:新生成的ORM模型有些地方可能不太适合使用。比如模型的名字,表之间的关系等等。那么以下选项还需要重新配置一下:
- 模型名:自动生成的模型,是根据表的名字生成的,可能不是你想要的。这时候模型的名字你可以改成任何你想要的。
- 模型所属app:根据自己的需要,将相应的模型放在对应的app中。放在同一个app中也是没有任何问题的。只是不方便管理。
- 模型外键引用:将所有使用
ForeignKey
的地方,模型引用都改成字符串。这样不会产生模型顺序的问题。另外,如果引用的模型已经移动到其他的app中了,那么还要加上这个app的前缀。 - 让
Django
管理模型:将Meta
下的managed=False
删掉,如果保留这个,那么以后这个模型有任何的修改,使用migrate
都不会映射到数据库中。 - 当有多对多的时候,应该也要修正模型。将中间表注释了,然后使用
ManyToManyField
来实现多对多。并且,使用ManyToManyField
生成的中间表的名字可能和数据库中那个中间表的名字不一致,这时候肯定就不能正常连接了。那么可以通过db_table
来指定中间表的名字。示例代码如下:
1
2
3
4
5
6
7
8
9
10
11class Article(models.Model):
title = models.CharField(max_length=100, blank=True, null=True)
content = models.TextField(blank=True, null=True)
author = models.ForeignKey('front.User', models.SET_NULL, blank=True, null=True)
# 使用ManyToManyField模型到表,生成的中间表的规则是:article_tags
# 但现在已经存在的表的名字叫做:article_tag
# 可以使用db_table,指定中间表的名字
tags = models.ManyToManyField("Tag",db_table='article_tag')
class Meta:
db_table = 'article'- 表名:切记不要修改表的名字。不然映射到数据库中,会发生找不到对应表的错误。
执行命令
python manage.py makemigrations
生成初始化的迁移脚本。方便后面通过ORM来管理表。这时候还需要执行命令python manage.py migrate [app_name] --fake-initial
,因为如果不使用--fake-initial
,那么会将迁移脚本会映射到数据库中。这时候迁移脚本会新创建表,而这个表之前是已经存在了的,所以肯定会报错。此时我们只要将这个0001-initial
的状态修改为已经映射,而不真正执行映射,下次再migrate
的时候,就会忽略它。将
Django
的核心表映射到数据库中:Django
中还有一些核心的表也是需要创建的。不然有些功能是用不了的。比如auth
相关表。如果这个数据库之前就是使用Django
开发的,那么这些表就已经存在了。可以不用管了。如果之前这个数据库不是使用Django
开发的,那么应该使用migrate
命令将Django
中的核心模型映射到数据库中。