from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated, IsAdminUser from django.utils import timezone from datetime import timedelta from django.db.models import Count, Sum, Q from django.db.models.functions import TruncDate from rest_framework.authentication import TokenAuthentication from apps.account.models import User, LoginHistory from apps.hadis.models import Hadis, HadisStatus, HadisCategory, Transmitters, BookReference from apps.library.models import Book from apps.article.models import Article from apps.video.models import Video from apps.podcast.models import Podcast from apps.bookmark.models import Bookmark def extract_text_from_json(value): if not value: return "" if isinstance(value, list): for item in value: if isinstance(item, dict): text = item.get('title') or item.get('value') or item.get('text') or item.get('name') if text: return str(text) else: if item: return str(item) return "" if isinstance(value, dict): for lang in ("fa", "en", "ru"): if lang in value and value[lang]: v = value[lang] if isinstance(v, dict): return str(v.get('title') or v.get('value') or v.get('text') or v.get('name') or "") return str(v) for v in value.values(): if isinstance(v, dict): txt = v.get('title') or v.get('value') or v.get('text') or v.get('name') if txt: return str(txt) elif v: return str(v) return "" if isinstance(value, (str, int, float)): return str(value) return "" class AdminDashboardStatsView(APIView): authentication_classes = [TokenAuthentication] permission_classes = [IsAuthenticated, IsAdminUser] def get(self, request): now = timezone.now() thirty_days_ago = now - timedelta(days=30) # --------------------------------------------------------- # 1. CARDS & BASIC STATS # --------------------------------------------------------- total_arguments = Hadis.objects.count() library_books = Book.objects.count() source_references = BookReference.objects.count() total_transmitters = Transmitters.objects.count() # Media Items: Video + Podcast count media_items = Video.objects.count() + Podcast.objects.count() scientific_articles = Article.objects.count() registered_users = User.objects.filter(is_active=True).count() total_bookmarks = Bookmark.objects.filter(status=True).count() # --------------------------------------------------------- # 2. CHARTS DATA # --------------------------------------------------------- # Argument Types (Donut Chart 1) argument_types_qs = Hadis.objects.exclude(category__isnull=True).values('category__source_type').annotate(total=Count('id')) argument_types_chart = [ {"name": item['category__source_type'], "value": item['total']} for item in argument_types_qs ] # Hadith Status (Donut Chart 2) hadith_statuses_qs = Hadis.objects.filter( category__source_type=HadisCategory.SourceType.HADITH, hadis_status__isnull=False ).values('hadis_status__slug', 'hadis_status__title').annotate(total=Count('id')) hadith_status_chart = [] for item in hadith_statuses_qs: title_text = extract_text_from_json(item['hadis_status__title']) if item['hadis_status__title'] else item['hadis_status__slug'] hadith_status_chart.append({ "name": title_text, "value": item['total'] }) # User Distribution (Donut Chart) registered_count = User.objects.filter(is_active=True).exclude(email__isnull=True).exclude(email='').count() guest_count = User.objects.filter(is_active=True).filter(Q(email__isnull=True) | Q(email='')).count() user_distribution_chart = [ {"name": "registered", "value": registered_count}, {"name": "guest", "value": guest_count} ] # New User Registrations Trend (Line Chart) registrations = User.objects.filter(date_joined__gte=thirty_days_ago).annotate( date=TruncDate('date_joined') ).values('date').annotate(count=Count('id')).order_by('date') registration_chart = [{"date": item['date'].strftime('%Y-%m-%d'), "users": item['count']} for item in registrations] # User Bookmark Activity Trend (Area Chart) bookmarks_trend = Bookmark.objects.filter(created_at__gte=thirty_days_ago, status=True).annotate( date=TruncDate('created_at') ).values('date').annotate(count=Count('id')).order_by('date') chat_chart = [{"date": item['date'].strftime('%Y-%m-%d'), "messages": item['count']} for item in bookmarks_trend] # --------------------------------------------------------- # 3. LISTS & LEADERBOARDS # --------------------------------------------------------- # Top 5 Media Items (List) top_videos = Video.objects.order_by('-view_count')[:5] top_media_list = [] for v in top_videos: title_text = extract_text_from_json(v.title) if v.title else "Untitled Video" top_media_list.append({ "id": v.id, "title": title_text[:40] + "..." if len(title_text) > 40 else title_text, "professor": "video", "participants": v.view_count }) top_podcasts = Podcast.objects.order_by('-view_count')[:5] for p in top_podcasts: title_text = extract_text_from_json(p.title) if p.title else "Untitled Podcast" top_media_list.append({ "id": p.id, "title": title_text[:40] + "..." if len(title_text) > 40 else title_text, "professor": "podcast", "participants": p.view_count }) top_media_list = sorted(top_media_list, key=lambda x: x['participants'], reverse=True)[:5] # Top 5 Articles (List) top_articles = Article.objects.order_by('-view_count')[:5] top_articles_list = [] for a in top_articles: title_text = extract_text_from_json(a.title) if a.title else "Untitled Article" top_articles_list.append({ "id": a.id, "title": title_text[:40] + "..." if len(title_text) > 40 else title_text, "views": a.view_count }) top_articles_list = sorted(top_articles_list, key=lambda x: x['views'], reverse=True)[:5] # Top 5 Books (List) top_books = Book.objects.order_by('-view_count')[:5] top_books_list = [] for b in top_books: title_text = extract_text_from_json(b.title) if b.title else "Untitled Book" top_books_list.append({ "id": b.id, "title": title_text[:40] + "..." if len(title_text) > 40 else title_text, "views": b.view_count }) top_books_list = sorted(top_books_list, key=lambda x: x['views'], reverse=True)[:5] # Top Bookmarked Arguments (Leaderboard) top_hadis_ids = Bookmark.objects.filter( service='hadith', status=True ).values('content_id').annotate( count=Count('id') ).order_by('-count')[:5] top_hadis_list = [] id_to_count = {item['content_id']: item['count'] for item in top_hadis_ids} hadiths = Hadis.objects.filter(id__in=id_to_count.keys()) for h in hadiths: title_text = extract_text_from_json(h.title) if h.title else f"گزاره {h.number}" top_hadis_list.append({ "id": h.id, "name": title_text[:30] + "..." if len(title_text) > 30 else title_text, "score": id_to_count[h.id] }) top_hadis_list = sorted(top_hadis_list, key=lambda x: x['score'], reverse=True) # Fallback if no bookmarks exist if not top_hadis_list: hadiths = Hadis.objects.filter(status=True)[:5] for h in hadiths: title_text = extract_text_from_json(h.title) if h.title else f"گزاره {h.number}" top_hadis_list.append({ "id": h.id, "name": title_text[:30] + "..." if len(title_text) > 30 else title_text, "score": 0 }) return Response({ "cards": { "active_students": total_arguments, # total_arguments "professors": library_books, # library_books "active_courses": source_references, # source_references "revenue_30d": total_transmitters, # total_transmitters "total_blogs": media_items, # media_items "pending_certificates": scientific_articles, # scientific_articles "active_live_sessions": registered_users, # registered_users "group_rooms": total_bookmarks, # total_bookmarks "private_rooms": 0, }, "charts": { "transactions_status": argument_types_chart, "hadith_statuses": hadith_status_chart, "user_distribution": user_distribution_chart, "registrations": registration_chart, "chat_volume": chat_chart, "course_pipeline": [], "course_efficacy": [] }, "lists": { "top_courses": top_media_list, "top_blogs": top_articles_list, "top_books": top_books_list, "leaderboard": top_hadis_list } })