129 votes

Django supprime FileField

Je suis en train de construire une application web en Django. J'ai un modèle qui télécharge un fichier, mais je ne peux pas supprimer le fichier. Voici mon code :

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)

Ensuite, en python manage.py shell Je fais ça :

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

Il supprime l'enregistrement de la base de données mais pas le fichier sur le serveur. Que puis-je essayer d'autre ?

Merci !

187voto

Anton Strogonoff Points 6792

Avant Django 1.3, le fichier était supprimé du système de fichiers automatiquement lorsque vous supprimiez l'instance de modèle correspondante. Vous utilisez probablement une version plus récente de Django, vous devrez donc implémenter vous-même la suppression du fichier du système de fichiers.

Echantillon simple basé sur le signal

Ma méthode de prédilection au moment de la rédaction de cet article est un mélange de post_delete y pre_save ce qui fait que les fichiers obsolètes sont supprimés chaque fois que les modèles correspondants sont supprimés ou que leurs fichiers sont modifiés.

Sur la base d'une hypothétique MediaFile modèle :

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)
  • Je pense que l'une des applications que j'ai construites il y a quelque temps a utilisé ce code en production, mais néanmoins, utilisez-le à vos propres risques.
  • Par exemple, il y a un perte possible de données scénario : vos données pourraient finir par faire référence à un fichier inexistant si votre save() se trouve dans une transaction qui est annulée. Vous pouvez envisager d'intégrer la logique de suppression de fichiers dans la méthode transaction.on_commit() selon les termes suivants transaction.on_commit(lambda: os.remove(old_file.path)) , comme suggéré dans le commentaire de Mikhail . django-cleanup bibliothèque fait quelque chose dans ce sens .
  • Cas limite : si votre application télécharge un nouveau fichier et fait pointer l'instance du modèle vers le nouveau fichier sans faire appel à save() (par exemple, en mettant à jour en masse une QuerySet ), l'ancien fichier continuera à traîner parce que les signaux ne seront pas exécutés. Cela ne se produit pas si vous utilisez des méthodes conventionnelles de gestion des fichiers.
  • Style de codage : cet exemple utilise file en tant que nom de champ, ce qui n'est pas un bon style car il entre en conflit avec la fonction intégrée file l'identifiant de l'objet.

Addendum : nettoyage périodique

De manière réaliste, vous pouvez vouloir también exécuter une tâche périodique pour gérer le nettoyage des fichiers orphelins au cas où une défaillance de l'exécution empêcherait la suppression d'un fichier. En gardant cela à l'esprit, vous pourriez probablement vous débarrasser complètement des gestionnaires de signaux, et faire en sorte qu'une telle tâche le site mécanisme permettant de traiter les données non sensibles et les fichiers peu volumineux.

Quoi qu'il en soit, si vous sont Si vous manipulez des données sensibles, il est toujours préférable de vérifier deux ou trois fois que vous ne manquez jamais de supprimer en temps voulu les données en production pour éviter toute responsabilité associée.

Voir aussi

  • FieldFile.delete() dans la référence du champ de modèle de Django 1.11 (notez qu'elle décrit l'élément FieldFile mais vous appelleriez .delete() directement sur le terrain : FileField à l'instance correspondante FieldFile et vous accédez à ses méthodes comme s'il s'agissait d'un champ).

    Notez que lorsqu'un modèle est supprimé, les fichiers associés ne sont pas supprimés. Si vous devez nettoyer les fichiers orphelins, vous devrez le faire vous-même (par exemple, avec une commande de gestion personnalisée qui peut être exécutée manuellement ou programmée pour être exécutée périodiquement via, par exemple, cron).

  • Pourquoi Django ne supprime pas les fichiers automatiquement : entrée dans les notes de version de Django 1.3

    Dans les versions antérieures de Django, lorsqu'une instance de modèle contenant un fichier FileField a été supprimé, FileField a pris sur lui de supprimer également le fichier du stockage dorsal. Cela a ouvert la porte à plusieurs scénarios de perte de données, y compris des transactions annulées et des champs sur différents modèles faisant référence au même fichier. Dans Django 1.3, lorsqu'un modèle est supprimé, la fonction FileField 's delete() ne sera pas appelée. Si vous avez besoin de nettoyer les fichiers orphelins, vous devrez le faire vous-même (par exemple, avec une commande de gestion personnalisée qui peut être exécutée manuellement ou programmée pour être exécutée périodiquement via, par exemple, cron).

  • Exemple d'utilisation d'un pre_delete signal uniquement

102voto

un1t Points 273

Essayez django-cleanup il invoque automatiquement la méthode de suppression sur FileField lorsque vous supprimez le modèle.

pip install django-cleanup

paramètres.py

INSTALLED_APPS = (
     ...
    'django_cleanup.apps.CleanupConfig',
)

50voto

mesuutt Points 459

Vous pouvez supprimer un fichier du système de fichiers en appelant .delete du champ de fichier s'affiche comme ci-dessous avec Django >= 1.10 :

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

18voto

Hamidreza Points 528

Django 2.x Solution :

Il est très facile de gérer la suppression de fichiers en Django 2 . J'ai essayé la solution suivante en utilisant Django 2 et SFTP Storage et aussi FTP STORAGE, et je suis presque sûr qu'elle fonctionnera avec n'importe quel autre gestionnaire de stockage qui implémente l'option delete méthode. ( delete est l'une des méthodes de storage méthodes abstraites qui sont censées supprimer le fichier du stockage, physiquement !)

Remplacer le delete du modèle de manière à ce que l'instance supprime ses FileFields avant de se supprimer elle-même :

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.image.name)
        super().delete()

Cela fonctionne assez facilement pour moi. Si vous voulez vérifier si le fichier existe avant la suppression, vous pouvez utiliser storage.exists . par exemple self.song.storage.exists(self.song.name) retournera un boolean représentant si la chanson existe. Cela ressemblera donc à ceci :

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.image.name)

    super().delete()

EDIT (En complément) :

Como @HeyMan mentionné, avec cette solution qui appelle Song.objects.all().delete() ne supprime pas les fichiers ! Cela se produit parce que Song.objects.all().delete() exécute une requête de suppression de Gestionnaire par défaut . Ainsi, si vous voulez être en mesure de supprimer les fichiers d'un modèle en utilisant l'option objects vous devez écrire et utiliser un Gestionnaire sur mesure (juste pour remplacer sa requête de suppression) :

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

et pour attribuer le CustomManager au modèle, vous devez initialiser objects à l'intérieur de votre modèle :

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.image.name)
        super().delete()

Vous pouvez maintenant utiliser .delete() à la fin de tout objects sous-requêtes. J'ai écrit la plus simple CustomManager mais vous pouvez faire mieux en renvoyant quelque chose sur les objets que vous avez supprimés ou ce que vous voulez.

17voto

user2608199 Points 31

Vous pouvez aussi simplement écraser la fonction de suppression du modèle pour vérifier le fichier s'il existe et le supprimer avant d'appeler la super fonction.

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)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X