Django model ImageField cria uma pasta aninhada cada vez que carrega o arquivo
Tenho certeza de que estou perdendo algo bobo, mas estou confuso.
Tenho o seguinte modelo para meu perfil:
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)
Mas a profile_pics
pasta continua se aninhando, então minha estrutura de pastas começa a ficar assim:

Minhas variáveis em settings.py parecem normais, eu acredito:
BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"
Acredito que o problema com a pasta de aninhamento se origina no meu método de salvamento na minha classe de perfil, especificamente este:
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)
que é acionado por meus sinais:
@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()
Por que isso está aninhando as pastas?
Estou usando o mesmo método de salvar nas imagens das postagens do blog e aí as pastas não se aninham.
o que estou perdendo?
PS: Caso ajude, aqui é 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
Editar:
Por acaso, cheguei a esta solução que está evitando o problema principal, embora não tenha certeza de quão eficiente pode ser:
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)
Respostas
Aposto que você queria ajustar esta postagem às suas necessidades. O autor não tem problema, porque ele "não está reutilizando o que está economizando".
self.image
tem atributo name
. Ao verificar se existe ( if self.image
), já tem um nome. Então, a cada atualização, você continua redimensionando a imagem já redimensionada, o que também continua adicionando o nome da imagem já existente ao upload_to
caminho, de modo que a cada iteração ele faz upload_to
+ self.image.name
. Mas self.image.name
já está /profile_pics/...
.
Para resolver esse problema, basta adicionar a is_resized
coluna.
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)
Lembre-se de definir is_resized
para False
sempre que a imagem mudar.
Nota lateral, sinais em geral são uma má prática. Também não acho uma boa ideia ter dois sinais que operam nos mesmos objetos.
Se você realmente precisa deles, considere substituí-los por um único sinal.
@receiver(post_save, sender=User)
def handle_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
else:
instance.profile.save()
Embora seja melhor se você redimensionar a miniatura em sua visualização.
Existe uma coisa no django chamada 'sinais duplicados'. Isso ocorre em todos os lugares em que seu projeto importa o módulo onde você define os sinais, pois o registro do sinal é executado tantas vezes quanto é importado.
Talvez você possa resolver seu problema passando um identificador único como o argumento dispatch_uid para identificar sua função de receptor.
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
Fonte: https://docs.djangoproject.com/en/3.1/topics/signals/