from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from worldtravel.models import Region, City, VisitedRegion, VisitedCity from adventures.models import Location from adventures.serializers import LocationSerializer from adventures.geocoding import reverse_geocode from django.conf import settings from adventures.geocoding import search_google, search_osm class ReverseGeocodeViewSet(viewsets.ViewSet): permission_classes = [IsAuthenticated] @action(detail=False, methods=["get"]) def reverse_geocode(self, request): lat = request.query_params.get("lat", "") lon = request.query_params.get("lon", "") if not lat or not lon: return Response( {"error": "Latitude and longitude are required"}, status=400 ) try: lat = float(lat) lon = float(lon) except ValueError: return Response({"error": "Invalid latitude or longitude"}, status=400) data = reverse_geocode(lat, lon, self.request.user) if "error" in data: return Response( {"error": "An internal error occurred while processing the request"}, status=400, ) return Response(data) @action(detail=False, methods=["get"]) def search(self, request): query = request.query_params.get("query", "") lang = request.query_params.get("lang", "en") if not query: return Response({"error": "Query parameter is required"}, status=400) try: if getattr(settings, "GOOGLE_MAPS_API_KEY", None): results = search_google(query, lang=lang) else: results = search_osm(query, lang=lang) return Response(results) except Exception: return Response( {"error": "An internal error occurred while processing the request"}, status=500, ) @action(detail=False, methods=["post"]) def mark_visited_region(self, request): """ Marks regions and cities as visited based on user's visited locations. Uses the pre-stored region/city data on locations to avoid expensive reverse geocoding. """ new_region_count = 0 new_regions = {} new_city_count = 0 new_cities = {} # Get all visited locations with their region and city data visited_locations = Location.objects.filter( user=self.request.user ).select_related("region", "city") # Track unique regions and cities to create VisitedRegion/VisitedCity entries regions_to_mark = set() cities_to_mark = set() for location in visited_locations: # Only process locations that are marked as visited if not location.is_visited_status(): continue # Collect regions if location.region: regions_to_mark.add(location.region.id) # Collect cities if location.city: cities_to_mark.add(location.city.id) # Get existing visited regions for this user existing_visited_regions = set( VisitedRegion.objects.filter( user=self.request.user, region_id__in=regions_to_mark ).values_list("region_id", flat=True) ) # Create new VisitedRegion entries new_visited_regions = [] for region_id in regions_to_mark: if region_id not in existing_visited_regions: new_visited_regions.append( VisitedRegion(region_id=region_id, user=self.request.user) ) if new_visited_regions: VisitedRegion.objects.bulk_create(new_visited_regions) new_region_count = len(new_visited_regions) # Get region names for response regions = Region.objects.filter( id__in=[vr.region_id for vr in new_visited_regions] ) new_regions = {r.id: r.name for r in regions} # Get existing visited cities for this user existing_visited_cities = set( VisitedCity.objects.filter( user=self.request.user, city_id__in=cities_to_mark ).values_list("city_id", flat=True) ) # Create new VisitedCity entries new_visited_cities = [] for city_id in cities_to_mark: if city_id not in existing_visited_cities: new_visited_cities.append( VisitedCity(city_id=city_id, user=self.request.user) ) if new_visited_cities: VisitedCity.objects.bulk_create(new_visited_cities) new_city_count = len(new_visited_cities) # Get city names for response cities = City.objects.filter( id__in=[vc.city_id for vc in new_visited_cities] ) new_cities = {c.id: c.name for c in cities} return Response( { "new_regions": new_region_count, "regions": new_regions, "new_cities": new_city_count, "cities": new_cities, } )