Estou usando 1.2.5 com um ImageField padrão e usando o back-end de armazenamento embutido. Os arquivos são carregados sem problemas, mas quando removo uma entrada do administrador, o arquivo real no servidor não é excluído.
Estou usando 1.2.5 com um ImageField padrão e usando o back-end de armazenamento embutido. Os arquivos são carregados sem problemas, mas quando removo uma entrada do administrador, o arquivo real no servidor não é excluído.
Respostas:
Você pode receber o sinal pre_delete
ou post_delete
(veja o comentário de @toto_tico abaixo) e chamar o método delete () no objeto FileField, assim (em models.py):
class MyModel(models.Model):
file = models.FileField()
...
# Receive the pre_delete signal and delete the file associated with the model instance.
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver
@receiver(pre_delete, sender=MyModel)
def mymodel_delete(sender, instance, **kwargs):
# Pass false so FileField doesn't save the model.
instance.file.delete(False)
instance.file
campo não está vazio ou pode (pelo menos tentar) excluir todo o diretório MEDIA_ROOT. Isso se aplica até mesmo aos ImageField(null=False)
campos.
post_delete
sinal porque é mais seguro caso o delete falhe por algum motivo. Então, nem o modelo, nem o arquivo seriam excluídos mantendo os dados consistentes. Por favor, corrija-me se minha compreensão post_delete
e pre_delete
sinais estiverem errados.
Experimente django-cleanup
pip install django-cleanup
settings.py
INSTALLED_APPS = (
...
'django_cleanup', # should go after your apps
)
Solução Django 1.5: Eu uso post_delete por vários motivos internos ao meu aplicativo.
from django.db.models.signals import post_delete
from django.dispatch import receiver
@receiver(post_delete, sender=Photo)
def photo_post_delete_handler(sender, **kwargs):
photo = kwargs['instance']
storage, path = photo.original_image.storage, photo.original_image.path
storage.delete(path)
Coloquei isso na parte inferior do arquivo models.py.
o original_image
campo é o ImageField
no meu Photo
modelo.
NotImplementedError: This backend doesn't support absolute paths.
Você pode corrigir isso facilmente passando o nome do campo do arquivo para em storage.delete()
vez do caminho do campo do arquivo. Por exemplo, substitua as duas últimas linhas desta resposta por storage, name = photo.original_image.storage, photo.original_image.name
então storage.delete(name)
.
mymodel.myimagefield.delete(save=False)
. docs.djangoproject.com/en/dev/ref/files/file/…
mymodel.myimagefield.delete(save=False)
no post_delete
? Em outras palavras, posso ver que posso excluir o arquivo, mas você pode excluir o arquivo quando um modelo que tem o imagefield é excluído?
post_delete
você instance.myimagefield.delete(save=False)
, observe o uso de instance
.
Este código funciona bem no Django 1.4 também com o painel de administração.
class ImageModel(models.Model):
image = ImageField(...)
def delete(self, *args, **kwargs):
# You have to prepare what you need before delete the model
storage, path = self.image.storage, self.image.path
# Delete the model before the file
super(ImageModel, self).delete(*args, **kwargs)
# Delete the file after the model
storage.delete(path)
É importante obter o armazenamento e o caminho antes de excluir o modelo, ou o último persistirá vazio também se excluído.
delete
nem sempre é chamado quando uma linha é excluída, você deve usar sinais.
Você precisa remover o arquivo real em delete
e update
.
from django.db import models
class MyImageModel(models.Model):
image = models.ImageField(upload_to='images')
def remove_on_image_update(self):
try:
# is the object in the database yet?
obj = MyImageModel.objects.get(id=self.id)
except MyImageModel.DoesNotExist:
# object is not in db, nothing to worry about
return
# is the save due to an update of the actual image file?
if obj.image and self.image and obj.image != self.image:
# delete the old image file from the storage in favor of the new file
obj.image.delete()
def delete(self, *args, **kwargs):
# object is being removed from db, remove the file from storage first
self.image.delete()
return super(MyImageModel, self).delete(*args, **kwargs)
def save(self, *args, **kwargs):
# object is possibly being updated, if so, clean up.
self.remove_on_image_update()
return super(MyImageModel, self).save(*args, **kwargs)
Você pode considerar o uso de um sinal pre_delete ou post_delete:
https://docs.djangoproject.com/en/dev/topics/signals/
Obviamente, os mesmos motivos pelos quais a exclusão automática do FileField foi removida também se aplicam aqui. Se você deletar um arquivo referenciado em outro lugar, terá problemas.
No meu caso, isso parecia apropriado porque eu tinha um modelo de arquivo dedicado para gerenciar todos os meus arquivos.
Nota: Por algum motivo, post_delete não parece funcionar direito. O arquivo foi excluído, mas o registro do banco de dados permaneceu, o que é completamente o oposto do que eu esperava, mesmo em condições de erro. pre_delete funciona bem.
post_delete
não funcionará, porque file_field.delete()
por padrão salva o modelo no banco de dados, tente file_field.delete(False)
docs.djangoproject.com/en/1.3/ref/models/fields/…
Talvez seja um pouco tarde. Mas a maneira mais fácil para mim é usar um sinal post_save. Apenas para lembrar que os sinais são executados mesmo durante um processo de exclusão de QuerySet, mas o método [model] .delete () não é executado durante o processo de exclusão de QuerySet, portanto, não é a melhor opção para substituí-lo.
core / models.py:
from django.db import models
from django.db.models.signals import post_delete
from core.signals import delete_image_slide
SLIDE1_IMGS = 'slide1_imgs/'
class Slide1(models.Model):
title = models.CharField(max_length = 200)
description = models.CharField(max_length = 200)
image = models.ImageField(upload_to = SLIDE1_IMGS, null = True, blank = True)
video_embed = models.TextField(null = True, blank = True)
enabled = models.BooleanField(default = True)
"""---------------------------- SLIDE 1 -------------------------------------"""
post_delete.connect(delete_image_slide, Slide1)
"""--------------------------------------------------------------------------"""
core / signs.py
import os
def delete_image_slide(sender, **kwargs):
slide = kwargs.get('instance')
try:
os.remove(slide.image.path)
except:
pass
Essa funcionalidade será removida no Django 1.3, então eu não confiaria nela.
Você pode substituir o delete
método do modelo em questão para excluir o arquivo antes de remover completamente a entrada do banco de dados.
Editar:
Aqui está um exemplo rápido.
class MyModel(models.Model):
self.somefile = models.FileField(...)
def delete(self, *args, **kwargs):
somefile.delete()
super(MyModel, self).delete(*args, **kwargs)
MyModel.objects.all()[0].delete()
irá deletar o arquivo enquanto MyModel.objects.all().delete()
não. Use sinais.
Usar o post_delete é com certeza o caminho certo a seguir. Às vezes, embora as coisas possam dar errado e os arquivos não sejam excluídos. É claro que você tem um monte de arquivos antigos que não foram excluídos antes de post_delete ser usado. Eu criei uma função que exclui arquivos para objetos com base em se o arquivo ao qual o objeto faz referência não existe, exclua o objeto, se o arquivo não tiver um objeto, exclua também, também pode excluir com base em um sinalizador "ativo" para um objeto .. Algo que adicionei à maioria dos meus modelos. Você deve passar para ele os objetos que deseja verificar, o caminho para os arquivos de objetos, o campo do arquivo e um sinalizador para excluir objetos inativos:
def cleanup_model_objects(m_objects, model_path, file_field='image', clear_inactive=False):
# PART 1 ------------------------- INVALID OBJECTS
#Creates photo_file list based on photo path, takes all files there
model_path_list = os.listdir(model_path)
#Gets photo image path for each photo object
model_files = list()
invalid_files = list()
valid_files = list()
for obj in m_objects:
exec("f = ntpath.basename(obj." + file_field + ".path)") # select the appropriate file/image field
model_files.append(f) # Checks for valid and invalid objects (using file path)
if f not in model_path_list:
invalid_files.append(f)
obj.delete()
else:
valid_files.append(f)
print "Total objects", len(model_files)
print "Valid objects:", len(valid_files)
print "Objects without file deleted:", len(invalid_files)
# PART 2 ------------------------- INVALID FILES
print "Files in model file path:", len(model_path_list)
#Checks for valid and invalid files
invalid_files = list()
valid_files = list()
for f in model_path_list:
if f not in model_files:
invalid_files.append(f)
else:
valid_files.append(f)
print "Valid files:", len(valid_files)
print "Files without model object to delete:", len(invalid_files)
for f in invalid_files:
os.unlink(os.path.join(model_path, f))
# PART 3 ------------------------- INACTIVE PHOTOS
if clear_inactive:
#inactive_photos = Photo.objects.filter(active=False)
inactive_objects = m_objects.filter(active=False)
print "Inactive Objects to Delete:", inactive_objects.count()
for obj in inactive_objects:
obj.delete()
print "Done cleaning model."
É assim que você pode usar:
photos = Photo.objects.all()
photos_path, tail = ntpath.split(photos[0].image.path) # Gets dir of photos path, this may be different for you
print "Photos -------------->"
cleanup_model_objects(photos, photos_path, file_field='image', clear_inactive=False) # image file is default
certifique-se de escrever " self " antes do arquivo. então o exemplo acima deve ser
def delete(self, *args, **kwargs):
self.somefile.delete()
super(MyModel, self).delete(*args, **kwargs)
Esqueci o "self" antes do meu arquivo e isso não funcionou porque estava procurando no namespace global.
Se você já tem vários arquivos não usados em seu projeto e deseja excluí-los, você pode usar o utilitário django django-unused-media
Não há necessidade de instalar nenhum pacote! É muito fácil de manusear no Django 2 . Tentei seguir a solução usando Django 2 e SFTP Storage (no entanto, acho que funcionaria com qualquer armazenamento)
Primeiro escreva um gerenciador personalizado . Portanto, se você deseja excluir arquivos de um modelo usando objects
métodos, deve escrever e usar um [Custom Manager] [3] (para substituir o delete()
método de objects
):
class CustomManager(models.Manager):
def delete(self):
for obj in self.get_queryset():
obj.delete()
Agora você deve excluir image
antes de excluir o próprio modelo e para atribuir o CustomManager
ao modelo, você deve inicializar objects
dentro de seu modelo:
class MyModel(models.Model):
image = models.ImageField(upload_to='/pictures/', blank=True)
objects = CustomManager() # add CustomManager to model
def delete(self, using=None, keep_parents=False):
objects = CustomManager() # just add this line of code inside of your model
def delete(self, using=None, keep_parents=False):
self.image.storage.delete(self.song.name)
super().delete()
Posso ter um caso especial, pois estou usando a opção upload_to em meu campo de arquivo com nomes de diretório dinâmico, mas a solução que encontrei foi usar os.rmdir.
Nos modelos:
import os
...
class Some_Model(models.Model):
save_path = models.CharField(max_length=50)
...
def delete(self, *args,**kwargs):
os.rmdir(os.path.join(settings.MEDIA_ROOT, self.save_path)
super(Some_Model,self).delete(*args, **kwargs)