文章目录
数据库的CRUD
- Create——新增
- Read——读/查询
- Update——修改
- Update——修改
模型的创建
from django.db import models
class CommonModule(models.Model):
# 自定义模型的基类
create_time = models.DateTimeField('注册时间', auto_now_add=True)
update_time = models.DateTimeField('修改时间', auto_now=True)
class Meta:
abstract = True
class User(CommonModule):
USER_STATUS = (
(1, '正常'),
(0, '删除')
)
username = models.CharField('用户名', max_length=128, unique=True)
password = models.CharField('密码',max_length=256)
nickname = models.CharField('昵称', max_length=256, null=True, blank=True)
avatar = models.ImageField('用户头像', upload_to='avatar', null=True, blank=True)
status = models.SmallIntegerField('用户状态', default=1, choices=USER_STATUS)
is_super = models.BooleanField('是否为超级用户', default=False)
class Meta:
db_table = 'accounts_user'
class UserProfile(CommonModule):
# 用户详细信息
SEX_CHOICES = (
(0, '未知'),
(1, '男'),
(2, '女')
)
user = models.OneToOneField('User', verbose_name='关联用户',
on_delete=models.CASCADE,
related_name='profile')
username = models.CharField('用户名', max_length=128, unique=True)
real_name = models.CharField('用户名', max_length=128, null=True, blank=True)
sex = models.SmallIntegerField('用户性别', default=0, choices=SEX_CHOICES)
maxim = models.CharField('用户格言', max_length=128, null=True, blank=True)
address = models.CharField('用户地址', max_length=128, null=True, blank=True)
class Meta:
db_table = 'accounts_user_profile'
class LoginHistory(models.Model):
user = models.ForeignKey(User, related_name='login_history_list',
on_delete=models.CASCADE,
verbose_name='关联的用户')
username = models.CharField('用户名', max_length=128)
login_type = models.CharField('账号平台', max_length=128)
ip = models.CharField('IP地址', max_length=32, default='')
ua = models.CharField('登陆来源', max_length=128, default='')
create_time = models.DateTimeField('注册时间', auto_now_add=True)
class Meta:
db_table = 'accounts_login_history'
# 倒叙排列
ordering = ['-create_time']
使用ORM实现数据新增
1.Django shell
从控制台(terminal)进入 python manage.py shell
从pyCharm进入
2.使用ORM新增数据
2.1 使用save()保存数据
author=Author(name='aaaa')
author.save()
或
author=Author()
author.name="bbb"
author.save()
2.2 使用create()新增数据
使用create只需要一行代码,更简单。
Author.objects.create(name='test')
或
info={'sex':0,'email':'[email protected]','address':'上海','author_id':1}
AuthorDetail.objects.create(**info)
2.3 使用bulk_create()批量新增数据
user1 = User(username='admin', password='password')
user2 = User(username='manager', password='password')
user_list = [user1, user2]
User.objects.bulk_create(user_list)
3.外键关联数据的插入
举例:记录用户登录的历史
user = User(username='admin', password='password')
LoginHistory.objects.create(user=user,*args, **kwargs)
使用ORM修改数据
使用save()修改单条数据
user_obj = User.objects.get(pk=1)
user_obj.nickname = '管理员'
user_obj.save()
使用update()批量修改数据
count = User.objects.all().update(*args, **kwargs)
count
>>> 3
或
author=Author.objects.filter(id=1).update(name='yqh')
注意不能修改外键关联的对象。
使用bulk_update()批量修改数据
obj:需要修改的记录列表
fields:指定需要修改的字段
batch_size:每次提交多少条记录进行修改
使用ORM删除数据
1.使用ORM物理删除数据
使用模型的delete()删除数据,
- 删除单条数据
user_obj = User.objects.get(pk=1)
user_obj.delete()
- 删除多条数据(批量删除)
2.使用ORM逻辑删除数据
实际上是做了修改的操作。
物理删除和逻辑删除
物理删除:将数据从数据库删除,删除后找不回来,不占磁盘空间。
逻辑删除:将数据标记删除,删除后还可以恢复,占用磁盘空间,删除后通过查询条件不展示给用户。
如果是多对多的关系: remove()和clear()方法
book = Book.objects.filter(id=1)
book.author.clear() #清空与book中id=1 关联的所有数据
book.author.remove(2) #可以为id
book.author.remove(*[1,2,3,4]) #可以为列表,前面加*
author = Author.objects.filter(id=1)
author.book_set.clear() #清空与book中id=1 关联的所有数据
使用ORM查询
返回QuerySet对象:
- all()
- filter()
- exclude()
- order_by()
- reverse()
- distinct()
返回特殊QuerySet
- values():返回一个可迭代的字典序列
- values_list():返回一个可迭代的元祖序列
返回具体对象
- get()
- first()
- last()
返回布尔值
- exists()
返回数字
- count()
1.查询单条数据
- get(**kwargs): 按照查询条件返回单条数据
- latest(*fields)/earliest(*fields) 返回最晚/最早的一条记录
使用get()查询单条数据
Author.objects.get(name='aaaa')
<Author: aaaa>
使用latest()和earliest()查询数据
latest = LoginHistory.objects.latest('created_at')
latest
>>> <LoginHistory: LoginHistory object (4)>
earliest = LoginHistory.objects.earliest('created_at')
earliest
>>> <LoginHistory: LoginHistory object (1)>
注意:
- 注意异常的处理:
- DoesNotExist:查询的记录不存在
- MultipleObjectsReturned:查询的记录有多条
- get_or_create()
若有则返回,若无则创建后返回。
- get_object_or_404(klass, *args, **kwargs)
如果没有则触发404异常。
2.使用all()查询所有数据
Author.objects.all()
<QuerySet [<Author: yang>, <Author: test>, <Author: aaaa>, <Author: bbb>]>
3.filter()
筛选出满足条件的多条记录
Author.objects.filter(name='aaaa')
<QuerySet [<Author: aaaa>]>
4.exclude()
exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象
Book.objects.all().exclude(title='shell')
<QuerySet [<Book: python>]>
Book.objects.exclude(title='shell')
<QuerySet [<Book: python>]>
5.order_by()
对查询结果排序
6.values()和values_list()
values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列
Book.objects.all().values('title')
<QuerySet [{'title': 'shell'}, {'title': 'python'}]>
values_list() 方法用于查询部分字段的数据。
values_list(*field): 它与values()非常相似,它返回的是一个元组序列。
查询条件
1. 相等、等于、布尔条件
1.1 exact 等于**值(默认的形式)
如:id__exact=6 或者 id=6
1.2 iexact 像 **值
如:name__iexact=‘zhangsan’
1.3 布尔条件:
- gt 大于某个值
- gte 大于或等于某个值
- lt 小于某个值
- lte 小于或等于某个值
2.是否包含**字符串
2.1 contains 包含值
如:name__contains=‘san’
2.2 icontains 包含值,不区分大小写
如:name__contains=‘san’
#ZhangSan和zhangsan都满足条件。
3.以**开始/结束
开始:startswith,istartswith
结束:endswith,iendswith
4.日期及时间
date 日期
year 年
month 月份
day 天
hour/minute/second 时分秒
week/week_day 星期
7.双下划线
7.1 单表查询
Book.objects.filter(id__lt=4,id__gt=0) # 获取id大于1 且 小于10的值
#<QuerySet [<Book: shell>, <Book: python>]>
Book.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据
Book.objects.exclude(id__in=[11, 22, 33]) # not in
Book.objects.filter(title__contains="p") #title中包含'p' 区分大小写
Book.objects.filter(title__icontains="p") # icontains大小写不敏感
Book.objects.exclude(id__range=[1, 2]) # 范围bettwen and
7.2 多表条件关联查询
正向查找:属性名称__跨表的属性名称
正向查找:查询书籍为php的出版社的名字
Book.objects.filter(title='php').values('publisher__name')
#<QuerySet [{'publisher__name': '上海出版社'}]>
正向查找:查询书籍为php的作者的姓名
Book.objects.filter(title='php').values('authors__name')
#<QuerySet [{'authors__name': 'yang'}]>
正向查找的publisher__name或者authors__name中的publisher,authors是book表中绑定的字段。一对多和多对多在这里用法没区别
反向查找:从外键不在本表开始查询对应关系表的数据
反向:小写类名__跨表的属性名称
查询上海出版社出版的书名
Publisher.objects.filter(name='上海出版社').values('book__title'))
#<QuerySet [{'book__title': 'shell'}, {'book__title': 'php'}]>
根据Publisher查询书籍为Python的出版社的名字
Publisher.objects.filter(book__title='Python').values('name')
#<QuerySet [{'name': '北京出版社'}]>
Publisher.objects.filter(book__title='php').values('book__authors')
#<QuerySet [{'book__authors': 1}]>
查询书籍为php作者的名字
Author.objects.filter(book__title='php').values('name')
#<QuerySet [{'name': 'yang'}]>
8.聚合查询(聚合函数)
from django.db.models import Avg,Max,Min,Count,Sum
返回值:字典。键的名称默认是(属性名称加上__聚合函数名),值是计算出来的聚合值。
聚合函数 aggregate(别名 = 聚合函数名("属性名称"))
通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。
示例:计算所有图书的平均价格:
from django.db.models import Avg,Max,Min,Count,Sum # 引入函数
...
res = models.Book.objects.aggregate(Avg("price"))
print(res, type(res))
...
结果:{‘price__avg’:Decimal (‘200.000000’)}
9.分组查询(annotate)
返回值:
- 分组后,用 values 取值,则返回值是 QuerySet 数据类型里面为一个个字典;
- 分组后,用 values_list 取值,则返回值是 QuerySet 数据类型里面为一个个元组。
示例:统计每一个出版社的最便宜的书的价格
res = models.Publish.objects.values("name").annotate(in_price = Min("book__price"))
print(res)
结果:<QuerySet [{'name': '菜鸟出版社', 'in_price': Decimal('100.00')}, {'name': '明教出版社', 'in_price': Decimal('300.00')}]>
示例:统计不止一个作者的图书名称
res = models.Book.objects.annotate(c = Count("authors__name")).filter(c__gt=0).values("title","c")
print(res)
结果:<QuerySet [{'title': '菜鸟教程', 'c': 1}, {'title': '吸星大法', 'c': 1}, {'title': '冲灵剑法', 'c': 1}]>
示例:查询各个作者出的书的总价格
res = models.Author.objects.annotate(all = Sum("book__price")).values("name","all")
print(res)
10.F() 查询
F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
from django.db.models import F
用法:F("字段名称")
F 动态获取对象字段的值,可以进行运算。Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取余的操作。
修改操作(update)也可以使用 F() 函数。
示例:查询工资大于年龄的人
from django.db.models import F
...
book=models.Emp.objects.filter(salary__gt=F("age")).values("name","age")
...
示例:将每一本书的价格提高100元
res = models.Book.objects.update(price=F("price")+100)
print(res)
11.Q()查询
使用Q()函数实现复杂的查询。Q()函数支持&和|,对应SQL中的AND和OR
优先级从高到低:~ & |。
from django.db.models import Q
用法:Q(条件判断)
示例:Q(title__startswith="菜")
可以混合使用 Q 对象和关键字参数,Q 对象和关键字参数是用"and"拼在一起的(即将逗号看成 and ),但是 Q 对象必须位于所有关键字参数的前面。
示例:查询价格大于 350 或者名称以菜开头的书籍的名称和价格。
res=models.Book.objects.filter(Q(price__gt=350)|Q(title__startswith="菜")).values("title","price")
print(res)
示例:查询出版日期是 2004 或者 1999 年,并且书名中包含有"菜"的书籍。
res = models.Book.objects.filter(Q(pub_date__year=2004) | Q(pub_date__year=1999), title__contains="菜")
print(res)
12.查询优化
哪些查询比较慢?到底执行了多少次查询?
1.QuerySet.query属性查看执行的SQL。
q = User.objects.all()
print(q.query)
>>> SELECT……
2.django-debug-tool
安装:pip install django-debug-tool
使用:详情可看https://django-debug-toolbar.readthedocs.io/en/latest/installation.html
settings.py
INSTALLED_APPS = [
# ...
'django.contrib.staticfiles',
# ...
'debug_toolbar',
]
STATIC_URL = '/static/'
在urls.py中:
import debug_toolbar
from django.conf import settings
from django.urls import include, path
urlpatterns = [
...
path('__debug__/', include(debug_toolbar.urls)),
]
settings.py中加:
放在最前面
MIDDLEWARE = [
# ...
'debug_toolbar.middleware.DebugToolbarMiddleware',
# ...
]
INTERNAL_IPS = [
# ...
'127.0.0.1',
# ...
]
示例:
ORM查询优化示例
显示页面:
右侧就是django-debug-toolbar,点击SQL可以看到SQL语句的相关信息
优化外键关联查询
QuerySet.select_related()
将外键关联的对象查询合并到主查询,一次性查询结果,减少SQL执行的数量。
示例:
views.py
……
profile_list = UserProfile.objects.all().select_related('user')
……
使用SQL查询
方式一:使用管理器的raw(sql)函数
返回django.db.models.query.RawQuerySet实例
示例:
# 使用SQL查询
user_list = User.objects.raw('SELECT * FROM `accounts_user`')
方式二:获取数据库连接、游标,直接执行sql
获取数据库连接:from django.db import connection
从连接得到游标:cursor = connection.cursor()
执行SQL:cursor.execute("SELECT * FROM table WHERE baz=%s, [baz])
查询结果:row = cursor.fetchone()
13.分页查询
分页的实现
1.对查询结果集QuerySet进行分页
返回前n个对象 User.objects.all()[:10]
返回第11到第20个对象 User.objects.all()[10:20]
def user_list_slice(request):
""" 分页-使用切片 """
page = request.GET.get('page', 1)
try:
page = int(page)
except:
# 出现异常默认设置为第一页
page = 1
# 每页放多少条数据
page_size = 10
# user_list = User.objects.all()[0: 10]
# user_list = User.objects.all()[10: 20]
start = (page - 1) * page_size
end = page * page_size
user_list = User.objects.all()[start: end]
return render(request, 'user_list_slice.html', {
'user_list': user_list
})
2.使用django.core.paginator进行分页处理
优点:功能更完善,对异常的处理很健壮
步骤一:取得分页器Paginator(objects, page_size)
objects:分页的数据
page_size:每页数据的多少
步骤二:取得页面实例page = p.get_page(page_num)
def user_list_paginator(request):
""" 分页-使用分页器 """
page = request.GET.get('page', 1)
user_list = User.objects.all()
# 第一步,取得分页器
p = Paginator(user_list, 15)
# 第二步,取得某一页的对象(实例)
# page_data = p.get_page(page)
try:
page_data = p.page(page)
except EmptyPage as e:
print('页面不存在')
raise Http404
except PageNotAnInteger as e:
print('无效的页码')
raise Http404
return render(request, 'user_list_paginator.html', {
'page_data': page_data
})
异常处理:
- InvalidPage 无效的页码
- PageNotAnInteger 页码必须是整数
- EmptyPage 空页(没有数据)
3.使用ListView进行分页
使用面向对象的方式来实现的。
page_obj 分页数据,如页码、当前第几页
object_list 当前页的数据列表
class UserListView(ListView):
""" 分页处理, 面向对象 """
# 对应的模板
template_name = 'user_list_class.html'
# 对应的ORM模型
model = User
# 页面大小
paginate_by = 20
# 获取页面的参数(当期第几页)
page_kwarg = 'p'
模型管理器(Manager)
Manager是Django的模型进行数据库查询操作的接口,每个模型都拥有至少一个Manager,Django为每个模型类添加一个名为objects的默认Manager。
自定义管理器
添加新的管理器
from django.db import models
class User(models.Model):
#...
users = models.Manager()
使用:
user_list = User.users.all()
user_list.count()
>>> 104
一般情况下不需要自定义。
聚合与统计
Django内置聚合函数:
- Sum 求和
- Avg 求平均数
- Count 计数
- Max/Min 最大值/最小值
数据统计
使用aggregate从整个查询结果集生成统计数据
示例1:求某个学生期末成绩的总和。
grade_list = Grade.objects.filter(student_name='张三')
total = grade_list.aggregate(total_score=Sum('score'))
print(total)
return render(request, 'page_grade.html', {
'total': total
})
示例2:求某一科目成绩的平均分
subject = Grade.objects.filter(subject_name='语文').aggregate(
max=Max('score'),
min=Min('score'),
avg=Avg('score'),
)
aggregate返回一个结果。
聚合查询
使用annotate为查询结果集的每一项生成统计数据
示例3:求每个学生期末成绩的总和
stu_list = Student.objects.annotate(total=Sum('stu_grade__score'))
return render(request, 'page_grade.html', {
'student': student,
'subject': subject,
'stu_list': stu_list
})
annotate在列表中使用,返回多个结果。
事务处理
什么是事务
例如:银行转账
事务:多个数据库逻辑操作的集合
回滚:多个逻辑中某个操作出错,回到初始状态。
事务的原子性要求事务要么全部完成,要么全部不完成,不可能停滞在某个中间状态。
在django中使用事务
1.自动提交:使用装饰器
示例:
from django.db import transaction
@transaction.atomic()
def user_signup_trans(request):
""" 事务的使用-装饰器 """
username = '13000000003'
user = User.objects.create(username=username,
password='123456',
nickname='王五')
profile = UserProfile.objects.create(user=user, usernamex=username)
return HttpResponse('ok')
过程解析:代码进入到最外层的atomic代码块时会打开一个事务,进入到内层atomic代码块会创建一个标记,退出内部块时会释放或回滚至标记,退出外部块时提交或回退事务。
2.自动提交:使用with语法
def user_signup_trans_with(request):
""" 事务的使用-with语法"""
with transaction.atomic():
username = '13000000005'
user = User.objects.create(username=username,
password='123456',
nickname='王五')
profile = UserProfile.objects.create(user=user, usernamex=username)
return HttpResponse('ok')
3.手动提交和回滚
def user_signup_trans_hand(request):
""" 事务的自动提交 """
username = '13000000007'
# 放弃自动提交事务
transaction.set_autocommit(False)
try:
user = User.objects.create(username=username,
password='123456',
nickname='王五')
profile = UserProfile.objects.create(user=user, username=username)
# profile = UserProfile.objects.create(user=user, usernamex=username)
# 手动提交事务
transaction.commit()
# 4. 反馈结果:成功/失败
return HttpResponse('ok')
except Exception as e:
# user.delete()
print(e)
# 手动控制事务,实现回滚
transaction.rollback()
return HttpResponse('no')