Files
dsp/dsp/backend/apps/creator/views.py
T

151 lines
6.0 KiB
Python

"""Creator views & analytics."""
from __future__ import annotations
from datetime import timedelta
from adrf.views import APIView
from django.db.models import Avg, Count, F, Q, Sum
from django.utils import timezone
from rest_framework import status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from apps.videos.models import Video
from core.api import opt_id, render_data
from .models import VideoPlayStat
class PlayStatReportView(APIView):
"""Client reports playback duration and completion.
Authenticated-only: anonymous reports let anyone inflate a video's play
and completion numbers, and the creator dashboard reads straight off them.
"""
permission_classes = [IsAuthenticated]
async def post(self, request):
try:
video_id = opt_id(request.data.get("video_id"))
watch_seconds = float(request.data.get("watch_seconds") or 0.0)
duration = float(request.data.get("duration") or 0.0)
except (TypeError, ValueError):
return Response({"detail": "invalid numeric field"}, status=400)
if not video_id:
return Response({"detail": "video_id required"}, status=400)
if not await Video.objects.filter(pk=video_id, status="published").aexists():
return Response({"detail": "not found"}, status=404)
# 观看进度 >= 85% 即判定为“有效完播”
is_completed = bool(duration > 0 and watch_seconds >= duration * 0.85)
await VideoPlayStat.objects.acreate(
video_id=video_id,
user=request.user,
watch_seconds=watch_seconds,
video_duration=duration,
is_completed=is_completed,
)
return Response({"status": "recorded", "is_completed": is_completed})
class CreatorDashboardView(APIView):
"""Creator analytics studio: completion rates, views, likes, trend."""
permission_classes = [IsAuthenticated]
async def get(self, request):
user = request.user
# 创作者名下视频统计
video_qs = Video.objects.filter(author=user, status="published")
total_videos = await video_qs.acount()
# 汇总基础数据:全量聚合,不再只累加最近 20 条(此前视频多于 20 个时看板偏小)。
totals = await video_qs.aaggregate(
views=Sum("view_count"),
likes=Sum("like_count"),
comments=Sum("comment_count"),
favorites=Sum("favorite_count"),
)
total_views = totals["views"] or 0
total_likes = totals["likes"] or 0
total_comments = totals["comments"] or 0
total_favorites = totals["favorites"] or 0
video_items = []
now = timezone.now()
# 完播统计一次性按 video 聚合,避免每行两次 count()。
stat_rows = {}
async for row in (
VideoPlayStat.objects.filter(video__author=user)
.values("video_id")
.annotate(total=Count("id"), completed=Count("id", filter=Q(is_completed=True)))
):
stat_rows[row["video_id"]] = row
async for v in video_qs.order_by("-created_at")[:20]:
row = stat_rows.get(v.id) or {"total": 0, "completed": 0}
stat_total = row["total"]
stat_completed = row["completed"]
completion_rate = round((stat_completed / stat_total * 100), 1) if stat_total > 0 else 0.0
# 绝对 URL:客户端(Android Coil / Web <img>)拿到相对路径无法拼接主机。
cover = v.cover_image.url if v.cover_image else ""
video_items.append({
"id": v.id,
"title": v.title,
"cover_url": request.build_absolute_uri(cover) if cover else "",
"view_count": v.view_count,
"like_count": v.like_count,
"comment_count": v.comment_count,
"favorite_count": v.favorite_count,
"play_count": stat_total,
"completed_count": stat_completed,
"completion_rate": completion_rate,
"created_at": v.created_at.strftime("%Y-%m-%d"),
})
# 创作者平均完播率
all_stats_qs = VideoPlayStat.objects.filter(video__author=user)
grand_plays = await all_stats_qs.acount()
grand_completed = await all_stats_qs.filter(is_completed=True).acount()
avg_completion_rate = round((grand_completed / grand_plays * 100), 1) if grand_plays > 0 else 0.0
# 近7日每日播放量趋势模拟与真实统计
daily_trends = []
for i in range(6, -1, -1):
day_start = (now - timedelta(days=i)).replace(hour=0, minute=0, second=0, microsecond=0)
day_end = day_start + timedelta(days=1)
count = await all_stats_qs.filter(created_at__gte=day_start, created_at__lt=day_end).acount()
daily_trends.append({
"date": day_start.strftime("%m-%d"),
"plays": count,
})
# 智能创作诊断建议
advice = "继续保持创作!高完播率的作品前3秒黄金节奏至关重要。"
if avg_completion_rate >= 60.0:
advice = "太棒了!您的作品平均完播率达到优秀水平(≥60%),系统算法推荐加权中!"
elif total_videos > 0 and avg_completion_rate < 30.0:
advice = "提示:平均完播率偏低,建议优化视频前3秒悬念、背景音乐节奏与中段反转。"
return Response({
"overview": {
"total_videos": total_videos,
"total_views": total_views,
"total_likes": total_likes,
"total_comments": total_comments,
"total_favorites": total_favorites,
"follower_count": user.follower_count,
"avg_completion_rate": avg_completion_rate,
},
"daily_trends": daily_trends,
"advice": advice,
"videos": video_items,
})