如果在我的某个模型中更改字段,我该如何处理?在这种特殊情况下,我有这个模型:
class Game(models.Model):
STATE_CHOICES = (
('S', 'Setup'),
('A', 'Active'),
('P', 'Paused'),
('F', 'Finished')
)
name = models.CharField(max_length=100)
owner = models.ForeignKey(User)
created = models.DateTimeField(auto_now_add=True)
started = models.DateTimeField(null=True)
state = models.CharField(max_length=1, choices=STATE_CHOICES, default='S')
我希望创建单位,并且当状态从“设置”变为“活动”时,“已启动”字段将填充当前日期时间(以及其他内容)。
我怀疑需要一个模型实例方法,但是文档似乎没有太多关于以这种方式使用它们的说法。
更新:我已将以下内容添加到我的游戏课程中:
def __init__(self, *args, **kwargs):
super(Game, self).__init__(*args, **kwargs)
self.old_state = self.state
def save(self, force_insert=False, force_update=False):
if self.old_state == 'S' and self.state == 'A':
self.started = datetime.datetime.now()
super(Game, self).save(force_insert, force_update)
self.old_state = self.state
答案 0 :(得分:21)
已经回答了,但这里有一个使用信号,post_init和post_save的例子。
from django.db.models.signals import post_save, post_init
class MyModel(models.Model):
state = models.IntegerField()
previous_state = None
@staticmethod
def post_save(sender, **kwargs):
instance = kwargs.get('instance')
created = kwargs.get('created')
if instance.previous_state != instance.state or created:
do_something_with_state_change()
@staticmethod
def remember_state(sender, **kwargs):
instance = kwargs.get('instance')
instance.previous_state = instance.state
post_save.connect(MyModel.post_save, sender=MyModel)
post_init.connect(MyModel.remember_state, sender=MyModel)
答案 1 :(得分:15)
Django有一个名为signals的漂亮功能,它是在特定时间启动的有效触发器:
阅读文档以获取完整信息,但您需要做的就是创建一个接收器功能并将其注册为信号。这通常在models.py。
中完成from django.core.signals import request_finished
def my_callback(sender, **kwargs):
print "Request finished!"
request_finished.connect(my_callback)
简单,嗯?
答案 2 :(得分:11)
基本上,您需要覆盖save
方法,检查state
字段是否已更改,如果需要设置started
,然后让模型基类完成持久保存到数据库。
棘手的部分是弄清楚该领域是否已经改变。查看此问题中的mixins和其他解决方案,以帮助您解决此问题:
答案 3 :(得分:8)
一种方法是为州添加一个setter。这只是一种常规方法,没什么特别的。
class Game(models.Model):
# ... other code
def set_state(self, newstate):
if self.state != newstate:
oldstate = self.state
self.state = newstate
if oldstate == 'S' and newstate == 'A':
self.started = datetime.now()
# create units, etc.
更新如果您希望在对模型实例进行更改时触发,则可以(而不是 of {{上面的1}}在set_state
中使用__setattr__
方法,如下所示:
Game
请注意,您不会在Django文档中特别发现这一点,因为它(def __setattr__(self, name, value):
if name != "state":
object.__setattr__(self, name, value)
else:
if self.state != value:
oldstate = self.state
object.__setattr__(self, name, value) # use base class setter
if oldstate == 'S' and value == 'A':
self.started = datetime.now()
# create units, etc.
)是一个标准的Python功能,记录为here,并且不是特定于Django的。
注意:不知道版本低于1.2的django,但使用__setattr__
的代码无法正常工作,在尝试访问{__setattr__
后,它会在第二个if
之后失败{1}}。
我尝试了类似的操作,并尝试通过在self.state
中强制初始化state
(__init__
中的第一个)来解决此问题,但这会导致令人讨厌的意外行为。
我正在编辑而不是出于显而易见的原因进行评论,还有:我不会删除这段代码,因为它可能适用于较早(或未来?)版本的django,并且可能还有另一种解决方法{ {1}}我不知道的问题
答案 4 :(得分:4)
@dcramer为这个问题提出了一个更优雅的解决方案(在我看来)。
https://gist.github.com/730765
from django.db.models.signals import post_init
def track_data(*fields):
"""
Tracks property changes on a model instance.
The changed list of properties is refreshed on model initialization
and save.
>>> @track_data('name')
>>> class Post(models.Model):
>>> name = models.CharField(...)
>>>
>>> @classmethod
>>> def post_save(cls, sender, instance, created, **kwargs):
>>> if instance.has_changed('name'):
>>> print "Hooray!"
"""
UNSAVED = dict()
def _store(self):
"Updates a local copy of attributes values"
if self.id:
self.__data = dict((f, getattr(self, f)) for f in fields)
else:
self.__data = UNSAVED
def inner(cls):
# contains a local copy of the previous values of attributes
cls.__data = {}
def has_changed(self, field):
"Returns ``True`` if ``field`` has changed since initialization."
if self.__data is UNSAVED:
return False
return self.__data.get(field) != getattr(self, field)
cls.has_changed = has_changed
def old_value(self, field):
"Returns the previous value of ``field``"
return self.__data.get(field)
cls.old_value = old_value
def whats_changed(self):
"Returns a list of changed attributes."
changed = {}
if self.__data is UNSAVED:
return changed
for k, v in self.__data.iteritems():
if v != getattr(self, k):
changed[k] = v
return changed
cls.whats_changed = whats_changed
# Ensure we are updating local attributes on model init
def _post_init(sender, instance, **kwargs):
_store(instance)
post_init.connect(_post_init, sender=cls, weak=False)
# Ensure we are updating local attributes on model save
def save(self, *args, **kwargs):
save._original(self, *args, **kwargs)
_store(self)
save._original = cls.save
cls.save = save
return cls
return inner
答案 5 :(得分:0)
我的解决方案是将以下代码添加到应用的__init__.py
:
from django.db.models import signals
from django.dispatch import receiver
@receiver(signals.pre_save)
def models_pre_save(sender, instance, **_):
if not sender.__module__.startswith('myproj.myapp.models'):
# ignore models of other apps
return
if instance.pk:
old = sender.objects.get(pk=instance.pk)
fields = sender._meta.local_fields
for field in fields:
try:
func = getattr(sender, field.name + '_changed', None) # class function or static function
if func and callable(func) and getattr(old, field.name, None) != getattr(instance, field.name, None):
# field has changed
func(old, instance)
except:
pass
并将<field_name>_changed
静态方法添加到我的模型类中:
class Product(models.Model):
sold = models.BooleanField(default=False, verbose_name=_('Product|sold'))
sold_dt = models.DateTimeField(null=True, blank=True, verbose_name=_('Product|sold datetime'))
@staticmethod
def sold_changed(old_obj, new_obj):
if new_obj.sold is True:
new_obj.sold_dt = timezone.now()
else:
new_obj.sold_dt = None
然后sold_dt
字段将在sold
字段更改时更改。
模型中定义的任何字段的任何更改都将触发<field_name>_changed
方法,旧对象和新对象作为参数。
答案 6 :(得分:0)
使用脏污检测更改和覆盖保存方法 dirty field
我的上一条:Actions triggered by field change in Django
class Game(DirtyFieldsMixin, models.Model):
STATE_CHOICES = (
('S', 'Setup'),
('A', 'Active'),
('P', 'Paused'),
('F', 'Finished')
)
state = models.CharField(max_length=1, choices=STATE_CHOICES, default='S')
def save(self, *args, **kwargs):
if self.is_dirty():
dirty_fields = self.get_dirty_fields()
if 'state' in dirty_fields:
Do_some_action()
super().save(*args, **kwargs)
答案 7 :(得分:0)
如果您使用 PostgreSQL,您可以创建一个触发器:
https://www.postgresql.org/docs/current/sql-createtrigger.html
示例:
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.balance IS DISTINCT FROM NEW.balance)
EXECUTE FUNCTION check_account_update();