Y.N*_*Y.N 57 python django django-signals
我有一个django模型,我需要在保存之前比较字段的新旧值.
我已经尝试了save()继承和pre_save信号.它被正确触发,但我找不到实际更改字段的列表,无法比较新旧值.有一种方法?我需要它来优化预先行动.
谢谢!
Odi*_*aeb 59
There is very simple django way for doing it.
"Memorise" the values in model init like this:
def __init__(self, *args, **kwargs):
super(MyClass, self).__init__(*args, **kwargs)
self.initial_parametername = self.parametername
---
self.initial_parameternameX = self.parameternameX
Run Code Online (Sandbox Code Playgroud)
Real life example:
At class:
def __init__(self, *args, **kwargs):
super(MyClass, self).__init__(*args, **kwargs)
self.__important_fields = ['target_type', 'target_id', 'target_object', 'number', 'chain', 'expiration_date']
for field in self.__important_fields:
setattr(self, '__original_%s' % field, getattr(self, field))
def has_changed(self):
for field in self.__important_fields:
orig = '__original_%s' % field
if getattr(self, orig) != getattr(self, field):
return True
return False
Run Code Online (Sandbox Code Playgroud)
And then in modelform save method:
def save(self, force_insert=False, force_update=False, commit=True):
# Prep the data
obj = super(MyClassForm, self).save(commit=False)
if obj.has_changed():
# If we're down with commitment, save this shit
if commit:
obj.save(force_insert=True)
return obj
Run Code Online (Sandbox Code Playgroud)
Sah*_*lra 39
It is better to do this at ModelForm level.
There you get all the Data that you need for comparison in save method:
If you want to do this at Model level then you can follow the method specified in Odif's answer.
psl*_*psl 30
你也可以使用FieldTracker从Django的模型utils的这个:
只需在您的模型中添加跟踪器字段:
tracker = FieldTracker()
Run Code Online (Sandbox Code Playgroud)现在在pre_save和post_save中你可以使用:
instance.tracker.previous('modelfield') # get the previous value
instance.tracker.has_changed('modelfield') # just check if it is changed
Run Code Online (Sandbox Code Playgroud)Gun*_*Fan 10
Django 1.8+及以上(包括Django 2.x和3.x),有一个from_db
classmethod,可以用来自定义从数据库加载时创建模型实例。
注意:如果您使用此方法,则没有额外的数据库查询。
from django.db import Model
class MyClass(models.Model):
@classmethod
def from_db(cls, db, field_names, values):
instance = super().from_db(db, field_names, values)
# save original values, when model is loaded from database,
# in a separate attribute on the model
instance._loaded_values = dict(zip(field_names, values))
return instance
Run Code Online (Sandbox Code Playgroud)
所以现在原始值_loaded_values
在模型的属性中可用。您可以在save
方法中访问此属性以检查是否正在更新某个值。
class MyClass(models.Model):
field_1 = models.CharField(max_length=1)
@classmethod
def from_db(cls, db, field_names, values):
...
# use code from above
def save(self, *args, **kwargs):
# check if a new db row is being added
# When this happens the `_loaded_values` attribute will not be available
if not self._state.adding:
# check if field_1 is being updated
if self._loaded_values['field_1'] != self.field_1:
# do something
super().save(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)
在现代Django中,有一个非常重要的问题需要添加到上述答案中接受的答案的内容中。当您使用QuerySet API 时,您可能会陷入无限递归。defer
only
__get__()
的方法django.db.models.query_utils.DeferredAttribute
调用的方法。中有一行,该行递归调用实例的方法。refresh_from_db()
django.db.models.Model
db_instance = db_instance_qs.get()
refresh_from_db()
__init__()
因此,有必要添加确保目标属性不被延迟的功能。
def __init__(self, *args, **kwargs):
super(MyClass, self).__init__(*args, **kwargs)
deferred_fields = self.get_deferred_fields()
important_fields = ['target_type', 'target_id', 'target_object', 'number', 'chain', 'expiration_date']
self.__important_fields = list(filter(lambda x: x not in deferred_fields, important_fields))
for field in self.__important_fields:
setattr(self, '__original_%s' % field, getattr(self, field))
Run Code Online (Sandbox Code Playgroud)
像这样的东西也有效:
class MyModel(models.Model):
my_field = fields.IntegerField()
def save(self, *args, **kwargs):
# Compare old vs new
if self.pk:
obj = MyModel.objects.values('my_value').get(pk=self.pk)
if obj['my_value'] != self.my_value:
# Do stuff...
pass
super().save(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)