Câu hỏi Làm thế nào để tôi có được Django Admin để xóa các tập tin khi tôi loại bỏ một đối tượng từ cơ sở dữ liệu / mô hình?


Tôi đang sử dụng 1.2.5 với một ImageField tiêu chuẩn và sử dụng phụ trợ lưu trữ được tích hợp sẵn. Các tập tin tải lên tốt nhưng khi tôi loại bỏ một mục từ admin, tập tin thực tế trên máy chủ sẽ không xóa.


76
2018-03-21 01:19


gốc


Hm, thực ra nó nên. Kiểm tra quyền của tệp trên thư mục tải lên của bạn (thay đổi thành 0777). - Torsten Engelbrecht
Django đã xóa tính năng xóa tự động (đối với nhân viên Google nhìn thấy nhận xét ở trên). - Mark


Các câu trả lời:


Bạn có thể nhận được pre_delete hoặc là post_delete tín hiệu (xem bình luận của @ toto_tico bên dưới) và gọi phương thức xóa 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)

90
2018-01-14 01:11



Đảm bảo thêm séc nếu instance.file trường không trống hoặc 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ả đối với ImageField(null=False) lĩnh vực. - Antony Hatchkins
Cảm ơn. Nói chung, tôi khuyên bạn nên sử dụng post_delete tí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 đó, không phải là mô hình, không phải tệp sẽ bị xóa giữ dữ liệu nhất quán. Hãy sửa tôi nếu hiểu biết của tôi về post_delete và pre_delete tín hiệu sai. - toto_tico
Lưu ý rằng điều này không xóa tệp cũ nếu bạn thay thế tệp trên một cá thể mô hình - Mark
Điều này không làm việc cho tôi trong Django 1.8 bên ngoài admin. Có cách nào mới để làm điều đó không? - caliph
Tôi không thể làm cho nó hoạt động ở Django 1.9 - jonalv


Giải pháp Django 1.5: Tôi sử dụng post_delete vì nhiều lý do khác nhau bên trong ứ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 bị kẹt ở cuối tệp models.py.

các original_image trường là ImageField trong tôi Photo mô hình.


33
2018-05-08 14:15



+1 cũng hoạt động với 1,6. - nima
Đối với bất cứ ai sử dụng Amazon S3 như là một 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 một 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 tới 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.name sau đó storage.delete(name). - Sean Azlin
@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ỉ đơn giản là sử dụng một 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 không mymodel.myimagefield.delete(save=False) trên post_delete? 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 ảnh bị xóa không? - eugene
@eugene Có bạn có thể, nó hoạt động (Tôi không chắc chắn tại sao mặc dù). Trong post_delete bạn làm instance.myimagefield.delete(save=False), lưu ý việc sử dụng instance. - user2616836


Thử django-cleanup

pip install django-cleanup

settings.py

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

33
2018-03-17 17:21



Gói rất tuyệt vời. Cảm ơn bạn! :) - BoJack Horseman
Sau khi kiểm tra 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. - Deleet
Nice, điều này thật dễ dàng - Tunn
Tốt đẹp. Làm việc 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à rất vui khi xóa các tệp khỏi S3. - routeburn


Mã này chạy tốt trên Django 1.4 cũng với bảng Admin.

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à để có được lưu trữ và đường dẫn trước khi xóa mô hình hoặc sau này cũng sẽ bị vô hiệu nếu bị xóa.


17
2017-07-04 17:28



Điều này không làm việc cho tôi (Django 1.5) và các bang CHangoELOG 1,3 Django: "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 tin mồ côi, bạn ' sẽ cần phải xử lý nó cho mình (ví dụ, với một lệnh quản lý tùy chỉnh có thể chạy thủ công hoặc được lên lịch để chạy định kỳ thông qua ví dụ cron). " - darrinm
Không làm việc cho tôi. Tín hiệu làm. - Antony Hatchkins
Giải pháp này là sai! delete khô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 tín hiệu. - lvella


Bạn nhu cầu để xóa tệp thực trên cả hai delete và 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)

7
2017-09-18 01:30





Bạn có thể xem xét 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ùng một lý do mà FileField tự động xóa đã được gỡ bỏ 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 File chuyên dụng để quản lý tất cả các tệp của tôi.

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


6
2017-11-08 16:50



có lẽ post_deletesẽ không hoạt động, bởi vì file_field.delete() theo mặc định, lưu mô hình thành db, hãy thử file_field.delete(False)  docs.djangoproject.com/en/1.3/ref/models/fields/… - Adam Jurczyk


Có lẽ hơi trễ một chút. 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 thực thi ngay cả trong quá trình xóa QuerySet, nhưng phương thức [model] .delete () không bị xử lý 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 đè lên 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 / signals.py

import os

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

3
2017-08-16 02:26





Chức năng này sẽ được gỡ bỏ trong Django 1.3 vì vậy tôi sẽ không dựa vào nó.

Bạn có thể ghi đè lên delete phươ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.

Chỉnh sửa:

Đâ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)

1
2018-03-21 02:54



Bạn có một ví dụ về cách sử dụng nó trong một mô hình để xóa các tập tin? Tôi đang xem tài liệu và xem 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ề việc xóa tệp. - narkeeso
Phương pháp này là sai bởi vì nó sẽ không hoạt động để xóa hàng loạt (như tính năng 'Xoá đã 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 tín hiệu. - Antony Hatchkins


Sử dụng post_delete là chắc chắn đúng cách để đi. Đôi khi mọi thứ có thể sai, và các tập tin không bị xóa. Tất nhiên là có trường hợp bạn có một loạt các tệp cũ không bị xóa trước khi post_delete được sử dụng. Tôi đã tạo một hàm xóa các tệp cho các đối tượng dựa trên tệp nếu tham chiếu đối tượng không tồn tại sau đó 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 đối tượng .. Điều tôi đã thêm vào hầu hết các mô hình của mình. Bạn phải vượt qua nó các đối tượng bạn muốn kiểm tra, đường dẫn đến các 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 điều này:

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

1
2018-06-05 23:22





đảm bảo bạn viết "bản thân"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 khi tập tin của tôi và điều đó đã không làm việc như nó đang tìm kiếm trong không gian tên toàn cầu.


0
2018-03-27 17:15