Djangoで複数ファイル(画像)のプレビューとアップロード
Djangoで複数の画像をアップロードする際、少し手間取ったのでメモを残します。
アップロードの前にプレビューを挟み、画像を確認するコードもまとめます。
Settings.pyとurls.pyの設定
settings.pyに下記を追記します。
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
さらに、アプリケーションディレクトリのurls.pyに下記を追記します。
# To serve files uploaded by a user during development
from django.conf import settings
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
プロジェクトのルートの media ディレクトリにパスが通ります。
本番環境では別の方法を推奨されていますが、取り急ぎこれで進めます。
models.pyの設定
次のようにモデルを作成しました。
# models.py
def user_portfolio_directory_path(instance, filename):
return 'image-{0}/{1}'.format(instance.id, filename)
class Image(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
image = models.ImageField(upload_to=user_portfolio_directory_path, null=True, blank=True)
uploaded_at = models.DateTimeField(auto_now_add=True)
upload_to
で画像の保存先を指定しています。
また、ImageFieldを使うにはPillowというパッケージが必要なので、インストールします。
pip install Pillow
forms.pyの設定
forms.pyを次のように設定します。
# forms.py
class ImageForm(forms.ModelForm):
image = forms.ImageField(
widget=forms.ClearableFileInput(attrs={'multiple': True}),
)
class Meta:
model = Image
fields = ('image',)
input
タグには multiple
という属性を付与することができます
multiple
属性を付与することで、複数のファイルを選択できるようになります。
# 例
<input type="file" name="image" multiple="" accept="image/*">
上記forms.pyのコードでは、 attrs={'multiple': True}
の部分でmultiple属性を付与しています。
multiple属性は widget_tweaks
を使って、テンプレート側で付与することもできます。わかりやすいので、私はこちらの方法を好んで使っています。
# template
{%!r(MISSING)ender_field form.image.image multiple="" %!}(MISSING)
画像のプレビュー
選択した画像をプレビューし、確認できるようにします。今回はjQueryで作りました。
# jsファイル
$(document).on('click', '.upload-portfolio-image-btn', function() {
var inputToUploadPortfolioImage = $(this).next('.upload-portfolio-image')
var portfolioImageGroup = inputToUploadPortfolioImage.closest('.form-row').next().next('.portfolio-image-group')
inputToUploadPortfolioImage.click()
inputToUploadPortfolioImage.off('change').on('change', function(e) {
if (e.target.files && e.target.files[0]) {
var files = e.target.files
for (var i = 0; i < files.length; i++) {
var file = files[i]
var reader = new FileReader()
reader.onload = function (e) {
portfolioImageGroup.append(`
<div class="form-group col-md-3">
<div class="responsive-img-wrapper portfolio-image">
<div class="responsive-img" style="background-image: url(${e.target.result})">
</div>
</div>
</div>`)
}
reader.readAsDataURL(file)
}
}
})
})
↓Templateは次のような感じです。widget_tweaksを使っています。
# template
{%!l(MISSING)oad widget_tweaks %!}(MISSING)
<form class="post-form-to-edit-portfolio" method="post" enctype="multipart/form-data">
{%!c(MISSING)srf_token %!}(MISSING)
<div class="form-group">
<div class="form-row">
<div class="col-md-12">
<div class="upload-portfolio-image-btn">
クリックで画像をアップロード<br>
JPEG / PNG / GIF形式に対応しています
</div>
{%!r(MISSING)ender_field form.image.image class="custom-file-input upload-portfolio-image" multiple="" %!}(MISSING)
{%!f(MISSING)or error in form.image.image.errors %!}(MISSING)
<span class="text-warning">{{ error }}</span>
{%!e(MISSING)ndfor %!}(MISSING)
</div>
</div>
<hr>
<div class="form-row portfolio-image-group">
<!-- ここで画像をプレビュー -->
</div>
</div>
<div class="form-btn">
<button type="button" class="btn btn-outline-info btn-to-cancel-upload">キャンセル</button>
<button type="submit" class="btn btn-info btn-to-upload-images">更新</button>
</div>
</form>
formタグに method="post"
enctype="multipart/form-data"
属性をつけるの忘れずに。
cssでデザインし、下図のような感じになりました。
画像のアップロード
選択した画像をviews.pyに渡し、DBに保存します。
# views.py
...
def portfolio_edit_post(request):
if request.method == 'POST':
image_form = ImageForm(request.FILES)
if image_form.is_valid():
portfolio_images = request.FILES.getlist('image', False)
for image in portfolio_images:
image_instance = Image(
image=image,
)
image_instance.save()
print("success save images.")
...
getlist()メソッドで、複数の画像をリストとして取得できます。
画像ファイルはリストとして送信されるので、for文で一つずつ処理します。
画像データのバリデーション
バリデーションは大事です。
アップロードされたファイルがウイルスだったり、GByteレベルの重いファイルだと困っちゃいますからね。
バリデーションにあたり考えるべき項目は次の3つです。
- ファイル名と拡張子
- メタデータ(コンテンツタイプとサイズ)
- 実際のデータの内容(コンテンツそのもの)
1と2は偽装やなりすましが簡単なので、データの内容そのものが正しいかチェックする3番のバリデーションが最も重要。だそうです。
が、ちょっと大変なので今回は飛ばして、そのうち実装します!
まとめ
Djangoで画像のアップロードができるようになりました。やったね!
さらにAjaxを使っていい感じにデータをやりとりできるようにもなりましたので、その方法についても後日まとめます。