Model Django ImageField membuat folder bersarang setiap kali mengunggah file

Nov 29 2020

Saya yakin saya melewatkan sesuatu yang konyol, tetapi saya bingung.

Saya memiliki model berikut untuk profil saya:

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)

Tapi profile_picsfoldernya tetap bersarang, jadi struktur folder saya mulai terlihat seperti ini:

Variabel saya di settings.py terlihat normal, saya percaya:

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

Saya yakin masalah dengan folder bersarang berasal dari metode penyimpanan saya di kelas Profil saya, khususnya ini:

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)

yang dipicu oleh sinyal saya:

@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()

Mengapa ini menumpuk folder?

Saya menggunakan metode penyimpanan yang sama pada gambar posting blog dan di sana folder tidak bersarang.

Apa yang saya lewatkan?

PS: Jika membantu, ini adalah 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

Edit:

Saya kebetulan sampai pada solusi ini yang menghindari masalah utama, meskipun saya tidak yakin seberapa efisien itu:

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)

Jawaban

2 TomWojcik Nov 30 2020 at 01:13

Saya yakin Anda ingin menyesuaikan posting ini dengan kebutuhan Anda. Penulis tidak memiliki masalah, karena dia "tidak menggunakan kembali apa yang dia simpan".

self.imagememiliki atribut name. Ketika Anda memeriksa apakah itu ada ( if self.image), itu sudah memiliki nama. Kemudian dengan setiap pembaruan Anda terus mengubah ukuran gambar yang sudah diubah ukurannya, yang juga terus menambahkan nama gambar yang sudah ada ke upload_tojalur, jadi dengan setiap iterasi itu upload_to+ self.image.name. Tapi self.image.namesudah /profile_pics/....

Untuk mengatasi masalah ini, cukup tambahkan is_resizedkolom.

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)

Hanya ingat untuk set is_resizedke Falsesetiap kali perubahan gambar.

Catatan tambahan, sinyal secara umum adalah praktik yang buruk. Saya juga tidak berpikir itu ide yang baik untuk memiliki dua sinyal yang beroperasi pada objek yang sama.

Jika Anda benar-benar perlu memilikinya, pertimbangkan untuk menggantinya dengan satu sinyal.

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

Meskipun akan lebih baik jika Anda menjalankan pengubahan ukuran thumbnail dalam tampilan Anda.

2 AadeshDhakal Nov 29 2020 at 22:21

Ada hal di Django yang disebut 'sinyal duplikat'. Ini terjadi di mana pun proyek Anda mengimpor modul tempat Anda menentukan sinyalnya, karena pendaftaran sinyal berjalan sebanyak itu diimpor.

Mungkin Anda bisa menyelesaikan masalah Anda dengan mengirimkan pengenal unik sebagai argumen dispatch_uid untuk mengidentifikasi fungsi penerima Anda.

from django.core.signals import request_finished

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

Sumber: https://docs.djangoproject.com/en/3.1/topics/signals/