模型层

sqlite3数据库对日期格式不是很敏感,处理的时候容易出错

register_time=models.DateTimeField(auto_now=True)	#年月日时分秒格式
register_time=models.DateField(auto_now_add=True)	#年月日格式

'''
auto_now每次操作数据时,该字段会自动将时间更新为当前时间,

auto_now_add(用的多) 在创建数据的时候自动将创建时间记录下来,之后只要不人为的修改创建时间,就一直不变

'''

测试脚本

  1. 当只想测试django中某一个py文件内容,可以不用写前后端交互的形式,而是直接写一个测试脚本
  2. pycharm提供了一个测试环境:python console
import os

if __name__ == '__main__':
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject.settings')	
    # djangoProject是一个项目名称
    import django
    django.setup()

    from app01 import models
    models.User.objects.all()

单表操作

#删除主键为5的那条数据,pk=5表示主键为5,不需要知道主键字段是什么,自动去找主键
res=models.User.objects.filter(pk=5).delete()
    print(res)
'''
返回的结果是:受影响的数据行数
(1, {'app01.User': 1})
'''

#增加一条数据
#方式一
res=models.User.objects.create(username='landson',password='123',register_time=datetime.datetime.now())

#方式二
user_obj=models.User(username='landson',password='123',register_time=datetime.datetime.now())
user_obj.save()

#修改数据,get方法获取的是对象,如果那个对象不存在,会报错,还是使用filter方法
 user_obj = models.User.objects.get(pk=1)
    user_obj.username='egon_dsb'
    user_obj.save()

取指定的字段

  1. values()
  2. values_list()
# values()可以指定要获取哪些字段
res=models.User.objects.values('username','password')
    print(res)
'''
列表套字典
<QuerySet [{'username': 'egon_dsb', 'password': '123'}, {'username': 'jason', 'password': '456'}, {'username': 'tank', 'password': '789'}]>
'''


res=models.User.objects.values_list('username','password')
    print(res)
    #只有QuerySet对象才能通过.query查看内部封装的sql语句
    print(res.query)	
'''
列表套元组
<QuerySet [('egon_dsb', '123'), ('jason', '456'), ('tank', '789')]>
SELECT `app01_user`.`username`, `app01_user`.`password` FROM `app01_user`
'''

查看内部sql语句的方式

只有QuerySet对象才能通过.query查看内部封装的sql语句

去重排序计数

去重要是一模一样的数据,如果带有主键,因为主键不同,即使其他字段都相同,整体也不完全相同

res=models.User.objects.values_list('id','username','password').distinct()
    print(res)
'''
<QuerySet [(1, 'egon_dsb', '123'), (2, 'jason', '456'), (3, 'tank', '789'), (4, 'tank', '789')]>
'''

res=models.User.objects.values_list('username','password').distinct()
    print(res)
'''
<QuerySet [('egon_dsb', '123'), ('jason', '456'), ('tank', '789')]>
'''

#按照指定字段排序,默认升序,加-表示降序
res=models.User.objects.order_by('username')
    print(res)
    
res=models.User.objects.order_by('-username')
    print(res)
    
#数据必须先排序然后才能使用reverse()进行反转
res=models.User.objects.order_by('username').reverse()


#计数
res=models.User.objects.filter(password='789').count()
print(res)

#不包含
res=models.User.objects.exclude(username='page')

双下划线查询

#age大于20的
res=models.User.objects.filter(age__gt=20)
    print(res)
    
#age大于等于18的    
    res = models.User.objects.filter(age__gte=18)
    print(res)
    
 #age小于20的   
    res = models.User.objects.filter(age__lt=20)
    print(res)
    
#age小于等于18的      
    res = models.User.objects.filter(age__lte=18)
    print(res)
    
#age等于18,或28,或35的     
    res = models.User.objects.filter(age__in=[18,28,35])
    print(res)
    
#age大于等于18,且小于等于32的,包含头尾     
     res = models.User.objects.filter(age__range=[18,32])
    print(res)
    
#名字中包含字符e的,默认区分大小写   
    res = models.User.objects.filter(username__contains='e')
    print(res)
    
 #名字中包含字符e的, 不区分大小写,i表示ignore  
res = models.User.objects.filter(username__icontains='e')

#register_time在7月的
res = models.User.objects.filter(register_time__month='7')
    print(res)

#register_time在2021年的
 res = models.User.objects.filter(register_time__year='2021')
    print(res)

一对多外键增删改查

 # 增:
    # 直接写真实字段
    # models.Book.objects.create(book_name='三国演义',price=30,publish_id=1)
     # 虚拟字段,一个对象
    publish_obj=models.Publish.objects.filter(pk=2).first()
    models.Book.objects.create(book_name='三国演义',price=30,publish=publish_obj)
   
    
    
    #改
    # models.Book.objects.filter(pk=2).update(book_name='红楼梦')
    #可以一下改一条记录的多个字段
    models.Book.objects.filter(pk=3).update(book_name='水浒传',publish=publish_obj)

多对多外键增删改查

就是对虚拟表的增删改查

 #增
    #方式一
    #获得id=2的book对象
    book_obj=models.Book.objects.filter(pk=2).first()
    #给它增加id=1和2的两个作者
    book_obj.author.add(1,2)
  
    
   #方式二 
	#获得作者对象
    author_obj1=models.Author.objects.filter(pk=1).first()
    author_obj2=models.Author.objects.filter(pk=2).first()

     
    book_obj.author.add(author_obj1, author_obj2)
    
 
#删
  #给它删掉id=1的那个作者
    #方式一
    book_obj.author.remove(1,2)
    #方式二
    book_obj.author.remove(author_obj1, author_obj2)
    
    
#改
#set的参数必须是可迭代对象,比如列表,先删除,后新增


#清空id=3的book的所有作者
book_obj=models.Book.objects.filter(pk=3).first()
book_obj.author.clear()

正向和反向

正向:看外键字段在哪个表

正向查询按字段,反向查询按表名小写,可能还要加_set.all()

多表查询

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

正向

# 一对多
    book_obj = models.Book.objects.filter(pk=2).first()
    # publish是book表里面的外键字段
    res = book_obj.publish
    print(res)
    print(res.name)
'''
Publish object (2)
北方出版社
'''

# 多对多
book_obj = models.Book.objects.filter(pk=2).first()
#当结果可能有多个的时候,要加.all()
res = book_obj.author.all().first()
print(res)
print(res.name)
'''
Author object (2)
tank
'''

 # 一对一
    author_obj = models.Author.objects.filter(name='jason').first()
    res=author_obj.author_detail
    print(res)
    print(res.phone)
'''
AuthorDetail object (3)
789
'''   
    

反向

在设计表的字段时,给字段加related_name参数,反向操作时使用的字段名,用于代替原反向查询时的:''表名_set"

# 一对多
# 查询东方出版社的书
    publish_obj = models.Publish.objects.filter(name='东方出版社').first()
    #当结果可能有多个的时候,要加_set.all()
    res=publish_obj.book_set.all()
    print(res)
'''
<QuerySet [<Book: Book object (1)>, <Book: Book object (3)>]>
'''
# 多对多
# 查询作者为Jason的书
    author_obj = models.Author.objects.filter(name='jason').first()
    res=author_obj.book_set.all()
    print(res)
'''
<QuerySet [<Book: Book object (1)>, <Book: Book object (3)>]>
'''
    
# 一对一    
# 查询手机号为123的作者姓名
    author_detail_obj=models.AuthorDetail.objects.filter(phone=123).first()
    res=author_detail_obj.author
    print(res.name)
'''
Author object (1)
egon
'''
    
联表操作(基于双下划线的跨表查询)
# 查询作者jason的手机号
#author_detail__phone表示跨到author_detail这个表里面,取phone字段
    res=models.Author.objects.filter(name='jason').values('author_detail__phone')
    print(res)
    '''
    <QuerySet [{'author_detail__phone': '789'}]>
    '''
    #filter也支持跨表,author__name表示跨到author取name字段
    res=models.AuthorDetail.objects.filter(author__name='jason').values('phone')
	print(res)
    '''
    <QuerySet [{'phone': '789'}]>
    '''
    
     # 查询id=1的书的出版社名称
        #正向
    res=models.Book.objects.filter(pk=1).values('publish__name')
    print(res)
    print(res.query)
    '''
    <QuerySet [{'publish__name': '东方出版社'}]>
    SELECT `app01_publish`.`name` FROM `app01_book` INNER JOIN `app01_publish` ON (`app01_book`.`publish_id` = `app01_publish`.`id`) WHERE `app01_book`.`id` = 1
    '''
    
    #反向
    
    res=models.Publish.objects.filter(book__id=1).values('name')
    print(res)
    '''
    <QuerySet [{'name': '东方出版社'}]>
    '''
    
    
    # 查询id=1的书的作者的手机号
    #author_detail是一个外键字段,author__author_detail表示跨到了authordetail这个表
    res=models.Book.objects.filter(pk=1).values('author__author_detail__phone')
    print(res)

聚合查询(聚合函数的使用)

在写sql语句时,一般先分组,后聚合

这里通过aggregate来使用聚合函数

聚合函数:max,min,sum,avg,count

只要是和数据库相关的模块,基本都在django.db.models里面

from django.db.models import Avg,Sum,Max,Min,Count
avg_price=models.Book.objects.aggregate(Avg('price'))
print(avg_price)
    
   res=models.Book.objects.aggregate(Avg('price'),Sum('price'),Max('price'),Min('price'),Count('price'))
print(res)  
'''
{'price__avg': Decimal('40.000000'), 'price__sum': Decimal('160.00'), 'price__max': Decimal('55.00'), 'price__min': Decimal('30.00'), 'price__count': 4}
'''

分组查询

mysql分组查询的特点:分组之后,默认只能获取到分组的依据,组内其他字段无法直接获取了

show variables like "%mode";

严格模式:ONLY_FULL_GROUP_BY

如果出现分组查询报错的情况,可能需要去掉数据库严格模式

models.Book.objects.annotate() 默认按照models后面的东西来分组

models.Book.objects.values('publish').annotate() #按照values内部指定的字段来分组

# 统计每本书的作者人数
from django.db.models import Avg,Sum,Max,Min,Count
#以Book作为分组依据,author_count是自定义的字段名字,author__id表示author外键对应的虚拟表的author_id字段
res=models.Book.objects.annotate(author_count=Count('author__id')).values('author_count')
print(res)
print(res.query)
'''
<QuerySet [{'author_count': 1}, {'author_count': 2}, {'author_count': 2}, {'author_count': 2}]>
SELECT COUNT(`app01_book_author`.`author_id`) AS `author_count` FROM `app01_book` LEFT OUTER JOIN `app01_book_author` ON (`app01_book`.`id` = `app01_book_author`.`book_id`) GROUP BY `app01_book`.`id` ORDER BY NULL

'''
#等价语句    
res=models.Book.objects.annotate(author_count=Count('author')).values('author_count')

'''
<QuerySet [{'author_count': 2}, {'author_count': 1}, {'author_count': 3}, {'author_count': 0}]>
'''

 # 查询每个出版社最便宜的图书的价格
    res=models.Publish.objects.annotate(min_price=Min('book__price')).values('min_price')
    print(res)
    
# 查询作者人数大于1的图书
  res=models.Book.objects.annotate(author_count=Count('author__id')).filter(author_count__gt=1).values('book_name','author_count')
    print(res)

只要orm语句的结果是QuerySet对象,就可以通过 . 的方式来使用QuerySet封装的方法

F查询

获取表中某个字段的数据

在操作字符类型的数据时,F不能直接做字符串的拼接

 # 查询卖出大于库存的图书
    from django.db.models import F
    #把每一条记录的卖出和库存进行比较
    res = models.Book.objects.filter(sold_out__gt=F('inventory'))

    print(res)
    '''
    <QuerySet [<Book: Book object (1)>, <Book: Book object (3)>]>
    
    
    '''
    
#所有书的价格-5    
models.Book.objects.update(price=F('price')-5)

# 在书名后面加文本:(热销)
from django.db.models import F,Value
    from django.db.models.functions import Concat
    models.Book.objects.update(book_name=Concat(F('book_name'),Value('(热销)')))

Q查询

filter方法内多个参数之间默认是and关系

 from django.db.models import Q
 #用Q包起来以后,用竖线| 表示或的关系,逗号表示且的关系,~Q(price__gt=40),~表示取反
    res=models.Book.objects.filter(Q(price__gt=40)|Q(sold_out__gt=150))
    print(res)
    
    
 q=Q()
    # q.connector= 'OR'
    q.connector= 'or' #默认是and关系

    q.children.append(('price__gt',40))
    q.children.append(('sold_out__gt',150))
    res = models.Book.objects.filter(q)
    print(res)
    

django中如何开启事务

# 开启事务
    from django.db import transaction
    try:
        with transaction.atomic():
            #sql语句
            # 在with代码块内的所有orm操作都属于同一个事务
    except Exception as e:
        print(e)

orm中常用字段类型及参数

AutoField	#主键字段,primary_key=True
CharField(max_length=32,verbose_name='xxx')	 #varchar
DecimalField(max_digits=8, decimal_places=2)
EmailField		#varchar(254)
DateField(auto_now_add=True)  #date
DateTimeField				#datetime
#auto_now 每次修改数据的时候都会更新当前时间
#auto_now_add 
BooleanField 	#该字段传布尔值True/False,数据库里面存0或1
TextField		#文本类型,用来存大段文本,没有字数限制
FileField		#参数:upload to ='路径',给该字段传一个文件对象,会自动将文件保存到这个路径下,数据库中存的是路径,不是文件本身


#外键字段
#以下两句等价
ForeignKey(unique=True) 
OneToOneField()
# db_index=True 参数表示为字段创建索引

还可以自定义字段类型

数据库查询优化

orm语句的特点:

惰性查询:如果仅仅只是写了一条orm语句,在后面根本没有用到该语句查询出来的结果,orm会自动识别这种情况,不执行该语句

#only和defer

 # only表示只取括号内的字段(默认带ID字),res的结果是对象,这些对象只有book_name字段
    #defer和only相反,
    res = models.Book.objects.only('book_name')
    for e in res:

        print(e.book_name)  
        # 这里可以取到其他字段的值,是因为重新到数据库中进行了查询
        print(e.price)

#select_related和prefetch_related

'''
    该查询是inner join,select_related内部将book表和publish表连接起来,
    然后一次性将大表里面的全部数据封装给查询出来的对象
    之后,不管是取book表还是publish表里面的数据,都不需要去数据库里面查询了
    select_related的参数是外键字段,一对一,一对多
'''
res = models.Book.objects.select_related('publish')
print( res.query)
for e in res:
    print(e.publish.name)
        
   
'''
SELECT `app01_book`.`id`, `app01_book`.`book_name`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id`, `app01_book`.`inventory`, `app01_book`.`sold_out`, `app01_publish`.`id`, `app01_publish`.`name`, `app01_publish`.`addr`, `app01_publish`.`email` FROM `app01_book` INNER JOIN `app01_publish` ON (`app01_book`.`publish_id` = `app01_publish`.`id`)
'''

'''
prefetch_related内部其实是子查询
'''
# 该语句效率低
res = models.Book.objects.prefetch_related('publish')
print(res.query)
for e in res:
    print(e.publish.name)
'''
SELECT `app01_book`.`id`, `app01_book`.`book_name`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id`, `app01_book`.`inventory`, `app01_book`.`sold_out` FROM `app01_book`
'''

choices参数

字段的所有取值能够列举出来的,都可以用choices参数

class Author(models.Model):
    name = models.CharField(max_length=16)
    age = models.IntegerField()
    gender_choices=(
        (1,'男'),
        (2,'女'),
        (3,'其他')

    )
    # 这个字段类型和内部元祖的第一个元素的类型相同
    gender=models.IntegerField(choices=gender_choices,null=True)
    
author_obj=models.Author.objects.filter(pk=1).first()
    # print(author_obj.gender)	#这样取出来的是编号
    print(author_obj.get_gender_display())	##这样取出来的是编号对应的内容

MTV和MVC模型

多对多的三种创建方式

#全自动
class Book(models.Model):
    ...
    author = models.ManyToManyField(to='Author')
    
class Author(models.Model):
    ...
#优点:代码不需要自己写,orm提供操作第三张关系表的方法
#缺点:第三张关系表的扩展性很差,没有办法添加字段


#纯手动
class Book(models.Model):
    ...
    
    
class Author(models.Model):
    ...
    
class Book2Author(models.Model):
    book_id=models.ForeignKey(to='Book',on_delete=models.CASCADE)
    author_id=models.ForeignKey(to='Author',on_delete=models.CASCADE)
    ...
#优点:第三张关系表的扩展性好
#缺点:不能使用orm提供的操作第三张关系表的方法,比如:add,remove,clear ,以及正反向查询


#半自动(一般使用这种)
class Book(models.Model):
    ...
    # 情况一
    author = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('book','author'))
    
    
class Author(models.Model):
    ...
    # 情况二
    # book = models.ManyToManyField(to='Book',through='Book2Author',through_fields=('author','book'))
'''
through_fields参数中字段先后顺序
通过第三张表查询对应的表 ,需要用到哪个字段,就把它放在前面
'''
    
class Book2Author(models.Model):
    #会自动在字段后面加:_id
    book=models.ForeignKey(to='Book',on_delete=models.CASCADE)
    author=models.ForeignKey(to='Author',on_delete=models.CASCADE)
    ...
    
#可以使用orm的正反向查询,不能使用add,remove,clear,set方法