本記事について
Djangoのライブラリでは,画像を複数同時にアップロードし,データベースに記録するという機能がない.ネットで調べても方法が見あたらなかったため,JavaScriptと組み合わせることで実装した.そのノウハウを備忘録及び,同じ問題に直面した人に貢献できるようにと記事を投稿する.
#もくじ
- 実装方法の説明
- Djangoの初期設定
- views.pyの記述
- テンプレートの記述
- 注意点
- まとめ
- 参考文献
##実装方法の説明
まず前提知識として,Djagoのモデルは,ImageFieldにおいて1フィールドにつき1つの画像しか保存することができない.したがって複数の画像を同時アップロードしたい場合は,必要な数だけフィールドを作成する必要がある.
以下に具体的な実装方法の手順を示す.なお,各詳細は後に説明する.
- HTMLのmultipleのinputタグを作成
- モデルフォームでアップロード枚数の上限とフォームセットを生成
- 送信と同時に,JavaScriptのDataTransferで選択された画像の数だけオブジェクトを生成し,別々のinputタグに格納する
- サーバーサイドのviews.pyで,formsetをデータベースに保存する
Djangoの初期設定
ここでは,実際に実装する前に,一例として開発するtest用のDjangoの各ファイルの初期設定を行う.
以下に,urls.py, models.py, forms.pyについてのコードを記す.
なお,urls.pyのuploadfuncは後にviews.pyに記述する関数である.
from django.urls import path, include
from .views import uploadfunc
urlpatterns = [
path('upload/', uploadfunc, name='upload'),
]
from django.db import models
class TestModel(models.Model):
files = models.ImageField('画像ファイル', null=True, blank=True, default='')
from django import forms
from .models import TestModel
class SingleUploadModelForm(forms.ModelForm):
class Meta:
model = TestModel
fields = '__all__'
views.pyの記述
EXTRAは,アップロードする画像の上限枚数を意味している.
formsetはDjangoのformsのライブラリ(フォームモデル)を使用する.
from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from .models import TestModel
from django import forms
from .forms import SingleUploadModelForm
def uploadfunc(request):
EXTRA = 5
UploadModelFormSet = forms.modelformset_factory(TestModel, form=SingleUploadModelForm,extra=EXTRA)
formset = UploadModelFormSet(request.POST or None, files=request.FILES or None, queryset=TestModel.objects.none())
if request.method == 'POST': #画像が選択され,送信されたとき
formset.save() #データベースに保存
context = {
'form': formset,
'number_list': list(range(EXTRA)),
'total_number': EXTRA,
}
return render(request, 'upload.html', context)
テンプレートの記述
views.pyのcontextで,'form'を返している.これは,テンプレートで{{ form }}と,フォームオブジェクトを変数表示させるだけで,入力データがセットされた入力フィールドを全て出力することができる.セットされたinputタグには,それぞれデフォルトでidがつけられており,画像ファイルでは,id_form-num-filesというidがつけられている.なお,今回はEXTRAは5と設定したため,5つのinputタグがセットされ,idはnumの部分に0~4までの数字が入る.
今回,複数画像を選択するため,{{ form }}とは別に,multipleを有効としたinputタグを準備する.JavaScriptのDataTransfer()機能を使うことで,選択された画像を{{ form }}のinputタグ内にそれぞれ格納する.そうすることで,送信を完了させると,選択された画像がviews.pyへ返され,複数同時選択とアップロードが実現する.
以上のことを以下の画像で説明する.これは,EXTRA=5,そして画像を3枚選択したときの例である.
今回{{ form }}の部分は非表示にしておく.送信ボタンが押されたとき,JavaScriptの関数(multipleFunction)を呼び出し,完了後views.pyへ返す.
以下にテンプレートファイルのコードと結果を示す.
<form action="" method="POST" enctype="multipart/form-data" style="margin: 10px;">
<div hidden>{{ form }}</div>
{% csrf_token %}
<p><同時に複数の写真を選択できます></p>
<input type="file" multiple accept="image/*" id="multiple-files">
<br>
<button id="sending" type="submit" class="sending" onclick="multipleFunction()">送信</button>
</form>
<script type='text/javascript'>
function multipleFunction() {
for (let i = 0; i < document.getElementById('multiple-files').files.length; i++) {
const dt = new DataTransfer();
dt.items.add(document.getElementById('multiple-files').files[i]);
document.getElementById("id_form-%number-files".replace("%number", i)).files = dt.files;
}
}
</script>
注意点
以上により画像複数同時アップロード機能の実装が完了した.
しかし,ここで注意点が2つがある.
1つ目は,JavaScriptのDataTrandsferを対応していないブラウザが多数あるという点である.当サイトによると,特にiOSのブラウザでは現在非対応なため,iPhoneやmacでは使用できないず,要注意である.
2つ目は,今回画像のみのアップロードをしたが,その他にメモや日付などといった他のデータを同時に保存したい場合は,上記のやり方では対応できないという点である.
例えばEXTRA=5に対して3枚の画像のみをアップロードする場合,差の2つは保存されないためうまくいく.しかし,他のフィールドを同時に保存しようとした場合,5つ全て保存してしまうため,画像が2つ選択されていないというエラーが生じてしまう.
今回その突破口については別の機会に記述するが,ヒントとしては,上記の例で言うと5つアップロードした後に,余計なを2つ削除するという記述をviews.pyに書く,ということである.
まとめ
画像同時アップロードの実装方法を記述した.今回は,JavaScriptのDataTransfer()機能を用いて実装した.少々説明がわくりにくかったかもしれないが,わからなかった場合は各ファイルのコードを参考にして頂きたい.
参考文献
- 横瀬明仁, 現場で使えるDjangoの教科書<<基礎編>>,2019