DjangoでImageFieldを用いた画像ファイルをアップロードする方法をご紹介します。
目次
条件
- Django 2.1.4
- Python 3.7.0
事前準備
Pillow
ImageFieldでは「Pillow」が必須となります。
以下のコマンド、またはPyCharmのファイル > 設定 > プロジェクト > プロジェクト・インタープリター > 使用可能なパッケージからPillowを追加します。
pip install Pillow
settings.py
settings.pyに以下の記述を追加します。
# settings.py抜粋 MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/'
ちなみに、「MEDIA_URLで指定したパス」と下記のmodels.pyで定義する「ImageFieldのupload_toで指定したパス」を連結したパスが実際に画像ファイルがアップロードされるパスとなります。
ImageField(upload_to=”image/”)
⇒今回の場合、「プロジェクト\media\image」の下に画像ファイルがアップロードされます。
実装
ここでは、ユーザ毎に作品ポートフォリオを管理するサービスを例にとります。
モデルの関係は以下の通りです。
- ユーザ:作品 = 1:N
- 作品:画像 = 1:N
なお、本記事においてはログイン認証に関する説明は割愛します。
models.py
以下のようなモデルを作成します。
# models.py from django.db import models from django.urls import reverse class Work(models.Model): """作品モデル""" class Meta: db_table = 'work' name = models.CharField(verbose_name='作品名', max_length=255) memo = models.CharField(verbose_name='メモ', max_length=255, default='', blank=True) author = models.ForeignKey( 'auth.User', on_delete=models.CASCADE, ) created_at = models.DateTimeField(verbose_name='登録日時', auto_now_add=True) updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True) def __str__(self): return self.name @staticmethod def get_absolute_url(self): return reverse('pictures:index') class Image(models.Model): """イメージモデル""" class Meta: db_table = 'image' work = models.ForeignKey(Work, verbose_name='作品', on_delete=models.PROTECT) image = models.ImageField(upload_to="image/", verbose_name='イメージ') created_at = models.DateTimeField(verbose_name='登録日時', auto_now_add=True) updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True) def __str__(self): return self.work.name + ":" + str(self.data_datetime)
urls.py
作品の一覧画面、詳細画面、画像ファイルアップロード画面のパスを定義します。
# urls.py from django.urls import path from . import views app_name = 'pictures' urlpatterns = [ # トップ画面 path('', views.IndexView.as_view(), name='index'), # 詳細画面 path('pictures/<int:pk>/', views.DetailView.as_view(), name='detail'), # ファイルアップロード用 path('pictures/<int:pk>/upload/', views.Upload.as_view(), name='upload'), path('pictures/<int:pk>/upload_complete/', views.UploadComplete.as_view(), name='upload_complete'), ]
forms.py
画像ファイルアップロード用のformを作成します。
ここでは拡張子をjpgに限定するバリデーションチェックを追加しています。
from django import forms import os VALID_EXTENSIONS = ['.jpg'] class UploadFileForm(forms.Form): image = forms.ImageField() def clean_image(self): image = self.cleaned_data['image'] extension = os.path.splitext(image.name)[1] # 拡張子を取得 if not extension.lower() in VALID_EXTENSIONS: raise forms.ValidationError('jpgファイルを選択してください!') return image # viewsでcleaned_dataを参照するためreturnする
views.py
views.pyを以下のように記述します。
アップロードのバリデーションが通った後の処理「def form_valid(self, form):」でDBにレコードを保存するところがポイントです。
画像ファイル自体は「プロジェクト\media\image」の下にアップロードされます。
# views.py from django.shortcuts import redirect from django.views import generic from .models import Work, Image from django.contrib.auth.mixins import LoginRequiredMixin import logging from .forms import UploadFileForm from django.views.generic.edit import FormView logger = logging.getLogger('development') # 一覧画面 class IndexView(LoginRequiredMixin, generic.ListView): model = Work paginate_by = 5 ordering = ['-updated_at'] template_name = 'pictures/index.html' # 詳細画面 class DetailView(generic.DetailView): model = Work template_name = 'pictures/detail.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) self.request.session.modified = True # セッション更新 context['work_pk'] = self.kwargs['pk'] return context # ファイルアップロード class Upload(FormView): template_name = 'pictures/upload.html' form_class = UploadFileForm def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) form = self.get_form() context = { 'work_pk': self.kwargs['pk'], 'form': form, } return context def form_valid(self, form): image = Image() # DBへの保存 image.work_id = self.kwargs['pk'] # 作品ID image.image = form.cleaned_data['image'] # アップロードしたイメージパス image.save() return redirect('pictures:upload_complete', self.kwargs['pk']) # アップロード完了画面にリダイレクト # ファイルアップロード完了 class UploadComplete(FormView): template_name = 'pictures/upload_complete.html' form_class = UploadFileForm def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) self.request.session.modified = True # セッション更新 context['work_pk'] = self.kwargs['pk'] return context
テンプレート
アップロード画面とアップロード完了画面のテンプレートファイルを作成します。
(プロジェクト\pictures\templates\picturesの下)
upload.html
<!-- upload.html --> {% extends 'base.html' %} {% block content %} <h1>アップロード サンプル</h1> <form method="POST" enctype="multipart/form-data"> {% csrf_token %} {% if form.errors %} {% for field in form %} {% for error in field.errors %} <div class="alert alert-danger"> <strong>{{ error|escape }}</strong> </div> {% endfor %} {% endfor %} {% for error in form.non_field_errors %} <div class="alert alert-danger"> <strong>{{ error|escape }}</strong> </div> {% endfor %} {% endif %} {% for field in form %} <div class="form-group form-inline"> <div class="col-md-8"> {{ field }} </div> </div> {% endfor %} <div class="form-group"> <button type="submit">アップロード</button> </div> </form> <section> <p><a href="javascript:history.back()">< Back</a></p> </section> {% endblock %}
upload_complete.html
{% extends 'base.html' %} {% block content %} <h1>アップロード完了</h1> <button type="submit" onClick="javascript:history.back();">戻る</button> {% endblock %}
動作確認
事前準備
「createsuperuser」でsuperuserを作成します。
(ここではadminという名前で作成)
サーバーを起動して管理者サイトでworkにレコードを追加します。
レコード状態
workに1レコード存在し、imageにはレコードが存在しない状態です。
アップロード実行
ログインすると一覧画面に遷移し、先ほど追加したworkの情報が表示されます。
work情報のリンクをクリックして詳細画面に遷移します。
メニューに「ファイルアップロード」のリンクが表示されるのでクリックします。
アップロード画面に遷移します。
「ファイルを選択」で任意のJPG画像ファイルを選択し、アップロードボタンを押します。
アップロード完了画面に遷移します。
media\imageというディレクトリが作成され、その下にtest.jpgがアップロードされます。
imageテーブルにレコードが追加されます。
再度同じ画像ファイルをアップロードすると、適当な文字列が付加されてアップロードされます。
(同じ名称の画像ファイルでも以前にアップロードした画像ファイルが上書きされることはありません。)
以上で画像ファイルアップロード方法の紹介は終わりです。
サンプルソース
GitHubに当該記事のサンプルソースを公開しています。
https://github.com/kzmrt/works
参考
Qiita:Django – 画像ファイルのアップロード処理
https://qiita.com/narupo/items/e3dbdd5d030952d10661
Django公式:特定のフィールド属性をクリーニングする
https://docs.djangoproject.com/ja/2.1/ref/forms/validation/#cleaning-a-specific-field-attribute