Refactor test utilities and enhance ASGI settings

- Cleaned up and standardized assertions in ApiTestMixin for API response validation.
- Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE.
- Removed unused imports and improved formatting in settings.py.
- Refactored URL patterns in urls.py for better readability and organization.
- Enhanced view functions in views.py for consistency and clarity.
- Added .flake8 configuration for linting and style enforcement.
- Introduced type stubs for django-environ to improve type checking with Pylance.
This commit is contained in:
pacnpal
2025-08-20 19:51:59 -04:00
parent 69c07d1381
commit 66ed4347a9
230 changed files with 15094 additions and 11578 deletions

View File

@@ -1,4 +1,4 @@
from .roadtrip import RoadTripService
from .park_management import ParkService, LocationService
__all__ = ['RoadTripService', 'ParkService', 'LocationService']
__all__ = ["RoadTripService", "ParkService", "LocationService"]

View File

@@ -6,7 +6,6 @@ Following Django styleguide pattern for business logic encapsulation.
from typing import Optional, Dict, Any, TYPE_CHECKING
from django.db import transaction
from django.db.models import Q
from django.core.exceptions import ValidationError
if TYPE_CHECKING:
from django.contrib.auth.models import AbstractUser
@@ -32,11 +31,11 @@ class ParkService:
size_acres: Optional[float] = None,
website: str = "",
location_data: Optional[Dict[str, Any]] = None,
created_by: Optional['AbstractUser'] = None
created_by: Optional["AbstractUser"] = None,
) -> Park:
"""
Create a new park with validation and location handling.
Args:
name: Park name
description: Park description
@@ -50,10 +49,10 @@ class ParkService:
website: Park website URL
location_data: Dictionary containing location information
created_by: User creating the park
Returns:
Created Park instance
Raises:
ValidationError: If park data is invalid
"""
@@ -67,16 +66,18 @@ class ParkService:
closing_date=closing_date,
operating_season=operating_season,
size_acres=size_acres,
website=website
website=website,
)
# Set foreign key relationships if provided
if operator_id:
from parks.models import Company
park.operator = Company.objects.get(id=operator_id)
if property_owner_id:
from parks.models import Company
park.property_owner = Company.objects.get(id=property_owner_id)
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
@@ -85,10 +86,7 @@ class ParkService:
# Handle location if provided
if location_data:
LocationService.create_park_location(
park=park,
**location_data
)
LocationService.create_park_location(park=park, **location_data)
return park
@@ -97,19 +95,19 @@ class ParkService:
*,
park_id: int,
updates: Dict[str, Any],
updated_by: Optional['AbstractUser'] = None
updated_by: Optional["AbstractUser"] = None,
) -> Park:
"""
Update an existing park with validation.
Args:
park_id: ID of park to update
updates: Dictionary of field updates
updated_by: User performing the update
Returns:
Updated Park instance
Raises:
Park.DoesNotExist: If park doesn't exist
ValidationError: If update data is invalid
@@ -129,23 +127,25 @@ class ParkService:
return park
@staticmethod
def delete_park(*, park_id: int, deleted_by: Optional['AbstractUser'] = None) -> bool:
def delete_park(
*, park_id: int, deleted_by: Optional["AbstractUser"] = None
) -> bool:
"""
Soft delete a park by setting status to DEMOLISHED.
Args:
park_id: ID of park to delete
deleted_by: User performing the deletion
Returns:
True if successfully deleted
Raises:
Park.DoesNotExist: If park doesn't exist
"""
with transaction.atomic():
park = Park.objects.select_for_update().get(id=park_id)
park.status = 'DEMOLISHED'
park.status = "DEMOLISHED"
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
park.full_clean()
@@ -159,31 +159,27 @@ class ParkService:
park_id: int,
name: str,
description: str = "",
created_by: Optional['AbstractUser'] = None
created_by: Optional["AbstractUser"] = None,
) -> ParkArea:
"""
Create a new area within a park.
Args:
park_id: ID of the parent park
name: Area name
description: Area description
created_by: User creating the area
Returns:
Created ParkArea instance
Raises:
Park.DoesNotExist: If park doesn't exist
ValidationError: If area data is invalid
"""
park = Park.objects.get(id=park_id)
area = ParkArea(
park=park,
name=name,
description=description
)
area = ParkArea(park=park, name=name, description=description)
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
area.full_clean()
@@ -195,10 +191,10 @@ class ParkService:
def update_park_statistics(*, park_id: int) -> Park:
"""
Recalculate and update park statistics (ride counts, ratings).
Args:
park_id: ID of park to update statistics for
Returns:
Updated Park instance with fresh statistics
"""
@@ -211,19 +207,18 @@ class ParkService:
# Calculate ride counts
ride_stats = Ride.objects.filter(park=park).aggregate(
total_rides=Count('id'),
coaster_count=Count('id', filter=Q(category__in=['RC', 'WC']))
total_rides=Count("id"),
coaster_count=Count("id", filter=Q(category__in=["RC", "WC"])),
)
# Calculate average rating
avg_rating = ParkReview.objects.filter(
park=park,
is_published=True
).aggregate(avg_rating=Avg('rating'))['avg_rating']
park=park, is_published=True
).aggregate(avg_rating=Avg("rating"))["avg_rating"]
# Update park fields
park.ride_count = ride_stats['total_rides'] or 0
park.coaster_count = ride_stats['coaster_count'] or 0
park.ride_count = ride_stats["total_rides"] or 0
park.coaster_count = ride_stats["coaster_count"] or 0
park.average_rating = avg_rating
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
@@ -246,11 +241,11 @@ class LocationService:
city: str = "",
state: str = "",
country: str = "",
postal_code: str = ""
postal_code: str = "",
) -> Location:
"""
Create a location for a park.
Args:
park: Park instance
latitude: Latitude coordinate
@@ -260,24 +255,24 @@ class LocationService:
state: State/region name
country: Country name
postal_code: Postal/ZIP code
Returns:
Created Location instance
Raises:
ValidationError: If location data is invalid
"""
location = Location(
content_object=park,
name=park.name,
location_type='park',
location_type="park",
latitude=latitude,
longitude=longitude,
street_address=street_address,
city=city,
state=state,
country=country,
postal_code=postal_code
postal_code=postal_code,
)
# CRITICAL STYLEGUIDE FIX: Call full_clean before save
@@ -288,20 +283,18 @@ class LocationService:
@staticmethod
def update_park_location(
*,
park_id: int,
location_updates: Dict[str, Any]
*, park_id: int, location_updates: Dict[str, Any]
) -> Location:
"""
Update location information for a park.
Args:
park_id: ID of the park
location_updates: Dictionary of location field updates
Returns:
Updated Location instance
Raises:
Location.DoesNotExist: If location doesn't exist
ValidationError: If location data is invalid
@@ -314,8 +307,7 @@ class LocationService:
except Location.DoesNotExist:
# Create location if it doesn't exist
return LocationService.create_park_location(
park=park,
**location_updates
park=park, **location_updates
)
# Apply updates

View File

@@ -13,7 +13,7 @@ import time
import math
import logging
import requests
from typing import Dict, List, Tuple, Optional, Any, Union
from typing import Dict, List, Tuple, Optional, Any
from dataclasses import dataclass
from itertools import permutations
@@ -21,7 +21,6 @@ from django.conf import settings
from django.core.cache import cache
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import Distance
from django.db.models import Q
from parks.models import Park
logger = logging.getLogger(__name__)
@@ -30,6 +29,7 @@ logger = logging.getLogger(__name__)
@dataclass
class Coordinates:
"""Represents latitude and longitude coordinates."""
latitude: float
longitude: float
@@ -45,6 +45,7 @@ class Coordinates:
@dataclass
class RouteInfo:
"""Information about a calculated route."""
distance_km: float
duration_minutes: int
geometry: Optional[str] = None # Encoded polyline
@@ -72,12 +73,13 @@ class RouteInfo:
@dataclass
class TripLeg:
"""Represents one leg of a multi-park trip."""
from_park: 'Park'
to_park: 'Park'
from_park: "Park"
to_park: "Park"
route: RouteInfo
@property
def parks_along_route(self) -> List['Park']:
def parks_along_route(self) -> List["Park"]:
"""Get parks along this route segment."""
# This would be populated by find_parks_along_route
return []
@@ -86,7 +88,8 @@ class TripLeg:
@dataclass
class RoadTrip:
"""Complete road trip with multiple parks."""
parks: List['Park']
parks: List["Park"]
legs: List[TripLeg]
total_distance_km: float
total_duration_minutes: int
@@ -131,7 +134,6 @@ class RateLimiter:
class OSMAPIException(Exception):
"""Exception for OSM API related errors."""
pass
class RoadTripService:
@@ -144,27 +146,29 @@ class RoadTripService:
self.osrm_base_url = "http://router.project-osrm.org/route/v1/driving"
# Configuration from Django settings
self.cache_timeout = getattr(
settings, 'ROADTRIP_CACHE_TIMEOUT', 3600 * 24)
self.cache_timeout = getattr(settings, "ROADTRIP_CACHE_TIMEOUT", 3600 * 24)
self.route_cache_timeout = getattr(
settings, 'ROADTRIP_ROUTE_CACHE_TIMEOUT', 3600 * 6)
settings, "ROADTRIP_ROUTE_CACHE_TIMEOUT", 3600 * 6
)
self.user_agent = getattr(
settings, 'ROADTRIP_USER_AGENT', 'ThrillWiki Road Trip Planner')
self.request_timeout = getattr(
settings, 'ROADTRIP_REQUEST_TIMEOUT', 10)
self.max_retries = getattr(settings, 'ROADTRIP_MAX_RETRIES', 3)
self.backoff_factor = getattr(settings, 'ROADTRIP_BACKOFF_FACTOR', 2)
settings, "ROADTRIP_USER_AGENT", "ThrillWiki Road Trip Planner"
)
self.request_timeout = getattr(settings, "ROADTRIP_REQUEST_TIMEOUT", 10)
self.max_retries = getattr(settings, "ROADTRIP_MAX_RETRIES", 3)
self.backoff_factor = getattr(settings, "ROADTRIP_BACKOFF_FACTOR", 2)
# Rate limiter
max_rps = getattr(settings, 'ROADTRIP_MAX_REQUESTS_PER_SECOND', 1)
max_rps = getattr(settings, "ROADTRIP_MAX_REQUESTS_PER_SECOND", 1)
self.rate_limiter = RateLimiter(max_rps)
# Request session with proper headers
self.session = requests.Session()
self.session.headers.update({
'User-Agent': self.user_agent,
'Accept': 'application/json',
})
self.session.headers.update(
{
"User-Agent": self.user_agent,
"Accept": "application/json",
}
)
def _make_request(self, url: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""
@@ -175,9 +179,7 @@ class RoadTripService:
for attempt in range(self.max_retries):
try:
response = self.session.get(
url,
params=params,
timeout=self.request_timeout
url, params=params, timeout=self.request_timeout
)
response.raise_for_status()
return response.json()
@@ -186,11 +188,13 @@ class RoadTripService:
logger.warning(f"Request attempt {attempt + 1} failed: {e}")
if attempt < self.max_retries - 1:
wait_time = self.backoff_factor ** attempt
wait_time = self.backoff_factor**attempt
time.sleep(wait_time)
else:
raise OSMAPIException(
f"Failed to make request after {self.max_retries} attempts: {e}")
f"Failed to make request after {
self.max_retries} attempts: {e}"
)
def geocode_address(self, address: str) -> Optional[Coordinates]:
"""
@@ -213,10 +217,10 @@ class RoadTripService:
try:
params = {
'q': address.strip(),
'format': 'json',
'limit': 1,
'addressdetails': 1,
"q": address.strip(),
"format": "json",
"limit": 1,
"addressdetails": 1,
}
url = f"{self.nominatim_base_url}/search"
@@ -225,18 +229,25 @@ class RoadTripService:
if response and len(response) > 0:
result = response[0]
coords = Coordinates(
latitude=float(result['lat']),
longitude=float(result['lon'])
latitude=float(result["lat"]),
longitude=float(result["lon"]),
)
# Cache the result
cache.set(cache_key, {
'latitude': coords.latitude,
'longitude': coords.longitude
}, self.cache_timeout)
cache.set(
cache_key,
{
"latitude": coords.latitude,
"longitude": coords.longitude,
},
self.cache_timeout,
)
logger.info(
f"Geocoded '{address}' to {coords.latitude}, {coords.longitude}")
f"Geocoded '{address}' to {
coords.latitude}, {
coords.longitude}"
)
return coords
else:
logger.warning(f"No geocoding results for address: {address}")
@@ -246,7 +257,9 @@ class RoadTripService:
logger.error(f"Geocoding failed for '{address}': {e}")
return None
def calculate_route(self, start_coords: Coordinates, end_coords: Coordinates) -> Optional[RouteInfo]:
def calculate_route(
self, start_coords: Coordinates, end_coords: Coordinates
) -> Optional[RouteInfo]:
"""
Calculate route between two coordinate points using OSRM.
@@ -261,52 +274,68 @@ class RoadTripService:
return None
# Check cache first
cache_key = f"roadtrip:route:{start_coords.latitude},{start_coords.longitude}:{end_coords.latitude},{end_coords.longitude}"
cache_key = f"roadtrip:route:{
start_coords.latitude},{
start_coords.longitude}:{
end_coords.latitude},{
end_coords.longitude}"
cached_result = cache.get(cache_key)
if cached_result:
return RouteInfo(**cached_result)
try:
# Format coordinates for OSRM (lon,lat format)
coords_string = f"{start_coords.longitude},{start_coords.latitude};{end_coords.longitude},{end_coords.latitude}"
coords_string = f"{
start_coords.longitude},{
start_coords.latitude};{
end_coords.longitude},{
end_coords.latitude}"
url = f"{self.osrm_base_url}/{coords_string}"
params = {
'overview': 'full',
'geometries': 'polyline',
'steps': 'false',
"overview": "full",
"geometries": "polyline",
"steps": "false",
}
response = self._make_request(url, params)
if response.get('code') == 'Ok' and response.get('routes'):
route_data = response['routes'][0]
if response.get("code") == "Ok" and response.get("routes"):
route_data = response["routes"][0]
# Distance is in meters, convert to km
distance_km = route_data['distance'] / 1000.0
distance_km = route_data["distance"] / 1000.0
# Duration is in seconds, convert to minutes
duration_minutes = int(route_data['duration'] / 60)
duration_minutes = int(route_data["duration"] / 60)
route_info = RouteInfo(
distance_km=distance_km,
duration_minutes=duration_minutes,
geometry=route_data.get('geometry')
geometry=route_data.get("geometry"),
)
# Cache the result
cache.set(cache_key, {
'distance_km': route_info.distance_km,
'duration_minutes': route_info.duration_minutes,
'geometry': route_info.geometry
}, self.route_cache_timeout)
cache.set(
cache_key,
{
"distance_km": route_info.distance_km,
"duration_minutes": route_info.duration_minutes,
"geometry": route_info.geometry,
},
self.route_cache_timeout,
)
logger.info(
f"Route calculated: {route_info.formatted_distance}, {route_info.formatted_duration}")
f"Route calculated: {
route_info.formatted_distance}, {
route_info.formatted_duration}"
)
return route_info
else:
# Fallback to straight-line distance calculation
logger.warning(
f"OSRM routing failed, falling back to straight-line distance")
f"OSRM routing failed, falling back to straight-line distance"
)
return self._calculate_straight_line_route(start_coords, end_coords)
except Exception as e:
@@ -314,37 +343,46 @@ class RoadTripService:
# Fallback to straight-line distance
return self._calculate_straight_line_route(start_coords, end_coords)
def _calculate_straight_line_route(self, start_coords: Coordinates, end_coords: Coordinates) -> RouteInfo:
def _calculate_straight_line_route(
self, start_coords: Coordinates, end_coords: Coordinates
) -> RouteInfo:
"""
Calculate straight-line distance as fallback when routing fails.
"""
# Haversine formula for great-circle distance
lat1, lon1 = math.radians(start_coords.latitude), math.radians(
start_coords.longitude)
lat2, lon2 = math.radians(
end_coords.latitude), math.radians(end_coords.longitude)
start_coords.longitude
)
lat2, lon2 = math.radians(end_coords.latitude), math.radians(
end_coords.longitude
)
dlat = lat2 - lat1
dlon = lon2 - lon1
a = math.sin(dlat/2)**2 + math.cos(lat1) * \
math.cos(lat2) * math.sin(dlon/2)**2
a = (
math.sin(dlat / 2) ** 2
+ math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
)
c = 2 * math.asin(math.sqrt(a))
# Earth's radius in kilometers
earth_radius_km = 6371.0
distance_km = earth_radius_km * c
# Estimate driving time (assume average 80 km/h with 25% extra for roads)
# Estimate driving time (assume average 80 km/h with 25% extra for
# roads)
estimated_duration_minutes = int((distance_km * 1.25 / 80.0) * 60)
return RouteInfo(
distance_km=distance_km,
duration_minutes=estimated_duration_minutes,
geometry=None
geometry=None,
)
def find_parks_along_route(self, start_park: 'Park', end_park: 'Park', max_detour_km: float = 50) -> List['Park']:
def find_parks_along_route(
self, start_park: "Park", end_park: "Park", max_detour_km: float = 50
) -> List["Park"]:
"""
Find parks along a route within specified detour distance.
@@ -358,7 +396,7 @@ class RoadTripService:
"""
from parks.models import Park
if not hasattr(start_park, 'location') or not hasattr(end_park, 'location'):
if not hasattr(start_park, "location") or not hasattr(end_park, "location"):
return []
if not start_park.location or not end_park.location:
@@ -370,18 +408,22 @@ class RoadTripService:
if not start_coords or not end_coords:
return []
start_point = Point(
start_coords[1], start_coords[0], srid=4326) # lon, lat
end_point = Point(end_coords[1], end_coords[0], srid=4326)
start_point = Point(start_coords[1], start_coords[0], srid=4326) # lon, lat
# end_point is not used in this method - we use coordinates directly
# Find all parks within a reasonable distance from both start and end
max_search_distance = Distance(km=max_detour_km * 2)
candidate_parks = Park.objects.filter(
location__point__distance_lte=(start_point, max_search_distance)
).exclude(
id__in=[start_park.id, end_park.id]
).select_related('location')
candidate_parks = (
Park.objects.filter(
location__point__distance_lte=(
start_point,
max_search_distance,
)
)
.exclude(id__in=[start_park.id, end_park.id])
.select_related("location")
)
parks_along_route = []
@@ -397,7 +439,7 @@ class RoadTripService:
detour_distance = self._calculate_detour_distance(
Coordinates(*start_coords),
Coordinates(*end_coords),
Coordinates(*park_coords)
Coordinates(*park_coords),
)
if detour_distance and detour_distance <= max_detour_km:
@@ -405,7 +447,9 @@ class RoadTripService:
return parks_along_route
def _calculate_detour_distance(self, start: Coordinates, end: Coordinates, waypoint: Coordinates) -> Optional[float]:
def _calculate_detour_distance(
self, start: Coordinates, end: Coordinates, waypoint: Coordinates
) -> Optional[float]:
"""
Calculate the detour distance when visiting a waypoint.
"""
@@ -422,15 +466,16 @@ class RoadTripService:
if not route_to_waypoint or not route_from_waypoint:
return None
detour_distance = (route_to_waypoint.distance_km +
route_from_waypoint.distance_km) - direct_route.distance_km
detour_distance = (
route_to_waypoint.distance_km + route_from_waypoint.distance_km
) - direct_route.distance_km
return max(0, detour_distance) # Don't return negative detours
except Exception as e:
logger.error(f"Failed to calculate detour distance: {e}")
return None
def create_multi_park_trip(self, park_list: List['Park']) -> Optional[RoadTrip]:
def create_multi_park_trip(self, park_list: List["Park"]) -> Optional[RoadTrip]:
"""
Create optimized multi-park road trip using simple nearest neighbor heuristic.
@@ -449,12 +494,12 @@ class RoadTripService:
else:
return self._optimize_trip_nearest_neighbor(park_list)
def _optimize_trip_exhaustive(self, park_list: List['Park']) -> Optional[RoadTrip]:
def _optimize_trip_exhaustive(self, park_list: List["Park"]) -> Optional[RoadTrip]:
"""
Find optimal route by testing all permutations (for small lists).
"""
best_trip = None
best_distance = float('inf')
best_distance = float("inf")
# Try all possible orders (excluding the first park as starting point)
for perm in permutations(park_list[1:]):
@@ -467,7 +512,9 @@ class RoadTripService:
return best_trip
def _optimize_trip_nearest_neighbor(self, park_list: List['Park']) -> Optional[RoadTrip]:
def _optimize_trip_nearest_neighbor(
self, park_list: List["Park"]
) -> Optional[RoadTrip]:
"""
Optimize trip using nearest neighbor heuristic (for larger lists).
"""
@@ -482,7 +529,7 @@ class RoadTripService:
while remaining_parks:
# Find nearest unvisited park
nearest_park = None
min_distance = float('inf')
min_distance = float("inf")
current_coords = current_park.coordinates
if not current_coords:
@@ -494,8 +541,7 @@ class RoadTripService:
continue
route = self.calculate_route(
Coordinates(*current_coords),
Coordinates(*park_coords)
Coordinates(*current_coords), Coordinates(*park_coords)
)
if route and route.distance_km < min_distance:
@@ -511,7 +557,9 @@ class RoadTripService:
return self._create_trip_from_order(ordered_parks)
def _create_trip_from_order(self, ordered_parks: List['Park']) -> Optional[RoadTrip]:
def _create_trip_from_order(
self, ordered_parks: List["Park"]
) -> Optional[RoadTrip]:
"""
Create a RoadTrip object from an ordered list of parks.
"""
@@ -533,16 +581,11 @@ class RoadTripService:
continue
route = self.calculate_route(
Coordinates(*from_coords),
Coordinates(*to_coords)
Coordinates(*from_coords), Coordinates(*to_coords)
)
if route:
legs.append(TripLeg(
from_park=from_park,
to_park=to_park,
route=route
))
legs.append(TripLeg(from_park=from_park, to_park=to_park, route=route))
total_distance += route.distance_km
total_duration += route.duration_minutes
@@ -553,10 +596,12 @@ class RoadTripService:
parks=ordered_parks,
legs=legs,
total_distance_km=total_distance,
total_duration_minutes=total_duration
total_duration_minutes=total_duration,
)
def get_park_distances(self, center_park: 'Park', radius_km: float = 100) -> List[Dict[str, Any]]:
def get_park_distances(
self, center_park: "Park", radius_km: float = 100
) -> List[Dict[str, Any]]:
"""
Get all parks within radius of a center park with distances.
@@ -569,22 +614,23 @@ class RoadTripService:
"""
from parks.models import Park
if not hasattr(center_park, 'location') or not center_park.location:
if not hasattr(center_park, "location") or not center_park.location:
return []
center_coords = center_park.coordinates
if not center_coords:
return []
center_point = Point(
center_coords[1], center_coords[0], srid=4326) # lon, lat
center_point = Point(center_coords[1], center_coords[0], srid=4326) # lon, lat
search_distance = Distance(km=radius_km)
nearby_parks = Park.objects.filter(
location__point__distance_lte=(center_point, search_distance)
).exclude(
id=center_park.id
).select_related('location')
nearby_parks = (
Park.objects.filter(
location__point__distance_lte=(center_point, search_distance)
)
.exclude(id=center_park.id)
.select_related("location")
)
results = []
@@ -594,25 +640,26 @@ class RoadTripService:
continue
route = self.calculate_route(
Coordinates(*center_coords),
Coordinates(*park_coords)
Coordinates(*center_coords), Coordinates(*park_coords)
)
if route:
results.append({
'park': park,
'distance_km': route.distance_km,
'duration_minutes': route.duration_minutes,
'formatted_distance': route.formatted_distance,
'formatted_duration': route.formatted_duration,
})
results.append(
{
"park": park,
"distance_km": route.distance_km,
"duration_minutes": route.duration_minutes,
"formatted_distance": route.formatted_distance,
"formatted_duration": route.formatted_duration,
}
)
# Sort by distance
results.sort(key=lambda x: x['distance_km'])
results.sort(key=lambda x: x["distance_km"])
return results
def geocode_park_if_needed(self, park: 'Park') -> bool:
def geocode_park_if_needed(self, park: "Park") -> bool:
"""
Geocode park location if coordinates are missing.
@@ -622,7 +669,7 @@ class RoadTripService:
Returns:
True if geocoding succeeded or wasn't needed, False otherwise
"""
if not hasattr(park, 'location') or not park.location:
if not hasattr(park, "location") or not park.location:
return False
location = park.location
@@ -637,7 +684,7 @@ class RoadTripService:
location.street_address,
location.city,
location.state,
location.country
location.country,
]
address = ", ".join(part for part in address_parts if part)
@@ -649,7 +696,11 @@ class RoadTripService:
location.set_coordinates(coords.latitude, coords.longitude)
location.save()
logger.info(
f"Geocoded park '{park.name}' to {coords.latitude}, {coords.longitude}")
f"Geocoded park '{
park.name}' to {
coords.latitude}, {
coords.longitude}"
)
return True
return False