diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2016-01-17 18:09:26 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2016-01-17 18:09:26 +0100 |
commit | f384337ef0d9d2e40d09204f18c4a486e925132c (patch) | |
tree | 434a8a1ee961aa3adf79b35fe42ac41f075b50ef /django-simple-history/simple_history/models.py | |
parent | ad4a7e6015c26fef5bbad4783d638a138e687bdb (diff) | |
download | Ishtar-f384337ef0d9d2e40d09204f18c4a486e925132c.tar.bz2 Ishtar-f384337ef0d9d2e40d09204f18c4a486e925132c.zip |
Include django-simple-history - fix install script
Diffstat (limited to 'django-simple-history/simple_history/models.py')
-rw-r--r-- | django-simple-history/simple_history/models.py | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/django-simple-history/simple_history/models.py b/django-simple-history/simple_history/models.py new file mode 100644 index 000000000..06054ba34 --- /dev/null +++ b/django-simple-history/simple_history/models.py @@ -0,0 +1,169 @@ +import copy +from django.db import models +from django.contrib import admin +from django.contrib.auth.models import User +from django.utils import importlib +from manager import HistoryDescriptor + + +class HistoricalRecords(object): + def contribute_to_class(self, cls, name): + self.manager_name = name + self.module = cls.__module__ + models.signals.class_prepared.connect(self.finalize, sender=cls) + + def save_without_historical_record(self, *args, **kwargs): + """Caution! Make sure you know what you're doing before you use this method.""" + self.skip_history_when_saving = True + ret = self.save(*args, **kwargs) + del self.skip_history_when_saving + return ret + setattr(cls, 'save_without_historical_record', save_without_historical_record) + + def finalize(self, sender, **kwargs): + history_model = self.create_history_model(sender) + module = importlib.import_module(self.module) + setattr(module, history_model.__name__, history_model) + + # The HistoricalRecords object will be discarded, + # so the signal handlers can't use weak references. + models.signals.post_save.connect(self.post_save, sender=sender, + weak=False) + models.signals.post_delete.connect(self.post_delete, sender=sender, + weak=False) + + descriptor = HistoryDescriptor(history_model) + setattr(sender, self.manager_name, descriptor) + sender._meta.simple_history_manager_attribute = self.manager_name + + def create_history_model(self, model): + """ + Creates a historical model to associate with the model provided. + """ + attrs = {'__module__': self.module} + + fields = self.copy_fields(model) + attrs.update(fields) + attrs.update(self.get_extra_fields(model, fields)) + attrs.update(Meta=type('Meta', (), self.get_meta_options(model))) + name = 'Historical%s' % model._meta.object_name + return type(name, (models.Model,), attrs) + + def copy_fields(self, model): + """ + Creates copies of the model's original fields, returning + a dictionary mapping field name to copied field object. + """ + fields = {} + + for field in model._meta.fields: + field = copy.copy(field) + fk = None + + if isinstance(field, models.AutoField): + # The historical model gets its own AutoField, so any + # existing one must be replaced with an IntegerField. + field.__class__ = models.IntegerField + + if isinstance(field, models.ForeignKey): + field.__class__ = models.IntegerField + #ughhhh. open to suggestions here + try: + field.rel = None + except: + pass + try: + field.related = None + except: + pass + try: + field.related_query_name = None + except: + pass + field.null = True + field.blank = True + fk = True + else: + fk = False + + # The historical instance should not change creation/modification timestamps. + field.auto_now = False + field.auto_now_add = False + + if field.primary_key or field.unique: + # Unique fields can no longer be guaranteed unique, + # but they should still be indexed for faster lookups. + field.primary_key = False + field._unique = False + field.db_index = True + field.serialize = True + if fk: + field.name = field.name + "_id" + fields[field.name] = field + + return fields + + def get_extra_fields(self, model, fields): + """ + Returns a dictionary of fields that will be added to the historical + record model, in addition to the ones returned by copy_fields below. + """ + @models.permalink + def revert_url(self): + opts = model._meta + return ('%s:%s_%s_simple_history' % + (admin.site.name, opts.app_label, opts.module_name), + [getattr(self, opts.pk.attname), self.history_id]) + def get_instance(self): + return model(**dict([(k, getattr(self, k)) for k in fields])) + + return { + 'history_id': models.AutoField(primary_key=True), + 'history_date': models.DateTimeField(auto_now_add=True), + 'history_user': models.ForeignKey(User, null=True), + 'history_type': models.CharField(max_length=1, choices=( + ('+', 'Created'), + ('~', 'Changed'), + ('-', 'Deleted'), + )), + 'history_object': HistoricalObjectDescriptor(model), + 'instance': property(get_instance), + 'revert_url': revert_url, + '__unicode__': lambda self: u'%s as of %s' % (self.history_object, + self.history_date) + } + + def get_meta_options(self, model): + """ + Returns a dictionary of fields that will be added to + the Meta inner class of the historical record model. + """ + return { + 'ordering': ('-history_date', '-history_id'), + } + + def post_save(self, instance, created, **kwargs): + if not created and hasattr(instance, 'skip_history_when_saving'): + return + if not kwargs.get('raw', False): + self.create_historical_record(instance, created and '+' or '~') + + def post_delete(self, instance, **kwargs): + self.create_historical_record(instance, '-') + + def create_historical_record(self, instance, type): + history_user = getattr(instance, '_history_user', None) + manager = getattr(instance, self.manager_name) + attrs = {} + for field in instance._meta.fields: + attrs[field.attname] = getattr(instance, field.attname) + manager.create(history_type=type, history_user=history_user, **attrs) + + +class HistoricalObjectDescriptor(object): + def __init__(self, model): + self.model = model + + def __get__(self, instance, owner): + values = (getattr(instance, f.attname) for f in self.model._meta.fields) + return self.model(*values) |