""" Admin interface for versioning models. Provides Django admin interface for viewing version history, comparing versions, and managing version records. """ from django.contrib import admin from django.utils.html import format_html from django.urls import reverse from unfold.admin import ModelAdmin from apps.versioning.models import EntityVersion @admin.register(EntityVersion) class EntityVersionAdmin(ModelAdmin): """ Admin interface for EntityVersion model. Provides read-only view of version history with search and filtering. """ # Display settings list_display = [ 'version_number', 'entity_link', 'change_type', 'changed_by_link', 'submission_link', 'changed_field_count', 'created', ] list_filter = [ 'change_type', 'entity_type', 'created', ] search_fields = [ 'entity_id', 'comment', 'changed_by__email', 'changed_by__username', ] ordering = ['-created'] date_hierarchy = 'created' # Read-only admin (versions should not be modified) readonly_fields = [ 'id', 'entity_type', 'entity_id', 'entity_link', 'version_number', 'change_type', 'snapshot_display', 'changed_fields_display', 'changed_by', 'submission', 'comment', 'ip_address', 'user_agent', 'created', 'modified', ] fieldsets = ( ('Version Information', { 'fields': ( 'id', 'version_number', 'change_type', 'created', 'modified', ) }), ('Entity', { 'fields': ( 'entity_type', 'entity_id', 'entity_link', ) }), ('Changes', { 'fields': ( 'changed_fields_display', 'snapshot_display', ) }), ('Metadata', { 'fields': ( 'changed_by', 'submission', 'comment', 'ip_address', 'user_agent', ) }), ) def has_add_permission(self, request): """Disable adding versions manually.""" return False def has_delete_permission(self, request, obj=None): """Disable deleting versions.""" return False def has_change_permission(self, request, obj=None): """Only allow viewing versions, not editing.""" return False def entity_link(self, obj): """Display link to the entity.""" try: entity = obj.entity if entity: # Try to get admin URL for entity admin_url = reverse( f'admin:{obj.entity_type.app_label}_{obj.entity_type.model}_change', args=[entity.pk] ) return format_html( '{}', admin_url, str(entity) ) except: pass return f"{obj.entity_type.model}:{obj.entity_id}" entity_link.short_description = 'Entity' def changed_by_link(self, obj): """Display link to user who made the change.""" if obj.changed_by: try: admin_url = reverse( 'admin:users_user_change', args=[obj.changed_by.pk] ) return format_html( '{}', admin_url, obj.changed_by.email ) except: return obj.changed_by.email return '-' changed_by_link.short_description = 'Changed By' def submission_link(self, obj): """Display link to content submission if applicable.""" if obj.submission: try: admin_url = reverse( 'admin:moderation_contentsubmission_change', args=[obj.submission.pk] ) return format_html( '#{}', admin_url, obj.submission.pk ) except: return str(obj.submission.pk) return '-' submission_link.short_description = 'Submission' def changed_field_count(self, obj): """Display count of changed fields.""" count = len(obj.changed_fields) if count == 0: return '-' return f"{count} field{'s' if count != 1 else ''}" changed_field_count.short_description = 'Changed Fields' def snapshot_display(self, obj): """Display snapshot in a formatted way.""" import json snapshot = obj.get_snapshot_dict() # Format as pretty JSON formatted = json.dumps(snapshot, indent=2, sort_keys=True) return format_html( '
{}
', formatted ) snapshot_display.short_description = 'Snapshot' def changed_fields_display(self, obj): """Display changed fields in a formatted way.""" if not obj.changed_fields: return format_html('No fields changed') html_parts = [''] html_parts.append('') html_parts.append('') html_parts.append('') html_parts.append('') html_parts.append('') for field_name, change in obj.changed_fields.items(): old_val = change.get('old', '-') new_val = change.get('new', '-') # Truncate long values if isinstance(old_val, str) and len(old_val) > 100: old_val = old_val[:97] + '...' if isinstance(new_val, str) and len(new_val) > 100: new_val = new_val[:97] + '...' html_parts.append('') html_parts.append(f'') html_parts.append(f'') html_parts.append(f'') html_parts.append('') html_parts.append('
FieldOld ValueNew Value
{field_name}{old_val}{new_val}
') return format_html(''.join(html_parts)) changed_fields_display.short_description = 'Changed Fields' def get_queryset(self, request): """Optimize queryset with select_related.""" qs = super().get_queryset(request) return qs.select_related( 'entity_type', 'changed_by', 'submission', 'submission__user' )