Revert last 3 commits

This commit is contained in:
cuom1999 2022-10-07 12:17:07 -05:00
parent 87e8f3d966
commit dfc12f81f2
20 changed files with 486 additions and 272 deletions

View file

@ -891,27 +891,19 @@ urlpatterns = [
"^language/",
include(
[
url("^$", stats.language, name="language_stats"),
url(
"^data/all/$",
stats.language_data,
name="language_stats_data_all",
"^$",
stats.StatLanguage.as_view(),
name="language_stats",
),
]
),
),
url(
"^data/ac/$",
stats.ac_language_data,
name="language_stats_data_ac",
),
url(
"^data/status/$",
stats.status_data,
name="stats_data_status",
),
url(
"^data/ac_rate/$",
stats.ac_rate,
name="language_stats_data_ac_rate",
),
"^site/",
include(
[
url("^$", stats.StatSite.as_view(), name="site_stats"),
]
),
),

View file

@ -184,10 +184,49 @@ class AddOrganizationForm(ModelForm):
return res
class OrganizationContestForm(ModelForm):
class AddOrganizationContestForm(ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request", None)
super(AddOrganizationContestForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
contest = super(AddOrganizationContestForm, self).save(commit=False)
old_save_m2m = self.save_m2m
def save_m2m():
for i, problem in enumerate(self.cleaned_data["problems"]):
contest_problem = ContestProblem(
contest=contest, problem=problem, points=100, order=i + 1
)
contest_problem.save()
contest.contest_problems.add(contest_problem)
old_save_m2m()
self.save_m2m = save_m2m
contest.save()
self.save_m2m()
return contest
class Meta:
model = Contest
fields = (
"key",
"name",
"start_time",
"end_time",
"problems",
)
widgets = {
"start_time": DateTimePickerWidget(),
"end_time": DateTimePickerWidget(),
"problems": HeavySelect2MultipleWidget(data_view="problem_select2"),
}
class EditOrganizationContestForm(ModelForm):
def __init__(self, *args, **kwargs):
self.org_id = kwargs.pop("org_id", 0)
super(OrganizationContestForm, self).__init__(*args, **kwargs)
super(EditOrganizationContestForm, self).__init__(*args, **kwargs)
for field in [
"authors",
"curators",
@ -203,27 +242,25 @@ class OrganizationContestForm(ModelForm):
class Meta:
model = Contest
fields = (
"is_visible",
"key",
"name",
"start_time",
"end_time",
"format_name",
"authors",
"curators",
"testers",
"is_visible",
"time_limit",
"use_clarifications",
"hide_problem_tags",
"scoreboard_visibility",
"run_pretests_only",
"points_precision",
"start_time",
"end_time",
"time_limit",
"description",
"og_image",
"logo_override_image",
"summary",
"format_name",
"format_config",
"problem_label_script",
"access_code",
"private_contestants",
"view_contest_scoreboard",

View file

@ -5,7 +5,7 @@ from urllib.parse import urlparse
import mistune
from django.conf import settings
from jinja2 import Markup
from markupsafe import Markup
from lxml import html
from lxml.etree import ParserError, XMLSyntaxError

View file

@ -1,7 +1,8 @@
import re
from jinja2 import Markup, nodes
from jinja2 import nodes
from jinja2.ext import Extension
from markupsafe import Markup
class SpacelessExtension(Extension):

View file

@ -98,13 +98,7 @@ class BlogPost(models.Model):
return self.title
def get_absolute_url(self):
try:
BlogPost.objects.get(id=self.id)
return reverse("blog_post", args=(self.id , self.slug))
except BlogPost.DoesNotExist:
return reverse("organization_pending_blogs",
args=(self.organizations.first().pk, self.organizations.first().slug))
def can_see(self, user):
if self.visible and self.publish_on <= timezone.now():
if not self.is_organization_private:

View file

@ -152,19 +152,21 @@ class ProblemData(models.Model):
if e.errno != errno.ENOENT:
raise
if self.zipfile:
self.zipfile.name = _problem_directory_file(new, self.zipfile.name)
self.zipfile.name = problem_directory_file_helper(new, self.zipfile.name)
if self.generator:
self.generator.name = _problem_directory_file(new, self.generator.name)
self.generator.name = problem_directory_file_helper(
new, self.generator.name
)
if self.custom_checker:
self.custom_checker.name = _problem_directory_file(
self.custom_checker.name = problem_directory_file_helper(
new, self.custom_checker.name
)
if self.custom_checker:
self.custom_checker.name = _problem_directory_file(
self.custom_checker.name = problem_directory_file_helper(
new, self.custom_checker.name
)
if self.custom_validator:
self.custom_validator.name = _problem_directory_file(
self.custom_validator.name = problem_directory_file_helper(
new, self.custom_validator.name
)
self.save()

View file

@ -47,8 +47,9 @@ from judge.forms import (
AddOrganizationMemberForm,
OrganizationBlogForm,
OrganizationAdminBlogForm,
OrganizationContestForm,
EditOrganizationContestForm,
ContestProblemFormSet,
AddOrganizationContestForm,
)
from judge.models import (
BlogPost,
@ -159,7 +160,9 @@ class OrganizationMixin(OrganizationBase):
class AdminOrganizationMixin(OrganizationMixin):
def dispatch(self, request, *args, **kwargs):
res = super(AdminOrganizationMixin, self).dispatch(request, *args, **kwargs)
if self.can_edit_organization(self.organization):
if not hasattr(self, "organization") or self.can_edit_organization(
self.organization
):
return res
return generic_message(
request,
@ -172,7 +175,7 @@ class AdminOrganizationMixin(OrganizationMixin):
class MemberOrganizationMixin(OrganizationMixin):
def dispatch(self, request, *args, **kwargs):
res = super(MemberOrganizationMixin, self).dispatch(request, *args, **kwargs)
if self.can_access(self.organization):
if not hasattr(self, "organization") or self.can_access(self.organization):
return res
return generic_message(
request,
@ -389,18 +392,12 @@ class OrganizationContestMixin(
OrganizationHomeViewContext,
):
model = Contest
form_class = OrganizationContestForm
def is_contest_editable(self, request, contest):
return request.profile in contest.authors.all() or self.can_edit_organization(
self.organization
)
def get_form_kwargs(self):
kwargs = super(OrganizationContestMixin, self).get_form_kwargs()
kwargs["org_id"] = self.organization.id
return kwargs
class OrganizationContests(
OrganizationContestMixin, MemberOrganizationMixin, ContestList
@ -862,17 +859,26 @@ class AddOrganizationContest(
AdminOrganizationMixin, OrganizationContestMixin, CreateView
):
template_name = "organization/contest/add.html"
form_class = AddOrganizationContestForm
def get_title(self):
return _("Add contest")
def get_form_kwargs(self):
kwargs = super(AddOrganizationContest, self).get_form_kwargs()
kwargs["request"] = self.request
return kwargs
def form_valid(self, form):
with transaction.atomic(), revisions.create_revision():
revisions.set_comment(_("Added from site"))
revisions.set_user(self.request.user)
res = super(AddOrganizationContest, self).form_valid(form)
self.object.organizations.add(self.organization)
self.object.is_organization_private = True
self.object.authors.add(self.request.profile)
self.object.save()
return res
@ -886,7 +892,8 @@ class AddOrganizationContest(
class EditOrganizationContest(
OrganizationContestMixin, MemberOrganizationMixin, UpdateView
):
template_name = "organization/contest/add.html"
template_name = "organization/contest/edit.html"
form_class = EditOrganizationContestForm
def setup_contest(self, request, *args, **kwargs):
contest_key = kwargs.get("contest", None)
@ -903,6 +910,11 @@ class EditOrganizationContest(
status=400,
)
def get_form_kwargs(self):
kwargs = super(EditOrganizationContest, self).get_form_kwargs()
kwargs["org_id"] = self.organization.id
return kwargs
def get(self, request, *args, **kwargs):
res = self.setup_contest(request, *args, **kwargs)
if res:
@ -921,8 +933,8 @@ class EditOrganizationContest(
problem_form.save()
for problem_form in problem_formset.deleted_objects:
problem_form.delete()
return super().post(request, *args, **kwargs)
super().post(request, *args, **kwargs)
return HttpResponseRedirect(reverse("organization_contests", args=(self.organization_id,self.organization.slug) ))
self.object = self.contest
return self.render_to_response(
self.get_context_data(

View file

@ -290,15 +290,19 @@ class ProblemDetail(ProblemMixin, SolvedProblemMixin, CommentedDetailView):
except ObjectDoesNotExist:
pass
try:
print(self.request.LANGUAGE_CODE)
print(ProblemTranslation.objects.last().__dict__)
translation = self.object.translations.get(
language=self.request.LANGUAGE_CODE
)
except ProblemTranslation.DoesNotExist:
print('DNE')
context["title"] = self.object.name
context["language"] = settings.LANGUAGE_CODE
context["description"] = self.object.description
context["translated"] = False
else:
print('E')
context["title"] = translation.name
context["language"] = self.request.LANGUAGE_CODE
context["description"] = translation.description
@ -845,8 +849,10 @@ class ProblemFeed(ProblemList):
if self.feed_type == "new":
return queryset.order_by("-date")
elif user and self.feed_type == "volunteer":
voted_problems = user.volunteer_problem_votes.values_list(
"problem", flat=True
voted_problems = (
user.volunteer_problem_votes.values_list("problem", flat=True)
if not bool(self.search_query)
else []
)
if self.show_solved_only:
queryset = queryset.filter(

View file

@ -1,5 +1,7 @@
from itertools import chain, repeat
from operator import itemgetter
import json
import pandas as pd
from django.conf import settings
from django.db.models import Case, Count, FloatField, IntegerField, Value, When
@ -7,6 +9,10 @@ from django.db.models.expressions import CombinedExpression
from django.http import JsonResponse
from django.shortcuts import render
from django.utils.translation import gettext as _
from django.http import Http404
from django.views.generic import TemplateView
from django.utils.safestring import mark_safe
from django.db import connection
from judge.models import Language, Submission
from judge.utils.stats import (
@ -17,18 +23,25 @@ from judge.utils.stats import (
)
ac_count = Count(
class StatViewBase(TemplateView):
def get(self, request, *args, **kwargs):
if not request.user.is_superuser:
raise Http404
return super().get(request, *args, **kwargs)
class StatLanguage(StatViewBase):
template_name = "stats/language.html"
ac_count = Count(
Case(When(submission__result="AC", then=Value(1)), output_field=IntegerField())
)
)
def repeat_chain(iterable):
def repeat_chain(iterable):
return chain.from_iterable(repeat(iterable))
def language_data(
request, language_count=Language.objects.annotate(count=Count("submission"))
):
def language_data(
self, language_count=Language.objects.annotate(count=Count("submission"))
):
languages = (
language_count.filter(count__gt=0)
.values("key", "name", "count")
@ -37,8 +50,7 @@ def language_data(
num_languages = min(len(languages), settings.DMOJ_STATS_LANGUAGE_THRESHOLD)
other_count = sum(map(itemgetter("count"), languages[num_languages:]))
return JsonResponse(
{
return {
"labels": list(map(itemgetter("name"), languages[:num_languages]))
+ ["Other"],
"datasets": [
@ -50,16 +62,12 @@ def language_data(
+ [other_count],
},
],
},
safe=False,
)
}
def ac_language_data(self):
return self.language_data(Language.objects.annotate(count=self.ac_count))
def ac_language_data(request):
return language_data(request, Language.objects.annotate(count=ac_count))
def status_data(request, statuses=None):
def status_data(self, statuses=None):
if not statuses:
statuses = (
Submission.objects.values("result")
@ -75,12 +83,14 @@ def status_data(request, statuses=None):
count = status["count"]
data.append((str(Submission.USER_DISPLAY_CODES[res]), count))
return JsonResponse(get_pie_chart(data), safe=False)
return get_pie_chart(data)
def ac_rate(request):
def ac_rate(self):
rate = CombinedExpression(
ac_count / Count("submission"), "*", Value(100.0), output_field=FloatField()
self.ac_count / Count("submission"),
"*",
Value(100.0),
output_field=FloatField(),
)
data = (
Language.objects.annotate(total=Count("submission"), ac_rate=rate)
@ -88,15 +98,103 @@ def ac_rate(request):
.order_by("total")
.values_list("name", "ac_rate")
)
return JsonResponse(get_bar_chart(list(data)))
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 language(request):
return render(
request,
"stats/language.html",
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": [
{
"title": _("Language statistics"),
"tab": "language",
},
)
"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

View file

@ -1,86 +1,41 @@
amqp==5.1.1
ansi2html @ git+https://github.com/DMOJ/ansi2html.git@1791b4a468942eba19cb29242d9075f5e4284450
async-timeout==4.0.2
billiard==3.6.4.0
celery==5.2.7
certifi==2022.6.15.1
cffi==1.15.1
chardet==5.0.0
charset-normalizer==2.1.1
click==8.1.3
click-didyoumean==0.3.0
click-plugins==1.1.1
click-repl==0.2.0
cryptography==38.0.1
defusedxml==0.7.1
Deprecated==1.2.13
diff-match-patch==20200713
Django==2.2.28
django-appconf==1.0.5
django-compressor==4.1
django-fernet-fields==0.6
django-impersonate==1.8.2
django-jinja==2.10.2
django-js-asset==2.0.0
django-mptt==0.13.4
django-newsletter==0.9.1
django-pagedown==2.2.1
django-registration-redux==2.11
django-reversion==5.0.2
django-reversion-compare==0.15.0
django-social-share==2.3.0
django-sortedm2m==2.0.0
django-statici18n==2.3.1
dmoj-wpadmin==1.10.2
Flask==2.2.2
idna==3.3
itsdangerous==2.1.2
Jinja2==3.1.2
jsonfield==3.1.0
kombu==5.2.4
ldif3==3.1.1
llist==0.7.1
lupa==1.13
lxml==4.9.1
MarkupSafe==2.1.1
mistune==0.8.4
mysqlclient==2.1.1
netaddr==0.8.0
numpy==1.23.3
oauthlib==3.2.1
packaging==21.3
pika==1.3.0
Pillow==9.2.0
prompt-toolkit==3.0.31
pycparser==2.21
Pygments==2.13.0
PyJWT==2.4.0
pymoss==0.2
pyotp==2.7.0
pyparsing==3.0.9
python-card-me==0.9.3
python-dateutil==2.8.2
python-memcached==1.59
python3-openid==3.2.0
pytz==2022.2.1
PyYAML==6.0
qrcode==7.3.1
rcssmin==1.1.0
redis==4.3.4
requests==2.28.1
requests-oauthlib==1.3.1
rjsmin==1.2.0
six==1.16.0
social-auth-app-django==5.0.0
social-auth-core==4.3.0
sorl-thumbnail==12.9.0
sqlparse==0.4.2
surlex==0.2.0
ua-parser==0.16.1
unicodecsv==0.14.1
urllib3==1.26.12
vine==5.0.0
wcwidth==0.2.5
websocket-client==1.4.1
Werkzeug==2.2.2
wrapt==1.14.1
Django>=2.2,<3
django_compressor
django-mptt
django-pagedown
django-registration-redux
django-reversion
django-reversion-compare
django-social-share
django-sortedm2m @ git+https://github.com/DMOJ/django-sortedm2m.git
django-impersonate
dmoj-wpadmin @ git+https://github.com/LQDJudge/dmoj-wpadmin.git
lxml
Pygments
mistune<2
social-auth-app-django
pytz
django-statici18n
pika
ua-parser
pyyaml
jinja2
django_jinja
llist
requests
django-fernet-fields
pyotp
qrcode[pil]
jsonfield
pymoss
packaging
celery
ansi2html @ git+https://github.com/DMOJ/ansi2html.git
sqlparse
django-newsletter
netaddr
redis
lupa
websocket-client
python-memcached
numpy
pandas

View file

@ -5,6 +5,7 @@
<a target="_blank" href="">LQDOJ (Le Quy Don Online Judge)</a> là một trang web chấm bài tự động được phát triển dựa trên nền tảng mã nguồn mở <a target="_blank" href="https://dmoj.ca/">DMOJ</a>. Được xây dựng với mục đích ban đầu là tạo ra một môi trường học tập cho học sinh khối chuyên Tin <a target="_blank" href="http://thpt-lequydon-danang.edu.vn/">trường THPT chuyên Lê Quý Đôn (TP Đà Nẵng)</a>, hiện nay LQDOJ đã cho phép đăng ký tự do để trở thành một sân chơi rộng mở cho toàn bộ cộng đồng học sinh yêu Tin học. Trang web cung cấp lượng bài luyện tập đồ sộ từ các kỳ thi HSG Quốc Gia, ACM ICPC, Olympic Duyên Hải Bắc Bộ, etc. cho đến các contest định kỳ để xếp loại khả năng (rating) giúp các bạn có thêm động lực cạnh tranh và khí thế phấn đấu rèn luyện nâng cao trình độ lập trình. Các bạn có thể tham khảo mã nguồn của trang web tại <a target="_blank" href="https://github.com/LQDJudge/online-judge">Github repo chính thức</a>. Mọi ý kiến đóng góp và thắc mắc xin gửi về:
<ul>
<li>Thầy <a target="_blank" href="https://www.facebook.com/dovannho">Đỗ Văn Nhỏ</a> (handle: <span class="rating rate-none user"><a target="_blank" href="../user/Small">Small</a></span>), giáo viên Tin học trường THPT chuyên Lê Quý Đôn, email: <a href="mailto:admin@lqdoj.edu.vn">admin@lqdoj.edu.vn</a></li>
<li><a target="_blank" href="https://www.facebook.com/floweronstone">Nguyễn Đức Thuận</a> (handle: <span class="rating rate-master user"><a target="_blank" href="../user/Flower_On_Stone">Flower_On_Stone</a></span>), email: <a href="mailto:thuanbn03@gmail.com">thuanbn03@gmail.com</a></li>
<li><a target="_blank" href="https://www.facebook.com/profile.php?id=100011662657075">Lê Phước Định</a> (handle: <span class="rating rate-grandmaster user"><a target="_blank" href="../user/cuom1999">cuom1999</a></span>), email: <a href="mailto:dinh@lqdoj.edu.vn">dinh@lqdoj.edu.vn</a></li>
<li><a target="_blank" href="https://www.facebook.com/doannguyenthanhluong">Đoàn Nguyễn Thành Lương</a> (handle: <span class="rating rate-master user"><a target="_blank" href="../user/CaiWinDao">CaiWinDao</a></span>), email: <a href="mailto:luong@lqdoj.edu.vn">luong@lqdoj.edu.vn</a></li>
</ul>

View file

@ -282,6 +282,7 @@
{% endif %}
{% if request.user.is_superuser %}
<li><a href="{{ url('internal_problem') }}">{{ _('Internal') }}</a></li>
<li><a href="{{ url('site_stats') }}">{{ _('Stats') }}</a></li>
{% endif %}
<li><a href="{{ url('user_edit_profile') }}">{{ _('Edit profile') }}</a></li>
{% if request.user.is_impersonate %}

View file

@ -6,28 +6,6 @@
{% block three_col_media %}
{{ form.media.css }}
<style>
#org-field-wrapper-scoreboard_visibility,
#org-field-wrapper-points_precision,
#org-field-wrapper-start_time,
#org-field-wrapper-end_time,
#org-field-wrapper-time_limit,
#org-field-wrapper-format_name {
display: inline-flex;
}
.problems-problem {
width: 40%;
}
input[type=number] {
width: 5em;
}
.middle-content {
z-index: 1;
}
#three-col-container {
overflow: auto;
}
</style>
{% endblock %}
{% block middle_content %}
@ -55,36 +33,6 @@
{% endif %}
{% endfor %}
{% if problems_form %}
<hr><br>
{{ problems_form.management_form }}
<i>{{_('If you run out of rows, click Save')}}</i>
<table class="table">
<thead>
<tr>
{% for field in problems_form[0] %}
{% if not field.is_hidden %}
<th>
{{field.label}}
</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for form in problems_form %}
<tr>
{% for field in form %}
<td class="problems-{{field.name}}" title="
{{ field.help_text|safe if field.help_text }}"
style="{{ 'display:none' if field.is_hidden }}"
>{{field}}<div style="color:red">{{field.errors}}</div></td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<button type="submit">{{ _('Save') }}</button>
</form>
{% endblock %}

View file

@ -0,0 +1,88 @@
{% extends "organization/home-base.html" %}
{% block three_col_js %}
{{ form.media.js }}
{% endblock %}
{% block three_col_media %}
{{ form.media.css }}
<style>
#org-field-wrapper-scoreboard_visibility,
#org-field-wrapper-points_precision,
#org-field-wrapper-start_time,
#org-field-wrapper-end_time,
#org-field-wrapper-time_limit,
#org-field-wrapper-format_name {
display: inline-flex;
}
.problems-problem {
width: 40%;
}
input[type=number] {
width: 5em;
}
.middle-content {
z-index: 1;
}
#three-col-container {
overflow: auto;
}
</style>
{% endblock %}
{% block middle_content %}
<form action="" method="post">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-dismissable">
<a href="#" class="close">x</a>
{{ form.non_field_errors() }}
{{ form.errors }}
</div>
{% endif %}
{% for field in form %}
{% if not field.is_hidden %}
<div style="margin-bottom: 1em;">
{{ field.errors }}
<label for="{{field.id_for_label }}"><b>{{ field.label }}{% if field.field.required %}<span style="color:red"> * </span>{% endif %}:</b> </label>
<div class="org-field-wrapper" id="org-field-wrapper-{{field.html_name }}">
{{ field }}
</div>
{% if field.help_text %}
<i style="display: block">{{ field.help_text|safe }}</i>
{% endif %}
</div>
{% endif %}
{% endfor %}
<hr><br>
{{ problems_form.management_form }}
<i>{{_('If you run out of rows, click Save')}}</i>
<table class="table">
<thead>
<tr>
{% for field in problems_form[0] %}
{% if not field.is_hidden %}
<th>
{{field.label}}
</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for form in problems_form %}
<tr>
{% for field in form %}
<td class="problems-{{field.name}}" title="
{{ field.help_text|safe if field.help_text }}"
style="{{ 'display:none' if field.is_hidden }}"
>{{field}}<div style="color:red">{{field.errors}}</div></td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<button type="submit">{{ _('Save') }}</button>
</form>
{% endblock %}

View file

@ -6,13 +6,10 @@
{% endcompress %}
{% endblock %}
{% block content_title %}{{ _('Statistics') }}{% endblock %}
{% block content_title %}{% endblock %}
{% block title_ruler %}{% endblock %}
{% block body %}
<div class="tabs">
<li{% if tab == 'language' %} class="active"{% endif %}>
<a href="{{ url('language_stats') }}">{{ _('Language') }}</a>
</li>
</div>
{% include "stats/tab.html" %}
{% block chart_body %}{% endblock %}
{% endblock %}

View file

@ -36,20 +36,10 @@
Chart.defaults.global.tooltipFontFamily =
Chart.defaults.global.tooltipTitleFontFamily =
$('body').css('font-family');
function pie_chart(url, $chart) {
$.getJSON(url, function (data) {
draw_pie_chart(data, $chart);
});
}
pie_chart('{{ url('language_stats_data_all') }}', $('#lang-all'));
pie_chart('{{ url('language_stats_data_ac') }}', $('#lang-ac'));
pie_chart('{{ url('stats_data_status') }}', $('#status-counts'));
$.getJSON('{{ url('language_stats_data_ac_rate') }}', function (data) {
draw_bar_chart(data, $('#ac-rate'));
});
draw_pie_chart({{ data_all }}, $('#lang-all'));
draw_pie_chart({{ lang_ac }}, $('#lang-ac'));
draw_pie_chart({{ status_counts }}, $('#status-counts'));
draw_bar_chart({{ ac_rate }}, $('#ac-rate'));
});
</script>
{% endblock %}

View file

@ -87,4 +87,24 @@
});
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>

65
templates/stats/site.html Normal file
View 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 %}

6
templates/stats/tab.html Normal file
View file

@ -0,0 +1,6 @@
{% extends "tabs-base.html" %}
{% block tabs %}
{{ make_tab('language', 'fa-list', url('language_stats'), _('Language')) }}
{{ make_tab('site', 'fa-list', url('site_stats'), _('Site')) }}
{% endblock %}

View file

@ -1,4 +1,5 @@
<input
autocomplete="off"
type="{{ widget.type }}"
name="{{ widget.name }}"
{% if widget.value != None %}