diff --git a/oj/urls.py b/oj/urls.py index 09ab4a6..91c7538 100644 --- a/oj/urls.py +++ b/oj/urls.py @@ -16,6 +16,7 @@ urlpatterns = [ path("api/admin/", include("utils.urls")), path("api/", include("message.urls.oj")), path("api/", include("reaction.urls.oj")), + path("api/admin/", include("reaction.urls.admin")), path("api/", include("comment.urls.oj")), path("api/admin/", include("comment.urls.admin")), path("api/", include("tutorial.urls.tutorial")), diff --git a/reaction/urls/admin.py b/reaction/urls/admin.py new file mode 100644 index 0000000..9bf6fb8 --- /dev/null +++ b/reaction/urls/admin.py @@ -0,0 +1,7 @@ +from django.urls import path + +from ..views.admin import ReactionStatsAPI + +urlpatterns = [ + path("reaction", ReactionStatsAPI.as_view()), +] diff --git a/reaction/views/admin.py b/reaction/views/admin.py new file mode 100644 index 0000000..9af0285 --- /dev/null +++ b/reaction/views/admin.py @@ -0,0 +1,39 @@ +from django.db.models import Count, F, FloatField, Q +from django.db.models.functions import Cast + +from account.decorators import super_admin_required +from reaction.models import Reaction, ReactionType +from utils.api import APIView + +MIN_USERS_FOR_RATIO = 3 +DEFAULT_ORDERING = "-users" + + +class ReactionStatsAPI(APIView): + @super_admin_required + def get(self, request): + queryset = ( + Reaction.objects.values(pid=F("problem___id"), title=F("problem__title")) + .annotate(users=Count("user", distinct=True)) + .annotate(**{t.value: Count("id", filter=Q(type=t.value)) for t in ReactionType}) + .annotate(**{f"{t.value}_ratio": Cast(t.value, FloatField()) / Cast("users", FloatField()) for t in ReactionType}) + ) + + problem_id = request.GET.get("problem") + if problem_id: + queryset = queryset.filter(problem___id__iexact=problem_id, problem__contest_id__isnull=True) + + ordering = request.GET.get("ordering") or DEFAULT_ORDERING + prefix = "-" if ordering.startswith("-") else "" + field = ordering.lstrip("-") + if field == "users": + order_by = f"{prefix}users" + elif field in ReactionType.values: + # 按占比排序时过滤低样本,避免 1 人点 1 个就冲到 100% + queryset = queryset.filter(users__gte=MIN_USERS_FOR_RATIO) + order_by = f"{prefix}{field}_ratio" + else: + order_by = DEFAULT_ORDERING + + queryset = queryset.order_by(order_by) + return self.success(self.paginate_data(request, queryset))