最近在捣鼓 Django 文件上传和下载,过程还好,挺简单,但其中遇到好多问题,折腾好久。把自己实现的方法分享下。

好,先来说上传吧。

参考 Django 官方文档:https://docs.djangoproject.com/en/1.7/topics/http/file-uploads/
由于之后还要考虑下载的问题,那我们就多做一步,将文件记录在数据库。

model

那么,首先写一个用于记录上传文件的 model :

1
2
3
class UploadFile(models.Model):
name = models.CharField(max_length=50)
file = models.FileField(upload_to = "uploads/%Y/%m")

其中包含一个 CharField 字段,用于存放上传文件名,还有一个 FileField 字段,用来存放 文件本身(其实是文件的路径)。upload_to 参数是用来设置上传文件的保存路径,此处是按上传日期的年月来归档。

路由配置

配置 settings 文件,添加如下两行:

1
2
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

如此,文件将上传到 根目录下的 media/uploads/… 下。

接下来写 html 模板 index.html :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
{% for file in file_list %}
<li><a href="#">{{ file.name }}</a></li>
{% endfor %}
<form enctype="multipart/form-data" method="POST" action="{% url 'uploads' %}"> {% csrf_token %}
<input type="file" name="file" />
<br />
<input type="submit" value="上传文件" />
</form>
</body>
</html>

配置 URL,urls.py 中添加:

1
2
url(r'^$',index),
url(r'^uploads', uploads, name ="uploads")

后台逻辑

接着重点来了,去写具体的方法,views.py :

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
28
29
30
31
32
33
34
35
36
37
def index(request):
file_list = UploadFile.objects.all()
return render(request, "index.html", {"file_list" : file_list})
def handle_uploaded_file(f, path):
#从路径中提取出 文件名 和 文件所在文件夹路径
t = path.split("/")
file_name = t[-1]
extensions = file_name.split(".")[1]
t.remove(t[-1])
t.remove(t[0])
path = "/".join(t)
try:
if not os.path.exists(path):
os.makedirs(path)
file_name = path + "/" + file_name +"." + extensions
print file_name
destination = open(file_name, 'wb+')
for chunk in f.chunks():
destination.write(chunk)
destination.close()
except Exception, e:
print e
return file_name
def uploads(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
upload_file = UploadFile.objects.create(name = request.FILES['file'].name, file = request.FILES['file']) #写入数据库
path = upload_file.file.url
handle_uploaded_file(request.FILES['file'], path) #上传处理
upload_file.save() #保存
return redirect("blog.views.index") #blog 是 app 名称
else:
form = UploadFileForm()
return render(request, 'index.html', {'form': form})

到这按说已经可以了,但,在之后的下载中出了问题,是因为文件名的问题,如果文件名是中文的,在传递的过程中编码的问题就不得不考虑,而且很是烦人,所以在这,我们要避过使用中文文件名的问题。好,查一查,Django 上传文件重命名的问题。

方法来自这:link.

赶紧实践一下。

首先修改settings.py的配置,通过查看源文件,可以看到DEFAULT_FILE_STORAGE默认指向的是FileStorage,我们可以修改指向,然后重写save方法。

settings 文件中添加:

1
DEFAULT_FILE_STORAGE = "mysite.customfilefield.storage.FileStorage"

mysite 是工程名,在工程文件夹下新建文件夹 customfilefield ,将下面的 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
from django.conf import settings
import os, time
class FileStorage(FileSystemStorage):
def __init__(self, location=settings.MEDIA_ROOT, base_url=settings.MEDIA_URL):
#初始化
super(FileStorage, self).__init__(location, base_url)
#重写 _save方法
def _save(self, name, content):
#文件扩展名
ext = os.path.splitext(name)[1]
#文件目录
d = os.path.dirname(name)
#定义文件名,以上传时间命名,像这样的一串数字144557245952
fn = str(time.time())
fn = ("").join(fn.split("."))
#重写合成文件名
name = os.path.join(d, fn + ext)
#调用父类方法
return super(FileStorage, self)._save(name, content)

好了,这样上传的文件的名字就会以上传的时间来命名了。
上传就到这里了。