2020-01-21 06:35:58 +00:00
|
|
|
from itertools import chain, repeat
|
|
|
|
from operator import itemgetter
|
2022-10-07 17:17:07 +00:00
|
|
|
import json
|
|
|
|
import pandas as pd
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
from django.db.models import Case, Count, FloatField, IntegerField, Value, When
|
|
|
|
from django.db.models.expressions import CombinedExpression
|
|
|
|
from django.http import JsonResponse
|
|
|
|
from django.shortcuts import render
|
|
|
|
from django.utils.translation import gettext as _
|
2022-10-07 17:17:07 +00:00
|
|
|
from django.http import Http404
|
|
|
|
from django.views.generic import TemplateView
|
|
|
|
from django.utils.safestring import mark_safe
|
|
|
|
from django.db import connection
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
from judge.models import Language, Submission
|
2022-05-14 17:57:27 +00:00
|
|
|
from judge.utils.stats import (
|
|
|
|
chart_colors,
|
|
|
|
get_bar_chart,
|
|
|
|
get_pie_chart,
|
|
|
|
highlight_colors,
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
2022-10-07 17:17:07 +00:00
|
|
|
class StatViewBase(TemplateView):
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
if not request.user.is_superuser:
|
|
|
|
raise Http404
|
|
|
|
return super().get(request, *args, **kwargs)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
2022-10-07 17:17:07 +00:00
|
|
|
class StatLanguage(StatViewBase):
|
|
|
|
template_name = "stats/language.html"
|
|
|
|
ac_count = Count(
|
|
|
|
Case(When(submission__result="AC", then=Value(1)), output_field=IntegerField())
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2022-10-07 17:17:07 +00:00
|
|
|
def repeat_chain(iterable):
|
|
|
|
return chain.from_iterable(repeat(iterable))
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2022-10-07 17:17:07 +00:00
|
|
|
def language_data(
|
|
|
|
self, language_count=Language.objects.annotate(count=Count("submission"))
|
|
|
|
):
|
|
|
|
languages = (
|
|
|
|
language_count.filter(count__gt=0)
|
|
|
|
.values("key", "name", "count")
|
|
|
|
.order_by("-count")
|
|
|
|
)
|
|
|
|
num_languages = min(len(languages), settings.DMOJ_STATS_LANGUAGE_THRESHOLD)
|
|
|
|
other_count = sum(map(itemgetter("count"), languages[num_languages:]))
|
2022-05-14 17:57:27 +00:00
|
|
|
|
2022-10-07 17:17:07 +00:00
|
|
|
return {
|
2022-05-14 17:57:27 +00:00
|
|
|
"labels": list(map(itemgetter("name"), languages[:num_languages]))
|
|
|
|
+ ["Other"],
|
|
|
|
"datasets": [
|
|
|
|
{
|
|
|
|
"backgroundColor": chart_colors[:num_languages] + ["#FDB45C"],
|
|
|
|
"highlightBackgroundColor": highlight_colors[:num_languages]
|
|
|
|
+ ["#FFC870"],
|
|
|
|
"data": list(map(itemgetter("count"), languages[:num_languages]))
|
|
|
|
+ [other_count],
|
|
|
|
},
|
|
|
|
],
|
2022-10-07 17:17:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def ac_language_data(self):
|
|
|
|
return self.language_data(Language.objects.annotate(count=self.ac_count))
|
|
|
|
|
|
|
|
def status_data(self, statuses=None):
|
|
|
|
if not statuses:
|
|
|
|
statuses = (
|
|
|
|
Submission.objects.values("result")
|
|
|
|
.annotate(count=Count("result"))
|
|
|
|
.values("result", "count")
|
|
|
|
.order_by("-count")
|
|
|
|
)
|
|
|
|
data = []
|
|
|
|
for status in statuses:
|
|
|
|
res = status["result"]
|
|
|
|
if not res:
|
|
|
|
continue
|
|
|
|
count = status["count"]
|
|
|
|
data.append((str(Submission.USER_DISPLAY_CODES[res]), count))
|
|
|
|
|
|
|
|
return get_pie_chart(data)
|
|
|
|
|
|
|
|
def ac_rate(self):
|
|
|
|
rate = CombinedExpression(
|
|
|
|
self.ac_count / Count("submission"),
|
|
|
|
"*",
|
|
|
|
Value(100.0),
|
|
|
|
output_field=FloatField(),
|
2022-05-14 17:57:27 +00:00
|
|
|
)
|
2022-10-07 17:17:07 +00:00
|
|
|
data = (
|
|
|
|
Language.objects.annotate(total=Count("submission"), ac_rate=rate)
|
|
|
|
.filter(total__gt=0)
|
|
|
|
.order_by("total")
|
|
|
|
.values_list("name", "ac_rate")
|
|
|
|
)
|
|
|
|
return get_bar_chart(list(data))
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
context["title"] = _("Language statistics")
|
|
|
|
context["tab"] = "language"
|
|
|
|
context["data_all"] = mark_safe(json.dumps(self.language_data()))
|
|
|
|
context["lang_ac"] = mark_safe(json.dumps(self.ac_language_data()))
|
|
|
|
context["status_counts"] = mark_safe(json.dumps(self.status_data()))
|
|
|
|
context["ac_rate"] = mark_safe(json.dumps(self.ac_rate()))
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
def group_data_by_period(dates, data, period="1W"):
|
|
|
|
df = pd.DataFrame()
|
|
|
|
df["dates"] = pd.to_datetime(dates)
|
|
|
|
df["data"] = data
|
|
|
|
df = df.groupby([pd.Grouper(key="dates", freq=period)])["data"].mean().reset_index()
|
|
|
|
return list(df["dates"]), list(df["data"])
|
|
|
|
|
|
|
|
|
|
|
|
class StatSite(StatViewBase):
|
|
|
|
template_name = "stats/site.html"
|
|
|
|
|
|
|
|
def create_dataset(self, query, label):
|
|
|
|
labels = []
|
|
|
|
data = []
|
|
|
|
with connection.cursor() as cursor:
|
|
|
|
cursor.execute(query)
|
|
|
|
for row in cursor.fetchall():
|
|
|
|
data.append(row[0])
|
|
|
|
labels.append(row[1])
|
|
|
|
|
|
|
|
labels, data = group_data_by_period(labels, data, self.period)
|
|
|
|
|
|
|
|
res = {
|
|
|
|
"labels": labels,
|
|
|
|
"datasets": [
|
|
|
|
{
|
|
|
|
"label": label,
|
|
|
|
"data": data,
|
|
|
|
"borderColor": "rgb(75, 192, 192)",
|
|
|
|
"pointRadius": 1,
|
|
|
|
"borderWidth": 2,
|
|
|
|
}
|
|
|
|
],
|
|
|
|
}
|
|
|
|
return mark_safe(json.dumps(res, default=str))
|
|
|
|
|
|
|
|
def get_submissions(self):
|
|
|
|
query = """
|
|
|
|
SELECT COUNT(*), CAST(date AS Date) as day from judge_submission GROUP BY day;
|
|
|
|
"""
|
|
|
|
return self.create_dataset(query, _("Submissions"))
|
|
|
|
|
|
|
|
def get_comments(self):
|
|
|
|
query = """
|
|
|
|
SELECT COUNT(*), CAST(time AS Date) as day from judge_comment GROUP BY day;
|
|
|
|
"""
|
|
|
|
return self.create_dataset(query, _("Comments"))
|
|
|
|
|
|
|
|
def get_new_users(self):
|
|
|
|
query = """
|
|
|
|
SELECT COUNT(*), CAST(date_joined AS Date) as day from auth_user GROUP BY day;
|
|
|
|
"""
|
|
|
|
return self.create_dataset(query, _("New users"))
|
|
|
|
|
|
|
|
def get_chat_messages(self):
|
|
|
|
query = """
|
|
|
|
SELECT COUNT(*), CAST(time AS Date) as day from chat_box_message GROUP BY day;
|
|
|
|
"""
|
|
|
|
return self.create_dataset(query, _("Chat messages"))
|
|
|
|
|
|
|
|
def get_contests(self):
|
|
|
|
query = """
|
|
|
|
SELECT COUNT(*), CAST(start_time AS Date) as day from judge_contest GROUP BY day;
|
|
|
|
"""
|
|
|
|
return self.create_dataset(query, _("Contests"))
|
|
|
|
|
|
|
|
def get_groups(self):
|
|
|
|
query = """
|
|
|
|
SELECT COUNT(*), CAST(creation_date AS Date) as day from judge_organization GROUP BY day;
|
|
|
|
"""
|
|
|
|
return self.create_dataset(query, _("Groups"))
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
self.period = request.GET.get("period", "1W")
|
|
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
context["tab"] = "site"
|
|
|
|
context["title"] = _("Site statistics")
|
|
|
|
context["submissions"] = self.get_submissions()
|
|
|
|
context["comments"] = self.get_comments()
|
|
|
|
context["new_users"] = self.get_new_users()
|
|
|
|
context["chat_messages"] = self.get_chat_messages()
|
|
|
|
context["contests"] = self.get_contests()
|
|
|
|
context["groups"] = self.get_groups()
|
|
|
|
return context
|