장고 파일 필드 삭제


94

Django에서 웹 앱을 만들고 있습니다. 파일을 업로드하는 모델이 있는데 삭제할 수 없습니다. 내 코드는 다음과 같습니다.

class Song(models.Model):
    name = models.CharField(blank=True, max_length=100)
    author = models.ForeignKey(User, to_field='id', related_name="id_user2")
    song = models.FileField(upload_to='/songs/')
    image = models.ImageField(upload_to='/pictures/', blank=True)
    date_upload = models.DateField(auto_now_add=True)

    def delete(self, *args, **kwargs):
        # You have to prepare what you need before delete the model
        storage, path = self.song.storage, self.song.path
        # Delete the model before the file
        super(Song, self).delete(*args, **kwargs)
        # Delete the file after the model
        storage.delete(path)

그런 다음 "python manage.py shell"에서 다음을 수행합니다.

song = Song.objects.get(pk=1)
song.delete()

데이터베이스에서는 삭제되지만 서버의 파일은 삭제되지 않습니다. 또 무엇을 시도 할 수 있습니까?

감사!


default_storage를 직접 사용하는 것은 어떻습니까? docs.djangoproject.com/en/dev/topics/files
MGP

답변:


142

Django 1.3 이전에는 해당 모델 인스턴스를 삭제할 때 파일 시스템에서 파일이 자동으로 삭제되었습니다. 아마도 최신 Django 버전을 사용하고있을 것이므로 파일 시스템에서 파일 삭제를 직접 구현해야합니다.

몇 가지 방법으로이를 수행 할 수 있으며 그 중 하나는 pre_delete또는 post_delete신호를 사용하는 것 입니다.

선택의 나의 방법은 현재의 혼합이다 post_delete그리고 pre_save그 오래된 파일은 해당 모델이 삭제 될 때마다 삭제 또는 파일이 변경이되어 있도록한다 신호.

가상 MediaFile모델을 기반으로 :

import os
import uuid

from django.db import models
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _


class MediaFile(models.Model):
    file = models.FileField(_("file"),
        upload_to=lambda instance, filename: str(uuid.uuid4()))


# These two auto-delete files from filesystem when they are unneeded:

@receiver(models.signals.post_delete, sender=MediaFile)
def auto_delete_file_on_delete(sender, instance, **kwargs):
    """
    Deletes file from filesystem
    when corresponding `MediaFile` object is deleted.
    """
    if instance.file:
        if os.path.isfile(instance.file.path):
            os.remove(instance.file.path)

@receiver(models.signals.pre_save, sender=MediaFile)
def auto_delete_file_on_change(sender, instance, **kwargs):
    """
    Deletes old file from filesystem
    when corresponding `MediaFile` object is updated
    with new file.
    """
    if not instance.pk:
        return False

    try:
        old_file = MediaFile.objects.get(pk=instance.pk).file
    except MediaFile.DoesNotExist:
        return False

    new_file = instance.file
    if not old_file == new_file:
        if os.path.isfile(old_file.path):
            os.remove(old_file.path)
  • 엣지 케이스 : 앱이 새 파일을 업로드하고 호출하지 않고 모델 인스턴스를 새 파일로 지정하면 save()(예 : 대량 업데이트 QuerySet) 신호가 실행되지 않기 때문에 이전 파일이 계속 거짓말을합니다. 일반적인 파일 처리 방법을 사용하는 경우에는 발생하지 않습니다.
  • 내가 만든 앱 중 하나가이 코드를 프로덕션에 포함하고 있지만 그럼에도 불구하고 위험을 감수해야한다고 생각합니다.
  • 코딩 스타일 :이 예제는 file기본 제공 file개체 식별자 와 충돌하기 때문에 좋은 스타일이 아닌 필드 이름으로 사용 합니다.

또한보십시오

  • FieldFile.delete()Django 1.11 모델 필드 참조 ( FieldFile클래스를 설명 하지만 .delete()필드에서 직접 호출 합니다. FileField인스턴스는 해당 FieldFile인스턴스에 프록시를 사용하고 필드의 것처럼 메서드에 액세스합니다)

    모델을 삭제해도 관련 파일은 삭제되지 않습니다. 고아 파일을 정리해야하는 경우 직접 처리해야합니다 (예 : 수동으로 실행하거나 예를 들어 cron을 통해 주기적으로 실행하도록 예약 할 수있는 사용자 지정 관리 명령 사용).

  • Django가 파일을 자동으로 삭제하지 않는 이유 : Django 1.3 릴리스 정보 항목

    이전 Django 버전에서는를 포함하는 모델 인스턴스 FileField가 삭제 FileField되었을 때 자체적으로 백엔드 저장소에서 파일을 삭제했습니다. 이로 인해 롤백 된 트랜잭션과 동일한 파일을 참조하는 서로 다른 모델의 필드를 포함하여 여러 데이터 손실 시나리오의 문이 열렸습니다. Django 1.3에서는 모델이 삭제 될 때 FileFielddelete()메서드가 호출되지 않습니다. 분리 된 파일을 정리해야하는 경우 직접 처리해야합니다 (예 : 수동으로 실행하거나 예를 들어 cron을 통해 주기적으로 실행하도록 예약 할 수있는 사용자 지정 관리 명령 사용).

  • pre_delete신호 만 사용하는 예


2
예,하지만 적절한 확인을해야합니다. (
잠시만 기다려

7
instance.song.delete(save=False)올바른 django 스토리지 엔진을 사용하기 때문에를 사용하는 것이 좋습니다 .
Eduardo

1
요즘에는 코드를 복사하는 경우가 드물며 SO에서 직접 작성할 수 없었으며 제한된 수정으로 작동합니다. 환상적인 도움, 감사합니다!
GJStein

여기에서 인스턴스가 있지만 이전에 이미지가 저장 되지 않은 경우 오류가 발생하여 os.path.isfile(old_file.path)실패 하는 버그를 발견했습니다 old_file.path(필드와 관련된 파일 없음). 에 if old_file:대한 호출 직전 에 추가하여 문제 를 해결했습니다 os.path.isfile().
three_pineapples

@three_pineapples 말이 되네요. 파일 필드에 대한 NOT NULL 제약 조건이 무시되었거나 특정 지점에서 종료되지 않았을 수 있습니다.이 경우 일부 개체는 비어있게됩니다.
Anton Strogonoff

79

django-cleanup을 시도 하면 모델을 제거 할 때 FileField에서 delete 메서드를 자동으로 호출합니다.

pip install django-cleanup

settings.py

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

좋습니다. 기본적으로 FileField에 추가해야합니다. 감사합니다!
megajoe 19-04-26

이 파일을 삭제되는 반면, 업로드 아니라
치라 SONI

와. 나는 이런 일이 일어나지 않도록 노력하고 있었는데 그 이유를 알 수 없었습니다. 누군가가 몇 년 전에 설치했고 잊어 버렸다. 감사.
ryan28561

4
그렇다면 Django가 처음에 파일 필드 삭제 기능을 제거한 이유는 무엇입니까?
ha-neul

당신은 전설입니다!
marlonjd

33

.deleteDjango> = 1.10에서 아래와 같이 파일 필드의 호출 방법으로 파일 시스템에서 파일을 삭제할 수 있습니다 .

obj = Song.objects.get(pk=1)
obj.song.delete()

7
받아 들여진 대답이어야하며 간단하고 제대로 작동합니다.
Nikolay Shindarov

14

또한 모델의 삭제 기능을 덮어 쓰기 만하면 파일이 있는지 확인하고 super 기능을 호출하기 전에 삭제할 수 있습니다.

import os

class Excel(models.Model):
    upload_file = models.FileField(upload_to='/excels/', blank =True)   
    uploaded_on = models.DateTimeField(editable=False)


    def delete(self,*args,**kwargs):
        if os.path.isfile(self.upload_file.path):
            os.remove(self.upload_file.path)

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

8
queryset.delete()이 솔루션으로 전화 를 걸어도 파일이 정리되지는 않습니다. 쿼리 세트를 반복하고 .delete()각 개체를 호출해야 합니다.
Scott Woodall 2015

저는 Django를 처음 사용합니다. 이것은 좋지만 모델이 delete 메서드를 재정의 한 추상 클래스에서 상속하는 경우 어떻게 이것이 추상 클래스에서 재정의되지 않을까요? 신호를 사용하여 나에게 잘 나타납니다
theTypan

8

Django 2.x 솔루션 :

Django 2 에서 파일 삭제를 처리하는 것은 매우 쉽습니다 . Django 2 및 SFTP Storage 및 FTP STORAGE를 사용하여 다음 솔루션을 시도해 보았으며 delete방법 을 구현 한 다른 저장소 관리자와 함께 작동 할 것이라고 확신합니다 . ( delete방법은 storage추상적 인 방법 중 하나입니다 .)

delete인스턴스가 자신을 삭제하기 전에 파일 필드를 삭제하는 방식으로 모델 의 메서드를 재정의합니다 .

class Song(models.Model):
    name = models.CharField(blank=True, max_length=100)
    author = models.ForeignKey(User, to_field='id', related_name="id_user2")
    song = models.FileField(upload_to='/songs/')
    image = models.ImageField(upload_to='/pictures/', blank=True)
    date_upload = models.DateField(auto_now_add=True)

    def delete(self, using=None, keep_parents=False):
        self.song.storage.delete(self.song.name)
        self.image.storage.delete(self.song.name)
        super().delete()

그것은 나를 위해 매우 쉽게 작동합니다. 삭제하기 전에 파일이 있는지 확인하려면 storage.exists. 예를 들어 노래가 존재 self.song.storage.exists(self.song.name)하면 boolean대표 를 반환 합니다. 따라서 다음과 같이 보일 것입니다.

def delete(self, using=None, keep_parents=False):
    # assuming that you use same storage for all files in this model:
    storage = self.song.storage

    if storage.exists(self.song.name):
        storage.delete(self.song.name)

    if storage.exists(self.image.name):
        storage.delete(self.song.name)

    super().delete()

편집 (추가) :

으로 @HeyMan가 언급 한이 솔루션의 호출로 Song.objects.all().delete()파일을 삭제하지 않습니다! 이것은 기본 관리자의Song.objects.all().delete() 삭제 쿼리를 실행 중이기 때문에 발생 합니다. 따라서 메서드 를 사용하여 모델의 파일을 삭제하려면 Custom Manager를 작성하고 사용해야합니다 (삭제 쿼리를 재정의하기 위해).objects

class CustomManager(models.Manager):
    def delete(self):
        for obj in self.get_queryset():
            obj.delete()

CustomManager모델 에 할당하려면 모델 objects내부에서 이니셜을 작성해야합니다 .

class Song(models.Model):
    name = models.CharField(blank=True, max_length=100)
    author = models.ForeignKey(User, to_field='id', related_name="id_user2")
    song = models.FileField(upload_to='/songs/')
    image = models.ImageField(upload_to='/pictures/', blank=True)
    date_upload = models.DateField(auto_now_add=True)
    
    objects = CustomManager() # just add this line of code inside of your model

    def delete(self, using=None, keep_parents=False):
        self.song.storage.delete(self.song.name)
        self.image.storage.delete(self.song.name)
        super().delete()

이제 .delete()모든 objects하위 쿼리 끝에 사용할 수 있습니다 . 가장 간단한을 작성 CustomManager했지만 삭제 한 객체 또는 원하는 항목에 대해 반환하면 더 잘 할 수 있습니다.


1
네, 질문을 올린 이후로 그들이 그 기능을 추가했다고 생각합니다.
Marcos Aguayo

1
여전히 삭제는 Song.objects.all (). delete ()를 호출하는 wenn이 아닙니다. on_delete = models.CASCADE에 의해 인스턴스가 삭제되는 경우에도 동일합니다.
HeyMan

@HeyMan 나는 그것을 해결하고 지금 내 솔루션을 편집했습니다 :)
Hamidreza

4

다음은 모델이 삭제되거나 새 파일이 업로드 될 때마다 이전 파일을 제거하는 앱입니다. django-smartfields

from django.db import models
from smartfields import fields

class Song(models.Model):
    song = fields.FileField(upload_to='/songs/')
    image = fields.ImageField(upload_to='/pictures/', blank=True)

3

@Anton Strogonoff

파일 변경시 코드에서 누락 된 내용이 있습니다. 새 파일을 만들면 오류가 발생합니다. 새 파일이기 때문에 경로를 찾지 못했습니다. 기능 코드를 수정하고 try / except 문장을 추가했는데 잘 작동합니다.

@receiver(models.signals.pre_save, sender=MediaFile)
def auto_delete_file_on_change(sender, instance, **kwargs):
    """Deletes file from filesystem
    when corresponding `MediaFile` object is changed.
    """
    if not instance.pk:
        return False

    try:
        old_file = MediaFile.objects.get(pk=instance.pk).file
    except MediaFile.DoesNotExist:
        return False

    new_file = instance.file
    if not old_file == new_file:
        try:
            if os.path.isfile(old_file.path):
                os.remove(old_file.path)
        except Exception:
            return False

제 코드의 버그이거나 Django에서 변경된 내용 일 수 있습니다. try:그래도 블록 에서 특정 예외를 잡는 것이 좋습니다 ( AttributeError아마도?).
Anton Strogonoff

다른 스토리지 (예 : Amazon S3)로 마이그레이션하면 문제가 발생하므로 os 라이브러리를 사용하는 것은 좋지 않습니다.
Igor Pomaranskiy 2014 년

@IgorPomaranskiy os.remove를 사용할 때 Amazon S3와 같은 스토리지에서 어떤 일이 발생합니까 ??
Daniel González Fernández

@ DanielGonzálezFernández 나는 그것이 실패 할 것이라고 생각합니다 (존재하지 않는 경로에 관한 오류와 같은 오류로). 이것이 Django가 저장소에 추상화를 사용하는 이유입니다.
Igor Pomaranskiy

0

이 코드는 새 이미지 (로고 필드)를 업로드 할 때마다 실행되고 로고가 이미 존재하는지 확인한 후 닫고 디스크에서 제거합니다. 물론 수신기 기능에서도 동일한 절차를 수행 할 수 있습니다. 도움이 되었기를 바랍니다.

 #  Returns the file path with a folder named by the company under /media/uploads
    def logo_file_path(instance, filename):
        company_instance = Company.objects.get(pk=instance.pk)
        if company_instance.logo:
            logo = company_instance.logo
            if logo.file:
                if os.path.isfile(logo.path):
                    logo.file.close()
                    os.remove(logo.path)

        return 'uploads/{0}/{1}'.format(instance.name.lower(), filename)


    class Company(models.Model):
        name = models.CharField(_("Company"), null=False, blank=False, unique=True, max_length=100) 
        logo = models.ImageField(upload_to=logo_file_path, default='')
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.