本篇文章重点概要:
1.如何使用ORM框架对数据库进行更、删、改的操作?
2.如何使用path转换器和ORM在网页实现对数据库的更改?
3.F对象和Q对象
4.聚合查询和原生数据库查询
1.ORM-查询方法
常见查询方法
数据库的查询需要使用管理器对象进行,通过MyModel.objects管理器方法调用查询方法,以下提供几种常见的查询的方法:
- all()方法
使用方法:MyModel.object.all() |
对应的sql语句:select * from table |
解释:QuerySet容器对象(类似列表),内部存放MyModel实例对象 |
这里我们在django自带的shell环境中进行查询,下面根据之前的bookstore案例查询所有书的标题:
#python3 manage.py shell
from bookstore.models import Book
a1 = Book.objects.all()
for book in al:
print(book.title)
- value()方法
解释:查询部分列的数据并返回QuerySet容器对象,内部元素为字典 |
例如:book.objects.value('title')会返回所有书的标题 |
- values_list()方法
解释:返回元组形式的查询结果,需要用索引定位 |
- order_by()
作用:对查询结果进行排序 |
解释:默认升序排列,返回QuerySet,内容元素为MyModel实例对象 |
例如:a4 = Book.objects.order_by('-price') 按价格降序排列 a5 = Book.objects.values('title').order_by('-price') 按价格降序排序显示书名(无论顺序) select * from book\G;格式化输出 print(a4.query)可以输出对应的SQL语句 |
如何实现条件查询?
上述方法只能对数据表进行总体查询,以下提供几种常见的条件查询的方法:
- filter(条件)
使用方法:myModel.objects.filter(属性1=值1,属性2=值2) |
解释:返回QuerySet容器对象,多个属性查询为and关系 |
例如:authors = Author.objects.filter(name='xx',age=28) |
- exclude(条件)
除满足后面条件之外的记录都返回,返回QuerySet容器对象 |
- get(条件)
作用:返回满足条件的一条数据 |
解释:查询结果多于一条或没有都会报错,但会抛出不同异常,所以一定要异常处理(try..except) |
上述方法只能做等值查询,下面提供一个新的方式做做非等值的过滤查询:
查询谓词:
解释:每一个查询谓词时一个独立的查询功能,提供多种查询谓词 |
语法:字段(类属性)+双下划线+方法 |
- __exact
例如:id__exact=1 等值匹配,查询id==1的记录 |
- __contains:
例如:name__contains = 'w' ,name包含指定值'w' 对应的sql语句:select * from author where name like '%w%' |
__startswith 对应上述例子就是以w开头的name |
__endswith 以w结尾的name |
- _gt,__gte,__lt,__lte
上述从左到右分别是[大于,大于等于,小于,小于等于] |
- __in
例如:country__in = ['中国','日本','美国'] |
- __range
例如:age__range =(35,50) |
还有很多的查询谓词,请上django官网查询文档
2. ORM-更新操作
更改单个实体的某些字段
django shell
- 查:b1 = Book.objects.get(id=1)
- 改:b1.price = 22
- 保存:b1.save()
批量数据更新
可以直接调用QuerySet的update(属性=值)方法,以下为案例,让id>3的book价格定为0:
- books = Book.objects.filter(id__gt=3)
- books.update(price=0)
3. ORM-删除操作
一般删除
单个数据
- 查询结果对应的一个数据对象
- 对象.delete()删除
多个数据
- 查找查询结果集中满足条件的全部QuerySet集合对象
- 对象.delete()删除
以下示例对id为1的书的信息实现删除(记得使用get方法一定要异常处理):
try:
b1 = Book.objects.get(id=1)
b1.delete()
#auths = Author.objects.filter(age__gt=65)
#auths.delete()
except:
print('删除失败')
伪删除
在实际环境中,用户的数据是很宝贵的,直接使用delete方法删除后数据是找不回来的,所以我们采用在模式中添加一个布尔型字段(is_active),默认True,将欲删除数据的is_active=False,过段时间后通过脚本自动删除,最后一定要注意所有要显示数据的地方加入过滤条件 is_active=True,否则依然会向用户显示全部数据。
4. 练习:在网页显示数据并对其实现更改和伪删除
创建‘查看所有书籍’的页面
(此次实验从创建bookstore应用文件夹开始进行)
创建bookstore应用文件夹:
python3 manage.py startapp bookstore
1.数据库的初始化
在应用文件夹下的models.py模版文件中写入数据库的模式:
from django.db import models
# Create your models here.
class Book(models.Model):
title = models.CharField('书名', max_length=50, default='',unique=True)
pub = models.CharField('出版社', max_length=100, default='')
price = models.DecimalField('价格',max_digits=7, decimal_places=2, default=0.0)
market_price = models.DecimalField('零售价', max_digits=7, decimal_places=2 ,default=True)
is_active = models.BooleanField('是否活跃', default=True)
class Meta:
db_table = 'book'
def __str__(self):
return "%s,零售价:%s" %(self.title, self.market_price)
class Author(models.Model):
name = models.CharField('姓名', max_length=11)
age = models.IntegerField('年龄', default=True)
email =models.EmailField('邮箱', null=True)
class Meta:
db_table = 'author'
在settings.py文件中配置数据库并添加应用:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mysite2',
'USER':'root',
'PASSWORD':'324195',
'HOST':'127.0.0.1',
'PORT':'3306',
}
}
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bookstore',
]
mysql下创建mysite2数据库,然后进行数据迁移:
在django shell中导入数据:
2.创建分布式路由
主路由添加映射:
path('bookstore/',include('bookstore.urls')),
bookstore应用文件夹下创建urls.py文件并写出路由映射:
path('all_book',views.all_book),
3.具体实现
在bookstore文件夹的views.py写入处理请求的函数:
def all_book(request):
#all_book = Book.objects.all()
all_book = Book.objects.filter(is_active=True) #进行伪删除时显示is_active为True的部分
return render(request, 'bookstore/all_book.html', locals())
在bookstore文件夹下创建templates文件夹,templates下创建bookstore文件夹,最后在里面创建all_book.html文件:
<html lang=en>
<head>
<meta charset='UTF-8'>
<title>查看所有书籍</title>
</head>
<body>
<table border="1">
<tr>
<th> id </th>
<th>title</th>
<th>pub</th>
<th>price</th>
<th>market_price</th>
<th>op</th>
</tr>
{% for book in all_book %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.title }}</td>
<td>{{ book.pub }}</td>
<td>{{ book.price }}</td>
<td>{{ book.market_price }}</td>
<td>
<a href="">更新</a>
<a href="">删除</a>
</td>
</tr>
{% endfor %}
</body>
</html>
最终呈现结果如下:
实现op下的更新和删除操作
通过前端修改后端数据库有两种方式:
- 使用path转换器,通过url来判断是哪本书
- 查询字符串判断点击的时哪本书
这里更新我们使用第一种,删除使用第二种方法实现,在all_book.html中添加更新和删除的目的跳转相对路径:
<a href="/bookstore/update_book/{{ book.id }}">更新</a>
<a href="/bookstore/delete_book?book_id={{ book.id }}">删除</a>
上述是针对每本书唯一的主键id进行更新和删除,接下来在应用文件夹下urls.py添加路径映射:
path('update_book/<int:book_id>', views.update_book),
path('delete_book', views.delete_book),
在与all_book.html同级目录下创建update_book.html:
<html lang=en>
<head>
<meta charset='UTF-8'>
<title>更改书籍</title>
</head>
<body>
<form action= "/bookstore/update_book/{{ book.id }}" method="post">
<p>
title <input type="text" value="{{ book.title }}" disabled="disabled">
</p>
<p>
pub <input type="text" value="{{ book.pub }}" disabled= "disabled">
</P>
<p>
price <input type="text" name="price" value="{{ book.price }}" >
</P>
<p>
market_price <input type="text" name="market_price" value="{{book.market_price}}">
</P>
<p>
<input type="submit" value="更新">
</p>
</body>
</html>
需要注意的是title和pub都是不能改变的,所以在form表单中设置disabled。
在views.py中处理更新请求:
def update_book(request,book_id):
try:
book = Book.objects.get(id=book_id, is_active=True)
except Exception as e:
print('--update book error is %s'%(e))
if request.method == 'GET':
return render(request, 'bookstore/update_book.html', locals())
elif request.method == 'POST':
price = request.POST['price']
market_price = request.POST['market_price']
book.price = price
book.market_price = market_price
book.save()
return HttpResponseRedirect('/bookstore/all_book')
分别对request的GET、POST方法进行处理,如果是请求就直接跳转到修改页面,如果是提交就把数据提交后跳转到all_book.html。
另外再添加处理删除请求的方法:
def delete_book(request):
book_id = request.GET.get('book_id')
if not book_id:
return HttpResponse('--请求异常')
try:
book = Book.objects.get(id=book_id, is_active=True)
except Exception as e:
print('---delete book get error %s' % (e))
return HttpResponse('---The book id is error')
book.is_active = False
book.save()
return HttpResponseRedirect('/bookstore/all_book')
最终结果如下:
修改python的数值
随机(伪)删除几本书:
在mysql中验证一下是否修改:
至此所有的功能都已实现,试验完成!
5. F对象和Q对象
- F标记对象
- Q实现 与或非
F对象
F对象:不获取的情况下对数据库中的字段值进行操作,用于类属性(字段)之间的比较
导入 | from django.db.models import F |
使用方法 | F('列名') |
示例:更新Book实例中所有书市场价+10
- 一般方法:
books = Book.object.all()
for book in books:
book.market_price = book.market_price+10
book.save()
- 使用F对象:
Book.objects.all().update(market_price=F('market_price')+10)
其中方法二是直接使用python计算好数值在一次性提交给数据库,而方法一是要进行两次查询,拿到数据后使用sql语句提交更新。
点赞数问题
资源竞争:同时处理一个相同的字段
def add_like(request, topic_id):
topic = Topic.objects.get(id=topic_id)
# F对象
topic.like = F('like')+1
topic.save()
# 普通方法
topic.like = topic.like+1
topic.save()
- F对象:update topic set like+=1 where id = xxx
- 普通方法:update topic set like = 1 where id = xxx
Q对象
Q对象:使用复杂的逻辑或、逻辑非等进行操作,总共有|&~(或与非)三种操作
示例:Book.objects.filter(Q(price_gt=20)|Q(pub="清华大学出版社"))
6. 聚合查询和原生数据库操作
聚合查询
聚合查询:对一个数据表中的一个字段的数据进行部分或全部进行统计查询,分为两种:
- 整表聚合
导入: | from django.db.models import * |
提供的聚合函数: | Sum,Avg, Count, Ma,Min等 |
使用方法: | MyModel.objects.aggregate(结果变量名=聚合函数('列')) |
解释: | 返回结果:{'结果变量名':"值"} |
示例: | from django.db.models import Count Book.objects.aggregate(res=Count('id')) {'res': 5} |
- 分组聚合
语法: | QuerySet.annotate(结果变量名=聚合函数('列')) |
返回值: | QuerySet |
示例: | bs = Book.objects.values('pub') 查询部分列的结果 bs.annotate(res=Count('id')).filter(res__gt=2)对结果实现having的效果 |
原生数据库操作
提供两种方法:
- raw()方法
- django体系下的游标cursor
raw()方法
导入: | MyModel.objects.raw(sql语句,拼接参数) 【专门负责查询】 |
返回值: | RawQuerySet集合对象【只支持基础操作,比如循环】 |
但是使用raw方法可能会出现SQL注入攻击,
什么是SQL注入?
- 定义:用户通过数据上传,将恶意的sql语句提交给服务器
比如在实际应用中的查找好友:
sql = select * from user where id = '%s' % (fid)
f = User.objects.raw(sql)
当提交的fid为 '1 or 1=1' ,那么整个SQL语句拼接起来就会因为1=1把数据库中的所有数据查出来!
又或者fid = '1#' ,其中的#可能把sql语句后的password字段注释掉!
解决方法:
- 使用拼接参数让django转义(加入双引号)
示例:sql = Book.objects.raw('select * from bookstore_book where id=%s', ['1 or 1=1'])
在上述示例中 ,由于sql的特性,sql语句只看到字符传中第一个字符 1。
django体系下的游标cursor
完全跨过模型类操作数据库-查询、更新、删除
导入 | from django.db import connection |
使用方法 | with connection.cursor() as cur: # 创建cursor对象,使用with在出现异常时能释放cursor资源 cur.execute('sql语句','拼接参数') |
建议 | 还是不推荐,不能返回查询结果,只能返回受影响的数据条数,查询使用raw() |
课程地址:2021最新版Django全套视频(django框架快速上手)_Python全栈_哔哩哔哩_bilibili