DjangoモデルImageFieldは、ファイルをアップロードするたびにネストされたフォルダーを作成します

Nov 29 2020

私は愚かな何かを見逃していると確信していますが、私は混乱しています。

私のプロファイルには次のモデルがあります。

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    image = models.ImageField(
        default="default.jpg",
        upload_to="profile_pics/",
        validators=[FileExtensionValidator(["jpeg", "jpg", "png"])],
    )

    def __str__(self):
        return f"{self.user.username} Profile"

    def save(self, *args, **kwargs):
        if self.image:
            self.image = make_thumbnail(self.image, size=(200, 200))
            super().save(*args, **kwargs)
        else:
            super().save(*args, **kwargs)

しかし、profile_picsフォルダーはネストし続けるので、私のフォルダー構造は次のようになります。

settings.pyの私の変数は正常に見えます、私は信じています:

BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"

ネストフォルダの問題は、Profileクラスのsaveメソッドに起因すると思います。具体的には次のとおりです。

def save(self, *args, **kwargs):
    if self.image:
        self.image = make_thumbnail(self.image, size=(200, 200))
        super().save(*args, **kwargs)
    else:
        super().save(*args, **kwargs)

これは私の信号によってトリガーされます:

@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)


@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
    instance.profile.save()

なぜこれがフォルダをネストしているのですか?

ブログの投稿画像で同じ保存方法を使用していますが、フォルダーがネストされていません。

何が足りないのですか?

PS:それが役立つ場合、これはmake_thumbnailです:

from io import BytesIO
from django.core.files import File
from PIL import Image

def make_thumbnail(image, size=(600, 600)):
    im = Image.open(image)
    if im.format == "JPEG":
        im.convert("RGB")
        im.thumbnail(size)
        thumb_io = BytesIO()
        im.save(thumb_io, "JPEG", quality=85)
        image = File(thumb_io, name=image.name)
    else:
        im.convert("RGBA")
        im.thumbnail(size)
        thumb_io = BytesIO()
        im.save(thumb_io, "PNG", quality=85)
        image = File(thumb_io, name=image.name)
    return image

編集:

私は偶然にも、主な問題を回避しているこの解決策にたどり着きましたが、それがどれほど効率的であるかはわかりません。

def save(self, *args, **kwargs):
    if self.image:
        self.image = make_thumbnail(self.image, size=(200, 200))
        image_name = self.image.name
        ext = image_name.split(".")[-1]
        filename = "%s.%s" % (uuid.uuid4(), ext)
        clean_name = os.path.join("", filename)
        self.image.name = clean_name
        super().save(*args, **kwargs)
    else:
        super().save(*args, **kwargs)

回答

2 TomWojcik Nov 30 2020 at 01:13

この投稿をニーズに合わせて調整したいと思ったに違いありません。作者は「保存しているものを再利用していない」ので、問題はありません。

self.image属性がありますname。存在するかどうかを確認すると(if self.image)、すでに名前が付いています。次に、更新のたびに、サイズ変更済みの画像のサイズを変更し続けupload_toます。これにより、既存の画像名がパスに追加され続けるため、反復ごとにupload_to+が実行されますself.image.name。しかしself.image.name、すでに/profile_pics/...です。

この問題を解決するには、is_resized列を追加するだけです。

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    image = models.ImageField(
        default="default.jpg",
        upload_to="profile_pics/",
        validators=[FileExtensionValidator(["jpeg", "jpg", "png"])],
    )
    is_resized = models.BooleanField(default=False)

    def __str__(self):
        return f"{self.user.username} Profile"

    def save(self, *args, **kwargs):
        if self.image and not self.is_resized:
            self.is_resized = True
            self.image = make_thumbnail(self.image, size=(200, 200))
        super().save(*args, **kwargs)

画像が変わるたびにに設定is_resizedすることを忘れないでくださいFalse

ちなみに、シグナルは一般的に悪い習慣です。また、同じオブジェクトを操作する2つの信号を用意するのは良い考えではないと思います。

本当に必要な場合は、単一の信号に置き換えることを検討してください。

@receiver(post_save, sender=User)
def handle_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)
    else:
        instance.profile.save()

ただし、ビューでサムネイルのサイズ変更を実行した方がよいでしょう。

2 AadeshDhakal Nov 29 2020 at 22:21

djangoには「重複信号」と呼ばれるものがあります。これは、シグナル登録がインポートされた回数だけ実行されるため、プロジェクトがシグナルを定義するモジュールをインポートするすべての場所で発生します。

おそらく、dispatch_uid引数として一意の識別子を渡してレシーバー関数を識別することで問題を解決できます。

from django.core.signals import request_finished

request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")

ソース: https://docs.djangoproject.com/en/3.1/topics/signals/