pull requirement.txt
This commit is contained in:
commit
c44fb6c47a
19 changed files with 479 additions and 263 deletions
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -845,8 +845,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(
|
||||
|
|
|
@ -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,28 +23,34 @@ from judge.utils.stats import (
|
|||
)
|
||||
|
||||
|
||||
ac_count = Count(
|
||||
Case(When(submission__result="AC", then=Value(1)), output_field=IntegerField())
|
||||
)
|
||||
class StatViewBase(TemplateView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
if not request.user.is_superuser:
|
||||
raise Http404
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
def repeat_chain(iterable):
|
||||
return chain.from_iterable(repeat(iterable))
|
||||
|
||||
|
||||
def language_data(
|
||||
request, language_count=Language.objects.annotate(count=Count("submission"))
|
||||
):
|
||||
languages = (
|
||||
language_count.filter(count__gt=0)
|
||||
.values("key", "name", "count")
|
||||
.order_by("-count")
|
||||
class StatLanguage(StatViewBase):
|
||||
template_name = "stats/language.html"
|
||||
ac_count = Count(
|
||||
Case(When(submission__result="AC", then=Value(1)), output_field=IntegerField())
|
||||
)
|
||||
num_languages = min(len(languages), settings.DMOJ_STATS_LANGUAGE_THRESHOLD)
|
||||
other_count = sum(map(itemgetter("count"), languages[num_languages:]))
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
def repeat_chain(iterable):
|
||||
return chain.from_iterable(repeat(iterable))
|
||||
|
||||
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:]))
|
||||
|
||||
return {
|
||||
"labels": list(map(itemgetter("name"), languages[:num_languages]))
|
||||
+ ["Other"],
|
||||
"datasets": [
|
||||
|
@ -50,53 +62,139 @@ 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(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 status_data(request, statuses=None):
|
||||
if not statuses:
|
||||
statuses = (
|
||||
Submission.objects.values("result")
|
||||
.annotate(count=Count("result"))
|
||||
.values("result", "count")
|
||||
.order_by("-count")
|
||||
def ac_rate(self):
|
||||
rate = CombinedExpression(
|
||||
self.ac_count / Count("submission"),
|
||||
"*",
|
||||
Value(100.0),
|
||||
output_field=FloatField(),
|
||||
)
|
||||
data = []
|
||||
for status in statuses:
|
||||
res = status["result"]
|
||||
if not res:
|
||||
continue
|
||||
count = status["count"]
|
||||
data.append((str(Submission.USER_DISPLAY_CODES[res]), count))
|
||||
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))
|
||||
|
||||
return JsonResponse(get_pie_chart(data), safe=False)
|
||||
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 ac_rate(request):
|
||||
rate = CombinedExpression(
|
||||
ac_count / Count("submission"), "*", Value(100.0), output_field=FloatField()
|
||||
)
|
||||
data = (
|
||||
Language.objects.annotate(total=Count("submission"), ac_rate=rate)
|
||||
.filter(total__gt=0)
|
||||
.order_by("total")
|
||||
.values_list("name", "ac_rate")
|
||||
)
|
||||
return JsonResponse(get_bar_chart(list(data)))
|
||||
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"])
|
||||
|
||||
|
||||
def language(request):
|
||||
return render(
|
||||
request,
|
||||
"stats/language.html",
|
||||
{
|
||||
"title": _("Language statistics"),
|
||||
"tab": "language",
|
||||
},
|
||||
)
|
||||
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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue