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