feat: add source field with 3 options, fix EventListAPI fallback, add is_eventify_event to API response

- Event.source field updated: eventify, community, partner (radio select in form)
- EventListAPI: fallback to all events when pincode returns < 6
- EventListAPI: include is_eventify_event and source in serializer
- Admin API: add source to list serializer
- Django admin: source in list_display, list_filter, list_editable
- Event form template: proper radio button rendering for source field

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 11:23:03 +00:00
parent 388057b641
commit 43123d0ff1
19 changed files with 1381 additions and 38 deletions

View File

@@ -37,6 +37,36 @@ class EventTypeListAPIView(APIView):
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:
@@ -44,40 +74,53 @@ class EventListAPI(APIView):
except Exception:
data = {}
paginate = "page" in data
page = int(data.get("page", 1))
page_size = int(data.get("page_size", 15))
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))
events = Event.objects.select_related('event_type').order_by('-created_date')
# Build base queryset (lazy - no DB hit yet)
MIN_EVENTS_THRESHOLD = 6
qs = Event.objects.all()
if pincode and pincode != 'all':
pincode_qs = qs.filter(pincode=pincode)
# Fallback to all events if pincode has too few
if pincode_qs.count() >= MIN_EVENTS_THRESHOLD:
qs = pincode_qs
# else: keep qs as Event.objects.all()
total = events.count()
if paginate:
start = (page - 1) * page_size
end = start + page_size
page_qs = list(events[start:end])
if per_type > 0 and page == 1:
# Diverse mode: one bounded query per event type
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:
page_qs = list(events)
start, end = 0, total
# Standard pagination at DB level
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])
event_ids = [e.id for e in page_qs]
primary_images = EventImages.objects.filter(event_id__in=event_ids, is_primary=True)
# Fetch images ONLY for the events we will return
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 = []
for e in page_qs:
d = model_to_dict(e)
img = thumb_map.get(e.id)
d['thumb_img'] = img.event_image.url if img else ''
event_list.append(d)
# Serialize with direct attribute access (fast)
event_list = [self._serialize_event(e, thumb_map) for e in events_page]
return JsonResponse({
"status": "success",
"events": event_list,
"total": total,
"total_count": total_count,
"page": page,
"page_size": page_size,
"has_next": end < total,
"has_next": end < total_count,
})
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)})
@@ -110,6 +153,8 @@ class EventDetailAPI(APIView):
class EventImagesListAPI(APIView):
authentication_classes = []
permission_classes = [AllowAny]
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
@@ -139,6 +184,8 @@ class EventImagesListAPI(APIView):
@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)
@@ -176,6 +223,8 @@ class EventsByCategoryAPI(APIView):
@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.
@@ -298,6 +347,8 @@ class EventsByMonthYearAPI(APIView):
@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.
@@ -354,6 +405,8 @@ class EventsByDateAPI(APIView):
@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):
@@ -380,6 +433,8 @@ class FeaturedEventsAPI(APIView):
@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):