处理文件存取

前一篇笔记我们介绍了大部分HTTP请求和响应的处理方式,这篇笔记我们继续学习Django中如何进行和文件相关的处理,包括文件上传和下载、文件如何存储以及如何在数据模型中存储文件的路径。

基础文件上传

HTTP协议中,最基础的文件上传方式是基于form-data格式表单请求实现的。在Django中,我们可以使用request.FILES来获取上传的文件,下面是一个例子。

from django.http import HttpResponse, HttpResponseNotAllowed


def upload(request):
    if request.method == 'POST':
        avatar = request.FILES.get('avatar')
        print('Avatar Name', avatar.name)
        print('Avatar Size', avatar.size)
        print('Avatar ContentType', avatar.content_type)

        with open('C:\\Users\\HUAWEI\\workspace\\' + avatar.name, 'wb+') as destination:
            for chunk in avatar.chunks():
                destination.write(chunk)

        return HttpResponse('OK')
    else:
        return HttpResponseNotAllowed(['POST'])

request.FILES.get()的返回值可能是InMemoryUploadedFileTemporaryUploadedFile,前者是存储在内存中的文件,后者是存储在磁盘中的临时文件,这种设计是出于IO性能优化考虑的,当超过DATA_UPLOAD_MAX_MEMORY_SIZE时Django会自动将上传的文件临时存储在磁盘上。

代码中,我们读取了上传文件的名称、大小和类型,随后将其以流的形式读取并写入磁盘目标位置,最终返回了一个简单的OK响应。注意以上代码仅作演示,实际开发中较少使用,我们更倾向于使用下面介绍的Storage存储层和数据模型封装的相关上传方式。

使用Storage存储层

前面我们处理文件上传时,保存文件的步骤是我们手动编写的,实际上这种方式比较麻烦,Django提供了一个Storage类作为文件上传功能更高级的抽象,它的实现类FileSystemStorage可以方便地实现磁盘文件存储操作,Storage模块甚至还能通过扩展的方式切换云存储等其它存储后端。

下面例子代码中,我们上传文件并使用FileSystemStorage将文件保存在MEDIA_ROOT下,随后返回文件的访问路径。

from django.core.files.storage import FileSystemStorage
from django.http import HttpResponse, HttpResponseNotAllowed
from mysite.settings import MEDIA_ROOT


def upload(request):
    if request.method == 'POST':
        avatar = request.FILES.get('avatar')
        print('Avatar Name', avatar.name)
        print('Avatar Size', avatar.size)
        print('Avatar ContentType', avatar.content_type)

        fs = FileSystemStorage(location=MEDIA_ROOT)
        filename = fs.save(avatar.name, avatar)
        uploaded_file_url = fs.url(filename)

        return HttpResponse(uploaded_file_url)
    else:
        return HttpResponseNotAllowed(['POST'])

以上代码需要正确配置MEDIA_ROOTMEDIA_URL才能正常使用。

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

上传文件后,我们可以看到文件被保存在了项目根目录下的media文件夹中,我们也可以用返回的URL访问到这个文件。

注:MEDIA_ROOT是Django设置中的一个配置项,用于指定用户上传的媒体文件存储的绝对路径。它通常在settings.py文件中定义,帮助管理和存储网站用户生成的内容,如图片、文档、音频和视频等。MEDIA_ROOT必须和MEDIA_URL结合使用,后者指定了媒体文件的URL前缀,以便告知Django框架如何响应这些文件,输出MEDIA_ROOT文件的代码逻辑不需要我们手动编写,它是Django自动处理的,当然,这也意味着MEDIA_ROOT下的文件都是公开可访问的,如果需要上传到非公开目录,这需要我们自己编写文件的存取逻辑。

数据模型存储文件路径

我们上传的文件存储路径通常需要和数据库中的数据模型关联起来,这样我们才能在需要的时候获取到文件,前面数据模型定义相关章节我们提到过数据模型字段有FileFieldImageField等,它们可以很方便地存储上传文件路径,我们直接将请求中的文件对象设置给相关字段即可,下面是一个例子。

from django.http import HttpResponseNotAllowed, HttpResponse
from demo01.models import *


def upload(request):
    if request.method == 'POST':
        avatar = request.FILES.get('avatar')
        student = Student(avatar=avatar, stu_code='00001', name='张三', age=18)
        student.save()
        return HttpResponse('OK')
    else:
        return HttpResponseNotAllowed(['POST'])

代码中,我们将上传文件对象设置给了Student数据模型的avatar字段,它是一个ImageField类型字段,上传成功后我们可以观察media文件夹的变化以及数据库中对应字段存储的值。注意,这种上传方式同样需要配置好MEDIA_ROOTMEDIA_URL,上传的位置默认就是磁盘上的MEDIA_ROOT路径。

想要取回文件时,我们直接访问文件字段的url属性即可,下面例子代码我们从数据库中查询Student对象,并获取avatar字段的URL。

from django.http import JsonResponse, HttpResponseNotFound
from demo01.models import *


def student(request, stu_id):
    students = Student.objects.filter(pk=stu_id)
    if students:
        student = students[0]
        return JsonResponse({
            'stuCode': student.stu_code,
            'name': student.name,
            'age': student.age,
            'avatar': student.avatar.url
        })
    else:
        return HttpResponseNotFound()

如果一切正常,根据之前的配置,avatar字段会得到类似如下的结果(文件名可能不同)。

/media/Camera_2024-09-23_180507.png

手动指定上传规则

前面我们上传后,文件系统上的文件名就是文件本身的名字,这有时候不符合我们的要求,下面例子修改了之前的数据模型,它在上传时会将文件名重命名为UUID + 扩展名的格式,并统一存储在/media/avatars/目录下。

def get_avatar_upload_path(instance, filename):
    ext = filename.split('.')[-1]
    return f'avatars/{uuid.uuid4()}.{ext}'


class Student(models.Model):
    class Meta:
        verbose_name = '学生'
        verbose_name_plural = '学生'

    avatar = models.ImageField(null=True, upload_to=get_avatar_upload_path, verbose_name='头像')
    stu_code = models.CharField(max_length=10, unique=True, verbose_name='学号')
    name = models.CharField(max_length=20, verbose_name='学生姓名')
    age = models.IntegerField(default=-1, verbose_name='年龄')
    class_room = models.ForeignKey(ClassRoom, null=True, on_delete=models.DO_NOTHING, verbose_name='所在教室')

代码中,我们设置了ImageFieldupload_to属性,该属性指定了上传的文件命名规则。

文件下载

前面我们提到过,上传到media路径的文件都是可以公开访问的,该文件夹下的文件会由Django框架负责分发,我们不需要手动编写视图函数就可以访问类似http://localhost:8000/media/Camera_2024-09-23_180507.png这样的文件,只有那些磁盘上其它目录位置的文件或即时生成的文件时才会遇到需要手动编写文件下载代码的情况,Django提供了FileResponse封装实现该功能。

from django.http import FileResponse


def download(request):
    file_path = 'E:/1676269710887.jpeg'
    return FileResponse(open(file_path, 'rb'), content_type='image/jpeg')

FileResponse使用非常简单,我们将打开的文件指针传递给它即可,FileResponse负责在使用完成后关闭它。

注意:手动编写文件下载功能时,一定要小心目录穿越漏洞,不要让用户通过文件路径参数访问到未授权目录下的文件。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap