문제

내 앱에서는 모델이 저장될 때 변경된 값(이전 및 새 값)을 저장해야 합니다.예제나 작업 코드가 있나요?

콘텐츠 사전 조정을 위해 이것이 필요합니다.예를 들어, 사용자가 모델에서 무언가를 변경하면 관리자는 별도의 테이블에서 모든 변경 사항을 확인한 다음 적용 여부를 결정할 수 있습니다.

도움이 되었습니까?

해결책

구체적인 사용 사례나 요구 사항에 대해 많이 언급하지 않았습니다.특히, 변경 정보를 어떻게 처리해야 하는지(얼마나 오래 보관해야 하는지) 알아두면 도움이 될 것입니다.일시적인 목적으로만 저장해야 하는 경우 @S.Lott의 세션 솔루션이 가장 적합할 수 있습니다.DB에 저장된 객체에 대한 모든 변경 사항에 대한 전체 감사 추적을 원한다면 다음을 시도하십시오. AuditTrail 솔루션.

업데이트:위에 링크한 AuditTrail 코드는 몇 가지 제한 사항이 있지만(ManyToMany 필드에서는 전혀 작동하지 않음) 귀하의 경우에 작동하는 전체 솔루션에 가장 가까운 코드입니다.이전 버전의 개체를 모두 DB에 저장하므로 관리자는 이전 버전으로 롤백할 수 있습니다.승인될 때까지 변경 사항이 적용되지 않도록 하려면 약간의 작업이 필요합니다.

@Armin Ronacher의 DiffingMixin과 같은 것을 기반으로 사용자 정의 솔루션을 구축할 수도 있습니다.관리자가 나중에 검토하고 원하는 경우 적용할 수 있도록 diff 사전(절임?)을 테이블에 저장합니다(diff 사전을 가져오고 인스턴스에 적용하려면 코드를 작성해야 합니다).

다른 팁

나는 Armin의 아이디어가 매우 유용하다는 것을 알았습니다.여기 내 변형이 있습니다.

class DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        self._original_state = self._as_dict()

    def _as_dict(self):
        return dict([(f.name, getattr(self, f.name)) for f in self._meta.local_fields if not f.rel])

    def get_dirty_fields(self):
        new_state = self._as_dict()
        return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])

편집하다:나는 이 BTW를 테스트했습니다.

줄이 길어서 죄송합니다.차이점은 (이름과는 별도로) 로컬 비관계 필드만 캐시한다는 것입니다.즉, 상위 모델의 필드가 있는 경우 이를 캐시하지 않습니다.

그리고 한 가지가 더 있습니다.재설정해야 해 _original_state 저장 후 딕셔너리.하지만 덮어쓰고 싶지는 않았어요 save() 대부분의 경우 저장 후 모델 인스턴스를 삭제하기 때문에 이 방법을 사용합니다.

def save(self, *args, **kwargs):
    super(Klass, self).save(*args, **kwargs)
    self._original_state = self._as_dict()

Django는 방금 변경한 경우에도 현재 모든 열을 데이터베이스로 보내고 있습니다.이를 변경하려면 데이터베이스 시스템의 일부 변경이 필요합니다.이는 모델에 더티 필드 세트를 추가하고 열 이름을 추가함으로써 기존 코드에서 쉽게 구현할 수 있습니다. __set__ 열 값.

해당 기능이 필요하다면 Django ORM을 살펴보고 이를 구현한 후 Django trac에 패치를 적용하는 것이 좋습니다.이를 추가하는 것은 매우 쉬워야 하며 다른 사용자에게도 도움이 될 것입니다.그렇게 할 때 열이 설정될 때마다 호출되는 후크를 추가하세요.

Django 자체를 해킹하고 싶지 않다면 객체 생성 시 딕셔너리를 복사하여 비교할 수 있습니다.

어쩌면 다음과 같은 믹스인을 사용하면 됩니다:

class DiffingMixin(object):

    def __init__(self, *args, **kwargs):
        super(DiffingMixin, self).__init__(*args, **kwargs)
        self._original_state = dict(self.__dict__)

    def get_changed_columns(self):
        missing = object()
        result = {}
        for key, value in self._original_state.iteritems():
            if key != self.__dict__.get(key, missing):
                result[key] = value
        return result

 class MyModel(DiffingMixin, models.Model):
     pass

이 코드는 테스트되지 않았지만 작동할 것입니다.전화할 때 model.get_changed_columns() 변경된 모든 값에 대한 사전을 얻습니다.물론 원래 상태는 dict의 단순 복사본이기 때문에 열의 변경 가능한 객체에는 작동하지 않습니다.

m2m 관계를 지원하기 위해 Trey Hunner의 솔루션을 확장했습니다.이것이 유사한 솔루션을 찾는 다른 사람들에게 도움이 되기를 바랍니다.

from django.db.models.signals import post_save

DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(self._reset_state, sender=self.__class__,
            dispatch_uid='%s._reset_state' % self.__class__.__name__)
        self._reset_state()

    def _as_dict(self):
        fields =  dict([
            (f.attname, getattr(self, f.attname))
            for f in self._meta.local_fields
        ])
        m2m_fields = dict([
            (f.attname, set([
                obj.id for obj in getattr(self, f.attname).all()
            ]))
            for f in self._meta.local_many_to_many
        ])
        return fields, m2m_fields

    def _reset_state(self, *args, **kwargs):
        self._original_state, self._original_m2m_state = self._as_dict()

    def get_dirty_fields(self):
        new_state, new_m2m_state = self._as_dict()
        changed_fields = dict([
            (key, value)
            for key, value in self._original_state.iteritems()
            if value != new_state[key]
        ])
        changed_m2m_fields = dict([
            (key, value)
            for key, value in self._original_m2m_state.iteritems()
            if sorted(value) != sorted(new_m2m_state[key])
        ])
        return changed_fields, changed_m2m_fields

두 개의 필드 목록을 병합할 수도 있습니다.이를 위해 마지막 줄을 바꾸십시오.

return changed_fields, changed_m2m_fields

~와 함께

changed_fields.update(changed_m2m_fields)
return changed_fields

두 번째 답변을 추가하는 이유는 다음과 같습니다. 이 질문이 처음 게시된 이후로 많은 것이 변경되었습니다..

현재 Django 세계에는 이 문제를 해결하는 수많은 앱이 있습니다.전체를 찾아보실 수 있습니다 모델 감사 및 기록 앱 목록 Django 패키지 사이트에서.

나는 썼다 블로그 게시물 몇 가지 앱을 비교해 보세요.이 게시물은 이제 4년이 되었으며 약간 오래된 내용입니다.하지만 이 문제를 해결하기 위한 다양한 접근 방식은 동일한 것 같습니다.

접근 방식:

  1. 모든 기록 변경 사항을 직렬화된 형식(JSON?)으로 단일 테이블에 저장합니다.
  2. 각 모델의 원본을 미러링하는 테이블에 모든 기록 변경 사항을 저장합니다.
  3. 모든 기록 변경 사항을 원래 모델과 동일한 테이블에 저장합니다(권장하지 않음)

그만큼 장고 복귀 패키지는 여전히 이 문제에 대한 가장 인기 있는 솔루션인 것 같습니다.첫 번째 접근 방식이 필요합니다.테이블을 미러링하는 대신 변경 사항을 직렬화합니다.

나는 부활했다 django-단순-역사 몇 년 전.두 번째 접근 방식이 필요합니다.각 테이블을 미러링합니다.

그래서 나는 추천하고 싶다 이 문제를 해결하기 위해 앱을 사용하는 중.이 시점에서 꽤 잘 작동하는 몇 가지 인기 있는 것들이 있습니다.

아, 그리고 모든 기록 변경 사항을 저장하지 않고 더티 필드 확인만 찾고 있다면 다음을 확인해 보세요. django-model-utils의 FieldTracker.

Muhuk의 제안에 따라 Django의 신호와 고유한 dispatch_uid를 추가하면 save()를 재정의하지 않고도 저장 시 상태를 재설정할 수 있습니다.

from django.db.models.signals import post_save

class DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(self._reset_state, sender=self.__class__, 
                            dispatch_uid='%s-DirtyFieldsMixin-sweeper' % self.__class__.__name__)
        self._reset_state()

    def _reset_state(self, *args, **kwargs):
        self._original_state = self._as_dict()

    def _as_dict(self):
        return dict([(f.name, getattr(self, f.name)) for f in self._meta.local_fields if not f.rel])

    def get_dirty_fields(self):
        new_state = self._as_dict()
        return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])

save()를 재정의하지 않고도 일단 저장된 원래 상태를 정리합니다.코드는 작동하지만 __init__에서 신호를 연결하면 성능 저하가 무엇인지 확실하지 않습니다.

나는 외래 키와 일대일 필드의 기본 키에 대한 차이점 검사를 포함하도록 muhuk 및 smn의 솔루션을 확장했습니다.

from django.db.models.signals import post_save

class DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(self._reset_state, sender=self.__class__,
                            dispatch_uid='%s-DirtyFieldsMixin-sweeper' % self.__class__.__name__)
        self._reset_state()

    def _reset_state(self, *args, **kwargs):
        self._original_state = self._as_dict()

    def _as_dict(self):
        return dict([(f.attname, getattr(self, f.attname)) for f in self._meta.local_fields])

    def get_dirty_fields(self):
        new_state = self._as_dict()
        return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])

유일한 차이점은 _as_dict 마지막 줄을 다음에서 변경했습니다.

return dict([
    (f.name, getattr(self, f.name)) for f in self._meta.local_fields
    if not f.rel
])

에게

return dict([
    (f.attname, getattr(self, f.attname)) for f in self._meta.local_fields
])

위의 믹스인과 마찬가지로 이 믹스인은 다음과 같이 사용할 수 있습니다.

class MyModel(DirtyFieldsMixin, models.Model):
    ....

기본 관리 응용 프로그램이 아닌 자체 트랜잭션을 사용하는 경우 개체의 이전 버전과 이후 버전을 저장할 수 있습니다.세션에서 이전 버전을 저장하거나 양식의 "숨겨진" 필드에 넣을 수 있습니다.숨겨진 필드는 보안에 있어서 악몽입니다.따라서 세션을 사용하여 이 사용자에게 일어나는 일에 대한 기록을 유지하십시오.

또한, 물론 이전 개체를 가져와서 변경해야 합니다.따라서 차이점을 모니터링하는 방법에는 여러 가지가 있습니다.

def updateSomething( request, object_id ):
    object= Model.objects.get( id=object_id )
    if request.method == "GET":
        request.session['before']= object
        form= SomethingForm( instance=object )
    else request.method == "POST"
        form= SomethingForm( request.POST )
        if form.is_valid():
            # You have before in the session
            # You have the old object
            # You have after in the form.cleaned_data
            # Log the changes
            # Apply the changes to the object
            object.save()

m2m을 지원하는 업데이트된 솔루션(업데이트된 솔루션 사용) 더티필드 그리고 새로운 _메타 API 및 일부 버그 수정) 위의 @Trey 및 @Tony를 기반으로 합니다.이것은 나에게 몇 가지 기본적인 조명 테스트를 통과했습니다.

from dirtyfields import DirtyFieldsMixin
class M2MDirtyFieldsMixin(DirtyFieldsMixin):
    def __init__(self, *args, **kwargs):
        super(M2MDirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(
            reset_state, sender=self.__class__,
            dispatch_uid='{name}-DirtyFieldsMixin-sweeper'.format(
                name=self.__class__.__name__))
        reset_state(sender=self.__class__, instance=self)

    def _as_dict_m2m(self):
        if self.pk:
            m2m_fields = dict([
                (f.attname, set([
                    obj.id for obj in getattr(self, f.attname).all()
                ]))
                for f,model in self._meta.get_m2m_with_model()
            ])
            return m2m_fields
        return {}

    def get_dirty_fields(self, check_relationship=False):
        changed_fields = super(M2MDirtyFieldsMixin, self).get_dirty_fields(check_relationship)
        new_m2m_state = self._as_dict_m2m()
        changed_m2m_fields = dict([
            (key, value)
            for key, value in self._original_m2m_state.iteritems()
            if sorted(value) != sorted(new_m2m_state[key])
        ])
        changed_fields.update(changed_m2m_fields)
        return changed_fields

def reset_state(sender, instance, **kwargs):
    # original state should hold all possible dirty fields to avoid
    # getting a `KeyError` when checking if a field is dirty or not
    instance._original_state = instance._as_dict(check_relationship=True)
    instance._original_m2m_state = instance._as_dict_m2m()

모든 사람의 정보를 위해 muhuk의 솔루션은 'object.__ init __()'가 인수를 허용하지 않는다는 예외를 발생시키기 때문에 python2.6에서 실패합니다.

편집하다:호!아무래도 제가 믹스인을 잘못 사용한 것 같습니다...나는 주의를 기울이지 않고 그것을 마지막 부모로 선언했고 그 때문에 초기화 일반적으로 다이아몬드 다이어그램 상속과 마찬가지로 다음 부모가 아닌 객체 부모가 됩니다!그러니 제 댓글을 무시해주세요 :)

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top