Merge branch 'LQDJudge:master' into master

This commit is contained in:
Bao Le 2023-11-21 14:21:00 +08:00 committed by GitHub
commit 0403c4be31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 2242 additions and 1361 deletions

View file

@ -11,3 +11,7 @@ repos:
rev: 22.12.0
hooks:
- id: black
- repo: https://github.com/hadialqattan/pycln
rev: 'v2.3.0'
hooks:
- id: pycln

View file

@ -0,0 +1,33 @@
# Generated by Django 3.2.18 on 2023-11-02 01:41
from django.db import migrations, models
def migrate(apps, schema_editor):
Room = apps.get_model("chat_box", "Room")
Message = apps.get_model("chat_box", "Message")
for room in Room.objects.all():
messages = room.message_set
last_msg = messages.first()
if last_msg:
room.last_msg_time = last_msg.time
room.save()
class Migration(migrations.Migration):
dependencies = [
("chat_box", "0014_userroom_unread_count"),
]
operations = [
migrations.AddField(
model_name="room",
name="last_msg_time",
field=models.DateTimeField(
db_index=True, null=True, verbose_name="last seen"
),
),
migrations.RunPython(migrate, migrations.RunPython.noop, atomic=True),
]

View file

@ -1,6 +1,7 @@
from django.db import models
from django.db.models import CASCADE, Q
from django.utils.translation import gettext_lazy as _
from django.utils.functional import cached_property
from judge.models.profile import Profile
@ -17,25 +18,40 @@ class Room(models.Model):
user_two = models.ForeignKey(
Profile, related_name="user_two", verbose_name="user 2", on_delete=CASCADE
)
last_msg_time = models.DateTimeField(
verbose_name=_("last seen"), null=True, db_index=True
)
class Meta:
app_label = "chat_box"
@cache_wrapper(prefix="Rc")
def contain(self, profile):
return self.user_one == profile or self.user_two == profile
@cache_wrapper(prefix="Rinfo")
def _info(self):
last_msg = self.message_set.first()
return {
"user_ids": [self.user_one.id, self.user_two.id],
"last_message": last_msg.body if last_msg else None,
}
@cached_property
def _cached_info(self):
return self._info()
def contain(self, profile):
return profile.id in self._cached_info["user_ids"]
@cache_wrapper(prefix="Rou")
def other_user(self, profile):
return self.user_one if profile == self.user_two else self.user_two
@cache_wrapper(prefix="Rus")
def other_user_id(self, profile):
user_ids = self._cached_info["user_ids"]
return sum(user_ids) - profile.id
def users(self):
return [self.user_one, self.user_two]
@cache_wrapper(prefix="Rlmb")
def last_message_body(self):
return self.message_set.first().body
return self._cached_info["last_message"]
class Message(models.Model):

View file

@ -29,7 +29,6 @@ from django.utils import timezone
from django.contrib.auth.decorators import login_required
from django.urls import reverse
import datetime
from judge import event_poster as event
from judge.jinja2.gravatar import gravatar
@ -38,8 +37,6 @@ from judge.models import Friend
from chat_box.models import Message, Profile, Room, UserRoom, Ignore
from chat_box.utils import encrypt_url, decrypt_url, encrypt_channel, get_unread_boxes
import json
class ChatView(ListView):
context_object_name = "message"
@ -87,8 +84,8 @@ class ChatView(ListView):
self.room_id = request_room
self.messages = (
Message.objects.filter(hidden=False, room=self.room_id, id__lt=last_id)
.select_related("author", "author__user")
.defer("author__about", "author__user_script")[:page_size]
.select_related("author")
.only("body", "time", "author__rating", "author__display_rank")[:page_size]
)
if not only_messages:
return super().get(request, *args, **kwargs)
@ -207,7 +204,9 @@ def post_message(request):
},
)
else:
Room.last_message_body.dirty(room)
Room._info.dirty(room)
room.last_msg_time = new_message.time
room.save()
for user in room.users():
event.post(
@ -354,11 +353,11 @@ def get_online_status(profile, other_profile_ids, rooms=None):
room = Room.objects.get(id=i["room"])
other_profile = room.other_user(profile)
count[other_profile.id] = i["unread_count"]
rooms = Room.objects.filter(id__in=rooms)
for room in rooms:
room = Room.objects.get(id=room)
other_profile = room.other_user(profile)
last_msg[other_profile.id] = room.last_message_body()
room_of_user[other_profile.id] = room.id
other_profile_id = room.other_user_id(profile)
last_msg[other_profile_id] = room.last_message_body()
room_of_user[other_profile_id] = room.id
for other_profile in other_profiles:
is_online = False
@ -392,9 +391,6 @@ def get_status_context(profile, include_ignored=False):
recent_profile = (
Room.objects.filter(Q(user_one=profile) | Q(user_two=profile))
.annotate(
last_msg_time=Subquery(
Message.objects.filter(room=OuterRef("pk")).values("time")[:1]
),
other_user=Case(
When(user_one=profile, then="user_two"),
default="user_one",
@ -415,28 +411,15 @@ def get_status_context(profile, include_ignored=False):
.values_list("id", flat=True)
)
all_user_status = (
queryset.filter(last_access__gte=last_5_minutes)
.annotate(is_online=Case(default=True, output_field=BooleanField()))
.order_by("-rating")
.exclude(id__in=admin_list)
.exclude(id__in=recent_profile_ids)
.values_list("id", flat=True)[:30]
)
return [
{
"title": "Recent",
"title": _("Recent"),
"user_list": get_online_status(profile, recent_profile_ids, recent_rooms),
},
{
"title": "Admin",
"title": _("Admin"),
"user_list": get_online_status(profile, admin_list),
},
{
"title": "Other",
"user_list": get_online_status(profile, all_user_status),
},
]

View file

@ -9,7 +9,6 @@ https://docs.djangoproject.com/en/1.11/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import datetime
import os
import tempfile

View file

@ -1100,6 +1100,11 @@ urlpatterns = [
internal.InternalProblem.as_view(),
name="internal_problem",
),
url(
r"^problem_votes$",
internal.get_problem_votes,
name="internal_problem_votes",
),
url(
r"^request_time$",
internal.InternalRequestTime.as_view(),

View file

@ -3,7 +3,6 @@ import json
import logging
import threading
import time
import os
from collections import deque, namedtuple
from operator import itemgetter
@ -25,6 +24,7 @@ from judge.models import (
Submission,
SubmissionTestCase,
)
from judge.bridge.utils import VanishedSubmission
logger = logging.getLogger("judge.bridge")
json_log = logging.getLogger("judge.json.bridge")
@ -94,12 +94,6 @@ class JudgeHandler(ZlibPacketHandler):
def on_disconnect(self):
self._stop_ping.set()
if self._working:
logger.error(
"Judge %s disconnected while handling submission %s",
self.name,
self._working,
)
self.judges.remove(self)
if self.name is not None:
self._disconnected()
@ -119,16 +113,6 @@ class JudgeHandler(ZlibPacketHandler):
None,
0,
)
# Submission.objects.filter(id=self._working).update(
# status="IE", result="IE", error=""
# )
# json_log.error(
# self._make_json_log(
# sub=self._working,
# action="close",
# info="IE due to shutdown on grading",
# )
# )
def _authenticate(self, id, key):
try:
@ -327,6 +311,9 @@ class JudgeHandler(ZlibPacketHandler):
def submit(self, id, problem, language, source):
data = self.get_related_submission_data(id)
if not data:
self._update_internal_error_submission(id, "Submission vanished")
raise VanishedSubmission()
self._working = id
self._working_data = {
"problem": problem,
@ -675,8 +662,11 @@ class JudgeHandler(ZlibPacketHandler):
self._free_self(packet)
id = packet["submission-id"]
self._update_internal_error_submission(id, packet["message"])
def _update_internal_error_submission(self, id, message):
if Submission.objects.filter(id=id).update(
status="IE", result="IE", error=packet["message"]
status="IE", result="IE", error=message
):
event.post(
"sub_%s" % Submission.get_id_secret(id), {"type": "internal-error"}
@ -684,9 +674,9 @@ class JudgeHandler(ZlibPacketHandler):
self._post_update_submission(id, "internal-error", done=True)
json_log.info(
self._make_json_log(
packet,
sub=id,
action="internal-error",
message=packet["message"],
message=message,
finish=True,
result="IE",
)
@ -695,10 +685,10 @@ class JudgeHandler(ZlibPacketHandler):
logger.warning("Unknown submission: %s", id)
json_log.error(
self._make_json_log(
packet,
sub=id,
action="internal-error",
info="unknown submission",
message=packet["message"],
message=message,
finish=True,
result="IE",
)

View file

@ -3,6 +3,8 @@ from collections import namedtuple
from operator import attrgetter
from threading import RLock
from judge.bridge.utils import VanishedSubmission
try:
from llist import dllist
except ImportError:
@ -39,6 +41,8 @@ class JudgeList(object):
)
try:
judge.submit(id, problem, language, source)
except VanishedSubmission:
pass
except Exception:
logger.exception(
"Failed to dispatch %d (%s, %s) to %s",

2
judge/bridge/utils.py Normal file
View file

@ -0,0 +1,2 @@
class VanishedSubmission(Exception):
pass

View file

@ -40,7 +40,10 @@ def cache_wrapper(prefix, timeout=None):
def _get(key):
if not l0_cache:
return cache.get(key)
return l0_cache.get(key) or cache.get(key)
result = l0_cache.get(key)
if result is None:
result = cache.get(key)
return result
def _set_l0(key, value):
if l0_cache:
@ -56,7 +59,7 @@ def cache_wrapper(prefix, timeout=None):
result = _get(cache_key)
if result is not None:
_set_l0(cache_key, result)
if result == NONE_RESULT:
if type(result) == str and result == NONE_RESULT:
result = None
return result
result = func(*args, **kwargs)

View file

@ -144,14 +144,16 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
def get(self, request, *args, **kwargs):
target_comment = None
self.object = self.get_object()
if "comment-id" in request.GET:
comment_id = int(request.GET["comment-id"])
try:
comment_id = int(request.GET["comment-id"])
comment_obj = Comment.objects.get(id=comment_id)
except Comment.DoesNotExist:
except (Comment.DoesNotExist, ValueError):
raise Http404
if comment_obj.linked_object != self.object:
raise Http404
target_comment = comment_obj.get_root()
self.object = self.get_object()
return self.render_to_response(
self.get_context_data(
object=self.object,

View file

@ -11,7 +11,6 @@ from django.contrib.auth.models import User
from django.contrib.auth.forms import AuthenticationForm
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.core.validators import RegexValidator
from django.db import transaction
from django.db.models import Q
from django.forms import (
CharField,
@ -52,7 +51,6 @@ from judge.widgets import (
DateTimePickerWidget,
ImageWidget,
)
from judge.tasks import rescore_contest
def fix_unicode(string, unsafe=tuple("\u202a\u202b\u202d\u202e")):
@ -282,16 +280,9 @@ class EditOrganizationContestForm(ModelForm):
"view_contest_scoreboard",
]:
self.fields[field].widget.data_url = (
self.fields[field].widget.get_url() + "?org_id=1"
self.fields[field].widget.get_url() + f"?org_id={self.org_id}"
)
def save(self, commit=True):
res = super(EditOrganizationContestForm, self).save(commit=False)
if commit:
res.save()
transaction.on_commit(rescore_contest.s(res.key).delay)
return res
class Meta:
model = Contest
fields = (

7
judge/logging.py Normal file
View file

@ -0,0 +1,7 @@
import logging
error_log = logging.getLogger("judge.errors")
def log_exception(msg):
error_log.exception(msg)

View file

@ -1,6 +1,5 @@
from django.core.management.base import BaseCommand
from judge.models import *
from collections import defaultdict
import csv
import os
from django.conf import settings

File diff suppressed because one or more lines are too long

View file

@ -1,9 +1,10 @@
import numpy as np
from django.conf import settings
import os
from django.core.cache import cache
import hashlib
from django.core.cache import cache
from django.conf import settings
from judge.caching import cache_wrapper
@ -13,14 +14,13 @@ class CollabFilter:
# name = 'collab_filter' or 'collab_filter_time'
def __init__(self, name):
embeddings = np.load(
self.embeddings = np.load(
os.path.join(settings.ML_OUTPUT_PATH, name + "/embeddings.npz"),
allow_pickle=True,
)
arr0, arr1 = embeddings.files
_, problem_arr = self.embeddings.files
self.name = name
self.user_embeddings = embeddings[arr0]
self.problem_embeddings = embeddings[arr1]
self.problem_embeddings = self.embeddings[problem_arr]
def __str__(self):
return self.name
@ -44,18 +44,32 @@ class CollabFilter:
scores = u.dot(V.T)
return scores
def _get_embedding_version(self):
first_problem = self.problem_embeddings[0]
array_bytes = first_problem.tobytes()
hash_object = hashlib.sha256(array_bytes)
hash_bytes = hash_object.digest()
return hash_bytes.hex()[:5]
@cache_wrapper(prefix="CFgue", timeout=86400)
def _get_user_embedding(self, user_id, embedding_version):
user_arr, _ = self.embeddings.files
user_embeddings = self.embeddings[user_arr]
if user_id >= len(user_embeddings):
return user_embeddings[0]
return user_embeddings[user_id]
def get_user_embedding(self, user_id):
version = self._get_embedding_version()
return self._get_user_embedding(user_id, version)
@cache_wrapper(prefix="user_recommendations", timeout=3600)
def user_recommendations(self, user, problems, measure=DOT, limit=None):
uid = user.id
if uid >= len(self.user_embeddings):
uid = 0
scores = self.compute_scores(
self.user_embeddings[uid], self.problem_embeddings, measure
)
def user_recommendations(self, user_id, problems, measure=DOT, limit=None):
user_embedding = self.get_user_embedding(user_id)
scores = self.compute_scores(user_embedding, self.problem_embeddings, measure)
res = [] # [(score, problem)]
for pid in problems:
# pid = problem.id
if pid < len(scores):
res.append((scores[pid], pid))

View file

@ -99,11 +99,13 @@ class Contest(models.Model, PageVotable, Bookmarkable):
)
authors = models.ManyToManyField(
Profile,
verbose_name=_("authors"),
help_text=_("These users will be able to edit the contest."),
related_name="authors+",
)
curators = models.ManyToManyField(
Profile,
verbose_name=_("curators"),
help_text=_(
"These users will be able to edit the contest, "
"but will not be listed as authors."
@ -113,6 +115,7 @@ class Contest(models.Model, PageVotable, Bookmarkable):
)
testers = models.ManyToManyField(
Profile,
verbose_name=_("testers"),
help_text=_(
"These users will be able to view the contest, " "but not edit it."
),

View file

@ -1,6 +1,5 @@
import errno
from operator import attrgetter
from math import sqrt
from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
@ -557,7 +556,7 @@ class Problem(models.Model, PageVotable, Bookmarkable):
def save(self, *args, **kwargs):
super(Problem, self).save(*args, **kwargs)
if self.code != self.__original_code:
if self.__original_code and self.code != self.__original_code:
if hasattr(self, "data_files") or self.pdf_description:
try:
problem_data_storage.rename(self.__original_code, self.code)

View file

@ -162,10 +162,10 @@ class ProblemData(models.Model):
get_file_cachekey(file),
)
cache.delete(cache_key)
except BadZipFile:
except (BadZipFile, FileNotFoundError):
pass
if self.zipfile != self.__original_zipfile and self.__original_zipfile:
self.__original_zipfile.delete(save=False)
if self.zipfile != self.__original_zipfile:
self.__original_zipfile.delete(save=False)
return super(ProblemData, self).save(*args, **kwargs)
def has_yml(self):

View file

@ -1,5 +1,4 @@
import csv
from tempfile import mktemp
import re
from django.conf import settings

View file

@ -5,7 +5,6 @@ from django import forms
from django.forms import ClearableFileInput
import os, os.path
import tempfile
import shutil
__all__ = ("handle_upload", "save_upload", "FineUploadForm", "FineUploadFileInput")

View file

@ -2,7 +2,6 @@ import hashlib
import json
import os
import re
import shutil
import yaml
import zipfile
@ -13,6 +12,8 @@ from django.urls import reverse
from django.utils.translation import gettext as _
from django.core.cache import cache
from judge.logging import log_exception
if os.altsep:
def split_path_first(
@ -324,11 +325,13 @@ def get_problem_case(problem, files):
settings.DMOJ_PROBLEM_DATA_ROOT, str(problem.data_files.zipfile)
)
if not os.path.exists(archive_path):
raise Exception('archive file "%s" does not exist' % archive_path)
log_exception('archive file "%s" does not exist' % archive_path)
return {}
try:
archive = zipfile.ZipFile(archive_path, "r")
except zipfile.BadZipfile:
raise Exception('bad archive: "%s"' % archive_path)
log_exception('bad archive: "%s"' % archive_path)
return {}
for file in uncached_files:
cache_key = "problem_archive:%s:%s" % (problem.code, get_file_cachekey(file))

View file

@ -1,8 +1,8 @@
from collections import defaultdict
from math import e
import os, zipfile
from datetime import datetime
from datetime import datetime, timedelta
import random
from enum import Enum
from django.conf import settings
from django.core.cache import cache
@ -10,6 +10,7 @@ from django.db.models import Case, Count, ExpressionWrapper, F, Max, Q, When
from django.db.models.fields import FloatField
from django.utils import timezone
from django.utils.translation import gettext as _, gettext_noop
from django.http import Http404
from judge.models import Problem, Submission
from judge.ml.collab_filter import CollabFilter
@ -249,3 +250,72 @@ def finished_submission(sub):
keys += ["contest_complete:%d" % participation.id]
keys += ["contest_attempted:%d" % participation.id]
cache.delete_many(keys)
class RecommendationType(Enum):
HOT_PROBLEM = 1
CF_DOT = 2
CF_COSINE = 3
CF_TIME_DOT = 4
CF_TIME_COSINE = 5
# Return a list of list. Each inner list correspond to each type in types
def get_user_recommended_problems(
user_id,
problem_ids,
recommendation_types,
limits,
shuffle=False,
):
cf_model = CollabFilter("collab_filter")
cf_time_model = CollabFilter("collab_filter_time")
def get_problem_ids_from_type(rec_type, limit):
if type(rec_type) == int:
try:
rec_type = RecommendationType(rec_type)
except ValueError:
raise Http404()
if rec_type == RecommendationType.HOT_PROBLEM:
return [
problem.id
for problem in hot_problems(timedelta(days=7), limit)
if problem.id in set(problem_ids)
]
if rec_type == RecommendationType.CF_DOT:
return cf_model.user_recommendations(
user_id, problem_ids, cf_model.DOT, limit
)
if rec_type == RecommendationType.CF_COSINE:
return cf_model.user_recommendations(
user_id, problem_ids, cf_model.COSINE, limit
)
if rec_type == RecommendationType.CF_TIME_DOT:
return cf_time_model.user_recommendations(
user_id, problem_ids, cf_model.DOT, limit
)
if rec_type == RecommendationType.CF_TIME_COSINE:
return cf_time_model.user_recommendations(
user_id, problem_ids, cf_model.COSINE, limit
)
return []
all_problems = []
for rec_type, limit in zip(recommendation_types, limits):
all_problems += get_problem_ids_from_type(rec_type, limit)
if shuffle:
seed = datetime.now().strftime("%d%m%Y")
random.Random(seed).shuffle(all_problems)
# deduplicate problems
res = []
used_pid = set()
for obj in all_problems:
if type(obj) == tuple:
obj = obj[1]
if obj not in used_pid:
res.append(obj)
used_pid.add(obj)
return res

View file

@ -144,7 +144,11 @@ class ContestList(
context_object_name = "past_contests"
all_sorts = frozenset(("name", "user_count", "start_time"))
default_desc = frozenset(("name", "user_count"))
default_sort = "-start_time"
def get_default_sort_order(self, request):
if request.GET.get("contest") and settings.ENABLE_FTS:
return "-relevance"
return "-start_time"
@cached_property
def _now(self):
@ -157,7 +161,7 @@ class ContestList(
if request.GET.get("show_orgs"):
self.show_orgs = 1
if "orgs" in self.request.GET and self.request.profile:
if self.request.GET.get("orgs") and self.request.profile:
try:
self.org_query = list(map(int, request.GET.getlist("orgs")))
if not self.request.user.is_superuser:
@ -165,8 +169,10 @@ class ContestList(
i
for i in self.org_query
if i
in self.request.profile.organizations.values_list(
"id", flat=True
in set(
self.request.profile.organizations.values_list(
"id", flat=True
)
)
]
except ValueError:
@ -181,7 +187,7 @@ class ContestList(
.prefetch_related("tags", "organizations", "authors", "curators", "testers")
)
if "contest" in self.request.GET:
if self.request.GET.get("contest"):
self.contest_query = query = " ".join(
self.request.GET.getlist("contest")
).strip()
@ -249,10 +255,7 @@ class ContestList(
context["org_query"] = self.org_query
context["show_orgs"] = int(self.show_orgs)
if self.request.profile:
if self.request.user.is_superuser:
context["organizations"] = Organization.objects.all()
else:
context["organizations"] = self.request.profile.organizations.all()
context["organizations"] = self.request.profile.organizations.all()
context["page_type"] = "list"
context.update(self.get_sort_context())
context.update(self.get_sort_paginate_context())
@ -419,7 +422,14 @@ class ContestDetail(
return []
res = []
for organization in self.object.organizations.all():
can_edit = False
if self.request.profile.can_edit_organization(organization):
can_edit = True
if self.request.profile in organization and self.object.is_editable_by(
self.request.user
):
can_edit = True
if can_edit:
res.append(organization)
return res
@ -441,16 +451,32 @@ class ContestDetail(
.add_i18n_name(self.request.LANGUAGE_CODE)
)
context["editable_organizations"] = self.get_editable_organizations()
context["is_clonable"] = is_contest_clonable(self.request, self.object)
return context
class ContestClone(
ContestMixin, PermissionRequiredMixin, TitleMixin, SingleObjectFormView
):
def is_contest_clonable(request, contest):
if not request.profile:
return False
if not Organization.objects.filter(admins=request.profile).exists():
return False
if request.user.has_perm("judge.clone_contest"):
return True
if contest.ended:
return True
return False
class ContestClone(ContestMixin, TitleMixin, SingleObjectFormView):
title = _("Clone Contest")
template_name = "contest/clone.html"
form_class = ContestCloneForm
permission_required = "judge.clone_contest"
def get_object(self, queryset=None):
contest = super().get_object(queryset)
if not is_contest_clonable(self.request, contest):
raise Http404()
return contest
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
@ -475,6 +501,7 @@ class ContestClone(
contest.is_visible = False
contest.user_count = 0
contest.key = form.cleaned_data["key"]
contest.is_rated = False
contest.save()
contest.tags.set(tags)

View file

@ -6,6 +6,7 @@ from django.utils.translation import gettext as _, gettext_lazy
from django.db.models import Count, Q
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.shortcuts import render
from judge.utils.diggpaginator import DiggPaginator
from judge.models import VolunteerProblemVote, Problem
@ -21,7 +22,7 @@ class InternalView(object):
class InternalProblem(InternalView, ListView):
model = Problem
title = _("Internal problems")
template_name = "internal/problem.html"
template_name = "internal/problem/problem.html"
paginate_by = 100
context_object_name = "problems"
@ -63,6 +64,28 @@ class InternalProblem(InternalView, ListView):
return context
def get_problem_votes(request):
if not request.user.is_superuser:
return HttpResponseForbidden()
try:
problem = Problem.objects.get(id=request.GET.get("id"))
except:
return HttpResponseForbidden()
votes = (
problem.volunteer_user_votes.select_related("voter")
.prefetch_related("types")
.order_by("id")
)
return render(
request,
"internal/problem/votes.html",
{
"problem": problem,
"votes": votes,
},
)
class RequestTimeMixin(object):
def get_requests_data(self):
logger = logging.getLogger(self.log_name)

View file

@ -73,6 +73,7 @@ from judge.views.problem import ProblemList
from judge.views.contests import ContestList
from judge.views.submission import AllSubmissions, SubmissionsListBase
from judge.views.feed import FeedView
from judge.tasks import rescore_contest
__all__ = [
"OrganizationList",
@ -394,7 +395,7 @@ class OrganizationContestMixin(
model = Contest
def is_contest_editable(self, request, contest):
return request.profile in contest.authors.all() or self.can_edit_organization(
return contest.is_editable_by(request.user) or self.can_edit_organization(
self.organization
)
@ -947,7 +948,7 @@ class EditOrganizationContest(
def get_content_title(self):
href = reverse("contest_view", args=[self.contest.key])
return mark_safe(f'Edit <a href="{href}">{self.contest.key}</a>')
return mark_safe(_("Edit") + f' <a href="{href}">{self.contest.key}</a>')
def get_object(self):
return self.contest
@ -960,6 +961,19 @@ class EditOrganizationContest(
self.object.organizations.add(self.organization)
self.object.is_organization_private = True
self.object.save()
if any(
f in form.changed_data
for f in (
"start_time",
"end_time",
"time_limit",
"format_config",
"format_name",
"freeze_after",
)
):
transaction.on_commit(rescore_contest.s(self.object.key).delay)
return res
def get_problem_formset(self, post=False):

View file

@ -1,10 +1,8 @@
import logging
import os
import shutil
from datetime import timedelta, datetime
from operator import itemgetter
from random import randrange
import random
from copy import deepcopy
from django.core.cache import cache
@ -77,6 +75,8 @@ from judge.utils.problems import (
user_attempted_ids,
user_completed_ids,
get_related_problems,
get_user_recommended_problems,
RecommendationType,
)
from judge.utils.strings import safe_float_or_none, safe_int_or_none
from judge.utils.tickets import own_ticket_filter
@ -834,24 +834,34 @@ class ProblemFeed(ProblemList, FeedView):
title = _("Problem feed")
feed_type = None
# arr = [[], [], ..]
def merge_recommendation(self, arr):
seed = datetime.now().strftime("%d%m%Y")
merged_array = []
for a in arr:
merged_array += a
random.Random(seed).shuffle(merged_array)
def get_recommended_problem_ids(self, queryset):
user_id = self.request.profile.id
problem_ids = queryset.values_list("id", flat=True)
rec_types = [
RecommendationType.CF_DOT,
RecommendationType.CF_COSINE,
RecommendationType.CF_TIME_DOT,
RecommendationType.CF_TIME_COSINE,
RecommendationType.HOT_PROBLEM,
]
limits = [100, 100, 100, 100, 20]
shuffle = True
res = []
used_pid = set()
allow_debug_type = (
self.request.user.is_impersonate or self.request.user.is_superuser
)
if allow_debug_type and "debug_type" in self.request.GET:
try:
debug_type = int(self.request.GET.get("debug_type"))
except ValueError:
raise Http404()
rec_types = [debug_type]
limits = [100]
shuffle = False
for obj in merged_array:
if type(obj) == tuple:
obj = obj[1]
if obj not in used_pid:
res.append(obj)
used_pid.add(obj)
return res
return get_user_recommended_problems(
user_id, problem_ids, rec_types, limits, shuffle
)
def get_queryset(self):
if self.feed_type == "volunteer":
@ -885,40 +895,8 @@ class ProblemFeed(ProblemList, FeedView):
if not settings.ML_OUTPUT_PATH or not user:
return queryset.order_by("?").add_i18n_name(self.request.LANGUAGE_CODE)
cf_model = CollabFilter("collab_filter")
cf_time_model = CollabFilter("collab_filter_time")
q = self.get_recommended_problem_ids(queryset)
queryset = queryset.values_list("id", flat=True)
hot_problems_recommendations = [
problem.id
for problem in hot_problems(timedelta(days=7), 20)
if problem.id in set(queryset)
]
q = self.merge_recommendation(
[
cf_model.user_recommendations(user, queryset, cf_model.DOT, 100),
cf_model.user_recommendations(
user,
queryset,
cf_model.COSINE,
100,
),
cf_time_model.user_recommendations(
user,
queryset,
cf_time_model.COSINE,
100,
),
cf_time_model.user_recommendations(
user,
queryset,
cf_time_model.DOT,
100,
),
hot_problems_recommendations,
]
)
queryset = Problem.objects.filter(id__in=q)
queryset = queryset.add_i18n_name(self.request.LANGUAGE_CODE)

View file

@ -56,6 +56,7 @@ from judge.utils.fine_uploader import (
FineUploadForm,
)
from judge.views.problem import ProblemMixin
from judge.logging import log_exception
mimetypes.init()
mimetypes.add_type("application/x-yaml", ".yml")
@ -249,6 +250,9 @@ class ProblemDataView(TitleMixin, ProblemManagerMixin):
return ZipFile(data.zipfile.path).namelist()
except BadZipfile:
return []
except FileNotFoundError as e:
log_exception(e)
return []
return []
def get_context_data(self, **kwargs):

View file

@ -1,6 +1,6 @@
from django.views.generic import TemplateView
from django.utils.translation import gettext as _
from django.http import HttpResponseForbidden
from django.http import HttpResponseForbidden, JsonResponse
from judge.models import Contest
from django.utils.safestring import mark_safe
@ -21,7 +21,7 @@ class Resolver(TemplateView):
hidden_subtasks = self.contest.format.get_hidden_subtasks()
num_problems = len(problems)
problem_sub = [0] * num_problems
sub_frozen = [0] * num_problems
sub_frozen = [[] for _ in range(num_problems)]
problems_json = {str(i): {} for i in range(1, num_problems + 1)}
users = {}
@ -126,10 +126,8 @@ class Resolver(TemplateView):
for i in hidden_subtasks:
order = id_to_order[i]
if hidden_subtasks[i]:
sub_frozen[order - 1] = min(hidden_subtasks[i])
else:
sub_frozen[order - 1] = problem_sub[order - 1] + 1
sub_frozen[order - 1] = list(hidden_subtasks[i])
return {
"problem_sub": problem_sub,
"sub_frozen": sub_frozen,
@ -143,8 +141,15 @@ class Resolver(TemplateView):
return context
def get(self, request, *args, **kwargs):
if request.user.is_superuser:
self.contest = Contest.objects.get(key=kwargs.get("contest"))
if self.contest.format.has_hidden_subtasks:
return super(Resolver, self).get(request, *args, **kwargs)
return HttpResponseForbidden()
if not request.user.is_superuser:
return HttpResponseForbidden()
self.contest = Contest.objects.get(key=kwargs.get("contest"))
if not self.contest.format.has_hidden_subtasks:
return HttpResponseForbidden()
if self.request.GET.get("json"):
json_dumps_params = {"ensure_ascii": False}
return JsonResponse(
self.get_contest_json(), json_dumps_params=json_dumps_params
)
return super(Resolver, self).get(request, *args, **kwargs)

View file

@ -1,6 +1,5 @@
import json
import os.path
import zipfile
from operator import attrgetter
from django.conf import settings
@ -196,8 +195,8 @@ def get_cases_data(submission):
continue
count += 1
problem_data[count] = {
"input": case_data[case.input_file] if case.input_file else "",
"answer": case_data[case.output_file] if case.output_file else "",
"input": case_data.get(case.input_file, "") if case.input_file else "",
"answer": case_data.get(case.output_file, "") if case.output_file else "",
}
return problem_data

File diff suppressed because it is too large Load diff

View file

@ -39,12 +39,6 @@ msgstr "Đăng ký tên"
msgid "Report"
msgstr "Báo cáo"
msgid "Insert Image"
msgstr "Chèn hình ảnh"
msgid "Save"
msgstr "Lưu"
msgid "2sat"
msgstr ""
@ -600,3 +594,8 @@ msgstr ""
msgid "z-function"
msgstr ""
#~ msgid "Insert Image"
#~ msgstr "Chèn hình ảnh"
#~ msgid "Save"
#~ msgstr "Lưu"

View file

@ -147,8 +147,9 @@
}
}
.blog-box:hover {
.blog-box:hover, .blog-box:not(.pre-expand-blog) {
border-color: #8a8a8a;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
}
.blog-description {

View file

@ -269,10 +269,10 @@ a {
.actionbar-button {
cursor: pointer;
padding: 0.8em;
border: 0.2px solid lightgray;
border-radius: 5em;
font-weight: bold;
display: inherit;
background: lightgray;
}
.actionbar-block {
display: flex;
@ -287,7 +287,7 @@ a {
border-radius: 5em 0 0 5em;
}
.actionbar-button:hover {
background: lightgray;
background: darkgray;
}
.dislike-button {
padding-left: 0.5em;

View file

@ -42,7 +42,6 @@
white-space: pre-wrap;
word-wrap: break-word;
margin: 1.5em 0 1.5em 0;
padding: 1em;
border: 1px solid $border_gray;
background-color: #f8f8f8;

View file

@ -1956,8 +1956,10 @@ input::placeholder {
background-color: rgb(24, 26, 27);
box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 5px;
}
.blog-box:hover {
.blog-box:hover,
.blog-box:not(.pre-expand-blog) {
border-color: rgb(81, 88, 91);
box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 2px;
}
.problem-feed-name a {
color: rgb(102, 177, 250);
@ -2091,6 +2093,9 @@ ul.problem-list {
#comment-announcement:hover {
background-color: rgb(96, 104, 108);
}
.new-problem-info {
background-color: rgb(54, 39, 0);
}
.admin a,
.admin {
color: rgb(232, 230, 227) !important;
@ -2780,11 +2785,12 @@ a.voted {
border-left-color: rgb(48, 52, 54);
}
.actionbar .actionbar-button {
border-color: rgb(60, 65, 68);
background-image: initial;
background-color: rgb(49, 53, 55);
}
.actionbar .actionbar-button:hover {
background-image: initial;
background-color: rgb(49, 53, 55);
background-color: rgb(73, 79, 82);
}
.actionbar .dislike-button {
border-left-color: initial;

View file

@ -25,17 +25,17 @@ div.dmmd-preview-has-content div.dmmd-preview-content {
}
div.dmmd-no-button div.dmmd-preview-update {
display: none;
display: none;
}
div.dmmd-no-button div.dmmd-preview-content {
padding-bottom: 0;
padding-bottom: 0;
}
div.dmmd-no-button:not(.dmmd-preview-has-content) {
display: none;
display: none;
}
div.dmmd-preview-stale {
background: repeating-linear-gradient(-45deg, #fff, #fff 10px, #f8f8f8 10px, #f8f8f8 20px);
background: repeating-linear-gradient(-45deg, #fff, #fff 10px, #f8f8f8 10px, #f8f8f8 20px);
}

View file

@ -4,59 +4,59 @@
if you are not yet familiar with Fine Uploader UI.
-->
<script type="text/template" id="qq-template">
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
</div>
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
<span class="qq-upload-drop-area-text-selector"></span>
</div>
<div class="qq-upload-button-selector qq-upload-button">
<div>Upload a file</div>
</div>
<span class="qq-drop-processing-selector qq-drop-processing">
<span>Processing dropped files...</span>
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
</span>
<ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
<li>
<div class="qq-progress-bar-container-selector">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
</div>
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
<span class="qq-upload-file-selector qq-upload-file"></span>
<span class="qq-edit-filename-icon-selector qq-edit-filename-icon" aria-label="Edit filename"></span>
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
<span class="qq-upload-size-selector qq-upload-size"></span>
<button type="button" class="qq-btn qq-upload-cancel-selector qq-upload-cancel">Cancel</button>
<button type="button" class="qq-btn qq-upload-retry-selector qq-upload-retry">Retry</button>
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">Delete</button>
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
</li>
</ul>
<dialog class="qq-alert-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Close</button>
</div>
</dialog>
<dialog class="qq-confirm-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">No</button>
<button type="button" class="qq-ok-button-selector">Yes</button>
</div>
</dialog>
<dialog class="qq-prompt-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<input type="text">
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Cancel</button>
<button type="button" class="qq-ok-button-selector">Ok</button>
</div>
</dialog>
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
</div>
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
<span class="qq-upload-drop-area-text-selector"></span>
</div>
<div class="qq-upload-button-selector qq-upload-button">
<div>Upload a file</div>
</div>
<span class="qq-drop-processing-selector qq-drop-processing">
<span>Processing dropped files...</span>
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
</span>
<ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
<li>
<div class="qq-progress-bar-container-selector">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
</div>
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
<span class="qq-upload-file-selector qq-upload-file"></span>
<span class="qq-edit-filename-icon-selector qq-edit-filename-icon" aria-label="Edit filename"></span>
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
<span class="qq-upload-size-selector qq-upload-size"></span>
<button type="button" class="qq-btn qq-upload-cancel-selector qq-upload-cancel">Cancel</button>
<button type="button" class="qq-btn qq-upload-retry-selector qq-upload-retry">Retry</button>
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">Delete</button>
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
</li>
</ul>
<dialog class="qq-alert-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Close</button>
</div>
</dialog>
<dialog class="qq-confirm-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">No</button>
<button type="button" class="qq-ok-button-selector">Yes</button>
</div>
</dialog>
<dialog class="qq-prompt-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<input type="text">
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Cancel</button>
<button type="button" class="qq-ok-button-selector">Ok</button>
</div>
</dialog>
</div>
</script>

View file

@ -5,78 +5,78 @@
on how to customize this template.
-->
<script type="text/template" id="qq-template">
<div class="qq-uploader-selector qq-uploader qq-gallery" qq-drop-area-text="Drop files here">
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
</div>
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
<span class="qq-upload-drop-area-text-selector"></span>
</div>
<div class="qq-upload-button-selector qq-upload-button">
<div>Upload a file</div>
</div>
<span class="qq-drop-processing-selector qq-drop-processing">
<span>Processing dropped files...</span>
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
</span>
<ul class="qq-upload-list-selector qq-upload-list" role="region" aria-live="polite" aria-relevant="additions removals">
<li>
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
<div class="qq-progress-bar-container-selector qq-progress-bar-container">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
</div>
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
<div class="qq-thumbnail-wrapper">
<img class="qq-thumbnail-selector" qq-max-size="120" qq-server-scale>
</div>
<button type="button" class="qq-upload-cancel-selector qq-upload-cancel">X</button>
<button type="button" class="qq-upload-retry-selector qq-upload-retry">
<span class="qq-btn qq-retry-icon" aria-label="Retry"></span>
Retry
</button>
<div class="qq-file-info">
<div class="qq-file-name">
<span class="qq-upload-file-selector qq-upload-file"></span>
<span class="qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon" aria-label="Edit filename"></span>
</div>
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
<span class="qq-upload-size-selector qq-upload-size"></span>
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">
<span class="qq-btn qq-delete-icon" aria-label="Delete"></span>
</button>
<button type="button" class="qq-btn qq-upload-pause-selector qq-upload-pause">
<span class="qq-btn qq-pause-icon" aria-label="Pause"></span>
</button>
<button type="button" class="qq-btn qq-upload-continue-selector qq-upload-continue">
<span class="qq-btn qq-continue-icon" aria-label="Continue"></span>
</button>
</div>
</li>
</ul>
<dialog class="qq-alert-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Close</button>
</div>
</dialog>
<dialog class="qq-confirm-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">No</button>
<button type="button" class="qq-ok-button-selector">Yes</button>
</div>
</dialog>
<dialog class="qq-prompt-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<input type="text">
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Cancel</button>
<button type="button" class="qq-ok-button-selector">Ok</button>
</div>
</dialog>
<div class="qq-uploader-selector qq-uploader qq-gallery" qq-drop-area-text="Drop files here">
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
</div>
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
<span class="qq-upload-drop-area-text-selector"></span>
</div>
<div class="qq-upload-button-selector qq-upload-button">
<div>Upload a file</div>
</div>
<span class="qq-drop-processing-selector qq-drop-processing">
<span>Processing dropped files...</span>
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
</span>
<ul class="qq-upload-list-selector qq-upload-list" role="region" aria-live="polite" aria-relevant="additions removals">
<li>
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
<div class="qq-progress-bar-container-selector qq-progress-bar-container">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
</div>
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
<div class="qq-thumbnail-wrapper">
<img class="qq-thumbnail-selector" qq-max-size="120" qq-server-scale>
</div>
<button type="button" class="qq-upload-cancel-selector qq-upload-cancel">X</button>
<button type="button" class="qq-upload-retry-selector qq-upload-retry">
<span class="qq-btn qq-retry-icon" aria-label="Retry"></span>
Retry
</button>
<div class="qq-file-info">
<div class="qq-file-name">
<span class="qq-upload-file-selector qq-upload-file"></span>
<span class="qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon" aria-label="Edit filename"></span>
</div>
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
<span class="qq-upload-size-selector qq-upload-size"></span>
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">
<span class="qq-btn qq-delete-icon" aria-label="Delete"></span>
</button>
<button type="button" class="qq-btn qq-upload-pause-selector qq-upload-pause">
<span class="qq-btn qq-pause-icon" aria-label="Pause"></span>
</button>
<button type="button" class="qq-btn qq-upload-continue-selector qq-upload-continue">
<span class="qq-btn qq-continue-icon" aria-label="Continue"></span>
</button>
</div>
</li>
</ul>
<dialog class="qq-alert-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Close</button>
</div>
</dialog>
<dialog class="qq-confirm-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">No</button>
<button type="button" class="qq-ok-button-selector">Yes</button>
</div>
</dialog>
<dialog class="qq-prompt-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<input type="text">
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Cancel</button>
<button type="button" class="qq-ok-button-selector">Ok</button>
</div>
</dialog>
</div>
</script>

View file

@ -5,60 +5,60 @@
on how to customize this template.
-->
<script type="text/template" id="qq-simple-thumbnails-template">
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
</div>
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
<span class="qq-upload-drop-area-text-selector"></span>
</div>
<div class="qq-upload-button-selector qq-upload-button">
<div>Upload a file</div>
</div>
<span class="qq-drop-processing-selector qq-drop-processing">
<span>Processing dropped files...</span>
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
</span>
<ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
<li>
<div class="qq-progress-bar-container-selector">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
</div>
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
<img class="qq-thumbnail-selector" qq-max-size="100" qq-server-scale>
<span class="qq-upload-file-selector qq-upload-file"></span>
<span class="qq-edit-filename-icon-selector qq-edit-filename-icon" aria-label="Edit filename"></span>
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
<span class="qq-upload-size-selector qq-upload-size"></span>
<button type="button" class="qq-btn qq-upload-cancel-selector qq-upload-cancel">Cancel</button>
<button type="button" class="qq-btn qq-upload-retry-selector qq-upload-retry">Retry</button>
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">Delete</button>
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
</li>
</ul>
<dialog class="qq-alert-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Close</button>
</div>
</dialog>
<dialog class="qq-confirm-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">No</button>
<button type="button" class="qq-ok-button-selector">Yes</button>
</div>
</dialog>
<dialog class="qq-prompt-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<input type="text">
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Cancel</button>
<button type="button" class="qq-ok-button-selector">Ok</button>
</div>
</dialog>
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
</div>
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
<span class="qq-upload-drop-area-text-selector"></span>
</div>
<div class="qq-upload-button-selector qq-upload-button">
<div>Upload a file</div>
</div>
<span class="qq-drop-processing-selector qq-drop-processing">
<span>Processing dropped files...</span>
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
</span>
<ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
<li>
<div class="qq-progress-bar-container-selector">
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
</div>
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
<img class="qq-thumbnail-selector" qq-max-size="100" qq-server-scale>
<span class="qq-upload-file-selector qq-upload-file"></span>
<span class="qq-edit-filename-icon-selector qq-edit-filename-icon" aria-label="Edit filename"></span>
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
<span class="qq-upload-size-selector qq-upload-size"></span>
<button type="button" class="qq-btn qq-upload-cancel-selector qq-upload-cancel">Cancel</button>
<button type="button" class="qq-btn qq-upload-retry-selector qq-upload-retry">Retry</button>
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">Delete</button>
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
</li>
</ul>
<dialog class="qq-alert-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Close</button>
</div>
</dialog>
<dialog class="qq-confirm-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">No</button>
<button type="button" class="qq-ok-button-selector">Yes</button>
</div>
</dialog>
<dialog class="qq-prompt-dialog-selector">
<div class="qq-dialog-message-selector"></div>
<input type="text">
<div class="qq-dialog-buttons">
<button type="button" class="qq-cancel-button-selector">Cancel</button>
<button type="button" class="qq-ok-button-selector">Ok</button>
</div>
</dialog>
</div>
</script>

View file

@ -1,49 +1,49 @@
.mwe-math-mathml-inline {
display: inline !important;
display: inline !important;
}
.mwe-math-mathml-display {
display: block !important;
margin-left: auto;
margin-right: auto;
display: block !important;
margin-left: auto;
margin-right: auto;
}
.mwe-math-mathml-a11y {
clip: rect(1px, 1px, 1px, 1px);
overflow: hidden;
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
clip: rect(1px, 1px, 1px, 1px);
overflow: hidden;
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
}
.mwe-math-fallback-image-inline {
display: inline-block;
vertical-align: middle;
display: inline-block;
vertical-align: middle;
}
.mwe-math-fallback-image-display {
display: block;
margin-left: auto !important;
margin-right: auto !important;
display: block;
margin-left: auto !important;
margin-right: auto !important;
}
@font-face {
font-family: 'Latin Modern Math';
src: url('libs/latinmodernmath/latinmodern-math.eot'); /* IE9 Compat Modes */
src: local('Latin Modern Math'), local('LatinModernMath-Regular'),
font-family: 'Latin Modern Math';
src: url('libs/latinmodernmath/latinmodern-math.eot'); /* IE9 Compat Modes */
src: local('Latin Modern Math'), local('LatinModernMath-Regular'),
url('libs/latinmodernmath/latinmodern-math.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('libs/latinmodernmath/latinmodern-math.woff2') format('woff2'), /* Modern Browsers */
url('libs/latinmodernmath/latinmodern-math.woff') format('woff'), /* Modern Browsers */
url('libs/latinmodernmath/latinmodern-math.ttf') format('truetype'); /* Safari, Android, iOS */
font-weight: normal;
font-style: normal;
font-weight: normal;
font-style: normal;
}
math {
font-family: "Latin Modern Math";
font-family: "Latin Modern Math";
}
img.inline-math {
display: inline;
display: inline;
}

View file

@ -416,3 +416,19 @@ ul.problem-list {
}
}
}
.new-problem-info {
background-color: #fff6dd;
border-radius: 25px;
font-size: 14px;
height: 25px;
width: 100%;
display: table;
padding: 5px;
margin-top: 12px;
}
.info-block {
display:table-cell;
vertical-align:middle;
}

View file

@ -50,7 +50,7 @@ th.header.rank {
th a:hover {
color: #0F0;
}
.about-column {
width: 30%;
}
@ -362,7 +362,7 @@ a.edit-profile {
}
.follow {
background: green;
background: green;
border-color: lightgreen;
}
.follow:hover {

View file

@ -113,7 +113,7 @@
navigator.clipboard
.writeText(link)
.then(() => {
showTooltip(element, "Copied link", 'n');
showTooltip(element, "{{_('Copied link')}}", 'n');
});
};

View file

@ -75,112 +75,6 @@
<link rel="stylesheet" href="{{ static('darkmode-svg.css') }}">
{% endcompress %}
{% endif %}
{% if not INLINE_JQUERY %}
<script src="{{ JQUERY_JS }}"></script>
{% endif %}
<script src="https://unpkg.com/@popperjs/core@2"></script>
{% compress js %}
<script>{{ inlinei18n(LANGUAGE_CODE)|safe }}</script>
{% if INLINE_JQUERY %}
<script src="{{ static('libs/jquery-3.4.1.min.js') }}"></script>
{% endif %}
<script src="{{ static('libs/jquery-cookie.js') }}"></script>
<script src="{{ static('libs/jquery-taphold.js') }}"></script>
<script src="{{ static('libs/jquery.unveil.js') }}"></script>
<script src="{{ static('libs/moment.js') }}"></script>
<script src="{{ static('libs/select2/select2.js') }}"></script>
<script src="{{ static('libs/clipboard/clipboard.js') }}"></script>
{% include "extra_js.html" %}
<script src="{{ static('common.js') }}"></script>
<script src="{{ static('libs/clipboard/tooltip.js') }}"></script>
<script>
moment.locale('{{ LANGUAGE_CODE }}');
$(function () {
$('img.unveil').unveil(200);
});
const loading_page = `{% include "loading-page.html" %}`;
</script>
{% endcompress %}
{% block js_media %}{% endblock %}
{% if request.in_contest %}
<script>$(function () {
if ($("#contest-time-remaining").length) {
count_down($("#contest-time-remaining"));
}
var selected = null,
x_pos = 0, y_pos = 0,
x_elem = 0, y_elem = 0;
$('#contest-info').mousedown(function () {
selected = $(this);
x_elem = x_pos - selected.offset().left;
y_elem = y_pos - (selected.offset().top - $(window).scrollTop());
return false;
});
if (localStorage.getItem("contest_timer_position")) {
data = localStorage.getItem("contest_timer_position").split(":");
$("#contest-info").css({
left: data[0],
top: data[1]
});
}
$("#contest-info").show();
$("#contest-info-toggle").on('click', function() {
$.post("{{url('contest_mode_ajax')}}", function() {
window.location.reload();
})
});
$(document).mousemove(function (e) {
x_pos = e.screenX;
y_pos = e.screenY;
if (selected !== null) {
left_px = (x_pos - x_elem);
top_px = (y_pos - y_elem);
left_px = Math.max(Math.min(left_px, window.innerWidth), 0) / window.innerWidth * 100 + '%';
top_px = Math.max(Math.min(top_px, window.innerHeight), 0) / window.innerHeight * 100 + '%';
localStorage.setItem("contest_timer_position", left_px + ":" + top_px);
selected.css({
left: left_px,
top: top_px
});
}
});
$(document).mouseup(function () {
selected = null;
})
});
</script>
{% endif %}
{% if request.user.is_authenticated %}
<script>
window.user = {
email: '{{ request.user.email|escapejs }}',
id: '{{ request.user.id|escapejs }}',
name: '{{ request.user.username|escapejs }}'
};
</script>
{% else %}
<script>window.user = {};</script>
{% endif %}
{% if misc_config.analytics %}
{{ misc_config.analytics|safe }}
{% endif %}
{# Don't run userscript since it may be malicious #}
{% if request.user.is_authenticated and request.profile.user_script and not request.user.is_impersonate %}
<script type="text/javascript">{{ request.profile.user_script|safe }}</script>
{% endif %}
<noscript>
<style>
@ -378,12 +272,121 @@
<div id="announcement">{{ i18n_config.announcement|safe }}</div>
{% endif %}
{% if not INLINE_JQUERY %}
<script src="{{ JQUERY_JS }}"></script>
{% endif %}
<script src="https://unpkg.com/@popperjs/core@2"></script>
{% compress js %}
<script>{{ inlinei18n(LANGUAGE_CODE)|safe }}</script>
{% if INLINE_JQUERY %}
<script src="{{ static('libs/jquery-3.4.1.min.js') }}"></script>
{% endif %}
<script src="{{ static('libs/jquery-cookie.js') }}"></script>
<script src="{{ static('libs/jquery-taphold.js') }}"></script>
<script src="{{ static('libs/jquery.unveil.js') }}"></script>
<script src="{{ static('libs/moment.js') }}"></script>
<script src="{{ static('libs/select2/select2.js') }}"></script>
<script src="{{ static('libs/clipboard/clipboard.js') }}"></script>
{% include "extra_js.html" %}
<script src="{{ static('common.js') }}"></script>
<script src="{{ static('libs/clipboard/tooltip.js') }}"></script>
<script>
moment.locale('{{ LANGUAGE_CODE }}');
$(function () {
$('img.unveil').unveil(200);
});
</script>
{% endcompress %}
{% block js_media %}{% endblock %}
{% if request.in_contest %}
<script>$(function () {
if ($("#contest-time-remaining").length) {
count_down($("#contest-time-remaining"));
}
var selected = null,
x_pos = 0, y_pos = 0,
x_elem = 0, y_elem = 0;
$('#contest-info').mousedown(function () {
selected = $(this);
x_elem = x_pos - selected.offset().left;
y_elem = y_pos - (selected.offset().top - $(window).scrollTop());
return false;
});
if (localStorage.getItem("contest_timer_position")) {
data = localStorage.getItem("contest_timer_position").split(":");
$("#contest-info").css({
left: data[0],
top: data[1]
});
}
$("#contest-info").show();
$("#contest-info-toggle").on('click', function() {
$.post("{{url('contest_mode_ajax')}}", function() {
window.location.reload();
})
});
$(document).mousemove(function (e) {
x_pos = e.screenX;
y_pos = e.screenY;
if (selected !== null) {
left_px = (x_pos - x_elem);
top_px = (y_pos - y_elem);
left_px = Math.max(Math.min(left_px, window.innerWidth), 0) / window.innerWidth * 100 + '%';
top_px = Math.max(Math.min(top_px, window.innerHeight), 0) / window.innerHeight * 100 + '%';
localStorage.setItem("contest_timer_position", left_px + ":" + top_px);
selected.css({
left: left_px,
top: top_px
});
}
});
$(document).mouseup(function () {
selected = null;
})
});
</script>
{% endif %}
{% if request.user.is_authenticated %}
<script>
window.user = {
email: '{{ request.user.email|escapejs }}',
id: '{{ request.user.id|escapejs }}',
name: '{{ request.user.username|escapejs }}'
};
</script>
{% else %}
<script>window.user = {};</script>
{% endif %}
{% if misc_config.analytics %}
{{ misc_config.analytics|safe }}
{% endif %}
{# Don't run userscript since it may be malicious #}
{% if request.user.is_authenticated and request.profile.user_script and not request.user.is_impersonate %}
<script type="text/javascript">{{ request.profile.user_script|safe }}</script>
{% endif %}
<div id="extra_js">
{% block extra_js %}{% endblock %}
</div>
{% block bodyend %}{% endblock %}
{% block footer %}
<footer>
<span id="footer-content">
<br>
<a class="background-footer" target="_blank" href="https://dmoj.ca">proudly powered by <b>DMOJ</b></a><a target="_blank" href="https://github.com/LQDJudge/online-judge"> | developed by LQDJudge team</a> |
<a class="background-footer" target="_blank" href="https://dmoj.ca">proudly powered by <b>DMOJ</b></a>|<a target="_blank" href="https://github.com/LQDJudge/online-judge"> developed by LQDJudge team</a>
{% if i18n_config.footer %}
{{ i18n_config.footer|safe }} |
{% endif %}

View file

@ -5,7 +5,10 @@
{% block title %} {{_('Chat Box')}} {% endblock %}
{% block js_media %}
<script type="text/javascript" src="{{ static('mathjax3_config.js') }}"></script>
{% if REQUIRE_JAX %}
{% include "mathjax-load.html" %}
{% endif %}
{% include "comments/math.html" %}
<script type="text/javascript" src="{{ static('event.js') }}"></script>
<script type="module" src="https://unpkg.com/emoji-picker-element@1"></script>
{% compress js %}
@ -79,7 +82,7 @@
{% include 'chat/user_online_status.html' %}
</div>
<div id="chat-box">
<img src="{{static('loading.gif')}}" id="loader">
<img src="{{static('loading.gif')}}" id="loader" style="display: none;">
<ul id="chat-log">
{% include 'chat/message_list.html' %}
</ul>

View file

@ -10,7 +10,15 @@
}
::-webkit-scrollbar {
width: 20px;
width: 16px;
background-color: transparent !important;
}
#chat-input::-webkit-scrollbar {
width: 22px;
}
#chat-input::-webkit-scrollbar-thumb {
border: 10px solid transparent;
}
::-webkit-scrollbar-track {
@ -144,9 +152,11 @@
transition: box-shadow 0.3s ease-in-out;
width: 80%;
resize: none;
height: 80%;
height: 70%;
max-height: 200px;
overflow-y: auto;
margin-top: auto;
margin-bottom: 6px;
}
#chat-input:focus {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);

View file

@ -1,10 +1,4 @@
<script type="text/javascript">
let META_HEADER = [
"{{_('Recent')}}",
"{{_('Following')}}",
"{{_('Admin')}}",
"{{_('Other')}}",
];
let isMobile = window.matchMedia("only screen and (max-width: 799px)").matches;
function load_next_page(last_id, refresh_html=false) {
@ -561,7 +555,7 @@
this.style.height = (this.scrollHeight) + 'px';
$(this).css('border-radius', '30px');
} else {
$(this).css('height', '80%');
$(this).css('height', '70%');
}
});

View file

@ -1,12 +1,12 @@
<li class="message" id="message-{{ message.id }}" message-id="{{ message.id }}">
<a href="{{ url('user_page', message.author.user.username) }}">
<a href="{{ url('user_page', message.author.username) }}">
<img src="{{ gravatar(message.author, 135) }}" class="profile-pic">
</a>
<div class="body-message">
<div class="user-time">
<span class="username {{ message.author.css_class }}">
<a href="{{ url('user_page', message.author.user.username) }}">
{{ message.author }}
<a href="{{ url('user_page', message.author.username) }}">
{{ message.author.username }}
</a>
</span>
<span class="time">

View file

@ -6,8 +6,4 @@
{% endfor %}
{% else %}
<center id="empty_msg">{{_('You are connect now. Say something to start the conversation.')}}</center>
{% endif %}
{% if REQUIRE_JAX %}
{% include "mathjax-load.html" %}
{% endif %}
{% include "comments/math.html" %}
{% endif %}

View file

@ -145,6 +145,8 @@
$comment_loading.hide();
var $comment = $("#comment-" + id + "-children");
$comment.append(data);
MathJax.typeset($('#comments')[0]);
register_time($('.time-with-rel'));
}
})
}
@ -187,6 +189,7 @@
$comment.append(data);
}
MathJax.typeset($('#comments')[0]);
register_time($('.time-with-rel'));
}
})
}

View file

@ -44,7 +44,7 @@
<div><label class="inline-header grayed">{{ _('Enter a new key for the cloned contest:') }}</label></div>
<div id="contest-key-container"><span class="fullwidth">{{ form.key }}</span></div>
<div><label class="inline-header grayed">{{ _('Group:') }}</label></div>
<div><label class="inline-header grayed">{{ _('Group') }}:</label></div>
{{form.organization}}
{% if form.organization.errors %}
<div id="form-errors">

View file

@ -36,7 +36,4 @@
{% endif %}
{{ make_tab_item('edit', 'fa fa-edit', url('admin:judge_contest_change', contest.id), _('Edit')) }}
{% endif %}
{% if perms.judge.clone_contest %}
{{ make_tab_item('clone', 'fa fa-copy', url('contest_clone', contest.key), _('Clone')) }}
{% endif %}
</div>

View file

@ -82,11 +82,16 @@
{{ contest.description|markdown|reference|str|safe }}
{% endcache %}
</div>
{% if editable_organizations %}
<div>
{% if editable_organizations or is_clonable %}
<div style="display: flex; gap: 0.5em;">
{% for org in editable_organizations %}
<span> [<a href="{{ url('organization_contest_edit', org.id , org.slug , contest.key) }}">{{ _('Edit in') }} {{org.slug}}</a>]</span>
{% endfor %}
{% if is_clonable %}
<span>
[<a href="{{url('contest_clone', contest.key)}}"}}>{{_('Clone')}}</a>]
</span>
{% endif %}
</div>
{% endif %}

View file

@ -13,15 +13,15 @@
{% endblock %}
{% block user_footer %}
{% if user.first_name %}
{% if user.user.first_name %}
<div style="font-weight: 600; display: none" class="fullname gray">
{{ user.first_name if user.first_name else ''}}
{{ user.user.first_name }}
</div>
{% endif %}
{% if user.last_name %}
<div class="school gray" style="display: none"><a style="font-weight: 600">
{{- user.last_name -}}
</a></div>
{% if user.user.last_name %}
<div class="school gray" style="display: none"><div style="font-weight: 600">
{{- user.user.last_name -}}
</div></div>
{% endif %}
{% endblock %}

View file

@ -1,12 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Courses</title>
</head>
<body>
</body>
</head>
<body>
</body>
</html>

View file

@ -1,19 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</head>
<body>
<h1>Enrolling</h1>
{% for course in enrolling %}
<h2> {{ course }} </h2>
<h2> {{ course }} </h2>
{% endfor %}
<h1> Available </h1>
{% for course in available %}
<h2> {{ course }} </h2>
<h2> {{ course }} </h2>
{% endfor %}
</body>
</body>
</html>

View file

@ -16,8 +16,9 @@
$('.vote-detail').each(function() {
$(this).on('click', function() {
var pid = $(this).attr('pid');
$('.detail').hide();
$('#detail-'+pid).show();
$.get("{{url('internal_problem_votes')}}?id="+pid, function(data) {
$('#detail').html(data);
});
})
})
});
@ -59,37 +60,8 @@
{% block right_sidebar %}
<div style="display: block; width: 100%">
<div><a href="{{url('admin:judge_volunteerproblemvote_changelist')}}">{{_('Admin')}}</a></div>
{% for problem in problems %}
<div class="detail" id="detail-{{problem.id}}" style="display: none;">
<h3>{{_('Votes for problem') }} {{problem.name}}</h3>
<ol>
{% for vote in problem.volunteer_user_votes.order_by('id') %}
<li>
<h4> {{link_user(vote.voter)}} </h4>
<table class="table">
<tbody>
<tr>
<td style="width:10%">{{_('Knowledge')}}</td>
<td>{{vote.knowledge_points}}</td>
</tr>
<tr>
<td>{{_('Thinking')}}</td>
<td>{{vote.thinking_points}}</td>
</tr>
<tr>
<td>{{_('Types')}}</td>
<td>{{vote.types.all() | join(', ')}}</td>
</tr>
<tr>
<td>{{_('Feedback')}}</td>
<td>{{vote.feedback}}</td>
</tr>
</tbody>
</table>
</li>
{% endfor %}
</ol>
</div>
{% endfor %}
<a href="{{url('admin:judge_volunteerproblemvote_changelist')}}">{{_('Admin')}}</a>
<div class="detail" id="detail">
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,29 @@
<h3>{{_('Votes for problem') }} {{problem.name}}</h3>
<ol>
{% for vote in votes %}
<li>
<h4> {{link_user(vote.voter)}} </h4>
<table class="table">
<tbody>
<tr>
<td style="width:10%">{{_('Knowledge')}}</td>
<td>{{vote.knowledge_points}}</td>
</tr>
<tr>
<td>{{_('Thinking')}}</td>
<td>{{vote.thinking_points}}</td>
</tr>
<tr>
<td>{{_('Types')}}</td>
<td>{{vote.types.all() | join(', ')}}</td>
</tr>
<tr>
<td>{{_('Feedback')}}</td>
<td>{{vote.feedback}}</td>
</tr>
</tbody>
</table>
</li>
{% endfor %}
</ol>
</div>

View file

@ -29,12 +29,6 @@
height: 2em;
padding-top: 4px;
}
@media(min-width: 800px) {
#content {
width: 99%;
margin-left: 0;
}
}
@media(max-width: 799px) {
#content {
width: 100%;

View file

@ -117,7 +117,6 @@
{% endfor %}
</span>
{% endif %}
<span class="spacer"></span>
{% if has_render %}
<a href="{{ url('problem_pdf', problem.code) }}" class="view-pdf" target="_blank">
@ -135,6 +134,7 @@
</div>
{% endblock %}
{% block info_float %}
{% if request.user.is_authenticated and request.in_contest_mode and submission_limit %}
{% if submissions_left > 0 %}
@ -204,53 +204,6 @@
</div>
{% endif %}
<hr style="padding-top: 0.3em">
<div class="problem-info-entry">
<i class="fa fa-check fa-fw"></i><span class="pi-name">{{ _('Points:') }}</span>
<span class="pi-value">
{% if contest_problem %}
{{ contest_problem.points }}{% if contest_problem.partial %} {{ _('(partial)') }}{% endif %}
{% else %}
{{ problem.points|floatformat }}{% if problem.partial %} {{ _('(partial)') }}{% endif %}
{% endif %}
</span>
</div>
<div class="problem-info-entry">
<i class="fa fa-clock-o fa-fw"></i><span class="pi-name">{{ _('Time limit:') }}</span>
<span class="pi-value">{{ problem.time_limit }}s</span>
</div>
<div class="problem-lang-limits">
{% for name, limit in problem.language_time_limit %}
<div class="lang-limit">
<span class="lang-name">{{ name }}</span>
<span class="lang-tl">{{ limit }}s</span>
</div>
{% endfor %}
</div>
<div class="problem-info-entry">
<i class="fa fa-server fa-fw"></i><span class="pi-name">{{ _('Memory limit:') }}</span>
<span class="pi-value">{{ problem.memory_limit|kbsimpleformat }}</span>
</div>
<div class="problem-lang-limits">
{% for name, limit in problem.language_memory_limit %}
<div class="lang-limit">
<span class="lang-name">{{ name }}</span>
<span class="lang-ml">{{ limit|kbsimpleformat }}</span>
</div>
{% endfor %}
</div>
<div class="problem-info-entry">
<i class="fa fa-file-o fa-fw"></i><span class="pi-name">{{ _('Input:') }}</span>
<span class="pi-value">
{{ fileio_input or _('stdin') }}
</span>
</div>
<div class="problem-info-entry">
<i class="fa fa-file fa-fw"></i><span class="pi-name">{{ _('Output:') }}</span>
<span class="pi-value">{{ fileio_output or _('stdout') }}</span>
</div>
<hr style="padding-top: 0.7em">
{% cache 86400 'problem_authors' problem.id LANGUAGE_CODE %}
@ -342,6 +295,40 @@
</a>
</div>
{% endif %}
<div class = "new-problem-info center">
<span class="info-block">
<i class="fa fa-check fa-fw"></i><span class="pi-name">{{ _('Points:') }}</span>
<span class="new-pi-value">
{% if contest_problem %}
{{ contest_problem.points }} {% if contest_problem.partial %}(p){% endif %}
{% else %}
{{ problem.points|floatformat }} {% if problem.partial %}(p){% endif %}
{% endif %}
</span>
</span>
<span class="info-block">
<i class="fa fa-clock-o fa-fw"></i><span class="pi-name">{{ _('Time limit:') }}</span>
<span class="new-pi-value">{{ problem.time_limit }}s</span>
</span>
<span class="info-block">
<i class="fa fa-server fa-fw"></i><span class="pi-name">{{ _('Memory limit:') }}</span>
<span class="new-pi-value">{{ problem.memory_limit|kbsimpleformat }}</span>
</span>
<span class="info-block">
<i class="fa fa-file-o fa-fw"></i><span class="pi-name">{{ _('Input:') }}</span>
<span class="new-pi-value">
{{ fileio_input or _('stdin') }}
</span>
</span>
<span class="info-block">
<i class="fa fa-file fa-fw"></i><span class="pi-name">{{ _('Output:') }}</span>
<span class="new-pi-value">{{ fileio_output or _('stdout') }}</span>
</span>
</div>
{% cache 86400 'problem_html' problem.id MATH_ENGINE LANGUAGE_CODE %}
{{ description|markdown(lazy_load=True)|reference|str|safe }}

View file

@ -28,6 +28,8 @@
{% block js_media %}
<script type="text/javascript">
let loadingPage;
function activateBlogBoxOnClick() {
$('.blog-box').on('click', function () {
var $description = $(this).children('.blog-description');
@ -64,13 +66,16 @@
$elem.addClass('active');
}
$(window).off("scroll");
$('.middle-right-content').html(loading_page);
$('.middle-right-content').html(loadingPage);
$.get(url, function (data) {
var reload_content = $(data).find('.middle-right-content');
var bodyend_script = $(data).find('#extra_js');
if (reload_content.length) {
window.history.pushState("", "", url);
$('html, body').animate({scrollTop: 0}, 'fast');
$('.middle-right-content').html(reload_content.first().html());
$('#extra_js').html(bodyend_script.first().html());
$("#loading-bar").hide().css({ width: 0});
if (reload_content.hasClass("wrapper")) {
$('.middle-right-content').addClass("wrapper");
}
@ -83,7 +88,6 @@
activateBlogBoxOnClick();
$('.xdsoft_datetimepicker').hide();
registerNavigation();
$("#loading-bar").hide().css({ width: 0});
}
else {
window.location.href = url;
@ -112,6 +116,9 @@
navigateTo($(this), true);
});
registerNavigation();
$.get("{{static('html/loading-page.html')}}", function(data) {
loadingPage = data;
});
});
</script>
{% endblock %}
@ -129,7 +136,6 @@
{% block left_sidebar %}{% endblock %}
<div class="middle-right-content {{'wrapper' if is_two_column}}">
{% block three_col_media %}{% endblock %}
{% block three_col_js %}{% endblock %}
<div class="middle-content">
{% block middle_title %}{% endblock %}
{% block middle_content %}{% endblock %}
@ -140,6 +146,10 @@
{% block after_posts %}{% endblock %}
{% endblock %}
{% block extra_js %}
{% block three_col_js %}{% endblock %}
{% endblock %}
{% block bodyend %}
{{ super() }}
{% if REQUIRE_JAX %}