ORM对数据库的基本操作:
添加数据:
只要使用ORM
模型创建一个对象。然后再调用这个ORM模型的save
方法就可以保存了。
示例代码如下:
book = Book(name='西游记',author='吴承恩',price=100)
book.save()
查找数据:
所有的查找工作都是使用模型上的objects
属性来完成的。当然也可以自定义查询对象。这部分功能会在后面讲到。
- 根据主键进行查找:使用主键进行查找。可以使用
objects.get
方法。然后传递pk=xx
的方式进行查找。示例代码如下:book = Book.objects.get(pk=2)
- 根据其他字段进行查找:可以使用
objects.filter
方法进行查找。示例代码如下:
使用books = Book.objects.filter(name='三国演义')
filter
方法返回来的是一个QuerySet
对象。这个对象类似于列表。我们可以使用这个对象的first
方法来获取第一个值。
删除数据:
首先查找到对应的数据模型。然后再执行这个模型的delete
方法即可删除。示例代码如下:
book = Book.objects.get(pk=1)
book.delete()
修改数据:
首先查找到对应的数据模型。然后修改这个模型上的属性的值。再执行save
方法即可修改完成。示例代码如下:
book = Book.objects.get(pk=2)
book.price = 200
book.save()
聚合函数:
-
所有的聚合函数都是放在
django.db.models
下面。 -
聚合函数不能够单独的执行,需要放在一些可以执行聚合函数的方法下面中去执行。比如
aggregate
。示例代码如下:result = Book.objects.aggregate(Avg("price"))
-
聚合函数执行完成后,给这个聚合函数的值取个名字。取名字的规则,默认是
filed+__+聚合函数名字
形成的。比如以上代码形成的名字叫做price__avg
。如果不想使用默认的名字,那么可以在使用聚合函数的时候传递关键字参数进去,参数的名字就是聚合函数执行完成的名字。实示例代码如下:result = Book.objects.aggregate(avg=Avg("price"))
以上传递了关键字参数
avg=Avg("price")
,那么以后Avg
聚合函数执行完成的名字就叫做avg
。 -
aggregate
:这个方法不会返回一个QuerySet
对象,而是返回一个字典。这个字典中的key就是聚合函数的名字,值就是聚合函数执行后的结果。 -
aggregate
和annotate
的相同和不同:- 相同:这两个方法都可以执行聚合函数。
- 不同:
aggregate
返回的是一个字典,在这个字典中存储的是这个聚合函数执行的结果。而annotate
返回的是一个QuerySet
对象,并且会在查找的模型上添加一个聚合函数的属性。aggregate
不会做分组,而annotate
会使用group by
子句进行分组,只有调用了group by
子句,才能对每一条数据求聚合函数的值。
-
Count
:用来求某个数据的个数。比如要求所有图书的数量,那么可以使用以下代码:result = Book.objects.aggregate(book_nums=Count("id"))
并且
Count
可以传递distinct=True
参数,用来剔除那些重复的值,只保留一个。比如要获取作者表中,不同邮箱的个数,那么这时候可以使用distinct=True
。示例代码如下:result = Author.objects.aggregate(email_nums=Count('email',distinct=True))
-
Max
和Min
:求指定字段的最大值和最小值。示例代码如下:result = Author.objects.aggregate(max=Max("age"),min=Min("age"))
-
Sum
:求某个字段值的总和。示例代码如下:result = BookOrder.objects.aggregate(total=Sum('price'))
aggregate
和annotate
方法可以在任何的QuerySet
对象上调用。因此只要是返回了QuerySet
对象,那么就可以进行链式调用。比如要获取2018年度的销售总额,那么可以先过滤年份,再求聚合函数。示例代码如下:BookOrder.objects.filter(create_time__year=2018).aggregate(total=Sum('price'))
-
F表达式
: 动态的获取某个字段上的值。并且这个F表达式,不会真正的去数据库中查询数据,他相当于只是起一个标识的作用。比如想要将原来每本图书的价格都在原来的基础之上增加10元,那么可以使用以下代码来实现:from django.db.models import F Book.objects.update(price=F("price")+10)
-
Q表达式
:使用Q
表达式包裹查询条件,可以在条件之间进行多种操作。与/或非等,从而实现一些复杂的查询操作。例子如下:- 查找价格大于100,并且评分达到4.85以上的图书:
# 不使用Q表达式的 books = Book.objects.filter(price__gte=100,rating__gte=4.85) # 使用Q表达式的 books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
- 查找价格低于100元,或者评分低于4分的图书:
books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
- 获取价格大于100,并且图书名字中不包含”传“字的图书:
books = Book.objects.filter(Q(price__gte=100)&~Q(name__icontains='传'))
- 查找价格大于100,并且评分达到4.85以上的图书:
常用Field:
navie时间和aware时间:
什么是navie时间?什么是aware时间?
- navie时间:不知道自己的时间表示的是哪个时区的。也就是不知道自己几斤几两。比较幼稚。
- aware时间:知道自己的时间表示的是哪个时区的。也就是比较清醒。
pytz库:
专门用来处理时区的库。这个库会经常更新一些时区的数据,不需要我们担心。并且这个库在安装Django的时候会默认的安装。如果没有安装,那么可以通过pip install pytz
的方式进行安装。
astimezone方法:
将一个时区的时间转换为另外一个时区的时间。这个方法只能被aware
类型的时间调用。不能被navie
类型的时间调用。
示例代码如下:
import pytz
from datetime import datetime
now = datetime.now() # 这是一个navie类型的时间
utc_timezone = pytz.timezone("UTC") # 定义UTC的时区对象
utc_now = now.astimezone(utc_timezone) # 将当前的时间转换为UTC时区的时间
>> ValueError: astimezone() cannot be applied to a naive datetime # 会抛出一个异常,原因就是因为navie类型的时间不能调用astimezone方法
now = now.replace(tzinfo=pytz.timezone('Asia/Shanghai'))
utc_now = now.astimezone(utc_timezone)
# 这时候就可以正确的转换。
replace方法:
可以将一个时间的某些属性进行更改。
django.utils.timezone.now方法:
会根据settings.py
中是否设置了USE_TZ=True
获取当前的时间。如果设置了,那么就获取一个aware
类型的UTC
时间。如果没有设置,那么就会获取一个navie
类型的时间。
django.utils.timezone.localtime方法:
会根据setting.py
中的TIME_ZONE
来将一个aware
类型的时间转换为TIME_ZONE
指定时区的时间。
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
类型。
navie和aware介绍以及在django中的用法:
https://docs.djangoproject.com/en/2.0/topics/i18n/timezones/
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:
在表中这个字段的值是否唯一。一般是设置手机号码/邮箱等。
更多Field
参数请参考官方文档:https://docs.djangoproject.com/zh-hans/2.0/ref/models/fields/
模型中Meta
配置:
对于一些模型级别的配置。我们可以在模型中定义一个类,叫做Meta
。然后在这个类中添加一些类属性来控制模型的作用。比如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称。那么我们可以在Meta
类中添加一个db_table
的属性。示例代码如下:
class Book(models.Model):
name = models.CharField(max_length=20,null=False)
desc = models.CharField(max_length=100,name='description',db_column="description1")
class Meta:
db_table = 'book_model'
以下将对Meta
类中的一些常用配置进行解释。
db_table:
这个模型映射到数据库中的表名。如果没有指定这个参数,那么在映射的时候将会使用模型名来作为默认的表名。
ordering:
设置在提取数据的排序方式。后面章节会讲到如何查找数据。比如我想在查找数据的时候根据添加的时间排序,那么示例代码如下:
class Book(models.Model):
name = models.CharField(max_length=20,null=False)
desc = models.CharField(max_length=100,name='description',db_column="description1")
pub_date = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'book_model'
ordering = ['pub_date']
更多的配置后面会慢慢介绍到。
官方文档:https://docs.djangoproject.com/en/2.0/ref/models/options/
查询条件:
-
exact:在底层会被翻译成
=
。 -
iexact:在底层会被翻译成
LIKE
。- LIKE和=:大部分情况下都是等价的,只有少数情况下是不等价的。
- exict和iexact:他们的区别其实就是LIKE和=的区别,因为exact会被翻译成=,而iexact会被翻译成LIKE。
- 因为
field__exact=xxx
其实等价于filed=xxx
,因此我们直接使用filed=xxx
就可以了,并且因为大部分情况exact
和iexact
又是等价的,因此我们以后直接使用field=xxx
就可以了。
-
QuerySet.query:
query
可以用来查看这个ORM
查询语句最终被翻译成的SQL
语句。但是query
只能被用在QuerySet
对象上,不能用在普通的ORM模型
上。因此如果你的查询语句是通过get
来获取数据的,那么就不能使用query
,因为get
返回的是满足条件的ORM
模型,而不是QuerySet
。如果你是通过filter
等其他返回QuerySet
的方法查询的,那么就可以使用query
。 -
contains:使用大小写敏感的判断,某个字符串是否在指定的字段中。这个判断条件会使用大小敏感,因此在被翻译成
SQL
语句的时候,会使用like binary
,而like binary
就是使用大小写敏感的。 -
icontains:使用大小写不敏感的判断,某个字符串是否被包含在指定的字段中。这个查询语句在被翻译成
SQL
的时候,使用的是like
,而like
在MySQL
层面就是不区分大小写的。 -
contains和icontains:在被翻译成
SQL
的时候使用的是%hello%
,就是只要整个字符串中出现了hello
都能过够被找到,而iexact
没有百分号,那么意味着只有完全相等的时候才会被匹配到。 -
in:可以直接指定某个字段的是否在某个集合中。示例代码如下:
articles = Article.objects.filter(id__in=[1,2,3])
也可以通过其他的表的字段来判断是否在某个集合中。示例代码如下:
categories = Category.objects.filter(article__id__in=[1,2,3])
如果要判断相关联的表的字段,那么也是通过
__
来连接。并且在做关联查询的时候,不需要写models_set
,直接使用模型的名字的小写化
就可以了。比如通过分类去查找相应的文章,那么通过article__id__in
就可以了,而不是写成article_set__id__in
的形式。当然如果不想使用默认的形式,可以在外键定义的时候传递related_query_name
来指定反向查询的名字。示例代码如下:class Category(models.Model): name = models.CharField(max_length=100) class Meta: db_table = 'category' class Article(models.Model): title = models.CharField(max_length=200) content = models.TextField() cateogry = models.ForeignKey("Category",on_delete=models.CASCADE,null=True,related_query_name='articles') class Meta: db_table = 'article'
因为在
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的所有分类”,那么可以通过以下代码来实现:articles = Article.objects.filter(title__icontains='hello') categories = Category.objects.filter(articles__in=articles) for cateogry in categories: print(cateogry)
-
gt
、gte
、lt
、lte
:代表的是大于、大于等于、小于、小于等于的条件。示例代码如下:articles = Article.objects.filter(id__lte=3)
9.startswith
、istartswith
、endswith
、iendswith
:表示以某个值开始,不区分大小写的以某个值开始、以某个值结束、不区分大小写的以某个值结束。示例代码如下:
articles = Article.objects.filter(title__endswith="hello")
- 关于时间的查询条件:
range
:可以指定一个时间段。并且时间应该标记为aware
时间,不然django
会报警告。示例代码如下:
start_time = make_aware(datetime(year=2018,month=4,day=4,hour=17,minute=0,second=0))
end_time = make_aware(datetime(year=2018,month=4,day=4,hour=18,minute=0,second=0))
articles = Article.objects.filter(create_time__range=(start_time,end_time))
print(articles.query)
print(articles)
date
:用年月日来进行过滤。如果想要使用这个过滤条件,那么前提必须要在MySQL
中添加好那些时区文件。如何添加呢?参考教案。示例代码如下:articles = Article.objects.filter(create_time__date=datetime(year=2018,month=4,day=4))
year/month/day
:表示根据年/月/日进行查找。示例代码如下:
articles = Article.objects.filter(create_time__year__gte=2018)
*week_day
:根据星期来进行查找。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。比如要查找星期三的所有文章,那么可以通过以下代码来实现:
articles = Article.objects.filter(create_time__week_day=4)
time
:根据分时秒来进行查找。如果要具体到秒,一般比较难匹配到,可以使用区间的方式来进行查找。区间使用range
条件。比如想要获取17
时/10分/27-28秒之间的文章,那么可以通过以下代码来实现:
start_time = time(hour=17,minute=10,second=27)
end_time = time(hour=17,minute=10,second=28)
articles = Article.objects.filter(create_time__time__range=(start_time,end_time))
migrate怎么判断哪些迁移脚本需要执行:
他会将代码中的迁移脚本和数据库中django_migrations
中的迁移脚本进行对比,如果发现数据库中,没有这个迁移脚本,那么就会执行这个迁移脚本。
migrate做了什么事情:
- 将相关的迁移脚本翻译成
SQL
语句,在数据库中执行这个SQL
语句。 - 如果这个SQL语句执行没有问题,那么就会将这个迁移脚本的名字记录到
django_migrations
中。
执行migrate命令的时候报错的解决办法:
原因:
执行migrate
命令会报错的原因是。数据库的django_migrations
表中的迁移版本记录和代码中的迁移脚本不一致导致的。
解决办法:
使用–fake参数:
首先对比数据库中的迁移脚本和代码中的迁移脚本。然后找到哪个不同,之后再使用--fake
,将代码中的迁移脚本添加到django_migrations
中,但是并不会执行sql
语句。这样就可以避免每次执行migrate
的时候,都执行一些重复的迁移脚本。
终极解决方案:
如果代码中的迁移脚本和数据库中的迁移脚本实在太多,就是搞不清了。那么这时候就可以使用以下终极解决方案:
- 终极解决方案原理:就是将之前的那些迁移脚本都不用了。重新来过。要将出问题的app下的所有模型和数据库中表保持一致,重新映射。
- 将出问题的app下的所有模型,都和数据库中的表保持一致。
- 将出问题的app下的所有迁移脚本文件都删掉。再在
django_migrations
表中将出问题的app相关的迁移记录都删掉。 - 使用
makemigrations
,重新将模型生成一个迁移脚本。 - 使用
migrate --fake-initial
参数,将刚刚生成的迁移脚本,标记为已经完成(因为这些模型相对应的表,其实都已经在数据库中存在了,不需要重复执行了。) - 可以做其他的映射了。
QuerySet API:
模型.objects:
这个对象是django.db.models.manager.Manager
的对象,这个类是一个空壳类,它上面的所有方法都是从QuerySet
这个类上面拷贝过来的。因此我们只要学会了QuerySet
,这个objects
也就知道该如何使用了。
Manager
源码解析:
class_name = "BaseManagerFromQuerySet"
class_dict = {
'_queryset_class': QuerySet
}
class_dict.update(cls._get_queryset_methods(QuerySet))
# type动态的时候创建类
# 第一个参数是用来指定创建的类的名字。创建的类名是:BaseManagerFromQuerySet
# 第二个参数是用来指定这个类的父类。
# 第三个参数是用来指定这个类的一些属性和方法
return type(class_name,(cls,),class_dict)
_get_queryset_methods:这个方法就是将QuerySet中的一些方法拷贝出来
filter/exclude/annotate
:过滤/排除满足条件的/给模型添加新的字段。
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
,会把前面排序的规则给打乱,而使用后面的排序方式。比如以下代码:
articles = Article.objects.order_by("create_time").order_by("author__name")
它会根据作者的名字进行排序,而不是使用文章的创建时间。
当然,也可以在模型定义的在Meta
类中定义ordering
来指定默认的排序方式。示例代码如下:
class Meta:
db_table = 'book_order'
ordering = ['create_time','-price']
还可以根据annotate
定义的字段进行排序。比如要实现图书的销量进行排序,那么示例代码如下:
books = Book.objects.annotate(order_nums=Count("bookorder")).order_by("-order_nums")
for book in books:
print('%s/%s'%(book.name,book.order_nums))
表关系笔记:
一对多:
- 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。
- 实现方式:一对多或者多对一,都是通过
ForeignKey
来实现的。还是以文章和作者的案例进行讲解。
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='zhiliao',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)
并且如果想要将文章添加到某个分类中。可以使用一下的方式:
category = Category.objects.first()
article = Article(title='bbb',content='vvv')
article.author = FrontUser.objects.first()
category.article_set.add(article,bulk=False)
- 使用
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)
,来实现一对一。 - 以后如果想要反向引用,那么是通过引用的模型的名字转换为小写的形式进行访问。比如以下模型:
class FrontUser(models.Model): username = models.CharField(max_length=200) class UserExtension(models.Model): school = models.CharField(max_length=100) user = models.OneToOneField("FrontUser",on_delete=models.CASCADE) # 通过userextension来访问UserExtension对象 user = FrontUser.objects.first() print(user.userextension)
UserExtension
的对象,可以通过user
来访问到对应的user对象。并且FrontUser
对象可以使用userextension
来访问对应的UserExtension
对象。
如果不想使用Django
默认的引用属性名字。那么可以在OneToOneField
中添加一个related_name
参数。示例代码如下:
class FrontUser(models.Model):
username = models.CharField(max_length=200)
class UserExtension(models.Model):
school = models.CharField(max_length=100)
user = models.OneToOneField("FrontUser",on_delete=models.CASCADE,related_name='extension')
# 通过extension来访问到UserExtension对象
user = FrontUser.objects.first()
print(user.extension)
那么以后就FrontUser
的对象就可以通过extension
属性来访问到对应的UserExtension
对象。
多对多:
-
应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。
-
实现方式:
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
两张表的主键。