Refactor parks and rides views for improved organization and readability

- Updated imports in parks/views.py to use ParkReview as Review for clarity.
- Enhanced road trip views in parks/views_roadtrip.py by removing unnecessary parameters and improving context handling.
- Streamlined error handling and response messages in CreateTripView and FindParksAlongRouteView.
- Improved code formatting and consistency across various methods in parks/views_roadtrip.py.
- Refactored rides/models.py to import Company from models for better clarity.
- Updated rides/views.py to import RideSearchForm from services for better organization.
- Added a comprehensive Django best practices analysis document to memory-bank/documentation.
This commit is contained in:
pacnpal
2025-08-16 12:58:19 -04:00
parent b5bae44cb8
commit 32736ae660
8 changed files with 588 additions and 251 deletions

View File

@@ -20,16 +20,18 @@ from .models import Park
from .services.roadtrip import RoadTripService
from core.services.map_service import unified_map_service
from core.services.data_structures import LocationType, MapFilters
JSON_DECODE_ERROR_MSG = 'Invalid JSON data'
PARKS_ALONG_ROUTE_HTML = 'parks/partials/parks_along_route.html'
class RoadTripViewMixin:
"""Mixin providing common functionality for road trip views."""
def __init__(self):
super().__init__()
self.roadtrip_service = RoadTripService()
def get_roadtrip_context(self, request: HttpRequest) -> Dict[str, Any]:
def get_roadtrip_context(self) -> Dict[str, Any]:
"""Get common context data for road trip views."""
return {
'roadtrip_api_urls': {
@@ -46,21 +48,21 @@ class RoadTripViewMixin:
class RoadTripPlannerView(RoadTripViewMixin, TemplateView):
"""
Main road trip planning interface.
URL: /roadtrip/
"""
template_name = 'parks/roadtrip_planner.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(self.get_roadtrip_context(self.request))
# Get popular parks for suggestions
popular_parks = Park.objects.filter(
status='OPERATING',
location__isnull=False
).select_related('location', 'operator').order_by('-ride_count')[:20]
context.update({
'page_title': 'Road Trip Planner',
'popular_parks': popular_parks,
@@ -68,9 +70,9 @@ class RoadTripPlannerView(RoadTripViewMixin, TemplateView):
'enable_route_optimization': True,
'show_distance_estimates': True,
})
return context
def _get_countries_with_parks(self) -> List[str]:
"""Get list of countries that have theme parks."""
countries = Park.objects.filter(
@@ -83,15 +85,15 @@ class RoadTripPlannerView(RoadTripViewMixin, TemplateView):
class CreateTripView(RoadTripViewMixin, View):
"""
Generate optimized road trip routes.
URL: /roadtrip/create/
"""
def post(self, request: HttpRequest) -> HttpResponse:
"""Create a new road trip with optimized routing."""
try:
data = json.loads(request.body)
# Parse park IDs
park_ids = data.get('park_ids', [])
if not park_ids or len(park_ids) < 2:
@@ -99,34 +101,34 @@ class CreateTripView(RoadTripViewMixin, View):
'status': 'error',
'message': 'At least 2 parks are required for a road trip'
}, status=400)
if len(park_ids) > 10:
return JsonResponse({
'status': 'error',
'message': 'Maximum 10 parks allowed per trip'
}, status=400)
# Get parks
parks = list(Park.objects.filter(
id__in=park_ids,
location__isnull=False
).select_related('location', 'operator'))
if len(parks) != len(park_ids):
return JsonResponse({
'status': 'error',
'message': 'Some parks could not be found or do not have location data'
}, status=400)
# Create optimized trip
trip = self.roadtrip_service.create_multi_park_trip(parks)
if not trip:
return JsonResponse({
'status': 'error',
'message': 'Could not create optimized route for the selected parks'
}, status=400)
# Convert trip to dict for JSON response
trip_data = {
'parks': [self._park_to_dict(park) for park in trip.parks],
@@ -136,24 +138,24 @@ class CreateTripView(RoadTripViewMixin, View):
'formatted_total_distance': trip.formatted_total_distance,
'formatted_total_duration': trip.formatted_total_duration,
}
return JsonResponse({
'status': 'success',
'data': trip_data,
'trip_url': reverse('parks:roadtrip_detail', kwargs={'trip_id': 'temp'})
})
except json.JSONDecodeError:
return JsonResponse({
'status': 'error',
'message': 'Invalid JSON data'
'message': JSON_DECODE_ERROR_MSG
}, status=400)
except Exception as e:
return JsonResponse({
'status': 'error',
'message': f'Failed to create trip: {str(e)}'
}, status=500)
def _park_to_dict(self, park: Park) -> Dict[str, Any]:
"""Convert park instance to dictionary."""
return {
@@ -166,7 +168,7 @@ class CreateTripView(RoadTripViewMixin, View):
'ride_count': getattr(park, 'ride_count', 0),
'url': reverse('parks:park_detail', kwargs={'slug': park.slug}),
}
def _leg_to_dict(self, leg) -> Dict[str, Any]:
"""Convert trip leg to dictionary."""
return {
@@ -183,49 +185,50 @@ class CreateTripView(RoadTripViewMixin, View):
class TripDetailView(RoadTripViewMixin, TemplateView):
"""
Show trip details and map.
URL: /roadtrip/<trip_id>/
"""
template_name = 'parks/trip_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(self.get_roadtrip_context(self.request))
# For now, this is a placeholder since we don't persist trips
# In a full implementation, you would retrieve the trip from database
trip_id = kwargs.get('trip_id')
context.update({
'page_title': f'Road Trip #{trip_id}',
'trip_id': trip_id,
'message': 'Trip details would be loaded here. Currently trips are not persisted.',
})
return context
class FindParksAlongRouteView(RoadTripViewMixin, View):
"""
HTMX endpoint for route-based park discovery.
URL: /roadtrip/htmx/parks-along-route/
"""
def post(self, request: HttpRequest) -> HttpResponse:
"""Find parks along a route between two points."""
try:
data = json.loads(request.body)
start_park_id = data.get('start_park_id')
end_park_id = data.get('end_park_id')
max_detour_km = min(100, max(10, float(data.get('max_detour_km', 50))))
max_detour_km = min(
100, max(10, float(data.get('max_detour_km', 50))))
if not start_park_id or not end_park_id:
return render(request, 'parks/partials/parks_along_route.html', {
return render(request, PARKS_ALONG_ROUTE_HTML, {
'error': 'Start and end parks are required'
})
# Get start and end parks
try:
start_park = Park.objects.select_related('location').get(
@@ -235,29 +238,29 @@ class FindParksAlongRouteView(RoadTripViewMixin, View):
id=end_park_id, location__isnull=False
)
except Park.DoesNotExist:
return render(request, 'parks/partials/parks_along_route.html', {
return render(request, PARKS_ALONG_ROUTE_HTML, {
'error': 'One or both parks could not be found'
})
# Find parks along route
parks_along_route = self.roadtrip_service.find_parks_along_route(
start_park, end_park, max_detour_km
)
return render(request, 'parks/partials/parks_along_route.html', {
return render(request, PARKS_ALONG_ROUTE_HTML, {
'parks': parks_along_route,
'start_park': start_park,
'end_park': end_park,
'max_detour_km': max_detour_km,
'count': len(parks_along_route)
})
except json.JSONDecodeError:
return render(request, 'parks/partials/parks_along_route.html', {
'error': 'Invalid request data'
return render(request, PARKS_ALONG_ROUTE_HTML, {
'error': JSON_DECODE_ERROR_MSG
})
except Exception as e:
return render(request, 'parks/partials/parks_along_route.html', {
return render(request, PARKS_ALONG_ROUTE_HTML, {
'error': str(e)
})
@@ -265,50 +268,50 @@ class FindParksAlongRouteView(RoadTripViewMixin, View):
class GeocodeAddressView(RoadTripViewMixin, View):
"""
HTMX endpoint for geocoding addresses.
URL: /roadtrip/htmx/geocode/
"""
def post(self, request: HttpRequest) -> HttpResponse:
"""Geocode an address and find nearby parks."""
try:
data = json.loads(request.body)
address = data.get('address', '').strip()
if not address:
return JsonResponse({
'status': 'error',
'message': 'Address is required'
}, status=400)
# Geocode the address
coordinates = self.roadtrip_service.geocode_address(address)
if not coordinates:
return JsonResponse({
'status': 'error',
'message': 'Could not geocode the provided address'
}, status=400)
# Find nearby parks
radius_km = min(200, max(10, float(data.get('radius_km', 100))))
# Use map service to find parks near coordinates
from core.services.data_structures import GeoBounds
# Create a bounding box around the coordinates
lat_delta = radius_km / 111.0 # Rough conversion: 1 degree ≈ 111km
lng_delta = radius_km / (111.0 * abs(coordinates.latitude / 90.0))
bounds = GeoBounds(
north=coordinates.latitude + lat_delta,
south=coordinates.latitude - lat_delta,
east=coordinates.longitude + lng_delta,
west=coordinates.longitude - lng_delta
)
filters = MapFilters(location_types={LocationType.PARK})
map_response = unified_map_service.get_locations_by_bounds(
north=bounds.north,
south=bounds.south,
@@ -316,7 +319,7 @@ class GeocodeAddressView(RoadTripViewMixin, View):
west=bounds.west,
location_types={LocationType.PARK}
)
return JsonResponse({
'status': 'success',
'data': {
@@ -329,11 +332,11 @@ class GeocodeAddressView(RoadTripViewMixin, View):
'radius_km': radius_km
}
})
except json.JSONDecodeError:
return JsonResponse({
'status': 'error',
'message': 'Invalid JSON data'
'message': JSON_DECODE_ERROR_MSG
}, status=400)
except Exception as e:
return JsonResponse({
@@ -345,24 +348,24 @@ class GeocodeAddressView(RoadTripViewMixin, View):
class ParkDistanceCalculatorView(RoadTripViewMixin, View):
"""
HTMX endpoint for calculating distances between parks.
URL: /roadtrip/htmx/distance/
"""
def post(self, request: HttpRequest) -> HttpResponse:
"""Calculate distance and duration between two parks."""
try:
data = json.loads(request.body)
park1_id = data.get('park1_id')
park2_id = data.get('park2_id')
if not park1_id or not park2_id:
return JsonResponse({
'status': 'error',
'message': 'Both park IDs are required'
}, status=400)
# Get parks
try:
park1 = Park.objects.select_related('location').get(
@@ -376,30 +379,30 @@ class ParkDistanceCalculatorView(RoadTripViewMixin, View):
'status': 'error',
'message': 'One or both parks could not be found'
}, status=400)
# Calculate route
coords1 = park1.coordinates
coords2 = park2.coordinates
if not coords1 or not coords2:
return JsonResponse({
'status': 'error',
'message': 'One or both parks do not have coordinate data'
}, status=400)
from ..services.roadtrip import Coordinates
from services.roadtrip import Coordinates
route = self.roadtrip_service.calculate_route(
Coordinates(*coords1),
Coordinates(*coords2)
)
if not route:
return JsonResponse({
'status': 'error',
'message': 'Could not calculate route between parks'
}, status=400)
return JsonResponse({
'status': 'success',
'data': {
@@ -417,14 +420,14 @@ class ParkDistanceCalculatorView(RoadTripViewMixin, View):
}
}
})
except json.JSONDecodeError:
return JsonResponse({
'status': 'error',
'message': 'Invalid JSON data'
'message': JSON_DECODE_ERROR_MSG
}, status=400)
except Exception as e:
return JsonResponse({
'status': 'error',
'message': str(e)
}, status=500)
}, status=500)