Djangoでユーザーが画像をアップロードすることができる機能を作ってみましょう。また、Djangoで画像を扱う際の前準備や注意点についても解説します。
※この記事では学習に必要な機能だけのアプリケーションを新たに作成して学習します。Djangoプロジェクトのディレクトリ構成は弊サイトの Django入門編 に準じますのでご了承ください。
目次
完成イメージ
今回作成するのは次の2つのページと1つの機能のみです。
- ユーザーが画像をアップロードするためのページ
- ユーザーが画像をアップロードできる機能
- アップロードされた画像を表示するページ
アップロードされた画像の一覧ページや画像を表示するページへのリンクは省略します。それらの方法については弊サイトの Django入門編 をご参照ください。
ユーザーが画像をアップロードするためのページ
画像のアップロードページです。
見てのとおり画像ファイルを選択してアップロードする機能のみを作ります。
アップロードされた画像を表示するページ
アップロードされた画像を表示するページです。
こちらも画像を表示するだけのページです。
たったこれだけのページと機能ですが、Djangoでの画像のアップロードについて初めて学ぶには最適な教材です。
このアプリの作成を通してDjangoでの画像の扱いを理解したら、ぜひ他のページや機能と組み合わせてみてください。
画像アップロード機能
Pillow のインストール
まずは前準備としてPythonライブラリのPillowをインストールしておきます。
PillowはPythonで画像の開閉、処理、解析などを行うためのライブラリで、PIL(Python Imaging Library)の後継です。
PowerShellでDjangoのプロジェクトディレクトリに移動し仮想環境を有効化。次のコマンドを実行します。
python -m pip install Pillow
アプリケーションの作成と登録
画像アップロードアプリを作成しましょう。
Djangoのインストールとプロジェクトの作成は既に済んでいるものとします。
python manage.py startapp img_upload_app
作成したアプリをプロジェクトに登録しておきましょう。
settings.pyのINSTALLED_APPSに次のように記述します。
INSTALLED_APPS = [ 'img_upload_app', # 追加 'django.contrib.admin', . . . ]
画像のアップロード先フォルダの設定
画像のアップロード先フォルダを指定します。settings.pyに次のように MEDIA_ROOT を定義します。
記述する場所はどこでもいいですが、静的ファイルを扱う STATIC_URL や STATICFILES_DIRS の次あたりに記述しておくとわかりやすいです。
import os . . . MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
モデルの作成
では、画像アップロードアプリのモデルを作成しましょう。
Djangoでは画像ファイルはディレクトリに保存しますが、保存先のパス情報の管理はmodels.pyで行います。
画像のパス情報は ImageField に持たせておくことで
- 画像アップロード用のフォーム作成
- 画像ファイルの保存
- 保存先パスの管理
を簡単かつ効率的に行えるようになります。
from django.db import models class UploadImage(models.Model): image = models.ImageField(upload_to='img/') # 画像の保存先としてMEDIA_ROOTで指定したディレクトリの下のimgディレクトリを指定
ImageField を持つモデルのインスタンスに画像をセットした状態で save メソッドを実行した場合、データベースへのレコードの保存だけでなく、アップロードされた画像ファイルをsettings.py の MEDIA_ROOT で指定したフォルダに保存します。
さらにImageField の引数に上記のように upload_to を指定すると、画像を MEDIA_ROOT 以下のどのファイルに保存するか指定できます。
この場合、画像はMEDIA_ROOT で指定した media ディレクトリの下の img ディレクトリに保存されます。
フォームの作成
次に、画像アップロード用のフォームを作成します。
from django import forms from .models import UploadImage class UploadForm(forms.ModelForm): class Meta: model = UploadImage fields = ['image']
このように ModelForm を継承して、MetaクラスにImageField を持った Model を指定すれば簡単に画像アップロード用のフォームを作成できます。
あとはこのインスタンスをテンプレートに渡して render 関数でレンダリングするだけです。
ビューの作成
画像アップロードページ用のビューを作成します。img_upload_appアプリのviews.pyに次のように記述します。
from django.shortcuts import render from .forms import UploadForm from .models import UploadImage def index(request): params = { 'title': '画像のアップロード', 'upload_form': UploadForm(), 'id': None, } if (request.method == 'POST'): form = UploadForm(request.POST, request.FILES) if form.is_valid(): upload_image = form.save() params['id'] = upload_image.id return render(request, 'img_upload_app/index.html', params)
このindex関数では params でページのタイトルやUploadFormインスタンスをテンプレートに渡しています。
また、画像アップロード用のフォームから画像がアップロードされた際に、画像ファイルと保存先のパスをデータベースに保存しています。
ルーティング
画像アップロードページのルーティングを設定しましょう。
まずは config/urls.py の urlpatterns に次のように記述します。
urlpatterns = [ path('img_upload_app/', include('img_upload_app.urls')), # img_upload_app/以下のルーティングはimg_upload_app.urls.pyに任せる . . . ]
これで img_upload_app/ 以下のルーティングは img_upload_app アプリの urls.py に任せるよう指定しました。
次に、img_upload_app/urls.py を作成し、次のように記述します。
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ]
これでユーザーが img_upload_app/ 以下なにも続かないURLにアクセスした場合、img_upload_appアプリのviews.pyのindex関数が実行されるようになりました。
テンプレートの作成
画像アップロードページのテンプレートを作成しましょう。
img_upload_app/index.htmlを作成し、次のように記述します。
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>{{ title }}</title> </head> <body> <h1>{{ title }}</h1> <div> {% if id %} <h2>画像が登録されました</h2> <p>画像のIDは {{ id }} です</p> {% endif %} </div> <div> <h2>画像を登録する</h2> <form action="{% url 'index' %}" method="post" enctype="multipart/form-data"> {% csrf_token %} <p>{{ upload_form.as_p }}</p> <p><input type="submit" value="アップロード"></p> </form> </div> </body> </html>
17行目から21行目で画像をアップロードするためのフォームとボタンを用意しています。ビューからupload_formという名前でUploadFormクラスのインスタンスを渡してあるのでそれを表示します。
画像など、ファイルのアップロードを行う場合はformタグのenctype属性にmultipart/form-dataを指定する必要があります。
また、画像のアップロード後はこのページに戻ってくるよう記述しています。
10行目から13行目の表記は画像がアップロードされた後のこのページの表記です。id、つまりレコードのプライマリキーは画像アップロード後にこのページを訪れた場合のみ存在するので、if id で条件分岐しています。
動作確認
それでは動作確認してみましょう。
まずはPowerShellでプロジェクトディレクトリに入り仮想環境を有効化。次のコマンドを実行してデータベースのマイグレーションを行います。
python manage.py makemigrations python manage.py migrate
次のコマンドでDjangoの開発用ウェブサーバーを起動します。
python manage.py runserver
ウェブブラウザを開いて http://127.0.0.1:8000/img_upload_app/ にアクセスします
実際に使ってみましょう。
「ファイルを選択」ボタンをクリックして画像ファイルを選択、「アップロード」ボタンをクリックします。
同じURLに戻ってきますが、今度は「画像が登録されました」という表記と「画像のID」が表示されています。
※私がいじくっていたせいでIDが10になっていますが、idは1から順番に割り振られます。
実際に画像をアップロードできているか確認してみましょう。
/media/img/ フォルダの中を覗いてみます。
ちゃんと画像がアップロードされています。
ちなみに上記のようにまったく同じ名前のファイルをアップロードした場合、Djangoが適当な文字列を追加して保存してくれるため、一度保存したファイルが上書きされてしまう心配はありません。
アップロードされた画像の表示
アップロードされた画像を表示する方法についても学んでおきましょう。先ほどのアプリでアップロードした画像ファイルを表示するページを作成してみます。
アップロードファイルをレスポンスで返却
Djangoでは静的ファイルやアップロードされたメディアファイルを返すのはWebサーバーの役割としています。つまり静的ファイルへのリクエストがあった際にDjangoを介さず、ApacheやNginxなどのWebサーバーから直接レスポンスを返します。
開発時にはrunserverというDjangoのテスト用Webサーバーを使っていますが、初期設定ではメディアファイルを配信する設定がされていないため、settings.pyに自分で設定する必要があります。
MEDIA_URL = 'media/' # 追加
このように記述することでDjangoはMEDIA_URL、この場合 media/ にリクエストがあった場合、メディアファイルへのアクセスと判断してくれるようになります。
続いて、リクエストされたURLが MEDIA_URL から始まる場合に MEDIA_ROOT からファイルを取得するよう、MEDIA_URL と MEDIA_ROOT を関連付けます。
※MEDIA_ROOT は画像アップロード機能の項で設定した画像のアップロード先フォルダのパスです。
config/urls.py に次のように追記します。
from django.contrib import admin from django.urls import include, path from django.conf import settings # 追記 from django.conf.urls.static import static # 追記 urlpatterns = [ path('img_upload_app/', include('img_upload_app.urls')), . . . ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # 追記
これでリクエストされたURLが MEDIA_URL から始まる場合に、Djangoの開発用Webサーバーが MEDIA_ROOT からファイルを取得してレスポンスとして返却してくれるようになりました。
ビューの作成
それではアップロードされた画像を表示するページのビューを作成しましょう。
views.py に次のように記述します。
from django.shortcuts import render, get_object_or_404 # get_object_or_404を追加 from .forms import UploadForm from .models import UploadImage def index(request): # 略 def preview(request, image_id=0): upload_image = get_object_or_404(UploadImage, id=image_id) params = { 'title': '画像の表示', 'id': upload_image.id, 'url': upload_image.image.url } return render(request, 'img_upload_app/preview.html', params)
アップロードされた画像を表示するページのビューとしてpreview関数を作成しました。
最初に get_object_or_404関数で id が image_id と一致する UploadImage インスタンスを取得。
params にタイトル、画像のid、画像のURL(パス)をそれぞれ指定して、最後のrender関数でテンプレートに渡しています。
ルーティング
アップロードされた画像を表示するページのルーティングを設定しましょう。
img_upload_app/urls.py に次のように記述します。
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('preview/<int:image_id>/', views.preview, name='preview'), # 追記 ]
これで /upload_app/preview/ID/ にリクエストがあった場合に views.py の preview 関数が実行されるようになりました。
テンプレートの作成
最後に画像を表示するページのテンプレートを作成しましょう。
img_upload_app/preview.htmlを作成し、次のように記述します。
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>{{ title }}</title> </head> <body> <h1>{{ title }}</h1> <div> <h2>ID:{{ id }}の画像</h2> <img src="{{ url }}" width="400px"> </div> </body> </html>
ビューから受け取ったtitle、id、urlをHTMLに当てはめてそのまま表示しているだけです。
動作確認
それでは動作確認してみましょう。
まずはPowerShellでプロジェクトディレクトリに入り仮想環境を有効化。Djangoの開発用ウェブサーバーを起動して、ブラウザで http://127.0.0.1:8000/img_upload_app/preview/ID/ にアクセスします。
※最後のIDは1や2など画像を登録した時のIDにしてください。
無事、アップロードされた画像を表示することができました!
本番環境でのアップロードファイルの扱い方
最後に本番環境でのメディアファイルの配信について少し触れておきます。
ここではstatic関数を利用して MEDIA_URL 以下に配置されたメディアファイルを配信していましたが、このstatic関数はsettings.pyで開発モードをオフ(Debug = False)にすると無効化されます。
本番環境ではApacheやNginxなどのWebサーバーからこれらのファイルを配信する必要があります。
例えばNginxの場合、Nginxの設定ファイルに次のように記述してメディアファイルを直接配信します。
server { # 略 location /MEDIA_URL { alias MEDIA_ROOT; } # 略 }