import json import requests from django.views.generic import View from django.http import JsonResponse from django.contrib.auth.mixins import LoginRequiredMixin from django.core.cache import cache from django.conf import settings from django.views.decorators.http import require_http_methods from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_protect from django.db.models import Q from .models import Location class LocationSearchView(View): """ View for searching locations using OpenStreetMap Nominatim. Returns search results in JSON format. """ @method_decorator(csrf_protect) def get(self, request, *args, **kwargs): query = request.GET.get('q', '').strip() filter_type = request.GET.get('type', '') # country, state, city filter_parks = request.GET.get('filter_parks', 'false') == 'true' if not query: return JsonResponse({'results': []}) # Check cache first cache_key = f'location_search_{query}_{filter_type}_{filter_parks}' cached_results = cache.get(cache_key) if cached_results: return JsonResponse({'results': cached_results}) # Search OpenStreetMap try: params = { 'q': query, 'format': 'json', 'addressdetails': 1, 'limit': 10 } # Add type-specific filters if filter_type == 'country': params['featuretype'] = 'country' elif filter_type == 'state': params['featuretype'] = 'state' elif filter_type == 'city': params['featuretype'] = 'city' response = requests.get( 'https://nominatim.openstreetmap.org/search', params=params, headers={'User-Agent': 'ThrillWiki/1.0'} ) response.raise_for_status() results = response.json() except requests.RequestException as e: return JsonResponse({ 'error': 'Failed to fetch location data', 'details': str(e) }, status=500) # Process and format results formatted_results = [] for result in results: address = result.get('address', {}) formatted_result = { 'name': result.get('display_name', ''), 'lat': result.get('lat'), 'lon': result.get('lon'), 'type': result.get('type', ''), 'address': { 'street': address.get('road', ''), 'house_number': address.get('house_number', ''), 'city': address.get('city', '') or address.get('town', '') or address.get('village', ''), 'state': address.get('state', ''), 'country': address.get('country', ''), 'postcode': address.get('postcode', '') } } # If filtering by parks, only include results that have parks if filter_parks: location_exists = Location.objects.filter( Q(country__icontains=formatted_result['address']['country']) & (Q(state__icontains=formatted_result['address']['state']) if formatted_result['address']['state'] else Q()) & (Q(city__icontains=formatted_result['address']['city']) if formatted_result['address']['city'] else Q()) ).exists() if not location_exists: continue formatted_results.append(formatted_result) # Cache results for 1 hour cache.set(cache_key, formatted_results, 3600) return JsonResponse({'results': formatted_results}) class LocationCreateView(LoginRequiredMixin, View): """View for creating new Location objects""" @method_decorator(csrf_protect) def post(self, request, *args, **kwargs): form = LocationForm(request.POST) if form.is_valid(): location = form.save() return JsonResponse({ 'id': location.id, 'name': location.name, 'formatted_address': location.get_formatted_address(), 'coordinates': location.coordinates }) return JsonResponse({'errors': form.errors}, status=400) class LocationUpdateView(LoginRequiredMixin, View): """View for updating existing Location objects""" @method_decorator(csrf_protect) def post(self, request, *args, **kwargs): location = Location.objects.get(pk=kwargs['pk']) form = LocationForm(request.POST, instance=location) if form.is_valid(): location = form.save() return JsonResponse({ 'id': location.id, 'name': location.name, 'formatted_address': location.get_formatted_address(), 'coordinates': location.coordinates }) return JsonResponse({'errors': form.errors}, status=400) class LocationDeleteView(LoginRequiredMixin, View): """View for deleting Location objects""" @method_decorator(csrf_protect) def post(self, request, *args, **kwargs): try: location = Location.objects.get(pk=kwargs['pk']) location.delete() return JsonResponse({'status': 'success'}) except Location.DoesNotExist: return JsonResponse({'error': 'Location not found'}, status=404) @require_http_methods(["GET"]) def reverse_geocode(request): """ View for reverse geocoding coordinates to address using OpenStreetMap. Returns address details in JSON format. """ lat = request.GET.get('lat') lon = request.GET.get('lon') if not lat or not lon: return JsonResponse({'error': 'Latitude and longitude are required'}, status=400) # Check cache first cache_key = f'reverse_geocode_{lat}_{lon}' cached_result = cache.get(cache_key) if cached_result: return JsonResponse(cached_result) try: response = requests.get( 'https://nominatim.openstreetmap.org/reverse', params={ 'lat': lat, 'lon': lon, 'format': 'json', 'addressdetails': 1 }, headers={'User-Agent': 'ThrillWiki/1.0'} ) response.raise_for_status() result = response.json() address = result.get('address', {}) formatted_result = { 'name': result.get('display_name', ''), 'address': { 'street': address.get('road', ''), 'house_number': address.get('house_number', ''), 'city': address.get('city', '') or address.get('town', '') or address.get('village', ''), 'state': address.get('state', ''), 'country': address.get('country', ''), 'postcode': address.get('postcode', '') } } # Cache result for 1 day cache.set(cache_key, formatted_result, 86400) return JsonResponse(formatted_result) except requests.RequestException as e: return JsonResponse({ 'error': 'Failed to fetch address data', 'details': str(e) }, status=500)