mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 11:51:14 -05:00
- 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.
237 lines
7.3 KiB
Python
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'
|
|
)
|