Add stat page
This commit is contained in:
parent
1258d6dc25
commit
3da6a25867
8 changed files with 213 additions and 19 deletions
14
dmoj/urls.py
14
dmoj/urls.py
|
@ -886,7 +886,19 @@ urlpatterns = [
|
||||||
"^language/",
|
"^language/",
|
||||||
include(
|
include(
|
||||||
[
|
[
|
||||||
url("^$", stats.StatLanguage.as_view(), name="language_stats"),
|
url(
|
||||||
|
"^$",
|
||||||
|
stats.StatLanguage.as_view(),
|
||||||
|
name="language_stats",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
"^site/",
|
||||||
|
include(
|
||||||
|
[
|
||||||
|
url("^$", stats.StatSite.as_view(), name="site_stats"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -154,7 +154,9 @@ class ProblemData(models.Model):
|
||||||
if self.zipfile:
|
if self.zipfile:
|
||||||
self.zipfile.name = problem_directory_file_helper(new, self.zipfile.name)
|
self.zipfile.name = problem_directory_file_helper(new, self.zipfile.name)
|
||||||
if self.generator:
|
if self.generator:
|
||||||
self.generator.name = problem_directory_file_helper(new, self.generator.name)
|
self.generator.name = problem_directory_file_helper(
|
||||||
|
new, self.generator.name
|
||||||
|
)
|
||||||
if self.custom_checker:
|
if self.custom_checker:
|
||||||
self.custom_checker.name = problem_directory_file_helper(
|
self.custom_checker.name = problem_directory_file_helper(
|
||||||
new, self.custom_checker.name
|
new, self.custom_checker.name
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from itertools import chain, repeat
|
from itertools import chain, repeat
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
import json
|
import json
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Case, Count, FloatField, IntegerField, Value, When
|
from django.db.models import Case, Count, FloatField, IntegerField, Value, When
|
||||||
|
@ -11,6 +12,7 @@ from django.utils.translation import gettext as _
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
from judge.models import Language, Submission
|
from judge.models import Language, Submission
|
||||||
from judge.utils.stats import (
|
from judge.utils.stats import (
|
||||||
|
@ -49,18 +51,18 @@ class StatLanguage(StatViewBase):
|
||||||
other_count = sum(map(itemgetter("count"), languages[num_languages:]))
|
other_count = sum(map(itemgetter("count"), languages[num_languages:]))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"labels": list(map(itemgetter("name"), languages[:num_languages]))
|
"labels": list(map(itemgetter("name"), languages[:num_languages]))
|
||||||
+ ["Other"],
|
+ ["Other"],
|
||||||
"datasets": [
|
"datasets": [
|
||||||
{
|
{
|
||||||
"backgroundColor": chart_colors[:num_languages] + ["#FDB45C"],
|
"backgroundColor": chart_colors[:num_languages] + ["#FDB45C"],
|
||||||
"highlightBackgroundColor": highlight_colors[:num_languages]
|
"highlightBackgroundColor": highlight_colors[:num_languages]
|
||||||
+ ["#FFC870"],
|
+ ["#FFC870"],
|
||||||
"data": list(map(itemgetter("count"), languages[:num_languages]))
|
"data": list(map(itemgetter("count"), languages[:num_languages]))
|
||||||
+ [other_count],
|
+ [other_count],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
def ac_language_data(self):
|
def ac_language_data(self):
|
||||||
return self.language_data(Language.objects.annotate(count=self.ac_count))
|
return self.language_data(Language.objects.annotate(count=self.ac_count))
|
||||||
|
@ -83,10 +85,12 @@ class StatLanguage(StatViewBase):
|
||||||
|
|
||||||
return get_pie_chart(data)
|
return get_pie_chart(data)
|
||||||
|
|
||||||
|
|
||||||
def ac_rate(self):
|
def ac_rate(self):
|
||||||
rate = CombinedExpression(
|
rate = CombinedExpression(
|
||||||
self.ac_count / Count("submission"), "*", Value(100.0), output_field=FloatField()
|
self.ac_count / Count("submission"),
|
||||||
|
"*",
|
||||||
|
Value(100.0),
|
||||||
|
output_field=FloatField(),
|
||||||
)
|
)
|
||||||
data = (
|
data = (
|
||||||
Language.objects.annotate(total=Count("submission"), ac_rate=rate)
|
Language.objects.annotate(total=Count("submission"), ac_rate=rate)
|
||||||
|
@ -96,7 +100,6 @@ class StatLanguage(StatViewBase):
|
||||||
)
|
)
|
||||||
return get_bar_chart(list(data))
|
return get_bar_chart(list(data))
|
||||||
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["title"] = _("Language statistics")
|
context["title"] = _("Language statistics")
|
||||||
|
@ -105,4 +108,93 @@ class StatLanguage(StatViewBase):
|
||||||
context["lang_ac"] = mark_safe(json.dumps(self.ac_language_data()))
|
context["lang_ac"] = mark_safe(json.dumps(self.ac_language_data()))
|
||||||
context["status_counts"] = mark_safe(json.dumps(self.status_data()))
|
context["status_counts"] = mark_safe(json.dumps(self.status_data()))
|
||||||
context["ac_rate"] = mark_safe(json.dumps(self.ac_rate()))
|
context["ac_rate"] = mark_safe(json.dumps(self.ac_rate()))
|
||||||
return context
|
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
|
||||||
|
|
|
@ -37,4 +37,5 @@ redis
|
||||||
lupa
|
lupa
|
||||||
websocket-client
|
websocket-client
|
||||||
python-memcached
|
python-memcached
|
||||||
numpy
|
numpy
|
||||||
|
pandas
|
|
@ -282,6 +282,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
<li><a href="{{ url('internal_problem') }}">{{ _('Internal') }}</a></li>
|
<li><a href="{{ url('internal_problem') }}">{{ _('Internal') }}</a></li>
|
||||||
|
<li><a href="{{ url('site_stats') }}">{{ _('Stats') }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><a href="{{ url('user_edit_profile') }}">{{ _('Edit profile') }}</a></li>
|
<li><a href="{{ url('user_edit_profile') }}">{{ _('Edit profile') }}</a></li>
|
||||||
{% if request.user.is_impersonate %}
|
{% if request.user.is_impersonate %}
|
||||||
|
|
|
@ -87,4 +87,24 @@
|
||||||
});
|
});
|
||||||
return chart;
|
return chart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function draw_timeline(data, $chart) {
|
||||||
|
var ctx = $chart.find('canvas')[0].getContext('2d');
|
||||||
|
var chart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
type: 'time',
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return chart;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
65
templates/stats/site.html
Normal file
65
templates/stats/site.html
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
{% extends "stats/base.html" %}
|
||||||
|
{% block media %}
|
||||||
|
<style>
|
||||||
|
.graph {
|
||||||
|
padding-bottom: 3em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block chart_body %}
|
||||||
|
<div class="graph">
|
||||||
|
<div id="submissions" class="chart">
|
||||||
|
<canvas width="90%" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
<center><h3>{{ _('Submissions') }}</h3></center>
|
||||||
|
</div>
|
||||||
|
<div class="graph">
|
||||||
|
<div id="contests" class="chart">
|
||||||
|
<canvas width="90%" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
<center><h3>{{ _('Contests') }}</h3></center>
|
||||||
|
</div>
|
||||||
|
<div class="graph">
|
||||||
|
<div id="new_users" class="chart">
|
||||||
|
<canvas width="90%" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
<center><h3>{{ _('New users') }}</h3></center>
|
||||||
|
</div>
|
||||||
|
<div class="graph">
|
||||||
|
<div id="groups" class="chart">
|
||||||
|
<canvas width="90%" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
<center><h3>{{ _('Groups') }}</h3></center>
|
||||||
|
</div>
|
||||||
|
<div class="graph">
|
||||||
|
<div id="comments" class="chart">
|
||||||
|
<canvas width="90%" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
<center><h3>{{ _('Comments') }}</h3></center>
|
||||||
|
</div>
|
||||||
|
<div class="graph">
|
||||||
|
<div id="chat_messages" class="chart">
|
||||||
|
<canvas width="90%" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
<center><h3>{{ _('Chat messages') }}</h3></center>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block bodyend %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
Chart.defaults.global.scaleFontFamily =
|
||||||
|
Chart.defaults.global.tooltipFontFamily =
|
||||||
|
Chart.defaults.global.tooltipTitleFontFamily =
|
||||||
|
$('body').css('font-family');
|
||||||
|
|
||||||
|
draw_timeline({{submissions}}, $('#submissions'));
|
||||||
|
draw_timeline({{new_users}}, $('#new_users'));
|
||||||
|
draw_timeline({{comments}}, $('#comments'));
|
||||||
|
draw_timeline({{chat_messages}}, $('#chat_messages'));
|
||||||
|
draw_timeline({{contests}}, $('#contests'));
|
||||||
|
draw_timeline({{groups}}, $('#groups'));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -2,4 +2,5 @@
|
||||||
|
|
||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
{{ make_tab('language', 'fa-list', url('language_stats'), _('Language')) }}
|
{{ make_tab('language', 'fa-list', url('language_stats'), _('Language')) }}
|
||||||
|
{{ make_tab('site', 'fa-list', url('site_stats'), _('Site')) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
Loading…
Reference in a new issue