mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 15:31:08 -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
|
run: brew install gdal
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
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
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Run Claude Review
|
- name: Run Claude Review
|
||||||
uses: pacnpal/claude-code-review@v1.0.7
|
uses: pacnpal/claude-code-review@main
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
anthropic-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Django and REST framework
|
# Django and REST framework
|
||||||
Django==5.1.6
|
Django==5.1.6
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.15.2
|
||||||
django-cors-headers==4.6.0
|
django-cors-headers==4.7.0
|
||||||
|
|
||||||
# Authentication
|
# Authentication
|
||||||
django-allauth==65.4.0
|
django-allauth==65.4.1
|
||||||
django-oauth-toolkit==3.0.1
|
django-oauth-toolkit==3.0.1
|
||||||
dj-rest-auth==7.0.1 # Added for REST authentication endpoints
|
dj-rest-auth==7.0.1 # Added for REST authentication endpoints
|
||||||
pyjwt==2.10.1
|
pyjwt==2.10.1
|
||||||
@@ -12,7 +12,7 @@ pyjwt==2.10.1
|
|||||||
# Database
|
# Database
|
||||||
psycopg2-binary==2.9.10
|
psycopg2-binary==2.9.10
|
||||||
dj-database-url==2.3.0
|
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
|
# Email
|
||||||
requests==2.32.3 # For ForwardEmail.net API
|
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
|
django-cleanup==9.0.0 # Automatically delete files
|
||||||
piexif==1.1.3 # For image EXIF metadata handling
|
piexif==1.1.3 # For image EXIF metadata handling
|
||||||
django-filter==24.3
|
django-filter==24.3
|
||||||
django-htmx==1.21.0
|
django-htmx==1.22.0
|
||||||
whitenoise==6.9.0 # Static file serving
|
whitenoise==6.9.0 # Static file serving
|
||||||
pycountry==24.6.1
|
pycountry==24.6.1
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ pycountry==24.6.1
|
|||||||
black==25.1.0
|
black==25.1.0
|
||||||
flake8==7.1.1
|
flake8==7.1.1
|
||||||
pytest==8.3.4
|
pytest==8.3.4
|
||||||
pytest-django==4.9.0
|
pytest-django==4.10.0
|
||||||
|
|
||||||
# WebSocket Support
|
# WebSocket Support
|
||||||
channels==4.2.0
|
channels==4.2.0
|
||||||
@@ -42,4 +42,4 @@ channels-redis==4.2.1
|
|||||||
daphne==4.1.2
|
daphne==4.1.2
|
||||||
|
|
||||||
# React and Material UI will be handled via npm in the frontend directory
|
# 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.auth.decorators import login_required
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib import messages
|
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 django.db.models import Count
|
||||||
from .models import (
|
from .models import (
|
||||||
Ride, RollerCoasterStats, RideModel, RideEvent,
|
Ride, RollerCoasterStats, RideModel, RideEvent,
|
||||||
@@ -21,6 +21,13 @@ from moderation.models import EditSubmission
|
|||||||
from companies.models import Manufacturer
|
from companies.models import Manufacturer
|
||||||
from designers.models import Designer
|
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:
|
def show_coaster_fields(request: HttpRequest) -> HttpResponse:
|
||||||
"""Show roller coaster specific fields based on category selection"""
|
"""Show roller coaster specific fields based on category selection"""
|
||||||
category = request.GET.get('category')
|
category = request.GET.get('category')
|
||||||
@@ -61,7 +68,7 @@ class RideDetailView(HistoryMixin, DetailView):
|
|||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class RideCreateView(LoginRequiredMixin, CreateView):
|
class RideCreateView(LoginRequiredMixin, ParkContextRequired, CreateView):
|
||||||
"""View for creating a new ride"""
|
"""View for creating a new ride"""
|
||||||
model = Ride
|
model = Ride
|
||||||
form_class = RideForm
|
form_class = RideForm
|
||||||
@@ -69,17 +76,14 @@ class RideCreateView(LoginRequiredMixin, CreateView):
|
|||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Get URL to redirect to after successful creation"""
|
"""Get URL to redirect to after successful creation"""
|
||||||
if hasattr(self, 'park'):
|
|
||||||
return reverse('parks:rides:ride_detail', kwargs={
|
return reverse('parks:rides:ride_detail', kwargs={
|
||||||
'park_slug': self.park.slug,
|
'park_slug': self.park.slug,
|
||||||
'ride_slug': self.object.slug
|
'ride_slug': self.object.slug
|
||||||
})
|
})
|
||||||
return reverse('rides:ride_detail', kwargs={'ride_slug': self.object.slug})
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
"""Pass park to the form"""
|
"""Pass park to the form"""
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
if 'park_slug' in self.kwargs:
|
|
||||||
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
|
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
|
||||||
kwargs['park'] = self.park
|
kwargs['park'] = self.park
|
||||||
return kwargs
|
return kwargs
|
||||||
@@ -87,7 +91,6 @@ class RideCreateView(LoginRequiredMixin, CreateView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""Add park and park_slug to context"""
|
"""Add park and park_slug to context"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if hasattr(self, 'park'):
|
|
||||||
context['park'] = self.park
|
context['park'] = self.park
|
||||||
context['park_slug'] = self.park.slug
|
context['park_slug'] = self.park.slug
|
||||||
context['is_edit'] = False
|
context['is_edit'] = False
|
||||||
@@ -131,7 +134,7 @@ class RideCreateView(LoginRequiredMixin, CreateView):
|
|||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView):
|
class RideUpdateView(LoginRequiredMixin, ParkContextRequired, EditSubmissionMixin, UpdateView):
|
||||||
"""View for updating an existing ride"""
|
"""View for updating an existing ride"""
|
||||||
model = Ride
|
model = Ride
|
||||||
form_class = RideForm
|
form_class = RideForm
|
||||||
@@ -140,37 +143,25 @@ class RideUpdateView(LoginRequiredMixin, EditSubmissionMixin, UpdateView):
|
|||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Get URL to redirect to after successful update"""
|
"""Get URL to redirect to after successful update"""
|
||||||
if hasattr(self, 'park'):
|
|
||||||
return reverse('parks:rides:ride_detail', kwargs={
|
return reverse('parks:rides:ride_detail', kwargs={
|
||||||
'park_slug': self.park.slug,
|
'park_slug': self.park.slug,
|
||||||
'ride_slug': self.object.slug
|
'ride_slug': self.object.slug
|
||||||
})
|
})
|
||||||
return reverse('rides:ride_detail', kwargs={'ride_slug': self.object.slug})
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Get ride for the specific park if park_slug is provided"""
|
"""Get ride for the specific park"""
|
||||||
queryset = Ride.objects.all()
|
return Ride.objects.filter(park__slug=self.kwargs['park_slug'])
|
||||||
if 'park_slug' in self.kwargs:
|
|
||||||
queryset = queryset.filter(park__slug=self.kwargs['park_slug'])
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
"""Pass park to the form"""
|
"""Pass park to the form"""
|
||||||
kwargs = super().get_form_kwargs()
|
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'])
|
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
|
||||||
kwargs['park'] = self.park
|
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
|
return kwargs
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""Add park and park_slug to context"""
|
"""Add park and park_slug to context"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if hasattr(self, 'park'):
|
|
||||||
context['park'] = self.park
|
context['park'] = self.park
|
||||||
context['park_slug'] = self.park.slug
|
context['park_slug'] = self.park.slug
|
||||||
context['is_edit'] = True
|
context['is_edit'] = True
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
<i class="fas fa-map-marker-alt"></i>
|
<i class="fas fa-map-marker-alt"></i>
|
||||||
<span>Parks</span>
|
<span>Parks</span>
|
||||||
</a>
|
</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>
|
<i class="fas fa-rocket"></i>
|
||||||
<span>Rides</span>
|
<span>Rides</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
class="px-8 py-3 text-lg btn-primary">
|
class="px-8 py-3 text-lg btn-primary">
|
||||||
Explore Parks
|
Explore Parks
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'rides:ride_list' %}"
|
<a href="{% url 'rides:global_ride_list' %}"
|
||||||
class="px-8 py-3 text-lg btn-secondary">
|
class="px-8 py-3 text-lg btn-secondary">
|
||||||
View Rides
|
View Rides
|
||||||
</a>
|
</a>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Total Attractions -->
|
<!-- 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">
|
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">
|
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
|
||||||
{{ stats.ride_count }}
|
{{ stats.ride_count }}
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Total Roller Coasters -->
|
<!-- 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">
|
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">
|
<div class="mb-2 text-4xl font-bold text-blue-600 dark:text-blue-400">
|
||||||
{{ stats.coaster_count }}
|
{{ stats.coaster_count }}
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{% for ride in popular_rides %}
|
{% 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"
|
class="relative block h-48 overflow-hidden transition-all rounded-lg group hover:-translate-y-1 hover:shadow-xl"
|
||||||
{% if ride.photos.first %}
|
{% 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;"
|
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 %}
|
{% for item in highest_rated %}
|
||||||
{% if item.park %}
|
{% if item.park %}
|
||||||
<!-- This is a ride -->
|
<!-- 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"
|
class="relative block h-48 overflow-hidden transition-all rounded-lg group hover:-translate-y-1 hover:shadow-xl"
|
||||||
{% if item.photos.first %}
|
{% 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;"
|
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
|
Add Ride
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% 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">
|
<!-- No add ride button in global view - rides must be added from park pages -->
|
||||||
<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>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +40,7 @@
|
|||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div class="p-4 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
<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"
|
<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-trigger="change from:select, input from:input[type='text']"
|
||||||
hx-target="#rides-grid"
|
hx-target="#rides-grid"
|
||||||
hx-push-url="true">
|
hx-push-url="true">
|
||||||
@@ -90,6 +85,9 @@
|
|||||||
{% for ride in rides %}
|
{% 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="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">
|
<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 %}
|
{% if ride.photos.exists %}
|
||||||
<img src="{{ ride.photos.first.image.url }}"
|
<img src="{{ ride.photos.first.image.url }}"
|
||||||
alt="{{ ride.name }}"
|
alt="{{ ride.name }}"
|
||||||
@@ -99,6 +97,7 @@
|
|||||||
alt="{{ ride.name }}"
|
alt="{{ ride.name }}"
|
||||||
class="object-cover w-full">
|
class="object-cover w-full">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h2 class="mb-2 text-xl font-bold">
|
<h2 class="mb-2 text-xl font-bold">
|
||||||
|
|||||||
Reference in New Issue
Block a user