Merge branch 'main' into dependabot/pip/whitenoise-6.9.0

This commit is contained in:
pacnpal
2025-02-10 17:19:54 -05:00
committed by GitHub
8 changed files with 49 additions and 58 deletions

View File

@@ -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 }}

View File

@@ -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 }}

1
README.md Normal file
View File

@@ -0,0 +1 @@
ThrillWiki.com

View File

@@ -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

View File

@@ -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

View File

@@ -99,7 +99,7 @@
<i class="fas fa-map-marker-alt"></i>
<span>Parks</span>
</a>
<a href="{% url 'rides:ride_list' %}" class="nav-link">
<a href="{% url 'rides:global_ride_list' %}" class="nav-link">
<i class="fas fa-rocket"></i>
<span>Rides</span>
</a>

View File

@@ -18,7 +18,7 @@
class="px-8 py-3 text-lg btn-primary">
Explore Parks
</a>
<a href="{% url 'rides:ride_list' %}"
<a href="{% url 'rides:global_ride_list' %}"
class="px-8 py-3 text-lg btn-secondary">
View Rides
</a>
@@ -40,7 +40,7 @@
</a>
<!-- Total Attractions -->
<a href="{% url 'rides:ride_list' %}"
<a href="{% url 'rides:global_ride_list' %}"
class="flex flex-col items-center justify-center p-6 text-center transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1 hover:shadow-xl">
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
{{ stats.ride_count }}
@@ -51,7 +51,7 @@
</a>
<!-- Total Roller Coasters -->
<a href="{% url 'rides:roller_coasters' %}"
<a href="{% url 'rides:global_roller_coasters' %}"
class="flex flex-col items-center justify-center p-6 text-center transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1 hover:shadow-xl">
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
{{ stats.coaster_count }}
@@ -108,7 +108,7 @@
</h2>
<div class="space-y-4">
{% for ride in popular_rides %}
<a href="{% url 'rides:ride_detail' ride.slug %}"
<a href="{% url 'parks:rides:ride_detail' ride.park.slug ride.slug %}"
class="relative block h-48 overflow-hidden transition-all rounded-lg group hover:-translate-y-1 hover:shadow-xl"
{% if ride.photos.first %}
style="background: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.7)), url('{{ ride.photos.first.image.url }}') center/cover no-repeat;"
@@ -147,7 +147,7 @@
{% for item in highest_rated %}
{% if item.park %}
<!-- This is a ride -->
<a href="{% url 'rides:ride_detail' item.slug %}"
<a href="{% url 'parks:rides:ride_detail' item.park.slug item.slug %}"
class="relative block h-48 overflow-hidden transition-all rounded-lg group hover:-translate-y-1 hover:shadow-xl"
{% if item.photos.first %}
style="background: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.7)), url('{{ item.photos.first.image.url }}') center/cover no-repeat;"

View File

@@ -32,12 +32,7 @@
Add Ride
</a>
{% else %}
<a href="{% url 'rides:ride_create' %}" class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
</svg>
Add Ride
</a>
<!-- No add ride button in global view - rides must be added from park pages -->
{% endif %}
{% endif %}
</div>
@@ -45,7 +40,7 @@
<!-- Filters -->
<div class="p-4 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
<form class="grid grid-cols-1 gap-4 md:grid-cols-3"
hx-get="{% if park %}{% url 'parks:rides:ride_list' park.slug %}{% else %}{% url 'rides:ride_list' %}{% endif %}"
hx-get="{% if park %}{% url 'parks:rides:ride_list' park.slug %}{% else %}{% url 'rides:global_ride_list' %}{% endif %}"
hx-trigger="change from:select, input from:input[type='text']"
hx-target="#rides-grid"
hx-push-url="true">
@@ -90,6 +85,9 @@
{% for ride in rides %}
<div class="overflow-hidden transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1">
<div class="aspect-w-16 aspect-h-9">
<a href="{% url 'parks:rides:ride_detail' ride.park.slug ride.slug %}"
class="text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400">
{{ ride.name }}
{% if ride.photos.exists %}
<img src="{{ ride.photos.first.image.url }}"
alt="{{ ride.name }}"
@@ -99,6 +97,7 @@
alt="{{ ride.name }}"
class="object-cover w-full">
{% endif %}
</a>
</div>
<div class="p-4">
<h2 class="mb-2 text-xl font-bold">