如何将 Django 信号与抽象模型一起使用?

Posted

技术标签:

【中文标题】如何将 Django 信号与抽象模型一起使用?【英文标题】:How do I use Django signals with an abstract model? 【发布时间】:2011-02-11 03:53:38 【问题描述】:

我有一个保留磁盘缓存的抽象模型。当我删除模型时,我需要它来删除缓存。我希望每个派生模型也能做到这一点。

如果我连接指定抽象模型的信号,这不会传播到派生模型:

pre_delete.connect(clear_cache, sender=MyAbstractModel, weak=False)

如果我尝试在 init 中连接信号,我可以在其中获取派生类名称,它可以工作,但我担心它会尝试清除缓存的次数与我一样多次已经初始化了一个派生模型,而不仅仅是一次。

我应该在哪里连接信号?

【问题讨论】:

【参考方案1】:

基于 Justin Abrahms 的回答,我创建了一个自定义管理器,它将 post_save 信号绑定到类的每个子级,无论它是否抽象。

这是一些一次性的、测试不佳的代码,因此不提供任何保证!不过,它似乎有效。

在此示例中,我们允许抽象模型将 CachedModelManager 定义为管理器,然后将基本缓存功能扩展到模型及其子级。它允许您定义一个可变键列表(一个名为 volatile_cache_keys 的类属性),每次保存时都应该删除它们(因此post_save 信号),并添加了几个帮助函数来生成缓存键,以及检索, 设置和删除键。

这当然假设您有一个缓存后端设置并正常工作。

# helperapp\models.py
# -*- coding: UTF-8
from django.db import models
from django.core.cache import cache

class CachedModelManager(models.Manager):
    def contribute_to_class(self, model, name):
        super(CachedModelManager, self).contribute_to_class(model, name)

        setattr(model, 'volatile_cache_keys',
                getattr(model, 'volatile_cache_keys', []))

        setattr(model, 'cache_key', getattr(model, 'cache_key', cache_key))
        setattr(model, 'get_cache', getattr(model, 'get_cache', get_cache))
        setattr(model, 'set_cache', getattr(model, 'set_cache', set_cache))
        setattr(model, 'del_cache', getattr(model, 'del_cache', del_cache))

        self._bind_flush_signal(model)

    def _bind_flush_signal(self, model):
        models.signals.post_save.connect(flush_volatile_keys, model)

def flush_volatile_keys(sender, **kwargs):
    instance = kwargs.pop('instance', False)

    for key in instance.volatile_cache_keys:
        instance.del_cache(key)

def cache_key(instance, key):
    if not instance.pk:
        name = "%s.%s" % (instance._meta.app_label, instance._meta.module_name)
        raise models.ObjectDoesNotExist("Can't generate a cache key for " +
                                        "this instance of '%s' " % name +
                                        "before defining a primary key.")
    else:
        return "%s.%s.%s.%s" % (instance._meta.app_label,
                                instance._meta.module_name,
                                instance.pk, key)

def get_cache(instance, key):
    result = cache.get(instance.cache_key(key))
    return result

def set_cache(instance, key, value, timeout=60*60*24*3):
    result = cache.set(instance.cache_key(key), value, timeout)
    return result

def del_cache(instance, key):
    result = cache.delete(instance.cache_key(key))
    return result


# myapp\models.py
from django.contrib.auth.models import User
from django.db import models

from helperapp.models import CachedModelManager

class Abstract(models.Model):
    creator = models.ForeignKey(User)

    cache = CachedModelManager()

    class Meta:
        abstract = True


class Community(Abstract):
    members = models.ManyToManyField(User)

    volatile_cache_keys = ['members_list',]

    @property
    def members_list(self):
        result = self.get_cache('members_list')

        if not result:
            result = self.members.all()
            self.set_cache('members_list', result)

        return result

欢迎补丁!

【讨论】:

【参考方案2】:

我认为您可以在不指定发件人的情况下连接到 post_delete,然后检查实际发件人是否在模型类列表中。比如:

def my_handler(sender, **kwargs):
    if sender.__class__ in get_models(someapp.models):
        ...

显然你需要更复杂的检查等,但你明白了。

【讨论】:

聪明,谢谢!对于抽象模型的孩子,这对我有用:if sender.__class__ in MyAbstractModel.__subclasses__(): … if issubclass(sender, BaseAbstractModel):【参考方案3】:

为您的模型创建自定义管理器。在其contribute_to_class 方法中,让它为class_prepared 设置一个信号。该信号调用一个函数,该函数将更多信号绑定到模型。

【讨论】:

我明白这背后的想法,但你能举个例子吗?这真的有助于在我当前的项目中保持干燥。 编辑:我尝试在自己的答案中处理您的建议。它对我有用,但我不是 100% 确定!

以上是关于如何将 Django 信号与抽象模型一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

将 json 模型字段与 django 石墨烯一起使用

Django:如何使用 pre_save 信号更新模型实例?

为啥我不能将 __getattr__ 与 Django 模型一起使用?

django 抽象模型与常规继承

风格 - 何时序列化 Django 模型实例:信号与模型的保存方法

django抽象模型继承导入