Django ajax,基于ajax的form表单上传,ajax的csrf认证和同源策略,csrf局部禁用, ImageField 和 FileField字段

ajax常见的参数

type: "post",
contentType: "application/json; charset=utf-8",//请求时候的传参类型
dataType: "json",          //返回值类型
url: "ajax/getData",       发送请求的地址  默认为当前页地址
data:JSON.stringify(json),
timeout:80                  //要求为Number类型的参数,设置请求超时时间(毫秒)。此设置将覆盖$.ajaxSetup()方法的全局设置。
async: True  //默认设置为true,所有请求均为异步请求。如果需要发送同步请求,请将此选项设置为false。注意,同步请求将锁住浏览器,用户其他操作必      
             //须等待请求完成才可以执行
success: function(data){   //成功回调函数
    console.log(data);
},
error : function(msg) {    //错误回调函数
    console.log(msg);

ajax接受的数据类型 ContentType

ContentType:application/x-www-form-urlencoded :

中默认的encType,form表单数据被编码为key/value格式发送到服务器
(表单默认的提交数据的格式)默认的是urlencoded编码格式 对应的form表单的enctype是multipart/form-data 用来上传文件 用request.FILES获取对象

ContentType:urlencode 对应的form表单的enctype是application/x-www-form-urlencoded 请求体内容为(a=1&b=2&c=3),也会按照这种url的构
成形式进行解码, request.POST和request.BODY都可以取到值,
只不过request.POST按照url的格式解析成了字典格式,而request.BODY解析的是a=1&b=2的字符串
如果content_type是application/json的话 提交数据要从request.body取.需要使用JSON.stringify对data进行序列化,content_type是urlencode的话是从request.POST取
ContentType:application/json 对应的json字符串
ContentType:application/xhtml+xml :XHTML格式
ContentType:application/xml : XML数据格式
ContentType:application/atom+xml :Atom XML聚合格式
ContentType:application/json : JSON数据格式
ContentType:application/pdf :pdf格式
ContentType:application/msword : Word文档格式
ContentType:application/octet-stream : 二进制流数据(如常见的文件下载)

基于ajax实现的账号密码校验

基于Form表单实现的文件上传

form的action 如果加aaa/ 那么发送请求的网址是:当前url/aaa/ 如果加/aaa/ 那么当前网址会被覆盖请求的url是:/aaa/

<h3>form表单上传文件</h3>

<form action="/upload_file/" method="post" enctype="multipart/form-data"> //传文件必须定义enctype的属性值
    <p><input type="file" name="upload_file_form"></p>
    <input type="submit">
</form>

视图代码

def index(request):
    return render(request,"index.html")

def upload_file(request):
    print("FILES:",request.FILES) # 对应后端,用request.FILES接受文件内容
    print("POST:",request.POST)
    file_obj = request.FILES.get("upload_file_form") #这里的get字段要和html中的input标签的name属性对齐
        print(file_obj)
        print(file_obj.name)
        with open(file_obj.name,"wb")as f:
            for i in file_obj:
                f.write(i)
    return HttpResponse("上传成功!")

基于ajax实现form标签的文件上传

js代码

<h3>Ajax上传文件</h3>

<p><input type="text" name="username" id="username" placeholder="username"></p>
<p><input type="file" name="upload_file_ajax" id="upload_file_ajax"></p>

<button id="upload_button">提交</button>
{#注意button标签不要用在form表单中使用#}

<script>
    //ajax上传文件和form上传不同的点
    $("#upload_button").click(function(){

        var username=$("#username").val();
        //1.这里2个索引[0] 第一个是jQuery对象返回的是列表
        //文件批量上传可以给input标签添加multiples属性,但是必须通过for循环一个个上传,不能同时上传2个,第二个索引是第一个文件
        var upload_file=$("#upload_file_ajax")[0].files[0];
		//2. 必须要用formData对象,把文件内容添加进去
        var formData=new FormData();
        formData.append("username",username);
        formData.append("upload_file_ajax",upload_file);


        $.ajax({
            url:"/upload_file/",
            type:"POST",
            data:formData,
            //3. 下面2个属性设置为False
            contentType:false,
            processData:false,

            success:function(){
                alert("上传成功!")
            }
        });


    })
</script>

视图代码

def index(request):
  
    return render(request,"index.html")
  
 #ajax上传文件,后端接收逻辑没有任何变化
def upload_file(request):
    if request.method=="POST":
        print("file>>",request.FILES)
        file_obj = request.FILES.get("upload_file_ajax")
        print(file_obj)
        print(file_obj.name)
        with open(file_obj.name,"wb")as f:
            for i in file_obj:
                f.write(i)
    return HttpResponse("上传成功!")

ajax的csrf验证

第一种,html中写上{% csrf_token %},然后ajax代码中做设置如下

第二种 直接把csrf写在js脚本.

$.ajax({
  url: "/my/cal/",
  type: "post",
  data: {
  "n1": $(".num1").val(),
  "n2": $(".num2").val(),
  "csrfmiddlewaretoken":'{{ csrf_token }}',
 },

也可以写在header中
$.ajax({
            url: '/csrf_test/',
            method: 'post',
            headers:{'X-CSRFToken':'token值'},  // 注意放到引号里面
            data:{}
}

django会将请求头里的-转换成_,并且加上HTTP_字段和转换成大写,比如X-CSRFToken在django中的request.META可以捕获,字段会转换成HTTP_X_CSRFTOKEN

ajax的一个方法提交所有post参数

jQuery有个serialize方法,可以提交form表单中的所有参数

<form id="myform">
  <input type="text" name="name" value="张三">
  <input type="radio" name="sex" value="男" checked>
  <input type="radio" name="sex" value="女">
  <input type="checkbox" name="hobby" value="篮球" checked>
  <input type="checkbox" name="hobby" value="足球">
  <select name="city">
    <option value="北京">北京</option>
    <option value="上海" selected>上海</option>
    <option value="广州">广州</option>
  </select>
  <textarea name="comment">这是一条评论。</textarea>
</form>

我们可以使用serialize()方法将表单数据序列化为URL编码的字符串,如下所示:

var formData = $('#myform').serialize();
console.log(formData);

输出结果为
name=%E5%BC%A0%E4%B8%89&sex=%E7%94%B7&hobby=%E7%AF%AE%E7%90%83&hobby=%E8%B6%B3%E7%90%83&city=%E4%B8%8A%E6%B5%B7&comment=%E8%BF%99%E6%98%AF%E4%B8%80%E6%9D%A1%E8%AF%84%E8%AE%BA%E3%80%82
由于URL编码,表单数据中的中文字符和特殊字符会被转义为%xx的形式。在服务器端接收到数据后,需要进行解码才能恢复原始的表单数据。

csrf全局(局部)禁用

(1) 在视图函数上加装饰器

from django.views.decorators.csrf import csrf_exempt,csrf_protect
@csrf_exempt
def 函数名(request):  # 加上装饰器后,这个视图函数,就没有csrf校验了

(2) 视图类

from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt,name='dispatch')
class index(View):
    def get(self,request):
        return HttpResponse("GET")
    def post(self,request):
        return HttpResponse("POST")

ImageField 和 FileField字段

ImageField 和 FileField 可以分别对图片和文件进行上传到指定的文件夹中。
关于该字段对于已存在的文件如何判断且重命名可以参考https://www.cnblogs.com/Young-shi/p/15096689.html 里面的 图片和文件新增相同文件名的处理方式和单表自定义新增的方法

  1. 在下面的 models.py 中 :

picture = models.ImageField(upload_to='avatars/', default="avatars/default.png",blank=True, null=True) 注:定义 ImageField 字段时必须制定参数 upload_to这个字段要写相对路径,

这个参数会加在 settings.py 中的 MEDIA_ROOT后面, 形成一个路径, 这个路径就是上 传图片的存放位置,默认在Django项目根路径下,也就是MEDIA_ROOT默认是Django根目录

所以要先设置好 mysite/settings.py中的 settings.py 中的 MEDIA_ROOT

class Userinfo(models.Model):
    name = models.CharField(max_length=32)
    avatar_img = models.FileField("avatars/") 
username = request.POST.get("username")
#获取文件对象
file = request.FILES.get("file")   
#插入数据,将图片对象直接赋值给字段
user = Userinfo.objects.create(name=username,avatar_img=file)

Django会在项目的根目录创建avatars文件夹,将上传文件下载到该文件夹中,avatar字段保存的是文件的相对路径。

  1. 在 mysite/settings.py中 :
MEDIA_ROOT = os.path.join(BASE_DIR,"media")
MEDIA_URL='/media/'

MEDIA_ROOT:存放 media 的路径, 这个值加上 upload_to的值就是真实存放上传图片文件位置

MEDIA_URL:给这个属性设值之后,静态文件的链接前面会加上这个值,如果设置这个值,则UserInfo.avatar.url自动替换成:/media/avatars/default.png,可以在模板中直接调用:<img src="{{ user.avatar.url }}" alt="">

3.url.py:

from django.views.static import serve
# 添加media 配置
re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),

浏览器可以直接访问http://127.0.0.1:8000/media/yuan/avatars/%E7%86%8A%E7%8C%AB.webp,即我们的用户上传文件。

最后再给大家补充一个用户文件夹路径

def user_directory_path(instance, filename):
    return os.path.join(instance.name,"avatars", filename)

class Userinfo(models.Model):
    name = models.CharField(max_length=32)
    avatar_img = models.FileField(upload_to=user_directory_path)  

4.FileField 和 ImageFiled 相同。

总结:

1. 考虑继承AbstractUser类  from django.contrib.auth.models import AbstractUser
2. 设计avatar字段,字段名字自定义,类型是FileField,ImageFiled,注意里面有upload_to属性和default属性
3. 配置url的上传路径去获取上传的对象,文件图片都用request.FILES对象获取
4. 前端配置POST上传参数
5. setting配置MEDIA_ROOT(和STATIC_ROOT不同,必须是字符串,不可以是列表)和MEDIA_URL参数
6. # 添加media 配置  from django.views.static import serve
   re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
7. ORM把上传的文件或者图片对象赋值给 avatar字段,文件的写入动作django会自动完成
8. avatar字段是一个图片/文件类对象,要看到真实url要用avatar.url属性

ImageField 和 FileField对象调用api接口的细节

2个字段调用的文件或者图片对象可以用type查看

f = request.data.get("avatar_img")
        print(f,type(f))   # ESCAN.jpg <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>

通过from django.core.files.uploadedfile import InMemoryUploadedFile查看对象有size, content_type属性,也有open chunks方法

class UploadedFile(File):
    """
    An abstract uploaded file (``TemporaryUploadedFile`` and
    ``InMemoryUploadedFile`` are the built-in concrete subclasses).

    An ``UploadedFile`` object behaves somewhat like a file object and
    represents some file data that the user submitted with a form.
    """

    def __init__(self, file=None, name=None, content_type=None, size=None, charset=None, content_type_extra=None):
        super().__init__(file, name)
        self.size = size
        self.content_type = content_type
        self.charset = charset
        self.content_type_extra = content_type_extra

class InMemoryUploadedFile(UploadedFile):

    def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra=None):
        super().__init__(file, name, content_type, size, charset, content_type_extra)
        self.field_name = field_name

    def open(self, mode=None):
        self.file.seek(0)
        return self

    def chunks(self, chunk_size=None):
        self.file.seek(0)
        yield self.read()

细节,文件上传通过chunk方法读写保存,此时文件已经从头读写到尾了,因此需要再内存中重新读写一次,并通过Base64发给api接口,需要f.seek(0),将光标重新回到起始
再用f.read()读取所有内容
代码做如下改变
这个是自己自定义的一个drf混合类,里面的逻辑是对上传的图片进行百度接口的图文识别和保存,以及将文件路径写入数据库
具体看if instance:这一个代码分支块的img_obj.seek(0)这一行

class CreateUpdateModelMixin:
    def get_instance(self):
        """这是一个钩子,返回对象则表示做更新操作,无对象表示创建操作"""
        pass

    def create(self, request, *args, **kwargs):
        # 1. 是否认证信息已存在
        instance = self.get_instance(request)
        myresponse = CommonResponse()
        if instance:
            # 1.2 存在 走更新逻辑
            serializer = self.get_serializer(instance=instance, data=request.data,partial=True)
            # 如果上传过文件或者图片做文件名上传是否一致的判断
            if instance.avatar_img:
                # 数据库的文件路径提取文件名  /a/b/c/abc.txt 提取abc.txt
                img_name = instance.avatar_img.url.rsplit("/",1)[1]
                # 就是上传的图片和文件对象
                img_obj=request.data.get("avatar_img") #等于request.FILES.get("avatar_img")
                new_img_name=img_obj.name
                # 读取图片的所有内容
                content = img_obj.read()
                # 通过百度接口进行文字识别
                pic_verify(content)
                if img_name==new_img_name:
                    #如果上传文件名和数据文件名一致则重命名  这里建议使用 default_storage的save方法,可以随机重命名
                    # 看https://www.cnblogs.com/Young-shi/p/15096689.html
                    # from django.core.files.storage import default_storage
                    # 这里只是加了222,测试用
                    request.data["avatar_img"].name="222%s"%img_name
                print(new_img_name)
                # 很重要,pic_verify(content) 这一步会读取所有内容 光标位置就是在底部.因此必须光标必须返回0,不然无法保存成功
                img_obj.seek(0)
            if not serializer.is_valid():
                myresponse.status = 401
                myresponse.error_detail["message"] = collect_error(serializer.errors)
                return Response(myresponse.dict())
            self.perform_update(serializer)
            myresponse.status = 200
            myresponse.success = True
            myresponse.result = serializer.data
            return Response(myresponse.dict())
        else:
            # 1.3 不存在 走新建逻辑
            serializer = self.get_serializer(data=request.data)
            if not serializer.is_valid():
                myresponse.status = 401
                myresponse.error_detail["message"] = collect_error(serializer.errors)
                return Response(myresponse.dict())
            self.perform_create(serializer)
            myresponse.status = 200
            myresponse.success = True
            myresponse.result = serializer.data
            return Response(myresponse.dict())

同源策略和跨域

将html网页单独放在客户端,用浏览器打开,再触发事件,会发现报错

这是因为浏览器的同源策略导致的。
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。

那么如何解决这种跨域问题呢,我们主要由三个思路:

  1. jsonp
  2. cors
  3. 前端代理 比如用nodejs代替浏览器发送请求

这里主要给大家介绍第二个:cors

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

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

所以,服务器方面只要添加一个响应头,同意跨域请求,浏览器也就不再拦截:

response = JsonResponse(data)
response["Access-Control-Allow-Origin"] = "*"

cors

cors有两种请求:简单请求和非简单请求

只要同时满足以下两大条件,就属于简单的请求

(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同时满足上面两个条件,就属于非简单请求。浏览器对这两种请求的处理,是不一样的。

简单请求:一次请求

非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。

- 请求方式:OPTIONS
- “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
- 如何“预检”
     => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
        Access-Control-Request-Method
     => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
        Access-Control-Request-Headers

支持跨域,简单请求:

服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'

支持跨域复杂请求:

由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。
“预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
“预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers

cors在Django中的实现:

在返回结果中加入允许信息(简单请求):

def test(request):
    import json
    obj=HttpResponse(json.dumps({'name':'yuan'}))
    # obj['Access-Control-Allow-Origin']='*'
    obj['Access-Control-Allow-Origin']='http://127.0.0.1:8004'
    return obj

放到中间件中处理复杂和简单请求:

from django.utils.deprecation import MiddlewareMixin
class MyCorsMiddle(MiddlewareMixin):
    def process_response(self, request, response):
        if request.method == "OPTIONS":
            # 任意网址
            response["Access-Control-Allow-Origin"] = "http://127.0.0.1:8001"
            # 任意的请求方式
            response["Access-Control-Allow-Methods"] = "OPTIONS"  # "PUT,DELETE,GET,POST"
            # 允许任意的请求头
            # response["Access-Control-Allow-Headers"] = "*"
            # X-Custom-Header 来确定实际的跨域请求是否被服务器允许 ,如果要区分options请求则必须代这个参数
            # Content-Type,Authorization表示正常请求需要者2个参数
            response["Access-Control-Allow-Headers"] = "Content-Type,Authorization,X-Custom-Header"
            # 在接下来的3600秒内,允许跳过预检请求 直接发送实际的请求 减少跨域请求所需的预检请求数量,从而减少了网络流量和延迟。
            response["Access-Control-Max-Age"] = "3600"
            return response
        # 任意网址
        response["Access-Control-Allow-Origin"] = "http://127.0.0.1:4000"
        # 任意的请求方式
        response["Access-Control-Allow-Methods"] = "PUT,DELETE,GET,POST"  # "PUT,DELETE,GET,POST"
        # 允许任意的请求头
        response["Access-Control-Allow-Headers"] = "Content-Type,Authorization"
        return response

在settings中配置即可,在中间件中的位置可以随意放置.

也可以通过第三方组件:pip install django-cors-headers

# (1)
pip install django-cors-headers

# (2)
 INSTALLED_APPS = (
 'corsheaders',
)
  
# (3)
 1 MIDDLEWARE = [
 2     'django.middleware.security.SecurityMiddleware',
 3     'django.contrib.sessions.middleware.SessionMiddleware',
 4     'django.middleware.csrf.CsrfViewMiddleware',
 5     'django.contrib.auth.middleware.AuthenticationMiddleware',
 6     'django.contrib.messages.middleware.MessageMiddleware',
 7     'django.middleware.clickjacking.XFrameOptionsMiddleware',
 8     'corsheaders.middleware.CorsMiddleware',  # 按顺序
 9     'django.middleware.common.CommonMiddleware',  # 按顺序
10 ]
  
  # 配置白名单
 1 CORS_ALLOW_CREDENTIALS = True#允许携带cookie
 2 CORS_ORIGIN_ALLOW_ALL = True
 3 CORS_ORIGIN_WHITELIST = ( '*')#跨域增加忽略
 4 CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', )
 5 #允许的请求头
 6 CORS_ALLOW_HEADERS = (
 7     'XMLHttpRequest',
 8     'X_FILENAME',
 9     'accept-encoding',
10     'authorization',
11     'content-type',
12     'dnt',
13     'origin',
14     'user-agent',
15     'x-csrftoken',
16     'x-requested-with',
17     'Pragma',
18 )
  

备注: 前端项目设置请求头记得添加到CORS_ALLOW_HEADERS

在CORS_ALLOWED_ORIGINS中,您可以指定您希望允许跨域访问的一级域名、特定IP和端口等。这将允许来自这些来源的请求通过跨域访问。您可以根据需要添加多个源。

最后,确保将CORS_ALLOW_ALL_ORIGINS设置为False,以明确指定只允许配置的源进行跨域访问

CORS_ALLOWED_ORIGINS = [
    'http://example.com',  # 指定允许访问的一级域名
    'http://localhost:3000',  # 指定允许访问的特定IP和端口
]

CORS_ALLOW_ALL_ORIGINS = False
posted @ 2021-09-11 17:18  零哭谷  阅读(105)  评论(0编辑  收藏  举报