Files
thrilltrack-explorer/django/apps/versioning/admin.py
pacnpal d6ff4cc3a3 Add email templates for user notifications and account management
- Created a base email template (base.html) for consistent styling across all emails.
- Added moderation approval email template (moderation_approved.html) to notify users of approved submissions.
- Added moderation rejection email template (moderation_rejected.html) to inform users of required changes for their submissions.
- Created password reset email template (password_reset.html) for users requesting to reset their passwords.
- Developed a welcome email template (welcome.html) to greet new users and provide account details and tips for using ThrillWiki.
2025-11-08 15:34:04 -05:00

237 lines
7.3 KiB
Python

"""
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(
'<a href="{}">{}</a>',
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(
'<a href="{}">{}</a>',
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(
'<a href="{}">#{}</a>',
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(
'<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto;">{}</pre>',
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('<em>No fields changed</em>')
html_parts = ['<table style="width: 100%; border-collapse: collapse;">']
html_parts.append('<thead><tr style="background: #f5f5f5;">')
html_parts.append('<th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Field</th>')
html_parts.append('<th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Old Value</th>')
html_parts.append('<th style="padding: 8px; text-align: left; border: 1px solid #ddd;">New Value</th>')
html_parts.append('</tr></thead><tbody>')
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('<tr>')
html_parts.append(f'<td style="padding: 8px; border: 1px solid #ddd;"><strong>{field_name}</strong></td>')
html_parts.append(f'<td style="padding: 8px; border: 1px solid #ddd; color: #d32f2f;">{old_val}</td>')
html_parts.append(f'<td style="padding: 8px; border: 1px solid #ddd; color: #388e3c;">{new_val}</td>')
html_parts.append('</tr>')
html_parts.append('</tbody></table>')
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'
)