저장시 필드가 변경되었는지 어떻게 확인할 수 있습니까?


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별칭을 수정했을 때 새 이미지를 가져 오려면 어떻게 해야합니까? 둘째, 원격 이미지를 캐시하는 더 좋은 방법이 있습니까?

답변:


423

기본적으로 원래 값의 사본을 유지하도록 __init__메소드 를 대체하려고 models.Model합니다. 따라서 다른 DB 조회를 수행 할 필요가 없습니다 (항상 좋은 것임).

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
대신 초기화를 덮어 쓰는, 나는 post_init 신호 사용하십시오 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

17
@Josh는 메모리의 변경 사항 만 추적하는 동일한 데이터베이스에 대해 여러 응용 프로그램 서버가 작동하는 경우 문제가 없습니다.
Jens Alm

13
@ lajarre, 나는 당신의 의견이 약간 오도라고 생각합니다. 문서는 그렇게 할 때주의를 기울일 것을 제안합니다. 그들은 그것을 반대하지 않는 것이 좋습니다.
Josh

199

다음 믹스 인을 사용합니다.

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
정말 완벽하며 추가 쿼리를 수행하지 않습니다. 고마워요!
Stéphane

28
믹스 인 사용시 +1 추가 DB 히트가 없으면 +1입니다. 유용한 방법 / 속성에 대해 +1 여러 번 공감할 수 있어야합니다.
Jake

네. Mixin을 사용하기위한 1 개이며 추가 DB 조회가 없습니다.
David S

2
Mixin은 훌륭하지만이 버전은 .only ()와 함께 사용할 때 문제가 있습니다. Model에 3 개 이상의 필드가있는 경우 Model.objects.only ( 'id')를 호출하면 무한 재귀가 발생합니다. 이를 해결하기 위해 지연된 필드를 초기에 저장하지 못하게하고 _dict 속성 을 약간
gleb.pitsevich 1

19
Josh의 답변과 마찬가지로이 코드는 단일 프로세스 테스트 서버에서 현혹 적으로 작동하지만 모든 종류의 다중 처리 서버에 배포하는 순간 잘못된 결과를 초래합니다. 데이터베이스를 쿼리하지 않고 데이터베이스의 값을 변경하고 있는지 알 수 없습니다.
rspeer

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

36
1) 그 방법은 해킹입니다. 신호는 기본적으로 이와 같은 용도로 설계되었습니다 .2)이 방법은 모델을 변경해야합니다.이 방법은 아닙니다. 잠재적으로 문제가 될 수있다,이 솔루션은하지 않습니다
크리스 프랫

2
이 방법은 저장하기 직전에 변경 사항을 포착하는 데에만 관심이있는 경우 유용합니다. 그러나 변경 사항에 즉시 대응하려면 작동하지 않습니다. 나는 후자의 시나리오를 여러 번 겪었습니다 (그리고 지금은 그러한 인스턴스 중 하나를 연구하고 있습니다).
Josh

5
@ 조쉬 : "바로 변화에 반응한다"는 것은 무엇을 의미합니까? 어떤 방법으로 "반응"할 수 없습니까?
Chris Pratt

2
죄송합니다, 나는이 질문의 범위를 잊어 버렸고 완전히 다른 문제를 언급하고있었습니다. 즉, 신호는 여기에가는 좋은 방법이라고 생각합니다 (이제 신호를 사용할 수 있음). 그러나 많은 사람들이 "핵심"저장을 재정의하는 것을 고려하고 있습니다. 나는 이것이 사실이라고 생각하지 않습니다. 이 답변에서 알 수 있듯이 ( stackoverflow.com/questions/170337/… ) "문제의 모델에 특정한"변경 사항을 처리하지 않을 때는 재정의가 최선의 방법이라고 생각합니다. 즉, 나는 그 믿음을 누구에게도 강요하려는 의도는 없습니다.
Josh

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

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_db, refresh_from_db등을 save.
claytond


18

양식을 사용하는 경우 양식의 changed_data ( docs )를 사용할 수 있습니다 .

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

이것은 장고 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

또 다른 늦은 답변이지만 파일 필드에 새 파일이 업로드되었는지 확인하려는 경우 다음을 시도하십시오. ( http://zmsmith.com/2010/05/django 링크에 대한 Christopher Adams의 의견에서 수정되었습니다. zach의 의견에서 -check-if-a-field-has-changed /

업데이트 된 링크 : 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
다음은 오디오 정보를 읽기 위해 mutagen을 사용하여 파일을 업데이트 할 때 데이터베이스에서 오디오 지속 시간을 업데이트하는 예입니다.- gist.github.com / DataGreed / 1ba46ca7387950abba2ff53baf70fec2
DataGreed

3

최적의 솔루션은 아마도 모델 인스턴스를 저장하기 전에 추가 데이터베이스 읽기 작업이나 추가 장고 라이브러리를 포함하지 않는 솔루션 일 것입니다. 이것이 laffuste의 솔루션이 선호되는 이유입니다. 관리 사이트의 맥락에서, 위의 시온의 답변에서와 같이 단순히 save_model-method 를 재정의하고 has_changed거기 에서 폼의 메소드를 호출 할 수 있습니다 . 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")


그리고 여전히 추가 DB 쿼리를 작성하지 않습니까?
andilabs

코드를 구현하려고했습니다. 필드를 편집하면 정상적으로 작동합니다. 그러나 이제는 새로운 삽입에 문제가 있습니다. 수업 시간에 FK 필드에 DoesNotExist가 표시됩니다. 그것을 해결하는 방법에 대한 힌트를 주시면 감사하겠습니다.
andilabs

방금 코드를 업데이트했습니다. 이제 외래 키를 건너 뛰므로 추가 쿼리 (매우 비싼)로 해당 파일을 가져올 필요가 없으며 객체가 존재하지 않으면 추가 논리를 건너 뜁니다.
Hassek

1

@livskiy의 믹스 인을 다음과 같이 확장했습니다.

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)를 잊어 버린 경우 저장 기능을 비활성화하는 것이므로 저장 방법에 저장하십시오.
최대

기사의 링크가 오래되었습니다, 이것은 새로운 링크입니다 : cra.mr/2010/12/06/tracking-changes-to-fields-in-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

get_fields대신 django 1.10의 공개 방법을 사용합니다. 이로 인해 코드가 미래에 대한 증거가되지만 더 중요한 것은 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의 답변 버전transactionselect_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에 대한 날짜 시간 필드를 모델에 추가하고 변경 사항을 확인하기 전에 도달 할 수있는 최대 연령에 대한 일종의 제한을 설정할 수 있습니다


0

@ivanlivski의 믹스 인은 훌륭합니다.

나는 그것을 확장했다

  • 10 진 필드와 작동하는지 확인하십시오.
  • 사용법을 단순화하기 위해 속성 노출

업데이트 된 코드는 https://github.com/sknutsonsf/python-contrib/blob/master/src/django/utils/ModelDiffMixin.py에서 확인할 수 있습니다.

파이썬이나 장고를 처음 접하는 사람들을 돕기 위해 좀 더 완전한 예를 들겠습니다. 이 특정 사용법은 데이터 제공자에서 파일을 가져 와서 데이터베이스의 레코드가 파일을 반영하도록하는 것입니다.

내 모델 객체 :

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()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.