Files
eventify_backend/admin_api/views.py
Sicherhaven a3d1bbad30 fix: scope users API to end-users and tag new registrations as customers
- UserListView and UserMetricsView now filter is_superuser=False so only
  end-user accounts appear in the admin Users page (not admin/staff)
- _serialize_user now returns avatarUrl from profile_picture field so the
  grid view renders profile images instead of broken img tags
- RegisterForm and WebRegisterForm now set is_customer=True and
  role='customer' on save so future registrants are correctly classified
2026-03-25 11:10:29 +05:30

973 lines
36 KiB
Python

from django.contrib.auth import authenticate, get_user_model
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.views import TokenRefreshView
from django.db import connection
from .serializers import UserSerializer
User = get_user_model()
class AdminLoginView(APIView):
permission_classes = [AllowAny]
def post(self, request):
identifier = request.data.get('username') or request.data.get('email')
password = request.data.get('password')
if not identifier or not password:
return Response({'error': 'username/email and password required'}, status=status.HTTP_400_BAD_REQUEST)
# Try username first, then email
user = authenticate(request, username=identifier, password=password)
if not user:
try:
u = User.objects.get(email=identifier)
user = authenticate(request, username=u.username, password=password)
except User.DoesNotExist:
pass
if not user:
return Response({'error': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)
if not user.is_active:
return Response({'error': 'Account is disabled'}, status=status.HTTP_403_FORBIDDEN)
refresh = RefreshToken.for_user(user)
return Response({
'access': str(refresh.access_token),
'refresh': str(refresh),
'user': UserSerializer(user).data,
})
class MeView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({'user': UserSerializer(request.user).data})
class HealthView(APIView):
permission_classes = [AllowAny]
def get(self, request):
try:
connection.ensure_connection()
db_status = 'ok'
except Exception:
db_status = 'error'
return Response({'status': 'ok', 'db': db_status})
# ---------------------------------------------------------------------------
# Phase 2: Dashboard Views
# ---------------------------------------------------------------------------
class DashboardMetricsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from ledger.models import RazorpayTransaction
from partner.models import Partner
from events.models import Event
from bookings.models import Ticket, Booking
from django.db.models import Sum
from django.utils import timezone
import datetime
today = timezone.now().date()
# --- Revenue ---
total_paise = (
RazorpayTransaction.objects
.filter(status='captured')
.aggregate(total=Sum('amount'))['total'] or 0
)
total_revenue = total_paise / 100
# This-month / last-month revenue for growth
first_of_this_month = today.replace(day=1)
first_of_last_month = (first_of_this_month - datetime.timedelta(days=1)).replace(day=1)
this_month_paise = (
RazorpayTransaction.objects
.filter(status='captured', captured_at__date__gte=first_of_this_month)
.aggregate(total=Sum('amount'))['total'] or 0
)
last_month_paise = (
RazorpayTransaction.objects
.filter(
status='captured',
captured_at__date__gte=first_of_last_month,
captured_at__date__lt=first_of_this_month,
)
.aggregate(total=Sum('amount'))['total'] or 0
)
this_month_rev = this_month_paise / 100
last_month_rev = last_month_paise / 100
revenue_growth = (
round((this_month_rev - last_month_rev) / last_month_rev * 100, 2)
if last_month_rev else 0
)
# --- Partners ---
active_partners = Partner.objects.filter(status='active').count()
pending_partners = Partner.objects.filter(kyc_compliance_status='pending').count()
# --- Events ---
live_events = Event.objects.filter(event_status='live').count()
events_today = Event.objects.filter(start_date=today).count()
# --- Tickets ---
ticket_sales = Ticket.objects.count()
# This-week / last-week ticket growth
week_start = today - datetime.timedelta(days=today.weekday()) # Monday
last_week_start = week_start - datetime.timedelta(days=7)
this_week_tickets = Ticket.objects.filter(
booking__created_date__gte=week_start
).count()
last_week_tickets = Ticket.objects.filter(
booking__created_date__gte=last_week_start,
booking__created_date__lt=week_start,
).count()
ticket_growth = (
round((this_week_tickets - last_week_tickets) / last_week_tickets * 100, 2)
if last_week_tickets else 0
)
return Response({
'totalRevenue': total_revenue,
'revenueGrowth': revenue_growth,
'activePartners': active_partners,
'pendingPartners': pending_partners,
'liveEvents': live_events,
'eventsToday': events_today,
'ticketSales': ticket_sales,
'ticketGrowth': ticket_growth,
})
class DashboardRevenueView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from ledger.models import RazorpayTransaction
from banking_operations.models import PaymentTransaction
from django.db.models import Sum
from django.utils import timezone
import datetime
today = timezone.now().date()
result = []
for i in range(6, -1, -1):
day = today - datetime.timedelta(days=i)
rev_paise = (
RazorpayTransaction.objects
.filter(status='captured', captured_at__date=day)
.aggregate(total=Sum('amount'))['total'] or 0
)
revenue = rev_paise / 100
payouts = (
PaymentTransaction.objects
.filter(
payment_type='debit',
payment_transaction_status='completed',
payment_transaction_date=day,
)
.aggregate(total=Sum('payment_transaction_amount'))['total'] or 0
)
result.append({
'day': day.strftime('%a'),
'revenue': float(revenue),
'payouts': float(payouts),
})
return Response(result)
class DashboardActivityView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from partner.models import Partner
from events.models import Event
from bookings.models import Booking
from django.utils import timezone
items = []
# --- Partners (last 5 by id desc; no date field — timestamp=None, filtered out later) ---
for p in Partner.objects.order_by('-id')[:5]:
items.append({
'id': f'partner-{p.id}',
'type': 'partner',
'title': f'{p.name} registered',
'description': f'New partner — {getattr(p, "partner_type", "individual")}',
'timestamp': None,
'status': p.kyc_compliance_status,
})
# --- Events (last 5 by created_date desc) ---
for e in Event.objects.order_by('-created_date')[:5]:
display_name = e.title if getattr(e, 'title', None) else getattr(e, 'name', '')
ts = e.created_date
items.append({
'id': f'event-{e.id}',
'type': 'event',
'title': f'{display_name} created',
'description': f'Event status: {e.event_status}',
'timestamp': ts.isoformat() if ts else None,
'status': e.event_status,
})
# --- Bookings (last 5 by id desc) ---
for b in Booking.objects.order_by('-id')[:5]:
ts = b.created_date
items.append({
'id': f'booking-{b.booking_id}',
'type': 'booking',
'title': f'Booking {b.booking_id} placed',
'description': f'{b.quantity} ticket(s) at ₹{b.price}',
'timestamp': ts.isoformat() if ts else None,
'status': 'confirmed',
})
# Filter out items with no timestamp, sort desc, return top 10
dated = [item for item in items if item['timestamp'] is not None]
dated.sort(key=lambda x: x['timestamp'], reverse=True)
return Response(dated[:10])
class DashboardActionsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from partner.models import Partner
from events.models import Event
from banking_operations.models import PaymentTransaction
from django.db.models import Sum
kyc_count = Partner.objects.filter(kyc_compliance_status='pending').count()
flagged_count = Event.objects.filter(event_status='flagged').count()
pending_payouts = float(
PaymentTransaction.objects
.filter(payment_type='debit', payment_transaction_status='pending')
.aggregate(total=Sum('payment_transaction_amount'))['total'] or 0
)
actions = [
{
'id': 'kyc',
'type': 'kyc',
'count': kyc_count,
'title': 'Partner Approval Queue',
'description': f'{kyc_count} partners awaiting KYC review',
'href': '/partners?filter=pending',
'priority': 'high',
},
{
'id': 'flagged',
'type': 'flagged',
'count': flagged_count,
'title': 'Flagged Events',
'description': f'{flagged_count} events reported for review',
'href': '/events?filter=flagged',
'priority': 'high',
},
{
'id': 'payout',
'type': 'payout',
'count': pending_payouts,
'title': 'Pending Payouts',
'description': f'{pending_payouts:,.0f} ready for release',
'href': '/financials?tab=payouts',
'priority': 'medium',
},
]
return Response(actions)
# ---------------------------------------------------------------------------
# Phase 3: Partner helpers
# ---------------------------------------------------------------------------
_PARTNER_STATUS_MAP = {
'active': 'Active', 'pending': 'Invited', 'suspended': 'Suspended',
'archived': 'Archived', 'deleted': 'Archived', 'inactive': 'Archived',
}
_PARTNER_KYC_MAP = {'approved': 'Verified', 'rejected': 'Rejected'}
_PARTNER_TYPE_MAP = {
'venue': 'Venue', 'promoter': 'Promoter', 'sponsor': 'Sponsor',
'vendor': 'Vendor', 'affiliate': 'Affiliate', 'other': 'Other',
}
_RISK_MAP = {
'high_risk': 80, 'medium_risk': 45, 'low_risk': 15,
'rejected': 90, 'pending': 30, 'approved': 5,
}
def _partner_kyc_docs(p):
if not p.kyc_compliance_document_type:
return []
return [{
'id': f'kyc-{p.id}',
'partnerId': str(p.id),
'type': p.kyc_compliance_document_type.upper(),
'name': p.kyc_compliance_document_other_type or p.kyc_compliance_document_type,
'url': p.kyc_compliance_document_file.url if p.kyc_compliance_document_file else '',
'status': {'approved': 'APPROVED', 'rejected': 'REJECTED'}.get(p.kyc_compliance_status, 'PENDING'),
'mandatory': True,
'adminNote': p.kyc_compliance_reason or '',
'uploadedBy': p.primary_contact_person_name,
'uploadedAt': '',
}]
def _serialize_partner(p, events_count=0):
addr = ', '.join(filter(None, [p.address, p.city, p.state, p.country]))
return {
'id': str(p.id),
'name': p.name,
'type': _PARTNER_TYPE_MAP.get(p.partner_type, 'Other'),
'status': _PARTNER_STATUS_MAP.get(p.status, 'Invited'),
'primaryContact': {
'name': p.primary_contact_person_name,
'email': p.primary_contact_person_email,
'phone': p.primary_contact_person_phone,
},
'companyDetails': {
'website': p.website_url or '',
'address': addr,
},
'metrics': {
'eventsCount': events_count,
'totalRevenue': 0,
'openBalance': 0,
'activeDeals': 0,
'lastActivity': None,
},
'verificationStatus': _PARTNER_KYC_MAP.get(p.kyc_compliance_status, 'Pending'),
'kycComplianceStatus': p.kyc_compliance_status,
'riskScore': _RISK_MAP.get(p.kyc_compliance_status, 0),
'joinedAt': None,
'tags': [],
'notes': p.kyc_compliance_reason or '',
'kycDocuments': _partner_kyc_docs(p),
}
# ---------------------------------------------------------------------------
# Phase 3: Partner Views
# ---------------------------------------------------------------------------
class PartnerStatsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from partner.models import Partner
return Response({
'total': Partner.objects.count(),
'active': Partner.objects.filter(status='active').count(),
'pendingKyc': Partner.objects.filter(kyc_compliance_status='pending').count(),
'highRisk': Partner.objects.filter(kyc_compliance_status='high_risk').count(),
})
class PartnerListView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from partner.models import Partner
from django.db.models import Count, Q
qs = Partner.objects.annotate(events_count=Count('event'))
if s := request.GET.get('status'): qs = qs.filter(status=s)
if k := request.GET.get('kyc_status'): qs = qs.filter(kyc_compliance_status=k)
if t := request.GET.get('partner_type'): qs = qs.filter(partner_type=t)
if q := request.GET.get('search'):
qs = qs.filter(
Q(name__icontains=q) |
Q(primary_contact_person_email__icontains=q) |
Q(primary_contact_person_name__icontains=q)
)
page = max(1, int(request.GET.get('page', 1)))
page_size = min(100, int(request.GET.get('page_size', 20)))
total = qs.count()
partners = qs.order_by('-id')[(page - 1) * page_size: page * page_size]
return Response({
'count': total,
'results': [_serialize_partner(p, p.events_count) for p in partners],
})
class PartnerDetailView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk):
from partner.models import Partner
from events.models import Event
from django.shortcuts import get_object_or_404
p = get_object_or_404(Partner, pk=pk)
events_count = Event.objects.filter(partner_id=pk).count()
data = _serialize_partner(p, events_count)
_EVENT_STATUS_MAP = {
'live': 'LIVE', 'published': 'LIVE', 'draft': 'DRAFT',
'cancelled': 'CANCELLED', 'flagged': 'PENDING_REVIEW', 'pending': 'PENDING_REVIEW',
}
events_qs = Event.objects.filter(partner_id=pk).order_by('-id')[:20]
data['events'] = [{
'id': str(e.id),
'partnerId': str(pk),
'title': e.title or e.name or '',
'date': e.start_date.isoformat() if e.start_date else '',
'venue': e.venue_name or '',
'category': '',
'ticketPrice': 0,
'totalTickets': 0,
'ticketsSold': 0,
'revenue': 0,
'status': _EVENT_STATUS_MAP.get(e.event_status, 'DRAFT'),
'submittedAt': e.created_date.isoformat() if e.created_date else '',
'createdAt': e.created_date.isoformat() if e.created_date else '',
'rejectionReason': '',
} for e in events_qs]
data['dealTerms'] = []
data['ledger'] = []
return Response(data)
class PartnerStatusView(APIView):
permission_classes = [IsAuthenticated]
def patch(self, request, pk):
from partner.models import Partner
from django.shortcuts import get_object_or_404
p = get_object_or_404(Partner, pk=pk)
new_status = request.data.get('status')
valid = ('active', 'suspended', 'inactive', 'archived')
if new_status not in valid:
return Response({'error': f'status must be one of {valid}'}, status=400)
p.status = new_status
p.kyc_compliance_reason = request.data.get('reason', p.kyc_compliance_reason or '')
p.save(update_fields=['status', 'kyc_compliance_reason'])
return Response({'id': str(p.id), 'status': p.status})
class PartnerKYCReviewView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request, pk):
from partner.models import Partner
from django.shortcuts import get_object_or_404
p = get_object_or_404(Partner, pk=pk)
decision = request.data.get('decision')
if decision not in ('approved', 'rejected'):
return Response({'error': 'decision must be approved or rejected'}, status=400)
p.kyc_compliance_status = decision
p.is_kyc_compliant = (decision == 'approved')
p.kyc_compliance_reason = request.data.get('reason', '')
p.save(update_fields=['kyc_compliance_status', 'is_kyc_compliant', 'kyc_compliance_reason'])
return Response({
'id': str(p.id),
'kyc_compliance_status': p.kyc_compliance_status,
'is_kyc_compliant': p.is_kyc_compliant,
'verificationStatus': 'Verified' if decision == 'approved' else 'Rejected',
})
# ---------------------------------------------------------------------------
# Phase 4: Users API
# ---------------------------------------------------------------------------
def _user_status(u):
return 'Active' if u.is_active else 'Suspended'
_USER_ROLE_MAP = {
'admin': 'Admin', 'manager': 'Admin',
'staff': 'Support Agent',
'customer': 'User', 'is_user': 'User',
'partner': 'Partner', 'partner_manager': 'Partner',
'partner_staff': 'Partner', 'partner_customer': 'Partner',
}
def _serialize_user(u):
full_name = f'{u.first_name} {u.last_name}'.strip() or u.username
role_key = u.role if u.role else ('customer' if getattr(u, 'is_customer', False) else 'staff')
try:
pic = u.profile_picture
avatar = pic.url if pic and pic.name and pic.name != 'default.png' else ''
except Exception:
avatar = ''
return {
'id': str(u.id),
'name': full_name,
'email': u.email,
'phone': getattr(u, 'phone_number', '') or '',
'countryCode': '+91',
'avatarUrl': avatar,
'role': _USER_ROLE_MAP.get(role_key, 'User'),
'status': _user_status(u),
'tier': 'Bronze',
'healthScore': 'warm',
'isVerified': u.is_active,
'is2FAEnabled': False,
'totalSpent': 0,
'bookingsCount': 0,
'refundRate': 0,
'averageOrderValue': 0,
'language': 'en',
'timezone': 'Asia/Kolkata',
'currency': 'INR',
'emailNotifications': True,
'pushNotifications': True,
'smsNotifications': True,
'tags': [],
'pinnedNote': '',
'createdAt': u.date_joined.isoformat() if u.date_joined else '',
'updatedAt': u.date_joined.isoformat() if u.date_joined else '',
'lastLoginAt': u.last_login.isoformat() if u.last_login else '',
'lastActivityAt': u.last_login.isoformat() if u.last_login else '',
}
class UserMetricsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.utils import timezone
import datetime
User = get_user_model()
today = timezone.now().date()
week_ago = today - datetime.timedelta(days=7)
# Customers = all non-superuser accounts (end users registered via mobile/web)
customer_qs = User.objects.filter(is_superuser=False)
return Response({
'total': customer_qs.count(),
'active': customer_qs.filter(is_active=True).count(),
'suspended': customer_qs.filter(is_active=False).count(),
'newThisWeek': customer_qs.filter(date_joined__date__gte=week_ago).count(),
})
class UserListView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from django.contrib.auth import get_user_model
from django.db.models import Q
User = get_user_model()
# Customers = all non-superuser accounts (end users registered via mobile/web)
qs = User.objects.filter(is_superuser=False)
if s := request.GET.get('status'):
if s == 'Active':
qs = qs.filter(is_active=True)
elif s in ('Suspended', 'Banned'):
qs = qs.filter(is_active=False)
if r := request.GET.get('role'):
role_reverse = {v: k for k, v in _USER_ROLE_MAP.items()}
backend_role = role_reverse.get(r)
if backend_role:
qs = qs.filter(role=backend_role)
if q := request.GET.get('search'):
qs = qs.filter(
Q(username__icontains=q) | Q(email__icontains=q) |
Q(first_name__icontains=q) | Q(last_name__icontains=q) |
Q(phone_number__icontains=q)
)
try:
page = max(1, int(request.GET.get('page', 1)))
page_size = min(100, int(request.GET.get('page_size', 20)))
except (ValueError, TypeError):
page, page_size = 1, 20
total = qs.count()
users = qs.order_by('-date_joined')[(page - 1) * page_size: page * page_size]
return Response({'count': total, 'results': [_serialize_user(u) for u in users]})
class UserDetailView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk):
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
User = get_user_model()
u = get_object_or_404(User, pk=pk)
return Response(_serialize_user(u))
class UserStatusView(APIView):
permission_classes = [IsAuthenticated]
def patch(self, request, pk):
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
User = get_user_model()
u = get_object_or_404(User, pk=pk)
action = request.data.get('action')
if action in ('suspend', 'ban'):
u.is_active = False
elif action == 'reinstate':
u.is_active = True
else:
return Response({'error': 'action must be suspend, ban, or reinstate'}, status=400)
u.save(update_fields=['is_active'])
return Response({'id': str(u.id), 'status': _user_status(u)})
# ---------------------------------------------------------------------------
# Phase 5: Events API
# ---------------------------------------------------------------------------
_EVENT_STATUS_MAP = {
'live': 'live', 'published': 'published',
'pending': 'published', 'created': 'draft',
'flagged': 'flagged', 'cancelled': 'cancelled',
'postponed': 'cancelled', 'completed': 'completed',
}
def _serialize_event(e):
partner_name = ''
if e.partner_id:
try:
partner_name = e.partner.name
except Exception:
partner_name = ''
return {
'id': str(e.id),
'title': e.title or getattr(e, 'name', '') or '',
'partnerId': str(e.partner_id) if e.partner_id else '',
'partnerName': partner_name,
'date': e.start_date.isoformat() if e.start_date else '',
'status': _EVENT_STATUS_MAP.get(e.event_status, 'draft'),
'ticketsSold': 0,
'revenue': 0,
'venueName': e.venue_name or '',
'createdAt': e.created_date.isoformat() if e.created_date else '',
'isFeatured': bool(e.is_featured),
'isTopEvent': bool(e.is_top_event),
}
class EventStatsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from events.models import Event
return Response({
'total': Event.objects.count(),
'live': Event.objects.filter(event_status='live').count(),
'pending': Event.objects.filter(event_status__in=['pending', 'created']).count(),
'flagged': Event.objects.filter(event_status='flagged').count(),
'published': Event.objects.filter(event_status='published').count(),
})
class EventListView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from events.models import Event
from django.db.models import Q
qs = Event.objects.select_related('partner').all()
if s := request.GET.get('status'):
reverse_map = {v: k for k, v in _EVENT_STATUS_MAP.items()}
backend_status = reverse_map.get(s, s)
qs = qs.filter(event_status=backend_status)
if pid := request.GET.get('partner_id'):
qs = qs.filter(partner_id=pid)
if q := request.GET.get('search'):
qs = qs.filter(
Q(title__icontains=q) | Q(name__icontains=q) | Q(venue_name__icontains=q)
)
try:
page = max(1, int(request.GET.get('page', 1)))
page_size = min(100, int(request.GET.get('page_size', 20)))
except (ValueError, TypeError):
page, page_size = 1, 20
total = qs.count()
events = qs.order_by('-id')[(page - 1) * page_size: page * page_size]
return Response({'count': total, 'results': [_serialize_event(e) for e in events]})
class EventDetailView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk):
from events.models import Event
from django.shortcuts import get_object_or_404
e = get_object_or_404(Event.objects.select_related('partner'), pk=pk)
return Response(_serialize_event(e))
class EventModerationView(APIView):
permission_classes = [IsAuthenticated]
def patch(self, request, pk):
from events.models import Event
from django.shortcuts import get_object_or_404
e = get_object_or_404(Event, pk=pk)
action = request.data.get('action')
if action == 'approve':
e.event_status = 'published'
e.save(update_fields=['event_status'])
elif action == 'reject':
e.event_status = 'cancelled'
e.cancelled_reason = request.data.get('reason', '')
e.save(update_fields=['event_status', 'cancelled_reason'])
elif action == 'flag':
e.event_status = 'flagged'
e.save(update_fields=['event_status'])
elif action == 'feature':
e.is_featured = True
e.save(update_fields=['is_featured'])
elif action == 'unfeature':
e.is_featured = False
e.save(update_fields=['is_featured'])
else:
return Response({'error': 'Invalid action'}, status=400)
e.refresh_from_db()
return Response(_serialize_event(e))
# ---------------------------------------------------------------------------
# Phase 6: Financials & Payouts
# ---------------------------------------------------------------------------
_TX_STATUS_MAP = {
'captured': 'Completed',
'created': 'Pending',
'failed': 'Failed',
'refunded': 'Failed',
}
def _serialize_transaction(t):
ts = t.captured_at or t.created_at
order_ref = getattr(t, 'razorpay_order_id', None) or getattr(t, 'transaction_id', None)
return {
'id': str(t.id),
'title': f'Payment {order_ref}' if order_ref else f'Transaction #{t.id}',
'partner': '',
'amount': t.amount / 100,
'date': ts.isoformat() if ts else '',
'type': 'in',
'method': 'Razorpay',
'fees': 0,
'net': t.amount / 100,
'status': _TX_STATUS_MAP.get(t.status, 'Pending'),
}
_SETTLEMENT_STATUS_MAP = {
'pending': 'Ready',
'failed': 'Overdue',
'cancelled': 'Overdue',
'completed': 'On Hold',
'refunded': 'On Hold',
}
def _serialize_settlement(p):
return {
'id': str(p.id),
'partnerName': '',
'eventName': '',
'amount': float(p.payment_transaction_amount),
'dueDate': p.payment_transaction_date.isoformat() if p.payment_transaction_date else '',
'status': _SETTLEMENT_STATUS_MAP.get(p.payment_transaction_status, 'On Hold'),
}
class FinancialMetricsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from ledger.models import RazorpayTransaction
from banking_operations.models import PaymentTransaction
from django.db.models import Sum
total_paise = (
RazorpayTransaction.objects.filter(status='captured')
.aggregate(t=Sum('amount'))['t'] or 0
)
total_revenue = total_paise / 100
total_payouts = float(
PaymentTransaction.objects
.filter(payment_type='debit', payment_transaction_status='completed')
.aggregate(t=Sum('payment_transaction_amount'))['t'] or 0
)
platform_earnings = round(total_revenue * 0.12, 2)
pending_count = PaymentTransaction.objects.filter(
payment_type='debit', payment_transaction_status='pending'
).count()
pending_amount = float(
PaymentTransaction.objects
.filter(payment_type='debit', payment_transaction_status='pending')
.aggregate(t=Sum('payment_transaction_amount'))['t'] or 0
)
return Response({
'totalRevenue': total_revenue,
'totalPayouts': total_payouts,
'platformEarnings': platform_earnings,
'pendingPayouts': pending_count,
'pendingPayoutAmount': pending_amount,
})
class TransactionListView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from ledger.models import RazorpayTransaction
try:
page = max(1, int(request.GET.get('page', 1)))
page_size = min(100, int(request.GET.get('page_size', 20)))
except (ValueError, TypeError):
page, page_size = 1, 20
qs = RazorpayTransaction.objects.order_by('-id')
total = qs.count()
txs = qs[(page - 1) * page_size: page * page_size]
return Response({'count': total, 'results': [_serialize_transaction(t) for t in txs]})
class SettlementListView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from banking_operations.models import PaymentTransaction
qs = PaymentTransaction.objects.filter(
payment_type='debit'
).order_by('-id')[:50]
return Response([_serialize_settlement(p) for p in qs])
class SettlementReleaseView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request, pk):
from banking_operations.models import PaymentTransaction
from django.shortcuts import get_object_or_404
p = get_object_or_404(PaymentTransaction, pk=pk, payment_type='debit')
p.payment_transaction_status = 'completed'
p.save(update_fields=['payment_transaction_status'])
return Response(_serialize_settlement(p))
# ---------------------------------------------------------------------------
# Phase 7: Reviews Moderation
# ---------------------------------------------------------------------------
def _reviewer_rank(total_reviews):
if total_reviews >= 41:
return 'Legend'
if total_reviews >= 21:
return 'Champion'
if total_reviews >= 11:
return 'Enthusiast'
if total_reviews >= 4:
return 'Contributor'
return 'Explorer'
def _serialize_review(r):
from admin_api.models import Review
try:
name = r.reviewer.get_full_name() or r.reviewer.username
email = r.reviewer.email
total = Review.objects.filter(reviewer=r.reviewer, status='live').count()
except Exception:
name, email, total = '', '', 0
try:
event_name = getattr(r.event, 'title', None) or getattr(r.event, 'name', '') or ''
event_id = str(r.event.id)
start = getattr(r.event, 'start_date', None)
event_date = start.isoformat() if start else ''
except Exception:
event_name, event_id, event_date = '', '', ''
return {
'id': str(r.id),
'reviewerName': name,
'reviewerEmail': email,
'reviewerAvatar': '',
'eventName': event_name,
'eventId': event_id,
'eventDate': event_date,
'rating': r.rating,
'reviewText': r.review_text,
'submissionDate': r.submission_date.isoformat(),
'status': r.status,
'reviewerRank': _reviewer_rank(total),
'reviewerTotalReviews': total,
'rejectReason': r.reject_reason or '',
}
class ReviewMetricsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from admin_api.models import Review
return Response({
'totalPending': Review.objects.filter(status='pending').count(),
'liveReviews': Review.objects.filter(status='live').count(),
'rejected': Review.objects.filter(status='rejected').count(),
})
class ReviewListView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
from admin_api.models import Review
qs = Review.objects.select_related('reviewer', 'event').order_by('-submission_date')
status = request.GET.get('status')
if status in ('pending', 'live', 'rejected'):
qs = qs.filter(status=status)
try:
page = max(1, int(request.GET.get('page', 1)))
page_size = min(100, int(request.GET.get('page_size', 20)))
except (ValueError, TypeError):
page, page_size = 1, 20
total = qs.count()
reviews = qs[(page - 1) * page_size: page * page_size]
return Response({'count': total, 'results': [_serialize_review(r) for r in reviews]})
class ReviewModerationView(APIView):
permission_classes = [IsAuthenticated]
def patch(self, request, pk):
from admin_api.models import Review
from django.shortcuts import get_object_or_404
review = get_object_or_404(Review, pk=pk)
action = request.data.get('action')
if action == 'approve':
review.status = 'live'
elif action == 'reject':
rr = request.data.get('reject_reason', 'spam')
valid_reasons = [c[0] for c in Review.REJECT_CHOICES]
if rr not in valid_reasons:
return Response({'error': 'Invalid reject_reason'}, status=400)
review.status = 'rejected'
review.reject_reason = rr
elif action == 'save_and_approve':
review.review_text = request.data.get('review_text', review.review_text)
review.status = 'live'
elif action == 'save_live':
review.review_text = request.data.get('review_text', review.review_text)
else:
return Response({'error': 'Invalid action'}, status=400)
review.save()
return Response(_serialize_review(review))
class ReviewDeleteView(APIView):
permission_classes = [IsAuthenticated]
def delete(self, request, pk):
from admin_api.models import Review
from django.shortcuts import get_object_or_404
review = get_object_or_404(Review, pk=pk)
review.delete()
return Response(status=204)