Django
python3

Django - 画像ファイルのアップロード処理

はじめに

 Djangoで画像ファイルのアップロード処理を書くには settings.py, urls.py, models.py, views.py, forms.py, templateファイル の6つを編集する必要があります。
 一連の処理としては

  1. メディアの定義
  2. モデルの定義
  3. フォームの定義
  4. テンプレートファイルにHTMLを書く
  5. views.pyで画像を保存する

 という流れになります。

環境

 環境はDjango1.11を想定しています。
 ImageFieldの使用にPillowが必要なのでPillowもインストール済みの前提です。

$ pip install django==1.11 Pillow==5.0.0

 また、myprjというプロジェクトと、myappというアプリケーションを作成済みという前提です。

$ django-admin startproject myprj
$ python3 manage.py startapp myapp

 また、サーバーはDjangoの開発サーバーが前提です。本番環境へのデプロイではメディアのエイリアスをApache2側で定義する必要があります。

メディアの定義

 画像ファイルのパスのルート用にMEDIA_ROOT, 画像ファイルのURLのルート用にMEDIA_URLを定義します。

settings.py
STATIC_URL = '/static/'                                                           
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

 それからユーザーが画像にアクセス出来るようにプロジェクトのURLパターンにメディアの設定を加えます。

myprj/urls.py
from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

# 以下を定義
from django.conf import settings
from django.conf.urls.static import static

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

モデルの定義

 次にモデルを定義します。今回はPhotoというモデルを定義します。定義後はマイグレーションを忘れずに。
 Photoimageフィールドですが、upload_toで指定するパスは内部的にMEDIA_ROOTと結合されます。また、途中のディレクトリは画像保存時に自動で作成されます。今回はmyappというディレクトリを指定しています。

myapp/models.py
from django.db import models

class Photo(models.Model):

    image = models.ImageField(upload_to='myapp')

フォームの定義

 フォームを定義します。アプリケーションのディレクトリにforms.pyを作成し定義します。

myapp/forms.py
from django import forms                                                           

class PhotoForm(forms.Form):

    image = forms.ImageField()

テンプレートファイルにHTMLを書く

 今回はmyappindexビューに処理を書きます。また、テンプレートファイルはindex.htmlを利用します。テンプレートファイル内でフォームを利用するためコンテキストにフォームを渡しています。

myapp/views.py
from django.shortcuts import render                                                
from .forms import PhotoForm

def index(req):
    if req.method == 'GET':
        return render(req, 'myapp/index.html', {
            'form': PhotoForm(),
        })

 テンプレートファイルです。画像の送信にはenctypeの指定が必要です。これが無いとform.is_validで不正になります。

myapp/templates/myapp/index.html
<form action="{% url 'index' %}" method="POST" enctype="multipart/form-data">      
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="投稿" />
</form>

 actionにはindexビューへのネームを指定しています。

myapp/urls.py
from django.conf.urls import url                                                   
from . import views

urlpatterns = [
    url(r'^/?$', views.index, name='index'),
]

views.pyで画像を保存する

 再びindexビューにPOST時の処理を書き加えます。

myapp/views.py
from django.shortcuts import render, redirect
from .forms import PhotoForm
from .models import Photo

def index(req):
    if req.method == 'GET':
        return render(req, 'myapp/index.html', {
            'form': PhotoForm(),
        })

    elif req.method == 'POST':                                                     
        form = PhotoForm(req.POST, req.FILES)
        if not form.is_valid():
            raise ValueError('invalid form')

        photo = Photo()
        photo.image = form.cleaned_data['image']
        photo.save()

        return redirect('/')

 これで完成です。フォームから画像をアップロードすると/media/ディレクトリ以下に画像が保存されます。本当に保存されているか確認してみましょう。indexビューとテンプレートファイルに処理を加えます。

myapp/views.py
...
    if req.method == 'GET':
        return render(req, 'myapp/index.html', {
            'form': PhotoForm(),
            'photos': Photo.objects.all(), # ここを追加
        })
...

myapp/templates/myapp/index.html
...
{% for p in photos %}
    <div>
        <img src="{{ p.image.url }}" />                                           
    </div>
{% endfor %}

 画像が表示されない場合は、まず/media/ディレクトリ以下に画像が保存されているか確認してみましょう。保存されていればメディアの設定がおかしい可能性が高いです。画像のURLをコピペしてよく観察してみましょう。

参照