mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 15:11:09 -05:00
- Added functions for checking user privileges, handling photo uploads, preparing form data, and managing form errors. - Created views for listing, creating, updating, and displaying rides, including category-specific views. - Integrated submission handling for ride changes and improved user feedback through messages. - Enhanced data handling with appropriate context and queryset management for better performance and usability.
506 lines
17 KiB
Python
506 lines
17 KiB
Python
from typing import Any, Dict, Optional, Tuple, Union, cast
|
|
from django.views.generic import DetailView, ListView, CreateView, UpdateView
|
|
from django.shortcuts import get_object_or_404
|
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
from django.urls import reverse
|
|
from django.db.models import Q
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.contrib import messages
|
|
from django.http import (
|
|
JsonResponse,
|
|
HttpResponseRedirect,
|
|
Http404,
|
|
HttpRequest,
|
|
HttpResponse,
|
|
)
|
|
from django.db.models import Count
|
|
from django.core.files.uploadedfile import UploadedFile
|
|
from django.forms import ModelForm
|
|
from .models import Ride, RollerCoasterStats
|
|
from .forms import RideForm
|
|
from parks.models import Park
|
|
from core.views import SlugRedirectMixin
|
|
from moderation.mixins import EditSubmissionMixin, PhotoSubmissionMixin, HistoryMixin
|
|
from moderation.models import EditSubmission
|
|
from media.models import Photo
|
|
from accounts.models import User
|
|
|
|
|
|
def is_privileged_user(user: Any) -> bool:
|
|
"""Check if the user has privileged access.
|
|
|
|
Args:
|
|
user: The user to check
|
|
|
|
Returns:
|
|
bool: True if user has privileged or higher privileges
|
|
"""
|
|
return isinstance(user, User) and user.role in ["MODERATOR", "ADMIN", "SUPERUSER"]
|
|
|
|
|
|
def handle_photo_uploads(request: HttpRequest, ride: Ride) -> int:
|
|
"""Handle photo uploads for a ride.
|
|
|
|
Args:
|
|
request: The HTTP request containing files
|
|
ride: The ride to attach photos to
|
|
|
|
Returns:
|
|
int: Number of successfully uploaded photos
|
|
"""
|
|
uploaded_count = 0
|
|
photos = request.FILES.getlist("photos")
|
|
for photo_file in photos:
|
|
try:
|
|
Photo.objects.create(
|
|
image=photo_file,
|
|
uploaded_by=request.user,
|
|
content_type=ContentType.objects.get_for_model(Ride),
|
|
object_id=ride.pk,
|
|
)
|
|
uploaded_count += 1
|
|
except Exception as e:
|
|
messages.error(request, f"Error uploading photo {photo_file.name}: {str(e)}")
|
|
return uploaded_count
|
|
|
|
|
|
def prepare_form_data(cleaned_data: Dict[str, Any], park: Park) -> Dict[str, Any]:
|
|
"""Prepare form data for submission.
|
|
|
|
Args:
|
|
cleaned_data: The form's cleaned data
|
|
park: The park instance
|
|
|
|
Returns:
|
|
Dict[str, Any]: Processed form data ready for submission
|
|
"""
|
|
data = cleaned_data.copy()
|
|
data["park"] = park.pk
|
|
if data.get("park_area"):
|
|
data["park_area"] = data["park_area"].pk
|
|
if data.get("manufacturer"):
|
|
data["manufacturer"] = data["manufacturer"].pk
|
|
return data
|
|
|
|
|
|
def handle_form_errors(request: HttpRequest, form: ModelForm) -> None:
|
|
"""Handle form validation errors by adding appropriate error messages.
|
|
|
|
Args:
|
|
request: The HTTP request
|
|
form: The form containing validation errors
|
|
"""
|
|
messages.error(
|
|
request,
|
|
"Please correct the errors below. Required fields are marked with an asterisk (*).",
|
|
)
|
|
for field, errors in form.errors.items():
|
|
for error in errors:
|
|
messages.error(request, f"{field}: {error}")
|
|
|
|
|
|
def create_edit_submission(
|
|
request: HttpRequest,
|
|
submission_type: str,
|
|
changes: Dict[str, Any],
|
|
object_id: Optional[int] = None,
|
|
) -> EditSubmission:
|
|
"""Create an EditSubmission object for ride changes.
|
|
|
|
Args:
|
|
request: The HTTP request
|
|
submission_type: Type of submission (CREATE or EDIT)
|
|
changes: The changes to be submitted
|
|
object_id: Optional ID of the existing object for edits
|
|
|
|
Returns:
|
|
EditSubmission: The created submission object
|
|
"""
|
|
submission_data = {
|
|
"user": request.user,
|
|
"content_type": ContentType.objects.get_for_model(Ride),
|
|
"submission_type": submission_type,
|
|
"changes": changes,
|
|
"reason": request.POST.get("reason", ""),
|
|
"source": request.POST.get("source", ""),
|
|
}
|
|
|
|
if object_id is not None:
|
|
submission_data["object_id"] = object_id
|
|
|
|
return EditSubmission.objects.create(**submission_data)
|
|
|
|
|
|
def handle_privileged_save(
|
|
request: HttpRequest, form: RideForm, submission: EditSubmission
|
|
) -> Tuple[bool, str]:
|
|
"""Handle saving form and updating submission for privileged users.
|
|
|
|
Args:
|
|
request: The HTTP request
|
|
form: The form to save
|
|
submission: The edit submission to update
|
|
|
|
Returns:
|
|
Tuple[bool, str]: Success status and error message (empty string if successful)
|
|
"""
|
|
try:
|
|
ride = form.save()
|
|
if submission.submission_type == "CREATE":
|
|
submission.object_id = ride.pk
|
|
submission.status = "APPROVED"
|
|
submission.handled_by = request.user
|
|
submission.save()
|
|
return True, ""
|
|
except Exception as e:
|
|
error_msg = (
|
|
f"Error {submission.submission_type.lower()}ing ride: {str(e)}. "
|
|
"Please check your input and try again."
|
|
)
|
|
return False, error_msg
|
|
|
|
|
|
class SingleCategoryListView(ListView):
|
|
model = Ride
|
|
template_name = "rides/ride_category_list.html"
|
|
context_object_name = "categories"
|
|
|
|
def get_category_code(self) -> str:
|
|
if category := self.kwargs.get("category"):
|
|
return category
|
|
raise Http404("Category not found")
|
|
|
|
def get_queryset(self):
|
|
category_code = self.get_category_code()
|
|
category_name = dict(Ride.CATEGORY_CHOICES)[category_code]
|
|
|
|
rides = (
|
|
Ride.objects.filter(category=category_code)
|
|
.select_related("park", "manufacturer")
|
|
.order_by("name")
|
|
)
|
|
|
|
return {category_name: rides} if rides.exists() else {}
|
|
|
|
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
|
context = super().get_context_data(**kwargs)
|
|
category_code = self.get_category_code()
|
|
category_name = dict(Ride.CATEGORY_CHOICES)[category_code]
|
|
context["title"] = f"All {category_name}s"
|
|
context["category_code"] = category_code
|
|
return context
|
|
|
|
|
|
class ParkSingleCategoryListView(ListView):
|
|
model = Ride
|
|
template_name = "rides/ride_category_list.html"
|
|
context_object_name = "categories"
|
|
|
|
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
|
|
super().setup(request, *args, **kwargs)
|
|
self.park = get_object_or_404(Park, slug=self.kwargs["park_slug"])
|
|
|
|
def get_category_code(self) -> str:
|
|
if category := self.kwargs.get("category"):
|
|
return category
|
|
raise Http404("Category not found")
|
|
|
|
def get_queryset(self):
|
|
category_code = self.get_category_code()
|
|
category_name = dict(Ride.CATEGORY_CHOICES)[category_code]
|
|
|
|
rides = (
|
|
Ride.objects.filter(park=self.park, category=category_code)
|
|
.select_related("manufacturer")
|
|
.order_by("name")
|
|
)
|
|
|
|
return {category_name: rides} if rides.exists() else {}
|
|
|
|
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
|
context = super().get_context_data(**kwargs)
|
|
context["park"] = self.park
|
|
category_code = self.get_category_code()
|
|
category_name = dict(Ride.CATEGORY_CHOICES)[category_code]
|
|
context["title"] = f"{category_name}s at {self.park.name}"
|
|
context["category_code"] = category_code
|
|
return context
|
|
|
|
|
|
class RideCreateView(LoginRequiredMixin, CreateView):
|
|
model = Ride
|
|
form_class = RideForm
|
|
template_name = "rides/ride_form.html"
|
|
|
|
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
|
|
super().setup(request, *args, **kwargs)
|
|
self.park = get_object_or_404(Park, slug=self.kwargs["park_slug"])
|
|
|
|
def get_form_kwargs(self) -> Dict[str, Any]:
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["park"] = self.park
|
|
return kwargs
|
|
|
|
def handle_submission(
|
|
self, form: RideForm, cleaned_data: Dict[str, Any]
|
|
) -> HttpResponseRedirect:
|
|
"""Handle the form submission.
|
|
|
|
Args:
|
|
form: The form to process
|
|
cleaned_data: The cleaned form data
|
|
|
|
Returns:
|
|
HttpResponseRedirect to appropriate URL
|
|
"""
|
|
submission = create_edit_submission(self.request, "CREATE", cleaned_data)
|
|
|
|
if is_privileged_user(self.request.user):
|
|
success, error_msg = handle_privileged_save(self.request, form, submission)
|
|
if success:
|
|
self.object = form.instance
|
|
uploaded_count = handle_photo_uploads(self.request, self.object)
|
|
messages.success(
|
|
self.request,
|
|
f"Successfully created {self.object.name} at {self.park.name}. "
|
|
f"Added {uploaded_count} photo(s).",
|
|
)
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
else:
|
|
if error_msg: # Only add error message if there is one
|
|
messages.error(self.request, error_msg)
|
|
return cast(HttpResponseRedirect, self.form_invalid(form))
|
|
|
|
messages.success(
|
|
self.request,
|
|
"Your ride submission has been sent for review. "
|
|
"You will be notified when it is approved.",
|
|
)
|
|
return HttpResponseRedirect(
|
|
reverse("parks:rides:ride_list", kwargs={"park_slug": self.park.slug})
|
|
)
|
|
|
|
def form_valid(self, form: RideForm) -> HttpResponseRedirect:
|
|
form.instance.park = self.park
|
|
cleaned_data = prepare_form_data(form.cleaned_data, self.park)
|
|
return self.handle_submission(form, cleaned_data)
|
|
|
|
def form_invalid(self, form: RideForm) -> Union[HttpResponse, HttpResponseRedirect]:
|
|
"""Handle invalid form submission.
|
|
|
|
Args:
|
|
form: The invalid form
|
|
|
|
Returns:
|
|
Response with error messages
|
|
"""
|
|
handle_form_errors(self.request, form)
|
|
return super().form_invalid(form)
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse(
|
|
"parks:rides:ride_detail",
|
|
kwargs={"park_slug": self.park.slug, "ride_slug": self.object.slug},
|
|
)
|
|
|
|
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
|
context = super().get_context_data(**kwargs)
|
|
context["park"] = self.park
|
|
return context
|
|
|
|
|
|
class RideUpdateView(LoginRequiredMixin, UpdateView):
|
|
model = Ride
|
|
form_class = RideForm
|
|
template_name = "rides/ride_form.html"
|
|
slug_url_kwarg = "ride_slug"
|
|
|
|
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
|
|
super().setup(request, *args, **kwargs)
|
|
self.park = get_object_or_404(Park, slug=self.kwargs["park_slug"])
|
|
|
|
def get_form_kwargs(self) -> Dict[str, Any]:
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["park"] = self.park
|
|
return kwargs
|
|
|
|
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
|
context = super().get_context_data(**kwargs)
|
|
context["park"] = self.park
|
|
context["is_edit"] = True
|
|
return context
|
|
|
|
def handle_submission(
|
|
self, form: RideForm, cleaned_data: Dict[str, Any]
|
|
) -> HttpResponseRedirect:
|
|
"""Handle the form submission.
|
|
|
|
Args:
|
|
form: The form to process
|
|
cleaned_data: The cleaned form data
|
|
|
|
Returns:
|
|
HttpResponseRedirect to appropriate URL
|
|
"""
|
|
submission = create_edit_submission(
|
|
self.request, "EDIT", cleaned_data, self.object.pk
|
|
)
|
|
|
|
if is_privileged_user(self.request.user):
|
|
success, error_msg = handle_privileged_save(self.request, form, submission)
|
|
if success:
|
|
self.object = form.instance
|
|
uploaded_count = handle_photo_uploads(self.request, self.object)
|
|
messages.success(
|
|
self.request,
|
|
f"Successfully updated {self.object.name}. "
|
|
f"Added {uploaded_count} new photo(s).",
|
|
)
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
else:
|
|
if error_msg: # Only add error message if there is one
|
|
messages.error(self.request, error_msg)
|
|
return cast(HttpResponseRedirect, self.form_invalid(form))
|
|
|
|
messages.success(
|
|
self.request,
|
|
f"Your changes to {self.object.name} have been sent for review. "
|
|
"You will be notified when they are approved.",
|
|
)
|
|
return HttpResponseRedirect(
|
|
reverse(
|
|
"parks:rides:ride_detail",
|
|
kwargs={"park_slug": self.park.slug, "ride_slug": self.object.slug},
|
|
)
|
|
)
|
|
|
|
def form_valid(self, form: RideForm) -> HttpResponseRedirect:
|
|
cleaned_data = prepare_form_data(form.cleaned_data, self.park)
|
|
return self.handle_submission(form, cleaned_data)
|
|
|
|
def form_invalid(self, form: RideForm) -> Union[HttpResponse, HttpResponseRedirect]:
|
|
"""Handle invalid form submission.
|
|
|
|
Args:
|
|
form: The invalid form
|
|
|
|
Returns:
|
|
Response with error messages
|
|
"""
|
|
handle_form_errors(self.request, form)
|
|
return super().form_invalid(form)
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse(
|
|
"parks:rides:ride_detail",
|
|
kwargs={"park_slug": self.park.slug, "ride_slug": self.object.slug},
|
|
)
|
|
|
|
|
|
class RideDetailView(
|
|
SlugRedirectMixin,
|
|
EditSubmissionMixin,
|
|
PhotoSubmissionMixin,
|
|
HistoryMixin,
|
|
DetailView,
|
|
):
|
|
model = Ride
|
|
template_name = "rides/ride_detail.html"
|
|
context_object_name = "ride"
|
|
slug_url_kwarg = "ride_slug"
|
|
|
|
def get_object(self, queryset=None):
|
|
if queryset is None:
|
|
queryset = self.get_queryset()
|
|
park_slug = self.kwargs.get("park_slug")
|
|
ride_slug = self.kwargs.get("ride_slug")
|
|
obj, is_old_slug = self.model.get_by_slug(ride_slug) # type: ignore[attr-defined]
|
|
if obj.park.slug != park_slug:
|
|
raise self.model.DoesNotExist("Park slug doesn't match")
|
|
return obj
|
|
|
|
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
|
context = super().get_context_data(**kwargs)
|
|
if self.object.category == "RC":
|
|
context["coaster_stats"] = RollerCoasterStats.objects.filter(
|
|
ride=self.object
|
|
).first()
|
|
return context
|
|
|
|
def get_redirect_url_pattern(self) -> str:
|
|
return "parks:rides:ride_detail"
|
|
|
|
def get_redirect_url_kwargs(self) -> Dict[str, Any]:
|
|
return {"park_slug": self.object.park.slug, "ride_slug": self.object.slug}
|
|
|
|
|
|
class RideListView(ListView):
|
|
model = Ride
|
|
template_name = "rides/ride_list.html"
|
|
context_object_name = "rides"
|
|
|
|
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
|
|
super().setup(request, *args, **kwargs)
|
|
self.park = None
|
|
if "park_slug" in self.kwargs:
|
|
self.park = get_object_or_404(Park, slug=self.kwargs["park_slug"])
|
|
|
|
def get_queryset(self):
|
|
queryset = Ride.objects.select_related(
|
|
"park", "coaster_stats", "manufacturer"
|
|
).prefetch_related("photos")
|
|
|
|
if self.park:
|
|
queryset = queryset.filter(park=self.park)
|
|
|
|
search = self.request.GET.get("search", "").strip() or None
|
|
category = self.request.GET.get("category", "").strip() or None
|
|
status = self.request.GET.get("status", "").strip() or None
|
|
manufacturer = self.request.GET.get("manufacturer", "").strip() or None
|
|
|
|
if search:
|
|
if self.park:
|
|
queryset = queryset.filter(name__icontains=search)
|
|
else:
|
|
queryset = queryset.filter(
|
|
Q(name__icontains=search) | Q(park__name__icontains=search)
|
|
)
|
|
if category:
|
|
queryset = queryset.filter(category=category)
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
if manufacturer:
|
|
queryset = queryset.exclude(manufacturer__isnull=True)
|
|
|
|
return queryset
|
|
|
|
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
|
context = super().get_context_data(**kwargs)
|
|
context["park"] = self.park
|
|
|
|
manufacturer_query = Ride.objects
|
|
if self.park:
|
|
manufacturer_query = manufacturer_query.filter(park=self.park)
|
|
|
|
context["manufacturers"] = list(
|
|
manufacturer_query.exclude(manufacturer__isnull=True)
|
|
.values_list("manufacturer__name", flat=True)
|
|
.distinct()
|
|
.order_by("manufacturer__name")
|
|
)
|
|
|
|
context["current_filters"] = {
|
|
"search": self.request.GET.get("search", ""),
|
|
"category": self.request.GET.get("category", ""),
|
|
"status": self.request.GET.get("status", ""),
|
|
"manufacturer": self.request.GET.get("manufacturer", ""),
|
|
}
|
|
|
|
return context
|
|
|
|
def get(self, request: HttpRequest, *args: Any, **kwargs: Any):
|
|
if getattr(request, "htmx", False): # type: ignore[attr-defined]
|
|
self.template_name = "rides/partials/ride_list.html"
|
|
return super().get(request, *args, **kwargs)
|