Python django-保存前比较新旧字段值

Python django-保存前比较新旧字段值,python,django,django-signals,Python,Django,Django Signals,我有一个django模型,我需要在保存之前比较字段的新旧值 我尝试了save()继承和pre\u save信号。它被正确触发,但我找不到实际更改字段的列表,也无法比较新旧值。有办法吗?我需要它来优化预保存操作 谢谢大家! 有一种非常简单的django方法 “记忆”model init中的值,如下所示: def __init__(self, *args, **kwargs): super(MyClass, self).__init__(*args, **kwargs) self.i

我有一个django模型,我需要在保存之前比较字段的新旧值

我尝试了
save()
继承和
pre\u save
信号。它被正确触发,但我找不到实际更改字段的列表,也无法比较新旧值。有办法吗?我需要它来优化预保存操作


谢谢大家!

有一种非常简单的django方法

“记忆”model init中的值,如下所示:

def __init__(self, *args, **kwargs):
    super(MyClass, self).__init__(*args, **kwargs)
    self.initial_parametername = self.parametername
    ---
    self.initial_parameternameX = self.parameternameX
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
现实生活中的例子:

上课时:

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

最好在模型级执行此操作

在这里,您可以在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
  • self.data:传递到表单的实际数据
  • self.cleaned_data:验证后清理的数据包含符合条件保存在模型中的数据
  • self.changed\u数据:已更改的字段列表。如果没有任何更改,则此字段将为空
  • 如果您想在模型级别执行此操作,则可以按照Odif答案中指定的方法进行操作。

    您也可以使用from来执行此操作:

  • 只需将跟踪器字段添加到您的模型:

    tracker = FieldTracker()
    
  • 现在,在预保存和后保存中,您可以使用:

    instance.tracker.previous('modelfield')     # get the previous value
    instance.tracker.has_changed('modelfield')  # just check if it is changed
    

  • 以下是一个应用程序,您可以在保存模型之前访问字段的上一个值和当前值:

    下面是如何用一个好的声明性语句解决这个问题:

    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())
        ])
    

    此外,只有当字段的值被替换时,才会调用该处理器。我的用例是,每当某个字段更改其值时,我需要在模型中设置一个非规范化值。但是,由于被监视的字段是m2m关系,所以我不想在调用save时进行DB查找,以检查非规范化字段是否需要更新。因此,我编写了这个小mixin(使用@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
    

    我同意Sahil的观点,使用ModelForm做这件事更好更容易。但是,您需要自定义ModelForm的clean方法并在那里执行验证。在我的例子中,如果模型上设置了字段,我希望阻止对模型实例的更新

    我的代码如下所示:

    def __init__(self, *args, **kwargs):
        super(MyClass, self).__init__(*args, **kwargs)
        self.initial_parametername = self.parametername
        ---
        self.initial_parameternameX = self.parameternameX
    
    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
    

    类似的方法也有效:

    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 1.8+及更高版本(包括Django 2.x和3.x),有一个
    from_db
    classmethod,可用于自定义从数据库加载时的模型实例创建

    注意:如果使用此方法,则不会有额外的数据库查询

    这是官方文件的链接

    因此,现在可以在模型的
    \u loaded\u 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)
                
                
    

    另一种方法是使用
    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时,可能会陷入无限递归

    \uu get\uu()
    django.db.models.query\u utils.DeferredAttribute的方法调用
    刷新\u from\u db()
    django.db.models.Model的方法。在
    refresh\u from\u db()
    中有一行
    db\u instance=db\u instance.get()
    ,该行递归调用实例的
    \u init\u()
    方法

    因此,有必要添加确保目标属性不被延迟的属性

    def\uuuu init\uuuu(self,*args,**kwargs):
    超级(MyClass,self)。\uuuuuuuuuuuuuuu初始化(*args,**kwargs)
    延迟的\u字段=self.get\u延迟的\u字段()
    重要_字段=['target_type'、'target_id'、'target_object'、'number'、'chain'、'expiration_date']
    self.\uuuu重要字段=列表(过滤器(lambda x:x不在延迟字段中,重要字段))
    对于自身中的字段。\u重要\u字段:
    setattr(self),\uuuu原始\uuu%s'%field,getattr(self,field))
    
    save
    方法中从DB中提取旧值,然后检查每个字段是否相等,如何处理?您需要哪种优化?@J0HN在提取、比较和保存过程中,da之间发生了变化。我认为,它可以而且必须降低性能?可能会重复我同意您的答案,self.instance也可以用于此问题。@AlexeyKuleshevich同意,但只有在表单的
    \u post\u clean
    有效->错误->完整\u clean->\u post\u clean
    )之前,才会更新实例以包含新值。在
    form.clean\u fieldname()
    form.clean()
    中访问似乎可以,前提是这是他们的第一次调用。这很有效,但只有在使用表单保存时才有效,情况并非总是如此。是的,没错。如果您没有使用表单,则无法执行此操作。但是使用表单是理想的方式。
    self.changed\u data
    对于我来说是一种新的方式,我更喜欢Odif的方式,因为我需要在调用
    \u init\u
    时触发没有表单的模型的操作(在更改来自api或管理站点之后)?它是否仅适用于初始创建或后续更新?每次创建模型实例时都会调用Init。如果实例在其生命周期内多次更新,则只会在开始时调用
    \uuu init\uuuu
    。如果使用
    save
    bulk\u create
    将模型保存在其他位置,则这将不适用。但是模型保存几乎从来不会突然发生,因为之前没有交互。has_change方法可以在所有这些地方使用。当你创建对象的时候