Django+jQuery cropper实现用户头像裁剪, 预览和上传[原创]

版权声明:本文系大江狗原创,请勿直接copy应用于你的出版物或任何公众平台。 https://blog.csdn.net/weixin_42134789/article/details/81382146

学习Django的最终目的还是应用,尤其是漂亮的应用。今天小编我要教你利用Django开发一个经典的用户头像上传和变更的app,主要实现以下功能场景。

  • 用户点击现有头像,弹出一个图片上传和编辑窗口。

  • 用户上传图片,对图片进行缩放和裁剪,并可以预览编辑过的头像。

  • 用户点击确认上传,保存编辑过后的图片,并实时更新头像。

 

这个小应用的功能不亚于知乎,百度和CSDN上用户头像上传和变更的功能,可以直接应用到你的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()

            # 向前台返回一个jsonresult值是图片路径
            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">&times;</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。

 

最终效果

查看用户资料,点击头像即可更换头像。

上传头像,裁剪和预览

时间过得很快,不知不觉又写了一下午。希望本文对大家有所启发和帮助。

猜你喜欢

转载自blog.csdn.net/weixin_42134789/article/details/81382146
今日推荐