mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
123 lines
4 KiB
Python
123 lines
4 KiB
Python
import json
|
|
|
|
from django import urls as urlresolvers
|
|
from django.conf import settings
|
|
from django.urls.exceptions import NoReverseMatch
|
|
from django.utils.html import format_html, format_html_join
|
|
from django.utils.safestring import mark_safe
|
|
|
|
from auditlog.models import LogEntry
|
|
|
|
MAX = 75
|
|
|
|
|
|
class LogEntryAdminMixin:
|
|
def created(self, obj):
|
|
return obj.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
created.short_description = "Created"
|
|
|
|
def user_url(self, obj):
|
|
if obj.actor:
|
|
app_label, model = settings.AUTH_USER_MODEL.split(".")
|
|
viewname = f"admin:{app_label}_{model.lower()}_change"
|
|
try:
|
|
link = urlresolvers.reverse(viewname, args=[obj.actor.pk])
|
|
except NoReverseMatch:
|
|
return "%s" % (obj.actor)
|
|
return format_html('<a href="{}">{}</a>', link, obj.actor)
|
|
|
|
return "system"
|
|
|
|
user_url.short_description = "User"
|
|
|
|
def resource_url(self, obj):
|
|
app_label, model = obj.content_type.app_label, obj.content_type.model
|
|
viewname = f"admin:{app_label}_{model}_change"
|
|
try:
|
|
args = [obj.object_pk] if obj.object_id is None else [obj.object_id]
|
|
link = urlresolvers.reverse(viewname, args=args)
|
|
except NoReverseMatch:
|
|
return obj.object_repr
|
|
else:
|
|
return format_html(
|
|
'<a href="{}">{} - {}</a>', link, obj.content_type, obj.object_repr
|
|
)
|
|
|
|
resource_url.short_description = "Resource"
|
|
|
|
def msg_short(self, obj):
|
|
if obj.action == LogEntry.Action.DELETE:
|
|
return "" # delete
|
|
changes = json.loads(obj.changes)
|
|
s = "" if len(changes) == 1 else "s"
|
|
fields = ", ".join(changes.keys())
|
|
if len(fields) > MAX:
|
|
i = fields.rfind(" ", 0, MAX)
|
|
fields = fields[:i] + " .."
|
|
return "%d change%s: %s" % (len(changes), s, fields)
|
|
|
|
msg_short.short_description = "Changes"
|
|
|
|
def msg(self, obj):
|
|
if obj.action == LogEntry.Action.DELETE:
|
|
return "" # delete
|
|
changes = json.loads(obj.changes)
|
|
|
|
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:
|
|
msg.append("<table>")
|
|
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)
|
|
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,
|
|
field,
|
|
change["operation"],
|
|
change_html,
|
|
)
|
|
)
|
|
|
|
msg.append("</table>")
|
|
|
|
return mark_safe("".join(msg))
|
|
|
|
msg.short_description = "Changes"
|
|
|
|
def _format_header(self, *labels):
|
|
return format_html(
|
|
"".join(["<tr>", "<th>{}</th>" * len(labels), "</tr>"]), *labels
|
|
)
|
|
|
|
def _format_line(self, *values):
|
|
return format_html(
|
|
"".join(["<tr>", "<td>{}</td>" * len(values), "</tr>"]), *values
|
|
)
|