Veritabanından / modelden bir nesneyi kaldırdığımda Django Admin'in dosyaları silmesini nasıl sağlayabilirim?


86

1.2.5'i standart bir ImageField ile ve yerleşik depolama arka ucunu kullanıyorum. Dosyalar sorunsuz yükleniyor, ancak yöneticiden bir girişi kaldırdığımda, sunucudaki asıl dosya silinmiyor.


Hm, aslında olmalı. Yükleme klasörünüzdeki dosya izinlerini kontrol edin (0777 olarak değiştirin).
Torsten Engelbrecht

5
Django, otomatik silme özelliğini kaldırdı (yukarıdaki yorumu gören Google çalışanları için).
Mark

Yanıtlar:


102

pre_deleteVeya post_deletesinyalini alabilir (aşağıdaki @ toto_tico'nun açıklamasına bakın) ve FileField nesnesinde delete () yöntemini çağırabilirsiniz , böylece (models.py içinde):

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.fileAlanın boş olup olmadığını kontrol ettiğinizden emin olun veya (en azından deneyebilirsiniz) MEDIA_ROOT dizininin tamamını silebilirsiniz. Bu ImageField(null=False)tarlalar için bile geçerlidir .
Antony Hatchkins

49
Teşekkürler. Genel olarak, post_deletesinyali kullanmanızı tavsiye ederim çünkü silme işleminin herhangi bir nedenle başarısız olması durumunda daha güvenlidir. O zaman ne model ne dosya ne de veriler tutarlı bir şekilde silinir. Anladığım post_deleteve pre_deletesinyallerim yanlışsa lütfen beni düzeltin .
toto_tico

9
Manken örneğinde dosyanın yerini ise bu eski dosyayı silmek etmediğini Not
Mark

3
Bu benim için admin dışında Django 1.8'de çalışmıyor. Bunu yapmanın yeni bir yolu var mı?
halife

harika. bunu uzun zamandır arıyordu
RL Shyam

46

Django-cleanup'ı deneyin

pip install django-cleanup

settings.py

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

1
Çok harika bir paket. Teşekkür ederim! :)
BoJack Horseman

3
Sınırlı testlerden sonra, bu paketin Django 1.10 için hala çalıştığını doğrulayabilirim.
CoderGuy123

1
Güzel, bu çok kolay
Tunn

Güzel. Benim için Django 2.0'da çalışıyor. Ayrıca depolama arka ucum olarak S3 kullanıyorum ( django-storages.readthedocs.io/en/latest/backends/… ) ve mutlu bir şekilde S3'ten dosya siliyor .
routeeburn

35

Django 1.5 çözümü: Post_delete'yi uygulamamın içinde olan çeşitli nedenlerle kullanıyorum.

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)

Bunu models.py dosyasının altına yapıştırdım.

original_imagealandır ImageFieldbenim de Photomodelin.


7
Amazon S3'ü depolama arka ucu olarak kullanan herkes için (django-depolama yoluyla), bu özel yanıt işe yaramayacaktır. Bir alırsınız NotImplementedError: This backend doesn't support absolute paths.dosya alanınızın adını geçirerek kolayca düzeltebilirim storage.delete()yerine dosya alanın yolunun. Örneğin, bu cevabın son iki satırını storage, name = photo.original_image.storage, photo.original_image.nameo zaman ile değiştirin storage.delete(name).
Sean Azlin

2
@Sean +1, bu ayarlamayı 1.7'de django-storages aracılığıyla django-imagekit tarafından S3'te oluşturulan küçük resimleri silmek için kullanıyorum. docs.djangoproject.com/en/dev/ref/files/storage/… . Not: Yalnızca bir ImageField (veya FileField) kullanıyorsanız, mymodel.myimagefield.delete(save=False)bunun yerine kullanabilirsiniz . docs.djangoproject.com/en/dev/ref/files/file/…
user2616836

@ user2616836 sen kullanabilir miyim mymodel.myimagefield.delete(save=False)üzerinde post_delete? Başka bir deyişle, dosyayı silebileceğimi görebiliyorum, ancak imaj alanına sahip bir model silindiğinde dosyayı silebilir misiniz?
eugene

1
@eugene Evet yapabilirsin, işe yarıyor (Yine de neden olduğundan emin değilim). Gelen post_deleteyapmanız instance.myimagefield.delete(save=False), kullanımına dikkat instance.
user2616836

17

Bu kod Django 1.4'te Yönetici paneliyle de iyi çalışır.

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)

Modeli silmeden önce depolamayı ve yolu almak önemlidir, aksi takdirde ikincisi silinirse de geçersiz kalacaktır.


3
Bu benim için çalışmıyor (Django 1.5) ve Django 1.3 CHANGELOG şunu söylüyor: "Django 1.3'te, bir model silindiğinde FileField'ın delete () yöntemi çağrılmayacak. Sahipsiz dosyaların temizlenmesine ihtiyacınız varsa, siz ' Bunu kendiniz halletmeniz gerekecek (örneğin, manuel olarak çalıştırılabilen veya örneğin cron aracılığıyla periyodik olarak çalışacak şekilde programlanan özel bir yönetim komutuyla). "
darrinm

4
Bu çözüm yanlış! deletebir satır silindiğinde her zaman çağrılmaz, sinyalleri kullanmanız gerekir.
lvella

14

Sen lüzum hem gerçek dosyayı kaldırmak için deleteve 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)

Harika çözüm!
AlexKh

6

Pre_delete veya post_delete sinyali kullanmayı düşünebilirsiniz:

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

Elbette, FileField otomatik silmesinin kaldırılmasının aynı nedenleri burada da geçerlidir. Başka bir yerde referans verilen bir dosyayı silerseniz sorun yaşarsınız.

Benim durumumda bu uygun görünüyordu çünkü tüm dosyalarımı yönetmek için özel bir Dosya modelim vardı.

Not: Bazı nedenlerden dolayı post_delete doğru çalışmıyor. Dosya silindi, ancak veritabanı kaydı kaldı; bu, hata koşullarında bile beklediğimin tam tersi. pre_delete yine de iyi çalışıyor.


3
muhtemelen post_deleteçalışmaz, çünkü file_field.delete()varsayılan olarak modeli file_field.delete(False) db'ye
Adam Jurczyk

3

Belki biraz geç kalmıştır. Ama benim için en kolay yol post_save sinyali kullanmaktır. Bir QuerySet silme işlemi sırasında bile sinyallerin çalıştırıldığını, ancak QuerySet silme işlemi sırasında [model] .delete () yönteminin çalıştırılmadığını hatırlamak için, bu nedenle onu geçersiz kılmak için en iyi seçenek bu değildir.

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

Bu işlevsellik Django 1.3'te kaldırılacak, böylece ona güvenmeyeceğim.

deleteGirişi veritabanından tamamen kaldırmadan önce dosyayı silmek için söz konusu modelin yöntemini geçersiz kılabilirsiniz .

Düzenle:

İşte hızlı bir örnek.

class MyModel(models.Model):

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

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

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

Dosyayı silmek için bunu bir modelde nasıl kullanacağınıza dair bir örneğiniz var mı? Dokümanlara bakıyorum ve nesnenin veritabanından nasıl kaldırılacağına dair örnekler görüyorum, ancak dosya silme konusunda herhangi bir uygulama görmüyorum.
narkeeso

2
Bu yöntem yanlıştır çünkü toplu silme için çalışmayacaktır (yöneticinin 'Seçileni sil' özelliği gibi). Örneğin MyModel.objects.all()[0].delete()dosyayı silerken silinmez MyModel.objects.all().delete(). Sinyalleri kullanın.
Antony Hatchkins

1

Post_delete kullanmak kesinlikle doğru yoldur. Bazen işler ters gidebilir ve dosyalar silinmez. Elbette post_delete kullanılmadan önce silinmemiş olan bir sürü eski dosyanız olması da söz konusu. Nesnelerin başvurduğu dosyanın bulunup bulunmadığına bağlı olarak nesnelerin dosyalarını silen bir işlev oluşturdum, sonra nesneyi silip, dosyanın bir nesnesi yoksa, sonra da silebilir, ayrıca bir nesne .. Modellerimin çoğuna eklediğim bir şey. Kontrol etmek istediğiniz nesneleri, nesne dosyalarının yolunu, dosya alanını ve etkin olmayan nesneleri silmek için bir bayrak iletmelisiniz:

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

Bunu şu şekilde kullanabilirsiniz:

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

dosyadan önce " self " yazdığınızdan emin olun . bu yüzden yukarıdaki örnek şöyle olmalıdır

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

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

Dosyamdan önce "kendini" unuttum ve bu, global ad alanına baktığı için çalışmadı.



0

Django 2.x Çözümü:

Herhangi bir paket kurmanıza gerek yok! Django 2'de kullanımı çok kolaydır . Django 2 ve SFTP Depolamasını kullanarak aşağıdaki çözümü denedim (ancak herhangi bir depo ile çalışacağını düşünüyorum)

Önce bir Özel Yönetici yazın . Dolayısıyla objects, yöntemleri kullanarak bir modelin dosyalarını silebilmek istiyorsanız, bir [Özel Yönetici] [3] yazmalı ve kullanmalısınız (geçersiz kılma delete()yöntemi için objects):

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

Artık imagemodelin kendisini silmeden önce silmelisiniz ve modele atamak CustomManageriçin, modelinizin objectsiçinde baş harfleri almanız gerekir :

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

Dosya alanımda upload_to seçeneğini dinamik dizin isimleriyle kullandığım için özel bir durumum olabilir, ancak bulduğum çözüm os.rmdir kullanmaktı.

Modellerde:

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
Bu çok kötü bir fikir. Yalnızca tek bir dosyaya karşı tüm bir dizini kaldırmakla kalmaz (potansiyel olarak diğer dosyaları etkiler), gerçek nesne silme işlemi başarısız olsa bile bunu yaparsınız.
tbm

Sahip olduğum sorun üzerinde çalışıyor olsaydın fena fikir değil;) Bahsettiğim gibi, silinen modelin bir üst model olduğu benzersiz bir kullanım durumum vardı. Çocuklar dosyaları ana klasöre yazdı ve bu nedenle, ana klasörü sildiyseniz, istenen davranış, klasördeki tüm dosyaların silinmesiydi. Yine de operasyonların sırası için iyi bir nokta. Bu o zamanlar aklıma gelmemişti.
carruthd

Yine de bir çocuk silindiğinde alt dosyaları tek tek kaldırmayı tercih ederim; daha sonra gerekirse üst dizini boşken kaldırabilirsiniz.
tbm

Bu, alt nesneleri çıkardığınız için mantıklıdır, ancak ana nesne yok edilirse, her seferinde bir çocuktan geçmek sıkıcı ve gereksiz görünür. Her şeye rağmen, verdiğim cevabın OP sorusuna yeterince spesifik olmadığını şimdi görüyorum. Yorumlarınız için teşekkür ederim, ileride daha az kör bir alet kullanmayı düşündürdünüz.
carruthd
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.