Das Django-Modell ImageField erstellt bei jedem Hochladen einer Datei einen verschachtelten Ordner
Ich bin sicher, ich vermisse etwas Dummes, aber ich bin verwirrt.
Ich habe das folgende Modell für mein Profil:
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)
Der profile_pics
Ordner wird jedoch weiterhin verschachtelt, sodass meine Ordnerstruktur folgendermaßen aussieht:

Meine Variablen in settings.py sehen normal aus, glaube ich:
BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"
Ich glaube, das Problem mit dem Verschachtelungsordner ist auf meine Speichermethode in meiner Profilklasse zurückzuführen, insbesondere auf Folgendes:
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)
was durch meine Signale ausgelöst wird:
@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()
Warum verschachtelt dies die Ordner?
Ich verwende die gleiche Speichermethode für die Blogpost-Bilder und dort werden die Ordner nicht verschachtelt.
Was vermisse ich?
PS: Falls es hilft, ist dies 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
Bearbeiten:
Ich bin übrigens zu dieser Lösung gekommen, die das Hauptproblem vermeidet, obwohl ich nicht sicher bin, wie effizient sie sein kann:
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)
Antworten
Ich wette, Sie wollten diesen Beitrag an Ihre Bedürfnisse anpassen . Der Autor hat kein Problem, weil er "nicht wiederverwendet, in was er speichert".
self.image
hat Attribut name
. Wenn Sie überprüfen, ob es existiert ( if self.image
), hat es bereits einen Namen. Dann ändern Sie bei jedem Update die Größe des bereits veränderten Bilds, wodurch auch der bereits vorhandene Bildname zum upload_to
Pfad hinzugefügt wird. Bei jeder Iteration wird upload_to
+ self.image.name
. Ist self.image.name
aber schon /profile_pics/...
.
Um dieses Problem zu beheben, fügen Sie einfach eine is_resized
Spalte hinzu.
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)
Denken Sie daran, immer dann einzustellen is_resized
, False
wenn sich das Bild ändert.
Nebenbei bemerkt, Signale sind im Allgemeinen eine schlechte Praxis. Ich denke auch nicht, dass es eine gute Idee ist, zwei Signale zu haben, die an denselben Objekten arbeiten.
Wenn Sie sie wirklich benötigen, sollten Sie sie durch ein einzelnes Signal ersetzen.
@receiver(post_save, sender=User)
def handle_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
else:
instance.profile.save()
Es ist jedoch besser, wenn Sie die Größe des Miniaturbilds in Ihrer Ansicht ändern.
In Django gibt es etwas, das als "doppelte Signale" bezeichnet wird. Dies tritt überall dort auf, wo Ihr Projekt das Modul importiert, in das Sie die Signale definieren, da die Signalregistrierung so oft ausgeführt wird, wie sie importiert wird.
Möglicherweise können Sie Ihr Problem lösen, indem Sie eine eindeutige Kennung als dispatch_uid-Argument übergeben, um Ihre Empfängerfunktion zu identifizieren.
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
Quelle: https://docs.djangoproject.com/en/3.1/topics/signals/