jazzband / django-auditlog

A Django app that keeps a log of changes made to an object.
MIT License
1.12k stars 410 forks source link

Add support for tracking changes in from django admin inlines m2m #638

Open alexkiro opened 5 months ago

alexkiro commented 5 months ago

When using django admin inlines for M2M relationships, there is documented limitation that means m2m_changed signal isn't triggered. From django docs:

Note that when using this technique the m2m_changed signals aren’t triggered. This is because as far as the admin is concerned, through is just a model with two foreign key fields rather than a many-to-many relation.


Because of this limitations all changes done via the admin inlines for M2M relationships are not logged.

hramezani commented 5 months ago

django-auditlog uses the m2m_changed signal and connects a receiver function to the signal. So, If the signals aren't triggered, django-auditlog can't receive it.

As it is a limitation in Django, I am not sure that we can find an easy way to implement this feature.

alexkiro commented 5 months ago

As it is a limitation in Django, I am not sure that we can find an easy way to implement this feature.

Yep, it does seem quite tricky to pull of, can't think of any good way to implement this.

I did manage to find a workaround, however could not figure any clean way to integrate this directly with django-auditlog. Adding my workaround here, in case anyones else runs into this issue and needs some inspiration.


The idea is to override the save_related method of the ModelAdmin and create the log entries manually. Could not find any neat way to introspect arguments, so they are all harcoded.

    def save_related(self, request, form, formsets, change):
        super().save_related(request, form, formsets, change)

        for formset in formsets:
            # M2M logging doesn't work in inline models, so create them manually here.
            # See upstream: https://github.com/jazzband/django-auditlog/issues/638
            if formset.model == Contact.groups.through:
                LogEntry.objects.log_m2m_changes(
                    [obj.contactgroup for obj in formset.deleted_objects],
                    formset.instance,
                    "delete",
                    "groups",
                )
                LogEntry.objects.log_m2m_changes(
                    [obj.contactgroup for obj in formset.new_objects],
                    formset.instance,
                    "add",
                    "groups",
                )

This solution also has the limitation that it won't work with updates in the m2m inline. Only add/delete, so only can be used safely if you have changes disabled in inline. That is done with adding the following method:

    def has_change_permission(self, request, obj=None):
        return False