diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 8cb4ef87..7679e101 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -27,7 +27,7 @@ jobs: run: brew install gdal - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index c66940a6..d9e96095 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 - name: Run Claude Review - uses: pacnpal/claude-code-review@v1.0.7 + uses: pacnpal/claude-code-review@main with: github-token: ${{ secrets.GITHUB_TOKEN }} anthropic-key: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/README.md b/README.md new file mode 100644 index 00000000..fdb9bfc9 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +ThrillWiki.com diff --git a/requirements.txt b/requirements.txt index 0b62f4f3..edc9d279 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ # Django and REST framework Django==5.1.6 djangorestframework==3.15.2 -django-cors-headers==4.6.0 +django-cors-headers==4.7.0 # Authentication -django-allauth==65.4.0 +django-allauth==65.4.1 django-oauth-toolkit==3.0.1 dj-rest-auth==7.0.1 # Added for REST authentication endpoints pyjwt==2.10.1 @@ -12,7 +12,7 @@ pyjwt==2.10.1 # Database psycopg2-binary==2.9.10 dj-database-url==2.3.0 -django-pghistory==2.9.0 # For model history tracking +django-pghistory==3.5.2 # For model history tracking # Email requests==2.32.3 # For ForwardEmail.net API @@ -26,7 +26,7 @@ Pillow==11.1.0 # For image handling django-cleanup==9.0.0 # Automatically delete files piexif==1.1.3 # For image EXIF metadata handling django-filter==24.3 -django-htmx==1.21.0 +django-htmx==1.22.0 whitenoise==6.9.0 # Static file serving pycountry==24.6.1 @@ -34,7 +34,7 @@ pycountry==24.6.1 black==25.1.0 flake8==7.1.1 pytest==8.3.4 -pytest-django==4.9.0 +pytest-django==4.10.0 # WebSocket Support channels==4.2.0 @@ -42,4 +42,4 @@ channels-redis==4.2.1 daphne==4.1.2 # React and Material UI will be handled via npm in the frontend directory -django-tailwind-cli==2.21.1 +django-tailwind-cli==4.0.1 diff --git a/rides/views.py b/rides/views.py index 7461da93..1eeb7126 100644 --- a/rides/views.py +++ b/rides/views.py @@ -7,7 +7,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.decorators import login_required from django.contrib.contenttypes.models import ContentType from django.contrib import messages -from django.http import HttpRequest, HttpResponse +from django.http import HttpRequest, HttpResponse, Http404 from django.db.models import Count from .models import ( Ride, RollerCoasterStats, RideModel, RideEvent, @@ -21,6 +21,13 @@ from moderation.models import EditSubmission from companies.models import Manufacturer from designers.models import Designer +class ParkContextRequired: + """Mixin to require park context for views""" + def dispatch(self, request, *args, **kwargs): + if 'park_slug' not in self.kwargs: + raise Http404("Park context is required") + return super().dispatch(request, *args, **kwargs) + def show_coaster_fields(request: HttpRequest) -> HttpResponse: """Show roller coaster specific fields based on category selection""" category = request.GET.get('category') @@ -61,7 +68,7 @@ class RideDetailView(HistoryMixin, DetailView): return context -class RideCreateView(LoginRequiredMixin, CreateView): +class RideCreateView(LoginRequiredMixin, ParkContextRequired, CreateView): """View for creating a new ride""" model = Ride form_class = RideForm @@ -69,27 +76,23 @@ class RideCreateView(LoginRequiredMixin, CreateView): def get_success_url(self): """Get URL to redirect to after successful creation""" - if hasattr(self, 'park'): - return reverse('parks:rides:ride_detail', kwargs={ - 'park_slug': self.park.slug, - 'ride_slug': self.object.slug - }) - return reverse('rides:ride_detail', kwargs={'ride_slug': self.object.slug}) + return reverse('parks:rides:ride_detail', kwargs={ + 'park_slug': self.park.slug, + 'ride_slug': self.object.slug + }) def get_form_kwargs(self): """Pass park to the form""" kwargs = super().get_form_kwargs() - if 'park_slug' in self.kwargs: - self.park = get_object_or_404(Park, slug=self.kwargs['park_slug']) - kwargs['park'] = self.park + self.park = get_object_or_404(Park, slug=self.kwargs['park_slug']) + kwargs['park'] = self.park return kwargs def get_context_data(self, **kwargs): """Add park and park_slug to context""" context = super().get_context_data(**kwargs) - if hasattr(self, 'park'): - context['park'] = self.park - context['park_slug'] = self.park.slug + context['park'] = self.park + context['park_slug'] = self.park.slug context['is_edit'] = False return context @@ -131,7 +134,7 @@ class RideCreateView(LoginRequiredMixin, CreateView): return super().form_valid(form) -class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView): +class RideUpdateView(LoginRequiredMixin, ParkContextRequired, EditSubmissionMixin, UpdateView): """View for updating an existing ride""" model = Ride form_class = RideForm @@ -140,39 +143,27 @@ class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView): def get_success_url(self): """Get URL to redirect to after successful update""" - if hasattr(self, 'park'): - return reverse('parks:rides:ride_detail', kwargs={ - 'park_slug': self.park.slug, - 'ride_slug': self.object.slug - }) - return reverse('rides:ride_detail', kwargs={'ride_slug': self.object.slug}) + return reverse('parks:rides:ride_detail', kwargs={ + 'park_slug': self.park.slug, + 'ride_slug': self.object.slug + }) def get_queryset(self): - """Get ride for the specific park if park_slug is provided""" - queryset = Ride.objects.all() - if 'park_slug' in self.kwargs: - queryset = queryset.filter(park__slug=self.kwargs['park_slug']) - return queryset + """Get ride for the specific park""" + return Ride.objects.filter(park__slug=self.kwargs['park_slug']) def get_form_kwargs(self): """Pass park to the form""" kwargs = super().get_form_kwargs() - # For park-specific URLs, use the park from the URL - if 'park_slug' in self.kwargs: - self.park = get_object_or_404(Park, slug=self.kwargs['park_slug']) - kwargs['park'] = self.park - # For global URLs, use the ride's park - else: - self.park = self.get_object().park - kwargs['park'] = self.park + self.park = get_object_or_404(Park, slug=self.kwargs['park_slug']) + kwargs['park'] = self.park return kwargs def get_context_data(self, **kwargs): """Add park and park_slug to context""" context = super().get_context_data(**kwargs) - if hasattr(self, 'park'): - context['park'] = self.park - context['park_slug'] = self.park.slug + context['park'] = self.park + context['park_slug'] = self.park.slug context['is_edit'] = True return context diff --git a/templates/base/base.html b/templates/base/base.html index 90173632..7343ddfc 100644 --- a/templates/base/base.html +++ b/templates/base/base.html @@ -99,7 +99,7 @@ Parks - + Rides diff --git a/templates/home.html b/templates/home.html index de15bfa7..fb4a3a69 100644 --- a/templates/home.html +++ b/templates/home.html @@ -18,7 +18,7 @@ class="px-8 py-3 text-lg btn-primary"> Explore Parks - View Rides @@ -40,7 +40,7 @@ -
{{ stats.ride_count }} @@ -51,7 +51,7 @@ -
{{ stats.coaster_count }} @@ -108,7 +108,7 @@ @@ -45,7 +40,7 @@
@@ -90,6 +85,9 @@ {% for ride in rides %}