十Django的文件上传

Posted yangzm

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了十Django的文件上传相关的知识,希望对你有一定的参考价值。

一、上传文件相关

  1. 请求头ContentType

ContentType指的是请求体的编码类型,常见的类型共有3种:

  1. application/x-www-form-urlencoded
    最常见的 POST 提交数据的方式了
    浏览器的原生 form 表单,如果不设置?enctype?属性,那么最终就会以 默认格式application/x-www-form-urlencoded 方式提交数据,ajax默认也是这个
    urlencoded是一种数据格式,
    比如:
    username=yang&password=123&csrfmiddlewaretoken=.......
    # 这样的一种格式,类似get请求提交数据的格式

        # 这样在后台,就可以通过request.POST,就可以直接拿到数据了
        <QueryDict:'username':['yang'],'password':['123'],......>
  2. multipart/form-data
    专门用来传输文件的消息格式,会把文件作为片段数据发送,而不是整个文件一起发送
  3. application/json
    告诉服务端消息主体是序列化后的 JSON 字符串
    之前给ajax传递数据,就是把字典写成了一个类似json类型的数据
    def login(request):
    ...
    ret = ‘"code":0,"redirect_url":"/index/"‘
    return HttpResponse(ret)

    在不加content_type=‘application/json‘时,传递过去的就是一个字符串
    def login(request):
    d1 = "code":0,"redirect_url":"/index/"
    import json
    d1_json = json.dumps(d1)
    return HttpResponse(d1_json,)

    success:function (res) 
     console.log(res,typeof res);
        # "code": 0, "redirect_url": "/index/" string
        ....
     
    
    这个时候,就不方便我们后续的使用,我们期望还是能拿到一个字典(自定义对象的类型)

    使用content_type=‘application/json‘,ajax就会调用自己内部的反序列化机制,直接调用反序列化方法
    def login(request):
    d1 = "code":0,"redirect_url":"/index/"
    import json
    d1_json = json.dumps(d1)
    return HttpResponse(d1_json,content_type=‘application/json‘)

    success:function (res) 
     console.log(res,typeof res);
        # code: 0, redirect_url: "/index/" "object"
        ....
     
    
    此时,ajax端不需要自己序列化,也拿到了object类型的对象,这是因为内部自动调用的
  4. application/json 在ajax端发送数据,我们如何在视图函数中应用

    $.ajax(
    url:‘% url "data" %‘,
    type:‘post‘,
    data:JSON.stringify(k1:‘v1‘,k2:‘v2‘), # 序列化成字符串
    contentType:‘application/json‘, # 指定类型
    ....
    )

    3 此时再
    def data(request):
        print(request.POST)   # <QueryDict: >,拿不到数据
        print(request.body)   # b'"k1":"v1","k2":"v2"',要从原始的数据拿
        return HttpResponse('ok')
    
    
    
    b'"k1":"v1","k2":"v2"'
  5. 总之:
    • 在django内部,对于content-type是application/x-www-form-urlencoded的,可以解析,通过request.POST.get()就能取值
    • 在django内部,对于content-type是application/json类型的,是不能解析的,需要再request.body中取原始的数据,再反序列化,才能拿到数据
    • content-type就是一个数据格式,后端针对不同的数据格式进行不同的解析,默认是urlencoded的,django可以解析
      技术图片
  6. JsonResponse

JsonResponse是HttpResponse的子类,专门用来生成JSON编码的响应

此时传递数据的时候,既不需要自己做序列化,也不需要指定content_type=‘application/json‘,只需要把数据直接放在JsonResponse里,就会自动帮你转换成json字符串,并且自动拼接content_type=‘application/json‘,

from django.http import JsonResponse  # 需要先导入

def login(request):
    d1 = "code":0,"redirect_url":"/index/"
    return JsonResponse(d1)
    # 注意:字典类型可以直接JsonResponse(d1),若不是字典,是列表等,就要JsonResponse(d1,safe=False)
    
success:function (res) 
    console.log(res,typeof res);
    # code: 0, redirect_url: "/index/" "object"
    ....
    
    
此时,也仍然拿到了object类型的数据

例子:

需求:用户输入127.0.0.1:8000/home,通过路径返回home.html文件,home.html文件中用ajax从data路径得到数据,然后在自己页面显示成一个列表显示出来

# 配置路径:
    url(r'^home/', views.home,name='home'),
    url(r'^data/', views.data,name='data'),
    
# 视图views:
    def home(request):
        return render(request,'home.html')

    def data(request):
        # l1 = ['故人西辞黄鹤楼','烟花三月下扬州','孤帆远影碧空尽','唯见长江天际流']
        l1 = [11,22,33,44]
        return JsonResponse(l1,safe=False) # 列表要加safe
    
# home.html:
    <ul></ul>
    <script src="% static 'jquery.js' %"></script>
    <script>
        $.ajax(
            url:'% url "data" %',     # 从data路径获得数据
            type:'get',
            success:function (res) 
                console.log(res);
                $.each(res,function (k,v) 
                # each 就是循环,k,v就是索引和值
                    var listr = '<li>'+ v.toString() + '</li>';
                    # v.toString()是把数字类型转换成字符串类型
                    $('ul').append(listr)
                )
            
        )
    </script>


可见,ajax通过异步请求,在data路径拿到了数据组成了li标签,然后放在了ul标签下。

自动拼接content_type='application/json',可以从网页的Network下的XHR观察出来,XHR下的请求就是ajax请求

技术图片

  1. form表单上传文件

  2. 配置路径 url(r‘^upload/‘, views.upload,name=‘upload‘),
  3. 写html,就一个表单提交数据,文件提交的(input属性是type="file")


    % csrf_token %
    头像:
    用户名:

  4. 写视图函数
    def upload(request):
    if request.method == ‘GET‘:
    return render(request,‘upload.html‘)
    else:
    print(request.POST)
    # 拿到的是post请求的数据,文件相关数据去request.FILES拿
    return HttpResponse(‘ok‘)

    打印的request.POST,数据如下:
    <QueryDict: 'csrfmiddlewaretoken': ['XR5FUZ8nf9KPo8uNzt8OD9q0OKmeVfuLEDVDyDlTMCrWtX0jvZ6gZcwwopucWQo8'], 'head_pic': ['111.png'], 'username': ['yangzm']>

    从得到的数据可以发现,request.POST得到的文件数据就是一个名字,没有任何意义

  5. 文件的数据,都在request.FILES中
    # 在form表单里,要先指定消息格式
    # 默认的请求消息格式是urlencoded,携带不了文件数据
    # 要指定multipart/form-data才可以携带数据


    ......

    def upload(request):
        if request.method == 'GET':
            return render(request,'upload.html')
        else:
            print(request.FILES)
            return HttpResponse('ok')
    
    # 如果form表单没有指定文件的消息格式,接收不到数据,就得到一个空字典:
    <MultiValueDict: >
    
    # 此时再打印request.FILES就拿到了上传了文件的数据:
    <MultiValueDict: 'head_pic': [<InMemoryUploadedFile: 111.png (image/png)>]>
  6. 把得到的文件数据读取出来,并保存到本地
    def upload(request):
    if request.method == ‘GET‘:
    return render(request,‘upload.html‘)
    else:
    file_obj = request.FILES.get(‘head_pic‘)
    file_name = file_obj.name

            with open(file_name,'wb') as f:
                for i in file_obj:
                    f.write(i)
            return HttpResponse('ok')
  7. 以上方法上传的文件没有指定路径,就直接保存在了项目的文件夹里;此时想要把上传的文件保存在指定的文件夹中,比如项目下的statics中的img文件夹中
    from ajax_pro import settings
    import os

    def upload(request):
        if request.method == 'GET':
            return render(request,'upload.html')
        else:
    
            file_obj = request.FILES.get('head_pic')
            file_name = file_obj.name
    
            path = os.path.join(settings.BASE_DIR,'statics','img',file_name)
            # 拼接路径
            with open(path,'wb') as f:
                for i in file_obj:
                    f.write(i) 
                    # 每次读的文件不是固定长度,每次读一行,识别符为\\r,\\n,,\\r\\n,遇到这几个符号就算是读了一行
    
            return HttpResponse('ok')
  8. 另一种保存文件的方法:
    from ajax_pro import settings
    import os

    def upload(request):
        if request.method == 'GET':
            return render(request,'upload.html')
        else:
    
            file_obj = request.FILES.get('head_pic')
            file_name = file_obj.name
    
            path = os.path.join(settings.BASE_DIR,'statics','img',file_name)
            # 拼接路径
            with open(path,'wb') as f:
                for chunck in file_obj.chunks():
                    f.write(chunck) 
                    # 固定长度读取,默认一次读65536B,也就是64KB,2.5M,是一个生成器
    
            return HttpResponse('ok')
  9. ajax上传文件

如何获取当前文件对象的数据:$(‘#file‘)[0].files[0]

技术图片

后端的读取文件并保存的代码跟form的文件上传一样

前端的html文件,用到ajax

ajax上传头像:<input type="file" id="file">
ajax用户名:<input type="text" id="uname">
<button id="btn">提交</button>

% csrf_token %

<script src="% static 'jquery.js' %"></script>
<script>
    $('#btn').click(function () 
        $.ajax(
            url:'/upload/',
            type:'post',
            data:
                aa: $('#uname').val(),
                head_pic:$('#file')[0].files[0],
                csrfmiddlewaretoken:$('[name=csrfmiddlewaretoken]').val()
         
            ,
            success:function (res) 
                console.log(res)
            
        )
    )
</script>

根据以前写ajax代码的习惯,会这样写,但是会报错,因为文件上传要依靠FormData这个工具

正确写法:

ajax上传头像:<input type="file" id="file">
ajax用户名:<input type="text" id="uname">
<button id="btn">提交</button>

% csrf_token %

<script src="% static 'jquery.js' %"></script>
<script>
    $('#btn').click(function () 
        var formdata = new FormData();
        formdata.append('aa',$('#uname').val());
        formdata.append('head_pic',$('#file')[0].files[0]);
        formdata.append('csrfmiddlewaretoken',$('[name=csrfmiddlewaretoken]').val());
        $.ajax(
            url:'/upload/',
            type:'post',
            data:formdata,
            processData:false,  //不处理数据
            contentType:false,  //不设置内容类型
            success:function (res) 
                console.log(res)
            
        )
    )
</script>

注意点:

  • ajax文件上传需要借助FormData,给它填键值对的方式传输数据
  • data直接传formdata,但是后面要加processData:false和contentType:false
  1. JSON 机制
  • JSON 指的是 javascript 对象表示法(JavaScript Object Notation)
  • JSON 是轻量级的文本数据交换格式
  • JSON 独立于语言 *
  • JSON 具有自我描述性,更易理解

技术图片

json和python数据类型对比:

技术图片

但是,json在转换时间类型的时候,会出现问题:

import datetime
import json

d = 'name':'yang','birthday':datetime.datetime.now()
json_str = json.dumps(d)

print(json_str)

# 报错,TypeError: Object of type 'datetime' is not JSON serializable,意思是'datetime'类型的是不能序列化的

json序列化时间日期类型的数据的方法

from datetime import date,datetime
import json

#对含有日期格式数据的json数据进行转换
class JsonCustomEncoder(json.JSONEncoder):
    # 序列化的功能就是json.JSONEncoder这个类做的,因为它不能序列化时间,所以继承他的情况下进行拓展
    def default(self, field):
        if isinstance(field,datetime):
            # 如果是datetime类型的,返回一个datetime类型的字符串格式化输出
            return field.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(field,date):
            # 如果是date类型的,返回一个date类型的字符串格式化输出
            return field.strftime('%Y-%m-%d')
        else:
            # 如果不是date、datetime类型的,就用父类的方法
            return json.JSONEncoder.default(self,field)


d1 = datetime.now()
json_str = json.dumps(d1,cls=JsonCustomEncoder)
print(json_str)  # "2019-07-29 21:08:25"

以上是关于十Django的文件上传的主要内容,如果未能解决你的问题,请参考以下文章

django 上传文件问题

django文件上传的时候怎么能加一个上传进度的显示

django python上传文件有中文名称报错

第七十五天上课 php注册登入审核和文件上传

python Django之文件上传

Django 文件上传大小限制