I've started using django-auditlog in my project. I was able to successfully log entries for changes to my model, however I want to make a tweak on how audit log displays change list in django admin. The problem is I've a ForeignKey
in my Task
model, it does have a __str__
method defined. When I make change to stage
field on my Task
model, the entry is logged, but it shows the stage id
instead of stage.name
as per defined in __str__
(i believe this is how it's supposed to work.
Here is my models.py
class Stage(models.Model):
name = models.CharField(max_length=64)
def __str__(self):
return self.name
class Task(models.Model):
name = models.CharField(max_length=64)
stage = models.ForeignKey(Stage, on_delete=models.SET_NULL, null=True)
## other unrelated fields...
def __str__(self):
return self.name
Here is how the Log Entry
is displayed currently:
I've read the documentation of django-auditlog but couldn't find any solution. Any suggestion?
I was able to solve the issue by overriding LogEntryAdminMixin
class.
Here is what I did to solve the issue:
mixins.py
file in my app
folder with the following content:from auditlog.mixins import LogEntryAdminMixin
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from django.utils.safestring import mark_safe
import backend.api.models
class CustomLogEntryAdminMixin(LogEntryAdminMixin):
@admin.display(description=_("Changes"))
def msg(self, obj):
changes = obj.changes_dict
atom_changes = {}
m2m_changes = {}
for field, change in changes.items():
if isinstance(change, dict):
assert (
change["type"] == "m2m"
), "Only m2m operations are expected to produce dict changes now"
m2m_changes[field] = change
else:
atom_changes[field] = change
msg = []
if atom_changes:
## this is the part we are overriding
## This is the only change from the original code
## Change stage ids to stage names if obj is a Buy object
if obj.content_type.model == "buy" and "stage" in atom_changes:
atom_changes["stage"] = [
backend.api.models.Stage.objects.get(id=stage_id).name
for stage_id in atom_changes["stage"]
]
msg.append("<table>")
msg.append(self._format_header("#", "Field", "From", "To"))
for i, (field, change) in enumerate(sorted(atom_changes.items()), 1):
value = [i, self.field_verbose_name(obj, field)] + (
["***", "***"] if field == "password" else change
)
msg.append(self._format_line(*value))
msg.append("</table>")
if m2m_changes:
msg.append("<table>")
msg.append(self._format_header("#", "Relationship", "Action", "Objects"))
for i, (field, change) in enumerate(sorted(m2m_changes.items()), 1):
change_html = format_html_join(
mark_safe("<br>"),
"{}",
[(value,) for value in change["objects"]],
)
msg.append(
format_html(
"<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>",
i,
self.field_verbose_name(obj, field),
change["operation"],
change_html,
)
)
msg.append("</table>")
return mark_safe("".join(msg))
admin.py
to use this mixin:from auditlog.admin import LogEntryAdmin
from auditlog.models import LogEntry
class CustomLogEnterAdmin(LogEntryAdmin, backend.api.mixins.CustomLogEntryAdminMixin):
"""
This class is a custom LogEntryAdmin that extends the default LogEntryAdmin
and uses custom mixins to customize the display of the changes field in the DAP for Buy objects stage field.
"""
pass
LogEntry
class and register the CustomLogEntryAdmin
class I just created.# unregister the default LogEntryAdmin and register the custom one
admin.site.unregister(LogEntry)
## register custom log entry admin
admin.site.register(LogEntry, CustomLogEnterAdmin)
Now it shows ForeignKey
model value, instead of keys.
I hope it helps!