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 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 class RideCreateView(LoginRequiredMixin, CreateView): model = Ride form_class = RideForm template_name = 'rides/ride_form.html' def setup(self, request, *args, **kwargs): super().setup(request, *args, **kwargs) self.park = get_object_or_404(Park, slug=self.kwargs['park_slug']) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['park'] = self.park return kwargs def form_valid(self, form): form.instance.park = self.park cleaned_data = form.cleaned_data.copy() cleaned_data['park'] = self.park.id # Convert model instances to IDs for JSON serialization if cleaned_data.get('park_area'): cleaned_data['park_area'] = cleaned_data['park_area'].id if cleaned_data.get('manufacturer'): cleaned_data['manufacturer'] = cleaned_data['manufacturer'].id # Create submission record submission = EditSubmission.objects.create( user=self.request.user, content_type=ContentType.objects.get_for_model(Ride), submission_type='CREATE', changes=cleaned_data, reason=self.request.POST.get('reason', ''), source=self.request.POST.get('source', '') ) # If user is moderator or above, auto-approve if self.request.user.role in ['MODERATOR', 'ADMIN', 'SUPERUSER']: try: self.object = form.save() submission.object_id = self.object.id submission.status = 'APPROVED' submission.handled_by = self.request.user submission.save() # Handle photo uploads photos = self.request.FILES.getlist('photos') uploaded_count = 0 for photo_file in photos: try: Photo.objects.create( image=photo_file, uploaded_by=self.request.user, content_type=ContentType.objects.get_for_model(Ride), object_id=self.object.id ) uploaded_count += 1 except Exception as e: messages.error(self.request, f"Error uploading photo {photo_file.name}: {str(e)}") 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()) except Exception as e: messages.error( self.request, f"Error creating ride: {str(e)}. Please check your input and try again." ) return 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_invalid(self, form): messages.error( self.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(self.request, f"{field}: {error}") return super().form_invalid(form) def get_success_url(self): return reverse('parks:rides:ride_detail', kwargs={ 'park_slug': self.park.slug, 'ride_slug': self.object.slug }) def get_context_data(self, **kwargs): 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, *args, **kwargs): super().setup(request, *args, **kwargs) self.park = get_object_or_404(Park, slug=self.kwargs['park_slug']) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['park'] = self.park return kwargs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['park'] = self.park context['is_edit'] = True return context def form_valid(self, form): cleaned_data = form.cleaned_data.copy() cleaned_data['park'] = self.park.id # Convert model instances to IDs for JSON serialization if cleaned_data.get('park_area'): cleaned_data['park_area'] = cleaned_data['park_area'].id if cleaned_data.get('manufacturer'): cleaned_data['manufacturer'] = cleaned_data['manufacturer'].id # Create submission record submission = EditSubmission.objects.create( user=self.request.user, content_type=ContentType.objects.get_for_model(Ride), object_id=self.object.id, submission_type='EDIT', changes=cleaned_data, reason=self.request.POST.get('reason', ''), source=self.request.POST.get('source', '') ) # If user is moderator or above, auto-approve if self.request.user.role in ['MODERATOR', 'ADMIN', 'SUPERUSER']: try: self.object = form.save() submission.status = 'APPROVED' submission.handled_by = self.request.user submission.save() # Handle photo uploads photos = self.request.FILES.getlist('photos') uploaded_count = 0 for photo_file in photos: try: Photo.objects.create( image=photo_file, uploaded_by=self.request.user, content_type=ContentType.objects.get_for_model(Ride), object_id=self.object.id ) uploaded_count += 1 except Exception as e: messages.error(self.request, f"Error uploading photo {photo_file.name}: {str(e)}") messages.success( self.request, f"Successfully updated {self.object.name}. " f"Added {uploaded_count} new photo(s)." ) return HttpResponseRedirect(self.get_success_url()) except Exception as e: messages.error( self.request, f"Error updating ride: {str(e)}. Please check your input and try again." ) return 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_invalid(self, form): messages.error( self.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(self.request, f"{field}: {error}") return super().form_invalid(form) def get_success_url(self): 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') # Try to get by current or historical slug obj, is_old_slug = self.model.get_by_slug(ride_slug) if obj.park.slug != park_slug: raise self.model.DoesNotExist("Park slug doesn't match") return obj def get_context_data(self, **kwargs): 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): return 'parks:rides:ride_detail' def get_redirect_url_kwargs(self): 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, *args, **kwargs): 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') # Filter by park if viewing park-specific rides 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): context = super().get_context_data(**kwargs) context['park'] = self.park # Get manufacturers for the filter dropdown 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') ) # Add current filter values to context 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, *args, **kwargs): # Check if this is an HTMX request if request.htmx: # If it is, return just the rides list partial self.template_name = 'rides/partials/ride_list.html' return super().get(request, *args, **kwargs)