Djangoで画像を扱うときにやること。ファイル名やリサイズ、更新時の削除などについて。

Django
Django
0

Djangoで画像を扱う場合、画像の保存周りに少々癖があるため初めてやる場合には戸惑いやすい。本記事ではDjangoで画像をリサイズするときや画像を更新するときに元の画像を削除する方法についてメモする。

スポンサーリンク

環境

  • Django==3.1.3
  • Pillow==8.0.1
  • django-cleanup==5.1.0
  • sorl-thumbnail==12.7.0

Djangoで画像を扱う場合はPillowをインストールする必要がある。
下2つは今回の更新時の削除と、リサイズで利用する。

スポンサーリンク

画像のファイル名について

Djangoではファイルはアップロードされたファイル名のまま保存されるのがデフォルトの設定になっている。
ファイル名が簡単に推定できる状態だったり、もともと存在するファイルを上書きできる状態だとセキュリティ上まずいし、ファイル管理を楽にするためにもファイル名を変更したほうが良い。

公式にも書かれているようにupload_toに関数を呼び出すことができる。
uuidを使ってファイル名を保存する場合は次のようにする。

import uuid

def image_directory_path(instance, filename):
    return 'images/{}.{}'.format(str(uuid.uuid4()), filename.split('.')[-1])
   
class MyModel(models.Model):
    image = models.ImageField(upload_to=image_directory_path)
  • uuid4()はユニークなIDをランダムに生成する関数。
  • 例えばファイル名にユーザIDなどを使用したい場合は、instance.user.idを使う。
  • image_directory_path関数(関数名は自由だが、引数は必ずinstancefilenameこちらを参照。)の中身を自由に変えてファイル名を変更することができる。
  • filenameはもとのファイル名が入るので、拡張子だけ取り出して利用している。

更新時に古いファイルを削除する方法

画像を新しいものに置き換えたときやimageFieldを持つオブジェクトが削除されたとき、デフォルトでは古いファイルは残る設定になっている。
このままだとストレージを圧迫してしまうので、古いファイルは消したい場合があるはずである。

自分自身で関数を作ることで消すこともできるが、django-cleanupというモジュールを使ってしまうのが最も簡単である。公式はこちら
使い方はとても簡単で、

pip install django-cleanup

でインストールしたら、settings.pyINSTALLED_APPSに、

INSTALLED_APPS = (
    ...,
    'django_cleanup.apps.CleanupConfig', # 追加
)

と追加するだけで機能する。
古いファイルを保存してほしい場合は、models.pyの対象のモデルににデコレータを追加するだけで良い。

from django_cleanup import cleanup

@cleanup.ignore
class MyModel(models.Model):
    image = models.FileField()

画像のリサイズの仕方

ユーザがアップロードした画像をそのまま使うのではなくWebサイトで表示する大きさによってリサイズすることでパフォーマンスが向上する。
sorl-thumbnailというモジュールを使うと簡単にDjangoで画像をリサイズすることができる。公式はこちら

「django 画像 リサイズ」と検索するとdjango-imagekitを利用する方法を書いた日本語記事がたくさん出てくる。
しかし、django-imagekitは2021年4月現在、最新版が2017年11月20日になっており、更新が3年以上止まっているのに加えて、上述したdjango-cleanupsorl-thumbnailと互換性を持つことからこの記事ではsorl-thumbnailを使う方法を紹介する。

使い方は簡単であり、次のように使用する。

pip install sorl-thumbnail

でインストールしたら、settings.pyINSTALLED_APPSに、

INSTALLED_APPS = (
    ...,
    'sorl.thumbnail', # 追加
)

リサイズを行うのはフォームでもできるが、models.pyで行う場合は次のようにすれば良い。

import uuid
from django.core.files.base import ContentFile
from sorl.thumbnail import get_thumbnail, delete
from google.api_core.exceptions import NotFound

def image_directory_path(instance, filename):
    return 'images/{}.{}'.format(str(uuid.uuid4()), filename.split('.')[-1])
   
class MyModel(models.Model):
    image = models.ImageField(upload_to=image_directory_path)

    def save(self, *args, **kwargs):
        super(MyModel, self).save(*args, **kwargs)
        temp_img_name = self.image.name
        if self.image.width > 500 or self.image.height > 500:
            new_width = 500
            new_height = 500

            resized = get_thumbnail(self.image, "{}x{}".format(new_width, new_height))
            name = resized.name.split('/')[-1] # 結局上で定義したUUIDの名前になるのでこれは仮の名前
            self.image.save(name, ContentFile(resized.read()), True)
            try:
                delete(temp_img_name)
            except NotFound: # ここの例外は自身のものに変える
                pass
  • super(MyModel, self).save(*args, **kwargs)で一旦元画像を保存する。
  • 保存された画像に対してリサイズを施し、更新している。
  • 更新した際に元画像はdelete(temp_img_name)によって削除されるのでリサイズした画像のみ残る。逆に元画像を残したい場合はこの部分を消去する。
  • django-cleanupを用い、delete(temp_img_name)を使わない場合、新規作成のときアップロードされた元画像が残り、更新時には古い画像に加えてアップロードされた元画像も消去されるというよく分からない仕様になっている。(本当は新規作成のときも元画像を削除してほしい。。。)したがって、最後のような例外処理を加えている。

まとめ

本記事ではDjangoで画像を扱う場合に考えたほうが良い3つの処理(ファイル名の変更、更新時の古いファイルの削除、リサイズ)についてまとめた。

修正があったり他にもやったほうが良さそうなことがあれば教えていただきたいです。

スポンサーリンク
H-MEMO

コメント