Merge branch 'LQDJudge:master' into master
This commit is contained in:
commit
0403c4be31
63 changed files with 2242 additions and 1361 deletions
|
@ -11,3 +11,7 @@ repos:
|
||||||
rev: 22.12.0
|
rev: 22.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
- repo: https://github.com/hadialqattan/pycln
|
||||||
|
rev: 'v2.3.0'
|
||||||
|
hooks:
|
||||||
|
- id: pycln
|
||||||
|
|
33
chat_box/migrations/0015_room_last_msg_time.py
Normal file
33
chat_box/migrations/0015_room_last_msg_time.py
Normal 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),
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import CASCADE, Q
|
from django.db.models import CASCADE, Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
|
|
||||||
from judge.models.profile import Profile
|
from judge.models.profile import Profile
|
||||||
|
@ -17,25 +18,40 @@ class Room(models.Model):
|
||||||
user_two = models.ForeignKey(
|
user_two = models.ForeignKey(
|
||||||
Profile, related_name="user_two", verbose_name="user 2", on_delete=CASCADE
|
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:
|
class Meta:
|
||||||
app_label = "chat_box"
|
app_label = "chat_box"
|
||||||
|
|
||||||
@cache_wrapper(prefix="Rc")
|
@cache_wrapper(prefix="Rinfo")
|
||||||
def contain(self, profile):
|
def _info(self):
|
||||||
return self.user_one == profile or self.user_two == profile
|
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):
|
def other_user(self, profile):
|
||||||
return self.user_one if profile == self.user_two else self.user_two
|
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):
|
def users(self):
|
||||||
return [self.user_one, self.user_two]
|
return [self.user_one, self.user_two]
|
||||||
|
|
||||||
@cache_wrapper(prefix="Rlmb")
|
|
||||||
def last_message_body(self):
|
def last_message_body(self):
|
||||||
return self.message_set.first().body
|
return self._cached_info["last_message"]
|
||||||
|
|
||||||
|
|
||||||
class Message(models.Model):
|
class Message(models.Model):
|
||||||
|
|
|
@ -29,7 +29,6 @@ from django.utils import timezone
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from judge import event_poster as event
|
from judge import event_poster as event
|
||||||
from judge.jinja2.gravatar import gravatar
|
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.models import Message, Profile, Room, UserRoom, Ignore
|
||||||
from chat_box.utils import encrypt_url, decrypt_url, encrypt_channel, get_unread_boxes
|
from chat_box.utils import encrypt_url, decrypt_url, encrypt_channel, get_unread_boxes
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
class ChatView(ListView):
|
class ChatView(ListView):
|
||||||
context_object_name = "message"
|
context_object_name = "message"
|
||||||
|
@ -87,8 +84,8 @@ class ChatView(ListView):
|
||||||
self.room_id = request_room
|
self.room_id = request_room
|
||||||
self.messages = (
|
self.messages = (
|
||||||
Message.objects.filter(hidden=False, room=self.room_id, id__lt=last_id)
|
Message.objects.filter(hidden=False, room=self.room_id, id__lt=last_id)
|
||||||
.select_related("author", "author__user")
|
.select_related("author")
|
||||||
.defer("author__about", "author__user_script")[:page_size]
|
.only("body", "time", "author__rating", "author__display_rank")[:page_size]
|
||||||
)
|
)
|
||||||
if not only_messages:
|
if not only_messages:
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
@ -207,7 +204,9 @@ def post_message(request):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else:
|
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():
|
for user in room.users():
|
||||||
event.post(
|
event.post(
|
||||||
|
@ -354,11 +353,11 @@ def get_online_status(profile, other_profile_ids, rooms=None):
|
||||||
room = Room.objects.get(id=i["room"])
|
room = Room.objects.get(id=i["room"])
|
||||||
other_profile = room.other_user(profile)
|
other_profile = room.other_user(profile)
|
||||||
count[other_profile.id] = i["unread_count"]
|
count[other_profile.id] = i["unread_count"]
|
||||||
|
rooms = Room.objects.filter(id__in=rooms)
|
||||||
for room in rooms:
|
for room in rooms:
|
||||||
room = Room.objects.get(id=room)
|
other_profile_id = room.other_user_id(profile)
|
||||||
other_profile = room.other_user(profile)
|
last_msg[other_profile_id] = room.last_message_body()
|
||||||
last_msg[other_profile.id] = room.last_message_body()
|
room_of_user[other_profile_id] = room.id
|
||||||
room_of_user[other_profile.id] = room.id
|
|
||||||
|
|
||||||
for other_profile in other_profiles:
|
for other_profile in other_profiles:
|
||||||
is_online = False
|
is_online = False
|
||||||
|
@ -392,9 +391,6 @@ def get_status_context(profile, include_ignored=False):
|
||||||
recent_profile = (
|
recent_profile = (
|
||||||
Room.objects.filter(Q(user_one=profile) | Q(user_two=profile))
|
Room.objects.filter(Q(user_one=profile) | Q(user_two=profile))
|
||||||
.annotate(
|
.annotate(
|
||||||
last_msg_time=Subquery(
|
|
||||||
Message.objects.filter(room=OuterRef("pk")).values("time")[:1]
|
|
||||||
),
|
|
||||||
other_user=Case(
|
other_user=Case(
|
||||||
When(user_one=profile, then="user_two"),
|
When(user_one=profile, then="user_two"),
|
||||||
default="user_one",
|
default="user_one",
|
||||||
|
@ -415,28 +411,15 @@ def get_status_context(profile, include_ignored=False):
|
||||||
.values_list("id", flat=True)
|
.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 [
|
return [
|
||||||
{
|
{
|
||||||
"title": "Recent",
|
"title": _("Recent"),
|
||||||
"user_list": get_online_status(profile, recent_profile_ids, recent_rooms),
|
"user_list": get_online_status(profile, recent_profile_ids, recent_rooms),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Admin",
|
"title": _("Admin"),
|
||||||
"user_list": get_online_status(profile, admin_list),
|
"user_list": get_online_status(profile, admin_list),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"title": "Other",
|
|
||||||
"user_list": get_online_status(profile, all_user_status),
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
import datetime
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
|
@ -1100,6 +1100,11 @@ urlpatterns = [
|
||||||
internal.InternalProblem.as_view(),
|
internal.InternalProblem.as_view(),
|
||||||
name="internal_problem",
|
name="internal_problem",
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
r"^problem_votes$",
|
||||||
|
internal.get_problem_votes,
|
||||||
|
name="internal_problem_votes",
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
r"^request_time$",
|
r"^request_time$",
|
||||||
internal.InternalRequestTime.as_view(),
|
internal.InternalRequestTime.as_view(),
|
||||||
|
|
|
@ -3,7 +3,6 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import os
|
|
||||||
from collections import deque, namedtuple
|
from collections import deque, namedtuple
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
|
@ -25,6 +24,7 @@ from judge.models import (
|
||||||
Submission,
|
Submission,
|
||||||
SubmissionTestCase,
|
SubmissionTestCase,
|
||||||
)
|
)
|
||||||
|
from judge.bridge.utils import VanishedSubmission
|
||||||
|
|
||||||
logger = logging.getLogger("judge.bridge")
|
logger = logging.getLogger("judge.bridge")
|
||||||
json_log = logging.getLogger("judge.json.bridge")
|
json_log = logging.getLogger("judge.json.bridge")
|
||||||
|
@ -94,12 +94,6 @@ class JudgeHandler(ZlibPacketHandler):
|
||||||
|
|
||||||
def on_disconnect(self):
|
def on_disconnect(self):
|
||||||
self._stop_ping.set()
|
self._stop_ping.set()
|
||||||
if self._working:
|
|
||||||
logger.error(
|
|
||||||
"Judge %s disconnected while handling submission %s",
|
|
||||||
self.name,
|
|
||||||
self._working,
|
|
||||||
)
|
|
||||||
self.judges.remove(self)
|
self.judges.remove(self)
|
||||||
if self.name is not None:
|
if self.name is not None:
|
||||||
self._disconnected()
|
self._disconnected()
|
||||||
|
@ -119,16 +113,6 @@ class JudgeHandler(ZlibPacketHandler):
|
||||||
None,
|
None,
|
||||||
0,
|
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):
|
def _authenticate(self, id, key):
|
||||||
try:
|
try:
|
||||||
|
@ -327,6 +311,9 @@ class JudgeHandler(ZlibPacketHandler):
|
||||||
|
|
||||||
def submit(self, id, problem, language, source):
|
def submit(self, id, problem, language, source):
|
||||||
data = self.get_related_submission_data(id)
|
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 = id
|
||||||
self._working_data = {
|
self._working_data = {
|
||||||
"problem": problem,
|
"problem": problem,
|
||||||
|
@ -675,8 +662,11 @@ class JudgeHandler(ZlibPacketHandler):
|
||||||
self._free_self(packet)
|
self._free_self(packet)
|
||||||
|
|
||||||
id = packet["submission-id"]
|
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(
|
if Submission.objects.filter(id=id).update(
|
||||||
status="IE", result="IE", error=packet["message"]
|
status="IE", result="IE", error=message
|
||||||
):
|
):
|
||||||
event.post(
|
event.post(
|
||||||
"sub_%s" % Submission.get_id_secret(id), {"type": "internal-error"}
|
"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)
|
self._post_update_submission(id, "internal-error", done=True)
|
||||||
json_log.info(
|
json_log.info(
|
||||||
self._make_json_log(
|
self._make_json_log(
|
||||||
packet,
|
sub=id,
|
||||||
action="internal-error",
|
action="internal-error",
|
||||||
message=packet["message"],
|
message=message,
|
||||||
finish=True,
|
finish=True,
|
||||||
result="IE",
|
result="IE",
|
||||||
)
|
)
|
||||||
|
@ -695,10 +685,10 @@ class JudgeHandler(ZlibPacketHandler):
|
||||||
logger.warning("Unknown submission: %s", id)
|
logger.warning("Unknown submission: %s", id)
|
||||||
json_log.error(
|
json_log.error(
|
||||||
self._make_json_log(
|
self._make_json_log(
|
||||||
packet,
|
sub=id,
|
||||||
action="internal-error",
|
action="internal-error",
|
||||||
info="unknown submission",
|
info="unknown submission",
|
||||||
message=packet["message"],
|
message=message,
|
||||||
finish=True,
|
finish=True,
|
||||||
result="IE",
|
result="IE",
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,8 @@ from collections import namedtuple
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
|
|
||||||
|
from judge.bridge.utils import VanishedSubmission
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from llist import dllist
|
from llist import dllist
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -39,6 +41,8 @@ class JudgeList(object):
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
judge.submit(id, problem, language, source)
|
judge.submit(id, problem, language, source)
|
||||||
|
except VanishedSubmission:
|
||||||
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Failed to dispatch %d (%s, %s) to %s",
|
"Failed to dispatch %d (%s, %s) to %s",
|
||||||
|
|
2
judge/bridge/utils.py
Normal file
2
judge/bridge/utils.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
class VanishedSubmission(Exception):
|
||||||
|
pass
|
|
@ -40,7 +40,10 @@ def cache_wrapper(prefix, timeout=None):
|
||||||
def _get(key):
|
def _get(key):
|
||||||
if not l0_cache:
|
if not l0_cache:
|
||||||
return cache.get(key)
|
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):
|
def _set_l0(key, value):
|
||||||
if l0_cache:
|
if l0_cache:
|
||||||
|
@ -56,7 +59,7 @@ def cache_wrapper(prefix, timeout=None):
|
||||||
result = _get(cache_key)
|
result = _get(cache_key)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
_set_l0(cache_key, result)
|
_set_l0(cache_key, result)
|
||||||
if result == NONE_RESULT:
|
if type(result) == str and result == NONE_RESULT:
|
||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
result = func(*args, **kwargs)
|
result = func(*args, **kwargs)
|
||||||
|
|
|
@ -144,14 +144,16 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
target_comment = None
|
target_comment = None
|
||||||
|
self.object = self.get_object()
|
||||||
if "comment-id" in request.GET:
|
if "comment-id" in request.GET:
|
||||||
comment_id = int(request.GET["comment-id"])
|
|
||||||
try:
|
try:
|
||||||
|
comment_id = int(request.GET["comment-id"])
|
||||||
comment_obj = Comment.objects.get(id=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
|
raise Http404
|
||||||
target_comment = comment_obj.get_root()
|
target_comment = comment_obj.get_root()
|
||||||
self.object = self.get_object()
|
|
||||||
return self.render_to_response(
|
return self.render_to_response(
|
||||||
self.get_context_data(
|
self.get_context_data(
|
||||||
object=self.object,
|
object=self.object,
|
||||||
|
|
|
@ -11,7 +11,6 @@ from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db import transaction
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.forms import (
|
from django.forms import (
|
||||||
CharField,
|
CharField,
|
||||||
|
@ -52,7 +51,6 @@ from judge.widgets import (
|
||||||
DateTimePickerWidget,
|
DateTimePickerWidget,
|
||||||
ImageWidget,
|
ImageWidget,
|
||||||
)
|
)
|
||||||
from judge.tasks import rescore_contest
|
|
||||||
|
|
||||||
|
|
||||||
def fix_unicode(string, unsafe=tuple("\u202a\u202b\u202d\u202e")):
|
def fix_unicode(string, unsafe=tuple("\u202a\u202b\u202d\u202e")):
|
||||||
|
@ -282,16 +280,9 @@ class EditOrganizationContestForm(ModelForm):
|
||||||
"view_contest_scoreboard",
|
"view_contest_scoreboard",
|
||||||
]:
|
]:
|
||||||
self.fields[field].widget.data_url = (
|
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:
|
class Meta:
|
||||||
model = Contest
|
model = Contest
|
||||||
fields = (
|
fields = (
|
||||||
|
|
7
judge/logging.py
Normal file
7
judge/logging.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
error_log = logging.getLogger("judge.errors")
|
||||||
|
|
||||||
|
|
||||||
|
def log_exception(msg):
|
||||||
|
error_log.exception(msg)
|
|
@ -1,6 +1,5 @@
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from judge.models import *
|
from judge.models import *
|
||||||
from collections import defaultdict
|
|
||||||
import csv
|
import csv
|
||||||
import os
|
import os
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,9 +1,10 @@
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from django.conf import settings
|
|
||||||
import os
|
import os
|
||||||
from django.core.cache import cache
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from judge.caching import cache_wrapper
|
from judge.caching import cache_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,14 +14,13 @@ class CollabFilter:
|
||||||
|
|
||||||
# name = 'collab_filter' or 'collab_filter_time'
|
# name = 'collab_filter' or 'collab_filter_time'
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
embeddings = np.load(
|
self.embeddings = np.load(
|
||||||
os.path.join(settings.ML_OUTPUT_PATH, name + "/embeddings.npz"),
|
os.path.join(settings.ML_OUTPUT_PATH, name + "/embeddings.npz"),
|
||||||
allow_pickle=True,
|
allow_pickle=True,
|
||||||
)
|
)
|
||||||
arr0, arr1 = embeddings.files
|
_, problem_arr = self.embeddings.files
|
||||||
self.name = name
|
self.name = name
|
||||||
self.user_embeddings = embeddings[arr0]
|
self.problem_embeddings = self.embeddings[problem_arr]
|
||||||
self.problem_embeddings = embeddings[arr1]
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -44,18 +44,32 @@ class CollabFilter:
|
||||||
scores = u.dot(V.T)
|
scores = u.dot(V.T)
|
||||||
return scores
|
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)
|
@cache_wrapper(prefix="user_recommendations", timeout=3600)
|
||||||
def user_recommendations(self, user, problems, measure=DOT, limit=None):
|
def user_recommendations(self, user_id, problems, measure=DOT, limit=None):
|
||||||
uid = user.id
|
user_embedding = self.get_user_embedding(user_id)
|
||||||
if uid >= len(self.user_embeddings):
|
scores = self.compute_scores(user_embedding, self.problem_embeddings, measure)
|
||||||
uid = 0
|
|
||||||
scores = self.compute_scores(
|
|
||||||
self.user_embeddings[uid], self.problem_embeddings, measure
|
|
||||||
)
|
|
||||||
|
|
||||||
res = [] # [(score, problem)]
|
res = [] # [(score, problem)]
|
||||||
for pid in problems:
|
for pid in problems:
|
||||||
# pid = problem.id
|
|
||||||
if pid < len(scores):
|
if pid < len(scores):
|
||||||
res.append((scores[pid], pid))
|
res.append((scores[pid], pid))
|
||||||
|
|
||||||
|
|
|
@ -99,11 +99,13 @@ class Contest(models.Model, PageVotable, Bookmarkable):
|
||||||
)
|
)
|
||||||
authors = models.ManyToManyField(
|
authors = models.ManyToManyField(
|
||||||
Profile,
|
Profile,
|
||||||
|
verbose_name=_("authors"),
|
||||||
help_text=_("These users will be able to edit the contest."),
|
help_text=_("These users will be able to edit the contest."),
|
||||||
related_name="authors+",
|
related_name="authors+",
|
||||||
)
|
)
|
||||||
curators = models.ManyToManyField(
|
curators = models.ManyToManyField(
|
||||||
Profile,
|
Profile,
|
||||||
|
verbose_name=_("curators"),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"These users will be able to edit the contest, "
|
"These users will be able to edit the contest, "
|
||||||
"but will not be listed as authors."
|
"but will not be listed as authors."
|
||||||
|
@ -113,6 +115,7 @@ class Contest(models.Model, PageVotable, Bookmarkable):
|
||||||
)
|
)
|
||||||
testers = models.ManyToManyField(
|
testers = models.ManyToManyField(
|
||||||
Profile,
|
Profile,
|
||||||
|
verbose_name=_("testers"),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"These users will be able to view the contest, " "but not edit it."
|
"These users will be able to view the contest, " "but not edit it."
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import errno
|
import errno
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from math import sqrt
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
|
@ -557,7 +556,7 @@ class Problem(models.Model, PageVotable, Bookmarkable):
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
super(Problem, self).save(*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:
|
if hasattr(self, "data_files") or self.pdf_description:
|
||||||
try:
|
try:
|
||||||
problem_data_storage.rename(self.__original_code, self.code)
|
problem_data_storage.rename(self.__original_code, self.code)
|
||||||
|
|
|
@ -162,10 +162,10 @@ class ProblemData(models.Model):
|
||||||
get_file_cachekey(file),
|
get_file_cachekey(file),
|
||||||
)
|
)
|
||||||
cache.delete(cache_key)
|
cache.delete(cache_key)
|
||||||
except BadZipFile:
|
except (BadZipFile, FileNotFoundError):
|
||||||
pass
|
pass
|
||||||
if self.zipfile != self.__original_zipfile and self.__original_zipfile:
|
if self.zipfile != self.__original_zipfile:
|
||||||
self.__original_zipfile.delete(save=False)
|
self.__original_zipfile.delete(save=False)
|
||||||
return super(ProblemData, self).save(*args, **kwargs)
|
return super(ProblemData, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def has_yml(self):
|
def has_yml(self):
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import csv
|
import csv
|
||||||
from tempfile import mktemp
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
|
@ -5,7 +5,6 @@ from django import forms
|
||||||
from django.forms import ClearableFileInput
|
from django.forms import ClearableFileInput
|
||||||
|
|
||||||
import os, os.path
|
import os, os.path
|
||||||
import tempfile
|
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
__all__ = ("handle_upload", "save_upload", "FineUploadForm", "FineUploadFileInput")
|
__all__ = ("handle_upload", "save_upload", "FineUploadForm", "FineUploadFileInput")
|
||||||
|
|
|
@ -2,7 +2,6 @@ import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
import yaml
|
import yaml
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
@ -13,6 +12,8 @@ from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
from judge.logging import log_exception
|
||||||
|
|
||||||
if os.altsep:
|
if os.altsep:
|
||||||
|
|
||||||
def split_path_first(
|
def split_path_first(
|
||||||
|
@ -324,11 +325,13 @@ def get_problem_case(problem, files):
|
||||||
settings.DMOJ_PROBLEM_DATA_ROOT, str(problem.data_files.zipfile)
|
settings.DMOJ_PROBLEM_DATA_ROOT, str(problem.data_files.zipfile)
|
||||||
)
|
)
|
||||||
if not os.path.exists(archive_path):
|
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:
|
try:
|
||||||
archive = zipfile.ZipFile(archive_path, "r")
|
archive = zipfile.ZipFile(archive_path, "r")
|
||||||
except zipfile.BadZipfile:
|
except zipfile.BadZipfile:
|
||||||
raise Exception('bad archive: "%s"' % archive_path)
|
log_exception('bad archive: "%s"' % archive_path)
|
||||||
|
return {}
|
||||||
|
|
||||||
for file in uncached_files:
|
for file in uncached_files:
|
||||||
cache_key = "problem_archive:%s:%s" % (problem.code, get_file_cachekey(file))
|
cache_key = "problem_archive:%s:%s" % (problem.code, get_file_cachekey(file))
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from math import e
|
from math import e
|
||||||
import os, zipfile
|
from datetime import datetime, timedelta
|
||||||
from datetime import datetime
|
|
||||||
import random
|
import random
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
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.db.models.fields import FloatField
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext as _, gettext_noop
|
from django.utils.translation import gettext as _, gettext_noop
|
||||||
|
from django.http import Http404
|
||||||
|
|
||||||
from judge.models import Problem, Submission
|
from judge.models import Problem, Submission
|
||||||
from judge.ml.collab_filter import CollabFilter
|
from judge.ml.collab_filter import CollabFilter
|
||||||
|
@ -249,3 +250,72 @@ def finished_submission(sub):
|
||||||
keys += ["contest_complete:%d" % participation.id]
|
keys += ["contest_complete:%d" % participation.id]
|
||||||
keys += ["contest_attempted:%d" % participation.id]
|
keys += ["contest_attempted:%d" % participation.id]
|
||||||
cache.delete_many(keys)
|
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
|
||||||
|
|
|
@ -144,7 +144,11 @@ class ContestList(
|
||||||
context_object_name = "past_contests"
|
context_object_name = "past_contests"
|
||||||
all_sorts = frozenset(("name", "user_count", "start_time"))
|
all_sorts = frozenset(("name", "user_count", "start_time"))
|
||||||
default_desc = frozenset(("name", "user_count"))
|
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
|
@cached_property
|
||||||
def _now(self):
|
def _now(self):
|
||||||
|
@ -157,7 +161,7 @@ class ContestList(
|
||||||
if request.GET.get("show_orgs"):
|
if request.GET.get("show_orgs"):
|
||||||
self.show_orgs = 1
|
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:
|
try:
|
||||||
self.org_query = list(map(int, request.GET.getlist("orgs")))
|
self.org_query = list(map(int, request.GET.getlist("orgs")))
|
||||||
if not self.request.user.is_superuser:
|
if not self.request.user.is_superuser:
|
||||||
|
@ -165,8 +169,10 @@ class ContestList(
|
||||||
i
|
i
|
||||||
for i in self.org_query
|
for i in self.org_query
|
||||||
if i
|
if i
|
||||||
in self.request.profile.organizations.values_list(
|
in set(
|
||||||
"id", flat=True
|
self.request.profile.organizations.values_list(
|
||||||
|
"id", flat=True
|
||||||
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -181,7 +187,7 @@ class ContestList(
|
||||||
.prefetch_related("tags", "organizations", "authors", "curators", "testers")
|
.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.contest_query = query = " ".join(
|
||||||
self.request.GET.getlist("contest")
|
self.request.GET.getlist("contest")
|
||||||
).strip()
|
).strip()
|
||||||
|
@ -249,10 +255,7 @@ class ContestList(
|
||||||
context["org_query"] = self.org_query
|
context["org_query"] = self.org_query
|
||||||
context["show_orgs"] = int(self.show_orgs)
|
context["show_orgs"] = int(self.show_orgs)
|
||||||
if self.request.profile:
|
if self.request.profile:
|
||||||
if self.request.user.is_superuser:
|
context["organizations"] = self.request.profile.organizations.all()
|
||||||
context["organizations"] = Organization.objects.all()
|
|
||||||
else:
|
|
||||||
context["organizations"] = self.request.profile.organizations.all()
|
|
||||||
context["page_type"] = "list"
|
context["page_type"] = "list"
|
||||||
context.update(self.get_sort_context())
|
context.update(self.get_sort_context())
|
||||||
context.update(self.get_sort_paginate_context())
|
context.update(self.get_sort_paginate_context())
|
||||||
|
@ -419,7 +422,14 @@ class ContestDetail(
|
||||||
return []
|
return []
|
||||||
res = []
|
res = []
|
||||||
for organization in self.object.organizations.all():
|
for organization in self.object.organizations.all():
|
||||||
|
can_edit = False
|
||||||
if self.request.profile.can_edit_organization(organization):
|
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)
|
res.append(organization)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -441,16 +451,32 @@ class ContestDetail(
|
||||||
.add_i18n_name(self.request.LANGUAGE_CODE)
|
.add_i18n_name(self.request.LANGUAGE_CODE)
|
||||||
)
|
)
|
||||||
context["editable_organizations"] = self.get_editable_organizations()
|
context["editable_organizations"] = self.get_editable_organizations()
|
||||||
|
context["is_clonable"] = is_contest_clonable(self.request, self.object)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ContestClone(
|
def is_contest_clonable(request, contest):
|
||||||
ContestMixin, PermissionRequiredMixin, TitleMixin, SingleObjectFormView
|
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")
|
title = _("Clone Contest")
|
||||||
template_name = "contest/clone.html"
|
template_name = "contest/clone.html"
|
||||||
form_class = ContestCloneForm
|
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):
|
def get_form_kwargs(self):
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
|
@ -475,6 +501,7 @@ class ContestClone(
|
||||||
contest.is_visible = False
|
contest.is_visible = False
|
||||||
contest.user_count = 0
|
contest.user_count = 0
|
||||||
contest.key = form.cleaned_data["key"]
|
contest.key = form.cleaned_data["key"]
|
||||||
|
contest.is_rated = False
|
||||||
contest.save()
|
contest.save()
|
||||||
|
|
||||||
contest.tags.set(tags)
|
contest.tags.set(tags)
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.utils.translation import gettext as _, gettext_lazy
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
from django.http import HttpResponseForbidden
|
from django.http import HttpResponseForbidden
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
from judge.utils.diggpaginator import DiggPaginator
|
from judge.utils.diggpaginator import DiggPaginator
|
||||||
from judge.models import VolunteerProblemVote, Problem
|
from judge.models import VolunteerProblemVote, Problem
|
||||||
|
@ -21,7 +22,7 @@ class InternalView(object):
|
||||||
class InternalProblem(InternalView, ListView):
|
class InternalProblem(InternalView, ListView):
|
||||||
model = Problem
|
model = Problem
|
||||||
title = _("Internal problems")
|
title = _("Internal problems")
|
||||||
template_name = "internal/problem.html"
|
template_name = "internal/problem/problem.html"
|
||||||
paginate_by = 100
|
paginate_by = 100
|
||||||
context_object_name = "problems"
|
context_object_name = "problems"
|
||||||
|
|
||||||
|
@ -63,6 +64,28 @@ class InternalProblem(InternalView, ListView):
|
||||||
return context
|
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):
|
class RequestTimeMixin(object):
|
||||||
def get_requests_data(self):
|
def get_requests_data(self):
|
||||||
logger = logging.getLogger(self.log_name)
|
logger = logging.getLogger(self.log_name)
|
||||||
|
|
|
@ -73,6 +73,7 @@ from judge.views.problem import ProblemList
|
||||||
from judge.views.contests import ContestList
|
from judge.views.contests import ContestList
|
||||||
from judge.views.submission import AllSubmissions, SubmissionsListBase
|
from judge.views.submission import AllSubmissions, SubmissionsListBase
|
||||||
from judge.views.feed import FeedView
|
from judge.views.feed import FeedView
|
||||||
|
from judge.tasks import rescore_contest
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"OrganizationList",
|
"OrganizationList",
|
||||||
|
@ -394,7 +395,7 @@ class OrganizationContestMixin(
|
||||||
model = Contest
|
model = Contest
|
||||||
|
|
||||||
def is_contest_editable(self, request, 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
|
self.organization
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -947,7 +948,7 @@ class EditOrganizationContest(
|
||||||
|
|
||||||
def get_content_title(self):
|
def get_content_title(self):
|
||||||
href = reverse("contest_view", args=[self.contest.key])
|
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):
|
def get_object(self):
|
||||||
return self.contest
|
return self.contest
|
||||||
|
@ -960,6 +961,19 @@ class EditOrganizationContest(
|
||||||
self.object.organizations.add(self.organization)
|
self.object.organizations.add(self.organization)
|
||||||
self.object.is_organization_private = True
|
self.object.is_organization_private = True
|
||||||
self.object.save()
|
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
|
return res
|
||||||
|
|
||||||
def get_problem_formset(self, post=False):
|
def get_problem_formset(self, post=False):
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from datetime import timedelta, datetime
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from random import randrange
|
from random import randrange
|
||||||
import random
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
@ -77,6 +75,8 @@ from judge.utils.problems import (
|
||||||
user_attempted_ids,
|
user_attempted_ids,
|
||||||
user_completed_ids,
|
user_completed_ids,
|
||||||
get_related_problems,
|
get_related_problems,
|
||||||
|
get_user_recommended_problems,
|
||||||
|
RecommendationType,
|
||||||
)
|
)
|
||||||
from judge.utils.strings import safe_float_or_none, safe_int_or_none
|
from judge.utils.strings import safe_float_or_none, safe_int_or_none
|
||||||
from judge.utils.tickets import own_ticket_filter
|
from judge.utils.tickets import own_ticket_filter
|
||||||
|
@ -834,24 +834,34 @@ class ProblemFeed(ProblemList, FeedView):
|
||||||
title = _("Problem feed")
|
title = _("Problem feed")
|
||||||
feed_type = None
|
feed_type = None
|
||||||
|
|
||||||
# arr = [[], [], ..]
|
def get_recommended_problem_ids(self, queryset):
|
||||||
def merge_recommendation(self, arr):
|
user_id = self.request.profile.id
|
||||||
seed = datetime.now().strftime("%d%m%Y")
|
problem_ids = queryset.values_list("id", flat=True)
|
||||||
merged_array = []
|
rec_types = [
|
||||||
for a in arr:
|
RecommendationType.CF_DOT,
|
||||||
merged_array += a
|
RecommendationType.CF_COSINE,
|
||||||
random.Random(seed).shuffle(merged_array)
|
RecommendationType.CF_TIME_DOT,
|
||||||
|
RecommendationType.CF_TIME_COSINE,
|
||||||
|
RecommendationType.HOT_PROBLEM,
|
||||||
|
]
|
||||||
|
limits = [100, 100, 100, 100, 20]
|
||||||
|
shuffle = True
|
||||||
|
|
||||||
res = []
|
allow_debug_type = (
|
||||||
used_pid = set()
|
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:
|
return get_user_recommended_problems(
|
||||||
if type(obj) == tuple:
|
user_id, problem_ids, rec_types, limits, shuffle
|
||||||
obj = obj[1]
|
)
|
||||||
if obj not in used_pid:
|
|
||||||
res.append(obj)
|
|
||||||
used_pid.add(obj)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
if self.feed_type == "volunteer":
|
if self.feed_type == "volunteer":
|
||||||
|
@ -885,40 +895,8 @@ class ProblemFeed(ProblemList, FeedView):
|
||||||
if not settings.ML_OUTPUT_PATH or not user:
|
if not settings.ML_OUTPUT_PATH or not user:
|
||||||
return queryset.order_by("?").add_i18n_name(self.request.LANGUAGE_CODE)
|
return queryset.order_by("?").add_i18n_name(self.request.LANGUAGE_CODE)
|
||||||
|
|
||||||
cf_model = CollabFilter("collab_filter")
|
q = self.get_recommended_problem_ids(queryset)
|
||||||
cf_time_model = CollabFilter("collab_filter_time")
|
|
||||||
|
|
||||||
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 = Problem.objects.filter(id__in=q)
|
||||||
queryset = queryset.add_i18n_name(self.request.LANGUAGE_CODE)
|
queryset = queryset.add_i18n_name(self.request.LANGUAGE_CODE)
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ from judge.utils.fine_uploader import (
|
||||||
FineUploadForm,
|
FineUploadForm,
|
||||||
)
|
)
|
||||||
from judge.views.problem import ProblemMixin
|
from judge.views.problem import ProblemMixin
|
||||||
|
from judge.logging import log_exception
|
||||||
|
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
mimetypes.add_type("application/x-yaml", ".yml")
|
mimetypes.add_type("application/x-yaml", ".yml")
|
||||||
|
@ -249,6 +250,9 @@ class ProblemDataView(TitleMixin, ProblemManagerMixin):
|
||||||
return ZipFile(data.zipfile.path).namelist()
|
return ZipFile(data.zipfile.path).namelist()
|
||||||
except BadZipfile:
|
except BadZipfile:
|
||||||
return []
|
return []
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
log_exception(e)
|
||||||
|
return []
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.http import HttpResponseForbidden
|
from django.http import HttpResponseForbidden, JsonResponse
|
||||||
from judge.models import Contest
|
from judge.models import Contest
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class Resolver(TemplateView):
|
||||||
hidden_subtasks = self.contest.format.get_hidden_subtasks()
|
hidden_subtasks = self.contest.format.get_hidden_subtasks()
|
||||||
num_problems = len(problems)
|
num_problems = len(problems)
|
||||||
problem_sub = [0] * num_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)}
|
problems_json = {str(i): {} for i in range(1, num_problems + 1)}
|
||||||
|
|
||||||
users = {}
|
users = {}
|
||||||
|
@ -126,10 +126,8 @@ class Resolver(TemplateView):
|
||||||
|
|
||||||
for i in hidden_subtasks:
|
for i in hidden_subtasks:
|
||||||
order = id_to_order[i]
|
order = id_to_order[i]
|
||||||
if hidden_subtasks[i]:
|
sub_frozen[order - 1] = list(hidden_subtasks[i])
|
||||||
sub_frozen[order - 1] = min(hidden_subtasks[i])
|
|
||||||
else:
|
|
||||||
sub_frozen[order - 1] = problem_sub[order - 1] + 1
|
|
||||||
return {
|
return {
|
||||||
"problem_sub": problem_sub,
|
"problem_sub": problem_sub,
|
||||||
"sub_frozen": sub_frozen,
|
"sub_frozen": sub_frozen,
|
||||||
|
@ -143,8 +141,15 @@ class Resolver(TemplateView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if request.user.is_superuser:
|
if not request.user.is_superuser:
|
||||||
self.contest = Contest.objects.get(key=kwargs.get("contest"))
|
return HttpResponseForbidden()
|
||||||
if self.contest.format.has_hidden_subtasks:
|
self.contest = Contest.objects.get(key=kwargs.get("contest"))
|
||||||
return super(Resolver, self).get(request, *args, **kwargs)
|
if not self.contest.format.has_hidden_subtasks:
|
||||||
return HttpResponseForbidden()
|
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)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import json
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
import zipfile
|
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -196,8 +195,8 @@ def get_cases_data(submission):
|
||||||
continue
|
continue
|
||||||
count += 1
|
count += 1
|
||||||
problem_data[count] = {
|
problem_data[count] = {
|
||||||
"input": case_data[case.input_file] if case.input_file else "",
|
"input": case_data.get(case.input_file, "") if case.input_file else "",
|
||||||
"answer": case_data[case.output_file] if case.output_file else "",
|
"answer": case_data.get(case.output_file, "") if case.output_file else "",
|
||||||
}
|
}
|
||||||
|
|
||||||
return problem_data
|
return problem_data
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -39,12 +39,6 @@ msgstr "Đăng ký tên"
|
||||||
msgid "Report"
|
msgid "Report"
|
||||||
msgstr "Báo cáo"
|
msgstr "Báo cáo"
|
||||||
|
|
||||||
msgid "Insert Image"
|
|
||||||
msgstr "Chèn hình ảnh"
|
|
||||||
|
|
||||||
msgid "Save"
|
|
||||||
msgstr "Lưu"
|
|
||||||
|
|
||||||
msgid "2sat"
|
msgid "2sat"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -600,3 +594,8 @@ msgstr ""
|
||||||
msgid "z-function"
|
msgid "z-function"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#~ msgid "Insert Image"
|
||||||
|
#~ msgstr "Chèn hình ảnh"
|
||||||
|
|
||||||
|
#~ msgid "Save"
|
||||||
|
#~ msgstr "Lưu"
|
||||||
|
|
|
@ -147,8 +147,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-box:hover {
|
.blog-box:hover, .blog-box:not(.pre-expand-blog) {
|
||||||
border-color: #8a8a8a;
|
border-color: #8a8a8a;
|
||||||
|
box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-description {
|
.blog-description {
|
||||||
|
|
|
@ -269,10 +269,10 @@ a {
|
||||||
.actionbar-button {
|
.actionbar-button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.8em;
|
padding: 0.8em;
|
||||||
border: 0.2px solid lightgray;
|
|
||||||
border-radius: 5em;
|
border-radius: 5em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: inherit;
|
display: inherit;
|
||||||
|
background: lightgray;
|
||||||
}
|
}
|
||||||
.actionbar-block {
|
.actionbar-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -287,7 +287,7 @@ a {
|
||||||
border-radius: 5em 0 0 5em;
|
border-radius: 5em 0 0 5em;
|
||||||
}
|
}
|
||||||
.actionbar-button:hover {
|
.actionbar-button:hover {
|
||||||
background: lightgray;
|
background: darkgray;
|
||||||
}
|
}
|
||||||
.dislike-button {
|
.dislike-button {
|
||||||
padding-left: 0.5em;
|
padding-left: 0.5em;
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
|
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
margin: 1.5em 0 1.5em 0;
|
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border: 1px solid $border_gray;
|
border: 1px solid $border_gray;
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
|
|
|
@ -1956,8 +1956,10 @@ input::placeholder {
|
||||||
background-color: rgb(24, 26, 27);
|
background-color: rgb(24, 26, 27);
|
||||||
box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 5px;
|
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);
|
border-color: rgb(81, 88, 91);
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 2px;
|
||||||
}
|
}
|
||||||
.problem-feed-name a {
|
.problem-feed-name a {
|
||||||
color: rgb(102, 177, 250);
|
color: rgb(102, 177, 250);
|
||||||
|
@ -2091,6 +2093,9 @@ ul.problem-list {
|
||||||
#comment-announcement:hover {
|
#comment-announcement:hover {
|
||||||
background-color: rgb(96, 104, 108);
|
background-color: rgb(96, 104, 108);
|
||||||
}
|
}
|
||||||
|
.new-problem-info {
|
||||||
|
background-color: rgb(54, 39, 0);
|
||||||
|
}
|
||||||
.admin a,
|
.admin a,
|
||||||
.admin {
|
.admin {
|
||||||
color: rgb(232, 230, 227) !important;
|
color: rgb(232, 230, 227) !important;
|
||||||
|
@ -2780,11 +2785,12 @@ a.voted {
|
||||||
border-left-color: rgb(48, 52, 54);
|
border-left-color: rgb(48, 52, 54);
|
||||||
}
|
}
|
||||||
.actionbar .actionbar-button {
|
.actionbar .actionbar-button {
|
||||||
border-color: rgb(60, 65, 68);
|
background-image: initial;
|
||||||
|
background-color: rgb(49, 53, 55);
|
||||||
}
|
}
|
||||||
.actionbar .actionbar-button:hover {
|
.actionbar .actionbar-button:hover {
|
||||||
background-image: initial;
|
background-image: initial;
|
||||||
background-color: rgb(49, 53, 55);
|
background-color: rgb(73, 79, 82);
|
||||||
}
|
}
|
||||||
.actionbar .dislike-button {
|
.actionbar .dislike-button {
|
||||||
border-left-color: initial;
|
border-left-color: initial;
|
||||||
|
|
|
@ -25,17 +25,17 @@ div.dmmd-preview-has-content div.dmmd-preview-content {
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dmmd-no-button div.dmmd-preview-update {
|
div.dmmd-no-button div.dmmd-preview-update {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dmmd-no-button div.dmmd-preview-content {
|
div.dmmd-no-button div.dmmd-preview-content {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dmmd-no-button:not(.dmmd-preview-has-content) {
|
div.dmmd-no-button:not(.dmmd-preview-has-content) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.dmmd-preview-stale {
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,59 +4,59 @@
|
||||||
if you are not yet familiar with Fine Uploader UI.
|
if you are not yet familiar with Fine Uploader UI.
|
||||||
-->
|
-->
|
||||||
<script type="text/template" id="qq-template">
|
<script type="text/template" id="qq-template">
|
||||||
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
|
<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 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 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>
|
</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>
|
</script>
|
||||||
|
|
|
@ -5,78 +5,78 @@
|
||||||
on how to customize this template.
|
on how to customize this template.
|
||||||
-->
|
-->
|
||||||
<script type="text/template" id="qq-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-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 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 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>
|
</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>
|
</script>
|
||||||
|
|
|
@ -5,60 +5,60 @@
|
||||||
on how to customize this template.
|
on how to customize this template.
|
||||||
-->
|
-->
|
||||||
<script type="text/template" id="qq-simple-thumbnails-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-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 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 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>
|
</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>
|
</script>
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
.mwe-math-mathml-inline {
|
.mwe-math-mathml-inline {
|
||||||
display: inline !important;
|
display: inline !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mwe-math-mathml-display {
|
.mwe-math-mathml-display {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mwe-math-mathml-a11y {
|
.mwe-math-mathml-a11y {
|
||||||
clip: rect(1px, 1px, 1px, 1px);
|
clip: rect(1px, 1px, 1px, 1px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mwe-math-fallback-image-inline {
|
.mwe-math-fallback-image-inline {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mwe-math-fallback-image-display {
|
.mwe-math-fallback-image-display {
|
||||||
display: block;
|
display: block;
|
||||||
margin-left: auto !important;
|
margin-left: auto !important;
|
||||||
margin-right: auto !important;
|
margin-right: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Latin Modern Math';
|
font-family: 'Latin Modern Math';
|
||||||
src: url('libs/latinmodernmath/latinmodern-math.eot'); /* IE9 Compat Modes */
|
src: url('libs/latinmodernmath/latinmodern-math.eot'); /* IE9 Compat Modes */
|
||||||
src: local('Latin Modern Math'), local('LatinModernMath-Regular'),
|
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.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||||
url('libs/latinmodernmath/latinmodern-math.woff2') format('woff2'), /* Modern Browsers */
|
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.woff') format('woff'), /* Modern Browsers */
|
||||||
url('libs/latinmodernmath/latinmodern-math.ttf') format('truetype'); /* Safari, Android, iOS */
|
url('libs/latinmodernmath/latinmodern-math.ttf') format('truetype'); /* Safari, Android, iOS */
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
math {
|
math {
|
||||||
font-family: "Latin Modern Math";
|
font-family: "Latin Modern Math";
|
||||||
}
|
}
|
||||||
|
|
||||||
img.inline-math {
|
img.inline-math {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ th.header.rank {
|
||||||
th a:hover {
|
th a:hover {
|
||||||
color: #0F0;
|
color: #0F0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about-column {
|
.about-column {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
}
|
}
|
||||||
|
@ -362,7 +362,7 @@ a.edit-profile {
|
||||||
}
|
}
|
||||||
|
|
||||||
.follow {
|
.follow {
|
||||||
background: green;
|
background: green;
|
||||||
border-color: lightgreen;
|
border-color: lightgreen;
|
||||||
}
|
}
|
||||||
.follow:hover {
|
.follow:hover {
|
||||||
|
|
|
@ -113,7 +113,7 @@
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(link)
|
.writeText(link)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showTooltip(element, "Copied link", 'n');
|
showTooltip(element, "{{_('Copied link')}}", 'n');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -75,112 +75,6 @@
|
||||||
<link rel="stylesheet" href="{{ static('darkmode-svg.css') }}">
|
<link rel="stylesheet" href="{{ static('darkmode-svg.css') }}">
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% endif %}
|
{% 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>
|
<noscript>
|
||||||
<style>
|
<style>
|
||||||
|
@ -378,12 +272,121 @@
|
||||||
<div id="announcement">{{ i18n_config.announcement|safe }}</div>
|
<div id="announcement">{{ i18n_config.announcement|safe }}</div>
|
||||||
{% endif %}
|
{% 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 bodyend %}{% endblock %}
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
<footer>
|
<footer>
|
||||||
<span id="footer-content">
|
<span id="footer-content">
|
||||||
<br>
|
<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 %}
|
{% if i18n_config.footer %}
|
||||||
{{ i18n_config.footer|safe }} |
|
{{ i18n_config.footer|safe }} |
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -5,7 +5,10 @@
|
||||||
{% block title %} {{_('Chat Box')}} {% endblock %}
|
{% block title %} {{_('Chat Box')}} {% endblock %}
|
||||||
{% block js_media %}
|
{% 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="text/javascript" src="{{ static('event.js') }}"></script>
|
||||||
<script type="module" src="https://unpkg.com/emoji-picker-element@1"></script>
|
<script type="module" src="https://unpkg.com/emoji-picker-element@1"></script>
|
||||||
{% compress js %}
|
{% compress js %}
|
||||||
|
@ -79,7 +82,7 @@
|
||||||
{% include 'chat/user_online_status.html' %}
|
{% include 'chat/user_online_status.html' %}
|
||||||
</div>
|
</div>
|
||||||
<div id="chat-box">
|
<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">
|
<ul id="chat-log">
|
||||||
{% include 'chat/message_list.html' %}
|
{% include 'chat/message_list.html' %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -10,7 +10,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-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 {
|
::-webkit-scrollbar-track {
|
||||||
|
@ -144,9 +152,11 @@
|
||||||
transition: box-shadow 0.3s ease-in-out;
|
transition: box-shadow 0.3s ease-in-out;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
resize: none;
|
resize: none;
|
||||||
height: 80%;
|
height: 70%;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
#chat-input:focus {
|
#chat-input:focus {
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
let META_HEADER = [
|
|
||||||
"{{_('Recent')}}",
|
|
||||||
"{{_('Following')}}",
|
|
||||||
"{{_('Admin')}}",
|
|
||||||
"{{_('Other')}}",
|
|
||||||
];
|
|
||||||
let isMobile = window.matchMedia("only screen and (max-width: 799px)").matches;
|
let isMobile = window.matchMedia("only screen and (max-width: 799px)").matches;
|
||||||
|
|
||||||
function load_next_page(last_id, refresh_html=false) {
|
function load_next_page(last_id, refresh_html=false) {
|
||||||
|
@ -561,7 +555,7 @@
|
||||||
this.style.height = (this.scrollHeight) + 'px';
|
this.style.height = (this.scrollHeight) + 'px';
|
||||||
$(this).css('border-radius', '30px');
|
$(this).css('border-radius', '30px');
|
||||||
} else {
|
} else {
|
||||||
$(this).css('height', '80%');
|
$(this).css('height', '70%');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<li class="message" id="message-{{ message.id }}" message-id="{{ message.id }}">
|
<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">
|
<img src="{{ gravatar(message.author, 135) }}" class="profile-pic">
|
||||||
</a>
|
</a>
|
||||||
<div class="body-message">
|
<div class="body-message">
|
||||||
<div class="user-time">
|
<div class="user-time">
|
||||||
<span class="username {{ message.author.css_class }}">
|
<span class="username {{ message.author.css_class }}">
|
||||||
<a href="{{ url('user_page', message.author.user.username) }}">
|
<a href="{{ url('user_page', message.author.username) }}">
|
||||||
{{ message.author }}
|
{{ message.author.username }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="time">
|
<span class="time">
|
||||||
|
|
|
@ -6,8 +6,4 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<center id="empty_msg">{{_('You are connect now. Say something to start the conversation.')}}</center>
|
<center id="empty_msg">{{_('You are connect now. Say something to start the conversation.')}}</center>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if REQUIRE_JAX %}
|
|
||||||
{% include "mathjax-load.html" %}
|
|
||||||
{% endif %}
|
|
||||||
{% include "comments/math.html" %}
|
|
|
@ -145,6 +145,8 @@
|
||||||
$comment_loading.hide();
|
$comment_loading.hide();
|
||||||
var $comment = $("#comment-" + id + "-children");
|
var $comment = $("#comment-" + id + "-children");
|
||||||
$comment.append(data);
|
$comment.append(data);
|
||||||
|
MathJax.typeset($('#comments')[0]);
|
||||||
|
register_time($('.time-with-rel'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -187,6 +189,7 @@
|
||||||
$comment.append(data);
|
$comment.append(data);
|
||||||
}
|
}
|
||||||
MathJax.typeset($('#comments')[0]);
|
MathJax.typeset($('#comments')[0]);
|
||||||
|
register_time($('.time-with-rel'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
|
|
||||||
<div><label class="inline-header grayed">{{ _('Enter a new key for the cloned contest:') }}</label></div>
|
<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 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}}
|
{{form.organization}}
|
||||||
{% if form.organization.errors %}
|
{% if form.organization.errors %}
|
||||||
<div id="form-errors">
|
<div id="form-errors">
|
||||||
|
|
|
@ -36,7 +36,4 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ make_tab_item('edit', 'fa fa-edit', url('admin:judge_contest_change', contest.id), _('Edit')) }}
|
{{ make_tab_item('edit', 'fa fa-edit', url('admin:judge_contest_change', contest.id), _('Edit')) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.judge.clone_contest %}
|
|
||||||
{{ make_tab_item('clone', 'fa fa-copy', url('contest_clone', contest.key), _('Clone')) }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -82,11 +82,16 @@
|
||||||
{{ contest.description|markdown|reference|str|safe }}
|
{{ contest.description|markdown|reference|str|safe }}
|
||||||
{% endcache %}
|
{% endcache %}
|
||||||
</div>
|
</div>
|
||||||
{% if editable_organizations %}
|
{% if editable_organizations or is_clonable %}
|
||||||
<div>
|
<div style="display: flex; gap: 0.5em;">
|
||||||
{% for org in editable_organizations %}
|
{% for org in editable_organizations %}
|
||||||
<span> [<a href="{{ url('organization_contest_edit', org.id , org.slug , contest.key) }}">{{ _('Edit in') }} {{org.slug}}</a>]</span>
|
<span> [<a href="{{ url('organization_contest_edit', org.id , org.slug , contest.key) }}">{{ _('Edit in') }} {{org.slug}}</a>]</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if is_clonable %}
|
||||||
|
<span>
|
||||||
|
[<a href="{{url('contest_clone', contest.key)}}"}}>{{_('Clone')}}</a>]
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -13,15 +13,15 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block user_footer %}
|
{% block user_footer %}
|
||||||
{% if user.first_name %}
|
{% if user.user.first_name %}
|
||||||
<div style="font-weight: 600; display: none" class="fullname gray">
|
<div style="font-weight: 600; display: none" class="fullname gray">
|
||||||
{{ user.first_name if user.first_name else ''}}
|
{{ user.user.first_name }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.last_name %}
|
{% if user.user.last_name %}
|
||||||
<div class="school gray" style="display: none"><a style="font-weight: 600">
|
<div class="school gray" style="display: none"><div style="font-weight: 600">
|
||||||
{{- user.last_name -}}
|
{{- user.user.last_name -}}
|
||||||
</a></div>
|
</div></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Courses</title>
|
<title>Courses</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -1,19 +1,19 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Enrolling</h1>
|
<h1>Enrolling</h1>
|
||||||
{% for course in enrolling %}
|
{% for course in enrolling %}
|
||||||
<h2> {{ course }} </h2>
|
<h2> {{ course }} </h2>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<h1> Available </h1>
|
<h1> Available </h1>
|
||||||
{% for course in available %}
|
{% for course in available %}
|
||||||
<h2> {{ course }} </h2>
|
<h2> {{ course }} </h2>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -16,8 +16,9 @@
|
||||||
$('.vote-detail').each(function() {
|
$('.vote-detail').each(function() {
|
||||||
$(this).on('click', function() {
|
$(this).on('click', function() {
|
||||||
var pid = $(this).attr('pid');
|
var pid = $(this).attr('pid');
|
||||||
$('.detail').hide();
|
$.get("{{url('internal_problem_votes')}}?id="+pid, function(data) {
|
||||||
$('#detail-'+pid).show();
|
$('#detail').html(data);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -59,37 +60,8 @@
|
||||||
|
|
||||||
{% block right_sidebar %}
|
{% block right_sidebar %}
|
||||||
<div style="display: block; width: 100%">
|
<div style="display: block; width: 100%">
|
||||||
<div><a href="{{url('admin:judge_volunteerproblemvote_changelist')}}">{{_('Admin')}}</a></div>
|
<a href="{{url('admin:judge_volunteerproblemvote_changelist')}}">{{_('Admin')}}</a>
|
||||||
{% for problem in problems %}
|
<div class="detail" id="detail">
|
||||||
<div class="detail" id="detail-{{problem.id}}" style="display: none;">
|
</div>
|
||||||
<h3>{{_('Votes for problem') }} {{problem.name}}</h3>
|
</div>
|
||||||
<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 %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
29
templates/internal/problem/votes.html
Normal file
29
templates/internal/problem/votes.html
Normal 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>
|
|
@ -29,12 +29,6 @@
|
||||||
height: 2em;
|
height: 2em;
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
@media(min-width: 800px) {
|
|
||||||
#content {
|
|
||||||
width: 99%;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media(max-width: 799px) {
|
@media(max-width: 799px) {
|
||||||
#content {
|
#content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -117,7 +117,6 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
{% if has_render %}
|
{% if has_render %}
|
||||||
<a href="{{ url('problem_pdf', problem.code) }}" class="view-pdf" target="_blank">
|
<a href="{{ url('problem_pdf', problem.code) }}" class="view-pdf" target="_blank">
|
||||||
|
@ -135,6 +134,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block info_float %}
|
{% block info_float %}
|
||||||
{% if request.user.is_authenticated and request.in_contest_mode and submission_limit %}
|
{% if request.user.is_authenticated and request.in_contest_mode and submission_limit %}
|
||||||
{% if submissions_left > 0 %}
|
{% if submissions_left > 0 %}
|
||||||
|
@ -204,53 +204,6 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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">
|
<hr style="padding-top: 0.7em">
|
||||||
|
|
||||||
{% cache 86400 'problem_authors' problem.id LANGUAGE_CODE %}
|
{% cache 86400 'problem_authors' problem.id LANGUAGE_CODE %}
|
||||||
|
@ -342,6 +295,40 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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 %}
|
{% cache 86400 'problem_html' problem.id MATH_ENGINE LANGUAGE_CODE %}
|
||||||
{{ description|markdown(lazy_load=True)|reference|str|safe }}
|
{{ description|markdown(lazy_load=True)|reference|str|safe }}
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
|
|
||||||
{% block js_media %}
|
{% block js_media %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
let loadingPage;
|
||||||
|
|
||||||
function activateBlogBoxOnClick() {
|
function activateBlogBoxOnClick() {
|
||||||
$('.blog-box').on('click', function () {
|
$('.blog-box').on('click', function () {
|
||||||
var $description = $(this).children('.blog-description');
|
var $description = $(this).children('.blog-description');
|
||||||
|
@ -64,13 +66,16 @@
|
||||||
$elem.addClass('active');
|
$elem.addClass('active');
|
||||||
}
|
}
|
||||||
$(window).off("scroll");
|
$(window).off("scroll");
|
||||||
$('.middle-right-content').html(loading_page);
|
$('.middle-right-content').html(loadingPage);
|
||||||
$.get(url, function (data) {
|
$.get(url, function (data) {
|
||||||
var reload_content = $(data).find('.middle-right-content');
|
var reload_content = $(data).find('.middle-right-content');
|
||||||
|
var bodyend_script = $(data).find('#extra_js');
|
||||||
if (reload_content.length) {
|
if (reload_content.length) {
|
||||||
window.history.pushState("", "", url);
|
window.history.pushState("", "", url);
|
||||||
$('html, body').animate({scrollTop: 0}, 'fast');
|
$('html, body').animate({scrollTop: 0}, 'fast');
|
||||||
$('.middle-right-content').html(reload_content.first().html());
|
$('.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")) {
|
if (reload_content.hasClass("wrapper")) {
|
||||||
$('.middle-right-content').addClass("wrapper");
|
$('.middle-right-content').addClass("wrapper");
|
||||||
}
|
}
|
||||||
|
@ -83,7 +88,6 @@
|
||||||
activateBlogBoxOnClick();
|
activateBlogBoxOnClick();
|
||||||
$('.xdsoft_datetimepicker').hide();
|
$('.xdsoft_datetimepicker').hide();
|
||||||
registerNavigation();
|
registerNavigation();
|
||||||
$("#loading-bar").hide().css({ width: 0});
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
|
@ -112,6 +116,9 @@
|
||||||
navigateTo($(this), true);
|
navigateTo($(this), true);
|
||||||
});
|
});
|
||||||
registerNavigation();
|
registerNavigation();
|
||||||
|
$.get("{{static('html/loading-page.html')}}", function(data) {
|
||||||
|
loadingPage = data;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -129,7 +136,6 @@
|
||||||
{% block left_sidebar %}{% endblock %}
|
{% block left_sidebar %}{% endblock %}
|
||||||
<div class="middle-right-content {{'wrapper' if is_two_column}}">
|
<div class="middle-right-content {{'wrapper' if is_two_column}}">
|
||||||
{% block three_col_media %}{% endblock %}
|
{% block three_col_media %}{% endblock %}
|
||||||
{% block three_col_js %}{% endblock %}
|
|
||||||
<div class="middle-content">
|
<div class="middle-content">
|
||||||
{% block middle_title %}{% endblock %}
|
{% block middle_title %}{% endblock %}
|
||||||
{% block middle_content %}{% endblock %}
|
{% block middle_content %}{% endblock %}
|
||||||
|
@ -140,6 +146,10 @@
|
||||||
{% block after_posts %}{% endblock %}
|
{% block after_posts %}{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
{% block three_col_js %}{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block bodyend %}
|
{% block bodyend %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{% if REQUIRE_JAX %}
|
{% if REQUIRE_JAX %}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue