26.10 Django Ajax异步提交

1. 表单提交

1.1 表单的作用

表单是Web开发中常见的数据收集方式, 它允许用户通过表单输入数据, 并通过提交操作将这些数据发送到服务器进行处理.

表单提交方式主要分为两大类: 传统的同步提交(也称为标准提交)和异步提交(主要通过Ajax实现).
它们在工作方式, 用户体验和数据传输等方面存在显著的差异.
开发Web应用程序的注意事项:
安全性: 无论使用哪种提交方式, 都需要考虑数据的安全性, 如防止CSRF攻击, 数据加密等.
验证: 在客户端进行基本的验证(如输入格式验证)可以提高用户体验, 但服务器端的验证是必不可少的, 以防止恶意提交.
用户体验: 提供清晰的反馈(如加载指示器, 成功/错误消息)可以提升用户体验.

1.2 请求方式

在Django中, 常用的数据提交方式主要依赖于HTTP请求的方法, 特别是GET和POST方法,.

表单提交中使用GET与POST方法的区别主要体现在以下几个方面:
* 1. 数据传输方向.
    - GET: 主要用于从服务器上获取数据.
      虽然名义上是'获取', 但在表单提交场景中, 它实际上是将表单数据附加到URL上发送到服务器,
      但这一行为本质上仍被视为一种请求数据的方式.
    - POST: 用于向服务器传送数据, 即客户端向服务器提交表单数据.
* 2. 数据传输方式.
    - GET: 将表单数据附加在URL之后进行发送, 数据和表单内各个字段一一对应, 并且在URL中可以看到.
      这种方式会限制数据的长度和类型, 因为URL有长度限制, 并且不是所有的数据都适合放在URL中.
    - POST: 将数据放在HTTP请求的消息体中发送给服务器.
      这种方式对用户是透明的, 用户看不到这个过程, 且理论上可以发送的数据量远大于GET方法.
* 3. 安全性.
    - GET: 由于数据暴露在URL中, 因此安全性较低.
      这可能导致敏感信息(如密码, 个人身份信息等)被泄露或被缓存, 从而增加安全风险.
    - POST: 由于数据不在URL中传输, 因此安全性相对较高. 它更适合用于发送敏感信息或大量数据.
* 4. 数据量限制.
    - GET: 由于URL长度的限制, GET方法传输的数据量通常较小, 一般不能超过2KB.
      (但在实际应用中, 这个限制可能会因浏览器和服务器的不同而有所差异).
    - POST: 理论上, POST方法传输的数据量没有限制, 但实际上可能会受到服务器配置的限制.
      例如, 在IIS4中, POST请求的最大数据量可能为80KB, 在IIS5中可能为100KB, 但这些值都可以通过服务器配置进行调整.
* 5. 缓存与书签.
    - GET: 由于URL中包含表单数据, 因此GET请求的结果可能会被浏览器缓存.
      此外, 用户还可以将包含表单数据的URL添加到书签中, 这可能会导致安全问题或数据不一致.
    - POST: POST请求的结果不会被浏览器缓存, 且用户无法将包含表单数据的请求添加到书签中.
* 6. 服务器端接收方式.
    - GET: 在服务器端, 通常使用request.GET来接收GET方法提交的数据.
    - POST: 在服务器端, 通常使用request.POST来接收POST方法提交的数据.
      如果表单的enctype属性被设置为multipart/form-data(通常用于文件上传), 则需要使用request.Files属性来接收数据.
      如果是通过Ajax使用json格式提交, 则需要使用request.body属性来获取原始数据, 后续需要手动处理.

1.3 表单属性

<form action="" method="post"> 是HTML中用于创建表单的标记的一部分.
这个标记定义了一个表单, 用户可以在其中输入数据, 然后提交给服务器处理.

action="": 这个属性指定了当表单提交时, 数据应该发送到哪里.
在这个例子中, action属性的值是空的(""), 这意味着表单数据将不会发送到任何URL.
在大多数情况下, 浏览器可能会尝试将表单数据提交回当前页面的URL(但这并不是绝对的,也不是HTML规范的要求).
在实际应用中, 会将action属性的值设置为一个服务器端脚本的URL,这个脚本进行处理.

method="post": 这个属性指定了表单数据应该如何发送到服务器. method属性有两个常用的值: GET和POST.
GET方法会将表单数据附加在URL之后发送(以?分隔URL和传输数据, 参数之间以&相连), 并且发送的数据量有限制(URL长度限制).
此外, 由于数据是附加在URL上的, 因此它会在浏览器的历史记录中留下痕迹, 并且可能会被缓存.
POST方法会将表单数据包含在表单提交的内容中发送, 不会附加在URL之后.
这意味着使用POST方法发送的数据量没有限制(实际上还是受限于服务器配置), 并且不会在URL中显示,
因此更加安全(尽管它并不能完全防止数据被拦截或篡改).

1.4 页面刷新

表单提交通常会触发页面的刷新, 这是因为它默认的行为是向服务器发送表单数据, 并请求服务器处理这些数据.
当点击表单的提交按钮时, 浏览器会收集表单中所有输入字段的值, 将它们编码成一个查询字符串(对于GET方法)或请求体(对于POST方法),
然后向表单的action属性指定的URL发送一个HTTP请求.

这个请求被发送到服务器后, 服务器上的应用程序会接收并处理这些数据.
处理完毕后, 服务器会返回一个HTTP响应, 该响应通常包含一个新的HTML页面①或重定向到另一个URL的指令.
(: 可能是同一个页面, 但带有不同的查询参数或表单处理的结果).
浏览器接收到这个响应后, 会根据其内容加载新的页面或进行重定向, 从而导致了页面的刷新.
在表单中, 导致页面刷新的按钮行为通常与按钮的type属性有关.
type="button": 没有内置的功能(包括表单内容检查), 通常被用作自定义的按钮, 以便使用JavaScript或其他脚本语言来为它添加功能.
type="submit": 设置提交按钮, 提交表单的数据并刷新网页.
type="reset": 设置重置按钮, 清空表单的数据.

默认情况下, 如果<button>标签在HTML中没有明确设置type属性, 那么它的行为会根据浏览器的不同而有所差异.
在大多数现代浏览器中, 未设置type的<button>默认行为是submit, 这意味着它会触发表单的提交并刷新网页.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面刷新</title>
</head>
<body>
<form id="" action="" method="post">
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username">
    <!-- 问题按钮 -->
    <button>提交</button>
</form>
</body>
</html>

image-20240813191637629

如果表单的action属性设置为当前页面的URL, 或者未设置(默认为当前页面的URL), 那么提交表单后页面会重新加载, 即刷新页面.
用户通常可以通过浏览器上的后退按钮(或快捷键, 如Ctrl+左箭头键)返回到他们之前浏览的页面, 这个行为是浏览器内置的功能.

GIF 2024-8-12 19-28-15

1.5 GET方式提交示例

编写一个GET请求提交表单数据的示例:
* 1. 编写主路由, 将请求分发到子路由.
# MyDjango/urls.py 主路由
from django.urls import path, include

urlpatterns = [
    path('', include(('index.urls', 'index'), namespace='index')),
]

image-20240811081452656

* 2. 编写子路由, 子路由处理请求, 调用视图函数.
# index/urls.py 主路由
from django.urls import path
from index.views import index

urlpatterns = [
    path('', index, name='index'),
]

image-20240811081722045

* 3. 编写视图函数处理请求, 第一次GET请求返回一个页面, 而通过表单发送的GET请求则需要从请求中获取数据.
# index/views.py
from django.shortcuts import render, HttpResponse


def index(request):
    print(request.GET)
    if request.GET:
        return HttpResponse('提交成功!')

    return render(request, 'index.html')

image-20240816133307402

* 3. 编写模板页面, 包含一个表单.
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>
<div>
    <form action="" method="get">
        <p>
            <label for="username">用户:<input type="text" name="username"></label>
        </p>
        <p>
            <label for="password">密码:<input type="text" name="password"></label>
        </p>
        <p>
            <input type="submit">
        <p>
    </form>
</div>

</body>
</html>

image-20240816133403810

* 4. 启动项目, 访问: 127.0.0.1:8000 , 在表单中填写数据并提交.

image-20240816133745128

* 5. 查询终端显示的信息和提交请求后返回的页面信息.

image-20240816133839417

1.6 POST方式提交示例

编写POST请求提交表单数据的示例:
* 1. 修改上面的示例的模板, 修改表单的method属性值为POST.
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>
<div>
    <form action="" method="post">
        <p>
            <label for="username">用户:<input type="text" name="username"></label>
        </p>
        <p>
            <label for="password">密码:<input type="text" name="password"></label>
        </p>
        <p>
            <input type="submit">
        <p>
    </form>
</div>

</body>
</html>

image-20240816134607971

* 2. 修改视图, 处理GET请求与POST请求.
# index/views.py
from django.shortcuts import render, HttpResponse


def index(request):
    if request.method == 'GET':
        return render(request, 'index.html')

    if request.method == 'POST':
        print(request.POST)
        return HttpResponse('提交成功!')

image-20240816135315187

* 3. 启动项目, 访问: 127.0.0.1:8000 , 在表单中填写数据并提交.

image-20240816134731940

* 4. 使用POST请求提交表单时会遇到'Forbidden (403)'错误: 'CSRF verification failed, Request aborted.'.
    这与跨站请求伪造(Cross-Site Request Forgery, CSRF)保护机制有关.
    CSRF是一种网络攻击方式, 攻击者通过诱导用户访问一个恶意网站, 
    该网站在后台向受害网站发送请求, 从而冒充用户执行未授权的操作.

image-20240811124626799

* 5. 在任何地方添加模板语法{% csrf_token %}.
    在Django框架中, {% csrf_token %}是一个模板标签, 用于在表单中生成一个跨站请求伪造(CSRF)保护令牌.
    这个令牌是一个隐藏的表单字段, 它会自动包含在渲染的HTML中, 以确保表单的提交是来自于可信的源, 而不是通过CSRF攻击发起的.
    当在Django的模板中编写表单时, 应该在表单的<form>标签内部添加{% csrf_token %}标签.
    这样, 当表单被渲染并发送到服务器时, 它会包含一个CSRF令牌, 服务器将使用这个令牌来验证请求的有效性.

image-20240816134901360

* 6. 启动项目, 访问: 127.0.0.1:8000 , 在表单中填写数据并提交.

image-20240816135435375

2. 同步提交

同步提交(Standard Submission): 同步提交是最常见的表单提交方式, 当用户填写完表单并点击提交按钮时,
浏览器会按照表单的action属性指定的URL, 将表单数据发送给服务器.
在发送数据的过程中, 浏览器会暂时锁定界面, 等待服务器的响应.
服务器处理完数据后, 会返回一个新的页面或刷新当前页面来显示处理结果.
   
特点: 
- 同步性: 传统的表单提交是同步的, 即用户提交表单后, 页面会重新加载以显示服务器处理后的结果.
- 全页刷新: 表单提交会导致整个页面刷新, 这可能会中断用户的当前操作或导致页面上的其他数据丢失(用户体验可能因页面刷新而中断).
- 简单直接: 表单提交方式简单直接, 易于理解和实现, 特别适合于简单的数据收集场景.
- 应用场景: 表单提交适用于需要收集用户输入信息并将其发送到服务器进行处理的场景, 如用户注册, 登录, 提交反馈等.
    
实现:
HTML表单中定义action属性指定提交的目标URL.
使用method属性指定提交方法(GET或POST).
用户填写表单后, 点击提交按钮或触发表单的submit事件.
编写一个页面计算器作为示例.
* 1. 修改视图, 如果是get请求返回一个表单, 如果是post请求则获取表单的数据, 并进行计算, 最后返回页面, 计算参数, 计算结果.
    在Django的QueryDict对象中, get()方法返回的是第一个值(字符串), 而getlist()方法返回的是值的完整列表(字符串列表).
# index/views.py
from django.shortcuts import render


def index(request):
    # 处理get请求
    if request.method == 'GET':
        return render(request, 'index.html')

    # 处理POST请求
    else:
        print(request.POST)
        x = request.POST.get('x')
        y = request.POST.get('y')
        z = int(x) + int(y)
        return render(request, 'index.html', locals())

image-20240813121925930

* 2. 修改index模板. 
    表单中通过模板语法, : {{ x }}, 判断是否填充默认值.
    如果get请求访问视图, 没有为页面提供这些值, 输入框框则是空白的, 
    如果点击计算发送post请求访问视图, 正常填写信息的情况下, 视图为页面提供值, 输入框不在空白. 
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>计算器</title>
</head>
<body>
<div class="form-container">
    <form action="" method="post">
        <input type="text" name="x" value="{{ x }}"> +
        <input type="text" name="y" value="{{ y }}"> =
        <input type="text" name="z" value="{{ z }}">
        <input type="submit" id="button" value="计算">
    </form>
</div>
</body>
</html>

image-20240813115426973

* 3. 启动项目, 访问: 127.0.0.1:8000 , 返回一个空表单, 填写信息后点击按钮提交.

image-20240813120700308

image-20240813120611143

* 4. 在终端查看到, QueryDict字有一个键值对就是CSRF令牌信息.

image-20240813121732825

* 5. 视图中重新返回视图页面, 并为输入框填充默认值.

image-20240813122008200

用户填写表单后, 点击'计算'按钮, 此时, 浏览器会向服务器发送一个HTTP POST请求, 请求中包含表单数据(即x和y的值).
服务器接收到请求后, 处理数据(在这个例子中是计算x和y的和), 然后将结果(连同整个页面)返回给浏览器.
这个过程中, 浏览器会刷新页面, 以显示新的内容(包括计算结果).
由于页面需要刷新, 页面需要重新渲染, 如果表单数据很长或网络延迟较高时, 用户可能需要等待较长时间才能看到结果.

3. 异步提交

3.1 Ajax介绍

异步提交(Ajax Submission, 主要通过Ajax实现): 异步提交是通过JavaScript(特别是Ajax技术)实现的,
它允许表单数据在不重新加载整个页面的情况下发送到服务器.

Ajax请求通过XMLHttpRequest对象(或现代浏览器中的Fetch API)发送, 
服务器处理完数据后, JavaScript可以接收到响应, 并动态地更新页面的部分内容, 而不是整个页面.

特点:  
- 异步性: Ajax请求是异步的, 即用户可以在等待服务器响应的同时继续与页面进行交互.
- 局部更新: Ajax可以只更新页面的一部分, 而不是整个页面, 从而提高了用户体验和性能.
- 无刷新: Ajax请求不会导致页面刷新, 这对于需要频繁与服务器交互的应用来说非常有用.

应用场景: Ajax广泛应用于需要实时更新数据的应用中, 如搜索引擎的自动补全, 社交媒体的实时更新, 网页的异步表单验证等.

兼容性: 对于需要支持较老浏览器的应用, 需要注意Ajax技术的兼容性.

注意事项:
* 1. 确保服务器端的脚本能够处理接收到的数据, 并返回适当的响应.
* 2. 在生产环境中, 请确保Ajax请求是安全的, 特别是当涉及到敏感信息时, 考虑使用HTTPS.
* 3. 可以通过设置dataType属性来指定服务器返回的数据类型(如json, xml等), 这有助于jQuery正确解析响应数据.
* 4. 可以根据需要调整多个选项, 包括请求类型(type), 发送的数据(data), 是否缓存响应(cache), 超时时间(timeout).

实现:
使用JavaScript监听表单的submit事件, 并阻止其默认行为(即页面刷新).
使用Ajax技术(如XMLHttpRequest或Fetch API)将表单数据发送到服务器.
服务器处理完数据后, 返回JSON格式的响应数据.
JavaScript接收响应数据, 并动态地更新页面的相应部分.
在Web开发中, 视图负责将信息展示给用户.
这个展示过程可以通过多种方式实现, 包括直接渲染HTML页面和通过AJAX(Asynchronous JavaScript and XML)技术动态更新页面内容.
直接渲染HTML页面: 用户发起请求, 服务器处理请求并返回完整的HTML页面, 浏览器加载并展示这个页面.
使用AJAX请求: 用户发起AJAX请求, 服务器处理请求并返回数据(而不是完整的HTML页面), AJAX请求的回调函数接收这些数据.
后续一般会使用JavaScript动态更新页面的部分内容.

3.2 基础模板

jQuery提供了一系列简化的函数, 用于实现Ajax功能.
这些函数是对原生JavaScript Ajax功能的封装和扩展, 使开发者能够以更简洁, 更直观的方式执行Ajax请求.
下面是一个基本的模板, 展示了如何使用jQuery的$.ajax()方法来发送数据:
$.ajax({  
    url: 'your-server-endpoint', // 服务器端点URL  
    type: 'POST', // 请求类型, 可以是GET或POST等  
    dataType: 'json', // 预期服务器返回的数据类型,这里假设是JSON  
    data: { // 发送到服务器的数据  
        csrf: csrf_token, // csrf验证
        key1: 'value1',  
        key2: 'value2',  
        // 可以继续添加更多的键值对  
    },  
    success: function(data, textStatus, jqXHR) {  
        // 请求成功时执行的回调函数  
        // response变量包含了服务器返回的数据  
        console.log(response);  
        // 可以在这里根据返回的数据进行DOM操作等  
    },  
    error: function(xhr, status, error) {  
        // 请求失败时执行的回调函数  
        console.error("请求失败: " + error);  
        // 可以在这里处理错误情况, 比如给用户一个提示等  
    },  
    // 还可以添加其他选项, 如: beforeSend, complete等.  
});

使用模板的注意事项:
- URL: 确保提供的URL是正确的, 并且服务器能够处理这个请求.
- 类型(type): 根据需求选择GET或POST等HTTP方法. GET方法通常用于请求数据, 而POST方法通常用于提交数据.
- 数据类型(dataType): 根据服务器响应来设置.
- 数据(data): 这是一个对象, 包含了你要发送到服务器的键值对数据.
 在发送前, jQuery会自动将这个对象转换成查询字符串(对于GET请求)或表单数据(对于POST请求).
- 成功(success)和错误(error)回调函数: 这两个函数分别用于处理请求成功和请求失败的情况.
 在success回调函数中, 可以处理从服务器返回的数据;
 在error回调函数中, 可以处理错误情况, 比如给用户一个错误提示.
- 跨域请求(CORS): 如果请求是跨域的, 请确保服务器支持CORS, 并且配置了正确的CORS策略.

通过调整这个模板, 可以根据自己的需求来发送数据, 并处理服务器的响应.
回调函数介绍:
* success()触发: 
 当AJAX请求成功发送到服务器, 服务器响应状态码为2xx(200 OK),
 并且响应内容成功被客户端接收和处理时, success()回调函数会被触发.
 
 success()回调函数用来处理当AJAX请求成功完成时执行的代码, 接受三个参数, 这些参数提供了关于响应的详细信息:
- data: 这是从服务器返回的数据, 具体的数据类型取决于服务器返回的内容类型.
- textStatus: 这是一个字符串, 表示请求的状态. 对于成功的请求, 这个值通常是"success".
 然而, 如果仍然在使用success回调, 它仍然会提供这个参数.
- jqXHR: 这是一个jQuery XMLHttpRequest(jqXHR)对象, 它是原生的XMLHttpRequest对象的增强版, 提供了额外的功能.
 通过这个对象, 可以访问响应的原始数据(通过jqXHR.responseText或jqXHR.responseXML), 
 设置请求的超时时间(通过jqXHR.abort()), 或者检查请求的状态(通过jqXHR.status状态码和jqXHR.statusText状态码描述文字).

* error()触发:
 如果请求未能成功发送到服务器(如网络问题), 
 或者服务器返回了一个非2xx状态码(404 Not Found, 500 Internal Server Error等), error()回调函数会被触发.
 
 error()回调函数通常接收三个参数: jqXHR, textStatus, errorThrown(抛出的错误信息).

3.3 使用示例

在Django中, request.is_ajax()方法曾经是用来判断一个请求是否是通过Ajax发起的.
然而, 随着Web技术的发展和HTTP标准的变化, request.is_ajax()方法已经不再被认为是判断Ajax请求的最佳方式.
这是因为request.is_ajax()主要依赖于检查请求头中的X-Requested-With: XMLHttpRequest, 但这个请求头并不是Ajax请求的标准部分,
也不是所有Ajax请求都会包含它(比如使用Fetch API或Axios等现代JavaScript库发起的请求可能就不包含这个请求头).

在Django中判断一个请求是否可能是Ajax请求, 更推荐的做法是直接检查请求头或请求体的内容.
对于表单数据, 可以像处理普通POST请求一样使用request.POST:
if request.method == 'POST':  
    # 假设Ajax请求发送的是表单数据  
    data = request.POST  # 普通数据
    file = request.FILE  # 文件
对于JSON数据, 需要先解析request.body中的原始数据:
if request.method == 'POST' and request.content_type == 'application/json':  
    # 假设Ajax请求发送的是JSON数据  
    try:  
        data = json.loads(request.body)  
        # 现在可以根据data(一个Python字典)来进行处理了 
    except json.JSONDecodeError:  
        # 处理解析JSON时发生的错误  
        return HttpResponseBadRequest("Invalid JSON data")
下面是一个基本的示例, 展示了如何使用jQuery的$.ajax()方法来进行异步提交.
* 1. 在项目根目录下创建static静态文件目录, 将bootstrap和jquery文件都复制过来.

image-20240817233900673

* 2. 配置静态文件的路径.
# MyDjango/settings.py
# 静态文件配置
STATIC_URL = 'static/'

STATICFILES_DIRS = [
    BASE_DIR / 'static'
]

image-20240813095533364

image-20240817233719993

* 3. 在页面中添加jQuery Ajax代码.
    首先, 确保页面已经引入了jQuery库. 
    然后, 将的type属性设置为button, 而不是submit, 因为不希望表单通过传统的方式提交.
    接下来, <script>标签中编写jQuery代码, 以在用户点击提交按钮时, 通过Ajax异步提交数据.
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>计算器</title>
    {% load static %}
    <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
</head>
<body>
<div class="form-container">
    <form action="" method="post">
        {% csrf_token %}
        <input type="text" name="x" id="x" value="{{ x }}"> +
        <input type="text" name="y" id="y" value="{{ y }}"> =
        <input type="text" name="z" id="z" value="{{ z }}">
        <input type="button" id="button" value="计算">
    </form>
</div>

<script>
    // 页面加载执行
    $(document).ready(function () {
        // 绑定点击事件
        $('#button').click(function () {
            // 阻止表单的默认提交行为(button按钮用不着, 可以不用添加)
            event.preventDefault();

            // 获取 CSRF Token
            var csrfmiddlewaretoken = $('input[name=csrfmiddlewaretoken]').val();
            // 获取用户输入的值
            var x = $('#x').val();
            var y = $('#y').val();


            // 使用jQuery的$.ajax方法进行异步请求
            $.ajax({
                type: "POST", // 请求方式
                url: "/", // 请求发送到的地址
                data: { // 要发送的数据
                    x: x,
                    y: y,
                    csrfmiddlewaretoken: csrfmiddlewaretoken  // 不要修改键的名称!!!
                },
                success: function (z) { // 请求成功时调用的函数
                    $('#z').val(z)  // 修改数据
                },
                error: function (jqXHR, textStatus, errorThrown) { // 请求失败时调用的函数
                    alert(jqXHR.responseText)  // jqXHR.responseText 将包含Django视图返回的异常信息的字符串
                    console.log('HTTP Status Code:', jqXHR.status);  // jqXHR.status 将包含HTTP状态码(如400)
                    console.log('Text Status:', textStatus);  // textStatus 是一个字符串,描述了请求的状态(如"error")
                    console.log('Error Thrown:', errorThrown);  // errorThrown 是抛出的错误信息
                }
            });
        });
    });
</script>
</body>
</html>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

* 4. 修改视图函数, 从POST请求中获取数据, 并进行计算.
# index/views.py
from django.shortcuts import render, HttpResponse


def index(request):
    # 处理get请求
    if request.method == 'GET':
        return render(request, 'index.html')

    # 处理POST请求
    else:
        try:
            print(request.POST)
            x = request.POST.get('x')
            y = request.POST.get('y')
            z = int(x) + int(y)
            return HttpResponse(z, status=200)  # success()回调函数会被触发

        except Exception as e:
            return HttpResponse(e, status=400)  # error()回调函数会被触发

image-20240813194740524

* 6. 启动项目, 访问: 127.0.0.1:8000 , 在表单中填写信息后点击按钮提交.
    通过异步提交的方式不会刷新页面, 在服务器计算出结果后再返回到页面, 在修改输入框的值.

GIF 2024-8-13 14-13-30

用户填写表单后, 点击'计算'按钮. 但是, 这次不是直接提交表单, 而是使用Ajax在后台向服务器发送HTTP请求.
服务器接收到AJAX请求后, 同样处理数据并返回结果.
但这次, 服务器只返回计算结果, 而不是整个页面的HTML.
JavaScript在接收到这个结果后, 可以使用它来更新页面上的某个部分(例如, 只更新显示结果的文本框), 而不需要刷新整个页面.

由于页面不需要刷新, 几乎可以立即看到结果, 且页面上的其他元素(如未修改的表单输入)保持不.
* 7. 错误测试: 访问: 127.0.0.1:8000 , 在表单中填写错误信息后点击按钮提交. 
    视图中出现异常, 返回信息的状态码为400, 触发error回调函数.

image-20240815205832669

4. 内容编码类型

在HTTP请求中, Content-Type头部用于指示发送给服务器的数据的类型.
例如, 如果正在提交一个表单数据, 可能会设置Content-Type为application/x-www-form-urlencoded.
如果正在发送JSON数据, 会设置它为application/json.

在HTTP响应中, Content-Type头部告诉客户端响应体的数据类型, 以便客户端可以正确地解析和展示它.
例如, 如果服务器返回的是一个HTML页面, 它会设置Content-Type为text/html.
如果返回的是图片, 则可能是image/jpeg或image/png.

在HTTP协议中, GET请求通常不用于提交数据到服务器以改变服务器上的资源状态, 而是用于请求服务器发送资源.
因此, GET请求的设计初衷并不包含请求体(body), 这意味着GET请求通常不包含Content-Type头部,
因为Content-Type头部是用来指示请求体(body)中数据的媒体类型的.

4.1 请求与响应

请求数据的格式在Web开发中, 特别是在HTTP请求中, 扮演着至关重要的角色.
一个HTTP请求通常包含请求行, 请求头部, 空行(用于分隔请求头部和请求体)以及可选的请求体.
请求数据的格式主要涉及到请求体(body)的编码方式, 以及请求头部中某些字段(如Content-Type)的设置.

以下是对请求数据格式的详细介绍:
* 1. 请求行.
    请求行由三部分组成: 请求方法, 请求URI(统一资源标识符)和协议版本, 这三部分之间通过空格分隔.
    请求方法定义了客户端希望服务器对资源执行的操作, 如GET, POST, PUT, DELETE等.
    请求URI指定了请求的资源路径, 可能包含查询字符串.
    协议版本则标识了客户端用于构造请求的HTTP协议的版本, 如HTTP/1.1或HTTP/2.0.

* 2. 请求头部.
    请求头部包含了一系列的键值对, 为HTTP请求提供了额外的上下文和参数设置.
    其中, Content-Type是一个非常重要的请求头部字段, 它用于指示请求体的媒体类型(即数据的格式).
    常见的Content-Type值包括:
    - application/x-www-form-urlencoded: 这是HTML表单在默认情况下使用的编码类型.
      当表单通过POST方法提交时, 表单数据(除了文件上传)会被编码为这种格式. 数据被编码为键值对, 并通过&符号分隔.
    - multipart/form-data: 当HTML表单需要上传文件时, 通常会使用这种编码类型. 
      请求体被划分为多个部分, 每个部分都包含了自己的MIME头部信息和相应的数据(如文件内容).
    - application/json: 现在越来越多的API使用JSON作为数据交换格式.
      当客户端向服务器发送JSON数据时, 会将Content-Type设置为application/json.

* 3. 请求体.
     请求体是HTTP请求消息的可选部分, 仅在请求方法支持且需要发送数据时使用.
     请求体的实际数据类型和格式取决于Content-Type字段的值.
     例如, 如果Content-Type为application/x-www-form-urlencoded,
     则请求体中的数据将按照key1=value1&key2=value2的格式进行编码;
     如果Content-Type为application/json, 则请求体中的数据将是JSON格式的字符串.
     
在Web开发中, form编码格式和urlencoded格式经常被提及, 它们之间有着密切的关系, 但也可以从细微的差别上进行区分.
HTTP响应通常由状态行, 响应头部, 空行(用于分隔响应头部和响应体)以及可选的响应体组成.
以下是对HTTP响应格式的详细介绍:
* 1. 状态行.
    状态行由三部分组成: HTTP协议版本, 状态码和状态消息, 这三部分之间通过空格和特定的分隔符(如空格和回车换行符)分隔.
    HTTP协议版本标识了服务器用于构造响应的HTTP协议的版本, 如HTTP/1.1或HTTP/2.0.
    状态码是一个三位数字, 用于告知客户端请求的结果, 200表示请求成功, 404表示未找到资源等.
    状态消息是对状态码的简短描述, 提供有关响应的额外信息.

* 2. 响应头部.
    响应头部包含了一系列的键值对, 为HTTP响应提供了额外的上下文和元数据.
    这些头部字段可以包括关于内容类型, 内容长度, 编码, 缓存策略, 服务器信息等的信息.
    其中, Content-Type是一个非常重要的响应头部字段, 它告诉客户端实际返回的内容的媒体类型(即数据的格式).
    常见的Content-Type值包括:
    - text/html: 表示返回的是HTML文档.
    - text/plain: 表示返回的是纯文本.
    - application/json: 表示返回的是JSON格式的数据, 这在RESTful API中非常常见.
    - image/jpeg, image/png等: 表示返回的是图片资源, 并指定了图片的具体格式.

* 3. 响应体.
    响应体是HTTP响应消息的可选部分, 包含了实际返回给客户端的数据.
    响应体的数据类型和格式取决于Content-Type字段的值.
    例如, 如果Content-Type为text/html, 则响应体将包含HTML标记的文档;
    如果Content-Type为application/json, 则响应体将是JSON格式的字符串, 表示返回的数据结构.

* 4. 缓存和重定向.
    除了上述基本组成部分外, HTTP响应还涉及缓存和重定向等机制.
    通过设置如Cache-Control, Expires等头部, 服务器可以指示浏览器如何缓存响应内容.
    而状态码如301(永久重定向), 302(临时重定向)等则用于指导浏览器重定向到另一个URL.

综上所述, HTTP响应通过状态行, 响应头部, 响应体以及缓存和重定向机制, 共同构成了Web开发中服务器与客户端之间通信的基础.
理解和正确设置这些部分对于开发高效, 可靠的Web应用至关重要.

4.2 请求体编码

表单(<form>)的enctype属性用于控制表单数据发送到服务器时请求体的编码方式.
这个属性特别重要, 因为它决定了如何对表单数据进行编码, 以便服务器能够正确地解析和处理这些数据.
4.2.1 表单编码类型
HTML表单可以通过不同的方式提交数据, 这主要取决于表单的enctype属性(encoding type).
enctype属性定义了发送到服务器之前表单数据应该如何编码.

表单的enctype有三个可选值:
* 1. application/x-www-form-urlencoded(默认值): 这是最常见的编码类型, 它适用于大多数情况, 尤其是表单中包含文本字段时.
    数据被编码为键值对, 类似于URL查询字符串.
* 2. multipart/form-data: 当表单需要上传文件时, 应该使用这种编码类型.
    它允许表单数据包含文件内容, 并将表单的每部分编码为多部分消息体.
* 3. text/plain: 这个值实际上并不被广泛支持用于HTML表单提交.
    如果设置了enctype="text/plain", 那么表单数据将不会被编码为键值对或多部分消息体, 而是直接作为纯文本发送.
    这通常不是Web服务器或Web框架所期望的格式, 因此它们无法正确解析这些数据.
4.2.2 表单URL编码
在HTTP协议中, application/x-www-form-urlencoded是一种常见的内容编码类型, 主要用于POST请求的body(主体)内容编码.
这种编码方式会将表单数据编码为URL的查询字符串形式, '键=值'对的形式,
键和值之间用等号(=)连接, 不同的键值对之间用与号(&)分隔, 特殊字符会被转义(: 空格变为+, 特殊字符如&, =, +等会被百分比编码).
表单提交示例:
* 1. 修改模板页面, 页面中包含一个注册表单.
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>
<div>
    <form action="" method="post">
        {% csrf_token %}
        <p>
            <label for="username">用户: <input type="text" name="username"></label>
        </p>
        <p>
            <label for="password">密码:<input type="text" name="password"></label>
        </p>
        <p>
            <input type="submit">
        <p>
    </form>
</div>

</body>
</html>

image-20240815213852620

* 2. 启动项目, 打开开发者工具选择网络, 访问: 127.0.0.1:8000 , 输入账户密码后提交, 在网络工具(Payload栏)中查看提交的格式.

image-20240814082840322

* 3. 默认展示格式后的数据, 点击view source查看源代码:

2024-08-14_082855

* 4. 查询请求数据的格式(Headers栏).

2024-08-14_083551

4.2.3 多部分表单数据
multipart/form-data编码类型允许表单数据以多个部分(parts)的形式发送, 每个部分都可以包含不同的内容类型.
这种编码方式特别适用于需要上传文件的表单, 因为它允许将表单字段和文件内容作为单独的部分发送.

当HTML表单的enctype属性被设置为multipart/form-data时, 浏览器会将表单数据编码为多个部分,
每个部分都包含了一个表单字段(如文本输入框, 选择框等)或文件的内容.
每个部分都有自己的Content-Disposition头部, 用于描述该部分的内容类型和名称.

在后端, 服务器需要解析这种编码的请求体以获取表单字段和文件内容.
Django框架中, 当处理multipart/form-data编码的POST请求时,
Django会自动将表单字段解析为请求对象的属性, 并将上传的文件存储在request.FILES属性中.
Django的FileSystemStorage提供了一种方便的方式来处理文件的保存.
直接使用myfile.name作为文件名可能会带来安全风险, 因为上传的文件名可能包含恶意路径. 因此, 需要清理或重新生成文件名.
使用FileSystemStorage的save方法保存文件时, 文件会被保存到Django项目的MEDIA_ROOT设置指定的目录中.
MEDIA_ROOT是一个字符串, 表示文件系统中用于存储上传文件的根目录的绝对路径.
定义一个文件上传示例:
* 1. 在Django根目录下创建一个media文件夹, 这个文件夹将用来存储所有用户上传的文件.
* 2. 在配置文件中配置MEDIA_ROOT属性和MEDIA_URL属性.
# settings.py

# 媒体文件配置
MEDIA_URL = '/media/'  # 媒体文件在Web服务器上的URL前缀

MEDIA_ROOT = BASE_DIR / 'media'  # 指向你的media文件夹的绝对路径

image-20240815223502011

* 3. 开发媒体文件访问.
    为了让媒体文件(: 用户上传的图片等)在开发环境中能够被访问, 需要在项目的urls.py文件中添加对MEDIA_URL的静态文件服务.
    这种做法仅适用于开发环境, 因为Django的static函数是为开发环境服务的.
    在生产环境中, 通常会使用Web服务器(如Nginx或Apache)来处理静态文件和媒体文件的服务.
# MyDjango/urls.py  (主路由)
from django.conf import settings  
from django.conf.urls.static import static  
from django.urls import path, include  

  
urlpatterns = [  
    path('admin/', admin.site.urls),  
    # 其他你的URLs  
    # 假设你有一个app叫myapp,并且它的urls.py配置已经通过include包含进来  
    path('', include('myapp.urls')),  
    # ...  
]  
  
# 在开发环境中添加对媒体文件的访问  
if settings.DEBUG:  
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    

image-20240815231229551

* 4. 修改视图, 如果是get请求则返回一个模板页面, 如果是post请求则获取文件并保存.
# index/views.py
from django.shortcuts import render, HttpResponse
from django.core.files.storage import FileSystemStorage
import time
from django.utils.safestring import mark_safe


def index(request):
    # 处理POST请求
    if request.method == 'POST':
        # 获取文件信息
        print(request.FILES)  # 文件
        print(request.POST)  # 其他数据

        up_file = request.FILES['up_file']  # 获取文件

        # 注意: 直接使用up_file.name可能会导致安全问题, 因为它可能包含恶意文件路径, 可能需要清理或重新生成文件名d
        # 获取文件后缀
        suffix = '.' + up_file.name.split('.')[-1]
        # 获取当前时间戳作为文件名
        file_name = str(time.time()).replace('.', '') + suffix

        # 创建文件系统存储对象
        fs = FileSystemStorage()
        filename = fs.save(file_name, up_file)
        # 文件路径
        uploaded_file_url = fs.url(filename)
        # 返回一个a标签
        html_a = f"""<a href="{uploaded_file_url}">查看上传的图片</a>"""
        safe_html = mark_safe(html_a)
        return HttpResponse(safe_html)

    return render(request, 'index.html')

image-20240816001138958

* 5. 编写模板, 包含一个文件提交表单.
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
<div>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <p>
            <label for="username">文件上传:<input type="file" name="up_file"></label>
        </p>

        <p><input type="submit"><p>
    </form>
</div>

</body>
</html>

image-20240815231518649

* 6. 启动项目, 访问: 127.0.0.1:8000 , 返回一个文件上传框, 选择一个文件后提交, 在网络工具中查看提交的格式(vinary, 二进制).
    文件会被保存到media目录中.

image-20240814075006742

image-20240816001205222

* 7. 查看源代码.

image-20240814080400654

* 8. 查询请求数据的格式(Headers栏).

2024-08-14_084013

* 8. 查看终端中显示的信息.

image-20240814081732827

* 9. 点击视图返回的链接地址, 可以访问上传的图片.

image-20240816001036781

* 10. 如果将表单的属性enctype="multipart/form-data"去掉, 则使用application/x-www-form-urlencoded格式.
     提交文件的时候 request.FILES 获取不到文件(<MultiValueDict: {}>), 只能在request.POST属性中获取到文件名称.

202408140825620

* 11. 查询请求数据的格式(Headers栏).

2024-08-14_084245

4.3 Ajax请求体编码

Ajax的contentType属性用于设置发送信息至服务器时body(主体)的类型, 它是一个字符串值, 用于告诉服务器客户端发送的数据格式.
常见的contentType属性具体值包括:
* application/x-www-form-urlencoded: 默认值, 常用于普通的表单提交.
 数据被编码为类似key1=value1&key2=value2的形式.
 大部分服务端语言都支持此类型的数据解析.
 
* multipart/form-data: 常用于表单上传文件的场景(不能设置contentType: 'multipart/form-data', 而是使用false让它自动使用).
 当发送文件时, 浏览器会自动设置正确的Content-Type头部, 该头部包含了一个多部分(multipart)的边界字符串,
 用于分隔请求体中的不同部分(如文件和表单字段).
 在使用FormData对象时, 通常需要设置contentType为false, 以允许浏览器自动生成正确的Content-Type头部.

* application/json: 常用于发送JSON格式的数据.
 告诉服务器消息主体是序列化后的JSON字符串.
 需要将数据对象使用JSON.stringify()方法转换为字符串形式发送.
4.3.1 表单URL编码
Ajax提交POST请求, 默认的编码格式是urlencoded, 最终会被解析到request.POST属性中.
编写一个ajax提交表单的示例:
* 1. 修改模板页面, 代码如下:
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form表单</title>
    {% load static %}
    <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
</head>
<body>

<div>
    <form action="">
        {% csrf_token %}
        <p>
            <label for="username">用户:<input type="text" name="username"></label>
        </p>
        <p>
            <label for="password">密码:<input type="text" name="password"></label>
        </p>

        <p><input type="button" value="提交"><p>
    </form>
</div>
<script>
    $(':button').on('click', function () {
        var username = $('input[name=text]').val() // 获取表单的数据
        var password = $('input[name=password]').val()
        var csrfmiddlewaretoken = $('input[name=csrfmiddlewaretoken]').val();
        // ajax 发送数据
        $.ajax({
            url: '', // 提交地址
            type: 'post', // 提交方式
            data: {username: username, password: password, csrfmiddlewaretoken: csrfmiddlewaretoken}, // 提交的数据
            success: function (args) { // 回调函数
                alert(args)
            }
        })
    })
</script>
</body>
</html>

image-20240816001825479

* 2. 修改视图函数, 返回一个响应信息给ajax.
# index/views.py
from django.shortcuts import render, HttpResponse


def index(request):
    # 处理POST请求
    if request.method == 'POST':
        print(request.POST)

        return HttpResponse('ok!')

    return render(request, 'index.html')

image-20240816002104220

* 3. 启动项目, 访问: 127.0.0.0.1:8000 , 在页面中输入信息并提交, 在网络工具中查看请求的内容类型.
    Content-Type: application/x-www-form-urlencoded.

2024-08-14_212627

* 4. 在终端查看提交的数据.

image-20240816002218848

4.3.2 多部分表单数据
使用AJAX通过JavaScript发送文件时, 需要利用FormData对象来构建要发送的数据集.
同时, 在发送这个FormData对象时, 需要特别配置AJAX请求的两个参数: contentType和processData.

使用步骤:
* 1. 首先, 需要创建一个FormData的实例.
    这个对象可以用来构建一组键/值对, 这些键/值对可以被用于异步方式(AJAX)与服务器交换数据.
    使用JSON.stringify()方法序列化一个FormData对象时, 会得到一个空对象({}), 因为它的结构并不直接对应于JSON可以表示的格式.

* 2. 向FormData添加数据: 可以通过调用append()方法来向FormData对象中添加新的字段.
    这个方法接收两个参数: (字段名)和值(字段值).

* 3. 使用jQuery的$.ajax()方法来发送数据时, 如果数据是FormData对象, 需要设置contentType为false. 
    这是因为发送文件时, 浏览器应该自动设置正确的Content-Type头部, 
    该头部包含了一个多部分(multipart)的边界字符串, 这是发送文件所必需的.
    同时, 需要将processData设置为false, 以防止jQuery自动将数据转换为查询字符串.
    这是因为FormData对象需要被原样发送, 以便服务器能够正确地解析出文件内容和其他表单字段.

* 4. 在Django的后端, 当接收到一个包含FormData对象的AJAX请求时, Django会自动处理这个请求,
    并将普通的键值对存储在request.POST中, 而将文件存储在request.FILES中.
解释一下为什么在使用jQuery的$.ajax()方法发送FormData对象时需要设置contentType为false以及processData为false.

* contentType: false.
 在AJAX请求中, contentType选项用于设置发送给服务器的内容编码类型.
 默认情况下, jQuery会基于发送的数据类型自动设置这个值. 然而, 当发送FormData对象时, 情况有所不同.
  
 FormData对象用于构建一套键/值对, 这些键/值对可以包含文件.
 为了正确地发送这些文件, 浏览器需要生成一个包含多部分(multipart)数据的请求体, 并且这个请求体需要一个特殊的Content-Type头部,
 该头部类似于multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW.
 这个boundary字符串是一个随机生成的标识符, 用于分隔请求体中的不同部分(比如文件和表单字段).

 如果contentType被设置为一个具体的值(比如application/x-www-form-urlencoded),
 那么浏览器就不会自动生成这个包含boundary的Content-Type头部, 从而导致服务器无法正确解析请求体中的文件.
 因此, 当发送FormData对象时, 需要将contentType设置为false, 这样jQuery就不会尝试去设置这个头部, 而是让浏览器自动处理.

* processData: false.
 processData选项告诉jQuery是否需要对发送的数据进行预处理.
 默认情况下, 当发送的数据是对象或数组时, jQuery会将其转换为查询字符串
 (即application/x-www-form-urlencoded或multipart/form-data, 后者在文件上传时自动使用, 但不受processData影响).
 然而, FormData对象已经是一个准备好被发送的原始数据格式, 不需要再进行任何预处理.

 如果processData被设置为true(默认值), jQuery会尝试将FormData对象转换为查询字符串, 
 这显然是不正确的, 因为FormData对象包含的是复杂的结构, 包括文件, 这些都不能简单地转换为查询字符串.
 因此, 需要将processData设置为false, 以确保FormData对象被原样发送.

综上所述, 当使用jQuery的$.ajax()方法发送FormData对象时, 必须设置contentType: false和processData: false,
以确保请求能够正确地包含文件和其他表单字段, 并且服务器能够正确地解析这些数据.
编写一个ajax提交表单并包含文件的示例:
* 1. 修改模板页面:
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form表单</title>
    {% load static %}
    <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
</head>
<body>

<div>
    <form action="">
        {% csrf_token %}
        <p>
            <label for="message">信息:<input type="text" id="message" name="message"></label>
        </p>
        <p>
            <label for="file">文件:<input type="file" id="file" name="up_file"></label>
        </p>

        <p><input type="button" value="提交">
        <p>
    </form>
</div>
<script>
    $(':button').on('click', function () {
        // 获取input表单的数据
        var message = $('input[name=message]').val()
        var csrfmiddlewaretoken = $('input[name=csrfmiddlewaretoken]').val();
        var up_file = $('input[name=up_file]')[0].files[0]  // 获取到文件对象

        // 生成表单数据对象
        var file_obj = new FormData()

        // 为对象添加数据
        file_obj.append('message', message)
        file_obj.append('csrfmiddlewaretoken', csrfmiddlewaretoken)
        file_obj.append('up_file', up_file)
        // ajax 发送数据
        $.ajax({
            // 提交地址
            url: '',
            // 提交方式
            type: 'post',
            contentType: false,  // 不需要任何编码, 浏览器会自动使用form-data类型
            processData: false,  // 告诉jQuery不需要对发送的数据进行预处理

            // 提交的数据
            data: file_obj,
            // 回调函数
            success: function (args) {
                alert(args)
            }
        })
    })
</script>
</body>
</html>

image-20240814230235754

* 2. 启动项目, 访问: 127.0.0.1:8000 , 填写信息和选择图片后点击提交, 在网络工具中查看请求的内容类型.

2024-08-14_230605

* 3. 在终端查询信息, 验证普通的键值对存储在request.POST中, 文件存储在request.FILES中.

image-20240814230751966

4.3.3 json格式
当发送JSON数据时, 将contentType设置为application/json, 告诉服务器, 发送的数据是JSON格式的, 
而不是传统的表单数据(application/x-www-form-urlencoded)或多部分表单数据(multipart/form-data).

在发送数据之前, 需要确保数据是JSON格式的.
可以使用JSON.stringify()方法将JavaScript对象转换成JSON格式的字符串.

注意: 使用JSON.stringify()来序列化包括文件在内的数据, 但是这种方法并不适用于文件对象.
JSON.stringify()只能处理JavaScript的原始数据类型(: 字符串, 数字, 布尔值, 数组, 对象等),
并且它会忽略undefined, 函数和Symbol.
文件对象(File  Blob)是复杂类型, 不能直接用JSON.stringify()进行序列化.

在Django中, 当contentType为application/json时, 
发送的数据不会被自动解析到request.POST中, 而是会作为原始字符串存储在request.body中.
因此, 需要在Django视图中手动使用json.loads()解析这个JSON数据.
不能直接将CSRF令牌作为json对象的一部分来发送, 可以使用ajax的beforeSend函数在发送请求之前设置CSRF令牌作为请求头, 示例如下:
* 1. 修改模板页面:
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form表单</title>
    {% load static %}
    <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
</head>
<body>

<div>
    <form action="">
        {% csrf_token %}
        <p>
            <label for="username">账号:<input type="text" name="username"></label>
        </p>
        <p>
            <label for="password">密码:<input type="password" name="password"></label>
        </p>
        <p><input type="button" value="提交">
        <p>
    </form>
</div>
<script>
    $(':button').on('click', function () {
        var csrfmiddlewaretoken = $('input[name=csrfmiddlewaretoken]').val();
        // 获取input表单的数据
        var username = $('input[name=username]').val()
        var password = $('input[name=password]').val()


        // ajax 发送数据
        $.ajax({
            url: '',  // 提交地址
            type: 'post',  // 提交方式
            contentType: 'application/json',  // 提交的格式
            data: JSON.stringify({
                username: username,
                password: password
            }),  // 提交的数据
            beforeSend: function (xhr, settings) {  // 设置csrf
                // 在这里设置 CSRF 令牌作为请求头
                xhr.setRequestHeader("X-CSRFToken", csrfmiddlewaretoken);
            },
            // 回调函数
            success: function (args) {
                alert(args)
            }
        })
    })
</script>
</body>
</html>

image-20240815120606163

* 2. 修改视图函数.
    注意: 从Django 3.1开始, request.is_ajax()方法被标记为已弃用, 并在后续的版本中被移除.
# index/views.py
from django.shortcuts import render, HttpResponse
import json


def file(request):
    # 处理POST请求
    if request.method == 'POST':
        print(request.body)
        print(json.loads(request.body))

        return HttpResponse('ok!')

    return render(request, 'index.html')

image-20240815104448938

* 3. 启动项目, 访问: 127.0.0.1:8000 , 在表单中输入信息并提交, 在网络工具中查看请求的内容类型.

image-20240815103539197

* 4. 查看终端显示的信息.

image-20240815111940279

4.4 响应体编码

在Django中, 设置响应体内容编码主要是通过设置Content-Type头部来实现的, 可以通过HttpResponse的content_type参数设置它.
对于JSON响应, JsonResponse类提供了一个方便的封装, 自动处理了这些设置.

以下是一些常见的Content-Type类型及其用途:
* text/plain: 表示纯文本内容, 不包含任何格式化信息. 这种类型通常用于简单的文本文件, .txt文件.
* text/html: 表示HTML文档. 这是网页内容最常见的类型, 浏览器接收到这种类型的内容后, 会将其渲染为网页.
* text/css: 表示CSS(层叠样式表)文档. CSS用于控制网页的布局和样式.
* text/javascript(或application/javascript): 表示JavaScript代码.
* application/json: 表示JSON(JavaScript Object Notation)格式的数据.
* application/xml(或text/xml): 表示XML(eXtensible Markup Language)文档.
* image/jpeg(或image/jpg): 表示JPEG格式的图片. JPEG是一种广泛使用的图片压缩格式, 支持有损压缩, 适用于存储照片等图像.
* image/png: 表示PNG格式的图片. PNG是一种无损压缩的图片格式, 支持透明背景, 适用于需要高质量图像的场合.
* audio/mpeg(或audio/mp3): 表示MP3格式的音频文件. MP3是一种广泛使用的音频压缩格式, 具有较高的压缩比和较好的音质.
* video/mpeg: 表示MPEG格式的视频文件. MPEG是一种视频压缩格式, 包括MPEG-1, MPEG-2等多个版本, 广泛用于视频存储和传输.
4.4.1 纯文本类型
如果想返回一个纯文本内容, 不包含任何HTML, CSS或其他格式化信息时, 可以将Content-Type设置为text/plain.
这种类型通常用于返回简单的文本文件, 比如: txt文件, 或者是在API响应中直接返回文本信息.
* 1. 修改视图函数, 用户访问直接返回纯文本内容.
# index/views.py
from django.shortcuts import HttpResponse


def index(request):
    text_content = "Hello, World!"

    return HttpResponse(text_content, content_type="text/plain")

image-20240816234442143

* 2. 启动项目, 访问: 127.0.0.1:8000 , 在网络工具中查看响应的内容类型.

image-20240816234047824

4.4.2 html文档类型
text/html是一种MIME类型(多用途互联网邮件扩展类型), 它用于指示文档或响应的内容是HTML(HyperText Markup Language)格式的.
当浏览器接收到一个标记为text/html的响应时, 它会解析这个响应体中的HTML代码, 并将其渲染成用户可以交互的网页.

在Django中, 当想要返回一个HTML页面作为HTTP响应时, 通常会使用Django的模板系统来生成HTML内容, 
并使用HttpResponse类(或更常见的, 使用render函数或TemplateResponse类)来发送这个HTML内容给客户端.
然而, 不需要显式地设置Content-Type为text/html, 因为Django的HttpResponse类在默认情况下就会为HTML内容设置这个MIME类型.
* 1. 修改视图函数, 用户访问直接返回html文档内容.
# index/views.py
from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, World!")

image-20240816234507029

* 2. 启动项目, 访问: 127.0.0.1:8000 , 在网络工具中查看响应的内容类型.

image-20240816232948347

4.4.3 图片文件类型
使用FileResponse来发送文件时, 可以使用content_type参数指定文件的MIME类型, 告诉客户端如何解释或展示接收到的数据.
如果正在发送一个PNG图像文件, 应该将content_type设置为'image/png'.
* 1. 修改视图函数, 用户访问直接返回图片文件.
# index/views.py
from django.http import FileResponse  # 文件响应
from django.conf import settings


def index(request):
    # 图片存储在MEDIA_ROOT下
    # 这里以MEDIA_ROOT为例, 但也可以使用其他路径
    image_path = f"{settings.MEDIA_ROOT}/{'17237381936145282.ico'}"

    # 使用FileResponse来返回文件
    # as_attachment=True表示以附件形式发送, 通常用于下载; 如果不需要下载, 可以设置为False或省略
    response = FileResponse(open(image_path, 'rb'), content_type='image/png')

    # 返回响应
    return response

image-20240816234637949

* 2. 启动项目, 访问: 127.0.0.1:8000 , 在网络工具中查看响应的内容类型.

image-20240816234902041

4.4.4 json格式类型
JSON是一种轻量级的数据交换格式, 易于人阅读和编写, 同时也易于机器解析和生成.
如果数据不是字符串, 比如是字典, 列表或其他Python对象, 通常需要先将这些对象序列化为字符串(或字节串).
可以使用json.dumps()方法将Python对象转换为JSON格式的字符串, 
然后将其设置为HttpResponse的内容, 并将响应类型设置application/json.
* 1. 修改视图函数, 直接返回一个序列化字典.
# index/views.py
from django.shortcuts import HttpResponse
import json


def index(request):
    json_data = json.dumps({'aa': 'bb'})
    return HttpResponse(json_data, content_type='application/json')

image-20240816235822030

* 2. 启动项目, 访问: 127.0.0.1:8000 , 在网络工具中查看响应的内容类型.

image-20240817000008118

在Django中, 可以使用JsonResponse类来更方便地返回JSON数据, 这个类会自动设置Content-Type为'application/json'.
JsonResponse是Django中用于返回JSON格式数据的一个便捷类, 它继承自HttpResponse, 
并自动处理数据序列化和设置正确的Content-Type为application/json.
默认情况下, JsonResponse只接受字典对象作为参数, 如果需要传递非字典对象(如列表, 元组等), 需要将safe参数设置为False.
* 3. 修改视图函数, 使用JsonResponse返回一个序列化字典.
# index/views.py
from django.http import JsonResponse


def index(request):
    return JsonResponse({'aa': 'bb'})

image-20240817000114395

* 4. 启动项目, 访问: 127.0.0.1:8000 , 在网络工具中查看响应的内容类型.

image-20240817000340124

5. 序列化组件

在前后端分离的项目中, 后端通常不会直接将数据库中的对象(如ORM模型实例)直接序列化为一个完整的对象结构返回给前端,
而是会将数据以更通用, 扁平化的格式返回, 比如: 列表套字典(或JSON对象).
这样做的好处是减少了数据传输的冗余, 提高了接口的可维护性和可扩展性, 同时也方便了前端进行数据展示和处理.

序列化通常用于将数据导出到前端或保存到文件中, 而反序列化则用于从文件或外部源导入数据到Django数据库.

5.1 序列化组件的引入

为什么需要序列化组件?
* 数据交换: 在Web应用中, 前端和后端通常运行在不同的环境中, 甚至可能由不同的团队开发.
 序列化组件提供了一种标准化的方式来交换数据, 使得前端可以轻松地解析后端发送的数据, 反之亦然.
* 安全性: 直接从数据库中获取的数据可能包含敏感信息或不必要的复杂结构.
 通过序列化组件, 可以过滤掉这些信息, 只保留前端需要的数据, 从而增强API的安全性.
* 灵活性: 序列化组件允许你自定义序列化逻辑, 包括字段的选择, 嵌套关系的处理, 自定义字段的添加等.
* 一致性: 在大型项目中, 保持数据输出的一致性是非常重要的.
 序列化组件通过定义明确的序列化规则, 确保了不同部分的数据输出遵循相同的格式和标准.
下面将使用一个实例展示手动将查询集(queryset)中的数据转换为列表套字典的格式, 并使用JsonResponse返回给前端.
* 1. 定义一用户模型.
# index/models.py 
from django.db import models


class User(models.Model):
    username = models.CharField(max_length=32, verbose_name='用户姓名')
    age = models.IntegerField(verbose_name='用户年龄')
    gender_choices = (
        (1, '男'),
        (2, '女'),
        (3, '其他'),
    )
    gender = models.IntegerField(choices=gender_choices, verbose_name='用户性别')

image-20240817005932879

在Django模型中, 使用编号(或称为整数标识符)来表示具有固定选项的字段(如性别)是一种常见的做法,
数据库在处理整数时通常比处理字符串要快, 因为整数占用的存储空间更少, 
且可以直接用于比较和索引, 而不需要进行额外的字符串比较或转换.

虽然直接看数据库中的整数可能不如看字符串直观, 
但在Django模型中, 可以使用get_<field_name>_display()方法来获取选择字段的人类可读形式.
# 2. 执行数据库迁移命令:
PS D:\MyDjango> python manage.py makemigrations
Migrations for 'index':
  index\migrations\0001_initial.py
    - Create model User
PS D:\MyDjango> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, index, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK

image-20240817003844787

* 3. 在测试文件中插入一些数据.
# index/test.py
import os

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "MyDjango.settings")
    import django

    django.setup()
    from index import models

    models.User.objects.create(username='kid', age=18, gender=1)
    models.User.objects.create(username='qz', age=19, gender=2)
    models.User.objects.create(username='qq', age=20, gender=3)

image-20240817004517261

* 4. 修改视图函数, 返回查询集对象.
# index/views.py
from django.shortcuts import render
from index import models


def index(request):
    # 获取所有数据
    queryset_obj = models.User.objects.all()
    print(queryset_obj)

    return render(request, 'index.html', locals())

image-20240817005912867

* 5. 修改模板页面, 展示所有的用户信息.
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户数据</title>
    {% load static %}
    <!-- 导入bootstrap css 文件-->
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
    <!-- 导入bootstrap css 文件-->
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="row">
    <div class="col-md-8 col-md-offset-2">
        <table class="table table-hover table-striped">
            <thead>
            <tr>
                <th>姓名</th>
                <th>年龄</th>
                <th>性别</th>
            </tr>
            </thead>
            <tbody>
            {% for obj in queryset_obj %}
                <tr>
                    <td>{{ obj.username }}</td>
                    <td>{{ obj.age }} </td>
                    <td>{{ obj.get_gender_display }}</td>
                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
</div>
</body>
</html>

image-20240817235853778

在HTML和Bootstrap框架中, <div class="row"><div class="col-md-8 col-md-offset-2">
以及 <table class="table table-hover table-striped"> 的组合用于创建一个具有特定样式和布局的表格.
下面是对这些类和属性的详细解释:
* <div class="row">
 作用: 在Bootstrap的网格系统中, .row类用于创建一个行容器. 它是网格系统布局的基本单位, 用于包裹列(.col-*)元素.
 特点: .row类通过负边距(margin)和padding的巧妙设置, 与列(.col-*)一起工作, 以创建灵活的响应式布局.
* <div class="col-md-8 col-md-offset-2">
 col-md-8: 这是Bootstrap网格系统中的一个列类, 表示在中等屏幕(md, 即桌面显示器)及以上尺寸的设备上,
 该元素将占据8个网格单元(Bootstrap网格系统通常基于12个网格单元).
  col-md-offset-2: 这个类用于在中等屏幕及以上尺寸的设备上, 给当前列添加一个偏移量, 即向左偏移2个网格单元,
  这通常用于居中对齐或调整列之间的间距.
* 组合效果: 这两个类一起使用时, 意味着在中等屏幕及以上尺寸的设备上, 该列将占据8个网格单元, 并且从左侧开始向右偏移2个网格单元,
 从而实现了在12个网格单元的总宽度中居中显示的效果.
* <table class="table table-hover table-striped">
 table: 这是Bootstrap中用于表格的基本类, 它提供了一些基本的样式设置, 比如边框和间距.
 table-hover: 这个类给表格的行添加了鼠标悬停(hover)效果.
 当鼠标指针悬停在表格的某一行上时, 该行会以不同的背景色高亮显示, 提高了用户体验.
 table-striped: 这个类给表格的行添加了斑马线效果, 即表格的奇数行和偶数行会有不同的背景色, 使得表格数据更加易于阅读.
* 6. 启动项目, 访问: 127.0.0.1:8000 , 查询用户信息.

image-20240817235241365

* 7. 修改视图函数, 返回json格式信息.
# index/views.py
from django.http import JsonResponse
from index import models


def index(request):
    # 获取所有用户实例
    queryset_obj = models.User.objects.all()
    # 重新封装用户数据
    users_list = []
    for user in queryset_obj:
        users_list.append({'username': user.username, 'age': user.age, 'gender': user.get_gender_display()})

    return JsonResponse(users_list, safe=False)

image-20240817010839177

* 8. 启动项目, 访问: 127.0.0.1:8000 , 查询用户信息(点击美化输入).

image-20240817010959392

5.2 serializers模块

5.2.1 序列化
在Django框架中, django.core.serializers模块提供了一种机制,
允许将Django的模型实例(如数据库中的记录)序列化为Python数据结构(如字典或列表), 或者反序列化这些数据结构回模型实例.
这对于数据的导出, 备份, API开发等场景非常有用.
序列化(Serialization)是将Django模型实例转换为其他格式(如JSON, XML等)的过程.
这对于将数据发送到前端或保存到文件中非常有用. Django的序列化API支持多种格式, 但最常用的是JSON.
示例: 将Book模型的所有实例序列化为JSON. 
* 1. 修改视图函数.
# index/views.py
from django.shortcuts import HttpResponse
from django.core import serializers
from index import models


def index(request):
    # 获取所有用户实例
    queryset_obj = models.User.objects.all()
    # 序列化成json格式数据
    data = serializers.serialize('json', queryset_obj)

    return HttpResponse(data, content_type="application/json")

serialize函数的第一个参数是你想要的序列化格式(在这个例子中是'json'), 
第二个参数是一个查询集(QuerySet), 包含了你想要序列化的模型实例.

image-20240817013922634

* 2. 启动项目, 访问: 127.0.0.1:8000 , 查询用户信息(点击美化输入).

image-20240817013850088

* 3. 如果不设置content_type="application/json", 浏览器无法识别返的数据是json格式, 无法提供美观输出.
    可以使用一些在线的json美观工具, 例如: https://www.sojson.com/ .

image-20240817014240726

5.2.2 反序列化
反序列化(Deserialization)是序列化的逆过程, 即将序列化后的数据(如JSON字符串)转换回Django模型实例.
这对于从文件或外部源导入数据到Django数据库非常有用.

注意事项: 
- 在反序列化时, 需要确保JSON字符串的格式与Django序列化API生成的格式相匹配, 包括fields, model和pk等键.
- 在进行反序列化时, 请确保是信任数据输入, 因为恶意构造的数据可能会导致安全问题.
示例: 将JSON数据反序列化为模型实例.
现有一个JSON字符串, 包含了Book模型的数据, 将这些数据保存到数据库中. 
* 1. 修改视图函数.
# index/views.py
from django.shortcuts import HttpResponse
from django.core import serializers


def index(request):
    # 假设的JSON数据,这里应该是从请求中获取的
    json_data = """  
        [  
            {  
                "model": "index.user",  
                "pk": null,  
                "fields": {  
                    "username": "xx",
                    "age": 22,
                    "gender": 1
                }  
            }  
        ]  
    """

    # 反序列化JSON数据
    data = serializers.deserialize('json', json_data)

    # 保存反序列化后的数据到数据库
    for obj in data:
        obj.save()
    return HttpResponse('写入成功!')

* 2. 启动项目, 访问: 127.0.0.1:8000 , 将实例写入数据库中.

image-20240817020009421

5.3 接口文档

在前后端分离的项目中, 后端通常会将这些数据以API接口的形式提供给前端,
并伴随着一个接口文档来详细描述每个字段的含义和接口的使用说明.

对于给出的JSON数据, 可以编写一个简单的接口文档来描述这个接口.
接口文档可以包含接口URL, 请求方法(GET, POST等), 请求参数, 响应格式, 每个字段的含义以及可能的错误响应等.
以下是一个简单的接口文档示例:
* 接口名称: 获取用户信息列表
* 接口URL: /
* 请求方法: GET
* 请求参数:  (因为这是一个获取列表的接口, 通常不需要额外的请求参数, 除非有分页, 搜索等需求).
* 响应格式: json
[  
    {  
        "username": "用户名",  
        "age": "年龄",  
        "gender": "性别"  
    },  
    ...  
]
* 字段说明:
 - username: 用户的用户名.
 - age: 用户的年龄.
 - gender: 用户的性别, 可选值包括'男', '女', '其他'.
 
* 可能的错误响应:
 - 404 Not Found: 如果请求的接口不存在.
 - 500 Internal Server Error: 服务器内部错误, 无法处理请求.
 - 自定义错误: 如接口验证失败(例如, 参数不正确)可能会返回特定的错误码和错误信息.

...
注意事项: 在实际项目中, 接口文档应该更加详细, 包括每个接口的用途, 权限要求, 请求头和响应头的定义, 分页参数, 搜索参数等.
通常会使用Swagger, API Blueprint、Postman等工具来编写和维护接口文档, 这些工具可以提供更丰富的功能和更好的用户体验.
接口的URL, 请求方法, 请求参数和响应格式等信息应该在项目一开始就定义好, 并随着项目的进行逐步完善.
在进行接口开发时, 需要遵循接口文档中的规范, 确保前后端之间的数据交互是正确和一致的.

6. 二次确认操作

二次确认操作, 又称为双重确认或双重验证, 是一种用户界面设计模式, 
用于在用户执行具有潜在重大影响或不可逆的操作之前, 再次请求用户确认其意图.
这种设计旨在减少因误操作或冲动决策而导致的错误或不良后果.

在二次确认操作中, 当用户首次尝试执行某个敏感操作(如删除文件, 清空购物车, 提交重要表单等),
系统会立即显示一个对话框或弹出窗口, 要求用户再次确认他们是否真的想要执行该操作.
这个对话框通常会包含关于操作后果的明确信息, 以及两个选项:
一个是确认继续的按钮(: '确定', '是'), 另一个是取消操作的按钮(: '取消', '否').

二次确认操作的重要性在于它为用户提供了一个'后悔'的机会, 
即使用户在最初的选择中犯了错误或改变了主意,他们也可以在执行不可逆操作之前撤回他们的选择.
这不仅有助于保护用户的数据和利益, 还有助于提高用户体验和满意度.

在实际应用中, 二次确认操作可以通过多种方式实现,包括使用JavaScript的确认对话框(虽然这种方法较为简单, 但用户体验可能不佳), 
使用专门的库或框架(如SweetAlert2), 来创建更美观, 更用户友好的确认对话框, 以及通过服务器端逻辑来确保用户已经进行了二次确认.

无论采用哪种方式, 关键都在于确保二次确认操作的清晰性, 可见性和易用性.
这意味着对话框应该包含明确且易于理解的信息, 应该位于用户视线的显著位置, 并且应该易于访问和操作.
此外, 为了确保用户体验的一致性, 建议在应用程序的所有相关部分中都采用相同的二次确认模式.

6.1 SweetAlert2框架

SweetAlert2是一个基于JavaScript的库, 官网地址: https://sweetalert2.github.io/ .
它常用用于在网页上替换标准的警告框(alert), 确认框(confirm)和提示框(prompt), 并提供更加美观和用户友好的界面.
需要在项目中引入SweetAlert2, 可以通过CDN链接或者将库文件下载到你的项目中来实现这一点. 通过CDN引入:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.4.5/dist/sweetalert2.min.css">  
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.4.5/dist/sweetalert2.min.js"></script>
文件下载地址: https://github.com/sweetalert2/sweetalert2/releases .
只需要下载sweetalert2.min.css和sweetalert2.min.js即可.

image-20240818103640484

在静态文件夹中创建一个文件夹sweetalert2-11.12.4存放下载的文件.

image-20240818104141821

以下是一个简单的示例, 展示了如何在用户点击某个按钮时触发SweetAlert2对话框进行二次确认, 并在确认后通过AJAX发送请求:
* 1. 修改视图函数, GET请求返回一个页面, POST返回一个提醒信息.
from django.shortcuts import render, HttpResponse


def index(request):
    if request.method == 'GET':
        return render(request, 'index.html')
    else:
        return HttpResponse('操作成功!')

* 2. 修改模板页面, 点击删除按键的时候弹出一个二次确认框, 点击确认操作后使用Ajax发送POST请求.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>二次确认实例</title>
    <!-- 引入 SweetAlert2 CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.4.5/dist/sweetalert2.min.css">
    <!-- 引入 SweetAlert2 JS -->

    <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.4.5/dist/sweetalert2.min.js"></script>
    <!-- 导入jQuery 文件-->
    {% load static %}
    <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
</head>
<body>
{% csrf_token %}
<button id="deleteButton">删除</button>


<script>
    //  为按键绑定二次弹框
    $('#deleteButton').on('click', function () {
        // 获取csrf令牌
        var csrfmiddlewaretoken = $('input[name=csrfmiddlewaretoken]').val();

        // 使用 SweetAlert2 显示确认对话框
        Swal.fire({
            title: '确定吗?',  // 设置对话框的标题
            text: "操作后无法撤销的!",  // 设置对话框的文本内容
            icon: 'warning',  // 设置对话框的图标类型为警告

            confirmButtonColor: '#3085d6',  // 设置确认按钮的颜色
            confirmButtonText: '确定!',  // 自定义确认按钮的文本

            showCancelButton: true,  // 显示取消按钮
            cancelButtonColor: '#d33',  // 设置取消按钮的颜色
            cancelButtonText: '取消'  // 自定义取消按钮的文本

        }).then((result) => {  // 点击确认后发送ajax请求
            if (result.isConfirmed) {  // 用户点击了确认按钮
                // 这里使用 AJAX 发送请求
                $.ajax({
                    url: '/',  // 请求地址
                    type: 'post',  // 请求方式
                    data: {
                        meg: '提交一个删除的请求!',
                        csrfmiddlewaretoken: csrfmiddlewaretoken
                    },  // 发送的信息
                    success: function (data, textStatus, jqXHR) {
                        // 删除成功
                        Swal.fire(
                            '删除成功!',
                            data,  // 展示返回的信息
                            'success'
                        );
                    },
                    error: function (xhr, status, error) {
                        Swal.fire(
                            '网络错误',
                            '无法连接到服务器.',
                            'error'
                        );
                    },
                })
            }
        });
    });
</script>
</body>
</html>

image-20240817231802285

* 3. 启动项目, 访问: 127.0.0.1 , 点击删除按键进行测试.

GIF 2024-8-17 23-13-47

这个示例展示了如何在用户点击删除按钮后, 使用SweetAlert2弹出确认对话框, 并在用户确认后通过AJAX发送删除请求.
请求成功后, 会再次使用SweetAlert2显示成功或错误消息.

6.2 二次确认删除操作

二次确认删除操作示例:
* 1. 编写视图函数返回所有用户的信息.
# index/views.py
from django.shortcuts import render
from index.models import User


def index(request):
    if request.method == 'GET':
        # 获取所有用户实例
        users = User.objects.all()
        return render(request, 'index.html', locals())

image-20240817235413097

* 2. 编写模板页面, 展示所有用户的信息, 用户点击删除按钮的时候使用ajax发送get请求删除数据.
    前端页面二次确认删除之后发送ajax请求并携带删除的数据id. 携带数据的方式:
    方式1: 通过URL传递.
    方式2: 放在请求体内.
   
   通过jQuery来访问这种自定义的数据属性, 只要将它们以data-前缀开头来命名.
   这是HTML5中引入的自定义数据属性(data-*)的标准方式.
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>用户数据</title>
        {% load static %}
        <!-- 导入jQuery 文件-->
        <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
        <!-- 导入bootstrap css 文件-->
        <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
        <!-- 导入bootstrap css 文件-->
        <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
        <!-- 导入sweetalert2 css 文件-->
        <link rel="stylesheet" href="{% static 'sweetalert2-11.12.4/sweetalert2.min.css' %}">
        <!-- 导入sweetalert2 js 文件-->
        <script src="{% static 'sweetalert2-11.12.4/sweetalert2.min.js' %}"></script>
    </head>
    <body>
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <table class="table table-hover table-striped">
                    <thead>
                        <tr>
                            <th>姓名</th>
                            <th>年龄</th>
                            <th>性别</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for obj in queryset_obj %}
                        <tr>
                            <td>{{ obj.username }}</td>
                            <td>{{ obj.age }} </td>
                            <td>{{ obj.get_gender_display }}</td>
                            <td>
                                <!--删除按钮, 使用data-*自定义表单属性-->
                                <a class="btn btn-danger btn-sm del" data-del_id="{{ obj.pk }}">删除</a>
                            </td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </body>
    <script>
        // 为删除按钮绑定点击事件
        $('.del').on('click', function () {
            // 有多个按钮, 使用this获取当前点击按钮, 再获取id的值
            var currentBtn = $(this)
            // 使用jQuery的.data()方法或.attr('data-del_id')来获取值
            var del_id = currentBtn.data('del_id');

            // 使用 SweetAlert2 显示确认对话框
            Swal.fire({
                title: '确定吗?',  // 设置对话框的标题
                text: "操作后无法撤销的!",  // 设置对话框的文本内容
                icon: 'warning',  // 设置对话框的图标类型为警告

                confirmButtonColor: '#3085d6',  // 设置确认按钮的颜色
                confirmButtonText: '确定!',  // 自定义确认按钮的文本

                showCancelButton: true,  // 显示取消按钮
                cancelButtonColor: '#d33',  // 设置取消按钮的颜色
                cancelButtonText: '取消'  // 自定义取消按钮的文本

            }).then((result) => {  // 点击确认后发送ajax请求
                if (result.isConfirmed) {  // 用户点击了确认按钮
                    // 这里使用 AJAX 发送请求
                    $.ajax({
                        url: '/del_user/' + del_id,  // 请求地址, 携带用户id
                        type: 'get',  // 请求方式
                        success: function (data, textStatus, jqXHR) {
                            // 删除成功
                            Swal.fire(
                                '删除成功!',
                                data,  // 展示返回的信息
                                'success'
                            );
                        },
                        error: function (jqXHR, textStatus, errorThrown) {
                            if (jqXHR.status === 400) {
                                Swal.fire('删除失败', jqXHR.responseText, 'error');

                            } else {
                                Swal.fire('网络错误', '请稍候再试!', 'error');
                            }
                        },
                    })
                }
            });

        })
    </script>
</html>

2024-08-18_112847

* 3. 添加删除用户的请求, 获取get请求中的del_id参数.
# index/urls.py
from django.urls import path
from index.views import index, del_user

urlpatterns = [
    path('', index, name='index'),
    path('del_user/<int:user_id>', del_user, name='del_user')
]

image-20240818121012973

* 4. 添加视图函数, 通过用户id删除用户数据.
# index/views.py
from django.shortcuts import render, HttpResponse
from index.models import User


def index(request):
    if request.method == 'GET':
        # 获取所有用户实例
        queryset_obj = User.objects.all()
        return render(request, 'index.html', locals())


def del_user(request, user_id):
    if request.method == 'GET':
        # 删除数据
        User.objects.filter(pk=user_id).delete()
    return HttpResponse('删除数据成功!')

image-20240818121036691

* 5. 删除数据后触发回调函数提示删除成功, 并更新页面的信息.
    删除数据后的页面更新的方式:
    - 方式1: 使用window.location.reload().
      这种方式会重新加载整个页面, 简单直接, 但可能不是最高效的, 因为它会重新加载所有资源, 包括那些可能未改变的资源.
    - 方式2(推荐): 利用DOM操作动态刷新.
      如果只需要更新页面上的特定部分, 使用DOM操作是更好的选择.
      例如, 如果知道要删除的元素是某个按钮的父元素的父元素, 可以这样做:
      currentBtn.parent().parent().remove(); // 移除包含被删除数据的DOM元素     
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>用户数据</title>
        {% load static %}
        <!-- 导入jQuery 文件-->
        <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
        <!-- 导入bootstrap css 文件-->
        <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
        <!-- 导入bootstrap css 文件-->
        <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
        <!-- 导入sweetalert2 css 文件-->
        <link rel="stylesheet" href="{% static 'sweetalert2-11.12.4/sweetalert2.min.css' %}">
        <!-- 导入sweetalert2 js 文件-->
        <script src="{% static 'sweetalert2-11.12.4/sweetalert2.min.js' %}"></script>
    </head>
    <body>
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <table class="table table-hover table-striped">
                    <thead>
                        <tr>
                            <th>姓名</th>
                            <th>年龄</th>
                            <th>性别</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for obj in queryset_obj %}
                        <tr>
                            <td>{{ obj.username }}</td>
                            <td>{{ obj.age }} </td>
                            <td>{{ obj.get_gender_display }}</td>
                            <td>
                                <!--删除按钮, 使用data-*自定义表单属性-->
                                <a class="btn btn-danger btn-sm del" data-del_id="{{ obj.pk }}">删除</a>
                            </td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </body>
    <script>
        // 为删除按钮绑定点击事件
        $('.del').on('click', function () {
            // 有多个按钮, 使用this获取当前点击按钮, 再获取id的值
            var currentBtn = $(this)
            // 使用jQuery的.data()方法或.attr('data-del_id')来获取值
            var del_id = currentBtn.data('del_id');

            // 使用 SweetAlert2 显示确认对话框
            Swal.fire({
                title: '确定吗?',  // 设置对话框的标题
                text: "操作后无法撤销的!",  // 设置对话框的文本内容
                icon: 'warning',  // 设置对话框的图标类型为警告

                confirmButtonColor: '#3085d6',  // 设置确认按钮的颜色
                confirmButtonText: '确定!',  // 自定义确认按钮的文本

                showCancelButton: true,  // 显示取消按钮
                cancelButtonColor: '#d33',  // 设置取消按钮的颜色
                cancelButtonText: '取消'  // 自定义取消按钮的文本

            }).then((result) => {  // 点击确认后发送ajax请求
                if (result.isConfirmed) {  // 用户点击了确认按钮
                    // 这里使用 AJAX 发送请求
                    $.ajax({
                        url: '/del_user/' + del_id,  // 请求地址, 携带用户id
                        type: 'get',  // 请求方式
                        success: function (data, textStatus, jqXHR) {
                            // 删除成功
                            Swal.fire(
                                '删除成功!',
                                data,  // 展示返回的信息
                                'success'
                            );
                            // 找到tr标签进行移除
                            currentBtn.parent().parent().remove();
                        },
                        error: function (jqXHR, textStatus, errorThrown) {
                            if (jqXHR.status === 400) {
                                Swal.fire('删除失败', jqXHR.responseText, 'error');

                            } else {
                                Swal.fire('网络错误', '请稍候再试!', 'error');
                            }
                        },
                    })
                }
            });

        })
    </script>
</html>
* 6. 启动项目, 进行删除数据的测试.

GIF 2024-8-18 11-26-28

说一个小故事, 有人孩子刚睡醒想要测试上面的示例, 于是启动项目, 访问: 127.0.0.1:8000/1 , 
然后数据删除了返回的是页面提示, 而不是SweetAlert2提示, 他就疑惑为啥不是SweetAlert2提示???
还想什么呢? 通过ajax请求才会触发回调函数, 直接访问返回肯定是一个页面啊...
  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值