mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-01-02 01:47:04 -05:00
feat: Implement initial schema and add various API, service, and management command enhancements across the application.
This commit is contained in:
@@ -42,9 +42,7 @@ logger = logging.getLogger(__name__)
|
||||
# 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 (*)."
|
||||
)
|
||||
REQUIRED_FIELDS_ERROR = "Please correct the errors below. Required fields are marked with an asterisk (*)."
|
||||
TRIP_PARKS_TEMPLATE = "parks/partials/trip_parks_list.html"
|
||||
TRIP_SUMMARY_TEMPLATE = "parks/partials/trip_summary.html"
|
||||
SAVED_TRIPS_TEMPLATE = "parks/partials/saved_trips.html"
|
||||
@@ -87,18 +85,10 @@ def normalize_osm_result(result: dict) -> dict:
|
||||
neighborhood = address.get("neighbourhood", "")
|
||||
|
||||
# Build city from available components
|
||||
city = (
|
||||
address.get("city")
|
||||
or address.get("town")
|
||||
or address.get("village")
|
||||
or address.get("municipality")
|
||||
or ""
|
||||
)
|
||||
city = address.get("city") or address.get("town") or address.get("village") or address.get("municipality") or ""
|
||||
|
||||
# Get detailed state/region information
|
||||
state = (
|
||||
address.get("state") or address.get("province") or address.get("region") or ""
|
||||
)
|
||||
state = address.get("state") or address.get("province") or address.get("region") or ""
|
||||
|
||||
# Get postal code with fallbacks
|
||||
postal_code = address.get("postcode") or address.get("postal_code") or ""
|
||||
@@ -170,9 +160,7 @@ def get_park_areas(request: HttpRequest) -> HttpResponse:
|
||||
park = Park.objects.get(id=park_id)
|
||||
areas = park.areas.all()
|
||||
options = ['<option value="">No specific area</option>']
|
||||
options.extend(
|
||||
[f'<option value="{area.id}">{area.name}</option>' for area in areas]
|
||||
)
|
||||
options.extend([f'<option value="{area.id}">{area.name}</option>' for area in areas])
|
||||
return HttpResponse("\n".join(options))
|
||||
except Park.DoesNotExist:
|
||||
return HttpResponse('<option value="">Invalid park selected</option>')
|
||||
@@ -201,11 +189,7 @@ def location_search(request: HttpRequest) -> JsonResponse:
|
||||
if response.status_code == 200:
|
||||
results = response.json()
|
||||
normalized_results = [normalize_osm_result(result) for result in results]
|
||||
valid_results = [
|
||||
r
|
||||
for r in normalized_results
|
||||
if r["lat"] is not None and r["lon"] is not None
|
||||
]
|
||||
valid_results = [r for r in normalized_results if r["lat"] is not None and r["lon"] is not None]
|
||||
return JsonResponse({"results": valid_results})
|
||||
|
||||
return JsonResponse({"results": []})
|
||||
@@ -226,13 +210,9 @@ def reverse_geocode(request: HttpRequest) -> JsonResponse:
|
||||
lon = lon.quantize(Decimal("0.000001"), rounding=ROUND_DOWN)
|
||||
|
||||
if lat < -90 or lat > 90:
|
||||
return JsonResponse(
|
||||
{"error": "Latitude must be between -90 and 90"}, status=400
|
||||
)
|
||||
return JsonResponse({"error": "Latitude must be between -90 and 90"}, status=400)
|
||||
if lon < -180 or lon > 180:
|
||||
return JsonResponse(
|
||||
{"error": "Longitude must be between -180 and 180"}, status=400
|
||||
)
|
||||
return JsonResponse({"error": "Longitude must be between -180 and 180"}, status=400)
|
||||
|
||||
response = requests.get(
|
||||
"https://nominatim.openstreetmap.org/reverse",
|
||||
@@ -306,9 +286,7 @@ class ParkListView(HTMXFilterableMixin, ListView):
|
||||
try:
|
||||
# Initialize filterset if not exists
|
||||
if not hasattr(self, "filterset"):
|
||||
self.filterset = self.filter_class(
|
||||
self.request.GET, queryset=self.model.objects.none()
|
||||
)
|
||||
self.filterset = self.filter_class(self.request.GET, queryset=self.model.objects.none())
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
@@ -323,20 +301,14 @@ class ParkListView(HTMXFilterableMixin, ListView):
|
||||
"search_query": self.request.GET.get("search", ""),
|
||||
"filter_counts": filter_counts,
|
||||
"popular_filters": popular_filters,
|
||||
"total_results": (
|
||||
context.get("paginator").count
|
||||
if context.get("paginator")
|
||||
else 0
|
||||
),
|
||||
"total_results": (context.get("paginator").count if context.get("paginator") else 0),
|
||||
}
|
||||
)
|
||||
|
||||
# Add filter suggestions for search queries
|
||||
search_query = self.request.GET.get("search", "")
|
||||
if search_query:
|
||||
context["filter_suggestions"] = (
|
||||
self.filter_service.get_filter_suggestions(search_query)
|
||||
)
|
||||
context["filter_suggestions"] = self.filter_service.get_filter_suggestions(search_query)
|
||||
|
||||
return context
|
||||
|
||||
@@ -353,9 +325,7 @@ class ParkListView(HTMXFilterableMixin, ListView):
|
||||
messages.error(self.request, f"Error applying filters: {str(e)}")
|
||||
# Ensure filterset exists in error case
|
||||
if not hasattr(self, "filterset"):
|
||||
self.filterset = self.filter_class(
|
||||
self.request.GET, queryset=self.model.objects.none()
|
||||
)
|
||||
self.filterset = self.filter_class(self.request.GET, queryset=self.model.objects.none())
|
||||
return {
|
||||
"filter": self.filterset,
|
||||
"error": "Unable to apply filters. Please try adjusting your criteria.",
|
||||
@@ -427,9 +397,7 @@ class ParkListView(HTMXFilterableMixin, ListView):
|
||||
|
||||
return urlencode(url_params)
|
||||
|
||||
def _get_pagination_urls(
|
||||
self, page_obj, filter_params: dict[str, Any]
|
||||
) -> dict[str, str]:
|
||||
def _get_pagination_urls(self, page_obj, filter_params: dict[str, Any]) -> dict[str, str]:
|
||||
"""Generate pagination URLs that preserve filter state."""
|
||||
|
||||
base_query = self._build_filter_query_string(filter_params)
|
||||
@@ -476,9 +444,7 @@ def search_parks(request: HttpRequest) -> HttpResponse:
|
||||
|
||||
# Get current view mode from request
|
||||
current_view_mode = request.GET.get("view_mode", "grid")
|
||||
park_filter = ParkFilter(
|
||||
{"search": search_query}, queryset=get_base_park_queryset()
|
||||
)
|
||||
park_filter = ParkFilter({"search": search_query}, queryset=get_base_park_queryset())
|
||||
|
||||
parks = park_filter.qs
|
||||
if request.GET.get("quick_search"):
|
||||
@@ -747,10 +713,7 @@ def htmx_optimize_route(request: HttpRequest) -> HttpResponse:
|
||||
rlat1, rlon1, rlat2, rlon2 = map(math.radians, [lat1, lon1, lat2, lon2])
|
||||
dlat = rlat2 - rlat1
|
||||
dlon = rlon2 - rlon1
|
||||
a = (
|
||||
math.sin(dlat / 2) ** 2
|
||||
+ math.cos(rlat1) * math.cos(rlat2) * math.sin(dlon / 2) ** 2
|
||||
)
|
||||
a = math.sin(dlat / 2) ** 2 + math.cos(rlat1) * math.cos(rlat2) * math.sin(dlon / 2) ** 2
|
||||
c = 2 * math.asin(min(1, math.sqrt(a)))
|
||||
miles = 3958.8 * c
|
||||
return miles
|
||||
@@ -762,18 +725,14 @@ def htmx_optimize_route(request: HttpRequest) -> HttpResponse:
|
||||
lat = getattr(loc, "latitude", None) if loc else None
|
||||
lon = getattr(loc, "longitude", None) if loc else None
|
||||
if lat is not None and lon is not None:
|
||||
waypoints.append(
|
||||
{"id": p.id, "name": p.name, "latitude": lat, "longitude": lon}
|
||||
)
|
||||
waypoints.append({"id": p.id, "name": p.name, "latitude": lat, "longitude": lon})
|
||||
|
||||
# sum straight-line distances between consecutive waypoints
|
||||
for i in range(len(waypoints) - 1):
|
||||
a = waypoints[i]
|
||||
b = waypoints[i + 1]
|
||||
try:
|
||||
total_miles += haversine_miles(
|
||||
a["latitude"], a["longitude"], b["latitude"], b["longitude"]
|
||||
)
|
||||
total_miles += haversine_miles(a["latitude"], a["longitude"], b["latitude"], b["longitude"])
|
||||
except Exception as e:
|
||||
log_exception(
|
||||
logger,
|
||||
@@ -807,9 +766,7 @@ def htmx_optimize_route(request: HttpRequest) -> HttpResponse:
|
||||
"total_rides": sum(getattr(p, "ride_count", 0) or 0 for p in parks),
|
||||
}
|
||||
|
||||
html = render_to_string(
|
||||
TRIP_SUMMARY_TEMPLATE, {"summary": summary}, request=request
|
||||
)
|
||||
html = render_to_string(TRIP_SUMMARY_TEMPLATE, {"summary": summary}, request=request)
|
||||
resp = HttpResponse(html)
|
||||
# Include waypoints payload in HX-Trigger so client can render route on the map
|
||||
resp["HX-Trigger"] = json.dumps({"tripOptimized": {"parks": waypoints}})
|
||||
@@ -843,9 +800,7 @@ def htmx_save_trip(request: HttpRequest) -> HttpResponse:
|
||||
# attempt to associate parks if the Trip model supports it
|
||||
with contextlib.suppress(Exception):
|
||||
trip.parks.set([p.id for p in parks])
|
||||
trips = list(
|
||||
Trip.objects.filter(owner=request.user).order_by("-created_at")[:10]
|
||||
)
|
||||
trips = list(Trip.objects.filter(owner=request.user).order_by("-created_at")[:10])
|
||||
except Exception:
|
||||
trips = []
|
||||
|
||||
@@ -892,14 +847,10 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
form.cleaned_data["longitude"] = lon.quantize(Decimal("0.000001"), rounding=ROUND_DOWN)
|
||||
|
||||
def form_valid(self, form: ParkForm) -> HttpResponse:
|
||||
self.normalize_coordinates(form)
|
||||
@@ -942,8 +893,7 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
)
|
||||
messages.success(
|
||||
self.request,
|
||||
f"Successfully created {self.object.name}. "
|
||||
f"Added {service_result['uploaded_count']} photo(s).",
|
||||
f"Successfully created {self.object.name}. " f"Added {service_result['uploaded_count']} photo(s).",
|
||||
)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
@@ -960,8 +910,7 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
)
|
||||
messages.success(
|
||||
self.request,
|
||||
"Your park submission has been sent for review. "
|
||||
"You will be notified when it is approved.",
|
||||
"Your park submission has been sent for review. " "You will be notified when it is approved.",
|
||||
)
|
||||
return HttpResponseRedirect(reverse("parks:park_list"))
|
||||
|
||||
@@ -1016,14 +965,10 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView):
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
form.cleaned_data["longitude"] = lon.quantize(Decimal("0.000001"), rounding=ROUND_DOWN)
|
||||
|
||||
def form_valid(self, form: ParkForm) -> HttpResponse:
|
||||
self.normalize_coordinates(form)
|
||||
@@ -1068,8 +1013,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView):
|
||||
)
|
||||
messages.success(
|
||||
self.request,
|
||||
f"Successfully updated {self.object.name}. "
|
||||
f"Added {service_result['uploaded_count']} new photo(s).",
|
||||
f"Successfully updated {self.object.name}. " f"Added {service_result['uploaded_count']} new photo(s).",
|
||||
)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
@@ -1090,9 +1034,7 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView):
|
||||
f"Your changes to {self.object.name} have been sent for review. "
|
||||
"You will be notified when they are approved.",
|
||||
)
|
||||
return HttpResponseRedirect(
|
||||
reverse(PARK_DETAIL_URL, kwargs={"slug": self.object.slug})
|
||||
)
|
||||
return HttpResponseRedirect(reverse(PARK_DETAIL_URL, kwargs={"slug": self.object.slug}))
|
||||
|
||||
elif service_result["status"] == "failed":
|
||||
messages.error(
|
||||
@@ -1143,11 +1085,7 @@ class ParkDetailView(
|
||||
def get_queryset(self) -> QuerySet[Park]:
|
||||
return cast(
|
||||
QuerySet[Park],
|
||||
super()
|
||||
.get_queryset()
|
||||
.prefetch_related(
|
||||
"rides", "rides__manufacturer", "photos", "areas", "location"
|
||||
),
|
||||
super().get_queryset().prefetch_related("rides", "rides__manufacturer", "photos", "areas", "location"),
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user