Làm cách nào để yêu cầu Quản trị viên Django xóa tệp khi tôi xóa một đối tượng khỏi cơ sở dữ liệu / mô hình?


85

Tôi đang sử dụng 1.2.5 với ImageField tiêu chuẩn và sử dụng phần phụ trợ lưu trữ tích hợp sẵn. Tệp tải lên tốt nhưng khi tôi xóa mục nhập từ quản trị viên, tệp thực tế trên máy chủ không xóa.


Hm, thực ra thì phải. Kiểm tra quyền đối với tệp trên thư mục tải lên của bạn (đổi thành 0777).
Torsten Engelbrecht

5
Django đã gỡ bỏ tính năng xóa tự động (dành cho nhân viên Google xem bình luận trên).
Đánh dấu

Câu trả lời:


100

Bạn có thể nhận pre_deletehoặc post_deletesignal (xem nhận xét của @ toto_tico bên dưới) và gọi phương thức delete () trên đối tượng FileField, do đó (trong 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
Đảm bảo thêm dấu kiểm nếu instance.filetrường không trống hoặc nó có thể (ít nhất là thử) xóa toàn bộ thư mục MEDIA_ROOT. Điều này áp dụng ngay cả cho ImageField(null=False)các trường.
Antony Hatchkins

47
Cảm ơn. Nói chung, tôi khuyên bạn nên sử dụng post_deletetín hiệu vì nó an toàn hơn trong trường hợp xóa không thành công vì bất kỳ lý do gì. Sau đó, cả mô hình, tệp tin sẽ không bị xóa để giữ cho dữ liệu nhất quán. Vui lòng sửa cho tôi nếu sự hiểu biết post_deletepre_deletetín hiệu của tôi là sai.
toto_tico

9
Lưu ý rằng thao tác này không xóa tệp cũ nếu bạn thay thế tệp trên một phiên bản mô hình
Đánh dấu

3
Điều này không hoạt động đối với tôi trong Django 1.8 bên ngoài quản trị viên. Có một cách mới để làm điều đó?
caliph

tuyệt vời. đang tìm kiếm này trong một thời gian dài
RL Shyam

46

Thử django-cleanup

pip install django-cleanup

settings.py

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

1
Gói rất tuyệt vời. Cảm ơn bạn! :)
BoJack Horseman

3
Sau khi thử nghiệm giới hạn, tôi có thể xác nhận rằng gói này vẫn hoạt động cho Django 1.10.
CoderGuy123

1

Đẹp. Hoạt động cho tôi trên Django 2.0. Tôi cũng đang sử dụng S3 làm chương trình phụ trợ lưu trữ của mình ( django-storages.readthedocs.io/en/latest/backends/… ) và tôi rất vui khi xóa các tệp khỏi S3.
routeburn

35

Giải pháp Django 1.5: Tôi sử dụng post_delete vì nhiều lý do nội bộ ứng dụng của tôi.

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)

Tôi đã mắc kẹt cái này ở cuối tệp models.py.

các original_imagelĩnh vực là ImageFieldtrong tôi Photomô hình.


7
Đối với bất kỳ ai sử dụng Amazon S3 làm chương trình phụ trợ lưu trữ (thông qua django-storages), câu trả lời cụ thể này sẽ không hoạt động. Bạn sẽ nhận được thông báo NotImplementedError: This backend doesn't support absolute paths.Bạn có thể dễ dàng sửa lỗi này bằng cách chuyển tên của trường tệp storage.delete()thay vì đường dẫn của trường tệp. Ví dụ: thay thế hai dòng cuối cùng của câu trả lời này bằng storage, name = photo.original_image.storage, photo.original_image.namesau đó storage.delete(name).
Sean Azlin

2
@Sean +1, tôi đang sử dụng điều chỉnh đó trong 1.7 để xóa hình thu nhỏ được tạo bởi django-imagekit trên S3 thông qua django-storages. docs.djangoproject.com/en/dev/ref/files/storage/… . Lưu ý: Nếu bạn chỉ đang sử dụng ImageField (hoặc FileField), bạn có thể sử dụng mymodel.myimagefield.delete(save=False)thay thế. docs.djangoproject.com/en/dev/ref/files/file/…
user2616836

@ user2616836 Bạn có thể sử dụng mymodel.myimagefield.delete(save=False)trên post_deletekhông? Nói cách khác, tôi có thể thấy rằng tôi có thể xóa tệp, nhưng bạn có thể xóa tệp khi mô hình có trường hình ảnh bị xóa không?
eugene

1
@eugene Có bạn có thể, nó hoạt động (tôi không chắc tại sao mặc dù vậy). Trong post_deletebạn làm instance.myimagefield.delete(save=False), lưu ý việc sử dụng instance.
user2616836,

17

Mã này chạy tốt trên Django 1.4 cũng với bảng điều khiển Quản trị.

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)

Điều quan trọng là phải lấy được bộ nhớ và đường dẫn trước khi xóa mô hình, nếu không mô hình sau đó cũng sẽ vô hiệu nếu bị xóa.


3
Điều này không hiệu quả với tôi (Django 1.5) và Django 1.3 CHANGELOG cho biết: "Trong Django 1.3, khi một mô hình bị xóa, phương thức delete () của FileField sẽ không được gọi. Nếu bạn cần dọn dẹp các tệp mồ côi, bạn ' Tôi sẽ cần tự xử lý nó (ví dụ: với một lệnh quản lý tùy chỉnh có thể được chạy theo cách thủ công hoặc được lập lịch để chạy định kỳ thông qua ví dụ: cron). "
darrinm

4
Giải pháp này là sai! deletekhông phải lúc nào cũng được gọi khi một hàng bị xóa, bạn phải sử dụng các tín hiệu.
lvella

11

Bạn cần xóa tệp thực trên cả deleteupdate.

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)

Giải pháp tuyệt vời!
AlexKh

6

Bạn có thể cân nhắc sử dụng tín hiệu pre_delete hoặc post_delete:

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

Tất nhiên, các lý do tương tự khiến tính năng xóa tự động FileField bị xóa cũng được áp dụng ở đây. Nếu bạn xóa một tệp được tham chiếu ở một nơi khác, bạn sẽ gặp sự cố.

Trong trường hợp của tôi, điều này có vẻ phù hợp vì tôi có một mô hình Tệp chuyên dụng để quản lý tất cả các tệp của mình.

Lưu ý: Vì một số lý do post_delete dường như không hoạt động bình thường. Tệp đã bị xóa, nhưng bản ghi cơ sở dữ liệu vẫn còn, điều này hoàn toàn trái ngược với những gì tôi mong đợi, ngay cả trong các điều kiện lỗi. pre_delete hoạt động tốt mặc dù.


3
có lẽ post_deletesẽ không làm việc, vì file_field.delete()theo mặc định tiết kiệm mô hình để db, hãy thử file_field.delete(False) docs.djangoproject.com/en/1.3/ref/models/fields/...
Adam Jurczyk

3

Có lẽ hơi muộn. Nhưng cách dễ nhất đối với tôi là sử dụng tín hiệu post_save. Chỉ cần nhớ rằng các tín hiệu được xuất phát ngay cả trong quá trình xóa QuerySet, nhưng phương thức [model] .delete () không được cấp trong quá trình xóa QuerySet, vì vậy, nó không phải là tùy chọn tốt nhất để ghi đè nó.

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

1

Chức năng này sẽ bị loại bỏ trong Django 1.3 nên tôi sẽ không dựa vào nó.

Bạn có thể ghi đè deletephương thức của mô hình được đề cập để xóa tệp trước khi xóa hoàn toàn mục nhập khỏi cơ sở dữ liệu.

Biên tập:

Đây là một ví dụ nhanh.

class MyModel(models.Model):

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

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

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

Bạn có ví dụ về cách sử dụng nó trong một mô hình để xóa tệp không? Tôi đang xem tài liệu và thấy các ví dụ về cách xóa đối tượng khỏi cơ sở dữ liệu nhưng không thấy bất kỳ triển khai nào về xóa tệp.
narkeeso

2
Phương pháp này sai vì nó sẽ không hoạt động để xóa hàng loạt (như tính năng 'Xóa đã chọn' của quản trị viên). Ví dụ MyModel.objects.all()[0].delete()sẽ xóa tệp trong khi MyModel.objects.all().delete()sẽ không. Sử dụng các tín hiệu.
Antony Hatchkins

1

Sử dụng post_delete chắc chắn là cách đi đúng đắn. Đôi khi, mặc dù mọi thứ có thể sai và các tệp không bị xóa. Tất nhiên có trường hợp bạn có một loạt các tệp cũ chưa bị xóa trước khi post_delete được sử dụng. Tôi đã tạo một chức năng xóa tệp cho các đối tượng dựa trên nếu tệp mà đối tượng tham chiếu không tồn tại thì xóa đối tượng, nếu tệp không có đối tượng, thì cũng xóa, cũng có thể xóa dựa trên cờ "hoạt động" cho một đối tượng .. Một cái gì đó tôi đã thêm vào hầu hết các mô hình của mình. Bạn phải chuyển cho nó các đối tượng bạn muốn kiểm tra, đường dẫn đến tệp đối tượng, trường tệp và cờ để xóa các đối tượng không hoạt động:

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

Đây là cách bạn có thể sử dụng:

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

đảm bảo rằng bạn viết " self " trước tệp. vì vậy ví dụ trên phải là

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

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

Tôi đã quên "bản thân" trước tệp của mình và điều đó không hoạt động khi nó đang tìm kiếm trong không gian tên chung.



0

Giải pháp Django 2.x:

Không cần cài đặt bất kỳ gói nào! Nó rất dễ dàng để xử lý trong Django 2 . Tôi đã thử giải pháp sau bằng cách sử dụng Django 2 và SFTP Storage (tuy nhiên tôi nghĩ nó sẽ hoạt động với bất kỳ bộ lưu trữ nào)

Đầu tiên hãy viết Trình quản lý tùy chỉnh . Vì vậy, nếu bạn muốn có thể xóa các tệp của một mô hình bằng objectscác phương thức, bạn phải viết và sử dụng [Trình quản lý tùy chỉnh] [3] (đối với delete()phương pháp ghi đè của objects):

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

Bây giờ bạn phải xóa imagetrước khi xóa xóa chính mô hình và để gán CustomManagermô hình đó, bạn phải ký tắt objectsbên trong mô hình của mình:

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

Tôi có thể gặp trường hợp đặc biệt vì tôi đang sử dụng tùy chọn upload_to trên trường tệp của mình với các tên thư mục động nhưng giải pháp tôi tìm thấy là sử dụng os.rmdir.

Trong các mô hình:

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
Đây là một ý kiến ​​tồi. Bạn không chỉ xóa toàn bộ thư mục so với một tệp duy nhất (có khả năng ảnh hưởng đến các tệp khác), bạn sẽ làm như vậy ngay cả khi việc xóa đối tượng thực sự không thành công.
tbm 14/02/17

Đó không phải là một ý tưởng tồi nếu bạn đang giải quyết vấn đề mà tôi gặp phải;) Như tôi đã đề cập, tôi có một trường hợp sử dụng duy nhất trong đó mô hình bị xóa là mô hình mẹ. Trẻ em đã ghi tệp vào thư mục mẹ và vì vậy nếu bạn xóa tệp gốc, hành vi mong muốn là tất cả các tệp trong thư mục đã bị xóa. Tuy nhiên, điểm tốt về thứ tự hoạt động. Điều đó đã không xảy ra với tôi vào thời điểm đó.
carruthd

Tôi vẫn muốn xóa các tệp con riêng lẻ khi một tệp con bị xóa; sau đó nếu bạn cần, bạn có thể xóa thư mục mẹ khi nó trống.
tbm 16/02/17

Điều đó có ý nghĩa khi bạn đang lấy ra các đối tượng con nhưng nếu đối tượng chính bị phá hủy, việc bước qua các đối tượng con một lần có vẻ tẻ nhạt và không cần thiết. Bất chấp, tôi thấy rằng câu trả lời tôi đưa ra không đủ cụ thể cho câu hỏi OP. Cảm ơn bạn vì những lời nhận xét, bạn đã khiến tôi suy nghĩ về việc sử dụng một công cụ ít cùn hơn trong tương lai.
carruthd 17/02/17
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.