Add new setting to control FK change representation (#779)

* add new setting to activate string repr

* skip using foreign key to construct and display diff of foreign key fields

* concise name and documentation update

# Conflicts:
#	docs/source/usage.rst

* add test for enabled setting

* fix code block in docs

* fix version

* add warning to documentation
This commit is contained in:
Fabian Allendorf 2025-12-02 16:49:16 +01:00 committed by GitHub
parent d02ed6b9e0
commit eb9eefd76f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 69 additions and 1 deletions

View file

@ -4,6 +4,7 @@
#### Improvements
- feat: Add `AUDITLOG_USE_FK_STRING_REPRESENTATION` setting that controls how foreign key changes are represented ([#779)](https://github.com/jazzband/django-auditlog/pull/779))
- Add `AUDITLOG_USE_BASE_MANAGER` setting to override default manager use ([#766](https://github.com/jazzband/django-auditlog/pull/766))
- Drop 'Python 3.9' support ([#773](https://github.com/jazzband/django-auditlog/pull/773))

View file

@ -71,3 +71,8 @@ settings.AUDITLOG_LOGENTRY_MODEL = getattr(
settings.AUDITLOG_USE_BASE_MANAGER = getattr(
settings, "AUDITLOG_USE_BASE_MANAGER", False
)
# Use string representation of referenced object in foreign key changes instead of its primary key
settings.AUDITLOG_USE_FK_STRING_REPRESENTATION = getattr(
settings, "AUDITLOG_USE_FK_STRING_REPRESENTATION", False
)

View file

@ -106,7 +106,11 @@ def get_field_value(obj, field, use_json_for_changes=False):
value = json.dumps(value, sort_keys=True, cls=field.encoder)
except TypeError:
pass
elif (field.one_to_one or field.many_to_one) and hasattr(field, "rel_class"):
elif (
not settings.AUDITLOG_USE_FK_STRING_REPRESENTATION
and (field.one_to_one or field.many_to_one)
and hasattr(field, "rel_class")
):
value = smart_str(getattr(obj, field.get_attname()), strings_only=True)
else:
value = getattr(obj, field.name)

View file

@ -2407,6 +2407,29 @@ class TestRelatedDiffs(TestCase):
self.assertEqual(int(log_create.changes_dict["related"][1]), one_simple.id)
self.assertEqual(int(log_update.changes_dict["related"][1]), two_simple.id)
@override_settings(AUDITLOG_USE_FK_STRING_REPRESENTATION=True)
def test_string_representation_of_fk_changes(self):
"""FK changes should be stored using string representation when setting is enabled"""
t1 = self.test_date
with freezegun.freeze_time(t1):
simple = SimpleModel.objects.create(text="Test Foo")
two_simple = SimpleModel.objects.create(text="Test Bar")
instance = RelatedModel.objects.create(one_to_one=simple, related=simple)
t2 = self.test_date + datetime.timedelta(days=20)
with freezegun.freeze_time(t2):
instance.one_to_one = two_simple
instance.related = two_simple
instance.save()
self.assertEqual(instance.history.all().count(), 2)
log_update = instance.history.filter(timestamp=t2).first()
self.assertEqual(log_update.changes_dict["related"][0], "Test Foo")
self.assertEqual(log_update.changes_dict["related"][1], "Test Bar")
self.assertEqual(log_update.changes_dict["one_to_one"][0], "Test Foo")
self.assertEqual(log_update.changes_dict["one_to_one"][1], "Test Bar")
class TestModelSerialization(TestCase):
def setUp(self):

View file

@ -444,6 +444,41 @@ of the default one.
.. versionadded:: 3.5.0
Custom LogEntry model configuration via ``AUDITLOG_LOGENTRY_MODEL``
**AUDITLOG_USE_FK_STRING_REPRESENTATION**
Determines how changes to foreign key fields are recorded in log entries.
When `True`, changes to foreign key fields are stored using the string representation of related objects.
When `False` (default), the primary key of the related objects is stored instead.
Before version 2.2.0, foreign key changes were stored using the string representation of the related objects.
Starting from version 2.2.0, the default behavior was updated to store the primary key of the related objects instead.
Before:
.. code-block:: json
{ "foreign_key_field": ["foo", "bar"] }
After:
.. code-block:: json
{ "foreign_key_field": [1, 2] }
You can use this option to enable the legacy behavior.
.. warning::
This reintroduces a known issue https://github.com/jazzband/django-auditlog/issues/421
Commission Error: Causes unnecessary LogEntries even though no update occurrs because the string representation in memory changed
Omission Error: More common problem, a related object is updated to another object with the same string representation, no update is logged
Beware of these problem when enabling this setting.
.. versionadded:: 3.4.0
Actors
------