4 changed files with 234 additions and 1 deletions
@ -0,0 +1,227 @@ |
|||||
|
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 |
||||
|
} |
||||
|
}) |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue