ImageFieldを使ってみよう

このレッスンでは、「Photo」という名前のモデルを作ります。今回作成するサービスでは、ユーザーがタイトルやコメントと共に写真を投稿できるようにします。この投稿1つ1つを表すモデルがPhotoモデルとなります。

Photoモデルによってできるテーブルのイメージ図です。

新しく、ImageFieldとForeignKeyというものが出てきました。このレッスンでImageField、次のレッスンでForeignKeyについて説明します。

Photoモデルは、models.pyに定義していきます。userとcategoryフィールドはForeignKeyを使うので、このレッスンでは省きます。

~/PhotoService/app/models.py

from django.db import models

class Photo(models.Model):
    title = models.CharField(max_length=150)
    comment = models.TextField(blank=True)
    image = models.ImageField(upload_to = 'photos')
    created_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

models.ImageFieldは、画像ファイルが対応するフィールドであることを表しています。

この段階でmodels.pyを保存するとターミナルに以下のようなエラーがでると思います。

ターミナル

ERRORS:
app.Photo.image: (fields.E210) Cannot use ImageField because Pillow is not installed.
        HINT: Get Pillow at https://pypi.org/project/Pillow/ or run command "pip install Pillow".

エラーメッセージに書いてある通り、ImageFieldを使う場合は、Pillowというパッケージが必要となりますので、pipコマンドでインストールしておきましょう。

~/PhotoService

$ pip install Pillow

画像の保存先を設定しよう

ImageFieldからは画像をアップロードすることができますが、アップロードする画像の保存先を設定しておく必要があります。保存先は、settings.pyの中にMEDIA_ROOTというものを追記して定義します。

~/PhotoService/PhotoService/settings.py

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

MEDIA_ROOTは画像の保存先を表すものです。MEDIA_URLについては後述します。

この記述により、アップロードされた画像はBASE_DIR(ルートディレクトリ)直下のmediaというディレクトリに保存されることになります。(mediaディレクトリは、1つ目の画像がアップロードされた時点で自動で生成されます。もしくは、最初から自分で作成しておいても問題ありません。)

Photoモデルでは、image = models.ImageField(upload_to = 'photos')のようにアップロード先をphotosに指定していますので、Photoモデルのimageフィールドからアップロードされた画像はPhotoService/media/photosの中に保存されることになります。

ここまでできたら、マイグレートしてPhotoモデルをDBに反映して、Adminページから画像をアップロードしてみましょう。

~/PhotoService

$ python manage.py makemigrations
$ python manage.py migrate

~/PhotoService/app/admin.py

from django.contrib import admin
from .models import Photo

class PhotoAdmin(admin.ModelAdmin):
    list_display = ('id', 'title')
    list_display_links = ('id', 'title')

admin.site.register(Photo, PhotoAdmin)

無事Photoモデルからインスタンスを作成することができたら、/media/photosというディレクトリが新しくできていて、その中に画像ファイルが保存されてあることを確認してみましょう。

上のように、画像が保存されていれば無事アップロードはできています!このレッスンで紹介したように、画像のアップロード先となるディレクトリを指定して、アップロードされた画像はそのディレクトリに保存されるような設定をします。しかし、これにはちょっと注意が必要です。本番環境においては、画像保管用のサーバーを別途用意することが一般的です。つまり、開発環境下ではmediaディレクトリに、本番環境下では本番用画像サーバーにアップロードするように、環境によってアップロード先を切り替える設定が必要です。この切り替え設定については、別のブログ記事で紹介したいと思います。すぐに知りたい方は、すでに他の方がブログなどで書かれていますので調べてみてください。

MEDIA_URLの役割

画像のアップロードができたので次は、ImageFieldからアップロードされた画像にアクセスする方法を説明します。以下のようにフィールド名(今回の場合image)のあとに、.url.pathをつけることでアクセスすることができるので、実際に試してみましょう。

~/PhotoService

$ python manage.py shell
>>> from app.models import Photo
# 1つ目のPhotoインスタンスを取得
>>> photo = Photo.objects.all()[0]
>>> photo.image
<ImageFieldFile: photos/dog.jpeg>
>>> photo.image.url
'/media/photos/dog.jpeg'
>>> photo.image.path
'~/PhotoService/media/photos/dog.jpeg'

photo.image.urlのように、.urlでその画像のアドレスが取得できるのですが、その際アドレスは、MEDIA_URL/photos/dog.imgのように、アドレスの頭にはMEDIA_URLに指定した文字列がつきます。(今回は、MEDIA_URL= '/media/'と設定しているので、画像のアドレスは'/media/photos/dog.jpeg'となります。)

このように、MEDIA_URLはユーザーが生成したコンテンツ(アップロードされた画像、ファイルなど)のURLを表すのに使用されます。

MEDIA_URLは、ユーザーが生成したコンテンツのURLを表すのに対して、STATIC_URLは、CSSファイルやトップページに固定で使われる画像など、サイト開発者が最初から用意している静的コンテンツを保管するディレクトリを表します。

保存されている画像を表示する

ここまでで画像の保存と、画像へのアクセスを学びました。次は保存されている画像をトップページに表示させてみましょう。やり方としては、「views.pyでPhotoインスタンスを全て取得してTEMPLATE側に渡す」→「templateでfor文を回して各Photoインスタンスのimageフィールドにアクセスする」という流れです。

~/PhotoService/app/views.py

from django.shortcuts import get_object_or_404, redirect, render
from django.contrib.auth.models import User

from .models import Photo   # 追加

# 更新
def index(request):
    photos = Photo.objects.all().order_by('-created_at')
    return render(request, 'app/index.html', {'photos': photos})

~/PhotoService/app/templates/app/index.html

{% extends 'app/base.html' %}

{% block content %}

<h2>トップページ</h2>

{% for photo in photos %}
    <img src="{{ photo.image.url }}" class="photo-img">
{% endfor %}

{% endblock %}

ここまでできたらトップページ(http://127.0.0.1:8000/)を確認してみてください。下図のようになりましたか?画像の枠は表示されていますが、実際の画像はうまく表示されていませんね。

これは、画像が保管されているディレクトリ、つまり(mediaディレクトリ)が一般に公開されていないことが原因です。管理者であればAdminページから画像を見ることができますが、一般の人は画像のURLにアクセスしても見ることができません。

ディレクトリが公開されていないため、im gタグのsrcで指定したアドレスにアクセスしても画像が取得できないのです。試しに、画像のアドレスをコピー(画像の上で右クリック)して、新しいタブでそのアドレスにアクセスしてみましょう。

下のように404ページが表示されます。ディレクトリにアクセスできず画像が取得できていないことがわかりますね。

mediaディレクトリを公開するには、urls.pyに以下を追加してください。これにより、mediaディレクトリが公開されてアドレスにアクセスできるようになります。

~/PhotoService/PhotoService/urls.py

from django.contrib import admin
from django.urls import path, include
from django.conf import settings             # 追加
from django.conf.urls.static import static  # 追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('app.urls')),
]

# MEDIA_ROOTを公開する(アクセス可能にする)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # 追加

設定が完了したら、もう一度画像にアクセスしてみましょう。うまく表示されれば成功です!トップページにも画像が表示されます。表示されない場合は、キャッシュが影響している可能性もありますのでスーパーリロードを試してみてください。

django-cleanupで画像ファイルを削除する

画像のアップロード、表示ができました。最後に、投稿が削除された場合の処理について説明します。

今の実装のままだと、Photoインスタンスが削除されても、mediaディレクトリ内にアップロードされた画像は削除されずに残ったままとなります。

つまり、画像ファイルを手動で削除していかない限りはずっと画像がディレクトリ内に増え続けることになります。

仮にユーザーが投稿を削除した場合、それに紐づいた画像にアクセスされることもなくなるので、画像を保存しておく必要はありません。そこで、投稿が削除されるのと同時に、それと紐づいた画像ファイルも削除されるような設定をしてみましょう。

この設定には、django-cleanupというパッケージを使用します。使い方は簡単でpipコマンドでインストールして、INSTALLED_APPSに追加するだけです。

~/PhotoService

$ pip install django-cleanup

~/PhotoService/PhotoService/settings.py

INSTALLED_APPS = (
    ...
    'django_cleanup',
)

これで、投稿と同時に画像も削除されるようになりました。

次のレッスンの準備

このレッスンで、全てのPhotoインスタンスを削除してしまった場合は、次のレッスンのためにインスタンスを作り直しておいてください。次のレッスンでは、少なくとも1つのPhotoインスタンスがある状態で進めてください。

< PREV NEXT >
SHARE ! Tweet