Add secret management guide, client-side performance monitoring, and search accessibility enhancements

- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
This commit is contained in:
pacnpal
2025-12-23 16:41:42 -05:00
parent ae31e889d7
commit edcd8f2076
155 changed files with 22046 additions and 4645 deletions

View File

@@ -33,6 +33,11 @@ from django.views.decorators.http import require_POST
from django.template.loader import render_to_string
import json
import logging
from apps.core.logging import log_exception, log_business_event
logger = logging.getLogger(__name__)
# Constants
PARK_DETAIL_URL = "parks:park_detail"
@@ -285,6 +290,12 @@ class ParkListView(HTMXFilterableMixin, ListView):
self.filterset = self.filter_class(self.request.GET, queryset=queryset)
return self.filterset.qs
except Exception as e:
log_exception(
logger,
e,
context={"operation": "get_filtered_queryset", "filters": filter_params},
request=self.request,
)
messages.error(self.request, f"Error loading parks: {str(e)}")
queryset = self.model.objects.none()
self.filterset = self.filter_class(self.request.GET, queryset=queryset)
@@ -330,6 +341,15 @@ class ParkListView(HTMXFilterableMixin, ListView):
return context
except Exception as e:
log_exception(
logger,
e,
context={
"operation": "get_context_data",
"search_query": self.request.GET.get("search", ""),
},
request=self.request,
)
messages.error(self.request, f"Error applying filters: {str(e)}")
# Ensure filterset exists in error case
if not hasattr(self, "filterset"):
@@ -478,6 +498,16 @@ def search_parks(request: HttpRequest) -> HttpResponse:
return response
except Exception as e:
log_exception(
logger,
e,
context={
"operation": "search_parks",
"search_query": request.GET.get("search", ""),
"view_mode": request.GET.get("view_mode", "grid"),
},
request=request,
)
response = render(
request,
PARK_LIST_ITEM_TEMPLATE,
@@ -505,7 +535,13 @@ def htmx_saved_trips(request: HttpRequest) -> HttpResponse:
qs = Trip.objects.filter(owner=request.user).order_by("-created_at")
trips = list(qs[:10])
except Exception:
except Exception as e:
log_exception(
logger,
e,
context={"operation": "htmx_saved_trips"},
request=request,
)
trips = []
return render(request, SAVED_TRIPS_TEMPLATE, {"trips": trips})
@@ -514,7 +550,13 @@ def _get_session_trip(request: HttpRequest) -> list:
raw = request.session.get("trip_parks", [])
try:
return [int(x) for x in raw]
except Exception:
except Exception as e:
log_exception(
logger,
e,
context={"operation": "get_session_trip", "raw": raw},
request=request,
)
return []
@@ -527,11 +569,21 @@ def _save_session_trip(request: HttpRequest, trip_list: list) -> None:
def htmx_add_park_to_trip(request: HttpRequest) -> HttpResponse:
"""Add a park id to `request.session['trip_parks']` and return the full trip list partial."""
park_id = request.POST.get("park_id")
payload = None
if not park_id:
try:
payload = json.loads(request.body.decode("utf-8"))
park_id = payload.get("park_id")
except Exception:
except Exception as e:
log_exception(
logger,
e,
context={
"operation": "htmx_add_park_to_trip",
"payload": request.body.decode("utf-8", errors="replace")[:500],
},
request=request,
)
park_id = None
if not park_id:
@@ -539,7 +591,16 @@ def htmx_add_park_to_trip(request: HttpRequest) -> HttpResponse:
try:
pid = int(park_id)
except Exception:
except Exception as e:
log_exception(
logger,
e,
context={
"operation": "htmx_add_park_to_trip",
"park_id": park_id,
},
request=request,
)
return HttpResponse("", status=400)
trip = _get_session_trip(request)
@@ -565,11 +626,21 @@ def htmx_add_park_to_trip(request: HttpRequest) -> HttpResponse:
def htmx_remove_park_from_trip(request: HttpRequest) -> HttpResponse:
"""Remove a park id from `request.session['trip_parks']` and return the updated trip list partial."""
park_id = request.POST.get("park_id")
payload = None
if not park_id:
try:
payload = json.loads(request.body.decode("utf-8"))
park_id = payload.get("park_id")
except Exception:
except Exception as e:
log_exception(
logger,
e,
context={
"operation": "htmx_remove_park_from_trip",
"payload": request.body.decode("utf-8", errors="replace")[:500],
},
request=request,
)
park_id = None
if not park_id:
@@ -577,7 +648,16 @@ def htmx_remove_park_from_trip(request: HttpRequest) -> HttpResponse:
try:
pid = int(park_id)
except Exception:
except Exception as e:
log_exception(
logger,
e,
context={
"operation": "htmx_remove_park_from_trip",
"park_id": park_id,
},
request=request,
)
return HttpResponse("", status=400)
trip = _get_session_trip(request)
@@ -605,7 +685,16 @@ def htmx_reorder_parks(request: HttpRequest) -> HttpResponse:
try:
payload = json.loads(request.body.decode("utf-8"))
order = payload.get("order", [])
except Exception:
except Exception as e:
log_exception(
logger,
e,
context={
"operation": "htmx_reorder_parks",
"payload": request.body.decode("utf-8", errors="replace")[:500],
},
request=request,
)
order = request.POST.getlist("order[]")
# Normalize to ints
@@ -613,7 +702,16 @@ def htmx_reorder_parks(request: HttpRequest) -> HttpResponse:
for item in order:
try:
clean_order.append(int(item))
except Exception:
except Exception as e:
log_exception(
logger,
e,
context={
"operation": "htmx_reorder_parks",
"order_item": item,
},
request=request,
)
continue
_save_session_trip(request, clean_order)
@@ -676,7 +774,27 @@ def htmx_optimize_route(request: HttpRequest) -> HttpResponse:
total_miles += haversine_miles(
a["latitude"], a["longitude"], b["latitude"], b["longitude"]
)
except Exception:
except Exception as e:
log_exception(
logger,
e,
context={
"operation": "htmx_optimize_route",
"waypoint_index_a": i,
"waypoint_index_b": i + 1,
"waypoint_a": {
"id": a.get("id"),
"latitude": a.get("latitude"),
"longitude": a.get("longitude"),
},
"waypoint_b": {
"id": b.get("id"),
"latitude": b.get("latitude"),
"longitude": b.get("longitude"),
},
},
request=request,
)
continue
# Estimate drive time assuming average speed of 60 mph
@@ -812,6 +930,18 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
if service_result["status"] == "auto_approved":
self.object = service_result["park"]
log_business_event(
logger,
event_type="park_created",
message=f"Park created: {self.object.name} (auto-approved)",
context={
"park_id": self.object.id,
"park_name": self.object.name,
"status": "auto_approved",
"photo_count": service_result["uploaded_count"],
},
request=self.request,
)
messages.success(
self.request,
f"Successfully created {self.object.name}. "
@@ -820,6 +950,16 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
return HttpResponseRedirect(self.get_success_url())
elif service_result["status"] == "queued":
log_business_event(
logger,
event_type="park_created",
message="Park submission queued for moderation",
context={
"status": "queued",
"park_name": form.cleaned_data.get("name"),
},
request=self.request,
)
messages.success(
self.request,
"Your park submission has been sent for review. "
@@ -916,6 +1056,18 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView):
if service_result["status"] == "auto_approved":
self.object = service_result["park"]
log_business_event(
logger,
event_type="park_updated",
message=f"Park updated: {self.object.name} (auto-approved)",
context={
"park_id": self.object.id,
"park_name": self.object.name,
"status": "auto_approved",
"photo_count": service_result["uploaded_count"],
},
request=self.request,
)
messages.success(
self.request,
f"Successfully updated {self.object.name}. "
@@ -924,6 +1076,17 @@ class ParkUpdateView(LoginRequiredMixin, UpdateView):
return HttpResponseRedirect(self.get_success_url())
elif service_result["status"] == "queued":
log_business_event(
logger,
event_type="park_updated",
message=f"Park update queued for moderation: {self.object.name}",
context={
"park_id": self.object.id,
"park_name": self.object.name,
"status": "queued",
},
request=self.request,
)
messages.success(
self.request,
f"Your changes to {self.object.name} have been sent for review. "