在Django中覆盖QuerySet.delete()方法

27

我有一个Django模型,其中包含对应用程序功能核心的设置。您不应该删除此模型。我正在尝试在整个应用程序中执行此操作。我已经在管理界面中禁用了删除功能,并且还禁用了该模型上的删除方法,但是QuerySet有它自己的删除方法。例如:

MyModel.objects.all()[0].delete() # Overridden, does nothing

MyModel.objects.all().delete() # POOF!

讽刺的是,Django文档对于为什么delete()是QuerySet的方法而不是Manager的方法有以下说法:

这是一种安全机制,防止您意外请求Entry.objects.delete()并删除所有条目。

必须包含.all()是一种“安全机制”的说法可谓站不住脚。相反,这实际上创建了一个后门,不能通过常规手段(覆盖管理器)关闭。

有人知道如何在像QuerySet这样核心的东西上覆盖此方法,而不需要对源代码进行Monkey Patching吗?

2个回答

59

通过覆盖Manager.get_query_set()方法,可以覆盖Manager的默认QuerySet

示例:

class MyQuerySet(models.query.QuerySet):

    def delete(self):
        pass  # you can throw an exception


class NoDeleteManager(models.Manager):
    def get_query_set(self):
        return MyQuerySet(self.model, using=self._db)

class MyModel(models.Model)
    field1 = ..
    field2 = ..


    objects = NoDeleteManager()

现在,MyModel.objects.all().delete()将不会有任何作用。

更多信息请参见:修改初始Manager QuerySets


嗯,这是我的大疏忽。本应该自己想到的,但还是感谢你弥补了我这颗老化的脑袋的不足 ;)。 - Chris Pratt
9
Django 1.6及以上版本中的方法被称为get_queryset(self) - Andrei-Niculae Petre
3
如果我没错的话,您可以使用QuerySet.as_manager来更加简洁地完成此操作。您可以删除上面的管理类,然后只需执行以下操作:objects = MyQuerySet.as_manager() - mgalgs
1
截至目前(Django 1.11),"get_queryset"方法看起来像这样:def get_queryset(self): return MyQuerySet(model=self.model, using=self._db, hints=self._hints) - Mark Chackerian

12

混合方法

https://gist.github.com/dnozay/373571d8a276e6b2af1a

使用与@manji发布的类似的方法,

class DeactivateQuerySet(models.query.QuerySet):
    '''
    QuerySet whose delete() does not delete items, but instead marks the
    rows as not active, and updates the timestamps
    '''
    def delete(self):
        self.deactivate()

    def deactivate(self):
        deleted = now()
        self.update(active=False, deleted=deleted)

    def active(self):
        return self.filter(active=True)


class DeactivateManager(models.Manager):
    '''
    Manager that returns a DeactivateQuerySet,
    to prevent object deletion.
    '''
    def get_query_set(self):
        return DeactivateQuerySet(self.model, using=self._db)

    def active(self):
        return self.get_query_set().active()

并创建一个mixin:

class DeactivateMixin(models.Model):
    '''
    abstract class for models whose rows should not be deleted but
    items should be 'deactivated' instead.

    note: needs to be the first abstract class for the default objects
    manager to be replaced on the subclass.
    '''
    active = models.BooleanField(default=True, editable=False, db_index=True)
    deleted = models.DateTimeField(default=None, editable=False, null=True)
    objects = DeactivateManager()

    class Meta:
        abstract = True

其他有趣的东西


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接