2010-12-09 142 views
24

En Django, si tiene un archivo de imagen en un modelo, eliminar eliminará el archivo asociado del disco y eliminará el registro de la base de datos.Reemplazar una imagen de Django no elimina el original

¿No debería reemplazar una imagen y también eliminar el archivo innecesario del disco? En cambio, veo que mantiene el original y agrega el reemplazo.

Al eliminar el objeto, no se eliminará el archivo original solo el reemplazo.

¿Hay alguna buena estrategia para hacer esto? No quiero tener un montón de archivos huérfanos si mis usuarios reemplazan sus imágenes con frecuencia.

+0

¿Ha intentado utilizar un método de guardar en el modelo para verificar si el archivo se está actualizando y eliminar el archivo anterior si es necesario? – PhoebeB

+0

Tengo un [problema similar y he añadido una pregunta] (http://stackoverflow.com/q/4787141/207894). Es posible que encuentre lo que está buscando allí en los próximos días ... –

Respuesta

0

he usado un método simple con popen, así que cuando puedo guardar mi modelo Info Puedo eliminar el antiguo archivo antes de vincular al nuevo:

import os 

try: 
    os.popen("rm %s" % str(info.photo.path)) 
except: 
    #deal with error 
    pass 
info.photo = nd['photo'] 
1

Ha habido una serie de entradas con respecto a este problema, aunque es es probable que esto no llegue al núcleo. El más completo es http://code.djangoproject.com/ticket/11663. Los parches y los comentarios del ticket le pueden dar una dirección si está buscando una solución.

También puede considerar el uso de un StorageBackend diferente, como el Sistema de almacenamiento de archivos Sobrescribir proporcionado por Django snippet 976. http://djangosnippets.org/snippets/976/. Puede cambiar su almacenamiento predeterminado a este servidor o puede anularlo en cada declaración FileField/ImageField.

+0

Creo que hay un problema con el fragmento 976. Considere un modelo de perfil de usuario que tenga un campo de imagen de perfil usando este backend OverwriteStorage con upload_to = "% Y /% Maryland". Dos usuarios en el mismo día cargan una foto de perfil con el nombre "me.jpg". La primera carga se publicará en 2012/02/11/me.jpg. La segunda carga eliminará y reemplazará esta imagen y hará que los dos campos de imagen del perfil de usuario hagan referencia a la misma imagen. – chris

+0

El comportamiento esperado de OverwriteStorage es reemplazar el archivo. Eso significa garantizar que la exclusividad del nombre recaiga en el desarrollador del sitio en lugar de en el back-end. –

24

La mejor estrategia que he encontrado es hacer una costumbre método para guardar en el modelo:

class Photo(models.Model): 

    image = ImageField(...) # works with FileField also 

    def save(self, *args, **kwargs): 
     # delete old file when replacing by updating the file 
     try: 
      this = Photo.objects.get(id=self.id) 
      if this.image != self.image: 
       this.image.delete(save=False) 
     except: pass # when new photo then we do nothing, normal case   
     super(Photo, self).save(*args, **kwargs) 

Y cuidado, al igual que con la actualización que no elimina el archivo de back-end, la eliminación de un modelo de instancia (aquí Foto) va a no borre el archivo de fondo, no en Django 1.3 de todos modos, tendrá que agregar más código personalizado para hacer eso (o regularmente hacer algún trabajo de cron sucio).

Finalmente, pruebe todos sus casos de actualización/eliminación con su ForeignKey, ManytoMany y otras relaciones para verificar si los archivos de fondo se borraron correctamente. Cree solo lo que prueba.

+2

Después de casi 3 años de uso en 2 sitios web, puedo confirmar que este método está listo para la producción y sin ningún problema. Ahora uso este método en un tercer sitio web con Django 1.6.2. y sigue funcionando de maravilla. –

+0

lo usé en las funciones 'save_model()' y 'delete_model()' de la clase 'ModelAdmin' en django 1.9 y funciona como un amuleto – samix73

+0

Veo que el nombre del archivo todavía se agrega con un código hash para evitar conflictos con el original nombre del archivo. Esto podría ser un inconveniente? por ejemplo, si el nombre del archivo se especificó como nombredeusuario.jpg, por primera vez el almacenamiento se guarda como nombredeusuario.jpg, pero posteriormente se actualiza a username_lksjdflaj.jpg. Es decir, la resolución de conflictos del nombre del archivo aún se produjo ... –

8

El código en el siguiente ejemplo de trabajo, al cargar una imagen en un campo de imagen, detectará si existe un archivo con el mismo nombre, y en ese caso, eliminará ese archivo antes de almacenar el nuevo.

Se podría modificar fácilmente para que elimine el archivo anterior independientemente del nombre del archivo. Pero eso no es lo que quería en mi proyecto.

Agregue la clase siguiente:

from django.core.files.storage import FileSystemStorage 
class OverwriteStorage(FileSystemStorage): 
    def _save(self, name, content): 
     if self.exists(name): 
      self.delete(name) 
     return super(OverwriteStorage, self)._save(name, content) 

    def get_available_name(self, name): 
     return name 

y utilizarlo con ImageField así:

class MyModel(models.Model): 
    myfield = models.ImageField(
     'description of purpose', 
     upload_to='folder_name', 
     storage=OverwriteStorage(), ### using OverwriteStorage here 
     max_length=500, 
     null=True, 
     blank=True, 
     height_field='height', 
     width_field='width' 
    ) 
    height = models.IntegerField(blank=True, null=True) 
    width = models.IntegerField(blank=True, null=True) 
+0

¡muchas gracias, eso es exactamente lo que necesitaba! –

+0

NO funciona para mí. ¿Qué representa la variable 'name'? – Philip007

+0

@ Philip007 Hace mucho tiempo escribí esto ... pero estoy bastante seguro de que es el nombre del archivo? – thnee

2

Si no utiliza transacciones o no se tiene miedo de perder archivos en rollback de transacciones , puede usar django-cleanup

12

¿No debería reemplazar una imagen y también eliminar el archivo innecesario del disco?

En los viejos tiempos, FileField estaba ansioso por limpiar los archivos huérfanos. Pero eso cambió en Django 1.2:

En versiones anteriores de Django, cuando se elimina una instancia de modelo que contiene una FileField, FileField se encargó de eliminar también el archivo desde el almacenamiento de back-end. Esto abrió la puerta a varios escenarios potencialmente graves de pérdida de datos, incluidas las transacciones retrotraídas y campos en diferentes modelos que hacen referencia al mismo archivo. En Django 1.2.5, FileField nunca eliminará archivos del almacenamiento de back-end.

+0

+1 para mencionar la historia – Philip007

1

Aquí es un código que puede trabajar con o sin upload_to=... o blank=True, y cuando el expediente presentado tiene el mismo nombre que el antiguo.

(sintaxis AP3, probado en Django 1,7)

class Attachment(models.Model): 

    document = models.FileField(...) # or ImageField 

    def delete(self, *args, **kwargs): 
     self.document.delete(save=False) 
     super().delete(*args, **kwargs) 

    def save(self, *args, **kwargs): 
     if self.pk: 
      old = self.__class__._default_manager.get(pk=self.pk) 
      if old.document.name and (not self.document._committed or not self.document.name): 
       old.document.delete(save=False) 
     super().save(*args, **kwargs) 

recordar que este tipo de solución sólo es aplicable si se encuentra en un contexto no transaccional (sin reversión, ya que el archivo se pierde definitivamente)

+0

para aquellos que todavía usan Python 2.x usan 'super (Adjunto, self)' en lugar de 'super()' –

Cuestiones relacionadas