developer tip

데이터베이스 / 모델에서 개체를 제거 할 때 Django 관리자가 파일을 삭제하도록하려면 어떻게해야합니까?

optionbox 2020. 9. 18. 08:04
반응형

데이터베이스 / 모델에서 개체를 제거 할 때 Django 관리자가 파일을 삭제하도록하려면 어떻게해야합니까?


표준 ImageField와 함께 1.2.5를 사용하고 내장 스토리지 백엔드를 사용하고 있습니다. 파일은 잘 업로드되지만 관리자에서 항목을 제거하면 서버의 실제 파일이 삭제되지 않습니다.


pre_delete또는 post_delete신호를 수신하고 (아래 @toto_tico의 주석 참조) FileField 객체 에서 delete () 메서드를 호출 할 수 있습니다. 따라서 (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)

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모델.


이 코드는 관리자 패널과 함께 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하고 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는 잘 작동합니다.


Maybe it's a little late. But the easiest way for me is to use a post_save signal. Just to remember that signals are excecuted even during a QuerySet delete process, but the [model].delete() method is not excecuted during the QuerySet delete process, so it's not the best option to override it.

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/signals.py

import os

def delete_image_slide(sender, **kwargs):
    slide = kwargs.get('instance')
    try:
        os.remove(slide.image.path)
    except:
        pass

This functionality will be removed in Django 1.3 so I wouldn't rely on it.

You could override the delete method of the model in question to delete the file before removing the entry from the database completely.

Edit:

Here is a quick example.

class MyModel(models.Model):

    self.somefile = models.FileField(...)

    def delete(self, *args, **kwargs):
        somefile.delete()

        super(MyModel, self).delete(*args, **kwargs)

Using the post_delete is for sure the right way to go. Sometimes though things can go wrong, and files don't get deleted. There is of course the case that you have a bunch of old files that weren't deleted before post_delete was used. I created a function that deletes files for objects based on if the file the object references does not exist then delete object, if the file does not have an object, then also delete, also it can delete based on an "active" flag for an object.. Something I added to most of my models. You have to pass it the objects you want to check, the path to the objects files, the file field and a flag to delete inactive objects:

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."

This is how you can use this:

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

make sure you write "self" before the file. so example above should be

def delete(self, *args, **kwargs):
        self.somefile.delete()

        super(MyModel, self).delete(*args, **kwargs)

I've forgotten the "self" before my file and that didn't work as it was looking in the global namespace.


If you already have number of unused files in your project and want to delete them, you can use django utility django-unused-media


I may have a special case since I am using the upload_to option on my file field with dynamic directory names but the solution I found was to use os.rmdir.

In models:

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)

참고URL : https://stackoverflow.com/questions/5372934/how-do-i-get-django-admin-to-delete-files-when-i-remove-an-object-from-the-datab

반응형