You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
10 KiB
227 lines
10 KiB
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
|
|
}
|
|
})
|