from django.contrib import admin from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION from django.contrib.contenttypes.models import ContentType from django.urls import reverse, NoReverseMatch from django.utils.encoding import force_str from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import pgettext_lazy, gettext_lazy as _ action_names = { ADDITION: pgettext_lazy('logentry_admin:action_type', 'Addition'), DELETION: pgettext_lazy('logentry_admin:action_type', 'Deletion'), CHANGE: pgettext_lazy('logentry_admin:action_type', 'Change'), } class LogEntryAdmin(admin.ModelAdmin): date_hierarchy = 'action_time' readonly_fields = ([f.name for f in LogEntry._meta.fields] + ['object_link', 'action_description', 'user_link', 'get_change_message']) fieldsets = ( (_('Metadata'), { 'fields': ( 'action_time', 'user_link', 'action_description', 'object_link', ) }), (_('Details'), { 'fields': ( 'get_change_message', 'content_type', 'object_id', 'object_repr', ) }), ) list_filter = [ 'content_type' ] search_fields = [ 'object_repr', 'change_message' ] list_display_links = [ 'action_time', 'get_change_message', ] list_display = [ 'action_time', 'user_link', 'content_type', 'object_link', 'action_description', 'get_change_message', ] def has_add_permission(self, request): return False def has_change_permission(self, request, obj=None): return ( request.user.is_superuser or request.user.has_perm('admin.change_logentry') ) and request.method != 'POST' def has_delete_permission(self, request, obj=None): return False def object_link(self, obj): object_link = escape(obj.object_repr) content_type = obj.content_type if obj.action_flag != DELETION and content_type is not None: # try returning an actual link instead of object repr string try: url = reverse( 'admin:{}_{}_change'.format(content_type.app_label, content_type.model), args=[obj.object_id] ) object_link = '{}'.format(url, object_link) except NoReverseMatch: pass return mark_safe(object_link) object_link.admin_order_field = 'object_repr' object_link.short_description = _('object') def user_link(self, obj): content_type = ContentType.objects.get_for_model(type(obj.user)) user_link = escape(force_str(obj.user)) try: # try returning an actual link instead of object repr string url = reverse( 'admin:{}_{}_change'.format(content_type.app_label, content_type.model), args=[obj.user.pk] ) user_link = '{}'.format(url, user_link) except NoReverseMatch: pass return mark_safe(user_link) user_link.admin_order_field = 'user' user_link.short_description = _('user') def get_queryset(self, request): queryset = super(LogEntryAdmin, self).get_queryset(request) return queryset.prefetch_related('content_type') def get_actions(self, request): actions = super(LogEntryAdmin, self).get_actions(request) if 'delete_selected' in actions: del actions['delete_selected'] return actions def action_description(self, obj): return action_names[obj.action_flag] action_description.short_description = _('action') def get_change_message(self, obj): return obj.get_change_message() get_change_message.short_description = _('change message')