zoukankan      html  css  js  c++  java
  • Django中的文件上传和原生Ajax

    概述

    Django中的上传有3种方案:

    1. form 表单常规上传,但点击提交后会自动刷新页面
    2. Ajax 上传,不刷新页面,(分为原生ajax上传和jQuery上传),IE7以上不兼容
    3. iframe 上传,兼容性没问题,也不会刷新页面

    常规表单上传

    这种方式有个缺点,就是会自动刷新页面

    后台接收文件时,通过request.files.get('xxx')得到文件对象,并通过对文件循环,循环对象为文件对象.chunks()写入新文件即可!后台接收文件都使用这种方式!

    可以看下源码:

    class InMemoryUploadedFile(UploadedFile):
        """
        A file uploaded into memory (i.e. stream-to-memory).
        """
        def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra=None):
            super(InMemoryUploadedFile, self).__init__(file, name, content_type, size, charset, content_type_extra)
            self.field_name = field_name
    
        def open(self, mode=None):
            self.file.seek(0)
    	
    	#这里可以看到本身是一个迭代器!
        def chunks(self, chunk_size=None):
            self.file.seek(0)
            yield self.read()
    
        def multiple_chunks(self, chunk_size=None):
            # Since it's in memory, we'll never have multiple chunks.
            return False
    

    他的父类:

    lass UploadedFile(File):
        """
        A 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.
        """
        DEFAULT_CHUNK_SIZE = 64 * 2 ** 10
    
        def __init__(self, file=None, name=None, content_type=None, size=None, charset=None, content_type_extra=None):
            super(UploadedFile, self).__init__(file, name)
            self.size = size
            self.content_type = content_type
            self.charset = charset
            self.content_type_extra = content_type_extra
    
        def __repr__(self):
            return force_str("<%s: %s (%s)>" % (
                self.__class__.__name__, self.name, self.content_type))
    
        def _get_name(self):
            return self._name
    
        def _set_name(self, name):
            # Sanitize the file name so that it can't be dangerous.
            if name is not None:
                # Just use the basename of the file -- anything else is dangerous.
                name = os.path.basename(name)
    
                # File names longer than 255 characters can cause problems on older OSes.
                if len(name) > 255:
                    name, ext = os.path.splitext(name)
                    ext = ext[:255]
                    name = name[:255 - len(ext)] + ext
    
            self._name = name
    
        name = property(_get_name, _set_name)
    

    然后我们来看下代码吧:

    HTML文件内容:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>upload</title>
    </head>
    <body>
        <form action="/upload/" enctype="multipart/form-data" method="POST">
            <input type="text" name="user"/>
            <input type="file" name="img"/>
            <input type="submit" value="提交"/>
    
        </form>
    
    </body>
    </html>
    

    views.py 后台处理:

    def upload(request):
        if request.method == 'POST':
            user = request.POST.get('user')
            img = request.FILES.get('img')
            print(user, img.name)
            f = open(os.path.join('static', img.name), 'wb')
            for chunk in img.chunks():
                f.write(chunk)
            f.close()
    
        return render(request, 'upload.html')
    

    原生Ajax

    AJAX,Asynchronous JavaScript and XML (异步的JavaScript和XML),一种创建交互式网页应用的网页开发技术方案。

    异步的JavaScript:
    使用 【JavaScript语言】 以及 相关【浏览器提供类库】 的功能向服务端发送请求,当服务端处理完请求之后,【自动执行某个JavaScript的回调函数】。
    PS:以上请求和响应的整个过程是【偷偷】进行的,页面上无任何感知。
    XML
    XML是一种标记语言,是Ajax在和后台交互时传输数据的格式之一
    利用AJAX可以做:
    1、注册时,输入用户名自动检测用户是否已经存在。
    2、登陆时,提示用户名密码错误
    3、删除数据行时,将行ID发送到后台,后台在数据库中删除,数据库删除成功后,在页面DOM中将数据行也删除。
    

    Ajax主要就是使用 【XmlHttpRequest】对象来完成请求的操作,该对象在主流浏览器中均存在(除早起的IE),Ajax首次出现IE5.5中存在(ActiveX控件)。

    XmlHttpRequest对象介绍

    XmlHttpRequest对象的主要方法:

    a. void open(String method,String url,Boolen async)
       用于创建请求
        
       参数:
           method: 请求方式(字符串类型),如:POST、GET、DELETE...
           url:    要请求的地址(字符串类型)
           async:  是否异步(布尔类型)
     
    b. void send(String body)
        用于发送请求
     
        参数:
            body: 要发送的数据(字符串类型)
     
    c. void setRequestHeader(String header,String value)
        用于设置请求头
     
        参数:
            header: 请求头的key(字符串类型)
            vlaue:  请求头的value(字符串类型)
     
    d. String getAllResponseHeaders()
        获取所有响应头
     
        返回值:
            响应头数据(字符串类型)
     
    e. String getResponseHeader(String header)
        获取响应头中指定header的值
     
        参数:
            header: 响应头的key(字符串类型)
     
        返回值:
            响应头中指定的header对应的值
     
    f. void abort()
     
        终止请求
    

    XmlHttpRequest对象的主要属性:

    a. Number readyState
       状态值(整数)
     
       详细:
          0-未初始化,尚未调用open()方法;
          1-启动,调用了open()方法,未调用send()方法;
          2-发送,已经调用了send()方法,未接收到响应;
          3-接收,已经接收到部分响应数据;
          4-完成,已经接收到全部响应数据;
     
    b. Function onreadystatechange
       当readyState的值改变时自动触发执行其对应的函数(回调函数)
     
    c. String responseText
       服务器返回的数据(字符串类型)
     
    d. XmlDocument responseXML
       服务器返回的数据(Xml对象)
     
    e. Number states
       状态码(整数),如:200、404...
     
    f. String statesText
       状态文本(字符串),如:OK、NotFound...
    

    原生Ajax例子

    我们来看个Django中Ajax的例子吧:

    views.py文件:

    #发送页面
    def ajax(request):
        import time
        time = time.time()
        return render(request,'ajax.html',{'time':time})
        
    #接受数据,并返回response数据
    def xhr_ajax(request):
        print(request.GET)
        print(request.POST)
        return HttpResponse('OK')
    

    HTML文件 ajax.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>ajax-原生</title>
    </head>
    <body>
    {{ time }}
    <input type="button" value="XMLHttpRequest按钮" onclick="XhrAjax();"/>
    <input type="button" value="XMLHttpRequest,FormData按钮" onclick="XhrAjaxForm();"/>
    
    <script>
        function XhrAjax() {
            var xhr = new XMLHttpRequest();
            // 如果xhr的值改变时就处罚函数
            xhr.onreadystatechange = function () {
            		// 如果数据接受完毕,打印responseText
                if (xhr.readyState == 4) {
                    console.log(xhr.responseText);
                }
    
            };
            xhr.open('POST', '/xhr_ajax/');
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');
            xhr.send('k1=v1;k2=v2');
        }
        
        
        // 使用FormData对象传送
        
        function XhrAjaxForm() {
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4) {
                    console.log(xhr.responseText);
                }
    
            };
            // GET请求
            //xhr.open('GET', '/xhr_ajax?p=123');
            //xhr.send();
            // POST请求
            xhr.open('POST', '/xhr_ajax/');
            // 设置请求头
            // xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');
            var form = new FormData();
            form.append('user', 'alex');
            form.append('pwd', '123');
            xhr.send(form);
    
        }
    
    </script>
    
    </body>
    </html>
    

    例子中有两种方式:

    1. 原生XMLHttpRequest传送数据,send()发送的是字符串,而且必须使用setRequestHeader设置信息请求头
    2. FormData对象介质传送数据,通过append将(可以看为元组信息)信息传入,然后send(),但send对象为form对象,此种方式不需要设置信息请求头
    3. 特别需要注意的一点:如果使用GET方式,需要设置URL,字符串方式

    Ajax上传文件

    为了上传文件后不刷新页面,我们引入ajax方式来上传,请求时都是通过FormData对象封装数据,这种方式又分为两种:

    1. 原生Ajax方式上传
    2. jQuery + Ajax方式上传

    views.py 配置:

    def upload_ajax(request):
        if request.method == 'POST':
            user = request.POST.get('user')
            img = request.FILES.get('img')
            print(user, img.name)
            f = open(os.path.join('static', img.name), 'wb')
            for chunk in img.chunks():
                f.write(chunk)
            f.close()
            return HttpResponse('ok')
    
        return render(request, 'upload_ajax.html')
    

    html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>upload</title>
    </head>
    <body>
    	// 注意form上传文件时,需要配置enctype 
    <form action="/upload_ajax/" enctype="multipart/form-data" method="POST">
        <input type="text" name="user" id="user"/>
        <input type="file" name="img" id="img"/>
    
    </form>
        <a style="cursor: pointer;display: inline-block;background-color: black;color: white" onclick="UploadFile1();">XMLHttpRequest上传</a>
        <a style="cursor: pointer;display: inline-block;background-color: black;color: white" onclick="UploadFile2();">jQuery上传</a>
        <script src="/static/jquery-1.12.4.js"></script>
    
    <script>
    
        // 原生上传
        function UploadFile1() {
            var form = new FormData();
            form.append('user', document.getElementById('user').value);
            
            // 得到上传图片中的第一张,使用files[下标]
            var fileObj = document.getElementById('img').files[0];
            form.append('img', fileObj);
    
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4){
                    var data = xhr.responseText;
                    console.log(data);
                }
    
            };
            xhr.open('POST','/upload_ajax/',true);
            xhr.send(form);
    
        }
    
        // ajax + jQuery上传
        function UploadFile2() {
            var fileobj = $("#img")[0].files[0];
            var form = new FormData();
            form.append('img',fileobj);
            form.append('user','alex');
            $.ajax({
                type:'POST',
                url:'/upload_ajax/',
                data:form,
                processData:false,  // 告诉jquery不转换数据
                contentType:false,  // 告诉jquery不设置内容格式
                success:function (arg) {
                    console.log(arg);
                    
                }
            })
    
        }
    
    </script>
    
    </body>
    </html>
    

    从上面的代码来看,第二张方式要简单很多,不需要send、open,之需要按常规ajax写,然后声明不转换数据,不设置内容格式即可.

    需要注意的是,上面的这两种方式,兼容性不是很好,对IE6之前的浏览器不支持,所以我们使用iframe来做.

    iframe 上传

    需求分析

    1. 异步上传
    2. 上传完后页面显示上传后的图片

    因为iframe有内嵌页面的功能,所以可以使用iframe将上传后的图片显示,分以下步骤:

    1. 提交图片前将图片提交给iframe
    2. iframe代替本身页面发送文件给后台
    3. 后台返回数据提交给iframe
    4. 页面通过回调函数,取到后台返回的数据
    5. 将数据拆分,取到后台的上传后的文件目录
    6. 页面创建一个img的标签,添加到本页面显示图片

    总结一下:页面向后台提交数据时,中间由iframe提交文件并接收后台返回的数据

    views.py 后台:

    def upload_iframe(request):
        if request.method == 'POST':
            ret = {'data':None,'status':False,'error':None}
            try:
                user = request.POST.get('user')
                img = request.FILES.get('img')
                file_path = os.path.join('static', img.name)
                f = open(file_path, 'wb')
                for chunk in img.chunks():
                    f.write(chunk)
                f.close()
                ret['status'] = True
                ret['data'] = file_path
            except Exception as e:
                ret['error'] = str(e)
    
            return HttpResponse(json.dumps(ret))
    
        return render(request, 'upload_iframe.html')
    

    HTML页面:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>upload_iframe</title>
        <style>
            /* 设置显示上传后图片的样式*/
            .img{
                 250px;
                height: 250px;
            }
        </style>
    </head>
    <body>
    <iframe id="my_iframe" name="my_iframe" style="display: none" src=""></iframe>
    <form id="fo" method="POST" action="upload_iframe.html" enctype="multipart/form-data">
    {#    <input type="text" id="user" name="user"/>#}
        // id 和 name 的属性必须都有
        <input type="file" id="img" name="img" onchange="UploadFile3();"/>
    {#    <input type="submit"/>#}
    
    </form>
    <div id="container"></div>
    <script src="/static/jquery-1.12.4.js"></script>
    <script>
        function UploadFile3() {
            // 方式重复显示图片,删除之前显示的图片
            $('#container').find('img').remove();
            // onload事件会在页面或者图像加载完成后立即触发
            document.getElementById('my_iframe').onload = callback;
            // target 属性指定在何处打开表单中的 action-URL
            document.getElementById('fo').target = 'my_iframe'
            // 提交
            document.getElementById('fo').submit();
        }
    
        function callback() {
            // 使用contents(),可以将iframe中内嵌的页面HTML内容取出,取出的数据为字符串格式
            var text = $('#my_iframe').contents().find('body').text();
            // 字符串通过json转化为字典
            var json_data = JSON.parse(text);
            console.log(json_data);
            if (json_data.status) {
                // 创建一个img的元素
                var tag = document.createElement('img');
                // 字符串拼接
                tag.src = '/' + json_data.data;
                tag.className = 'img';
                // 添加到目标元素标签内
                $('#container').append(tag);
            } else {
                alert(json_data.error)
            }
    
        }
    </script>
    
    
    </body>
    </html>
    
  • 相关阅读:
    get_json_object 用法
    vim中的特殊字符
    vim常用命令
    mac下如何配置用户的环境变量
    vim如何替换^M ?
    git ssh 配置
    mac上pip install时提示/System/Library/... 无权限
    mysql语句--table中某列有多值时,删除其他,仅保留1条
    mysql语句如何把多行的数据合并到一行?
    微服务分布式电商项目学习笔记(二)---- 微服务架构图+微服务划分图(2020/7/1)
  • 原文地址:https://www.cnblogs.com/ccorz/p/5912478.html
Copyright © 2011-2022 走看看