mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 15:11:09 -05:00
404 lines
16 KiB
Python
404 lines
16 KiB
Python
from django.views.generic import ListView, TemplateView
|
|
from django.shortcuts import get_object_or_404, render
|
|
from django.http import HttpResponse, JsonResponse, HttpRequest
|
|
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.template.loader import render_to_string
|
|
from django.db.models import Q
|
|
from django.core.exceptions import PermissionDenied
|
|
from typing import Optional, Any, cast
|
|
from accounts.models import User
|
|
|
|
from .models import EditSubmission, PhotoSubmission
|
|
from parks.models import Park, ParkArea
|
|
from designers.models import Designer
|
|
from companies.models import Manufacturer
|
|
from rides.models import RideModel
|
|
|
|
MODERATOR_ROLES = ['MODERATOR', 'ADMIN', 'SUPERUSER']
|
|
|
|
class ModeratorRequiredMixin(UserPassesTestMixin):
|
|
request: HttpRequest
|
|
|
|
def test_func(self) -> bool:
|
|
"""Check if user has moderator permissions."""
|
|
user = cast(User, self.request.user)
|
|
return (
|
|
user.is_authenticated and
|
|
(getattr(user, 'role', None) in MODERATOR_ROLES or user.is_superuser)
|
|
)
|
|
|
|
def handle_no_permission(self) -> HttpResponse:
|
|
if not self.request.user.is_authenticated:
|
|
return super().handle_no_permission()
|
|
raise PermissionDenied("You do not have moderator permissions.")
|
|
|
|
@login_required
|
|
def search_parks(request: HttpRequest) -> HttpResponse:
|
|
"""HTMX endpoint for searching parks in moderation dashboard"""
|
|
user = cast(User, request.user)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
query = request.GET.get('q', '').strip()
|
|
submission_id = request.GET.get('submission_id')
|
|
|
|
# If no query, show first 10 parks
|
|
if not query:
|
|
parks = Park.objects.all().order_by('name')[:10]
|
|
else:
|
|
parks = Park.objects.filter(name__icontains=query).order_by('name')[:10]
|
|
|
|
context = {
|
|
'parks': parks,
|
|
'search_term': query,
|
|
'submission_id': submission_id
|
|
}
|
|
|
|
return render(request, 'moderation/partials/park_search_results.html', context)
|
|
|
|
@login_required
|
|
def search_manufacturers(request: HttpRequest) -> HttpResponse:
|
|
"""HTMX endpoint for searching manufacturers in moderation dashboard"""
|
|
user = cast(User, request.user)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
query = request.GET.get('q', '').strip()
|
|
submission_id = request.GET.get('submission_id')
|
|
|
|
# If no query, show first 10 manufacturers
|
|
if not query:
|
|
manufacturers = Manufacturer.objects.all().order_by('name')[:10]
|
|
else:
|
|
manufacturers = Manufacturer.objects.filter(name__icontains=query).order_by('name')[:10]
|
|
|
|
context = {
|
|
'manufacturers': manufacturers,
|
|
'search_term': query,
|
|
'submission_id': submission_id
|
|
}
|
|
|
|
return render(request, 'moderation/partials/manufacturer_search_results.html', context)
|
|
|
|
@login_required
|
|
def search_designers(request: HttpRequest) -> HttpResponse:
|
|
"""HTMX endpoint for searching designers in moderation dashboard"""
|
|
user = cast(User, request.user)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
query = request.GET.get('q', '').strip()
|
|
submission_id = request.GET.get('submission_id')
|
|
|
|
# If no query, show first 10 designers
|
|
if not query:
|
|
designers = Designer.objects.all().order_by('name')[:10]
|
|
else:
|
|
designers = Designer.objects.filter(name__icontains=query).order_by('name')[:10]
|
|
|
|
context = {
|
|
'designers': designers,
|
|
'search_term': query,
|
|
'submission_id': submission_id
|
|
}
|
|
|
|
return render(request, 'moderation/partials/designer_search_results.html', context)
|
|
|
|
@login_required
|
|
def search_ride_models(request: HttpRequest) -> HttpResponse:
|
|
"""HTMX endpoint for searching ride models in moderation dashboard"""
|
|
user = cast(User, request.user)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
query = request.GET.get('q', '').strip()
|
|
submission_id = request.GET.get('submission_id')
|
|
manufacturer_id = request.GET.get('manufacturer')
|
|
|
|
queryset = RideModel.objects.all()
|
|
|
|
if manufacturer_id:
|
|
queryset = queryset.filter(manufacturer_id=manufacturer_id)
|
|
|
|
# If no query, show first 10 models
|
|
if not query:
|
|
ride_models = queryset.order_by('name')[:10]
|
|
else:
|
|
ride_models = queryset.filter(name__icontains=query).order_by('name')[:10]
|
|
|
|
context = {
|
|
'ride_models': ride_models,
|
|
'search_term': query,
|
|
'submission_id': submission_id
|
|
}
|
|
|
|
return render(request, 'moderation/partials/ride_model_search_results.html', context)
|
|
|
|
class DashboardView(LoginRequiredMixin, ModeratorRequiredMixin, ListView):
|
|
template_name = 'moderation/dashboard.html'
|
|
context_object_name = 'submissions'
|
|
paginate_by = 10
|
|
|
|
def get_template_names(self):
|
|
if self.request.headers.get('HX-Request'):
|
|
return ['moderation/partials/dashboard_content.html']
|
|
return [self.template_name]
|
|
|
|
def get_queryset(self):
|
|
status = self.request.GET.get('status', 'PENDING')
|
|
submission_type = self.request.GET.get('submission_type', '')
|
|
|
|
if submission_type == 'photo':
|
|
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
|
else:
|
|
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
|
|
|
if type_filter := self.request.GET.get('type'):
|
|
queryset = queryset.filter(submission_type=type_filter)
|
|
|
|
if content_type := self.request.GET.get('content_type'):
|
|
queryset = queryset.filter(content_type__model=content_type)
|
|
|
|
return queryset
|
|
|
|
@login_required
|
|
def submission_list(request: HttpRequest) -> HttpResponse:
|
|
"""View for submission list with filters"""
|
|
user = cast(User, request.user)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
status = request.GET.get('status', 'PENDING')
|
|
submission_type = request.GET.get('submission_type', '')
|
|
|
|
if submission_type == 'photo':
|
|
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
|
else:
|
|
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
|
|
|
if type_filter := request.GET.get('type'):
|
|
queryset = queryset.filter(submission_type=type_filter)
|
|
|
|
if content_type := request.GET.get('content_type'):
|
|
queryset = queryset.filter(content_type__model=content_type)
|
|
|
|
context = {
|
|
'submissions': queryset,
|
|
'user': request.user,
|
|
'parks': [(p.id, str(p)) for p in Park.objects.all()],
|
|
'designers': [(d.id, str(d)) for d in Designer.objects.all()],
|
|
'manufacturers': [(m.id, str(m)) for m in Manufacturer.objects.all()],
|
|
'ride_models': [(m.id, str(m)) for m in RideModel.objects.all()],
|
|
'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])]
|
|
}
|
|
|
|
# If it's an HTMX request, return just the content
|
|
if request.headers.get('HX-Request'):
|
|
return render(request, 'moderation/partials/dashboard_content.html', context)
|
|
|
|
# For direct URL access, return the full dashboard template
|
|
return render(request, 'moderation/dashboard.html', context)
|
|
|
|
@login_required
|
|
def edit_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|
"""HTMX endpoint for editing a submission"""
|
|
user = cast(User, request.user)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
submission = get_object_or_404(EditSubmission, id=submission_id)
|
|
|
|
if request.method == 'POST':
|
|
# Handle the edit submission
|
|
notes = request.POST.get('notes')
|
|
if not notes:
|
|
return HttpResponse("Notes are required when editing a submission", status=400)
|
|
|
|
try:
|
|
# Update the moderator_changes with the edited values
|
|
edited_changes = submission.changes.copy()
|
|
for field in submission.changes.keys():
|
|
if field == 'stats':
|
|
edited_stats = {}
|
|
for key in submission.changes['stats'].keys():
|
|
if new_value := request.POST.get(f'stats.{key}'):
|
|
edited_stats[key] = new_value
|
|
edited_changes['stats'] = edited_stats
|
|
else:
|
|
if new_value := request.POST.get(field):
|
|
# Handle special field types
|
|
if field in ['latitude', 'longitude', 'size_acres']:
|
|
try:
|
|
edited_changes[field] = float(new_value)
|
|
except ValueError:
|
|
return HttpResponse(f"Invalid value for {field}", status=400)
|
|
else:
|
|
edited_changes[field] = new_value
|
|
|
|
submission.moderator_changes = edited_changes
|
|
submission.notes = notes
|
|
submission.save()
|
|
|
|
# Return the updated submission
|
|
context = {
|
|
'submission': submission,
|
|
'user': request.user,
|
|
'parks': [(p.id, str(p)) for p in Park.objects.all()],
|
|
'designers': [(d.id, str(d)) for d in Designer.objects.all()],
|
|
'manufacturers': [(m.id, str(m)) for m in Manufacturer.objects.all()],
|
|
'ride_models': [(m.id, str(m)) for m in RideModel.objects.all()],
|
|
'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])],
|
|
'park_areas': [(a.id, str(a)) for a in ParkArea.objects.filter(park_id=edited_changes.get('park'))] if edited_changes.get('park') else []
|
|
}
|
|
return render(request, 'moderation/partials/submission_list.html', context)
|
|
|
|
except Exception as e:
|
|
return HttpResponse(str(e), status=400)
|
|
|
|
return HttpResponse("Invalid request method", status=405)
|
|
|
|
@login_required
|
|
def approve_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|
"""HTMX endpoint for approving a submission"""
|
|
user = cast(User, request.user)
|
|
submission = get_object_or_404(EditSubmission, id=submission_id)
|
|
|
|
if submission.status == 'ESCALATED' and not (user.role in ['ADMIN', 'SUPERUSER'] or user.is_superuser):
|
|
return HttpResponse("Only admins can handle escalated submissions", status=403)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
try:
|
|
submission.approve(user)
|
|
_update_submission_notes(submission, request.POST.get('notes'))
|
|
|
|
# Get updated queryset with filters
|
|
status = request.GET.get('status', 'PENDING')
|
|
submission_type = request.GET.get('submission_type', '')
|
|
|
|
if submission_type == 'photo':
|
|
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
|
else:
|
|
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
|
|
|
if type_filter := request.GET.get('type'):
|
|
queryset = queryset.filter(submission_type=type_filter)
|
|
|
|
if content_type := request.GET.get('content_type'):
|
|
queryset = queryset.filter(content_type__model=content_type)
|
|
|
|
context = {
|
|
'submissions': queryset,
|
|
'user': request.user,
|
|
}
|
|
|
|
return render(request, 'moderation/partials/dashboard_content.html', context)
|
|
except ValueError as e:
|
|
return HttpResponse(str(e), status=400)
|
|
|
|
@login_required
|
|
def reject_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|
"""HTMX endpoint for rejecting a submission"""
|
|
user = cast(User, request.user)
|
|
submission = get_object_or_404(EditSubmission, id=submission_id)
|
|
|
|
if submission.status == 'ESCALATED' and not (user.role in ['ADMIN', 'SUPERUSER'] or user.is_superuser):
|
|
return HttpResponse("Only admins can handle escalated submissions", status=403)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
submission.reject(user)
|
|
_update_submission_notes(submission, request.POST.get('notes'))
|
|
|
|
# Get updated queryset with filters
|
|
status = request.GET.get('status', 'PENDING')
|
|
submission_type = request.GET.get('submission_type', '')
|
|
|
|
if submission_type == 'photo':
|
|
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
|
else:
|
|
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
|
|
|
if type_filter := request.GET.get('type'):
|
|
queryset = queryset.filter(submission_type=type_filter)
|
|
|
|
if content_type := request.GET.get('content_type'):
|
|
queryset = queryset.filter(content_type__model=content_type)
|
|
|
|
context = {
|
|
'submissions': queryset,
|
|
'user': request.user,
|
|
}
|
|
|
|
return render(request, 'moderation/partials/dashboard_content.html', context)
|
|
|
|
@login_required
|
|
def escalate_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|
"""HTMX endpoint for escalating a submission"""
|
|
user = cast(User, request.user)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
submission = get_object_or_404(EditSubmission, id=submission_id)
|
|
if submission.status == 'ESCALATED':
|
|
return HttpResponse("Submission is already escalated", status=400)
|
|
|
|
submission.escalate(user)
|
|
_update_submission_notes(submission, request.POST.get('notes'))
|
|
|
|
# Get updated queryset with filters
|
|
status = request.GET.get('status', 'PENDING')
|
|
submission_type = request.GET.get('submission_type', '')
|
|
|
|
if submission_type == 'photo':
|
|
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
|
else:
|
|
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
|
|
|
if type_filter := request.GET.get('type'):
|
|
queryset = queryset.filter(submission_type=type_filter)
|
|
|
|
if content_type := request.GET.get('content_type'):
|
|
queryset = queryset.filter(content_type__model=content_type)
|
|
|
|
context = {
|
|
'submissions': queryset,
|
|
'user': request.user,
|
|
}
|
|
|
|
return render(request, 'moderation/partials/dashboard_content.html', context)
|
|
|
|
@login_required
|
|
def approve_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|
"""HTMX endpoint for approving a photo submission"""
|
|
user = cast(User, request.user)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
submission = get_object_or_404(PhotoSubmission, id=submission_id)
|
|
|
|
try:
|
|
submission.approve(user, request.POST.get('notes', ''))
|
|
return render(request, 'moderation/partials/photo_submission.html', {'submission': submission})
|
|
except Exception as e:
|
|
return HttpResponse(str(e), status=400)
|
|
|
|
@login_required
|
|
def reject_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|
"""HTMX endpoint for rejecting a photo submission"""
|
|
user = cast(User, request.user)
|
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
|
return HttpResponse(status=403)
|
|
|
|
submission = get_object_or_404(PhotoSubmission, id=submission_id)
|
|
submission.reject(user, request.POST.get('notes', ''))
|
|
|
|
return render(request, 'moderation/partials/photo_submission.html', {'submission': submission})
|
|
|
|
def _update_submission_notes(submission: EditSubmission, notes: Optional[str]) -> None:
|
|
"""Update submission notes if provided."""
|
|
if notes:
|
|
submission.notes = notes
|
|
submission.save()
|