[Python自学] day-23 (2) (文件上传、验证码、KindEditor)
一、文件上传
文件上传有4种方式。
1.表单形式上传
略,表单形式上传要刷新页面。
2.原生Ajax上传
HTML代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>UPLOAD</title> </head> <body> <input type="file" id='select_file' value="选择文件"/> <input type="button" value="上传" onclick="up_load_file();" /> <script> function up_load_file(){ // 获取文件对象(整个文件) var file_obj = document.getElementById("select_file").files[0]; var xhr = new XMLHttpRequest(); xhr.open('POST',"/up_load_by_ajax/",true); // 回调函数 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ var obj = JSON.parse(xhr.responseText); console.log(obj); } } // 使用FormData对象来封装POST数据(包含文件内容),文件必须要通过该对象封装 var fd = new FormData(); // 将需要发送的文本、文件数据都append到FormData对象中 fd.append("username",'leo'); fd.append("fafafa",file_obj); // 发送POST请求 xhr.send(fd); } </script> </body> </html>
视图函数:
# 上传文件页面 def up_load(request): return render(request,'up_load.html') # 响应ajax请求 def up_load_by_ajax(request): if request.method == 'POST': name = request.POST.get("username") # 获取文件对象 file_obj = request.FILES.get("fafafa") # 将文件内容存放到服务器 with open(file_obj.name,'wb') as f: for item in file_obj.chunks(): f.write(item) ret = {'code': True, 'data': name} import json # 返回JSON数据 return HttpResponse(json.dumps(ret))
3.jQuery AJAX上传
HTML代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>UPLOAD</title> </head> <body> <input type="file" id='select_file' value="选择文件"/> <input type="button" value="上传jq" onclick="up_load_file_jq();" /> <script src="/static/jquery-1.12.4.js"></script> <script> function up_load_file_jq() { var file_obj = document.getElementById("select_file").files[0]; // 将需要发送的文本、文件数据都append到FormData对象中 var fd = new FormData(); fd.append("username",'leo'); fd.append("fafafa",file_obj); $.ajax({ url: '/up_load_by_ajax/', type: 'POST', data: fd, // jQuery会默认对数据进行处理,例如拼接成一个字符串。如果要传递对象,则要告诉jQuery别处理,用下面两个参数 processData:false, // 告诉jQuery不要处理data中的数据 contentType:false, // 告诉jQuery不要设置contentType success: function (data, str, xhr_obj) { console.log(data); //console.log(typeof (str)); //打印success(string) //console.log(xhr_obj); } }) } </script> </body> </html>
视图函数:
# 上传文件页面 def up_load(request): return render(request,'up_load.html') # 响应ajax请求 def up_load_by_ajax(request): if request.method == 'POST': name = request.POST.get("username") # 获取文件对象 file_obj = request.FILES.get("fafafa") # 将文件内容存放到服务器 with open(file_obj.name,'wb') as f: for item in file_obj.chunks(): f.write(item) ret = {'code': True, 'data': name} import json # 返回JSON数据 return HttpResponse(json.dumps(ret))
4.利用iframe上传文件
HTML代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>UPLOAD</title> </head> <body> <form action="/up_load_by_ajax/" method="post" target="ifm1" enctype="multipart/form-data"> <iframe name="ifm1" id="ifm1" ></iframe> <input type="file" name="fafafa" value="选择文件"/> <input type="submit" name="Form提交" onclick="iframe_up_load();"/> </form> <script src="/static/jquery-1.12.4.js"></script> <script> function iframe_up_load(){ //为iframe绑定onload事件函数,当数据回来时,则会触发 $("#ifm1").load(function(){ //在这里去获取数据 var text = $("#ifm1").contents().find('body').text(); console.log(text); var obj = JSON.parse(text); // 拿到obj后,就可以随意处理数据了 //todo.. }) } </script> </body> </html>
视图函数:
from django.views.decorators.clickjacking import xframe_options_exempt # 响应ajax请求 @xframe_options_exempt def up_load_by_ajax(request): if request.method == 'POST': name = request.POST.get("username") # 获取文件对象 file_obj = request.FILES.get("fafafa") # 将文件内容存放到服务器 with open(file_obj.name, 'wb') as f: for item in file_obj.chunks(): f.write(item) ret = {'code': True, 'data': name} import json # 返回JSON数据 return HttpResponse(json.dumps(ret))
在iframe中,为了防止报错,还是要加上装饰器@xframe_options_exempt。
5.各种文件上传方式的优缺点
1)表单上传需要刷新页面,不是很好。
2)原生Ajax和jQuery AJax都要依赖FormData对象,低版本的浏览器可能不支持。
3)ifame兼容性最好,一般上传图片、头像什么的都用iframe。
二、预览功能
使用iframe上传图片后,我们需要预览图片。
首先在html中加上一个用于预览图片的div:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>UPLOAD</title> </head> <body> <!-- 添加一个预览图片的区域 --> <div id="preview"></div> <form action="/up_load_by_ajax/" method="post" target="ifm1" enctype="multipart/form-data"> <!-- 将iframe的默认框隐藏了 --> <iframe name="ifm1" id="ifm1" style="display: none;"></iframe> <input type="file" name="fafafa" value="选择文件"/> <input type="submit" name="Form提交" onclick="iframe_up_load();"/> </form> <script src="/static/jquery-1.12.4.js"></script> <script> function iframe_up_load(){ $("#ifm1").load(function(){ // 从iframe框中获取响应的数据 var text = $("#ifm1").contents().find('body').text(); var obj = JSON.parse(text); var imgTag = document.createElement('img') // 获取到上传图片在服务器的路径,然后添加到预览区 imgTag.src = '/'+obj.img_path $("#preview").append(imgTag) }); } </script> </body> </html>
视图函数中将图片的存储路径返回给浏览器:
from django.views.decorators.clickjacking import xframe_options_exempt # 响应ajax请求 @xframe_options_exempt def up_load_by_ajax(request): if request.method == 'POST': name = request.POST.get("username") # 获取文件对象 file_obj = request.FILES.get("fafafa") file_name = file_obj.name import os img_path = os.path.join("static/imgs/",file_name) print(img_path) # 将文件内容存放到服务器 with open(img_path, 'wb') as f: for item in file_obj.chunks(): f.write(item) # 返回img_path ret = {'code': True, 'img_path': img_path} import json # 返回JSON数据 return HttpResponse(json.dumps(ret))
效果如下:
如果对预览图片大小有要求,则使用样式限制img标签大小即可。如果想要节省图片传输带宽,则可以在服务器生成大小固定的缩略图,然后返回缩略图的path即可。
三、图片验证码
图片验证码流程图:
1)用户请求login页面,页面返回。显示验证码的img标签会再次请求一个验证码图片(专门的url,对应一个生成验证码的视图函数)。
2)在视图函数生成验证码的同时,会将生成内容的文本形式存放到用户对应的session中。并将图片形式,以二进制的形式返回给页面,页面将图片显示在img标签中。
3)用户根据图片中的验证码输入,并通过Ajax提交给后台验证,后台将用户输入与session中保存的验证码比对。
4)比对正确,验证通过,比对不正确,验证失败。
注意:在一般使用验证码的地方,我们都可以通过鼠标点击验证码图片进行换一张操作。其实就是让img标签重新请求一个验证码图片,并在session中更新为新的图片内容。
1.img标签请求动态验证码
我们一般使用img标签是这样的:
<img src="/static/imgs/xxxx.png">
也就是显示静态文件xxxx.png。
其实,我们也可以使用动态生成的图片数据:
img.html页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>IMG</title> </head> <body> <div> <img src="/gener_img/"> </div> </body> </html>
img视图函数:
def img(request): return render(request, "img.html")
gener_img视图函数:
def gener_img(request): with open("static/imgs/验证码.png", 'rb') as f: img = f.read() return HttpResponse(img)
当我们请求img.html页面时,img标签会自动请求gener_img页面(对应gener_img视图函数),视图函数动态的读取static/imgs/验证码.png并返回(后续会自动生成验证码)。
效果:
2.生成验证码
要生成验证码,一般要完成以下几个步骤:
1)创建一张图片。借助Pillow模块。
2)在图片中写入随机字符串
3)在图片画随机干扰线
4)在图片画随机干扰点
安装pillow:
pip install pillow
在工程目录下创建utils/tools.py(用于存放常用工具类和函数):
代码参照:https://www.cnblogs.com/6324TV/p/8811249.html
from PIL import Image, ImageDraw, ImageFont import random class ValidCodeImg(object): def __init__(self, width=150, height=30, code_count=5, font_size=32, point_count=20, line_count=3, img_format='png'): ''' 可以生成一个经过降噪后的随机验证码的图片 :param width: 图片宽度 单位px :param height: 图片高度 单位px :param code_count: 验证码个数 :param font_size: 字体大小 :param point_count: 噪点个数 :param line_count: 划线个数 :param img_format: 图片格式 :return 生成的图片的bytes类型的data ''' self.width = width self.height = height self.code_count = code_count self.font_size = font_size self.point_count = point_count self.line_count = line_count self.img_format = img_format @staticmethod def getRandomColor(): '''获取一个随机颜色(r,g,b)格式的''' c1 = random.randint(0, 255) c2 = random.randint(0, 255) c3 = random.randint(0, 255) return (c1, c2, c3) @staticmethod def getRandomStr(): '''获取一个随机字符串,每个字符的颜色也是随机的''' random_num = str(random.randint(0, 9)) random_low_alpha = chr(random.randint(97, 122)) random_upper_alpha = chr(random.randint(65, 90)) random_char = random.choice([random_num, random_low_alpha, random_upper_alpha]) return random_char def getValidCodeImg(self): # 获取一个Image对象,参数分别是RGB模式。宽150,高30,随机颜色 image = Image.new('RGB', (self.width, self.height), self.getRandomColor()) # 获取一个画笔对象,将图片对象传过去 draw = ImageDraw.Draw(image) # 获取一个font字体对象参数是ttf的字体文件的目录,以及字体的大小 font = ImageFont.truetype("static/KumoFont.ttf", size=self.font_size) temp = [] for i in range(self.code_count): # 循环5次,获取5个随机字符串 random_char = self.getRandomStr() # 在图片上一次写入得到的随机字符串,参数是:定位,字符串,颜色,字体 draw.text((10 + i * 30, -2), random_char, self.getRandomColor(), font=font) # 保存随机字符,以供验证用户输入的验证码是否正确时使用 temp.append(random_char) valid_str = "".join(temp) # 噪点噪线 # 划线 for i in range(self.line_count): x1 = random.randint(0, self.width) x2 = random.randint(0, self.width) y1 = random.randint(0, self.height) y2 = random.randint(0, self.height) draw.line((x1, y1, x2, y2), fill=self.getRandomColor()) # 画点 for i in range(self.point_count): draw.point([random.randint(0, self.width), random.randint(0, self.height)], fill=self.getRandomColor()) x = random.randint(0, self.width) y = random.randint(0, self.height) draw.arc((x, y, x + 4, y + 4), 0, 90, fill=self.getRandomColor()) # 在内存生成图片 from io import BytesIO f = BytesIO() image.save(f, self.img_format) data = f.getvalue() f.close() return data, valid_str
注意:以上代码中依赖一个字体文件 "static/KumoFont.ttf" ,我们需要实现准备该字体文件。
在gener_img视图函数中调用ValidCodeImg类来生成验证码图片:
from utils.tools import ValidCodeImg def gener_img(request): # 创建ValidCodeImg对象 img = ValidCodeImg() # 生成验证码图片和文本 img_data, valid_str = img.getValidCodeImg() # 将验证码的文本内容放入session中, 用于以后验证 request.session['validcode'] = valid_str # 将验证码返回给页面 return HttpResponse(img_data)
效果:
3.验证用户输入
html中实现用户验证码输入框,以及ajax提交:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>IMG</title> </head> <body> <div> <img src="/gener_img/"> </div> <div> <input type="text" id="validcode"/> <input type="button" value="验证" onclick="checkcode();"/> <label id="error_label"></label> </div> <script src="/static/jquery-1.12.4.js"></script> <script> function checkcode(){ // 获取text框中的验证码(用户输入) var vc = $("#validcode").val(); // 发送ajax请求去验证 $.ajax({ // 发送给/checkcode/页面 url: '/checkcode/', // POST方法 type: 'POST', // 验证码数据 data: {'code':vc}, // 回调函数,显示验证结果 success:function(data,str,xhr_obj) { var json_data = JSON.parse(data); var stat = json_data.status; var err_label = $("#error_label"); // 如果成功显示验证码正确,如果失败显示错误信息 if(stat){ err_label.css('color','green'); err_label.text("验证码正确"); }else{ err_label.text(json_data.error_msg); err_label.css('color','red'); } } }); } </script> </body> </html>
实现验证码验证视图函数checkcode:
# 验证图片验证码 def checkcode(request): code = request.POST.get('code') cur_code = request.session.get('validcode') # 如果验证成功返回status:True,否则返回status:False if code != cur_code: ret = {'status': False, 'error_msg': '验证码不正确'} else: ret = {'status': True, 'error_msg': ''} import json return HttpResponse(json.dumps(ret))
效果:
4.点击切换验证码
修改html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>IMG</title> </head> <body> <div> <img src="/gener_img/" onclick="changeCheckCode(this);"> </div> <div> <input type="text" id="validcode"/> <input type="button" value="验证" onclick="checkcode();"/> <label id="error_label"></label> </div> <script src="/static/jquery-1.12.4.js"></script> <script> function checkcode(){ // 获取text框中的验证码(用户输入) var vc = $("#validcode").val(); // 发送ajax请求去验证 $.ajax({ // 发送给/checkcode/页面 url: '/checkcode/', // POST方法 type: 'POST', // 验证码数据 data: {'code':vc}, // 回调函数,显示验证结果 success:function(data,str,xhr_obj) { var json_data = JSON.parse(data); var stat = json_data.status; var err_label = $("#error_label"); // 如果成功显示验证码正确,如果失败显示错误信息 if(stat){ err_label.css('color','green'); err_label.text("验证码正确"); }else{ err_label.text(json_data.error_msg); err_label.css('color','red'); } } }); } function changeCheckCode(ths){ ths.src= ths.src + '?'; } </script> </body> </html>
注意,在验证码图片的onclick事件函数中,每次给img的src属性多加一个"?",这样每次设置的src与上一次的src不一样,浏览器会自动去请求新的URL。否则不动。
四、KindEditor
KindEditor是一个富文本编辑框。
1.下载KindEditor
下载地址:https://github.com/kindsoft/kindeditor/releases/download/v4.1.11/kindeditor-4.1.11-zh-CN.zip
解压后有以下文件:
其中红框部分可以删除(都是些各种语言的实例文件)。留下语言、插件和主题文件,以及JS文件。
将删除后的文件夹拷贝到项目的/static目录下备用。
2.使用KindEditor
HTML代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Kind</title> </head> <body> <div style="width: 800px;margin: 0 auto;"> <textarea name="content" id="content"></textarea> </div> <script src="/static/jquery-1.12.4.js"></script> <script src="/static/kindeditor/kindeditor-all.js"></script> <script> $(function(){ initKind(); }); function initKind(){ var kind = KindEditor.create("#content",{ width:"100%", height:"300px", minWidth:200, minHeight:400 }); } </script> </body> </html>
视图函数:
def kind(request): return render(request,'kind.html')
效果:
在初始化中,除了高宽等参数,还有很多可用的参数。
参照中文文档:http://kindeditor.net/docs/option.html
在初始化参数中,我们注意几个常用的参数:
items:
[ 'source', '|', 'undo', 'redo', '|', 'preview', 'print', 'template', 'code', 'cut', 'copy', 'paste', 'plainpaste', 'wordpaste', '|', 'justifyleft', 'justifycenter', 'justifyright', 'justifyfull', 'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', 'subscript', 'superscript', 'clearhtml', 'quickformat', 'selectall', '|', 'fullscreen', '/', 'formatblock', 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold', 'italic', 'underline', 'strikethrough', 'lineheight', 'removeformat', '|', 'image', 'multiimage', 'flash', 'media', 'insertfile', 'table', 'hr', 'emoticons', 'baidumap', 'pagebreak', 'anchor', 'link', 'unlink', '|', 'about' ]
这个参数是一个列表,用于指定富文本框的工具栏菜单。我们可以从中选择我们需要的功能进行显示。
noDisableItems:
配合designMode:false,可以让选中的功能可用,其他不可用:
noDisableItems:["source","fullscreen"], designMode:false
效果:
除了source和fullscreen两个功能,其他全部禁用。
htmlTags:
可以使用的HTML标签的白名单。其他被过滤掉(但不是绝对的,所以还需要在后台进行验证过滤)。
sizeType:
编辑框的宽高是否可变。2表示宽高都可以变,1表示只能变高度,0表示不能动。
3.KindEditor上传图片
在KindEditor的初始化参数中,有一个uploadJson参数,可以参数用于配置上传图片时请求的URL。
function initKind(){ var kind = KindEditor.create("#content",{ width:"100%", height:"300px", minWidth:200, minHeight:400, uploadJson:"/upload_img/" }); }
对应视图函数:
@xframe_options_exempt def upload_img(request): # 使用imgFile来获取图片(KindEditor指定的),可以使用filePostName参数来修改。 img_file = request.FILES.get("imgFile") import os img_path = os.path.join("static/imgs/", img_file.name) print(img_file.name) with open(img_path, 'wb') as f: for i in img_file.chunks(): f.write(i) print('/' + img_path) # 这是固定返回给kindeditor的格式,error为0表示正常,url是图片保存的位置/static/imgs/xxx.png,message表示错误信息 dict = { 'error': 0, 'url': '/' + img_path, 'message': '' } import json return HttpResponse(json.dumps(dict))
因为KindEditor上传图片使用的是iframe,所以要使用@xframe_options_exempt 装饰器。
效果:
注意:这里上传图片时可以选择 "本地上传" 是通过参数 allowImageUpload 来指定的,默认为true,如果为false,则不提供本地上传。
注意:在视图函数中,我们使用"imgFile"来获取上传的图片文件。这个名称是可以修改的:
// 初始化参数中添加 filePostName:"upload_file",
再获取的时候:
file_obj = request.FILES.get("upload_file")
4.KindEditor上传其他类型文件
除了上传图片,KindEditor还可以上传其他类型的文件,例如flash、视音频、地图等。
实际上,这些文件都是向uploadJson指定的URL提交的。从F12中可以看到:
当我们上传图片的时候,后面带着一个GET请求参数,"?dir=image"。表示我们上传的是图片(利用上传图片功能上传)。如果上传其他类型,这里的参数值会根据上传类型来改变。
例如,我们使用上传flash功能:
这样,我们在视图函数中,可以通过GET中的dir数据来判断上传的什么文件。
也就是说,我们在处理上传文件时,只需要判断文件类型,并放置到不同的目录中即可:
@xframe_options_exempt def upload_img(request): import os file_obj = request.FILES.get("imgFile") file_type = request.GET.get('dir') if file_type == "image": # 使用imgFile来获取图片(KindEditor指定的) file_path = os.path.join("static/imgs/", file_obj.name) elif file_type == "file": file_path = os.path.join("static/uploads/", file_obj.name) elif file_type == "flash": file_path = os.path.join("static/flashes/", file_obj.name) # 写入文件 with open(file_path, 'wb') as f: for i in file_obj.chunks(): f.write(i) # 这是固定返回给kindeditor的格式,error为0表示正常,url是文件保存的位置/static/imgs/xxx.png,message表示错误信息 dict = { 'error': 0, 'url': '/' + file_path, 'message': '' } import json return HttpResponse(json.dumps(dict))
注意:
和上传图片一样,也有几个参数来控制是否允许上传。例如:
allowFlashUpload :默认为true,允许上传Flash。
allowMediaUpload:默认为true,允许上传音视频。
allowFileUpload:默认为true,允许上传文件。
allowImageRemote:默认为true,允许网络URL图片。
如果为false,则变为只有本地上传:
5.FileManager文件管理
在初始化参数中,加上以下配置:
allowFileManager:true, // 允许使用FileManager功能 fileManagerJson:"/file_manager/" // 设置请求路径
设置好后,我们打开文件上传或图片上传时:
可以看到多了一个"文件空间"。这就是FileManager提供的管理空间。
注意,图片上传一定要开启allowImageRemote:true,才能看到"图片空间"。
视图函数实现:
参考:https://www.cnblogs.com/wupeiqi/articles/6307554.html
import os import json import time def file_manager(request): """ 文件管理 :param request: :return: """ dic = {} root_path = 'D:/pycharm_workspace/day22/static/' static_root_path = '/static/' request_path = request.GET.get('path') if request_path: abs_current_dir_path = os.path.join(root_path, request_path) move_up_dir_path = os.path.dirname(request_path.rstrip('/')) dic['moveup_dir_path'] = move_up_dir_path + '/' if move_up_dir_path else move_up_dir_path else: abs_current_dir_path = root_path dic['moveup_dir_path'] = '' dic['current_dir_path'] = request_path dic['current_url'] = os.path.join(static_root_path, request_path) file_list = [] for item in os.listdir(abs_current_dir_path): abs_item_path = os.path.join(abs_current_dir_path, item) a, exts = os.path.splitext(item) is_dir = os.path.isdir(abs_item_path) if is_dir: temp = { 'is_dir': True, 'has_file': True, 'filesize': 0, 'dir_path': '', 'is_photo': False, 'filetype': '', 'filename': item, 'datetime': time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(os.path.getctime(abs_item_path))) } else: temp = { 'is_dir': False, 'has_file': False, 'filesize': os.stat(abs_item_path).st_size, 'dir_path': '', 'is_photo': True if exts.lower() in ['.jpg', '.png', '.jpeg'] else False, 'filetype': exts.lower().strip('.'), 'filename': item, 'datetime': time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(os.path.getctime(abs_item_path))) } file_list.append(temp) dic['file_list'] = file_list return HttpResponse(json.dumps(dic))
实现效果:
6.添加额外参数(例如CSRF)
如果存在CSRF问题(POST请求时),则可以在参数中添加:
extraFileUploadParams:{
csrfmiddlewaretoken:{{csrf_token}}
}
完整代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Kind</title> </head> <body> <form> {% csrf_token %} <div style="width: 800px;margin: 0 auto;"> <textarea name="content" id="content"></textarea> </div> </form> <script src="/static/jquery-1.12.4.js"></script> <script src="/static/kindeditor/kindeditor-all.js"></script> <script> $(function(){ initKind(); }); function initKind(){ var kind = KindEditor.create("#content",{ width:"100%", height:"300px", minWidth:200, minHeight:400, uploadJson:"/upload_img/", allowFileManager:true, fileManagerJson:"/file_manager/", extraFileUploadParams:{ csrfmiddlewaretoken:"{{ csrf_token }}" } }); } </script> </body> </html>
这样就可以正常使用CSRF了。
(─‿‿─)
D:\pycharm_workspace\day22\static