I fix somewhere, now it can run on both Django 1.02 and 1.1,
And also fixed many to many relation issue.
!/usr/bin/env python
-- coding: utf-8 --
from django.db import models from django.db.models.base import ModelBase from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User
import copy import datetime
class HistoryMixin(models.Model):
history_datetime = models.DateTimeField(default=datetime.datetime.now)
history_objectid = models.PositiveIntegerField() # these two are
history_revision = models.PositiveIntegerField() # unique_together ?
history_comment = models.CharField(max_length=1024, default="")
comment on history item
class Meta:
abstract = True
class HistoryModelBase(ModelBase): def new(cls, name, bases, dct): new_class = ModelBase.new(cls, name, bases, dct) if 'History' in dct: history_model = dct['History'].model for field in history_model._meta.fields: if 'AutoField' == field.class.name: continue if getattr(field, 'related_name', None): #print field.related_name pass _field = copy.deepcopy(field) if getattr(_field,'unique',False): try: _field.unique = False except AttributeError: # newer Django defines unique as a property # that uses _unique to store data. We're # jumping over the fence by setting _unique, # so this sucks, but this happens early enough # to be safe. _field._unique = False
rel= getattr(_field, 'rel', None)
if rel and rel.related_name:
#print rel.related_name
rel.related_name=rel.related_name +"history"
new_class.add_to_class(_field.name, _field)
for mm in history_model._meta.many_to_many:
_mm = copy.deepcopy(mm)
if getattr(_mm,'unique',False):
try:
_mm.unique = False
except AttributeError:
# newer Django defines unique as a property
# that uses _unique to store data. We're
# jumping over the fence by setting _unique,
# so this sucks, but this happens early enough
# to be safe.
_mm._unique = False
rel= getattr(_mm, 'rel', None)
if rel and rel.related_name:
#print rel.related_name
rel.related_name=rel.related_name +"history"
if rel and rel.through:
# Will not remain in history table, other wise will
report error
#print _mm.name+":"+ rel.through
rel.through=rel.through +"History"
else:
new_class.add_to_class(_mm.name, _mm)
return new_class
class HistoryModel(models.Model): metaclass = HistoryModelBase
history_datetime = models.DateTimeField(default=datetime.datetime.now)
history_objectid = models.PositiveIntegerField() # these two are
history_revision = models.PositiveIntegerField() # unique_together ?
history_comment = models.CharField(max_length=1024, default="") #
comment on history item
history_operatedby = models.ForeignKey(User)
def pre_save(self, request):
if not self.id:
self.history_operatedby = request.user
class Meta:
abstract = True
def history_save(sender, instance, signal, history_model, *args, **kwargs): if not instance.id: # initial save, don't have to do anything :) return # lookup history model if exists, otherwise return (quick exit if history is not managed) #history_model = CarHistory # right now this is a quick shortcut to our test # lookup orginal unchanged object orginal = sender.objects.get(pk=instance.id) # copy orginal object into history history_obj = history_model()
history_obj.history_objectid = orginal.id
history_obj.history_revision =
history_model.objects.filter(history_objectid=orginal.id).count()+1 history_obj.history_comment = "pre_save history item <%s>" % (repr(orginal))
for field in orginal._meta.fields:
if 'AutoField' == field.__class__.__name__:
continue
setattr(history_obj, field.name, getattr(orginal, field.name))
history_obj.save()
for mm in orginal._meta.many_to_many:
setattr(history_obj, mm.name, getattr(orginal, mm.name).all())
history_obj.save()
Tests
#
class Manufacturer(models.Model):
name = models.CharField(max_length=64, default="VW")
def unicode(self): return u"<manufacturer name=%s>" % (self.name)
class Admin: pass
#
class Color(models.Model):
name = models.CharField(max_length=64, default="Green")
def unicode(self): return u"<color name=%s>" % (self.name)
class Admin: pass
#
class Car(models.Model):
foo = models.CharField(max_length=64, default="foo")
bar = models.TextField(default="bar")
maker = models.ForeignKey(Manufacturer)
colors = models.ManyToManyField(Color)
slug = models.SlugField(unique=True)
desc = models.TextField()
def unicode(self): return u"<car foo=%s bar=%s maker=%s colors=%s
slug=%s>" % (self.foo, self.bar, self.maker, self.colors, self.slug)
class Admin: pass
#
class CarHistory(HistoryModel):
class History:
model = Car
eggs = "eggs"
def unicode(self): return u"<carhistory revision=%s>" %
(self.history_revision)
class Admin: pass
#
def carhistory_save(sender, instance, signal, *args, **kwargs):
history_model = CarHistory
history_save(sender, instance, signal, history_model, *args, **kwargs)
#
from django.dispatch import dispatcher
from django.db.models.signals import pre_delete, pre_save
pre_save.connect(carhistory_save, sender=Car)
pre_delete.connect(carhistory_save, sender=Car)
Comment #1
Posted on Jan 15, 2010 by Helpful Lion(No comment was entered for this change.)
Comment #2
Posted on Jan 27, 2010 by Helpful LionIncorporated into the new code in the mercurial repository on this site.
Status: Fixed
Labels:
Type-Defect
Priority-Medium