第七篇:Django之模型层

第七篇:Django之模型层

一、单表查询(增删改查)

模型层(ORM语法):是直接跟数据库打交道的,十分重要,学习的时候可以对比SQL语句,进行对比学习。ORM语法最后本质上都是要转化成SQL语法的。

django自带的sqlite3数据库对日期格式不是很敏感,处理的时候容易出错,所以我们使用mysql数据库进行数据存储和处理。

1、测试脚本

当只是要测试django中的某一个py文件内容,那么可以不用书写前后端交互的形式,而是直接写一个测试脚本即可。脚本代码无论是在应用下的tests.py文件下,还是自己单独开设py文件都可以。

测试环境配置代码如下,如此一来,我们便可以在在test.py中进行测试,不必像之前那样大费周章。

# 测试环境的准备 去manage.py中拷贝前四行代码 然后自己写两行
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings")
  
    import django
    django.setup()
    # 注意,之后测试的代码必须写在下面,到了这里,配置环境才准备完毕
    ...

2、单表测试操作

我们先使用navicat创建一个数据库day64,然后配置django中的数据库数据,之后在models.py文件中书写如下代码。

from django.db import models

# Create your models here.

# 用户表
class User(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    register_time = models.DateField()  # 年月日:在这里我们自定义,而不采用自动传数值的方式
    """
    DateField : 年月日
    DateTimeField : 年月日时分秒
        两个重要参数 
        auto_now:每次操作数据的时候 该字段会自动将当前时间更新
        auto_now_add:在创建数据的时候会自动将当前创建时间记录下来 之后只要不认为的修改 那么就一直不变
    """
    # 当打印数据对象时,调用该方法,打印对象的name字段
    def __str__(self):
        return '对象:{}'.format(self.name)

然后使用python3 manage.py makemigrations命令和python3 manage.py migrate命令,创建表。

  • 增加
"""第一种方式"""
res = models.User.objects.create(name='yangyi', age=18, register_time='2021-6-29')  # create方法会返回一个对象,为创建的数据对象
    print(res)  # 对象:yangyi  
  
"""第二种方法"""
import datetime  
    current_time = datetime.datetime.now()
    # 可以直接传递一个事件对象,即便格式看起来并不匹配
    user_obj = models.User(name='leichao', age=24, register_time=current_time)
    user_obj.save()
    print(current_time)  # 2021-06-29 13:44:47.769389

"""时间数据
         数据库     ----直接取到--->      前端页面  ---需要使用过滤器---> {{ user_obj.register_time|date:'Y-m-d h:i:s' }}
2021-08-22 11:35:58.404072       Aug. 22, 2021, 11:35 a.m.                      2021-08-22 11:35:58
"""

我们发现我们在数据库中创建了一条数据。

我们使用第二种方法创建的数据。

  • 删除

    补充:pk会自动查找到当前表的主键字段,指代的就是当前表的主键字段,用了pk之后, 你就不需要指代当前表的主键字段到底叫什么了。【可能有的主键值叫 uid、pid、sid、...】

"""第一种方式"""
res = models.User.objects.filter(pk=2).delete()  # 批量删除
print(res)  # 返回删除的数据的个数 (1, {'app01.User': 1}),并不是被删除的数据对象
    
"""第二种方式"""
user_obj = models.User.objects.filter(pk=1).first() # 指名道姓,拿到确定的数据对象
user_obj.delete()

"""补充,细品"""
print(models.User.objects.filter(pk=1))
# 拿到一个QuerySet:查询集,格式类似于列表【可通过索引取值 .first()】,但是比列表多一些方法
<QuerySet [<User: 对象:yangyi>]>   
  • 修改

    我们重新建立四条数据,用于测试。

"""第一种方法"""
models.User.objects.filter(pk=4).update(name='dsg')  # 批量更新

"""第二种方法"""
user_obj = models.User.objects.filter(pk=4).first()  # 指名道姓
    user_obj.name = 'nb'
    user_obj.save()
    
"""补充"""
get方法返回的直接就是当前的数据对象,但是该方法不推荐使用,一旦数据不存在该方法会直接报错。
而filter则不会报错,而filter得到一个查询集。

二、常见的十几种查询方法

1、操作测试

"""纠正一下以前的问题,User只是一个类,User.objects是一个表"""
# 1、all()  查询所有数据
print(models.User.objects)
app01.User.objects
# 可以理解为先拿到类产生的对象,之后使用对象的方法,来获得其中的数据对象。
print(models.User.objects.all())  
<QuerySet [<User: 对象:anan>, <User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:gangchuan>]>

# 2、filter()  带有过滤条件的查询【得到查询集】

# 3、get()  直接拿数据对象,但是条件不存在直接报错

# 4、first()  拿queryset查询集里面第一个元素

# 5、last()  拿queryset查询集里面最后一个元素

# 6、values()  可以指定获取的数据字段【列表套字典】【可以直接更在objects后面,也可以跟在查询集后面】
res = models.User.objects.values('name', 'age')
print(res)  # <QuerySet [{'name': 'anan', 'age': 18}, {'name': 'nb', 'age': 24}, {'name': 'yangxin', 'age': 24}, {'name': 'gangchuan', 'age': 23}]>
print(res.first().get('name'))  # anan 查询集的操作方式和列表、字典相同。
    
# 7、values_list()  【列表套元祖】
res = models.User.objects.values_list('name', 'age')
print(res)  # <QuerySet [('anan', 18), ('nb', 24), ('yangxin', 24), ('gangchuan', 23)]>

"""
补充:只有queryset对象才能够点击query查看内部的sql语句
res = models.User.objects.values('name', 'age')
print(res.query)  # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user`
"""

# 8、distinct()  去重
res = models.User.objects.all().distinct()
print(res)  # <QuerySet [<User: 对象:anan>, <User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:gangchuan>, <User: 对象:nb>]>
"""
我们发现并没有去重,去重一定要是一模一样的数据,如果带有主键那么肯定不一样,往后的查询中一定不要忽略主键。
"""
res = models.User.objects.values('name', 'age').distinct()
print(res)  # <QuerySet [{'name': 'anan', 'age': 18}, {'name': 'nb', 'age': 24}, {'name': 'yangxin', 'age': 24}, {'name': 'gangchuan', 'age': 23}]>  去重并不会对原数据库中的数据产生影响,只是查询时去重而已。

# 9.order_by()  排序
res = models.User.objects.order_by('age')  # 默认升序
print(res)  # <QuerySet [<User: 对象:anan>, <User: 对象:gangchuan>, <User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:nb>]>
res = models.User.objects.order_by('-age')  # 降序
print(res)  # <QuerySet [<User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:nb>, <User: 对象:gangchuan>, <User: 对象:anan>]>
    
# 10.reverse()  反转的前提是,数据已经排过序
res = models.User.objects.order_by('age').reverse()
print(res)  # <QuerySet [<User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:nb>, <User: 对象:gangchuan>, <User: 对象:anan>]>
res = models.User.objects.all().reverse()  # 如此没有效果
print(res)  # <QuerySet [<User: 对象:anan>, <User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:gangchuan>, <User: 对象:nb>]>
    
# 11.count()  统计当前数据的个数
res = models.User.objects.count()
print(res)  # 5
    
# 12.exclude()  排除在外
res = models.User.objects.exclude(name='yangxin')
print(res)  # <QuerySet [<User: 对象:anan>, <User: 对象:nb>, <User: 对象:gangchuan>, <User: 对象:nb>]>
    
# 13.exists()  是否存在【基本用不到因为数据本身就自带隐式布尔值】
res = models.User.objects.filter(pk=10).exists()
print(res)  # False

参考数据库如图所示。

2、查看内部sql语句的方式

"""方式一"""  # queryset对象才能够点击query查看内部的sql语句
res = models.User.objects.values('name', 'age')
print(res.query)  # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user`

"""方式二"""  # 所有的sql语句都能查看
# 去配置文件中配置一下即可
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

# 查询方式:直接打印要测试的语句即可。
res = models.User.objects.values('name', 'age')
print(res.query)  # 第一种
print(res)  # 第二种

三、神奇的双下划线查询

参考数据库。

# 1、年龄大于23岁的数据
res = models.User.objects.filter(age__gt=23)
print(res)  # <QuerySet [<User: 对象:nb>, <User: 对象:yangxin>]>
    
# 2、年龄小于23岁的数据
res = models.User.objects.filter(age__lt=23)
print(res)  # <QuerySet [<User: 对象:anan>]>
    
# 3、年龄大于等于23岁的数据
res = models.User.objects.filter(age__gte=23)
print(res)  # <QuerySet [<User: 对象:nb>, <User: 对象:yangxin>, <User: 对象:gangchuan>]>

# 4、年龄小于等于23岁的数据
res = models.User.objects.filter(age__lte=23)
print(res)  # <QuerySet [<User: 对象:anan>, <User: 对象:gangchuan>]>
    
# 5、年龄是18或23的数据
res = models.User.objects.filter(age__in=[18, 23])
print(res)  # <QuerySet [<User: 对象:anan>, <User: 对象:gangchuan>]>
    
# 6、年龄在15和23之间的数据 
res = models.User.objects.filter(age__range=[15, 23])   # 首尾都包括
print(res)  # <QuerySet [<User: 对象:anan>, <User: 对象:gangchuan>]>
    
# 7、查询名字中有g的数据【模糊查询】
res = models.User.objects.filter(name__contains='g')  # 区分大小写
print(res)  # <QuerySet [<User: 对象:yangxin>, <User: 对象:gangchuan>]>
res = models.User.objects.filter(name__icontains='g')  # 如此忽略大小写

# 8、名字中以y开头的数据
res = models.User.objects.filter(name__startswith='y')
print(res)  # <QuerySet [<User: 对象:yangxin>]>

# 9、名字中以n结尾的数据
res = models.User.objects.filter(name__endswith='n')
print(res)  # <QuerySet [<User: 对象:anan>, <User: 对象:yangxin>, <User: 对象:gangchuan>]>
    
# 10、查询注册时间是5月的数据
res = models.User.objects.filter(register_time__month=5)
print(res)  # <QuerySet [<User: 对象:yangxin>, <User: 对象:gangchuan>]>
"""register_time__year、register_time__month、register_time__day、register_time__week_day等等"""

四、外键字段的增删改查

1、一对多外键增删改查

我们先简单建立几张表,分别为图书表、出版社表、作者表、作者详情表和图书作者对象表。建表代码如下。

"""用户表"""
class User(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    register_time = models.DateField()  # 年月日:在这里我们自定义,而不采用自动传数值的方式
    """
    DateField : 年月日
    DateTimeField : 年月日时分秒
        两个重要参数 
        auto_now:每次操作数据的时候 该字段会自动将当前时间更新
        auto_now_add:在创建数据的时候会自动将当前创建时间记录下来 之后只要不认为的修改 那么就一直不变
    """
    # 当打印数据对象时,调用该方法,打印对象的name字段
    def __str__(self):
        return '对象:{}'.format(self.name)


"""图书表"""
class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8,decimal_places=2)
    publish_date = models.DateField(auto_now_add=True)
    # 外键:多对一 出版社
    publish = models.ForeignKey(to='Publish')
    # 外键:多对多 作者
    author = models.ManyToManyField(to='Author')


"""出版社表"""
class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    # varchar(254)  该字段类型不是给models看的,而是给校验性组件看的
    email = models.EmailField()


"""作者表"""
class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # 外键:一对一 作者详情
    author_detail = models.OneToOneField(to='AuthorDetail')


"""作者详情表"""
class AuthorDetail(models.Model):
    phone = models.BigIntegerField()  # 电话号码用BigIntegerField或者直接用CharField
    addr = models.CharField(max_length=64)

由此创建了五张表。

我们先给出版社表、作者详情表、作者表中添加数据【出版社表没有外键,一对一关系较为简单】。简单添加效果如下。

下面,我们执行下述操作。

  • 增加
"""第一种方式"""
# 直接写实际字段 id
models.Book.objects.create(title='三国演唱',price=899.23, publish_id=1)
models.Book.objects.create(title='水许传',price=444.23,publish_id=2)
models.Book.objects.create(title='东游记',price=333.66,publish_id=1)

"""第二种方式"""
 # 虚拟字段 直接传入出版社数据对象
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title='绿楼梦', price=123.3, publish=publish_obj)

  • 删除
# 级联删除
models.Publish.objects.filter(pk=2).delete()  # 删除主键为2的第二个出版社

publish_obj = models.Publish.objects.filter(pk=2).first()
publish_obj.delete()

  • 修改
# 把book表中主键为1的数据对象的出版社id改为2


"""第一种""" # id修改  【批量更新】
models.Book.objects.filter(pk=1).update(publish_id=2)  # 可以直接修改

"""第二种""" # 对象修改
publish_obj = models.Publish.objects.filter(pk=1).first()  # 拿到出版社对象
models.Book.objects.filter(pk=1).update(publish=publish_obj)  # 直接将拿到的出版社对象赋值给book对象的字段

2、多对多外键增删改查

多对多,增删改查,其实就是在操作第三张表。

  • 增加
# 如何给多对多关系的第三章表添加数据?【此时第三张关系表为虚拟表,并没有办法直接操作...】
"""
add给第三张关系表添加数据,括号内既可以传数字也可以传对象,并且都支持多个
"""

"""1、可以直接根据主键添加"""  # 给主键为1的书添加作者关系
book_obj = models.Book.objects.filter(pk=1).first()  # 拿到主键值为1的书的数据对象
print(book_obj.author)  # app01.Author.None,就类似于已经拿到了第三章关系表了
book_obj.authors.add(1)  # 书籍id为1的书籍绑定一个主键为1的作者
book_obj.authors.add(2,3)  # 可以添加多个作者

"""2、也可以直接添加作者对象"""
author_obj = models.Author.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=2).first()
author_obj2 = models.Author.objects.filter(pk=3).first()
book_obj.authors.add(author_obj)
book_obj.authors.add(author_obj1,author_obj2)
  • 删除
"""remove:括号内既可以传数字也可以传对象,并且都支持多个"""
"""第一种"""
book_obj.authors.remove(2)  # 拿到书的对象,给书移除作者
book_obj.authors.remove(1,3)

"""第二种"""
author_obj = models.Author.objects.filter(pk=2).first()  # 也可以拿到要被移除的作者的数据对象,然后直接传入数据对象移除即可。
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.remove(author_obj,author_obj1)
  • 修改
"""set:括号内必须传一个可迭代对象,该对象内既可以数字也可以对象,并且都支持多个"""
"""第一种"""  # 直接将关联的作者重置,可以这么理解
book_obj.authors.set([1,2])  
book_obj.authors.set([3])  
# 如果 book_obj.author.set(3) 则报错:TypeError: 'int' object is not iterable
"""第二种"""
author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.set([author_obj,author_obj1])  # 括号内
  • 清空
"""clear:括号内不要加任何参数"""
# 括号内不要加任何参数【删除有book_obj关联的所有作者关系】
book_obj.authors.clear() 

五、跨多表查询

1、正反向概念

正向:假设外键字段在我手上,那么我查你就是正向。
反向:假设外键字段如果不在手上,那么我查你就是反向。

比如:
正向:book【外键】 ----------> publish
反向:publish ----------> book【外键】

# 一对一和多对多正反向的判断也是如此

"""可以简单总结一下"""
# 正向查询按字段   如果多个 + all()
# 反向查询按表名小写 + _set  如果多个 + all()   如果只有一个 不用加 _set.all()

2、子查询(基于对象的跨表查询)

  • 正向查询
# 1.查询主键为1的书籍所对应的出版社对象
book_obj = models.Book.objects.filter(pk=1).first()
print(book_obj.publish)  # Publish object 得到出版社对象

# 2.查询主键为2的书籍所关联的的作者对象
book_obj = models.Book.objects.filter(pk=2).first()
print(book_obj.author)  # app01.Author.None
print(book_obj.author.all())  # <QuerySet [<Author: Author object>, <Author: Author object>]>
print(book_obj.author.all().first())  # Author object【查询集这样取值】

# 3.查询作者yangyi的电话号码
author_obj = models.Author.objects.filter(name='yangyi').first()
print(author_obj.author_detail)  # AuthorDetail object【如果只有一个,直接拿到对象】
print(author_obj.author_detail.phone)  # 通过数据对象取电话号码

总结:

在书写orm语句的时候跟写sql语句一样的,不要企图一次性将orm语句写完,如果比较复杂,就写一点看一点。

"""正向什么时候需要加.all()?"""
# 当你的结果可能有多个的时候就需要加.all()
# 如果是一个则直接拿到数据对象
如: book_obj.publish  # 多对一,拿出来一个
	book_obj.authors.all()  # 多对多,可能拿出来多个,也可能拿出来一个
    author_obj.author_detail  # 一对一,直接拿出对象
  • 反向查询
# 4.查询出版社属于东方出版社出版的所有的书
publish_obj = models.Publish.objects.filter(name='东方出版社').first() 
print(publish_obj.book_set)  # app01.Book.None
print(publish_obj.book_set.all())  # <QuerySet [<Book: Book object>, <Book: Book object>]>

# 5.查询作者yangyi写过的所有书籍数据对象
author_obj = models.Author.objects.filter(name='yangyi').first()
print(author_obj.book_set)  # app01.Book.None
print(author_obj.book_set.all())  # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>

# 6.查询手机号是11110的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone=11110).first()
print(author_detail_obj.author)  # Author object【只有一个结果不用加_set.all()】 
print(author_detail_obj.author.name)  # yangyi

"""
    基于对象 
        反向查询的时候
            当你的查询结果可以有多个的时候 就必须加_set.all()
            当你的结果只有一个的时候 不需要加_set.all()
"""

总结:

反向查询的时候,当你的查询结果可以有多个的时候,就必须加_set.all()。
但当查询的结果只有一个的时候,不需要加_set.all()。

3、联表查询(基于双下划线的跨表查询)

"""1.查询yangyi的手机号和作者姓名"""
# 正向
res = models.Author.objects.filter(name='yangyi').values('author_detail__phone','name')
print(res)  # <QuerySet [{'author_detail__phone': 11110, 'name': 'yangyi'}]>

# 反向
res = models.AuthorDetail.objects.filter(author__name='yangyi')  # 拿作者姓名是yangyi的作者详情
print(res)  # <QuerySet [<AuthorDetail: AuthorDetail object>]>
res = models.AuthorDetail.objects.filter(author__name='yangyi').values('phone','author__name')  
print(res)  # <QuerySet [{'phone': 11110, 'author__name': 'yangyi'}]>


"""2.查询主键为1的书籍对象所对应的出版社名称和书的名称"""
# 正向
res = models.Book.objects.filter(pk=1).values('title','publish__name')
print(res)  # <QuerySet [{'title': '三国演唱', 'publish__name': '东方出版社'}]>

# 反向
 res = models.Publish.objects.filter(book__id=1).values('name', 'book__title')
print(res)  # <QuerySet [{'name': '东方出版社', 'book__title': '三国演唱'}]>

"""3.查询主键为1的书籍所对应的的作者姓名"""
# 正向
res = models.Book.objects.filter(pk=1).values('author__name')
print(res)  # <QuerySet [{'author__name': 'yangyi'}, {'author__name': 'leichao'}]>

# 反向
res = models.Author.objects.filter(book__id=1).values('name')
print(res)  # <QuerySet [{'name': 'yangyi'}, {'name': 'leichao'}]>

"""4.查询主键是1的书籍所对应的作者的手机号"""
 # book --->  author ---> authordetail
res = models.Book.objects.filter(pk=1).values('author__author_detail__phone')
print(res)  # <QuerySet [{'author__author_detail__phone': 11110}, {'author__author_detail__phone': 22220}]>
【或者】  res = models.Author.objects.filter(book__id=1).values('author_detail__phone')

总结:

"""基于双下划线的跨表查询"""
# 正向查询
通过外键 + __  就可以跳转到与外键相连的表

# 反向查询
被关联的表,通过有外键的表的表名小写 + __  就可以反向跳转到具有外键的表

六、查询方式

1、聚合查询【aggregate】

聚合查询通常情况下都是配合分组查询一块使用的。

from django.db.models import Max, Min, Sum, Count, Avg
"""只要是跟数据库相关的模块,基本上都在django.db.models里面,如果上述没有那么应该在django.db里面"""

# 1、查看所有的书的平均价格
res = models.Book.objects.aggregate(Avg('price'))
res = models.Book.objects.all().aggregate(Avg('price'))  # 这种方式也可以,如果是所有的数据中查找,就无需加all()。也是在查询集中进行平均。
print(res)  # {'price__avg': 453.0225}

# 2、统一使用
res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Count('pk'), Avg('price'))
print(res)  # {'price__max': Decimal('899.23'), 'price__min': Decimal('123.20'), 'price__sum': Decimal('1812.09'), 'pk__count': 4, 'price__avg': 453.0225} 

2、分组查询【annotate】

回顾:MySQL分组查询都有哪些特点?

分组之后默认只能获取到分组的依据,组内其他字段都无法直接获取了,因为是严格模式,ONLY_FULL_GROUP_BY

# 1.统计每一本书的作者个数
"""author_num是我们自己定义的字段,用来存储统计出来的每本书对应的作者个数"""
res = models.Book.objects.annotate(author_num=Count('author')).values('title', 'author_num')
print(res)  # <QuerySet [{'title': '三国演唱', 'author_num': 2}, {'title': '东游记', 'author_num': 2}, {'title': '大学', 'author_num': 1}, {'title': '中庸', 'author_num': 0}]>


# 2.统计每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res)  # <QuerySet [{'name': '东方出版社', 'min_price': Decimal('333.66')}, {'name': '北方出版社', 'min_price': Decimal('123.20')}]>

# 3.统计不止一个作者的图书
"""1、先按照图书分组,求每一本书对应的作者个数;2.过滤出不止一个作者的图书"""
res = models.Book.objects.annotate(author_num=Count('author')).filter(author_num__gt=1).values('title','author_num')
print(res)  # <QuerySet [{'title': '三国演唱', 'author_num': 2}, {'title': '东游记', 'author_num': 2}]>

# 4.查询每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price')
print(res)  # <QuerySet [{'name': 'yangyi', 'sum_price': Decimal('1356.09')}, {'name': 'leichao', 'sum_price': Decimal('1232.89')}]>

"""
补充: 如果我想按照指定的字段分组该如何处理呢?
models.Book.objects.values('price').annotate()

如果出现分组查询报错的情况,取消mysql数据库的严格模式即可。
"""

3、F与Q查询

  • F查询

    能够帮助你直接获取到表中某个字段对应的数据。

    我们给书籍表添加两个字段,分别是库存数stock和卖出数sale

# 图书表
class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish_date = models.DateField(auto_now_add=True)
    # 库存数
    stock = models.IntegerField(default=1000)
    # 卖出数
    sale = models.IntegerField(default=1000)
    # 外键:多对一 出版社
    publish = models.ForeignKey(to='Publish')
    # 外键:多对多 作者
    author = models.ManyToManyField(to='Author')

然后对数据进行简单修改,效果如下。

from django.db.models import F
    
# 1.查询卖出数大于库存数的书籍
res = models.Book.objects.filter(sale__gt=F('stock'))
print(res)  # <QuerySet [<Book: 对象:大学>]>

# 2.将所有书籍的价格提升50块
models.Book.objects.update(price=F('price') + 50)

# 3.将所有书的名称后面加上爆款两个字
"""在操作字符类型的数据的时候,F不能够直接做到字符串的拼接,数据会全部消失"""
models.Book.objects.update(title=F('title') + '爆款')  # 错误

from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))

效果如下。

  • Q查询
# 1.1.查询卖出数大于400或者价格小于600的书籍
"""
filter括号内多个参数是and关系
# 查询卖出数大于400同时价格小于600的书籍
res = models.Book.objects.filter(sale__gt=400, price__lt=600)
print(res)  # <QuerySet [<Book: 对象:东游记爆款>, <Book: 对象:中庸爆款>]>
"""
rom django.db.models import Q 
# 如此一来,还是and关系 【,】
res = models.Book.objects.filter(Q(sale__gt=400), Q(price__lt=600))
print(res)  # <QuerySet [<Book: 对象:东游记爆款>, <Book: 对象:中庸爆款>]>

# or 关系 【|】
res = models.Book.objects.filter(Q(sale__gt=400) | Q(price__lt=600))
print(res)  # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:东游记爆款>, <Book: 对象:大学爆款>, <Book: 对象:中庸爆款>]>

# not 关系 【~】
res = models.Book.objects.filter(~Q(sale__gt=400) | ~Q(price__lt=600))
print(res)  # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:大学爆款>]>

Q的高阶用法

# 可以满足用户自定义查询【能够将查询条件的左边也变成字符串的形式】
q = Q()
q.connector = 'or'
q.children.append(('sale',400))
q.children.append(('price__lt',600))
res = models.Book.objects.filter(q)  # 如果不修改q.connector,默认还是and关系
print(res)  # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:东游记爆款>, <Book: 对象:大学爆款>, <Book: 对象:中庸爆款>]>

4、Django中如何开启事务

# 事务的四个特征
ACID
原子性:不可分割的最小单位
一致性:跟原子性是相辅相成
隔离性:事务之间互相不干扰
持久性:事务一旦确认永久生效
"""事物的回滚:rollback; 事务的确认:commit"""

"""如何开启事务?"""
from django.db import transaction
try:
    with transaction.atomic():
        # sql1
        # sql2
        ...
        # 在with代码快内书写的所有orm操作都是属于同一个事务
except Exception as e:
     print(e)
print('执行其他操作')

5、orm中常用字段及参数

# AutoField
主键字段 primary_key=True

# CharField  varchar(可变字符串)
verbose_name 字段的注释
max_length 长度【必须指定】

# IntegerField   int

# BigIntegerField  bigint

# DecimalField  decimal
max_digits=8  最长8位
decimal_places=2  小数2位

# EmailFiled  varchar(254)

# DateField  date
年月日

# DateTimeField  datetime
年月日时分秒
"""
auto_now:每次修改数据的时候都会自动更新当前时间
auto_now_add:只在创建数据的时候记录创建时间,后续不会自动修改
"""

# BooleanField()  - 布尔值类型
该字段传布尔值(False/True) 	对应数据库里面存0/1

# TextField(Field)	- 文本类型
该字段可以用来存大段内容(文章、博客...),并且没有字数限制

# FileField(Field)	- 字符类型
upload_to = "/data/"
给该字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保存到数据库中。比如,数据库中保存 /data/a.txt


"""外键字段及参数"""
# unique=True 
所以:    ForeignKey(unique=True)  <===>  OneToOneField()
如果使用 ForeignKey(unique=True)创建一对一关系,orm会有一个提示信息,orm推荐你使用后者但是前者也能用。

# db_index=
如果db_index=True,则代表着为此字段设置索引【索引参考mysql索引内容】

# to_field=
设置要关联的表的字段,默认不写关联的就是另外一张的主键字段

# on_delete=True
设置级联更新,级联删除使用。

# on_update=True
django2.X及以上版本,需要你自己指定外键字段的级联更新级联删除

自定义字段【了解即可】:

django除了给你提供了很多字段类型之外,还支持你自定义字段,自定义方式如下。

"""自定义字段"""
class MyCharField(models.Field):
    def __init__(self,max_length,*args,**kwargs):
        self.max_length = max_length
        # 调用父类的init方法
        super().__init__(max_length=max_length,*args,**kwargs)  # 一定要是关键字的形式传入

    def db_type(self, connection):
        """
        返回真正的数据类型及各种约束条件
        :param connection:
        :return:
        """
        return 'char(%s)'%self.max_length
    
    
"""自定义字段的使用"""
myfield = MyCharField(max_length=16, null=True)

七、数据库查询优化

orm查询语句的特点:

惰性查询,如果你仅仅只是书写了orm语句,在后面根本没有用到该语句所查询出来的参数,那么orm会自动识别,直接不执行。

1、only与defer

  • only
"""获取书籍表中所有书的名字"""
res = models.Book.objects.values('title')
print(res)  # <QuerySet [{'title': '三国演唱爆款'}, {'title': '东游记爆款'}, {'title': '大学爆款'}, {'title': '中庸爆款'}]>

for item in res:  # for循环,取得所有书的名单
    print(item.get('title'))
    
"""那么我想要实现获取到的是一个数据对象,然后点title就能够拿到书名,而且并且没有其他字段?"""
res = models.Book.objects.only('title')
print(res)  # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:东游记爆款>, <Book: 对象:大学爆款>, <Book: 对象:中庸爆款>]>

for item in res:
    print(item.title) # 取得书名
# 点击only括号内的字段,不会走数据库;击only括号内没有的字段,会重新走数据库查询而all不需要走

  • defer
res = models.Book.objects.defer('title')  # 和only刚好相反,对象除了没有title属性之外其他的都有
print(res)  # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:东游记爆款>, <Book: 对象:大学爆款>, <Book: 对象:中庸爆款>]>

res = models.Book.objects.defer('title')  # 和only刚好相反,对象除了没有title属性之外其他的都有
for item in res:
    print(item.price)  # 点defer中没有的,只需要一条sql查询语句
    
"""
defer与only刚好相反
    defer括号内放的字段不在查询出来的对象里面,查询该字段需要重新走数据
    而如果查询的是非括号内的字段,则不需要走数据库
"""

select_related与prefetch_related ,跟跨表操作有关。

  • select_related【联表查询】
 res = models.Book.objects.all()
    for item in res:
        print(item.publish.name)  # 每循环一次就要取数据库中查询一次

res = models.Book.objects.select_related('publish')
    print(res)  # <QuerySet [<Book: 对象:三国演唱爆款>, <Book: 对象:东游记爆款>, <Book: 对象:大学爆款>, <Book: 对象:中庸爆款>]>
    
"""
select_related内部直接先将book与publish连起来 然后一次性将大表里面的所有数据全部封装给查询出来的对象, 这个时候对象无论是点击book表的数据还是publish的数据都无需再走数据库查询。

select_related括号内只能放外键字段,只能一对多,一对一,多对多不行。
"""

  • prefetch_related 【子查询】

    prefetch_related该方法内部其实就是子查询,将子查询查询出来的所有结果也给你封装到对象中,给人的感觉好像也是一次性搞定的【其实分两次】。

res = models.Book.objects.prefetch_related('publish')
    # print(res)
    for item in res:
        print(item.publish.name)

posted @ 2021-06-29 21:05  YangYi215  阅读(157)  评论(0编辑  收藏  举报