Django FileField sil


93

Django'da bir web uygulaması oluşturuyorum. Dosya yükleyen bir modelim var ama onu silemiyorum. İşte kodum:

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)

Ardından, "python manage.py shell" içinde şunu yapıyorum:

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

Veritabanından siler, ancak sunucudaki dosyayı silmez. Başka ne deneyebilirim?

Teşekkürler!


Doğrudan default_storage kullanmaya ne dersiniz? docs.djangoproject.com/en/dev/topics/files
MGP

Yanıtlar:


142

Django 1.3'ten önce, ilgili model örneğini sildiğinizde dosya dosya sisteminden otomatik olarak siliniyordu. Muhtemelen daha yeni bir Django sürümü kullanıyorsunuz, bu nedenle dosyayı dosya sisteminden silmeyi kendiniz uygulamanız gerekecek.

Bunu birkaç şekilde yapabilirsiniz, bunlardan biri pre_deleteveya post_deletesinyali kullanmaktır.

Misal

Şu anda tercih ettiğim yöntem bir post_deleteve pre_savesinyallerin bir karışımıdır , bu da onu karşılık gelen modeller silindiğinde veya dosyaları değiştirildiğinde eski dosyaların silinmesini sağlar.

Varsayımsal bir MediaFilemodele göre:

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)
  • Uç durum: Uygulamanız yeni bir dosya yükler ve arama yapmadan model örneğini yeni dosyaya işaret ederse save()(örneğin, a'yı toplu olarak güncelleyerek QuerySet), sinyaller çalışmayacağı için eski dosya ortalıkta yatmaya devam eder. Geleneksel dosya işleme yöntemlerini kullanırsanız bu olmaz.
  • Sanırım geliştirdiğim uygulamalardan biri bu koda sahip ancak yine de riski size ait olacak şekilde kullanın.
  • Kodlama stili: Bu örnek file, yerleşik filenesne tanımlayıcısıyla çakıştığı için iyi bir stil olmayan alan adı olarak kullanır .

Ayrıca bakınız

  • FieldFile.delete()Django 1.11 model alan referansında ( FieldFilesınıfı tanımladığını , ancak .delete()doğrudan alanda çağıracağınızı unutmayın : FileFieldkarşılık gelen FieldFileörneğe örnek proxy'ler ve yöntemlerine sanki bir alanmış gibi erişirsiniz)

    Bir model silindiğinde, ilgili dosyaların silinmediğini unutmayın. Sahipsiz dosyaları temizlemeniz gerekiyorsa, bunu kendiniz halletmeniz gerekir (örneğin, manuel olarak çalıştırılabilen veya örneğin cron aracılığıyla periyodik olarak çalışmak üzere programlanan özel bir yönetim komutuyla).

  • Django neden dosyaları otomatik olarak silmiyor: Django 1.3 sürüm notlarına giriş

    Önceki Django sürümlerinde, içinde içinde bulunan bir model örneği FileFieldsilindiğinde, FileFielddosyayı arka uç depolama alanından da silmeyi üstlenirdi. Bu, geri alınan işlemler ve aynı dosyaya referans veren farklı modellerdeki alanlar dahil olmak üzere birçok veri kaybı senaryosunun kapısını açtı. Django 1.3'te, bir model silindiğinde FileField'nin delete()yöntemi çağrılmaz. Sahipsiz dosyaların temizlenmesine ihtiyacınız varsa, bunu kendiniz halletmeniz gerekir (örneğin, manuel olarak çalıştırılabilen veya örneğin cron aracılığıyla periyodik olarak çalışmak üzere programlanan özel bir yönetim komutu ile).

  • pre_deleteYalnızca sinyal kullanma örneği


2
Evet, ancak uygun kontrolleri yaptığınızdan emin olun. (Bana bir saniye verin, gerçek sistemde kullanımda bulduğum kodu
göndereceğim

7
instance.song.delete(save=False)Doğru django depolama motorunu kullandığı için muhtemelen kullanmak daha iyidir .
Eduardo

1
Nadiren, bugünlerde kodu kopyaladığım için kendimi doğrudan SO'dan yazamazdım ve sınırlı değişikliklerle çalışıyor. Harika yardım, teşekkürler!
GJStein

Örnek mevcutsa, ancak daha önce hiç görüntü kaydedilmediyse, bir hata oluşturduğu için os.path.isfile(old_file.path)başarısız olur old_file.path(alanla ilişkilendirilmiş dosya yoktur). if old_file:Aramadan hemen önce ekleyerek düzelttim os.path.isfile().
three_pineapples

@three_pineapples mantıklı. Dosya alanındaki NOT NULL kısıtlaması atlanmış veya bir noktada çıkmamış olabilir, bu durumda bazı nesneler boş olabilir.
Anton Strogonoff

78

Django-cleanup'ı deneyin , modeli kaldırdığınızda FileField'de otomatik olarak silme yöntemini çağırır.

pip install django-cleanup

settings.py

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

Harika, varsayılan olarak FileField'e eklenmesi gerekiyor, teşekkürler!
megajoe

Dosya yüklenirken de siliniyor
chirag soni

Vay. Bunun olmamasını sağlamaya çalışıyordum ve neden olduğunu anlayamadım. Birisi bunu yıllar önce kurmuş ve unutmuş. Teşekkürler.
ryan28561

4
Peki, Django neden ilk etapta dosya alanı silme işlevini kaldırdı?
ha-neul

Sen efsanesin !!
marlonjd

32

.deleteDjango> = 1.10 ile aşağıdaki gibi gösterilen dosya alanı yöntemini çağırarak dosya sisteminden dosyayı silebilirsiniz :

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

7
Kabul edilen cevap olmalı, basit ve adil çalışıyor.
Nikolay Shindarov

14

Dosya olup olmadığını kontrol etmek için modelin silme işlevinin üzerine yazabilir ve süper işlevi çağırmadan önce onu silebilirsiniz.

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()Bu çözümle aramanın dosyaları temizlemeyeceğine dikkat edin . Sorgu kümesini yinelemeniz ve .delete()her nesneyi aramanız gerekir .
Scott Woodall

Django'da yeniyim. Bu iyidir, ancak ya model silme yöntemini geçersiz kılan soyut bir sınıftan miras alıyorsa, bu soyut sınıftan bunu geçersiz kılmaz mı? Sinyalleri kullanmak bana daha iyi geliyor
TheTypan

8

Django 2.x Çözümü:

Django 2'de dosya silme işlemini gerçekleştirmek çok kolaydır . Django 2 ve SFTP Depolama ve ayrıca FTP DEPOLAMA kullanarak aşağıdaki çözümü denedim ve deleteyöntemi uygulayan diğer depolama yöneticileriyle çalışacağından oldukça eminim . ( deleteyöntem storagesoyut yöntemlerden biridir.)

deleteModelin yöntemini, örneğin kendisini silmeden önce FileFields'ı sileceği şekilde geçersiz kılı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()

Benim için oldukça kolay çalışıyor. Silme işleminden önce dosyanın var olup olmadığını kontrol etmek isterseniz kullanabilirsiniz storage.exists. örneğin şarkı varsa self.song.storage.exists(self.song.name)bir booleantemsilci döndürecektir . Yani şöyle görünecek:

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

DÜZENLE (Ek olarak):

As @HeyMan bahsedildiği, bu çözüm arama ile Song.objects.all().delete()dosyaları silmez! Bunun nedeni Song.objects.all().delete(), Varsayılan Yöneticinin silme sorgusunu çalıştırmasıdır . Bu nedenle objects, yöntemleri kullanarak bir modelin dosyalarını silebilmek istiyorsanız, bir Özel Yönetici yazmalı ve kullanmalısınız (sadece silme sorgusunu geçersiz kılmak için):

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

ve CustomManagermodele atamak için, modelinizin objectsiçinde baş harfleri almanız gerekir :

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

Artık .delete()herhangi bir objectsalt sorgunun sonunda kullanabilirsiniz . En basitini yazdım CustomManager, ancak sildiğiniz nesneler veya ne isterseniz onu geri getirerek daha iyisini yapabilirsiniz.


1
Evet, sanırım soruyu gönderdikten sonra bu özelliği eklediler.
Marcos Aguayo

1
Yine de silmeye Song.objects.all (). Delete () 'i çağıran wenn adı verilmez. Aynı Örnek, on_delete = models.CASCADE tarafından silindiğinde de geçerlidir.
HeyMan

@HeyMan Şu anda çözdüm ve çözümümü düzenledim :)
Hamidreza

4

İşte model silindiğinde veya yeni bir dosya yüklendiğinde eski dosyaları kaldıracak bir uygulama: 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

Bir dosya değiştiğinde kodda bir şey eksik, yeni bir dosya oluşturursanız bir hata oluşturur, çünkü yeni bir dosyadır a bir yol bulamadı. İşlev kodunu değiştirdim ve bir dene / hariç cümle ekledim ve iyi çalışıyor.

@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

Bununla karşılaşmadım - kodumdaki bir hata veya Django'da değişen bir şey olabilir. try:Yine AttributeErrorde bloğunuzda belirli bir istisna yakalamanızı öneririm ( belki?)
Anton Strogonoff

Farklı bir depolamaya geçerseniz (örneğin Amazon S3) sorunlarla karşılaşacağınız için os kitaplığını kullanmak o kadar iyi bir fikir değildir.
Igor Pomaranskiy

@IgorPomaranskiy os.remove kullandığınızda Amazon S3 gibi bir depolamada ne olur?
Daniel González Fernández

@ DanielGonzálezFern geçin Sanırım başarısız olacak (var olmayan yol gibi bir hata ile). Bu nedenle Django, depolamalar için soyutlamalar kullanır.
Igor Pomaranskiy

0

Bu kod, her yeni resim (logo alanı) yüklediğimde çalışacak ve bir logonun mevcut olup olmadığını kontrol edecek, varsa kapatın ve diskten çıkarın. Aynı prosedür elbette alıcı işlevinde de yapılabilir. Bu yardımcı olur umarım.

 #  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='')
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.