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


85

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


흠, 실제로 그래야합니다. 업로드 폴더에 대한 파일 권한을 확인하십시오 (0777로 변경).
Torsten Engelbrecht 2011 년

5
Django는 자동 삭제 기능을 제거했습니다 (위의 설명을 보는 Google 직원을 위해).
마크

답변:


100

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)

10
instance.file필드가 비어 있지 않거나 전체 MEDIA_ROOT 디렉토리를 삭제할 수 있는지 (적어도 시도) 확인을 추가해야합니다 . 이것은 ImageField(null=False)필드 에도 적용됩니다 .
Antony Hatchkins 2013

47
감사. 일반적으로 post_delete어떤 이유로 든 삭제가 실패하는 경우 더 안전하므로 신호 를 사용하는 것이 좋습니다 . 그런 다음 모델도 파일도 삭제되지 않고 데이터의 일관성을 유지합니다. 내 이해 post_deletepre_delete신호가 잘못된 경우 나를 수정하십시오 .
toto_tico 2013

9
당신이 모델 인스턴스의 파일로 교체하면이 이전 파일을 삭제하지 않습니다
마크

3
이것은 관리자 외부의 Django 1.8에서 나를 위해 작동하지 않습니다. 새로운 방법이 있습니까?
caliph

대박. 오랜 시간이 찾고 있었다
RL 시암에게

46

django-cleanup 시도

pip install django-cleanup

settings.py

INSTALLED_APPS = (
    ...
    'django_cleanup', # should go after your apps
)

1
아주 멋진 패키지. 감사합니다! :)
BoJack Horseman 2015

3
제한된 테스트 후에이 패키지가 여전히 Django 1.10에서 작동하는지 확인할 수 있습니다.
CoderGuy123

1
좋아요, 이것은 너무 쉽습니다
Tunn dec.

좋은. Django 2.0에서 나를 위해 작동합니다. 또한 S3를 스토리지 백엔드 ( django-storages.readthedocs.io/en/latest/backends/… )로 사용하고 있으며 S3에서 파일을 행복하게 삭제하고 있습니다.
Routeburn

35

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


7
Amazon S3를 스토리지 백엔드로 사용하는 사람에게는 (django-storages를 통해)이 특정 답변이 작동하지 않습니다. 당신은 얻을 것이다 NotImplementedError: This backend doesn't support absolute paths.에 파일 필드의 이름을 전달하여 쉽게이 문제를 해결할 수 있습니다 storage.delete()대신 파일 필드의 경로. 예를 들어,이 답변의 마지막 두 줄을 storage, name = photo.original_image.storage, photo.original_image.namethen 로 바꿉니다 storage.delete(name).
Sean Azlin 2014-08-26

2
@Sean +1, 1.7의 조정을 사용하여 django-storages를 통해 S3의 django-imagekit에서 생성 한 썸네일을 삭제합니다. docs.djangoproject.com/en/dev/ref/files/storage/... . 참고 : 단순히 ImageField (또는 FileField)를 사용 mymodel.myimagefield.delete(save=False)하는 경우 대신 사용할 수 있습니다 . docs.djangoproject.com/en/dev/ref/files/file/…
user2616836

@ user2616836 당신이 사용할 수 mymodel.myimagefield.delete(save=False)post_delete? 즉, 파일을 삭제할 수 있다는 것을 알 수 있지만 이미지 필드가있는 모델이 삭제되면 파일을 삭제할 수 있습니까?
유진

1
@eugene 예, 가능합니다 (왜 그런지 모르겠습니다). 에서 post_delete당신이 할 instance.myimagefield.delete(save=False)의 사용을주의 instance.
user2616836

17

이 코드는 관리자 패널에서도 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)

모델을 삭제하기 전에 스토리지와 경로를 가져 오는 것이 중요합니다. 그렇지 않으면 모델이 삭제 된 경우에도 무효가 유지됩니다.


3
이것은 나를 위해 작동하지 않습니다 (Django 1.5) 및 Django 1.3 CHANGELOG 상태 : "Django 1.3에서 모델이 삭제되면 FileField의 delete () 메서드가 호출되지 않습니다. 분리 된 파일을 정리해야하는 경우에는 ' 직접 처리해야합니다 (예 : 수동으로 실행하거나 cron을 통해 주기적으로 실행하도록 예약 할 수있는 사용자 지정 관리 명령 사용). "
darrinm

4
이 솔루션은 잘못되었습니다! delete행이 삭제 될 때 항상 호출되는 것은 아닙니다. 신호를 사용해야합니다.
lvella 2013

11

당신은 필요 에 모두 실제 파일을 제거 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)

훌륭한 솔루션!
AlexKh

6

pre_delete 또는 post_delete 신호 사용을 고려할 수 있습니다.

https://docs.djangoproject.com/en/dev/topics/signals/

물론 FileField 자동 삭제가 제거 된 동일한 이유가 여기에도 적용됩니다. 다른 곳에서 참조되는 파일을 삭제하면 문제가 발생합니다.

제 경우에는 모든 파일을 관리하는 전용 파일 모델이 있었기 때문에 이것이 적절 해 보였습니다.

참고 : 어떤 이유로 post_delete가 제대로 작동하지 않는 것 같습니다. 파일은 삭제되었지만 데이터베이스 레코드는 그대로 유지되었으므로 오류 조건에서도 예상했던 것과 완전히 반대입니다. pre_delete는 잘 작동합니다.


3
아마 post_delete때문에하지 작업합니다 file_field.delete()기본적으로 DB에 모델을 절약하려고 file_field.delete(False) docs.djangoproject.com/en/1.3/ref/models/fields/...
아담 Jurczyk입니다

3

조금 늦었을지도 모릅니다. 하지만 가장 쉬운 방법은 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 / signals.py

import os

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

1

이 기능은 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)

파일을 삭제하기 위해 모델에서 사용하는 방법에 대한 예가 있습니까? 문서를보고 데이터베이스에서 개체를 제거하는 방법에 대한 예제를보고 있지만 파일 삭제에 대한 구현은 볼 수 없습니다.
narkeeso

2
이 방법은 관리자의 '선택한 항목 삭제'기능과 같은 대량 삭제에는 작동하지 않기 때문에 잘못되었습니다. 예를 들어 MyModel.objects.all()[0].delete()파일을 삭제하지만 MyModel.objects.all().delete()그렇지 않습니다. 신호를 사용하십시오.
Antony Hatchkins 2013

1

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

0

파일 앞에 " self " 를 써야 합니다. 따라서 위의 예는

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

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

내 파일 앞에있는 "self"를 잊어 버렸고 전역 네임 스페이스에서 검색 할 때 작동하지 않았습니다.



0

Django 2.x 솔루션 :

패키지를 설치할 필요가 없습니다! Django 2 에서 처리하는 것은 매우 쉽습니다. . Django 2 및 SFTP Storage를 사용하여 다음 솔루션을 시도했습니다 (그러나 모든 저장소에서 작동한다고 생각합니다)

먼저 Custom Manager를 작성하십시오 . 따라서 objects메서드 를 사용하여 모델의 파일을 삭제하려면 [Custom Manager] [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()

-1

동적 디렉토리 이름으로 파일 필드에 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)

1
이것은 매우 나쁜 생각입니다. 전체 디렉터리와 단일 파일 (다른 파일에 영향을 미칠 수 있음)을 제거 할뿐만 아니라 실제 개체 삭제가 실패하더라도 제거합니다.
tbm

내가 가진 문제에 대해 작업하고 있다면 나쁘지 않습니다.) 내가 언급했듯이 삭제되는 모델이 상위 모델 인 고유 한 사용 사례가 있습니다. 자녀는 부모 폴더에 파일을 썼으므로 부모를 삭제 한 경우 원하는 동작은 폴더의 모든 파일이 삭제되는 것입니다. 그래도 작업 순서에 대한 좋은 지적입니다. 당시에는 그런 일이 없었습니다.
carruthd

하위 항목이 삭제 될 때 개별 하위 파일을 제거하고 싶습니다. 그런 다음 필요한 경우 비어있는 상위 디렉토리를 제거 할 수 있습니다.
tbm

그것은 당신이 자식 개체를 꺼내는 것처럼 의미가 있지만 부모 개체가 파괴되면 한 번에 하나씩 자식을 밟는 것이 지루하고 불필요 해 보입니다. 그럼에도 불구하고 내가 준 대답이 OP 질문에 충분히 구체적이지 않은 것을 알았습니다. 의견을 보내 주셔서 감사합니다. 앞으로 덜 무딘 도구를 사용하는 것에 대해 생각하게 만들었습니다.
carruthd
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.