Django xóa FileField


91

Tôi đang xây dựng một ứng dụng web ở Django. Tôi có một mô hình tải lên một tệp, nhưng tôi không thể xóa nó. Đây là mã của tôi:

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)

Sau đó, trong "python management.py shell" tôi thực hiện việc này:

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

Nó xóa khỏi cơ sở dữ liệu nhưng không xóa tệp trên máy chủ. Tôi có thể thử những gì khác?

Cảm ơn!


Còn việc sử dụng trực tiếp default_storage thì sao? docs.djangoproject.com/en/dev/topics/files
MGP

Câu trả lời:


141

Trước Django 1.3, tệp đã bị xóa khỏi hệ thống tệp tự động khi bạn xóa phiên bản mô hình tương ứng. Có thể bạn đang sử dụng phiên bản Django mới hơn, vì vậy bạn sẽ phải tự mình thực hiện xóa tệp khỏi hệ thống tệp.

Bạn có thể làm điều đó bằng một số cách, một trong số đó là sử dụng pre_deletehoặc post_deletetín hiệu.

Thí dụ

Phương pháp lựa chọn của tôi hiện tại là sự kết hợp giữa post_deletepre_savetín hiệu, điều này khiến các tệp lỗi thời bị xóa bất cứ khi nào các mô hình tương ứng bị xóa hoặc thay đổi tệp của chúng.

Dựa trên một MediaFilemô hình giả định :

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)
  • Trường hợp cạnh: nếu ứng dụng của bạn tải lên tệp mới và trỏ phiên bản mô hình vào tệp mới mà không gọi save()(ví dụ: bằng cách cập nhật hàng loạt a QuerySet), tệp cũ sẽ tiếp tục nằm xung quanh vì tín hiệu sẽ không được chạy. Điều này không xảy ra nếu bạn sử dụng các phương pháp xử lý tệp thông thường.
  • Tôi nghĩ rằng một trong những ứng dụng tôi đã xây dựng có mã này trong quá trình sản xuất nhưng tuy nhiên, bạn có thể tự chịu rủi ro khi sử dụng.
  • Kiểu mã hóa: ví dụ này sử dụng filelàm tên trường, đây không phải là kiểu tốt vì nó xung đột với mã fileđịnh danh đối tượng tích hợp sẵn.

Xem thêm

  • FieldFile.delete()trong tham chiếu trường mô hình Django 1.11 (lưu ý rằng nó mô tả FieldFilelớp, nhưng bạn sẽ gọi .delete()trực tiếp trên trường: FileFieldproxy phiên bản đến phiên bản tương ứng FieldFilevà bạn truy cập các phương thức của nó như thể chúng là của trường)

    Lưu ý rằng khi một mô hình bị xóa, các tệp liên quan sẽ không bị xóa. Nếu bạn cần dọn dẹp các tệp mồ côi, bạn sẽ cần phải tự xử lý (ví dụ: với lệnh quản lý tùy chỉnh có thể chạy theo cách thủ công hoặc được lập lịch để chạy định kỳ thông qua cron).

  • Tại sao Django không tự động xóa tệp: mục nhập trong ghi chú phát hành cho Django 1.3

    Trong các phiên bản Django trước đó, khi một cá thể mô hình chứa a FileFieldbị xóa, FileFieldnó sẽ tự xóa tệp khỏi bộ lưu trữ phụ trợ. Điều này đã mở ra cánh cửa cho một số trường hợp mất dữ liệu, bao gồm các giao dịch được khôi phục và các trường trên các mô hình khác nhau tham chiếu cùng một tệp. Trong Django 1.3, khi một mô hình bị xóa FileField, delete()phương thức của 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 sẽ cần phải tự xử lý (ví dụ: với 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 cron).

  • Ví dụ về việc chỉ sử dụng một pre_deletetín hiệu


2
Có, nhưng hãy đảm bảo thực hiện các kiểm tra thích hợp. (Hãy cho tôi một giây, tôi sẽ gửi mã tôi tìm thấy trong sử dụng trong hệ thống thực tế.)
Anton Strogonoff

7
Nó có lẽ tốt hơn để sử dụng instance.song.delete(save=False), vì nó sử dụng công cụ lưu trữ django chính xác.
Eduardo

1
Ngày nay hiếm khi tôi sao chép mã, tôi sẽ không thể tự viết trực tiếp từ SO và nó hoạt động với các sửa đổi hạn chế. Sự giúp đỡ tuyệt vời, cảm ơn!
GJStein

Tìm thấy một lỗi trong trường hợp này nếu trường hợp tồn tại, nhưng không có hình ảnh nào được lưu trước đó, sau đó os.path.isfile(old_file.path)không thành công vì old_file.pathphát sinh lỗi (không có tệp nào được liên kết với trường). Tôi đã sửa nó bằng cách thêm if old_file:ngay trước cuộc gọi tới os.path.isfile().
three_pineapples

@three_pineapples có lý. Có thể là ràng buộc NOT NULL trên trường tệp đã bị bỏ qua hoặc không thoát tại một số điểm, trong trường hợp đó, một số đối tượng sẽ làm trống trường.
Anton Strogonoff

77

Hãy thử django-cleanup , nó tự động gọi phương thức xóa trên FileField khi bạn loại bỏ mô hình.

pip install django-cleanup

settings.py

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

Tuyệt vời, nó cần được thêm vào FileField theo mặc định, cảm ơn!
megajoe

Nó được xóa các tập tin trong khi tải lên cũng
Chirag soni

Chà. Tôi đã cố gắng để điều này không xảy ra và tôi không thể hiểu tại sao lại như vậy. Ai đó đã cài đặt cái này nhiều năm trước và quên mất nó. Cảm ơn.
ryan28561

4
Vì vậy, tại sao Django lại loại bỏ chức năng xóa trường tập tin ngay từ đầu?
ha-neul

Bạn là huyền thoại !!
marlonjd

31

Bạn có thể xóa tệp khỏi hệ thống tệp bằng cách gọi .deletetrường tệp như bên dưới với Django> = 1.10:

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

7
Nên là câu trả lời được chấp nhận, đơn giản và chỉ hoạt động.
Nikolay Shindarov

14

Bạn cũng có thể chỉ cần ghi đè lên chức năng xóa của mô hình để kiểm tra xem tệp có tồn tại hay không và xóa nó trước khi gọi hàm siêu.

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
Lưu ý rằng việc gọi điện queryset.delete()sẽ không làm sạch các tệp bằng giải pháp này. Bạn sẽ cần phải lặp lại tập truy vấn và gọi .delete()từng đối tượng.
Scott Woodall

Tôi là người mới đến Django. Điều này là tốt, nhưng điều gì sẽ xảy ra nếu mô hình được kế thừa từ một lớp trừu tượng đã ghi đè phương thức xóa, điều này sẽ không ghi đè phương thức đó từ lớp trừu tượng? Sử dụng các tín hiệu có vẻ tốt hơn đối với tôi
theTypan

8

Giải pháp Django 2.x:

Rất dễ dàng để xử lý việc xóa tệp trong Django 2 . Tôi đã thử giải pháp sau bằng cách sử dụng Django 2 và SFTP Storage cũng như FTP STORAGE và tôi khá chắc chắn rằng nó sẽ hoạt động với bất kỳ trình quản lý lưu trữ nào khác đã triển khai deletephương pháp. ( deletemethod là một trong những storagemethod trừu tượng.)

Ghi đè deletephương thức của mô hình theo cách mà cá thể xóa các Trường tệp của nó trước khi xóa chính nó:

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()

Nó hoạt động khá dễ dàng đối với tôi. Nếu bạn muốn kiểm tra xem tệp có tồn tại trước khi xóa hay không, bạn có thể sử dụng storage.exists. Ví dụ: self.song.storage.exists(self.song.name)sẽ trả về booleanđại diện nếu bài hát tồn tại. Vì vậy, nó sẽ giống như thế này:

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()

CHỈNH SỬA (Ngoài ra):

Như @HeyMan đã đề cập, với giải pháp này, việc gọi Song.objects.all().delete()không xóa tệp! Điều này đang xảy ra vì Song.objects.all().delete()đang chạy truy vấn xóa của Trình quản lý mặc định . Vì vậy, nếu bạn muốn có thể xóa tệp của một mô hình bằng objectscác phương pháp, bạn phải viết và sử dụng Trình quản lý tùy chỉnh (chỉ để ghi đè truy vấn xóa của nó):

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

và để gán CustomManagercho mô hình, bạn phải viết tắt objectsbên trong mô hình của mình:

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()

Bây giờ bạn có thể sử dụng .delete()ở cuối bất kỳ objectstruy vấn phụ nào . Tôi đã viết đơn giản nhất CustomManager, nhưng bạn có thể làm điều đó tốt hơn bằng cách trả lại một cái gì đó về các đối tượng bạn đã xóa hoặc bất cứ thứ gì bạn muốn.


1
Vâng, tôi nghĩ họ đã thêm tính năng đó kể từ khi tôi đăng câu hỏi.
Marcos Aguayo

1
Vẫn xóa không được gọi là wenn gọi Song.objects.all (). Delete (). Tương tự khi Phiên bản bị xóa bởi on_delete = models.CASCADE.
HeyMan,

@HeyMan Tôi đã giải quyết nó và chỉnh sửa giải pháp của mình ngay bây giờ :)
Hamidreza

4

Đây là một ứng dụng sẽ xóa các tệp cũ bất cứ khi nào mô hình bị xóa hoặc một tệp mới được tải lên: 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

Tôi thiếu một cái gì đó trong mã khi một tệp thay đổi, nếu bạn tạo một tệp mới sẽ tạo ra lỗi, becuase là một tệp mới và không tìm thấy đường dẫn. Tôi đã sửa đổi mã của hàm và thêm một câu thử / ngoại trừ và nó hoạt động tốt.

@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

Tôi chưa gặp phải điều này — có thể là lỗi trong mã của tôi hoặc một cái gì đó đã thay đổi trong Django. Tuy nhiên, tôi sẽ đề xuất bắt một ngoại lệ cụ thể trong try:khối của bạn ( AttributeErrorcó lẽ?).
Anton Strogonoff

Sử dụng thư viện hệ điều hành không phải là ý kiến ​​hay vì bạn sẽ gặp phải sự cố nếu chuyển sang bộ nhớ khác (ví dụ: Amazon S3).
Igor Pomaranskiy

@IgorPomaranskiy điều gì sẽ xảy ra trong một bộ lưu trữ như Amazon S3 khi bạn sử dụng os.remove ??
Daniel González Fernández

@ DanielGonzálezFernández Tôi đoán nó sẽ thất bại (với lỗi như một cái gì đó về đường dẫn không tồn tại). Đó là lý do tại sao Django sử dụng trừu tượng cho kho lưu trữ.
Igor Pomaranskiy

0

Mã này sẽ chạy mỗi khi tôi tải lên một hình ảnh mới (trường biểu trưng) và kiểm tra xem một biểu trưng đã tồn tại chưa nếu có, hãy đóng nó và xóa nó khỏi đĩa. Tất nhiên, quy trình tương tự có thể được thực hiện trong hàm máy thu. Hi vọng điêu nay co ich.

 #  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='')
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.