我有一个django模型,在保存之前,我需要比较字段的新旧值。
我已经尝试过save()继承和pre_save信号。它已正确触发,但是我找不到实际更改的字段的列表,也无法比较新旧值。有一种方法?我需要它来优化预保存动作。
谢谢!
我有一个django模型,在保存之前,我需要比较字段的新旧值。
我已经尝试过save()继承和pre_save信号。它已正确触发,但是我找不到实际更改的字段的列表,也无法比较新旧值。有一种方法?我需要它来优化预保存动作。
谢谢!
Answers:
有一种非常简单的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__
叫?它也仅适用于初始创建或后续更新吗?
__init__
仅在开始时才调用。
save
bulk_create
最好在ModelForm级别执行此操作。
在那里,您可以在保存方法中获得进行比较所需的所有数据:
如果要在模型级别执行此操作,则可以遵循Odif答案中指定的方法。
_post_clean
(is_valid->errors->full_clean->_post_clean
)之前,此后实例将更新为包括新值。访问中form.clean_fieldname()
和form.clean()
似乎确定提供的是他们的第一个电话。
self.changed_data
对我来说是个新手
您也可以从django-model-utils使用FieldTracker:
只需在模型中添加跟踪器字段即可:
tracker = FieldTracker()
现在在pre_save和post_save中可以使用:
instance.tracker.previous('modelfield') # get the previous value
instance.tracker.has_changed('modelfield') # just check if it is changed
在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)
我的用例是,每当某个字段更改其值时,我都需要在模型中设置一个非规格化的值。但是,由于要监视的字段是m2m关系,因此我不想在每次调用save时都要执行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
这样的事情也可以:
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)
这是一个应用程序,可让您在保存模型之前访问字段的先前值和当前值: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
在现代Django中,将重要的问题添加到上述答案中已接受的答案的内容中。使用或时,您可以陷入无限递归defer
only
QuerySet API。
__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))
save
然后检查每个字段是否相等,该怎么办?