diff --git a/parks/views.py b/parks/views.py index 1e5f2eb3..c0c686fa 100644 --- a/parks/views.py +++ b/parks/views.py @@ -2,8 +2,11 @@ import requests from decimal import Decimal, ROUND_DOWN from typing import Any, Optional, cast, Literal -# Constants for error messages +# Constants +PARK_DETAIL_URL = "parks:park_detail" +PARK_LIST_ITEM_TEMPLATE = "parks/partials/park_list_item.html" REQUIRED_FIELDS_ERROR = "Please correct the errors below. Required fields are marked with an asterisk (*)." +ALLOWED_ROLES = ["MODERATOR", "ADMIN", "SUPERUSER"] from django.views.generic import DetailView, ListView, CreateView, UpdateView from decimal import InvalidOperation @@ -284,7 +287,7 @@ def search_parks(request: HttpRequest) -> HttpResponse: response = render( request, - "parks/partials/park_list_item.html", + PARK_LIST_ITEM_TEMPLATE, { "parks": parks, "view_mode": current_view_mode, @@ -298,7 +301,7 @@ def search_parks(request: HttpRequest) -> HttpResponse: except Exception as e: response = render( request, - "parks/partials/park_list_item.html", + PARK_LIST_ITEM_TEMPLATE, { "parks": [], "error": f"Error performing search: {str(e)}", @@ -354,7 +357,7 @@ class ParkCreateView(LoginRequiredMixin, CreateView): if hasattr(self.request.user, "role") and getattr( self.request.user, "role", None - ) in ["MODERATOR", "ADMIN", "SUPERUSER"]: + ) in ALLOWED_ROLES: try: self.object = form.save() submission.object_id = self.object.id @@ -420,7 +423,7 @@ class ParkCreateView(LoginRequiredMixin, CreateView): return super().form_invalid(form) def get_success_url(self) -> str: - return reverse("parks:park_detail", kwargs={"slug": self.object.slug}) + return reverse(PARK_DETAIL_URL, kwargs={"slug": self.object.slug}) class ParkUpdateView(LoginRequiredMixin, UpdateView): @@ -475,7 +478,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView): if hasattr(self.request.user, "role") and getattr( self.request.user, "role", None - ) in ["MODERATOR", "ADMIN", "SUPERUSER"]: + ) in ALLOWED_ROLES: try: self.object = form.save() submission.status = "APPROVED" @@ -542,7 +545,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView): "You will be notified when they are approved.", ) return HttpResponseRedirect( - reverse("parks:park_detail", kwargs={"slug": self.object.slug}) + reverse(PARK_DETAIL_URL, kwargs={"slug": self.object.slug}) ) def form_invalid(self, form: ParkForm) -> HttpResponse: @@ -556,7 +559,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView): return super().form_invalid(form) def get_success_url(self) -> str: - return reverse("parks:park_detail", kwargs={"slug": self.object.slug}) + return reverse(PARK_DETAIL_URL, kwargs={"slug": self.object.slug}) class ParkDetailView( @@ -611,7 +614,7 @@ class ParkDetailView( return context def get_redirect_url_pattern(self) -> str: - return "parks:park_detail" + return PARK_DETAIL_URL class ParkAreaDetailView( @@ -643,274 +646,10 @@ class ParkAreaDetailView( return context def get_redirect_url_pattern(self) -> str: - return "parks:park_detail" + return PARK_DETAIL_URL def get_redirect_url_kwargs(self) -> dict[str, str]: area = cast(ParkArea, self.object) return {"park_slug": area.park.slug, "area_slug": area.slug} - def form_valid(self, form: ParkForm) -> HttpResponse: - self.normalize_coordinates(form) - changes = self.prepare_changes_data(form.cleaned_data) - submission = EditSubmission.objects.create( - user=self.request.user, - content_type=ContentType.objects.get_for_model(Park), - submission_type="CREATE", - changes=changes, - reason=self.request.POST.get("reason", ""), - source=self.request.POST.get("source", ""), - ) - - 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 - submission.status = "APPROVED" - submission.handled_by = self.request.user - submission.save() - - if form.cleaned_data.get("latitude") and form.cleaned_data.get( - "longitude" - ): - Location.objects.create( - content_type=ContentType.objects.get_for_model(Park), - object_id=self.object.id, - name=self.object.name, - location_type="park", - latitude=form.cleaned_data["latitude"], - longitude=form.cleaned_data["longitude"], - street_address=form.cleaned_data.get( - "street_address", ""), - city=form.cleaned_data.get("city", ""), - state=form.cleaned_data.get("state", ""), - country=form.cleaned_data.get("country", ""), - postal_code=form.cleaned_data.get("postal_code", ""), - ) - - photos = self.request.FILES.getlist("photos") - uploaded_count = 0 - for photo_file in photos: - try: - Photo.objects.create( - image=photo_file, - uploaded_by=self.request.user, - content_type=ContentType.objects.get_for_model( - Park), - object_id=self.object.id, - ) - uploaded_count += 1 - except Exception as e: - messages.error( - self.request, - f"Error uploading photo {photo_file.name}: {str(e)}", - ) - - messages.success( - self.request, - f"Successfully created {self.object.name}. " - f"Added {uploaded_count} photo(s).", - ) - return HttpResponseRedirect(self.get_success_url()) - except Exception as e: - messages.error( - self.request, - f"Error creating park: {str(e)}. Please check your input and try again.", - ) - return self.form_invalid(form) - - messages.success( - self.request, - "Your park submission has been sent for review. " - "You will be notified when it is approved.", - ) - return HttpResponseRedirect(reverse("parks:park_list")) - - def form_invalid(self, form: ParkForm) -> HttpResponse: - messages.error( - self.request, - REQUIRED_FIELDS_ERROR, - ) - for field, errors in form.errors.items(): - for error in errors: - messages.error(self.request, f"{field}: {error}") - return super().form_invalid(form) - - def get_success_url(self) -> str: - return reverse("parks:park_detail", kwargs={"slug": self.object.slug}) - - -class ParkUpdateView(LoginRequiredMixin, UpdateView): - model = Park - form_class = ParkForm - template_name = "parks/park_form.html" - - def get_context_data(self, **kwargs: Any) -> dict[str, Any]: - context = super().get_context_data(**kwargs) - context["is_edit"] = True - return context - - def prepare_changes_data(self, cleaned_data: dict[str, Any]) -> dict[str, Any]: - data = cleaned_data.copy() - if data.get("owner"): - data["owner"] = data["owner"].id - if data.get("opening_date"): - data["opening_date"] = data["opening_date"].isoformat() - if data.get("closing_date"): - data["closing_date"] = data["closing_date"].isoformat() - decimal_fields = ["latitude", "longitude", - "size_acres", "average_rating"] - for field in decimal_fields: - if data.get(field): - data[field] = str(data[field]) - return data - - def normalize_coordinates(self, form: ParkForm) -> None: - 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 - ) - if form.cleaned_data.get("longitude"): - lon = Decimal(str(form.cleaned_data["longitude"])) - form.cleaned_data["longitude"] = lon.quantize( - Decimal("0.000001"), rounding=ROUND_DOWN - ) - - def form_valid(self, form: ParkForm) -> HttpResponse: - self.normalize_coordinates(form) - changes = self.prepare_changes_data(form.cleaned_data) - - submission = EditSubmission.objects.create( - user=self.request.user, - content_type=ContentType.objects.get_for_model(Park), - object_id=self.object.id, - submission_type="EDIT", - changes=changes, - reason=self.request.POST.get("reason", ""), - source=self.request.POST.get("source", ""), - ) - - 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" - submission.handled_by = self.request.user - submission.save() - - location_data = { - "name": self.object.name, - "location_type": "park", - "latitude": form.cleaned_data.get("latitude"), - "longitude": form.cleaned_data.get("longitude"), - "street_address": form.cleaned_data.get("street_address", ""), - "city": form.cleaned_data.get("city", ""), - "state": form.cleaned_data.get("state", ""), - "country": form.cleaned_data.get("country", ""), - "postal_code": form.cleaned_data.get("postal_code", ""), - } - - if self.object.location.exists(): - location = self.object.location.first() - for key, value in location_data.items(): - setattr(location, key, value) - location.save() - else: - Location.objects.create( - content_type=ContentType.objects.get_for_model(Park), - object_id=self.object.id, - **location_data, - ) - - photos = self.request.FILES.getlist("photos") - uploaded_count = 0 - for photo_file in photos: - try: - Photo.objects.create( - image=photo_file, - uploaded_by=self.request.user, - content_type=ContentType.objects.get_for_model( - Park), - object_id=self.object.id, - ) - uploaded_count += 1 - except Exception as e: - messages.error( - self.request, - f"Error uploading photo {photo_file.name}: {str(e)}", - ) - - messages.success( - self.request, - f"Successfully updated {self.object.name}. " - f"Added {uploaded_count} new photo(s).", - ) - return HttpResponseRedirect(self.get_success_url()) - except Exception as e: - messages.error( - self.request, - f"Error updating park: {str(e)}. Please check your input and try again.", - ) - return self.form_invalid(form) - - messages.success( - self.request, - f"Your changes to {self.object.name} have been sent for review. " - "You will be notified when they are approved.", - ) - return HttpResponseRedirect( - reverse("parks:park_detail", kwargs={"slug": self.object.slug}) - ) - - def form_invalid(self, form: ParkForm) -> HttpResponse: - messages.error( - self.request, - REQUIRED_FIELDS_ERROR, - ) - for field, errors in form.errors.items(): - for error in errors: - messages.error(self.request, f"{field}: {error}") - return super().form_invalid(form) - - def get_success_url(self) -> str: - return reverse("parks:park_detail", kwargs={"slug": self.object.slug}) - - -class ParkAreaDetailView( - SlugRedirectMixin, - EditSubmissionMixin, - PhotoSubmissionMixin, - HistoryMixin, - DetailView, -): - model = ParkArea - template_name = "parks/area_detail.html" - context_object_name = "area" - slug_url_kwarg = "area_slug" - - def get_object(self, queryset: Optional[QuerySet[ParkArea]] = None) -> ParkArea: - if queryset is None: - queryset = self.get_queryset() - park_slug = self.kwargs.get("park_slug") - area_slug = self.kwargs.get("area_slug") - if park_slug is None or area_slug is None: - raise ObjectDoesNotExist("Missing slug") - area, _ = ParkArea.get_by_slug(area_slug) - if area.park.slug != park_slug: - raise ObjectDoesNotExist("Park slug doesn't match") - return area - - def get_context_data(self, **kwargs: Any) -> dict[str, Any]: - context = super().get_context_data(**kwargs) - return context - - def get_redirect_url_pattern(self) -> str: - return "parks:park_detail" - - def get_redirect_url_kwargs(self) -> dict[str, str]: - area = cast(ParkArea, self.object) - return {"park_slug": area.park.slug, "area_slug": area.slug}