189,323

Flask—图片上传(对图片进行比例压缩、批量上传)、图片下载(批量下载)

接触python的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(imgw*size), int(imgh*size)
    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 < (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如下:

< script type="text/javascript">
    $(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 < chkval.length; index++) {
                  download(upsku[index], chkval[index]);
              }
          }
        })
< /script>

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

Flask—图片上传(对图片进行比例压缩、批量上传)、图片下载(批量下载)》有2个想法

发表评论

邮箱地址不会被公开。 必填项已用*标注