2026-03-26 09:50:03 +00:00
|
|
|
import json
|
2025-12-17 22:05:13 +05:30
|
|
|
from django.http import JsonResponse
|
|
|
|
|
from rest_framework.views import APIView
|
|
|
|
|
from rest_framework.authentication import TokenAuthentication
|
2026-03-26 09:50:03 +00:00
|
|
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
2025-12-17 22:05:13 +05:30
|
|
|
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
|
2026-04-03 08:56:00 +05:30
|
|
|
import math
|
2025-12-17 22:05:13 +05:30
|
|
|
from mobile_api.utils import validate_token_and_get_user
|
2026-04-04 17:33:56 +05:30
|
|
|
from accounts.models import User
|
2026-04-03 09:23:26 +05:30
|
|
|
from eventify_logger.services import log
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
|
2026-04-04 17:33:56 +05:30
|
|
|
def _resolve_contributor(identifier):
|
|
|
|
|
"""Resolve an eventifyId or email to a contributor dict. Returns None on miss."""
|
|
|
|
|
if not identifier:
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
user = User.objects.filter(
|
|
|
|
|
Q(eventify_id=identifier) | Q(email=identifier)
|
|
|
|
|
).first()
|
|
|
|
|
if not user:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# Count events this user contributed
|
|
|
|
|
events_count = Event.objects.filter(
|
|
|
|
|
Q(contributed_by=user.eventify_id) | Q(contributed_by=user.email)
|
|
|
|
|
).filter(
|
|
|
|
|
event_status__in=['published', 'live', 'completed']
|
|
|
|
|
).count()
|
|
|
|
|
|
|
|
|
|
full_name = user.get_full_name() or user.username or ''
|
|
|
|
|
avatar = ''
|
|
|
|
|
if user.profile_picture and hasattr(user.profile_picture, 'url'):
|
|
|
|
|
try:
|
|
|
|
|
avatar = user.profile_picture.url
|
|
|
|
|
except Exception:
|
|
|
|
|
avatar = ''
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'name': full_name,
|
|
|
|
|
'email': user.email,
|
|
|
|
|
'eventify_id': user.eventify_id or '',
|
|
|
|
|
'avatar': avatar,
|
|
|
|
|
'member_since': user.date_joined.strftime('%b %Y') if user.date_joined else '',
|
|
|
|
|
'events_contributed': events_count,
|
|
|
|
|
'location': ', '.join(filter(None, [user.place or '', user.district or '', user.state or ''])),
|
|
|
|
|
}
|
|
|
|
|
except Exception:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _serialize_event_for_contributor(event):
|
|
|
|
|
"""Lightweight event serializer for contributor profile listings."""
|
|
|
|
|
primary_img = ''
|
|
|
|
|
try:
|
|
|
|
|
img = EventImages.objects.filter(event=event, is_primary=True).first()
|
|
|
|
|
if not img:
|
|
|
|
|
img = EventImages.objects.filter(event=event).first()
|
|
|
|
|
if img and img.event_image:
|
|
|
|
|
primary_img = img.event_image.url
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'id': event.id,
|
|
|
|
|
'name': event.name or event.title or '',
|
|
|
|
|
'title': event.title or event.name or '',
|
|
|
|
|
'start_date': event.start_date.isoformat() if event.start_date else '',
|
|
|
|
|
'end_date': event.end_date.isoformat() if event.end_date else '',
|
|
|
|
|
'start_time': str(event.start_time or ''),
|
|
|
|
|
'end_time': str(event.end_time or ''),
|
|
|
|
|
'image': primary_img,
|
|
|
|
|
'venue_name': event.venue_name or '',
|
|
|
|
|
'place': event.place or '',
|
|
|
|
|
'district': event.district or '',
|
|
|
|
|
'state': event.state or '',
|
|
|
|
|
'pincode': event.pincode or '',
|
|
|
|
|
'latitude': str(event.latitude) if event.latitude else '',
|
|
|
|
|
'longitude': str(event.longitude) if event.longitude else '',
|
|
|
|
|
'event_type': event.event_type_id,
|
|
|
|
|
'event_status': event.event_status or '',
|
|
|
|
|
'source': event.source or '',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-04-03 08:56:00 +05:30
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
|
2025-12-17 22:05:13 +05:30
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class EventTypeListAPIView(APIView):
|
2026-03-26 09:50:03 +00:00
|
|
|
permission_classes = [AllowAny]
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
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,
|
2026-03-26 09:50:03 +00:00
|
|
|
"event_type_icon": event_type.event_type_icon.url if event_type.event_type_icon else None
|
2025-12-17 22:05:13 +05:30
|
|
|
}
|
|
|
|
|
event_types.append(event_type_data)
|
2026-03-26 09:50:03 +00:00
|
|
|
return JsonResponse({"status": "success", "event_types": event_types})
|
2025-12-17 22:05:13 +05:30
|
|
|
except Exception as e:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "EventTypeAPI exception", request=request, logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
class EventListAPI(APIView):
|
2026-03-26 09:50:03 +00:00
|
|
|
permission_classes = [AllowAny]
|
2025-12-17 22:05:13 +05:30
|
|
|
|
2026-03-30 11:23:03 +00:00
|
|
|
@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',
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 22:05:13 +05:30
|
|
|
def post(self, request):
|
|
|
|
|
try:
|
2026-03-26 09:50:03 +00:00
|
|
|
try:
|
|
|
|
|
data = json.loads(request.body) if request.body else {}
|
|
|
|
|
except Exception:
|
|
|
|
|
data = {}
|
2025-12-17 22:05:13 +05:30
|
|
|
|
2026-03-30 11:23:03 +00:00
|
|
|
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))
|
2026-04-04 17:33:56 +05:30
|
|
|
q = data.get("q", "").strip()
|
2026-03-30 11:23:03 +00:00
|
|
|
|
2026-04-03 08:56:00 +05:30
|
|
|
# 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
|
2026-03-30 11:23:03 +00:00
|
|
|
MIN_EVENTS_THRESHOLD = 6
|
|
|
|
|
qs = Event.objects.all()
|
2026-04-03 08:56:00 +05:30
|
|
|
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':
|
2026-03-30 11:23:03 +00:00
|
|
|
pincode_qs = qs.filter(pincode=pincode)
|
|
|
|
|
if pincode_qs.count() >= MIN_EVENTS_THRESHOLD:
|
|
|
|
|
qs = pincode_qs
|
|
|
|
|
|
2026-04-04 17:33:56 +05:30
|
|
|
# Priority 3: Full-text search on title / description
|
|
|
|
|
if q:
|
|
|
|
|
qs = qs.filter(Q(title__icontains=q) | Q(description__icontains=q))
|
|
|
|
|
|
2026-03-30 11:23:03 +00:00
|
|
|
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)
|
2026-03-26 09:50:03 +00:00
|
|
|
else:
|
2026-03-30 11:23:03 +00:00
|
|
|
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)
|
2026-03-26 09:50:03 +00:00
|
|
|
thumb_map = {img.event_id: img for img in primary_images}
|
|
|
|
|
|
2026-03-30 11:23:03 +00:00
|
|
|
event_list = [self._serialize_event(e, thumb_map) for e in events_page]
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
return JsonResponse({
|
|
|
|
|
"status": "success",
|
2026-03-26 09:50:03 +00:00
|
|
|
"events": event_list,
|
2026-03-30 11:23:03 +00:00
|
|
|
"total_count": total_count,
|
2026-03-26 09:50:03 +00:00
|
|
|
"page": page,
|
|
|
|
|
"page_size": page_size,
|
2026-03-30 11:23:03 +00:00
|
|
|
"has_next": end < total_count,
|
2026-04-03 08:56:00 +05:30
|
|
|
"radius_km": used_radius,
|
2025-12-17 22:05:13 +05:30
|
|
|
})
|
|
|
|
|
except Exception as e:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "EventListAPI exception", request=request, logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
class EventDetailAPI(APIView):
|
2026-03-26 09:50:03 +00:00
|
|
|
permission_classes = [AllowAny]
|
|
|
|
|
|
2025-12-17 22:05:13 +05:30
|
|
|
def post(self, request):
|
|
|
|
|
try:
|
2026-03-26 09:50:03 +00:00
|
|
|
try:
|
|
|
|
|
data = json.loads(request.body) if request.body else {}
|
|
|
|
|
except Exception:
|
|
|
|
|
data = {}
|
2025-12-17 22:05:13 +05:30
|
|
|
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
|
2026-03-26 09:50:03 +00:00
|
|
|
event_img['image'] = ei.event_image.url
|
2025-12-17 22:05:13 +05:30
|
|
|
event_images_list.append(event_img)
|
|
|
|
|
event_data["images"] = event_images_list
|
2026-04-04 17:33:56 +05:30
|
|
|
|
|
|
|
|
# Resolve contributor from contributed_by field
|
|
|
|
|
contributed_by = getattr(events, 'contributed_by', None)
|
|
|
|
|
if contributed_by:
|
|
|
|
|
contributor = _resolve_contributor(contributed_by)
|
|
|
|
|
if contributor:
|
|
|
|
|
event_data["contributor"] = contributor
|
|
|
|
|
|
2025-12-17 22:05:13 +05:30
|
|
|
return JsonResponse(event_data)
|
|
|
|
|
except Exception as e:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "EventDetailAPI exception", request=request, logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
class EventImagesListAPI(APIView):
|
2026-03-30 11:23:03 +00:00
|
|
|
authentication_classes = []
|
|
|
|
|
permission_classes = [AllowAny]
|
2025-12-17 22:05:13 +05:30
|
|
|
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:
|
2026-03-26 09:50:03 +00:00
|
|
|
event_images_list.append(ei.event_image.url)
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
res_data["images"] = event_images_list
|
|
|
|
|
|
|
|
|
|
print(res_data)
|
|
|
|
|
|
|
|
|
|
return JsonResponse(res_data)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "EventImagesListAPI exception", request=request, logger_data={"error": str(e)})
|
2025-12-17 22:05:13 +05:30
|
|
|
return JsonResponse(
|
2026-04-03 09:23:26 +05:30
|
|
|
{"status": "error", "message": "An unexpected server error occurred."},
|
2025-12-17 22:05:13 +05:30
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class EventsByCategoryAPI(APIView):
|
2026-03-30 11:23:03 +00:00
|
|
|
authentication_classes = []
|
|
|
|
|
permission_classes = [AllowAny]
|
2025-12-17 22:05:13 +05:30
|
|
|
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:
|
2026-03-26 09:50:03 +00:00
|
|
|
event['event_image'] = EventImages.objects.get(event=event['id'], is_primary=True).event_image.url
|
2025-12-17 22:05:13 +05:30
|
|
|
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:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "EventsByDateAPI exception", request=request, logger_data={"error": str(e)})
|
2025-12-17 22:05:13 +05:30
|
|
|
return JsonResponse(
|
2026-04-03 09:23:26 +05:30
|
|
|
{"status": "error", "message": "An unexpected server error occurred."},
|
2025-12-17 22:05:13 +05:30
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class EventsByMonthYearAPI(APIView):
|
2026-03-30 11:23:03 +00:00
|
|
|
authentication_classes = []
|
|
|
|
|
permission_classes = [AllowAny]
|
2025-12-17 22:05:13 +05:30
|
|
|
"""
|
|
|
|
|
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
|
2025-12-19 19:35:38 +05:30
|
|
|
# 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)
|
|
|
|
|
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
# 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",
|
2025-12-19 19:35:38 +05:30
|
|
|
"dates": date_strings,
|
2025-12-17 22:05:13 +05:30
|
|
|
"total_number_of_events": total_events,
|
|
|
|
|
"date_events": date_events
|
|
|
|
|
})
|
2026-04-03 09:23:26 +05:30
|
|
|
|
2025-12-17 22:05:13 +05:30
|
|
|
except Exception as e:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "DateSheetAPI exception", request=request, logger_data={"error": str(e)})
|
2025-12-17 22:05:13 +05:30
|
|
|
return JsonResponse(
|
2026-04-03 09:23:26 +05:30
|
|
|
{"status": "error", "message": "An unexpected server error occurred."},
|
2025-12-17 22:05:13 +05:30
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class EventsByDateAPI(APIView):
|
2026-03-30 11:23:03 +00:00
|
|
|
authentication_classes = []
|
|
|
|
|
permission_classes = [AllowAny]
|
2025-12-17 22:05:13 +05:30
|
|
|
"""
|
|
|
|
|
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(
|
2025-12-19 19:35:38 +05:30
|
|
|
start_date=event_date
|
|
|
|
|
).order_by('start_date')
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
event_list = []
|
|
|
|
|
|
|
|
|
|
for e in events:
|
|
|
|
|
data_dict = model_to_dict(e)
|
|
|
|
|
try:
|
|
|
|
|
thumb_img = EventImages.objects.get(event=e.id, is_primary=True)
|
2026-03-26 09:50:03 +00:00
|
|
|
data_dict['thumb_img'] = thumb_img.event_image.url
|
2025-12-17 22:05:13 +05:30
|
|
|
except EventImages.DoesNotExist:
|
|
|
|
|
data_dict['thumb_img'] = ''
|
|
|
|
|
|
|
|
|
|
event_list.append(data_dict)
|
2026-04-03 09:23:26 +05:30
|
|
|
|
2025-12-17 22:05:13 +05:30
|
|
|
return JsonResponse({
|
|
|
|
|
"status": "success",
|
|
|
|
|
"events": event_list
|
|
|
|
|
})
|
2026-04-03 09:23:26 +05:30
|
|
|
|
2025-12-17 22:05:13 +05:30
|
|
|
except Exception as e:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "PincodeEventsAPI exception", request=request, logger_data={"error": str(e)})
|
2025-12-17 22:05:13 +05:30
|
|
|
return JsonResponse(
|
2026-04-03 09:23:26 +05:30
|
|
|
{"status": "error", "message": "An unexpected server error occurred."},
|
2026-03-24 12:20:34 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class FeaturedEventsAPI(APIView):
|
2026-03-30 11:23:03 +00:00
|
|
|
authentication_classes = []
|
|
|
|
|
permission_classes = [AllowAny]
|
2026-03-24 12:20:34 +00:00
|
|
|
"""Returns events where is_featured=True — used for the homepage hero carousel."""
|
|
|
|
|
|
|
|
|
|
def post(self, request):
|
|
|
|
|
try:
|
2026-04-06 19:41:25 +05:30
|
|
|
events = Event.objects.filter(is_featured=True, event_status='published').order_by('-created_date')
|
2026-03-24 12:20:34 +00:00
|
|
|
event_list = []
|
|
|
|
|
for e in events:
|
|
|
|
|
data_dict = model_to_dict(e)
|
|
|
|
|
try:
|
|
|
|
|
thumb = EventImages.objects.get(event=e.id, is_primary=True)
|
2026-03-26 09:50:03 +00:00
|
|
|
data_dict['thumb_img'] = thumb.event_image.url
|
2026-03-24 12:20:34 +00:00
|
|
|
except EventImages.DoesNotExist:
|
|
|
|
|
data_dict['thumb_img'] = ''
|
|
|
|
|
event_list.append(data_dict)
|
|
|
|
|
|
2026-03-24 12:24:57 +00:00
|
|
|
return JsonResponse({"status": "success", "events": event_list})
|
2026-03-24 12:20:34 +00:00
|
|
|
except Exception as e:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "FeaturedEventsAPI exception", request=request, logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|
2026-03-24 12:20:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class TopEventsAPI(APIView):
|
2026-03-30 11:23:03 +00:00
|
|
|
authentication_classes = []
|
|
|
|
|
permission_classes = [AllowAny]
|
2026-03-24 12:20:34 +00:00
|
|
|
"""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)
|
2026-03-26 09:50:03 +00:00
|
|
|
data_dict['thumb_img'] = thumb.event_image.url
|
2026-03-24 12:20:34 +00:00
|
|
|
except EventImages.DoesNotExist:
|
|
|
|
|
data_dict['thumb_img'] = ''
|
|
|
|
|
event_list.append(data_dict)
|
|
|
|
|
|
2026-03-24 12:24:57 +00:00
|
|
|
return JsonResponse({"status": "success", "events": event_list})
|
2026-03-24 12:20:34 +00:00
|
|
|
except Exception as e:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "TopEventsAPI exception", request=request, logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|
2026-04-04 17:33:56 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class ContributorProfileAPI(APIView):
|
|
|
|
|
"""
|
|
|
|
|
Public API to fetch a contributor's profile and their events.
|
|
|
|
|
POST /api/events/contributor-profile/
|
|
|
|
|
Body: { "contributor_id": "EVT-XXXXXXXX" } (or email)
|
|
|
|
|
"""
|
|
|
|
|
authentication_classes = []
|
|
|
|
|
permission_classes = [AllowAny]
|
|
|
|
|
|
|
|
|
|
def post(self, request):
|
|
|
|
|
try:
|
|
|
|
|
try:
|
|
|
|
|
data = json.loads(request.body) if request.body else {}
|
|
|
|
|
except Exception:
|
|
|
|
|
data = {}
|
|
|
|
|
|
|
|
|
|
contributor_id = data.get("contributor_id", "").strip()
|
|
|
|
|
if not contributor_id:
|
|
|
|
|
return JsonResponse(
|
|
|
|
|
{"status": "error", "message": "contributor_id is required"},
|
|
|
|
|
status=400,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Resolve user
|
|
|
|
|
contributor = _resolve_contributor(contributor_id)
|
|
|
|
|
if not contributor:
|
|
|
|
|
return JsonResponse(
|
|
|
|
|
{"status": "error", "message": "Contributor not found"},
|
|
|
|
|
status=404,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Fetch this contributor's events
|
|
|
|
|
user_identifiers = [v for v in [contributor['eventify_id'], contributor['email']] if v]
|
|
|
|
|
events_qs = Event.objects.filter(
|
|
|
|
|
contributed_by__in=user_identifiers,
|
|
|
|
|
event_status__in=['published', 'live', 'completed'],
|
|
|
|
|
).order_by('-start_date', '-created_date')
|
|
|
|
|
|
|
|
|
|
events_list = [_serialize_event_for_contributor(e) for e in events_qs]
|
|
|
|
|
|
|
|
|
|
return JsonResponse({
|
|
|
|
|
"status": "success",
|
|
|
|
|
"contributor": contributor,
|
|
|
|
|
"events": events_list,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log("error", "ContributorProfileAPI exception", request=request, logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})
|