ฉันจะให้ Django Admin ลบไฟล์ได้อย่างไรเมื่อฉันลบวัตถุออกจากฐานข้อมูล / รุ่น


86

ฉันใช้ 1.2.5 กับ ImageField มาตรฐานและใช้แบ็กเอนด์ที่เก็บข้อมูลในตัว อัปโหลดไฟล์ได้ดี แต่เมื่อฉันลบรายการจากผู้ดูแลระบบไฟล์จริงบนเซิร์ฟเวอร์จะไม่ลบ


อืมมันควรจะเป็นจริง ตรวจสอบสิทธิ์ของไฟล์ในโฟลเดอร์อัปโหลดของคุณ (เปลี่ยนเป็น 0777)
Torsten Engelbrecht

5
Django ลบคุณลักษณะการลบอัตโนมัติ (สำหรับ Googler ที่เห็นความคิดเห็นด้านบน)
ทำเครื่องหมาย

คำตอบ:


102

คุณสามารถรับ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)

11
อย่าลืมเพิ่มการตรวจสอบว่าinstance.fileฟิลด์ไม่ว่างเปล่าหรืออย่างน้อยก็สามารถ (อย่างน้อยลอง) เพื่อลบไดเร็กทอรี MEDIA_ROOT ทั้งหมด สิ่งนี้ใช้ได้แม้กระทั่งกับImageField(null=False)เขตข้อมูล
Antony Hatchkins

49
ขอบคุณ. โดยทั่วไปฉันขอแนะนำให้ใช้post_deleteสัญญาณเพราะปลอดภัยกว่าในกรณีที่การลบล้มเหลวไม่ว่าด้วยเหตุผลใดก็ตาม จากนั้นทั้งรุ่นจะไม่มีการลบไฟล์เพื่อให้ข้อมูลสอดคล้องกัน โปรดแก้ไขฉันหากความเข้าใจpost_deleteและpre_deleteสัญญาณของฉันไม่ถูกต้อง
toto_tico

9
โปรดทราบว่าสิ่งนี้จะไม่ลบไฟล์เก่าหากคุณแทนที่ไฟล์ในอินสแตนซ์แบบจำลอง
ทำเครื่องหมาย

3
สิ่งนี้ใช้ไม่ได้กับฉันใน Django 1.8 ผู้ดูแลระบบภายนอก มีวิธีใหม่ในการทำหรือไม่?
กาหลิบ

น่ากลัว กำลังมองหาสิ่งนี้มานาน
RL Shyam

46

ลองdjango-cleanup

pip install django-cleanup

settings.py

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

1
แพคเกจสุดยอดมาก ขอขอบคุณ! :)
BoJack Horseman

3
หลังจากการทดสอบแบบ จำกัด ฉันสามารถยืนยันได้ว่าแพ็คเกจนี้ยังใช้งานได้กับ Django 1.10
CoderGuy123

1
ดีมันง่ายมาก
Tunn

ดี. ใช้งานได้กับฉันใน 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.name storage.delete(name)
Sean Azlin

2
@Sean +1 ฉันใช้การปรับแต่งใน 1.7 เพื่อลบภาพขนาดย่อที่สร้างโดย django-imagekit บน S3 ผ่าน django-storages 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? กล่าวอีกนัยหนึ่งฉันเห็นว่าฉันสามารถลบไฟล์ได้ แต่คุณสามารถลบไฟล์เมื่อโมเดลที่มีฟิลด์รูปภาพถูกลบได้หรือไม่?
eugene

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: "ใน Django 1.3 เมื่อโมเดลถูกลบจะไม่มีการเรียกเมธอด Delete () ของ FileField หากคุณต้องการล้างไฟล์ที่ไม่มีที่มาคุณจะต้อง จะต้องจัดการด้วยตัวเอง (ตัวอย่างเช่นด้วยคำสั่งการจัดการแบบกำหนดเองที่สามารถเรียกใช้ด้วยตนเองหรือกำหนดให้ทำงานเป็นระยะ ๆ ผ่านเช่น cron)
darrinm

4
วิธีนี้ผิด! deleteไม่ได้ถูกเรียกเสมอไปเมื่อแถวถูกลบคุณต้องใช้สัญญาณ
lvella

14

คุณต้องลบไฟล์จริงทั้ง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()โดยค่าเริ่มต้นจะบันทึกโมเดลลงในฐานข้อมูลลองfile_field.delete(False) docs.djangoproject.com/th/1.3/ref/models/fields/…
Adam Jurczyk

3

อาจจะสายไปหน่อย แต่วิธีที่ง่ายที่สุดสำหรับฉันคือใช้สัญญาณ post_save เพียงจำไว้ว่าสัญญาณจะถูกลบออกแม้ในระหว่างกระบวนการลบ QuerySet แต่เมธอด [model] .delete () จะไม่ถูกลบออกในระหว่างกระบวนการลบ QuerySet ดังนั้นจึงไม่ใช่ตัวเลือกที่ดีที่สุดในการแทนที่

หลัก / 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

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

1

การใช้ post_delete เป็นวิธีที่ถูกต้องแน่นอน บางครั้งแม้ว่าจะมีข้อผิดพลาดเกิดขึ้นและไฟล์จะไม่ถูกลบ แน่นอนว่าคุณมีไฟล์เก่าจำนวนมากที่ไม่ได้ลบออกไปก่อนที่จะใช้ post_delete ฉันสร้างฟังก์ชันที่ลบไฟล์สำหรับอ็อบเจ็กต์โดยขึ้นอยู่กับว่าไฟล์ที่การอ้างอิงอ็อบเจ็กต์ไม่มีอยู่หรือไม่จากนั้นลบอ็อบเจ็กต์หากไฟล์ไม่มีอ็อบเจ็กต์จากนั้นลบด้วยเช่นกันมันสามารถลบตามแฟล็ก "แอ็คทีฟ" สำหรับ วัตถุ .. บางอย่างที่ฉันเพิ่มเข้าไปในโมเดลส่วนใหญ่ของฉัน คุณต้องส่งผ่านวัตถุที่คุณต้องการตรวจสอบเส้นทางไปยังไฟล์อ็อบเจ็กต์ฟิลด์ไฟล์และแฟล็กเพื่อลบอ็อบเจ็กต์ที่ไม่ใช้งาน:

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)

ฉันลืม "ตัวตน" ก่อนไฟล์ของฉันและมันก็ไม่ได้ผลเหมือนที่ดูในเนมสเปซส่วนกลาง



0

Django 2.x โซลูชัน:

ไม่จำเป็นต้องติดตั้งแพ็คเกจใด ๆ ! มันง่ายมากที่จะจัดการในDjango 22 ฉันได้ลองใช้วิธีแก้ปัญหาต่อไปนี้โดยใช้ Django 2 และ SFTP Storage (แต่ฉันคิดว่ามันจะใช้ได้กับที่เก็บข้อมูลใด ๆ )

ก่อนอื่นให้เขียนไฟล์ ผู้จัดการที่กำหนดเอง ดังนั้นหากคุณต้องการลบไฟล์ของโมเดลโดยใช้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.