mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2026-05-08 23:15:11 -04:00
- Added LodgingQuickStart component to facilitate quick lodging entry. - Integrated Google Maps support for lodging selection and details enrichment. - Enhanced LodgingModal to include quick start step and handle prefill from Google Places. - Introduced utility function to infer lodging type from Google Places data. - Updated localization files to include new strings for quick start functionality.
203 lines
4.9 KiB
Python
203 lines
4.9 KiB
Python
from urllib.parse import urlparse
|
|
|
|
from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
|
|
from rest_framework import status
|
|
from rest_framework.exceptions import PermissionDenied as DRFPermissionDenied
|
|
from rest_framework.response import Response
|
|
|
|
from adventures.geocoding import get_place_details
|
|
from adventures.models import Collection
|
|
|
|
|
|
def coerce_coordinate(value, min_value, max_value):
|
|
try:
|
|
number = round(float(value), 6)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
if number < min_value or number > max_value:
|
|
return None
|
|
|
|
return number
|
|
|
|
|
|
def coerce_float(value):
|
|
try:
|
|
return float(value)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def coerce_int(value):
|
|
try:
|
|
return int(value)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def coerce_bool(value, default=False):
|
|
if isinstance(value, bool):
|
|
return value
|
|
|
|
if isinstance(value, str):
|
|
normalized = value.strip().lower()
|
|
if normalized in {"true", "1", "yes", "on"}:
|
|
return True
|
|
if normalized in {"false", "0", "no", "off"}:
|
|
return False
|
|
|
|
return default
|
|
|
|
|
|
def clean_url(value):
|
|
if not isinstance(value, str):
|
|
return None
|
|
|
|
normalized = value.strip()
|
|
if not normalized:
|
|
return None
|
|
|
|
parsed = urlparse(normalized)
|
|
if parsed.scheme in {"http", "https"} and parsed.netloc:
|
|
return normalized
|
|
|
|
return None
|
|
|
|
|
|
def sanitize_tags(raw_tags, max_tags=8):
|
|
if not isinstance(raw_tags, list):
|
|
return []
|
|
|
|
tags = []
|
|
for item in raw_tags:
|
|
if not isinstance(item, str):
|
|
continue
|
|
|
|
value = item.strip()
|
|
if not value or value in tags:
|
|
continue
|
|
|
|
tags.append(value)
|
|
if len(tags) >= max_tags:
|
|
break
|
|
|
|
return tags
|
|
|
|
|
|
def sanitize_photo_urls(raw_urls, max_urls=5):
|
|
if not isinstance(raw_urls, list):
|
|
return []
|
|
|
|
cleaned = []
|
|
for value in raw_urls:
|
|
url = clean_url(value)
|
|
if not url or url in cleaned:
|
|
continue
|
|
|
|
cleaned.append(url)
|
|
if len(cleaned) >= max_urls:
|
|
break
|
|
|
|
return cleaned
|
|
|
|
|
|
def build_quick_add_description(base_description, detailed_description):
|
|
description = str(detailed_description or "").strip() or str(base_description or "").strip()
|
|
return description or None
|
|
|
|
|
|
def resolve_quick_add_collection(collection_id, validate_permissions, permission_error_message):
|
|
if not collection_id:
|
|
return None
|
|
|
|
try:
|
|
collection = Collection.objects.get(id=collection_id)
|
|
except Collection.DoesNotExist:
|
|
return Response(
|
|
{"error": "Collection not found."},
|
|
status=status.HTTP_404_NOT_FOUND,
|
|
)
|
|
|
|
try:
|
|
validate_permissions([collection])
|
|
except (DjangoPermissionDenied, DRFPermissionDenied):
|
|
return Response(
|
|
{"error": permission_error_message},
|
|
status=status.HTTP_403_FORBIDDEN,
|
|
)
|
|
|
|
return collection
|
|
|
|
|
|
def extract_google_place_details(payload, fallback_query=""):
|
|
place_id = str(payload.get("place_id") or "").strip() or None
|
|
details = {}
|
|
|
|
if not place_id:
|
|
return place_id, details
|
|
|
|
details_result = get_place_details(place_id, fallback_query=fallback_query)
|
|
if isinstance(details_result, dict):
|
|
if "error" not in details_result or details_result.get("description"):
|
|
details = details_result
|
|
|
|
return place_id, details
|
|
|
|
|
|
def preferred_link(payload, details):
|
|
website = clean_url(details.get("website")) or clean_url(payload.get("website"))
|
|
maps_url = clean_url(details.get("google_maps_url")) or clean_url(payload.get("google_maps_url"))
|
|
return clean_url(payload.get("link")) or website or maps_url
|
|
|
|
|
|
def infer_lodging_type(primary_type, place_types):
|
|
valid_types = {
|
|
"hotel",
|
|
"hostel",
|
|
"resort",
|
|
"bnb",
|
|
"campground",
|
|
"cabin",
|
|
"apartment",
|
|
"house",
|
|
"villa",
|
|
"motel",
|
|
"other",
|
|
}
|
|
|
|
if isinstance(primary_type, str):
|
|
normalized = primary_type.strip().lower()
|
|
if normalized in valid_types:
|
|
return normalized
|
|
|
|
normalized_types = [
|
|
str(type_name).strip().lower()
|
|
for type_name in (place_types or [])
|
|
if str(type_name).strip()
|
|
]
|
|
|
|
mapping = {
|
|
"hotel": "hotel",
|
|
"resort_hotel": "resort",
|
|
"motel": "motel",
|
|
"hostel": "hostel",
|
|
"bed_and_breakfast": "bnb",
|
|
"guest_house": "bnb",
|
|
"campground": "campground",
|
|
"rv_park": "campground",
|
|
"camping_cabin": "cabin",
|
|
"apartment_building": "apartment",
|
|
"lodging": "hotel",
|
|
"villa": "villa",
|
|
}
|
|
|
|
for type_name in normalized_types:
|
|
if type_name in mapping:
|
|
return mapping[type_name]
|
|
|
|
for type_name in normalized_types:
|
|
if type_name in valid_types:
|
|
return type_name
|
|
|
|
return "other"
|