Django中的脏字段
在我的应用程序中,当保存模型时,我需要保存更改的值(旧值和新值)。有任何示例或者工作代码吗?
我需要这个来预审内容。例如,如果用户更改了模型中的某些内容,则管理员可以在单独的表中查看所有更改,然后决定是否应用它们。
解决方案
如果我们使用自己的事务(不是默认的管理应用程序),则可以保存对象的之前和之后版本。我们可以将以前的版本保存在会话中,也可以将其放在表单的"隐藏"字段中。隐藏的字段是一场安全噩梦。因此,使用该会话来保留该用户正在发生的事情的历史记录。
另外,当然,我们必须获取前一个对象,以便可以对其进行更改。因此,我们有几种方法可以监视差异。
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()
我们还没有对特定用例或者需求说太多。特别是,了解更改信息(需要存储多长时间?)将很有帮助。如果仅出于临时目的而存储它,@ S.Lott的会话解决方案可能是最好的。如果要对数据库中存储的对象的所有更改进行完整的审核跟踪,请尝试使用此AuditTrail解决方案。
更新:我上面链接到的AuditTrail代码是我所看到的最适合情况的完整解决方案,尽管它有一些限制(对于ManyToMany字段根本不起作用)。它将对象的所有先前版本存储在数据库中,因此管理员可以回滚到任何先前版本。如果我们希望更改在获得批准之前不生效,则需要使用它。
我们也可以基于@Armin Ronacher的DiffingMixin构建自定义解决方案。我们将diff字典(可能是腌制的?)存储在表格中,以供管理员稍后查看并在需要时应用(我们需要编写代码以获取diff字典并将其应用于实例)。
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()
时,我们将获得所有更改值的字典。当然,这对于列中的可变对象不起作用,因为原始状态是dict的平面副本。
我发现阿明的想法非常有用。这是我的变化;
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()
就所有人的信息而言,muhuk的解决方案在python2.6下失败,因为它引发了一个异常,指出" object .__ init __()"不接受任何参数...
编辑:ho!显然这可能是我滥用了mixin ...我没有注意并宣布它为最后一个父对象,因为对init的调用最终以对象父对象而不是下一个父对象结束,这与使用菱形的正常情况一样图继承!所以请无视我的评论:)
继续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__处连接信号的性能损失