在我的应用程序中,我需要在保存模型时保存更改的值(旧的和新的)。任何例子或工作代码?
我需要这个来预测内容。例如,如果用户在模型中更改某些内容,则管理员可以在单独的表中查看所有更改,然后决定是否应用它们。
答案 0 :(得分:20)
我发现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()
答案 1 :(得分:14)
您没有详细说明您的具体用例或需求。特别是,了解您需要对更改信息执行哪些操作会有所帮助(您需要存储多长时间?)。如果您只是为了瞬态目的而存储它,@ S.Lott的会话解决方案可能是最好的。如果您想要存储在数据库中的对象的所有更改的完整审计跟踪,请尝试此AuditTrail solution。
更新:我上面链接的AuditTrail代码是我看到的最适合您案例的完整解决方案的代码,尽管它有一些限制(根本不起作用) ManyToMany字段)。它会将所有先前版本的对象存储在数据库中,因此管理员可以回滚到以前的任何版本。如果您希望更改在批准之前不生效,则必须稍微使用它。
您还可以基于像@Armin Ronacher的DiffingMixin这样的东西来构建自定义解决方案。您将diff字典(可能是腌制?)存储在一个表中供管理员稍后查看并在需要时应用(您需要编写代码以获取diff字典并将其应用于实例)。
答案 2 :(得分:12)
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的平面副本。
答案 3 :(得分:6)
我扩展了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
答案 4 :(得分:5)
添加第二个答案,因为自问题最初发布以来发生了很多变化。
Django世界中有许多应用程序现在可以解决这个问题。您可以在Django Packages网站上找到完整的list of model auditing and history apps。
我写了a blog post来比较其中一些应用。这篇文章现在已经有4年了,而且有点过时了。解决这个问题的不同方法似乎是相同的。
方法:
django-reversion包似乎仍然是此问题最常用的解决方案。它采用第一种方法:序列化更改而不是镜像表。
几年前,我复活django-simple-history。它采用第二种方法:镜像每个表。所以我建议使用应用来解决此问题。在这一点上,有几种流行的功能非常好。
哦,如果您只是在寻找脏字段检查而不是存储所有历史更改,请查看FieldTracker from django-model-utils。
答案 5 :(得分:3)
继续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 __
连接信号的性能损失是多少答案 6 :(得分: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
])
这个mixin和上面的一样,可以这样使用:
class MyModel(DirtyFieldsMixin, models.Model):
....
答案 7 :(得分:2)
如果您正在使用自己的事务(而不是默认的管理应用程序),则可以保存对象的前后版本。您可以在会话中保存之前的版本,也可以将其放在表单中的“隐藏”字段中。隐藏的领域是一场安全噩梦。因此,请使用会话保留此用户所发生事件的历史记录。
此外,当然,您必须获取上一个对象,以便对其进行更改。所以你有几种方法来监控差异。
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()
答案 8 :(得分:0)
支持m2m的更新解决方案(使用更新的dirtyfields和新的_meta 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()
答案 9 :(得分:-1)
对于每个人的信息,muhuk的解决方案在python2.6下失败,因为它引发了一个异常,说明'object .__ init __()'不接受任何参数......
编辑:ho!显然它可能是我滥用mixin ...我没注意并宣称它是最后一个父母,因此对 init 的调用最终在对象父级而不是下一个noramlly的父母会用钻石图继承!所以请忽略我的评论:)