Django与ajax交互及序列化、跨域详解

前言

这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题

于是乎,我自己开发了一款面试宝典,希望能帮到大家,也希望有更多的Python新人真正加入从事到这个行业里,让python火不只是停留在广告上。

微信小程序搜索:Python面试宝典

或可关注原创个人博客:https://lienze.tech

也可关注微信公众号,不定时发送各类有趣猎奇的技术文章:Python编程学习

Ajax

ajax可以使当前浏览器不需要整个重新加载,只是局部刷新,给用户的体验良好,并且相对而言效率更高一些

  • 同步交互:客户端发出一个请求后,需要等待服务器相应结束后,才可以发起第二个请求

  • 异步交互:客户端发出一个请求后,无需等待该次服务器的相应,即可发起第二个请求

什么是json

解决xml格式复杂的问题,是一个多种语言通用的轻量级数据交换格式;几乎所有语言都可以生成和解析json格式的数据

  • 数据在键值对中
  • 数据由逗号分隔
  • 花括号存储数据
  • 方括号保存数组
[
    {
    
     "name":"Bill", "age":1 },
    {
    
     "name":"George", "age":2 },
    {
    
     "name":"Thomas", "age": 3 }
];
  • 我们一般通过后段返回json数据与前端进行ajax异步数据通信

Jqery-ajax

使用ajax进行django后台数据的异步获取,django只是提供的数据,不承担前端页面的渲染工程;这里使用jQuery所提供的ajax方法进行异步通信

  • 首先测试数据库中模型类定义如下:
class Article(models.Model):
    title = models.CharField(max_length=50,verbose_name="标题")
    author = models.CharField(max_length=20,verbose_name="作者")
    date = models.DateField(auto_now_add=True,verbose_name="发表日期")
    content = models.TextField(verbose_name="文章内容")

    def __str__(self):
        return self.title

测试数据可由用户自行添加,非常简单

  • 编写主页视图函数,返回所有数据库中内容
def index(request):
    articles = models.Article.objects.all()
    return render(request,'ajax/index.html',locals())

此处的index.html页面不光承担所有数据的渲染工作,还将负责未来ajax异步请求,获取对应文章的详细内容

  • index.html页面代码
<style>
    label{
    
    
        border: 5px outset gray;
        width: 150px;
        margin-top: 10px; 
    }
</style>
<head>
    {% load staticfiles %}
    <meta charset="utf-8">
    <title>Ajax测试</title>
    <script type="text/javascript" src="{% static 'js/jquery-1.10.2.min.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/jquery.cookie.js' %}"></script>
    <!-- 该js文件用来引入jquery所提供的获取cookie值的库 为了提取对应csrf_token-->
</head>

<body>
    <h1>这是一个ajax的请求测试</h1>
        {% for article in articles %}
            <label class="{
   
   { article.id }}">{
   
   { article.author }}:{
   
   { article.title }}</label>
        {% endfor %}
    <p class="content"></p>
</body>
<script type="text/javascript">
    $(document).ready(function () {
    
    
        $("label").click(function () {
    
    
            $.ajax({
    
    
                url: '/article/', // 请求地址,对应Django某个路由映射
                type: 'POST', // 请求方式 post
                data: {
    
    
                    'csrfmiddlewaretoken': $.cookie('csrftoken'),
                    // 提交数据需有当前csrf_token 防跨站请求伪造令牌
                    'id_': $(this).attr('class'),
                    // 获取当前的id值 传递到视图后台
                },
                success: function (result) {
    
    
                    var data = JSON.parse(result)
                    // 解析获得实际字符串
                    $('.content').html(data)
                    // 将内容以html形式显示到对应的p标签上
                }
            })
        })
    })
</script>

有了前端页面,并且ajax的请求地址为/article/

那么就需要我们定义一个视图函数返回对应的json数据,并且设置路由为/article/

#urls.py
path('ajax/',ajaxviews.index), # 首页路由
path('article/',ajaxviews.article) # ajax请求路由
#views.py
def article(request):
    if request.is_ajax(): # 判断是否为ajax请求
        if request.method == "POST": # 为ajax的post方式请求
            id_ = request.POST.get('id_')
            if id_:
                try:
                    content = models.Article.objects.get(id=id_)
                    content = content.replace('\r\n','<br>')
                		# 这里还将获取到的文章字符串内容中的换行替换为HTML的换行标签
                except models.Article.DoesNotExist:
                    raise Http404
                else:
                    data = json.dumps(content,ensure_ascii=False,cls=JsonEncoder) 
                    # 返回get对应取到的实际属性
                    return HttpResponse(data)
    raise Http404

这里要注意的是,后端返回的数据得是序列化之后的才可以被前端js所解析

直接返回一个django model数据实例是不行的。所以需要我们视图函数对需要返回的数据进行序列化操作


序列化

django中原生的对于数据的序列化操作主要有以下两种

Json序列化

普通Python数据直接使用json模块进行序列化

content = models.Article.objects.get(id=id_).content.replace('\r\n','<br>')
#这里将文章内容对应返回,之所以有replace函数,是因为文章数据是通过admin后台复制添加,需要将其中的\r\n换行转换为HTML可以解析的<br>标识符
data = json.dumps(content,ensure_ascii=False)
# 第二个参数是因为序列化时对中文默认使用的ascii编码,此时需要将该值设置为False,这样前端接收到时才是一个正常中文结果
return HttpResponse(data)
  • 注意:如果要序列化的数据中包含时间类型datedatetime时,这种办法就会报错
TypeError: Object of type date is not JSON serializable
class JsonEncoder(json.JSONEncoder):
    # 自定义json处理器
    def default(self, obj):
        if isinstance(obj, datetime):
            # 如果判断到类型为datetime格式
            return obj.strftime('%Y-%m-%d %H:%M:%S')
            # 处理为字符串类型的 (年-月-日 时:分:秒)
        elif isinstance(obj, date):
            # 如果判断到json处理数据为date类型
            return obj.strftime('%Y-%m-%d')
        else:
            return json.JSONEncoder.default(self,obj)
            # 其他数据类型按照默认的序列化方式处理即可

使用cls指定序列化方式,即可轻松解决特殊格式没有办法被json序列化的问题

content = models.Article.objects.get(id=id_).date
data = json.dumps(content,ensure_ascii=False,cls=JsonEncoder)
# 通过json.dumps的cls参数指明所使用的自定义序列化类
return HttpResponse(data)

如果返回的数据并不是一个单独的数据属性,那么也可以通过json进行处理,以一个数据列表的形式返回

content = models.Article.objects.filter(id=id_).values()
data = json.dumps(list(content),ensure_ascii=False,cls=JsonEncoder)
return HttpResponse(data)

对应前端接收展示

<div class="content">
	<!-- 这里用到的不是之前的p标签 而是一个div容器 -->
</div>
success: function (result) {
    
    
    var data = JSON.parse(result)
    var tag = ''
    for (var i = 0, len = data.length; i < len; i++) {
    
    
    // 如果需要展示的是所有的结果,可以通过js的for循环
        tag += '<p>' + data[i]['content'].replace(/\r\n/g, "<br>") + '</p>'
        tag += '<hr>'
    }
    $('.content').html(tag)
}

serializer序列化

serializer是由django所提供的一个专门用来处理django数据对象(django model)变为序列化数据的框架

这种序列化不支持单个对象,比如像objects.get获取到的数据,或是Python中的 str等数据类型

该序列化框架所提供的功能类位于django.core.serializers

#views.py 
from django.core import serializers
content = models.Article.objects.filter(id=id_)
data = serializers.serialize('json',content,ensure_ascii=False)
return HttpResponse(data)
var data = JSON.parse(result)[0]['fields']['content'] // 序列化传输方式
$('.content').html(data.replace(/\r\n/g,"<br>"))
console.log(data)
  • 总结:通过管理器的get方法获取到的是一个独立的结果,并不是一个QuerySet数据对象,也不是一个普通Python数据类型;只能对数据其中的某条属性进行json格式的处理或是将其变为列表等序列数据类型之后再进行序列化处理

serializer反序列化

这是一种序列化的反向操作,将json数据转换为序列化之前的样子

看个demo

from django.core import serializers
content = models.Article.objects.filter(id=id_) # QuerySet
data = serializers.serialize('json',content,ensure_ascii=False) # str
content = serializers.deserialize("json", data)
return HttpResponse(data)

跨域

浏览器有一个很重要的概念:同源策略(Same-Origin Policy)

所谓同源是指,域名,协议,端口相同

不同源的客户端脚本javascript、ActionScript在没明确授权的情况下,不能读写对方的资源

  • 同源:请求资源的地址与请求的发起方都属于同一域名下

JSONP

JSONPJSON with padding(填充式JSON 或参数式 JSON)的简写

JSONP实现跨域请求的原理简单的说,就是动态创建<script>标签,然后利用<script>src不受同源策略约束来跨域获取数据。


JSONP由两部分组成

  • 回调函数:回调函数是当响应到来时应该在页面中调用的函数;回调函数的名字一般是在请求中指定的

  • 数据:数据就是传入回调函数中的参数

  • 注意JSONP方式解决AJAX跨域,必须使用get方式,并且该方式常在一些数据量级比较小的情况下,因为需要服务端后台构建回调函数带参数的字符串,像是下面这样

def index(request):
    name = request.GET.get('name') + '哈哈哈哈哈'
    callback = request.GET.get('callback')
    data = '%s("%s")' % (callback,name) 
    # 这里以前端生成的回调函数名作为函数名,待返回数据作为参数返回
    return HttpResponse(data) 
  • 前端代码:点击按钮传送表单的值到后台,并由后台处理后追加内容返回,返回的结果展示再p标签处
<input type='text' id='ajax_data'>
<button>
    按钮
</button>
<p id="content"></p>
  • Ajax代码,获取当前表单数据,并使用get方式传递到服务端
$(document).ready(function () {
    
    
    $("button").click(function () {
    
    
        $.ajax({
    
    
            url: 'http://127.0.0.1:8000/axios/', // 请求地址,对应Django某个路由映射
            type: 'get', // 请求方式 post
            dataType: "jsonp", // 指定服务端返回的数据为jsonp格式
            data: {
    
    
                'name': $('#ajax_data').val(),
            },
            success: function (result) {
    
    
                console.log(result)
                $('#content').html(result)
            }
        })
    })
})
  1. ajax发起请求,并指定服务端返回数据类型为jsonp格式
  2. 服务端构建函数包含参数的字符串,为jsonp请求发起时,给定的回调参数名,参数为要返回的数据
  3. 客户端先会调用回调函数,然后会调用success回调函数可以接收处理服务端返回的数据
    • success回调函数是成功返回数据后必定会调用的函数

Cross-Origin

跨域资源共享CORS(Cross-Origin Resource Sharing)是一种机制,它使用额外的HTTP头来告诉浏览器,让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源

当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求

  • 注意:不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了

实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信

  • 这里需要我们将后端视图函数在接收到请求时,返回结果指明头部信息
class Cors(View):
	def post(self,request):
			#判断是否为ajax请求
		name = request.POST.get('name')
		response = HttpResponse(json.dumps('OK'))
		response["Access-Control-Allow-Origin"] = "http://127.0.0.1:5500"
		# 允许可以跨域请求的站点
		response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"
		# 允许可以跨域访问的请求方式
		response["Access-Control-Allow-Headers"] = "*"
		# 允许可以跨域请求时的头部字段
		return response
  • 前端页面的ajax代码正常提交数据即可
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Page Title</title>
    <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
</head>
<body>
    <input type="text" id='name'>
    <button id='button'>提交</button>
</body>
<script>
    $('#button').click(function (){
      
      
        $.ajax({
      
      
            url: 'http://127.0.0.1:8000/',
            type: 'post',
            data: {
      
      
                name: $('#name').val()
            },
            success: function(result){
      
      
                console.log(result)
            }
        })
    })
</script>
</html>

django-cors-headers

除了以上手动构建返回结果的头部信息用来解决跨域问题

django中还可以通过一个先成可以自动添加CORS-Header的中间件,只需要在settings.py中做一些简单的配置即可

  • 使用该中间件需要安装django的三方插件
pip install django-cors-headers
  • 安装完成之后,在djangosettings文件中加载app
# settings.py
INSTALLED_APPS = [
	...
    'django.contrib.staticfiles',
    'corsheaders',
]
  • 接下来在中间件配置部分加载该插件所提供的中间件
# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware', # 顺序需要在common组件之前
    'django.middleware.common.CommonMiddleware',
    #'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
  • 继续配置允许跨站请求的白名单设置等属性
# settings.py
CORS_ORIGIN_ALLOW_ALL = False # 是否允许其他所有站点发起跨站请求
CORS_ORIGIN_WHITELIST = (
    'http://127.0.0.1:5500',
) # 跨站请求白名单

CORS_ALLOW_METHODS = (
    'POST',
) #  允许跨站访问的请求方式

CORS_ALLOW_HEADERS = (
    '*',
) # 允许跨站请求头中的字段类型
  • 注:其中某些设置的默认值为如下所示
default_headers = (
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
)

default_methods = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
)

猜你喜欢

转载自blog.csdn.net/HeroicLee/article/details/121508528
今日推荐