web-dev-qa-db-fra.com

Remplacer la méthode de suppression de modèle de Django pour la suppression en masse

Je remplace la méthode de suppression de modèle de Django afin de supprimer les fichiers orphelins du disque pour les champs d'image, quelque chose comme ceci:

class Image(models.Model):
    img = models.ImageField(upload_to=get_image_path)
    ...
    def delete(self, *args, **kwargs):
        self.img.delete()
        super(Image, self).delete(*args, **kwargs)

Cela fonctionne bien lorsque je supprime des objets uniques de l'administrateur, mais lorsque je sélectionne plusieurs objets et les supprime, cela ne semble pas être appelé. Je recherche sur Google depuis un certain temps, mais je n'ai pas trouvé les bons mots clés pour obtenir la réponse, ni la documentation officielle ne semble parler de ce sujet.

20

Oui :

La méthode delete () effectue une suppression en bloc et n'appelle aucune méthode delete () sur vos modèles. Cependant, il émet les signaux pre_delete et post_delete pour tous les objets supprimés (y compris les suppressions en cascade).

Pour que cela fonctionne, vous pouvez remplacer la méthode de suppression sur QuerySet, puis appliquer cette QuerySet en tant que gestionnaire:

class ImageQuerySet(models.QuerySet):

    def delete(self, *args, **kwargs):
        for obj in self:
            obj.img.delete()
        super(ImageQuerySet, self).delete(*args, **kwargs)

class Image(models.Model):
    objects = ImageQuerySet.as_manager()
    img = models.ImageField(upload_to=get_image_path)
    ...
    def delete(self, *args, **kwargs):
        self.img.delete()
        super(Image, self).delete(*args, **kwargs)
33
GwynBleidD

La méthode de suppression de l'ensemble de requêtes fonctionne directement sur la base de données. Il n'appelle pas les méthodes Model.delete(). De la docs :

Gardez à l'esprit que cela sera, autant que possible, exécuté uniquement en SQL, et donc les méthodes delete () des instances d'objet individuelles ne seront pas nécessairement appelées pendant le processus. Si vous avez fourni une méthode delete () personnalisée sur une classe de modèle et que vous voulez vous assurer qu'elle est appelée, vous devrez supprimer "manuellement" les instances de ce modèle (par exemple, en itérant sur un QuerySet et en appelant delete () sur chaque objet individuellement) plutôt que d'utiliser la méthode de suppression en bloc () d'un QuerySet.

Si vous souhaitez remplacer Django, vous pouvez écrire une action delete personnalisée:

https://docs.djangoproject.com/en/1.7/ref/contrib/admin/actions/

Une autre méthode consiste à remplacer post_delete (ou pre_delete) signal au lieu de la méthode delete:

https://docs.djangoproject.com/en/1.7/ref/signals/#Django.db.models.signals.post_delete

Comme pre_delete, mais envoyé à la fin de la méthode delete () d'un modèle et de la méthode delete () d'un ensemble de requêtes .

7
Selcuk

Je crois que ce problème est résolu dans le docs

où il est dit:

Les méthodes de modèle remplacées ne sont pas appelées sur les opérations en bloc

Notez que la méthode delete () pour un objet n'est pas nécessairement appelée lors de la suppression d'objets en bloc à l'aide d'un QuerySet ou à la suite d'une suppression en cascade. Pour vous assurer que la logique de suppression personnalisée est exécutée, vous pouvez utiliser des signaux pre_delete et/ou post_delete.

Malheureusement, il n’existe pas de solution de contournement lors de la création ou de la mise à jour d’objets en bloc, car aucun des enregistrements save (), pre_save et post_save n’est appelé.

Comme suggéré dans les documents ci-dessus, je pense qu'une meilleure solution consiste à utiliser le post_delete signal, comme ceci:

from Django.db.models.signals import post_delete
from Django.dispatch import receiver

class Image(models.Model):
    img = models.ImageField(upload_to=get_image_path)
    ...

@receiver(post_delete, sender=Image)
def delete_image_hook(sender, instance, using, **kwargs):
    instance.img.delete()

Contrairement à la substitution de la méthode delete, la méthode delete_image_hook La fonction doit également être appelée pour les suppressions en masse et les suppressions en cascade. Voici plus d'informations sur l'utilisation des signaux de Django: https://docs.djangoproject.com/en/1.11/topics/signals/#connecting-to-signals-sent-by-specific-senders

Remarque sur les réponses précédentes: Certains des messages précédents suggèrent de remplacer la méthode delete de QuerySet, ce qui peut avoir des implications sur les performances et d'autres comportements involontaires . Peut-être que ces réponses ont été écrites avant la mise en œuvre des signaux de Django, mais je pense que l'utilisation de signaux est une approche plus propre.

0
modulitos