web-dev-qa-db-ja.com

Djangoモデル:delete()がトリガーされない

私はモデルを持っています:

class MyModel(models.Model):
 ...
    def save(self):
        print "saving"
        ...
    def delete(self):
        print "deleting"
        ...

Save()-メソッドはトリガーされますが、delete()はトリガーされません。私は最新のsvn-Version(Djangoバージョン1.2 pre-alpha SVN-11593)を使用しており、 http://www.djangoproject.com/documentation/models/save_delete_hooks/ のドキュメントに関してはこれで動作するはずです。 。何か案は?

35
schneck

あなたはおそらく管理者の一括削除機能を使用していて、管理者の一括削除メソッドがdelete()を呼び出さないという事実に遭遇していると思います(関連する チケット を参照)。

私は過去に、モデルを削除するためのカスタム管理アクションを作成することでこれを回避しました。

管理者の一括削除方法を使用していない場合(たとえば、オブジェクトの編集ページの削除ボタンをクリックしている場合)、別のことが起こっています。

警告を参照してください ここ

「選択したオブジェクトの削除」アクションでは、効率上の理由からQuerySet.delete()を使用しますが、これには重要な注意事項があります。モデルのdelete()メソッドは呼び出されません。

この動作をオーバーライドする場合は、たとえば、選択したアイテムごとにModel.delete()を呼び出すなど、好みの方法で削除を実行するカスタムアクションを作成するだけです。

一括削除の背景については、 オブジェクト削除 のドキュメントを参照してください。

私のカスタム管理モデルは次のようになります。

from photoblog.models import PhotoBlogEntry
from Django.contrib import admin    

class PhotoBlogEntryAdmin(admin.ModelAdmin):
    actions=['really_delete_selected']

    def get_actions(self, request):
        actions = super(PhotoBlogEntryAdmin, self).get_actions(request)
        del actions['delete_selected']
        return actions

    def really_delete_selected(self, request, queryset):
        for obj in queryset:
            obj.delete()

        if queryset.count() == 1:
            message_bit = "1 photoblog entry was"
        else:
            message_bit = "%s photoblog entries were" % queryset.count()
        self.message_user(request, "%s successfully deleted." % message_bit)
    really_delete_selected.short_description = "Delete selected entries"

admin.site.register(PhotoBlogEntry, PhotoBlogEntryAdmin)
72
Dominic Rodger

私はこの質問が古くからあることを知っていますが、私はこれに再び遭遇し、次のようにいつでもコードをpre_deleteまたはpost_deleteシグナルに移動できることを追加したいと思いました。

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):
    print("deleting")

これは、管理者の一括削除アクションで機能します(少なくとも1.3.1以降)。

35
David Bennett

管理者の一括アクションはqueryset.delete()を呼び出します。

クエリセットの.delete()メソッドをオーバーライドして、常にオブジェクトを1行1列で削除することができます。例えば:

managers.py

from Django.db import models
from Django.db.models.query import QuerySet

class PhotoQueryMixin(object):
    """ Methods that appear both in the manager and queryset. """
    def delete(self):
        # Use individual queries to the attachment is removed.
        for photo in self.all():
            photo.delete()

class PhotoQuerySet(PhotoQueryMixin, QuerySet):
    pass

class PhotoManager(PhotoQueryMixin, models.Manager):
    def get_query_set(self):
        return PhotoQuerySet(self.model, using=self._db)

models.py

from Django.db import models

class Photo(models.Model):
    image = models.ImageField(upload_to='images')

    objects = PhotoManager()

    def delete(self, *args, **kwargs):
        # Note this is a simple example. it only handles delete(),
        # and not replacing images in .save()
        super(Photo, self).delete(*args, **kwargs)
        self.image.delete()
7
vdboor

主な問題は、Django管理者の一括削除では、他の場所で説明されているように、instance.delete()ではなくSQLが使用されることです。管理者のみのソリューションの場合、次のソリューションはDjango管理者の「本当にこれらを削除しますか」インタースティシャル。ただし、vdboorのソリューションが最も一般的です。

from Django.contrib.admin.actions import delete_selected

class BulkDeleteMixin(object):
    class SafeDeleteQuerysetWrapper(object):
        def __init__(self, wrapped_queryset):
            self.wrapped_queryset = wrapped_queryset

        def _safe_delete(self):
            for obj in self.wrapped_queryset:
                obj.delete()

        def __getattr__(self, attr):
            if attr == 'delete':
                return self._safe_delete
            else:
                return getattr(self.wrapped_queryset, attr)

        def __iter__(self):
            for obj in self.wrapped_queryset:
                yield obj

        def __getitem__(self, index):
            return self.wrapped_queryset[index]

        def __len__(self):
            return len(self.wrapped_queryset)

    def get_actions(self, request):
        actions = super(BulkDeleteMixin, self).get_actions(request)
        actions['delete_selected'] = (BulkDeleteMixin.action_safe_bulk_delete, 'delete_selected', ugettext_lazy("Delete selected %(verbose_name_plural)s"))
        return actions

    def action_safe_bulk_delete(self, request, queryset):
        wrapped_queryset = BulkDeleteMixin.SafeDeleteQuerysetWrapper(queryset)
        return delete_selected(self, request, wrapped_queryset)


class SomeAdmin(BulkDeleteMixin, ModelAdmin):
    ...
3
Scott A

Django v2.2.2を使用して、次のコードでこの問題を解決しました

models.py

_class MyModel(models.Model):
    file = models.FileField(upload_to=<path>)

    def save(self, *args, **kwargs):
        if self.pk is not None:
            old_file = MyModel.objects.get(pk=self.pk).file
            if old_file.path != self.file.path:
                self.file.storage.delete(old_file.path)

        return super(MyModel, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        ret = super(MyModel, self).delete(*args, **kwargs)
        self.file.storage.delete(self.file.path)
        return ret
_

admin.py

_class MyModelAdmin(admin.ModelAdmin):

    def delete_queryset(self, request, queryset):
        for obj in queryset:
            obj.delete()
_

DefaultAdminSiteの場合、ユーザーが正しい権限を持っている場合にdelete_querysetが呼び出されますが、唯一の違いは、元の関数がqueryset.delete()を呼び出し、モデルdeleteメソッドをトリガーしないことです。バルク操作ではなくなったため、これは効率が低下しますが、ファイルシステムをクリーンに保ちます=)

2
MNV