diff --git a/easyaudit/signals/model_signals.py b/easyaudit/signals/model_signals.py index 568dc14..2d05ebf 100644 --- a/easyaudit/signals/model_signals.py +++ b/easyaudit/signals/model_signals.py @@ -278,7 +278,7 @@ def crud_flow(): if action == "post_clear": changed_fields = [] else: - changed_fields = json.dumps({get_m2m_field_name(model, instance): list(pk_set)}, cls=DjangoJSONEncoder) + changed_fields = json.dumps({get_m2m_field_name(model, instance, sender): list(pk_set)}, cls=DjangoJSONEncoder) with transaction.atomic(using=DATABASE_ALIAS): crud_event = audit_logger.crud({ 'event_type': event_type, diff --git a/easyaudit/tests/test_app/migrations/0004_testbigintm2mproxy_testm2mproxy_testuuidm2mproxy_and_more.py b/easyaudit/tests/test_app/migrations/0004_testbigintm2mproxy_testm2mproxy_testuuidm2mproxy_and_more.py new file mode 100644 index 0000000..c47b7a1 --- /dev/null +++ b/easyaudit/tests/test_app/migrations/0004_testbigintm2mproxy_testm2mproxy_testuuidm2mproxy_and_more.py @@ -0,0 +1,73 @@ +# Generated by Django 5.0.1 on 2024-01-29 18:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('test_app', '0003_testbigintforeignkey_testbigintm2m_testbigintmodel_testuuidforeignkey_testuuidm2m_testuuidmodel'), + ] + + operations = [ + migrations.CreateModel( + name='TestBigIntM2MProxy', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('test_app.testbigintm2m',), + ), + migrations.CreateModel( + name='TestM2MProxy', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('test_app.testm2m',), + ), + migrations.CreateModel( + name='TestUUIDM2MProxy', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('test_app.testuuidm2m',), + ), + migrations.CreateModel( + name='TestBigIntMultiM2M', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('test_m2m_a', models.ManyToManyField(related_name='testmultim2m_a', to='test_app.testbigintmodel')), + ('test_m2m_b', models.ManyToManyField(related_name='testmultim2m_b', to='test_app.testbigintmodel')), + ], + ), + migrations.CreateModel( + name='TestMultiM2M', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('test_m2m_a', models.ManyToManyField(related_name='testmultim2m_a', to='test_app.testmodel')), + ('test_m2m_b', models.ManyToManyField(related_name='testmultim2m_b', to='test_app.testmodel')), + ], + ), + migrations.CreateModel( + name='TestMultiUUIDM2M', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('test_m2m_a', models.ManyToManyField(related_name='testmultim2m_a', to='test_app.testuuidmodel')), + ('test_m2m_b', models.ManyToManyField(related_name='testmultim2m_b', to='test_app.testuuidmodel')), + ], + ), + ] diff --git a/easyaudit/tests/test_app/models.py b/easyaudit/tests/test_app/models.py index 519306c..41ff0bf 100644 --- a/easyaudit/tests/test_app/models.py +++ b/easyaudit/tests/test_app/models.py @@ -16,6 +16,20 @@ class TestM2M(models.Model): name = models.CharField(max_length=50) test_m2m = models.ManyToManyField(TestModel) +class TestM2MProxy(TestM2M): + class Meta: + proxy=True + +class TestMultiM2M(models.Model): + name = models.CharField(max_length=50) + test_m2m_a = models.ManyToManyField( + TestModel, + related_name="testmultim2m_a" + ) + test_m2m_b = models.ManyToManyField( + TestModel, + related_name="testmultim2m_b" + ) class TestUUIDModel(models.Model): id = models.UUIDField( @@ -39,6 +53,22 @@ class TestUUIDM2M(models.Model): name = models.CharField(max_length=50) test_m2m = models.ManyToManyField(TestUUIDModel) +class TestUUIDM2MProxy(TestUUIDM2M): + class Meta: + proxy=True + +class TestMultiUUIDM2M(models.Model): + name = models.CharField(max_length=50) + test_m2m_a = models.ManyToManyField( + TestUUIDModel, + related_name="testmultim2m_a" + ) + test_m2m_b = models.ManyToManyField( + TestUUIDModel, + related_name="testmultim2m_b" + ) + + class TestBigIntModel(models.Model): id = models.BigAutoField(primary_key=True) @@ -55,3 +85,20 @@ class TestBigIntM2M(models.Model): id = models.BigAutoField(primary_key=True) name = models.CharField(max_length=50) test_m2m = models.ManyToManyField(TestBigIntModel) + +class TestBigIntM2MProxy(TestBigIntM2M): + class Meta: + proxy=True + +class TestBigIntMultiM2M(models.Model): + name = models.CharField(max_length=50) + test_m2m_a = models.ManyToManyField( + TestBigIntModel, + related_name="testmultim2m_a" + ) + test_m2m_b = models.ManyToManyField( + TestBigIntModel, + related_name="testmultim2m_b" + ) + + diff --git a/easyaudit/tests/test_app/tests.py b/easyaudit/tests/test_app/tests.py index 4ef76c3..64b32af 100644 --- a/easyaudit/tests/test_app/tests.py +++ b/easyaudit/tests/test_app/tests.py @@ -16,9 +16,9 @@ from django.contrib.contenttypes.models import ContentType import bs4 from test_app.models import ( - TestModel, TestForeignKey, TestM2M, - TestBigIntModel, TestBigIntForeignKey, TestBigIntM2M, - TestUUIDModel, TestUUIDForeignKey, TestUUIDM2M + TestModel, TestForeignKey, TestM2M, TestM2MProxy, TestMultiM2M, + TestBigIntModel, TestBigIntForeignKey, TestBigIntM2M, TestBigIntM2MProxy, TestBigIntMultiM2M, + TestUUIDModel, TestUUIDForeignKey, TestUUIDM2M, TestMultiUUIDM2M, TestUUIDM2MProxy ) from easyaudit.models import CRUDEvent, RequestEvent from easyaudit.middleware.easyaudit import set_current_user, clear_request @@ -44,6 +44,8 @@ class TestAuditModels(TestCase): Model = TestModel FKModel = TestForeignKey M2MModel = TestM2M + M2MProxyModel = TestM2MProxy + M2MMultiModel = TestMultiM2M def test_create_model(self): obj = self.Model.objects.create() @@ -70,6 +72,38 @@ def test_m2m_model(self): data = json.loads(crud_event.object_json_repr)[0] self.assertEqual([str(d) for d in data['fields']['test_m2m']], [str(obj.id)]) + def test_m2m_proxy_model(self): + obj = self.Model.objects.create() + obj_m2m = self.M2MProxyModel(name='test') + obj_m2m.save() + obj_m2m.test_m2m.add(obj) + crud_event = CRUDEvent.objects.filter(object_id=obj_m2m.id, content_type=ContentType.objects.get_for_model(obj_m2m))[0] + obj_data = json.loads(crud_event.object_json_repr)[0] + changed_fields_data = json.loads(crud_event.changed_fields) + self.assertEqual([str(d) for d in obj_data['fields']['test_m2m']], [str(obj.id)]) + self.assertEqual(list(changed_fields_data.keys())[0], 'test_m2m') + + def test_multifield_m2m_model(self): + obj = self.Model.objects.create() + obj_m2m = self.M2MMultiModel(name='test') + obj_m2m.save() + + obj_m2m.test_m2m_a.add(obj) + crud_event = CRUDEvent.objects.filter(object_id=obj_m2m.id, content_type=ContentType.objects.get_for_model(obj_m2m))[0] + obj_data = json.loads(crud_event.object_json_repr)[0] + changed_fields_data = json.loads(crud_event.changed_fields) + self.assertEqual([str(d) for d in obj_data['fields']['test_m2m_a']], [str(obj.id)]) + self.assertEqual(list(changed_fields_data.keys())[0], 'test_m2m_a') + + obj_m2m.test_m2m_b.add(obj) + crud_event = CRUDEvent.objects.filter(object_id=obj_m2m.id, content_type=ContentType.objects.get_for_model(obj_m2m))[0] + obj_data = json.loads(crud_event.object_json_repr)[0] + changed_fields_data = json.loads(crud_event.changed_fields) + self.assertEqual([str(d) for d in obj_data['fields']['test_m2m_b']], [str(obj.id)]) + self.assertEqual(list(changed_fields_data.keys())[0], 'test_m2m_b') + + + def test_m2m_clear(self): obj = self.Model.objects.create() obj_m2m = self.M2MModel(name='test') @@ -140,12 +174,16 @@ class TestAuditUUIDModels(TestAuditModels): Model = TestUUIDModel FKModel = TestUUIDForeignKey M2MModel = TestUUIDM2M + M2MProxyModel = TestUUIDM2MProxy + M2MMultiModel = TestMultiUUIDM2M class TestAuditBigIntModels(TestAuditModels): Model = TestBigIntModel FKModel = TestBigIntForeignKey M2MModel = TestBigIntM2M + M2MProxyModel = TestBigIntM2MProxy + M2MMultiModel = TestBigIntMultiM2M @override_settings(TEST=True) diff --git a/easyaudit/utils.py b/easyaudit/utils.py index b66b11d..ea7231d 100644 --- a/easyaudit/utils.py +++ b/easyaudit/utils.py @@ -65,20 +65,23 @@ def model_delta(old_model, new_model): return delta -def get_m2m_field_name(model, instance): +def get_m2m_field_name(model, instance, through): """ Finds M2M field name on instance Called from m2m_changed signal :param model: m2m_changed signal model. :type model: Model :param instance:m2m_changed signal instance. - :type new: Model + :type instance: Model + :param through:m2m_changed intermediate model / signal sender. + :type through: Model :return: ManyToManyField name of instance related to model. :rtype: str """ - for x in model._meta.related_objects: - if x.related_model().__class__ == instance.__class__: - return x.remote_field.name + for field in instance._meta.many_to_many: + if field.remote_field.model == model and field.remote_field.through == through: + return field.name + def should_propagate_exceptions():