Carga del archivo de administración de Django con el ID del modelo actual

Estoy intentando crear una galería de fotos simple con el administrador de Django predeterminado. Me gustaría guardar una foto de muestra para cada galería, pero no quiero mantener el nombre del archivo. En lugar del nombre de archivo, me gustaría guardar la identificación del modelo ( N.jpg ). Pero la primera vez que quiero guardar el objeto, el ID no existe. ¿Cómo puedo saber el próximo incremento automático en el modelo, o guardar de alguna manera los datos del modelo antes de la carga con super.save y después de cargar el archivo cuando existe self.id ? ¿Hay una solución fresca?

Algo como esto:

 def upload_path_handler(instance, filename): ext = filename extension return "site_media/images/gallery/{id}.{ext}".format(id=instance.nextincrement, ext=ext) class Gallery(models.Model): name = models.TextField() image = models.FileField(upload_to=upload_path_handler) 

Y tal vez almacenar el nombre de archivo en un campo diferente.

El archivo de imagen se guarda antes de la instancia de la Galería. Por lo tanto, debe dividir el ahorro en dos fases mediante el uso de señales con la propia instancia de Gallery que lleva el estado:

 from django.db.models.signals import post_save, pre_save from django.dispatch import receiver _UNSAVED_FILEFIELD = 'unsaved_filefield' @receiver(pre_save, sender=Image) def skip_saving_file(sender, instance, **kwargs): if not instance.pk and not hasattr(instance, _UNSAVED_FILEFIELD): setattr(instance, _UNSAVED_FILEFIELD, instance.image) instance.image = None @receiver(post_save, sender=Image) def save_file(sender, instance, created, **kwargs): if created and hasattr(instance, _UNSAVED_FILEFIELD): instance.image = getattr(instance, _UNSAVED_FILEFIELD) instance.save() # delete it if you feel uncomfortable... # instance.__dict__.pop(_UNSAVED_FILEFIELD) 

El upload_path_handler parece

 def upload_path_handler(instance, filename): import os.path fn, ext = os.path.splitext(filename) return "site_media/images/gallery/{id}{ext}".format(id=instance.pk, ext=ext) 

Sugiero usar ImageField en lugar de FileField para verificar el tipo si el campo es solo para cargar imágenes. Además, es posible que desee normalizar la extensión del nombre de archivo (que no es necesaria debido al tipo MIME) como

 def normalize_ext(image_field): try: from PIL import Image except ImportError: import Image ext = Image.open(image_field).format if hasattr(image_field, 'seek') and callable(image_field.seek): image_field.seek(0) ext = ext.lower() if ext == 'jpeg': ext = 'jpg' return '.' + ext 

Tuve el mismo problema. La respuesta de Okm me envió por el camino correcto, pero me parece que es posible obtener la misma funcionalidad simplemente anulando el método save() .

 def save(self, *args, **kwargs): if self.pk is None: saved_image = self.image self.image = None super(Material, self).save(*args, **kwargs) self.image = saved_image super(Material, self).save(*args, **kwargs) 

Esto definitivamente guarda la información correctamente.

En django 1.7, las soluciones propuestas no parecían funcionar para mí, así que escribí mis subclases de FileField junto con una subclase de almacenamiento que elimina los archivos antiguos.

Almacenamiento:

 class OverwriteFileSystemStorage(FileSystemStorage): def _save(self, name, content): self.delete(name) return super()._save(name, content) def get_available_name(self, name): return name def delete(self, name): super().delete(name) last_dir = os.path.dirname(self.path(name)) while True: try: os.rmdir(last_dir) except OSError as e: if e.errno in {errno.ENOTEMPTY, errno.ENOENT}: break raise e last_dir = os.path.dirname(last_dir) 

FileField:

 def tweak_field_save(cls, field): field_defined_in_this_class = field.name in cls.__dict__ and field.name not in cls.__bases__[0].__dict__ if field_defined_in_this_class: orig_save = cls.save if orig_save and callable(orig_save): assert isinstance(field.storage, OverwriteFileSystemStorage), "Using other storage than '{0}' may cause unexpected behavior.".format(OverwriteFileSystemStorage.__name__) def save(self, *args, **kwargs): if self.pk is None: orig_save(self, *args, **kwargs) field_file = getattr(self, field.name) if field_file: old_path = field_file.path new_filename = field.generate_filename(self, os.path.basename(old_path)) new_path = field.storage.path(new_filename) os.makedirs(os.path.dirname(new_path), exist_ok=True) os.rename(old_path, new_path) setattr(self, field.name, new_filename) # for next save if len(args) > 0: args = tuple(v if k >= 2 else False for k, v in enumerate(args)) kwargs['force_insert'] = False kwargs['force_update'] = False orig_save(self, *args, **kwargs) cls.save = save def tweak_field_class(orig_cls): orig_init = orig_cls.__init__ def __init__(self, *args, **kwargs): if 'storage' not in kwargs: kwargs['storage'] = OverwriteFileSystemStorage() if orig_init and callable(orig_init): orig_init(self, *args, **kwargs) orig_cls.__init__ = __init__ orig_contribute_to_class = orig_cls.contribute_to_class def contribute_to_class(self, cls, name): if orig_contribute_to_class and callable(orig_contribute_to_class): orig_contribute_to_class(self, cls, name) tweak_field_save(cls, self) orig_cls.contribute_to_class = contribute_to_class return orig_cls def tweak_file_class(orig_cls): """ Overriding FieldFile.save method to remove the old associated file. I'm doing the same thing in OverwriteFileSystemStorage, but it works just when the names match. I probably want to preserve both methods if anyone calls Storage.save. """ orig_save = orig_cls.save def new_save(self, name, content, save=True): self.delete(save=False) if orig_save and callable(orig_save): orig_save(self, name, content, save=save) new_save.__name__ = 'save' orig_cls.save = new_save return orig_cls @tweak_file_class class OverwriteFieldFile(models.FileField.attr_class): pass @tweak_file_class class OverwriteImageFieldFile(models.ImageField.attr_class): pass @tweak_field_class class RenamedFileField(models.FileField): attr_class = OverwriteFieldFile @tweak_field_class class RenamedImageField(models.ImageField): attr_class = OverwriteImageFieldFile 

y mi upload_to callables se parece a esto:

 def user_image_path(instance, filename): name, ext = 'image', os.path.splitext(filename)[1] if instance.pk is not None: return os.path.join('users', os.path.join(str(instance.pk), name + ext)) return os.path.join('users', '{0}_{1}{2}'.format(uuid1(), name, ext)) 

Usando la respuesta de Louis , aquí hay una receta para procesar todo el FileField en el modelo:

 class MyModel(models.Model): file_field = models.FileField(upload_to=upload_to, blank=True, null=True) def save(self, *args, **kwargs): if self.id is None: saved = [] for f in self.__class__._meta.get_fields(): if isinstance(f, models.FileField): saved.append((f.name, getattr(self, f.name))) setattr(self, f.name, None) super(self.__class__, self).save(*args, **kwargs) for name, val in saved: setattr(self, name, val) super(self.__class__, self).save(*args, **kwargs)