Audit changes to FK fields when saved using *_id naming. (#525)

* Audit changes to FK fields when saved using *_id naming.

In Django you can save changes to a FK field on a model using the ID
field name (attname) in addition to the regular FK field name with:

    model.related_model_id = 42
    model.save(update_fields=["related_model_id'])

or:

    model.related_model = related_model
    model.save(update_fields=["related_model_id'])

as opposed to the more common:

    model.related_model = related_model
    model.save(update_fields=["related_model'])

This change ensures those model changes are logged properly.

* Apply suggested change from code review.

* Add a CHANGELOG entry for the fix.
This commit is contained in:
Darren Maki 2023-05-08 11:26:01 -04:00 committed by GitHub
parent fa955cd5c7
commit cd0d3ea311
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 36 additions and 2 deletions

View file

@ -16,6 +16,7 @@
#### Fixes
- fix: Audit changes to FK fields when saved using `*_id` naming. ([#525](https://github.com/jazzband/django-auditlog/pull/525))
- fix: Fix a bug in audit log admin page when `USE_TZ=False`. ([#511](https://github.com/jazzband/django-auditlog/pull/511))
- fix: Make sure `LogEntry.changes_dict()` returns an empty dict instead of `None` when `json.loads()` returns `None`. ([#472](https://github.com/jazzband/django-auditlog/pull/472))
- fix: Always set remote_addr even if the request has no authenticated user. ([#484](https://github.com/jazzband/django-auditlog/pull/484))

View file

@ -4,7 +4,7 @@ from typing import Optional
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import NOT_PROVIDED, DateTimeField, JSONField, Model
from django.db.models import NOT_PROVIDED, DateTimeField, ForeignKey, JSONField, Model
from django.utils import timezone as django_timezone
from django.utils.encoding import smart_str
@ -147,7 +147,14 @@ def model_instance_diff(
model_fields = None
if fields_to_check:
fields = {field for field in fields if field.name in fields_to_check}
fields = {
field
for field in fields
if (
(isinstance(field, ForeignKey) and field.attname in fields_to_check)
or (field.name in fields_to_check)
)
}
# Check if fields must be filtered
if (

View file

@ -1858,6 +1858,32 @@ class TestRelatedDiffs(TestCase):
self.assertEqual(int(log_one.changes_dict["one_to_one"][1]), simple.id)
self.assertEqual(int(log_two.changes_dict["related"][1]), two_simple.id)
def test_log_entry_changes_on_fk_object_id_update(self):
t1 = self.test_date
with freezegun.freeze_time(t1):
simple = SimpleModel.objects.create()
one_simple = SimpleModel.objects.create()
two_simple = SimpleModel.objects.create()
instance = RelatedModel.objects.create(
one_to_one=simple, related=one_simple
)
t2 = self.test_date + datetime.timedelta(days=20)
with freezegun.freeze_time(t2):
instance.related_id = two_simple.id
instance.one_to_one = one_simple
instance.save(update_fields=["related_id", "one_to_one_id"])
log_one = instance.history.filter(timestamp=t1).first()
log_two = instance.history.filter(timestamp=t2).first()
self.assertTrue(isinstance(log_one, LogEntry))
self.assertTrue(isinstance(log_two, LogEntry))
self.assertEqual(int(log_one.changes_dict["related"][1]), one_simple.id)
self.assertEqual(int(log_one.changes_dict["one_to_one"][1]), simple.id)
self.assertEqual(int(log_two.changes_dict["related"][1]), two_simple.id)
self.assertEqual(int(log_two.changes_dict["one_to_one"][1]), one_simple.id)
def test_log_entry_changes_on_fk_id_update(self):
t1 = self.test_date
with freezegun.freeze_time(t1):