diff --git a/CHANGELOG.md b/CHANGELOG.md
index 594eaeba..66afb7e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
#### Improvements
- feat: Add `serialized_data` field on `LogEntry` model. ([#412](https://github.com/jazzband/django-auditlog/pull/412))
+- feat: Display the field name as it would be displayed in Django Admin or use `mapping_field` if available [#428](https://github.com/jazzband/django-auditlog/pull/428)
#### Fixes
diff --git a/auditlog/mixins.py b/auditlog/mixins.py
index 2d16e1a0..1936763a 100644
--- a/auditlog/mixins.py
+++ b/auditlog/mixins.py
@@ -2,12 +2,15 @@
from django import urls as urlresolvers
from django.conf import settings
+from django.core.exceptions import FieldDoesNotExist
+from django.forms.utils import pretty_name
from django.urls.exceptions import NoReverseMatch
from django.utils.html import format_html, format_html_join
from django.utils.safestring import mark_safe
from django.utils.timezone import localtime
from auditlog.models import LogEntry
+from auditlog.registry import auditlog
MAX = 75
@@ -81,7 +84,9 @@ def msg(self, obj):
msg.append("
")
msg.append(self._format_header("#", "Field", "From", "To"))
for i, (field, change) in enumerate(sorted(atom_changes.items()), 1):
- value = [i, field] + (["***", "***"] if field == "password" else change)
+ value = [i, self.field_verbose_name(obj, field)] + (
+ ["***", "***"] if field == "password" else change
+ )
msg.append(self._format_line(*value))
msg.append("
")
@@ -99,7 +104,7 @@ def msg(self, obj):
format_html(
"{} | {} | {} | {} |
",
i,
- field,
+ self.field_verbose_name(obj, field),
change["operation"],
change_html,
)
@@ -120,3 +125,19 @@ def _format_line(self, *values):
return format_html(
"".join(["", "{} | " * len(values), "
"]), *values
)
+
+ def field_verbose_name(self, obj, field_name: str):
+ model = obj.content_type.model_class()
+ try:
+ model_fields = auditlog.get_model_fields(model._meta.model)
+ mapping_field_name = model_fields["mapping_fields"].get(field_name)
+ if mapping_field_name:
+ return mapping_field_name
+ except KeyError:
+ # Model definition in auditlog was probably removed
+ pass
+ try:
+ field = model._meta.get_field(field_name)
+ return pretty_name(getattr(field, "verbose_name", field_name))
+ except FieldDoesNotExist:
+ return pretty_name(field_name)
diff --git a/auditlog_tests/tests.py b/auditlog_tests/tests.py
index 0cb1e7a8..5751f4f4 100644
--- a/auditlog_tests/tests.py
+++ b/auditlog_tests/tests.py
@@ -1323,8 +1323,8 @@ def test_changes_msg_delete(self):
(
""
"# | Field | From | To |
"
- "1 | field one | value before deletion | None |
"
- "2 | field two | 11 | None |
"
+ "1 | Field one | value before deletion | None |
"
+ "2 | Field two | 11 | None |
"
"
"
),
)
@@ -1346,8 +1346,8 @@ def test_changes_msg_create(self):
(
""
"# | Field | From | To |
"
- "1 | field one | None | a value |
"
- "2 | field two | None | 11 |
"
+ "1 | Field one | None | a value |
"
+ "2 | Field two | None | 11 |
"
"
"
),
)
@@ -1369,9 +1369,9 @@ def test_changes_msg_update(self):
(
""
"# | Field | From | To |
"
- "1 | field one | old value of field one | "
+ "
1 | Field one | old value of field one | "
"new value of field one |
"
- "2 | field two | 11 | 42 |
"
+ "2 | Field two | 11 | 42 |
"
"
"
),
)
@@ -1394,12 +1394,38 @@ def test_changes_msg_m2m(self):
(
""
"# | Relationship | Action | Objects |
"
- "1 | some_m2m_field | add | Example User (user 1)"
+ " |
1 | Some m2m field | add | Example User (user 1)"
" Illustration (user 42) |
"
"
"
),
)
+ def test_unregister_after_log(self):
+ log_entry = self._create_log_entry(
+ LogEntry.Action.CREATE,
+ {
+ "field two": [None, 11],
+ "field one": [None, "a value"],
+ },
+ )
+ # Unregister
+ auditlog.unregister(SimpleModel)
+ self.assertEqual(
+ self.admin.msg_short(log_entry), "2 changes: field two, field one"
+ )
+ self.assertEqual(
+ self.admin.msg(log_entry),
+ (
+ ""
+ "# | Field | From | To |
"
+ "1 | Field one | None | a value |
"
+ "2 | Field two | None | 11 |
"
+ "
"
+ ),
+ )
+ # Re-register
+ auditlog.register(SimpleModel)
+
class NoDeleteHistoryTest(TestCase):
def test_delete_related(self):