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


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.