DjangoのImageFieldやFileFieldは非常に便利ですが、普通に使っているとテーブルデータは削除・更新しても古いファイルが残ったままになってしまいます。
更新・削除時に古いファイルを自動削除する方法についてまとめます。
目次
どうやって削除・更新時に古いファイルを削除するか
djangoにはreceiverという機能があり、デコレータでreceiver登録すると指定したシグナルが来たときに処理を実行することができます。
シグナルには、データ削除・更新前後のシグナルなどがあります。
https://docs.djangoproject.com/en/3.1/topics/signals/
更新前や削除後のシグナルを受け取って、古いファイルを削除する処理を割り込ませることでこの問題は解決できます。
サンプルコード
Userテーブルに紐づくユーザー情報DBがあり、ユーザーの古いサムネイルが更新・削除されるようにするサンプルコードです。
ユーザー情報DBはintro: 紹介文、thumbnail: サムネイルといったデータが保持されています。
更新時の古い画像削除
pre_save関数はmodels.signals.pre_saveシグナルを受け取るようにしており、データ登録・更新前に実行されます。
user__idで検索して、古いデータがあれば削除する感じです。
削除後の古い画像削除
post_delete関数はmodels.signals.post_deleteシグナルを受け取るようにしており、データ削除後に実行されます。
削除後は単純にサムネイル画像を削除するだけです。
import traceback
from django.dispatch import receiver
from django.core.validators import MaxLengthValidator
from django.db import models
from django.contrib.auth.models import User
# ImageFieldのファイルを削除する関数
def delete_file(image_field: ImageFieldFile):
if not image_field: # 画像がなければ何もしない
return
storage = image_field.storage
try:
path = image_field.path
storage.delete(path)
except ValueError: # pathが存在しない場合にValueErrorとなる
print(traceback.format_exc()) # エラー処理
class UserProfile(models.Model):
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
primary_key=True,
unique=True,
)
intro = models.TextField(validators=[MaxLengthValidator(500)])
thumbnail = models.ImageField(upload_to="thumbnail/")
# データ更新前の処理
@receiver(models.signals.pre_save, sender=UserProfile)
def pre_save(sender, instance, **kwargs):
# instanceは新たに登録するデータ
user_id = instance.user.id
# 古いデータを検索
rows = UserProfile.objects.filter(user__id=user_id)
# 更新処理なら元の画像を削除する
if not rows.exists():
return
delete_file(rows[0].thumbnail)
# データ削除後の処理
@receiver(models.signals.post_delete, sender=UserProfile)
def post_deleted(sender, instance, **kwargs):
# instanceは削除後のデータ
delete_file(instance.thumbnail)