我有一个Django模型,需要在保存前比较字段的旧值和新值。
我已经尝试过save()
继承和pre_save
信号。它们都被正确触发了,但我找不到实际更改的字段列表,也无法比较旧值和新值。是否有方法可以解决?我需要这个来优化pre-save操作。
谢谢!
我有一个Django模型,需要在保存前比较字段的旧值和新值。
我已经尝试过save()
继承和pre_save
信号。它们都被正确触发了,但我找不到实际更改的字段列表,也无法比较旧值和新值。是否有方法可以解决?我需要这个来优化pre-save操作。
谢谢!
对于这个问题,Django 有非常简单的方法。
可以在模型的 init 方法中使用“记忆”传入的值,如下所示:
def __init__(self, *args, **kwargs):
super(MyClass, self).__init__(*args, **kwargs)
self.initial_parametername = self.parametername
---
self.initial_parameternameX = self.parameternameX
现实生活中的例子:
在课堂上:
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
然后在modelform的保存方法中:
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
__init__
什么时候被调用?它只在初始创建时工作还是在后续更新中也有效? - wasabigeek__init__
。如果实例在其生命周期内更新了多次,则__init__
仅在开始时调用。 - Odif Yltsaebsave
或 bulk_create
将模型保存到其他位置的情况。 - Julio MarinsModel.objects.delete()
时,如果我想要缓存的字段是外键(即使您尝试将self._old_<field>_id
存储为整数),我会遇到许多问题(包括 Python 崩溃或达到递归限制)。 - DimmuR最好在ModelForm级别上执行此操作。
在那里,您可以获得所有需要比较的数据,以在保存方法中使用:
如果您想在模型级别进行此操作,则可以按照Odif答案中指定的方法进行。
_post_clean
(is_valid->errors->full_clean->_post_clean
)之前,此时实例将被更新以包括新值。如果是它们的第一次调用,则在form.clean_fieldname()
和form.clean()
中访问似乎是可以的。 - jozxyqkself.changed_data
对我来说是新的。 - Mohammed Shareef CJust add tracker field to your model:
tracker = FieldTracker()
Now in pre_save and post_save you can use:
instance.tracker.previous('modelfield') # get the previous value
instance.tracker.has_changed('modelfield') # just check if it is changed
tracker.py
的源代码。看起来需要很多工作和信号传递。所以问题是它是否值得使用,或者用例太有限只需要追踪一个或两个字段。 - John PangDjango的文档中包含一个示例,准确演示如何执行此操作:
从Django 1.8开始(包括Django 2.x和3.x),有一个from_db
类方法,可用于在从数据库加载时自定义模型实例创建。
注意:如果使用此方法,则不会有额外的数据库查询。
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
因此,现在原始值可以在模型的 _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)
defer
或only
查询集API时,您可能会陷入无限递归的情况中。
django.db.models.query_utils.DeferredAttribute
的__get __()
方法调用django.db.models.Model
的refresh_from_db()
方法。在refresh_from_db()
中有一行db_instance = db_instance_qs.get()
,这一行递归地调用了实例的__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))
这样也可以起作用:
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)
我的使用情况是,每当某个字段更改其值时,我需要在模型中设置一个非规范化的值。然而,由于被监视的字段是m2m关系,我不想在每次调用保存时执行DB查找以检查是否需要更新非规范化字段。因此,我编写了这个小混合类(使用@Odif Yitsaeb的答案作为灵感),以便仅在必要时更新非规范化字段。
class HasChangedMixin(object):
""" this mixin gives subclasses the ability to set fields for which they want to monitor if the field value changes """
monitor_fields = []
def __init__(self, *args, **kwargs):
super(HasChangedMixin, self).__init__(*args, **kwargs)
self.field_trackers = {}
def __setattr__(self, key, value):
super(HasChangedMixin, self).__setattr__(key, value)
if key in self.monitor_fields and key not in self.field_trackers:
self.field_trackers[key] = value
def changed_fields(self):
"""
:return: `list` of `str` the names of all monitor_fields which have changed
"""
changed_fields = []
for field, initial_field_val in self.field_trackers.items():
if getattr(self, field) != initial_field_val:
changed_fields.append(field)
return changed_fields
这里有一个应用程序可以让您在模型保存之前访问字段的先前和当前值:django-smartfields
以下是如何以优美的声明性方式解决此问题:
from django.db import models
from smartfields import fields, processors
from smartfields.dependencies import Dependency
class ConditionalProcessor(processors.BaseProcessor):
def process(self, value, stashed_value=None, **kwargs):
if value != stashed_value:
# do any necessary modifications to new value
value = ...
return value
class MyModel(models.Model):
my_field = fields.CharField(max_length=10, dependencies=[
Dependency(processor=ConditionalProcessor())
])
我同意Sahil的观点,使用ModelForm更好更容易。但是,您需要自定义ModelForm的clean方法并在那里执行验证。在我的情况下,我想要防止更新模型实例,如果模型上的字段已设置。
我的代码如下:
from django.forms import ModelForm
class ExampleForm(ModelForm):
def clean(self):
cleaned_data = super(ExampleForm, self).clean()
if self.instance.field:
raise Exception
return cleaned_data
post_init
和 post_save
信号来存储模型的初始状态。@receiver(models.signals.post_init)
@receiver(models.signals.post_save)
def _set_initial_state(
sender: Type[Any],
instance: Optional[models.Model] = None,
**kwargs: Any,
) -> None:
"""
Store the initial state of the model
"""
if isinstance(instance, MyModel):
instance._initial_state = instance.state
其中state
是MyModel
中一个字段的名称,_initial_state
是初始化/保存模态框时复制的初始版本。
请注意,如果state
是容器类型(例如字典),您可能需要适当使用deepcopy
。
state
属性。你是不是指的是instance._state
?无论如何,你怎么访问最初的字段值呢?instance._state
似乎没有存储这些值。 - JGCstate
是您希望保存的变量名称。 _initial_state
是已保存的副本。请使用适当的变量名称进行替换。 - Danielle Madeleyinstance._initial_state = instance.state
时出现错误,因为 instance.state
不存在。我收到了一个错误,指出“instance没有state属性”。 - JGCstate
的字段吗? - Danielle Madeleystate
是模型字段,而 _initial_state
是初始值的拷贝。这样就解释了一切。我之前以为 state
是 Django 内置变量,用于存储模型状态。 - JGCstate
是您模型中字段的名称。 - Danielle Madeley
save
方法中从数据库中获取旧值,然后逐个字段进行比较,这样怎么样? - J0HN