什么是ORM
ORM:Object Relational Mapping (关系对象映射)
ORM就是用面向对象的方式,描述数据库,操作数据库,达到不用编写SQL语句就能对数据库进行增删改查等操作。

通过对类、实例、类属性的各种操作,达到操作数据库的功能,底层是生成原生sql语句进行数据库操作。
创建ORM模型
ORM模型是一个用于表示数据的python类,包含基本的数据字段和行为。在Django中,通常一个模型都映射一张数据库表,模型类的每个属性都相当于一个数据库的字段。
如果要将一个普通的类变成一个可以映射到数据库的ORM模型,那么必须要将父类设置为django.db.models.Model
或者他的子类。
创建一个新的app:book
manage.py startapp book
在settings.py
的INSTALLED_APPS
中添加新建的app
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'book',
]
在book app的目录中的models.py编写设计图书模型
from django.db import models
class Book(models.Model):
# id:int类型,自增长
id = models.AutoField(primary_key=True)
# name:varchar(100), 图书的名字
name = models.CharField(max_length=100, null=False)
# author:varchar(100), 图书的作者
author = models.CharField(max_length=100, null=False)
# price:float,图书的价格
price = models.FloatField(null=False, default=0)
使用makemigrations将models生成迁移脚本文件,迁移脚本会生成在对应app下的migrations目录中。
manage.py makemigrations
使用migrate将新生成的迁移脚本文件映射到数据库中
manage.py migrate
执行过后,数据库中已经生成了对应的表。(表名为book_book,在模型程序中没有指定表名的话,django会自动生成默认的表名:app名_模型class名)
在models.py中新增出版社的模型
from django.db import models
class Book(models.Model):
# id:int类型,自增长
id = models.AutoField(primary_key=True)
# name:varchar(100), 图书的名字
name = models.CharField(max_length=100, null=False)
# author:varchar(100), 图书的作者
author = models.CharField(max_length=100, null=False)
# price:float,图书的价格
price = models.FloatField(null=False, default=0)
class Publisher(models.Model):
# 出版社名称
name = models.CharField(max_length=100, null=False)
# 出版社地址
address = models.CharField(max_length=100, null=False)
可以看到Publisher模型没有定义id字段和主键,Django中会默认添加一个自增的id字段并设置为主键,等效于id = models.AutoField(primary_key=True)
,也就是这句代码写与不写都会添加个自增的id字段并设置为主键。除非要变更id的生成规则,否则使用Django默认的即可。
通过ORM实现增删改查操作
在book/models.py的Book类中重新__str__()
方法
class Book(models.Model):
# id:int类型,自增长
id = models.AutoField(primary_key=True)
# name:varchar(100), 图书的名字
name = models.CharField(max_length=100, null=False)
# author:varchar(100), 图书的作者
author = models.CharField(max_length=100, null=False)
# price:float,图书的价格
price = models.FloatField(null=False, default=0)
# 重写str方法
def __str__(self):
return "<Book:({name},{author},{price})>".format(name=self.name, author=self.author, price=self.price)
在book/views.py中创建一个index()方法,来实现操作,先配置路由
主路由urls.py
from django.urls import path
from book import views
urlpatterns = [
path('', views.index)
]
在book/views.py中实现通过ORM:
1.添加1条数据后,访问:http://127.0.0.1:8000/ 即可执行index()
方法
from .models import Book
from django.http import HttpResponse
def index(request):
# 使用ORM添加一条数据到数据库中
# book = Book(name='西游记', author='吴承恩', price=80)
book = Book(name='红楼梦', author='曹雪芹', price=150)
book.save() # 保存数据
return HttpResponse("操作成功!")
2.根据主键查询
from .models import Book
from django.http import HttpResponse
def index(request):
# 查询
# 根据主键进行查找
book = Book.objects.get(pk=1) # primary key
print(book)
return HttpResponse("操作成功!")
3.根据其他条件查找
# 根据其他条件进行查找,查询结果是个列表
books = Book.objects.filter(name='西游记')
<QuerySet [<Book: <Book:(西游记,吴承恩,80.0)>>]>
4.根据其他条件查找,返回结果的第一个值,
books = Book.objects.filter(name='西游记').first()
<Book:(西游记,吴承恩,80.0)>
5.删除数据。先查找到要删除的数据,然后进行删除。
from .models import Book
from django.http import HttpResponse
def index(request):
# 删除数据。先查找到数据,然后进行删除
book = Book.objects.get(pk=1)
book.delete()
return HttpResponse("操作成功!")
执行成功后,查看数据库,pk=1的数据已经被删除了。
6.修改数据。先查找到数据,然后进行修改
from .models import Book
from django.http import HttpResponse
def index(request):
# 修改数据。先查找到数据,然后进行修改
book = Book.objects.get(pk=2)
book.price = 200
book.save()
return HttpResponse("操作成功!")
ORM常用Field
在Django中,定义了一些Field来与数据库表中的字段类型进行映射。下面将介绍一些常用的字段类型。
AutoField
映射到数据库中是int类型,有自动增长的特性。一般不需要使用这个类型,如果不指定主键,那么模型会自动的生成一个叫做id的自动增长的主键。如果想指定一其他名字的并且具有自动增长的主键,比如uid,可以使用AutoField。(若要将自定义的Field作为主键,必须设置primary_key=True
)
BigAutoField
64位整型,数据范围时1 - 9223372036854775807.
BooleanField
在模型层面接收的是True/False。在数据层面时tinyint类型。如果没有指定默认值,默认值为None。
若要使用可以为null的BooleanField,那么应该使用NullBooleanField来代替。
CharField
在数据库层面上varchar类型,在python层面就是普通的字符串。这个类型在使用的时候必须指定最大的长度,即max_length
这个关键字参数。
DateFild
日期类型。在python中是datetime.date类型,可以记录年月日。在映射到数据库中也是date类型。使用这个Field可以传递以下两个参数:
- auto_now:在每次保存数据的时候,都使用当前时间。
- auto_now_add:在每次数据第一次被保存时,都使用当前时间。
DateTimeField
日期时间类型,不仅仅可以存储日期,还可以存储时间。映射到数据库中是datetime类型。这个Fieldye可以使用auto_now和auto_now_add两个属性。可以用auto_now_add
标示创建时间字段,可以用auto_now
标示更改时间字段。
例子,在上面代码的基础上,新增创建时间字段
from django.db import models
class Book(models.Model):
# id:int类型,自增长
id = models.AutoField(primary_key=True)
# name:varchar(100), 图书的名字
name = models.CharField(max_length=100, null=False)
# author:varchar(100), 图书的作者
author = models.CharField(max_length=100, null=False)
# price:float,图书的价格
price = models.FloatField(null=False, default=0)
# create_time,创建时间,并auto_now_add=True
create_time = models.DateTimeField(auto_now_add=True)
进行映射迁移
manage.py makemigrations
It is impossible to add the field 'create_time' with 'auto_now_add=True' to book without providing a default. This is because the database needs something to populate existing rows.
1) Provide a one-off default now which will be set on all existing rows
2) Quit and manually define a default value in models.py.
Select an option:
同步被拦截,由于之前已经同步一般,生成了数据库中的表结构,并且已经添加了数据。这次的修改增加了create_time
字段,但没有指定默认值。这样会导致已经存在的数据缺失这个字段的值。Django提供了两个选择解决方案:1是在当前命令行中赋予初始值;2是退出当前执行,在models中设计初始值。
这里选择方案1,被指定初始值为timezone.now
Select an option: 1
Please enter the default value as valid Python.
Accept the default 'timezone.now' by pressing 'Enter' or provide another value.
The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
Type 'exit' to exit this prompt
[default: timezone.now] >>> timezone.now
Migrations for 'book':
book/migrations/0003_book_create_time.py
- Add field create_time to book
设置并执行成功。
注:在settings.py设置了
USE_TZ = True
和TIME_ZONE = 'Asia/Shanghai'
时,auto_now_add
和auto_now
保存到数据库中的时间都是UTC
时间,从数据库中获取时间还需要转换成localtime
本地化时间。由于目前所做功能均不涉及到跨时区的问题,目前将settings.py的USE_TZ = True
改回了False,这样直接存到数据库的就是本地化时间。(临时的解决方案)
TimeField
时间类型。在数据库中是time类型,在python中是datetime.time类型。
补充:
注:Django4.0开始,默认的时区库采用Zoneinfo。Zoneinfo是Python3.9开始加入的一个标准模块。pytz包在Django3.2之前作为默认时区库,在4.0中不在推荐使用,将在5.0中被移除。
from datetime import datetime
from zoneinfo import ZoneInfo
now = datetime.now()
print(now)
utc_time = now.astimezone(ZoneInfo("UTC"))
print(utc_time)
执行结果:
datetime.datetime(2023, 10, 27, 13, 4, 45, 221626)
datetime.datetime(2023, 10, 27, 5, 4, 45, 221626, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
EmailField
类似于CharField,在数据库底层也是一个varchar类型。最大长度是254个字符
FileField
用来存储文件的。
ImageField
用来存储图片文件的。
FloatField
浮点类型,映射到数据库中是float类型。
IntegerField
整型,值的区间是 -2147583648 - 2147483647 。
BigIntegerField
大整型,值的区间是 -9223372036854775908 - 9223372036854775907 。
PostitiveLntegerField
正整型,值的区间是 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传递任何值时也会使用一个空的字符串来作为默认值进行保存。如果想要在表单验证的时候允许这个字符串为空,则应该使用blank=True
;如果Field是BooleanField,那么对应的可以为空的字段为NullBooleanField。
blank
在表单验证的时候需要判断是否可以为空,使用blank,默认值为False。
blank跟上面的null的区别是:null是一个纯数据库级别的,而blank是表单验证级别的。
db_column
这个字段在数据库中的名字。如果没有设置这个参数,就会使用模型中属性的名字。
name = models.CharField(max_length=100, null=False, db_column='book_name')
上面的设置,映射到数据库是,字段的名称(列名)就会是book_name,而不是name。
default
默认值。可以是一个值,也可以是一个函数,但不支持lambda表达式及列表、字典、集合等可变的数据结构。
price = models.FloatField(null=False, default=0)
primary_key
是否为主键,默认值为False。
id = models.AutoField(primary_key=True)
unique
在表中这个字段的值是否唯一。一般是设置手机号码/邮箱/用户名等。
telephone = models.CharField(max_length=11, unique=True)
verbose_name
verbose_name为对应的字段提供了一个更容易让人阅读的名称
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
Meta类配置
在models.py模型中可以定义一个类,叫Meta
。这个类中添加一些类属性来控制模型的作用。
db_table
上面文章中创建的book app,映射到数据库中,表名为book_book。这是因为没有指定表名,而Django自动生成了一个’APP名_Class名’的表名。可以使用db_table
指定自定义的表名。
from django.db import models
class Book(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, null=False)
author = models.CharField(max_length=100, null=False)
price = models.FloatField(null=False, default=0)
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
def __str__(self):
return "<Book:({name},{author},{price},{create_time})>".format(name=self.name, author=self.author,price=self.price, create_time=self.create_time)
class Meta:
db_table = "book"
ordering
可以使用ordering指定默认的排序规则
class Meta:
db_table = "book"
ordering = ["-create_time","price"]
上面的例子是优先创建时间倒序排列即最新创建的在上面,若时间一致,再按价格进行排序。
ORM外键使用
在MySQL中,表最常用的引擎有两种,一种是InnoDB,另一种是myisam。如果使用的是InnoDB引擎,是支持外键约束的。
类定义为class ForeignKey(to, on_delete, **options)
。第一参数是引用的哪个模型(class),第二个参数是在使用外键引用的模型数据被删除了,这个字段该如何处理。
外键删除操作on_delete
可以指定的类型如下:
CASCADE
:级联操作。如果外键对应的那条数据被删除了,那么这条数据也会删除;author = models.ForeignKey("user.User", on_delete=models.CASCADE)
PROTECT
:受保护。只要这条数据引用的那条数据,那么就不能删除外键的那条数据;author = models.ForeignKey("user.User", on_delete=models.PROTECT)
SET_NULL
:设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空;category = models.ForeignKey("Category", on_delete=models.SET_NULL, null=True)
SET_DEFAULT
:设置为默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是要指定这个字段的默认值;category = models.ForeignKey("Category", on_delete=models.SET_DEFAULT, default=Category.objects.get(pk=1), null=True)
SET()
:如果外键的那条数据被删除了,那么将会获取SET
函数中的值来作为外键的值。SET
函数可以接收一个可以调用的对象,如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去;category = models.ForeignKey("Category", on_delete=models.SET(Category.objects.get(pk=1)), null=True)
DO_NOTHING
:不采取任何行为,一切看数据库级别的约束。
以上这些选项只是Django级别的,数据级别依旧是RESTRICT。
举例说明,创建一个新的app,article
manage.py startapp article
在article/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("Category", on_delete=models.CASCADE, null=True)
需要注意的是:这里Category和Article两个模型在同一个app中,ForeignKey()的第一个参数可以是模型的名称;若外键是本身自己的模型,ForeignKey()的第一个参数要写成’self’。若在不同的两个app中,ForeignKey()的第一个参数为”app名.模型名”
on_delete
这个参数的值需要根据具体情况而定。这里是删除了分类的话,文章也一并被删除。
将上面的模型映射到数据库
manage.py makemigrations
manage.py migrate
打开Navicat查看article_article表的设计字段,自动增加了category_id
字段(由于category表中是自动生成的id
,django会自动将外键的名字定义成表名_id)。

article/views.py代码如下:
from .models import Category, Article
from django.http import HttpResponse
def index(request):
category = Category(name="唐诗")
category.save() # 这里需要先对category进行保存,后面的调用才能够正确传值,否则会报错。
article = Article(title="登高",
content="风急天高猿啸哀,渚清沙白鸟飞回。无边落木萧萧下, 不尽长江滚滚来。万里悲秋常作客,百年多病独登台。艰难苦恨繁霜鬓, 潦倒新停浊酒杯。")
article.category = category
article.save()
return HttpResponse('success')
article/urls.py代码如下:
from django.urls import path
from . import views
app_name = 'atrticle'
urlpatterns = [
path('', views.index, name='index')
]
访问url执行程序后,查验数据库已经在article_article和article_category两个表中插入了数据。
新创建一个app,user,表示文章的作者
manage.py startapp user
在user/models.py输入以下代码
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
在文章的models中追加文章作者
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("Category", on_delete=models.CASCADE, null=True)
author = models.ForeignKey("user.User", on_delete=models.CASCADE, null=True)
表关系
表之间的关系都是通过外键来进行关联的。而表之间的关系分为3种:一对一,一对多(对对一)和多对多。
一对多
在上面的例子中,通常情况下,一篇文章有一个作者,一个作者有多篇文章。作者和文章就是典型的一对多的关系。
实现一对多或多对一都是通过ForeignKey
来实现的。
接上面的例子,在article/views.py新增一个one_to_many_view
模型
from django.shortcuts import render
from .models import Category, Article
from user.models import User
from django.http import HttpResponse
def index(request):
category = Category(name="唐诗")
category.save() # 这里需要先对category进行保存,后面的调用才能够正确传值,否则会报错。
article = Article(title="登高",
content="风急天高猿啸哀,渚清沙白鸟飞回。无边落木萧萧下, 不尽长江滚滚来。万里悲秋常作客,百年多病独登台。艰难苦恨繁霜鬓, 潦倒新停浊酒杯。")
article.category = category
article.save()
return HttpResponse('success')
def one_to_many_view(request):
# 1.一对多的关联操作
# 实例化一篇文章
article = Article(title="母猪的产后护理", content="巴拉巴拉小魔仙")
# 实例化一位作者
author = User(name="本山大叔")
# author后续被传值调用需先进行保存
author.save()
# 获取分类表中的第一个分类
category = Category.objects.first()
# 进行关联:上面的category赋值给article.category,上面的author赋值给article.author
article.category = category
article.author = author
# 对article进行保存
article.save()
return HttpResponse("success")
配置好urls.py
from django.urls import path
from . import views
app_name = 'atrticle'
urlpatterns = [
path('', views.index, name='index'),
path('one_to_many', views.one_to_many_view, name='one_to_many')
]
访问http://127.0.0.1:8000/one_to_many
查看数据库可以看到新的文章及对应的分类、作者的关联都存入数据库了。
下面进行获取某个分类下的所有文章
from django.shortcuts import render
from .models import Category, Article
from user.models import User
from django.http import HttpResponse
def index(request):
category = Category(name="唐诗")
category.save() # 这里需要先对category进行保存,后面的调用才能够正确传值,否则会报错。
article = Article(title="登高",
content="风急天高猿啸哀,渚清沙白鸟飞回。无边落木萧萧下, 不尽长江滚滚来。万里悲秋常作客,百年多病独登台。艰难苦恨繁霜鬓, 潦倒新停浊酒杯。")
article.category = category
article.save()
return HttpResponse('success')
def one_to_many_view(request):
# # 1.一对多的关联操作
# # 实例化一篇文章
# article = Article(title="母猪的产后护理", content="巴拉巴拉小魔仙")
# # 实例化一位作者
# author = User(name="本山大叔")
# # author后续被传值调用需先进行保存
# author.save()
# # 获取分类表中的第一个分类
# category = Category.objects.first()
#
# # 进行关联:上面的category赋值给article.category,上面的author赋值给article.author
# article.category = category
# article.author = author
# # 对article进行保存
# article.save()
# 2.获取某个分类下所有的文章
category = Category.objects.first()
# 当一个模型中的某个字段成为另一模型的外键,django会给被引用的模型自动生成一个属性,属性的名称为"引用模型的名称(小写字母)_set",返回结果是QuerySet。
articles = category.article_set.all()
for article in articles:
print(article)
return HttpResponse("success")
注:当一个模型中的某个字段成为另一模型的外键,django会给被引用的模型自动生成一个属性,属性的名称为”引用模型的名称(小写字母)_set”,返回结果是QuerySet。
上面使用的是django自动生成的article_set
来进行获取。还有另外的一种方式,在关联外键的ForeignKey()
方法中定义related_name
参数,可以实现一样的效果。
修改后如下:
article/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("Category", on_delete=models.SET_NULL, null=True, related_name="articles")
author = models.ForeignKey("user.User", on_delete=models.CASCADE, null=True)
def __str__(self):
return "<Article:(id:%s, title:%s)>" % (self.id, self.title)
article/views.py
from django.shortcuts import render
from .models import Category, Article
from user.models import User
from django.http import HttpResponse
def index(request):
category = Category(name="唐诗")
category.save() # 这里需要先对category进行保存,后面的调用才能够正确传值,否则会报错。
article = Article(title="登高",
content="风急天高猿啸哀,渚清沙白鸟飞回。无边落木萧萧下, 不尽长江滚滚来。万里悲秋常作客,百年多病独登台。艰难苦恨繁霜鬓, 潦倒新停浊酒杯。")
article.category = category
article.save()
return HttpResponse('success')
def one_to_many_view(request):
# 2.获取某个分类下所有的文章
# 获取数据库分类表中的第一条数据
category = Category.objects.first()
articles = category.articles.all()
for article in articles:
print(article)
return HttpResponse("success")
一对一
通常网站对登录用户会设计两张表,用户表和用户信息表。用户表保存用户经常使用的信息,比如用户名等;而用户信息表则保存不经常用到的信息,比如生日等。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
# 重新__str__()方法
def __str__(self):
return "<User:(id:%s, username:%s)>" % (self.id, self.name)
class UserExtension(models.Model):
birthday = models.DateField(null=True)
user = models.OneToOneField("User", on_delete=models.CASCADE)
# 重新__str__()方法
def __str__(self):
return "<UserExtension:(id:%s, birthday:%s, user_id:%s)>" % (self.id, self.birthday, self.user_id)
一对一关系映射通过OneToOneField
来实现。上面的代码是在UserExtension
模型上增加了一个一对一的关系映射,其底层是在UserExtension
这个表上增加了一个user_id
,来和user表进行关联,并且这个外键数据在表中必须是唯一的,来保证一对一。
在article/views.py中增加one_to_one方法
from django.shortcuts import render
from .models import Category, Article
from user.models import User, UserExtension
from django.http import HttpResponse
def one_to_one_view(request):
# 查找出用户表中的第一条数据
user = User.objects.first()
# 实例化一条用户扩展信息
extension = UserExtension(birthday='20010101')
# 查找到的user和用户扩展信息进行关联
extension.user = user
extension.save()
return HttpResponse("one_to_one success")
进行数据库映射后,访问http://127.0.0.1:8000/one_to_one (已在urls.py中增加对应路由)
在数据库中查验数据后,将上面的代码中改变一下birthday='20010102'
,再次执行,会出错(1062, "Duplicate entry '1' for key 'user_userextension.user_id'")
,因为数据库中已经有一条user_id
为1的数据了,无法再次添加。
上面已经有了用户表和用户扩展表的关联数据,下面实现通过用户来显示用户扩展表中的信息和通过用户扩展表显示用户表中的信息
from django.shortcuts import render
from .models import Category, Article
from user.models import User, UserExtension
from django.http import HttpResponse
def one_to_one_view(request):
# # 查找出用户表中的第一条数据
# user = User.objects.first()
# # 实例化一条用户扩展信息
# extension = UserExtension(birthday='20010101')
# # 查找到的user和用户扩展信息进行关联
# extension.user = user
# extension.save()
# 查找到用户扩展表的第一条数据
extension = UserExtension.objects.first()
# 通过用户扩展表展示用户表信息
print(extension.user)
# 查找到用户表中的第一条数据
user = User.objects.first()
# 通过用户表展示用户扩展表信息
print(user.userextension)
return HttpResponse("one_to_one success")
其中,在user表和userextension表进行one_two_one
关联时,django会自动为user视图函数增加一个userextension
属性(有外键的那个视图函数名称的全部小写为属性名称),指向UserExtension视图函数对应的实例。所以可以通过user.userextension
,来获取用户扩展表userextension的信息。
同样,可以在关联外键的ForeignKey()
方法中定义related_name
参数,来自定义属性名称
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
# 重新__str__()方法
def __str__(self):
return "<User:(id:%s, username:%s)>" % (self.id, self.name)
class UserExtension(models.Model):
birthday = models.DateField(null=True)
user = models.OneToOneField("User", on_delete=models.CASCADE, related_name='extension')
# 重新__str__()方法
def __str__(self):
return "<UserExtension:(id:%s, birthday:%s, user_id:%s)>" % (self.id, self.birthday, self.user_id)
from django.shortcuts import render
from .models import Category, Article
from user.models import User, UserExtension
from django.http import HttpResponse
def one_to_one_view(request):
# 查找到用户扩展表的第一条数据
extension = UserExtension.objects.first()
# 通过用户扩展表展示用户表信息
print(extension.user)
# 查找到用户表中的第一条数据
user = User.objects.first()
# 通过用户表展示用户扩展表信息
print(user.extension)
return HttpResponse("one_to_one success")
执行后,得到的结果如下:
<User:(id:1, username:本山大叔)>
<UserExtension:(id:1, birthday:2001-01-01, user_id:1)>
多对多
文章和标签就是多对多的关系,一篇文章可以有多个标签,一个标签可以标识多篇文章。
新建标签模型,并与文章建立多对多的关联article/models.py,通过ManyToManyField()
方法来实现多对多
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("Category", on_delete=models.SET_NULL, null=True, related_name="articles")
author = models.ForeignKey("user.User", on_delete=models.CASCADE, null=True)
tags = models.ManyToManyField("Tag")
def __str__(self):
return "<Article:(id:%s, title:%s)>" % (self.id, self.title)
class Tag(models.Model):
name = models.CharField(max_length=50)
映射到数据库后,除了生成了tab表之前,django还会自动生成一个多对多关系表article_article_tags
(app名称_模型名称_多对多关系模型名称s
)
在article/views.py中增加many_to_many方法
from django.shortcuts import render
from .models import Category, Article, Tag
from user.models import User, UserExtension
from django.http import HttpResponse
def many_to_many_view(request):
article = Article.objects.first()
tag = Tag(name="热门文章")
tag.save()
tag.article_set.add(article)
return HttpResponse("many_to_many success")
进行数据库映射后,访问http://127.0.0.1:8000/many_to_many (已在urls.py中增加对应路由)
在数据库表article_article_tags生成了对应的记录。需要注意的是,因为是多对多的关系,所以ManyToManyField()
可以定义在Article模型中,也可以定义在Tag模型中。对应的在视图函数中,前者是tag.article_set
,后者是article.tag_set
。article_set
和tag_set
都是django自动生成的,可以在ManyToManyField()
中增加related_name
参数进行自定义。
上面的many_to_many_view
视图函数还可以下面的方式实现。上面的是把文章指给标签,下面的方式是将标签指给文章。
def many_to_many_view(request):
tag = Tag.objects.get(pk=1)
article = Article.objects.get(pk=2)
article.tags.add(tag)
return HttpResponse("many_to_many success")
访问http://127.0.0.1:8000/many_to_many 依旧可以正确进行了文章和标签的绑定对应。
ORM查询操作
查找数据是数据库操作中一个非常重要的工作,一般使用filter
、exclude
和get
三种方法来实现。
创建一个新的项目,配置数据库
新建一个app:front
manage.py startapp front
front/models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
class Meta:
db_table = 'article'
front/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse("success")
front/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('index/', views.index)
]
映射模型到数据库
manage.py makemigrations
manage.py migrate
启动服务,访问 http://127.0.0.1:8000/index/ 能够正确放回结果。
查询条件
exact和iexact
在ORM中,查询条件都是使用field+__+condition
(要查询的字段__查询条件)的方式来使用的
exact:使用精确的=
进行查找。这里等价于id=?
。如果给定的值是None,最终会解释成NULL。
article/views.py
from django.http import HttpResponse
from .models import Article
def index(request):
# article = Article.objects.filter(id__exact=1)
article = Article.objects.filter(title__exact='登高')
print(article)
print(article.query)
return HttpResponse("success")
执行后,控制台的结果为:
<QuerySet [<Article: <Article(id:1, title:登高)>>]>
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` = 登高
iexact相等于SQL中的like
from django.shortcuts import render
from django.http import HttpResponse
from .models import Article
def index(request):
# article = Article.objects.filter(id__exact=1)
article = Article.objects.filter(title__iexact='登高')
print(article)
print(article.query) # article.query只适用QuerySet对象
return HttpResponse("success")
执行后,控制台的结果为:
<QuerySet [<Article: <Article(id:1, title:登高)>>]>
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE 登高
contains和icontains
contains:判断某个字段是否包含了某个数据,大小写敏感。
article/views.py
from django.shortcuts import render
from django.http import HttpResponse
from .models import Article
def index(request):
# article = Article.objects.filter(id__exact=1)
# article = Article.objects.filter(title__iexact='登高')
article = Article.objects.filter(title__contains='You')
print(article)
print(article.query)
return HttpResponse("success")
执行结果:
<QuerySet [<Article: <Article(id:2, title:You and Me)>>]>
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE BINARY %You%
icontains:判断某个字段是否包含了某个数据,大小写不敏感。
from django.shortcuts import render
from django.http import HttpResponse
from .models import Article
def index(request):
# article = Article.objects.filter(id__exact=1)
# article = Article.objects.filter(title__iexact='登高')
# article = Article.objects.filter(title__contains='You')
article = Article.objects.filter(title__icontains='You')
print(article)
print(article.query)
return HttpResponse("success")
执行结果:
<QuerySet [<Article: <Article(id:2, title:You and Me)>>, <Article: <Article(id:3, title:you and me)>>]>
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE %You%
in和关联模型查询
提取给定的field
的值,判断是否在给定的容器中。容器可以为list、tuple或者任何一个可以迭代的对象,包括QuerySet对象。
例:查找id在[1,2,3]中的文章
from django.shortcuts import render
from django.http import HttpResponse
from .models import Article
def query1(request):
articles = Article.objects.filter(id__in=[1, 2, 3])
for article in articles:
print(article)
return HttpResponse("success")
执行结果如下:
<Article(id:1, title:登高)>
<Article(id:2, title:You and Me)>
<Article(id:3, title:you and me)>
例:跨表实现查找文章id在[1,2,3]中的分类
首先定义分类模型
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
category = models.ForeignKey("Category", on_delete=models.CASCADE,null=True)
def __str__(self):
return "<Article(id:%s, title:%s)>" % (self.pk, self.title)
class Meta:
db_table = 'article'
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return "<Article(id:%s, name:%s)>" % (self.pk, self.name)
class Meta:
db_table = 'category'
同步映射数据库
实现查询条件
from django.shortcuts import render
from django.http import HttpResponse
from .models import Article, Category
def query2(request):
categories = Category.objects.filter(article__id__in=[1, 2, 3])
for category in categories:
print(category)
return HttpResponse("success")
执行结果为:
<Article(id:1, name:最新文章)>
<Article(id:1, name:最新文章)>
<Article(id:2, name:唐诗)>
例:上面两个例子判断的容器都是列表,下面指定一个QuerySet对象:查找所有标题中包含’and’的文章的分类
from django.shortcuts import render
from django.http import HttpResponse
from .models import Article, Category
def query3(request):
articles = Article.objects.filter(title__contains='and')
categories = Category.objects.filter(article__in=articles)
for category in categories:
print(category)
return HttpResponse("success")
执行结果为:
<Article(id:1, name:最新文章)>
<Article(id:1, name:最新文章)>
gt、gte、lt和lte
gt(greater than):某个field的值要大于给定的值。
def query4(request):
# 查找id大于2的所有文章
articles = Article.objects.filter(id__gt=2)
for article in articles:
print(article)
return HttpResponse("success")
<Article(id:3, title:you and me)>
gte(greater than equal):大于等于
def query4(request):
# 查找id大于等于2的所有文章
articles = Article.objects.filter(id__gte=2)
for article in articles:
print(article)
return HttpResponse("success")
<Article(id:2, title:You and Me)>
<Article(id:3, title:you and me)>
lt(lower than):小于
def query4(request):
# 查找id小于2的所有文章
articles = Article.objects.filter(id__lt=2)
for article in articles:
print(article)
return HttpResponse("success")
<Article(id:1, title:登高)>
lte(lower than equal):小于等于
def query4(request):
# 查找id小于等于2的所有文章
articles = Article.objects.filter(id__lte=2)
for article in articles:
print(article)
return HttpResponse("success")
<Article(id:1, title:登高)>
<Article(id:2, title:You and Me)>
startswith和endswith
startswith:判断某个字段的值是否以某个值开始的。大小写敏感
def query5(request):
articles = Article.objects.filter(title__startswith="you")
for article in articles:
print(article)
return HttpResponse("success")
<Article(id:3, title:you and me)>
istartswith:判断某个字段的值是否以某个值开始的。大小写不敏感
def query5(request):
articles = Article.objects.filter(title__istartswith="you")
for article in articles:
print(article)
return HttpResponse("success")
<Article(id:2, title:You and Me)>
<Article(id:3, title:you and me)>
endswith:判断某个字段的值是否以某个值结尾的。大小写敏感
def query5(request):
articles = Article.objects.filter(title__endswith="Me")
for article in articles:
print(article)
return HttpResponse("success")
<Article(id:2, title:You and Me)>
iendswith:判断某个字段的值是否以某个值结尾的。大小写不敏感
def query5(request):
articles = Article.objects.filter(title__iendswith="Me")
for article in articles:
print(article)
return HttpResponse("success")
<Article(id:2, title:You and Me)>
<Article(id:3, title:you and me)>
时间的查询条件
range
判断某个field的值是否在给定的区间内。
在front/models.py中为Article添加创建时间字段
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
category = models.ForeignKey("Category", on_delete=models.CASCADE, null=True)
create_time = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return "<Article(id:%s, title:%s)>" % (self.pk, self.title)
class Meta:
db_table = 'article'
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return "<Article(id:%s, name:%s)>" % (self.pk, self.name)
class Meta:
db_table = 'category'
同步映射到数据库
在front/views.py中新建视图函数
from django.shortcuts import render
from django.http import HttpResponse
from .models import Article, Category
from datetime import datetime
from django.utils.timezone import make_aware
def query6(request):
start_time = make_aware(datetime(year=2023, month=10, day=31, hour=10, minute=0, second=0))
end_time = make_aware(datetime(year=2023, month=10, day=31, hour=11, minute=0, second=0))
articles = Article.objects.filter(create_time__range=(start_time, end_time))
print(articles.query)
for article in articles:
print(article)
return HttpResponse("success")
通过create_time__range
查询某个时间段内的全部文章。执行结果如下:
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`category_id`, `article`.`create_time` FROM `article` WHERE `article`.`create_time` BETWEEN 2023-10-31 02:00:00 AND 2023-10-31 03:00:00
<Article(id:1, title:登高)>
<Article(id:2, title:You and Me)>
<Article(id:3, title:you and me)>
date
针对某些date或者datetime类型的字段,可以指定date的范围。并且这个时间过滤,还可以使用链式调用。
def query7(request):
articles = Article.objects.filter(create_time__date=datetime(year=2023, month=10, day=31))
print(articles.query)
for article in articles:
print(article)
return HttpResponse("success")
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`category_id`, `article`.`create_time` FROM `article` WHERE DATE(CONVERT_TZ(`article`.`create_time`, UTC, Asia/Shanghai)) = 2023-10-31
<Article(id:1, title:登高)>
<Article(id:2, title:You and Me)>
<Article(id:3, title:you and me)>
year
根据年份查找
def query7(request):
articles = Article.objects.filter(create_time__year=2023)
print(articles.query)
for article in articles:
print(article)
return HttpResponse("success")
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`category_id`, `article`.`create_time` FROM `article` WHERE `article`.`create_time` BETWEEN 2022-12-31 16:00:00 AND 2023-12-31 15:59:59.999999
<Article(id:1, title:登高)>
<Article(id:2, title:You and Me)>
<Article(id:3, title:you and me)>
month
根据月份查找
articles = Article.objects.filter(create_time__month=10)
day
根据日期查找
articles = Article.objects.filter(create_time__day=31)
week_day
根据星期几进行查找。1表示星期天,7表示星期六。
articles = Article.objects.filter(create_time__week_day=3)
time
根据时间进行查找。存入数据库的时间,秒后面还有毫秒,所以不能靠秒位来查找,可以指定当前秒和下一秒的区间进行查找。
def query7(request):
start_time = time(hour=10, minute=13, second=57)
end_time = time(hour=10, minute=13, second=58)
articles = Article.objects.filter(create_time__time__range=(start_time, end_time))
print(articles.query)
for article in articles:
print(article)
return HttpResponse("success")
isnull
isnull根据是否为空进行查找。
def query8(request):
articles = Article.objects.filter(create_time__isnull=True)
print(articles.query)
for article in articles:
print(article)
return HttpResponse("success")
regex和iregex
regex:大小写敏感的正则表达式
def query8(request):
articles = Article.objects.filter(title__regex=r"^you")
print(articles.query)
for article in articles:
print(article)
return HttpResponse("success")
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`category_id`, `article`.`create_time` FROM `article` WHERE REGEXP_LIKE(`article`.`title`, ^you, 'c')
<Article(id:3, title:you and me)>
iregex:大小写不敏感的正则表达式
def query8(request):
articles = Article.objects.filter(title__iregex=r"^you")
print(articles.query)
for article in articles:
print(article)
return HttpResponse("success")
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`category_id`, `article`.`create_time` FROM `article` WHERE REGEXP_LIKE(`article`.`title`, ^you, 'i')
<Article(id:2, title:You and Me)>
<Article(id:3, title:you and me)>
聚合函数
聚合函数是指对一组值执行计算并返回单一的值。所有聚合函数都是存放在django.db.models
中;聚合函数不能够单独执行,需要放在一些可以执行聚合函数的方法中去执行。
聚合函数执行完成后,会默认给这个聚合函数的值取个名字。默认的取名规则是filed+__+聚合函数名称
。如果不想使用默认的名称,可以在使用聚合函数的时候传递关键字参数进去。
创建一个新的项目,配置数据库
新建一个app:front
manage.py startapp front
front/models.py
from django.db import models
class Author(models.Model):
""" 作者模型 """
name = models.CharField(max_length=100)
age = models.IntegerField()
email = models.EmailField()
def __str__(self):
return "<Author(id:%s, name:%s, age:%s, email:%s)>" % (self.pk, self.name, self.age, self.email)
class Meta:
db_table = "author"
class Publisher(models.Model):
""" 出版社模型 """
name = models.CharField(max_length=300)
def __str__(self):
return "<Publisher(id:%s, name:%s)>" % (self.pk, self.name)
class Meta:
db_table = "publisher"
class Book(models.Model):
""" 图书模型 """
name = models.CharField(max_length=300)
pages = models.IntegerField()
price = models.FloatField()
rating = models.FloatField()
author = models.ForeignKey("Author", on_delete=models.CASCADE)
publisher = models.ForeignKey("Publisher", on_delete=models.CASCADE)
def __str__(self):
return "<Publisher(id:%s, name:%s, price:%s, rating:%s, author:%s, publisher:%s)>" \
% (self.pk, self.name, self.price, self.rating, self.author, self.publisher)
class Meta:
db_table = "book"
class BookOrder(models.Model):
""" 图书订单模型 """
book = models.ForeignKey("Book", on_delete=models.CASCADE)
price = models.FloatField()
create_time = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return "<Publisher(id:%s, book:%s, price:%s)>" % (self.pk, self.book, self.price)
class Meta:
db_table = "book_order"
front/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse("success")
front/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('index/', views.index)
]
映射模型到数据库
manage.py makemigrations
manage.py migrate
启动服务,访问 http://127.0.0.1:8000/index/ 能够正确放回结果。
导入测试数据。
Avg
Avg请求平均值,这里通过获取所有图书定价的平均值作为例子
from django.http import HttpResponse
from .models import Book
from django.db.models import Avg
from django.db import connection
def index(request):
result = Book.objects.aggregate(avg=Avg("price"))
print(result)
print(connection.queries)
return HttpResponse("success")
{'avg': 97.25}
[{'sql': "\n SELECT VERSION(),\n @@sql_mode,\n @@default_storage_engine,\n @@sql_auto_is_null,\n @@lower_case_table_names,\n CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL\n ", 'time': '0.001'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.001'}, {'sql': 'SELECT AVG(`book`.`price`) AS `avg` FROM `book`', 'time': '0.001'}]
aggregate和annotate
aggregate:返回使用聚合函数后的字段和值;
annotate:在原来模型字段的基础之上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型主键进行分组(group by)
from django.http import HttpResponse
from .models import Book, BookOrder
from django.db.models import Avg
from django.db import connection
def index1(request):
# 获取每本图书销售的平均价格
books = Book.objects.annotate(avg=Avg("bookorder__price"))
for book in books:
print('%s,%s' % (book.name, book.avg))
return HttpResponse("success")
其中,annotate()
函数会根据Book模型和BookOrder模型的外键关联,按照book.id = book_order.book_id
的条件进行分组,然后再计算出每本图书销售价格的平均值。
三国演义,104.5
水浒传,108.0
西游记,89.33333333333333
红楼梦,93.5
Count
Count:获取指定对象的个数。Count类有一个参数distinct,默认是等于False,如果等于True,那么将去掉重复的值。
获取book表中一共有多少本书
from django.http import HttpResponse
from .models import Book, BookOrder
from django.db.models import Avg, Count
def index2(request):
# 获取book表中一共有多少本书
result = Book.objects.aggregate(book_nums=Count("id"))
print(result)
return HttpResponse("success")
{'book_nums': 4}
统计作者表中不同的年龄数,即去掉相同的年龄
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count
from django.db import connection
def index2(request):
# 统计作者表中不同的年龄数,即去掉相同的年龄
result = Author.objects.aggregate(age_nums=Count("age", distinct=True))
print(result)
return HttpResponse("success")
{'age_nums': 3}
统计每本书的销量
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count
from django.db import connection
def index2(request):
# 统计每本书的销量
# annotate()函数的特点,会对book表按book_id进行分组,生成一个聚合字段book_nums。若为指定字段名,django会给出默认的字段名"bookorder__count"(模型名称__聚合函数名称(全部小写字母))
books = Book.objects.annotate(book_nums=Count("bookorder"))
for book in books:
print("%s,%s" % (book.name, book.book_nums))
# print("%s,%s" % (book.name, book.bookorder__count))
# print(connection.queries)
return HttpResponse("success")
其中annotate(book_nums=Count("bookorder"))
,annotate()函数的特点,会对book表按book_id
进行分组,生成一个聚合字段book_nums
。若未指定字段名,django会给出默认的字段名"bookorder__count"
(模型名称__聚合函数名称(全部小写字母))。Count("bookorder")
的参数bookorder
,实际上是bookorder__id
表示从bookorder
表中获取id
字段的数据,若只写了bookorder
,django会自动以这个表的主键id
为查询字段进行查询。
执行结果如下:
三国演义,2
水浒传,1
西游记,3
红楼梦,2
Max和Min
获取指定对象的最大值和最小值
获取作者的最大年龄和最小年龄
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min
from django.db import connection
def index3(request):
# 获取作者的最大年龄和最小年龄
result = Author.objects.aggregate(max=Max("age"), min=Min("age"))
print(result)
return HttpResponse("success")
{'max': 46, 'min': 28}
获取每一本书售卖的最大价格和最小价格
Max(bookorder__price)
从bookorder
表的price
字段获取最大的值
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min
from django.db import connection
def index3(request):
# 获取每一本书售卖的最大价格和最小价格
books = Book.objects.annotate(max=Max("bookorder__price"), min=Min("bookorder__price"))
for book in books:
print("%s,%s,%s" % (book.name, book.max, book.min))
return HttpResponse("success")
三国演义,110.0,99.0
水浒传,108.0,108.0
西游记,95.0,85.0
红楼梦,94.0,93.0
Sum
Sum:求指定对象的总和。
获取全部图书的销售总和
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum
from django.db import connection
def index4(request):
# 获取全部图书的销售总和
result = BookOrder.objects.aggregate(sum=Sum("price"))
print(result)
return HttpResponse("success")
{'sum': 772.0}
统计每一本书的销售总额
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum
from django.db import connection
def index4(request):
# 统计每一本书的销售总额
books = Book.objects.annotate(sum=Sum("bookorder__price"))
for book in books:
print("%s,%s" % (book.name, book.sum))
return HttpResponse("success")
三国演义,209.0
水浒传,108.0
西游记,268.0
红楼梦,187.0
计算2023年全部图书截止目前的销售总额
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum
from django.db import connection
def index4(request):
# 计算2023年全部图书截止目前的销售总额
result = BookOrder.objects.filter(create_time__year=2023).aggregate(sum=Sum("price"))
print(result)
return HttpResponse("success")
{'sum': 772.0}
计算2023年截止目前每本书的销售总额
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum
from django.db import connection
def index4(request):
# 计算2023年截止目前每本书的销售总额
result = Book.objects.filter(bookorder__create_time__year=2023).annotate(sum=Sum("bookorder__price"))
for result in result:
print("%s,%s" % (result.name, result.sum))
return HttpResponse("success")
西游记,268.0
红楼梦,187.0
水浒传,108.0
三国演义,209.0
F表达式
F表达式是用来优化ORM操作数据库的。F表达式不需要先把数据从数据库中查找出来才进行操作,而是在生成SQL语句的时候,动态的获取传给F表达式的值。
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F
def index5(request):
# 所有图书的价格都减10元
Book.objects.update(price=F("price") - 10)
return HttpResponse("success")
Q表达式
使用Q表达式包裹查询条件,可以在条件之间进行多种与&
、或|
、非~
等,从而实现一些复杂的查询操作。
获取价格大于100元,并且评分在4.9分以上的图书
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
def index6(request):
# 获取价格大于100元,并且评分在4.9分以上的图书
books = Book.objects.filter(price__gte=100, rating__gte=4.9)
for book in books:
print("%s,%s,%s" % (book.name, book.price, book.rating))
return HttpResponse("success")
三国演义,108.0,4.91
使用Q表达式
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
def index6(request):
# 获取价格大于100元,并且评分在4.9分以上的图书
books = Book.objects.filter(Q(price__gte=100) & Q(rating__gte=4.9))
for book in books:
print("%s,%s,%s" % (book.name, book.price, book.rating))
return HttpResponse("success")
获取价格小于100元,或者评分低于4.9分的图书
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
def index6(request):
# 获取价格小于100元,或者评分低于4.9分的图书
books = Book.objects.filter(Q(price__lt=100) | Q(rating__lt=4.9))
for book in books:
print("%s,%s,%s" % (book.name, book.price, book.rating))
return HttpResponse("success")
水浒传,101.0,4.83
西游记,88.0,4.85
红楼梦,99.0,4.9
QuerySet API
在做查找操作的时候,都是通过模型名称.objects
的方式进行操作。而模型名字.objects
是一个django.db.models.manager.Manager
对象,它本身没有任何属性和方法,都是在使用过程中动态添加的方式,从QuerySet
类中拷贝过来的。
在使用QuerySet
进行查询操作时,可以通过多种操作,比如上文中提到的,先进行条件过来然后对某个字段进行求和,这一系列的操作可以通过链式调用的方式进行。
Book.objects.filter(bookorder__create_time__year=2023).annotate(sum=Sum("bookorder__price"))
可以看到annotate
方法是直接在filter
执行后调用的。这说明filter
返回的对象是一个拥有annotate
方法的对象。而这个对象正是QuerySet
对象。
所以,若要学习ORM模型的查询操作,QuerySet
的一些常用API是必学的。
filter
将满足条件的数据提取出来,返回一个新的QuerySet
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
from django.db import connection
def index1(request):
# books1 = Book.objects.filter(id__gte=2)
# print(type(books1))
# books2 = books1.filter(~Q(id=3)) # 过滤掉id等于3的数据
books2 = Book.objects.filter(id__gte=2).filter(~Q(id=3)) # 链式调用
for book in books2:
print("%s,%s" % (book.id, book.name))
return HttpResponse("success!")
<class 'django.db.models.query.QuerySet'>
4,水浒传
5,西游记
6,红楼梦
exclude
排除满足条件的数据,返回一个新的QuerySet
上面的例子中,过滤掉id等于3的数据可以用exclude来实现
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
def index1(request):
# books1 = Book.objects.filter(id__gte=2)
# print(type(books1))
# books2 = books1.filter(~Q(id=3)) # 过滤掉id等于3的数据
books2 = Book.objects.filter(id__gte=2).exclude(id=3) # 链式调用
print(type(books2))
for book in books2:
print("%s,%s" % (book.id, book.name))
return HttpResponse("success!")
<class 'django.db.models.query.QuerySet'>
4,水浒传
5,西游记
6,红楼梦
annotate
给QuerySet
中的每个对象都添加一个使用查询表达式(聚合函数、F表达式、Q表达式等)的新字段
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
def index1(request):
books = Book.objects.annotate(author_name=F("author__name"))
print(type(books))
for book in books:
print("%s,%s,%s" % (book.id, book.name, book.author_name))
return HttpResponse("success!")
<class 'django.db.models.query.QuerySet'>
3,三国演义,罗贯中
4,水浒传,施耐庵
5,西游记,吴承恩
6,红楼梦,曹雪芹
order_by
指定将查询的结果根据某个字段进行排序。若进行倒序排列则是在字段前面加上负号即可。
# 根据创建的时间正序排序
articles = Article.objects.order_by("create_time")
# 根据创建的时间倒序排序
articles = Article.objects.order_by("-create_time")
# 根据作者的名字进行排序
articles = Article.objects.order_by("author__name")
# 首先根据创建的时间进行排序,如果时间相同,则根据作者的名字进行排序
articles = Article.objects.order_by("create_time",'author__name')
若多个order_by
链式调用,会把前面排序的规则给打乱,只有最后一个order_by
生效。
articles = Article.objects.order_by("create_time").order_by("author__name")
获取图书数据,根据图书的销量进行排序
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
from django.db import connection
def index1(request):
# 获取图书数据,根据图书的销量进行排序
books = Book.objects.annotate(order_nums=Count("bookorder")).order_by("-order_nums")
print(type(books))
for book in books:
print("%s,%s,%s" % (book.id, book.name, book.order_nums))
return HttpResponse("success!")
<class 'django.db.models.query.QuerySet'>
5,西游记,3
3,三国演义,2
6,红楼梦,2
4,水浒传,1
values和values_list
values
values用来指定获取数据时,需要提取哪些字段。默认情况下会把表中所有字段全部提取出来,可以使用values来进行指定,并且使用了values方法后,提取出的QuerySet中的数据类型不是模型,而是在values方法中指定的字段和值形成的字典。
获取单一模型中的指定字段
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
from django.db import connection
def index2(request):
books = Book.objects.values("id", "name")
print(type(books))
for book in books:
print(book)
return HttpResponse("success!")
<class 'django.db.models.query.QuerySet'>
{'id': 3, 'name': '三国演义'}
{'id': 4, 'name': '水浒传'}
{'id': 5, 'name': '西游记'}
{'id': 6, 'name': '红楼梦'}
获取关联模型中的指定字段
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
def index3(request):
books = Book.objects.values("id", "name", "author__name")
print(type(books))
for book in books:
print(book)
return HttpResponse("success!")
<class 'django.db.models.query.QuerySet'>
{'id': 3, 'name': '三国演义', 'author__name': '罗贯中'}
{'id': 4, 'name': '水浒传', 'author__name': '施耐庵'}
{'id': 5, 'name': '西游记', 'author__name': '吴承恩'}
{'id': 6, 'name': '红楼梦', 'author__name': '曹雪芹'}
可以给上面例子中的author__name
自定义字段名称
books = Book.objects.values("id", "name", author_name=F("author__name"))
获取图书的销量
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
def index3(request):
books = Book.objects.values("id", "name", order_nums=Count("bookorder"))
print(type(books))
for book in books:
print(book)
return HttpResponse("success!")
<class 'django.db.models.query.QuerySet'>
{'id': 3, 'name': '三国演义', 'order_nums': 2}
{'id': 4, 'name': '水浒传', 'order_nums': 1}
{'id': 5, 'name': '西游记', 'order_nums': 3}
{'id': 6, 'name': '红楼梦', 'order_nums': 2}
values_list
values_list类似于values,区别在于返回的QuerySet中,存储的不是字典,而是元祖。
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
def index3(request):
books = Book.objects.values_list("id", "name")
print(type(books))
for book in books:
print(book)
return HttpResponse("success!")
<class 'django.db.models.query.QuerySet'>
(3, '三国演义')
(4, '水浒传')
(5, '西游记')
(6, '红楼梦')
如果vlaues_list只指定一个字段,可以指定flat=True
,这样返回回来的结果就不是元祖,而是这个字段的值。
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
def index3(request):
books = Book.objects.values_list("name", flat=True)
print(type(books))
for book in books:
print(book)
return HttpResponse("success!")
<class 'django.db.models.query.QuerySet'>
三国演义
水浒传
西游记
红楼梦
all方法
获取这个ORM模型的QuerySet对象。
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
def index4(requeset):
books = Book.objects.all()
print(type(books))
for book in books:
print(book)
return HttpResponse("success!")
<class 'django.db.models.query.QuerySet'>
<Publisher(id:3, name:三国演义, price:108.0, rating:4.91, author:<Author(id:3, name:罗贯中, age:36, email:lgz@plscript.cn)>, publisher:<Publisher(id:1, name:中国邮电出版社)>)>
<Publisher(id:4, name:水浒传, price:101.0, rating:4.83, author:<Author(id:4, name:施耐庵, age:46, email:sna@plscript.cn)>, publisher:<Publisher(id:1, name:中国邮电出版社)>)>
<Publisher(id:5, name:西游记, price:88.0, rating:4.85, author:<Author(id:2, name:吴承恩, age:28, email:wce@plscript.cn)>, publisher:<Publisher(id:2, name:清华大学出版社)>)>
<Publisher(id:6, name:红楼梦, price:99.0, rating:4.9, author:<Author(id:1, name:曹雪芹, age:35, email:cxq@plscript.cn)>, publisher:<Publisher(id:2, name:清华大学出版社)>)>
select_related
在获取某个模型的数据的同时,也提前将相关联的数据提取出来。
结合上面的内容,查询都会是以下面的方式进行,但这样的方式,会导致访问数据库过于频繁,下面的例子中,访问数据库1+4=5次。
def index5(requeset):
books = Book.objects.all()
for book in books:
print(book.author.name)
return HttpResponse("success!")
通过使用select_related()
方法先将后续需要用的数据提前查询出来存在内存中,这样再次查找相关的数据时会在内存中查找,而不会去查数据库。
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
from django.db import connection
def index5(requeset):
# books = Book.objects.all()
books = Book.objects.select_related("author")
for book in books:
print(book.author.name)
print(connection.queries)
return HttpResponse("success!")
罗贯中
施耐庵
吴承恩
曹雪芹
[{'sql': "\n SELECT VERSION(),\n @@sql_mode,\n @@default_storage_engine,\n @@sql_auto_is_null,\n @@lower_case_table_names,\n CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL\n ", 'time': '0.001'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}, {'sql': 'SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, `author`.`id`, `author`.`name`, `author`.`age`, `author`.`email` FROM `book` INNER JOIN `author` ON (`book`.`author_id` = `author`.`id`)', 'time': '0.002'}]
selected_related
只能用在一对多或者一对一中,不能用在多对多或者多对一中。
prefetch_related
这个方法和selected_related
非常相似,就是访问多个表中的数据的时候,减少数据库查询次数。而prefetch_related
方法是为了解决多对一和多对对的关系的查询问题的。
查找每本图书对应的订单id
def index6(requeset):
books = Book.objects.all()
for book in books:
print(book.name)
orders = book.bookorder_set.all()
for order in orders:
print(order.id)
return HttpResponse("success!")
三国演义
8
9
水浒传
7
西游记
2
3
4
红楼梦
5
6
[{'sql': "\n SELECT VERSION(),\n @@sql_mode,\n @@default_storage_engine,\n @@sql_auto_is_null,\n @@lower_case_table_names,\n CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL\n ", 'time': '0.001'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}, {'sql': 'SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book`', 'time': '0.001'}, {'sql': 'SELECT `book_order`.`id`, `book_order`.`book_id`, `book_order`.`price`, `book_order`.`create_time` FROM `book_order` WHERE `book_order`.`book_id` = 3', 'time': '0.001'}, {'sql': 'SELECT `book_order`.`id`, `book_order`.`book_id`, `book_order`.`price`, `book_order`.`create_time` FROM `book_order` WHERE `book_order`.`book_id` = 4', 'time': '0.001'}, {'sql': 'SELECT `book_order`.`id`, `book_order`.`book_id`, `book_order`.`price`, `book_order`.`create_time` FROM `book_order` WHERE `book_order`.`book_id` = 5', 'time': '0.001'}, {'sql': 'SELECT `book_order`.`id`, `book_order`.`book_id`, `book_order`.`price`, `book_order`.`create_time` FROM `book_order` WHERE `book_order`.`book_id` = 6', 'time': '0.000'}]
通过prefetch_related
来实现
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q
from django.db import connection
def index6(requeset):
# books = Book.objects.all()
books = Book.objects.prefetch_related("bookorder_set")
for book in books:
print(book.name)
orders = book.bookorder_set.all()
for order in orders:
print(order.id)
return HttpResponse("success!")
三国演义
8
9
水浒传
7
西游记
2
3
4
红楼梦
5
6
[{'sql': "\n SELECT VERSION(),\n @@sql_mode,\n @@default_storage_engine,\n @@sql_auto_is_null,\n @@lower_case_table_names,\n CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL\n ", 'time': '0.001'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.001'}, {'sql': 'SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book`', 'time': '0.001'}, {'sql': 'SELECT `book_order`.`id`, `book_order`.`book_id`, `book_order`.`price`, `book_order`.`create_time` FROM `book_order` WHERE `book_order`.`book_id` IN (3, 4, 5, 6)', 'time': '0.002'}]
不使用prefetch_related
时,每循环一次都要查询一次数据库。而使用了prefetch_related
后,由于提前将bookorder的数据查出来存放在内存中,循环时读取内存中的数据即可。
在上面例子的基础上,还要对查询到数据进行筛选过滤,比如增加一个过滤条件,图书的售价大于90元,这样的操作就会从新去操作数据库,而不是从内存中拿数据。这时需要引入Prefetch进行自定义查询条件设置,这样查询数据库就可以一次性完成。
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q, Prefetch
from django.db import connection
def index7(requeset):
prefetch = Prefetch("bookorder_set", queryset=BookOrder.objects.filter(price__gte=90))
books = Book.objects.prefetch_related(prefetch)
for book in books:
print(book.name)
orders = book.bookorder_set.all()
for order in orders:
print(order.id)
print(connection.queries)
return HttpResponse("success!")
三国演义
8
9
水浒传
7
西游记
2
红楼梦
5
6
[{'sql': "\n SELECT VERSION(),\n @@sql_mode,\n @@default_storage_engine,\n @@sql_auto_is_null,\n @@lower_case_table_names,\n CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL\n ", 'time': '0.001'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.001'}, {'sql': 'SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book`', 'time': '0.001'}, {'sql': 'SELECT `book_order`.`id`, `book_order`.`book_id`, `book_order`.`price`, `book_order`.`create_time` FROM `book_order` WHERE (`book_order`.`price` >= 90.0e0 AND `book_order`.`book_id` IN (3, 4, 5, 6))', 'time': '0.003'}]
defer
在操作表时,不是表中的所有字段都会用上,到全部都查询出来会有不必要的资源损耗。这时就可以通过defer来过滤掉一些字段。defer跟上面的values有点儿类似,但defer返回的是模型,而values返回的是字典。id字段不能被过滤。
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q, Prefetch
from django.db import connection
def index8(requeset):
books = Book.objects.defer("price", "rating")
for book in books:
print(book.id)
print(connection.queries)
return HttpResponse("success!")
3
4
5
6
[{'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.001'}, {'sql': 'SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`author_id`, `book`.`publisher_id` FROM `book`', 'time': '0.002'}]
only
only跟defer类似,区别是defer过滤指定的字段,而only只获取指定的字段。
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q, Prefetch
from django.db import connection
def index8(requeset):
books = Book.objects.only("name", "price", "rating")
for book in books:
print(book.id)
print(connection.queries)
return HttpResponse("success!")
3
4
5
6
[{'sql': "\n SELECT VERSION(),\n @@sql_mode,\n @@default_storage_engine,\n @@sql_auto_is_null,\n @@lower_case_table_names,\n CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL\n ", 'time': '0.001'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.001'}, {'sql': 'SELECT `book`.`id`, `book`.`name`, `book`.`price`, `book`.`rating` FROM `book`', 'time': '0.001'}]
get方法
获取满足条件的数据。这个方法只能返回一条数据。如果给的条件有多条数据,那么这个方法会抛出异常MultipleObjectsReturned;如果给的条件没有任何数据,那么会抛出异常DoesNotExit。
from django.http import HttpResponse
from .models import Book, BookOrder, Author
from django.db.models import Avg, Count, Max, Min, Sum, F, Q, Prefetch
def index9(requeset):
book = Book.objects.get(pk=3)
print(book)
return HttpResponse("success!")
<Publisher(id:3, name:三国演义, price:108.0, rating:4.91, author:<Author(id:3, name:罗贯中, age:36, email:lgz@plscript.cn)>, publisher:<Publisher(id:1, name:中国邮电出版社)>)>
create方法
创建一条数据,并且保存到数据库中。这个方法相当于用指定的模型创建一个对象后,在调用这个对象的save方法。
publisher = Publisher(name="PlScript出版社")
publisher.save()
Publisher.objects.create(name="PlScript出版社")
get_or_create
根据某个条件进行查找,如果找到了就返回这条数据。如果没有查找到,就创建一个。
def index11(requeset):
result = Publisher.objects.get_or_create(name="PlScript出版社")
print(result)
return HttpResponse("success!")
第一次执行的结果
(<Publisher: <Publisher(id:3, name:PlScript出版社)>>, True)
第二次执行的结果:
(<Publisher: <Publisher(id:3, name:PlScript出版社)>>, False)
结果中的True和False表示是否创建了数据。
bulk_create
一次性创建多个数据。
from django.http import HttpResponse
from .models import Book, BookOrder, Author, Publisher
from django.db.models import Avg, Count, Max, Min, Sum, F, Q, Prefetch
def index11(requeset):
# result = Publisher.objects.get_or_create(name="PlScript出版社")
# print(result)
Publisher.objects.bulk_create([
Publisher(name="Laobai出版社"),
Publisher(name="AILynn出版社")
])
return HttpResponse("success!")
conut
获取的数据的个数。如果想知道总共有多少数据,建议使用count,底层是使用select count(*)
来实现的。
def index12(requeset):
count = Book.objects.count()
print(count)
print(connection.queries)
return HttpResponse("success!")
exists
判断某个条件的数据是否存在。如果要判断某个条件的元素是否存在,那么建议使用exists,这个比使用count或判断QuerySet更有效得多。
def index12(requeset):
if Book.objects.filter(name__contains="传").exists():
print(True)
return HttpResponse("success!")
first和last
返回QuerySet中的第一条和最后一条数据。
book1 = Book.objects.first()
book2 = Book.objects.last()
distinct
去除查询结果中的重复数据。
def index12(requeset):
orders = BookOrder.objects.values("book_id").distinct()
print(orders)
return HttpResponse("success!")
<QuerySet [{'book_id': 3}, {'book_id': 4}, {'book_id': 5}, {'book_id': 6}]>
def index12(requeset):
books = Book.objects.filter(bookorder__price__gte=80).distinct()
for book in books:
print(book)
return HttpResponse("success!")
<Publisher(id:5, name:西游记, price:88.0, rating:4.85, author:<Author(id:2, name:吴承恩, age:28, email:wce@plscript.cn)>, publisher:<Publisher(id:2, name:清华大学出版社)>)>
<Publisher(id:6, name:红楼梦, price:99.0, rating:4.9, author:<Author(id:1, name:曹雪芹, age:35, email:cxq@plscript.cn)>, publisher:<Publisher(id:2, name:清华大学出版社)>)>
<Publisher(id:4, name:水浒传, price:101.0, rating:4.83, author:<Author(id:4, name:施耐庵, age:46, email:sna@plscript.cn)>, publisher:<Publisher(id:1, name:中国邮电出版社)>)>
<Publisher(id:3, name:三国演义, price:108.0, rating:4.91, author:<Author(id:3, name:罗贯中, age:36, email:lgz@plscript.cn)>, publisher:<Publisher(id:1, name:中国邮电出版社)>)>
update
执行更新操作
def index12(requeset):
Book.objects.update(price=F("price")+5)
return HttpResponse("success!")
其中
Book.objects.update(price=F("price")+5)
等效于
books = Book.objects.all()
for book in books:
book.price = book.price + 5
book.save()
delete
删除所有满足条件的数据。删除数据的时候,要注意on_delete
指定的处理方式。
def index12(requeset):
Publisher.objects.filter(id__gte=4).delete()
return HttpResponse("success!")
切片操作
在查找数据时,有可能只需要其中的一部分,可以使用切片来操作。QuerySet切片操作与列表一样。切片操作不是把所有数据都从数据库中取出来再做切片,而是在数据库层面使用LIMIE
和OFFSET
来完成的。
def index12(requeset):
books = Book.objects.all()[1:3]
for book in books:
print(book)
return HttpResponse("success!")