Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix to handle proxy model and multiple M2M situations #270

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion easyaudit/signals/model_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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')),
],
),
]
47 changes: 47 additions & 0 deletions easyaudit/tests/test_app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
Expand All @@ -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"
)


44 changes: 41 additions & 3 deletions easyaudit/tests/test_app/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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')
Expand Down Expand Up @@ -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)
Expand Down
13 changes: 8 additions & 5 deletions easyaudit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down