在我的应用程序中,我需要在保存模型时保存更改的值(旧的和新的).任何例子或工作代码?
我需要这个来预先内容化.例如,如果用户更改了模型中的某些内容,则管理员可以在单独的表中查看所有更改,然后决定是否应用它们.
我发现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
在保存后重置dict.但我不想覆盖save()
方法,因为大多数时候我们在保存后丢弃模型实例.
def save(self, *args, **kwargs): super(Klass, self).save(*args, **kwargs) self._original_state = self._as_dict()
您没有详细说明您的具体用例或需求.特别是,了解您需要对更改信息执行哪些操作会有所帮助(您需要存储多长时间?).如果您只是为了瞬态目的而存储它,@ S.Lott的会话解决方案可能是最好的.如果您想要存储在数据库中的对象的所有更改的完整审计跟踪,请尝试此AuditTrail解决方案.
更新:我上面链接的AuditTrail代码是我看到的最适合您案例的完整解决方案,尽管它有一些限制(对于ManyToMany字段根本不起作用).它会将所有先前版本的对象存储在数据库中,因此管理员可以回滚到以前的任何版本.如果您希望更改在批准之前不生效,则必须稍微使用它.
您还可以基于@Armin Ronacher的DiffingMixin构建自定义解决方案.您可以将差异词典(可能是腌制?)存储在一个表中供管理员稍后查看并在需要时应用(您需要编写代码以获取差异词典并将其应用于实例).
Django目前正在将所有列发送到数据库,即使您刚刚更改了一列.要改变这种情况,需要对数据库系统进行一些更改.通过向模型添加一组脏字段并向每个__set__
列值添加列名称,可以在现有代码上轻松实现这一点.
如果您需要该功能,我建议您查看Django ORM,实现它并在Django trac中添加补丁.应该很容易添加它,它也会帮助其他用户.执行此操作时,添加每次设置列时调用的挂钩.
如果你不想破解Django本身,你可以复制对象创建的dict并将其区分开来.
也许用这样的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的平面副本.
我扩展了Trey 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
人们可能还希望合并两个字段列表.为此,替换最后一行
return changed_fields, changed_m2m_fields
同
changed_fields.update(changed_m2m_fields) return changed_fields
添加第二个答案,因为自问题最初发布以来已经发生了很多变化.
Django世界中有许多应用程序现在可以解决这个问题.您可以在Django Packages站点上找到模型审核和历史应用程序的完整列表.
我写了一篇博文,比较了其中一些应用程序.这篇文章现在已经有4年了,而且有点过时了.解决这个问题的不同方法似乎是相同的.
方法:
将所有历史更改以序列化格式(JSON?)存储在单个表中
将所有历史更改存储在镜像每个模型的原始表中
将所有历史更改存储在与原始模型相同的表中(我不建议这样做)
在Django的返包似乎仍然是最流行的解决了这个问题.它采用第一种方法:序列化更改而不是镜像表.
几年前,我恢复了django-simple-history.它采用第二种方法:镜像每个表.
所以我建议使用应用程序来解决这个问题.有几个流行的,在这一点上运作良好.
哦,如果您只是在寻找脏字段检查而不是存储所有历史更改,请查看来自django-model-utils的FieldTracker.