見出し画像

Djangoに画像を追加する

これまでDjangoにモデルを追加してきましたが文字情報ばかりでしたね。でも、Webアプリケーションにおいて、画像をつけたくなることはよくあります。

例えば、SNSのようなサイトであれば、ユーザーはアイコンに画像をつけたくなるでしょう。こうした場合に対応できるように、モデルに画像を追加する方法を学んでいきましょう。

pillowをインストールする

Django単体では画像ファイルを扱うことはできません。そこでpillowというパッケージをプロジェクトに追加します。このパッケージを追加することで、モデルの作成時にImageFieldを使うことができるようになります。

まずpythonの仮想環境に入っていることをしっかり確認してから、以下のコマンドでpillowをインストールします。

pip install pillow

それからDjangoプロジェクトの設定を行っていきます。

まずは、アップロードしたファイルの保存場所を設定する必要があります。Djangoではファイルをデータベースには保存せずに、そのファイルへのパスだけをデータベースに保存します。そのため、どこにアップロードされるのかをしっかりと把握しておく必要があります。

プロジェクトのsettings.pyに、MEDIA_URLとMEDIA_ROOTの変数を追加します。

# settings.py

import os

...省略...

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

​これでBASE_DIR(プロジェクトディレクトリ)内の、mediaというフォルダの中に、ファイルを保存することを指定できました。

次にモデルにImageFieldを追加する必要があります。今回は、Skillモデルにスキルのアイコンをつけたいと思います。skills/models.pyのSkillモデルに画像用のフィールドを追加します。

# skills/models.py

class Skill(models.Model):
   name = models.CharField(max_length=50)
   description = models.TextField(max_length=400)

   icon = models.ImageField(upload_to='skills_icon', blank=True, null=True)

モデルを変更したらmakemigrationとmigrateを実行しておきます。

python manage.py makemigrations
python manage.py migrate

さて、ここで2つの問題があります。

1つ目は、現在の状態でDjangoを実行すればわかりますが、画像を登録しても画像が表示されません。その原因は、画像ファイルをルーティングに追加していないためです。

その解決策として、プロジェクトのurls.pyに以下を追加します。

# urls.py
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
  ...省略...
]

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

settings.pyのDEBUGがTrueの場合、urlpatternsにMEDIA_URLとMEDIA_ROOTを追加しています。DEBUGがTrueの場合に限定しているのは、この設定は開発時はいいのですが、本番環境では使えないからです。

これで画像のアドレスを指定すれば、画像が表示されるようになりました。

2つ目の問題は、ファイルをそのままアップロードした場合、そのファイルの名前でアップロードされるということです。つまり、他の誰かが同じ名前の画像をアップロードしてしまう可能性があるということです。

そうなったら、画像が上書きされて予期しない画像が表示される可能性が出てきてしまいます。

そこで、ファイル名をアップロード時に変更するようにしましょう。ファイル名は独自のものにしたいので、pkでもいいのですが、今回はuuidにしておきます。

skills/models.pyのSkillクラスの前に、このような関数を用意します。

# skills/models.py

from django.db import models
from users.models import User
import os
from uuid import uuid4

def rename_image(path):
   def wrapper(instance, filename):
       ext = filename.split('.')[-1]
       filename = '{}.{}'.format(uuid4().hex, ext)
       return os.path.join(path, filename)
   return wrapper

class Skill(models.Model):
   name = models.CharField(max_length=50)
   description = models.TextField(max_length=400)

   icon = models.ImageField(upload_to=rename_image('skills_icon'),blank=True,null=True)

ここでrename_imageはdecoratorと呼ばれる関数になっています。関数の中に関数があるのはちょっと不思議な感じですよね。

実際に使うところをみると、イメージしやすいと思います。このdecoratorはこのように使われます。

media_path = rename_image('media')
media_path(instance, filename)

最初の1行で外側の関数の引数を与えて、2行目で内側の関数の引数を与えていますね。このようにすることで、内側の関数をちょっと拡張することができるのです。

このrename_image関数をImageFieldのupload_to引数に入れています。このupload_toですが、単純な文字列などのpathを渡せば、MEDIA_ROOTにfilenameをくっつけたpathに画像をアップロードしてくれます。

今回のようにupload_toにrename_image関数を渡せば、instanceとfilenameを利用してちょっと処理を変えることができます。instanceには現在作成中のモデル(今回の場合はSkillのデータベースに保存される前の状態Skillインスタンス)、filenameはそのままファイルの名前が入っています。

もし、instanceを使う場合は、このようにすることで、Skillのnameをファイル名にすることもできます。

def rename_image(path):
   def wrapper(instance, filename):
       ext = filename.split('.')[-1]
       filename = '{}.{}'.format(instance.name, ext)
       return os.path.join(path, filename)
   return wrapper

これで問題を2つとも解決することができました。

もし、複数のモデルで画像を使うのであれば、モデルごとにフォルダを分けておくとわかりやすいかもしれません。今回はupload_toでskills_iconというフォルダに保存するように設定しました。

テンプレートの対応

これで画像が登録できるようになりました。ただし、画像を登録することができるCreateとUpdateのテンプレートに少しだけ修正が必要です。

# skills/create.html

<!DOCTYPE html>
<html>
 <header>
   <title>Skill作成ページ</title>
 </header>
 <body>
   <div>Skillの情報を入力してください</div>
   <form method="post" enctype='multipart/form-data'>
     {% csrf_token %}
     {{ form.as_p }}
     <button type="submit">作成</button> 
   </form>
 <body>
</html>
# skills/update.html
<!DOCTYPE html>
<html>
 <header>
   <title>スキル更新ページ</title>
 </header>
 <body>
   <form method="post" enctype='multipart/form-data'>
     {% csrf_token %}
     {{ form.as_p }}
     <button type="submit">更新</button> 
   </form>
 <body>
</html>

両方とも、変更したのはこの行です。

<form method="post" enctype='multipart/form-data'>

ファイルや画像をアップロードするときはこのようにenctypeに'multipart/form-data'を設定しなければなりません。

最後に画像が登録されているか確認するために、DetailViewで画像を表示するように変更しましょう。

# skills/detail.html

<!DOCTYPE html>
<html>
 <header>
   <title>{{ object.name }}スキル</title>
 </header>
 <body>
   <p>{{ object.name }}</p>
   <p>{{ object.description }}</p>
   <img src="{{ object.icon.url }}" alt="skill_icon">
   
   <div><a href="{% url 'skills:update' object.pk %}">情報更新</a></div>    
   <div><a href="{% url 'skills:delete' object.pk %}">削除</a></div>
 <body>
</html>​

画像を表示するには<img>タグを使います。object.icon.urlとすることで画像のurlを取得できますので、それをsrc(画像のsource(ソース))に設定しています。

これでページにアクセスすれば画像が表示されるはずです!

実際に試してみましょう。

python manage.py runserver
↓
http://localhost:8000/skills/createにアクセス

Skillの作成画面にアイコン画像の設定ボタンが追加されました。

スクリーンショット 2021-04-05 17.34.29

適当に情報と画像を設定します。

スクリーンショット 2021-04-05 17.36.32

作成したデータのページにアクセスすると…

スクリーンショット 2021-04-05 21.55.44

画像が表示されました!

DjangoのAppパッケージを使う

ここまでで、画像を保存することができましたが、画像がすごい大きいですよね。ImageFieldを使う場合、画像を加工したい場合、その処理を自分で書かないといけません。

ですが、DjangoのAppパッケージには便利なパッケージがたくさんあります。それらを使うと、画像の大きさを変えたり、フィルターをかけたりするのが簡単にできます。自分の用途にあったパッケージをインストールして使ってみるのも良い考えです。

Djangoの画像関連のパッケージはこちらにたくさん掲載されています。

今回は、DJANGO-VERSATILEIMAGEFIELDを紹介します。

DJANGO-VERSATILEIMAGEFIELDとは?

Djangoの画像処理パッケージの1つで、機能がそれなりに多いのに、なかなか使いやすいパッケージです。詳細は以下から確認できます。

基本的な使い方を説明しますが、非常に簡単です。まずパッケージをインストールします。

pip install django-versatileimagefield

そして、プロジェクトのsettings.pyのINSTALLED_APPSにこのアプリケーションを追加します。

# settings.py

INSTALLED_APPS = [
   ...
   'versatileimagefield',
   ...
]

あとは実際にモデルで使用するだけです。

先ほどのSkillモデルでこのパッケージを使うには、ImageFieldをVersatileImageFieldに変更するだけで使えます。

# skills/models.py

from versatileimagefield.fields import VersatileImageField

class Skill(models.Model):
   name = models.CharField(max_length=50)
   description = models.TextField(max_length=400)

   icon = VersatileImageField(upload_to=rename_image('skills_icon'), blank=True, null=True)

以上です。ImageFieldからすぐに切り替えて使えるのが、楽で素晴らしいです。

注意:もし、makemigrationsで失敗する場合は、decoratorを普通の関数にしてみてください。以下のようになります。

def rename_image(instance, filename):
    ext = filename.split('.')[-1]
    filename = '{}.{}'.format(uuid4().hex, ext)
    return os.path.join('skills_icon', filename)

class Skill(models.Model):
   name = models.CharField(max_length=50)
   description = models.TextField(max_length=400)

   icon = VersatileImageField(upload_to=rename_image, blank=True, null=True)

画像のサイズ変更方法

最後に、画像のサイズ変更方法だけ紹介して終わりにしましょう。

簡単な方法としては、テンプレートで画像のサイズを指定するだけで、サイズ変更ができます。試してみましょう。

# skills/detail.html

<!DOCTYPE html>
<html>
 <header>
   <title>{{ object.name }}スキル</title>
 </header>
 <body>
   <p>{{ object.name }}</p>
   <p>{{ object.description }}</p>
   <img src="{{ object.icon.thumbnail.200x200 }}" alt="skill_icon">
   
   <div><a href="{% url 'skills:update' object.pk %}">情報更新</a></div>    
   <div><a href="{% url 'skills:delete' object.pk %}">削除</a></div>
 <body>
</html>

object.icon.urlをobject.icon.thumbnail.200x200に変更しただけです。このように指定するだけでサイズが200x200のちょうどいい大きさに変わりました。

スクリーンショット 2021-04-05 22.12.05

とりあえず、画像のサイズをちょっと調整したいなら、VersatileImageFieldを試してみるのもいいかもしれませんね!

サンプルサイト

repl.itにここまでに作成した内容を残しておきます。真ん中の再生ボタンを押すと、今回作成した部分を確認できます。また、左側にあるCodeタブを選択すると、ファイル構成やファイルの内容を確認できます。

練習問題

1. 画像を含めたモデルを作ってみましょう。

2. アップロードした画像のサイズを変更して、表示できるようにしてください。


ここまで読んでいただけたなら、”スキ”ボタンを押していただけると励みになります!(*´ー`*)ワクワク


この記事が気に入ったらサポートをしてみませんか?