在Django中装饰模型类并在装饰器中调用保存

1 投票
2 回答
2181 浏览
提问于 2025-04-17 04:59

我有几个模型,它们会把日志数据保存到我的数据库里。我还做了一个“最近事件”的应用程序,我想选择哪些模型可以把数据发送到这个事件应用。我觉得用装饰器来实现这个功能挺好的,这样我只需要把装饰器加到我想要的模型上就行了:

@logger
class TemperatureLog(models.Model):
    Date = models.DateTimeField(auto_now_add=True)
    Device = models.ForeignKey(TemperatureDevice)
    Data = models.PositiveIntegerField()

这是事件模型,我使用了通用外键:

class Event(models.Model):
    Active = models.BooleanField()
    Queue = models.BooleanField()
    ContentType = models.ForeignKey(ContentType)
    ObjectID = models.PositiveIntegerField()
    Event = generic.GenericForeignKey('ContentType', 'ObjectID')

这是装饰器:

def logger(event):
    def wrap(*args, **kwargs):
        from toolbox.event.models import Event
        event(*args, **kwargs).save()
        myid = event(*args, **kwargs).id
        new = Event(Event=event.objects.get(id=myid))

        if Event.objects.all().filter(Active=True).count() >= 25:
            new.Queue = True
            new.save()

        else:
            new.Active = True
            new.save()

            for item in Event.objects.all().filter(Queue=True):
                item.Queue = False
                item.Active = True
                item.save()

                if  Event.objects.all().filter(Active=True).count() >= 25:
                    break
        return event(*args, **kwargs)

    return wrap

这个装饰器工作得很好,它能创建事件实例并保存。但是我遇到的问题是,save() 方法会被调用两次。一次是在装饰器里,另一次是在实际收集温度日志的代码中(因为我无法提前知道哪些应用会发送事件,哪些不会,或者将来可能会改变)。所以我在想有没有更优雅的方法来解决这个问题。我喜欢用装饰器的方式,因为我只需要把它加到模型类上,但我对 save 被调用两次这件事并不是很放心。

2 个回答

1

简单来说,针对你的问题,可以考虑使用Django内置的pre_save信号

基本上,你需要把一个监听函数连接到pre_save信号上,具体的使用方法可以在上面的链接中找到。这样,你就可以在保存模型实例之前修改它的某些属性。只有在你的监听函数执行完毕后(还有其他连接到这个模型的pre_save信号的监听函数也执行完),模型实例才会被保存到数据库中。

如果我理解你的代码没错,你希望当数据库中有25个或更多的活动事件记录时,Queue变量设置为True,否则设置为False(而Active的情况正好相反——我不太明白你为什么需要两个布尔值)。你可以通过信号来实现这个功能,像这样……

from django.db.signals import pre_save

def update_event_active_queue_status(sender, instance=None, **kwargs):
    if Event.objects.filter(Active=True).count() >= 25:
        instance.Queue = True
    else:
        instance.Active = True
pre_save.connect(update_event_active_queue_status, sender=Event)

你还想解决的另一个问题是,当活动事件的数量降到25以下时,把排队的事件重新变为活动状态。我不太清楚你的具体需求,但我觉得这可能不适合在这里讨论,可能更适合用定时任务(cron job)或者其他事件管理工具来处理。现在,如果系统中没有添加新事件(或者以其他方式进行更改),队列中的项目就永远不会被取出。这可能不是你想要的结果。

当然,你对自己的需求比我更了解,所以我的建议仅供参考。

0

你觉得对所有模型使用 post_save 信号怎么样?

def log_saved_event(sender, instance, signal, *args, **kwargs):
    # handle Event class
    pass

from django.db.models import signals
from django.db import models

for m in models.get_models():
    signals.post_save.connect(log_saved_event, sender=m)

撰写回答