mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 17:51:08 -05:00
Improve moderation dashboard UI and functionality
- Add status tabs (Pending, Approved, Rejected, Escalated) - Implement HTMX for smooth tab switching and status changes - Add proper permissions for escalated submissions - Change Status filter to Submission Type (Text/Photo) - Move navigation into dashboard content - Fix tab menu visibility and transitions - Add contextual loading indicator - Update styling to match dark theme - Ensure consistent styling across components
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from django.views.generic import ListView, TemplateView
|
||||
from django.shortcuts import get_object_or_404
|
||||
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
|
||||
@@ -29,91 +29,102 @@ class ModeratorRequiredMixin(UserPassesTestMixin):
|
||||
return super().handle_no_permission()
|
||||
raise PermissionDenied("You do not have moderator permissions.")
|
||||
|
||||
class DashboardView(LoginRequiredMixin, ModeratorRequiredMixin, TemplateView):
|
||||
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 ['moderation/dashboard.html']
|
||||
return [self.template_name]
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['submissions'] = EditSubmission.objects.all().order_by('-created_at')[:10]
|
||||
return context
|
||||
|
||||
class EditSubmissionListView(LoginRequiredMixin, ModeratorRequiredMixin, ListView):
|
||||
context_object_name = 'submissions'
|
||||
|
||||
def get_template_names(self):
|
||||
if self.request.headers.get('HX-Request'):
|
||||
return ['moderation/partials/edit_submission_content.html']
|
||||
return ['moderation/edit_submission_list.html']
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = EditSubmission.objects.all().order_by('-created_at')
|
||||
status = self.request.GET.get('status', 'NEW')
|
||||
submission_type = self.request.GET.get('submission_type', '')
|
||||
|
||||
if status := self.request.GET.get('status'):
|
||||
queryset = queryset.filter(status=status)
|
||||
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 submission_type := self.request.GET.get('type'):
|
||||
queryset = queryset.filter(submission_type=submission_type)
|
||||
|
||||
if content_type := self.request.GET.get('content_type'):
|
||||
queryset = queryset.filter(content_type__model=content_type)
|
||||
|
||||
return queryset
|
||||
|
||||
class PhotoSubmissionListView(LoginRequiredMixin, ModeratorRequiredMixin, ListView):
|
||||
context_object_name = 'submissions'
|
||||
|
||||
def get_template_names(self):
|
||||
if self.request.headers.get('HX-Request'):
|
||||
return ['moderation/partials/photo_submission_content.html']
|
||||
return ['moderation/photo_submission_list.html']
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = PhotoSubmission.objects.all().order_by('-created_at')
|
||||
|
||||
if status := self.request.GET.get('status'):
|
||||
queryset = queryset.filter(status=status)
|
||||
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:
|
||||
"""HTMX endpoint for filtered submission list"""
|
||||
"""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)
|
||||
|
||||
queryset = EditSubmission.objects.all().order_by('-created_at')
|
||||
status = request.GET.get('status', 'NEW')
|
||||
submission_type = request.GET.get('submission_type', '')
|
||||
|
||||
if status := request.GET.get('status'):
|
||||
queryset = queryset.filter(status=status)
|
||||
if submission_type := request.GET.get('type'):
|
||||
queryset = queryset.filter(submission_type=submission_type)
|
||||
if content_type := request.GET.get('content_type'):
|
||||
queryset = queryset.filter(content_type__model=content_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)
|
||||
|
||||
html = render_to_string(
|
||||
'moderation/partials/submission_list.html',
|
||||
{'submissions': queryset},
|
||||
request=request
|
||||
)
|
||||
return HttpResponse(html)
|
||||
context = {
|
||||
'submissions': queryset,
|
||||
'user': request.user,
|
||||
}
|
||||
|
||||
# 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 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)
|
||||
|
||||
submission = get_object_or_404(EditSubmission, id=submission_id)
|
||||
|
||||
try:
|
||||
submission.approve(user)
|
||||
_update_submission_notes(submission, request.POST.get('notes'))
|
||||
return _render_submission_response('moderation/partials/submission_list.html', submission, request)
|
||||
|
||||
# Get updated queryset with filters
|
||||
status = request.GET.get('status', 'NEW')
|
||||
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)
|
||||
|
||||
@@ -121,27 +132,73 @@ def approve_submission(request: HttpRequest, submission_id: int) -> HttpResponse
|
||||
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 = get_object_or_404(EditSubmission, id=submission_id)
|
||||
|
||||
submission.reject(user)
|
||||
_update_submission_notes(submission, request.POST.get('notes'))
|
||||
|
||||
return _render_submission_response('moderation/partials/submission_list.html', submission, request)
|
||||
# Get updated queryset with filters
|
||||
status = request.GET.get('status', 'NEW')
|
||||
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 user.role != 'MODERATOR' and not user.is_superuser:
|
||||
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'))
|
||||
|
||||
return _render_submission_response('moderation/partials/submission_list.html', submission, request)
|
||||
# Get updated queryset with filters
|
||||
status = request.GET.get('status', 'NEW')
|
||||
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:
|
||||
@@ -154,7 +211,7 @@ def approve_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||
|
||||
try:
|
||||
submission.approve(user, request.POST.get('notes', ''))
|
||||
return _render_submission_response('moderation/partials/photo_submission.html', submission, request)
|
||||
return render(request, 'moderation/partials/photo_submission.html', {'submission': submission})
|
||||
except Exception as e:
|
||||
return HttpResponse(str(e), status=400)
|
||||
|
||||
@@ -168,16 +225,10 @@ def reject_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||
submission = get_object_or_404(PhotoSubmission, id=submission_id)
|
||||
submission.reject(user, request.POST.get('notes', ''))
|
||||
|
||||
return _render_submission_response('moderation/partials/photo_submission.html', submission, request)
|
||||
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()
|
||||
|
||||
def _render_submission_response(template: str, submission: Any, request: HttpRequest) -> HttpResponse:
|
||||
"""Render submission template response."""
|
||||
context = {'submission': submission}
|
||||
html = render_to_string(template, context, request=request)
|
||||
return HttpResponse(html)
|
||||
|
||||
Reference in New Issue
Block a user