Files
thrillwiki_django_no_react/location/views.py
2025-04-11 03:13:13 +00:00

204 lines
7.4 KiB
Python

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 location.forms import LocationForm
from .models import Location
from security import safe_requests
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 = safe_requests.get(
'https://nominatim.openstreetmap.org/search',
params=params,
headers={'User-Agent': 'ThrillWiki/1.0'},
timeout=60)
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 = safe_requests.get(
'https://nominatim.openstreetmap.org/reverse',
params={
'lat': lat,
'lon': lon,
'format': 'json',
'addressdetails': 1
},
headers={'User-Agent': 'ThrillWiki/1.0'},
timeout=60)
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)