【Django 020】Django2.2多文件同时上传和文件MIME判定以及数据模型中FileField和ImageField的使用详解

文件上传的本质就是把本地的文件复制到服务端,那么客户端和服务端要怎样操作才能完成这个复制过程呢,这一篇我们就一起来看看。

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

基本上传文件

客户端配置

页面的表单利用<input type="file" name="">来上传文件,不过需要注意的是,要想让服务端能成功接收到文件,需要给表单加一个额外的配置就是enctype="multipart/form-data"

同时,如果要允许一次性上传多个文件,可以给input加上multiple属性。

而要限制上传的文件MIME类型,可以给input加上accept属性。

创建一个h5页面upload_file.html如下备用,允许上传多个文件,同时限制了文件类型MIME

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Upload File</title>
</head>
<body>

<form action="{% url 'four:upload_file' %}" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <label for="upload">File:</label><input type="file" id="upload" name="upload"  multiple accept="image/jpeg,text/plain"><br>
    <input type="submit">

</form>
</body>
</html>

这里的{% csrf_token %}是为了防止csrf嵌入的标志,不加这个标志无法使用post方法。

UploadedFile类

在view函数中通过HttpRequest.FILES来获取上传的所有文件,类似HttpRequest.POST,也是通过字典的方式来进行访问,字典的key是h5页面中<input type="file">name名,而字典的value是一个UploadedFile类的实例。

根据官方文档,有下面几种方法获取上传文件的内容

  • UploadedFile.read()
    一次读取文件的所有内容,一般不使用这个方法因为一旦上传的文件过大,可能会使得内存溢出

  • UploadedFile.multiple_chunks(chunk_size=None)
    如果上传的文件过大以至于需要分多个trunk来读取,这个方法会返回True,这个大小的临界点默认是2.5MB,可配

  • UploadedFile.chunks(chunk_size=None)
    一个包含文件所有trunk的生成器,当上面的UploadedFile.multiple_chunks()==True的时候,通过在一个循环中读取每个trunk来获取文件而不是通过上面的UploadFile.read()。根据官方文档,推荐在任何情况下使用本方法来读取文件内容,经过我自己的测试,即使上面的UploadedFile.multiple_chunks()==False的时候也是没问题的

同时还有下面几种属性可以调用

  • UploadedFile.name
    上传文件的文件名,包含后缀

  • UploadedFile.size
    单位为bytes的文件大小

  • UploadedFile.content_type
    文件的MIME类型

服务端操作

static/upload/下创建两个目录imagestext来存储上传的两类文件,然后创建下面的路由和view函数用来接收文件

path('upload_file/', views.upload_file, name='upload_file'),
def upload_file(request):
    if request.method == 'GET':
        return render(request, 'upload_file.html')
    elif request.method == 'POST':
        uploaded_list = request.FILES.getlist('upload')
        for uploaded in uploaded_list:
            if uploaded.content_type.startswith('image/'):
                file_type = 'images'
            elif uploaded.content_type.startswith('text/'):
                file_type = 'text'
            with open('/home/fuhx/python_projects/django/DjangoModel/static/upload/' + file_type + '/' + uploaded.name,
                      'wb') as f:
                for chunk in uploaded.chunks():
                    f.write(chunk)
                    f.flush()
        return HttpResponse('upload successfully')

这里的逻辑如下:

  1. 内聚函数,根据请求的方法来看是返回html还是响应API
  2. 通过request.FILES.getlist()来获取包含所有文件的一个list,这里不能用get(),不然只能返回QueryDict的最后一个元素
  3. 通过文件类型来决定存储路径的子目录,值得一提的是,这里也可以用正则表达式来完成,例如if re.match(r'^image/',uploaded.content_type): pass
  4. 通过文件描述符,以二进制方式将每个trunk写入,每次写入都flush一下清空缓存
  5. 所有文件存储完毕,返回一个成功页面

效果演示

最后的效果如下:

  1. 打开http://127.0.0.1:8000/four/upload_file/如下
    1-h5.png

  2. 选择文件,将符合MIME的三个文件都选上
    2-choose.png

  3. 点击submit按钮上传
    3-submit.png

  4. 成功上传
    4-success.png

  5. 在服务端看到不同类型文件被存储到了不同目录
    5-directory.png

利用数据模型简化操作

上面这种存储方式可以说是放之四海而皆准,不管是Django还是Flask都可以用,虽然正确但不简洁。Django为了方便存储上传的文件,在数据模型中特意创建了一个FileField用来实现这一目的。下面我们就来看看怎么使用FileField来更简洁地存储文件。当然还有一个ImageField,它继承了FileField所有特征,只是会判断上传的文件是不是图片而已。

models.py中定义2个模型如下,分别用来存储图片和文件

class Upload_Image(models.Model):
    u_name = models.CharField(max_length=16)
    u_path = models.ImageField(upload_to='images')


class Upload_Text(models.Model):
    u_name = models.CharField(max_length=16)
    u_path = models.FileField(upload_to='text')

然后进行迁移,如果是第一次使用ImageField,迁移的时候会有下面的报错

Cannot use ImageField because Pillow is not installed.

直接用pip安装下pillow即可,或者在Pycharm中安装也可以。

需要额外注意的就是这里的upload_to配置项,下面的Django文件存储系统会详细讲。 这里只需要知道这是一个相对路径。

然后类似上面的方法创建h5页面filefield.html如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Image Field</title>
</head>
<body>
<form action="{% url 'four:filefield' %}" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <label for="name">Name:</label><input type="text" id="name" name="name"><br>
    <label for="upload">Upload:</label><input type="file" id="upload" name="upload" multiple accept="image/jpeg,text/plain"><br>
    <input type="submit">
</form>
</body>
</html>

创建路由和view函数

path('filefield/', views.filefield, name='filefield'),
def filefield(request):
    if request.method == 'GET':
        return render(request, 'filefield.html')
    elif request.method == 'POST':
        name = request.POST.get('name')
        upload_list = request.FILES.getlist('upload')
        for upload in upload_list:
            if upload.content_type.startswith('image/'):
                upload_image = Upload_Image()
                upload_image.u_name = name
                upload_image.u_path = upload
                upload_image.save()
            elif upload.content_type.startswith('text/'):
                upload_text = Upload_Text()
                upload_text.u_name = name
                upload_text.u_path = upload
                upload_text.save()
        return HttpResponse('Upload Successfully')

基本逻辑和上面利用trunk来存储类似,不过这里是通过实例化模型类,然后将上传的UploadedFile直接赋值给FileField或者ImageField,剩下的事情都由Django帮我们完成了。

Django的文件存储系统

上面在建立模型的时候说到upload_to参数是一个相对路径,那么是相对哪一个目录的路径呢?

答案是根据settings.py中配置的MEDIA_ROOT目录

In your settings file, you’ll need to define MEDIA_ROOT as the full path to a directory where you’d like Django to store uploaded files.

我这里配置的是MEDIA_ROOT = os.path.join(BASE_DIR, 'static/upload')

效果演示

效果演示如下:

  1. 访问http://127.0.0.1:8000/four/filefield/如下
    6-h5.png

  2. 同样选择刚才的三个文件并上传
    7-success.png

  3. 可以看到ImageField中存储了图片文件的存储路径
    8-imagefield.png

  4. 而FileField中存储了文本文件的存储路径
    9-filefield.png

这样就基本达到了存储上传文件的目的。

重复文件处理

那么如果用户将内容不同但名字相同的,能不能不要覆盖旧文件呢?

Django已经帮我们考虑了这个需求。

如果重复一遍上面的步骤结果如下
10-repeat.png

可以看到同样名字的文件被加入了后缀再存储,这样以后想获取同一名字的最新文件,用model.objects.last()即可。

按时间动态创建路径

Django帮我们考虑的还不仅这些,大家也许知道Linux有一个bug,就是当一个目录内的文件达到65535个的时候,该目录将无法打开。

所以在生产环境中对文件的分类存储就显得尤为重要,最简单有效的就是按照日期来创建目录归档。

上面FileField或者ImageFieldupload_to参数除了支持固定路径,还支持strftime()对象,也就是说可以用%Y%m%d来做为路径名

If you specify a string value, it may contain strftime() formatting, which will be replaced by the date/time of the file upload (so that uploaded files don’t fill up the given directory).

修改上面的模型定义如下

class Upload_Image(models.Model):
    u_name = models.CharField(max_length=16)
    u_path = models.ImageField(upload_to='images/%Y/%m/%d')


class Upload_Text(models.Model):
    u_name = models.CharField(max_length=16)
    u_path = models.FileField(upload_to='text/%Y/%m/%d')

重新迁移到数据库,再重复上面的上传操作,结果如下
11-strftime.png

可以看到自动按照当前日期创建了子目录,这样只要每天上传的文件不超过65535就不用担心了。

获取服务器文件

上传到服务器的文件也可以再次被获取,这里就通过获取最新上传的图片为例来演示一下。

创建路由和view函数如下

path('show_image/', views.show_image, name='show_image'),
def show_image(request):
    name = request.GET.get('name')
    upload_image = Upload_Image.objects.filter(u_name=name).last()
    context = {
        'name': name,
        'url': '/static/upload/'+upload_image.u_path.url
    }
    return render(request, 'show_image.html', context=context)

这里的upload_image.u_pathurlpath两个路径可以返回:

  • url是相对于MEDIA_ROOT的路径
  • path是相对于根目录/的路径

这里使用upload_image.u_path.url前面再加上MEDIA_ROOT定义中的后半部分,即可形成h5页面可使用的url了。

关于h5页面中的url问题,可以参考我的另一篇博客《HTML通过href实现页面跳转的几种不同路径写法》

其中的h5页面show_image.html如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Show Image</title>
</head>
<body>
<h2>{{ name }}</h2>
<img src="{{ url }}" alt="image">
</body>
</html>

此时访问http://127.0.0.1:8000/four/show_image/?name=xiaofu的效果如下
12-showimage.png

总结

文件的上传,存储和使用我们都了解的差不多了,那么可以说MTV模型剩余的一点边角料也都了解到了。下一篇开始我们就要进入Django的进阶学习了。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值