Я використовую 1.2.5 із стандартним ImageField та використовую вбудований серверний сервер. Файли завантажуються нормально, але коли я видаляю запис від адміністратора, фактичний файл на сервері не видаляється.
Я використовую 1.2.5 із стандартним ImageField та використовую вбудований серверний сервер. Файли завантажуються нормально, але коли я видаляю запис від адміністратора, фактичний файл на сервері не видаляється.
Відповіді:
Ви можете отримати сигнал pre_delete
або post_delete
(див. Коментар @ toto_tico нижче) і викликати метод delete () на об'єкті FileField, таким чином (у 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
поле не порожнє, або воно може (принаймні спробувати) видалити весь каталог MEDIA_ROOT. Це стосується навіть ImageField(null=False)
полів.
post_delete
сигнал, оскільки він безпечніший у випадку невдалого видалення з будь-якої причини. Тоді ні модель, ні файл не буде видалено, зберігаючи дані послідовними. Будь ласка, виправте мене, якщо моє розуміння post_delete
та pre_delete
сигнали неправильні.
Спробуйте django-cleanup
pip install django-cleanup
settings.py
INSTALLED_APPS = (
...
'django_cleanup', # should go after your apps
)
Рішення Django 1.5: я використовую post_delete з різних причин, які є внутрішніми для мого додатка.
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)
Я закріпив це внизу файлу models.py.
original_image
поле є ImageField
в моїй Photo
моделі.
NotImplementedError: This backend doesn't support absolute paths.
Ви можете легко це виправити, передавши ім'я поля файлу storage.delete()
замість шляху поля файлу. Наприклад, замініть останні два рядки цієї відповіді на storage, name = photo.original_image.storage, photo.original_image.name
then storage.delete(name)
.
mymodel.myimagefield.delete(save=False)
. docs.djangoproject.com/en/dev/ref/files/file/…
mymodel.myimagefield.delete(save=False)
на post_delete
? Іншими словами, я бачу, що можу видалити файл, але чи можете ви видалити файл, коли модель, що має поле зображення, буде видалена?
post_delete
цьому instance.myimagefield.delete(save=False)
зверніть увагу на використання instance
.
Цей код добре працює на Django 1.4, також із панеллю адміністратора.
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)
Важливо отримати пам'ять і шлях перед тим, як видалити модель, інакше вона також зберігатиметься недійсною, якщо її видалити.
delete
не завжди викликається при видаленні рядка, ви повинні використовувати сигнали.
Вам потрібно видалити фактичний файл на обох delete
і 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)
Ви можете розглянути можливість використання сигналу pre_delete або post_delete:
https://docs.djangoproject.com/en/dev/topics/signals/
Звичайно, ті самі причини, через які було видалено автоматичне видалення FileField, також застосовуються тут. Якщо ви видалите файл, на який є посилання в іншому місці, у вас будуть проблеми.
У моєму випадку це здавалося доречним, оскільки у мене була спеціальна модель файлів для управління всіма моїми файлами.
Примітка: З якихось причин post_delete, здається, не працює належним чином. Файл було видалено, але запис бази даних залишився, що абсолютно протилежне тому, що я очікував, навіть за умов помилки. pre_delete працює добре.
post_delete
, не буде працювати, тому що file_field.delete()
за замовчуванням зберігає модель у базі даних, спробуйте file_field.delete(False)
docs.djangoproject.com/en/1.3/ref/models/fields/…
Можливо, це трохи пізно. Але найпростіший спосіб для мене - використовувати сигнал post_save. Тільки щоб пам’ятати, що сигнали виконуються навіть під час процесу видалення QuerySet, але метод [model] .delete () не виконується під час процесу видалення QuerySet, тому це не найкращий варіант, щоб його замінити.
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 / signal.py
import os
def delete_image_slide(sender, **kwargs):
slide = kwargs.get('instance')
try:
os.remove(slide.image.path)
except:
pass
Ця функція буде вилучена в Django 1.3, тому я не покладався на неї.
Ви можете замінити delete
метод даної моделі, щоб видалити файл, перш ніж повністю видалити запис із бази даних.
Редагувати:
Ось короткий приклад.
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()
, файл буде видалено, тоді як MyModel.objects.all().delete()
ні. Використовуйте сигнали.
Використання post_delete - це, безсумнівно, правильний шлях. Іноді, хоча все може піти не так, і файли не видаляються. Звичайно, у вас є купа старих файлів, які не були видалені до використання post_delete. Я створив функцію, яка видаляє файли для об'єктів на основі, якщо файл, на який посилання на об'єкт не існує, видаліть об'єкт, якщо файл не має об'єкта, то також видаліть, також він може видалити на основі "активного" прапора для object .. Щось, що я додав до більшості своїх моделей. Вам потрібно передати йому об’єкти, які потрібно перевірити, шлях до файлів об’єктів, поле файлу та прапор для видалення неактивних об’єктів:
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."
Ось як ви можете використовувати це:
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
переконайтеся, що перед файлом пишете " self ". так приклад вище повинен бути
def delete(self, *args, **kwargs):
self.somefile.delete()
super(MyModel, self).delete(*args, **kwargs)
Я забув "self" перед своїм файлом, і це не спрацювало, оскільки воно виглядало у глобальному просторі імен.
Якщо у вашому проекті вже є кількість невикористаних файлів і ви хочете їх видалити, ви можете скористатися утилітою django django-unused-media
Не потрібно встановлювати будь-які пакунки! У Django 2 це дуже просто . Я спробував наступне рішення, використовуючи Django 2 і SFTP Storage (однак, я думаю, це буде працювати з будь-якими сховищами)
Спочатку напишіть Custom Manager . Отже, якщо ви хочете мати можливість видаляти файли моделі за допомогою objects
методів, ви повинні написати та використовувати [Спеціальний менеджер] [3] (для перевизначення delete()
методу objects
):
class CustomManager(models.Manager):
def delete(self):
for obj in self.get_queryset():
obj.delete()
Тепер ви повинні видалити image
перед видаленням самої моделі, а для присвоєння CustomManager
моделі потрібно зробити початковий знак objects
всередині своєї моделі:
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()
У мене може бути особливий випадок, оскільки я використовую опцію upload_to у своєму полі файлу з динамічними іменами каталогів, але вирішенням, яке я знайшов, було використання os.rmdir.
У моделях:
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)