Browse Source

dashboard api added

master
Mohsen Taba 1 week ago
parent
commit
d4472b4199
  1. 3
      apps/api/views/__init__.py
  2. 227
      apps/api/views/admin_dashboard.py
  3. 3
      config/urls.py
  4. 2
      manage.py

3
apps/api/views/__init__.py

@ -4,6 +4,8 @@
from .api_views import HomeView, CountryView, CommentListAPIView, CityListView from .api_views import HomeView, CountryView, CommentListAPIView, CityListView
from .documentation import CustomAPIDocumentationView from .documentation import CustomAPIDocumentationView
from .swagger_views import CustomSwaggerView, SwaggerTokenAuthView, clear_swagger_auth from .swagger_views import CustomSwaggerView, SwaggerTokenAuthView, clear_swagger_auth
from .admin_dashboard import AdminDashboardStatsView
__all__ = [ __all__ = [
'HomeView', 'HomeView',
'CountryView', 'CountryView',
@ -13,4 +15,5 @@ __all__ = [
'CustomSwaggerView', 'CustomSwaggerView',
'SwaggerTokenAuthView', 'SwaggerTokenAuthView',
'clear_swagger_auth', 'clear_swagger_auth',
'AdminDashboardStatsView',
] ]

227
apps/api/views/admin_dashboard.py

@ -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
}
})

3
config/urls.py

@ -53,7 +53,7 @@ sitemaps = {
} }
# Import custom API views # Import custom API views
from apps.api.views import CustomAPIDocumentationView, CustomSwaggerView, SwaggerTokenAuthView, clear_swagger_auth
from apps.api.views import CustomAPIDocumentationView, CustomSwaggerView, SwaggerTokenAuthView, clear_swagger_auth, AdminDashboardStatsView
from apps.api.permissions import IsAdminOrSwaggerToken from apps.api.permissions import IsAdminOrSwaggerToken
from apps.api.decorators import swagger_auth_required from apps.api.decorators import swagger_auth_required
@ -95,6 +95,7 @@ api_patterns = [
path('agent/', include('apps.agent.urls')), path('agent/', include('apps.agent.urls')),
path('bookmarks/', include('apps.bookmark.urls')), path('bookmarks/', include('apps.bookmark.urls')),
path('calendar/', include('apps.dobodbi_calendar.urls')), path('calendar/', include('apps.dobodbi_calendar.urls')),
path('admin/dashboard-stats/', AdminDashboardStatsView.as_view()),
path('settings/', include('dynamic_preferences.urls')), path('settings/', include('dynamic_preferences.urls')),

2
manage.py

@ -20,3 +20,5 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
# Force reload trigger for new dashboard views
Loading…
Cancel
Save