All except blocks in user.py and events.py now log the real error server-side (via eventify_logger) and return a generic "An unexpected server error occurred." message to the client. Python tracebacks, model field names, and ORM errors are no longer visible in API responses.
545 lines
22 KiB
Python
545 lines
22 KiB
Python
import json
|
|
from django.http import JsonResponse
|
|
from rest_framework.views import APIView
|
|
from rest_framework.authentication import TokenAuthentication
|
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
|
from events.models import Event, EventImages
|
|
from master_data.models import EventType
|
|
from django.forms.models import model_to_dict
|
|
from django.utils.decorators import method_decorator
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from django.db.models import Q
|
|
from datetime import datetime, timedelta
|
|
import calendar
|
|
import math
|
|
from mobile_api.utils import validate_token_and_get_user
|
|
from eventify_logger.services import log
|
|
|
|
|
|
def _haversine_km(lat1, lon1, lat2, lon2):
|
|
"""Great-circle distance between two points in km."""
|
|
R = 6371.0
|
|
dlat = math.radians(lat2 - lat1)
|
|
dlon = math.radians(lon2 - lon1)
|
|
a = (math.sin(dlat / 2) ** 2 +
|
|
math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
|
|
math.sin(dlon / 2) ** 2)
|
|
return R * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
class EventTypeListAPIView(APIView):
|
|
permission_classes = [AllowAny]
|
|
|
|
def post(self, request):
|
|
try:
|
|
event_types_queryset = EventType.objects.all()
|
|
event_types = []
|
|
for event_type in event_types_queryset:
|
|
event_type_data = {
|
|
"id": event_type.id,
|
|
"event_type": event_type.event_type,
|
|
"event_type_icon": event_type.event_type_icon.url if event_type.event_type_icon else None
|
|
}
|
|
event_types.append(event_type_data)
|
|
return JsonResponse({"status": "success", "event_types": event_types})
|
|
except Exception as e:
|
|
log("error", "EventTypeAPI exception", request=request, logger_data={"error": str(e)})
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|
|
|
|
|
|
class EventListAPI(APIView):
|
|
permission_classes = [AllowAny]
|
|
|
|
@staticmethod
|
|
def _serialize_event(e, thumb_map):
|
|
"""Slim serialization for list views — only fields the Flutter app uses."""
|
|
img = thumb_map.get(e.id)
|
|
lat = e.latitude
|
|
lng = e.longitude
|
|
desc = e.description or ''
|
|
return {
|
|
'id': e.id,
|
|
'name': e.name or '',
|
|
'title': e.title or '',
|
|
'description': desc[:200] if len(desc) > 200 else desc,
|
|
'start_date': str(e.start_date) if e.start_date else '',
|
|
'end_date': str(e.end_date) if e.end_date else '',
|
|
'start_time': str(e.start_time) if e.start_time else '',
|
|
'end_time': str(e.end_time) if e.end_time else '',
|
|
'pincode': e.pincode or '',
|
|
'place': e.place or '',
|
|
'is_bookable': bool(e.is_bookable),
|
|
'event_type': e.event_type_id,
|
|
'event_status': e.event_status or '',
|
|
'venue_name': getattr(e, 'venue_name', '') or '',
|
|
'latitude': float(lat) if lat is not None else None,
|
|
'longitude': float(lng) if lng is not None else None,
|
|
'location_name': getattr(e, 'location_name', '') or '',
|
|
'thumb_img': img.event_image.url if img and img.event_image else '',
|
|
'is_eventify_event': bool(e.is_eventify_event),
|
|
'source': e.source or 'eventify',
|
|
}
|
|
|
|
def post(self, request):
|
|
try:
|
|
try:
|
|
data = json.loads(request.body) if request.body else {}
|
|
except Exception:
|
|
data = {}
|
|
|
|
pincode = data.get("pincode", "all")
|
|
page = int(data.get("page", 1))
|
|
page_size = int(data.get("page_size", 50))
|
|
per_type = int(data.get("per_type", 0))
|
|
|
|
# New optional geo params
|
|
user_lat = data.get("latitude")
|
|
user_lng = data.get("longitude")
|
|
try:
|
|
radius_km = float(data.get("radius_km", 10))
|
|
except (ValueError, TypeError):
|
|
radius_km = 10
|
|
|
|
# Build base queryset
|
|
MIN_EVENTS_THRESHOLD = 6
|
|
qs = Event.objects.all()
|
|
used_radius = None
|
|
|
|
# Priority 1: Haversine radius filtering (if lat/lng provided)
|
|
if user_lat is not None and user_lng is not None:
|
|
try:
|
|
user_lat = float(user_lat)
|
|
user_lng = float(user_lng)
|
|
|
|
# Bounding box pre-filter (1 degree lat ≈ 111km)
|
|
lat_delta = radius_km / 111.0
|
|
lng_delta = radius_km / (111.0 * max(math.cos(math.radians(user_lat)), 0.01))
|
|
|
|
candidates = qs.filter(
|
|
latitude__gte=user_lat - lat_delta,
|
|
latitude__lte=user_lat + lat_delta,
|
|
longitude__gte=user_lng - lng_delta,
|
|
longitude__lte=user_lng + lng_delta,
|
|
latitude__isnull=False,
|
|
longitude__isnull=False,
|
|
)
|
|
|
|
# Exact Haversine filter in Python
|
|
nearby_ids = []
|
|
for e in candidates:
|
|
if e.latitude is not None and e.longitude is not None:
|
|
dist = _haversine_km(user_lat, user_lng, float(e.latitude), float(e.longitude))
|
|
if dist <= radius_km:
|
|
nearby_ids.append(e.id)
|
|
|
|
# Progressive radius expansion if too few results
|
|
if len(nearby_ids) < MIN_EVENTS_THRESHOLD:
|
|
for expanded_r in [r for r in [25, 50, 100] if r > radius_km]:
|
|
lat_delta_ex = expanded_r / 111.0
|
|
lng_delta_ex = expanded_r / (111.0 * max(math.cos(math.radians(user_lat)), 0.01))
|
|
candidates_ex = qs.filter(
|
|
latitude__gte=user_lat - lat_delta_ex,
|
|
latitude__lte=user_lat + lat_delta_ex,
|
|
longitude__gte=user_lng - lng_delta_ex,
|
|
longitude__lte=user_lng + lng_delta_ex,
|
|
latitude__isnull=False,
|
|
longitude__isnull=False,
|
|
)
|
|
nearby_ids = []
|
|
for e in candidates_ex:
|
|
if e.latitude is not None and e.longitude is not None:
|
|
dist = _haversine_km(user_lat, user_lng, float(e.latitude), float(e.longitude))
|
|
if dist <= expanded_r:
|
|
nearby_ids.append(e.id)
|
|
if len(nearby_ids) >= MIN_EVENTS_THRESHOLD:
|
|
radius_km = expanded_r
|
|
break
|
|
|
|
if nearby_ids:
|
|
qs = qs.filter(id__in=nearby_ids)
|
|
used_radius = radius_km
|
|
|
|
except (ValueError, TypeError):
|
|
pass # Invalid lat/lng — fall back to pincode
|
|
|
|
# Priority 2: Pincode filtering (backward compatible fallback)
|
|
if used_radius is None and pincode and pincode != 'all':
|
|
pincode_qs = qs.filter(pincode=pincode)
|
|
if pincode_qs.count() >= MIN_EVENTS_THRESHOLD:
|
|
qs = pincode_qs
|
|
|
|
if per_type > 0 and page == 1:
|
|
type_ids = list(qs.values_list('event_type_id', flat=True).distinct())
|
|
events_page = []
|
|
for tid in sorted(type_ids):
|
|
chunk = list(qs.filter(event_type_id=tid).order_by('-created_date')[:per_type])
|
|
events_page.extend(chunk)
|
|
total_count = qs.count()
|
|
end = len(events_page)
|
|
else:
|
|
total_count = qs.count()
|
|
qs = qs.order_by('-created_date')
|
|
start = (page - 1) * page_size
|
|
end = start + page_size
|
|
events_page = list(qs[start:end])
|
|
|
|
page_ids = [e.id for e in events_page]
|
|
primary_images = EventImages.objects.filter(event_id__in=page_ids, is_primary=True)
|
|
thumb_map = {img.event_id: img for img in primary_images}
|
|
|
|
event_list = [self._serialize_event(e, thumb_map) for e in events_page]
|
|
|
|
return JsonResponse({
|
|
"status": "success",
|
|
"events": event_list,
|
|
"total_count": total_count,
|
|
"page": page,
|
|
"page_size": page_size,
|
|
"has_next": end < total_count,
|
|
"radius_km": used_radius,
|
|
})
|
|
except Exception as e:
|
|
log("error", "EventListAPI exception", request=request, logger_data={"error": str(e)})
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|
|
|
|
|
|
class EventDetailAPI(APIView):
|
|
permission_classes = [AllowAny]
|
|
|
|
def post(self, request):
|
|
try:
|
|
try:
|
|
data = json.loads(request.body) if request.body else {}
|
|
except Exception:
|
|
data = {}
|
|
event_id = data.get("event_id")
|
|
events = Event.objects.get(id=event_id)
|
|
event_images = EventImages.objects.filter(event=event_id)
|
|
event_data = model_to_dict(events)
|
|
event_data["status"] = "success"
|
|
event_images_list = []
|
|
for ei in event_images:
|
|
event_img = {}
|
|
event_img['is_primary'] = ei.is_primary
|
|
event_img['image'] = ei.event_image.url
|
|
event_images_list.append(event_img)
|
|
event_data["images"] = event_images_list
|
|
return JsonResponse(event_data)
|
|
except Exception as e:
|
|
log("error", "EventDetailAPI exception", request=request, logger_data={"error": str(e)})
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|
|
|
|
|
|
class EventImagesListAPI(APIView):
|
|
authentication_classes = []
|
|
permission_classes = [AllowAny]
|
|
def post(self, request):
|
|
try:
|
|
user, token, data, error_response = validate_token_and_get_user(request)
|
|
if error_response:
|
|
return error_response
|
|
|
|
event_id = data.get("event_id")
|
|
|
|
event_images = EventImages.objects.filter(event=event_id)
|
|
res_data = {}
|
|
res_data["status"] = "success"
|
|
event_images_list = []
|
|
for ei in event_images:
|
|
event_images_list.append(ei.event_image.url)
|
|
|
|
res_data["images"] = event_images_list
|
|
|
|
print(res_data)
|
|
|
|
return JsonResponse(res_data)
|
|
|
|
except Exception as e:
|
|
log("error", "EventImagesListAPI exception", request=request, logger_data={"error": str(e)})
|
|
return JsonResponse(
|
|
{"status": "error", "message": "An unexpected server error occurred."},
|
|
)
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
class EventsByCategoryAPI(APIView):
|
|
authentication_classes = []
|
|
permission_classes = [AllowAny]
|
|
def post(self, request):
|
|
try:
|
|
user, token, data, error_response = validate_token_and_get_user(request)
|
|
if error_response:
|
|
return error_response
|
|
|
|
category_id = data.get("category_id")
|
|
|
|
if not category_id:
|
|
return JsonResponse(
|
|
{"status": "error", "message": "category_id is required"}
|
|
)
|
|
|
|
events = Event.objects.filter(event_type=category_id)
|
|
events_dict = [model_to_dict(obj) for obj in events]
|
|
|
|
for event in events_dict:
|
|
try:
|
|
event['event_image'] = EventImages.objects.get(event=event['id'], is_primary=True).event_image.url
|
|
except EventImages.DoesNotExist:
|
|
event['event_image'] = ''
|
|
# event['start_date'] = convert_date_to_dd_mm_yyyy(event['start_date'])
|
|
print(events_dict)
|
|
|
|
return JsonResponse({
|
|
"status": "success",
|
|
"events": events_dict
|
|
})
|
|
|
|
except Exception as e:
|
|
log("error", "EventsByDateAPI exception", request=request, logger_data={"error": str(e)})
|
|
return JsonResponse(
|
|
{"status": "error", "message": "An unexpected server error occurred."},
|
|
)
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
class EventsByMonthYearAPI(APIView):
|
|
authentication_classes = []
|
|
permission_classes = [AllowAny]
|
|
"""
|
|
API to get events by month and year.
|
|
Returns dates that have events, total count, and date-wise breakdown.
|
|
"""
|
|
|
|
def post(self, request):
|
|
try:
|
|
user, token, data, error_response = validate_token_and_get_user(request)
|
|
if error_response:
|
|
return error_response
|
|
|
|
month_name = data.get("month") # e.g., "August", "august", "Aug"
|
|
year = data.get("year") # e.g., 2025
|
|
|
|
if not month_name or not year:
|
|
return JsonResponse(
|
|
{"status": "error", "message": "month and year are required"}
|
|
)
|
|
|
|
# Convert month name to month number
|
|
month_name_lower = month_name.lower().capitalize()
|
|
month_abbr = month_name_lower[:3]
|
|
|
|
# Try full month name first, then abbreviation
|
|
month_number = None
|
|
for i in range(1, 13):
|
|
if calendar.month_name[i].lower() == month_name_lower or calendar.month_abbr[i].lower() == month_abbr.lower():
|
|
month_number = i
|
|
break
|
|
|
|
if not month_number:
|
|
return JsonResponse(
|
|
{"status": "error", "message": f"Invalid month name: {month_name}"}
|
|
)
|
|
|
|
# Convert year to integer
|
|
try:
|
|
year = int(year)
|
|
except (ValueError, TypeError):
|
|
return JsonResponse(
|
|
{"status": "error", "message": "Invalid year format"}
|
|
)
|
|
|
|
# Filter events where start_date or end_date falls in the given month/year
|
|
# An event is included if any part of it (start_date to end_date) overlaps with the month
|
|
# events = Event.objects.filter(
|
|
# Q(start_date__year=year, start_date__month=month_number) |
|
|
# Q(end_date__year=year, end_date__month=month_number) |
|
|
# Q(start_date__lte=datetime(year, month_number, 1).date(),
|
|
# end_date__gte=datetime(year, month_number, calendar.monthrange(year, month_number)[1]).date())
|
|
# ).distinct()
|
|
|
|
events = Event.objects.filter(start_date__year=year, start_date__month=month_number).distinct()
|
|
print('*' * 100)
|
|
print(f'Total events: {events.count()}')
|
|
print('*' * 100)
|
|
unique_start_dates = events.values_list('start_date', flat=True).distinct()
|
|
date_strings = [d.strftime('%Y-%m-%d') for d in unique_start_dates]
|
|
print('*' * 100)
|
|
print(f'Unique start dates: {date_strings}')
|
|
print('*' * 100)
|
|
|
|
|
|
# Group events by date
|
|
date_events_dict = {}
|
|
all_dates = set()
|
|
|
|
# Calculate month boundaries
|
|
month_start = datetime(year, month_number, 1).date()
|
|
month_end = datetime(year, month_number, calendar.monthrange(year, month_number)[1]).date()
|
|
|
|
for event in events:
|
|
# Get all dates between start_date and end_date that fall in the target month
|
|
current_date = max(event.start_date, month_start)
|
|
end_date = min(event.end_date, month_end)
|
|
|
|
# Iterate through each date in the event's date range that falls in the target month
|
|
while current_date <= end_date:
|
|
if current_date.year == year and current_date.month == month_number:
|
|
date_str = current_date.strftime('%Y-%m-%d')
|
|
all_dates.add(date_str)
|
|
if date_str not in date_events_dict:
|
|
date_events_dict[date_str] = 0
|
|
date_events_dict[date_str] += 1
|
|
|
|
# Move to next day
|
|
current_date += timedelta(days=1)
|
|
|
|
# Sort dates
|
|
sorted_dates = sorted(all_dates)
|
|
|
|
# Build date_events list
|
|
date_events = [
|
|
{
|
|
"date_of_event": date_str,
|
|
"events_of_date": date_events_dict[date_str]
|
|
}
|
|
for date_str in sorted_dates
|
|
]
|
|
|
|
# Calculate total number of events (unique events, not date occurrences)
|
|
total_events = events.count()
|
|
|
|
print(sorted_dates)
|
|
print(total_events)
|
|
print(date_events)
|
|
|
|
return JsonResponse({
|
|
"status": "success",
|
|
"dates": date_strings,
|
|
"total_number_of_events": total_events,
|
|
"date_events": date_events
|
|
})
|
|
|
|
except Exception as e:
|
|
log("error", "DateSheetAPI exception", request=request, logger_data={"error": str(e)})
|
|
return JsonResponse(
|
|
{"status": "error", "message": "An unexpected server error occurred."},
|
|
)
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
class EventsByDateAPI(APIView):
|
|
authentication_classes = []
|
|
permission_classes = [AllowAny]
|
|
"""
|
|
API to get events occurring on a specific date.
|
|
Returns complete event information with primary images.
|
|
"""
|
|
|
|
def post(self, request):
|
|
try:
|
|
user, token, data, error_response = validate_token_and_get_user(request)
|
|
if error_response:
|
|
return error_response
|
|
|
|
date_of_event = data.get("date_of_event")
|
|
|
|
if not date_of_event:
|
|
return JsonResponse(
|
|
{"status": "error", "message": "date_of_event is required"}
|
|
)
|
|
|
|
# Parse date_of_event in YYYY-MM-DD format
|
|
try:
|
|
event_date = datetime.strptime(date_of_event, "%Y-%m-%d").date()
|
|
except ValueError:
|
|
return JsonResponse(
|
|
{"status": "error", "message": "Invalid date format. Expected YYYY-MM-DD"}
|
|
)
|
|
|
|
# Filter events where the provided date falls between start_date and end_date (inclusive)
|
|
events = Event.objects.filter(
|
|
start_date=event_date
|
|
).order_by('start_date')
|
|
|
|
event_list = []
|
|
|
|
for e in events:
|
|
data_dict = model_to_dict(e)
|
|
try:
|
|
thumb_img = EventImages.objects.get(event=e.id, is_primary=True)
|
|
data_dict['thumb_img'] = thumb_img.event_image.url
|
|
except EventImages.DoesNotExist:
|
|
data_dict['thumb_img'] = ''
|
|
|
|
event_list.append(data_dict)
|
|
|
|
return JsonResponse({
|
|
"status": "success",
|
|
"events": event_list
|
|
})
|
|
|
|
except Exception as e:
|
|
log("error", "PincodeEventsAPI exception", request=request, logger_data={"error": str(e)})
|
|
return JsonResponse(
|
|
{"status": "error", "message": "An unexpected server error occurred."},
|
|
)
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
class FeaturedEventsAPI(APIView):
|
|
authentication_classes = []
|
|
permission_classes = [AllowAny]
|
|
"""Returns events where is_featured=True — used for the homepage hero carousel."""
|
|
|
|
def post(self, request):
|
|
try:
|
|
user, token, data, error_response = validate_token_and_get_user(request)
|
|
if error_response:
|
|
return error_response
|
|
|
|
events = Event.objects.filter(is_featured=True).order_by('-created_date')
|
|
event_list = []
|
|
for e in events:
|
|
data_dict = model_to_dict(e)
|
|
try:
|
|
thumb = EventImages.objects.get(event=e.id, is_primary=True)
|
|
data_dict['thumb_img'] = thumb.event_image.url
|
|
except EventImages.DoesNotExist:
|
|
data_dict['thumb_img'] = ''
|
|
event_list.append(data_dict)
|
|
|
|
return JsonResponse({"status": "success", "events": event_list})
|
|
except Exception as e:
|
|
log("error", "FeaturedEventsAPI exception", request=request, logger_data={"error": str(e)})
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
class TopEventsAPI(APIView):
|
|
authentication_classes = []
|
|
permission_classes = [AllowAny]
|
|
"""Returns events where is_top_event=True — used for the Top Events section."""
|
|
|
|
def post(self, request):
|
|
try:
|
|
user, token, data, error_response = validate_token_and_get_user(request)
|
|
if error_response:
|
|
return error_response
|
|
|
|
events = Event.objects.filter(is_top_event=True).order_by('-created_date')
|
|
event_list = []
|
|
for e in events:
|
|
data_dict = model_to_dict(e)
|
|
try:
|
|
thumb = EventImages.objects.get(event=e.id, is_primary=True)
|
|
data_dict['thumb_img'] = thumb.event_image.url
|
|
except EventImages.DoesNotExist:
|
|
data_dict['thumb_img'] = ''
|
|
event_list.append(data_dict)
|
|
|
|
return JsonResponse({"status": "success", "events": event_list})
|
|
except Exception as e:
|
|
log("error", "TopEventsAPI exception", request=request, logger_data={"error": str(e)})
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|