Skip to content

Commit

Permalink
Added Many-to-Many Support
Browse files Browse the repository at this point in the history
  • Loading branch information
Clinton Blackburn committed Jan 14, 2014
1 parent 49ddbbf commit 5ba8d2b
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ test_files/
/.ve
/.project
/.pydevproject
.idea/
38 changes: 37 additions & 1 deletion simple_history/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django.contrib import admin
from django.utils import importlib
from .manager import HistoryDescriptor
from simple_history import register

try:
basestring
Expand Down Expand Up @@ -35,14 +36,16 @@ def python_2_unicode_compatible(klass):


class HistoricalRecords(object):
def __init__(self, verbose_name=None):
def __init__(self, verbose_name=None, m2m_fields=None):
self.user_set_verbose_name = verbose_name
self.m2m_fields = m2m_fields

def contribute_to_class(self, cls, name):
self.manager_name = name
self.module = cls.__module__
models.signals.class_prepared.connect(self.finalize, sender=cls)
self.add_extra_methods(cls)
self.setup_m2m_history(cls)

def add_extra_methods(self, cls):
def save_without_historical_record(self, *args, **kwargs):
Expand All @@ -60,6 +63,17 @@ def save_without_historical_record(self, *args, **kwargs):
setattr(cls, 'save_without_historical_record',
save_without_historical_record)

def setup_m2m_history(self, cls):
m2m_history_fields = self.m2m_fields
if m2m_history_fields:
assert (isinstance(m2m_history_fields, list) or isinstance(m2m_history_fields, tuple)), 'm2m_history_fields must be a list or tuple'
for field_name in m2m_history_fields:
field = getattr(cls, field_name).field
assert isinstance(field, models.fields.related.ManyToManyField), ('%s must be a ManyToManyField' % field_name)
if not sum([isinstance(item, HistoricalRecords) for item in field.rel.through.__dict__.values()]):
field.rel.through.history = HistoricalRecords()
register(field.rel.through)

def finalize(self, sender, **kwargs):
history_model = self.create_history_model(sender)
module = importlib.import_module(self.module)
Expand All @@ -71,6 +85,7 @@ def finalize(self, sender, **kwargs):
weak=False)
models.signals.post_delete.connect(self.post_delete, sender=sender,
weak=False)
models.signals.m2m_changed.connect(self.m2m_changed, sender=sender, weak=False)

descriptor = HistoryDescriptor(history_model)
setattr(sender, self.manager_name, descriptor)
Expand Down Expand Up @@ -177,6 +192,27 @@ def post_save(self, instance, created, **kwargs):
def post_delete(self, instance, **kwargs):
self.create_historical_record(instance, '-')

def m2m_changed(self, action, instance, sender, **kwargs):
source_field_name, target_field_name = None, None
for field_name, field_value in sender.__dict__.items():
if isinstance(field_value, models.fields.related.ReverseSingleRelatedObjectDescriptor):
if field_value.field.related.parent_model == kwargs['model']:
target_field_name = field_name
elif field_value.field.related.parent_model == type(instance):
source_field_name = field_name
items = sender.objects.filter(**{source_field_name:instance})
if kwargs['pk_set']:
items = items.filter(**{target_field_name + '__id__in':kwargs['pk_set']})
for item in items:
if action == 'post_add':
if hasattr(item, 'skip_history_when_saving'):
return
self.create_historical_record(item, '+')
elif action == 'pre_remove':
self.create_historical_record(item, '-')
elif action == 'pre_clear':
self.create_historical_record(item, '-')

def create_historical_record(self, instance, type):
history_user = getattr(instance, '_history_user', None)
manager = getattr(instance, self.manager_name)
Expand Down

0 comments on commit 5ba8d2b

Please sign in to comment.