Django基础:如何上传处理文件及Ajax文件上传示范

小编我今天要写篇值得大家收藏的文章。我将重点解释Django上传处理文件中需要考虑的重要事项,并提供一般文件上传及Ajax文件上传的示范(附GitHub源码)。如果你的项目需要用到文件上传,你可以从GitHub获取源代码,简化你的开发。

Django文件上传需要考虑的重要事项

文件一般通过表单进行。用户在前端点击文件上传,然后以POST方式将数据和文件提交到服务器。服务器在接收到POST请求后需要将其存储在服务器上的某个地方。Django默认的存储地址是相对于根目录的/media/文件夹,存储的默认文件名就是文件本来的名字。上传的文件如果不大于2.5MB,会先存入服务器内存中,然后再写入磁盘。如果上传的文件很大,Django会把文件先存入临时文件,再写入磁盘。

Django默认处理方式会出现一个问题,所有文件都存储在一个文件夹里。不同用户上传的有相同名字的文件可能会相互覆盖。另外用户还可能上传一些不安全的文件如js和exe文件,我们必需对允许上传文件的类型进行限制。因此我们在利用Django处理文件上传时必需考虑如下3个因素:

设置存储上传文件的文件夹地址

对上传文件进行重命名

对可接受的文件类型进行限制(表单验证)

本文将会讲解在Django示范代码中如何实现上述3个功能。

Django文件上传的3种常见方式

Django文件上传一般有3种方式(如下所示)。我们会针对3种方式分别提供代码示范。

使用一般的表单上传,在视图中手动编写代码处理上传的文件

使用由模型创建的表单(ModelForm)上传,使用form.save()方法自动存储

使用Ajax实现文件异步上传,上传页面无需刷新即可显示新上传的文件

项目创建与设置

我们先使用django-admin startproject命令创建一个叫file_project的项目,然后cd进入file_project, 使用python manage.py startapp创建一个叫file_upload的app。

我们首先需要将file_upload这个app加入到我们项目里,然后设置/media/和/STATIC_URL/文件夹。我们上传的文件都会放在/media/文件夹里。我们还需要使用css和js这些静态文件,所以需要设置STATIC_URL。

#file_project/settings.py

INSTALLED_APPS = [

'django.contrib.admin',

'django.contrib.auth',

'django.contrib.contenttypes',

'django.contrib.sessions',

'django.contrib.messages',

'django.contrib.staticfiles',

'file_upload',

]

#file_project/settings.py

STATIC_URL ='/static/'

STATICFILES_DIRS = [os.path.join(BASE_DIR,"static"),]

# specify media root for user uploaded files,

MEDIA_ROOT = os.path.join(BASE_DIR,'media')

MEDIA_URL ='/media/'

#file_project/urls.py

fromdjango.contribimportadmin

fromdjango.urlsimportpath,include

fromdjango.confimportsettings

fromdjango.conf.urls.staticimportstatic

urlpatterns = [

path('admin/',admin.site.urls),

path('file/',include("file_upload.urls")),

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

创建模型

使用Django上传文件创建模型不是必需,然而如果我们需要对上传文件进行系统化管理,模型还是很重要的。我们的File模型包括file和upload_method两个字段。我们通过upload_to选项指定了文件上传后存储的地址,并对上传的文件进行了重命名。如果你想了解如何自定义用户上传文件夹地址和对上传文件进行重命名,请阅读

这里

#file_upload/models.py

fromdjango.dbimportmodels

importos

importuuid

# Create your models here.

# Define user directory path

defuser_directory_path(instance,filename):

ext = filename.split('.')[-1]

filename ='{}.{}'.format(uuid.uuid4().hex[:10],ext)

returnos.path.join("files",filename)

classFile(models.Model):

file = models.FileField(upload_to=user_directory_path,null=True)

upload_method = models.CharField(max_length=20,verbose_name="Upload Method")

注意:

如果你不使用ModelForm,你还需要手动编写代码存储上传文件。

URLConf配置

本项目一共包括5个urls, 分别对应普通表单上传,ModelForm上传和Ajax上传。还有两个urls,一个用来显示文件清单,一个专门处理ajax请求。

#file_upload/urls.py

fromdjango.urlsimportre_path,path

from.importviews

# namespace

app_name ="file_upload"

urlpatterns = [

# Upload File Without Using Model Form

re_path(r'^upload1/$',views.file_upload,name='file_upload'),

# Upload Files Using Model Form

re_path(r'^upload2/$',views.model_form_upload,name='model_form_upload'),

# Upload Files Using Ajax Form

re_path(r'^upload3/$',views.ajax_form_upload,name='ajax_form_upload'),

# Handling Ajax requests

re_path(r'^ajax_upload/$',views.ajax_upload,name='ajax_upload'),

# View File List

path('',views.file_list,name='file_list'),

]

使用一般表单上传文件

我们先定义一个一般表单FileUploadForm,并通过clean方法对用户上传的文件进行验证,如果上传的文件名不以jpg, pdf或xlsx结尾,将显示表单验证错误信息。关于表单的自定义和验证更多内容见Django基础(5): 表单forms的设计与使用。

#file_upload/forms.py

fromdjangoimportforms

from.modelsimportFile

# Regular form

classFileUploadForm(forms.Form):

file = forms.FileField(widget=forms.ClearableFileInput(attrs={'class':'form-control'}))

upload_method = forms.CharField(label="Upload Method",max_length=20,

widget=forms.TextInput(attrs={'class':'form-control'}))

defclean_file(self):

file =self.cleaned_data['file']

ext = file.name.split('.')[-1].lower()

ifextnot in["jpg","pdf","xlsx"]:

raiseforms.ValidationError("Only jpg, pdf and xlsx files are allowed.")

# return cleaned data is very important.

returnfile

注意:

使用clean方法对表单字段进行验证时,别忘了return验证过的数据,即cleaned_data。只有返回了cleaned_data, 视图中才可以使用form.cleaned_data.get('xxx')获取验证过的数据。

对应一般文件上传的视图file_upload方法如下所示。当用户的请求方法为POST时,我们通过form.cleaned_data.get('file')获取通过验证的文件,并调用自定义的handle_uploaded_file方法来对文件进行重命名,写入文件。如果用户的请求方法不为POST,则渲染一个空的FileUploadForm在upload_form.html里。我们还定义了一个file_list方法来显示文件清单。

#file_upload/views.py

fromdjango.shortcutsimportrender,redirect

from.modelsimportFile

from.formsimportFileUploadForm,FileUploadModelForm

importos

importuuid

fromdjango.httpimportJsonResponse

fromdjango.template.defaultfiltersimportfilesizeformat

# Create your views here.

# Show file list

deffile_list(request):

files = File.objects.all().order_by("-id")

returnrender(request,'file_upload/file_list.html',{'files': files})

# Regular file upload without using ModelForm

deffile_upload(request):

ifrequest.method =="POST":

form = FileUploadForm(request.POST,request.FILES)

ifform.is_valid():

# get cleaned data

upload_method = form.cleaned_data.get("upload_method")

raw_file = form.cleaned_data.get("file")

new_file = File()

new_file.file = handle_uploaded_file(raw_file)

new_file.upload_method = upload_method

new_file.save()

returnredirect("/file/")

else:

form = FileUploadForm()

returnrender(request,'file_upload/upload_form.html',{'form': form,

'heading':'Upload files with Regular Form'})

defhandle_uploaded_file(file):

ext = file.name.split('.')[-1]

file_name ='{}.{}'.format(uuid.uuid4().hex[:10],ext)

# file path relative to 'media' folder

file_path = os.path.join('files',file_name)

absolute_file_path = os.path.join('media','files',file_name)

directory = os.path.dirname(absolute_file_path)

if notos.path.exists(directory):

os.makedirs(directory)

withopen(absolute_file_path,'wb+')asdestination:

forchunkinfile.chunks():

destination.write(chunk)

returnfile_path

注意:

handle_uploaded_file方法里文件写入地址必需是包含/media/的绝对路径,如果/media/files/xxxx.jpg,而该方法返回的地址是相对于/media/文件夹的地址,如/files/xxx.jpg。存在数据中字段的是相对地址,而不是绝对地址。

构建文件写入绝对路径时请用os.path.join方法,因为不同系统文件夹分隔符不一样。

写入文件前一个良好的习惯是使用os.path.exists检查目标文件夹是否存在,如果不存在先创建文件夹,再写入。

上传表单模板upload_form.html代码如下。

#file_upload/templates/upload_form.html

{% extends "file_upload/base.html" %}

{% block content %}

{% if heading %}

{{ heading }}

{% endif %}

{% csrf_token %}

{{ form.as_p }}

Upload

{% endblock %}

注意:

发送必需有属性enctype="multipart/form-data",否则表单不能发送文件,request.FILES为空。

我们的模板继承了base.html, 别忘了添加哦, 目的是为了显示更漂亮。

#file_upload/templates/base.html

{% load static %}

{% block title %}Django File Upload and Download{% endblock %}

{% block content %} {% endblock %}

{% block js %} {% endblock %}

普通表单上传文件页面显示如下所示:

显示文件清单模板file_list.html代码如下所示:

#file_upload/templates/file_list.html

{% extends "file_upload/base.html" %}

{% block content %}

File List

RegularFormUpload|ModelFormUpload

|AjaxUpload

{% if files %}

{% else %}

No files uploaded yet. Please clickhere

to upload files.

{% endif %}

{% endblock %}

注意:

对于上传的文件我们可以调用file.url, file.name和file.size来查看上传文件的链接,地址和大小。

上传文件的大小默认是以B显示的,数字非常大。使用Django模板过滤器filesizeformat可以将文件大小显示为人们可读的方式,如MB,KB。

文件清单显示效果如下所示:

使用ModelForm上传文件

使用ModelForm上传是小编我推荐的上传方式,前提是你已经在模型中通过upload_to选项自定义了用户上传文件存储地址,并对文件进行了重命名。

我们首先要自定义自己的FileUploadModelForm,由模型重建的。代码如下所示:

#file_upload/forms.py

fromdjangoimportforms

from.modelsimportFile

# Model form

classFileUploadModelForm(forms.ModelForm):

classMeta:

model = File

fields = ('file','upload_method',)

widgets = {

'upload_method': forms.TextInput(attrs={'class':'form-control'}),

'file': forms.ClearableFileInput(attrs={'class':'form-control'}),

}

defclean_file(self):

file =self.cleaned_data['file']

ext = file.name.split('.')[-1].lower()

ifextnot in["jpg","pdf","xlsx"]:

raiseforms.ValidationError("Only jpg, pdf and xlsx files are allowed.")

# return cleaned data is very important.

returnfile

使用ModelForm处理文件上传的视图model_form_upload方法非常简单,只需使用form.save()即可,无需再手动编写代码写入文件。

#file_upload/views.py

fromdjango.shortcutsimportrender,redirect

from.modelsimportFile

from.formsimportFileUploadForm,FileUploadModelForm

importos

importuuid

fromdjango.httpimportJsonResponse

fromdjango.template.defaultfiltersimportfilesizeformat

# Create your views here.

# Upload File with ModelForm

defmodel_form_upload(request):

ifrequest.method =="POST":

form = FileUploadModelForm(request.POST,request.FILES)

ifform.is_valid():

form.save()

returnredirect("/file/")

else:

form = FileUploadModelForm()

returnrender(request,'file_upload/upload_form.html',{'form': form,

'heading':'Upload files with ModelForm'})

上传表单模板也是upload_form.html,和前例供用的。

#file_upload/templates/upload_form.html

{% extends "file_upload/base.html" %}

{% block content %}

{% if heading %}

{{ heading }}

{% endif %}

{% csrf_token %}

{{ form.as_p }}

Upload

{% endblock %}

显示效果如下所示:

使用Ajax上传文件

使用Ajax上传文件的好处是,你上传文件后无需刷新页面或跳转即可立刻显示新上传的文件信息(如下所示)。Ajax应用场景还是非常普遍的,比如用户上传头像后无需刷新实时显示新上传的头像。或则用户添加评论后无需刷新页面直接显示新增的评论。

AJAX文件上传代码最重要的部分在前端(代码如下所示)。我们构建了FormData对象,添加了file和upload_method, 并通过设置processData=False告诉jQuery不要处理上传的文件,交由后台处理。由于发送POST请求还需要提供csrftoken,我们还通过jQuery的cookie库获取crsftoken,添加到请求头里,一起发到服务器上。如果后台返回的data没有error_msg, 就显示后台返回的更新过的文件清单。处理ajax的请求地址是/file/ajax_upload/, 对应的视图方法是ajax_upload.

#file_upload/templates/ajax_upload_form.html

{% extends "file_upload/base.html" %}

{% block content %}

{% if heading %}

{{ heading }}

{% endif %}

{% csrf_token %}

{{ form.as_p }}

{% endblock %}

{% block js %}

{% endblock %}

注意:

Ajax代码部分代码请注意不要随意变动,尤其评论//部分要特别注意。

负责处理Ajax请求的视图ajax_upload方法如下所示。该方法将ajax发过来的数据于FileUploadModelForm先结合,然后直接调用form.save方法存储,最后以json格式返回更新过的文件清单。如何用户上传文件不符合要求,返回错误信息。

#file_upload/views.py

# Upload File with ModelForm

defajax_form_upload(request):

form = FileUploadModelForm()

returnrender(request,'file_upload/ajax_upload_form.html',{'form': form,

'heading':'File Upload with AJAX'})

# handling AJAX requests

defajax_upload(request):

ifrequest.method =="POST":

form = FileUploadModelForm(data=request.POST,files=request.FILES)

ifform.is_valid():

form.save()

# Obtain the latest file list

files = File.objects.all().order_by('-id')

data = []

forfileinfiles:

data.append({

"url": file.file.url,

"size": filesizeformat(file.file.size),

"upload_method": file.upload_method,

})

returnJsonResponse(data,safe=False)

else:

data = {'error_msg':"Only jpg, pdf and xlsx files are allowed."}

returnJsonResponse(data)

returnJsonResponse({'error_msg':'only POST method accpeted.'})

GitHub源码

https://github.com/shiyunbo/django-file-upload-download

小结

本文提供并解读了利用Django上传文件的3种主要方式(一般表单上传,ModelForm上传和Ajax上传)及示范代码。我们后续会专题讲解多文件上传和文件下载(如大文件下载), 欢迎关注我们的微信公众号。

如果喜欢本文就加入微信收藏或点赞吧。

大江狗

2018.10.21

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181021G0QV4B00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券