Export to GitHub

django-history-tables - issue #2

Enhancement


Posted on Sep 23, 2009 by Grumpy Monkey

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 Lion

Incorporated into the new code in the mercurial repository on this site.

Status: Fixed

Labels:
Type-Defect Priority-Medium