Django之超简单的处理图片上传

后端部分

就是苦了前端

写在前面

Django 本身用来开发已经很迅速了, 默认使用自带的 sqlite3 数据库, 可以完成基本的关系型数据存储的需求, 不过在其 model 中还有一个特别的字段, 就是 ImageField, 顾名思义, 就是存储图片的一个字段

ImageField

虽然这个字段能够完成存储图片的功能, 但不代表这个字段的值, 就是一个图片, 一般使用它的 url 属性

一般是下面这样简单的用法:

  1. project_name/settings.py 中写入:
1
2
3
MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')

MEDIA_URL = '/media/'

这样你就设定了一个 MEDIA_ROOT, 这个目录是用来保存一些动态文件; 后面 ImageField 的图片会保存在这个目录下

  1. 设计你的 model
1
Avatar = models.ImageField(upload_to = 'avatar')

upload_to 表示你上传到哪个文件夹, 这个例子是上传到 /media/avatar 这里

  1. view 中处理从前端提交的图片

这里其实就有很多中处理方法, 比如把图片视为简单的 python file, 然后自定义处理方式, 保存到自己想要的位置, 设定文件名等, 可以给一下实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@login_required
def upload_avatar(request):
if request.method == 'POST':
print(request.FILES)
# 从这里开始, 把文件拿出来然后像普通文件一样处理
file = request.FILES.get('avatar', None)
filePath = handel_avatar(file,request.user.id)
if filePath:
config = request.user.config
config.avatar.name = filePath
config.save()
return JsonResponse({'result':True})
return HttpResponse(status=403)
# 文件处理函数
def handel_avatar(file,id):
if file == None:
return None
extension = os.path.splitext(file.name)[1][1:].lower()
file_path = os.path.join(
settings.BASE_DIR, 'static/img/', str(id)+'.'+extension)
if extension not in ['jpg', 'bmp', 'png'] or file.size > 1024*1024*2:
return None
f = open(file_path, 'wb')
for c in file.chunks():
f.write(c)
f.close()
return os.path.join('static/img/',str(id) +'.'+extension)

不过这样有点重复造轮子的感觉, 毕竟 Django 设计了这个字段, 肯定有它的用法(当然需要高度自定义的话, 可能还是要自己造轮子)


其实最简单的方法就是把 取出来的 file 直接赋值给对应字段, 就像下面这样:

1
t_user.Avatar = request.FILES['file']

这样这个字段就保存好这个图片的信息了, 如果你要使用这个图片的话, 可以使用它的 url 属性, 然后找到这个图片, 进行自定义处理

1
2
# 在这里返回路径信息
return jsonMSG(state = 'ok', msg = t_user.Avatar.url)

其他细节

  1. 默认图片

因为这里本身的需求是对头像图片的处理, 所以要设定一张默认图片来显示, 其实也很简单, 在 model 里定义的时候添加 default 属性即可:

1
Avatar = models.ImageField(upload_to = 'avatar', default = 'avatar/default.jpg')

这样这个字段默认就会是 avatar/default.jpg 对应的图片

  1. 前端处理

前面在 view 中处理的时候是从 request.FILES['file'] 中拿到的, 所以前端要发送对应的请求, 这个是要求前端发送的 表单消息格式multipart/from-data, 也就是 Content-Typemultipart/from-data

一般的 html 都可以设定这种类型, 不过在 iOS 中并没有原生的支持, 所以一是自己写一个 HTTP 报文, 或者是使用 AFNetworking 这个框架

  1. 文件名的问题

如果只是上面这样简单的使用, 文件名是保留为上传时候的文件名, 这样当有重复文件名的图片被接受到时, 原来的就被 覆盖 了, 这就很不友好, 所以文件名一般需要后端这边另外设置一下

ImageField 这个字段在存储时其实也是有自己的文件处理函数, 所以可以把这个文件处理换成我们自己想要的, 在里面更改文件名, 这样就可以达到这个需求; 而这个文件处理类在字段定义的时候就可以赋予了, 不过还有一些操作过程:


BASE_DIR 中新建一个文件夹 system, 进入这个文件夹, 创建一个内容为空的 __init__.py 文件, 创建 storage.py, 输入以下内容, 也就是你的存储处理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: UTF-8 -*-
from django.core.files.storage import FileSystemStorage
class ImageStorage(FileSystemStorage):
from django.conf import settings

def __init__(self, location=settings.MEDIA_ROOT, base_url=settings.MEDIA_URL):
# 初始化
super(ImageStorage, self).__init__(location, base_url)

# 重写 _save方法
def _save(self, name, content):
import os, time, random
# 文件扩展名
ext = os.path.splitext(name)[1]
# 文件目录
d = os.path.dirname(name)
# 定义文件名,年月日时分秒随机数
fn = time.strftime('%Y%m%d%H%M%S')
fn = fn + '_%d' % random.randint(0,100)
# 重写合成文件名
name = os.path.join(d, fn + ext)
# 调用父类方法
return super(ImageStorage, self)._save(name, content)

(参考了网上的代码) 这个是把文件名改为 年月日时分秒_随机数iOS 这个简单的项目是可以用的了…

最后 ImageField 的定义为:

1
2
3
from system.storage import ImageStorage

Avatar = models.ImageField(upload_to = 'avatar', storage = ImageStorage(), default = 'avatar/default.jpg')
  1. url 直接访问图片文件

之前说到一般使用 ImageFieldurl 属性, 进一步, 可以让 Django 进行路由, 这样就可以直接通过 url 来访问图片文件了

关键点是设定路由, 在 urls.py 添加, 像下面这样:

1
2
3
4
5
6
7
# 引入相应库
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [

] + static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)

这样路由就设定好了, 可以测试一下看看:

成功!

上传图片测试

用html测试

iOS 关于 multipart/from-data 代码不好写, 就用 html 测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<form method="POST" action="http://127.0.0.1:8000/account/info/avatar" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit">
</form>
</body>
</html>

选择图片

返回消息

查看图片