自学Python第二十二天- Django框架(三) AJAX、文件上传、多APP开发、iframe、验证码、分页器、类视图、中间件、信号、日志、缓存、celery异步

Django官方文档

django 使用 AJAX

django 项目中也可以使用 ajax 技术

前端

前端和其他 web 框架一样,需要注意的是,django 接收 POST 请求时,需要 csrf_token 进行验证,而在 ajax 中获取 csrf_token 比较麻烦。所以通常会在后端免除 csrf_token 验证。

绑定事件方式

{% extends 'layout.html' %}
{% block content %}
    <div class="container">
        <h1>任务管理h1>
        <input type="button" class="btn btn-primary" value="点击" onclick="clickMe();"/>
    div>
{% endblock %}
{% block js %}
    <script type="text/javascript">
        function clickMe() {
            $.ajax({
                url: "/test/ajax/",
                type: "get",
                data: {
                	type:'add',
                    n1:123,
                    n2:456
                },
                success: function (res) {
                    console.log(res);
                }
            })
        }
    script>
{% endblock %}

以 jQuery 方式

{% extends 'layout.html' %}
{% block content %}
    <div class="container">
        <h1>任务管理h1>
        <input type="button" class="btn btn-primary" value="点击" id="btn1" />
    div>
{% endblock %}
{% block js %}
    <script type="text/javascript">
        $(function (){
            // 页面框架加载完成后代码自动执行
            bindBtn1Event();        // 绑定事件
        })
        function bindBtn1Event(){
            $("#btn1").click(function (){       // 绑定到 btn1 的 click 事件的函数
                $.ajax({
                    url: "/task/ajax/",
                    type: "POST",
                    data: $("#addForm").serialize(),
                    dataType:'JSON',
                    success: function (res) {
                        console.log(res);
                        // console.log(res.res_add)
                        // console.log(res['res_add'])
                    }
                })
            })
        }
    script>
{% endblock %}

后端

后端部分只要将响应请求 url 绑定视图函数,则可以在视图函数中进行处理

def task_ajax(request):
    """测试ajax"""
    return HttpResponse('成功')

从请求体中获取数据

对于 request.POST 在使用中有一些限制:

  • 只能用于 POST 请求,对于 PUT、DELETE 等非 POST 请求不能使用
  • 只能用于表单数据,即 content-typeapplication/x-www-form-urlencoded 类型的请求

对于其他数据,必须从请求体中获取。因为 request.body 中的数据是字节型,所以需要先解析。

import json

def data_form_body(request):
	req_dict = json.loads(request.body)
	category = req_dict.get('category)

返回 json 数据

可以使用 json 的 dumps 方法将字典转为 json 并返回

import json

def task_ajax(request):
	res_dict = {'res':'ok', 'data':{'k1': 'v1', 'k2': 'v2'}}
	res_dict = json.dumps(res_dict)
    return HttpResponse(res_dict)

也可以直接使用 JsonResponse 返回数据

from django.http import JsonResponse

def task_ajax(request):
	res_dict = {'res':'ok', 'data':{'k1': 'v1', 'k2': 'v2'}}
    return JsonResponse(res_dict)

禁用 csrf 校验检查

前端发送 post 请求时如果不加载 csrf_token,则后端需要禁用 csrf 校验检查

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def task_ajax(request):
	return HttpResponse('成功')

或在注册路由时注明

from django.views.decorators.csrf import csrf_exempt

urlpatterns = [
	path('goods/', csrf_exempt(views.goods), name='goods'),
]

或在settings.py 的中间件中取消 csrf 中间件(不推荐)。

Ajax 结合 ModelForm

Ajax 结合 ModelForm 在前端实际上几乎没有改变,要注意的也就是按钮绑定 Ajax 函数不用 csrf_token

在后端,ModelForm 其实接收到的数据也是 form 的字典格式,包含校验、保存等处理方式没有变化,只是在返回值时有了变化。

处理重定位信息

因为 Ajax 是接收处理数据,所以后端返回 重定位 信息是无法跳转的。如果希望跳转,则需要返回一个 json ,ajax 收到了这个特定的 json 数据后使用 js 进行页面跳转。

处理表单错误信息

另外返回 ModelForm 错误信息时,form.字段.errors 获取的是一个字典,可以整理成 json 格式返回给 ajax 处理。


<form id="addForm" novalidate>
    <div class="clearfix">
        {% for field in form %}
            
            <div class="col-xs-6">
                <div class="form-group">
                    <label>{{ field.label }}label>
                    {{ field }}
                    
                    
                    <span style="color: red">span>
                div>
            div>
        {% endfor %}
        <div class="col-xs-12">
            <button type="button" id="btnAdd" class="btn btn-primary">添加button>
        div>
    div>
form>
<script type="text/javascript">
    $(function () {
        // 页面框架加载完成后代码自动执行
        bindBtnAddEvent();
    })
    function bindBtnAddEvent() {
        $("#btnAdd").click(function () {
        	$(".error-msg").empty();        // 清空上一次的错误信息
            $.ajax({
                url: "/task/add/",
                type: "POST",
                data: $("#addForm").serialize(),
                dataType: 'JSON',
                success: function (res) {
                    if(res.status){
                        alert("添加成功!");
                    }else{
                        console.log(res);
                        $.each(res.error,function (name,data){      // 循环每一个错误,获取键值
                            // 拼接 id_ 和 name,获取对应文本框的 id
                            // 查找文本框元素的下一个元素(span),使其文本(text)为错误信息
                            $("#id_" + name).next().text(data[0]);
                        })
                    }
                }
            })
        })
    }
script>

文件上传

简单的文件上传

前端会将文件以 post 请求发送至后端,后端可以使用 request.FILES 来接收。需要注意的是 form 需要 enctype 属性。

<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <input type="text" name="filename">
    <input type="file" name="file">
    <input type="submit" value="提交">
form>
file_obj = request.FILES.get('file')  # 获取上传文件的对象
filename = request.POST.get('filename', file_obj.name)      # 获取设置的文件名,如果没有则为原文件名。
with open(filename, mode='wb') as f:
    for chunk in file_obj.chunks():  # 将上传文件分块读取
        f.write(chunk)
    f.flush()	# 文件写入完成,冲刷缓冲区

ajax 上传文件

ajax 上传文件和发送 json 的不同在于,发送的数据是一个 FormData 对象,创建这个对象时可以将表单 form 的 dom 对象传入。

$("#upload").click(function () {
   var formData = new FormData($('#uploadForm')[0]);
   // 或使用 FormDate 对象添加文件对象的方式
   // var formData = new FormData();
   // formDate.append('file', this.file[0]);		//这里的 this 指向上传文件的 input 标签的dom对象
   // formDate.append('key', value);		// 可以添加其他的数据
   $.ajax({
    type: 'post',
    url: "https://****:****/fileUpload", //上传文件的请求路径必须是绝对路劲
     data: formData,
     cache: false,
     processData: false,
     contentType: false,
      }).success(function (data) {
        console.log(data);
        alert("上传成功"+data);
        filename=data;
      }).error(function () {
         alert("上传失败");
     });
    });

后端接收时,注意接收字段是前端定义的 name ,文件从 requests.FILES 里获取

uid = requests.POST.get('uid')		# 获取数据
file = requests.FILES.get('file')	# 获取文件
# files = requests.FILES.getlist('files')	# 获取多个文件

使用 Form 组件上传文件

使用 Form 时可以定义文件字段 FileField ,定义后就能够上传文件了。

class UpForm(forms.Form):
	name = forms.CharField(label='姓名')
	age = forms.IntegerField(label='年龄')
	img = forms.FileField(label='头像')

def upload_form(request):
	form = UpForm(data=request.POST, files=request.FILES)
	if form.is_valid():
		print(form.cleaned_data)
		# 文件在 form.cleaned_data 的相应字段(img)内
		img = form.cleaned_data.get('img')
		file_path = os.path.join('app01', 'static', 'img', img.name)	# 拼接文件路径
		with open(file_path,'wb') as f:		# 写入文件
			for chunk in img.chunks():
				f.write(chunk)

	else:
		return render(request, 'upload.html', {'form': form})
<form method="post" enctype="multipart/form-data" novalidate>
	.
	.
	.
form>

文件上传至上传目录 media

通常使用的静态资源都在 static 文件夹内,如果想要用户上传文件到一个特定文件夹,例如项目根目录下的 media 文件夹,则需要在 urls.py 中进行设置启用。首先引入一些资源,再在 urls.py 文件的 urlpatterns 字段添加:

from django.views.static import serve
from django.urls import path, re_path
from django.conf import settings

re_path(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT}, name='media'),

然后在 settings.py 中进行配置:

import os

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')		# 项目根目录下的 media 目录
MEDIA_URL = '/media/'

此时上传文件保存的路径可以写为

media_file_path = os.path.join('media', image_obj.name)

这样将用户上传的文件放在 media 目录下,也可以通过 /media/文件名 的 url 来访问,例如 http://127.0.0.1:8000/media/001.png

使用 ModelForm 组件上传文件

ModelForm 组件可以自动上传文件,并存储保存路径到数据库,不用再写相应保存的代码,且能够自动进行重名处理。只是需要设置了上传保存目录。

需注意的是,保存到数据库中的文件路径也是 media 下的相对路径,并且不包含 media ,使用时候注意添加 media。

# models.py

class City(models.Model):
	"""城市"""
	name = models.CharField(verbose_name='名称', max_length=32)
	count = models.IntegerField(verbose_name='人口')

	# 本质上数据库存储的是文件路径,也是CharField,可以自动保存数据
	# upload_to 指的就是上传文件到哪个目录,是 media 目录下的相对路径
	img = models.FileField(verbose_name='logo', max_length=128, upload_to='city/')
# views.py

class UpModelForm(forms.ModelForm)
	class Meta:
		model = models.City
		fields = '__all__'
	
def upload_modal_form(request):
	form = UpModelForm(data=request.POST, files=request.FIELS)
	if form.is_valid():
		# 保存文件,保存 form 字段信息和文件路径并写入数据库
		form.save()
		return HttpResponse('成功')

多 app 开发

django 多人协作开发时,每人开发不同的 app 也可能会遇到一些问题:对于相同名称的模板或静态资源的使用问题。因为 django 搜索静态资源和模板是安装 app 注册的顺序,在各 app 目录下查找模板或静态资源文件。如果有相同名称的模板或静态资源文件,后注册的 app 就会使用先注册 app 的同名文件了。

所以对于模板文件来说,在各自 app 下的 templates 文件夹下建立 app 名称的文件夹,再建立模板文件,使得各模板文件引用时会加入各自 app 的名称,这样就不会出现引用路径相同的问题了。

对于静态资源文件,因为涉及公共静态资源,所以需要注册静态资源路径。

# settings.py

STATIC_URL = '/static/'		
STATICFILES_DIRS = [
  os.path.join(BASE_DIR, "static"),			# 主静态文件目录
  os.path.join(BASE_DIR, "main", "static"),		# main app 静态文件目录
  os.path.join(BASE_DIR, "login", "static"),	# login app 静态文件目录
]

然后可以在每个APP下的static下建立以APP名相同的文件夹,比如在 login/static/login/ 放入样式JS CSS等

调用时使用 static 结构,加上 app 名称

{% static 'main/img/firefox-logo-small.jpg' %}

{% static 'login/img/name.png' %}

另外对于静态文件的打包整合可以参考这篇文章:

解决django多个app下的静态文件无法访问问题

使用 iframe

django 使用 iframe 时,可能浏览器会出现错误,根据提示信息发现是因为 X-Frame-Options=deny 导致的。

Refused to display xxx in a frame because it set 'X-Frame-Options' to 'deny'.

官方文档中关于点击劫持保护

点击劫持保护

The X-Frame-Options HTTP 响应头是用来给浏览器 指示允许一个页面 可否在 ,