前一篇笔记我们介绍了大部分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()
的返回值可能是InMemoryUploadedFile
或TemporaryUploadedFile
,前者是存储在内存中的文件,后者是存储在磁盘中的临时文件,这种设计是出于IO性能优化考虑的,当超过DATA_UPLOAD_MAX_MEMORY_SIZE
时Django会自动将上传的文件临时存储在磁盘上。
代码中,我们读取了上传文件的名称、大小和类型,随后将其以流的形式读取并写入磁盘目标位置,最终返回了一个简单的OK
响应。注意以上代码仅作演示,实际开发中较少使用,我们更倾向于使用下面介绍的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_ROOT
和MEDIA_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
下的文件都是公开可访问的,如果需要上传到非公开目录,这需要我们自己编写文件的存取逻辑。
我们上传的文件存储路径通常需要和数据库中的数据模型关联起来,这样我们才能在需要的时候获取到文件,前面数据模型定义相关章节我们提到过数据模型字段有FileField
和ImageField
等,它们可以很方便地存储上传文件路径,我们直接将请求中的文件对象设置给相关字段即可,下面是一个例子。
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_ROOT
和MEDIA_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='所在教室')
代码中,我们设置了ImageField
的upload_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
负责在使用完成后关闭它。
注意:手动编写文件下载功能时,一定要小心目录穿越漏洞,不要让用户通过文件路径参数访问到未授权目录下的文件。