mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 17:11:09 -05:00
- Add complete backend/ directory with full Django application - Add frontend/ directory with Vite + TypeScript setup ready for Next.js - Add comprehensive shared/ directory with: - Complete documentation and memory-bank archives - Media files and avatars (letters, park/ride images) - Deployment scripts and automation tools - Shared types and utilities - Add architecture/ directory with migration guides - Configure pnpm workspace for monorepo development - Update .gitignore to exclude .django_tailwind_cli/ build artifacts - Preserve all historical documentation in shared/docs/memory-bank/ - Set up proper structure for full-stack development with shared resources
172 lines
5.3 KiB
Python
172 lines
5.3 KiB
Python
from django.contrib import admin
|
|
from django.contrib.admin import AdminSite
|
|
from django.utils.html import format_html
|
|
from django.urls import reverse
|
|
from django.utils.safestring import mark_safe
|
|
from .models import EditSubmission, PhotoSubmission
|
|
|
|
|
|
class ModerationAdminSite(AdminSite):
|
|
site_header = "ThrillWiki Moderation"
|
|
site_title = "ThrillWiki Moderation"
|
|
index_title = "Moderation Dashboard"
|
|
|
|
def has_permission(self, request):
|
|
"""Only allow moderators and above to access this admin site"""
|
|
return request.user.is_authenticated and request.user.role in [
|
|
"MODERATOR",
|
|
"ADMIN",
|
|
"SUPERUSER",
|
|
]
|
|
|
|
|
|
moderation_site = ModerationAdminSite(name="moderation")
|
|
|
|
|
|
class EditSubmissionAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"id",
|
|
"user_link",
|
|
"content_type",
|
|
"content_link",
|
|
"status",
|
|
"created_at",
|
|
"handled_by",
|
|
]
|
|
list_filter = ["status", "content_type", "created_at"]
|
|
search_fields = ["user__username", "reason", "source", "notes"]
|
|
readonly_fields = [
|
|
"user",
|
|
"content_type",
|
|
"object_id",
|
|
"changes",
|
|
"created_at",
|
|
]
|
|
|
|
def user_link(self, obj):
|
|
url = reverse("admin:accounts_user_change", args=[obj.user.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.user.username)
|
|
|
|
user_link.short_description = "User"
|
|
|
|
def content_link(self, obj):
|
|
if hasattr(obj.content_object, "get_absolute_url"):
|
|
url = obj.content_object.get_absolute_url()
|
|
return format_html('<a href="{}">{}</a>', url, str(obj.content_object))
|
|
return str(obj.content_object)
|
|
|
|
content_link.short_description = "Content"
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
if "status" in form.changed_data:
|
|
if obj.status == "APPROVED":
|
|
obj.approve(request.user)
|
|
elif obj.status == "REJECTED":
|
|
obj.reject(request.user)
|
|
elif obj.status == "ESCALATED":
|
|
obj.escalate(request.user)
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
class PhotoSubmissionAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"id",
|
|
"user_link",
|
|
"content_type",
|
|
"content_link",
|
|
"photo_preview",
|
|
"status",
|
|
"created_at",
|
|
"handled_by",
|
|
]
|
|
list_filter = ["status", "content_type", "created_at"]
|
|
search_fields = ["user__username", "caption", "notes"]
|
|
readonly_fields = [
|
|
"user",
|
|
"content_type",
|
|
"object_id",
|
|
"photo_preview",
|
|
"created_at",
|
|
]
|
|
|
|
def user_link(self, obj):
|
|
url = reverse("admin:accounts_user_change", args=[obj.user.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.user.username)
|
|
|
|
user_link.short_description = "User"
|
|
|
|
def content_link(self, obj):
|
|
if hasattr(obj.content_object, "get_absolute_url"):
|
|
url = obj.content_object.get_absolute_url()
|
|
return format_html('<a href="{}">{}</a>', url, str(obj.content_object))
|
|
return str(obj.content_object)
|
|
|
|
content_link.short_description = "Content"
|
|
|
|
def photo_preview(self, obj):
|
|
if obj.photo:
|
|
return format_html(
|
|
'<img src="{}" style="max-height: 100px; max-width: 200px;" />',
|
|
obj.photo.url,
|
|
)
|
|
return ""
|
|
|
|
photo_preview.short_description = "Photo Preview"
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
if "status" in form.changed_data:
|
|
if obj.status == "APPROVED":
|
|
obj.approve(request.user, obj.notes)
|
|
elif obj.status == "REJECTED":
|
|
obj.reject(request.user, obj.notes)
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
class HistoryEventAdmin(admin.ModelAdmin):
|
|
"""Admin interface for viewing model history events"""
|
|
|
|
list_display = [
|
|
"pgh_label",
|
|
"pgh_created_at",
|
|
"get_object_link",
|
|
"get_context",
|
|
]
|
|
list_filter = ["pgh_label", "pgh_created_at"]
|
|
readonly_fields = [
|
|
"pgh_label",
|
|
"pgh_obj_id",
|
|
"pgh_data",
|
|
"pgh_context",
|
|
"pgh_created_at",
|
|
]
|
|
date_hierarchy = "pgh_created_at"
|
|
|
|
def get_object_link(self, obj):
|
|
"""Display a link to the related object if possible"""
|
|
if obj.pgh_obj and hasattr(obj.pgh_obj, "get_absolute_url"):
|
|
url = obj.pgh_obj.get_absolute_url()
|
|
return format_html('<a href="{}">{}</a>', url, str(obj.pgh_obj))
|
|
return str(obj.pgh_obj or "")
|
|
|
|
get_object_link.short_description = "Object"
|
|
|
|
def get_context(self, obj):
|
|
"""Format the context data nicely"""
|
|
if not obj.pgh_context:
|
|
return "-"
|
|
html = ["<table>"]
|
|
for key, value in obj.pgh_context.items():
|
|
html.append(f"<tr><th>{key}</th><td>{value}</td></tr>")
|
|
html.append("</table>")
|
|
return mark_safe("".join(html))
|
|
|
|
get_context.short_description = "Context"
|
|
|
|
|
|
# Register with moderation site only
|
|
moderation_site.register(EditSubmission, EditSubmissionAdmin)
|
|
moderation_site.register(PhotoSubmission, PhotoSubmissionAdmin)
|
|
|
|
# We will register concrete event models as they are created during migrations
|
|
# Example: moderation_site.register(DesignerEvent, HistoryEventAdmin)
|