关于本站
1、基于Django+Bootstrap开发
2、主要发表本人的技术原创博客
3、本站于 2015-12-01 开始建站
前面我初步添加了相关设置:我的网站搭建(第44天) 添加头像字段
现在需要进一步处理。前端我用cropper获取裁剪图片参数和提供UI界面。后台用PIL处理图片。
在前端只是提供一个页面选择图片,获取裁剪的参数,并非实际裁剪图片。
将前端的图片和相关参数上传到后台之后,再用PIL处理裁剪等功能。
cropper:https://github.com/fengyuanchen/cropper
(Python2.7) PIL:pip install pillow
另外,我前端页面引用了bootstrap和jquery。
我刚接触cropper的时候,懵圈了。大量的代码不知道如何下载。摸索了几天,终于弄清楚了。
新建一个html模版页面,先引用相关文件。
<link rel="stylesheet" type="text/css" href="/static/css/cropper/cropper.min.css"> <script src="/static/js/cropper/cropper.min.js"></script>
再加入如下css样式,该样式是简单设置cropper的样式。
/*头像编辑*/ .avatar-wrapper { height: 370px; width: 100%; margin-top: 15px; box-shadow: inset 0 0 5px rgba(0,0,0,.25); background-color: #fcfcfc; overflow: hidden; } .avatar-wrapper img { display: block; height: auto; max-width: 100%;} /*头像预览*/ .avatar-preview { margin-top: 15px; margin-right: 15px; border: 1px solid #eee; border-radius: 4px; background-color: #fff; overflow: hidden; height: 96px; width: 96px; } .avatar-preview:hover { border-color: #ccf; box-shadow: 0 0 5px rgba(0,0,0,.15);}
cropper界面分两部分,编辑部分和预览部分。在body主体加入cropper容器。
<div class="col-sm-10"> <!--编辑区域--> <div class="avatar-wrapper" id='avatar-wrapper'> <img src=""> </div> </div> <div class="col-sm-2"> <!--头像预览--> <div class="avatar-preview"><img style="width: 96px; height: 96px;" src="/{{user.get_avatar_url}}"></div> <a id="avatar-upload" href="#" class="btn btn-primary disabled">上传头像</a> </div> <!--编辑相关数据--> <form id="avatar_form"> {%csrf_token%} <div> <input type="hidden" id="avatar_x" name="avatar_x"> <input type="hidden" id="avatar_y" name="avatar_y"> <input type="hidden" id="avatar_width" name="avatar_width"> <input type="hidden" id="avatar_height" name="avatar_height"> </div> </form>
并在js代码部分,加入cropper初始化代码:
<script type="text/javascript"> $(function(){ //初始化裁剪器 var image = $('#avatar-wrapper img'); image.cropper({ checkImageOrigin: true, //检查图片来源 dragMode: 'move', //图片可移动 restore:false, //窗体调整大小之后不自动恢复裁剪区域 zoomOnWheel: false, //不允许通过鼠标滚轮缩放 zoomOnTouch: false, //不允许通过触摸缩放 aspectRatio: 1 / 1, //裁剪比例 autoCropArea: 0.5, //裁剪背景透明度 autoCropArea: 1, //自动裁剪的比例 //文本的jQuery选择表达式,一个div preview: $(".avatar-preview").selector, crop: function (e) { //返回图片编辑相关数据 $('#avatar_x').val(e.x); $('#avatar_y').val(e.y); $('#avatar_width').val(e.width); $('#avatar_height').val(e.height); }, }); }); </script>
这里还需要在Django后台写代码打开该页面。
#coding:utf-8 from django.shortcuts import render_to_response from django.template import RequestContext #装饰器,登录判断 def check_login(func): def wrapper(request): #登录判断 if not request.user.is_authenticated(): data = {} data['goto_url'] = '/' data['goto_time'] = 3000 data['goto_page'] = True data['message'] = u'您尚未登录,请先登录' return render_to_response('message.html',data) else: return func(request) return wrapper @check_login def user_avatar(request): """修改头像页面""" data = {} data['user'] = request.user return render_to_response( 'user/avatar.html', data, context_instance=RequestContext(request))
因为我们页面上传头像的时候需要POST提交,需要csrf验证。所以加入context_instance。
由于编辑区域的img标签没有设置src图片来源。
设置urls路由之后,该页面加载的效果如下:
接下来,加一个选择本地图片并加载图片的功能按钮到form标签中。
<label class="btn btn-primary" for="avatar-input">本地图片</label> <input style="display:none" type="file" class="avatar-input" id="avatar-input" name="avatar_file" accept=".jpg,.jpeg,.png">
加载图片代码:
//选择图片 $("#avatar-input").change(function(){ var URL = window.URL || window.webkitURL; if(URL){ var files = this.files; if (files && files.length){ var file = files[0]; if (/^image\/\w+$/.test(file.type)) { var blobURL = URL.createObjectURL(file); image.cropper('reset').cropper('replace', blobURL); $('.avatar_crop .disabled').removeClass('disabled'); normal_tip('本地图片:可调整到最佳状态再上传'); } else { error_tip('请选择一张图片'); } } } });
其中,第10行代码中的image变量已经是前面获取到的cropper对象。
var image = $('#avatar-wrapper img');
点击选择一张图片,效果如下图:
接下来,将编辑好的图片上传到后台。后台裁剪处理之后,保存头像。
这里我采用ajax上传图片。后台需要写一个接口给该ajax使用。
#coding:utf-8 from django.http import HttpResponse from django.conf import settings import os import uuid import json @check_login def user_avatar_upload(request): """上传头像""" data = {} #判断图片来源(本地上传 or 在线图片) #获取临时路径 if request.FILES.has_key('avatar_file'): #本地上传 avatar_file = request.FILES['avatar_file'] temp_folder = os.path.join(settings.BASE_DIR, 'static', 'temp') if not os.path.isdir(temp_folder): os.makedirs(temp_folder) temp_filename = uuid.uuid1().hex + os.path.splitext(avatar_file.name)[-1] temp_path = os.path.join(temp_folder, temp_filename) #保存上传的文件 with open(temp_path, 'wb') as f: for chunk in avatar_file.chunks(): f.write(chunk) else: #在线图片 #该功能尚未开发,先留好位置 data['success'] = False return HttpResponse(json.dumps(data), content_type="application/json") #裁剪图片 top = int(float(request.POST['avatar_y'])) buttom = top + int(float(request.POST['avatar_height'])) left = int(float(request.POST['avatar_x'])) right = left + int(float(request.POST['avatar_width'])) #python2.7 pillow from PIL import Image im = Image.open(temp_path) #裁剪图片 crop_im = im.convert("RGBA").crop((left, top, right, buttom)).resize((64, 64), Image.ANTIALIAS) #设置背景颜色为白色 out = Image.new('RGBA', crop_im.size, (255,255,255)) out.paste(crop_im, (0, 0, 64, 64), crop_im) #保存图片 out.save(temp_path) #保存记录 avatar = request.user.set_avatar_url(temp_path) os.remove(temp_path) data['success'] = True data['avatar_url'] = avatar.avatar.url return HttpResponse(json.dumps(data), content_type="application/json")
此处,有好几个地方需要注意。
1)check_login 装饰器同样是前面代码中同一个装饰器。
2)我打算提供两种图片来源:本地图片和在线图片。在线图片尚未开发,先留空
3)图片处理需要安装pil
4)本地图片处理流程:下载图片,保存临时图片,裁剪图片,保存头像记录,删除临时图片
5)保存记录我使用user.set_avatar_url方法。该方法不是django自带的,是我后期绑定上去的方法,方便操作。该方法可以参考我的网站搭建(第44天) 添加头像字段。
打开User_Avatar模型所在的models.py。加入set_avatar_url方法的代码:
#coding:utf-8 from django.db import models from django.contrib.auth.models import User from django.conf import settings from types import MethodType #类动态绑定方法 import os, shutil #给user设置头像的方法 def set_avatar_url(self, src_path): try: avatar = User_Avatar.objects.get(user=self.id) old_path = os.path.join(settings.BASE_DIR, avatar.avatar.url) #旧的头像路径 old_filename = os.path.splitext(os.path.split(old_path)[-1])[0] #获得起始编号 start_num = int(old_filename.split('_')[-1]) + 1 except Exception as e: avatar = User_Avatar(user=self) start_num = 0 old_path = '' #根据user id设置新的头像名称 filename = os.path.split(src_path)[-1] img_format = os.path.splitext(filename)[-1] while True: new_filename = '%s_64_%s%s' % (self.id, start_num, img_format) new_path = os.path.join(settings.BASE_DIR, AVATAR_ROOT, new_filename) if not os.path.isfile(new_path): break start_num += 1 #保存头像 shutil.copy(src_path, new_path) avatar.avatar = os.path.join(AVATAR_ROOT, new_filename) avatar.save() #删除旧文件 if os.path.isfile(old_path): os.remove(old_path) return avatar #动态绑定方法 User.set_avatar_url = MethodType(set_avatar_url, None, User)
为了解决图片缓存问题。让前端及时更新图片,我生成不同的头像名称,即不同的头像地址。
相关User_Avatar模型可见我的网站搭建(第44天) 添加头像字段。
由于我该form表单的数据提交方式是POST,会有403拒绝访问的处理问题。
这个需要通过jquery.cookies.js处理。
<!--引用cookie处理js--> <script src="/static/js/jquery.cookie.js"></script> <!--获取csrf信息,并设置ajax--> function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { var csrftoken = $.cookie('csrftoken'); xhr.setRequestHeader("X-CSRFToken", csrftoken); } } });
该代码在加载页面的时候,执行即可。
设置urls路由,让前端可以访问刚刚写的响应方法。
ajax提交文件需要通过FormData对象,代码如下:
//上传头像 $("#avatar-upload").click(function(){ if($('#avatar-wrapper img').attr('src')==''){ alert('亲~ 你是不是忘记选择图片了'); return false; } //注意:FormData中的[0]是必备的 var formData = new FormData($("#avatar_form")[0]); $.ajax({ url: "{%url 'user_avatar_upload'%}", type: 'POST', data: formData, async: false, cache: false, contentType: false, processData: false, success: function (data){ //更新导航右上角的头像 $('.navbar-avatar').attr('src', '/' + data['avatar_url']); alert("上传头像成功"); }, error: function (err) { alert("提交失败,请重试!" + err); } }); return false; });
前面的代码是核心的代码。你可以再加一些东西优化一下,完善用户体验。
cropper提供缩放,旋转的功能。个人认为旋转其实没必要,就没添加。
最后效果如下,当然“在线图片”功能还没开发,先放一个按钮在那里。
缩放和复位按钮对应代码如下:
//缩放按钮 var zoom = 1; $("#zoom-in").click(function(){ if(zoom<1.5){ zoom += 0.1; image.cropper("zoom", 0.1); } }); $("#zoom-out").click(function(){ if(zoom>0.5){ zoom -= 0.1; image.cropper("zoom", -0.1); } }); //复位按钮 $('#reset').click(function(){ image.cropper("reset"); zoom = 1; });
相关专题: Django评论库开发
744421791@qq.com
博主,我按照您的步骤,现在我想不明白,之前加载到网页中裁剪的那张图片是怎么通过 ajax 传递给 view 视图的?
2017-08-21 18:26 回复
744421791@qq.com
已经解决了,少写了几个参数。
2017-08-21 19:46 回复
梦落の无声
晕乎乎的
2018-12-25 22:13 回复