mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 12:11:13 -05:00
Merge branch 'main' into dependabot/pip/whitenoise-6.9.0
This commit is contained in:
2
.github/workflows/django.yml
vendored
2
.github/workflows/django.yml
vendored
@@ -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 }}
|
||||
|
||||
|
||||
2
.github/workflows/review.yml
vendored
2
.github/workflows/review.yml
vendored
@@ -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,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
|
||||
|
||||
@@ -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,17 +76,14 @@ 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})
|
||||
|
||||
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
|
||||
return kwargs
|
||||
@@ -87,7 +91,6 @@ class RideCreateView(LoginRequiredMixin, CreateView):
|
||||
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['is_edit'] = False
|
||||
@@ -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,37 +143,25 @@ 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})
|
||||
|
||||
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
|
||||
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['is_edit'] = True
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;"
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user