最近有个需求,用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("/")
那么,来捋一下这个逻辑。
login_required
决定了登陆之后才能访问这个视图函数- 第三行代码,检测
request.POST
是否有值(也就是判断是否是空提交)和body
是否有值 - 建立一个Message对象,但暂时不存进数据库
- 检验是否有上传文件或者文字内容,如果两者都没有,就不保存。
- 如果有上传文件,把
requst.POST
和request.FILES
传给我们自己定义的MultiImageForm
- 遍历所有的上传文件对象,并且对其每个都建立对应的Image对象(在定义
MultiImageForm
时指定过了),然后把这些对象的message都指向此次保存的message。 - 跳转至首页
这看起来很简单是吧。接下来的就是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来处理的。让我们分别来看看这些输入的作用
form-TOTAL_FORMS
它的值用来指定有几个需要上传的文件fomr-INITIAL_FORMS
它的值用来指定第一个上传文件的文件序号form-MIN_NUM_FORMS
它的值用来指定最少的上传文件数form-MAX_NUM_FORMS
它的值用来指定最大的上传文件数form-0-file
第一个上传文件的inputform-0-id
第一个上传文件的隐藏input
我们需要几个文件上传就用JS渲染出几个input-file
,并且注意它们的序号递增(例如form-1-file
),但记住每个form-NUM-file
都要有对应的form-NUM-id
,然后在form-TOTAL_FORMS
里指定总数。