9 Django 传输数据的编码格式 Ajax发送json数据/文件 sweetalert二次确认 序列化组件 批量操作 分页器

Django

1 前后端传输数据的编码格式(contentType)

1.1 get请求与post请求

get请求
get请求直接将请求数据放到url中。

http://127.0.0.1:8080/test?arg1=val1&arg2=val2...

post请求
可以向后端发送post请求的方式。

  1. form表单
  2. ajax
1.2 前后端传输数据的编码格式

编码格式总共有三种:

  1. urlencoded
  2. formdata
  3. json

使用Content-Type指定编码格式。

1.2.1 form表单
  1. form表单默认的编码格式是urlencoded。
Content-Type: application/x-www-form-urlencoded

urlencoded的数据编码格式示例:username=abc&password=123

Django后端会自动对符合urlencoded编码格式的数据进行解析,封装到request.POST中。

  1. 如果将form表单的编码格式改为formdata,会将普通的键值对解析封装到request.POST中,将文件数据解析封装到request.FILES中。
  2. form表单不能发送json格式的数据。
1.2.2 Ajax
  1. Ajax默认的编码格式是urlencoded。
    urlencoded的数据编码格式示例:username=abc&password=123

  2. 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()
  1. request.POST不会接收json格式的数据,django后端也不会对json格式的数据做进一步处理。
  2. 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格式数据总结
  1. 前端,将数据封装为json格式;
  2. 前端,指定编码格式:contentType: 'application/json'
  3. 后端,手动处理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')

总结

  1. 需要利用内置对象FormData;
  2. 添加普通的键值对
formDateObj.append('username', $('#d1').val());
formDateObj.append('password', $('#d2').val());
  1. 添加文件对象
formDateObj.append('myfile', $('#d3')[0].files[0])
  1. 前端ajax指定两个关键参数
contentType: false,
processData: false,
  1. 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_pagestart_current_page = (current_page - 1) * num_per_page
当前页数据终止位置(顾头不顾尾)end_current_pageend_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 简单的自定义分页器
  1. 页面上一般显示奇数个页码。
  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">&laquo;</span>
		</a>
		{{ page_num_html|safe }}
		<a href="#" aria-label="Next">
			<span aria-hidden="true">&raquo;</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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值