django 的上传文件操作

一、简单文件上传实现

利用Django实现文件上传并且保存到指定路径下,其实并不困难,可以不需要用到django的forms,也不需要django的models,就可以简单实现上传功能。下面简单实现一下。

当Django在处理文件上传的时候,文件数据被保存在request.FILES。需要特别注意的是,只有当request方法是POST,且发送request的<form>有属性enctype=”multipart/form-data”时,表明不对字符进行编码,request.FILES中才会包含文件数据,否则request.FILES为空。

比如先写upload.html前面上传页面:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

# ./polls/templates/polls/upload.html

<!DOCTYPE html>

<head>

    <meta charset="UTF-8">

    <title>uploadFile</title>

</head>

<body>

    <form method="post" action="" enctype="multipart/form-data">

    {% csrf_token %}

       <label> 上传文件 </label>

       <input type="file" name="myfile" />

       <br/>

       <input type="submit" value="upload"/>

    </form>

</body>

</html>

然后写一个upload_file视图函数,处理文件上传,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

# ./polls/views

from django.shortcuts import render

from django.http import HttpResponse

def upload_file(request):

    # 请求方法为POST时,进行处理;

    if request.method == "POST":

        # 获取上传的文件,如果没有文件,则默认为None;

        File = request.FILES.get("myfile", None)

        if File is None:

            return HttpResponse("no files for upload!")

        else:

            # 打开特定的文件进行二进制的写操作;

            with open("/tmp/%s" % File.name, 'wb+') as f:

                # 分块写入文件;

                for chunk in File.chunks():

                    f.write(chunk)

            return HttpResponse("upload over!")

    else:

        return render(request, 'polls/upload.html')

处理上传文件就是往服务器上生成一个文件,并将上传的文件内容写到新的文件中。然后写文件使用FILE.chunks()方法,而不是使用read()方法,能确保大文件并不会占用系统过多的内存。FILE方法和属性下面介绍。

最后写一个url匹配就可以了:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

# ./project_name/urls.py

urlpatterns = [

    url(r'^admin/', admin.site.urls),

    url(r'^polls/', include('polls.urls'))

]

# ./polls/urls.py

from django.conf.urls import url

from . import views

app_name = 'polls'

urlpatterns = [

    url(r'^$', views.index, name='index'),

    url(r'^upload/$', views.upload_file, name='upload_file'),

]

最终效果如下图,选择文件就可以上传了:

那么这整个具体流程就是,访问http://10.10.0.109:8000/polls/upload/,调用upload_file函数,由于是GET请求,所以直接返回了upload.html页面,然后就可以进行POST请求上传文件了。

二、基于表单上传文件

在Django中我们可以采用Form类来处理表单,通过实例化处理和在模板中渲染,就可以轻松完成表单的需求,采用django的表单处理方式,能帮我们省去很多的工作,比如验证不能为空,或者要符合某种模式的输入才有效,这些处理起来非常方便,不用自己再单独写代码去验证表单的数据正确性,所以在开发中比较常用,Form提供了很多表单字段,比如日期,文本类型等,如果你熟悉基本的html,学起来会非常容易上手,下面只说下表单文件的上传,因为这个类型比较特殊,需要一点特殊的处理,我们来创建一个简单的一个实例:

1

2

3

4

5

6

# ./polls/forms.py

from django import forms

class UploadFileForm(forms.Form):

    title = forms.CharField(max_length=50)

    file = forms.FileField()

这个表单就2个字段,要求用户title和上传一个文件或图片。

处理这个表单的视图会在request中接收到上传文件的数据。FILES是个字典,它包含每个FileField的键(或者ImageField,FileField的子类)。这样的话就可以用request.FILES[‘file’]来存放表单中的这些数据了。

注意request.FILES只有在请求方法为POST并且提交请求的<form>具有enctype=”multipart/form-data”属性时才包含数据。否则,request.FILES将为空。

大多数时候,你将只是简单地从request向表单中传递数据,就像Binding uploaded files to a form描述的那样。所以我们的视图处理逻辑基本类似下面这样:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

# polls/views.py

from django.shortcuts import render

from django.http import HttpResponse

from polls.forms import UploadFileForm

def upload_file(request):

    if request.method == "POST":

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

        if form.is_valid():

            handle_upload_file(request.FILES['file'])

            #handle_upload_file(form.files['file'])

            return HttpResponse('upload success!')

    else:

        form = UploadFileForm()

    return render(request, 'polls/upload.html', {'form': form})

请注意,我们必须将request.FILES传递到form的构造函数中;这就是文件数据如何绑定到一个表单中。

这个函数判断用户的是否为POST请求,如果是并验证是有效的,然后就返回OK,在验证正确和返回OK的中间放我们的上传文件处理函数handle_upload_file,因为只有文件上传成功能返回OK。然后给这个handle_upload_file函数传递一个“request.FILES[‘file’]”,就是我们获取到的文件;也可以从表单中获取到,比如使用form提供的files或cleaned_data属性(form.files[‘file’]),这是表单提供的属性。如果是GET请求,就直接显示一个空表单,让用户输入。

注意我们必须向表单的构造器中传递request.FILES,这是文件数据绑定到表单的方法。FILES中的每个键为<input type=”file” name=”” />中的name。

然后写handle_upload_file函数,处理上传文件就是往服务器上生成一个文件,并将上传的文件内容写到新的文件中,所以它的基本函数是这样的,接收上传文件对象为参数,然后本地打开一个文件,从上传的文件中读出文件,写入新的文件中,代码如下:

1

2

3

4

def handle_upload_file(file):

    with open("/tmp/%s" % file.name, 'wb+') as f:

        for chunk in file.chunks():

            f.write(chunk)

接下来我们把form放到模板中去渲染,模板代码大概如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

# polls/templates/polls/upload.html

<!DOCTYPE html>

<head>

    <meta charset="UTF-8">

    <title>uploadFile</title>

</head>

<body>

    <form method="post" action="" enctype="multipart/form-data">

        {% csrf_token %}

        {{ form }}

       <input type="submit" value="upload"/>

    </form>

</body>

</html>

这个表单被模板渲染后,看上去应该像下面这样,会生成静态源码:

1

2

3

4

<label for="id_title">Title:</label>

<input type="text" name="title" id="id_title" required="" maxlength="50">

<label for="id_file">File:</label>

<input type="file" name="file" id="id_file" required="">

这样就完成了一个文件的上传,完毕。

上传数据在哪里储存?

在你保存上传文件之前,数据需要储存在某个地方。

通常,如果上传文件小于2.5MB,Django会把整个内容存到内存。这意味着,文件的保存仅仅涉及到从内存读取和写到磁盘,所以非常快。

但是,如果上传的文件很大,Django会把它写入一个临时文件,储存在你系统的临时目录中。在类Unix的平台下,你可以认为Django生成了一个文件,名称类似于/tmp/tmpzfp6I6.upload。如果上传的文件足够大,你可以观察到文件大小的增长,由于Django向磁盘写入数据。

这些特定值 – 2.5 MB,/tmp,以及其它 — 都仅仅是”合理的默认值”,它们可以自定义,这会在下一节中描述。

UploadedFile对象

在文件上传期间,实际文件数据存储在request.FILES中。此字典中的每个条目都是UploadedFile对象(或子类) – 上传文件的简单包装器。UploadedFile对象是对Python file对象的一个简单封装,并带有Django特定的附加功能。需要表示文件的时候,Django内部会使用这个类。UploadedFile对象拥有下列属性和方法:

name

含有MEDIA_ROOT相对路径的文件名称。

size

文件的字节数。

mode

文件的读写模式。

open([mode=None])

打开或者重新打开文件(同时会执行File.seek(0))。 mode参数的值和Python内建的open()相同。

重新打开一个文件时,无论文件原先以什么模式打开,mode都会覆盖;None的意思是以原先的模式重新打开。

read([num_bytes=None])

读取文件内容。可选的size参数是要读的字节数;没有指定的话,文件会一直读到结尾。

__iter__()

迭代整个文件,并且每次生成一行。

File现在使用通用的换行符。以下字符会识别为换行符:Unix换行符’\n’,WIndows换行符’\r\n’,以及Macintosh旧式换行符\r。

chunks([chunk_size=None])

迭代整个文件,并生成指定大小的一部分内容。chunk_size默认为64KB。

处理大文件时这会非常有用,因为这样可以把他们从磁盘中读取出来,而避免将整个文件存到内存中。

multiple_chunks([chunk_size=None])

如果文件足够大,需要按照提供的chunk_size切分成几个部分来访问到所有内容,则返回True 。

write([content])

将指定的内容字符串写到文件。取决于底层的储存系统,写入的内容在调用close()之前可能不会完全提交。

close()

关闭文件。

除了这些列出的方法,File沿用了file对象的以下属性和方法:encoding、fileno、flush、isatty、newlines、read、readinto、readlines、seek、softspace、tell、truncate、writelines、xreadlines。

基于这些属性和方法,我们把代码很轻松写的更规范,比如限制上传文件大小等。

三、文件下载

基于Django建立的网站,如果提供文件下载功能,最简单的方式莫过于将静态文件交给Nginx等处理,但有些时候,由于网站本身逻辑,需要通过Django提供下载功能,如页面数据导出功能(下载动态生成的文件)、先检查用户权限再下载文件等。因此,有必要研究一下文件下载功能在Django中的实现。

最简单的文件下载功能的实现

将文件流放入HttpResponse对象即可,如:

1

2

3

4

5

def download_file(request):

    # do something...

    with open('/tmp/file_name.txt', 'rb') as f:

        c = f.read()

    return HttpResponse(c)

这种方式简单粗暴,适合小文件的下载,但如果这个文件非常大,这种方式会占用大量的内存,甚至导致服务器崩溃。

更合理的文件下载功能

Django的HttpResponse对象允许将迭代器作为传入参数,将上面代码中的传入参数c换成一个迭代器,便可以将上述下载功能优化为对大小文件均适合;而Django更进一步,推荐使用 StreamingHttpResponse对象取代HttpResponse对象,StreamingHttpResponse对象用于将文件流发送给浏览器,与HttpResponse对象非常相似,对于文件下载功能,使用StreamingHttpResponse对象更合理。

因此,更加合理的文件下载功能,应该先写一个迭代器,用于处理文件,然后将这个迭代器作为参数传递给StreaminghttpResponse对象,如:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

from django.http import StreamingHttpResponse

def download_file(request):

    def file_iterator(file, chunk_size=512):

        with open(file) as f:

            while True:

                c = f.read(chunk_size)

                if c:

                    yield c

                else:

                    break

    file = "file_name.txt"

    response = StreamingHttpResponse(file_iterator(file))

    return response

文件下载功能再次优化

上述的代码,已经完成了将服务器上的文件,通过文件流传输到浏览器,但文件流通常会以乱码形式显示到浏览器中,而非下载到硬盘上,因此,还要在做点优化,让文件流写入硬盘。优化很简单,给StreamingHttpResponse对象的Content-Type和Content-Disposition字段赋下面的值即可,如:

1

2

response['Content-Type'] = 'application/octet-stream'

response['Content-Disposition'] = 'attachment;filename="test.pdf"'

所以,完整代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

from django.http import StreamingHttpResponse

def download_file(request):

    def file_iterator(file, chunk_size=512):

        with open(file) as f:

            while True:

                c = f.read(chunk_size)

                if c:

                    yield c

                else:

                    break

    file = "big_file.pdf"

    response = StreamingHttpResponse(file_iterator(file))

    response['Content-Type'] = 'application/octet-stream'

    response['Content-Disposition'] = 'attachment;filename="{0}"'.format(file)

    return response

 

  • 7
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值