2008-09-21 32 views
27

在我的應用程序中,我需要保存模型保存後更改的值(舊的和新的)。任何示例或工作代碼?django的髒字段

我需要這個預處理內容。例如,如果用戶更改模型中的某些內容,則管理員可以查看單獨表格中的所有更改,然後決定是否應用它們。

+0

我已經看到類似的問題,爲髒領域,但這是同樣的問題;爲了讓管理員查看更改的內容,首先需要確定更改的內容... – dnozay 2013-04-22 17:52:17

回答

13

您對於您的具體使用案例或需求沒有太多的瞭解。特別是,瞭解您需要如何處理變更信息會有所幫助(您需要多長時間來存儲變更信息?)。如果您只是爲了暫時的目的而存儲它,@ S.Lott的會話解決方案可能是最好的。如果您想要對存儲在數據庫中的對象所做的所有更改進行完整的審計跟蹤,請嘗試使用AuditTrail solution

UPDATE:該審計跟蹤代碼,我掛上面是我見過一個完整的解決方案,爲您的工作情況最接近的,儘管它有一些限制(不能在所有的工作爲多對多字段)。它會將對象的所有以前版本存儲在數據庫中,以便管理員可以回滾到任何以前的版本。如果您希望更改在批准之前無法生效,您必須稍微處理一下。

您也可以基於@Armin Ronacher的DiffingMixin構建自定義解決方案。你可以在表格中存儲差異字典(可能是醃漬的?),供管理員稍後檢查並根據需要應用(您需要編寫代碼以獲取差異字典並將其應用於實例)。

+0

是的,您說得對,我已經添加了更多精確的描述。 – 2008-09-21 15:52:59

1

如果您使用自己的事務(不是默認的管理應用程序),則可以保存對象的前後版本。您可以將以前的版本保存在會話中,也可以將其放在表單中的「隱藏」字段中。隱藏的領域是一個安全噩夢。因此,使用會話保留此用戶發生的事件的歷史記錄。

此外,當然,您必須獲取前一個對象,以便對其進行更改。所以你有幾種方法來監控差異。

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

即使您只是更改了一個,Django當前也會將所有列發送到數據庫。爲了改變這種情況,數據庫系統中的一些改變是必要的。這可以通過在模型中添加一組髒字段並向其添加列名來輕鬆地在現有代碼上實現,每次您爲__set__列值。

如果你需要這個功能,我建議你看看Django的ORM,實現它並在Django trac中添加一個補丁。添加它應該很容易,它也會幫助其他用戶。當你這樣做時,添加一個鉤子,每次設置列時都會調用它。

如果你不想破解Django本身,你可以複製對象創建和區分它的字典。

也許有這樣一個mixin:

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()時,您會得到所有更改值的字典。這當然不適用於列中的可變對象,因爲原始狀態是字典的平面副本。

+4

這可能早就該過期了,但它應該是'if value!= self .__ dict __。get(key,missing)`: – tghw 2009-10-23 23:22:35

+0

您可以詳細說明`__set__`方法嗎?這聽起來像是符合我當前的需求,但我無法取得任何進展。 – kasperd 2015-10-05 12:35:38

19

我發現阿明的想法非常有用。這是我的變化;

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

編輯:我測試了這個順便說一句。

對不起,排長隊。區別在於(除了名稱)它只緩存本地非關係字段。換句話說,它不會緩存父模型的字段(如果存在)。

還有一件事;您需要在保存後重置_original_state字典。但我不想覆蓋save()方法,因爲大多數時候我們會在保存後放棄模型實例。

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

[django-dirtyfields](https://github.com/smn/django-dirtyfields)應用程序提供了相同類型的mixin。 – dnozay 2013-04-22 17:48:17

-1

每個人的信息,muhuk的解決方案下python2.6的失敗,因爲它拋出一個異常,說明「對象初始化.__ __()」不接受任何說法......

編輯:嗬!顯然它可能是我濫用混音...我沒有注意,並宣佈它作爲最後一位家長,因爲初始最終在對象的父母,而不是下一個父母,因爲它noramlly會與鑽石圖繼承!所以請無視我的意見:)

3

繼續上Muhuk的建議&加入Django的信號和獨特的dispatch_uid你可以重置保存的狀態,而不會覆蓋保存():

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__

3

我伸出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): 
    .... 
6

我延長三分球的Hunner的解決方案,支持M2M的關係。希望這會幫助其他人尋找類似的解決方案。

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 

人們也可能希望合併兩個字段列表。對於這一點,與

changed_fields.update(changed_m2m_fields) 
return changed_fields 
0

替換最後一行

return changed_fields, changed_m2m_fields 

與M2M支持(使用更新dirtyfields和新_meta API和一些bug修復),基於@Trey和@託尼的上述更新的解決方案。這已經通過了一些基本的燈光測試。

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

增加,因爲,因爲這問題最初發布的時間發生了很多變化第二個答案。

Django世界中有許多應用程序現在可以解決這個問題。您可以在Django Packages網站上找到完整的list of model auditing and history apps

我寫了a blog post比較這些應用程序的幾個。這篇文章現在已經4歲了,有點過時了。解決這個問題的不同方法似乎是相同的。

的方法:

  1. 存放在序列化格式的所有歷史變遷(JSON?)在一個表中
  2. 存儲在表中的所有歷史變遷鏡像原來每個模型
  3. 存儲所有在同一個表作爲原始模型的歷史變化(我不提倡這種做法)

django-reversion包似乎仍然是最流行的小號解決這個問題。它採取第一種方法:序列化更改而不是鏡像表。

幾年前我恢復了django-simple-history。它採取第二種方法:鏡像每個表。

所以我會推薦使用的應用程序來解決這個問題。有一些流行的在這一點上工作得很好。

哦,如果你只是在尋找骯髒的現場檢查,並沒有存儲所有的歷史變化,請查看FieldTracker from django-model-utils