Вот что я сделал, чтобы сохранить историю объекта:
Для приложения Django История:
история / __ init__.py:
"""
history/__init__.py
"""
from django.core import serializers
from django.utils import simplejson as json
from django.db.models.signals import pre_save, post_save
# from http://code.google.com/p/google-diff-match-patch/
from contrib.diff_match_patch import diff_match_patch
from history.models import History
def register_history(M):
"""
Register Django model M for keeping its history
e.g. register_history(Document) - every time Document is saved,
its history (i.e. the differences) is saved.
"""
pre_save.connect(_pre_handler, sender=M)
post_save.connect(_post_handler, sender=M)
def _pre_handler(signal, sender, instance, **kwargs):
"""
Save objects that have been changed.
"""
if not instance.pk:
return
# there must be a before, if there's a pk, since
# this is before the saving of this object.
before = sender.objects.get(pk=instance.pk)
_save_history(instance, _serialize(before).get('fields'))
def _post_handler(signal, sender, instance, created, **kwargs):
"""
Save objects that are being created (otherwise we wouldn't have a pk!)
"""
if not created:
return
_save_history(instance, {})
def _serialize(instance):
"""
Given a Django model instance, return it as serialized data
"""
return serializers.serialize("python", [instance])[0]
def _save_history(instance, before):
"""
Save two serialized objects
"""
after = _serialize(instance).get('fields',{})
# All fields.
fields = set.union(set(before.keys()),set(after.keys()))
dmp = diff_match_patch()
diff = {}
for field in fields:
field_before = before.get(field,False)
field_after = after.get(field,False)
if field_before != field_after:
if isinstance(field_before, unicode) or isinstance(field_before, str):
# a patch
diff[field] = dmp.diff_main(field_before,field_after)
else:
diff[field] = field_before
history = History(history_for=instance, diff=json.dumps(diff))
history.save()
история / models.py
"""
history/models.py
"""
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from contrib import diff_match_patch as diff
class History(models.Model):
"""
Retain the history of generic objects, e.g. documents, people, etc..
"""
content_type = models.ForeignKey(ContentType, null=True)
object_id = models.PositiveIntegerField(null=True)
history_for = generic.GenericForeignKey('content_type', 'object_id')
diff = models.TextField()
def __unicode__(self):
return "<History (%s:%d):%d>" % (self.content_type, self. object_id, self.pk)
Надеюсь, что это кому-то поможет, и комментарии будут оценены.
Обратите внимание, что это не относится к состоянию расы моей самой большой заботы. Если в _pre_handler «before = sender.objects.get (pk = instance.pk)» вызывается до сохранения другого экземпляра, но после того, как этот другой экземпляр обновил историю, а текущий экземпляр сохраняет первым, будет история »(то есть не в порядке). К счастью, diff_match_patch пытается корректно обработать «нефатальные» разрывы, но нет гарантии успеха.
Одним из решений является атомарность. Однако я не уверен, как сделать вышеупомянутое условие гонки (то есть _pre_handler) атомарной операцией во всех случаях Django. Таблица HistoryLock или общий хеш в памяти (memcached?) Были бы хороши - предложения?
Другим решением, как уже упоминалось, является алгоритм сверки. Однако при одновременном сохранении могут возникать «подлинные» конфликты, и для определения правильной выверки может потребоваться вмешательство пользователя.
Очевидно, что объединение истории не является частью вышеупомянутых фрагментов.