关于本站
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 回复