在django APP目录下的models.py文件中编写数据模型类
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(default = 0, null = False)
class Publisher(models.Model):
'''
如果没有写id字段,django会自动生成id字段
'''
name = models.CharField(max_length=100, null = False)
address = models.CharField(max_length=100, null=True)
数据库迁移前请确保APP已在settings.py文件中已注册
这里使用mysql来保存数据,settings.py文件中的配置如下:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'dingdingtable',
'USER': 'root',
'PASSWORD': 'root',
'HOST': 'localhost',
'PORT': 3306,
"OPTIONS":{"init_command":"SET default_storage_engine=INNODB;"} # 此句代码为mysql5.7的写法
}
}
数据模型类创建好后,在命令行生迁移脚本文件及映射到数据库中
python manage.py makemigrations # 生成迁移脚本文件
python manage.py migrate # 将新生成的迁移脚本文件映射到数据库中
django默认会将 APP名.类名 的形式作为数据库中的表名
def index(request):
book = Book(name='三国演义', author='罗贯中', price=200)
book.save()
return HttpResponse('create success')
1:根据主键查询
def index(request):
book = Book.objects.get(pk=1) # pk根据主键查询(不管主键是id,还是bid)
print(book)
return HttpResponse('create success')
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(default = 0, null = False)
def __str__(self):
return '{class_name}:({name},{author},{price})'.format(class_name=__class__, name=self.name, author=self.author, price=self.price)
添加完成后,再次打印 book时,输出如下:
<class 'book.models.Book'>:(三国演义,罗贯中,200.0)
2:根据其他字段查询
book = Book.objects.filter(name='西游记')
# :(西游记,吴承恩,158.0)>]>
book = Book.objects.filter(name='西游记').first() # 使用first返回一条数据
# :(西游记,吴承恩,158.0)
def index(request):
book = Book.objects.get(pk=1)
book.delete()
return HttpResponse('delete success')
def index(request):
book = Book.objects.get(pk=2)
book.price = 200
book.save()
return HttpResponse('modify success')
在 Django 中,定义了一些 Field 来与数据库表中的字段类型来进行映射。
AutoField
映射到数据库中是 int 类型,可以有自动增长的特性。一般不需要使用这个类型,如果不指定主键,那么模型会自动的生成一个叫做 id 的自动增长的主键。如果你想指定一个其他名字的并且具有自动增长的主键,使用 AutoField也是可以的。
BigAutoField
64位的整形,类似于AutoField,只不过是产生的数据的范围是从 1-9223372036854775807。
BooleanField
在模型层面接收的是 True/False。在数据库层面是tinyint类型。如果没有指定默认值,默认值是 None 。
如果想使用可以为null的BooleanField,那么应用使用NullBooleanField
CharField
在数据库层面是 varchar 类型。在 Python 层面就是普通的字符串。这个类型在使用的时候必须要指定最大的长度,也即必须要传递 max_length 这个关键字参数进去。
EmailField
类似于CharField 。在数据库底层也是一个 varchar 类型。最大长度是254个字符。如果没有指定max_length,默认使用254个字符为最大长度。并不会限制字符串一定要满足邮箱格式
FloatField
浮点类型。映射到数据库中是 float 类型。
IntegerField
整形。值的区间是 -2147483648——2147483647。
TextField
大量的文本类型。映射到数据库中是longtext类型。
URLField
类似于 CharField ,只不过只能用来存储 url 格式的字符串。并且默认的max_length 是200。注意在表单验证的时候,会判断提交的字符串是否是一个URL格式类型。就是以http,https等开头的格式
DateField
日期类型。在python中是datetime.date类型,可以记录年月日。在映射数据库也是date类型。使用这个Field可以传递以几个参数:
DateTimeField
日期时间类开,类亿于DateField。不仅仅存储日期,还可以存储时间。映射到数据库中是datetime类型。这个Field也可以使用auto_now and auto_now_add两个属性
TimeField
时间类型。在数据库中是time类型。在python中是datetime.time类型
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 类中添加一个 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")
class Meta:
db_table = 'book_model'
ordering = ['pub_date']
在 MySQL 中,表有两种引擎,一种是 InnoDB,另外一种是myisam。如果使用的是 InnoDB 引擎,是支持外键约束的。
类定义为 class ForeignKey(to,on_delete,**options)
第一个参数是引用的是哪个模型(也就是哪一张表)
第二个参数是在使用外键引用的模型数据被删除了,这个字段该如何处理。
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) # 映射到数据库中字段为:category_id
# 如果是不同的app数据表关联,使用 app_name.class_name
# 如果关联的自己,那么使用 self 或者模型类名称
创建数据:
def index(request):
category = Category(name='最新文件')
category.save() # Category表必须先有数据,不然Article表save时会报错
article = Article(title = 'abc', content = '111')
article.category = category
article.save()
return HttpResponse('create success')
查询数据:
article = Article.objects.first()
print(article.category.name) # 可获取category表中对应的数据
Django2.0以上版本ForeignKey需添加新参数on_delete,on_delete指的是通过ForeignKey连接起来的对象被删除后,当前字段怎么变化。
常见的选项有:
models.CASCADE:级联删除,如果外键对应的那条数据被删除,那么所有关联该字段的数据都会被删除
models.PROTECT:受保护(删除时会引起ProtectedError)。只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据
models.SET_NULL:设置为空。如果外键的那条数据被删除了,那么在本条数据上将该字段设置为空。设置为该选项的前提是要指定该字段可以为空
models.SET_DEFAULT:设置为默认值。如果外键的那条数据被删除了,那么在本条数据上将该字段设置为默认值。设置为该选项的前提是要指定该字段的默认值
moels.SET:如果外键的那条数据被删除,那么将会获取set函数中的值来作为这个外键值。set函数可以接收一个可以调用的对象(函数或方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去
models.DO_NOTHING:什么也不做,一切看数据库级别的约束。
上面6.1节的模型类就是一对多
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) # 映射到数据库中字段为:category_id
创建Article数据时关联到Category表数据:
def index(request):
article = Article(title = '钢铁是怎样练成的', content = 'tonight, I fell close to you')
category = Category.objects.first()
article.category = category
article.save()
return HttpResponse('create success')
通过一端(Category)获取对应的多端(Article)数据:
category = Category.objects.first()
articles = category.article_set.all()
# Category表被Article表关联,此时Category表可以通过 关联模型类的小写_set 获取数据
print(articles)
如果不想使用Django内置的关联模型类的小写_set方法获取数据,可以在Article中关联的字段中添加related_name属性,如:
category = models.ForeignKey("Category", on_delete=models.CASCADE, related_name = 'articles')
# 如果添加了related_name属性,那么 article_set 方法就没有了
查询时通过设置的related_name的值来查询多端对应的数据:
category = Category.objects.first()
articles = category.articles.all()
print(articles)
通过一端保存多端的数据:
def index(request):
article = Article(title = '111', content = '222')
category = Category.objects.get(pk = 1)
category.articles.add(article, bulk = False) # 可以是多条数据;bulk默认为True,为False时会调用save()方法
return HttpResponse('create success')
class User(models.Model):
username = models.CharField(max_length=30)
class UserExtension(models.Model):
school = models.CharField(max_length=30)
user = models.OneToOneField('User', on_delete=models.CASCADE)
创建数据:
def index(request):
user = User.objects.first()
extension = UserExtension(school = 'tsinghua')
extension.user = user
extension.save()
return HttpResponse('create success')
两张表相互查询:
extension = UserExtension.objects.get(pk = 1) # 关联关系写在该
print(extension.user)
user = User.objects.get(pk = 1)
print(user.userextension) # 注意这里不是userextension_set,而是直接使用类名的小写来反查询
如果不想使用Django内置的方法来查询,同样可以模型类中添加related_name属性
class UserExtension(models.Model):
school = models.CharField(max_length=30)
user = models.OneToOneField('User', on_delete=models.CASCADE, related_name='extension')
设置了related_name属性,就只能通过related_name的值来做查询:
extension = UserExtension.objects.get(pk = 1)
print(extension.user)
user = User.objects.get(pk = 1)
print(user.extension)
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
category = models.ForeignKey("Category", on_delete=models.CASCADE, related_name = 'articles')
def __str__(self):
return '%s: (%s,%s)' % (__class__, self.id, self.title)
class Tag(models.Model):
name = models.CharField(max_length=100)
articles = models.ManyToManyField('Article')
article = Article.objects.get(pk = 1)
tag = Tag(name = 'hot news')
tag.save()
article.tag_set.add(tag) # 多对多中的add()方法没有bulk参数,所以必须先tag.save()
tag = Tag.objects.get(pk = 1)
article = Article.objects.get(pk = 3)
tag.articles.add(article) # add()方法内部已调用save()方法
如果不想使用tag_set内置方法查询数据,同样可以给模型类加related_name属性
class Tag(models.Model):
name = models.CharField(max_length=100)
articles = models.ManyToManyField('Article', related_name = 'tags')
article = Article.objects.get(pk = 1)
tags = article.tags.all()
print(tags)
精确的提供条件。如果提供的是一个None,那么在SQL层面会被解释为Null。
article = Article.objects.get(id__exact = 2) # __exact可以省略
article = Article.objects.filter(id__exact = 2)
article = Article.objects.get(title__exact = None) # select * from article where title is NULL;
article = Article.objects.filter(title__exact = 'abc')
# 可以通过print(article.query)打印对应的SQL语句(只能在queryset对象上使用)
# 如果使用的是get方法,打印时会报错,因为get返回的是ORM模型,而不是queryset
在Linux系统上,如果数据库的排序规则是utf8_bin,那么大小写敏感;如果是utf8_general_ci则大小写不敏感
使用like进行查询
article = Article.objects.filter(title__iexact = 'abc')
print(article.query)
# SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE abc
大小写敏感,判断某个字段是否包含了某个数据
article = Article.objects.filter(title__contains='Ab')
article = Article.objects.filter(title__icontains='ab')
SQL语句:
# contains
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE BINARY %Ab%
# icontains
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE %Ab%
判断某个字段值是否以某个值开始的。大小敏感,如果想大小写不敏感,使用istartswith
如果想判断某个字段值是否以某个值结尾,使用endswith,大小写敏感;iendswith大小写不敏感
article = Article.objects.filter(title__startswith='ab')
article = Article.objects.filter(title__istartswith='ab')
被翻译成的SQL语句:
# startswith
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE BINARY ab%
# istartwith
SELECT `article`.`id`, `article`.`title`, `article`.`content` FROM `article` WHERE `article`.`title` LIKE ab%
判断某个field的值是否在给定的区间中
首先给模型类添加时间类型的字段:
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
create_time = models.DateTimeField(auto_now_add = True, null = True)
def __str__(self):
return '%s: (%s,%s)' % (__class__, self.id, self.title)
class Meta:
db_table = 'article'
在settings配置文件中设置时区:
TIME_ZONE = 'Asia/Shanghai'
# USE_TZ=False django获取到的当前时间就是一个navie类型的时间
USE_TZ = True
数据库迁移后Article表中的create_time时间为UTC时间(比北京时间晚8个小时)
start_time = datetime(year=2019, month=10, day=20, hour = 8, minute=0, second = 0)
end_time = datetime(year=2019, month=10, day=20, hour = 12, minute=0, second = 0)
# 上面的start_time, end_time按北京时间写的
articles = Article.objects.filter(create_time__range=(start_time, end_time))
print(articles.query)
翻译成的SQL语句:
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE `article`.`create_time` BETWEEN 2019-10-20 00:00:00 AND 2019-10-20 04:00:00
数据可以正确查询出来,但通过翻译成的SQL语句看到后面的查询时间并非北京时间,而是被转换成了UTC时间,且Django还出现了一个警告:
RuntimeWarning: DateTimeField Article.create_time received a naive datetime (2019-10-20 12:00:00) while time zone support is active.
可以通过from django.utils.timezone import make_aware方法将navie时间转换为aware时间
start_time = make_aware(datetime(year=2019, month=10, day=20, hour = 8, minute=0, second = 0))
end_time = make_aware(datetime(year=2019, month=10, day=20, hour = 12, minute=0, second = 0))
make_aware方法内部会获取settings配置文件中设置的时区,即TIME_ZONE = ‘Asia/Shanghai’
针对某些date或者datetime类型的字段。可以指定date的范围。并且这个时间过滤,还可以使用链式调用
articles = Article.objects.filter(create_time__date = datetime(year = 2019, month = 10, day = 20)) # datetime方法中如果只给定年月日参数,那么就相当于datetime.date方法
print(articles.query)
# SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE DATE(CONVERT_TZ(`article`.`create_time`, 'UTC', 'Asia/Shanghai')) = 2019-10-20
上面的ORM查询语句是按北京时间写的,但发现结果查询不到数据。
DATE(CONVERT_TZ(article
.create_time
, ‘UTC’, ‘Asia/Shanghai’)):将UTC时间转换为本地时间(北京时间)
没有查询出正确的结果是因为默认情况下Mysql表中并没有存储时区相关的信息,因此我们需要下载一些时区表的文件,然后添加到Mysql的配置文件中
widows系统下载地址:https://dev.mysql.com/downloads/timezones.html,下载如下文件(我使用的mysql为5.7版本):
解压后会得到一个sql文件:timezone_posix.sql,将该sql文件导入到数据库中的mysql库,然后重启mysql服务。正常的话,上面的SQL语句即可正确查询出结果。
Linux或者mac系统操作同上。
year:根据年份进行查询
month:根据月份进行查询
day:根据日期进行查询
week_day:Django1.11新增的查询方式。根据星期几进行查询。1表示星期天,7表示星期六
time:根据时间进行查询
articles = Article.objects.filter(create_time__year = 2019)
articles = Article.objects.filter(create_time__year__gte = 2019) # 年份大于等于2019的
articles = Article.objects.filter(create_time__week_day=1)
被翻译成的SQL语句:
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE `article`.`create_time` BETWEEN 2018-12-31 16:00:00 AND 2019-12-31 15:59:59.999999
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE `article`.`create_time` >= 2018-12-31 16:00:00 # 这里为什么是16点??
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE DAYOFWEEK(CONVERT_TZ(`article`.`create_time`, 'UTC', 'Asia/Shanghai')) = 1
上表中的案例,如果要精确到秒,最好使用区间来查询:
start_time = datetime.time(hour = 10, minute = 22, second=22)
end_time = datetime.time(hour = 10, minute = 22, second=25)
articles = Article.objects.filter(create_time__time__range = (start_time, end_time))
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE TIME(CONVERT_TZ(`article`.`create_time`, 'UTC', 'Asia/Shanghai')) BETWEEN 10:22:22 AND 10:22:25
articles = Article.objects.filter(create_time__isnull = True)
SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE `article`.`create_time` IS NULL
articles = Article.objects.filter(title__regex=r'^A')
# SELECT `article`.`id`, `article`.`title`, `article`.`content`, `article`.`create_time` FROM `article` WHERE `article`.`title` REGEXP BINARY ^A
regex区分大小写,如果不区分大小写使用iregex
数据模型类:
class Author(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField()
email = models.EmailField()
class Meta:
db_table = 'author'
class Publisher(models.Model):
name = models.CharField(max_length=50)
class Meta:
db_table = 'publisher'
class Book(models.Model):
name = models.CharField(max_length=20)
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)
class Meta:
db_table = 'book'
class BookOrder(models.Model):
book = models.ForeignKey('Book', on_delete=models.CASCADE)
price = models.FloatField()
class Meta:
db_table = 'order'
Avg:求平均值
获取所有图书的价格平均值:
from django.db.models import Avg # 所有的聚合函数都在django.db.models中
from django.db import connection
def index(request):
result = Book.objects.aggregate(Avg('price')) # {'price__avg': 97.25}
print(connection.queries[-1].get('sql')) # 这里不能使用print(result.query)
return HttpResponse('OK')
翻译成SQL语句:
SELECT AVG(`book`.`price`) AS `price__avg` FROM `book`
如果不想使用Django默认生成的别名,可自行指定别名:
result = Book.objects.aggregate(PriceAvg = Avg('price'))
# SELECT AVG(`book`.`price`) AS `PriceAvg` FROM `book`
# {'PriceAvg': 97.25}
1:aggregate 返回使用聚合函数后的字段和值
2:annotate 在原来模型字段的基础上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候会使用当前这个模型的主键进行分组(group by)
result = Book.objects.annotate(avg = Avg('bookorder__price'))
for book in result:
print('%s/%s' % (book.name, book.avg))
翻译后的SQL语句:
SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, AVG(`order`.`price`) AS `avg` FROM `book` LEFT OUTER JOIN `order` ON (`book`.`id` = `order`.`book_id`) GROUP BY `book`.`id` ORDER BY NULL;
result = Book.objects.aggregate(book_nums = Count('id'))
print(connection.queries)
# SELECT COUNT(`book`.`id`) AS `book_nums` FROM `book`
Count方法中可以指定去重参数:distinct
result = Author.objects.aggregate(email_nums = Count('email', distinct=True))
# SELECT COUNT(DISTINCT `author`.`email`) AS `email_nums` FROM `author`
def index(request):
result = Book.objects.annotate(book_nums = Count('bookorder__id'))
print('='*100)
print(result)
for book in result:
print('%s/%s' % (book.name, book.book_nums))
return HttpResponse('OK')
输出结果 :
三国演义/3
水浒传/2
西游记/0
红楼梦/0
翻译后的SQL语句:
SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, COUNT(`order`.`id`) AS `book_nums` FROM `book` LEFT OUTER JOIN `order` ON (`book`.`id` = `order`.`book_id`) GROUP BY `book`.`id` ORDER BY NULL;
result = Author.objects.aggregate(max_age = Max('age'), min_age = Min('age')) # {'max_age': 46, 'min_age': 28}
# SELECT MAX(`author`.`age`) AS `max_age`, MIN(`author`.`age`) AS `min_age` FROM `author`;
获取每本书籍销售时的最大价格以及最小价格
result = Book.objects.annotate(max_price = Max('bookorder__price'), min_price = Min('bookorder__price'))
SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, MAX(`order`.`price`) AS `max_price`, MIN(`order`.`price`) AS `min_price` FROM `book` LEFT OUTER JOIN `order` ON (`book`.`id` = `order`.`book_id`) GROUP BY `book`.`id` ORDER BY NULL LIMIT 21
求所有书籍的销售总额:
result = BookOrder.objects.aggregate(total_price = Sum('price')) # {'total_price': 455.0}
# SELECT SUM(`order`.`price`) AS `total_price` FROM `order`;
求每一本书籍的销售总额:
result = Book.objects.annotate(every_book_total_price = Sum('bookorder__price'))
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, SUM(`order`.`price`) AS `every_book_total_price` FROM `book` LEFT OUTER JOIN `order` ON (`book`.`id` = `order`.`book_id`) GROUP BY `book`.`id` ORDER BY NULL LIMIT 21;
求2019年度所有书籍的销售总额:
首页给order表添加时间字段:
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)
class Meta:
db_table = 'order'
result = BookOrder.objects.filter(create_time__year=2019).aggregate(total = Sum('price'))
# SELECT SUM(`order`.`price`) AS `total` FROM `order` WHERE `order`.`create_time` BETWEEN '2018-12-31 16:00:00' AND '2019-12-31 15:59:59.999999'; args=('2018-12-31 16:00:00', '2019-12-31 15:59:59.999999')
求2019年度每一本书籍的销售总额:
result = Book.objects.filter(bookorder__create_time__year=2019).annotate(total = Sum('bookorder__price'))
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, SUM(`order`.`price`) AS `total` FROM `book` INNER JOIN `order` ON (`book`.`id` = `order`.`book_id`) WHERE `order`.`create_time` BETWEEN '2018-12-31 16:00:00' AND '2019-12-31 15:59:59.999999' GROUP BY `book`.`id` ORDER BY NULL LIMIT 21; args=('2018-12-31 16:00:00', '2019-12-31 15:59:59.999999')
F表达式是用来优化ORM操作数据库的。
class Employee(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField()
salary = models.FloatField()
email = models.EmailField()
username = models.CharField(max_length=20, null = True)
比如:我们要将公司所有员工的薪水都增加1000元,正常流程是从数据库中提取所有员工工资到内存中,然后使用python代码在员工工资的基础之上增加1000元,最后再保存到数据库中。
def index(request):
employee = Employee.objects.all()
for em in employee:
em.salary += 1000
em.save()
return HttpResponse('OK')
F表达式可以优化此流程,他可以不需要先把数据从数据库中提取出来,计算完后再保存,可以直接执行SQL语句将员工工资增加1000元
def index(request):
Employee.objects.update(salary = F('salary')+1000)
return HttpResponse('OK')
SQL语句:
UPDATE `book_employee` SET `salary` = (`book_employee`.`salary` + 1000); args=(1000,)
F表达式并不会马上从数据库中获取数据,而是在生成SQL语句的时候,动态的获取传给F表达式的值
比如:想要获取每个员工中username字段与email字段值相同的数据,如果不使用F表达式,那么需要以下代码来完成:
def index(request):
employees = Employee.objects.all()
for em in employees:
if em.username == em.email:
print(em.name, em.username, em.email)
return HttpResponse('OK')
使用F表达式:
employees = Employee.objects.filter(username = F('email'))
# SELECT `book_employee`.`id`, `book_employee`.`name`, `book_employee`.`age`, `book_employee`.`salary`, `book_employee`.`email`, `book_employee`.`username` FROM `book_employee` WHERE `book_employee`.`username` = (`book_employee`.`email`) LIMIT 21
作用:用于查询时条件之间的逻辑关系。not and or,可以对Q对象进行 & | ~ (与或非)操作
比如:查询所有价格高于100元且评分达到9.0以上的图书:
books = Book.objects.filter(price__gte = 100,rating__gte = 9) # 且关系
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` WHERE (`book`.`price` >= 100 AND `book`.`rating` >= 9) LIMIT 21; args=(100.0, 9.0)
现在需求改变,查询所有的价格高于100元或者评价9.0以上的书籍,那么就得使用Q对象
books = Book.objects.filter(Q(price__gte = 100)|Q(rating__gte = 9))
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` WHERE (`book`.`price` >= 100 OR `book`.`rating` >= 9) LIMIT 21; args=(100.0, 9.0)
比如:获取价格大于100,且书籍名字中不包含“记”字的书籍
books = Book.objects.filter(Q(price__gte = 100) & ~Q(name__icontains='记'))
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` WHERE (`book`.`price` >= 100 AND NOT (`book`.`name` LIKE '%记%')) LIMIT 21; args=(100.0, '%记%')
在实际开发过程中,可能会遇到很多条件的查询语句,下面介绍Q对象的另一个写法:
query = self.request.GET.get('query', '')
q = Q()
q.connector = 'OR'
q.children.append(Q(('qq__contains', query)))
q.children.append(Q(('name__contains', query)))
# 上面的四行代码等同于:Q(Q(qq__contains = query)| Q(name__contains = query)
cus_list = models.Customer.objects.filter(q, consultant__isnull=True)
我们通过做查询时,都是通过model_name.objects的方式进行操作。其实model_name.objects是一个django.db.models.manager.Manager对象,而Manager这个类是一个空壳类,他本身没有任何的属性和方法。他的方法全部都是Python动态添加的方式从QuerySet类中拷贝过来的。
在使用 QuerySet 进行查找操作的时候,可以提供多种操作。比如过滤完后还要根据某个字段进行排序,那么这一系列的操作我们可以通过一个非常流畅的 链式调用 的方式进行。比如要从文章表中获取标题为 123 ,并且提取后要将结果根据发布的时间进行排序,那么可以使用以下方式来完成:
articles = Articles.objects.filter(title = '123').order_by('create_time')
可以看到 order_by 方法是直接在 filter 执行后调用的。这说明 filter 返回的对象是一个拥有 order_by 方法的对象。而这个对象正是一个新的 QuerySet 对象。因此可以使用 order_by 方法。
将满足条件的数据提取出来,返回一个新的 QuerySet
def index(request):
result = Book.objects.filter(id__gt = 1).filter(~Q(id=3))
for row in result:
print(row.name)
return HttpResponse('OK')
排除满足条件的数据,返回一个新的 QuerySet
result = Book.objects.filter(id__gt = 1).exclude(id=3)
给 QuerySet 中的每个对象都添加一个使用查询表达式(聚合函数、F表达式、Q表达式、Func表达式等)的新字段。
result = Book.objects.annotate(author_name = F('author__name'))
# SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id`, `author`.`name` AS `author_name` FROM `book` INNER JOIN `author` ON (`book`.`author_id` = `author`.`id`); args=()
指定将查询的结果根据某个字段进行排序。如果要倒叙排序,那么可以在这个字段的前面加一个负号
result = Book.objects.order_by('price') # 升序
result = Book.objects.order_by('-price') # 降序
result = Book.objects.order_by('price','author__name') # 如果price字段有重复则根据author_name字段排序
用来指定在提取数据出来,需要提取哪些字段。默认情况下会把表中所有的字段全部都提取出来,可以使用 values 来进行指定,并且使用了 values 方法后,提取出的 QuerySet 中的数据类型不是模型,而是在 values 方法中指定的字段和值形成的字典
result = Book.objects.values('name', 'pages', 'price')
#
类似于values。只不过返回的QuerySet中,存储的不是字典,而是元组
result = Book.objects.values_list('name', 'pages', 'price')
#
如果在values_list中只有一个字段。那么你可以传递flat=True来将结果扁平化
result = Book.objects.values_list('name',flat=True)
#
获取这个ORM模型的QuerySet对象
result = Book.objects.all()
# , , ]>
在提取某个模型的数据的同时,也提前将相关联的数据提取出来。
def index(request):
books = Book.objects.all()
for book in books:
print(book.author.name) # 这里有多少个book对象就要执行多少次查询操作
return HttpResponse('OK')
因为数据库中只有三条数据,所以上面会执行三次数据库查询
而使用select_related可以将author信息提取出来,以后再次使用book.author时就不需要再次去访问数据库,可以减少数据库查询的次,优化查询性能
def index(request):
books = Book.objects.select_related('author')
for book in books:
print(book.author.name) # 只产生一个查询操作
return HttpResponse('OK')
# 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`); args=()
select_related可以传递多个参数:
books = Book.objects.select_related('author', 'publisher')
注意事项:selected_related只能用在一对多或者一对一中,不能用在多对多或者多对一中。比如可以提前获取书籍的作者,但是不能通过作者获取这个作者的书籍
这个方法和select_related非常的类似,就是在访问多个表中的数据的时候,减少查询的次数。这个方法是为了解决多对一和多对多的关系的查询问题
# 模型类
class Book(models.Model):
name = models.CharField(max_length=20)
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 self.name
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)
class Meta:
db_table = 'order'
def index(request):
books = Book.objects.prefetch_related('bookorder_set')
for book in books:
orders = book.bookorder_set.all()
for order in orders:
print(order.id) # 不管有多少个对象,这里只会进行一次查询操作
return HttpResponse('OK')
但是如果在使用prefetch_related的时候,如果又创建了一个新的QuerySet那么会把之前的SQL优化给破坏掉, 如:
def index(request):
books = Book.objects.prefetch_related('bookorder_set')
for book in books:
orders = book.bookorder_set.filter(price__gte = 10) # 这里有多少个对象就会进行多少次查询
for order in orders:
print(order.id)
return HttpResponse('OK')
那如果确实是想要在查询的时候指定过滤条件该如何做呢,这时候我们可以使用django.db.models.Prefetch来实现,Prefetch这个可以提前定义好queryset
def index(request):
prefetch = Prefetch('bookorder_set', queryset = BookOrder.objects.filter(price__gte=90))
books = Book.objects.prefetch_related(prefetch) # 虽然使用了Prefetch,但下面依然不能使用filter
for book in books:
orders = book.bookorder_set.all()
for order in orders:
print(order.id)
return HttpResponse('OK')
在一些表中,可能存在很多的字段,但是一些字段的数据量可能是比较庞大的,而此时你又不需要,比如我们在获取文章列表的时候,文章的内容我们是不需要的,因此这时候我们就可以使用defer来过滤掉一些字段。这个字段跟values有点类似,只不过defer返回的不是字典,而是模型对象
books = Book.objects.defer('name')
# SELECT `book`.`id`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book`; args=()
defer虽然能过滤字段,但是有些字段是不能过滤的,比如id,即使你过滤了,也会提取出来
跟defer类似,只不过defer是过滤掉指定的字段,而only是只提取指定的字段,同样无法操作id字段
获取满足条件的数据。这个函数只能返回一条数据,并且如果给的条件有多条数据,那么这个方法会抛出MultipleObjectsReturned错误,如果给的条件没有任何数据,那么就会抛出DoesNotExit错误
book = Book.objects.get(id = 1)
创建一条数据,并且保存到数据库中。这个方法相当于先用指定的模型创建一个对象,然后再调用这个对象的save方法
# publisher = Publisher(name = '北京出版社')
# publisher.save()
pub = Publisher.objects.create(name = '长沙出版社')
根据某个条件进行查找,如果找到了那么就返回这条数据,如果没有查找到,那么就创建一个
result = Publisher.objects.get_or_create(name = '武汉出版社') # 如果没有该条数据,会有两条SQL语句
# SELECT `publisher`.`id`, `publisher`.`name` FROM `publisher` WHERE `publisher`.`name` = '武汉出版社'; args=('武汉出版社',)
# INSERT INTO `publisher` (`name`) VALUES ('武汉出版社'); args=['武汉出版社']
这个方法的返回值是一个元组,元组的第一个参数obj是这个对象,第二个参数created代表是否创建的
(<Publisher: Publisher object (4)>, True) # 括号里的4表示该条数据的id
一次性创建多个数据,create方法一次只能创建一条数据
result = Publisher.objects.bulk_create([
Publisher(name = '长春出版社'),
Publisher(name = '沈阳出版社')
])
# INSERT INTO `publisher` (`name`) VALUES ('长春出版社'), ('沈阳出版社');
获取提取的数据的个数。如果想要知道总共有多少条数据,那么建议使用count,而不是使用len(articles)这种。因为count在底层是使用select count(*)来实现的,这种方式比使用len函数更加的高效
result = Publisher.objects.count()
# SELECT COUNT(*) AS `__count` FROM `publisher`;
返回QuerySet中的第一条和最后一条数据
result = Publisher.objects.first()
# SELECT `publisher`.`id`, `publisher`.`name` FROM `publisher` ORDER BY `publisher`.`id` ASC LIMIT 1;
判断某个条件的数据是否存在。如果要判断某个条件的元素是否存在,那么建议使用exists,这比使用count或者直接判断QuerySet更有效得多
result = Publisher.objects.filter(name__contains='长沙').exists() # 存在:True
# SELECT (1) AS `a` FROM `publisher` WHERE `publisher`.`name` LIKE BINARY '%长沙%' LIMIT 1; args=('%长沙%',)
去除掉那些重复的数据。这个方法如果底层数据库用的是MySQL,那么不能传递任何的参数。
比如想要提取所有销售的价格超过80元的图书,并且删掉那些重复的,那么可以使用distinct来帮我们实现
result = Book.objects.filter(bookorder__price__gte = 80).distinct()
# SELECT DISTINCT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` INNER JOIN `order` ON (`book`.`id` = `order`.`book_id`) WHERE `order`.`price` >= 80 LIMIT 21;
执行更新操作,在SQL底层走的也是update命令
result = Book.objects.update(price = F('price') + 5)
# UPDATE `book` SET `price` = (`book`.`price` + 5);
删除所有满足条件的数据。删除数据的时候,要注意on_delete指定的处理方式
result = Author.objects.filter(id__gte = 3).delete()
SQL语句(模型类中使用的级联删除):
SELECT `author`.`id`, `author`.`name`, `author`.`age`, `author`.`email` FROM `author` WHERE `author`.`id` >= 3;
SELECT `book`.`id`, `book`.`name`, `book`.`pages`, `book`.`price`, `book`.`rating`, `book`.`author_id`, `book`.`publisher_id` FROM `book` WHERE `book`.`author_id` IN (3);
DELETE FROM `order` WHERE `order`.`book_id` IN (3); args=(3,)
DELETE FROM `book` WHERE `book`.`id` IN (3); args=(3,)
DELETE FROM `author` WHERE `author`.`id` IN (3); args=(3,)
生成一个QuerySet对象并不会马上转换为SQL语句去执行
def index(request):
result = Book.objects.all()
# print(result) # 注释此行代码,下面的connect.queries打印就为空列表
print('=' * 100)
print(connection.queries)
return HttpResponse('OK')
打印connection.quries的时候打印的是一个空的列表。说明上面的QuerySet并没有真正的执行
在以下情况下QuerySet会被转换为SQL语句执行:
在实际开发中,有些时候可能数据库已经存在了。那么该如何将模型与数据库中的表映射呢?根据旧的数据库生成对应的ORM模型,需要以下几个步骤:
1:首先将sql文件导入数据库
2:settings配置文件中配置好数据库
3:在终端执行python manage.py inspectdb命令
python manage.py inspectd命令只会将表转换为模型后的代码显示在终端
4:将上面生成的模型类代码重定向到文件
python manage.py inspectdb >models.py # 会在当前目录下生成models.py文件
5:修改模型类
模型名:自动生成的模型,是根据表的名字生成的,可能不是你想要的。这时候模型的名字你可以改成任何你想要的。
模型所属app:根据自己的需要,将相应的模型放在对应的app中。放在同个app中也是没有任何问题的。只是不方便管 理。
模型外键引用:将所有使用Foreignkey 的地方,模型引用都改成字符串。这样不会产生模型顺序的问题。另外,如果引用的模型已经移动到其他的app中了,那么还要加上这个app的前缀。
让Django管理模型:将Meta下的managed=False删掉,如果保留这个,那么以后这个模型有任何的修改,使用migrate都不会映射到数据库中。
表名:切记不要修改表的名字(这里的表名不是模型类名,而是class Meta中的db_table。不然映射到数据库中,会发生找不到对应表的错误。
多对多关系:生成后的多对多关系数据表的第三张表也会在models.py文件中生成代码,把第三张表删除
Django默认生成的第三张表后面会加上s,如:article_tags,如果与数据库中的表名不一致,可以将数据库中的第三张表修改名称;如果不修改可以在模型类代码中指定第三张的表名称,如下:
tags = models.ManyToManyField('Tag', db_table = 'article_tag')
6:执行命令python manage.py migrate APP_name --fake-initial,因为如果不使用–fake-initial,那么会将迁移脚本会映射到数据库中。这时候迁移脚本会新到建表,而这个表之前是已经存在了的,所以肯定会报错。此时我们只要将这个0001-initial 的状态修改为已经映射,而不真正执行映射,下次再migrate的时候,就会忽略他
python manage.py migrate book --fake-initial
7:将Django的核心表映射到数据库中: Django中还有那些核心的表也是需要创建的。不然有些功能是用不了的。比如auth相关表。如果过个数据库之前就是使用Django开发的,那么这些表就已经存在了,可以不用管了。如果之前这个数据库不是使用Django开发的,那么应该使用python manage.py migrate命令将Django中的核心模型映射到数据库中
python manage.py migrate