diff --git a/.gitignore b/.gitignore index 41c92e18..6bbc4c93 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ moderation/__pycache__ rides/__pycache__ ssh_tools.jsonc thrillwiki/__pycache__/settings.cpython-312.pyc +parks/__pycache__/views.cpython-312.pyc diff --git a/media/views.py b/media/views.py index 99a7ec9a..5d6c98f5 100644 --- a/media/views.py +++ b/media/views.py @@ -57,7 +57,7 @@ def upload_photo(request): # Get the object instance try: - obj = content_type.get_object_for_this_type(id=object_id) + obj = content_type.get_object_for_this_type(pk=object_id) except Exception as e: return JsonResponse( { @@ -82,17 +82,17 @@ def upload_photo(request): photo = Photo.objects.create( image=request.FILES["image"], content_type=content_type, - object_id=obj.id, + object_id=obj.pk, uploaded_by=request.user, # Add the user who uploaded the photo is_primary=not Photo.objects.filter( - content_type=content_type, object_id=obj.id + content_type=content_type, object_id=obj.pk ).exists(), is_approved=is_approved # Auto-approve if the user is a moderator, admin, or superuser ) return JsonResponse( { - "id": photo.id, + "id": photo.pk, "url": photo.image.url, "caption": photo.caption, "is_primary": photo.is_primary, @@ -113,7 +113,7 @@ def upload_photo(request): def set_primary_photo(request, photo_id): """Set a photo as primary""" try: - photo = get_object_or_404(Photo, id=photo_id) + photo = get_object_or_404(Photo, pk=photo_id) # Check if user has permission to edit photos if not request.user.has_perm("media.change_photo"): @@ -137,7 +137,7 @@ def set_primary_photo(request, photo_id): def update_caption(request, photo_id): """Update a photo's caption""" try: - photo = get_object_or_404(Photo, id=photo_id) + photo = get_object_or_404(Photo, pk=photo_id) # Check if user has permission to edit photos if not request.user.has_perm("media.change_photo"): @@ -150,7 +150,7 @@ def update_caption(request, photo_id): photo.caption = data.get("caption", "") photo.save() - return JsonResponse({"id": photo.id, "caption": photo.caption}) + return JsonResponse({"id": photo.pk, "caption": photo.caption}) except Exception as e: logger.error(f"Error in update_caption: {str(e)}", exc_info=True) @@ -162,7 +162,7 @@ def update_caption(request, photo_id): def delete_photo(request, photo_id): """Delete a photo""" try: - photo = get_object_or_404(Photo, id=photo_id) + photo = get_object_or_404(Photo, pk=photo_id) # Check if user has permission to delete photos if not request.user.has_perm("media.delete_photo"): diff --git a/parks/__pycache__/views.cpython-312.pyc b/parks/__pycache__/views.cpython-312.pyc index c24ba5f6..519db473 100644 Binary files a/parks/__pycache__/views.cpython-312.pyc and b/parks/__pycache__/views.cpython-312.pyc differ diff --git a/parks/forms.py b/parks/forms.py index 869fb3b0..4e627025 100644 --- a/parks/forms.py +++ b/parks/forms.py @@ -1,6 +1,7 @@ from django import forms from decimal import Decimal, InvalidOperation, ROUND_DOWN from .models import Park +from location.models import Location class ParkForm(forms.ModelForm): @@ -207,8 +208,14 @@ class ParkForm(forms.ModelForm): 'postal_code': self.cleaned_data.get('postal_code'), } - # Set location data to be saved with the park - park.set_location(**location_data) + # Handle location: update if exists, create if not + if park.location.exists(): + location = park.location.first() + for key, value in location_data.items(): + setattr(location, key, value) + location.save() + else: + Location.objects.create(content_object=park, **location_data) if commit: park.save() diff --git a/parks/views.py b/parks/views.py index 987cedda..81fae91f 100644 --- a/parks/views.py +++ b/parks/views.py @@ -17,6 +17,7 @@ from moderation.mixins import EditSubmissionMixin, PhotoSubmissionMixin, History from moderation.models import EditSubmission from media.models import Photo from location.models import Location +from reviews.models import Review # Import the Review model def location_search(request): @@ -145,7 +146,7 @@ class ParkListView(ListView): def get(self, request, *args, **kwargs): # Check if this is an HTMX request - if request.htmx: + if hasattr(request, 'htmx') and getattr(request, 'htmx', False): # If it is, return just the parks list partial self.template_name = "parks/partials/park_list.html" return super().get(request, *args, **kwargs) @@ -167,7 +168,7 @@ class ParkDetailView( queryset = self.get_queryset() slug = self.kwargs.get(self.slug_url_kwarg) # Try to get by current or historical slug - return self.model.get_by_slug(slug)[0] + return Park.get_by_slug(slug)[0] def get_queryset(self): return super().get_queryset().prefetch_related( @@ -186,6 +187,17 @@ class ParkDetailView( '-status', # OPERATING will come before others 'name' ) + + # Check if the user has reviewed the park + if self.request.user.is_authenticated: + context["has_reviewed"] = Review.objects.filter( + user=self.request.user, + content_type=ContentType.objects.get_for_model(Park), + object_id=self.object.id + ).exists() + else: + context["has_reviewed"] = False + return context def get_redirect_url_pattern(self): @@ -214,8 +226,7 @@ class ParkCreateView(LoginRequiredMixin, CreateView): data[field] = str(data[field]) return data - def form_valid(self, form): - # Normalize coordinates before saving + def normalize_coordinates(self, form): if form.cleaned_data.get("latitude"): lat = Decimal(str(form.cleaned_data["latitude"])) form.cleaned_data["latitude"] = lat.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) @@ -223,6 +234,10 @@ class ParkCreateView(LoginRequiredMixin, CreateView): lon = Decimal(str(form.cleaned_data["longitude"])) form.cleaned_data["longitude"] = lon.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) + def form_valid(self, form): + # Normalize coordinates before saving + self.normalize_coordinates(form) + changes = self.prepare_changes_data(form.cleaned_data) # Create submission record @@ -236,7 +251,7 @@ class ParkCreateView(LoginRequiredMixin, CreateView): ) # If user is moderator or above, auto-approve - if self.request.user.role in ["MODERATOR", "ADMIN", "SUPERUSER"]: + if hasattr(self.request.user, 'role') and getattr(self.request.user, 'role', None) in ["MODERATOR", "ADMIN", "SUPERUSER"]: try: self.object = form.save() submission.object_id = self.object.id @@ -337,8 +352,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView): data[field] = str(data[field]) return data - def form_valid(self, form): - # Normalize coordinates before saving + def normalize_coordinates(self, form): if form.cleaned_data.get("latitude"): lat = Decimal(str(form.cleaned_data["latitude"])) form.cleaned_data["latitude"] = lat.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) @@ -346,6 +360,10 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView): lon = Decimal(str(form.cleaned_data["longitude"])) form.cleaned_data["longitude"] = lon.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) + def form_valid(self, form): + # Normalize coordinates before saving + self.normalize_coordinates(form) + changes = self.prepare_changes_data(form.cleaned_data) # Create submission record @@ -360,7 +378,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView): ) # If user is moderator or above, auto-approve - if self.request.user.role in ["MODERATOR", "ADMIN", "SUPERUSER"]: + if hasattr(self.request.user, 'role') and getattr(self.request.user, 'role', None) in ["MODERATOR", "ADMIN", "SUPERUSER"]: try: self.object = form.save() submission.status = "APPROVED" @@ -464,7 +482,7 @@ class ParkAreaDetailView( park_slug = self.kwargs.get("park_slug") area_slug = self.kwargs.get("area_slug") # Try to get by current or historical slug - obj, is_old_slug = self.model.get_by_slug(area_slug) + obj, is_old_slug = ParkArea.get_by_slug(area_slug) if obj.park.slug != park_slug: raise self.model.DoesNotExist("Park slug doesn't match") return obj diff --git a/static/css/tailwind.css b/static/css/tailwind.css index e886bbe3..38403b80 100644 --- a/static/css/tailwind.css +++ b/static/css/tailwind.css @@ -2948,6 +2948,10 @@ select { padding: 0.125rem; } +.p-1 { + padding: 0.25rem; +} + .p-1\.5 { padding: 0.375rem; } @@ -3912,6 +3916,11 @@ select { line-height: 1.5rem; } + .sm\:text-lg { + font-size: 1.125rem; + line-height: 1.75rem; + } + .sm\:text-sm { font-size: 0.875rem; line-height: 1.25rem; @@ -3926,11 +3935,6 @@ select { font-size: 0.75rem; line-height: 1rem; } - - .sm\:text-lg { - font-size: 1.125rem; - line-height: 1.75rem; - } } @media (min-width: 768px) { @@ -3972,11 +3976,6 @@ select { line-height: 1; } - .md\:text-xl { - font-size: 1.25rem; - line-height: 1.75rem; - } - .md\:text-lg { font-size: 1.125rem; line-height: 1.75rem; diff --git a/templates/parks/park_detail.html b/templates/parks/park_detail.html index 5510e04c..bac10ddd 100644 --- a/templates/parks/park_detail.html +++ b/templates/parks/park_detail.html @@ -33,22 +33,42 @@ Upload Photo {% endif %} + + + {% if not park.reviews.exists %} + + Add Review + + {% else %} + {% if user.has_reviewed_park(park) %} + + Edit Review + + {% else %} + + Add Review + + {% endif %} + {% endif %} {% endif %} -
+
-
+

{{ park.name }}

{% if park.formatted_location %} -
+

{{ park.formatted_location }}

- {% endif %} -
- + + {{ park.average_rating|floatformat:1 }}/10 @@ -65,31 +85,31 @@
-
+
-
+
+ class="flex flex-col items-center justify-center p-3 text-center transition-transform bg-white rounded-lg shadow-lg hover:scale-[1.02] dark:bg-gray-800">
Total Rides
-
+
{{ park.total_rides|default:"N/A" }}
-
+
Roller Coasters
-
+
{{ park.total_roller_coasters|default:"N/A" }}
-
+
{% if park.owner %} -
+
Owner
@@ -102,7 +122,7 @@ {% endif %} {% if park.opening_date %} -
+
Opened
{{ park.opening_date }}
@@ -110,7 +130,7 @@ {% endif %} {% if park.website %} -
+
Website