Model Django ImageField tworzy zagnieżdżony folder za każdym razem, gdy ładuje plik
Jestem pewien, że brakuje mi czegoś głupiego, ale jestem zdezorientowany.
Mam następujący model dla mojego profilu:
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)
Ale profile_pics
folder jest zagnieżdżony, więc moja struktura folderów zaczyna wyglądać następująco:

Moje zmienne w settings.py wyglądają normalnie, myślę, że:
BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"
Uważam, że problem z folderem zagnieżdżania pochodzi z mojej metody zapisu w mojej klasie Profile, a konkretnie z tego:
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)
który jest wyzwalany przez moje sygnały:
@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()
Dlaczego to zagnieżdżanie folderów?
Używam tej samej metody zapisywania na obrazach postów na blogu i tam foldery nie zagnieżdżają się.
czego mi brakuje?
PS: Jeśli to pomoże, oto 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
Edytować:
Przypadkowo dotarłem do tego rozwiązania, które omija główny problem, chociaż nie jestem pewien, jak wydajne może być:
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)
Odpowiedzi
Założę się, że chciałeś dostosować ten post do swoich potrzeb. Autor nie ma problemu, ponieważ „nie wykorzystuje ponownie tego, na czym oszczędza”.
self.image
ma atrybut name
. Kiedy sprawdzasz, czy istnieje ( if self.image
), ma już nazwę. Następnie z każdą aktualizacją zmieniasz rozmiar już zmienionego rozmiaru obrazu, co również dodaje już istniejącą nazwę obrazu do upload_to
ścieżki, więc przy każdej iteracji upload_to
+ self.image.name
. Ale self.image.name
już jest /profile_pics/...
.
Aby rozwiązać ten problem, po prostu dodaj is_resized
kolumnę.
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)
Pamiętaj tylko, aby ustawić is_resized
na False
każdą zmianę obrazu.
Uwaga dodatkowa, generalnie sygnały są złą praktyką. Uważam też, że nie jest dobrym pomysłem posiadanie dwóch sygnałów, które działają na tych samych obiektach.
Jeśli naprawdę potrzebujesz ich, rozważ zastąpienie ich jednym sygnałem.
@receiver(post_save, sender=User)
def handle_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
else:
instance.profile.save()
Chociaż byłoby lepiej, gdybyś uruchomił zmianę rozmiaru miniatury w swoim widoku.
W django jest coś, co nazywa się „zduplikowanymi sygnałami”. Dzieje się tak wszędzie tam, gdzie projekt importuje moduł, w którym definiujesz sygnały, ponieważ rejestracja sygnału przebiega tyle razy, ile jest importowana.
Być może uda Ci się rozwiązać problem, przekazując unikalny identyfikator jako argument dispatch_uid w celu zidentyfikowania funkcji odbiorcy.
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
Źródło: https://docs.djangoproject.com/en/3.1/topics/signals/