diff --git a/moderation/views.py b/moderation/views.py index f0aa9cfc..42f47a1c 100644 --- a/moderation/views.py +++ b/moderation/views.py @@ -183,6 +183,14 @@ def submission_list(request: HttpRequest) -> HttpResponse: if content_type := request.GET.get('content_type'): queryset = queryset.filter(content_type__model=content_type) + # Get park areas for each park submission + park_areas_by_park = {} + for submission in queryset: + if submission.content_type.model == 'park' and submission.changes.get('park'): + park_id = submission.changes['park'] + if park_id not in park_areas_by_park: + park_areas_by_park[park_id] = [(a.id, str(a)) for a in ParkArea.objects.filter(park_id=park_id)] + context = { 'submissions': queryset, 'user': request.user, @@ -190,7 +198,8 @@ def submission_list(request: HttpRequest) -> HttpResponse: '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'])] + 'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])], + 'park_areas_by_park': park_areas_by_park } # If it's an HTMX request, return just the content @@ -302,36 +311,42 @@ 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, + '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=submission.changes.get('park'))] if submission.changes.get('park') else [] } - - return render(request, 'moderation/partials/dashboard_content.html', context) + return render(request, 'moderation/partials/submission_list.html', context) + @login_required def escalate_submission(request: HttpRequest, submission_id: int) -> HttpResponse: @@ -339,35 +354,36 @@ def escalate_submission(request: HttpRequest, submission_id: int) -> HttpRespons 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': + if submission.status == "ESCALATED": return HttpResponse("Submission is already escalated", status=400) - + submission.escalate(user) - _update_submission_notes(submission, request.POST.get('notes')) - + _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') + 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 = 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'): + + if content_type := request.GET.get("content_type"): queryset = queryset.filter(content_type__model=content_type) - + context = { - 'submissions': queryset, - 'user': request.user, + "submissions": queryset, + "user": request.user, } - - return render(request, 'moderation/partials/dashboard_content.html', context) + + return render(request, "moderation/partials/dashboard_content.html", context) + @login_required def approve_photo(request: HttpRequest, submission_id: int) -> HttpResponse: @@ -375,26 +391,34 @@ def approve_photo(request: HttpRequest, submission_id: int) -> HttpResponse: 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}) + 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}) + 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.""" diff --git a/static/css/tailwind.css b/static/css/tailwind.css index 9567116a..c7d164cb 100644 --- a/static/css/tailwind.css +++ b/static/css/tailwind.css @@ -2181,6 +2181,10 @@ select { justify-content: center; } +.visible { + visibility: visible; +} + .static { position: static; } @@ -2304,6 +2308,11 @@ select { margin-right: auto; } +.my-auto { + margin-top: auto; + margin-bottom: auto; +} + .-mb-px { margin-bottom: -1px; } @@ -2500,10 +2509,6 @@ select { max-height: 90vh; } -.min-h-\[calc\(100vh-16rem\)\] { - min-height: calc(100vh - 16rem); -} - .min-h-screen { min-height: 100vh; } @@ -3033,6 +3038,10 @@ select { background-color: rgb(220 38 38 / var(--tw-bg-opacity)); } +.bg-red-900\/40 { + background-color: rgb(127 29 29 / 0.4); +} + .bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); @@ -3065,6 +3074,10 @@ select { background-color: rgb(202 138 4 / var(--tw-bg-opacity)); } +.bg-yellow-900\/40 { + background-color: rgb(113 63 18 / 0.4); +} + .bg-opacity-50 { --tw-bg-opacity: 0.5; } @@ -3392,6 +3405,11 @@ select { color: rgb(79 70 229 / var(--tw-text-opacity)); } +.text-red-400 { + --tw-text-opacity: 1; + color: rgb(248 113 113 / var(--tw-text-opacity)); +} + .text-red-500 { --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); @@ -4313,6 +4331,11 @@ select { margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); } + .md\:py-12 { + padding-top: 3rem; + padding-bottom: 3rem; + } + .md\:text-2xl { font-size: 1.5rem; line-height: 2rem; diff --git a/templates/moderation/partials/submission_list.html b/templates/moderation/partials/submission_list.html index 4cd0bab7..3e9ceab2 100644 --- a/templates/moderation/partials/submission_list.html +++ b/templates/moderation/partials/submission_list.html @@ -5,8 +5,445 @@ {% endblock %} - -{{ existing_content_until_approve_button|safe }} +{% for submission in submissions %} +
{ + this.showCoasterFields = value === 'RC'; + }); + } + }" + @htmx:afterOnLoad="showSuccess = true; setTimeout(() => showSuccess = false, 3000)"> + +
+ +
+
+

+ + + {{ submission.get_status_display }} + +

+
+
+ + {{ submission.get_content_type_display }} - + {% if submission.submission_type == 'CREATE' %}New{% else %}Edit{% endif %} +
+
+ + {{ submission.user.username }} +
+
+ + {{ submission.created_at|date:"M d, Y H:i" }} +
+ {% if submission.handled_by %} +
+ + {{ submission.handled_by.username }} +
+ {% endif %} +
+
+
+ + +
+ {% if submission.content_object %} +
+
Current Object:
+
+ {{ submission.content_object }} + {% if submission.content_type.model == 'park' or submission.content_type.model == 'company' or submission.content_type.model == 'designer' %} +
+ {{ submission.content_object.address }} +
+ {% endif %} +
+
+ {% endif %} + + {% if submission.reason %} +
+
Reason:
+
{{ submission.reason }}
+
+ {% endif %} + + {% if submission.source %} + + {% endif %} + + +
+ + {% if submission.content_type.model == 'park' and submission.changes.latitude and submission.changes.longitude %} +
+ {% include "moderation/partials/location_map.html" with location=submission.changes %} +
+ {% endif %} + +
+ {% for field, value in submission.changes.items %} + {% if field != 'model_name' and field != 'latitude' and field != 'longitude' and field != 'street_address' and field != 'city' and field != 'state' and field != 'postal_code' and field != 'country' %} +
+
+ {{ field|title }}: +
+
+ {% if field == 'opening_date' or field == 'closing_date' or field == 'status_since' %} + {{ value|date:"Y-m-d" }} + {% elif field == 'size_acres' %} + {{ value }} acres + {% elif field == 'website' %} + + {{ value }} + + {% elif field == 'park' %} + {% with park_name=value|get_object_name:'parks.Park' %} + {{ park_name }} + {% endwith %} + {% elif field == 'designer' %} + {% with designer_name=value|get_object_name:'designers.Designer' %} + {{ designer_name|default:'None' }} + {% endwith %} + {% elif field == 'manufacturer' %} + {% with manufacturer_name=value|get_object_name:'companies.Manufacturer' %} + {{ manufacturer_name|default:'None' }} + {% endwith %} + {% elif field == 'ride_model' %} + {% with model_name=value|get_object_name:'rides.RideModel' %} + {{ model_name|default:'None' }} + {% endwith %} + {% elif field == 'park_area' %} + {% with park_id=submission.changes.park %} + {{ value|get_park_area_name:park_id|default:'None' }} + {% endwith %} + {% elif field == 'category' %} + {{ value|get_category_display }} + {% elif field == 'stats' %} +
+ {% if value.height_ft %}
Height: {{ value.height_ft }} ft
{% endif %} + {% if value.length_ft %}
Length: {{ value.length_ft }} ft
{% endif %} + {% if value.speed_mph %}
Speed: {{ value.speed_mph }} mph
{% endif %} + {% if value.inversions %}
Inversions: {{ value.inversions }}
{% endif %} + {% if value.launch_type %}
Launch Type: {{ value.launch_type }}
{% endif %} + {% if value.track_material %}
Track Material: {{ value.track_material }}
{% endif %} + {% if value.roller_coaster_type %}
Type: {{ value.roller_coaster_type }}
{% endif %} + {% if value.trains_count %}
Number of Trains: {{ value.trains_count }}
{% endif %} + {% if value.cars_per_train %}
Cars per Train: {{ value.cars_per_train }}
{% endif %} + {% if value.seats_per_car %}
Seats per Car: {{ value.seats_per_car }}
{% endif %} +
+ {% else %} + {{ value }} + {% endif %} +
+
+ {% endif %} + {% endfor %} +
+
+ + +
+ {% for field, value in submission.changes.items %} + {% if field != 'model_name' and field != 'stats' and field != 'latitude' and field != 'longitude' and field != 'street_address' and field != 'city' and field != 'state' and field != 'postal_code' and field != 'country' %} +
+ + + {% if field == 'category' %} + + {% elif field == 'status' %} + + {% elif field == 'post_closing_status' %} + + {% elif field == 'opening_date' or field == 'closing_date' or field == 'status_since' %} + + {% else %} + {% if field == 'park' %} +
+ +
+ +
+ {% elif field == 'park_area' %} + + {% elif field == 'manufacturer' %} +
+ +
+ +
+ {% elif field == 'designer' %} +
+ +
+ +
+ {% elif field == 'ride_model' %} +
+ +
+ +
+ {% elif field == 'description' %} + + {% elif field == 'min_height_in' or field == 'max_height_in' %} + + {% elif field == 'capacity_per_hour' %} + + {% elif field == 'ride_duration_seconds' %} + + {% else %} + + {% endif %} + {% endif %} +
+ {% endif %} + {% endfor %} + + + {% if submission.content_type.model == 'park' %} +
+ {% include "moderation/partials/location_widget.html" with form=submission.changes %} +
+ {% endif %} + + +
+ {% include 'moderation/partials/coaster_fields.html' with stats=submission.changes.stats %} +
+ +
+ + +
+ +
+ + +
+
+ + + {% if submission.notes %} +
+
Review Notes:
+
{{ submission.notes }}
+
+ {% endif %} + + {% if submission.status == 'PENDING' or submission.status == 'ESCALATED' and user.role in 'ADMIN,SUPERUSER' %} +
+
+ +
+ +
+ + + + + {% if submission.status != 'ESCALATED' or user.role in 'ADMIN,SUPERUSER' %} +