AberSheeran
Aber Sheeran

Django多图片上传

起笔自
所属文集: 程序杂记
共计 3597 个字符
落笔于

最近有个需求,用Django的Form实现上传任意张图片。然而四下查找,没有人写过Django的多文件上传,于是花了一点时间去看Django文档,才优雅的实现出来。

先看一下我们的models结构

from django.db import models
from django.contrib.auth import get_user_model


class Message(models.Model):
    author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
    body = models.TextField()
    create_time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.body


class Image(models.Model):
    file = models.ImageField(upload_to='upload/%Y/%m/%d/')
    message = models.ForeignKey(Message, on_delete=models.CASCADE)

每个message都可能包含多个图片或者不包含图片。

根据Django的官方文档,我们建立如下的formset

from django.forms import modelformset_factory, BaseModelFormSet

from .models import Image


class BaseMultiImageFormSet(BaseModelFormSet):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.queryset = Image.objects.none()

MultiImageForm = modelformset_factory(Image, fields=('file',), formset=BaseMultiImageFormSet)

如果像下面这样直接建立FormSet,虽然也能使用,但如果把这个formset拿去作为模板渲染,就会暴露出所有已经存在数据库里的图片数据,有安全隐患。所以我个人建议像上面那样建立formset

MultiImageForm = modelformset_factory(Image, fields=('file',))

接下来,在views.py里,我们将使用这个formset

@login_required
def message(request):
    if request.POST and request.POST.get("body") is not None:
        mes = Message(
            author=request.user,
            body=request.POST["body"]
        )
        if request.FILES or request.POST["body"]:
            mes.save()
        if request.FILES:
            files = MultiImageForm(request.POST, request.FILES)
            for file in files:
                image = file.save(commit=False)
                image.message = mes
                image.save()
        return redirect("/")

那么,来捋一下这个逻辑。

  1. login_required决定了登陆之后才能访问这个视图函数
  2. 第三行代码,检测request.POST是否有值(也就是判断是否是空提交)和body是否有值
  3. 建立一个Message对象,但暂时不存进数据库
  4. 检验是否有上传文件或者文字内容,如果两者都没有,就不保存。
  5. 如果有上传文件,把requst.POSTrequest.FILES传给我们自己定义的MultiImageForm
  6. 遍历所有的上传文件对象,并且对其每个都建立对应的Image对象(在定义MultiImageForm时指定过了),然后把这些对象的message都指向此次保存的message。
  7. 跳转至首页

这看起来很简单是吧。接下来的就是html代码了

<form action="/message" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <textarea rows="4" name="body"></textarea>
    <input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS">
    <input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS">
    <input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS">
    <input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS">
    <div class="input-file">
        <input type="file" name="form-0-file" accept="image/*" id="id_form-0-file">
        <input type="hidden" name="form-0-id" id="id_form-0-id">
    </div>
    <button type="submit">提交</button>
</form>

除了body是我们直接处理的,其他的都是Django来处理的。让我们分别来看看这些输入的作用

  1. form-TOTAL_FORMS 它的值用来指定有几个需要上传的文件
  2. fomr-INITIAL_FORMS 它的值用来指定第一个上传文件的文件序号
  3. form-MIN_NUM_FORMS 它的值用来指定最少的上传文件数
  4. form-MAX_NUM_FORMS 它的值用来指定最大的上传文件数
  5. form-0-file 第一个上传文件的input
  6. form-0-id 第一个上传文件的隐藏input

我们需要几个文件上传就用JS渲染出几个input-file,并且注意它们的序号递增(例如form-1-file),但记住每个form-NUM-file都要有对应的form-NUM-id,然后在form-TOTAL_FORMS里指定总数。

如果你觉得本文值得,不妨赏杯茶
Offic2016-只安装Word三件套
Windows里安装MysqlClient