Flask实现图片(文件)的(批量)上传、下载及示例代码

一、图片的单张上传(包括图片的尺寸压缩)

图片上传的基本思想大概是这样,前端用一个表单中type为file的input标签作为图片上传的前台发起上传,并以ajax把整个表单发送到后台,后台则是接收到图对象,加以处理,保存到服务器指定目录即可。首先先介绍下前台的代码,以及上传文件的form表单。

form表单如下:

from flask_wtf import FlaskForm

from wtforms import FileField

from app import label

from flask_wtf.file import FileField, FileRequired, FileAllowed

from config import ALLOWED_EXTENSIONS

class ImgForm(FlaskForm):

    photo = FileField(validators=[FileAllowed(ALLOWED_EXTENSIONS, message=label('_allow_img', [str(ALLOWED_EXTENSIONS)]))])

其中的config 如下:

ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg'])

其中的label为如下:

def label(name, values=[], path=None):

    '''
        默认从app目录下的guide导入所需变量
        name 导入的数据变量名
        values 数据类型必须市列表,提示的动态内容
        path 为导入的路径 name
    '''
    path = ".".join(path.split(".")[:-1]) + ".guide:" if path else "app.guide:"
    msg = import_string(path + name)
    return msg.format(values) if len(values) else msg

_allow_img       = '''图片的格式只能{0[0]}'''

后台的视图函数则是实例化该ImgForm,传到前台,前台的html代码为:

<form id="img_file" method='POST' enctype=multipart/form-data>
    {{ imgform.hidden_tag() }}
    <div class="fileupload btn btn-primary btn-xs">
        <span class="btn-text">上传图片span>
        {{ imgform.photo(class="upload") }}
    div>
form>

hidden_tag()一定不要漏掉,不然后台将无法获取到我们传到后台的数据。js代码如下:

$('#img_file #photo').on('change',function(){ 
    var imgform = new FormData(document.getElementById("img_file")); 
    imgform.append ("key" , "value")
    $.ajax({
        url:'{{ url_for("app.upload_img") }}',
        type:"POST",
        dataType:'json',
        data:imgform,
        cache: false,  
        processData: false,  
        contentType: false, 
        success:function(response){  
            alert('上传成功'},  
        error:function(e){  
            alert('上传失败'} 
    })
})

其中的imgform.append,则是给form对象添加我们想要传到后台的数据,比如你要传一个name为MuMu的数据到后台就可以这么写:

imgform.append ("name" , "MuMu");

flask后台接口:

from PIL import Image 
@app.route('/upload_img', methods=['POST'])
def upload_img():
    form = ImgForm()
    if form.validate():
        file = request.files['photo']
        MuMu = request.form.get('name')
        img = Image.open(file)
        img_w, img_h = img.size
        img_name_path = os.getcwd() + label('_img_name_path')
        img_name = get_line(img_name_path, 1)
        set_size(img, img_w, img_h, 1.0, img_name_path, img_name+'_L', '.png')
        set_size(img, img_w, img_h, 0.5, img_name_path, img_name+'_M', '.png')
        set_size(img, img_w, img_h, 0.2, img_name_path, img_name+'_S', '.png')
        imgL = img_name+'_L.png'
        imgM = img_name+'_M.png'
        imgS = img_name+'_S.png'
        add_img(imgL, MuMu)
        add_img(imgM, MuMu)
        add_img(imgS, MuMu)
        result = {'success':label('_update_succ')}
    else:
        result = form.errors
	return json.dumps(result)

其中set_size函数为图片处理函数,如下:

def set_size(im, imgw, imgh, size, path, name, Suffix):
    img_w, img_h = int(imgwsize), int(imghsize)
    img = im.resize((img_w, img_h), Image.ANTIALIAS)
    imgpath = path + name + Suffix
    img.save(imgpath)

二、图片的批量上传(包括图片的尺寸压缩)

图片的保存到制定目录跟单张的一样,唯一区别在于,批量上传是以压缩文件的形式上传,思路是先上传到制定目录,然后解压出来,再把里面的图片保存到制定目录,最后再移除压缩文件与解压出来的文件。前台代码跟上面的上传图片类似,就不再重复介绍。主要介绍一下上传压缩文件后的从图片图片处理过程。假设现在已经上传了压缩文件。后台处理接口如下:

@app.route('/upload/imgfile', methods=['POST'])
def upload_imgfile():
    form = FileForm()
    if form.validate():
        file = request.files['name']
        sku_path = '/app/product/csv/'
        uploadname = upload_file(sku_path, file, flag=False)
        file_list=[]
        imgs={}
        child_file = []
        if uploadname:
            file_path = os.getcwd() + sku_path + uploadname
            if uploadname[-3:] == 'zip':
                filename=un_zip(file_path)
            elif uploadname[-3:] == 'tar':
                filename=un_tar(file_path)
            for i in os.walk(filename):
                        file_list.append(i)
                    for j in file_list:
                        for k in file_list[1][1]:
                            pathn = file_list[1][0] + '/' + k
                            if pathn == j[0]:
                                imgs[k]=j[2]
                    for key in imgs:
                        for img_l in imgs[key]:
                            file = file_list[1][0]+'/'+key +'/'+img_l
                            img = Image.open(file)
                            img_w, img_h = img.size
                            img_name_path = os.getcwd() + label('_img_name_path')
                            img_name = get_line(img_name_path, 1)
                            set_size(img, img_w, img_h, 1.0, img_name_path, img_name+'_L', '.png')
                            set_size(img, img_w, img_h, 0.5, img_name_path, img_name+'_M', '.png')
                            set_size(img, img_w, img_h, 0.2, img_name_path, img_name+'_S', '.png')
                            imgL = img_name+'_L.png'
                            imgM = img_name+'_M.png'
                            imgS = img_name+'_S.png'
                            add_img(imgL, img_l[:-4])
                            add_img(imgM, img_l[:-4])
                            add_img(imgS, img_l[:-4])
                    remove_file(sku_path, filename[:-1], flag=False)
                    remove_file(sku_path, uploadname)
                result = {'success':label('_update_succ')}
            else:
                result = form.errors
            return json.dumps(result)

其中un_zip、un_tar解压函数以及文件移除函数如下,需要注意的是移除单个文件只需要remove即可,而如果想移除一个文件夹则需要用rmtree方法才能移除,详情如下:

def remove_file(path, filename, flag=True):
    '''
    删除文件
        判断文件是否存在,存在则删除
    '''
    path = BASE_PATH + path
    filename = os.path.join(path, filename)
    if os.path.exists(filename):
        if flag:
            os.remove(filename)
        else:
            shutil.rmtree(filename)
            
def upload_file(path, file, flag=True):
    '''
    上传文件
    '''
    if flag:
        name = int(time.time()*1000)
    else:
        name = file.filename
    if file:
        path = BASE_PATH + path
        file.save(os.path.join(path, str(name)))
	return name

def un_zip(file_name):
    """unzip zip file"""
    zip_file = zipfile.ZipFile(file_name)
    if os.path.isdir(file_name[:-4] + "files"):
        pass
    else:
        os.mkdir(file_name[:-4] + "files")
    for names in zip_file.namelist():
        zip_file.extract(names, file_name[:-4] + "files/")
    zip_file.close()
	return (file_name[:-4] + "files/")

def un_tar(file_name):
    tar = tarfile.open(file_name)
    names = tar.getnames()
    if os.path.isdir(file_name[:-4] + "files"):
        pass
    else:
        os.mkdir(file_name[:-4] + "files")

    for name in names:
        tar.extract(name, file_name[:-4] + "files/")
    tar.close()

    return (file_name[:-4] + "files/")

其中的get_line函数则是一个操作txt文件的函数,我们事先把图片名放在txt中,上传一张图片就从文件中提取出一行图片名,并删除一行,若删除图片则写回txt中,这是个一个良好的解决命名冲突的问题。代码如下:

def get_line(file, del_line):
    with open(file, 'r') as old_file:
        with open(file, 'r+') as new_file:
			current_line = 0
            # 定位到需要删除的行
            while current_line &lt; (del_line - 1):
                old_file.readline()
                current_line += 1
            # 当前光标在被删除行的行首,记录该位置
            seek_point = old_file.tell()
            # 设置光标位置
            new_file.seek(seek_point, 0)
            # 读需要删除的行,光标移到下一行行首
            getline = old_file.readline()
            # 被删除行的下一行读给 next_line
            next_line = old_file.readline()
            # 连续覆盖剩余行,后面所有行上移一行
            while next_line:
                new_file.write(next_line)
                next_line = old_file.readline()
            # 写完最后一行后截断文件,因为删除操作,文件整体少了一行,原文件最后一行需要去掉
            new_file.truncate()
        return (getline.strip())
     

三、图片的单张下载

我是采用h5中a标签的新属性,即a标签中的href为图片的绝对路径,增加一个download属性即为图片下载,且download值则是下载后图片的名称。(有一个不足即使兼容性不怎么好,目前仅支持谷歌浏览器以及火狐浏览器),例子如下:

 <a href="MuMu.gif" download="MuMu01.gif">
     <button  class="btn btn-success btn-xs">下载 button> 
a> 

四、图片的批量下载

批量下载的本质也是跟单张的一样,只不过要点击批量下载,要用js遍历模拟点击a标签实现多张下载,js如下:

$(function(){
    $('#panel_head').height('45px')
    $('input[name="checkbtn"]').each(function(){
        $(this).on('click',function(){
            chkval = []
            upsku = []
            if($("input[name='checkbtn']:checked").length 0 ){
                $('#batch_download').removeClass('hide');
            }else{
                $('#batch_download').addClass('hide')
            }
            for(var j=0;j<$("input[name='checkbtn']:checked").length;j++){

                if($("input[name='checkbtn']:checked")[j].checked){
                    chkval.push($("input[name='checkbtn']:checked")[j].value)
                    upsku.push($("input[name='checkbtn']:checked")[j].getAttribute("data-sku"))
                }
            }
        })
    })

    var btn = document.getElementById('batch_download');     

    function download(name, href) {
        var a = document.createElement("a"), //创建a标签
        e = document.createEvent("MouseEvents"); //创建鼠标事件对象
        e.initEvent("click", false, false); //初始化事件对象
        a.href = href; //设置下载地址
        a.download = name; //设置下载文件名
        a.dispatchEvent(e); //给指定的元素,执行事件click事件
    }

    //给多文件下载按钮添加点击事件
    btn.onclick = function(){
        for (var index = 0; index &lt; chkval.length; index++) {
            download(upsku[index], chkval[index]);
        }
    }

})

附加:批量文件上传处理并下载

如果想实现多文件同时上传(例如:csv、xlsx、xls等),前端的表单需要加个属性:enctype="multipart/form-data"才能实现文件多选,且选择多文件上传的时候需要按住ctrl,按住shift则是把点选当前的文件以及前面的文件全部选定,然后点击打开即可完成文件选定。前端代码如下:

{% from "macros/upload_form.html" import modal %}
<div class="row">
    <div class="col-sm-12 col-lg-12 col-md-12">
        <div class="panel panel-default card-view">
            <div class="panel-heading">
                <div class="pull-left">
                    <h6 class="panel-title txt-dark">多文件上传h6>
                    <div class="button-list">
                        <a href="#upload_data" data-toggle="modal" title="Compose">
                            <div class="fileupload btn btn-default btn-outline btn-rounded btn-anim">
                                <i class="fa fa-upload">i>
                                <span class="btn-text">上传待处理数据span>
                            div>
                        a>
                        {{ modal(csvform, mymodal="upload_data", action='', enctype="multipart/form-data", id='upload_form', fade="modal fade", displays="display: none", title='上传待处理数据') }}
                    div>
                div>
                <div class="clearfix">div>
            div>
        div>
    div>
div>

其中引用到的宏模板如下:

{# 开始表单字段 #}

{% macro modal_form_field(field) %}
  {%- if field.type != 'SubmitField' %}
    {%- if field.type == 'FileField' %}
        <div class="col-md-12 mb-20">
            {{ field(class="form-control", placeholder=field.label.text, multiple="multiple") }}
        div>
    {% else %}
        <div class="col-md-12 mb-20">
            {{ field(class="form-control", placeholder=field.label.text) }}
        div>
    {% endif -%}
  {% endif -%}
{% endmacro %}

{# 结束表单字段 #}



{% macro modal(form, 
                mymodal = "myModal_1",
                title = "",
                action="",
                method="post",
                role="form",
                form_type="basic",
                enctype=None,
                button_map={},
                id="",
                fade="modal fade",
                displays="display: none",
                closeWin="",
                novalidate=False) %}

{%- set _enctype = [] %}
{%- if enctype is none -%}
  {%- for field in form %}
    {%- if field.type == 'FileField' %}
      {%- set _ = _enctype.append('multipart/form-data') -%}
    {%- endif %}
  {%- endfor %}
{%- else %}
  {% set _ = _enctype.append(enctype) %}
{%- endif %}


<div aria-hidden="true" role="dialog" tabindex="-1" id="{{ mymodal }}" class="{{ fade }}" style="{{displays}}">
    <div class="modal-dialog">

    
<div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×button> <h4 class="modal-title" id="myModalLabel">{{ title }}h4> div> <div class="modal-body"> <div class="form-group"> {% for field in form %} {{ modal_form_field(field) }} {% endfor %} div> div> <div class="modal-footer"> {{ form.submit(class="btn btn-info waves-effect") }} <button type="button" id="{{closeWin}}" class="btn btn-default waves-effect" data-dismiss="modal">取消button> div> div> form> div> div> {% endmacro %}

js实现文件ajax上传如下:

$(function(){
    $('#upload_form #submit').on('click',function(e){
        e.preventDefault()
        var fileList = document.getElementById("csvfile").files;
        var fileform = new FormData(document.getElementById("upload_form"));
        fileform.append('max_num', $('#upload_form #min_num').val()) 
        fileform.append('fileList', fileList) 
        swal({   
            title: "数据上传处理中!",   
            text: "请耐心等待...",  
            showConfirmButton: false 
        });
        $.ajax({
            url:'{{ url_for("fmdata.upload_csv") }}',
            type:"POST",
            dataType:'json',
            data:fileform,
            cache: false,  
            processData: false,  
            contentType: false, 
            success:function(response){  
                    errorinfo=[]
                    for (var key in response){
                      errorinfo.push(response[key]+'\n')
                    }
                    if(response["success"]){
                        swal({   
                            title: '上传成功',   
                            type: "success",
                            timer: 1500,
                            showConfirmButton: false
                        },function(){
                            swal({   
                                title: "数据处理中!",   
                                text: "当前进度:0%",  
                                showConfirmButton: false 
                            });
                            	//定时检测文件是否处理完毕,一旦处理完就下载文件
                                var time_interval = setInterval(function(){
                                $.ajax({
                                    url : "{{url_for('fmdata.download_csv')}}",
                                    data : {
                                        'fm_id' : response["fm_id"]
                                    },
                                    dataType:'json',
                                    success : function(responses){
                                      if (responses["progress"]) {
                                        swal({   
                                          title: "数据处理中!",   
                                          text: "当前进度:" + responses["progress"] +  "%",  
                                          showConfirmButton: false 
                                      });
                                      }
                                      if(responses["download"]){
                                        clearInterval(time_interval)
                                        for (var i = responses["download"].length - 1; i >= 0; i--) {
                                          download(responses["file_name"][i], responses["download"][i])
                                        }
                                        swal({   
                                            title: '处理完成',   
                                            type: "success",
                                            timer: 1500,
                                            showConfirmButton: false
                                        })
                                    }
                                  }
                                })
                            },3500)
                        });
                        return false;
                
                    }else{
                        swal({   
                            title: "上传失败!",
                            type: "error",
                            text: errorinfo,   
                            timer: 1500,   
                            showConfirmButton: false
                        },function(){
                            window.location.reload();
                        });
                        return false;
                    }
            },  
            error:function(e){  
                swal({   
                    title: '上传失败',
                    type: "error",
                    text: "可能文件格式有误或者网络错误!!"+e,   
                    timer: 2000,   
                    showConfirmButton: false
                    },function(){
                        window.location.reload();
                    });
                    return false;
            } 
        })
    })
})

function download(name, href) {
  var a = document.createElement("a"), //创建a标签
  e = document.createEvent("MouseEvents"); //创建鼠标事件对象
  e.initEvent("click", false, false); //初始化事件对象
  a.href = href; //设置下载地址
  a.download = name; //设置下载文件名
  a.dispatchEvent(e); //给指定的元素,执行事件click事件
}

后端接收数据如下:

@fmdata.route('/upload', methods=['GET', 'POST'])
def upload_csv():
    form = CsvForm()
    if form.validate():
        # 获取前端传入的上传文件数据
        upfiles = request.files.getlist('csvfile')
        fm_id_list = []
        for file in upfiles:
            csv_path = '/app/static/fmdata/'
            uploadname = upload_file(csv_path, file, flag=False)
            new_path = os.getcwd() + csv_path + uploadname
            # 放入进程池实现异步处理
            executor = ThreadPoolExecutor(1)
            # depart_data 为处理数据的函数
            executor.submit(depart_data, new_path, uploadname)
            fm_id_list.append(fm_id)
        result = {'success': "ok"}
    else:
        result = form.errors
    return json.dumps(result)

@fmdata.route('/download_csv', methods=['GET', 'POST'])
def download_csv():
    # 处理逻辑
    #return json.dumps(result)

对于批量(单张)上传,批量(单张)下载就先介绍到这,或许某些地方总结的不够好,大神如果有什么好的建议或补充,欢迎留言。。。

你可能感兴趣的:(Flask,学习笔记)