保存时,如何检查字段是否已更改?


293

在我的模型中,我有:

class Alias(MyBaseModel):
    remote_image = models.URLField(max_length=500, null=True, help_text="A URL that is downloaded and cached for the image. Only
 used when the alias is made")
    image = models.ImageField(upload_to='alias', default='alias-default.png', help_text="An image representing the alias")


    def save(self, *args, **kw):
        if (not self.image or self.image.name == 'alias-default.png') and self.remote_image :
            try :
                data = utils.fetch(self.remote_image)
                image = StringIO.StringIO(data)
                image = Image.open(image)
                buf = StringIO.StringIO()
                image.save(buf, format='PNG')
                self.image.save(hashlib.md5(self.string_id).hexdigest() + ".png", ContentFile(buf.getvalue()))
            except IOError :
                pass

第一次remote_image更改效果很好。

有人修改remote_image别名上的时,如何获取新图像?其次,是否有更好的方法来缓存远程映像?

Answers:


423

本质上,您想覆盖__init__方法,models.Model以便保留原始值的副本。这样一来,您就不必进行其他数据库查找(这总是一件好事)。

class Person(models.Model):
    name = models.CharField()

    __original_name = None

    def __init__(self, *args, **kwargs):
        super(Person, self).__init__(*args, **kwargs)
        self.__original_name = self.name

    def save(self, force_insert=False, force_update=False, *args, **kwargs):
        if self.name != self.__original_name:
            # name changed - do something here

        super(Person, self).save(force_insert, force_update, *args, **kwargs)
        self.__original_name = self.name

24
而不是覆盖init,我将使用post_init-signal docs.djangoproject.com/en/dev/ref/signals/#post-init
vikingosegundo

22
重写方法,建议通过Django文档:docs.djangoproject.com/en/dev/topics/db/models/...
上校Sponsz

10
@callum,这样,如果您对对象进行更改,将其保存,然后进行其他更改并save()再次调用它,它将仍然可以正常工作。
philfreo 2012年

17
@Josh不会出现问题,如果您有多个应用服务器在同一个数据库上工作,因为它只跟踪内存中的更改
Jens Alm 2012年

13
@lajarre,我认为您的评论有点误导。文档建议您在这样做时要小心。他们不建议反对。
2012年

199

我使用以下mixin:

from django.forms.models import model_to_dict


class ModelDiffMixin(object):
    """
    A model mixin that tracks model fields' values and provide some useful api
    to know what fields have been changed.
    """

    def __init__(self, *args, **kwargs):
        super(ModelDiffMixin, self).__init__(*args, **kwargs)
        self.__initial = self._dict

    @property
    def diff(self):
        d1 = self.__initial
        d2 = self._dict
        diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
        return dict(diffs)

    @property
    def has_changed(self):
        return bool(self.diff)

    @property
    def changed_fields(self):
        return self.diff.keys()

    def get_field_diff(self, field_name):
        """
        Returns a diff for field if it's changed and None otherwise.
        """
        return self.diff.get(field_name, None)

    def save(self, *args, **kwargs):
        """
        Saves model and set initial state.
        """
        super(ModelDiffMixin, self).save(*args, **kwargs)
        self.__initial = self._dict

    @property
    def _dict(self):
        return model_to_dict(self, fields=[field.name for field in
                             self._meta.fields])

用法:

>>> p = Place()
>>> p.has_changed
False
>>> p.changed_fields
[]
>>> p.rank = 42
>>> p.has_changed
True
>>> p.changed_fields
['rank']
>>> p.diff
{'rank': (0, 42)}
>>> p.categories = [1, 3, 5]
>>> p.diff
{'categories': (None, [1, 3, 5]), 'rank': (0, 42)}
>>> p.get_field_diff('categories')
(None, [1, 3, 5])
>>> p.get_field_diff('rank')
(0, 42)
>>>

注意

请注意,该解决方案仅在当前请求的情况下才有效。因此,它主要适用于简单情况。在多个请求可以同时操作同一模型实例的并发环境中,您肯定需要不同的方法。


4
真的很完美,并且不执行额外的查询。非常感谢 !
斯特凡2013年

28
+1为使用mixin。+1没有额外的数据库命中。+1获取许多有用的方法/属性。我需要能够多次投票。
杰克

是的 再加上一个使用Mixin的功能,没有额外的数据库命中。
David S

2
Mixin很棒,但是与.only()一起使用时,该版本存在问题。如果Model至少具有3个字段,则对Model.objects.only('id')的调用将导致无限递归。为了解决这个问题,我们应该remove从初始和变化_dict财产保存推迟场有点
gleb.pitsevich

19
就像Josh的答案一样,此代码在您的单进程测试服务器上似乎可以正常工作,但是一旦将其部署到任何类型的多进程服务器上,它就会给出错误的结果。如果不查询数据库就无法知道是否要更改数据库中的值。
rspeer 2015年

154

最好的方法是发出pre_save信号。当问及回答这个问题时,可能无法回到09年,但今天看到的人都应该这样:

@receiver(pre_save, sender=MyModel)
def do_something_if_changed(sender, instance, **kwargs):
    try:
        obj = sender.objects.get(pk=instance.pk)
    except sender.DoesNotExist:
        pass # Object is new, so field hasn't technically changed, but you may want to do something else here.
    else:
        if not obj.some_field == instance.some_field: # Field has changed
            # do something

6
如果Josh上面描述的方法不涉及额外的数据库命中,为什么这是最好的方法?
joshcartme,2011年

36
1)该方法是一种hack,信号基本上是为这种用途而设计的2)该方法需要对模型进行更改,而该方法不需要3)正如您可以在该答案的注释中看到的那样,它具有以下副作用:可能有问题,但此解决方案不会
克里斯·普拉特

2
如果您只想在保存之前捕获更改,这种方法非常有用。但是,如果您要立即对更改做出反应,则此方法将无效。我已经多次遇到后一种情况(并且我现在正在研究一个这样的实例)。
乔什(Josh)2012年

5
@Josh:“立即响应更改”是什么意思?这不会以什么方式让您“反应”?
克里斯·普拉特

2
抱歉,我忘记了这个问题的范围,而是指一个完全不同的问题。就是说,我认为信号是到达此处的一种好方法(现在已经可用)。但是,我发现许多人都认为重写比“保存”要好。我认为情况并非如此。正如这个答案所建议的那样(stackoverflow.com/questions/170337/…),我认为当您不进行“特定于所讨论模型的更改”时,覆盖是最佳实践。就是说,我无意将这种信念强加给任何人。
2012年

138

现在直接回答:一种检查字段值是否更改的方法是在保存实例之前从数据库中获取原始数据。考虑以下示例:

class MyModel(models.Model):
    f1 = models.CharField(max_length=1)

    def save(self, *args, **kw):
        if self.pk is not None:
            orig = MyModel.objects.get(pk=self.pk)
            if orig.f1 != self.f1:
                print 'f1 changed'
        super(MyModel, self).save(*args, **kw)

使用表单时,同样的情况适用。您可以在ModelForm的clean或save方法中检测到它:

class MyModelForm(forms.ModelForm):

    def clean(self):
        cleaned_data = super(ProjectForm, self).clean()
        #if self.has_changed():  # new instance or existing updated (form has data to save)
        if self.instance.pk is not None:  # new instance only
            if self.instance.f1 != cleaned_data['f1']:
                print 'f1 changed'
        return cleaned_data

    class Meta:
        model = MyModel
        exclude = []

24
Josh的解决方案对数据库更友好。额外的电话来验证更改是昂贵的。
dd。

4
在写之前多读一遍并不昂贵。另外,如果有多个请求,则跟踪更改方法将不起作用。尽管这会在获取和保存之间出现竞争状况。
dalore '16

1
停止告诉人们检查pk is not None它是否不适用,例如,如果使用UUIDField。这只是个坏建议。
user3467349

2
@dalore您可以通过装饰保存方法来避免竞争状况@transaction.atomic
Frank Pape

2
@dalore,尽管您需要确保事务隔离级别足够。在postgresql中,默认值是已提交读,但可重复读是必需的
Frank Pape

58

自Django 1.8发布以来,您可以使用from_db类方法来缓存remote_image的旧值。然后,在保存方法中,您可以比较字段的新旧值以检查该值是否已更改。

@classmethod
def from_db(cls, db, field_names, values):
    new = super(Alias, cls).from_db(db, field_names, values)
    # cache value went from the base
    new._loaded_remote_image = values[field_names.index('remote_image')]
    return new

def save(self, force_insert=False, force_update=False, using=None,
         update_fields=None):
    if (self._state.adding and self.remote_image) or \
        (not self._state.adding and self._loaded_remote_image != self.remote_image):
        # If it is first save and there is no cached remote_image but there is new one, 
        # or the value of remote_image has changed - do your stuff!

1
谢谢-这是对文档的引用: docs.djangoproject.com/en/1.8/ref/models/instances/…。我认为这仍然会导致上述问题,其中数据库可能会在评估时间和完成比较之间发生变化,但这是一个不错的新选择。
trpt4him

1
与其搜索值(基于值的数量为O(n))不是不是更快,更清晰new._loaded_remote_image = new.remote_image
dalore

1
不幸的是,我不得不撤消我以前的评论(现已删除)。当from_db由调用时refresh_from_db,实例上的属性(即已加载或先前的)不会更新。其结果是,我找不到任何理由,这是比更好__init__,你仍然需要处理三种情况: __init__/ from_dbrefresh_from_dbsave
claytond


18

如果您使用的是表格,则可以使用表格的changed_datadocs):

class AliasForm(ModelForm):

    def save(self, commit=True):
        if 'remote_image' in self.changed_data:
            # do things
            remote_image = self.cleaned_data['remote_image']
            do_things(remote_image)
        super(AliasForm, self).save(commit)

    class Meta:
        model = Alias



5

这对我在Django 1.8中有效

def clean(self):
    if self.cleaned_data['name'] != self.initial['name']:
        # Do something

4

您可以使用django-model-changes来执行此操作,而无需其他数据库查找:

from django.dispatch import receiver
from django_model_changes import ChangesMixin

class Alias(ChangesMixin, MyBaseModel):
   # your model

@receiver(pre_save, sender=Alias)
def do_something_if_changed(sender, instance, **kwargs):
    if 'remote_image' in instance.changes():
        # do something

4

另一个较晚的答案,但是如果您只是想查看是否有新文件上传到文件字段,请尝试以下操作:(改编自克里斯托弗·亚当斯(Christopher Adams)对链接http://zmsmith.com/2010/05/django的评论-在扎克的评论中检查是否更改了字段

更新的链接:https : //web.archive.org/web/20130101010327/http : //zmsmith.com : 80/2010/05/django-check-if-a-field-has-changed/

def save(self, *args, **kw):
    from django.core.files.uploadedfile import UploadedFile
    if hasattr(self.image, 'file') and isinstance(self.image.file, UploadedFile) :
        # Handle FileFields as special cases, because the uploaded filename could be
        # the same as the filename that's already there even though there may
        # be different file contents.

        # if a file was just uploaded, the storage model with be UploadedFile
        # Do new file stuff here
        pass

对于检查是否上传了新文件,这是一个很棒的解决方案。比起用数据库检查名称要好得多,因为文件名可以相同。您也可以在pre_save接收器中使用它。感谢您分享!
DataGreed

1
这是一个使用诱变剂读取音频信息来更新文件时更新数据库中音频持续时间的示例-gist.github.com/DataGreed/1ba46ca7387950abba2ff53baf70fec2
DataGreed

3

最佳解决方案可能是在保存模型实例之前不包括其他数据库读取操作的解决方案,也不包括任何其他django库。这就是Laffuste解决方案更可取的原因。在管理站点的上下文中,可以简单地覆盖save_model-method,然后在has_changed该处调用表单的方法,就像上面Sion的回答一样。使用Sion的示例设置,您会得到如下所示的信息,但它changed_data用来获取所有可能的更改:

class ModelAdmin(admin.ModelAdmin):
   fields=['name','mode']
   def save_model(self, request, obj, form, change):
     form.changed_data #output could be ['name']
     #do somethin the changed name value...
     #call the super method
     super(self,ModelAdmin).save_model(request, obj, form, change)
  • 覆写save_model

https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model

  • changed_data字段的内置方法:

https://docs.djangoproject.com/en/1.10/ref/forms/api/#django.forms.Form.changed_data


2

尽管这实际上并不能回答您的问题,但我将以另一种方式进行处理。

remote_image成功保存本地副本后,只需清除该字段。然后,在保存方法中,只要remote_image不为空,就始终可以更新图像。

如果您想保留对url的引用,则可以使用不可编辑的布尔字段来处理缓存标志,而不是remote_image字段本身。


2

在解决方案要覆盖pre_save()目标字段类的方法之前,我曾遇到过这种情况,仅当字段已更改
为FileField示例有用时才会调用该方法:

class PDFField(FileField):
    def pre_save(self, model_instance, add):
        # do some operations on your file 
        # if and only if you have changed the filefield

缺点:
如果您想执行任何(post_save)操作,例如在某些工作中使用创建的对象(如果某些字段已更改),则无用


2

为所有领域改进@josh答案:

class Person(models.Model):
  name = models.CharField()

def __init__(self, *args, **kwargs):
    super(Person, self).__init__(*args, **kwargs)
    self._original_fields = dict([(field.attname, getattr(self, field.attname))
        for field in self._meta.local_fields if not isinstance(field, models.ForeignKey)])

def save(self, *args, **kwargs):
  if self.id:
    for field in self._meta.local_fields:
      if not isinstance(field, models.ForeignKey) and\
        self._original_fields[field.name] != getattr(self, field.name):
        # Do Something    
  super(Person, self).save(*args, **kwargs)

只是为了澄清,getattr的工作原理是获取类似于person.name字符串的字段(即getattr(person, "name")


而且它仍然没有进行额外的数据库查询吗?
andilabs 2014年

我正在尝试实现您的代码。通过编辑字段可以正常工作。但是现在我有插入新的问题。我在课堂上的FK字段得到DidsNotExist。一些提示如何解决它将不胜感激。
andilabs 2014年

我刚刚更新了代码,现在它跳过了外键,因此您不需要通过额外的查询来获取这些文件(非常昂贵),并且如果该对象不存在,它将跳过额外的逻辑。
哈塞克2014年

1

我将@livskiy的mixin扩展如下:

class ModelDiffMixin(models.Model):
    """
    A model mixin that tracks model fields' values and provide some useful api
    to know what fields have been changed.
    """
    _dict = DictField(editable=False)
    def __init__(self, *args, **kwargs):
        super(ModelDiffMixin, self).__init__(*args, **kwargs)
        self._initial = self._dict

    @property
    def diff(self):
        d1 = self._initial
        d2 = self._dict
        diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
        return dict(diffs)

    @property
    def has_changed(self):
        return bool(self.diff)

    @property
    def changed_fields(self):
        return self.diff.keys()

    def get_field_diff(self, field_name):
        """
        Returns a diff for field if it's changed and None otherwise.
        """
        return self.diff.get(field_name, None)

    def save(self, *args, **kwargs):
        """
        Saves model and set initial state.
        """
        object_dict = model_to_dict(self,
               fields=[field.name for field in self._meta.fields])
        for field in object_dict:
            # for FileFields
            if issubclass(object_dict[field].__class__, FieldFile):
                try:
                    object_dict[field] = object_dict[field].path
                except :
                    object_dict[field] = object_dict[field].name

            # TODO: add other non-serializable field types
        self._dict = object_dict
        super(ModelDiffMixin, self).save(*args, **kwargs)

    class Meta:
        abstract = True

并且DictField是:

class DictField(models.TextField):
    __metaclass__ = models.SubfieldBase
    description = "Stores a python dict"

    def __init__(self, *args, **kwargs):
        super(DictField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        if not value:
            value = {}

        if isinstance(value, dict):
            return value

        return json.loads(value)

    def get_prep_value(self, value):
        if value is None:
            return value
        return json.dumps(value)

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

可以通过在模型中扩展它来使用它,当您同步/迁移时,将添加_dict字段,并且该字段将存储对象的状态


1

如何使用David Cramer的解决方案:

http://cramer.io/2010/12/06/tracking-changes-to-fields-in-django/

我已经成功地使用了它:

@track_data('name')
class Mode(models.Model):
    name = models.CharField(max_length=5)
    mode = models.CharField(max_length=5)

    def save(self, *args, **kwargs):
        if self.has_changed('name'):
            print 'name changed'

    # OR #

    @classmethod
    def post_save(cls, sender, instance, created, **kwargs):
        if instance.has_changed('name'):
            print "Hooray!"

2
如果您忘记了super(Mode,self).save(* args,** kwargs),那么您将禁用保存功能,因此请记住将其放在save方法中。
最多

本文的链接已过时,这是新链接:cra.mr/2010/12/06/tracking-changes-to-fields-d-django
GoTop

1

@ivanperelivskiy的答案的修改:

@property
def _dict(self):
    ret = {}
    for field in self._meta.get_fields():
        if isinstance(field, ForeignObjectRel):
            # foreign objects might not have corresponding objects in the database.
            if hasattr(self, field.get_accessor_name()):
                ret[field.get_accessor_name()] = getattr(self, field.get_accessor_name())
            else:
                ret[field.get_accessor_name()] = None
        else:
            ret[field.attname] = getattr(self, field.attname)
    return ret

这使用Django 1.10的public方法 get_fields。这使代码更具前瞻性,但更重要的是还包括外键和其中editable = False的字段。

供参考,这里是 .fields

@cached_property
def fields(self):
    """
    Returns a list of all forward fields on the model and its parents,
    excluding ManyToManyFields.

    Private API intended only to be used by Django itself; get_fields()
    combined with filtering of field properties is the public API for
    obtaining this field list.
    """
    # For legacy reasons, the fields property should only contain forward
    # fields that are not private or with a m2m cardinality. Therefore we
    # pass these three filters as filters to the generator.
    # The third lambda is a longwinded way of checking f.related_model - we don't
    # use that property directly because related_model is a cached property,
    # and all the models may not have been loaded yet; we don't want to cache
    # the string reference to the related_model.
    def is_not_an_m2m_field(f):
        return not (f.is_relation and f.many_to_many)

    def is_not_a_generic_relation(f):
        return not (f.is_relation and f.one_to_many)

    def is_not_a_generic_foreign_key(f):
        return not (
            f.is_relation and f.many_to_one and not (hasattr(f.remote_field, 'model') and f.remote_field.model)
        )

    return make_immutable_fields_list(
        "fields",
        (f for f in self._get_fields(reverse=False)
         if is_not_an_m2m_field(f) and is_not_a_generic_relation(f) and is_not_a_generic_foreign_key(f))
    )

1

这是另一种方法。

class Parameter(models.Model):

    def __init__(self, *args, **kwargs):
        super(Parameter, self).__init__(*args, **kwargs)
        self.__original_value = self.value

    def clean(self,*args,**kwargs):
        if self.__original_value == self.value:
            print("igual")
        else:
            print("distinto")

    def save(self,*args,**kwargs):
        self.full_clean()
        return super(Parameter, self).save(*args, **kwargs)
        self.__original_value = self.value

    key = models.CharField(max_length=24, db_index=True, unique=True)
    value = models.CharField(max_length=128)

根据文档: 验证对象

“ full_clean()执行的第二步是调用Model.clean()。应重写此方法以对模型执行自定义验证。应使用此方法提供自定义模型验证,并根据需要修改模型上的属性例如,您可以使用它为字段自动提供一个值,或进行需要访问多个字段的验证:“


1

有一个属性__dict__,它具有所有字段作为键,而值则作为字段值。所以我们可以比较两个

只需将模型的保存功能更改为以下功能

def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
    if self.pk is not None:
        initial = A.objects.get(pk=self.pk)
        initial_json, final_json = initial.__dict__.copy(), self.__dict__.copy()
        initial_json.pop('_state'), final_json.pop('_state')
        only_changed_fields = {k: {'final_value': final_json[k], 'initial_value': initial_json[k]} for k in initial_json if final_json[k] != initial_json[k]}
        print(only_changed_fields)
    super(A, self).save(force_insert=False, force_update=False, using=None, update_fields=None)

用法示例:

class A(models.Model):
    name = models.CharField(max_length=200, null=True, blank=True)
    senior = models.CharField(choices=choices, max_length=3)
    timestamp = models.DateTimeField(null=True, blank=True)

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        if self.pk is not None:
            initial = A.objects.get(pk=self.pk)
            initial_json, final_json = initial.__dict__.copy(), self.__dict__.copy()
            initial_json.pop('_state'), final_json.pop('_state')
            only_changed_fields = {k: {'final_value': final_json[k], 'initial_value': initial_json[k]} for k in initial_json if final_json[k] != initial_json[k]}
            print(only_changed_fields)
        super(A, self).save(force_insert=False, force_update=False, using=None, update_fields=None)

仅输出那些已更改的字段的输出

{'name': {'initial_value': '1234515', 'final_value': 'nim'}, 'senior': {'initial_value': 'no', 'final_value': 'yes'}}

1

比赛进行到很晚了,但这是克里斯·普拉特Chris Pratt)的回答版本,它通过使用transaction块和select_for_update()

@receiver(pre_save, sender=MyModel)
@transaction.atomic
def do_something_if_changed(sender, instance, **kwargs):
    try:
        obj = sender.objects.select_for_update().get(pk=instance.pk)
    except sender.DoesNotExist:
        pass # Object is new, so field hasn't technically changed, but you may want to do something else here.
    else:
        if not obj.some_field == instance.some_field: # Field has changed
            # do something

0

作为SmileyChris回答的扩展,您可以在模型中为last_updated添加datetime字段,并为检查更改之前允许的最大年龄设置某种限制


0

@ivanlivski的mixin很棒。

我将其扩展到

  • 确保它与十进制字段一起使用。
  • 公开属性以简化用法

更新的代码可在此处获得: https //github.com/sknutsonsf/python-contrib/blob/master/src/django/utils/ModelDiffMixin.py

为了帮助不熟悉Python或Django的人,我将举一个更完整的示例。这种特殊用法是从数据提供者那里获取文件,并确保数据库中的记录反映出该文件。

我的模型对象:

class Station(ModelDiffMixin.ModelDiffMixin, models.Model):
    station_name = models.CharField(max_length=200)
    nearby_city = models.CharField(max_length=200)

    precipitation = models.DecimalField(max_digits=5, decimal_places=2)
    # <list of many other fields>

   def is_float_changed (self,v1, v2):
        ''' Compare two floating values to just two digit precision
        Override Default precision is 5 digits
        '''
        return abs (round (v1 - v2, 2)) > 0.01

加载文件的类具有以下方法:

class UpdateWeather (object)
    # other methods omitted

    def update_stations (self, filename):
        # read all existing data 
        all_stations = models.Station.objects.all()
        self._existing_stations = {}

        # insert into a collection for referencing while we check if data exists
        for stn in all_stations.iterator():
            self._existing_stations[stn.id] = stn

        # read the file. result is array of objects in known column order
        data = read_tabbed_file(filename)

        # iterate rows from file and insert or update where needed
        for rownum in range(sh.nrows):
            self._update_row(sh.row(rownum));

        # now anything remaining in the collection is no longer active
        # since it was not found in the newest file
        # for now, delete that record
        # there should never be any of these if the file was created properly
        for stn in self._existing_stations.values():
            stn.delete()
            self._num_deleted = self._num_deleted+1


    def _update_row (self, rowdata):
        stnid = int(rowdata[0].value) 
        name = rowdata[1].value.strip()

        # skip the blank names where data source has ids with no data today
        if len(name) < 1:
            return

        # fetch rest of fields and do sanity test
        nearby_city = rowdata[2].value.strip()
        precip = rowdata[3].value

        if stnid in self._existing_stations:
            stn = self._existing_stations[stnid]
            del self._existing_stations[stnid]
            is_update = True;
        else:
            stn = models.Station()
            is_update = False;

        # object is new or old, don't care here            
        stn.id = stnid
        stn.station_name = name;
        stn.nearby_city = nearby_city
        stn.precipitation = precip

        # many other fields updated from the file 

        if is_update == True:

            # we use a model mixin to simplify detection of changes
            # at the cost of extra memory to store the objects            
            if stn.has_changed == True:
                self._num_updated = self._num_updated + 1;
                stn.save();
        else:
            self._num_created = self._num_created + 1;
            stn.save()

0

如果您对覆盖save方法不感兴趣,可以这样做

  model_fields = [f.name for f in YourModel._meta.get_fields()]
  valid_data = {
        key: new_data[key]
        for key in model_fields
        if key in new_data.keys()
  }

  for (key, value) in valid_data.items():
        if getattr(instance, key) != value:
           print ('Data has changed')

        setattr(instance, key, value)

 instance.save()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.