Django+jQuery cropper实现用户头像裁剪, 预览和上传[原创]
学习Django的最终目的还是应用,尤其是漂亮的应用。今天小编我要教你利用Django开发一个经典的用户头像上传和变更的app,主要实现以下功能场景。
-
用户点击现有头像,弹出一个图片上传和编辑窗口。
-
用户上传图片,对图片进行缩放和裁剪,并可以预览编辑过的头像。
-
用户点击确认上传,保存编辑过后的图片,并实时更新头像。
这个小应用的功能不亚于知乎,百度和****上用户头像上传和变更的功能,可以直接应用到你的app里。最终效果如下图所示:
总体开发思路
我们前端使用Bootstrap和jQuery cropper实现图片的上传,剪切和预览。后端Django对上传的图片进行裁剪存储,删除老的头像,并通过Ajax实时更新新头像。jQuery cropper是一款使用简单且功能强大的图片剪裁jquery插件。该插件支持图片放大,缩小,旋转,裁剪和预览等功能。
注意: jQuery cropper只提供了图片裁剪的坐标(x, y, 高度,宽度),并没有对图片本身进行裁剪。Django是在接收了cropper通过json反馈来的坐标后才对图片进行了真正的裁剪。因为我们需要对图片进行编辑,请确保你已经安装了python的pillow图片库。
Django代码
我们先创建一个名叫myaccount的app, 定义UserProfile的模型,编写URLConfs和视图,最后编写模板,在模板里加入Bootstrap和jQuery cropper。
#models.py
这里UserProfile模型对于Django自带的User模型进行了1对1的扩展,加入了头像Avatar和电话等补充信息。如果你不清楚如何对用户资料进行扩展,请先阅读此教程 。(django-allauth教程(2): 用户个人资料UserProfile扩展与编辑)
from django.db import models
from django.contrib.auth.models import User
import uuid
import os
# Create your models here.
def user_directory_path(instance, filename):
ext = filename.split('.')[-1]
filename = '{}.{}'.format(uuid.uuid4().hex[:10], ext)
sub_folder = 'file'
if ext.lower() in ["jpg", "png", "gif"]:
sub_folder = "avatar"
if ext.lower() in ["pdf", "docx"]:
sub_folder = "document"
return os.path.join(str(instance.user.id), sub_folder, filename)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
org = models.CharField('Organization', max_length=128, blank=True)
avatar = models.ImageField(upload_to=user_directory_path, default=os.path.join("avatar", "default.jpg"), verbose_name="头像")
join_date = models.DateTimeField("join date", blank=True, null=True, auto_now_add=True)
mod_date = models.DateTimeField("Mod date", blank=True, null=True, auto_now=True)
class Meta:
verbose_name = 'User Profile'
def __str__(self):
return "{}'s profile".format(self.user.username)
#urls.py
我们一共创建2个URLs, 一个查看用户资料,一个处理头像部分的上传,并通过ajax返回最新的头像图片链接。
from django.urls import re_path
from . import views
app_name = "myaccount"
urlpatterns = [
re_path(r'^profile/$', views.profile, name='profile'),
re_path(r'^profile/ajax/avatar/$', views.ajax_avatar_upload, name='ajax_avatar_upload'),
]
#views.py
视图里一共有3个方法,它们作用分别如下:
-
profile: 展示用户资料
-
ajax_avatar_upload: 调用crop_image对上传的图片进行裁剪存储,更新avatar图片地址,并以json格式给前端返回最新avatar链接地址。
-
crop_image: 接收前端cropper提供的包含有图片裁剪坐标信息的data,对图片进行裁剪,重命名,上传,并返回最新avatar地址。
from django.shortcuts import render, get_object_or_404
from .models import UserProfile
from .forms import ProfileForm, AvatarUploadForm
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required
import uuid
from django.http import JsonResponse
from PIL import Image
import os
import json
@login_required
def profile(request):
user = request.user
return render(request, 'account/profile.html', {'user': user})
@login_required
def ajax_avatar_upload(request):
user = request.user
user_profile = get_object_or_404(UserProfile, user=user)
if request.method == "POST":
form = AvatarUploadForm(request.POST, request.FILES)
if form.is_valid():
img = request.FILES['avatar_file'] # 获取上传图片
data = request.POST['avatar_data'] # 获取ajax返回图片坐标
if img.size/1024 > 700:
return JsonResponse({"message": "图片尺寸应小于900 X 1200 像素, 请重新上传。", })
current_avatar = user_profile.avatar
cropped_avatar = crop_image(current_avatar, img, data, user.id)
user_profile.avatar = cropped_avatar # 将图片路径修改到当前会员数据库
user_profile.save()
# 向前台返回一个json,result值是图片路径
data = {"result": user_profile.avatar.url, }
return JsonResponse(data)
else:
return JsonResponse({"msg": "请重新上传。只能上传图片"})
return HttpResponseRedirect(reverse('myaccount:profile'))
def crop_image(current_avatar, file, data, uid):
# 随机生成新的图片名,自定义路径。
ext = file.name.split('.')[-1]
file_name = '{}.{}'.format(uuid.uuid4().hex[:10], ext)
cropped_avatar = os.path.join(str(uid), "avatar", file_name)
# 相对根目录路径
file_path = os.path.join("media", str(uid), "avatar", file_name)
# 获取Ajax发送的裁剪参数data,先用json解析。
coords = json.loads(data)
t_x = int(coords['x'])
t_y = int(coords['y'])
t_width = t_x + int(coords['width'])
t_height = t_y + int(coords['height'])
t_rotate = coords['rotate']
# 裁剪图片,压缩尺寸为400*400。
img = Image.open(file)
crop_im = img.crop((t_x, t_y, t_width, t_height)).resize((400, 400), Image.ANTIALIAS).rotate(t_rotate)
directory = os.path.dirname(file_path)
if os.path.exists(directory):
crop_im.save(file_path)
else:
os.makedirs(directory)
crop_im.save(file_path)
# 如果头像不是默认头像,删除老头像图片, 节省空间
if not current_avatar == os.path.join("avatar", "default.jpg"):
current_avatar_path = os.path.join("media", str(uid), "avatar", os.path.basename(current_avatar.url))
os.remove(current_avatar_path)
return cropped_avatar
#forms.py
from django import forms
from .models import UserProfile
class ProfileForm(forms.Form):
first_name = forms.CharField(label='First Name', max_length=50, required=False)
last_name = forms.CharField(label='Last Name', max_length=50, required=False)
org = forms.CharField(label='Organization', max_length=50, required=False)
class AvatarUploadForm(forms.Form):
avatar_file = forms.ImageField()
#templates/myaccount/profile.html
模板里所引用的静态css和js文件,均来自17素材网jQuery cropper静态页面效果展示,几乎一字未改。你可以自己去下载,我这里就不详细贴出来了。
-
http://www.17sucai.com/pins/27291.html
{% extends "account/base.html" %}
{% load static %}
{% block content %}
{% if user.is_authenticated %}
| <a href="{% url 'account_email' %}">Manage Email</a> | <a href="{% url 'account_change_password' %}">Change Password</a> |
<a href="{% url 'account_logout' %}">Logout</a>
{% endif %}
<p>Welcome, {{ user.username }}.
{% if not user.profile.account_verified %}
(Unverified email.)
{% endif %}
</p>
<body style="overflow:hidden;">
<div class="ibox-content">
<div class="row">
<div id="crop-avatar" class="col-md-6">
<div class="avatar-view" title="点击更换头像" >
<img src="{{ user.profile.get_avatar_url }}" >
</div>
</div>
</div>
</div>
<div class="modal fade" id="avatar-modal" aria-hidden="true" aria-labelledby="avatar-modal-label" role="dialog" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form class="avatar-form" action="{% url 'myaccount:ajax_avatar_upload' %}" enctype="multipart/form-data" method="post">
<div class="modal-header">
<button class="close" data-dismiss="modal" type="button">×</button>
<h4 class="modal-title" id="avatar-modal-label">上传头像</h4>
</div>
<div class="modal-body">
<div class="avatar-body">
<div class="avatar-upload">
<input class="avatar-src" name="avatar_src" type="hidden">
<input class="avatar-data" name="avatar_data" type="hidden">
<label for="avatarInput">图片上传</label>
<input class="avatar-input" id="avatarInput" name="avatar_file" type="file"></div>
<div class="row">
<div class="col-md-9">
<div class="avatar-wrapper"></div> </div>
<div class="col-md-3">
<div class="avatar-preview preview-lg"></div>
<div class="avatar-preview preview-md"></div>
<div class="avatar-preview preview-sm"></div>
</div>
</div>
<div class="row avatar-btns">
<div class="col-md-9">
<div class="btn-group">
<button class="btn" data-method="zoom" data-option="0.1" type="button" title="放大图片"><i class="fa fa-repeat"></i> 放大图片</button>
</div>
<div class="btn-group">
<button class="btn" data-method="zoom" data-option="-0.1" type="button" title="缩小图片"><i class="fa fa-repeat"></i> 缩小图片</button>
</div>
<div class="btn-group">
<button class="btn" data-method="setDragMode" data-option="move" type="button" title="移动图片"><i class="fa fa-repeat"></i> 移动图片</button>
</div>
</div>
<div class="col-md-3">
<button class="btn btn-success btn-block avatar-save" type="submit"><i class="fa fa-save"></i>保存修改</button>
</div>
</div>
</div>
</div>
{% csrf_token %}
</form>
</div>
</div>
</div>
<div class="loading" aria-label="Loading" role="img" tabindex="-1"></div>
</body>
<h2>My Profile (<a href="{% url 'myaccount:profile_update' %}">Edit</a> )</h2>
<ul>
<li>First Name: {{ user.first_name }} </li>
<li>Last Name: {{ user.last_name }} </li>
<li>Organization: {{ user.profile.org }} </li>
</ul>
{% endblock %}
{% block css %}
<link href="{% static 'myaccount/cropper/cropper.min.css' %}" rel="stylesheet">
<link href="{% static 'myaccount/sitelogo/sitelogo.css' %}" rel="stylesheet">
{% endblock %}
{% block js %}
<script src="{% static 'myaccount/bootstrap/js/jquery.min.js' %}"></script>
<script src="{% static 'myaccount/cropper/cropper.min.js' %}"></script>
<script src="{% static 'myaccount/sitelogo/sitelogo.js' %}"></script>
<script src="{% static 'myaccount/bootstrap/js/bootstrap.min.js' %}"></script>
{% endblock %}
值得你注意的只有2点:
-
form中的action需要指向你自己的处理图片上传的视图。本例为ajax_avatar_upload。
-
别忘了给form加csrf_token。
最终效果
查看用户资料,点击头像即可更换头像。
上传头像,裁剪和预览
时间过得很快,不知不觉又写了一下午。希望本文对大家有所启发和帮助。