Django
1 前后端传输数据的编码格式(contentType)
1.1 get请求与post请求
get请求
get请求直接将请求数据放到url中。
http://127.0.0.1:8080/test?arg1=val1&arg2=val2...
post请求
可以向后端发送post请求的方式。
- form表单
- ajax
1.2 前后端传输数据的编码格式
编码格式总共有三种:
- urlencoded
- formdata
- json
使用Content-Type指定编码格式。
1.2.1 form表单
- form表单默认的编码格式是urlencoded。
Content-Type: application/x-www-form-urlencoded
urlencoded的数据编码格式示例:username=abc&password=123
Django后端会自动对符合urlencoded编码格式的数据进行解析,封装到request.POST中。
- 如果将form表单的编码格式改为formdata,会将普通的键值对解析封装到request.POST中,将文件数据解析封装到request.FILES中。
- form表单不能发送json格式的数据。
1.2.2 Ajax
-
Ajax默认的编码格式是urlencoded。
urlencoded的数据编码格式示例:username=abc&password=123
-
Ajax可以发送json格式的数据。
前提:前后端传输数据时一定要确保声明的编码格式跟数据实际封装的格式是一致的。
在前端,将数据封装为json格式JSON.stringify(数据)
,并指定编码格式contentType: 'application/json'
。
<script>
$('#d1').click(function () {
$.ajax({
url: '',
type: 'post',
// 确保声明的编码格式(contentType)跟数据(data)实际封装的格式是一致的。
data: JSON.stringify({'username':'Ben', 'age':25}),
contentType: 'application/json', // 指定编码格式,默认是application/x-www-form-urlencoded。
success: function () {
}
})
})
</script>
发送的数据为{"username":"Ben","age":25}
request对象方法补充 is_ajax
用于判断当前请求是否为Ajax请求,返回布尔值。
request.is_ajax()
- request.POST不会接收json格式的数据,django后端也不会对json格式的数据做进一步处理。
- request.body可以用于接收json格式的数据,直接得到的是二进制格式的数据,需要在后端手动处理(解码+反序列化)。
def get_json(request):
if request.is_ajax():
print(request.body) # b'{"username":"Ben","age":25}'
# 手动处理
json_bytes = request.body # 获取二进制数据
json_str = json_bytes.decode('utf-8') # 解码:二进制数据 => json格式的字符串
json_dict = json.loads(json_str) # 反序列化:json格式的字符串 => 字典
return render(request, 'index.html')
json.loads(),括号内如果传入二进制格式数据,内部会自动进行解码再进行反序列化。
def get_json(request):
if request.is_ajax():
print(request.body) # b'{"username":"Ben","age":25}'
# 手动处理
json_bytes = request.body # 获取二进制数据
json_dict = json.loads(json_bytes) # 解码+反序列化:二进制数据 => json格式的字符串 => 字典
return render(request, 'index.html')
1.2.3 Ajax发送json格式数据总结
- 前端,将数据封装为json格式;
- 前端,指定编码格式:
contentType: 'application/json'
; - 后端,手动处理json格式数据,对request.body接收的二进制数据进行处理:解码 + 反序列化。
1.3 Ajax发送文件
使用Ajax发送文件需要使用js的内置对象FormData。
<script>
// 点击按钮后向后端发送普通键值对和文件数据。
$('#d4').on('click',function () {
// 1. 生成FormData内置对象
let formDateObj = new FormData();
// 2. 添加普通的键值对
formDateObj.append('username', $('#d1').val());
formDateObj.append('password', $('#d2').val());
// 3. 添加文件对象
// jquery对象[0] => js对象
// js对象.files[0] => 文件对象
formDateObj.append('myfile', $('#d3')[0].files[0])
// 4. 将对象发送给后端(基于ajax)
$.ajax({
url: '',
type: 'post',
data: formDateObj, // 直接将对象交给data参数即可
// 使用ajax发送文件需要指定两个参数
contentType: false, // 不需要使用任何编码,django后端能够自动识别formdata对象
processData: false, // 禁止浏览器对数据进行任何处理
success:function (args) {
}
})
})
</script>
def get_file(request):
if request.is_ajax():
if request.method == 'POST':
print(request.POST) # <QueryDict: {'username': ['Ben'], 'password': ['123']}>
print(request.FILES) # <MultiValueDict: {'myfile': [<InMemoryUploadFile>: Demo.txt (text/plain)]}>
return render(request, 'index.html')
总结:
- 需要利用内置对象FormData;
- 添加普通的键值对
formDateObj.append('username', $('#d1').val());
formDateObj.append('password', $('#d2').val());
- 添加文件对象
formDateObj.append('myfile', $('#d3')[0].files[0])
- 前端ajax指定两个关键参数
contentType: false,
processData: false,
- django后端能够直接识别formdata对象,将其内部的普通键值对数据封装到request.POST中,将文件数据封装到request.FILES中。
2 Django自带的序列化组件
一些项目前后端是分离的,这意味着无法直接利用django提供的模版语法来实现前后端的数据交互,需要将数据转换成前后端都能接收处理的格式,即json,一般的格式都是列表套字典。
需求:在前端获取后端用户表里面所有的数据,格式是列表套字典。
方式1:使用JsonResponse对象
from django.http import JsonResponse
def demo(request):
user_queryset = models.User.objects.all()
# 列表套字典 [{}, {}, {}, {}, {}]
user_list = []
for user_obj in user_queryset:
tmp = {
'pk': user_obj.pk,
'username': user_obj.username,
'age': user_obj.age,
'gender': user_obj.get_gender_display()
}
user_list.append(tmp)
return JsonResponse(user_list, safe=False)
发送的数据
[
{
"pk": 1,
"username": "Ben",
"age": 25,
"gender": "male"
},
{
"pk": 2,
"username": "Elliot",
"age": 21,
"gender": "male"
}
]
方式2:使用django的序列化组件serializers
from django.core import serializers
def demo(request):
user_queryset = models.User.objects.all()
# 序列化
# 自动将数据转换为json格式的字符串
res = serializers.serialize('json', user_queryset) # json格式的字符串
return HttpResponse(res)
发送的数据
[
{
"model": "app01.user",
"pk": 1,
"fields": {
"username": "Ben",
"age": 25,
"gender": 1
}
},
{
"model": "app01.user",
"pk": 2,
"fields": {
"username": "Elliot",
"age": 21,
"gender": 1
}
}
]
写接口工作就是利用序列化组件对数据进行渲染,然后写一个接口描述文档,将必要信息交代清楚。
3 Ajax结合SweetAlert
SweetAlert2:https://github.com/sweetalert2/sweetalert2/
SweetAlert2 示例:https://sweetalert2.github.io/#examples/
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@9"></script>
案例:Ajax + SweetAlert 实现删除的二次确认
models.py
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
gender_choices = (
(1, '男'),
(2, '女'),
(3, '保密'),
)
gender = models.IntegerField(choices=gender_choices)
urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^user/list', views.user_list),
url(r'^user/delete', views.del_user),
]
views.py
from django.shortcuts import render
from django.http import JsonResponse
from app01 import models
def user_list(request):
user_queryset = models.User.objects.all()
return render(request, 'user_list.html', locals())
def del_user(request):
if request.is_ajax():
if request.method == 'POST':
# 前后端进行交互时,通常向前端ajax的回调函数返回一个字典格式的数据。
back_dic = {'code': 1000, 'msg': ''}
del_id = request.POST.get('del_id')
models.User.objects.filter(pk=del_id).delete()
back_dic['msg'] = '数据成功删除。'
return JsonResponse(back_dic)
user_list.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@9"></script>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>name</th>
<th>age</th>
<th>gender</th>
<th>action</th>
</tr>
</thead>
<tbody>
{% for user_obj in user_queryset %}
<tr>
<td>{{ user_obj.name }}</td>
<td>{{ user_obj.age }}</td>
<td>{{ user_obj.get_gender_display }}</td>
<td>
<button class="btn btn-primary btn-xs del" del_id="{{ user_obj.pk }}">Delete</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<script>
$('.del').on('click', function () {
// 将当前标签对象临时存储起来
let currentBtn = $(this);
Swal.fire({
type: 'warning', // 弹框类型
title: '删除用户', //标题
text: "删除后将无法恢复,请谨慎操作!", // 显示内容
confirmButtonColor: '#3085d6', // 确定按钮的颜色
confirmButtonText: '确定', // 确定按钮的文字
showCancelButton: true, // 是否显示取消按钮
cancelButtonColor: '#d33', // 取消按钮的颜色
cancelButtonText: "取消", // 取消按钮的文字
focusCancel: true, // 是否聚焦取消按钮
reverseButtons: true, // 是否反转两个按钮的位置,默认位置:左边确定,右边取消
showLoaderOnConfirm: true // 显示等待
// preConfirm
}).then((isConfirm) => {
try {
// 判断是否点击确定按钮
if (isConfirm.value) {
// 向后端发送Ajax请求,删除数据。
$.ajax({
// 方式1
// url: '/user/delete/' + currentBtn.attr('del_id'),
// 方式2
url: '/user/delete/',
type: 'post',
data: {'del_id': currentBtn.attr('del_id')},
success: function (args) {
// 判断返回的状态码
if (args.code === 1000) {
Swal.fire("删除成功", args.msg, "success");
// 方式1:删除后刷新页面
// window.location.reload()
// 方式2:利用dom操作动态删除指定标签
currentBtn.parent().parent().remove()
} else {
Swal.fire("删除失败。")
}
}
});
Swal.fire("已删除", "删除成功", "success");
} else {
Swal.fire("取消", "取消删除", "error");
}
} catch (e) {
alert(e);
}
});
})
</script>
</body>
</html>
4 批量插入数据
def insert_data(request):
for i in range(1000):
models.Book.objects.create(title='第%s本书' % i)
book_queryset = models.Book.objects.all()
return render(request, 'insert_data.html', locals())
批量插入数据,使用orm提供的bulk_create能够大大减少操作时间。
def insert_data(request):
book_list = []
for i in range(1000):
book_obj = models.Book(title='第%s本书' % i)
book_list.append(book_obj)
models.Book.objects.bulk_create(book_list)
return render(request, 'insert_data.html', locals())
5 自定义分页器
5.1 分页器参数
参数 | 变量名 | 关系 |
---|---|---|
当前访问第几页 | current_page | |
每一页展示条数 | num_per_page | |
当前页数据起始位置 | start_current_page | start_current_page = (current_page - 1) * num_per_page |
当前页数据终止位置(顾头不顾尾) | end_current_page | end_current_page = current_page * num_per_page |
通过数据总数计算总页数
data_count = data_list.count() # 当前数据总数
page_count, remainder = divmod(data_count, num_per_page)
if remainder:
page_count += 1
Django中有自带的分页器模块,使用较麻烦且功能简单。
5.2 简单的自定义分页器
- 页面上一般显示奇数个页码。
- 使用for循环展示数据,但前面模版语法不支持range。
在后端生成html标签代码,再传给前端,注意设置safe模式。
page_num_html = ''
current_page_show = current_page
# 页码从数字1开始
if current_page < 6:
current_page = 6
# 页面上一般显示奇数个页码,这里取11个页码。
for i in range(current_page - 5, current_page + 6):
# 高亮显示当前页
if i == current_page_show:
page_num_html += '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i)
else:
page_num_html += '<li><a href="?page=%s">%s</a></li>' % (i, i)
<ul>
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
{{ page_num_html|safe }}
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
5.3 自定义分页器封装代码
使用前需要在前端导入Bootstrap框架。
page.py
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page < 1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, remainder = divmod(all_count, per_page_num)
if remainder:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
# 生成分页器的代码
def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
# 高亮显示当前页码
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list)
5.4 使用自定义分页器封装代码
当使用第三方提供的代码时,需要在项目根目录/app文件夹下创建名为utils的文件夹,并在utils文件夹内进一步进行功能性划分。
这里直接在项目根目录下创建utils文件夹,并将page.py放入utils文件夹中。
使用前一定要导入bootstrap框架,
views.py
from django.shortcuts import render
from utils.page import Pagination
def show_data(request):
current_page_num = request.GET.get('page', 1) # 参数1
book_queryset = models.Book.objects.all()
all_page_count = book_queryset.count() # 参数2
# 1. 生成Pagination对象
page_obj = Pagination(
current_page=current_page_num,
all_count=all_page_count,
per_page_num=10, # 每一页展示10条数据
pager_count=11 # 展示11个页码
)
# 2. 对数据整体进行切片操作
page_queryset = book_queryset[page_obj.start: page_obj.end]
# 3. 将page_queryset传递给前端
return render(request, 'demo.html', locals())
demo.html
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<!-- 展示的数据 -->
{% for book in page_queryset %}
<p>{{ book.title }}</p>
{% endfor %}
<!-- 分页器 -->
{{ page_obj.page_html|safe }}
</div>
</div>
</div>