Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

344
Views
Django: guarda varias versiones de una imagen

Mi aplicación necesita guardar varias versiones de una imagen cargada. Una imagen de alta calidad y otra solo para uso de miniaturas (baja calidad). Actualmente, esto funciona la mayor parte del tiempo, pero a veces el método de guardar simplemente falla y todas mis imágenes en miniatura se eliminan, especialmente si uso la casilla de verificación remove_cover en mi formulario.

raise ValueError("El atributo '%s' no tiene ningún archivo asociado." % self.field.name) app | ValueError: el atributo 'postcover_tn' no tiene ningún archivo asociado.

-> Ver seguimiento completo aquí: https://pastebin.com/hgieMGet

modelos.py

 class Post(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) author = models.ForeignKey(User, on_delete=models.CASCADE) title = models.CharField() content = models.TextField(blank=False) postcover = models.ImageField( verbose_name="Post Cover", blank=True, null=True, upload_to=image_uploads, ) postcover_tn = models.ImageField( verbose_name="Post Cover Thumbnail", blank=True, null=True, upload_to=image_uploads, ) published_date = models.DateTimeField(auto_now_add=True, null=True) def save(self, *args, **kwargs): super(Post, self).save(*args, **kwargs) if self.postcover: if not (self.postcover_tn and os.path.exists(self.postcover_tn.path)): image = Image.open(self.postcover) outputIoStream = BytesIO() baseheight = 500 hpercent = baseheight / image.size[1] wsize = int(image.size[0] * hpercent) imageTemproaryResized = image.resize((wsize, baseheight)) imageTemproaryResized.save(outputIoStream, format='PNG') outputIoStream.seek(0) self.postcover = InMemoryUploadedFile(outputIoStream, 'ImageField', "%s.png" % self.postcover.name.split('.')[0], 'image/png', sys.getsizeof(outputIoStream), None) image = Image.open(self.postcover) outputIoStream = BytesIO() baseheight = 175 hpercent = baseheight / image.size[1] wsize = int(image.size[0] * hpercent) imageTemproaryResized = image.resize((wsize, baseheight)) imageTemproaryResized.save(outputIoStream, format='PNG') outputIoStream.seek(0) self.postcover_tn = InMemoryUploadedFile(outputIoStream, 'ImageField', "%s.png" % self.postcover.name.split('.')[0], 'image/png', sys.getsizeof(outputIoStream), None) elif self.postcover_tn: self.postcover_tn.delete() super(Post, self).save(*args, **kwargs)

También parece que no puedo resolver correctamente:

  • self.postcover_tn.delete() -> Referencia de atributo sin resolver 'eliminar' para la clase 'InMemoryUploadedFile'
  • self.postcover_tn.path -> Referencia de atributo sin resolver 'ruta' para la clase 'InMemoryUploadedFile'

formularios.py:

 def save(self, commit=True): instance = super(PostForm, self).save(commit=False) if self.cleaned_data.get('remove_cover'): try: os.unlink(instance.postcover.path) except OSError: pass instance.postcover = None if commit: instance.save() return instance
over 4 years ago · Santiago Trujillo
1 answers
Answer question

0

tal vez si miramos el problema desde otro ángulo, podríamos resolverlo de otra manera, fuera de la caja. las signals son muy útiles cuando se trata de manejar imágenes (agregar, actualizar y eliminar) y, a continuación, cómo logré resolver su problema:

en models.py :

 # from django.template.defaultfilters import slugify class Post(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) author = models.ForeignKey(User, on_delete=models.CASCADE) title = models.CharField() # slug = models.SlugField('slug', max_length=255, # unique=True, null=True, blank=True, # help_text='If blank, the slug will be generated automatically from the given title.' # ) content = models.TextField(blank=False) # ------------------------------------------------------------------------------------ # rename images with the current post id/pk (which is UUID) and keep the extension # for cover thumbnail we append "_thumbnail" to the name # eg: # img/posts/77b122a3d241461b80c51adc41d719fb.jpg # img/posts/77b122a3d241461b80c51adc41d719fb_thumbnail.jpg def upload_cover(instance, filename): ext = filename.split('.')[-1] filename = '{}.{}'.format(instance.id, ext) path = 'img/posts/' return '{}{}'.format(path, filename) postcover = models.ImageField('Post Cover', upload_to=upload_cover, # callback function null=True, blank=True, help_text=_('Upload Post Cover.') ) def upload_thumbnail(instance, filename): ext = filename.split('.')[-1] filename = '{}_thumbnail.{}'.format(instance.id, ext) path = 'img/posts/' return '{}{}'.format(path, filename) postcover_tn = models.ImageField('Post Cover Thumbnail', upload_to=upload_thumbnail, # callback function null=True, blank=True, help_text=_('Upload Post Cover Thumbnail.') ) # ------------------------------------------------------------------------------------ published_date = models.DateTimeField(auto_now_add=True, null=True) def save(self, *args, **kwargs): # i moved the logic to signals # if not self.slug: # self.slug = slugify(self.title) super(Post, self).save(*args, **kwargs)

cree un nuevo archivo y cámbiele el nombre a signals.py (cerca de models.py ):

 import io import sys from PIL import Image from django.core.files.uploadedfile import InMemoryUploadedFile from django.dispatch import receiver from django.db.models.signals import pre_save, pre_delete from .models import Post # DRY def image_resized(image, h): name = image.name _image = Image.open(image) content_type = Image.MIME[_image.format] r = h / _image.size[1] # ratio w = int(_image.size[0] * r) imageTemproaryResized = _image.resize((w, h)) file = io.BytesIO() imageTemproaryResized.save(file, _image.format) file.seek(0) size = sys.getsizeof(file) return file, name, content_type, size @receiver(pre_save, sender=Post, dispatch_uid='post.save_image') def save_image(sender, instance, **kwargs): # add image (cover | thumbnail) if instance._state.adding: # postcover file, name, content_type, size = image_resized(instance.postcover, 500) instance.postcover = InMemoryUploadedFile(file, 'ImageField', name, content_type, size, None) # postcover_tn file, name, content_type, size = image_resized(instance.postcover_tn, 175) instance.postcover_tn = InMemoryUploadedFile(file, 'ImageField', name, content_type, size, None) # update image (cover | thumbnail) if not instance._state.adding: # we have 2 cases: # - replace old with new # - delete old (when 'clear' checkbox is checked) # postcover old = sender.objects.get(pk=instance.pk).postcover new = instance.postcover if (old and not new) or (old and new and old.url != new.url): old.delete(save=False) # postcover_tn old = sender.objects.get(pk=instance.pk).postcover_tn new = instance.postcover_tn if (old and not new) or (old and new and old.url != new.url): old.delete(save=False) @receiver(pre_delete, sender=Post, dispatch_uid='post.delete_image') def delete_image(sender, instance, **kwargs): s = sender.objects.get(pk=instance.pk) if (not s.postcover or s.postcover is not None) and (not s.postcover_tn or s.postcover_tn is not None): s.postcover.delete(False) s.postcover_tn.delete(False)

en apps.py :

necesitamos registrar señales en apps.py ya que usamos decoradores @receiver :

 from django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ class BlogConfig(AppConfig): # change to the name of your app name = 'blog' # and here verbose_name = _('Blog Entries') def ready(self): from . import signals

y esta es la primera captura de pantalla del área de administración post

ingrese la descripción de la imagen aquí

dado que la miniatura se genera a partir de la portada de la publicación, como buenas prácticas de UI/UX, no es necesario mostrar un segundo archivo de entrada para la miniatura de la portada de la publicación (mantuve el segundo campo de imagen de solo lectura en admin.py ).

a continuación se muestra la segunda captura de pantalla después de que subí la imagen

PD: la captura de pantalla está tomada de otra aplicación en la que estoy trabajando, así que hay pequeños cambios, en tu caso deberías ver

  • Publicar portada en lugar de imagen destacada
  • Currently: img/posts/8b0be417db564c53ad06cb493029e2ca.jpg (ver upload_cover() en models.py ) en su lugar Currently: img/blog/posts/featured/8b0be417db564c53ad06cb493029e2ca.jpg

ingrese la descripción de la imagen aquí

en admin.py

 # "img/posts/default.jpg" and "img/posts/default_thumbnail.jpg" are placeholders # grab to 2 image placeholders from internet and put them under "/static" folder def get_post_cover(obj): src = obj.postcover.url if obj.postcover and \ hasattr(obj.postcover, 'url') else os.path.join( settings.STATIC_URL, 'img/posts/default.jpg') return mark_safe('<img src="{}" height="500" style="border:1px solid #ccc">'.format(src)) get_post_cover.short_description = '' get_post_cover.allow_tags = True def get_post_cover_thumbnail(obj): src = obj.postcover_tn.url if obj.postcover_tn and \ hasattr(obj.postcover_tn, 'url') else os.path.join( settings.STATIC_URL, 'img/posts/default_thumbnail.jpg') return mark_safe('<img src="{}" height="175" style="border:1px solid #ccc">'.format(src)) get_post_cover_thumbnail.short_description = '' get_post_cover_thumbnail.allow_tags = True class PostAdmin(admin.ModelAdmin): list_display = ('title', .. ) fields = ( 'author', 'title', 'content', get_post_cover, get_post_cover_thumbnail, 'postcover', ) readonly_fields = (get_post_cover, get_post_cover_thumbnail) [..]

y finalmente no necesita ninguna lógica de eliminación en la función save() en forms.py

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!