commit
0ee89f1c36
281 changed files with 15287 additions and 13660 deletions
13
.pre-commit-config.yaml
Normal file
13
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/rtts/djhtml
|
||||||
|
rev: 'v1.5.2' # replace with the latest tag on GitHub
|
||||||
|
hooks:
|
||||||
|
- id: djhtml
|
||||||
|
entry: djhtml -i -t 2
|
||||||
|
files: templates/.
|
||||||
|
- id: djcss
|
||||||
|
types: [scss]
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 22.12.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
|
@ -90,6 +90,8 @@ celery -A dmoj_celery worker
|
||||||
node websocket/daemon.js
|
node websocket/daemon.js
|
||||||
```
|
```
|
||||||
|
|
||||||
|
7. To use subdomain for each organization, go to admin page -> navigation bar -> sites, add domain name (e.g, "localhost:8000"). Then go to add `USE_SUBDOMAIN = True` to local_settings.py.
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
Most of the steps are similar to Django tutorials. Here are two usual steps:
|
Most of the steps are similar to Django tutorials. Here are two usual steps:
|
||||||
|
|
||||||
|
|
20
chat_box/migrations/0011_alter_message_hidden.py
Normal file
20
chat_box/migrations/0011_alter_message_hidden.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 3.2.18 on 2023-02-18 21:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("chat_box", "0010_auto_20221028_0300"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="message",
|
||||||
|
name="hidden",
|
||||||
|
field=models.BooleanField(
|
||||||
|
db_index=True, default=False, verbose_name="is hidden"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -31,7 +31,7 @@ class Message(models.Model):
|
||||||
author = models.ForeignKey(Profile, verbose_name=_("user"), on_delete=CASCADE)
|
author = models.ForeignKey(Profile, verbose_name=_("user"), on_delete=CASCADE)
|
||||||
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
|
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
|
||||||
body = models.TextField(verbose_name=_("body of comment"), max_length=8192)
|
body = models.TextField(verbose_name=_("body of comment"), max_length=8192)
|
||||||
hidden = models.BooleanField(verbose_name="is hidden", default=False)
|
hidden = models.BooleanField(verbose_name="is hidden", default=False, db_index=True)
|
||||||
room = models.ForeignKey(
|
room = models.ForeignKey(
|
||||||
Room, verbose_name="room id", on_delete=CASCADE, default=None, null=True
|
Room, verbose_name="room id", on_delete=CASCADE, default=None, null=True
|
||||||
)
|
)
|
||||||
|
|
|
@ -48,16 +48,26 @@ class ChatView(ListView):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.room_id = None
|
self.room_id = None
|
||||||
self.room = None
|
self.room = None
|
||||||
self.paginate_by = 50
|
|
||||||
self.messages = None
|
self.messages = None
|
||||||
self.paginator = None
|
self.page_size = 20
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.messages
|
return self.messages
|
||||||
|
|
||||||
|
def has_next(self):
|
||||||
|
try:
|
||||||
|
msg = Message.objects.filter(room=self.room_id).earliest("id")
|
||||||
|
except Exception as e:
|
||||||
|
return False
|
||||||
|
return msg not in self.messages
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
request_room = kwargs["room_id"]
|
request_room = kwargs["room_id"]
|
||||||
page = request.GET.get("page")
|
try:
|
||||||
|
last_id = int(request.GET.get("last_id"))
|
||||||
|
except Exception:
|
||||||
|
last_id = 1e15
|
||||||
|
only_messages = request.GET.get("only_messages")
|
||||||
|
|
||||||
if request_room:
|
if request_room:
|
||||||
try:
|
try:
|
||||||
|
@ -69,23 +79,20 @@ class ChatView(ListView):
|
||||||
else:
|
else:
|
||||||
request_room = None
|
request_room = None
|
||||||
|
|
||||||
if request_room != self.room_id or not self.messages:
|
self.room_id = request_room
|
||||||
self.room_id = request_room
|
self.messages = Message.objects.filter(
|
||||||
self.messages = Message.objects.filter(hidden=False, room=self.room_id)
|
hidden=False, room=self.room_id, id__lt=last_id
|
||||||
self.paginator = Paginator(self.messages, self.paginate_by)
|
)[: self.page_size]
|
||||||
|
if not only_messages:
|
||||||
if page == None:
|
|
||||||
update_last_seen(request, **kwargs)
|
update_last_seen(request, **kwargs)
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
cur_page = self.paginator.get_page(page)
|
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"chat/message_list.html",
|
"chat/message_list.html",
|
||||||
{
|
{
|
||||||
"object_list": cur_page.object_list,
|
"object_list": self.messages,
|
||||||
"num_pages": self.paginator.num_pages,
|
"has_next": self.has_next(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -96,6 +103,7 @@ class ChatView(ListView):
|
||||||
context["last_msg"] = event.last()
|
context["last_msg"] = event.last()
|
||||||
context["status_sections"] = get_status_context(self.request)
|
context["status_sections"] = get_status_context(self.request)
|
||||||
context["room"] = self.room_id
|
context["room"] = self.room_id
|
||||||
|
context["has_next"] = self.has_next()
|
||||||
context["unread_count_lobby"] = get_unread_count(None, self.request.profile)
|
context["unread_count_lobby"] = get_unread_count(None, self.request.profile)
|
||||||
if self.room:
|
if self.room:
|
||||||
users_room = [self.room.user_one, self.room.user_two]
|
users_room = [self.room.user_one, self.room.user_two]
|
||||||
|
|
|
@ -247,6 +247,7 @@ INSTALLED_APPS += (
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE = (
|
MIDDLEWARE = (
|
||||||
|
"judge.middleware.SlowRequestMiddleware",
|
||||||
"judge.middleware.ShortCircuitMiddleware",
|
"judge.middleware.ShortCircuitMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"django.middleware.locale.LocaleMiddleware",
|
"django.middleware.locale.LocaleMiddleware",
|
||||||
|
@ -262,6 +263,7 @@ MIDDLEWARE = (
|
||||||
"judge.middleware.DMOJImpersonationMiddleware",
|
"judge.middleware.DMOJImpersonationMiddleware",
|
||||||
"judge.middleware.ContestMiddleware",
|
"judge.middleware.ContestMiddleware",
|
||||||
"judge.middleware.DarkModeMiddleware",
|
"judge.middleware.DarkModeMiddleware",
|
||||||
|
"judge.middleware.SubdomainMiddleware",
|
||||||
"django.contrib.flatpages.middleware.FlatpageFallbackMiddleware",
|
"django.contrib.flatpages.middleware.FlatpageFallbackMiddleware",
|
||||||
"judge.social_auth.SocialAuthExceptionMiddleware",
|
"judge.social_auth.SocialAuthExceptionMiddleware",
|
||||||
"django.contrib.redirects.middleware.RedirectFallbackMiddleware",
|
"django.contrib.redirects.middleware.RedirectFallbackMiddleware",
|
||||||
|
@ -468,6 +470,9 @@ MESSAGES_TO_LOAD = 15
|
||||||
|
|
||||||
ML_OUTPUT_PATH = None
|
ML_OUTPUT_PATH = None
|
||||||
|
|
||||||
|
# Use subdomain for organizations
|
||||||
|
USE_SUBDOMAIN = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f:
|
with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f:
|
||||||
exec(f.read(), globals())
|
exec(f.read(), globals())
|
||||||
|
|
64
dmoj/urls.py
64
dmoj/urls.py
|
@ -64,6 +64,7 @@ from judge.views import (
|
||||||
widgets,
|
widgets,
|
||||||
internal,
|
internal,
|
||||||
resolver,
|
resolver,
|
||||||
|
course,
|
||||||
)
|
)
|
||||||
from judge.views.problem_data import (
|
from judge.views.problem_data import (
|
||||||
ProblemDataView,
|
ProblemDataView,
|
||||||
|
@ -230,18 +231,19 @@ urlpatterns = [
|
||||||
url(r"^problems/", paged_list_view(problem.ProblemList, "problem_list")),
|
url(r"^problems/", paged_list_view(problem.ProblemList, "problem_list")),
|
||||||
url(r"^problems/random/$", problem.RandomProblem.as_view(), name="problem_random"),
|
url(r"^problems/random/$", problem.RandomProblem.as_view(), name="problem_random"),
|
||||||
url(
|
url(
|
||||||
r"^problems/feed/",
|
r"^problems/feed/$",
|
||||||
paged_list_view(problem.ProblemFeed, "problem_feed", feed_type="for_you"),
|
problem.ProblemFeed.as_view(feed_type="for_you"),
|
||||||
|
name="problem_feed",
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r"^problems/feed/new/",
|
r"^problems/feed/new/$",
|
||||||
paged_list_view(problem.ProblemFeed, "problem_feed_new", feed_type="new"),
|
problem.ProblemFeed.as_view(feed_type="new"),
|
||||||
|
name="problem_feed_new",
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r"^problems/feed/volunteer/",
|
r"^problems/feed/volunteer/$",
|
||||||
paged_list_view(
|
problem.ProblemFeed.as_view(feed_type="volunteer"),
|
||||||
problem.ProblemFeed, "problem_feed_volunteer", feed_type="volunteer"
|
name="problem_feed_volunteer",
|
||||||
),
|
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r"^problem/(?P<problem>[^/]+)",
|
r"^problem/(?P<problem>[^/]+)",
|
||||||
|
@ -368,6 +370,10 @@ urlpatterns = [
|
||||||
r"^submissions/user/(?P<user>\w+)/",
|
r"^submissions/user/(?P<user>\w+)/",
|
||||||
paged_list_view(submission.AllUserSubmissions, "all_user_submissions"),
|
paged_list_view(submission.AllUserSubmissions, "all_user_submissions"),
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
r"^submissions/friends/",
|
||||||
|
paged_list_view(submission.AllFriendSubmissions, "all_friend_submissions"),
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
r"^src/(?P<submission>\d+)/raw$",
|
r"^src/(?P<submission>\d+)/raw$",
|
||||||
submission.SubmissionSourceRaw.as_view(),
|
submission.SubmissionSourceRaw.as_view(),
|
||||||
|
@ -486,6 +492,39 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
url(r"^contests/", paged_list_view(contests.ContestList, "contest_list")),
|
url(r"^contests/", paged_list_view(contests.ContestList, "contest_list")),
|
||||||
|
url(r"^courses/", paged_list_view(course.CourseList, "course_list")),
|
||||||
|
url(
|
||||||
|
r"^courses/(?P<pk>\d+)-(?P<slug>[\w-]*)",
|
||||||
|
include(
|
||||||
|
[
|
||||||
|
url(
|
||||||
|
r"^$",
|
||||||
|
course.CourseHome.as_view(),
|
||||||
|
name="course_home",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^/resource/$",
|
||||||
|
course.CourseResourceList.as_view(),
|
||||||
|
name="course_resource",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^/resource_edit/$",
|
||||||
|
course.CourseResourceEdit.as_view(),
|
||||||
|
name="course_resource_edit",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^/resource/(?P<pk>\d+)/$",
|
||||||
|
course.CourseResouceDetail.as_view(),
|
||||||
|
name="course_resource_detail",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^/resource/(?P<pk>\d+)/edit",
|
||||||
|
course.CourseResourceDetailEdit.as_view(),
|
||||||
|
name="course_resource_detail_edit",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
r"^contests/(?P<year>\d+)/(?P<month>\d+)/$",
|
r"^contests/(?P<year>\d+)/(?P<month>\d+)/$",
|
||||||
contests.ContestCalendar.as_view(),
|
contests.ContestCalendar.as_view(),
|
||||||
|
@ -536,13 +575,6 @@ urlpatterns = [
|
||||||
url(r"^/join$", contests.ContestJoin.as_view(), name="contest_join"),
|
url(r"^/join$", contests.ContestJoin.as_view(), name="contest_join"),
|
||||||
url(r"^/leave$", contests.ContestLeave.as_view(), name="contest_leave"),
|
url(r"^/leave$", contests.ContestLeave.as_view(), name="contest_leave"),
|
||||||
url(r"^/stats$", contests.ContestStats.as_view(), name="contest_stats"),
|
url(r"^/stats$", contests.ContestStats.as_view(), name="contest_stats"),
|
||||||
url(
|
|
||||||
r"^/rank/(?P<problem>\w+)/",
|
|
||||||
paged_list_view(
|
|
||||||
ranked_submission.ContestRankedSubmission,
|
|
||||||
"contest_ranked_submissions",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
url(
|
url(
|
||||||
r"^/submissions/(?P<user>\w+)/(?P<problem>\w+)",
|
r"^/submissions/(?P<user>\w+)/(?P<problem>\w+)",
|
||||||
paged_list_view(
|
paged_list_view(
|
||||||
|
@ -751,7 +783,7 @@ urlpatterns = [
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
url(r"^blog/", paged_list_view(blog.PostList, "blog_post_list")),
|
url(r"^blog/", blog.PostList.as_view(), name="blog_post_list"),
|
||||||
url(r"^post/(?P<id>\d+)-(?P<slug>.*)$", blog.PostView.as_view(), name="blog_post"),
|
url(r"^post/(?P<id>\d+)-(?P<slug>.*)$", blog.PostView.as_view(), name="blog_post"),
|
||||||
url(r"^license/(?P<key>[-\w.]+)$", license.LicenseDetail.as_view(), name="license"),
|
url(r"^license/(?P<key>[-\w.]+)$", license.LicenseDetail.as_view(), name="license"),
|
||||||
url(
|
url(
|
||||||
|
|
|
@ -39,8 +39,11 @@ from judge.models import (
|
||||||
Submission,
|
Submission,
|
||||||
Ticket,
|
Ticket,
|
||||||
VolunteerProblemVote,
|
VolunteerProblemVote,
|
||||||
|
Course,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from judge.models.course import CourseResource
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(BlogPost, BlogPostAdmin)
|
admin.site.register(BlogPost, BlogPostAdmin)
|
||||||
admin.site.register(Comment, CommentAdmin)
|
admin.site.register(Comment, CommentAdmin)
|
||||||
|
@ -64,3 +67,5 @@ admin.site.register(Profile, ProfileAdmin)
|
||||||
admin.site.register(Submission, SubmissionAdmin)
|
admin.site.register(Submission, SubmissionAdmin)
|
||||||
admin.site.register(Ticket, TicketAdmin)
|
admin.site.register(Ticket, TicketAdmin)
|
||||||
admin.site.register(VolunteerProblemVote, VolunteerProblemVoteAdmin)
|
admin.site.register(VolunteerProblemVote, VolunteerProblemVoteAdmin)
|
||||||
|
admin.site.register(Course)
|
||||||
|
admin.site.register(CourseResource)
|
||||||
|
|
|
@ -22,11 +22,23 @@ class CommentForm(ModelForm):
|
||||||
|
|
||||||
class CommentAdmin(VersionAdmin):
|
class CommentAdmin(VersionAdmin):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {"fields": ("author", "page", "parent", "score", "hidden")}),
|
(
|
||||||
|
None,
|
||||||
|
{
|
||||||
|
"fields": (
|
||||||
|
"author",
|
||||||
|
"parent",
|
||||||
|
"score",
|
||||||
|
"hidden",
|
||||||
|
"content_type",
|
||||||
|
"object_id",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
("Content", {"fields": ("body",)}),
|
("Content", {"fields": ("body",)}),
|
||||||
)
|
)
|
||||||
list_display = ["author", "linked_page", "time"]
|
list_display = ["author", "linked_object", "time"]
|
||||||
search_fields = ["author__user__username", "page", "body"]
|
search_fields = ["author__user__username", "body"]
|
||||||
readonly_fields = ["score"]
|
readonly_fields = ["score"]
|
||||||
actions = ["hide_comment", "unhide_comment"]
|
actions = ["hide_comment", "unhide_comment"]
|
||||||
list_filter = ["hidden"]
|
list_filter = ["hidden"]
|
||||||
|
@ -66,16 +78,6 @@ class CommentAdmin(VersionAdmin):
|
||||||
|
|
||||||
unhide_comment.short_description = _("Unhide comments")
|
unhide_comment.short_description = _("Unhide comments")
|
||||||
|
|
||||||
def linked_page(self, obj):
|
|
||||||
link = obj.link
|
|
||||||
if link is not None:
|
|
||||||
return format_html('<a href="{0}">{1}</a>', link, obj.page)
|
|
||||||
else:
|
|
||||||
return format_html("{0}", obj.page)
|
|
||||||
|
|
||||||
linked_page.short_description = _("Associated page")
|
|
||||||
linked_page.admin_order_field = "page"
|
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
super(CommentAdmin, self).save_model(request, obj, form, change)
|
super(CommentAdmin, self).save_model(request, obj, form, change)
|
||||||
if obj.hidden:
|
if obj.hidden:
|
||||||
|
|
|
@ -365,22 +365,27 @@ class ProblemAdmin(CompareVersionAdmin):
|
||||||
obj.is_organization_private = obj.organizations.count() > 0
|
obj.is_organization_private = obj.organizations.count() > 0
|
||||||
obj.save()
|
obj.save()
|
||||||
# Create notification
|
# Create notification
|
||||||
if "is_public" in form.changed_data:
|
if "is_public" in form.changed_data or "organizations" in form.changed_data:
|
||||||
users = set(obj.authors.all())
|
users = set(obj.authors.all())
|
||||||
users = users.union(users, set(obj.curators.all()))
|
users = users.union(users, set(obj.curators.all()))
|
||||||
|
orgs = []
|
||||||
if obj.organizations.count() > 0:
|
if obj.organizations.count() > 0:
|
||||||
for org in obj.organizations.all():
|
for org in obj.organizations.all():
|
||||||
users = users.union(users, set(org.admins.all()))
|
users = users.union(users, set(org.admins.all()))
|
||||||
|
orgs.append(org.name)
|
||||||
else:
|
else:
|
||||||
admins = Profile.objects.filter(user__is_superuser=True).all()
|
admins = Profile.objects.filter(user__is_superuser=True).all()
|
||||||
users = users.union(users, admins)
|
users = users.union(users, admins)
|
||||||
link = reverse_lazy("admin:judge_problem_change", args=(obj.id,))
|
link = reverse_lazy("admin:judge_problem_change", args=(obj.id,))
|
||||||
html = f'<a href="{link}">{obj.name}</a>'
|
html = f'<a href="{link}">{obj.name}</a>'
|
||||||
|
category = "Problem public: " + str(obj.is_public)
|
||||||
|
if orgs:
|
||||||
|
category += " (" + ", ".join(orgs) + ")"
|
||||||
for user in users:
|
for user in users:
|
||||||
notification = Notification(
|
notification = Notification(
|
||||||
owner=user,
|
owner=user,
|
||||||
html_link=html,
|
html_link=html,
|
||||||
category="Problem public: " + str(obj.is_public),
|
category=category,
|
||||||
author=request.profile,
|
author=request.profile,
|
||||||
)
|
)
|
||||||
notification.save()
|
notification.save()
|
||||||
|
|
|
@ -22,7 +22,7 @@ from reversion import revisions
|
||||||
from reversion.models import Revision, Version
|
from reversion.models import Revision, Version
|
||||||
|
|
||||||
from judge.dblock import LockModel
|
from judge.dblock import LockModel
|
||||||
from judge.models import Comment, CommentLock, Notification
|
from judge.models import Comment, Notification
|
||||||
from judge.widgets import HeavyPreviewPageDownWidget
|
from judge.widgets import HeavyPreviewPageDownWidget
|
||||||
from judge.jinja2.reference import get_user_from_text
|
from judge.jinja2.reference import get_user_from_text
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
||||||
def is_comment_locked(self):
|
def is_comment_locked(self):
|
||||||
if self.request.user.has_perm("judge.override_comment_lock"):
|
if self.request.user.has_perm("judge.override_comment_lock"):
|
||||||
return False
|
return False
|
||||||
return CommentLock.objects.filter(page=self.get_comment_page()).exists() or (
|
return (
|
||||||
self.request.in_contest
|
self.request.in_contest
|
||||||
and self.request.participation.contest.use_clarifications
|
and self.request.participation.contest.use_clarifications
|
||||||
)
|
)
|
||||||
|
@ -98,8 +98,6 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
||||||
@method_decorator(login_required)
|
@method_decorator(login_required)
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
page = self.get_comment_page()
|
|
||||||
|
|
||||||
if self.is_comment_locked():
|
if self.is_comment_locked():
|
||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
|
@ -110,16 +108,14 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
else:
|
else:
|
||||||
if not Comment.objects.filter(
|
if not self.object.comments.filter(hidden=False, id=parent).exists():
|
||||||
hidden=False, id=parent, page=page
|
|
||||||
).exists():
|
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
form = CommentForm(request, request.POST)
|
form = CommentForm(request, request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
comment = form.save(commit=False)
|
comment = form.save(commit=False)
|
||||||
comment.author = request.profile
|
comment.author = request.profile
|
||||||
comment.page = page
|
comment.linked_object = self.object
|
||||||
|
|
||||||
with LockModel(
|
with LockModel(
|
||||||
write=(Comment, Revision, Version), read=(ContentType,)
|
write=(Comment, Revision, Version), read=(ContentType,)
|
||||||
|
@ -136,7 +132,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
||||||
notification_reply.save()
|
notification_reply.save()
|
||||||
|
|
||||||
# add notification for page authors
|
# add notification for page authors
|
||||||
page_authors = comment.page_object.authors.all()
|
page_authors = comment.linked_object.authors.all()
|
||||||
for user in page_authors:
|
for user in page_authors:
|
||||||
if user == comment.author:
|
if user == comment.author:
|
||||||
continue
|
continue
|
||||||
|
@ -149,7 +145,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
||||||
|
|
||||||
add_mention_notifications(comment)
|
add_mention_notifications(comment)
|
||||||
|
|
||||||
return HttpResponseRedirect(request.path)
|
return HttpResponseRedirect(comment.get_absolute_url())
|
||||||
|
|
||||||
context = self.get_context_data(object=self.object, comment_form=form)
|
context = self.get_context_data(object=self.object, comment_form=form)
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
|
@ -159,15 +155,13 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
||||||
return self.render_to_response(
|
return self.render_to_response(
|
||||||
self.get_context_data(
|
self.get_context_data(
|
||||||
object=self.object,
|
object=self.object,
|
||||||
comment_form=CommentForm(
|
comment_form=CommentForm(request, initial={"parent": None}),
|
||||||
request, initial={"page": self.get_comment_page(), "parent": None}
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(CommentedDetailView, self).get_context_data(**kwargs)
|
context = super(CommentedDetailView, self).get_context_data(**kwargs)
|
||||||
queryset = Comment.objects.filter(hidden=False, page=self.get_comment_page())
|
queryset = self.object.comments
|
||||||
context["has_comments"] = queryset.exists()
|
context["has_comments"] = queryset.exists()
|
||||||
context["comment_lock"] = self.is_comment_locked()
|
context["comment_lock"] = self.is_comment_locked()
|
||||||
queryset = (
|
queryset = (
|
||||||
|
|
|
@ -263,6 +263,7 @@ class EditOrganizationContestForm(ModelForm):
|
||||||
"curators",
|
"curators",
|
||||||
"testers",
|
"testers",
|
||||||
"time_limit",
|
"time_limit",
|
||||||
|
"freeze_after",
|
||||||
"use_clarifications",
|
"use_clarifications",
|
||||||
"hide_problem_tags",
|
"hide_problem_tags",
|
||||||
"scoreboard_visibility",
|
"scoreboard_visibility",
|
||||||
|
|
|
@ -98,7 +98,7 @@ def markdown(value, lazy_load=False):
|
||||||
|
|
||||||
if not html:
|
if not html:
|
||||||
html = escape(value)
|
html = escape(value)
|
||||||
if lazy_load or True:
|
if lazy_load:
|
||||||
soup = BeautifulSoup(html, features="html.parser")
|
soup = BeautifulSoup(html, features="html.parser")
|
||||||
for img in soup.findAll("img"):
|
for img in soup.findAll("img"):
|
||||||
if img.get("src"):
|
if img.get("src"):
|
||||||
|
|
|
@ -3,34 +3,39 @@ from . import registry
|
||||||
|
|
||||||
@registry.function
|
@registry.function
|
||||||
def submission_layout(
|
def submission_layout(
|
||||||
submission, profile_id, user, editable_problem_ids, completed_problem_ids
|
submission,
|
||||||
|
profile_id,
|
||||||
|
user,
|
||||||
|
editable_problem_ids,
|
||||||
|
completed_problem_ids,
|
||||||
|
tester_problem_ids,
|
||||||
):
|
):
|
||||||
problem_id = submission.problem_id
|
problem_id = submission.problem_id
|
||||||
can_view = False
|
|
||||||
|
|
||||||
if problem_id in editable_problem_ids:
|
if problem_id in editable_problem_ids:
|
||||||
can_view = True
|
return True
|
||||||
|
|
||||||
|
if problem_id in tester_problem_ids:
|
||||||
|
return True
|
||||||
|
|
||||||
if profile_id == submission.user_id:
|
if profile_id == submission.user_id:
|
||||||
can_view = True
|
return True
|
||||||
|
|
||||||
if user.has_perm("judge.change_submission"):
|
if user.has_perm("judge.change_submission"):
|
||||||
can_view = True
|
return True
|
||||||
|
|
||||||
if user.has_perm("judge.view_all_submission"):
|
if user.has_perm("judge.view_all_submission"):
|
||||||
can_view = True
|
return True
|
||||||
|
|
||||||
if submission.problem.is_public and user.has_perm("judge.view_public_submission"):
|
if submission.problem.is_public and user.has_perm("judge.view_public_submission"):
|
||||||
can_view = True
|
return True
|
||||||
|
|
||||||
if submission.problem_id in completed_problem_ids:
|
if hasattr(submission, "contest"):
|
||||||
can_view |= (
|
|
||||||
submission.problem.is_public or profile_id in submission.problem.tester_ids
|
|
||||||
)
|
|
||||||
|
|
||||||
if not can_view and hasattr(submission, "contest"):
|
|
||||||
contest = submission.contest.participation.contest
|
contest = submission.contest.participation.contest
|
||||||
if contest.is_editable_by(user):
|
if contest.is_editable_by(user):
|
||||||
can_view = True
|
return True
|
||||||
|
|
||||||
return can_view
|
if submission.problem_id in completed_problem_ids and submission.problem.is_public:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
|
@ -1,7 +1,19 @@
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect, Http404
|
||||||
from django.urls import Resolver404, resolve, reverse
|
from django.urls import Resolver404, resolve, reverse
|
||||||
from django.utils.http import urlquote
|
from django.utils.http import urlquote
|
||||||
|
from django.contrib.sites.shortcuts import get_current_site
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from judge.models import Organization
|
||||||
|
from judge.utils.views import generic_message
|
||||||
|
|
||||||
|
|
||||||
|
USED_DOMAINS = ["www"]
|
||||||
|
|
||||||
|
|
||||||
class ShortCircuitMiddleware:
|
class ShortCircuitMiddleware:
|
||||||
|
@ -82,3 +94,71 @@ class DarkModeMiddleware(object):
|
||||||
reverse("toggle_darkmode") + "?next=" + urlquote(request.path)
|
reverse("toggle_darkmode") + "?next=" + urlquote(request.path)
|
||||||
)
|
)
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
||||||
|
|
||||||
|
class SubdomainMiddleware(object):
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
request.organization = None
|
||||||
|
if not settings.USE_SUBDOMAIN:
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
domain = request.get_host()
|
||||||
|
site = get_current_site(request).domain
|
||||||
|
subdomain = domain[: len(domain) - len(site)].lower()
|
||||||
|
|
||||||
|
if len(subdomain) <= 1:
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
subdomain = subdomain[:-1]
|
||||||
|
|
||||||
|
if subdomain in USED_DOMAINS:
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
try:
|
||||||
|
organization = Organization.objects.get(slug=subdomain)
|
||||||
|
if request.profile and organization in request.profile.organizations.all():
|
||||||
|
request.organization = organization
|
||||||
|
else:
|
||||||
|
if request.profile:
|
||||||
|
return generic_message(
|
||||||
|
request,
|
||||||
|
_("No permission"),
|
||||||
|
_("You need to join this group first"),
|
||||||
|
status=404,
|
||||||
|
)
|
||||||
|
if not request.GET.get("next", None):
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse("auth_login") + "?next=" + urlquote(request.path)
|
||||||
|
)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return generic_message(
|
||||||
|
request,
|
||||||
|
_("No such group"),
|
||||||
|
_("No such group"),
|
||||||
|
status=404,
|
||||||
|
)
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
|
||||||
|
class SlowRequestMiddleware(object):
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
logger = logging.getLogger("judge.slow_request")
|
||||||
|
start_time = time.time()
|
||||||
|
response = self.get_response(request)
|
||||||
|
response_time = time.time() - start_time
|
||||||
|
if response_time > 9:
|
||||||
|
message = {
|
||||||
|
"message": "Slow request",
|
||||||
|
"url": request.build_absolute_uri(),
|
||||||
|
"response_time": response_time * 1000,
|
||||||
|
"method": request.method,
|
||||||
|
"profile": request.profile,
|
||||||
|
}
|
||||||
|
logger.info(message)
|
||||||
|
return response
|
||||||
|
|
36
judge/migrations/0145_alter_organization_slug.py
Normal file
36
judge/migrations/0145_alter_organization_slug.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Generated by Django 3.2.16 on 2023-01-23 23:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def make_slug_unique(apps, schema_editor):
|
||||||
|
Organization = apps.get_model("judge", "Organization")
|
||||||
|
slugs = Organization.objects.values_list("slug", flat=True)
|
||||||
|
slugs = set([i.lower() for i in slugs])
|
||||||
|
for slug in slugs:
|
||||||
|
orgs = Organization.objects.filter(slug=slug)
|
||||||
|
if len(orgs) > 1:
|
||||||
|
for org in orgs:
|
||||||
|
org.slug += "-" + str(org.id)
|
||||||
|
org.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("judge", "0144_auto_20230103_0523"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(make_slug_unique, migrations.RunPython.noop, atomic=True),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="organization",
|
||||||
|
name="slug",
|
||||||
|
field=models.SlugField(
|
||||||
|
help_text="Organization name shown in URL",
|
||||||
|
max_length=128,
|
||||||
|
unique=True,
|
||||||
|
verbose_name="organization slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
29
judge/migrations/0146_alter_organization_slug.py
Normal file
29
judge/migrations/0146_alter_organization_slug.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 3.2.16 on 2023-01-25 19:12
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("judge", "0145_alter_organization_slug"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="organization",
|
||||||
|
name="slug",
|
||||||
|
field=models.SlugField(
|
||||||
|
help_text="Organization name shown in URL",
|
||||||
|
max_length=128,
|
||||||
|
unique=True,
|
||||||
|
validators=[
|
||||||
|
django.core.validators.RegexValidator(
|
||||||
|
"^[-a-zA-Z0-9]+$", "Only alphanumeric and hyphens"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
verbose_name="organization slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
678
judge/migrations/0147_alter_profile_timezone.py
Normal file
678
judge/migrations/0147_alter_profile_timezone.py
Normal file
|
@ -0,0 +1,678 @@
|
||||||
|
# Generated by Django 3.2.16 on 2023-01-30 11:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("judge", "0146_alter_organization_slug"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="profile",
|
||||||
|
name="timezone",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
(
|
||||||
|
"Africa",
|
||||||
|
[
|
||||||
|
("Africa/Abidjan", "Abidjan"),
|
||||||
|
("Africa/Accra", "Accra"),
|
||||||
|
("Africa/Addis_Ababa", "Addis_Ababa"),
|
||||||
|
("Africa/Algiers", "Algiers"),
|
||||||
|
("Africa/Asmara", "Asmara"),
|
||||||
|
("Africa/Asmera", "Asmera"),
|
||||||
|
("Africa/Bamako", "Bamako"),
|
||||||
|
("Africa/Bangui", "Bangui"),
|
||||||
|
("Africa/Banjul", "Banjul"),
|
||||||
|
("Africa/Bissau", "Bissau"),
|
||||||
|
("Africa/Blantyre", "Blantyre"),
|
||||||
|
("Africa/Brazzaville", "Brazzaville"),
|
||||||
|
("Africa/Bujumbura", "Bujumbura"),
|
||||||
|
("Africa/Cairo", "Cairo"),
|
||||||
|
("Africa/Casablanca", "Casablanca"),
|
||||||
|
("Africa/Ceuta", "Ceuta"),
|
||||||
|
("Africa/Conakry", "Conakry"),
|
||||||
|
("Africa/Dakar", "Dakar"),
|
||||||
|
("Africa/Dar_es_Salaam", "Dar_es_Salaam"),
|
||||||
|
("Africa/Djibouti", "Djibouti"),
|
||||||
|
("Africa/Douala", "Douala"),
|
||||||
|
("Africa/El_Aaiun", "El_Aaiun"),
|
||||||
|
("Africa/Freetown", "Freetown"),
|
||||||
|
("Africa/Gaborone", "Gaborone"),
|
||||||
|
("Africa/Harare", "Harare"),
|
||||||
|
("Africa/Johannesburg", "Johannesburg"),
|
||||||
|
("Africa/Juba", "Juba"),
|
||||||
|
("Africa/Kampala", "Kampala"),
|
||||||
|
("Africa/Khartoum", "Khartoum"),
|
||||||
|
("Africa/Kigali", "Kigali"),
|
||||||
|
("Africa/Kinshasa", "Kinshasa"),
|
||||||
|
("Africa/Lagos", "Lagos"),
|
||||||
|
("Africa/Libreville", "Libreville"),
|
||||||
|
("Africa/Lome", "Lome"),
|
||||||
|
("Africa/Luanda", "Luanda"),
|
||||||
|
("Africa/Lubumbashi", "Lubumbashi"),
|
||||||
|
("Africa/Lusaka", "Lusaka"),
|
||||||
|
("Africa/Malabo", "Malabo"),
|
||||||
|
("Africa/Maputo", "Maputo"),
|
||||||
|
("Africa/Maseru", "Maseru"),
|
||||||
|
("Africa/Mbabane", "Mbabane"),
|
||||||
|
("Africa/Mogadishu", "Mogadishu"),
|
||||||
|
("Africa/Monrovia", "Monrovia"),
|
||||||
|
("Africa/Nairobi", "Nairobi"),
|
||||||
|
("Africa/Ndjamena", "Ndjamena"),
|
||||||
|
("Africa/Niamey", "Niamey"),
|
||||||
|
("Africa/Nouakchott", "Nouakchott"),
|
||||||
|
("Africa/Ouagadougou", "Ouagadougou"),
|
||||||
|
("Africa/Porto-Novo", "Porto-Novo"),
|
||||||
|
("Africa/Sao_Tome", "Sao_Tome"),
|
||||||
|
("Africa/Timbuktu", "Timbuktu"),
|
||||||
|
("Africa/Tripoli", "Tripoli"),
|
||||||
|
("Africa/Tunis", "Tunis"),
|
||||||
|
("Africa/Windhoek", "Windhoek"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"America",
|
||||||
|
[
|
||||||
|
("America/Adak", "Adak"),
|
||||||
|
("America/Anchorage", "Anchorage"),
|
||||||
|
("America/Anguilla", "Anguilla"),
|
||||||
|
("America/Antigua", "Antigua"),
|
||||||
|
("America/Araguaina", "Araguaina"),
|
||||||
|
(
|
||||||
|
"America/Argentina/Buenos_Aires",
|
||||||
|
"Argentina/Buenos_Aires",
|
||||||
|
),
|
||||||
|
("America/Argentina/Catamarca", "Argentina/Catamarca"),
|
||||||
|
(
|
||||||
|
"America/Argentina/ComodRivadavia",
|
||||||
|
"Argentina/ComodRivadavia",
|
||||||
|
),
|
||||||
|
("America/Argentina/Cordoba", "Argentina/Cordoba"),
|
||||||
|
("America/Argentina/Jujuy", "Argentina/Jujuy"),
|
||||||
|
("America/Argentina/La_Rioja", "Argentina/La_Rioja"),
|
||||||
|
("America/Argentina/Mendoza", "Argentina/Mendoza"),
|
||||||
|
(
|
||||||
|
"America/Argentina/Rio_Gallegos",
|
||||||
|
"Argentina/Rio_Gallegos",
|
||||||
|
),
|
||||||
|
("America/Argentina/Salta", "Argentina/Salta"),
|
||||||
|
("America/Argentina/San_Juan", "Argentina/San_Juan"),
|
||||||
|
("America/Argentina/San_Luis", "Argentina/San_Luis"),
|
||||||
|
("America/Argentina/Tucuman", "Argentina/Tucuman"),
|
||||||
|
("America/Argentina/Ushuaia", "Argentina/Ushuaia"),
|
||||||
|
("America/Aruba", "Aruba"),
|
||||||
|
("America/Asuncion", "Asuncion"),
|
||||||
|
("America/Atikokan", "Atikokan"),
|
||||||
|
("America/Atka", "Atka"),
|
||||||
|
("America/Bahia", "Bahia"),
|
||||||
|
("America/Bahia_Banderas", "Bahia_Banderas"),
|
||||||
|
("America/Barbados", "Barbados"),
|
||||||
|
("America/Belem", "Belem"),
|
||||||
|
("America/Belize", "Belize"),
|
||||||
|
("America/Blanc-Sablon", "Blanc-Sablon"),
|
||||||
|
("America/Boa_Vista", "Boa_Vista"),
|
||||||
|
("America/Bogota", "Bogota"),
|
||||||
|
("America/Boise", "Boise"),
|
||||||
|
("America/Buenos_Aires", "Buenos_Aires"),
|
||||||
|
("America/Cambridge_Bay", "Cambridge_Bay"),
|
||||||
|
("America/Campo_Grande", "Campo_Grande"),
|
||||||
|
("America/Cancun", "Cancun"),
|
||||||
|
("America/Caracas", "Caracas"),
|
||||||
|
("America/Catamarca", "Catamarca"),
|
||||||
|
("America/Cayenne", "Cayenne"),
|
||||||
|
("America/Cayman", "Cayman"),
|
||||||
|
("America/Chicago", "Chicago"),
|
||||||
|
("America/Chihuahua", "Chihuahua"),
|
||||||
|
("America/Ciudad_Juarez", "Ciudad_Juarez"),
|
||||||
|
("America/Coral_Harbour", "Coral_Harbour"),
|
||||||
|
("America/Cordoba", "Cordoba"),
|
||||||
|
("America/Costa_Rica", "Costa_Rica"),
|
||||||
|
("America/Creston", "Creston"),
|
||||||
|
("America/Cuiaba", "Cuiaba"),
|
||||||
|
("America/Curacao", "Curacao"),
|
||||||
|
("America/Danmarkshavn", "Danmarkshavn"),
|
||||||
|
("America/Dawson", "Dawson"),
|
||||||
|
("America/Dawson_Creek", "Dawson_Creek"),
|
||||||
|
("America/Denver", "Denver"),
|
||||||
|
("America/Detroit", "Detroit"),
|
||||||
|
("America/Dominica", "Dominica"),
|
||||||
|
("America/Edmonton", "Edmonton"),
|
||||||
|
("America/Eirunepe", "Eirunepe"),
|
||||||
|
("America/El_Salvador", "El_Salvador"),
|
||||||
|
("America/Ensenada", "Ensenada"),
|
||||||
|
("America/Fort_Nelson", "Fort_Nelson"),
|
||||||
|
("America/Fort_Wayne", "Fort_Wayne"),
|
||||||
|
("America/Fortaleza", "Fortaleza"),
|
||||||
|
("America/Glace_Bay", "Glace_Bay"),
|
||||||
|
("America/Godthab", "Godthab"),
|
||||||
|
("America/Goose_Bay", "Goose_Bay"),
|
||||||
|
("America/Grand_Turk", "Grand_Turk"),
|
||||||
|
("America/Grenada", "Grenada"),
|
||||||
|
("America/Guadeloupe", "Guadeloupe"),
|
||||||
|
("America/Guatemala", "Guatemala"),
|
||||||
|
("America/Guayaquil", "Guayaquil"),
|
||||||
|
("America/Guyana", "Guyana"),
|
||||||
|
("America/Halifax", "Halifax"),
|
||||||
|
("America/Havana", "Havana"),
|
||||||
|
("America/Hermosillo", "Hermosillo"),
|
||||||
|
("America/Indiana/Indianapolis", "Indiana/Indianapolis"),
|
||||||
|
("America/Indiana/Knox", "Indiana/Knox"),
|
||||||
|
("America/Indiana/Marengo", "Indiana/Marengo"),
|
||||||
|
("America/Indiana/Petersburg", "Indiana/Petersburg"),
|
||||||
|
("America/Indiana/Tell_City", "Indiana/Tell_City"),
|
||||||
|
("America/Indiana/Vevay", "Indiana/Vevay"),
|
||||||
|
("America/Indiana/Vincennes", "Indiana/Vincennes"),
|
||||||
|
("America/Indiana/Winamac", "Indiana/Winamac"),
|
||||||
|
("America/Indianapolis", "Indianapolis"),
|
||||||
|
("America/Inuvik", "Inuvik"),
|
||||||
|
("America/Iqaluit", "Iqaluit"),
|
||||||
|
("America/Jamaica", "Jamaica"),
|
||||||
|
("America/Jujuy", "Jujuy"),
|
||||||
|
("America/Juneau", "Juneau"),
|
||||||
|
("America/Kentucky/Louisville", "Kentucky/Louisville"),
|
||||||
|
("America/Kentucky/Monticello", "Kentucky/Monticello"),
|
||||||
|
("America/Knox_IN", "Knox_IN"),
|
||||||
|
("America/Kralendijk", "Kralendijk"),
|
||||||
|
("America/La_Paz", "La_Paz"),
|
||||||
|
("America/Lima", "Lima"),
|
||||||
|
("America/Los_Angeles", "Los_Angeles"),
|
||||||
|
("America/Louisville", "Louisville"),
|
||||||
|
("America/Lower_Princes", "Lower_Princes"),
|
||||||
|
("America/Maceio", "Maceio"),
|
||||||
|
("America/Managua", "Managua"),
|
||||||
|
("America/Manaus", "Manaus"),
|
||||||
|
("America/Marigot", "Marigot"),
|
||||||
|
("America/Martinique", "Martinique"),
|
||||||
|
("America/Matamoros", "Matamoros"),
|
||||||
|
("America/Mazatlan", "Mazatlan"),
|
||||||
|
("America/Mendoza", "Mendoza"),
|
||||||
|
("America/Menominee", "Menominee"),
|
||||||
|
("America/Merida", "Merida"),
|
||||||
|
("America/Metlakatla", "Metlakatla"),
|
||||||
|
("America/Mexico_City", "Mexico_City"),
|
||||||
|
("America/Miquelon", "Miquelon"),
|
||||||
|
("America/Moncton", "Moncton"),
|
||||||
|
("America/Monterrey", "Monterrey"),
|
||||||
|
("America/Montevideo", "Montevideo"),
|
||||||
|
("America/Montreal", "Montreal"),
|
||||||
|
("America/Montserrat", "Montserrat"),
|
||||||
|
("America/Nassau", "Nassau"),
|
||||||
|
("America/New_York", "New_York"),
|
||||||
|
("America/Nipigon", "Nipigon"),
|
||||||
|
("America/Nome", "Nome"),
|
||||||
|
("America/Noronha", "Noronha"),
|
||||||
|
("America/North_Dakota/Beulah", "North_Dakota/Beulah"),
|
||||||
|
("America/North_Dakota/Center", "North_Dakota/Center"),
|
||||||
|
(
|
||||||
|
"America/North_Dakota/New_Salem",
|
||||||
|
"North_Dakota/New_Salem",
|
||||||
|
),
|
||||||
|
("America/Nuuk", "Nuuk"),
|
||||||
|
("America/Ojinaga", "Ojinaga"),
|
||||||
|
("America/Panama", "Panama"),
|
||||||
|
("America/Pangnirtung", "Pangnirtung"),
|
||||||
|
("America/Paramaribo", "Paramaribo"),
|
||||||
|
("America/Phoenix", "Phoenix"),
|
||||||
|
("America/Port-au-Prince", "Port-au-Prince"),
|
||||||
|
("America/Port_of_Spain", "Port_of_Spain"),
|
||||||
|
("America/Porto_Acre", "Porto_Acre"),
|
||||||
|
("America/Porto_Velho", "Porto_Velho"),
|
||||||
|
("America/Puerto_Rico", "Puerto_Rico"),
|
||||||
|
("America/Punta_Arenas", "Punta_Arenas"),
|
||||||
|
("America/Rainy_River", "Rainy_River"),
|
||||||
|
("America/Rankin_Inlet", "Rankin_Inlet"),
|
||||||
|
("America/Recife", "Recife"),
|
||||||
|
("America/Regina", "Regina"),
|
||||||
|
("America/Resolute", "Resolute"),
|
||||||
|
("America/Rio_Branco", "Rio_Branco"),
|
||||||
|
("America/Rosario", "Rosario"),
|
||||||
|
("America/Santa_Isabel", "Santa_Isabel"),
|
||||||
|
("America/Santarem", "Santarem"),
|
||||||
|
("America/Santiago", "Santiago"),
|
||||||
|
("America/Santo_Domingo", "Santo_Domingo"),
|
||||||
|
("America/Sao_Paulo", "Sao_Paulo"),
|
||||||
|
("America/Scoresbysund", "Scoresbysund"),
|
||||||
|
("America/Shiprock", "Shiprock"),
|
||||||
|
("America/Sitka", "Sitka"),
|
||||||
|
("America/St_Barthelemy", "St_Barthelemy"),
|
||||||
|
("America/St_Johns", "St_Johns"),
|
||||||
|
("America/St_Kitts", "St_Kitts"),
|
||||||
|
("America/St_Lucia", "St_Lucia"),
|
||||||
|
("America/St_Thomas", "St_Thomas"),
|
||||||
|
("America/St_Vincent", "St_Vincent"),
|
||||||
|
("America/Swift_Current", "Swift_Current"),
|
||||||
|
("America/Tegucigalpa", "Tegucigalpa"),
|
||||||
|
("America/Thule", "Thule"),
|
||||||
|
("America/Thunder_Bay", "Thunder_Bay"),
|
||||||
|
("America/Tijuana", "Tijuana"),
|
||||||
|
("America/Toronto", "Toronto"),
|
||||||
|
("America/Tortola", "Tortola"),
|
||||||
|
("America/Vancouver", "Vancouver"),
|
||||||
|
("America/Virgin", "Virgin"),
|
||||||
|
("America/Whitehorse", "Whitehorse"),
|
||||||
|
("America/Winnipeg", "Winnipeg"),
|
||||||
|
("America/Yakutat", "Yakutat"),
|
||||||
|
("America/Yellowknife", "Yellowknife"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Antarctica",
|
||||||
|
[
|
||||||
|
("Antarctica/Casey", "Casey"),
|
||||||
|
("Antarctica/Davis", "Davis"),
|
||||||
|
("Antarctica/DumontDUrville", "DumontDUrville"),
|
||||||
|
("Antarctica/Macquarie", "Macquarie"),
|
||||||
|
("Antarctica/Mawson", "Mawson"),
|
||||||
|
("Antarctica/McMurdo", "McMurdo"),
|
||||||
|
("Antarctica/Palmer", "Palmer"),
|
||||||
|
("Antarctica/Rothera", "Rothera"),
|
||||||
|
("Antarctica/South_Pole", "South_Pole"),
|
||||||
|
("Antarctica/Syowa", "Syowa"),
|
||||||
|
("Antarctica/Troll", "Troll"),
|
||||||
|
("Antarctica/Vostok", "Vostok"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
("Arctic", [("Arctic/Longyearbyen", "Longyearbyen")]),
|
||||||
|
(
|
||||||
|
"Asia",
|
||||||
|
[
|
||||||
|
("Asia/Aden", "Aden"),
|
||||||
|
("Asia/Almaty", "Almaty"),
|
||||||
|
("Asia/Amman", "Amman"),
|
||||||
|
("Asia/Anadyr", "Anadyr"),
|
||||||
|
("Asia/Aqtau", "Aqtau"),
|
||||||
|
("Asia/Aqtobe", "Aqtobe"),
|
||||||
|
("Asia/Ashgabat", "Ashgabat"),
|
||||||
|
("Asia/Ashkhabad", "Ashkhabad"),
|
||||||
|
("Asia/Atyrau", "Atyrau"),
|
||||||
|
("Asia/Baghdad", "Baghdad"),
|
||||||
|
("Asia/Bahrain", "Bahrain"),
|
||||||
|
("Asia/Baku", "Baku"),
|
||||||
|
("Asia/Bangkok", "Bangkok"),
|
||||||
|
("Asia/Barnaul", "Barnaul"),
|
||||||
|
("Asia/Beirut", "Beirut"),
|
||||||
|
("Asia/Bishkek", "Bishkek"),
|
||||||
|
("Asia/Brunei", "Brunei"),
|
||||||
|
("Asia/Calcutta", "Calcutta"),
|
||||||
|
("Asia/Chita", "Chita"),
|
||||||
|
("Asia/Choibalsan", "Choibalsan"),
|
||||||
|
("Asia/Chongqing", "Chongqing"),
|
||||||
|
("Asia/Chungking", "Chungking"),
|
||||||
|
("Asia/Colombo", "Colombo"),
|
||||||
|
("Asia/Dacca", "Dacca"),
|
||||||
|
("Asia/Damascus", "Damascus"),
|
||||||
|
("Asia/Dhaka", "Dhaka"),
|
||||||
|
("Asia/Dili", "Dili"),
|
||||||
|
("Asia/Dubai", "Dubai"),
|
||||||
|
("Asia/Dushanbe", "Dushanbe"),
|
||||||
|
("Asia/Famagusta", "Famagusta"),
|
||||||
|
("Asia/Gaza", "Gaza"),
|
||||||
|
("Asia/Harbin", "Harbin"),
|
||||||
|
("Asia/Hebron", "Hebron"),
|
||||||
|
("Asia/Ho_Chi_Minh", "Ho_Chi_Minh"),
|
||||||
|
("Asia/Hong_Kong", "Hong_Kong"),
|
||||||
|
("Asia/Hovd", "Hovd"),
|
||||||
|
("Asia/Irkutsk", "Irkutsk"),
|
||||||
|
("Asia/Istanbul", "Istanbul"),
|
||||||
|
("Asia/Jakarta", "Jakarta"),
|
||||||
|
("Asia/Jayapura", "Jayapura"),
|
||||||
|
("Asia/Jerusalem", "Jerusalem"),
|
||||||
|
("Asia/Kabul", "Kabul"),
|
||||||
|
("Asia/Kamchatka", "Kamchatka"),
|
||||||
|
("Asia/Karachi", "Karachi"),
|
||||||
|
("Asia/Kashgar", "Kashgar"),
|
||||||
|
("Asia/Kathmandu", "Kathmandu"),
|
||||||
|
("Asia/Katmandu", "Katmandu"),
|
||||||
|
("Asia/Khandyga", "Khandyga"),
|
||||||
|
("Asia/Kolkata", "Kolkata"),
|
||||||
|
("Asia/Krasnoyarsk", "Krasnoyarsk"),
|
||||||
|
("Asia/Kuala_Lumpur", "Kuala_Lumpur"),
|
||||||
|
("Asia/Kuching", "Kuching"),
|
||||||
|
("Asia/Kuwait", "Kuwait"),
|
||||||
|
("Asia/Macao", "Macao"),
|
||||||
|
("Asia/Macau", "Macau"),
|
||||||
|
("Asia/Magadan", "Magadan"),
|
||||||
|
("Asia/Makassar", "Makassar"),
|
||||||
|
("Asia/Manila", "Manila"),
|
||||||
|
("Asia/Muscat", "Muscat"),
|
||||||
|
("Asia/Nicosia", "Nicosia"),
|
||||||
|
("Asia/Novokuznetsk", "Novokuznetsk"),
|
||||||
|
("Asia/Novosibirsk", "Novosibirsk"),
|
||||||
|
("Asia/Omsk", "Omsk"),
|
||||||
|
("Asia/Oral", "Oral"),
|
||||||
|
("Asia/Phnom_Penh", "Phnom_Penh"),
|
||||||
|
("Asia/Pontianak", "Pontianak"),
|
||||||
|
("Asia/Pyongyang", "Pyongyang"),
|
||||||
|
("Asia/Qatar", "Qatar"),
|
||||||
|
("Asia/Qostanay", "Qostanay"),
|
||||||
|
("Asia/Qyzylorda", "Qyzylorda"),
|
||||||
|
("Asia/Rangoon", "Rangoon"),
|
||||||
|
("Asia/Riyadh", "Riyadh"),
|
||||||
|
("Asia/Saigon", "Saigon"),
|
||||||
|
("Asia/Sakhalin", "Sakhalin"),
|
||||||
|
("Asia/Samarkand", "Samarkand"),
|
||||||
|
("Asia/Seoul", "Seoul"),
|
||||||
|
("Asia/Shanghai", "Shanghai"),
|
||||||
|
("Asia/Singapore", "Singapore"),
|
||||||
|
("Asia/Srednekolymsk", "Srednekolymsk"),
|
||||||
|
("Asia/Taipei", "Taipei"),
|
||||||
|
("Asia/Tashkent", "Tashkent"),
|
||||||
|
("Asia/Tbilisi", "Tbilisi"),
|
||||||
|
("Asia/Tehran", "Tehran"),
|
||||||
|
("Asia/Tel_Aviv", "Tel_Aviv"),
|
||||||
|
("Asia/Thimbu", "Thimbu"),
|
||||||
|
("Asia/Thimphu", "Thimphu"),
|
||||||
|
("Asia/Tokyo", "Tokyo"),
|
||||||
|
("Asia/Tomsk", "Tomsk"),
|
||||||
|
("Asia/Ujung_Pandang", "Ujung_Pandang"),
|
||||||
|
("Asia/Ulaanbaatar", "Ulaanbaatar"),
|
||||||
|
("Asia/Ulan_Bator", "Ulan_Bator"),
|
||||||
|
("Asia/Urumqi", "Urumqi"),
|
||||||
|
("Asia/Ust-Nera", "Ust-Nera"),
|
||||||
|
("Asia/Vientiane", "Vientiane"),
|
||||||
|
("Asia/Vladivostok", "Vladivostok"),
|
||||||
|
("Asia/Yakutsk", "Yakutsk"),
|
||||||
|
("Asia/Yangon", "Yangon"),
|
||||||
|
("Asia/Yekaterinburg", "Yekaterinburg"),
|
||||||
|
("Asia/Yerevan", "Yerevan"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Atlantic",
|
||||||
|
[
|
||||||
|
("Atlantic/Azores", "Azores"),
|
||||||
|
("Atlantic/Bermuda", "Bermuda"),
|
||||||
|
("Atlantic/Canary", "Canary"),
|
||||||
|
("Atlantic/Cape_Verde", "Cape_Verde"),
|
||||||
|
("Atlantic/Faeroe", "Faeroe"),
|
||||||
|
("Atlantic/Faroe", "Faroe"),
|
||||||
|
("Atlantic/Jan_Mayen", "Jan_Mayen"),
|
||||||
|
("Atlantic/Madeira", "Madeira"),
|
||||||
|
("Atlantic/Reykjavik", "Reykjavik"),
|
||||||
|
("Atlantic/South_Georgia", "South_Georgia"),
|
||||||
|
("Atlantic/St_Helena", "St_Helena"),
|
||||||
|
("Atlantic/Stanley", "Stanley"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Australia",
|
||||||
|
[
|
||||||
|
("Australia/ACT", "ACT"),
|
||||||
|
("Australia/Adelaide", "Adelaide"),
|
||||||
|
("Australia/Brisbane", "Brisbane"),
|
||||||
|
("Australia/Broken_Hill", "Broken_Hill"),
|
||||||
|
("Australia/Canberra", "Canberra"),
|
||||||
|
("Australia/Currie", "Currie"),
|
||||||
|
("Australia/Darwin", "Darwin"),
|
||||||
|
("Australia/Eucla", "Eucla"),
|
||||||
|
("Australia/Hobart", "Hobart"),
|
||||||
|
("Australia/LHI", "LHI"),
|
||||||
|
("Australia/Lindeman", "Lindeman"),
|
||||||
|
("Australia/Lord_Howe", "Lord_Howe"),
|
||||||
|
("Australia/Melbourne", "Melbourne"),
|
||||||
|
("Australia/NSW", "NSW"),
|
||||||
|
("Australia/North", "North"),
|
||||||
|
("Australia/Perth", "Perth"),
|
||||||
|
("Australia/Queensland", "Queensland"),
|
||||||
|
("Australia/South", "South"),
|
||||||
|
("Australia/Sydney", "Sydney"),
|
||||||
|
("Australia/Tasmania", "Tasmania"),
|
||||||
|
("Australia/Victoria", "Victoria"),
|
||||||
|
("Australia/West", "West"),
|
||||||
|
("Australia/Yancowinna", "Yancowinna"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Brazil",
|
||||||
|
[
|
||||||
|
("Brazil/Acre", "Acre"),
|
||||||
|
("Brazil/DeNoronha", "DeNoronha"),
|
||||||
|
("Brazil/East", "East"),
|
||||||
|
("Brazil/West", "West"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Canada",
|
||||||
|
[
|
||||||
|
("Canada/Atlantic", "Atlantic"),
|
||||||
|
("Canada/Central", "Central"),
|
||||||
|
("Canada/Eastern", "Eastern"),
|
||||||
|
("Canada/Mountain", "Mountain"),
|
||||||
|
("Canada/Newfoundland", "Newfoundland"),
|
||||||
|
("Canada/Pacific", "Pacific"),
|
||||||
|
("Canada/Saskatchewan", "Saskatchewan"),
|
||||||
|
("Canada/Yukon", "Yukon"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Chile",
|
||||||
|
[
|
||||||
|
("Chile/Continental", "Continental"),
|
||||||
|
("Chile/EasterIsland", "EasterIsland"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Etc",
|
||||||
|
[
|
||||||
|
("Etc/Greenwich", "Greenwich"),
|
||||||
|
("Etc/UCT", "UCT"),
|
||||||
|
("Etc/UTC", "UTC"),
|
||||||
|
("Etc/Universal", "Universal"),
|
||||||
|
("Etc/Zulu", "Zulu"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Europe",
|
||||||
|
[
|
||||||
|
("Europe/Amsterdam", "Amsterdam"),
|
||||||
|
("Europe/Andorra", "Andorra"),
|
||||||
|
("Europe/Astrakhan", "Astrakhan"),
|
||||||
|
("Europe/Athens", "Athens"),
|
||||||
|
("Europe/Belfast", "Belfast"),
|
||||||
|
("Europe/Belgrade", "Belgrade"),
|
||||||
|
("Europe/Berlin", "Berlin"),
|
||||||
|
("Europe/Bratislava", "Bratislava"),
|
||||||
|
("Europe/Brussels", "Brussels"),
|
||||||
|
("Europe/Bucharest", "Bucharest"),
|
||||||
|
("Europe/Budapest", "Budapest"),
|
||||||
|
("Europe/Busingen", "Busingen"),
|
||||||
|
("Europe/Chisinau", "Chisinau"),
|
||||||
|
("Europe/Copenhagen", "Copenhagen"),
|
||||||
|
("Europe/Dublin", "Dublin"),
|
||||||
|
("Europe/Gibraltar", "Gibraltar"),
|
||||||
|
("Europe/Guernsey", "Guernsey"),
|
||||||
|
("Europe/Helsinki", "Helsinki"),
|
||||||
|
("Europe/Isle_of_Man", "Isle_of_Man"),
|
||||||
|
("Europe/Istanbul", "Istanbul"),
|
||||||
|
("Europe/Jersey", "Jersey"),
|
||||||
|
("Europe/Kaliningrad", "Kaliningrad"),
|
||||||
|
("Europe/Kiev", "Kiev"),
|
||||||
|
("Europe/Kirov", "Kirov"),
|
||||||
|
("Europe/Kyiv", "Kyiv"),
|
||||||
|
("Europe/Lisbon", "Lisbon"),
|
||||||
|
("Europe/Ljubljana", "Ljubljana"),
|
||||||
|
("Europe/London", "London"),
|
||||||
|
("Europe/Luxembourg", "Luxembourg"),
|
||||||
|
("Europe/Madrid", "Madrid"),
|
||||||
|
("Europe/Malta", "Malta"),
|
||||||
|
("Europe/Mariehamn", "Mariehamn"),
|
||||||
|
("Europe/Minsk", "Minsk"),
|
||||||
|
("Europe/Monaco", "Monaco"),
|
||||||
|
("Europe/Moscow", "Moscow"),
|
||||||
|
("Europe/Nicosia", "Nicosia"),
|
||||||
|
("Europe/Oslo", "Oslo"),
|
||||||
|
("Europe/Paris", "Paris"),
|
||||||
|
("Europe/Podgorica", "Podgorica"),
|
||||||
|
("Europe/Prague", "Prague"),
|
||||||
|
("Europe/Riga", "Riga"),
|
||||||
|
("Europe/Rome", "Rome"),
|
||||||
|
("Europe/Samara", "Samara"),
|
||||||
|
("Europe/San_Marino", "San_Marino"),
|
||||||
|
("Europe/Sarajevo", "Sarajevo"),
|
||||||
|
("Europe/Saratov", "Saratov"),
|
||||||
|
("Europe/Simferopol", "Simferopol"),
|
||||||
|
("Europe/Skopje", "Skopje"),
|
||||||
|
("Europe/Sofia", "Sofia"),
|
||||||
|
("Europe/Stockholm", "Stockholm"),
|
||||||
|
("Europe/Tallinn", "Tallinn"),
|
||||||
|
("Europe/Tirane", "Tirane"),
|
||||||
|
("Europe/Tiraspol", "Tiraspol"),
|
||||||
|
("Europe/Ulyanovsk", "Ulyanovsk"),
|
||||||
|
("Europe/Uzhgorod", "Uzhgorod"),
|
||||||
|
("Europe/Vaduz", "Vaduz"),
|
||||||
|
("Europe/Vatican", "Vatican"),
|
||||||
|
("Europe/Vienna", "Vienna"),
|
||||||
|
("Europe/Vilnius", "Vilnius"),
|
||||||
|
("Europe/Volgograd", "Volgograd"),
|
||||||
|
("Europe/Warsaw", "Warsaw"),
|
||||||
|
("Europe/Zagreb", "Zagreb"),
|
||||||
|
("Europe/Zaporozhye", "Zaporozhye"),
|
||||||
|
("Europe/Zurich", "Zurich"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Indian",
|
||||||
|
[
|
||||||
|
("Indian/Antananarivo", "Antananarivo"),
|
||||||
|
("Indian/Chagos", "Chagos"),
|
||||||
|
("Indian/Christmas", "Christmas"),
|
||||||
|
("Indian/Cocos", "Cocos"),
|
||||||
|
("Indian/Comoro", "Comoro"),
|
||||||
|
("Indian/Kerguelen", "Kerguelen"),
|
||||||
|
("Indian/Mahe", "Mahe"),
|
||||||
|
("Indian/Maldives", "Maldives"),
|
||||||
|
("Indian/Mauritius", "Mauritius"),
|
||||||
|
("Indian/Mayotte", "Mayotte"),
|
||||||
|
("Indian/Reunion", "Reunion"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Mexico",
|
||||||
|
[
|
||||||
|
("Mexico/BajaNorte", "BajaNorte"),
|
||||||
|
("Mexico/BajaSur", "BajaSur"),
|
||||||
|
("Mexico/General", "General"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Other",
|
||||||
|
[
|
||||||
|
("CET", "CET"),
|
||||||
|
("CST6CDT", "CST6CDT"),
|
||||||
|
("Cuba", "Cuba"),
|
||||||
|
("EET", "EET"),
|
||||||
|
("EST", "EST"),
|
||||||
|
("EST5EDT", "EST5EDT"),
|
||||||
|
("Egypt", "Egypt"),
|
||||||
|
("Eire", "Eire"),
|
||||||
|
("GB", "GB"),
|
||||||
|
("GB-Eire", "GB-Eire"),
|
||||||
|
("Greenwich", "Greenwich"),
|
||||||
|
("HST", "HST"),
|
||||||
|
("Hongkong", "Hongkong"),
|
||||||
|
("Iceland", "Iceland"),
|
||||||
|
("Iran", "Iran"),
|
||||||
|
("Israel", "Israel"),
|
||||||
|
("Jamaica", "Jamaica"),
|
||||||
|
("Japan", "Japan"),
|
||||||
|
("Kwajalein", "Kwajalein"),
|
||||||
|
("Libya", "Libya"),
|
||||||
|
("MET", "MET"),
|
||||||
|
("MST", "MST"),
|
||||||
|
("MST7MDT", "MST7MDT"),
|
||||||
|
("NZ", "NZ"),
|
||||||
|
("NZ-CHAT", "NZ-CHAT"),
|
||||||
|
("Navajo", "Navajo"),
|
||||||
|
("PRC", "PRC"),
|
||||||
|
("PST8PDT", "PST8PDT"),
|
||||||
|
("Poland", "Poland"),
|
||||||
|
("Portugal", "Portugal"),
|
||||||
|
("ROC", "ROC"),
|
||||||
|
("ROK", "ROK"),
|
||||||
|
("Singapore", "Singapore"),
|
||||||
|
("Turkey", "Turkey"),
|
||||||
|
("UCT", "UCT"),
|
||||||
|
("UTC", "UTC"),
|
||||||
|
("Universal", "Universal"),
|
||||||
|
("W-SU", "W-SU"),
|
||||||
|
("WET", "WET"),
|
||||||
|
("Zulu", "Zulu"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Pacific",
|
||||||
|
[
|
||||||
|
("Pacific/Apia", "Apia"),
|
||||||
|
("Pacific/Auckland", "Auckland"),
|
||||||
|
("Pacific/Bougainville", "Bougainville"),
|
||||||
|
("Pacific/Chatham", "Chatham"),
|
||||||
|
("Pacific/Chuuk", "Chuuk"),
|
||||||
|
("Pacific/Easter", "Easter"),
|
||||||
|
("Pacific/Efate", "Efate"),
|
||||||
|
("Pacific/Enderbury", "Enderbury"),
|
||||||
|
("Pacific/Fakaofo", "Fakaofo"),
|
||||||
|
("Pacific/Fiji", "Fiji"),
|
||||||
|
("Pacific/Funafuti", "Funafuti"),
|
||||||
|
("Pacific/Galapagos", "Galapagos"),
|
||||||
|
("Pacific/Gambier", "Gambier"),
|
||||||
|
("Pacific/Guadalcanal", "Guadalcanal"),
|
||||||
|
("Pacific/Guam", "Guam"),
|
||||||
|
("Pacific/Honolulu", "Honolulu"),
|
||||||
|
("Pacific/Johnston", "Johnston"),
|
||||||
|
("Pacific/Kanton", "Kanton"),
|
||||||
|
("Pacific/Kiritimati", "Kiritimati"),
|
||||||
|
("Pacific/Kosrae", "Kosrae"),
|
||||||
|
("Pacific/Kwajalein", "Kwajalein"),
|
||||||
|
("Pacific/Majuro", "Majuro"),
|
||||||
|
("Pacific/Marquesas", "Marquesas"),
|
||||||
|
("Pacific/Midway", "Midway"),
|
||||||
|
("Pacific/Nauru", "Nauru"),
|
||||||
|
("Pacific/Niue", "Niue"),
|
||||||
|
("Pacific/Norfolk", "Norfolk"),
|
||||||
|
("Pacific/Noumea", "Noumea"),
|
||||||
|
("Pacific/Pago_Pago", "Pago_Pago"),
|
||||||
|
("Pacific/Palau", "Palau"),
|
||||||
|
("Pacific/Pitcairn", "Pitcairn"),
|
||||||
|
("Pacific/Pohnpei", "Pohnpei"),
|
||||||
|
("Pacific/Ponape", "Ponape"),
|
||||||
|
("Pacific/Port_Moresby", "Port_Moresby"),
|
||||||
|
("Pacific/Rarotonga", "Rarotonga"),
|
||||||
|
("Pacific/Saipan", "Saipan"),
|
||||||
|
("Pacific/Samoa", "Samoa"),
|
||||||
|
("Pacific/Tahiti", "Tahiti"),
|
||||||
|
("Pacific/Tarawa", "Tarawa"),
|
||||||
|
("Pacific/Tongatapu", "Tongatapu"),
|
||||||
|
("Pacific/Truk", "Truk"),
|
||||||
|
("Pacific/Wake", "Wake"),
|
||||||
|
("Pacific/Wallis", "Wallis"),
|
||||||
|
("Pacific/Yap", "Yap"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"US",
|
||||||
|
[
|
||||||
|
("US/Alaska", "Alaska"),
|
||||||
|
("US/Aleutian", "Aleutian"),
|
||||||
|
("US/Arizona", "Arizona"),
|
||||||
|
("US/Central", "Central"),
|
||||||
|
("US/East-Indiana", "East-Indiana"),
|
||||||
|
("US/Eastern", "Eastern"),
|
||||||
|
("US/Hawaii", "Hawaii"),
|
||||||
|
("US/Indiana-Starke", "Indiana-Starke"),
|
||||||
|
("US/Michigan", "Michigan"),
|
||||||
|
("US/Mountain", "Mountain"),
|
||||||
|
("US/Pacific", "Pacific"),
|
||||||
|
("US/Samoa", "Samoa"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
default="Asia/Ho_Chi_Minh",
|
||||||
|
max_length=50,
|
||||||
|
verbose_name="location",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,187 @@
|
||||||
|
# Generated by Django 3.2.16 on 2023-01-31 15:49
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("judge", "0147_alter_profile_timezone"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Course",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=128, verbose_name="course name")),
|
||||||
|
("about", models.TextField(verbose_name="organization description")),
|
||||||
|
("ending_time", models.DateTimeField(verbose_name="ending time")),
|
||||||
|
(
|
||||||
|
"is_public",
|
||||||
|
models.BooleanField(default=False, verbose_name="publicly visible"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug",
|
||||||
|
models.SlugField(
|
||||||
|
help_text="Course name shown in URL",
|
||||||
|
max_length=128,
|
||||||
|
unique=True,
|
||||||
|
validators=[
|
||||||
|
django.core.validators.RegexValidator(
|
||||||
|
"^[-a-zA-Z0-9]+$", "Only alphanumeric and hyphens"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
verbose_name="course slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_open",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False, verbose_name="public registration"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"image_url",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
default="",
|
||||||
|
max_length=150,
|
||||||
|
verbose_name="course image",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"organizations",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="If private, only these organizations may see the course",
|
||||||
|
to="judge.Organization",
|
||||||
|
verbose_name="organizations",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="CourseRole",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"role",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("ST", "Student"),
|
||||||
|
("AS", "Assistant"),
|
||||||
|
("TE", "Teacher"),
|
||||||
|
],
|
||||||
|
default="ST",
|
||||||
|
max_length=2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"course",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="judge.course",
|
||||||
|
verbose_name="course",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="user_of_course",
|
||||||
|
to="judge.profile",
|
||||||
|
verbose_name="user",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="CourseResource",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"files",
|
||||||
|
models.FileField(
|
||||||
|
blank=True, null=True, upload_to="", verbose_name="course files"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=150, verbose_name="description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("order", models.IntegerField(default=None, null=True)),
|
||||||
|
(
|
||||||
|
"is_public",
|
||||||
|
models.BooleanField(default=False, verbose_name="publicly visible"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"course",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="judge.course",
|
||||||
|
verbose_name="course",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="CourseAssignment",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("points", models.FloatField(verbose_name="points")),
|
||||||
|
(
|
||||||
|
"contest",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="judge.contest",
|
||||||
|
verbose_name="contest",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"course",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="judge.course",
|
||||||
|
verbose_name="course",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
682
judge/migrations/0149_auto_20230202_0902.py
Normal file
682
judge/migrations/0149_auto_20230202_0902.py
Normal file
|
@ -0,0 +1,682 @@
|
||||||
|
# Generated by Django 3.2.16 on 2023-02-02 02:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("judge", "0148_course_courseassignment_courseresource_courserole"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="category",
|
||||||
|
field=models.CharField(max_length=1000, verbose_name="category"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="profile",
|
||||||
|
name="timezone",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
(
|
||||||
|
"Africa",
|
||||||
|
[
|
||||||
|
("Africa/Abidjan", "Abidjan"),
|
||||||
|
("Africa/Accra", "Accra"),
|
||||||
|
("Africa/Addis_Ababa", "Addis_Ababa"),
|
||||||
|
("Africa/Algiers", "Algiers"),
|
||||||
|
("Africa/Asmara", "Asmara"),
|
||||||
|
("Africa/Asmera", "Asmera"),
|
||||||
|
("Africa/Bamako", "Bamako"),
|
||||||
|
("Africa/Bangui", "Bangui"),
|
||||||
|
("Africa/Banjul", "Banjul"),
|
||||||
|
("Africa/Bissau", "Bissau"),
|
||||||
|
("Africa/Blantyre", "Blantyre"),
|
||||||
|
("Africa/Brazzaville", "Brazzaville"),
|
||||||
|
("Africa/Bujumbura", "Bujumbura"),
|
||||||
|
("Africa/Cairo", "Cairo"),
|
||||||
|
("Africa/Casablanca", "Casablanca"),
|
||||||
|
("Africa/Ceuta", "Ceuta"),
|
||||||
|
("Africa/Conakry", "Conakry"),
|
||||||
|
("Africa/Dakar", "Dakar"),
|
||||||
|
("Africa/Dar_es_Salaam", "Dar_es_Salaam"),
|
||||||
|
("Africa/Djibouti", "Djibouti"),
|
||||||
|
("Africa/Douala", "Douala"),
|
||||||
|
("Africa/El_Aaiun", "El_Aaiun"),
|
||||||
|
("Africa/Freetown", "Freetown"),
|
||||||
|
("Africa/Gaborone", "Gaborone"),
|
||||||
|
("Africa/Harare", "Harare"),
|
||||||
|
("Africa/Johannesburg", "Johannesburg"),
|
||||||
|
("Africa/Juba", "Juba"),
|
||||||
|
("Africa/Kampala", "Kampala"),
|
||||||
|
("Africa/Khartoum", "Khartoum"),
|
||||||
|
("Africa/Kigali", "Kigali"),
|
||||||
|
("Africa/Kinshasa", "Kinshasa"),
|
||||||
|
("Africa/Lagos", "Lagos"),
|
||||||
|
("Africa/Libreville", "Libreville"),
|
||||||
|
("Africa/Lome", "Lome"),
|
||||||
|
("Africa/Luanda", "Luanda"),
|
||||||
|
("Africa/Lubumbashi", "Lubumbashi"),
|
||||||
|
("Africa/Lusaka", "Lusaka"),
|
||||||
|
("Africa/Malabo", "Malabo"),
|
||||||
|
("Africa/Maputo", "Maputo"),
|
||||||
|
("Africa/Maseru", "Maseru"),
|
||||||
|
("Africa/Mbabane", "Mbabane"),
|
||||||
|
("Africa/Mogadishu", "Mogadishu"),
|
||||||
|
("Africa/Monrovia", "Monrovia"),
|
||||||
|
("Africa/Nairobi", "Nairobi"),
|
||||||
|
("Africa/Ndjamena", "Ndjamena"),
|
||||||
|
("Africa/Niamey", "Niamey"),
|
||||||
|
("Africa/Nouakchott", "Nouakchott"),
|
||||||
|
("Africa/Ouagadougou", "Ouagadougou"),
|
||||||
|
("Africa/Porto-Novo", "Porto-Novo"),
|
||||||
|
("Africa/Sao_Tome", "Sao_Tome"),
|
||||||
|
("Africa/Timbuktu", "Timbuktu"),
|
||||||
|
("Africa/Tripoli", "Tripoli"),
|
||||||
|
("Africa/Tunis", "Tunis"),
|
||||||
|
("Africa/Windhoek", "Windhoek"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"America",
|
||||||
|
[
|
||||||
|
("America/Adak", "Adak"),
|
||||||
|
("America/Anchorage", "Anchorage"),
|
||||||
|
("America/Anguilla", "Anguilla"),
|
||||||
|
("America/Antigua", "Antigua"),
|
||||||
|
("America/Araguaina", "Araguaina"),
|
||||||
|
(
|
||||||
|
"America/Argentina/Buenos_Aires",
|
||||||
|
"Argentina/Buenos_Aires",
|
||||||
|
),
|
||||||
|
("America/Argentina/Catamarca", "Argentina/Catamarca"),
|
||||||
|
(
|
||||||
|
"America/Argentina/ComodRivadavia",
|
||||||
|
"Argentina/ComodRivadavia",
|
||||||
|
),
|
||||||
|
("America/Argentina/Cordoba", "Argentina/Cordoba"),
|
||||||
|
("America/Argentina/Jujuy", "Argentina/Jujuy"),
|
||||||
|
("America/Argentina/La_Rioja", "Argentina/La_Rioja"),
|
||||||
|
("America/Argentina/Mendoza", "Argentina/Mendoza"),
|
||||||
|
(
|
||||||
|
"America/Argentina/Rio_Gallegos",
|
||||||
|
"Argentina/Rio_Gallegos",
|
||||||
|
),
|
||||||
|
("America/Argentina/Salta", "Argentina/Salta"),
|
||||||
|
("America/Argentina/San_Juan", "Argentina/San_Juan"),
|
||||||
|
("America/Argentina/San_Luis", "Argentina/San_Luis"),
|
||||||
|
("America/Argentina/Tucuman", "Argentina/Tucuman"),
|
||||||
|
("America/Argentina/Ushuaia", "Argentina/Ushuaia"),
|
||||||
|
("America/Aruba", "Aruba"),
|
||||||
|
("America/Asuncion", "Asuncion"),
|
||||||
|
("America/Atikokan", "Atikokan"),
|
||||||
|
("America/Atka", "Atka"),
|
||||||
|
("America/Bahia", "Bahia"),
|
||||||
|
("America/Bahia_Banderas", "Bahia_Banderas"),
|
||||||
|
("America/Barbados", "Barbados"),
|
||||||
|
("America/Belem", "Belem"),
|
||||||
|
("America/Belize", "Belize"),
|
||||||
|
("America/Blanc-Sablon", "Blanc-Sablon"),
|
||||||
|
("America/Boa_Vista", "Boa_Vista"),
|
||||||
|
("America/Bogota", "Bogota"),
|
||||||
|
("America/Boise", "Boise"),
|
||||||
|
("America/Buenos_Aires", "Buenos_Aires"),
|
||||||
|
("America/Cambridge_Bay", "Cambridge_Bay"),
|
||||||
|
("America/Campo_Grande", "Campo_Grande"),
|
||||||
|
("America/Cancun", "Cancun"),
|
||||||
|
("America/Caracas", "Caracas"),
|
||||||
|
("America/Catamarca", "Catamarca"),
|
||||||
|
("America/Cayenne", "Cayenne"),
|
||||||
|
("America/Cayman", "Cayman"),
|
||||||
|
("America/Chicago", "Chicago"),
|
||||||
|
("America/Chihuahua", "Chihuahua"),
|
||||||
|
("America/Coral_Harbour", "Coral_Harbour"),
|
||||||
|
("America/Cordoba", "Cordoba"),
|
||||||
|
("America/Costa_Rica", "Costa_Rica"),
|
||||||
|
("America/Creston", "Creston"),
|
||||||
|
("America/Cuiaba", "Cuiaba"),
|
||||||
|
("America/Curacao", "Curacao"),
|
||||||
|
("America/Danmarkshavn", "Danmarkshavn"),
|
||||||
|
("America/Dawson", "Dawson"),
|
||||||
|
("America/Dawson_Creek", "Dawson_Creek"),
|
||||||
|
("America/Denver", "Denver"),
|
||||||
|
("America/Detroit", "Detroit"),
|
||||||
|
("America/Dominica", "Dominica"),
|
||||||
|
("America/Edmonton", "Edmonton"),
|
||||||
|
("America/Eirunepe", "Eirunepe"),
|
||||||
|
("America/El_Salvador", "El_Salvador"),
|
||||||
|
("America/Ensenada", "Ensenada"),
|
||||||
|
("America/Fort_Nelson", "Fort_Nelson"),
|
||||||
|
("America/Fort_Wayne", "Fort_Wayne"),
|
||||||
|
("America/Fortaleza", "Fortaleza"),
|
||||||
|
("America/Glace_Bay", "Glace_Bay"),
|
||||||
|
("America/Godthab", "Godthab"),
|
||||||
|
("America/Goose_Bay", "Goose_Bay"),
|
||||||
|
("America/Grand_Turk", "Grand_Turk"),
|
||||||
|
("America/Grenada", "Grenada"),
|
||||||
|
("America/Guadeloupe", "Guadeloupe"),
|
||||||
|
("America/Guatemala", "Guatemala"),
|
||||||
|
("America/Guayaquil", "Guayaquil"),
|
||||||
|
("America/Guyana", "Guyana"),
|
||||||
|
("America/Halifax", "Halifax"),
|
||||||
|
("America/Havana", "Havana"),
|
||||||
|
("America/Hermosillo", "Hermosillo"),
|
||||||
|
("America/Indiana/Indianapolis", "Indiana/Indianapolis"),
|
||||||
|
("America/Indiana/Knox", "Indiana/Knox"),
|
||||||
|
("America/Indiana/Marengo", "Indiana/Marengo"),
|
||||||
|
("America/Indiana/Petersburg", "Indiana/Petersburg"),
|
||||||
|
("America/Indiana/Tell_City", "Indiana/Tell_City"),
|
||||||
|
("America/Indiana/Vevay", "Indiana/Vevay"),
|
||||||
|
("America/Indiana/Vincennes", "Indiana/Vincennes"),
|
||||||
|
("America/Indiana/Winamac", "Indiana/Winamac"),
|
||||||
|
("America/Indianapolis", "Indianapolis"),
|
||||||
|
("America/Inuvik", "Inuvik"),
|
||||||
|
("America/Iqaluit", "Iqaluit"),
|
||||||
|
("America/Jamaica", "Jamaica"),
|
||||||
|
("America/Jujuy", "Jujuy"),
|
||||||
|
("America/Juneau", "Juneau"),
|
||||||
|
("America/Kentucky/Louisville", "Kentucky/Louisville"),
|
||||||
|
("America/Kentucky/Monticello", "Kentucky/Monticello"),
|
||||||
|
("America/Knox_IN", "Knox_IN"),
|
||||||
|
("America/Kralendijk", "Kralendijk"),
|
||||||
|
("America/La_Paz", "La_Paz"),
|
||||||
|
("America/Lima", "Lima"),
|
||||||
|
("America/Los_Angeles", "Los_Angeles"),
|
||||||
|
("America/Louisville", "Louisville"),
|
||||||
|
("America/Lower_Princes", "Lower_Princes"),
|
||||||
|
("America/Maceio", "Maceio"),
|
||||||
|
("America/Managua", "Managua"),
|
||||||
|
("America/Manaus", "Manaus"),
|
||||||
|
("America/Marigot", "Marigot"),
|
||||||
|
("America/Martinique", "Martinique"),
|
||||||
|
("America/Matamoros", "Matamoros"),
|
||||||
|
("America/Mazatlan", "Mazatlan"),
|
||||||
|
("America/Mendoza", "Mendoza"),
|
||||||
|
("America/Menominee", "Menominee"),
|
||||||
|
("America/Merida", "Merida"),
|
||||||
|
("America/Metlakatla", "Metlakatla"),
|
||||||
|
("America/Mexico_City", "Mexico_City"),
|
||||||
|
("America/Miquelon", "Miquelon"),
|
||||||
|
("America/Moncton", "Moncton"),
|
||||||
|
("America/Monterrey", "Monterrey"),
|
||||||
|
("America/Montevideo", "Montevideo"),
|
||||||
|
("America/Montreal", "Montreal"),
|
||||||
|
("America/Montserrat", "Montserrat"),
|
||||||
|
("America/Nassau", "Nassau"),
|
||||||
|
("America/New_York", "New_York"),
|
||||||
|
("America/Nipigon", "Nipigon"),
|
||||||
|
("America/Nome", "Nome"),
|
||||||
|
("America/Noronha", "Noronha"),
|
||||||
|
("America/North_Dakota/Beulah", "North_Dakota/Beulah"),
|
||||||
|
("America/North_Dakota/Center", "North_Dakota/Center"),
|
||||||
|
(
|
||||||
|
"America/North_Dakota/New_Salem",
|
||||||
|
"North_Dakota/New_Salem",
|
||||||
|
),
|
||||||
|
("America/Nuuk", "Nuuk"),
|
||||||
|
("America/Ojinaga", "Ojinaga"),
|
||||||
|
("America/Panama", "Panama"),
|
||||||
|
("America/Pangnirtung", "Pangnirtung"),
|
||||||
|
("America/Paramaribo", "Paramaribo"),
|
||||||
|
("America/Phoenix", "Phoenix"),
|
||||||
|
("America/Port-au-Prince", "Port-au-Prince"),
|
||||||
|
("America/Port_of_Spain", "Port_of_Spain"),
|
||||||
|
("America/Porto_Acre", "Porto_Acre"),
|
||||||
|
("America/Porto_Velho", "Porto_Velho"),
|
||||||
|
("America/Puerto_Rico", "Puerto_Rico"),
|
||||||
|
("America/Punta_Arenas", "Punta_Arenas"),
|
||||||
|
("America/Rainy_River", "Rainy_River"),
|
||||||
|
("America/Rankin_Inlet", "Rankin_Inlet"),
|
||||||
|
("America/Recife", "Recife"),
|
||||||
|
("America/Regina", "Regina"),
|
||||||
|
("America/Resolute", "Resolute"),
|
||||||
|
("America/Rio_Branco", "Rio_Branco"),
|
||||||
|
("America/Rosario", "Rosario"),
|
||||||
|
("America/Santa_Isabel", "Santa_Isabel"),
|
||||||
|
("America/Santarem", "Santarem"),
|
||||||
|
("America/Santiago", "Santiago"),
|
||||||
|
("America/Santo_Domingo", "Santo_Domingo"),
|
||||||
|
("America/Sao_Paulo", "Sao_Paulo"),
|
||||||
|
("America/Scoresbysund", "Scoresbysund"),
|
||||||
|
("America/Shiprock", "Shiprock"),
|
||||||
|
("America/Sitka", "Sitka"),
|
||||||
|
("America/St_Barthelemy", "St_Barthelemy"),
|
||||||
|
("America/St_Johns", "St_Johns"),
|
||||||
|
("America/St_Kitts", "St_Kitts"),
|
||||||
|
("America/St_Lucia", "St_Lucia"),
|
||||||
|
("America/St_Thomas", "St_Thomas"),
|
||||||
|
("America/St_Vincent", "St_Vincent"),
|
||||||
|
("America/Swift_Current", "Swift_Current"),
|
||||||
|
("America/Tegucigalpa", "Tegucigalpa"),
|
||||||
|
("America/Thule", "Thule"),
|
||||||
|
("America/Thunder_Bay", "Thunder_Bay"),
|
||||||
|
("America/Tijuana", "Tijuana"),
|
||||||
|
("America/Toronto", "Toronto"),
|
||||||
|
("America/Tortola", "Tortola"),
|
||||||
|
("America/Vancouver", "Vancouver"),
|
||||||
|
("America/Virgin", "Virgin"),
|
||||||
|
("America/Whitehorse", "Whitehorse"),
|
||||||
|
("America/Winnipeg", "Winnipeg"),
|
||||||
|
("America/Yakutat", "Yakutat"),
|
||||||
|
("America/Yellowknife", "Yellowknife"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Antarctica",
|
||||||
|
[
|
||||||
|
("Antarctica/Casey", "Casey"),
|
||||||
|
("Antarctica/Davis", "Davis"),
|
||||||
|
("Antarctica/DumontDUrville", "DumontDUrville"),
|
||||||
|
("Antarctica/Macquarie", "Macquarie"),
|
||||||
|
("Antarctica/Mawson", "Mawson"),
|
||||||
|
("Antarctica/McMurdo", "McMurdo"),
|
||||||
|
("Antarctica/Palmer", "Palmer"),
|
||||||
|
("Antarctica/Rothera", "Rothera"),
|
||||||
|
("Antarctica/South_Pole", "South_Pole"),
|
||||||
|
("Antarctica/Syowa", "Syowa"),
|
||||||
|
("Antarctica/Troll", "Troll"),
|
||||||
|
("Antarctica/Vostok", "Vostok"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
("Arctic", [("Arctic/Longyearbyen", "Longyearbyen")]),
|
||||||
|
(
|
||||||
|
"Asia",
|
||||||
|
[
|
||||||
|
("Asia/Aden", "Aden"),
|
||||||
|
("Asia/Almaty", "Almaty"),
|
||||||
|
("Asia/Amman", "Amman"),
|
||||||
|
("Asia/Anadyr", "Anadyr"),
|
||||||
|
("Asia/Aqtau", "Aqtau"),
|
||||||
|
("Asia/Aqtobe", "Aqtobe"),
|
||||||
|
("Asia/Ashgabat", "Ashgabat"),
|
||||||
|
("Asia/Ashkhabad", "Ashkhabad"),
|
||||||
|
("Asia/Atyrau", "Atyrau"),
|
||||||
|
("Asia/Baghdad", "Baghdad"),
|
||||||
|
("Asia/Bahrain", "Bahrain"),
|
||||||
|
("Asia/Baku", "Baku"),
|
||||||
|
("Asia/Bangkok", "Bangkok"),
|
||||||
|
("Asia/Barnaul", "Barnaul"),
|
||||||
|
("Asia/Beirut", "Beirut"),
|
||||||
|
("Asia/Bishkek", "Bishkek"),
|
||||||
|
("Asia/Brunei", "Brunei"),
|
||||||
|
("Asia/Calcutta", "Calcutta"),
|
||||||
|
("Asia/Chita", "Chita"),
|
||||||
|
("Asia/Choibalsan", "Choibalsan"),
|
||||||
|
("Asia/Chongqing", "Chongqing"),
|
||||||
|
("Asia/Chungking", "Chungking"),
|
||||||
|
("Asia/Colombo", "Colombo"),
|
||||||
|
("Asia/Dacca", "Dacca"),
|
||||||
|
("Asia/Damascus", "Damascus"),
|
||||||
|
("Asia/Dhaka", "Dhaka"),
|
||||||
|
("Asia/Dili", "Dili"),
|
||||||
|
("Asia/Dubai", "Dubai"),
|
||||||
|
("Asia/Dushanbe", "Dushanbe"),
|
||||||
|
("Asia/Famagusta", "Famagusta"),
|
||||||
|
("Asia/Gaza", "Gaza"),
|
||||||
|
("Asia/Harbin", "Harbin"),
|
||||||
|
("Asia/Hebron", "Hebron"),
|
||||||
|
("Asia/Ho_Chi_Minh", "Ho_Chi_Minh"),
|
||||||
|
("Asia/Hong_Kong", "Hong_Kong"),
|
||||||
|
("Asia/Hovd", "Hovd"),
|
||||||
|
("Asia/Irkutsk", "Irkutsk"),
|
||||||
|
("Asia/Istanbul", "Istanbul"),
|
||||||
|
("Asia/Jakarta", "Jakarta"),
|
||||||
|
("Asia/Jayapura", "Jayapura"),
|
||||||
|
("Asia/Jerusalem", "Jerusalem"),
|
||||||
|
("Asia/Kabul", "Kabul"),
|
||||||
|
("Asia/Kamchatka", "Kamchatka"),
|
||||||
|
("Asia/Karachi", "Karachi"),
|
||||||
|
("Asia/Kashgar", "Kashgar"),
|
||||||
|
("Asia/Kathmandu", "Kathmandu"),
|
||||||
|
("Asia/Katmandu", "Katmandu"),
|
||||||
|
("Asia/Khandyga", "Khandyga"),
|
||||||
|
("Asia/Kolkata", "Kolkata"),
|
||||||
|
("Asia/Krasnoyarsk", "Krasnoyarsk"),
|
||||||
|
("Asia/Kuala_Lumpur", "Kuala_Lumpur"),
|
||||||
|
("Asia/Kuching", "Kuching"),
|
||||||
|
("Asia/Kuwait", "Kuwait"),
|
||||||
|
("Asia/Macao", "Macao"),
|
||||||
|
("Asia/Macau", "Macau"),
|
||||||
|
("Asia/Magadan", "Magadan"),
|
||||||
|
("Asia/Makassar", "Makassar"),
|
||||||
|
("Asia/Manila", "Manila"),
|
||||||
|
("Asia/Muscat", "Muscat"),
|
||||||
|
("Asia/Nicosia", "Nicosia"),
|
||||||
|
("Asia/Novokuznetsk", "Novokuznetsk"),
|
||||||
|
("Asia/Novosibirsk", "Novosibirsk"),
|
||||||
|
("Asia/Omsk", "Omsk"),
|
||||||
|
("Asia/Oral", "Oral"),
|
||||||
|
("Asia/Phnom_Penh", "Phnom_Penh"),
|
||||||
|
("Asia/Pontianak", "Pontianak"),
|
||||||
|
("Asia/Pyongyang", "Pyongyang"),
|
||||||
|
("Asia/Qatar", "Qatar"),
|
||||||
|
("Asia/Qostanay", "Qostanay"),
|
||||||
|
("Asia/Qyzylorda", "Qyzylorda"),
|
||||||
|
("Asia/Rangoon", "Rangoon"),
|
||||||
|
("Asia/Riyadh", "Riyadh"),
|
||||||
|
("Asia/Saigon", "Saigon"),
|
||||||
|
("Asia/Sakhalin", "Sakhalin"),
|
||||||
|
("Asia/Samarkand", "Samarkand"),
|
||||||
|
("Asia/Seoul", "Seoul"),
|
||||||
|
("Asia/Shanghai", "Shanghai"),
|
||||||
|
("Asia/Singapore", "Singapore"),
|
||||||
|
("Asia/Srednekolymsk", "Srednekolymsk"),
|
||||||
|
("Asia/Taipei", "Taipei"),
|
||||||
|
("Asia/Tashkent", "Tashkent"),
|
||||||
|
("Asia/Tbilisi", "Tbilisi"),
|
||||||
|
("Asia/Tehran", "Tehran"),
|
||||||
|
("Asia/Tel_Aviv", "Tel_Aviv"),
|
||||||
|
("Asia/Thimbu", "Thimbu"),
|
||||||
|
("Asia/Thimphu", "Thimphu"),
|
||||||
|
("Asia/Tokyo", "Tokyo"),
|
||||||
|
("Asia/Tomsk", "Tomsk"),
|
||||||
|
("Asia/Ujung_Pandang", "Ujung_Pandang"),
|
||||||
|
("Asia/Ulaanbaatar", "Ulaanbaatar"),
|
||||||
|
("Asia/Ulan_Bator", "Ulan_Bator"),
|
||||||
|
("Asia/Urumqi", "Urumqi"),
|
||||||
|
("Asia/Ust-Nera", "Ust-Nera"),
|
||||||
|
("Asia/Vientiane", "Vientiane"),
|
||||||
|
("Asia/Vladivostok", "Vladivostok"),
|
||||||
|
("Asia/Yakutsk", "Yakutsk"),
|
||||||
|
("Asia/Yangon", "Yangon"),
|
||||||
|
("Asia/Yekaterinburg", "Yekaterinburg"),
|
||||||
|
("Asia/Yerevan", "Yerevan"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Atlantic",
|
||||||
|
[
|
||||||
|
("Atlantic/Azores", "Azores"),
|
||||||
|
("Atlantic/Bermuda", "Bermuda"),
|
||||||
|
("Atlantic/Canary", "Canary"),
|
||||||
|
("Atlantic/Cape_Verde", "Cape_Verde"),
|
||||||
|
("Atlantic/Faeroe", "Faeroe"),
|
||||||
|
("Atlantic/Faroe", "Faroe"),
|
||||||
|
("Atlantic/Jan_Mayen", "Jan_Mayen"),
|
||||||
|
("Atlantic/Madeira", "Madeira"),
|
||||||
|
("Atlantic/Reykjavik", "Reykjavik"),
|
||||||
|
("Atlantic/South_Georgia", "South_Georgia"),
|
||||||
|
("Atlantic/St_Helena", "St_Helena"),
|
||||||
|
("Atlantic/Stanley", "Stanley"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Australia",
|
||||||
|
[
|
||||||
|
("Australia/ACT", "ACT"),
|
||||||
|
("Australia/Adelaide", "Adelaide"),
|
||||||
|
("Australia/Brisbane", "Brisbane"),
|
||||||
|
("Australia/Broken_Hill", "Broken_Hill"),
|
||||||
|
("Australia/Canberra", "Canberra"),
|
||||||
|
("Australia/Currie", "Currie"),
|
||||||
|
("Australia/Darwin", "Darwin"),
|
||||||
|
("Australia/Eucla", "Eucla"),
|
||||||
|
("Australia/Hobart", "Hobart"),
|
||||||
|
("Australia/LHI", "LHI"),
|
||||||
|
("Australia/Lindeman", "Lindeman"),
|
||||||
|
("Australia/Lord_Howe", "Lord_Howe"),
|
||||||
|
("Australia/Melbourne", "Melbourne"),
|
||||||
|
("Australia/NSW", "NSW"),
|
||||||
|
("Australia/North", "North"),
|
||||||
|
("Australia/Perth", "Perth"),
|
||||||
|
("Australia/Queensland", "Queensland"),
|
||||||
|
("Australia/South", "South"),
|
||||||
|
("Australia/Sydney", "Sydney"),
|
||||||
|
("Australia/Tasmania", "Tasmania"),
|
||||||
|
("Australia/Victoria", "Victoria"),
|
||||||
|
("Australia/West", "West"),
|
||||||
|
("Australia/Yancowinna", "Yancowinna"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Brazil",
|
||||||
|
[
|
||||||
|
("Brazil/Acre", "Acre"),
|
||||||
|
("Brazil/DeNoronha", "DeNoronha"),
|
||||||
|
("Brazil/East", "East"),
|
||||||
|
("Brazil/West", "West"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Canada",
|
||||||
|
[
|
||||||
|
("Canada/Atlantic", "Atlantic"),
|
||||||
|
("Canada/Central", "Central"),
|
||||||
|
("Canada/Eastern", "Eastern"),
|
||||||
|
("Canada/Mountain", "Mountain"),
|
||||||
|
("Canada/Newfoundland", "Newfoundland"),
|
||||||
|
("Canada/Pacific", "Pacific"),
|
||||||
|
("Canada/Saskatchewan", "Saskatchewan"),
|
||||||
|
("Canada/Yukon", "Yukon"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Chile",
|
||||||
|
[
|
||||||
|
("Chile/Continental", "Continental"),
|
||||||
|
("Chile/EasterIsland", "EasterIsland"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Etc",
|
||||||
|
[
|
||||||
|
("Etc/Greenwich", "Greenwich"),
|
||||||
|
("Etc/UCT", "UCT"),
|
||||||
|
("Etc/UTC", "UTC"),
|
||||||
|
("Etc/Universal", "Universal"),
|
||||||
|
("Etc/Zulu", "Zulu"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Europe",
|
||||||
|
[
|
||||||
|
("Europe/Amsterdam", "Amsterdam"),
|
||||||
|
("Europe/Andorra", "Andorra"),
|
||||||
|
("Europe/Astrakhan", "Astrakhan"),
|
||||||
|
("Europe/Athens", "Athens"),
|
||||||
|
("Europe/Belfast", "Belfast"),
|
||||||
|
("Europe/Belgrade", "Belgrade"),
|
||||||
|
("Europe/Berlin", "Berlin"),
|
||||||
|
("Europe/Bratislava", "Bratislava"),
|
||||||
|
("Europe/Brussels", "Brussels"),
|
||||||
|
("Europe/Bucharest", "Bucharest"),
|
||||||
|
("Europe/Budapest", "Budapest"),
|
||||||
|
("Europe/Busingen", "Busingen"),
|
||||||
|
("Europe/Chisinau", "Chisinau"),
|
||||||
|
("Europe/Copenhagen", "Copenhagen"),
|
||||||
|
("Europe/Dublin", "Dublin"),
|
||||||
|
("Europe/Gibraltar", "Gibraltar"),
|
||||||
|
("Europe/Guernsey", "Guernsey"),
|
||||||
|
("Europe/Helsinki", "Helsinki"),
|
||||||
|
("Europe/Isle_of_Man", "Isle_of_Man"),
|
||||||
|
("Europe/Istanbul", "Istanbul"),
|
||||||
|
("Europe/Jersey", "Jersey"),
|
||||||
|
("Europe/Kaliningrad", "Kaliningrad"),
|
||||||
|
("Europe/Kiev", "Kiev"),
|
||||||
|
("Europe/Kirov", "Kirov"),
|
||||||
|
("Europe/Kyiv", "Kyiv"),
|
||||||
|
("Europe/Lisbon", "Lisbon"),
|
||||||
|
("Europe/Ljubljana", "Ljubljana"),
|
||||||
|
("Europe/London", "London"),
|
||||||
|
("Europe/Luxembourg", "Luxembourg"),
|
||||||
|
("Europe/Madrid", "Madrid"),
|
||||||
|
("Europe/Malta", "Malta"),
|
||||||
|
("Europe/Mariehamn", "Mariehamn"),
|
||||||
|
("Europe/Minsk", "Minsk"),
|
||||||
|
("Europe/Monaco", "Monaco"),
|
||||||
|
("Europe/Moscow", "Moscow"),
|
||||||
|
("Europe/Nicosia", "Nicosia"),
|
||||||
|
("Europe/Oslo", "Oslo"),
|
||||||
|
("Europe/Paris", "Paris"),
|
||||||
|
("Europe/Podgorica", "Podgorica"),
|
||||||
|
("Europe/Prague", "Prague"),
|
||||||
|
("Europe/Riga", "Riga"),
|
||||||
|
("Europe/Rome", "Rome"),
|
||||||
|
("Europe/Samara", "Samara"),
|
||||||
|
("Europe/San_Marino", "San_Marino"),
|
||||||
|
("Europe/Sarajevo", "Sarajevo"),
|
||||||
|
("Europe/Saratov", "Saratov"),
|
||||||
|
("Europe/Simferopol", "Simferopol"),
|
||||||
|
("Europe/Skopje", "Skopje"),
|
||||||
|
("Europe/Sofia", "Sofia"),
|
||||||
|
("Europe/Stockholm", "Stockholm"),
|
||||||
|
("Europe/Tallinn", "Tallinn"),
|
||||||
|
("Europe/Tirane", "Tirane"),
|
||||||
|
("Europe/Tiraspol", "Tiraspol"),
|
||||||
|
("Europe/Ulyanovsk", "Ulyanovsk"),
|
||||||
|
("Europe/Uzhgorod", "Uzhgorod"),
|
||||||
|
("Europe/Vaduz", "Vaduz"),
|
||||||
|
("Europe/Vatican", "Vatican"),
|
||||||
|
("Europe/Vienna", "Vienna"),
|
||||||
|
("Europe/Vilnius", "Vilnius"),
|
||||||
|
("Europe/Volgograd", "Volgograd"),
|
||||||
|
("Europe/Warsaw", "Warsaw"),
|
||||||
|
("Europe/Zagreb", "Zagreb"),
|
||||||
|
("Europe/Zaporozhye", "Zaporozhye"),
|
||||||
|
("Europe/Zurich", "Zurich"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Indian",
|
||||||
|
[
|
||||||
|
("Indian/Antananarivo", "Antananarivo"),
|
||||||
|
("Indian/Chagos", "Chagos"),
|
||||||
|
("Indian/Christmas", "Christmas"),
|
||||||
|
("Indian/Cocos", "Cocos"),
|
||||||
|
("Indian/Comoro", "Comoro"),
|
||||||
|
("Indian/Kerguelen", "Kerguelen"),
|
||||||
|
("Indian/Mahe", "Mahe"),
|
||||||
|
("Indian/Maldives", "Maldives"),
|
||||||
|
("Indian/Mauritius", "Mauritius"),
|
||||||
|
("Indian/Mayotte", "Mayotte"),
|
||||||
|
("Indian/Reunion", "Reunion"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Mexico",
|
||||||
|
[
|
||||||
|
("Mexico/BajaNorte", "BajaNorte"),
|
||||||
|
("Mexico/BajaSur", "BajaSur"),
|
||||||
|
("Mexico/General", "General"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Other",
|
||||||
|
[
|
||||||
|
("CET", "CET"),
|
||||||
|
("CST6CDT", "CST6CDT"),
|
||||||
|
("Cuba", "Cuba"),
|
||||||
|
("EET", "EET"),
|
||||||
|
("EST", "EST"),
|
||||||
|
("EST5EDT", "EST5EDT"),
|
||||||
|
("Egypt", "Egypt"),
|
||||||
|
("Eire", "Eire"),
|
||||||
|
("GB", "GB"),
|
||||||
|
("GB-Eire", "GB-Eire"),
|
||||||
|
("Greenwich", "Greenwich"),
|
||||||
|
("HST", "HST"),
|
||||||
|
("Hongkong", "Hongkong"),
|
||||||
|
("Iceland", "Iceland"),
|
||||||
|
("Iran", "Iran"),
|
||||||
|
("Israel", "Israel"),
|
||||||
|
("Jamaica", "Jamaica"),
|
||||||
|
("Japan", "Japan"),
|
||||||
|
("Kwajalein", "Kwajalein"),
|
||||||
|
("Libya", "Libya"),
|
||||||
|
("MET", "MET"),
|
||||||
|
("MST", "MST"),
|
||||||
|
("MST7MDT", "MST7MDT"),
|
||||||
|
("NZ", "NZ"),
|
||||||
|
("NZ-CHAT", "NZ-CHAT"),
|
||||||
|
("Navajo", "Navajo"),
|
||||||
|
("PRC", "PRC"),
|
||||||
|
("PST8PDT", "PST8PDT"),
|
||||||
|
("Poland", "Poland"),
|
||||||
|
("Portugal", "Portugal"),
|
||||||
|
("ROC", "ROC"),
|
||||||
|
("ROK", "ROK"),
|
||||||
|
("Singapore", "Singapore"),
|
||||||
|
("Turkey", "Turkey"),
|
||||||
|
("UCT", "UCT"),
|
||||||
|
("UTC", "UTC"),
|
||||||
|
("Universal", "Universal"),
|
||||||
|
("W-SU", "W-SU"),
|
||||||
|
("WET", "WET"),
|
||||||
|
("Zulu", "Zulu"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Pacific",
|
||||||
|
[
|
||||||
|
("Pacific/Apia", "Apia"),
|
||||||
|
("Pacific/Auckland", "Auckland"),
|
||||||
|
("Pacific/Bougainville", "Bougainville"),
|
||||||
|
("Pacific/Chatham", "Chatham"),
|
||||||
|
("Pacific/Chuuk", "Chuuk"),
|
||||||
|
("Pacific/Easter", "Easter"),
|
||||||
|
("Pacific/Efate", "Efate"),
|
||||||
|
("Pacific/Enderbury", "Enderbury"),
|
||||||
|
("Pacific/Fakaofo", "Fakaofo"),
|
||||||
|
("Pacific/Fiji", "Fiji"),
|
||||||
|
("Pacific/Funafuti", "Funafuti"),
|
||||||
|
("Pacific/Galapagos", "Galapagos"),
|
||||||
|
("Pacific/Gambier", "Gambier"),
|
||||||
|
("Pacific/Guadalcanal", "Guadalcanal"),
|
||||||
|
("Pacific/Guam", "Guam"),
|
||||||
|
("Pacific/Honolulu", "Honolulu"),
|
||||||
|
("Pacific/Johnston", "Johnston"),
|
||||||
|
("Pacific/Kanton", "Kanton"),
|
||||||
|
("Pacific/Kiritimati", "Kiritimati"),
|
||||||
|
("Pacific/Kosrae", "Kosrae"),
|
||||||
|
("Pacific/Kwajalein", "Kwajalein"),
|
||||||
|
("Pacific/Majuro", "Majuro"),
|
||||||
|
("Pacific/Marquesas", "Marquesas"),
|
||||||
|
("Pacific/Midway", "Midway"),
|
||||||
|
("Pacific/Nauru", "Nauru"),
|
||||||
|
("Pacific/Niue", "Niue"),
|
||||||
|
("Pacific/Norfolk", "Norfolk"),
|
||||||
|
("Pacific/Noumea", "Noumea"),
|
||||||
|
("Pacific/Pago_Pago", "Pago_Pago"),
|
||||||
|
("Pacific/Palau", "Palau"),
|
||||||
|
("Pacific/Pitcairn", "Pitcairn"),
|
||||||
|
("Pacific/Pohnpei", "Pohnpei"),
|
||||||
|
("Pacific/Ponape", "Ponape"),
|
||||||
|
("Pacific/Port_Moresby", "Port_Moresby"),
|
||||||
|
("Pacific/Rarotonga", "Rarotonga"),
|
||||||
|
("Pacific/Saipan", "Saipan"),
|
||||||
|
("Pacific/Samoa", "Samoa"),
|
||||||
|
("Pacific/Tahiti", "Tahiti"),
|
||||||
|
("Pacific/Tarawa", "Tarawa"),
|
||||||
|
("Pacific/Tongatapu", "Tongatapu"),
|
||||||
|
("Pacific/Truk", "Truk"),
|
||||||
|
("Pacific/Wake", "Wake"),
|
||||||
|
("Pacific/Wallis", "Wallis"),
|
||||||
|
("Pacific/Yap", "Yap"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"US",
|
||||||
|
[
|
||||||
|
("US/Alaska", "Alaska"),
|
||||||
|
("US/Aleutian", "Aleutian"),
|
||||||
|
("US/Arizona", "Arizona"),
|
||||||
|
("US/Central", "Central"),
|
||||||
|
("US/East-Indiana", "East-Indiana"),
|
||||||
|
("US/Eastern", "Eastern"),
|
||||||
|
("US/Hawaii", "Hawaii"),
|
||||||
|
("US/Indiana-Starke", "Indiana-Starke"),
|
||||||
|
("US/Michigan", "Michigan"),
|
||||||
|
("US/Mountain", "Mountain"),
|
||||||
|
("US/Pacific", "Pacific"),
|
||||||
|
("US/Samoa", "Samoa"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
default="Asia/Ho_Chi_Minh",
|
||||||
|
max_length=50,
|
||||||
|
verbose_name="location",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
18
judge/migrations/0150_alter_profile_timezone.py
Normal file
18
judge/migrations/0150_alter_profile_timezone.py
Normal file
File diff suppressed because one or more lines are too long
50
judge/migrations/0151_comment_content_type.py
Normal file
50
judge/migrations/0151_comment_content_type.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Generated by Django 3.2.18 on 2023-02-20 21:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("contenttypes", "0002_remove_content_type_name"),
|
||||||
|
("judge", "0150_alter_profile_timezone"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="comment",
|
||||||
|
name="content_type",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="comment",
|
||||||
|
name="object_id",
|
||||||
|
field=models.PositiveIntegerField(null=True),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="solution",
|
||||||
|
name="problem",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="solution",
|
||||||
|
to="judge.problem",
|
||||||
|
verbose_name="associated problem",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="comment",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["content_type", "object_id"],
|
||||||
|
name="judge_comme_content_2dce05_idx",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
54
judge/migrations/0152_migrate_comments.py
Normal file
54
judge/migrations/0152_migrate_comments.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_comments(apps, schema_editor):
|
||||||
|
Comment = apps.get_model("judge", "Comment")
|
||||||
|
Problem = apps.get_model("judge", "Problem")
|
||||||
|
Solution = apps.get_model("judge", "Solution")
|
||||||
|
BlogPost = apps.get_model("judge", "BlogPost")
|
||||||
|
Contest = apps.get_model("judge", "Contest")
|
||||||
|
|
||||||
|
for comment in Comment.objects.all():
|
||||||
|
page = comment.page
|
||||||
|
try:
|
||||||
|
if page.startswith("p:"):
|
||||||
|
code = page[2:]
|
||||||
|
comment.linked_object = Problem.objects.get(code=code)
|
||||||
|
elif page.startswith("s:"):
|
||||||
|
code = page[2:]
|
||||||
|
comment.linked_object = Solution.objects.get(problem__code=code)
|
||||||
|
elif page.startswith("c:"):
|
||||||
|
key = page[2:]
|
||||||
|
comment.linked_object = Contest.objects.get(key=key)
|
||||||
|
elif page.startswith("b:"):
|
||||||
|
blog_id = page[2:]
|
||||||
|
comment.linked_object = BlogPost.objects.get(id=blog_id)
|
||||||
|
comment.save()
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
comment.delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("contenttypes", "0002_remove_content_type_name"),
|
||||||
|
("judge", "0151_comment_content_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate_comments, migrations.RunPython.noop, atomic=True),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="comment",
|
||||||
|
name="content_type",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="comment",
|
||||||
|
name="object_id",
|
||||||
|
field=models.PositiveIntegerField(),
|
||||||
|
),
|
||||||
|
]
|
24
judge/migrations/0153_drop_comment_page.py
Normal file
24
judge/migrations/0153_drop_comment_page.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 3.2.18 on 2023-02-20 23:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("judge", "0152_migrate_comments"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="comment",
|
||||||
|
name="page",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="commentlock",
|
||||||
|
name="page",
|
||||||
|
field=models.CharField(
|
||||||
|
db_index=True, max_length=30, verbose_name="associated page"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
39
judge/migrations/0154_auto_20230301_1659.py
Normal file
39
judge/migrations/0154_auto_20230301_1659.py
Normal file
File diff suppressed because one or more lines are too long
|
@ -41,7 +41,7 @@ class CollabFilter:
|
||||||
|
|
||||||
def user_recommendations(self, user, problems, measure=DOT, limit=None, **kwargs):
|
def user_recommendations(self, user, problems, measure=DOT, limit=None, **kwargs):
|
||||||
uid = user.id
|
uid = user.id
|
||||||
problems_hash = hashlib.sha1(str(problems).encode()).hexdigest()
|
problems_hash = hashlib.sha1(str(list(problems)).encode()).hexdigest()
|
||||||
cache_key = ":".join(map(str, [self.name, uid, measure, limit, problems_hash]))
|
cache_key = ":".join(map(str, [self.name, uid, measure, limit, problems_hash]))
|
||||||
value = cache.get(cache_key)
|
value = cache.get(cache_key)
|
||||||
if value:
|
if value:
|
||||||
|
@ -65,16 +65,16 @@ class CollabFilter:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
# return a list of pid
|
# return a list of pid
|
||||||
def problems_neighbors(self, problem, problemset, measure=DOT, limit=None):
|
def problem_neighbors(self, problem, problemset, measure=DOT, limit=None):
|
||||||
pid = problem.id
|
pid = problem.id
|
||||||
if pid >= len(self.problem_embeddings):
|
if pid >= len(self.problem_embeddings):
|
||||||
return None
|
return []
|
||||||
scores = self.compute_scores(
|
scores = self.compute_scores(
|
||||||
self.problem_embeddings[pid], self.problem_embeddings, measure
|
self.problem_embeddings[pid], self.problem_embeddings, measure
|
||||||
)
|
)
|
||||||
res = []
|
res = []
|
||||||
for p in problemset:
|
for p in problemset:
|
||||||
if p.id < len(scores):
|
if p < len(scores):
|
||||||
res.append((scores[p.id], p))
|
res.append((scores[p], p))
|
||||||
res.sort(reverse=True, key=lambda x: x[0])
|
res.sort(reverse=True, key=lambda x: x[0])
|
||||||
return res[:limit]
|
return res[:limit]
|
||||||
|
|
|
@ -56,6 +56,7 @@ from judge.models.ticket import Ticket, TicketMessage
|
||||||
from judge.models.volunteer import VolunteerProblemVote
|
from judge.models.volunteer import VolunteerProblemVote
|
||||||
from judge.models.pagevote import PageVote, PageVoteVoter
|
from judge.models.pagevote import PageVote, PageVoteVoter
|
||||||
from judge.models.bookmark import BookMark, MakeBookMark
|
from judge.models.bookmark import BookMark, MakeBookMark
|
||||||
|
from judge.models.course import Course
|
||||||
|
|
||||||
revisions.register(Profile, exclude=["points", "last_access", "ip", "rating"])
|
revisions.register(Profile, exclude=["points", "last_access", "ip", "rating"])
|
||||||
revisions.register(Problem, follow=["language_limits"])
|
revisions.register(Problem, follow=["language_limits"])
|
||||||
|
@ -81,4 +82,5 @@ revisions.register(Rating)
|
||||||
revisions.register(PageVoteVoter)
|
revisions.register(PageVoteVoter)
|
||||||
revisions.register(VolunteerProblemVote)
|
revisions.register(VolunteerProblemVote)
|
||||||
revisions.register(MakeBookMark)
|
revisions.register(MakeBookMark)
|
||||||
|
revisions.register(Course)
|
||||||
del revisions
|
del revisions
|
||||||
|
|
|
@ -3,10 +3,7 @@ from django.db.models import CASCADE
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
from judge.models import Profile
|
from judge.models.profile import Profile
|
||||||
from judge.models.contest import Contest
|
|
||||||
from judge.models.interface import BlogPost
|
|
||||||
from judge.models.problem import Problem, Solution
|
|
||||||
|
|
||||||
__all__ = ["BookMark"]
|
__all__ = ["BookMark"]
|
||||||
|
|
||||||
|
@ -26,6 +23,10 @@ class BookMark(models.Model):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def page_object(self):
|
def page_object(self):
|
||||||
|
from judge.models.contest import Contest
|
||||||
|
from judge.models.interface import BlogPost
|
||||||
|
from judge.models.problem import Problem, Solution
|
||||||
|
|
||||||
try:
|
try:
|
||||||
page = self.page
|
page = self.page
|
||||||
if page.startswith("p:"):
|
if page.startswith("p:"):
|
||||||
|
|
|
@ -9,6 +9,8 @@ from django.db.models import CASCADE
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from mptt.fields import TreeForeignKey
|
from mptt.fields import TreeForeignKey
|
||||||
from mptt.models import MPTTModel
|
from mptt.models import MPTTModel
|
||||||
from reversion.models import Version
|
from reversion.models import Version
|
||||||
|
@ -22,10 +24,6 @@ from judge.utils.cachedict import CacheDict
|
||||||
|
|
||||||
__all__ = ["Comment", "CommentLock", "CommentVote", "Notification"]
|
__all__ = ["Comment", "CommentLock", "CommentVote", "Notification"]
|
||||||
|
|
||||||
comment_validator = RegexValidator(
|
|
||||||
r"^[pcs]:[a-z0-9]+$|^b:\d+$", _(r"Page code must be ^[pcs]:[a-z0-9]+$|^b:\d+$")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class VersionRelation(GenericRelation):
|
class VersionRelation(GenericRelation):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -44,12 +42,9 @@ class VersionRelation(GenericRelation):
|
||||||
class Comment(MPTTModel):
|
class Comment(MPTTModel):
|
||||||
author = models.ForeignKey(Profile, verbose_name=_("commenter"), on_delete=CASCADE)
|
author = models.ForeignKey(Profile, verbose_name=_("commenter"), on_delete=CASCADE)
|
||||||
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
|
time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True)
|
||||||
page = models.CharField(
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
max_length=30,
|
object_id = models.PositiveIntegerField()
|
||||||
verbose_name=_("associated page"),
|
linked_object = GenericForeignKey("content_type", "object_id")
|
||||||
db_index=True,
|
|
||||||
validators=[comment_validator],
|
|
||||||
)
|
|
||||||
score = models.IntegerField(verbose_name=_("votes"), default=0)
|
score = models.IntegerField(verbose_name=_("votes"), default=0)
|
||||||
body = models.TextField(verbose_name=_("body of comment"), max_length=8192)
|
body = models.TextField(verbose_name=_("body of comment"), max_length=8192)
|
||||||
hidden = models.BooleanField(verbose_name=_("hide the comment"), default=0)
|
hidden = models.BooleanField(verbose_name=_("hide the comment"), default=0)
|
||||||
|
@ -66,12 +61,15 @@ class Comment(MPTTModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("comment")
|
verbose_name = _("comment")
|
||||||
verbose_name_plural = _("comments")
|
verbose_name_plural = _("comments")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["content_type", "object_id"]),
|
||||||
|
]
|
||||||
|
|
||||||
class MPTTMeta:
|
class MPTTMeta:
|
||||||
order_insertion_by = ["-time"]
|
order_insertion_by = ["-time"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def most_recent(cls, user, n, batch=None):
|
def most_recent(cls, user, n, batch=None, organization=None):
|
||||||
queryset = (
|
queryset = (
|
||||||
cls.objects.filter(hidden=False)
|
cls.objects.filter(hidden=False)
|
||||||
.select_related("author__user")
|
.select_related("author__user")
|
||||||
|
@ -79,13 +77,12 @@ class Comment(MPTTModel):
|
||||||
.order_by("-id")
|
.order_by("-id")
|
||||||
)
|
)
|
||||||
|
|
||||||
problem_access = CacheDict(
|
if organization:
|
||||||
lambda code: Problem.objects.get(code=code).is_accessible_by(user)
|
queryset = queryset.filter(author__in=organization.members.all())
|
||||||
)
|
|
||||||
contest_access = CacheDict(
|
problem_access = CacheDict(lambda p: p.is_accessible_by(user))
|
||||||
lambda key: Contest.objects.get(key=key).is_accessible_by(user)
|
contest_access = CacheDict(lambda c: c.is_accessible_by(user))
|
||||||
)
|
blog_access = CacheDict(lambda b: b.can_see(user))
|
||||||
blog_access = CacheDict(lambda id: BlogPost.objects.get(id=id).can_see(user))
|
|
||||||
|
|
||||||
if n == -1:
|
if n == -1:
|
||||||
n = len(queryset)
|
n = len(queryset)
|
||||||
|
@ -99,112 +96,53 @@ class Comment(MPTTModel):
|
||||||
if not slice:
|
if not slice:
|
||||||
break
|
break
|
||||||
for comment in slice:
|
for comment in slice:
|
||||||
if comment.page.startswith("p:") or comment.page.startswith("s:"):
|
if isinstance(comment.linked_object, Problem):
|
||||||
try:
|
if problem_access[comment.linked_object]:
|
||||||
if problem_access[comment.page[2:]]:
|
output.append(comment)
|
||||||
output.append(comment)
|
elif isinstance(comment.linked_object, Contest):
|
||||||
except Problem.DoesNotExist:
|
if contest_access[comment.linked_object]:
|
||||||
pass
|
output.append(comment)
|
||||||
elif comment.page.startswith("c:"):
|
elif isinstance(comment.linked_object, BlogPost):
|
||||||
try:
|
if blog_access[comment.linked_object]:
|
||||||
if contest_access[comment.page[2:]]:
|
output.append(comment)
|
||||||
output.append(comment)
|
elif isinstance(comment.linked_object, Solution):
|
||||||
except Contest.DoesNotExist:
|
if problem_access[comment.linked_object.problem]:
|
||||||
pass
|
output.append(comment)
|
||||||
elif comment.page.startswith("b:"):
|
|
||||||
try:
|
|
||||||
if blog_access[comment.page[2:]]:
|
|
||||||
output.append(comment)
|
|
||||||
except BlogPost.DoesNotExist:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
output.append(comment)
|
|
||||||
if len(output) >= n:
|
if len(output) >= n:
|
||||||
return output
|
return output
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def link(self):
|
def page_title(self):
|
||||||
try:
|
if isinstance(self.linked_object, Problem):
|
||||||
link = None
|
return self.linked_object.name
|
||||||
if self.page.startswith("p:"):
|
elif isinstance(self.linked_object, Contest):
|
||||||
link = reverse("problem_detail", args=(self.page[2:],))
|
return self.linked_object.name
|
||||||
elif self.page.startswith("c:"):
|
elif isinstance(self.linked_object, Solution):
|
||||||
link = reverse("contest_view", args=(self.page[2:],))
|
return _("Editorial for ") + self.linked_object.problem.name
|
||||||
elif self.page.startswith("b:"):
|
elif isinstance(self.linked_object, BlogPost):
|
||||||
key = "blog_slug:%s" % self.page[2:]
|
return self.linked_object.title
|
||||||
slug = cache.get(key)
|
|
||||||
if slug is None:
|
|
||||||
try:
|
|
||||||
slug = BlogPost.objects.get(id=self.page[2:]).slug
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
slug = ""
|
|
||||||
cache.set(key, slug, 3600)
|
|
||||||
link = reverse("blog_post", args=(self.page[2:], slug))
|
|
||||||
elif self.page.startswith("s:"):
|
|
||||||
link = reverse("problem_editorial", args=(self.page[2:],))
|
|
||||||
except Exception:
|
|
||||||
link = "invalid"
|
|
||||||
return link
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_page_title(cls, page):
|
|
||||||
try:
|
|
||||||
if page.startswith("p:"):
|
|
||||||
return Problem.objects.values_list("name", flat=True).get(code=page[2:])
|
|
||||||
elif page.startswith("c:"):
|
|
||||||
return Contest.objects.values_list("name", flat=True).get(key=page[2:])
|
|
||||||
elif page.startswith("b:"):
|
|
||||||
return BlogPost.objects.values_list("title", flat=True).get(id=page[2:])
|
|
||||||
elif page.startswith("s:"):
|
|
||||||
return _("Editorial for %s") % Problem.objects.values_list(
|
|
||||||
"name", flat=True
|
|
||||||
).get(code=page[2:])
|
|
||||||
return "<unknown>"
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return "<deleted>"
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def page_title(self):
|
def link(self):
|
||||||
return self.get_page_title(self.page)
|
if isinstance(self.linked_object, Problem):
|
||||||
|
return reverse("problem_detail", args=(self.linked_object.code,))
|
||||||
|
elif isinstance(self.linked_object, Contest):
|
||||||
|
return reverse("contest_view", args=(self.linked_object.key,))
|
||||||
|
elif isinstance(self.linked_object, Solution):
|
||||||
|
return reverse("problem_editorial", args=(self.linked_object.problem.code,))
|
||||||
|
elif isinstance(self.linked_object, BlogPost):
|
||||||
|
return reverse(
|
||||||
|
"blog_post",
|
||||||
|
args=(
|
||||||
|
self.object_id,
|
||||||
|
self.linked_object.slug,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return "%s#comment-%d" % (self.link, self.id)
|
return "%s#comment-%d" % (self.link, self.id)
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def page_object(self):
|
|
||||||
try:
|
|
||||||
page = self.page
|
|
||||||
if page.startswith("p:"):
|
|
||||||
return Problem.objects.get(code=page[2:])
|
|
||||||
elif page.startswith("c:"):
|
|
||||||
return Contest.objects.get(key=page[2:])
|
|
||||||
elif page.startswith("b:"):
|
|
||||||
return BlogPost.objects.get(id=page[2:])
|
|
||||||
elif page.startswith("s:"):
|
|
||||||
return Solution.objects.get(problem__code=page[2:])
|
|
||||||
return None
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%(page)s by %(user)s" % {
|
|
||||||
"page": self.page,
|
|
||||||
"user": self.author.user.username,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Only use this when queried with
|
|
||||||
# .prefetch_related(Prefetch('votes', queryset=CommentVote.objects.filter(voter_id=profile_id)))
|
|
||||||
# It's rather stupid to put a query specific property on the model, but the alternative requires
|
|
||||||
# digging Django internals, and could not be guaranteed to work forever.
|
|
||||||
# Hence it is left here for when the alternative breaks.
|
|
||||||
# @property
|
|
||||||
# def vote_score(self):
|
|
||||||
# queryset = self.votes.all()
|
|
||||||
# if not queryset:
|
|
||||||
# return 0
|
|
||||||
# return queryset[0].score
|
|
||||||
|
|
||||||
|
|
||||||
class CommentVote(models.Model):
|
class CommentVote(models.Model):
|
||||||
voter = models.ForeignKey(Profile, related_name="voted_comments", on_delete=CASCADE)
|
voter = models.ForeignKey(Profile, related_name="voted_comments", on_delete=CASCADE)
|
||||||
|
@ -222,7 +160,6 @@ class CommentLock(models.Model):
|
||||||
max_length=30,
|
max_length=30,
|
||||||
verbose_name=_("associated page"),
|
verbose_name=_("associated page"),
|
||||||
db_index=True,
|
db_index=True,
|
||||||
validators=[comment_validator],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -244,7 +181,7 @@ class Notification(models.Model):
|
||||||
Comment, null=True, verbose_name=_("comment"), on_delete=CASCADE
|
Comment, null=True, verbose_name=_("comment"), on_delete=CASCADE
|
||||||
)
|
)
|
||||||
read = models.BooleanField(verbose_name=_("read"), default=False)
|
read = models.BooleanField(verbose_name=_("read"), default=False)
|
||||||
category = models.CharField(verbose_name=_("category"), max_length=50)
|
category = models.CharField(verbose_name=_("category"), max_length=1000)
|
||||||
html_link = models.TextField(
|
html_link = models.TextField(
|
||||||
default="",
|
default="",
|
||||||
verbose_name=_("html link to comments, used for non-comments"),
|
verbose_name=_("html link to comments, used for non-comments"),
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext, gettext_lazy as _
|
from django.utils.translation import gettext, gettext_lazy as _
|
||||||
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from jsonfield import JSONField
|
from jsonfield import JSONField
|
||||||
from lupa import LuaRuntime
|
from lupa import LuaRuntime
|
||||||
from moss import (
|
from moss import (
|
||||||
|
@ -297,6 +298,7 @@ class Contest(models.Model):
|
||||||
validators=[MinValueValidator(0), MaxValueValidator(10)],
|
validators=[MinValueValidator(0), MaxValueValidator(10)],
|
||||||
help_text=_("Number of digits to round points to."),
|
help_text=_("Number of digits to round points to."),
|
||||||
)
|
)
|
||||||
|
comments = GenericRelation("Comment")
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def format_class(self):
|
def format_class(self):
|
||||||
|
|
184
judge/models/course.py
Normal file
184
judge/models/course.py
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
from django.core.validators import RegexValidator
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext, gettext_lazy as _
|
||||||
|
|
||||||
|
from judge.models import Contest
|
||||||
|
from judge.models.profile import Organization, Profile
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Course",
|
||||||
|
"CourseRole",
|
||||||
|
"CourseResource",
|
||||||
|
"CourseAssignment",
|
||||||
|
]
|
||||||
|
|
||||||
|
course_directory_file = ""
|
||||||
|
|
||||||
|
|
||||||
|
class Course(models.Model):
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=128,
|
||||||
|
verbose_name=_("course name"),
|
||||||
|
)
|
||||||
|
about = models.TextField(verbose_name=_("organization description"))
|
||||||
|
ending_time = models.DateTimeField(
|
||||||
|
verbose_name=_("ending time"),
|
||||||
|
)
|
||||||
|
is_public = models.BooleanField(
|
||||||
|
verbose_name=_("publicly visible"),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
organizations = models.ManyToManyField(
|
||||||
|
Organization,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("organizations"),
|
||||||
|
help_text=_("If private, only these organizations may see the course"),
|
||||||
|
)
|
||||||
|
slug = models.SlugField(
|
||||||
|
max_length=128,
|
||||||
|
verbose_name=_("course slug"),
|
||||||
|
help_text=_("Course name shown in URL"),
|
||||||
|
unique=True,
|
||||||
|
validators=[
|
||||||
|
RegexValidator("^[-a-zA-Z0-9]+$", _("Only alphanumeric and hyphens"))
|
||||||
|
],
|
||||||
|
)
|
||||||
|
is_open = models.BooleanField(
|
||||||
|
verbose_name=_("public registration"),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
image_url = models.CharField(
|
||||||
|
verbose_name=_("course image"),
|
||||||
|
default="",
|
||||||
|
max_length=150,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_editable_by(cls, course, profile):
|
||||||
|
userquery = CourseRole.objects.filter(course=course, user=profile)
|
||||||
|
if userquery.exists():
|
||||||
|
if userquery[0].role == "AS" or userquery[0].role == "TE":
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_accessible_by(cls, course, profile):
|
||||||
|
userqueryset = CourseRole.objects.filter(course=course, user=profile)
|
||||||
|
if userqueryset.exists():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_students(cls, course):
|
||||||
|
return CourseRole.objects.filter(course=course, role="ST").values("user")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_assistants(cls, course):
|
||||||
|
return CourseRole.objects.filter(course=course, role="AS").values("user")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_teachers(cls, course):
|
||||||
|
return CourseRole.objects.filter(course=course, role="TE").values("user")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_student(cls, course, profiles):
|
||||||
|
for profile in profiles:
|
||||||
|
CourseRole.make_role(course=course, user=profile, role="ST")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_teachers(cls, course, profiles):
|
||||||
|
for profile in profiles:
|
||||||
|
CourseRole.make_role(course=course, user=profile, role="TE")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_assistants(cls, course, profiles):
|
||||||
|
for profile in profiles:
|
||||||
|
CourseRole.make_role(course=course, user=profile, role="AS")
|
||||||
|
|
||||||
|
|
||||||
|
class CourseRole(models.Model):
|
||||||
|
course = models.ForeignKey(
|
||||||
|
Course,
|
||||||
|
verbose_name=_("course"),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
Profile,
|
||||||
|
verbose_name=_("user"),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name=_("user_of_course"),
|
||||||
|
)
|
||||||
|
|
||||||
|
class RoleInCourse(models.TextChoices):
|
||||||
|
STUDENT = "ST", _("Student")
|
||||||
|
ASSISTANT = "AS", _("Assistant")
|
||||||
|
TEACHER = "TE", _("Teacher")
|
||||||
|
|
||||||
|
role = models.CharField(
|
||||||
|
max_length=2,
|
||||||
|
choices=RoleInCourse.choices,
|
||||||
|
default=RoleInCourse.STUDENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_role(self, course, user, role):
|
||||||
|
userqueryset = CourseRole.objects.filter(course=course, user=user)
|
||||||
|
if userqueryset.exists():
|
||||||
|
userqueryset[0].role = role
|
||||||
|
else:
|
||||||
|
couresrole = CourseRole()
|
||||||
|
couresrole.course = course
|
||||||
|
couresrole.user = user
|
||||||
|
couresrole.role = role
|
||||||
|
couresrole.save()
|
||||||
|
|
||||||
|
|
||||||
|
class CourseResource(models.Model):
|
||||||
|
course = models.ForeignKey(
|
||||||
|
Course,
|
||||||
|
verbose_name=_("course"),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
files = models.FileField(
|
||||||
|
verbose_name=_("course files"),
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
upload_to=course_directory_file,
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
verbose_name=_("description"),
|
||||||
|
blank=True,
|
||||||
|
max_length=150,
|
||||||
|
)
|
||||||
|
order = models.IntegerField(null=True, default=None)
|
||||||
|
is_public = models.BooleanField(
|
||||||
|
verbose_name=_("publicly visible"),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Resource - {self.pk}"
|
||||||
|
|
||||||
|
|
||||||
|
class CourseAssignment(models.Model):
|
||||||
|
course = models.ForeignKey(
|
||||||
|
Course,
|
||||||
|
verbose_name=_("course"),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
contest = models.ForeignKey(
|
||||||
|
Contest,
|
||||||
|
verbose_name=_("contest"),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
|
points = models.FloatField(
|
||||||
|
verbose_name=_("points"),
|
||||||
|
)
|
|
@ -5,10 +5,14 @@ from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from mptt.fields import TreeForeignKey
|
from mptt.fields import TreeForeignKey
|
||||||
from mptt.models import MPTTModel
|
from mptt.models import MPTTModel
|
||||||
|
|
||||||
from judge.models.profile import Organization, Profile
|
from judge.models.profile import Organization, Profile
|
||||||
|
from judge.models.pagevote import PageVote
|
||||||
|
from judge.models.bookmark import BookMark
|
||||||
|
|
||||||
__all__ = ["MiscConfig", "validate_regex", "NavigationBar", "BlogPost"]
|
__all__ = ["MiscConfig", "validate_regex", "NavigationBar", "BlogPost"]
|
||||||
|
|
||||||
|
@ -91,6 +95,7 @@ class BlogPost(models.Model):
|
||||||
is_organization_private = models.BooleanField(
|
is_organization_private = models.BooleanField(
|
||||||
verbose_name=_("private to organizations"), default=False
|
verbose_name=_("private to organizations"), default=False
|
||||||
)
|
)
|
||||||
|
comments = GenericRelation("Comment")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
@ -125,6 +130,18 @@ class BlogPost(models.Model):
|
||||||
and self.authors.filter(id=user.profile.id).exists()
|
and self.authors.filter(id=user.profile.id).exists()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def pagevote(self):
|
||||||
|
page = "b:%s" % self.id
|
||||||
|
pagevote, _ = PageVote.objects.get_or_create(page=page)
|
||||||
|
return pagevote
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def bookmark(self):
|
||||||
|
page = "b:%s" % self.id
|
||||||
|
bookmark, _ = BookMark.objects.get_or_create(page=page)
|
||||||
|
return bookmark
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (("edit_all_post", _("Edit all posts")),)
|
permissions = (("edit_all_post", _("Edit all posts")),)
|
||||||
verbose_name = _("blog post")
|
verbose_name = _("blog post")
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.db import models
|
||||||
from django.db.models import CASCADE
|
from django.db.models import CASCADE
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from judge.models import Profile
|
from judge.models.profile import Profile
|
||||||
|
|
||||||
__all__ = ["PageVote", "PageVoteVoter"]
|
__all__ = ["PageVote", "PageVoteVoter"]
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,15 @@ from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import CASCADE, F, FilteredRelation, Q, SET_NULL
|
from django.db.models import CASCADE, F, FilteredRelation, Q, SET_NULL, Exists, OuterRef
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from judge.fulltext import SearchQuerySet
|
from judge.fulltext import SearchQuerySet
|
||||||
|
from judge.models.pagevote import PageVote
|
||||||
|
from judge.models.bookmark import BookMark
|
||||||
from judge.models.profile import Organization, Profile
|
from judge.models.profile import Organization, Profile
|
||||||
from judge.models.runtime import Language
|
from judge.models.runtime import Language
|
||||||
from judge.user_translations import gettext as user_gettext
|
from judge.user_translations import gettext as user_gettext
|
||||||
|
@ -268,6 +270,7 @@ class Problem(models.Model):
|
||||||
|
|
||||||
objects = TranslatedProblemQuerySet.as_manager()
|
objects = TranslatedProblemQuerySet.as_manager()
|
||||||
tickets = GenericRelation("Ticket")
|
tickets = GenericRelation("Ticket")
|
||||||
|
comments = GenericRelation("Comment")
|
||||||
|
|
||||||
organizations = models.ManyToManyField(
|
organizations = models.ManyToManyField(
|
||||||
Organization,
|
Organization,
|
||||||
|
@ -369,9 +372,9 @@ class Problem(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_visible_problems(cls, user):
|
def get_visible_problems(cls, user, profile=None):
|
||||||
# Do unauthenticated check here so we can skip authentication checks later on.
|
# Do unauthenticated check here so we can skip authentication checks later on.
|
||||||
if not user.is_authenticated:
|
if not user.is_authenticated or not user:
|
||||||
return cls.get_public_problems()
|
return cls.get_public_problems()
|
||||||
|
|
||||||
# Conditions for visible problem:
|
# Conditions for visible problem:
|
||||||
|
@ -383,7 +386,7 @@ class Problem(models.Model):
|
||||||
# - not is_organization_private or in organization or `judge.see_organization_problem`
|
# - not is_organization_private or in organization or `judge.see_organization_problem`
|
||||||
# - author or curator or tester
|
# - author or curator or tester
|
||||||
queryset = cls.objects.defer("description")
|
queryset = cls.objects.defer("description")
|
||||||
|
profile = profile or user.profile
|
||||||
if not (
|
if not (
|
||||||
user.has_perm("judge.see_private_problem")
|
user.has_perm("judge.see_private_problem")
|
||||||
or user.has_perm("judge.edit_all_problem")
|
or user.has_perm("judge.edit_all_problem")
|
||||||
|
@ -393,13 +396,25 @@ class Problem(models.Model):
|
||||||
# Either not organization private or in the organization.
|
# Either not organization private or in the organization.
|
||||||
q &= Q(is_organization_private=False) | Q(
|
q &= Q(is_organization_private=False) | Q(
|
||||||
is_organization_private=True,
|
is_organization_private=True,
|
||||||
organizations__in=user.profile.organizations.all(),
|
organizations__in=profile.organizations.all(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Authors, curators, and testers should always have access, so OR at the very end.
|
# Authors, curators, and testers should always have access, so OR at the very end.
|
||||||
q |= Q(authors=user.profile)
|
filter = Exists(
|
||||||
q |= Q(curators=user.profile)
|
Problem.authors.through.objects.filter(
|
||||||
q |= Q(testers=user.profile)
|
problem=OuterRef("pk"), profile=profile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
filter |= Exists(
|
||||||
|
Problem.curators.through.objects.filter(
|
||||||
|
problem=OuterRef("pk"), profile=profile
|
||||||
|
)
|
||||||
|
)
|
||||||
|
filter |= Exists(
|
||||||
|
Problem.testers.through.objects.filter(
|
||||||
|
problem=OuterRef("pk"), profile=profile
|
||||||
|
)
|
||||||
|
)
|
||||||
queryset = queryset.filter(q)
|
queryset = queryset.filter(q)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
@ -432,6 +447,18 @@ class Problem(models.Model):
|
||||||
def usable_common_names(self):
|
def usable_common_names(self):
|
||||||
return set(self.usable_languages.values_list("common_name", flat=True))
|
return set(self.usable_languages.values_list("common_name", flat=True))
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def pagevote(self):
|
||||||
|
page = "p:%s" % self.code
|
||||||
|
pagevote, _ = PageVote.objects.get_or_create(page=page)
|
||||||
|
return pagevote
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def bookmark(self):
|
||||||
|
page = "p:%s" % self.code
|
||||||
|
bookmark, _ = BookMark.objects.get_or_create(page=page)
|
||||||
|
return bookmark
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def usable_languages(self):
|
def usable_languages(self):
|
||||||
return self.allowed_languages.filter(
|
return self.allowed_languages.filter(
|
||||||
|
@ -532,6 +559,10 @@ class Problem(models.Model):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
if self.pdf_description:
|
||||||
|
self.pdf_description.name = problem_directory_file_helper(
|
||||||
|
self.code, self.pdf_description.name
|
||||||
|
)
|
||||||
super(Problem, self).save(*args, **kwargs)
|
super(Problem, self).save(*args, **kwargs)
|
||||||
if self.code != self.__original_code:
|
if self.code != self.__original_code:
|
||||||
try:
|
try:
|
||||||
|
@ -632,7 +663,7 @@ class LanguageTemplate(models.Model):
|
||||||
class Solution(models.Model):
|
class Solution(models.Model):
|
||||||
problem = models.OneToOneField(
|
problem = models.OneToOneField(
|
||||||
Problem,
|
Problem,
|
||||||
on_delete=SET_NULL,
|
on_delete=CASCADE,
|
||||||
verbose_name=_("associated problem"),
|
verbose_name=_("associated problem"),
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
@ -642,6 +673,7 @@ class Solution(models.Model):
|
||||||
publish_on = models.DateTimeField(verbose_name=_("publish date"))
|
publish_on = models.DateTimeField(verbose_name=_("publish date"))
|
||||||
authors = models.ManyToManyField(Profile, verbose_name=_("authors"), blank=True)
|
authors = models.ManyToManyField(Profile, verbose_name=_("authors"), blank=True)
|
||||||
content = models.TextField(verbose_name=_("editorial content"))
|
content = models.TextField(verbose_name=_("editorial content"))
|
||||||
|
comments = GenericRelation("Comment")
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
problem = self.problem
|
problem = self.problem
|
||||||
|
|
|
@ -33,6 +33,10 @@ class Organization(models.Model):
|
||||||
max_length=128,
|
max_length=128,
|
||||||
verbose_name=_("organization slug"),
|
verbose_name=_("organization slug"),
|
||||||
help_text=_("Organization name shown in URL"),
|
help_text=_("Organization name shown in URL"),
|
||||||
|
unique=True,
|
||||||
|
validators=[
|
||||||
|
RegexValidator("^[-a-zA-Z0-9]+$", _("Only alphanumeric and hyphens"))
|
||||||
|
],
|
||||||
)
|
)
|
||||||
short_name = models.CharField(
|
short_name = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
|
@ -329,16 +333,24 @@ class Profile(models.Model):
|
||||||
def css_class(self):
|
def css_class(self):
|
||||||
return self.get_user_css_class(self.display_rank, self.rating)
|
return self.get_user_css_class(self.display_rank, self.rating)
|
||||||
|
|
||||||
def get_friends(self): # list of usernames, including you
|
def get_friends(self): # list of ids, including you
|
||||||
friend_obj = self.following_users.all()
|
friend_obj = self.following_users.prefetch_related("users")
|
||||||
ret = set()
|
ret = []
|
||||||
|
|
||||||
if friend_obj:
|
if friend_obj:
|
||||||
ret = set(friend.username for friend in friend_obj[0].users.all())
|
ret = [friend.id for friend in friend_obj[0].users.all()]
|
||||||
|
ret.append(self.id)
|
||||||
ret.add(self.username)
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def can_edit_organization(self, org):
|
||||||
|
if not self.user.is_authenticated:
|
||||||
|
return False
|
||||||
|
profile_id = self.id
|
||||||
|
return (
|
||||||
|
org.admins.filter(id=profile_id).exists()
|
||||||
|
or org.registrant_id == profile_id
|
||||||
|
or self.user.is_superuser
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
("test_site", "Shows in-progress development stuff"),
|
("test_site", "Shows in-progress development stuff"),
|
||||||
|
@ -381,7 +393,9 @@ class OrganizationRequest(models.Model):
|
||||||
class Friend(models.Model):
|
class Friend(models.Model):
|
||||||
users = models.ManyToManyField(Profile)
|
users = models.ManyToManyField(Profile)
|
||||||
current_user = models.ForeignKey(
|
current_user = models.ForeignKey(
|
||||||
Profile, related_name="following_users", on_delete=CASCADE
|
Profile,
|
||||||
|
related_name="following_users",
|
||||||
|
on_delete=CASCADE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -22,37 +22,48 @@ def get_pp_breakdown(user, start=0, end=settings.DMOJ_PP_ENTRIES):
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
SELECT max_points_table.problem_code,
|
SELECT submission.problem_code,
|
||||||
max_points_table.problem_name,
|
submission.problem_name,
|
||||||
max_points_table.max_points,
|
submission.max_points,
|
||||||
judge_submission.id,
|
submission.sub_id,
|
||||||
judge_submission.date,
|
submission.sub_date,
|
||||||
judge_submission.case_points,
|
submission.case_points,
|
||||||
judge_submission.case_total,
|
submission.case_total,
|
||||||
judge_submission.result,
|
submission.result,
|
||||||
judge_language.short_name,
|
judge_language.short_name,
|
||||||
judge_language.key
|
judge_language.key
|
||||||
FROM judge_submission
|
FROM
|
||||||
JOIN (SELECT judge_problem.id problem_id,
|
(SELECT max_points_table.problem_code problem_code,
|
||||||
judge_problem.name problem_name,
|
max_points_table.problem_name problem_name,
|
||||||
judge_problem.code problem_code,
|
max_points_table.max_points max_points,
|
||||||
MAX(judge_submission.points) AS max_points
|
judge_submission.id sub_id,
|
||||||
FROM judge_problem
|
judge_submission.date sub_date,
|
||||||
INNER JOIN judge_submission ON (judge_problem.id = judge_submission.problem_id)
|
judge_submission.case_points case_points,
|
||||||
WHERE (judge_problem.is_public = True AND
|
judge_submission.case_total case_total,
|
||||||
judge_problem.is_organization_private = False AND
|
judge_submission.result result,
|
||||||
judge_submission.points IS NOT NULL AND
|
judge_submission.language_id language_id
|
||||||
judge_submission.user_id = %s)
|
FROM judge_submission
|
||||||
GROUP BY judge_problem.id
|
JOIN (
|
||||||
HAVING MAX(judge_submission.points) > 0.0) AS max_points_table
|
SELECT judge_problem.id problem_id,
|
||||||
ON (judge_submission.problem_id = max_points_table.problem_id AND
|
judge_problem.name problem_name,
|
||||||
judge_submission.points = max_points_table.max_points AND
|
judge_problem.code problem_code,
|
||||||
judge_submission.user_id = %s)
|
MAX(judge_submission.points) AS max_points
|
||||||
|
FROM judge_problem
|
||||||
|
INNER JOIN judge_submission
|
||||||
|
ON (judge_problem.id = judge_submission.problem_id)
|
||||||
|
WHERE (judge_problem.is_public = True AND judge_problem.is_organization_private = False AND judge_submission.points IS NOT NULL AND judge_submission.user_id = %s)
|
||||||
|
GROUP BY judge_problem.id
|
||||||
|
HAVING MAX(judge_submission.points) > 0.0
|
||||||
|
) AS max_points_table
|
||||||
|
ON (judge_submission.problem_id = max_points_table.problem_id AND judge_submission.points = max_points_table.max_points AND judge_submission.user_id = %s)
|
||||||
|
GROUP BY max_points_table.problem_id
|
||||||
|
ORDER BY max_points DESC, judge_submission.date DESC
|
||||||
|
LIMIT %s
|
||||||
|
OFFSET %s
|
||||||
|
) AS submission
|
||||||
JOIN judge_language
|
JOIN judge_language
|
||||||
ON judge_submission.language_id = judge_language.id
|
ON submission.language_id = judge_language.id
|
||||||
GROUP BY max_points_table.problem_id
|
ORDER BY submission.max_points DESC, submission.sub_date DESC;
|
||||||
ORDER BY max_points DESC, judge_submission.date DESC
|
|
||||||
LIMIT %s OFFSET %s
|
|
||||||
""",
|
""",
|
||||||
(user.id, user.id, end - start + 1, start),
|
(user.id, user.id, end - start + 1, start),
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,6 +20,23 @@ def rescore_contest(self, contest_key):
|
||||||
self, participations.count(), stage=_("Recalculating contest scores")
|
self, participations.count(), stage=_("Recalculating contest scores")
|
||||||
) as p:
|
) as p:
|
||||||
for participation in participations.iterator():
|
for participation in participations.iterator():
|
||||||
|
for contest_submission in participation.submissions.iterator():
|
||||||
|
submission = contest_submission.submission
|
||||||
|
contest_problem = contest_submission.problem
|
||||||
|
contest_submission.points = round(
|
||||||
|
submission.case_points
|
||||||
|
/ submission.case_total
|
||||||
|
* contest_problem.points
|
||||||
|
if submission.case_total > 0
|
||||||
|
else 0,
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
not contest_problem.partial
|
||||||
|
and contest_submission.points != contest_problem.points
|
||||||
|
):
|
||||||
|
contest_submission.points = 0
|
||||||
|
contest_submission.save()
|
||||||
participation.recompute_results()
|
participation.recompute_results()
|
||||||
rescored += 1
|
rescored += 1
|
||||||
if rescored % 10 == 0:
|
if rescored % 10 == 0:
|
||||||
|
|
|
@ -191,6 +191,9 @@ class DiggPaginator(ExPaginator):
|
||||||
# validate padding value
|
# validate padding value
|
||||||
max_padding = int(math.ceil(self.body / 2.0) - 1)
|
max_padding = int(math.ceil(self.body / 2.0) - 1)
|
||||||
self.padding = kwargs.pop("padding", min(4, max_padding))
|
self.padding = kwargs.pop("padding", min(4, max_padding))
|
||||||
|
count_override = kwargs.pop("count", None)
|
||||||
|
if count_override is not None:
|
||||||
|
self.__dict__["count"] = count_override
|
||||||
if self.padding > max_padding:
|
if self.padding > max_padding:
|
||||||
raise ValueError("padding too large for body (max %d)" % max_padding)
|
raise ValueError("padding too large for body (max %d)" % max_padding)
|
||||||
super(DiggPaginator, self).__init__(*args, **kwargs)
|
super(DiggPaginator, self).__init__(*args, **kwargs)
|
||||||
|
|
152
judge/utils/infinite_paginator.py
Normal file
152
judge/utils/infinite_paginator.py
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import collections
|
||||||
|
import inspect
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
|
from django.core.paginator import EmptyPage, InvalidPage
|
||||||
|
from django.http import Http404
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.inspect import method_has_no_args
|
||||||
|
|
||||||
|
|
||||||
|
class InfinitePage(collections.abc.Sequence):
|
||||||
|
def __init__(
|
||||||
|
self, object_list, number, unfiltered_queryset, page_size, pad_pages, paginator
|
||||||
|
):
|
||||||
|
self.object_list = list(object_list)
|
||||||
|
self.number = number
|
||||||
|
self.unfiltered_queryset = unfiltered_queryset
|
||||||
|
self.page_size = page_size
|
||||||
|
self.pad_pages = pad_pages
|
||||||
|
self.num_pages = 1e3000
|
||||||
|
self.paginator = paginator
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Page %s of many>" % self.number
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.object_list)
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return self.object_list[index]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def _after_up_to_pad(self):
|
||||||
|
first_after = self.number * self.page_size
|
||||||
|
padding_length = self.pad_pages * self.page_size
|
||||||
|
queryset = self.unfiltered_queryset[
|
||||||
|
first_after : first_after + padding_length + 1
|
||||||
|
]
|
||||||
|
c = getattr(queryset, "count", None)
|
||||||
|
if callable(c) and not inspect.isbuiltin(c) and method_has_no_args(c):
|
||||||
|
return c()
|
||||||
|
return len(queryset)
|
||||||
|
|
||||||
|
def has_next(self):
|
||||||
|
return self._after_up_to_pad > 0
|
||||||
|
|
||||||
|
def has_previous(self):
|
||||||
|
return self.number > 1
|
||||||
|
|
||||||
|
def has_other_pages(self):
|
||||||
|
return self.has_previous() or self.has_next()
|
||||||
|
|
||||||
|
def next_page_number(self):
|
||||||
|
if not self.has_next():
|
||||||
|
raise EmptyPage()
|
||||||
|
return self.number + 1
|
||||||
|
|
||||||
|
def previous_page_number(self):
|
||||||
|
if self.number <= 1:
|
||||||
|
raise EmptyPage()
|
||||||
|
return self.number - 1
|
||||||
|
|
||||||
|
def start_index(self):
|
||||||
|
return (self.page_size * (self.number - 1)) + 1
|
||||||
|
|
||||||
|
def end_index(self):
|
||||||
|
return self.start_index() + len(self.object_list)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def main_range(self):
|
||||||
|
start = max(1, self.number - self.pad_pages)
|
||||||
|
end = self.number + min(
|
||||||
|
int(ceil(self._after_up_to_pad / self.page_size)), self.pad_pages
|
||||||
|
)
|
||||||
|
return range(start, end + 1)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def leading_range(self):
|
||||||
|
return range(1, min(3, self.main_range[0]))
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def has_trailing(self):
|
||||||
|
return self._after_up_to_pad > self.pad_pages * self.page_size
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def page_range(self):
|
||||||
|
result = list(self.leading_range)
|
||||||
|
main_range = self.main_range
|
||||||
|
|
||||||
|
# Add ... element if there is space in between.
|
||||||
|
if result and result[-1] + 1 < self.main_range[0]:
|
||||||
|
result.append(False)
|
||||||
|
|
||||||
|
result += list(main_range)
|
||||||
|
|
||||||
|
# Add ... element if there are elements after main_range.
|
||||||
|
if self.has_trailing:
|
||||||
|
result.append(False)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class DummyPaginator:
|
||||||
|
is_infinite = True
|
||||||
|
|
||||||
|
def __init__(self, per_page):
|
||||||
|
self.per_page = per_page
|
||||||
|
|
||||||
|
|
||||||
|
def infinite_paginate(queryset, page, page_size, pad_pages, paginator=None):
|
||||||
|
if page < 1:
|
||||||
|
raise EmptyPage()
|
||||||
|
sliced = queryset[(page - 1) * page_size : page * page_size]
|
||||||
|
if page > 1 and not sliced:
|
||||||
|
raise EmptyPage()
|
||||||
|
return InfinitePage(sliced, page, queryset, page_size, pad_pages, paginator)
|
||||||
|
|
||||||
|
|
||||||
|
class InfinitePaginationMixin:
|
||||||
|
pad_pages = 4
|
||||||
|
|
||||||
|
@property
|
||||||
|
def use_infinite_pagination(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def paginate_queryset(self, queryset, page_size):
|
||||||
|
if not self.use_infinite_pagination:
|
||||||
|
paginator, page, object_list, has_other = super().paginate_queryset(
|
||||||
|
queryset, page_size
|
||||||
|
)
|
||||||
|
paginator.is_infinite = False
|
||||||
|
return paginator, page, object_list, has_other
|
||||||
|
|
||||||
|
page_kwarg = self.page_kwarg
|
||||||
|
page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
|
||||||
|
try:
|
||||||
|
page_number = int(page)
|
||||||
|
except ValueError:
|
||||||
|
raise Http404("Page cannot be converted to an int.")
|
||||||
|
try:
|
||||||
|
paginator = DummyPaginator(page_size)
|
||||||
|
page = infinite_paginate(
|
||||||
|
queryset, page_number, page_size, self.pad_pages, paginator
|
||||||
|
)
|
||||||
|
return paginator, page, page.object_list, page.has_other_pages()
|
||||||
|
except InvalidPage as e:
|
||||||
|
raise Http404(
|
||||||
|
"Invalid page (%(page_number)s): %(message)s"
|
||||||
|
% {
|
||||||
|
"page_number": page_number,
|
||||||
|
"message": str(e),
|
||||||
|
}
|
||||||
|
)
|
|
@ -1,6 +1,8 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from math import e
|
from math import e
|
||||||
import os, zipfile
|
import os, zipfile
|
||||||
|
from datetime import datetime
|
||||||
|
import random
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
@ -10,19 +12,24 @@ from django.utils import timezone
|
||||||
from django.utils.translation import gettext as _, gettext_noop
|
from django.utils.translation import gettext as _, gettext_noop
|
||||||
|
|
||||||
from judge.models import Problem, Submission
|
from judge.models import Problem, Submission
|
||||||
|
from judge.ml.collab_filter import CollabFilter
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"contest_completed_ids",
|
"contest_completed_ids",
|
||||||
"get_result_data",
|
"get_result_data",
|
||||||
"user_completed_ids",
|
"user_completed_ids",
|
||||||
"user_authored_ids",
|
|
||||||
"user_editable_ids",
|
"user_editable_ids",
|
||||||
|
"user_tester_ids",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def user_authored_ids(profile):
|
def user_tester_ids(profile):
|
||||||
result = set(Problem.objects.filter(authors=profile).values_list("id", flat=True))
|
return set(
|
||||||
return result
|
Problem.testers.through.objects.filter(profile=profile).values_list(
|
||||||
|
"problem_id", flat=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def user_editable_ids(profile):
|
def user_editable_ids(profile):
|
||||||
|
@ -229,3 +236,26 @@ def hot_problems(duration, limit):
|
||||||
|
|
||||||
cache.set(cache_key, qs, 900)
|
cache.set(cache_key, qs, 900)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
def get_related_problems(profile, problem, limit=8):
|
||||||
|
if not profile or not settings.ML_OUTPUT_PATH:
|
||||||
|
return None
|
||||||
|
cache_key = "related_problems:%d:%d" % (profile.id, problem.id)
|
||||||
|
qs = cache.get(cache_key)
|
||||||
|
if qs is not None:
|
||||||
|
return qs
|
||||||
|
problemset = Problem.get_visible_problems(profile.user).values_list("id", flat=True)
|
||||||
|
problemset = problemset.exclude(id__in=user_completed_ids(profile))
|
||||||
|
problemset = problemset.exclude(id=problem.id)
|
||||||
|
cf_model = CollabFilter("collab_filter")
|
||||||
|
results = cf_model.problem_neighbors(
|
||||||
|
problem, problemset, CollabFilter.DOT, limit
|
||||||
|
) + cf_model.problem_neighbors(problem, problemset, CollabFilter.COSINE, limit)
|
||||||
|
results = list(set([i[1] for i in results]))
|
||||||
|
seed = datetime.now().strftime("%d%m%Y")
|
||||||
|
random.Random(seed).shuffle(results)
|
||||||
|
results = results[:limit]
|
||||||
|
results = [Problem.objects.get(id=i) for i in results]
|
||||||
|
cache.set(cache_key, results, 21600)
|
||||||
|
return results
|
||||||
|
|
|
@ -7,8 +7,8 @@ from django.utils.translation import ugettext as _
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
|
|
||||||
from judge.comments import CommentedDetailView
|
from judge.comments import CommentedDetailView
|
||||||
from judge.views.pagevote import PageVoteDetailView, PageVoteListView
|
from judge.views.pagevote import PageVoteDetailView
|
||||||
from judge.views.bookmark import BookMarkDetailView, BookMarkListView
|
from judge.views.bookmark import BookMarkDetailView
|
||||||
from judge.models import (
|
from judge.models import (
|
||||||
BlogPost,
|
BlogPost,
|
||||||
Comment,
|
Comment,
|
||||||
|
@ -26,28 +26,16 @@ from judge.utils.diggpaginator import DiggPaginator
|
||||||
from judge.utils.problems import user_completed_ids
|
from judge.utils.problems import user_completed_ids
|
||||||
from judge.utils.tickets import filter_visible_tickets
|
from judge.utils.tickets import filter_visible_tickets
|
||||||
from judge.utils.views import TitleMixin
|
from judge.utils.views import TitleMixin
|
||||||
|
from judge.views.feed import FeedView
|
||||||
|
|
||||||
|
|
||||||
# General view for all content list on home feed
|
# General view for all content list on home feed
|
||||||
class FeedView(ListView):
|
class HomeFeedView(FeedView):
|
||||||
template_name = "blog/list.html"
|
template_name = "blog/list.html"
|
||||||
title = None
|
title = None
|
||||||
|
|
||||||
def get_paginator(
|
|
||||||
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
|
|
||||||
):
|
|
||||||
return DiggPaginator(
|
|
||||||
queryset,
|
|
||||||
per_page,
|
|
||||||
body=6,
|
|
||||||
padding=2,
|
|
||||||
orphans=orphans,
|
|
||||||
allow_empty_first_page=allow_empty_first_page,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(FeedView, self).get_context_data(**kwargs)
|
context = super(HomeFeedView, self).get_context_data(**kwargs)
|
||||||
context["has_clarifications"] = False
|
context["has_clarifications"] = False
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
participation = self.request.profile.current_contest
|
participation = self.request.profile.current_contest
|
||||||
|
@ -60,22 +48,16 @@ class FeedView(ListView):
|
||||||
if participation.contest.is_editable_by(self.request.user):
|
if participation.contest.is_editable_by(self.request.user):
|
||||||
context["can_edit_contest"] = True
|
context["can_edit_contest"] = True
|
||||||
|
|
||||||
context["page_titles"] = CacheDict(lambda page: Comment.get_page_title(page))
|
|
||||||
|
|
||||||
context["user_count"] = lazy(Profile.objects.count, int, int)
|
|
||||||
context["problem_count"] = lazy(
|
|
||||||
Problem.objects.filter(is_public=True).count, int, int
|
|
||||||
)
|
|
||||||
context["submission_count"] = lazy(Submission.objects.count, int, int)
|
|
||||||
context["language_count"] = lazy(Language.objects.count, int, int)
|
|
||||||
|
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
|
||||||
visible_contests = (
|
visible_contests = (
|
||||||
Contest.get_visible_contests(self.request.user, show_own_contests_only=True)
|
Contest.get_visible_contests(self.request.user, show_own_contests_only=True)
|
||||||
.filter(is_visible=True)
|
.filter(is_visible=True)
|
||||||
.order_by("start_time")
|
.order_by("start_time")
|
||||||
)
|
)
|
||||||
|
if self.request.organization:
|
||||||
|
visible_contests = visible_contests.filter(
|
||||||
|
is_organization_private=True, organizations=self.request.organization
|
||||||
|
)
|
||||||
|
|
||||||
context["current_contests"] = visible_contests.filter(
|
context["current_contests"] = visible_contests.filter(
|
||||||
start_time__lte=now, end_time__gt=now
|
start_time__lte=now, end_time__gt=now
|
||||||
|
@ -84,20 +66,26 @@ class FeedView(ListView):
|
||||||
context[
|
context[
|
||||||
"recent_organizations"
|
"recent_organizations"
|
||||||
] = OrganizationProfile.get_most_recent_organizations(self.request.profile)
|
] = OrganizationProfile.get_most_recent_organizations(self.request.profile)
|
||||||
context["top_rated"] = Profile.objects.filter(is_unlisted=False).order_by(
|
|
||||||
|
profile_queryset = Profile.objects
|
||||||
|
if self.request.organization:
|
||||||
|
profile_queryset = self.request.organization.members
|
||||||
|
context["top_rated"] = profile_queryset.filter(is_unlisted=False).order_by(
|
||||||
"-rating"
|
"-rating"
|
||||||
)[:10]
|
)[:10]
|
||||||
context["top_scorer"] = Profile.objects.filter(is_unlisted=False).order_by(
|
context["top_scorer"] = profile_queryset.filter(is_unlisted=False).order_by(
|
||||||
"-performance_points"
|
"-performance_points"
|
||||||
)[:10]
|
)[:10]
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class PostList(FeedView, PageVoteListView, BookMarkListView):
|
class PostList(HomeFeedView):
|
||||||
model = BlogPost
|
model = BlogPost
|
||||||
paginate_by = 10
|
paginate_by = 4
|
||||||
context_object_name = "posts"
|
context_object_name = "posts"
|
||||||
|
feed_content_template_name = "blog/content.html"
|
||||||
|
url_name = "blog_post_list"
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = (
|
queryset = (
|
||||||
|
@ -108,6 +96,8 @@ class PostList(FeedView, PageVoteListView, BookMarkListView):
|
||||||
filter = Q(is_organization_private=False)
|
filter = Q(is_organization_private=False)
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
filter |= Q(organizations__in=self.request.profile.organizations.all())
|
filter |= Q(organizations__in=self.request.profile.organizations.all())
|
||||||
|
if self.request.organization:
|
||||||
|
filter &= Q(organizations=self.request.organization)
|
||||||
queryset = queryset.filter(filter)
|
queryset = queryset.filter(filter)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -116,30 +106,18 @@ class PostList(FeedView, PageVoteListView, BookMarkListView):
|
||||||
context["title"] = (
|
context["title"] = (
|
||||||
self.title or _("Page %d of Posts") % context["page_obj"].number
|
self.title or _("Page %d of Posts") % context["page_obj"].number
|
||||||
)
|
)
|
||||||
context["first_page_href"] = reverse("home")
|
|
||||||
context["page_prefix"] = reverse("blog_post_list")
|
|
||||||
context["page_type"] = "blog"
|
context["page_type"] = "blog"
|
||||||
context["post_comment_counts"] = {
|
|
||||||
int(page[2:]): count
|
|
||||||
for page, count in Comment.objects.filter(
|
|
||||||
page__in=["b:%d" % post.id for post in context["posts"]], hidden=False
|
|
||||||
)
|
|
||||||
.values_list("page")
|
|
||||||
.annotate(count=Count("page"))
|
|
||||||
.order_by()
|
|
||||||
}
|
|
||||||
context = self.add_pagevote_context_data(context)
|
|
||||||
context = self.add_bookmark_context_data(context)
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_comment_page(self, post):
|
def get_comment_page(self, post):
|
||||||
return "b:%s" % post.id
|
return "b:%s" % post.id
|
||||||
|
|
||||||
|
|
||||||
class TicketFeed(FeedView):
|
class TicketFeed(HomeFeedView):
|
||||||
model = Ticket
|
model = Ticket
|
||||||
context_object_name = "tickets"
|
context_object_name = "tickets"
|
||||||
paginate_by = 30
|
paginate_by = 8
|
||||||
|
feed_content_template_name = "ticket/feed.html"
|
||||||
|
|
||||||
def get_queryset(self, is_own=True):
|
def get_queryset(self, is_own=True):
|
||||||
profile = self.request.profile
|
profile = self.request.profile
|
||||||
|
@ -171,28 +149,25 @@ class TicketFeed(FeedView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(TicketFeed, self).get_context_data(**kwargs)
|
context = super(TicketFeed, self).get_context_data(**kwargs)
|
||||||
context["page_type"] = "ticket"
|
context["page_type"] = "ticket"
|
||||||
context["first_page_href"] = self.request.path
|
|
||||||
context["page_prefix"] = "?page="
|
|
||||||
context["title"] = _("Ticket feed")
|
context["title"] = _("Ticket feed")
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class CommentFeed(FeedView):
|
class CommentFeed(HomeFeedView):
|
||||||
model = Comment
|
model = Comment
|
||||||
context_object_name = "comments"
|
context_object_name = "comments"
|
||||||
paginate_by = 50
|
paginate_by = 15
|
||||||
|
feed_content_template_name = "comments/feed.html"
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Comment.most_recent(self.request.user, 1000)
|
return Comment.most_recent(
|
||||||
|
self.request.user, 100, organization=self.request.organization
|
||||||
|
)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(CommentFeed, self).get_context_data(**kwargs)
|
context = super(CommentFeed, self).get_context_data(**kwargs)
|
||||||
context["page_type"] = "comment"
|
|
||||||
context["first_page_href"] = self.request.path
|
|
||||||
context["page_prefix"] = "?page="
|
|
||||||
context["title"] = _("Comment feed")
|
context["title"] = _("Comment feed")
|
||||||
|
context["page_type"] = "comment"
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -74,13 +74,3 @@ class BookMarkDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
||||||
queryset = BookMark.objects.get_or_create(page=self.get_comment_page())
|
queryset = BookMark.objects.get_or_create(page=self.get_comment_page())
|
||||||
context["bookmark"] = queryset[0]
|
context["bookmark"] = queryset[0]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class BookMarkListView:
|
|
||||||
def add_bookmark_context_data(self, context, obj_list="object_list"):
|
|
||||||
for item in context[obj_list]:
|
|
||||||
bookmark, _ = BookMark.objects.get_or_create(
|
|
||||||
page=self.get_comment_page(item)
|
|
||||||
)
|
|
||||||
setattr(item, "bookmark", bookmark)
|
|
||||||
return context
|
|
||||||
|
|
|
@ -149,16 +149,22 @@ class ContestList(
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.contest_query = None
|
self.contest_query = None
|
||||||
self.org_query = []
|
self.org_query = []
|
||||||
|
self.show_orgs = 0
|
||||||
|
if request.GET.get("show_orgs"):
|
||||||
|
self.show_orgs = 1
|
||||||
|
|
||||||
if "orgs" in self.request.GET and self.request.profile:
|
if "orgs" in self.request.GET 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")))
|
||||||
self.org_query = [
|
if not self.request.user.is_superuser:
|
||||||
i
|
self.org_query = [
|
||||||
for i in self.org_query
|
i
|
||||||
if i
|
for i in self.org_query
|
||||||
in self.request.profile.organizations.values_list("id", flat=True)
|
if i
|
||||||
]
|
in self.request.profile.organizations.values_list(
|
||||||
|
"id", flat=True
|
||||||
|
)
|
||||||
|
]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -179,6 +185,10 @@ class ContestList(
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
Q(key__icontains=query) | Q(name__icontains=query)
|
Q(key__icontains=query) | Q(name__icontains=query)
|
||||||
)
|
)
|
||||||
|
if not self.org_query and self.request.organization:
|
||||||
|
self.org_query = [self.request.organization.id]
|
||||||
|
if self.show_orgs:
|
||||||
|
queryset = queryset.filter(organizations=None)
|
||||||
if self.org_query:
|
if self.org_query:
|
||||||
queryset = queryset.filter(organizations__in=self.org_query)
|
queryset = queryset.filter(organizations__in=self.org_query)
|
||||||
|
|
||||||
|
@ -225,8 +235,12 @@ class ContestList(
|
||||||
context["first_page_href"] = "."
|
context["first_page_href"] = "."
|
||||||
context["contest_query"] = self.contest_query
|
context["contest_query"] = self.contest_query
|
||||||
context["org_query"] = self.org_query
|
context["org_query"] = self.org_query
|
||||||
|
context["show_orgs"] = int(self.show_orgs)
|
||||||
if self.request.profile:
|
if self.request.profile:
|
||||||
context["organizations"] = self.request.profile.organizations.all()
|
if self.request.user.is_superuser:
|
||||||
|
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())
|
||||||
|
@ -404,6 +418,15 @@ class ContestDetail(
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.object.name
|
return self.object.name
|
||||||
|
|
||||||
|
def get_editable_organizations(self):
|
||||||
|
if not self.request.profile:
|
||||||
|
return []
|
||||||
|
res = []
|
||||||
|
for organization in self.object.organizations.all():
|
||||||
|
if self.request.profile.can_edit_organization(organization):
|
||||||
|
res.append(organization)
|
||||||
|
return res
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ContestDetail, self).get_context_data(**kwargs)
|
context = super(ContestDetail, self).get_context_data(**kwargs)
|
||||||
context["contest_problems"] = (
|
context["contest_problems"] = (
|
||||||
|
@ -421,6 +444,7 @@ class ContestDetail(
|
||||||
)
|
)
|
||||||
.add_i18n_name(self.request.LANGUAGE_CODE)
|
.add_i18n_name(self.request.LANGUAGE_CODE)
|
||||||
)
|
)
|
||||||
|
context["editable_organizations"] = self.get_editable_organizations()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@ -1002,8 +1026,8 @@ def contest_ranking_ajax(request, contest, participation=None):
|
||||||
|
|
||||||
queryset = contest.users.filter(virtual__gte=0)
|
queryset = contest.users.filter(virtual__gte=0)
|
||||||
if request.GET.get("friend") == "true" and request.profile:
|
if request.GET.get("friend") == "true" and request.profile:
|
||||||
friends = list(request.profile.get_friends())
|
friends = request.profile.get_friends()
|
||||||
queryset = queryset.filter(user__user__username__in=friends)
|
queryset = queryset.filter(user_id__in=friends)
|
||||||
if request.GET.get("virtual") != "true":
|
if request.GET.get("virtual") != "true":
|
||||||
queryset = queryset.filter(virtual=0)
|
queryset = queryset.filter(virtual=0)
|
||||||
|
|
||||||
|
@ -1085,9 +1109,8 @@ class ContestFinalRanking(LoginRequiredMixin, ContestRanking):
|
||||||
def get_ranking_list(self):
|
def get_ranking_list(self):
|
||||||
if not self.object.is_editable_by(self.request.user):
|
if not self.object.is_editable_by(self.request.user):
|
||||||
raise Http404()
|
raise Http404()
|
||||||
if self.object.format.has_hidden_subtasks:
|
if not self.object.format.has_hidden_subtasks:
|
||||||
raise Http404()
|
raise Http404()
|
||||||
|
|
||||||
return get_contest_ranking_list(self.request, self.object, show_final=True)
|
return get_contest_ranking_list(self.request, self.object, show_final=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
214
judge/views/course.py
Normal file
214
judge/views/course.py
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
from django.forms import ModelForm
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.views.generic.edit import UpdateView
|
||||||
|
from judge.models.course import Course, CourseResource
|
||||||
|
from django.views.generic import ListView, UpdateView, DetailView
|
||||||
|
from judge.views.feed import FeedView
|
||||||
|
from django.http import (
|
||||||
|
Http404,
|
||||||
|
HttpResponsePermanentRedirect,
|
||||||
|
HttpResponseRedirect,
|
||||||
|
)
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from judge.utils.views import (
|
||||||
|
generic_message,
|
||||||
|
)
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"CourseList",
|
||||||
|
"CourseDetail",
|
||||||
|
"CourseResource",
|
||||||
|
"CourseResourceDetail",
|
||||||
|
"CourseStudentResults",
|
||||||
|
"CourseEdit",
|
||||||
|
"CourseResourceDetailEdit",
|
||||||
|
"CourseResourceEdit",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CourseBase(object):
|
||||||
|
def is_editable_by(self, course=None):
|
||||||
|
if course is None:
|
||||||
|
course = self.object
|
||||||
|
if self.request.profile:
|
||||||
|
return Course.is_editable_by(course, self.request.profile)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_accessible_by(self, course):
|
||||||
|
if course is None:
|
||||||
|
course = self.object
|
||||||
|
if self.request.profile:
|
||||||
|
return Course.is_accessible_by(course, self.request.profile)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class CourseMixin(CourseBase):
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context["can_edit"] = self.is_editable_by(self.course)
|
||||||
|
context["can access"] = self.is_accessible_by(self.course)
|
||||||
|
context["course"] = self.course
|
||||||
|
return context
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
print(self)
|
||||||
|
try:
|
||||||
|
self.course_id = int(kwargs["pk"])
|
||||||
|
self.course = get_object_or_404(Course, id=self.course_id)
|
||||||
|
except Http404:
|
||||||
|
key = None
|
||||||
|
if hasattr(self, "slug_url_kwarg"):
|
||||||
|
key = kwargs.get(self.slug_url_kwarg, None)
|
||||||
|
if key:
|
||||||
|
return generic_message(
|
||||||
|
request,
|
||||||
|
_("No such course"),
|
||||||
|
_('Could not find a course with the key "%s".') % key,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return generic_message(
|
||||||
|
request,
|
||||||
|
_("No such course"),
|
||||||
|
_("Could not find such course."),
|
||||||
|
)
|
||||||
|
if self.course.slug != kwargs["slug"]:
|
||||||
|
return HttpResponsePermanentRedirect(
|
||||||
|
request.get_full_path().replace(kwargs["slug"], self.course.slug)
|
||||||
|
)
|
||||||
|
|
||||||
|
return super(CourseMixin, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CourseHomeView(CourseMixin):
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
if not hasattr(self, "course"):
|
||||||
|
self.course = self.object
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class CourseHome(CourseHomeView, FeedView):
|
||||||
|
template_name = "course/home.html"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return CourseResource.objects.filter(
|
||||||
|
is_public=True,
|
||||||
|
course=self.course,
|
||||||
|
).order_by("order")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CourseHome, self).get_context_data(**kwargs)
|
||||||
|
context["title"] = self.course.name
|
||||||
|
context["description"] = self.course.about
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class CourseResourceList(CourseMixin, ListView):
|
||||||
|
template_name = "course/resource.html"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return CourseResource.objects.filter(
|
||||||
|
is_public=True,
|
||||||
|
course=self.course,
|
||||||
|
).order_by("order")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CourseResourceList, self).get_context_data(**kwargs)
|
||||||
|
context["title"] = self.course.name
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class CourseResouceDetail(DetailView):
|
||||||
|
template_name = "course/resource-content.html"
|
||||||
|
model = CourseResource
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CourseResouceDetail, self).get_context_data(**kwargs)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class CourseAdminMixin(CourseMixin):
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
res = super(CourseAdminMixin, self).dispatch(request, *args, **kwargs)
|
||||||
|
if not hasattr(self, "course") or self.is_editable_by(self.course):
|
||||||
|
return res
|
||||||
|
return generic_message(
|
||||||
|
request,
|
||||||
|
_("Can't edit course"),
|
||||||
|
_("You are not allowed to edit this course."),
|
||||||
|
status=403,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CourseResourceDetailEditForm(ModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(CourseResourceDetailEditForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CourseResourceDetailEdit(LoginRequiredMixin, UpdateView):
|
||||||
|
template_name = "course/resource_detail_edit.html"
|
||||||
|
model = CourseResource
|
||||||
|
fields = ["description", "files", "is_public"]
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return self.request.get_full_path()
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.save()
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class CourseResourceEdit(CourseMixin, LoginRequiredMixin, ListView):
|
||||||
|
template_name = "course/resource_edit.html"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return CourseResource.objects.filter(
|
||||||
|
course=self.course,
|
||||||
|
).order_by("order")
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
for resource in queryset:
|
||||||
|
if request.POST.get("resource-" + str(resource.pk) + "-delete") != None:
|
||||||
|
resource.delete()
|
||||||
|
else:
|
||||||
|
if request.POST.get("resource-" + str(resource.pk) + "-public") != None:
|
||||||
|
resource.is_public = True
|
||||||
|
else:
|
||||||
|
resource.is_public = False
|
||||||
|
resource.order = request.POST.get(
|
||||||
|
"resource-" + str(resource.pk) + "-order"
|
||||||
|
)
|
||||||
|
resource.save()
|
||||||
|
return HttpResponseRedirect(request.path)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
return super(CourseResourceEdit, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CourseListMixin(object):
|
||||||
|
def get_queryset(self):
|
||||||
|
return Course.objects.filter(is_open="true").values()
|
||||||
|
|
||||||
|
|
||||||
|
class CourseList(ListView):
|
||||||
|
model = Course
|
||||||
|
template_name = "course/list.html"
|
||||||
|
queryset = Course.objects.filter(is_public=True).filter(is_open=True)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CourseList, self).get_context_data(**kwargs)
|
||||||
|
available, enrolling = [], []
|
||||||
|
for course in Course.objects.filter(is_public=True).filter(is_open=True):
|
||||||
|
if Course.is_accessible_by(course, self.request.profile):
|
||||||
|
enrolling.append(course)
|
||||||
|
else:
|
||||||
|
available.append(course)
|
||||||
|
context["available"] = available
|
||||||
|
context["enrolling"] = enrolling
|
||||||
|
return context
|
34
judge/views/feed.py
Normal file
34
judge/views/feed.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
from django.views.generic import ListView
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from judge.utils.infinite_paginator import InfinitePaginationMixin
|
||||||
|
|
||||||
|
|
||||||
|
class FeedView(InfinitePaginationMixin, ListView):
|
||||||
|
def get_feed_context(selfl, object_list):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
only_content = request.GET.get("only_content", None)
|
||||||
|
if only_content and self.feed_content_template_name:
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
paginator, page, object_list, _ = self.paginate_queryset(
|
||||||
|
queryset, self.paginate_by
|
||||||
|
)
|
||||||
|
context = {
|
||||||
|
self.context_object_name: object_list,
|
||||||
|
"has_next_page": page.has_next(),
|
||||||
|
}
|
||||||
|
context.update(self.get_feed_context(object_list))
|
||||||
|
return render(request, self.feed_content_template_name, context)
|
||||||
|
|
||||||
|
return super(FeedView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
try:
|
||||||
|
context["feed_content_url"] = reverse(self.url_name)
|
||||||
|
except Exception as e:
|
||||||
|
context["feed_content_url"] = self.request.path
|
||||||
|
return context
|
|
@ -42,7 +42,6 @@ class NotificationList(ListView):
|
||||||
context["unseen_count"] = self.unseen_cnt
|
context["unseen_count"] = self.unseen_cnt
|
||||||
context["title"] = _("Notifications (%d unseen)" % context["unseen_count"])
|
context["title"] = _("Notifications (%d unseen)" % context["unseen_count"])
|
||||||
context["has_notifications"] = self.queryset.exists()
|
context["has_notifications"] = self.queryset.exists()
|
||||||
context["page_titles"] = CacheDict(lambda page: Comment.get_page_title(page))
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
|
@ -35,6 +35,7 @@ from django.views.generic.detail import (
|
||||||
SingleObjectTemplateResponseMixin,
|
SingleObjectTemplateResponseMixin,
|
||||||
)
|
)
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
from django.contrib.sites.shortcuts import get_current_site
|
||||||
from reversion import revisions
|
from reversion import revisions
|
||||||
|
|
||||||
from judge.forms import (
|
from judge.forms import (
|
||||||
|
@ -71,8 +72,7 @@ from judge.utils.problems import user_attempted_ids, user_completed_ids
|
||||||
from judge.views.problem import ProblemList
|
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.pagevote import PageVoteListView
|
from judge.views.feed import FeedView
|
||||||
from judge.views.bookmark import BookMarkListView
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"OrganizationList",
|
"OrganizationList",
|
||||||
|
@ -96,14 +96,9 @@ class OrganizationBase(object):
|
||||||
def can_edit_organization(self, org=None):
|
def can_edit_organization(self, org=None):
|
||||||
if org is None:
|
if org is None:
|
||||||
org = self.object
|
org = self.object
|
||||||
if not self.request.user.is_authenticated:
|
if self.request.profile:
|
||||||
return False
|
return self.request.profile.can_edit_organization(org)
|
||||||
profile_id = self.request.profile.id
|
return False
|
||||||
return (
|
|
||||||
org.admins.filter(id=profile_id).exists()
|
|
||||||
or org.registrant_id == profile_id
|
|
||||||
or self.request.user.is_superuser
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_member(self, org=None):
|
def is_member(self, org=None):
|
||||||
if org is None:
|
if org is None:
|
||||||
|
@ -112,6 +107,13 @@ class OrganizationBase(object):
|
||||||
self.request.profile in org if self.request.user.is_authenticated else False
|
self.request.profile in org if self.request.user.is_authenticated else False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_admin(self, org=None):
|
||||||
|
if org is None:
|
||||||
|
org = self.object
|
||||||
|
if self.request.profile:
|
||||||
|
return org.admins.filter(id=self.request.profile.id).exists()
|
||||||
|
return False
|
||||||
|
|
||||||
def can_access(self, org):
|
def can_access(self, org):
|
||||||
if self.request.user.is_superuser:
|
if self.request.user.is_superuser:
|
||||||
return True
|
return True
|
||||||
|
@ -124,6 +126,7 @@ class OrganizationMixin(OrganizationBase):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["is_member"] = self.is_member(self.organization)
|
context["is_member"] = self.is_member(self.organization)
|
||||||
|
context["is_admin"] = self.is_admin(self.organization)
|
||||||
context["can_edit"] = self.can_edit_organization(self.organization)
|
context["can_edit"] = self.can_edit_organization(self.organization)
|
||||||
context["organization"] = self.organization
|
context["organization"] = self.organization
|
||||||
context["logo_override_image"] = self.organization.logo_override_image
|
context["logo_override_image"] = self.organization.logo_override_image
|
||||||
|
@ -136,7 +139,9 @@ class OrganizationMixin(OrganizationBase):
|
||||||
self.organization_id = int(kwargs["pk"])
|
self.organization_id = int(kwargs["pk"])
|
||||||
self.organization = get_object_or_404(Organization, id=self.organization_id)
|
self.organization = get_object_or_404(Organization, id=self.organization_id)
|
||||||
except Http404:
|
except Http404:
|
||||||
key = kwargs.get(self.slug_url_kwarg, None)
|
key = None
|
||||||
|
if hasattr(self, "slug_url_kwarg"):
|
||||||
|
key = kwargs.get(self.slug_url_kwarg, None)
|
||||||
if key:
|
if key:
|
||||||
return generic_message(
|
return generic_message(
|
||||||
request,
|
request,
|
||||||
|
@ -189,7 +194,7 @@ class MemberOrganizationMixin(OrganizationMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OrganizationHomeViewContext:
|
class OrganizationHomeView(OrganizationMixin):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if not hasattr(self, "organization"):
|
if not hasattr(self, "organization"):
|
||||||
|
@ -216,28 +221,6 @@ class OrganizationHomeViewContext:
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class OrganizationDetailView(
|
|
||||||
OrganizationMixin, OrganizationHomeViewContext, DetailView
|
|
||||||
):
|
|
||||||
context_object_name = "organization"
|
|
||||||
model = Organization
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
self.object = self.get_object()
|
|
||||||
if self.object.slug != kwargs["slug"]:
|
|
||||||
return HttpResponsePermanentRedirect(
|
|
||||||
request.get_full_path().replace(kwargs["slug"], self.object.slug)
|
|
||||||
)
|
|
||||||
context = self.get_context_data(object=self.object)
|
|
||||||
return self.render_to_response(context)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
context["can_edit"] = self.can_edit_organization()
|
|
||||||
context["is_member"] = self.is_member()
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizationList(TitleMixin, ListView, OrganizationBase):
|
class OrganizationList(TitleMixin, ListView, OrganizationBase):
|
||||||
model = Organization
|
model = Organization
|
||||||
context_object_name = "organizations"
|
context_object_name = "organizations"
|
||||||
|
@ -267,58 +250,46 @@ class OrganizationList(TitleMixin, ListView, OrganizationBase):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class OrganizationHome(OrganizationDetailView, PageVoteListView, BookMarkListView):
|
class OrganizationHome(OrganizationHomeView, FeedView):
|
||||||
template_name = "organization/home.html"
|
template_name = "organization/home.html"
|
||||||
pagevote_object_name = "posts"
|
paginate_by = 4
|
||||||
|
context_object_name = "posts"
|
||||||
|
feed_content_template_name = "blog/content.html"
|
||||||
|
|
||||||
def get_posts_and_page_obj(self):
|
def get_queryset(self):
|
||||||
posts = (
|
return (
|
||||||
BlogPost.objects.filter(
|
BlogPost.objects.filter(
|
||||||
visible=True,
|
visible=True,
|
||||||
publish_on__lte=timezone.now(),
|
publish_on__lte=timezone.now(),
|
||||||
is_organization_private=True,
|
is_organization_private=True,
|
||||||
organizations=self.object,
|
organizations=self.organization,
|
||||||
)
|
)
|
||||||
.order_by("-sticky", "-publish_on")
|
.order_by("-sticky", "-publish_on")
|
||||||
.prefetch_related("authors__user", "organizations")
|
.prefetch_related("authors__user", "organizations")
|
||||||
)
|
)
|
||||||
paginator = Paginator(posts, 10)
|
|
||||||
page_number = self.request.GET.get("page", 1)
|
|
||||||
posts = paginator.get_page(page_number)
|
|
||||||
return posts, paginator.page(page_number)
|
|
||||||
|
|
||||||
def get_comment_page(self, post):
|
def get_comment_page(self, post):
|
||||||
return "b:%s" % post.id
|
return "b:%s" % post.id
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(OrganizationHome, self).get_context_data(**kwargs)
|
context = super(OrganizationHome, self).get_context_data(**kwargs)
|
||||||
context["title"] = self.object.name
|
context["title"] = self.organization.name
|
||||||
context["posts"], context["page_obj"] = self.get_posts_and_page_obj()
|
http = "http" if settings.DMOJ_SSL == 0 else "https"
|
||||||
context = self.add_pagevote_context_data(context, "posts")
|
context["organization_subdomain"] = (
|
||||||
context = self.add_bookmark_context_data(context, "posts")
|
http
|
||||||
|
+ "://"
|
||||||
# Hack: This allows page_obj to have page_range for non-ListView class
|
+ self.organization.slug
|
||||||
setattr(
|
+ "."
|
||||||
context["page_obj"], "page_range", context["posts"].paginator.page_range
|
+ get_current_site(self.request).domain
|
||||||
)
|
)
|
||||||
context["first_page_href"] = self.request.path
|
|
||||||
context["page_prefix"] = "?page="
|
|
||||||
|
|
||||||
context["post_comment_counts"] = {
|
|
||||||
int(page[2:]): count
|
|
||||||
for page, count in Comment.objects.filter(
|
|
||||||
page__in=["b:%d" % post.id for post in context["posts"]], hidden=False
|
|
||||||
)
|
|
||||||
.values_list("page")
|
|
||||||
.annotate(count=Count("page"))
|
|
||||||
.order_by()
|
|
||||||
}
|
|
||||||
|
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
visible_contests = (
|
visible_contests = (
|
||||||
Contest.get_visible_contests(self.request.user)
|
Contest.get_visible_contests(self.request.user)
|
||||||
.filter(
|
.filter(
|
||||||
is_visible=True, is_organization_private=True, organizations=self.object
|
is_visible=True,
|
||||||
|
is_organization_private=True,
|
||||||
|
organizations=self.organization,
|
||||||
)
|
)
|
||||||
.order_by("start_time")
|
.order_by("start_time")
|
||||||
)
|
)
|
||||||
|
@ -331,11 +302,27 @@ class OrganizationHome(OrganizationDetailView, PageVoteListView, BookMarkListVie
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView):
|
class OrganizationUsers(QueryStringSortMixin, OrganizationMixin, FeedView):
|
||||||
template_name = "organization/users.html"
|
template_name = "organization/users.html"
|
||||||
all_sorts = frozenset(("points", "problem_count", "rating", "performance_points"))
|
all_sorts = frozenset(("points", "problem_count", "rating", "performance_points"))
|
||||||
default_desc = all_sorts
|
default_desc = all_sorts
|
||||||
default_sort = "-performance_points"
|
default_sort = "-performance_points"
|
||||||
|
context_object_name = "users"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return ranker(
|
||||||
|
self.organization.members.filter(is_unlisted=False)
|
||||||
|
.order_by(self.order, "id")
|
||||||
|
.select_related("user")
|
||||||
|
.only(
|
||||||
|
"display_rank",
|
||||||
|
"user__username",
|
||||||
|
"points",
|
||||||
|
"rating",
|
||||||
|
"performance_points",
|
||||||
|
"problem_count",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
res = super(OrganizationUsers, self).dispatch(request, *args, **kwargs)
|
res = super(OrganizationUsers, self).dispatch(request, *args, **kwargs)
|
||||||
|
@ -350,26 +337,13 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(OrganizationUsers, self).get_context_data(**kwargs)
|
context = super(OrganizationUsers, self).get_context_data(**kwargs)
|
||||||
context["title"] = _("%s Members") % self.object.name
|
context["title"] = _("%s Members") % self.organization.name
|
||||||
context["partial"] = True
|
context["partial"] = True
|
||||||
context["kick_url"] = reverse(
|
context["kick_url"] = reverse(
|
||||||
"organization_user_kick", args=[self.object.id, self.object.slug]
|
"organization_user_kick",
|
||||||
|
args=[self.organization.id, self.organization.slug],
|
||||||
)
|
)
|
||||||
|
|
||||||
context["users"] = ranker(
|
|
||||||
self.get_object()
|
|
||||||
.members.filter(is_unlisted=False)
|
|
||||||
.order_by(self.order, "id")
|
|
||||||
.select_related("user")
|
|
||||||
.only(
|
|
||||||
"display_rank",
|
|
||||||
"user__username",
|
|
||||||
"points",
|
|
||||||
"rating",
|
|
||||||
"performance_points",
|
|
||||||
"problem_count",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
context["first_page_href"] = "."
|
context["first_page_href"] = "."
|
||||||
context["page_type"] = "users"
|
context["page_type"] = "users"
|
||||||
context.update(self.get_sort_context())
|
context.update(self.get_sort_context())
|
||||||
|
@ -378,6 +352,7 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView):
|
||||||
|
|
||||||
class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemList):
|
class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemList):
|
||||||
template_name = "organization/problems.html"
|
template_name = "organization/problems.html"
|
||||||
|
filter_organization = True
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.org_query = [self.organization_id]
|
self.org_query = [self.organization_id]
|
||||||
|
@ -387,17 +362,6 @@ class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemL
|
||||||
self.setup_problem_list(request)
|
self.setup_problem_list(request)
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_latest_attempted_problems(self, limit=None):
|
|
||||||
if not self.profile:
|
|
||||||
return ()
|
|
||||||
problems = set(self.get_queryset().values_list("code", flat=True))
|
|
||||||
result = list(user_attempted_ids(self.profile).values())
|
|
||||||
result = [i for i in result if i["code"] in problems]
|
|
||||||
result = sorted(result, key=lambda d: -d["last_submission"])
|
|
||||||
if limit:
|
|
||||||
result = result[:limit]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_completed_problems(self):
|
def get_completed_problems(self):
|
||||||
return user_completed_ids(self.profile) if self.profile is not None else ()
|
return user_completed_ids(self.profile) if self.profile is not None else ()
|
||||||
|
|
||||||
|
@ -418,8 +382,7 @@ class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemL
|
||||||
class OrganizationContestMixin(
|
class OrganizationContestMixin(
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
OrganizationMixin,
|
OrganizationHomeView,
|
||||||
OrganizationHomeViewContext,
|
|
||||||
):
|
):
|
||||||
model = Contest
|
model = Contest
|
||||||
|
|
||||||
|
@ -477,7 +440,7 @@ class OrganizationSubmissions(
|
||||||
def contest(self):
|
def contest(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_queryset(self):
|
def get_queryset(self):
|
||||||
return (
|
return (
|
||||||
super()
|
super()
|
||||||
._get_entire_queryset()
|
._get_entire_queryset()
|
||||||
|
@ -610,8 +573,7 @@ class RequestJoinOrganization(LoginRequiredMixin, SingleObjectMixin, FormView):
|
||||||
class OrganizationRequestDetail(
|
class OrganizationRequestDetail(
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
OrganizationMixin,
|
OrganizationHomeView,
|
||||||
OrganizationHomeViewContext,
|
|
||||||
DetailView,
|
DetailView,
|
||||||
):
|
):
|
||||||
model = OrganizationRequest
|
model = OrganizationRequest
|
||||||
|
@ -636,7 +598,8 @@ OrganizationRequestFormSet = modelformset_factory(
|
||||||
|
|
||||||
|
|
||||||
class OrganizationRequestBaseView(
|
class OrganizationRequestBaseView(
|
||||||
OrganizationDetailView,
|
DetailView,
|
||||||
|
OrganizationHomeView,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
SingleObjectTemplateResponseMixin,
|
SingleObjectTemplateResponseMixin,
|
||||||
|
@ -757,7 +720,7 @@ class AddOrganizationMember(
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
AdminOrganizationMixin,
|
AdminOrganizationMixin,
|
||||||
OrganizationDetailView,
|
OrganizationHomeView,
|
||||||
UpdateView,
|
UpdateView,
|
||||||
):
|
):
|
||||||
template_name = "organization/add-member.html"
|
template_name = "organization/add-member.html"
|
||||||
|
@ -819,7 +782,7 @@ class EditOrganization(
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
AdminOrganizationMixin,
|
AdminOrganizationMixin,
|
||||||
OrganizationDetailView,
|
OrganizationHomeView,
|
||||||
UpdateView,
|
UpdateView,
|
||||||
):
|
):
|
||||||
template_name = "organization/edit.html"
|
template_name = "organization/edit.html"
|
||||||
|
@ -1020,7 +983,7 @@ class EditOrganizationContest(
|
||||||
class AddOrganizationBlog(
|
class AddOrganizationBlog(
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
OrganizationHomeViewContext,
|
OrganizationHomeView,
|
||||||
MemberOrganizationMixin,
|
MemberOrganizationMixin,
|
||||||
CreateView,
|
CreateView,
|
||||||
):
|
):
|
||||||
|
@ -1071,7 +1034,7 @@ class AddOrganizationBlog(
|
||||||
class EditOrganizationBlog(
|
class EditOrganizationBlog(
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
OrganizationHomeViewContext,
|
OrganizationHomeView,
|
||||||
MemberOrganizationMixin,
|
MemberOrganizationMixin,
|
||||||
UpdateView,
|
UpdateView,
|
||||||
):
|
):
|
||||||
|
@ -1165,7 +1128,7 @@ class PendingBlogs(
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
MemberOrganizationMixin,
|
MemberOrganizationMixin,
|
||||||
OrganizationHomeViewContext,
|
OrganizationHomeView,
|
||||||
ListView,
|
ListView,
|
||||||
):
|
):
|
||||||
model = BlogPost
|
model = BlogPost
|
||||||
|
|
|
@ -104,13 +104,3 @@ class PageVoteDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
||||||
queryset = PageVote.objects.get_or_create(page=self.get_comment_page())
|
queryset = PageVote.objects.get_or_create(page=self.get_comment_page())
|
||||||
context["pagevote"] = queryset[0]
|
context["pagevote"] = queryset[0]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class PageVoteListView:
|
|
||||||
def add_pagevote_context_data(self, context, obj_list="object_list"):
|
|
||||||
for item in context[obj_list]:
|
|
||||||
pagevote, _ = PageVote.objects.get_or_create(
|
|
||||||
page=self.get_comment_page(item)
|
|
||||||
)
|
|
||||||
setattr(item, "pagevote", pagevote)
|
|
||||||
return context
|
|
||||||
|
|
|
@ -63,7 +63,6 @@ from judge.models import (
|
||||||
Submission,
|
Submission,
|
||||||
SubmissionSource,
|
SubmissionSource,
|
||||||
Organization,
|
Organization,
|
||||||
VolunteerProblemVote,
|
|
||||||
Profile,
|
Profile,
|
||||||
LanguageTemplate,
|
LanguageTemplate,
|
||||||
)
|
)
|
||||||
|
@ -76,6 +75,7 @@ from judge.utils.problems import (
|
||||||
hot_problems,
|
hot_problems,
|
||||||
user_attempted_ids,
|
user_attempted_ids,
|
||||||
user_completed_ids,
|
user_completed_ids,
|
||||||
|
get_related_problems,
|
||||||
)
|
)
|
||||||
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
|
||||||
|
@ -86,8 +86,9 @@ from judge.utils.views import (
|
||||||
generic_message,
|
generic_message,
|
||||||
)
|
)
|
||||||
from judge.ml.collab_filter import CollabFilter
|
from judge.ml.collab_filter import CollabFilter
|
||||||
from judge.views.pagevote import PageVoteDetailView, PageVoteListView
|
from judge.views.pagevote import PageVoteDetailView
|
||||||
from judge.views.bookmark import BookMarkDetailView, BookMarkListView
|
from judge.views.bookmark import BookMarkDetailView
|
||||||
|
from judge.views.feed import FeedView
|
||||||
|
|
||||||
|
|
||||||
def get_contest_problem(problem, profile):
|
def get_contest_problem(problem, profile):
|
||||||
|
@ -105,6 +106,14 @@ def get_contest_submission_count(problem, profile, virtual):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_problems_in_organization(request, organization):
|
||||||
|
problem_list = ProblemList(request=request)
|
||||||
|
problem_list.setup_problem_list(request)
|
||||||
|
problem_list.org_query = [organization.id]
|
||||||
|
problems = problem_list.get_normal_queryset()
|
||||||
|
return problems
|
||||||
|
|
||||||
|
|
||||||
class ProblemMixin(object):
|
class ProblemMixin(object):
|
||||||
model = Problem
|
model = Problem
|
||||||
slug_url_kwarg = "problem"
|
slug_url_kwarg = "problem"
|
||||||
|
@ -145,10 +154,13 @@ class SolvedProblemMixin(object):
|
||||||
else:
|
else:
|
||||||
return user_attempted_ids(self.profile) if self.profile is not None else ()
|
return user_attempted_ids(self.profile) if self.profile is not None else ()
|
||||||
|
|
||||||
def get_latest_attempted_problems(self, limit=None):
|
def get_latest_attempted_problems(self, limit=None, queryset=None):
|
||||||
if self.in_contest or not self.profile:
|
if self.in_contest or not self.profile:
|
||||||
return ()
|
return ()
|
||||||
result = list(user_attempted_ids(self.profile).values())
|
result = list(user_attempted_ids(self.profile).values())
|
||||||
|
if queryset:
|
||||||
|
queryset_ids = set([i.code for i in queryset])
|
||||||
|
result = filter(lambda i: i["code"] in queryset_ids, result)
|
||||||
result = sorted(result, key=lambda d: -d["last_submission"])
|
result = sorted(result, key=lambda d: -d["last_submission"])
|
||||||
if limit:
|
if limit:
|
||||||
result = result[:limit]
|
result = result[:limit]
|
||||||
|
@ -185,31 +197,34 @@ class ProblemSolution(
|
||||||
template_name = "problem/editorial.html"
|
template_name = "problem/editorial.html"
|
||||||
|
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
return _("Editorial for {0}").format(self.object.name)
|
return _("Editorial for {0}").format(self.problem.name)
|
||||||
|
|
||||||
def get_content_title(self):
|
def get_content_title(self):
|
||||||
return format_html(
|
return format_html(
|
||||||
_('Editorial for <a href="{1}">{0}</a>'),
|
_('Editorial for <a href="{1}">{0}</a>'),
|
||||||
self.object.name,
|
self.problem.name,
|
||||||
reverse("problem_detail", args=[self.object.code]),
|
reverse("problem_detail", args=[self.problem.code]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
self.problem = super().get_object()
|
||||||
|
solution = get_object_or_404(Solution, problem=self.problem)
|
||||||
|
return solution
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ProblemSolution, self).get_context_data(**kwargs)
|
context = super(ProblemSolution, self).get_context_data(**kwargs)
|
||||||
|
solution = self.get_object()
|
||||||
solution = get_object_or_404(Solution, problem=self.object)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not solution.is_public or solution.publish_on > timezone.now()
|
not solution.is_public or solution.publish_on > timezone.now()
|
||||||
) and not self.request.user.has_perm("judge.see_private_solution"):
|
) and not self.request.user.has_perm("judge.see_private_solution"):
|
||||||
raise Http404()
|
raise Http404()
|
||||||
|
|
||||||
context["solution"] = solution
|
context["solution"] = solution
|
||||||
context["has_solved_problem"] = self.object.id in self.get_completed_problems()
|
context["has_solved_problem"] = self.problem.id in self.get_completed_problems()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_comment_page(self):
|
def get_comment_page(self):
|
||||||
return "s:" + self.object.code
|
return "s:" + self.problem.code
|
||||||
|
|
||||||
|
|
||||||
class ProblemRaw(
|
class ProblemRaw(
|
||||||
|
@ -341,6 +356,10 @@ class ProblemDetail(
|
||||||
else:
|
else:
|
||||||
context["fileio_input"] = None
|
context["fileio_input"] = None
|
||||||
context["fileio_output"] = None
|
context["fileio_output"] = None
|
||||||
|
if not self.in_contest:
|
||||||
|
context["related_problems"] = get_related_problems(
|
||||||
|
self.profile, self.object
|
||||||
|
)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -454,6 +473,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
default_desc = frozenset(("date", "points", "ac_rate", "user_count"))
|
default_desc = frozenset(("date", "points", "ac_rate", "user_count"))
|
||||||
default_sort = "-date"
|
default_sort = "-date"
|
||||||
first_page_href = None
|
first_page_href = None
|
||||||
|
filter_organization = False
|
||||||
|
|
||||||
def get_paginator(
|
def get_paginator(
|
||||||
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
|
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
|
||||||
|
@ -465,13 +485,10 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
padding=2,
|
padding=2,
|
||||||
orphans=orphans,
|
orphans=orphans,
|
||||||
allow_empty_first_page=allow_empty_first_page,
|
allow_empty_first_page=allow_empty_first_page,
|
||||||
|
count=queryset.values("pk").count() if not self.in_contest else None,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
if not self.in_contest:
|
if not self.in_contest:
|
||||||
# Get the number of pages and then add in this magic.
|
|
||||||
# noinspection PyStatementEffect
|
|
||||||
paginator.num_pages
|
|
||||||
|
|
||||||
queryset = queryset.add_i18n_name(self.request.LANGUAGE_CODE)
|
queryset = queryset.add_i18n_name(self.request.LANGUAGE_CODE)
|
||||||
sort_key = self.order.lstrip("-")
|
sort_key = self.order.lstrip("-")
|
||||||
if sort_key in self.sql_sort:
|
if sort_key in self.sql_sort:
|
||||||
|
@ -573,25 +590,13 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_normal_queryset(self):
|
def get_normal_queryset(self):
|
||||||
filter = Q(is_public=True)
|
queryset = Problem.get_visible_problems(self.request.user)
|
||||||
if self.profile is not None:
|
queryset = queryset.select_related("group")
|
||||||
filter |= Q(authors=self.profile)
|
|
||||||
filter |= Q(curators=self.profile)
|
|
||||||
filter |= Q(testers=self.profile)
|
|
||||||
queryset = (
|
|
||||||
Problem.objects.filter(filter).select_related("group").defer("description")
|
|
||||||
)
|
|
||||||
if not self.request.user.has_perm("see_organization_problem"):
|
|
||||||
filter = Q(is_organization_private=False)
|
|
||||||
if self.profile is not None:
|
|
||||||
filter |= Q(organizations__in=self.profile.organizations.all())
|
|
||||||
queryset = queryset.filter(filter)
|
|
||||||
if self.profile is not None and self.hide_solved:
|
if self.profile is not None and self.hide_solved:
|
||||||
queryset = queryset.exclude(
|
solved_problems = self.get_completed_problems()
|
||||||
id__in=Submission.objects.filter(
|
queryset = queryset.exclude(id__in=solved_problems)
|
||||||
user=self.profile, points=F("problem__points")
|
if not self.org_query and self.request.organization:
|
||||||
).values_list("problem__id", flat=True)
|
self.org_query = [self.request.organization.id]
|
||||||
)
|
|
||||||
if self.org_query:
|
if self.org_query:
|
||||||
self.org_query = self.get_org_query(self.org_query)
|
self.org_query = self.get_org_query(self.org_query)
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
|
@ -652,6 +657,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ProblemList, self).get_context_data(**kwargs)
|
context = super(ProblemList, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
if self.request.organization:
|
||||||
|
self.filter_organization = True
|
||||||
context["hide_solved"] = 0 if self.in_contest else int(self.hide_solved)
|
context["hide_solved"] = 0 if self.in_contest else int(self.hide_solved)
|
||||||
context["show_types"] = 0 if self.in_contest else int(self.show_types)
|
context["show_types"] = 0 if self.in_contest else int(self.show_types)
|
||||||
context["full_text"] = 0 if self.in_contest else int(self.full_text)
|
context["full_text"] = 0 if self.in_contest else int(self.full_text)
|
||||||
|
@ -663,8 +670,12 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
|
|
||||||
if self.request.profile:
|
if self.request.profile:
|
||||||
context["organizations"] = self.request.profile.organizations.all()
|
context["organizations"] = self.request.profile.organizations.all()
|
||||||
all_authors_ids = set(Problem.objects.values_list("authors", flat=True))
|
all_authors_ids = Problem.objects.values_list("authors", flat=True)
|
||||||
context["all_authors"] = Profile.objects.filter(id__in=all_authors_ids)
|
context["all_authors"] = (
|
||||||
|
Profile.objects.filter(id__in=all_authors_ids)
|
||||||
|
.select_related("user")
|
||||||
|
.values("id", "user__username")
|
||||||
|
)
|
||||||
context["category"] = self.category
|
context["category"] = self.category
|
||||||
context["categories"] = ProblemGroup.objects.all()
|
context["categories"] = ProblemGroup.objects.all()
|
||||||
if self.show_types:
|
if self.show_types:
|
||||||
|
@ -676,7 +687,9 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
context["search_query"] = self.search_query
|
context["search_query"] = self.search_query
|
||||||
context["completed_problem_ids"] = self.get_completed_problems()
|
context["completed_problem_ids"] = self.get_completed_problems()
|
||||||
context["attempted_problems"] = self.get_attempted_problems()
|
context["attempted_problems"] = self.get_attempted_problems()
|
||||||
context["last_attempted_problems"] = self.get_latest_attempted_problems(15)
|
context["last_attempted_problems"] = self.get_latest_attempted_problems(
|
||||||
|
15, context["problems"] if self.filter_organization else None
|
||||||
|
)
|
||||||
context["page_type"] = "list"
|
context["page_type"] = "list"
|
||||||
context.update(self.get_sort_paginate_context())
|
context.update(self.get_sort_paginate_context())
|
||||||
if not self.in_contest:
|
if not self.in_contest:
|
||||||
|
@ -751,7 +764,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
)
|
)
|
||||||
|
|
||||||
def GET_with_session(self, request, key):
|
def GET_with_session(self, request, key):
|
||||||
if not request.GET:
|
if not request.GET.get(key):
|
||||||
return request.session.get(key, False)
|
return request.session.get(key, False)
|
||||||
return request.GET.get(key, None) == "1"
|
return request.GET.get(key, None) == "1"
|
||||||
|
|
||||||
|
@ -820,32 +833,15 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
return HttpResponseRedirect(request.get_full_path())
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
|
||||||
|
|
||||||
class ProblemFeed(ProblemList, PageVoteListView, BookMarkListView):
|
class ProblemFeed(ProblemList, FeedView):
|
||||||
model = Problem
|
model = Problem
|
||||||
context_object_name = "problems"
|
context_object_name = "problems"
|
||||||
template_name = "problem/feed.html"
|
template_name = "problem/feed.html"
|
||||||
paginate_by = 20
|
feed_content_template_name = "problem/feed/problems.html"
|
||||||
|
paginate_by = 4
|
||||||
title = _("Problem feed")
|
title = _("Problem feed")
|
||||||
feed_type = None
|
feed_type = None
|
||||||
|
|
||||||
def GET_with_session(self, request, key):
|
|
||||||
if not request.GET:
|
|
||||||
return request.session.get(key, key == "hide_solved")
|
|
||||||
return request.GET.get(key, None) == "1"
|
|
||||||
|
|
||||||
def get_paginator(
|
|
||||||
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
|
|
||||||
):
|
|
||||||
return DiggPaginator(
|
|
||||||
queryset,
|
|
||||||
per_page,
|
|
||||||
body=6,
|
|
||||||
padding=2,
|
|
||||||
orphans=orphans,
|
|
||||||
allow_empty_first_page=allow_empty_first_page,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_comment_page(self, problem):
|
def get_comment_page(self, problem):
|
||||||
return "p:%s" % problem.code
|
return "p:%s" % problem.code
|
||||||
|
|
||||||
|
@ -945,6 +941,12 @@ class ProblemFeed(ProblemList, PageVoteListView, BookMarkListView):
|
||||||
res[position_in_q[problem.id]] = problem
|
res[position_in_q[problem.id]] = problem
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def get_feed_context(self, object_list):
|
||||||
|
return {
|
||||||
|
"completed_problem_ids": self.get_completed_problems(),
|
||||||
|
"attempted_problems": self.get_attempted_problems(),
|
||||||
|
}
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ProblemFeed, self).get_context_data(**kwargs)
|
context = super(ProblemFeed, self).get_context_data(**kwargs)
|
||||||
context["page_type"] = "feed"
|
context["page_type"] = "feed"
|
||||||
|
@ -952,8 +954,6 @@ class ProblemFeed(ProblemList, PageVoteListView, BookMarkListView):
|
||||||
context["feed_type"] = self.feed_type
|
context["feed_type"] = self.feed_type
|
||||||
context["has_show_editorial_option"] = False
|
context["has_show_editorial_option"] = False
|
||||||
context["has_have_editorial_option"] = False
|
context["has_have_editorial_option"] = False
|
||||||
context = self.add_pagevote_context_data(context)
|
|
||||||
context = self.add_bookmark_context_data(context)
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from judge.utils.problems import get_result_data
|
||||||
from judge.utils.raw_sql import join_sql_subquery
|
from judge.utils.raw_sql import join_sql_subquery
|
||||||
from judge.views.submission import ForceContestMixin, ProblemSubmissions
|
from judge.views.submission import ForceContestMixin, ProblemSubmissions
|
||||||
|
|
||||||
__all__ = ["RankedSubmissions", "ContestRankedSubmission"]
|
__all__ = ["RankedSubmissions"]
|
||||||
|
|
||||||
|
|
||||||
class RankedSubmissions(ProblemSubmissions):
|
class RankedSubmissions(ProblemSubmissions):
|
||||||
|
@ -27,7 +27,7 @@ class RankedSubmissions(ProblemSubmissions):
|
||||||
constraint = ""
|
constraint = ""
|
||||||
queryset = (
|
queryset = (
|
||||||
super(RankedSubmissions, self)
|
super(RankedSubmissions, self)
|
||||||
._get_queryset()
|
.get_queryset()
|
||||||
.filter(user__is_unlisted=False)
|
.filter(user__is_unlisted=False)
|
||||||
)
|
)
|
||||||
join_sql_subquery(
|
join_sql_subquery(
|
||||||
|
@ -76,43 +76,4 @@ class RankedSubmissions(ProblemSubmissions):
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_result_data(self):
|
def _get_result_data(self):
|
||||||
return get_result_data(
|
return get_result_data(super(RankedSubmissions, self).get_queryset().order_by())
|
||||||
super(RankedSubmissions, self)._get_queryset().order_by()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ContestRankedSubmission(ForceContestMixin, RankedSubmissions):
|
|
||||||
def get_title(self):
|
|
||||||
if self.problem.is_accessible_by(self.request.user):
|
|
||||||
return _("Best solutions for %(problem)s in %(contest)s") % {
|
|
||||||
"problem": self.problem_name,
|
|
||||||
"contest": self.contest.name,
|
|
||||||
}
|
|
||||||
return _("Best solutions for problem %(number)s in %(contest)s") % {
|
|
||||||
"number": self.get_problem_number(self.problem),
|
|
||||||
"contest": self.contest.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_content_title(self):
|
|
||||||
if self.problem.is_accessible_by(self.request.user):
|
|
||||||
return format_html(
|
|
||||||
_('Best solutions for <a href="{1}">{0}</a> in <a href="{3}">{2}</a>'),
|
|
||||||
self.problem_name,
|
|
||||||
reverse("problem_detail", args=[self.problem.code]),
|
|
||||||
self.contest.name,
|
|
||||||
reverse("contest_view", args=[self.contest.key]),
|
|
||||||
)
|
|
||||||
return format_html(
|
|
||||||
_('Best solutions for problem {0} in <a href="{2}">{1}</a>'),
|
|
||||||
self.get_problem_number(self.problem),
|
|
||||||
self.contest.name,
|
|
||||||
reverse("contest_view", args=[self.contest.key]),
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_result_data(self):
|
|
||||||
return get_result_data(
|
|
||||||
Submission.objects.filter(
|
|
||||||
problem_id=self.problem.id,
|
|
||||||
contest__participation__contest_id=self.contest.id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
|
@ -41,19 +41,15 @@ from judge.models import ProblemTranslation
|
||||||
from judge.models import Profile
|
from judge.models import Profile
|
||||||
from judge.models import Submission
|
from judge.models import Submission
|
||||||
from judge.utils.problems import get_result_data
|
from judge.utils.problems import get_result_data
|
||||||
from judge.utils.problems import user_authored_ids
|
from judge.utils.problems import user_completed_ids, user_editable_ids, user_tester_ids
|
||||||
from judge.utils.problems import user_completed_ids
|
|
||||||
from judge.utils.problems import user_editable_ids
|
|
||||||
from judge.utils.problem_data import get_problem_case
|
from judge.utils.problem_data import get_problem_case
|
||||||
from judge.utils.raw_sql import join_sql_subquery, use_straight_join
|
from judge.utils.raw_sql import join_sql_subquery, use_straight_join
|
||||||
from judge.utils.views import DiggPaginatorMixin
|
from judge.utils.views import DiggPaginatorMixin
|
||||||
|
from judge.utils.infinite_paginator import InfinitePaginationMixin
|
||||||
from judge.utils.views import TitleMixin
|
from judge.utils.views import TitleMixin
|
||||||
from judge.utils.timedelta import nice_repr
|
from judge.utils.timedelta import nice_repr
|
||||||
|
|
||||||
|
|
||||||
MAX_NUMBER_OF_QUERY_SUBMISSIONS = 50000
|
|
||||||
|
|
||||||
|
|
||||||
def submission_related(queryset):
|
def submission_related(queryset):
|
||||||
return queryset.select_related("user__user", "problem", "language").only(
|
return queryset.select_related("user__user", "problem", "language").only(
|
||||||
"id",
|
"id",
|
||||||
|
@ -266,7 +262,7 @@ class SubmissionStatus(SubmissionDetailBase):
|
||||||
can_see_testcases = self.access_testcases_in_contest()
|
can_see_testcases = self.access_testcases_in_contest()
|
||||||
|
|
||||||
if contest is not None:
|
if contest is not None:
|
||||||
prefix_length = contest.problem.output_prefix_override
|
prefix_length = contest.problem.output_prefix_override or 0
|
||||||
|
|
||||||
if contest is None or prefix_length > 0 or can_see_testcases:
|
if contest is None or prefix_length > 0 or can_see_testcases:
|
||||||
context["cases_data"] = get_cases_data(submission)
|
context["cases_data"] = get_cases_data(submission)
|
||||||
|
@ -333,7 +329,7 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _get_result_data(self):
|
def _get_result_data(self):
|
||||||
return get_result_data(self._get_queryset().order_by())
|
return get_result_data(self.get_queryset().order_by())
|
||||||
|
|
||||||
def access_check(self, request):
|
def access_check(self, request):
|
||||||
pass
|
pass
|
||||||
|
@ -404,16 +400,21 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
language__in=Language.objects.filter(key__in=self.selected_languages)
|
language__in=Language.objects.filter(key__in=self.selected_languages)
|
||||||
)
|
)
|
||||||
if self.selected_statuses:
|
if self.selected_statuses:
|
||||||
queryset = queryset.filter(
|
submission_results = [i for i, _ in Submission.RESULT]
|
||||||
Q(result__in=self.selected_statuses)
|
if self.selected_statuses[0] in submission_results:
|
||||||
| Q(status__in=self.selected_statuses)
|
queryset = queryset.filter(result__in=self.selected_statuses)
|
||||||
)
|
else:
|
||||||
|
queryset = queryset.filter(status__in=self.selected_statuses)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def _get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = self._get_entire_queryset()
|
queryset = self._get_entire_queryset()
|
||||||
if not self.in_contest:
|
if not self.in_contest:
|
||||||
|
if self.request.organization:
|
||||||
|
queryset = queryset.filter(
|
||||||
|
contest_object__organizations=self.request.organization
|
||||||
|
)
|
||||||
join_sql_subquery(
|
join_sql_subquery(
|
||||||
queryset,
|
queryset,
|
||||||
subquery=str(
|
subquery=str(
|
||||||
|
@ -429,12 +430,12 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
)
|
)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return self._get_queryset()[:MAX_NUMBER_OF_QUERY_SUBMISSIONS]
|
|
||||||
|
|
||||||
def get_my_submissions_page(self):
|
def get_my_submissions_page(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_friend_submissions_page(self):
|
||||||
|
return None
|
||||||
|
|
||||||
def get_all_submissions_page(self):
|
def get_all_submissions_page(self):
|
||||||
return reverse("all_submissions")
|
return reverse("all_submissions")
|
||||||
|
|
||||||
|
@ -473,12 +474,12 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
context["completed_problem_ids"] = (
|
context["completed_problem_ids"] = (
|
||||||
user_completed_ids(self.request.profile) if authenticated else []
|
user_completed_ids(self.request.profile) if authenticated else []
|
||||||
)
|
)
|
||||||
context["authored_problem_ids"] = (
|
|
||||||
user_authored_ids(self.request.profile) if authenticated else []
|
|
||||||
)
|
|
||||||
context["editable_problem_ids"] = (
|
context["editable_problem_ids"] = (
|
||||||
user_editable_ids(self.request.profile) if authenticated else []
|
user_editable_ids(self.request.profile) if authenticated else []
|
||||||
)
|
)
|
||||||
|
context["tester_problem_ids"] = (
|
||||||
|
user_tester_ids(self.request.profile) if authenticated else []
|
||||||
|
)
|
||||||
|
|
||||||
context["all_languages"] = Language.objects.all().values_list("key", "name")
|
context["all_languages"] = Language.objects.all().values_list("key", "name")
|
||||||
context["selected_languages"] = self.selected_languages
|
context["selected_languages"] = self.selected_languages
|
||||||
|
@ -499,6 +500,7 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
)
|
)
|
||||||
context["first_page_href"] = (self.first_page_href or ".") + suffix
|
context["first_page_href"] = (self.first_page_href or ".") + suffix
|
||||||
context["my_submissions_link"] = self.get_my_submissions_page()
|
context["my_submissions_link"] = self.get_my_submissions_page()
|
||||||
|
context["friend_submissions_link"] = self.get_friend_submissions_page()
|
||||||
context["all_submissions_link"] = self.get_all_submissions_page()
|
context["all_submissions_link"] = self.get_all_submissions_page()
|
||||||
context["page_type"] = self.page_type
|
context["page_type"] = self.page_type
|
||||||
|
|
||||||
|
@ -513,8 +515,8 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
if check is not None:
|
if check is not None:
|
||||||
return check
|
return check
|
||||||
|
|
||||||
self.selected_languages = set(request.GET.getlist("language"))
|
self.selected_languages = request.GET.getlist("language")
|
||||||
self.selected_statuses = set(request.GET.getlist("status"))
|
self.selected_statuses = request.GET.getlist("status")
|
||||||
|
|
||||||
if self.in_contest and self.contest.is_editable_by(self.request.user):
|
if self.in_contest and self.contest.is_editable_by(self.request.user):
|
||||||
self.include_frozen = True
|
self.include_frozen = True
|
||||||
|
@ -554,11 +556,25 @@ class ConditionalUserTabMixin(object):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class AllUserSubmissions(ConditionalUserTabMixin, UserMixin, SubmissionsListBase):
|
class GeneralSubmissions(SubmissionsListBase):
|
||||||
def _get_queryset(self):
|
def get_my_submissions_page(self):
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
return reverse(
|
||||||
|
"all_user_submissions", kwargs={"user": self.request.user.username}
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_friend_submissions_page(self):
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
return reverse("all_friend_submissions")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AllUserSubmissions(ConditionalUserTabMixin, UserMixin, GeneralSubmissions):
|
||||||
|
def get_queryset(self):
|
||||||
return (
|
return (
|
||||||
super(AllUserSubmissions, self)
|
super(AllUserSubmissions, self)
|
||||||
._get_queryset()
|
.get_queryset()
|
||||||
.filter(user_id=self.profile.id)
|
.filter(user_id=self.profile.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -569,19 +585,13 @@ class AllUserSubmissions(ConditionalUserTabMixin, UserMixin, SubmissionsListBase
|
||||||
|
|
||||||
def get_content_title(self):
|
def get_content_title(self):
|
||||||
if self.request.user.is_authenticated and self.request.profile == self.profile:
|
if self.request.user.is_authenticated and self.request.profile == self.profile:
|
||||||
return format_html("All my submissions")
|
return format_html(_("All my submissions"))
|
||||||
return format_html(
|
return format_html(
|
||||||
'All submissions by <a href="{1}">{0}</a>',
|
_('All submissions by <a href="{1}">{0}</a>'),
|
||||||
self.username,
|
self.username,
|
||||||
reverse("user_page", args=[self.username]),
|
reverse("user_page", args=[self.username]),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_my_submissions_page(self):
|
|
||||||
if self.request.user.is_authenticated:
|
|
||||||
return reverse(
|
|
||||||
"all_user_submissions", kwargs={"user": self.request.user.username}
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(AllUserSubmissions, self).get_context_data(**kwargs)
|
context = super(AllUserSubmissions, self).get_context_data(**kwargs)
|
||||||
context["dynamic_update"] = context["page_obj"].number == 1
|
context["dynamic_update"] = context["page_obj"].number == 1
|
||||||
|
@ -590,12 +600,29 @@ class AllUserSubmissions(ConditionalUserTabMixin, UserMixin, SubmissionsListBase
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class AllFriendSubmissions(LoginRequiredMixin, GeneralSubmissions):
|
||||||
|
def get_queryset(self):
|
||||||
|
friends = self.request.profile.get_friends()
|
||||||
|
return (
|
||||||
|
super(AllFriendSubmissions, self).get_queryset().filter(user_id__in=friends)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_title(self):
|
||||||
|
return _("All friend submissions")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(AllFriendSubmissions, self).get_context_data(**kwargs)
|
||||||
|
context["dynamic_update"] = False
|
||||||
|
context["page_type"] = "friend_tab"
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ProblemSubmissionsBase(SubmissionsListBase):
|
class ProblemSubmissionsBase(SubmissionsListBase):
|
||||||
show_problem = False
|
show_problem = False
|
||||||
dynamic_update = True
|
dynamic_update = True
|
||||||
check_contest_in_access_check = False
|
check_contest_in_access_check = False
|
||||||
|
|
||||||
def _get_queryset(self):
|
def get_queryset(self):
|
||||||
if (
|
if (
|
||||||
self.in_contest
|
self.in_contest
|
||||||
and not self.contest.contest_problems.filter(
|
and not self.contest.contest_problems.filter(
|
||||||
|
@ -687,10 +714,10 @@ class UserProblemSubmissions(ConditionalUserTabMixin, UserMixin, ProblemSubmissi
|
||||||
if not self.is_own:
|
if not self.is_own:
|
||||||
self.access_check_contest(request)
|
self.access_check_contest(request)
|
||||||
|
|
||||||
def _get_queryset(self):
|
def get_queryset(self):
|
||||||
return (
|
return (
|
||||||
super(UserProblemSubmissions, self)
|
super(UserProblemSubmissions, self)
|
||||||
._get_queryset()
|
.get_queryset()
|
||||||
.filter(user_id=self.profile.id)
|
.filter(user_id=self.profile.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -740,15 +767,15 @@ def single_submission(request, submission_id, show_problem=True):
|
||||||
"submission/row.html",
|
"submission/row.html",
|
||||||
{
|
{
|
||||||
"submission": submission,
|
"submission": submission,
|
||||||
"authored_problem_ids": user_authored_ids(request.profile)
|
|
||||||
if authenticated
|
|
||||||
else [],
|
|
||||||
"completed_problem_ids": user_completed_ids(request.profile)
|
"completed_problem_ids": user_completed_ids(request.profile)
|
||||||
if authenticated
|
if authenticated
|
||||||
else [],
|
else [],
|
||||||
"editable_problem_ids": user_editable_ids(request.profile)
|
"editable_problem_ids": user_editable_ids(request.profile)
|
||||||
if authenticated
|
if authenticated
|
||||||
else [],
|
else [],
|
||||||
|
"tester_problem_ids": user_tester_ids(request.profile)
|
||||||
|
if authenticated
|
||||||
|
else [],
|
||||||
"show_problem": show_problem,
|
"show_problem": show_problem,
|
||||||
"problem_name": show_problem
|
"problem_name": show_problem
|
||||||
and submission.problem.translated_name(request.LANGUAGE_CODE),
|
and submission.problem.translated_name(request.LANGUAGE_CODE),
|
||||||
|
@ -768,31 +795,46 @@ def single_submission_query(request):
|
||||||
return single_submission(request, int(request.GET["id"]), bool(show_problem))
|
return single_submission(request, int(request.GET["id"]), bool(show_problem))
|
||||||
|
|
||||||
|
|
||||||
class AllSubmissions(SubmissionsListBase):
|
class AllSubmissions(InfinitePaginationMixin, GeneralSubmissions):
|
||||||
stats_update_interval = 3600
|
stats_update_interval = 3600
|
||||||
|
|
||||||
def get_my_submissions_page(self):
|
@property
|
||||||
if self.request.user.is_authenticated:
|
def use_infinite_pagination(self):
|
||||||
return reverse(
|
return not self.in_contest
|
||||||
"all_user_submissions", kwargs={"user": self.request.user.username}
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(AllSubmissions, self).get_context_data(**kwargs)
|
context = super(AllSubmissions, self).get_context_data(**kwargs)
|
||||||
context["dynamic_update"] = context["page_obj"].number == 1
|
context["dynamic_update"] = (
|
||||||
|
context["page_obj"].number == 1
|
||||||
|
) and not self.request.organization
|
||||||
context["last_msg"] = event.last()
|
context["last_msg"] = event.last()
|
||||||
context["stats_update_interval"] = self.stats_update_interval
|
context["stats_update_interval"] = self.stats_update_interval
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def _get_result_data(self):
|
def _get_result_data(self):
|
||||||
if self.in_contest or self.selected_languages or self.selected_statuses:
|
if self.request.organization or self.in_contest:
|
||||||
return super(AllSubmissions, self)._get_result_data()
|
return super(AllSubmissions, self)._get_result_data()
|
||||||
|
|
||||||
key = "global_submission_result_data"
|
key = "global_submission_result_data"
|
||||||
|
if self.selected_statuses:
|
||||||
|
key += ":" + ",".join(self.selected_statuses)
|
||||||
|
if self.selected_languages:
|
||||||
|
key += ":" + ",".join(self.selected_languages)
|
||||||
result = cache.get(key)
|
result = cache.get(key)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
result = super(AllSubmissions, self)._get_result_data()
|
queryset = Submission.objects
|
||||||
|
if self.selected_languages:
|
||||||
|
queryset = queryset.filter(
|
||||||
|
language__in=Language.objects.filter(key__in=self.selected_languages)
|
||||||
|
)
|
||||||
|
if self.selected_statuses:
|
||||||
|
submission_results = [i for i, _ in Submission.RESULT]
|
||||||
|
if self.selected_statuses[0] in submission_results:
|
||||||
|
queryset = queryset.filter(result__in=self.selected_statuses)
|
||||||
|
else:
|
||||||
|
queryset = queryset.filter(status__in=self.selected_statuses)
|
||||||
|
result = get_result_data(queryset)
|
||||||
cache.set(key, result, self.stats_update_interval)
|
cache.set(key, result, self.stats_update_interval)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -44,12 +44,12 @@ from judge.utils.problems import contest_completed_ids, user_completed_ids
|
||||||
from judge.utils.ranker import ranker
|
from judge.utils.ranker import ranker
|
||||||
from judge.utils.unicode import utf8text
|
from judge.utils.unicode import utf8text
|
||||||
from judge.utils.views import (
|
from judge.utils.views import (
|
||||||
DiggPaginatorMixin,
|
|
||||||
QueryStringSortMixin,
|
QueryStringSortMixin,
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
generic_message,
|
generic_message,
|
||||||
SingleObjectFormView,
|
SingleObjectFormView,
|
||||||
)
|
)
|
||||||
|
from judge.utils.infinite_paginator import InfinitePaginationMixin
|
||||||
from .contests import ContestRanking
|
from .contests import ContestRanking
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -437,7 +437,7 @@ def edit_profile(request):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView):
|
class UserList(QueryStringSortMixin, InfinitePaginationMixin, TitleMixin, ListView):
|
||||||
model = Profile
|
model = Profile
|
||||||
title = gettext_lazy("Leaderboard")
|
title = gettext_lazy("Leaderboard")
|
||||||
context_object_name = "users"
|
context_object_name = "users"
|
||||||
|
@ -449,12 +449,12 @@ class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
filter_friend = False
|
filter_friend = False
|
||||||
|
|
||||||
def filter_friend_queryset(self, queryset):
|
def filter_friend_queryset(self, queryset):
|
||||||
friends = list(self.request.profile.get_friends())
|
friends = self.request.profile.get_friends()
|
||||||
ret = queryset.filter(user__username__in=friends)
|
ret = queryset.filter(id__in=friends)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
ret = (
|
queryset = (
|
||||||
Profile.objects.filter(is_unlisted=False)
|
Profile.objects.filter(is_unlisted=False)
|
||||||
.order_by(self.order, "id")
|
.order_by(self.order, "id")
|
||||||
.select_related("user")
|
.select_related("user")
|
||||||
|
@ -467,11 +467,13 @@ class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
"problem_count",
|
"problem_count",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if self.request.organization:
|
||||||
|
queryset = queryset.filter(organizations=self.request.organization)
|
||||||
if (self.request.GET.get("friend") == "true") and self.request.profile:
|
if (self.request.GET.get("friend") == "true") and self.request.profile:
|
||||||
ret = self.filter_friend_queryset(ret)
|
queryset = self.filter_friend_queryset(queryset)
|
||||||
self.filter_friend = True
|
self.filter_friend = True
|
||||||
return ret
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(UserList, self).get_context_data(**kwargs)
|
context = super(UserList, self).get_context_data(**kwargs)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
||||||
Django>=3.2,<4
|
Django>=3.2.17,<4
|
||||||
django_compressor>=3
|
django_compressor>=3
|
||||||
django-mptt>=0.13
|
django-mptt>=0.13
|
||||||
django-pagedown
|
django-pagedown
|
||||||
|
@ -41,4 +41,5 @@ markdown
|
||||||
bleach
|
bleach
|
||||||
pymdown-extensions
|
pymdown-extensions
|
||||||
mdx-breakless-lists
|
mdx-breakless-lists
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
|
pre-commit
|
|
@ -36,7 +36,7 @@ img {
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.full {
|
.full {
|
||||||
|
@ -327,7 +327,7 @@ nav {
|
||||||
a {
|
a {
|
||||||
color: black !important;
|
color: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
a, button {
|
a, button {
|
||||||
padding: 8px 20px 8px 8px !important;
|
padding: 8px 20px 8px 8px !important;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
|
@ -377,7 +377,7 @@ hr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
margin: 4.5em auto 1em auto;
|
margin: 3.2em auto 1em auto;
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
@ -558,7 +558,6 @@ noscript #noscript {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
border-right: 1px solid $border_gray;
|
border-right: 1px solid $border_gray;
|
||||||
border-left: 1px solid $border_gray;
|
border-left: 1px solid $border_gray;
|
||||||
background: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// border-bottom: 1px solid rgb(204, 204, 204)
|
// border-bottom: 1px solid rgb(204, 204, 204)
|
||||||
|
@ -595,7 +594,7 @@ math {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 799px) {
|
||||||
#navigation {
|
#navigation {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
}
|
}
|
||||||
|
@ -859,17 +858,22 @@ select {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
@media(min-width: 800px) {
|
||||||
.anon {
|
.anon {
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
.navbar-icons {
|
.navbar-icons {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
}
|
#page-container {
|
||||||
|
background: #f1f2f2;
|
||||||
@media (min-width: 800px) {
|
}
|
||||||
#event-tab {
|
#event-tab {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
#content.wrapper {
|
||||||
|
background: white;
|
||||||
|
padding: 2em;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-sidebar, .right-sidebar {
|
.blog-sidebar,
|
||||||
|
.right-sidebar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
@ -41,6 +42,7 @@
|
||||||
color: green;
|
color: green;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background-color: lightgreen;
|
background-color: lightgreen;
|
||||||
|
|
||||||
.sidebar-icon {
|
.sidebar-icon {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
|
@ -143,6 +145,7 @@
|
||||||
background-color: white;
|
background-color: white;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 0.2em;
|
margin-bottom: 0.2em;
|
||||||
|
@ -150,24 +153,33 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blog-box:hover {
|
||||||
|
border-color: #8a8a8a;
|
||||||
|
}
|
||||||
|
|
||||||
.blog-description {
|
.blog-description {
|
||||||
max-height: 30em;
|
max-height: 30em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
padding-bottom: 1em;
|
padding-bottom: 1em;
|
||||||
clear: both;
|
clear: both;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem-feed-name {
|
.problem-feed-name {
|
||||||
display: inline;
|
display: inline;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem-feed-name a {
|
.problem-feed-name a {
|
||||||
color: #0645ad;
|
color: #0645ad;
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem-feed-info-entry {
|
.problem-feed-info-entry {
|
||||||
display: inline;
|
display: inline;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem-feed-types {
|
.problem-feed-types {
|
||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
|
@ -175,24 +187,30 @@
|
||||||
.left-sidebar-item {
|
.left-sidebar-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.sidebar-icon {
|
.sidebar-icon {
|
||||||
font-size: large;
|
font-size: large;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
i {
|
i {
|
||||||
width: 1.4em;
|
width: 1.4em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-sidebar-item:hover {
|
.left-sidebar-item:hover {
|
||||||
background-color: #e3e3e3;
|
background-color: #e3e3e3;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-sidebar-item.active:hover {
|
.left-sidebar-item.active:hover {
|
||||||
background-color: lightgreen;
|
background-color: lightgreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-icon {
|
.sidebar-icon {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-sidebar-header {
|
.left-sidebar-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-bottom: 1em;
|
padding-bottom: 1em;
|
||||||
|
@ -200,15 +218,36 @@
|
||||||
color: black;
|
color: black;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feed-table {
|
.feed-table {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pre-expand-blog {
|
.pre-expand-blog {
|
||||||
background-image: -webkit-linear-gradient(bottom, gray, lightgray 3%, transparent 8%, transparent 100%);
|
position: relative;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
.pre-expand-blog:hover {
|
|
||||||
background-color: #f3f3f3;
|
.show-more {
|
||||||
|
display: flex;
|
||||||
|
color: black;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0px 12px;
|
||||||
|
margin-top: 5px;
|
||||||
|
position: absolute;
|
||||||
|
inset: 50% 0px 0px;
|
||||||
|
background: linear-gradient(transparent, white);
|
||||||
|
-webkit-box-pack: end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: flex-end;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionbar-box {
|
||||||
|
margin: 8px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-full {
|
.post-full {
|
||||||
|
@ -219,40 +258,59 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.middle-right-content.wrapper {
|
||||||
|
padding: 1em 0;
|
||||||
|
background: white;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 799px) {
|
@media (max-width: 799px) {
|
||||||
|
.actionbar-box {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.left-sidebar-header {
|
.left-sidebar-header {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-sidebar-item {
|
.left-sidebar-item {
|
||||||
padding: 0.8em 0.2em 0.8em 0.2em;
|
padding: 0.8em 0.2em 0.8em 0.2em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
.sidebar-icon {
|
.sidebar-icon {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-sidebar {
|
.left-sidebar {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-box {
|
.blog-box {
|
||||||
padding-left: 5%;
|
padding-left: 5%;
|
||||||
padding-right: 5%;
|
padding-right: 5%;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-title {
|
.post-title {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
@media (min-width: 800px) {
|
||||||
.left-sidebar-item {
|
.left-sidebar-item {
|
||||||
padding: 0.8em 0.2em 0.8em 1em;
|
padding: 0.8em 0.2em 0.8em 1em;
|
||||||
}
|
}
|
||||||
.middle-content, .blog-sidebar, .right-sidebar {
|
|
||||||
|
.middle-content,
|
||||||
|
.blog-sidebar,
|
||||||
|
.right-sidebar {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,20 +332,22 @@
|
||||||
width: -webkit-fill-available;
|
width: -webkit-fill-available;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blog-sidebar, .right-sidebar {
|
.blog-sidebar,
|
||||||
|
.right-sidebar {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-sidebar {
|
.left-sidebar {
|
||||||
width: 11%;
|
width: 11%;
|
||||||
|
max-width: 11%;
|
||||||
|
min-width: 11%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-top: -4em;
|
margin-top: -4em;
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
}
|
border-right: 1px solid lightgray;
|
||||||
|
box-shadow: 0px -10px 2px 0px rgb(0 0 0 / 50%);
|
||||||
.left-sidebar-item {
|
background: white;
|
||||||
border-radius: 0 0.5em 0.5em 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.feed-table {
|
.feed-table {
|
||||||
|
@ -297,7 +357,7 @@
|
||||||
.blog-box {
|
.blog-box {
|
||||||
border-left: 1.4px solid lightgray;
|
border-left: 1.4px solid lightgray;
|
||||||
border-right: 1.4px solid lightgray;
|
border-right: 1.4px solid lightgray;
|
||||||
border-radius: 8px;
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-full {
|
.post-full {
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
.chat {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
#chat-log p {
|
#chat-log p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-top: 0.1em;
|
padding-top: 0.1em;
|
||||||
|
@ -101,7 +104,7 @@
|
||||||
margin-bottom: 1.5px;
|
margin-bottom: 1.5px;
|
||||||
}
|
}
|
||||||
.info-circle {
|
.info-circle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cx: 86%;
|
cx: 86%;
|
||||||
cy: 80%;
|
cy: 80%;
|
||||||
r: 6px;
|
r: 6px;
|
||||||
|
@ -146,8 +149,8 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
.status-circle {
|
.status-circle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
cx: 18px;
|
cx: 18px;
|
||||||
cy: 18px;
|
cy: 18px;
|
||||||
|
|
|
@ -277,4 +277,10 @@ a {
|
||||||
.bookmarked {
|
.bookmarked {
|
||||||
color: rgb(180, 180, 7);
|
color: rgb(180, 180, 7);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 799px) {
|
||||||
|
.hide_texts_on_mobile .actionbar-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -359,6 +359,32 @@ function onWindowReady() {
|
||||||
$('#form-lang').submit();
|
$('#form-lang').submit();
|
||||||
})
|
})
|
||||||
$('#logout').on('click', () => $('#logout-form').submit());
|
$('#logout').on('click', () => $('#logout-form').submit());
|
||||||
|
|
||||||
|
var copyButton;
|
||||||
|
$('pre code').each(function () {
|
||||||
|
$(this).parent().before($('<div>', {'class': 'copy-clipboard'})
|
||||||
|
.append(copyButton = $('<span>', {
|
||||||
|
'class': 'btn-clipboard',
|
||||||
|
'data-clipboard-text': $(this).text(),
|
||||||
|
'title': 'Click to copy'
|
||||||
|
}).text('Copy')));
|
||||||
|
|
||||||
|
$(copyButton.get(0)).mouseleave(function () {
|
||||||
|
$(this).attr('class', 'btn-clipboard');
|
||||||
|
$(this).removeAttr('aria-label');
|
||||||
|
});
|
||||||
|
|
||||||
|
var curClipboard = new Clipboard(copyButton.get(0));
|
||||||
|
|
||||||
|
curClipboard.on('success', function (e) {
|
||||||
|
e.clearSelection();
|
||||||
|
showTooltip(e.trigger, 'Copied!');
|
||||||
|
});
|
||||||
|
|
||||||
|
curClipboard.on('error', function (e) {
|
||||||
|
showTooltip(e.trigger, fallbackMessage(e.action));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -23,6 +23,12 @@ $(function () {
|
||||||
csrfmiddlewaretoken: $.cookie('csrftoken')
|
csrfmiddlewaretoken: $.cookie('csrftoken')
|
||||||
}, function (result) {
|
}, function (result) {
|
||||||
$content.html(result);
|
$content.html(result);
|
||||||
|
$(".dmmd-preview-content [data-src]img").each(function() {
|
||||||
|
$(this).attr("src", $(this).attr("data-src"));
|
||||||
|
})
|
||||||
|
$(".dmmd-preview-content [data-src]iframe").each(function() {
|
||||||
|
$(this).attr("src", $(this).attr("data-src"));
|
||||||
|
})
|
||||||
$preview.addClass('dmmd-preview-has-content').removeClass('dmmd-preview-stale');
|
$preview.addClass('dmmd-preview-has-content').removeClass('dmmd-preview-stale');
|
||||||
|
|
||||||
var $jax = $content.find('.require-mathjax-support');
|
var $jax = $content.find('.require-mathjax-support');
|
||||||
|
|
|
@ -12,6 +12,6 @@ function mathjax_pagedown($) {
|
||||||
|
|
||||||
window.mathjax_pagedown = mathjax_pagedown;
|
window.mathjax_pagedown = mathjax_pagedown;
|
||||||
|
|
||||||
$(window).load(function () {
|
$(function () {
|
||||||
(mathjax_pagedown)('$' in window ? $ : django.jQuery);
|
(mathjax_pagedown)('$' in window ? $ : django.jQuery);
|
||||||
});
|
});
|
|
@ -369,7 +369,6 @@ ul.problem-list {
|
||||||
margin-left: 2.5%;
|
margin-left: 2.5%;
|
||||||
padding-bottom: 1em;
|
padding-bottom: 1em;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin-bottom: 1em;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#submissions-table {
|
#submissions-table {
|
||||||
background: rgba(0, 0, 0, .01);
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.submission-row {
|
.submission-row {
|
||||||
|
@ -212,8 +212,8 @@ label[for="language"], label[for="status"] {
|
||||||
}
|
}
|
||||||
|
|
||||||
// .batch-cases .case-row td b {
|
// .batch-cases .case-row td b {
|
||||||
// font-weight: 500;
|
// font-weight: 500;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
.case-info {
|
.case-info {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -252,7 +252,7 @@ label[for="language"], label[for="status"] {
|
||||||
td {
|
td {
|
||||||
padding: 0.6em 0.8em;
|
padding: 0.6em 0.8em;
|
||||||
width: 18.75%;
|
width: 18.75%;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.case-row td:nth-child(2) {
|
.case-row td:nth-child(2) {
|
||||||
|
@ -288,7 +288,7 @@ label[for="language"], label[for="status"] {
|
||||||
}
|
}
|
||||||
|
|
||||||
.overall-result-WA {
|
.overall-result-WA {
|
||||||
background: linear-gradient(45deg, yellow, red);
|
background: linear-gradient(45deg, yellow, red);
|
||||||
}
|
}
|
||||||
|
|
||||||
.overall-result-TLE {
|
.overall-result-TLE {
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.striped tr:nth-child(odd) {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
td:first-child {
|
td:first-child {
|
||||||
border-color: $border_gray;
|
border-color: $border_gray;
|
||||||
border-width: 1px 1px 0 1px;
|
border-width: 1px 1px 0 1px;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
.ticket-container {
|
.ticket-container {
|
||||||
|
display: flex;
|
||||||
#content > h2:first-child small {
|
#content > h2:first-child small {
|
||||||
color: #999;
|
color: #999;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
|
@ -495,6 +495,7 @@ ul.select2-selection__rendered {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
margin: 0 -5px;
|
margin: 0 -5px;
|
||||||
padding: 1px 0.5em 3px;
|
padding: 1px 0.5em 3px;
|
||||||
|
background: white;
|
||||||
|
|
||||||
&.sidebox-table {
|
&.sidebox-table {
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -510,7 +511,8 @@ ul.select2-selection__rendered {
|
||||||
border-top-left-radius: $widget_border_radius;
|
border-top-left-radius: $widget_border_radius;
|
||||||
border-top-right-radius: $widget_border_radius;
|
border-top-right-radius: $widget_border_radius;
|
||||||
padding: 0 5px !important;
|
padding: 0 5px !important;
|
||||||
padding-bottom: 1.5em !important;
|
margin-bottom: 1.5em !important;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ws-closed {
|
.ws-closed {
|
||||||
|
@ -591,7 +593,7 @@ ul.select2-selection__rendered {
|
||||||
.control-button {
|
.control-button {
|
||||||
background: lightgray;
|
background: lightgray;
|
||||||
color: black !important;
|
color: black !important;
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-button:hover {
|
.control-button:hover {
|
||||||
|
@ -701,7 +703,7 @@ ul.select2-selection__rendered {
|
||||||
}
|
}
|
||||||
|
|
||||||
#login-panel {
|
#login-panel {
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 5em auto auto -10em;
|
margin: 5em auto auto -10em;
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
{% if request.organization %}
|
||||||
|
{% cache 3600 'organization_html' request.organization.id MATH_ENGINE %}
|
||||||
|
{{ request.organization.about|markdown|reference|str|safe }}
|
||||||
|
{% endcache %}
|
||||||
|
{% else %}
|
||||||
<h4>
|
<h4>
|
||||||
<a target="_blank" href="">LQDOJ (Le Quy Don Online Judge)</a> là một trang web chấm bài tự động được phát triển dựa trên nền tảng mã nguồn mở <a target="_blank" href="https://dmoj.ca/">DMOJ</a>. Được xây dựng với mục đích ban đầu là tạo ra một môi trường học tập cho học sinh khối chuyên Tin <a target="_blank" href="http://thpt-lequydon-danang.edu.vn/">trường THPT chuyên Lê Quý Đôn (TP Đà Nẵng)</a>, hiện nay LQDOJ đã cho phép đăng ký tự do để trở thành một sân chơi rộng mở cho toàn bộ cộng đồng học sinh yêu Tin học. Trang web cung cấp lượng bài luyện tập đồ sộ từ các kỳ thi HSG Quốc Gia, ACM ICPC, Olympic Duyên Hải Bắc Bộ, etc. cho đến các contest định kỳ để xếp loại khả năng (rating) giúp các bạn có thêm động lực cạnh tranh và khí thế phấn đấu rèn luyện nâng cao trình độ lập trình. Các bạn có thể tham khảo mã nguồn của trang web tại <a target="_blank" href="https://github.com/LQDJudge/online-judge">Github repo chính thức</a>. Mọi ý kiến đóng góp và thắc mắc xin gửi về:
|
<a target="_blank" href="">LQDOJ (Le Quy Don Online Judge)</a> là một trang web chấm bài tự động được phát triển dựa trên nền tảng mã nguồn mở <a target="_blank" href="https://dmoj.ca/">DMOJ</a>. Được xây dựng với mục đích ban đầu là tạo ra một môi trường học tập cho học sinh khối chuyên Tin <a target="_blank" href="http://thpt-lequydon-danang.edu.vn/">trường THPT chuyên Lê Quý Đôn (TP Đà Nẵng)</a>, hiện nay LQDOJ đã cho phép đăng ký tự do để trở thành một sân chơi rộng mở cho toàn bộ cộng đồng học sinh yêu Tin học. Trang web cung cấp lượng bài luyện tập đồ sộ từ các kỳ thi HSG Quốc Gia, ACM ICPC, Olympic Duyên Hải Bắc Bộ, etc. cho đến các contest định kỳ để xếp loại khả năng (rating) giúp các bạn có thêm động lực cạnh tranh và khí thế phấn đấu rèn luyện nâng cao trình độ lập trình. Các bạn có thể tham khảo mã nguồn của trang web tại <a target="_blank" href="https://github.com/LQDJudge/online-judge">Github repo chính thức</a>. Mọi ý kiến đóng góp và thắc mắc xin gửi về:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Thầy <a target="_blank" href="https://www.facebook.com/dovannho">Đỗ Văn Nhỏ</a> (handle: <span class="rating rate-none user"><a target="_blank" href="../user/Small">Small</a></span>), giáo viên Tin học trường THPT chuyên Lê Quý Đôn, email: <a href="mailto:admin@lqdoj.edu.vn">admin@lqdoj.edu.vn</a></li>
|
<li>Thầy <a target="_blank" href="https://www.facebook.com/dovannho">Đỗ Văn Nhỏ</a> (handle: <span class="rating rate-none user"><a target="_blank" href="../user/Small">Small</a></span>), giáo viên Tin học trường THPT chuyên Lê Quý Đôn, email: <a href="mailto:admin@lqdoj.edu.vn">admin@lqdoj.edu.vn</a></li>
|
||||||
<li><a target="_blank" href="https://www.facebook.com/floweronstone">Nguyễn Đức Thuận</a> (handle: <span class="rating rate-master user"><a target="_blank" href="../user/Flower_On_Stone">Flower_On_Stone</a></span>), email: <a href="mailto:thuanbn03@gmail.com">thuanbn03@gmail.com</a></li>
|
<li><a target="_blank" href="https://www.facebook.com/floweronstone">Nguyễn Đức Thuận</a> (handle: <span class="rating rate-master user"><a target="_blank" href="../user/Flower_On_Stone">Flower_On_Stone</a></span>), email: <a href="mailto:thuanbn03@gmail.com">thuanbn03@gmail.com</a></li>
|
||||||
<li><a target="_blank" href="https://www.facebook.com/profile.php?id=100011662657075">Lê Phước Định</a> (handle: <span class="rating rate-grandmaster user"><a target="_blank" href="../user/cuom1999">cuom1999</a></span>), email: <a href="mailto:dinh@lqdoj.edu.vn">dinh@lqdoj.edu.vn</a></li>
|
<li><a target="_blank" href="https://www.facebook.com/profile.php?id=100011662657075">Lê Phước Định</a> (handle: <span class="rating rate-grandmaster user"><a target="_blank" href="../user/cuom1999">cuom1999</a></span>), email: <a href="mailto:dinh@lqdoj.edu.vn">dinh@lqdoj.edu.vn</a></li>
|
||||||
<li><a target="_blank" href="https://www.facebook.com/doannguyenthanhluong">Đoàn Nguyễn Thành Lương</a> (handle: <span class="rating rate-master user"><a target="_blank" href="../user/CaiWinDao">CaiWinDao</a></span>), email: <a href="mailto:luong@lqdoj.edu.vn">luong@lqdoj.edu.vn</a></li>
|
<li><a target="_blank" href="https://www.facebook.com/doannguyenthanhluong">Đoàn Nguyễn Thành Lương</a> (handle: <span class="rating rate-master user"><a target="_blank" href="../user/CaiWinDao">CaiWinDao</a></span>), email: <a href="mailto:luong@lqdoj.edu.vn">luong@lqdoj.edu.vn</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</h4>
|
</h4>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,62 +1,62 @@
|
||||||
{% extends "common-content.html" %}
|
{% extends "common-content.html" %}
|
||||||
{% block description %}
|
{% block description %}
|
||||||
<style>
|
<style>
|
||||||
article {
|
article {
|
||||||
padding: 2.5em 3.5em;
|
padding: 2.5em 3.5em;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
}
|
}
|
||||||
#content-right {
|
#content-right {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<article id="py">
|
<article id="py">
|
||||||
<h2>1. Custom checker (PY)</h2>
|
<h2>1. Custom checker (PY)</h2>
|
||||||
<hr>
|
<hr>
|
||||||
<p>
|
<p>
|
||||||
Đây là checker mặc định của website, cho phép người dùng cập nhật được nhiều thông tin nhất (chi tiết xem ở bên dưới). Chúng ta cần hoàn thành hàm check dưới đây:
|
Đây là checker mặc định của website, cho phép người dùng cập nhật được nhiều thông tin nhất (chi tiết xem ở bên dưới). Chúng ta cần hoàn thành hàm check dưới đây:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{{
|
{{
|
||||||
"""
|
"""
|
||||||
def check(process_output, judge_output, **kwargs):
|
def check(process_output, judge_output, **kwargs):
|
||||||
# return True/False
|
# return True/False
|
||||||
"""|highlight('py')}}
|
"""|highlight('py')}}
|
||||||
|
|
||||||
<p>
|
|
||||||
Trong đó, <code>**kwargs</code> có thể chứa các biến sau:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li><code>process_output:</code> output</li>
|
|
||||||
<li><code>judge_output:</code> đáp án</li>
|
|
||||||
<li><code>submission_source</code>: Code bài nộp</li>
|
|
||||||
<li><code>judge_input</code>: input</li>
|
|
||||||
<li><code>point_value:</code> điểm của test đang chấm</li>
|
|
||||||
<li><code>case_position:</code> thứ tự của test</li>
|
|
||||||
<li><code>submission_language:</code> ngôn ngữ của bài nộp</li>
|
|
||||||
<li><code>execution_time:</code> thời gian chạy</li>
|
|
||||||
</ul>
|
|
||||||
<h2>Return: </h2>
|
|
||||||
<ul>
|
|
||||||
<li>Cách 1: Trả về True/False</li>
|
|
||||||
<li>Cách 2: Trả về một object <code>CheckerResult</code> có thể được gọi như sau <pre class="code2">CheckerResult(case_passed_bool, points_awarded, feedback='')</pre></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Ví dụ: </h2>
|
<p>
|
||||||
<p>Dưới đây là ví dụ cho bài toán: Input gồm 1 số nguyên n. In ra 2 số nguyên a, b sao cho a + b = n.
|
Trong đó, <code>**kwargs</code> có thể chứa các biến sau:
|
||||||
</p>
|
</p>
|
||||||
{{
|
<ul>
|
||||||
"""
|
<li><code>process_output:</code> output</li>
|
||||||
from dmoj.result import CheckerResult
|
<li><code>judge_output:</code> đáp án</li>
|
||||||
|
<li><code>submission_source</code>: Code bài nộp</li>
|
||||||
|
<li><code>judge_input</code>: input</li>
|
||||||
|
<li><code>point_value:</code> điểm của test đang chấm</li>
|
||||||
|
<li><code>case_position:</code> thứ tự của test</li>
|
||||||
|
<li><code>submission_language:</code> ngôn ngữ của bài nộp</li>
|
||||||
|
<li><code>execution_time:</code> thời gian chạy</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Return: </h2>
|
||||||
|
<ul>
|
||||||
|
<li>Cách 1: Trả về True/False</li>
|
||||||
|
<li>Cách 2: Trả về một object <code>CheckerResult</code> có thể được gọi như sau <pre class="code2">CheckerResult(case_passed_bool, points_awarded, feedback='')</pre></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Ví dụ: </h2>
|
||||||
|
<p>Dưới đây là ví dụ cho bài toán: Input gồm 1 số nguyên n. In ra 2 số nguyên a, b sao cho a + b = n.
|
||||||
|
</p>
|
||||||
|
{{
|
||||||
|
"""
|
||||||
|
from dmoj.result import CheckerResult
|
||||||
|
|
||||||
|
|
||||||
def wa(feedback):
|
def wa(feedback):
|
||||||
return CheckerResult(False, 0, feedback)
|
return CheckerResult(False, 0, feedback)
|
||||||
|
|
||||||
|
|
||||||
def check(process_output, judge_output, judge_input, **kwargs):
|
def check(process_output, judge_output, judge_input, **kwargs):
|
||||||
# process the input
|
# process the input
|
||||||
input_arr = judge_input.split()
|
input_arr = judge_input.split()
|
||||||
assert(len(input_arr) == 1)
|
assert(len(input_arr) == 1)
|
||||||
|
@ -66,143 +66,143 @@ def check(process_output, judge_output, judge_input, **kwargs):
|
||||||
output_arr = process_output.split()
|
output_arr = process_output.split()
|
||||||
|
|
||||||
if (len(output_arr) != 2):
|
if (len(output_arr) != 2):
|
||||||
return wa('Wrong output format')
|
return wa('Wrong output format')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
a, b = int(output_arr[0]), int(output_arr[1])
|
a, b = int(output_arr[0]), int(output_arr[1])
|
||||||
except:
|
except:
|
||||||
return wa('Wrong output format')
|
return wa('Wrong output format')
|
||||||
|
|
||||||
if (n == a + b):
|
if (n == a + b):
|
||||||
return True
|
return True
|
||||||
return wa('a + b != n')
|
return wa('a + b != n')
|
||||||
"""| highlight('py')}}
|
"""| highlight('py')}}
|
||||||
</article>
|
</article>
|
||||||
<article id="cpp">
|
<article id="cpp">
|
||||||
<h2>2. Custom validator (CPP)</h2>
|
<h2>2. Custom validator (CPP)</h2>
|
||||||
<hr>
|
<hr>
|
||||||
<p>
|
<p>
|
||||||
Để sử dụng chức năng này, cần viết một chương trình C++ pass vào 3 arguments theo thứ tự <code>input_file</code>, <code>output_file</code>, <code>ans_file</code> tương ứng với các file input, output, đáp án.
|
Để sử dụng chức năng này, cần viết một chương trình C++ pass vào 3 arguments theo thứ tự <code>input_file</code>, <code>output_file</code>, <code>ans_file</code> tương ứng với các file input, output, đáp án.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Để test chương trình trên máy tính, có thể dùng lệnh như sau (Windows):
|
Để test chương trình trên máy tính, có thể dùng lệnh như sau (Windows):
|
||||||
<pre class="code2">
|
<pre class="code2">
|
||||||
main.exe [input_file] [output_file] [ans_file]</pre>
|
main.exe [input_file] [output_file] [ans_file]</pre>
|
||||||
hoặc thay bằng <code>./main</code> trên Linux/MacOS.
|
hoặc thay bằng <code>./main</code> trên Linux/MacOS.
|
||||||
</p>
|
</p>
|
||||||
<h2>Return: </h2>
|
<h2>Return: </h2>
|
||||||
<p>
|
<p>
|
||||||
Chương trình trả về giá trị:
|
Chương trình trả về giá trị:
|
||||||
<ul>
|
<ul>
|
||||||
<li> 0 nếu AC (100% điểm)</li>
|
<li> 0 nếu AC (100% điểm)</li>
|
||||||
<li> 1 nếu WA (0 điểm)</li>
|
<li> 1 nếu WA (0 điểm)</li>
|
||||||
<li> 2 nếu điểm thành phần. Khi đó cần in ra stderr một số thực trong đoạn [0, 1] thể hiện cho tỷ lệ điểm. Nếu điểm < 1 thì hiển thị WA, điểm = 1 thì hiển thị AC. </li>
|
<li> 2 nếu điểm thành phần. Khi đó cần in ra stderr một số thực trong đoạn [0, 1] thể hiện cho tỷ lệ điểm. Nếu điểm < 1 thì hiển thị WA, điểm = 1 thì hiển thị AC. </li>
|
||||||
</ul>
|
</ul>
|
||||||
Những thông tin được viết ra stdout (bằng cout) sẽ được in ra màn hình cho người nộp bài(feedback)
|
Những thông tin được viết ra stdout (bằng cout) sẽ được in ra màn hình cho người nộp bài(feedback)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Ví dụ: </h2>
|
<h2>Ví dụ: </h2>
|
||||||
<p>Chương trình sau dùng để chấm bài toán: Cho n là một số nguyên dương. In ra hai số tự nhiên a, b sao cho a + b = n. </p>
|
<p>Chương trình sau dùng để chấm bài toán: Cho n là một số nguyên dương. In ra hai số tự nhiên a, b sao cho a + b = n. </p>
|
||||||
<p>Nếu in ra a + b = n và a, b >= 0 thì được 100% số điểm, nếu a + b = n nhưng một trong 2 số a, b âm thì được 50% số điểm. </p>
|
<p>Nếu in ra a + b = n và a, b >= 0 thì được 100% số điểm, nếu a + b = n nhưng một trong 2 số a, b âm thì được 50% số điểm. </p>
|
||||||
{{
|
{{
|
||||||
"""
|
"""
|
||||||
#include <bits/stdc++.h>
|
#include <bits/stdc++.h>
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
ifstream inp(argv[1]);
|
ifstream inp(argv[1]);
|
||||||
ifstream out(argv[2]);
|
ifstream out(argv[2]);
|
||||||
ifstream ans(argv[3]);
|
ifstream ans(argv[3]);
|
||||||
|
|
||||||
int n, a, b, c, d;
|
int n, a, b, c, d;
|
||||||
|
|
||||||
inp >> n;
|
|
||||||
out >> a >> b;
|
|
||||||
ans >> c >> d;
|
|
||||||
|
|
||||||
if (a + b == c + d) {
|
inp >> n;
|
||||||
cout << a << \" + \" << b << \" = \" << c << \" + \" << d << endl;
|
out >> a >> b;
|
||||||
|
ans >> c >> d;
|
||||||
if (a >= 0 && b >= 0) {
|
|
||||||
return 0; // AC
|
if (a + b == c + d) {
|
||||||
}
|
cout << a << \" + \" << b << \" = \" << c << \" + \" << d << endl;
|
||||||
else {
|
|
||||||
cerr << 0.5;
|
if (a >= 0 && b >= 0) {
|
||||||
return 2; // PARTIAL
|
return 0; // AC
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
else {
|
cerr << 0.5;
|
||||||
cout << \"a + b = \" << a + b << \" != \" << n << endl;
|
return 2; // PARTIAL
|
||||||
return 1; // WA
|
}
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
""" | highlight('cpp')}}
|
cout << \"a + b = \" << a + b << \" != \" << n << endl;
|
||||||
|
return 1; // WA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""" | highlight('cpp')}}
|
||||||
</article>
|
</article>
|
||||||
<article id="interactive">
|
<article id="interactive">
|
||||||
<h2>3. Interactive (CPP)</h2>
|
<h2>3. Interactive (CPP)</h2>
|
||||||
<hr>
|
<hr>
|
||||||
<p>
|
<p>
|
||||||
Để sử dụng chức năng này, cần viết một chương trình C++ pass vào 2 arguments <code>input_file</code> <code>answer_file</code> tương ứng file input và đáp án (nếu cần thiết).
|
Để sử dụng chức năng này, cần viết một chương trình C++ pass vào 2 arguments <code>input_file</code> <code>answer_file</code> tương ứng file input và đáp án (nếu cần thiết).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Để test chương trình trên máy tính với tư cách thí sinh, có thể dùng lệnh như sau (Windows):
|
Để test chương trình trên máy tính với tư cách thí sinh, có thể dùng lệnh như sau (Windows):
|
||||||
<pre class="code2">
|
<pre class="code2">
|
||||||
main.exe [input_file] [answer_file]</pre>
|
main.exe [input_file] [answer_file]</pre>
|
||||||
hoặc thay bằng <code>./main</code> trên Linux/MacOS.
|
hoặc thay bằng <code>./main</code> trên Linux/MacOS.
|
||||||
</p>
|
</p>
|
||||||
<h2>Return: </h2>
|
<h2>Return: </h2>
|
||||||
<p>
|
<p>
|
||||||
Chương trình trả về giá trị:
|
Chương trình trả về giá trị:
|
||||||
<ul>
|
<ul>
|
||||||
<li> 0 nếu AC (100% điểm)</li>
|
<li> 0 nếu AC (100% điểm)</li>
|
||||||
<li> 1 nếu WA (0 điểm)</li>
|
<li> 1 nếu WA (0 điểm)</li>
|
||||||
<li> 2 nếu điểm thành phần. Khi đó cần in ra stderr một số thực trong đoạn [0, 1] thể hiện cho tỷ lệ điểm. Nếu điểm < 1 thì hiển thị WA, điểm = 1 thì hiển thị AC. </li>
|
<li> 2 nếu điểm thành phần. Khi đó cần in ra stderr một số thực trong đoạn [0, 1] thể hiện cho tỷ lệ điểm. Nếu điểm < 1 thì hiển thị WA, điểm = 1 thì hiển thị AC. </li>
|
||||||
</ul>
|
</ul>
|
||||||
Thông tin được in ra trong stderr (bằng cerr) sẽ là feedback hiển thị cho người dùng.
|
Thông tin được in ra trong stderr (bằng cerr) sẽ là feedback hiển thị cho người dùng.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Ví dụ: </h2>
|
<h2>Ví dụ: </h2>
|
||||||
<p>Chương trình sau dùng để chấm bài toán guessgame: Người chơi phải tìm 1 số bí mật n (n chứa trong file input). Mỗi lần họ được hỏi một số x, và chương trình sẽ trả về "SMALLER", "BIGGER" hoặc "HOLA" dựa trên giá trị của n và x. Cần tìm ra n sau không quá 31 câu hỏi. </p>
|
<p>Chương trình sau dùng để chấm bài toán guessgame: Người chơi phải tìm 1 số bí mật n (n chứa trong file input). Mỗi lần họ được hỏi một số x, và chương trình sẽ trả về "SMALLER", "BIGGER" hoặc "HOLA" dựa trên giá trị của n và x. Cần tìm ra n sau không quá 31 câu hỏi. </p>
|
||||||
{{
|
{{
|
||||||
"""
|
"""
|
||||||
#include <bits/stdc++.h>
|
#include <bits/stdc++.h>
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
void quit(string reason) {
|
void quit(string reason) {
|
||||||
cerr << reason << endl;
|
cerr << reason << endl;
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void read(long long& guess) {
|
void read(long long& guess) {
|
||||||
if (!(cin >> guess)) exit(1); // Nếu không có dòng này, chương trình sẽ chờ vô hạn
|
if (!(cin >> guess)) exit(1); // Nếu không có dòng này, chương trình sẽ chờ vô hạn
|
||||||
if (guess < 1 || guess > 2e9) exit(1);
|
if (guess < 1 || guess > 2e9) exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
ifstream inp(argv[1]);
|
ifstream inp(argv[1]);
|
||||||
int N, guesses = 0;
|
int N, guesses = 0;
|
||||||
long long guess;
|
long long guess;
|
||||||
inp >> N;
|
inp >> N;
|
||||||
|
|
||||||
while (guess != N && guesses <= 31) {
|
while (guess != N && guesses <= 31) {
|
||||||
read(guess);
|
read(guess);
|
||||||
if (guess == N) {
|
if (guess == N) {
|
||||||
cout << \"HOLA\" << endl;
|
cout << \"HOLA\" << endl;
|
||||||
} else if (guess > N) {
|
} else if (guess > N) {
|
||||||
cout << \"SMALLER\" << endl;
|
cout << \"SMALLER\" << endl;
|
||||||
} else {
|
} else {
|
||||||
cout << \"BIGGER\" << endl;
|
cout << \"BIGGER\" << endl;
|
||||||
}
|
}
|
||||||
guesses++;
|
guesses++;
|
||||||
}
|
}
|
||||||
cerr << \"Number of used guesses: \" << guesses << endl;
|
cerr << \"Number of used guesses: \" << guesses << endl;
|
||||||
if (guesses <= 31)
|
if (guesses <= 31)
|
||||||
return 0; // AC
|
return 0; // AC
|
||||||
else {
|
else {
|
||||||
cerr << \"Used too many guesses\" << endl;
|
cerr << \"Used too many guesses\" << endl;
|
||||||
return 1; // WA
|
return 1; // WA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""" | highlight('cpp')}}
|
""" | highlight('cpp')}}
|
||||||
</article>
|
</article>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,51 +1,62 @@
|
||||||
{% set logged_in = request.user.is_authenticated %}
|
{% set logged_in = request.user.is_authenticated %}
|
||||||
{% set profile = request.profile if logged_in else None %}
|
{% set profile = request.profile if logged_in else None %}
|
||||||
|
{% set hide_texts_on_mobile = (not hide_actionbar_comment) or actionbar_report_url %}
|
||||||
{% if logged_in %}
|
{% if logged_in %}
|
||||||
{% if include_hr %}<hr>{% endif %}
|
{% if include_hr %}
|
||||||
<div class="page-vote actionbar">
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
<div class="page-vote actionbar {{'hide_texts_on_mobile' if hide_texts_on_mobile}} ">
|
||||||
<span class="actionbar-block" style="justify-content: flex-start;">
|
<span class="actionbar-block" style="justify-content: flex-start;">
|
||||||
<span id="like-button-{{pagevote.id}}"
|
<span id="like-button-{{pagevote.id}}"
|
||||||
class="like-button actionbar-button {% if pagevote.vote_score(request.profile) == 1 %}voted{% endif %}"
|
class="like-button actionbar-button {% if pagevote.vote_score(request.profile) == 1 %}voted{% endif %}"
|
||||||
onclick="javascript:pagevote_upvote({{ pagevote.id }})"
|
onclick="javascript:pagevote_upvote({{ pagevote.id }}, event)">
|
||||||
>
|
<span class="pagevote-score" id="pagevote-score-{{pagevote.id}}">{{ pagevote.score }}</span>
|
||||||
<span class="pagevote-score" id="pagevote-score-{{pagevote.id}}">{{ pagevote.score }}</span>
|
<i class="fa fa-thumbs-o-up" style="font-size: large;"></i>
|
||||||
<i class="fa fa-thumbs-o-up" style="font-size: large;"></i>
|
<span class="actionbar-text">{{_("Like")}}</span>
|
||||||
<span class="actionbar-text">{{_("Like")}}</span>
|
</span>
|
||||||
</span>
|
<span id="dislike-button-{{pagevote.id}}"
|
||||||
<span id="dislike-button-{{pagevote.id}}" class="dislike-button actionbar-button {% if pagevote.vote_score(request.profile) == -1 %}voted{% endif %}" onclick="javascript:pagevote_downvote({{ pagevote.id }})">
|
class="dislike-button actionbar-button {% if pagevote.vote_score(request.profile) == -1 %}voted{% endif %}"
|
||||||
<i class="fa fa-thumbs-o-down" style="font-size: large;"></i>
|
onclick="javascript:pagevote_downvote({{ pagevote.id }}, event)">
|
||||||
</span>
|
<i class="fa fa-thumbs-o-down" style="font-size: large;"></i>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% if not hide_actionbar_comment %}
|
{% if not hide_actionbar_comment %}
|
||||||
<span class="actionbar-block">
|
<span class="actionbar-block">
|
||||||
<span class="actionbar-button actionbar-comment">
|
<span class="actionbar-button actionbar-comment">
|
||||||
<i class="fa fa-comment-o" style="font-size: large;"></i>
|
<i class="fa fa-comment-o" style="font-size: large;"></i>
|
||||||
<span class="actionbar-text">{{_("Comment")}}</span>
|
<span class="actionbar-text">
|
||||||
|
{{_("Comment")}}
|
||||||
|
</span>
|
||||||
|
{% if comment_list.count() %}
|
||||||
|
<span style="margin-left: 0.2em">
|
||||||
|
({{comment_list.count()}})
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="actionbar-block">
|
<span class="actionbar-block">
|
||||||
<span id="bookmark-button-{{bookmark.id}}"
|
<span id="bookmark-button-{{bookmark.id}}"
|
||||||
class="bookmark-button actionbar-button {% if bookmark.get_bookmark(request.profile) == True %} bookmarked {% endif %}"
|
class="bookmark-button actionbar-button {% if bookmark.get_bookmark(request.profile) == True %} bookmarked {% endif %}"
|
||||||
onclick="javascript:bookmark({{ bookmark.id }})"
|
onclick="javascript:bookmark({{ bookmark.id }}, event)">
|
||||||
>
|
<i class="fa fa-bookmark-o" style="font-size: large;"></i>
|
||||||
<i class="fa fa-bookmark-o" style="font-size: large;"></i>
|
<span class="actionbar-text">{{_("Bookmark")}}</span>
|
||||||
<span class="actionbar-text">{{_("Bookmark")}}</span>
|
</span>
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
<span class="actionbar-block" >
|
<span class="actionbar-block" style="justify-content: flex-end;">
|
||||||
<span class="actionbar-button actionbar-share" style="position: relative" {{"share-url=" + share_url if share_url else ""}}>
|
<span class="actionbar-button actionbar-share" style="position: relative"
|
||||||
<i class="fa fa-share" style="font-size: large;"></i>
|
{{"share-url=" + share_url if share_url else ""}} onclick="javascript:actionbar_share(this, event)">
|
||||||
<span class="actionbar-text">{{_("Share")}}</span>
|
<i class=" fa fa-share" style="font-size: large;"></i>
|
||||||
</span>
|
<span class="actionbar-text">{{_("Share")}}</span>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{% if actionbar_report_url %}
|
{% if actionbar_report_url %}
|
||||||
<span class="actionbar-block">
|
<span class="actionbar-block">
|
||||||
<a class="actionbar-button black" href="{{actionbar_report_url}}">
|
<a class="actionbar-button black" href="{{actionbar_report_url}}">
|
||||||
<i class="fa fa-flag-o" style="font-size: large;"></i>
|
<i class="fa fa-flag-o" style="font-size: large;"></i>
|
||||||
<span class="actionbar-text">{{_("Report")}}</span>
|
<span class="actionbar-text">{{_("Report")}}</span>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
|
@ -1,7 +0,0 @@
|
||||||
<style>
|
|
||||||
@media (max-width: 799px) {
|
|
||||||
.actionbar-text {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,124 +1,126 @@
|
||||||
{% compress js %}
|
{% compress js %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
function ajax_vote(url, id, delta, on_success) {
|
function ajax_vote(url, id, delta, on_success) {
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
id: id
|
id: id
|
||||||
},
|
},
|
||||||
success: function (data, textStatus, jqXHR) {
|
success: function (data, textStatus, jqXHR) {
|
||||||
var score = $('#pagevote-score-' + id);
|
var score = $('#pagevote-score-' + id);
|
||||||
score.text(parseInt(score.text()) + delta);
|
score.text(parseInt(score.text()) + delta);
|
||||||
if (typeof on_success !== 'undefined')
|
if (typeof on_success !== 'undefined')
|
||||||
on_success();
|
on_success();
|
||||||
},
|
},
|
||||||
error: function (data, textStatus, jqXHR) {
|
error: function (data, textStatus, jqXHR) {
|
||||||
alert('Could not vote: ' + data.responseText);
|
alert('Could not vote: ' + data.responseText);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function ajax_bookmark(url, id, on_success) {
|
|
||||||
return $.ajax({
|
|
||||||
url: url,
|
|
||||||
type: 'POST',
|
|
||||||
data: {
|
|
||||||
id: id
|
|
||||||
},
|
|
||||||
success: function (data, textStatus, jqXHR) {
|
|
||||||
if (typeof on_success !== 'undefined')
|
|
||||||
on_success();
|
|
||||||
},
|
|
||||||
error: function (data, textStatus, jqXHR) {
|
|
||||||
alert('Could not bookmark: ' + data.responseText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.bookmark = function(id) {
|
|
||||||
var $bookmark = $('#bookmark-button-' + id);
|
|
||||||
if ($bookmark.hasClass('bookmarked')) {
|
|
||||||
ajax_bookmark('{{ url('undobookmark') }}', id, function () {
|
|
||||||
$bookmark.removeClass('bookmarked');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ajax_bookmark('{{ url('dobookmark') }}', id, function () {
|
|
||||||
if ($bookmark.hasClass('bookmarked'))
|
|
||||||
$bookmark.removeClass('bookmarked');
|
|
||||||
$bookmark.addClass('bookmarked');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var get_$votes = function (id) {
|
|
||||||
var $post = $('#page-vote-' + id);
|
|
||||||
return {
|
|
||||||
upvote: $('#like-button-' + id),
|
|
||||||
downvote: $('#dislike-button-' + id),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
window.pagevote_upvote = function (id) {
|
|
||||||
var $votes = get_$votes(id);
|
|
||||||
if ($votes.upvote.hasClass('voted')) {
|
|
||||||
ajax_vote('{{ url('pagevote_downvote') }}', id, -1, function () {
|
|
||||||
$votes.upvote.removeClass('voted');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var delta = 1;
|
|
||||||
if ($votes.downvote.hasClass('voted')) {
|
|
||||||
delta = 2;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < delta; i++) {
|
|
||||||
ajax_vote('{{ url('pagevote_upvote') }}', id, 1, function () {
|
|
||||||
if ($votes.downvote.hasClass('voted'))
|
|
||||||
$votes.downvote.removeClass('voted');
|
|
||||||
$votes.upvote.addClass('voted');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.pagevote_downvote = function (id) {
|
|
||||||
var $votes = get_$votes(id);
|
|
||||||
if ($votes.downvote.hasClass('voted')) {
|
|
||||||
ajax_vote('{{ url('pagevote_upvote') }}', id, 1, function () {
|
|
||||||
$votes.downvote.removeClass('voted');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var delta = -1;
|
|
||||||
if ($votes.upvote.hasClass('voted')) {
|
|
||||||
delta = -2;
|
|
||||||
}
|
|
||||||
for (let i = 0; i > delta; i--) {
|
|
||||||
ajax_vote('{{ url('pagevote_downvote') }}', id, -1, function () {
|
|
||||||
if ($votes.upvote.hasClass('voted'))
|
|
||||||
$votes.upvote.removeClass('voted');
|
|
||||||
$votes.downvote.addClass('voted');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
$(".actionbar-share").click( function() {
|
|
||||||
link = $(this).attr("share-url") || window.location.href;
|
|
||||||
navigator.clipboard
|
|
||||||
.writeText(link)
|
|
||||||
.then(() => {
|
|
||||||
showTooltip(this, "Copied link", 'n');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.actionbar-comment').on('click', function() {
|
|
||||||
if ($('#comment-announcement').length) {
|
|
||||||
$('#comment-announcement').click();
|
|
||||||
}
|
|
||||||
$('#write-comment').click();
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
</script>
|
}
|
||||||
|
|
||||||
|
function ajax_bookmark(url, id, on_success) {
|
||||||
|
return $.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
id: id
|
||||||
|
},
|
||||||
|
success: function (data, textStatus, jqXHR) {
|
||||||
|
if (typeof on_success !== 'undefined')
|
||||||
|
on_success();
|
||||||
|
},
|
||||||
|
error: function (data, textStatus, jqXHR) {
|
||||||
|
alert('Could not bookmark: ' + data.responseText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.bookmark = function(id, e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
var $bookmark = $('#bookmark-button-' + id);
|
||||||
|
if ($bookmark.hasClass('bookmarked')) {
|
||||||
|
ajax_bookmark('{{ url('undobookmark') }}', id, function () {
|
||||||
|
$bookmark.removeClass('bookmarked');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ajax_bookmark('{{ url('dobookmark') }}', id, function () {
|
||||||
|
if ($bookmark.hasClass('bookmarked'))
|
||||||
|
$bookmark.removeClass('bookmarked');
|
||||||
|
$bookmark.addClass('bookmarked');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var get_$votes = function (id) {
|
||||||
|
var $post = $('#page-vote-' + id);
|
||||||
|
return {
|
||||||
|
upvote: $('#like-button-' + id),
|
||||||
|
downvote: $('#dislike-button-' + id),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
window.pagevote_upvote = function (id, e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
var $votes = get_$votes(id);
|
||||||
|
if ($votes.upvote.hasClass('voted')) {
|
||||||
|
ajax_vote('{{ url('pagevote_downvote') }}', id, -1, function () {
|
||||||
|
$votes.upvote.removeClass('voted');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var delta = 1;
|
||||||
|
if ($votes.downvote.hasClass('voted')) {
|
||||||
|
delta = 2;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < delta; i++) {
|
||||||
|
ajax_vote('{{ url('pagevote_upvote') }}', id, 1, function () {
|
||||||
|
if ($votes.downvote.hasClass('voted'))
|
||||||
|
$votes.downvote.removeClass('voted');
|
||||||
|
$votes.upvote.addClass('voted');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.pagevote_downvote = function (id, e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
var $votes = get_$votes(id);
|
||||||
|
if ($votes.downvote.hasClass('voted')) {
|
||||||
|
ajax_vote('{{ url('pagevote_upvote') }}', id, 1, function () {
|
||||||
|
$votes.downvote.removeClass('voted');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var delta = -1;
|
||||||
|
if ($votes.upvote.hasClass('voted')) {
|
||||||
|
delta = -2;
|
||||||
|
}
|
||||||
|
for (let i = 0; i > delta; i--) {
|
||||||
|
ajax_vote('{{ url('pagevote_downvote') }}', id, -1, function () {
|
||||||
|
if ($votes.upvote.hasClass('voted'))
|
||||||
|
$votes.upvote.removeClass('voted');
|
||||||
|
$votes.downvote.addClass('voted');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.actionbar_share = function(element, e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
link = $(element).attr("share-url") || window.location.href;
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(link)
|
||||||
|
.then(() => {
|
||||||
|
showTooltip(element, "Copied link", 'n');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$('.actionbar-comment').on('click', function() {
|
||||||
|
$('#comment-section').show();
|
||||||
|
$('#write-comment').click();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
|
@ -2,19 +2,19 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block extrahead %}{{ block.super }}
|
{% block extrahead %}{{ block.super }}
|
||||||
<script>
|
<script>
|
||||||
django.jQuery(function ($) {
|
django.jQuery(function ($) {
|
||||||
$('.profilelink').appendTo('div#bottombar').show();
|
$('.profilelink').appendTo('div#bottombar').show();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock extrahead %}
|
{% endblock extrahead %}
|
||||||
|
|
||||||
{% block after_field_sets %}{{ block.super }}
|
{% block after_field_sets %}{{ block.super }}
|
||||||
{% if original %}
|
{% if original %}
|
||||||
<a style="display: none" title="{% trans "Edit profile" %}" href="{% url 'admin:judge_profile_change' original.profile.pk %}"
|
<a style="display: none" title="{% trans "Edit profile" %}" href="{% url 'admin:judge_profile_change' original.profile.pk %}"
|
||||||
class="button profilelink">
|
class="button profilelink">
|
||||||
<i class="fa fa-lg fa-user-plus"></i>
|
<i class="fa fa-lg fa-user-plus"></i>
|
||||||
<span class="text">{% trans "Edit profile" %}</span>
|
<span class="text">{% trans "Edit profile" %}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,22 +2,22 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block extrahead %}{{ block.super }}
|
{% block extrahead %}{{ block.super }}
|
||||||
<script>
|
<script>
|
||||||
django.jQuery(function ($) {
|
django.jQuery(function ($) {
|
||||||
$('.rerate-link').appendTo('div#bottombar').show();
|
$('.rerate-link').appendTo('div#bottombar').show();
|
||||||
$('.rejudge-link').click(function () {
|
$('.rejudge-link').click(function () {
|
||||||
return confirm('{{ _('Are you sure you want to rejudge ALL the submissions?') }}');
|
return confirm('{{ _('Are you sure you want to rejudge ALL the submissions?') }}');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock extrahead %}
|
{% endblock extrahead %}
|
||||||
|
|
||||||
{% block after_field_sets %}{{ block.super }}
|
{% block after_field_sets %}{{ block.super }}
|
||||||
{% if original and original.is_rated and original.ended and perms.judge.contest_rating %}
|
{% if original and original.is_rated and original.ended and perms.judge.contest_rating %}
|
||||||
<a style="display: none" title="{% trans "Rate" %}" href="{% url 'admin:judge_contest_rate' original.pk %}"
|
<a style="display: none" title="{% trans "Rate" %}" href="{% url 'admin:judge_contest_rate' original.pk %}"
|
||||||
class="button rerate-link">
|
class="button rerate-link">
|
||||||
<i class="fa fa-lg fa-signal"></i>
|
<i class="fa fa-lg fa-signal"></i>
|
||||||
<span class="text">{% trans "Rate" %}</span>
|
<span class="text">{% trans "Rate" %}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block object-tools-items %}
|
{% block object-tools-items %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% if not is_popup and perms.judge.contest_rating %}
|
{% if not is_popup and perms.judge.contest_rating %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'admin:judge_contest_rate_all' %}" class="ratealllink">
|
<a href="{% url 'admin:judge_contest_rate_all' %}" class="ratealllink">
|
||||||
<i class="fa fa-signal"></i> {% trans "Rate all ratable contests" %}
|
<i class="fa fa-signal"></i> {% trans "Rate all ratable contests" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,25 +2,25 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block extrahead %}{{ block.super }}
|
{% block extrahead %}{{ block.super }}
|
||||||
<script>
|
<script>
|
||||||
django.jQuery(function ($) {
|
django.jQuery(function ($) {
|
||||||
$('.disconnect-link').appendTo('div#bottombar').show();
|
$('.disconnect-link').appendTo('div#bottombar').show();
|
||||||
$('.terminate-link').appendTo('div#bottombar').show();
|
$('.terminate-link').appendTo('div#bottombar').show();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock extrahead %}
|
{% endblock extrahead %}
|
||||||
|
|
||||||
{% block after_field_sets %}{{ block.super }}
|
{% block after_field_sets %}{{ block.super }}
|
||||||
{% if original %}
|
{% if original %}
|
||||||
<a style="display: none" title="{% trans "Disconnect" %}" href="{% url 'admin:judge_judge_disconnect' original.pk %}"
|
<a style="display: none" title="{% trans "Disconnect" %}" href="{% url 'admin:judge_judge_disconnect' original.pk %}"
|
||||||
class="button disconnect-link">
|
class="button disconnect-link">
|
||||||
<i class="fa fa-lg fa-power-off"></i>
|
<i class="fa fa-lg fa-power-off"></i>
|
||||||
<span class="text">{% trans "Disconnect" %}</span>
|
<span class="text">{% trans "Disconnect" %}</span>
|
||||||
</a>
|
</a>
|
||||||
<a style="display: none" title="{% trans "Terminate" %}" href="{% url 'admin:judge_judge_terminate' original.pk %}"
|
<a style="display: none" title="{% trans "Terminate" %}" href="{% url 'admin:judge_judge_terminate' original.pk %}"
|
||||||
class="button terminate-link">
|
class="button terminate-link">
|
||||||
<i class="fa fa-lg fa-plug"></i>
|
<i class="fa fa-lg fa-plug"></i>
|
||||||
<span class="text">{% trans "Terminate" %}</span>
|
<span class="text">{% trans "Terminate" %}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,24 +2,24 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block extrahead %}{{ block.super }}
|
{% block extrahead %}{{ block.super }}
|
||||||
<script>
|
<script>
|
||||||
django.jQuery(function ($) {
|
django.jQuery(function ($) {
|
||||||
$('.submissions-link').appendTo('div#bottombar').show();
|
$('.submissions-link').appendTo('div#bottombar').show();
|
||||||
$('.votes-link').appendTo('div#bottombar').show();
|
$('.votes-link').appendTo('div#bottombar').show();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock extrahead %}
|
{% endblock extrahead %}
|
||||||
|
|
||||||
{% block after_field_sets %}{{ block.super }}
|
{% block after_field_sets %}{{ block.super }}
|
||||||
{% if original %}
|
{% if original %}
|
||||||
<a style="display: none" title="{% trans "View Submissions" %}" class="button submissions-link"
|
<a style="display: none" title="{% trans "View Submissions" %}" class="button submissions-link"
|
||||||
href="{% url 'admin:judge_submission_changelist' %}?problem__code={{ original.code }}">
|
href="{% url 'admin:judge_submission_changelist' %}?problem__code={{ original.code }}">
|
||||||
<i class="fa fa-lg fa-search-plus"></i>
|
<i class="fa fa-lg fa-search-plus"></i>
|
||||||
<span class="text">{% trans "View submissions" %}</span>
|
<span class="text">{% trans "View submissions" %}</span>
|
||||||
<a style="display: none" title="{{ _('View votes') }}" class="button votes-link"
|
<a style="display: none" title="{{ _('View votes') }}" class="button votes-link"
|
||||||
href="{% url 'admin:judge_problempointsvote_changelist' %}?problem__code={{ original.code }}">
|
href="{% url 'admin:judge_problempointsvote_changelist' %}?problem__code={{ original.code }}">
|
||||||
<i class="fa fa-lg fa-envelope"></i>
|
<i class="fa fa-lg fa-envelope"></i>
|
||||||
<span class="text">{{ _('View votes') }}</span>
|
<span class="text">{{ _('View votes') }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,19 +2,19 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block extrahead %}{{ block.super }}
|
{% block extrahead %}{{ block.super }}
|
||||||
<script>
|
<script>
|
||||||
django.jQuery(function ($) {
|
django.jQuery(function ($) {
|
||||||
$('.userlink').appendTo('div#bottombar').show();
|
$('.userlink').appendTo('div#bottombar').show();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock extrahead %}
|
{% endblock extrahead %}
|
||||||
|
|
||||||
{% block after_field_sets %}{{ block.super }}
|
{% block after_field_sets %}{{ block.super }}
|
||||||
{% if original %}
|
{% if original %}
|
||||||
<a style="display: none" title="{% trans "Edit user" %}" href="{% url 'admin:auth_user_change' original.user.pk %}"
|
<a style="display: none" title="{% trans "Edit user" %}" href="{% url 'admin:auth_user_change' original.user.pk %}"
|
||||||
class="button userlink">
|
class="button userlink">
|
||||||
<i class="fa fa-lg fa-user"></i>
|
<i class="fa fa-lg fa-user"></i>
|
||||||
<span class="text">{% trans "Edit user" %}</span>
|
<span class="text">{% trans "Edit user" %}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,19 +2,19 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block extrahead %}{{ block.super }}
|
{% block extrahead %}{{ block.super }}
|
||||||
<script>
|
<script>
|
||||||
django.jQuery(function ($) {
|
django.jQuery(function ($) {
|
||||||
$('.rejudgelink').appendTo('div#bottombar').show();
|
$('.rejudgelink').appendTo('div#bottombar').show();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock extrahead %}
|
{% endblock extrahead %}
|
||||||
|
|
||||||
{% block after_field_sets %}{{ block.super }}
|
{% block after_field_sets %}{{ block.super }}
|
||||||
{% if original %}
|
{% if original %}
|
||||||
<a style="display: none" title="{% trans "Rejudge" %}" href="{% url 'admin:judge_submission_rejudge' original.pk %}"
|
<a style="display: none" title="{% trans "Rejudge" %}" href="{% url 'admin:judge_submission_rejudge' original.pk %}"
|
||||||
class="button rejudgelink">
|
class="button rejudgelink">
|
||||||
<i class="fa fa-lg fa-refresh"></i>
|
<i class="fa fa-lg fa-refresh"></i>
|
||||||
<span class="text">{% trans "Rejudge" %}</span>
|
<span class="text">{% trans "Rejudge" %}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,13 +1,13 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ LANGUAGE_CODE }}">
|
<html lang="{{ LANGUAGE_CODE }}">
|
||||||
<head>
|
<head>
|
||||||
<title>{% block title %}{{ title }} - {{ SITE_LONG_NAME }}{% endblock %}</title>
|
<title>{% block title %}{{ title }} - {{ SITE_LONG_NAME }}{% endblock %}</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
{% if misc_config.meta_keywords %}
|
{% if misc_config.meta_keywords %}
|
||||||
<meta name="keywords" content="{{ misc_config.meta_keywords }}">
|
<meta name="keywords" content="{{ misc_config.meta_keywords }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if meta_description %}
|
{% if meta_description %}
|
||||||
<meta name="description" content="{{ meta_description }}">
|
<meta name="description" content="{{ meta_description }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<meta id="viewport" name="viewport" content="width=device-width, initial-scale=1">
|
<meta id="viewport" name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<!-- Favicons-->
|
<!-- Favicons-->
|
||||||
|
@ -30,14 +30,14 @@
|
||||||
{# Chrome 39 for Android colour #}
|
{# Chrome 39 for Android colour #}
|
||||||
<meta name="theme-color" content="#FFBB33">
|
<meta name="theme-color" content="#FFBB33">
|
||||||
{% if og_image %}
|
{% if og_image %}
|
||||||
<meta property="og:image" content="{{ request.build_absolute_uri(og_image) }}">
|
<meta property="og:image" content="{{ request.build_absolute_uri(og_image) }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% block og_title %}{% endblock %}
|
{% block og_title %}{% endblock %}
|
||||||
<meta property="og:site_name" content="{{ SITE_LONG_NAME }}">
|
<meta property="og:site_name" content="{{ SITE_LONG_NAME }}">
|
||||||
<meta property="og:url"
|
<meta property="og:url"
|
||||||
content="{{ DMOJ_SCHEME }}://{{ DMOJ_CANONICAL|default(site.domain) }}{{ request.get_full_path() }}">
|
content="{{ DMOJ_SCHEME }}://{{ DMOJ_CANONICAL|default(site.domain) }}{{ request.get_full_path() }}">
|
||||||
{% if meta_description %}
|
{% if meta_description %}
|
||||||
<meta property="og:description" content="{{ meta_description }}">
|
<meta property="og:description" content="{{ meta_description }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
|
@ -45,337 +45,338 @@
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
{% block meta %}{% endblock %}
|
{% block meta %}{% endblock %}
|
||||||
{% if not INLINE_FONTAWESOME %}
|
{% if not INLINE_FONTAWESOME %}
|
||||||
<link rel="stylesheet" href="{{ FONTAWESOME_CSS }}">
|
<link rel="stylesheet" href="{{ FONTAWESOME_CSS }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<link rel="stylesheet" type="text/css" href="{{ static('markdown.css') }}">
|
<link rel="stylesheet" type="text/css" href="{{ static('markdown.css') }}">
|
||||||
{% compress css %}
|
{% compress css %}
|
||||||
<link rel="stylesheet" href="{{ static('style.css') }}">
|
<link rel="stylesheet" href="{{ static('style.css') }}">
|
||||||
{% if PYGMENT_THEME %}
|
{% if PYGMENT_THEME %}
|
||||||
<link rel="stylesheet" href="{{ static(PYGMENT_THEME) }}">
|
<link rel="stylesheet" href="{{ static(PYGMENT_THEME) }}">
|
||||||
{% endif %}{% if INLINE_FONTAWESOME %}
|
{% endif %}{% if INLINE_FONTAWESOME %}
|
||||||
<link rel="stylesheet" href="{{ static('libs/fontawesome/font-awesome.css') }}">{% endif %}
|
<link rel="stylesheet" href="{{ static('libs/fontawesome/font-awesome.css') }}">{% endif %}
|
||||||
<link rel="stylesheet" type="text/css" href="{{ static('libs/featherlight/featherlight.min.css') }}">
|
<link rel="stylesheet" type="text/css" href="{{ static('libs/featherlight/featherlight.min.css') }}">
|
||||||
<link rel="stylesheet" type="text/css" href="{{ static('libs/clipboard/tooltip.css') }}">
|
<link rel="stylesheet" type="text/css" href="{{ static('libs/clipboard/tooltip.css') }}">
|
||||||
<link rel="stylesheet" type="text/css" href="{{ static('libs/select2/select2.css') }}">
|
<link rel="stylesheet" type="text/css" href="{{ static('libs/select2/select2.css') }}">
|
||||||
<link rel="stylesheet" type="text/css" href="{{ static('icofont/icofont.min.css') }}">
|
<link rel="stylesheet" type="text/css" href="{{ static('icofont/icofont.min.css') }}">
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
<link rel="canonical"
|
<link rel="canonical"
|
||||||
href="{{ DMOJ_SCHEME }}://{{ DMOJ_CANONICAL|default(site.domain) }}{{ request.get_full_path() }}">
|
href="{{ DMOJ_SCHEME }}://{{ DMOJ_CANONICAL|default(site.domain) }}{{ request.get_full_path() }}">
|
||||||
{% if request.user.is_impersonate %}
|
{% if request.user.is_impersonate %}
|
||||||
<style>
|
<style>
|
||||||
#nav-container {
|
#nav-container {
|
||||||
background: #893e89 !important;
|
background: #893e89 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% block media %}{% endblock %}
|
{% block media %}{% endblock %}
|
||||||
{% if use_darkmode %}
|
{% if use_darkmode %}
|
||||||
{% compress css %}
|
{% compress css %}
|
||||||
<link rel="stylesheet" href="{{ static('darkmode.css') }}">
|
<link rel="stylesheet" href="{{ static('darkmode.css') }}">
|
||||||
<link rel="stylesheet" href="{{ static('darkmode-svg.css') }}">
|
<link rel="stylesheet" href="{{ static('darkmode-svg.css') }}">
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not INLINE_JQUERY %}
|
{% if not INLINE_JQUERY %}
|
||||||
<script src="{{ JQUERY_JS }}"></script>
|
<script src="{{ JQUERY_JS }}"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||||
{% compress js %}
|
{% compress js %}
|
||||||
<script>{{ inlinei18n(LANGUAGE_CODE)|safe }}</script>
|
<script>{{ inlinei18n(LANGUAGE_CODE)|safe }}</script>
|
||||||
{% if INLINE_JQUERY %}
|
{% if INLINE_JQUERY %}
|
||||||
<script src="{{ static('libs/jquery-3.4.1.min.js') }}"></script>
|
<script src="{{ static('libs/jquery-3.4.1.min.js') }}"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script src="{{ static('libs/jquery-cookie.js') }}"></script>
|
<script src="{{ static('libs/jquery-cookie.js') }}"></script>
|
||||||
<script src="{{ static('libs/jquery-taphold.js') }}"></script>
|
<script src="{{ static('libs/jquery-taphold.js') }}"></script>
|
||||||
<script src="{{ static('libs/jquery.unveil.js') }}"></script>
|
<script src="{{ static('libs/jquery.unveil.js') }}"></script>
|
||||||
<script src="{{ static('libs/moment.js') }}"></script>
|
<script src="{{ static('libs/moment.js') }}"></script>
|
||||||
<script src="{{ static('libs/select2/select2.js') }}"></script>
|
<script src="{{ static('libs/select2/select2.js') }}"></script>
|
||||||
{% include "extra_js.html" %}
|
<script src="{{ static('libs/clipboard/clipboard.js') }}"></script>
|
||||||
<script src="{{ static('common.js') }}"></script>
|
{% include "extra_js.html" %}
|
||||||
<script src="{{ static('libs/clipboard/tooltip.js') }}"></script>
|
<script src="{{ static('common.js') }}"></script>
|
||||||
<script>
|
<script src="{{ static('libs/clipboard/tooltip.js') }}"></script>
|
||||||
moment.locale('{{ LANGUAGE_CODE }}');
|
<script>
|
||||||
$(function () {
|
moment.locale('{{ LANGUAGE_CODE }}');
|
||||||
$('img.unveil').unveil(200);
|
$(function () {
|
||||||
});
|
$('img.unveil').unveil(200);
|
||||||
const loading_page = `{% include "loading-page.html" %}`;
|
});
|
||||||
</script>
|
const loading_page = `{% include "loading-page.html" %}`;
|
||||||
|
</script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
|
|
||||||
{% block js_media %}{% endblock %}
|
{% block js_media %}{% endblock %}
|
||||||
{% if request.in_contest %}
|
{% if request.in_contest %}
|
||||||
<script>$(function () {
|
<script>$(function () {
|
||||||
if ($("#contest-time-remaining").length) {
|
if ($("#contest-time-remaining").length) {
|
||||||
count_down($("#contest-time-remaining"));
|
count_down($("#contest-time-remaining"));
|
||||||
}
|
}
|
||||||
|
|
||||||
var selected = null,
|
var selected = null,
|
||||||
x_pos = 0, y_pos = 0,
|
x_pos = 0, y_pos = 0,
|
||||||
x_elem = 0, y_elem = 0;
|
x_elem = 0, y_elem = 0;
|
||||||
|
|
||||||
$('#contest-info').mousedown(function () {
|
$('#contest-info').mousedown(function () {
|
||||||
selected = $(this);
|
selected = $(this);
|
||||||
x_elem = x_pos - selected.offset().left;
|
x_elem = x_pos - selected.offset().left;
|
||||||
y_elem = y_pos - (selected.offset().top - $(window).scrollTop());
|
y_elem = y_pos - (selected.offset().top - $(window).scrollTop());
|
||||||
return false;
|
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>
|
|
||||||
|
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 %}
|
{% endif %}
|
||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<script>
|
<script>
|
||||||
window.user = {
|
window.user = {
|
||||||
email: '{{ request.user.email|escapejs }}',
|
email: '{{ request.user.email|escapejs }}',
|
||||||
id: '{{ request.user.id|escapejs }}',
|
id: '{{ request.user.id|escapejs }}',
|
||||||
name: '{{ request.user.username|escapejs }}'
|
name: '{{ request.user.username|escapejs }}'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
{% else %}
|
{% else %}
|
||||||
<script>window.user = {};</script>
|
<script>window.user = {};</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if misc_config.analytics %}
|
{% if misc_config.analytics %}
|
||||||
{{ misc_config.analytics|safe }}
|
{{ misc_config.analytics|safe }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Don't run userscript since it may be malicious #}
|
{# 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 %}
|
{% 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>
|
<script type="text/javascript">{{ request.profile.user_script|safe }}</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<noscript>
|
<noscript>
|
||||||
<style>
|
<style>
|
||||||
#content {
|
#content {
|
||||||
margin: 80px auto auto;
|
margin: 80px auto auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#navigation {
|
#navigation {
|
||||||
top: 27px;
|
top: 27px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</noscript>
|
</noscript>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<svg width="0" height="0" style="display: block">
|
<svg width="0" height="0" style="display: block">
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="rating-clip"><circle cx="8" cy="8" r="7"/></clipPath>
|
<clipPath id="rating-clip"><circle cx="8" cy="8" r="7"/></clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
<nav id="navigation" class="unselectable">
|
<nav id="navigation" class="unselectable">
|
||||||
<div id="nav-container">
|
<div id="nav-container">
|
||||||
<a id="navicon" href="javascript:void(0)"><i class="fa fa-bars"></i></a>
|
<a id="navicon" href="javascript:void(0)"><i class="fa fa-bars"></i></a>
|
||||||
<ul id="nav-list">
|
<ul id="nav-list">
|
||||||
<li class="home-nav-element"><a href="{{ url('home') }}">{% include "site-logo-fragment.html" %}</a></li>
|
<li class="home-nav-element"><a href="{{ url('home') }}">{% include "site-logo-fragment.html" %}</a></li>
|
||||||
<li class="home-nav-element"><span class="nav-divider"></span></li>
|
<li class="home-nav-element"><span class="nav-divider"></span></li>
|
||||||
<li class="home-menu-item"><a href="{{ url('home') }}" class="nav-home">{{ _('Home') }}</a></li>
|
<li class="home-menu-item"><a href="{{ url('home') }}" class="nav-home">{{ _('Home') }}</a></li>
|
||||||
{% for node in mptt_tree(nav_bar) recursive %}
|
{% for node in mptt_tree(nav_bar) recursive %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ node.path }}" class="nav-{{ node.key }}{% if node.key in nav_tab %} active{% endif %}">
|
<a href="{{ node.path }}" class="nav-{{ node.key }}{% if node.key in nav_tab %} active{% endif %}">
|
||||||
{{ user_trans(node.label) }}
|
{{ user_trans(node.label) }}
|
||||||
{% if not node.is_leaf_node %}
|
{% if not node.is_leaf_node %}
|
||||||
<div href="javascript:void(0)" class="nav-expand">></div>
|
<div href="javascript:void(0)" class="nav-expand">></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% with children=node.get_children() %}
|
{% with children=node.get_children() %}
|
||||||
{% if children %}<ul>{{ loop(children) }}</ul>{% endif %}
|
{% if children %}<ul>{{ loop(children) }}</ul>{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<div style="float: right; display: inline-flex; font-size: larger; align-items: center; height: 100%;">
|
<div style="float: right; display: inline-flex; font-size: larger; align-items: center; height: 100%;">
|
||||||
<span class="navbar-icons">
|
<span class="navbar-icons">
|
||||||
{% if request.user.is_authenticated %}
|
|
||||||
<span title="{{_('Chat')}}">
|
|
||||||
<a id="chat-icon" href="{{ url('chat', '') }}" class="icofont-wechat navbar-icon" aria-hidden="true">
|
|
||||||
{% set unread_chat = request.profile.count_unread_chat_boxes %}
|
|
||||||
{% if unread_chat %}
|
|
||||||
<sub class="unread_boxes">{{unread_chat}}</sub>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{% set unseen_cnt = request.profile.count_unseen_notifications %}
|
|
||||||
<span title="{{_('Notification')}}" class="{{ 'notification-open' if unseen_cnt > 0 }}">
|
|
||||||
<a href="{{ url('notification') }}" class="icofont-alarm navbar-icon" id="notification" aria-hidden="true">
|
|
||||||
{% if unseen_cnt > 0 %}
|
|
||||||
<sub class="unread_boxes">{{unseen_cnt}}</sub>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
<span title="{{_('Language')}}">
|
|
||||||
<a class="icofont-globe navbar-icon" id="nav-lang-icon" aria-hidden="true">
|
|
||||||
<sub class="sub-lang">{{LANGUAGE_CODE}}</sub>
|
|
||||||
</a>
|
|
||||||
<div id="lang-dropdown" class="dropdown" role="tooltip">
|
|
||||||
{% for language in language_info_list(LANGUAGES) %}
|
|
||||||
<div value="{{ language.code }}"
|
|
||||||
class="dropdown-item lang-dropdown-item" style="{{'font-weight: bold' if language.code == LANGUAGE_CODE}}">
|
|
||||||
{{ language.name_local }}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
<span title="{{_('Dark Mode')}}">
|
|
||||||
<a class="icofont-adjust navbar-icon black" id="nav-darkmode-icon" aria-hidden="true" href="?darkmode=1"></a>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<span id="user-links">
|
<span title="{{_('Chat')}}">
|
||||||
<ul><li><a href="javascript:void(0)">
|
<a id="chat-icon" href="{{ url('chat', '') }}" class="icofont-wechat navbar-icon" aria-hidden="true">
|
||||||
<span>
|
{% set unread_chat = request.profile.count_unread_chat_boxes %}
|
||||||
<img src="{{ gravatar(request.user, 32) }}" height="24" width="24">{# -#}
|
{% if unread_chat %}
|
||||||
<span>
|
<sub class="unread_boxes">{{unread_chat}}</sub>
|
||||||
<b class="{{request.profile.css_class}}">{{ request.user.username }}</b>
|
{% endif %}
|
||||||
</span>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</a></li></ul>
|
|
||||||
</span>
|
{% set unseen_cnt = request.profile.count_unseen_notifications %}
|
||||||
<div class="dropdown" id="userlink_dropdown" role="tooptip">
|
<span title="{{_('Notification')}}" class="{{ 'notification-open' if unseen_cnt > 0 }}">
|
||||||
<div class="dropdown-item"><a href="{{ url('user_page') }}">{{ _('Profile') }}</a></div>
|
<a href="{{ url('notification') }}" class="icofont-alarm navbar-icon" id="notification" aria-hidden="true">
|
||||||
{% if request.user.is_staff or request.user.is_superuser %}
|
{% if unseen_cnt > 0 %}
|
||||||
<div class="dropdown-item"><a href="{{ url('admin:index') }}">{{ _('Admin') }}</a></div>
|
<sub class="unread_boxes">{{unseen_cnt}}</sub>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.user.is_superuser %}
|
</a>
|
||||||
<div class="dropdown-item"><a href="{{ url('internal_problem') }}">{{ _('Internal') }}</a></div>
|
</span>
|
||||||
<div class="dropdown-item"><a href="{{ url('site_stats') }}">{{ _('Stats') }}</a></div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="dropdown-item"><a href="{{ url('user_edit_profile') }}">{{ _('Edit profile') }}</a></div>
|
|
||||||
{% if request.user.is_impersonate %}
|
|
||||||
<div class="dropdown-item"><a href="{{ url('impersonate-stop') }}">Stop impersonating</a></div>
|
|
||||||
{% else %}
|
|
||||||
<div class="dropdown-item">
|
|
||||||
<a href="#" id="logout" class="red">{{ _('Log out') }}</a>
|
|
||||||
<form id="logout-form" action="{{ url('auth_logout') }}" method="POST">
|
|
||||||
{% csrf_token %}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<span class="anon">
|
|
||||||
<a href="{{ url('auth_login') }}?next={{ LOGIN_RETURN_PATH|urlencode }}"><b>{{ _('Log in') }}</b></a>
|
|
||||||
{{ _('or') }}
|
|
||||||
<a href="{{ url('registration_register') }}"><b>{{ _('Sign up') }}</b></a>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<span title="{{_('Language')}}">
|
||||||
|
<a class="icofont-globe navbar-icon" id="nav-lang-icon" aria-hidden="true">
|
||||||
|
<sub class="sub-lang">{{LANGUAGE_CODE}}</sub>
|
||||||
|
</a>
|
||||||
|
<div id="lang-dropdown" class="dropdown" role="tooltip">
|
||||||
|
{% for language in language_info_list(LANGUAGES) %}
|
||||||
|
<div value="{{ language.code }}"
|
||||||
|
class="dropdown-item lang-dropdown-item" style="{{'font-weight: bold' if language.code == LANGUAGE_CODE}}">
|
||||||
|
{{ language.name_local }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span title="{{_('Dark Mode')}}">
|
||||||
|
<a class="icofont-adjust navbar-icon black" id="nav-darkmode-icon" aria-hidden="true" href="?darkmode=1"></a>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<span id="user-links">
|
||||||
|
<ul><li><a href="javascript:void(0)">
|
||||||
|
<span>
|
||||||
|
<img src="{{ gravatar(request.user, 32) }}" height="24" width="24">{# -#}
|
||||||
|
<span>
|
||||||
|
<b class="{{request.profile.css_class}}">{{ request.user.username }}</b>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a></li></ul>
|
||||||
|
</span>
|
||||||
|
<div class="dropdown" id="userlink_dropdown" role="tooptip">
|
||||||
|
<div class="dropdown-item"><a href="{{ url('user_page') }}">{{ _('Profile') }}</a></div>
|
||||||
|
{% if request.user.is_staff or request.user.is_superuser %}
|
||||||
|
<div class="dropdown-item"><a href="{{ url('admin:index') }}">{{ _('Admin') }}</a></div>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
|
<div class="dropdown-item"><a href="{{ url('internal_problem') }}">{{ _('Internal') }}</a></div>
|
||||||
|
<div class="dropdown-item"><a href="{{ url('site_stats') }}">{{ _('Stats') }}</a></div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="dropdown-item"><a href="{{ url('user_edit_profile') }}">{{ _('Edit profile') }}</a></div>
|
||||||
|
{% if request.user.is_impersonate %}
|
||||||
|
<div class="dropdown-item"><a href="{{ url('impersonate-stop') }}">Stop impersonating</a></div>
|
||||||
|
{% else %}
|
||||||
|
<div class="dropdown-item">
|
||||||
|
<a href="#" id="logout" class="red">{{ _('Log out') }}</a>
|
||||||
|
<form id="logout-form" action="{{ url('auth_logout') }}" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<span class="anon">
|
||||||
|
<a href="{{ url('auth_login') }}?next={{ LOGIN_RETURN_PATH|urlencode }}"><b>{{ _('Log in') }}</b></a>
|
||||||
|
{{ _('or') }}
|
||||||
|
<a href="{{ url('registration_register') }}"><b>{{ _('Sign up') }}</b></a>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="nav-shadow"></div>
|
<div id="nav-shadow"></div>
|
||||||
</nav>
|
</nav>
|
||||||
{% if request.in_contest %}
|
{% if request.in_contest %}
|
||||||
<div id="contest-info">
|
<div id="contest-info">
|
||||||
<div id="contest-info-main">
|
<div id="contest-info-main">
|
||||||
<a href="{{ url('contest_view', request.participation.contest.key) }}" style="vertical-align: middle; display: inline">
|
<a href="{{ url('contest_view', request.participation.contest.key) }}" style="vertical-align: middle; display: inline">
|
||||||
{{ request.participation.contest.name }} -
|
{{ request.participation.contest.name }} -
|
||||||
{% if request.participation.spectate %}
|
{% if request.participation.spectate %}
|
||||||
{{ _('spectating') }}
|
{{ _('spectating') }}
|
||||||
{% elif request.participation.end_time %}
|
{% elif request.participation.end_time %}
|
||||||
<div id="contest-time-remaining" data-secs="{{request.participation.end_time}}">
|
<div id="contest-time-remaining" data-secs="{{request.participation.end_time}}">
|
||||||
{{ request.participation.time_remaining|timedelta("localized") }}
|
{{ request.participation.time_remaining|timedelta("localized") }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ _('virtual') }}
|
{{ _('virtual') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="contest-info-toggle" class="{{'contest-info-toggle-mode-on' if request.contest_mode else 'contest-info-toggle-mode-off'}}">
|
<div id="contest-info-toggle" class="{{'contest-info-toggle-mode-on' if request.contest_mode else 'contest-info-toggle-mode-off'}}">
|
||||||
{% if request.contest_mode %}
|
{% if request.contest_mode %}
|
||||||
<i class="fa fa-toggle-on white"></i> {{_('Compete')}}
|
<i class="fa fa-toggle-on white"></i> {{_('Compete')}}
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="fa fa-toggle-off white"></i> {{_('General')}}
|
<i class="fa fa-toggle-off white"></i> {{_('General')}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<noscript>
|
<noscript>
|
||||||
<div id="noscript">{{ _('This site works best with JavaScript enabled.') }}</div>
|
<div id="noscript">{{ _('This site works best with JavaScript enabled.') }}</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
<br>
|
<br>
|
||||||
<main id="content">
|
<main id="content" class="{{'wrapper' if layout != 'no_wrapper'}}">
|
||||||
{% block title_row %}
|
{% block title_row %}
|
||||||
<h2 class="title-row">
|
<h2 class="title-row">
|
||||||
{% block content_title %}
|
{% block content_title %}
|
||||||
{% if content_title %}{{ content_title }}{% else %}{{ title }}{% endif %}
|
{% if content_title %}{{ content_title }}{% else %}{{ title }}{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</h2>
|
</h2>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block header %}{% endblock %}
|
{% block header %}{% endblock %}
|
||||||
{% block title_ruler %}
|
{% block title_ruler %}
|
||||||
<hr>
|
<hr>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<div id="content-body">{% block body %}{% endblock %}</div>
|
<div id="content-body">{% block body %}{% endblock %}</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{% if i18n_config.announcement %}
|
{% if i18n_config.announcement %}
|
||||||
<div id="announcement">{{ i18n_config.announcement|safe }}</div>
|
<div id="announcement">{{ i18n_config.announcement|safe }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% 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 %}
|
||||||
<form action="{{ url('set_language') }}" method="post" style="display: inline" id="form-lang">
|
<form action="{{ url('set_language') }}" method="post" style="display: inline" id="form-lang">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path() }}">
|
<input name="next" type="hidden" value="{{ request.get_full_path() }}">
|
||||||
<select name="language" onchange="form.submit()" style="height: 1.5em">
|
<select name="language" onchange="form.submit()" style="height: 1.5em">
|
||||||
{% for language in language_info_list(LANGUAGES) %}
|
{% for language in language_info_list(LANGUAGES) %}
|
||||||
<option value="{{ language.code }}" {% if language.code == LANGUAGE_CODE %}selected{% endif %}>
|
<option value="{{ language.code }}" {% if language.code == LANGUAGE_CODE %}selected{% endif %}>
|
||||||
{{ language.name_local }} ({{ language.code }})
|
{{ language.name_local }} ({{ language.code }})
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
</span>
|
</span>
|
||||||
</footer>
|
</footer>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code&family=Noto+Sans&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Fira+Code&family=Noto+Sans&display=swap" rel="stylesheet">
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block js_media %}
|
{% block js_media %}
|
||||||
{% include "comments/media-js.html" %}
|
{% include "comments/media-js.html" %}
|
||||||
{% include "actionbar/media-js.html" %}
|
{% include "actionbar/media-js.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block media %}
|
{% block media %}
|
||||||
{% include "comments/media-css.html" %}
|
{% include "comments/media-css.html" %}
|
||||||
{% include "actionbar/media-css.html" %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title_row %}
|
{% block title_row %}
|
||||||
|
@ -17,40 +16,41 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="post-full">
|
<div class="post-full">
|
||||||
<div class="post-title">{{ title }}</div>
|
<div class="post-title">{{ title }}</div>
|
||||||
<div class="time">
|
<div class="time">
|
||||||
{% with authors=post.authors.all() %}
|
{% with authors=post.authors.all() %}
|
||||||
{% if authors %}
|
{% if authors %}
|
||||||
<span class="post-authors">{{ link_users(authors) }}</span>
|
<span class="post-authors">{{ link_users(authors) }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<span class="post-time">
|
<span class="post-time">
|
||||||
{% trans time=post.publish_on|date(_("N j, Y, g:i a")) %} posted on {{ time }}{% endtrans %}
|
{% trans time=post.publish_on|date(_("N j, Y, g:i a")) %} posted on {{ time }}{% endtrans %}
|
||||||
</span>
|
</span>
|
||||||
{% if post.is_editable_by(request.user) %}
|
{% if post.is_editable_by(request.user) %}
|
||||||
<span> [<a href="{{ url('admin:judge_blogpost_change', post.id) }}">{{ _('Edit') }}</a>]</span>
|
<span> [<a href="{{ url('admin:judge_blogpost_change', post.id) }}">{{ _('Edit') }}</a>]</span>
|
||||||
{% elif valid_user_to_show_edit %}
|
{% endif %}
|
||||||
{% for org in valid_org_to_show_edit %}
|
{% if valid_user_to_show_edit %}
|
||||||
<span> [<a href="{{ url('edit_organization_blog', org.id , org.slug , post.id) }}">{{ _('Edit in') }} {{org.slug}}</a>]</span>
|
{% for org in valid_org_to_show_edit %}
|
||||||
{% endfor %}
|
<span> [<a href="{{ url('edit_organization_blog', org.id , org.slug , post.id) }}">{{ _('Edit in') }} {{org.slug}}</a>]</span>
|
||||||
{% endif %}
|
{% endfor %}
|
||||||
</div>
|
{% endif %}
|
||||||
<div class="body content-description">
|
|
||||||
{% cache 86400 'post_content' post.id MATH_ENGINE %}
|
|
||||||
{{ post.content|markdown|reference|str|safe}}
|
|
||||||
{% endcache %}
|
|
||||||
</div>
|
|
||||||
{% include "actionbar/list.html" %}
|
|
||||||
</div>
|
</div>
|
||||||
<hr style="width: 60%; margin:4em auto;">
|
<div class="body content-description">
|
||||||
{% include "comments/list.html" %}
|
{% cache 86400 'post_content' post.id MATH_ENGINE %}
|
||||||
|
{{ post.content|markdown|reference|str|safe}}
|
||||||
|
{% endcache %}
|
||||||
|
</div>
|
||||||
|
{% include "actionbar/list.html" %}
|
||||||
|
</div>
|
||||||
|
<hr style="width: 60%; margin:4em auto;">
|
||||||
|
{% include "comments/list.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block bodyend %}
|
{% block bodyend %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{% if REQUIRE_JAX %}
|
{% if REQUIRE_JAX %}
|
||||||
{% include "mathjax-load.html" %}
|
{% include "mathjax-load.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "comments/math.html" %}
|
{% include "comments/math.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,52 +1,58 @@
|
||||||
<section class="{% if post.sticky %}sticky {% endif %}blog-box">
|
{% for post in posts%}
|
||||||
|
<section class="{% if post.sticky %}sticky {% endif %}blog-box">
|
||||||
<div style="margin-bottom: 0.5em">
|
<div style="margin-bottom: 0.5em">
|
||||||
<span class="time">
|
<span class="time">
|
||||||
{% with authors=post.authors.all() %}
|
{% with authors=post.authors.all() %}
|
||||||
{%- if authors -%}
|
{%- if authors -%}
|
||||||
<img src="{{gravatar(authors[0])}}" style="width: 1.5em; border-radius: 50%; margin-bottom: -0.3em">
|
<img src="{{gravatar(authors[0])}}" style="width: 1.5em; border-radius: 50%; margin-bottom: -0.3em">
|
||||||
<span class="post-authors">{{ link_users(authors) }}</span>
|
<span class="post-authors">{{ link_users(authors) }}</span>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
•
|
•
|
||||||
{{ relative_time(post.publish_on, abs=_('on {time}'), rel=_('{time}')) -}}
|
{{ relative_time(post.publish_on, abs=_('on {time}'), rel=_('{time}')) -}}
|
||||||
{%- if post.sticky %} •
|
{%- if post.sticky %} •
|
||||||
<i title="Sticky" class="fa fa-star fa-fw"></i>{% endif -%}
|
<i title="Sticky" class="fa fa-star fa-fw"></i>{% endif -%}
|
||||||
{% if post.is_organization_private and show_organization_private_icon %}
|
{% if post.is_organization_private and show_organization_private_icon %}
|
||||||
•
|
•
|
||||||
<span>
|
<span>
|
||||||
{% for org in post.organizations.all() %}
|
{% for org in post.organizations.all() %}
|
||||||
<span class="organization-tag" style="display: inherit;">
|
<span class="organization-tag" style="display: inherit;">
|
||||||
<a href="{{ org.get_absolute_url() }}">
|
<a href="{{ org.get_absolute_url() }}">
|
||||||
<i class="fa fa-lock"></i> {{ org.name }}
|
<i class="fa fa-lock"></i> {{ org.name }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
<span style="float: right">
|
<span style="float: right">
|
||||||
<a href="{{ url('blog_post', post.id, post.slug) }}#comments" class="blog-comment-count-link">
|
<a href="{{ url('blog_post', post.id, post.slug) }}#comments" class="blog-comment-count-link">
|
||||||
<i class="fa fa-comments blog-comment-icon"></i>
|
<i class="fa fa-comments blog-comment-icon"></i>
|
||||||
<span class="blog-comment-count">
|
<span class="blog-comment-count">
|
||||||
{{- post_comment_counts[post.id] or 0 -}}
|
{{- post.comments.filter(hidden=False).count() or 0 -}}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="title">
|
<h2 class="title">
|
||||||
<a href="{{ url('blog_post', post.id, post.slug) }}">{{ post.title }}</a>
|
<a href="{{ url('blog_post', post.id, post.slug) }}">{{ post.title }}</a>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="blog-description">
|
<div class="blog-description">
|
||||||
<div class="summary content-description">
|
<div class="summary content-description">
|
||||||
{% cache 86400 'post_summary' post.id %}
|
{% cache 86400 'post_summary' post.id %}
|
||||||
{{ post.summary|default(post.content, true)|markdown(lazy_load=True)|reference|str|safe }}
|
{{ post.summary|default(post.content, true)|markdown(lazy_load=True)|reference|str|safe }}
|
||||||
{% endcache %}
|
{% endcache %}
|
||||||
</div>
|
</div>
|
||||||
{% set pagevote = post.pagevote %}
|
<div class="show-more"> {{_("...More")}} </div>
|
||||||
{% set bookmark = post.bookmark %}
|
|
||||||
{% set hide_actionbar_comment = True %}
|
|
||||||
{% set include_hr = True %}
|
|
||||||
{% set share_url = request.build_absolute_uri(post.get_absolute_url()) %}
|
|
||||||
{% include "actionbar/list.html" %}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div class="actionbar-box">
|
||||||
|
{% set pagevote = post.pagevote %}
|
||||||
|
{% set bookmark = post.bookmark %}
|
||||||
|
{% set hide_actionbar_comment = True %}
|
||||||
|
{% set include_hr = False %}
|
||||||
|
{% set share_url = request.build_absolute_uri(post.get_absolute_url()) %}
|
||||||
|
{% include "actionbar/list.html" %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endfor %}
|
||||||
|
{% include "feed/has_next.html" %}
|
|
@ -1,34 +1,34 @@
|
||||||
<div class="sidebox dashboard">
|
<div class="sidebox dashboard">
|
||||||
<h3>Dashboard <i class="fa fa-dashboard"></i>
|
<h3>Dashboard <i class="fa fa-dashboard"></i>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="sidebox-content">
|
<div class="sidebox-content">
|
||||||
<div class="user-gravatar">
|
<div class="user-gravatar">
|
||||||
<img src="{{ gravatar(request.user, 135) }}"
|
<img src="{{ gravatar(request.user, 135) }}"
|
||||||
alt="gravatar" width="135px" height="135px">
|
alt="gravatar" width="135px" height="135px">
|
||||||
</div>
|
|
||||||
<div class="recently-attempted">
|
|
||||||
<h4>Recently attempted problems</h4>
|
|
||||||
<ul>
|
|
||||||
{% for code, name, problem_points, user_points, s_date in recently_attempted_problems %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ url('problem_detail', code) }}">{{ name }}</a>
|
|
||||||
[<a href="{{ url('user_submissions', code, request.user.username }}">
|
|
||||||
{{- user_points }}/{{ problem_points|floatformat }}</a>]
|
|
||||||
|
|
||||||
<span class="time">
|
|
||||||
<span data-unix="{{ submission.date|utc|date("c") }}"
|
|
||||||
class="recent-time moment-time-toggle">
|
|
||||||
{% trans time=submission.date|date(_("N j, Y, g:i a")) %}
|
|
||||||
on {{ time }}
|
|
||||||
{% endtrans %}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="recommended-problems">
|
|
||||||
<h4>Recommended problems</h4><i>Coming soon.</i>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="recently-attempted">
|
||||||
|
<h4>Recently attempted problems</h4>
|
||||||
|
<ul>
|
||||||
|
{% for code, name, problem_points, user_points, s_date in recently_attempted_problems %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ url('problem_detail', code) }}">{{ name }}</a>
|
||||||
|
[<a href="{{ url('user_submissions', code, request.user.username }}">
|
||||||
|
{{- user_points }}/{{ problem_points|floatformat }}</a>]
|
||||||
|
|
||||||
|
<span class="time">
|
||||||
|
<span data-unix="{{ submission.date|utc|date("c") }}"
|
||||||
|
class="recent-time moment-time-toggle">
|
||||||
|
{% trans time=submission.date|date(_("N j, Y, g:i a")) %}
|
||||||
|
on {{ time }}
|
||||||
|
{% endtrans %}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="recommended-problems">
|
||||||
|
<h4>Recommended problems</h4><i>Coming soon.</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,115 +1,106 @@
|
||||||
{% extends "three-column-content.html" %}
|
{% extends "three-column-content.html" %}
|
||||||
{% block three_col_media %}
|
{% block three_col_media %}
|
||||||
{% include "blog/media-css.html" %}
|
{% include "blog/media-css.html" %}
|
||||||
{% include "actionbar/media-css.html" %}
|
<style>
|
||||||
<style>
|
@media (max-width: 799px) {
|
||||||
@media (max-width: 799px) {
|
.title {
|
||||||
.title {
|
clear: both;
|
||||||
clear: both;
|
}
|
||||||
}
|
}
|
||||||
}
|
.time {
|
||||||
.time {
|
margin-left: 0;
|
||||||
margin-left: 0;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.no-clarifications-message {
|
.no-clarifications-message {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block three_col_js %}
|
{% block three_col_js %}
|
||||||
{% include "actionbar/media-js.html" %}
|
{% include "actionbar/media-js.html" %}
|
||||||
<script type="text/javascript">
|
{% include "feed/feed_js.html" %}
|
||||||
$(document).ready(function () {
|
<script type="text/javascript">
|
||||||
$('.time-remaining').each(function () {
|
$(document).ready(function () {
|
||||||
count_down($(this));
|
$('.time-remaining').each(function () {
|
||||||
});
|
count_down($(this));
|
||||||
|
});
|
||||||
|
|
||||||
$('.right-sidebar').hide();
|
$('.right-sidebar').hide();
|
||||||
$('#event-tab').click(function (e) {
|
$('#event-tab').click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('.left-sidebar-item').removeClass('active');
|
$('.left-sidebar-item').removeClass('active');
|
||||||
$('#event-tab').addClass('active');
|
$('#event-tab').addClass('active');
|
||||||
$('.middle-content').hide();
|
$('.middle-content').hide();
|
||||||
$('.right-sidebar').show();
|
$('.right-sidebar').show();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block left_sidebar %}
|
{% block left_sidebar %}
|
||||||
<div class="left-sidebar">
|
<div class="left-sidebar">
|
||||||
{{ make_tab_item('blog', 'fa fa-rss', url('home'), _('News')) }}
|
{{ make_tab_item('blog', 'fa fa-rss', url('home'), _('News')) }}
|
||||||
{{ make_tab_item('comment', 'fa fa-comments', url('comment_feed'), _('Comments')) }}
|
{{ make_tab_item('comment', 'fa fa-comments', url('comment_feed'), _('Comments')) }}
|
||||||
{{ make_tab_item('ticket', 'fa fa-question-circle', url('ticket_feed'), _('Tickets')) }}
|
{{ make_tab_item('ticket', 'fa fa-question-circle', url('ticket_feed'), _('Tickets')) }}
|
||||||
{{ make_tab_item('event', 'fa fa-calendar', '#', _('Events')) }}
|
{{ make_tab_item('event', 'fa fa-calendar', '#', _('Events')) }}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block middle_content %}
|
{% block middle_content %}
|
||||||
{% set show_organization_private_icon=True %}
|
{% set show_organization_private_icon=True %}
|
||||||
{% if page_type == 'blog' %}
|
{% if page_type == 'blog' %}
|
||||||
{% for post in posts %}
|
{% include "blog/content.html" %}
|
||||||
{% include "blog/content.html" %}
|
{% elif page_type == 'ticket' %}
|
||||||
{% endfor %}
|
{% if tickets %}
|
||||||
{% elif page_type == 'ticket' %}
|
{% include "ticket/feed.html" %}
|
||||||
{% if tickets %}
|
{% else %}
|
||||||
{% for ticket in tickets %}
|
<h3 style="text-align: center">{{_('You have no ticket')}}</h3>
|
||||||
{% include "ticket/feed.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<h3 style="text-align: center">{{_('You have no ticket')}}</h3>
|
|
||||||
{% endif %}
|
|
||||||
{% elif page_type == 'comment' %}
|
|
||||||
{% for comment in comments %}
|
|
||||||
{% include "comments/feed.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if page_obj.num_pages > 1 %}
|
|
||||||
<div style="margin-bottom:10px;margin-top:10px">{% include "list-pages.html" %}</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% elif page_type == 'comment' %}
|
||||||
|
{% include "comments/feed.html" %}
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block right_sidebar %}
|
{% block right_sidebar %}
|
||||||
<div class="right-sidebar">
|
<div class="right-sidebar">
|
||||||
{% if request.in_contest_mode and request.participation.contest.use_clarifications %}
|
{% if request.in_contest_mode and request.participation.contest.use_clarifications %}
|
||||||
<div class="blog-sidebox sidebox">
|
<div class="blog-sidebox sidebox">
|
||||||
<h3>{{ _('Clarifications') }}
|
<h3>{{ _('Clarifications') }}
|
||||||
<i class="fa fa-question-circle"></i>
|
<i class="fa fa-question-circle"></i>
|
||||||
{% if can_edit_contest %}
|
{% if can_edit_contest %}
|
||||||
<a href="{{url('new_contest_clarification', request.participation.contest.key)}}"
|
<a href="{{url('new_contest_clarification', request.participation.contest.key)}}"
|
||||||
class="fa fa-plus-circle"
|
class="fa fa-plus-circle"
|
||||||
id="add-clarification"
|
id="add-clarification"
|
||||||
title="{{_('Add')}}"
|
title="{{_('Add')}}"
|
||||||
style="color: lightcyan">
|
style="color: lightcyan">
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="sidebox-content">
|
<div class="sidebox-content">
|
||||||
{% if has_clarifications %}
|
{% if has_clarifications %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for clarification in clarifications %}
|
{% for clarification in clarifications %}
|
||||||
<li class="clarification">
|
<li class="clarification">
|
||||||
<a href="{{ url('problem_detail', clarification.problem.problem.code) }}"
|
<a href="{{ url('problem_detail', clarification.problem.problem.code) }}"
|
||||||
class="problem">
|
class="problem">
|
||||||
{{ clarification.problem.problem.name }}
|
{{ clarification.problem.problem.name }}
|
||||||
</a>
|
</a>
|
||||||
<span class="time">{{ relative_time(clarification.date) }}</span>
|
<span class="time">{{ relative_time(clarification.date) }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="no-clarifications-message">
|
<p class="no-clarifications-message">
|
||||||
{{ _('No clarifications have been made at this time.') }}
|
{{ _('No clarifications have been made at this time.') }}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'contests-countdown.html' %}
|
{% include 'contests-countdown.html' %}
|
||||||
{% include 'recent-organization.html' %}
|
{% include 'recent-organization.html' %}
|
||||||
{% include 'top-users.html' %}
|
{% include 'top-users.html' %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,30 +1,30 @@
|
||||||
<style>
|
<style>
|
||||||
.user-gravatar {
|
.user-gravatar {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
width: 135px;
|
width: 135px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-gravatar img {
|
.user-gravatar img {
|
||||||
width: 135px;
|
width: 135px;
|
||||||
height: 135px;
|
height: 135px;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recently-attempted, .recommended-problems {
|
.recently-attempted, .recommended-problems {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recently-attempted ul {
|
.recently-attempted ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
padding-right: .5em;
|
padding-right: .5em;
|
||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recently-attempted h4, .recommended-problems h4 {
|
.recently-attempted h4, .recommended-problems h4 {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
File diff suppressed because it is too large
Load diff
|
@ -1,134 +1,134 @@
|
||||||
<style>
|
<style>
|
||||||
footer {
|
footer {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
|
||||||
margin: 2.5em 1em 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
#content {
|
||||||
background-color: transparent;
|
margin: 2.5em 1em 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar {
|
||||||
background-color: #d6dee1;
|
width: 20px;
|
||||||
border-radius: 20px;
|
}
|
||||||
border: 6px solid transparent;
|
|
||||||
background-clip: content-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-track {
|
||||||
background-color: #a8bbbf;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #d6dee1;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 6px solid transparent;
|
||||||
|
background-clip: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: #a8bbbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-message img{
|
||||||
|
max-height: 12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip:not(.shown) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
left: 120vh !important;
|
||||||
|
transform: translate(100px, 0) !important;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 4%;
|
||||||
|
}
|
||||||
|
.profile-pic {
|
||||||
|
height: 2.6em;
|
||||||
|
width: 2.6em;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
margin-top: 0.1em;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.body-message {
|
||||||
|
padding-left: 3em;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
border-bottom: 1px dotted lightgray;
|
||||||
|
}
|
||||||
|
.user-time {
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
.time {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.clear {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.content-message {
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
.content-message p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#content-body {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
#page-container {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.sidebox h3 {
|
||||||
|
border-radius: 0;
|
||||||
|
margin: -1px -5.5px 0 -5.8px;
|
||||||
|
}
|
||||||
|
.body-block {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.05em 0.6em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#search-form {
|
||||||
|
float: inherit;
|
||||||
|
}
|
||||||
|
#search-container {
|
||||||
|
margin-bottom: 0.4em;
|
||||||
|
}
|
||||||
|
#setting {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (min-width: 800px) {
|
||||||
#page-container {
|
#page-container {
|
||||||
width: 100%;
|
position:fixed;
|
||||||
|
overflow:hidden;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.body-message img{
|
@media (max-width: 799px) {
|
||||||
max-height: 12em;
|
html, body {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
#mobile ul {
|
||||||
.tooltip:not(.shown) {
|
width: 100%;
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
.info-pic {
|
||||||
textarea {
|
margin-left: 0.5em;
|
||||||
resize: none;
|
|
||||||
}
|
}
|
||||||
|
.active-span {
|
||||||
.tooltip {
|
display: none;
|
||||||
left: 120vh !important;
|
|
||||||
transform: translate(100px, 0) !important;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loader {
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
width: 4%;
|
|
||||||
}
|
|
||||||
.profile-pic {
|
|
||||||
height: 2.6em;
|
|
||||||
width: 2.6em;
|
|
||||||
border-radius: 0.3em;
|
|
||||||
margin-top: 0.1em;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.body-message {
|
|
||||||
padding-left: 3em;
|
|
||||||
padding-bottom: 0.5em;
|
|
||||||
border-bottom: 1px dotted lightgray;
|
|
||||||
}
|
|
||||||
.user-time {
|
|
||||||
margin-bottom: 0.3em;
|
|
||||||
}
|
|
||||||
.time {
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
.clear {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
.content-message {
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
.content-message p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#content {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#content-body {
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
#page-container {
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
.sidebox h3 {
|
|
||||||
border-radius: 0;
|
|
||||||
margin: -1px -5.5px 0 -5.8px;
|
|
||||||
}
|
|
||||||
.body-block {
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0.05em 0.6em;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#search-form {
|
|
||||||
float: inherit;
|
|
||||||
}
|
|
||||||
#search-container {
|
|
||||||
margin-bottom: 0.4em;
|
|
||||||
}
|
|
||||||
#setting {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
|
||||||
#page-container {
|
|
||||||
position:fixed;
|
|
||||||
overflow:hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 799px) {
|
|
||||||
html, body {
|
|
||||||
max-width: 100%;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
#mobile ul {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.info-pic {
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
.active-span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
<li class="message" id="message-{{ 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.user.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.user.username) }}">
|
||||||
{{ message.author }}
|
{{ message.author }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="time">
|
<span class="time">
|
||||||
{{ relative_time(message.time, abs=_('{time}'), rel=_('{time}'), format=_('g:i a d/m/Y')) }}
|
{{ relative_time(message.time, abs=_('{time}'), rel=_('{time}'), format=_('g:i a d/m/Y')) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
<span class="content-message">
|
|
||||||
<div class="body-block" id="body-block-{{ message.id }}" title="{{ message.time|date('g:i a') }}">
|
|
||||||
{% if request.user.is_staff %}
|
|
||||||
<a class="red chatbtn_remove_mess" value="{{message.id}}" style="cursor: pointer;">
|
|
||||||
{{_('Delete')}}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<div class="message-text message-text-other">
|
|
||||||
{{message.body|markdown(lazy_load=True)|reference|str|safe }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="clear"></div>
|
<span class="content-message">
|
||||||
|
<div class="body-block" id="body-block-{{ message.id }}" title="{{ message.time|date('g:i a') }}">
|
||||||
|
{% if request.user.is_staff %}
|
||||||
|
<a class="red chatbtn_remove_mess" value="{{message.id}}" style="cursor: pointer;">
|
||||||
|
{{_('Delete')}}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<div class="message-text message-text-other">
|
||||||
|
{{message.body|markdown(lazy_load=False)|reference|str|safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="clear"></div>
|
||||||
</li>
|
</li>
|
|
@ -1,12 +1,13 @@
|
||||||
|
<div class="has_next" style="display: none;" value="{{1 if has_next else 0}}"></div>
|
||||||
{% if object_list %}
|
{% if object_list %}
|
||||||
<div style="display: none" id="num_pages">{{num_pages}}</div>
|
<div style="display: none" id="num_pages">{{num_pages}}</div>
|
||||||
{% for message in object_list | reverse%}
|
{% for message in object_list | reverse%}
|
||||||
{% include "chat/message.html" %}
|
{% include "chat/message.html" %}
|
||||||
{% 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 %}
|
{% if REQUIRE_JAX %}
|
||||||
{% include "mathjax-load.html" %}
|
{% include "mathjax-load.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "comments/math.html" %}
|
{% include "comments/math.html" %}
|
|
@ -1,40 +1,40 @@
|
||||||
<li class="status-row" id="lobby_row">
|
<li class="status-row" id="lobby_row">
|
||||||
<div class="status-container">
|
<div class="status-container">
|
||||||
<img src="{{ static('icons/logo.png') }}" style="height:1.3em">
|
<img src="{{ static('icons/logo.png') }}" style="height:1.3em">
|
||||||
</div>
|
</div>
|
||||||
<span style="padding-left:0.5em">
|
<span style="padding-left:0.5em">
|
||||||
<b>{{_('Lobby')}}</b>
|
<b>{{_('Lobby')}}</b>
|
||||||
</span>
|
</span>
|
||||||
<span class="spacer">
|
<span class="spacer">
|
||||||
<span class="unread-count" id="unread-count-lobby">{{unread_count_lobby if unread_count_lobby}}</span>
|
<span class="unread-count" id="unread-count-lobby">{{unread_count_lobby if unread_count_lobby}}</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{% for section in status_sections %}
|
{% for section in status_sections %}
|
||||||
{% if section.user_list %}
|
{% if section.user_list %}
|
||||||
<div class="status-section-title toggle open">
|
<div class="status-section-title toggle open">
|
||||||
<h4>
|
<h4>
|
||||||
<i class="fa fa-chevron-right fa-fw"></i>{{_(section.title)}}
|
<i class="fa fa-chevron-right fa-fw"></i>{{_(section.title)}}
|
||||||
</h4>
|
</h4>
|
||||||
<hr/>
|
<hr/>
|
||||||
</div>
|
</div>
|
||||||
<ul class="status-list toggled">
|
<ul class="status-list toggled">
|
||||||
{% for user in section.user_list %}
|
{% for user in section.user_list %}
|
||||||
<li class="click_space status-row" id="click_space_{{user.user.id}}" value="{{user.url}}">
|
<li class="click_space status-row" id="click_space_{{user.user.id}}" value="{{user.url}}">
|
||||||
<div class="status-container">
|
<div class="status-container">
|
||||||
<img src="{{ gravatar(user.user, 135) }}" class="status-pic">
|
<img src="{{ gravatar(user.user, 135) }}" class="status-pic">
|
||||||
<svg style="position:absolute;" height="32" width="32">
|
<svg style="position:absolute;" height="32" width="32">
|
||||||
<circle class="status-circle"
|
<circle class="status-circle"
|
||||||
fill="{{'green' if user.is_online else 'red'}}"/>
|
fill="{{'green' if user.is_online else 'red'}}"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span style="padding-left:0.3em" class="username {{ user.user.css_class }}">
|
<span style="padding-left:0.3em" class="username {{ user.user.css_class }}">
|
||||||
{{ user.user.username }}
|
{{ user.user.username }}
|
||||||
</span>
|
</span>
|
||||||
<span class="spacer">
|
<span class="spacer">
|
||||||
<span class="unread-count" id="unread-count-{{user.user.id}}">{{user.unread_count if user.unread_count}}</span>
|
<span class="unread-count" id="unread-count-{{user.user.id}}">{{user.unread_count if user.unread_count}}</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,41 +1,41 @@
|
||||||
{% if other_user %}
|
{% if other_user %}
|
||||||
<div class="status-container" style="height: 100%">
|
<div class="status-container" style="height: 100%">
|
||||||
<img src="{{ gravatar(other_user.user, 135) }}" class="info-pic">
|
<img src="{{ gravatar(other_user.user, 135) }}" class="info-pic">
|
||||||
<svg style="position:absolute; height:100%; width: 110%">
|
<svg style="position:absolute; height:100%; width: 110%">
|
||||||
<circle class="info-circle"
|
<circle class="info-circle"
|
||||||
fill="{{'green' if other_online else 'red'}}"/>
|
fill="{{'green' if other_online else 'red'}}"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="info-name username">
|
<span class="info-name username">
|
||||||
{% if other_user %}
|
{% if other_user %}
|
||||||
<a href="{{url('user_page', other_user)}}">{{other_user.user.username}}</a>
|
<a href="{{url('user_page', other_user)}}">{{other_user.user.username}}</a>
|
||||||
{% else%}
|
{% else%}
|
||||||
<a href="#" style="margin-left: 3em">{{ _('Lobby') }}</a>
|
<a href="#" style="margin-left: 3em">{{ _('Lobby') }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
{% if other_user and not other_online %}
|
{% if other_user and not other_online %}
|
||||||
<span class="active-span">{{ relative_time(other_user.last_access, abs=_('Last online on {time}'), rel=_('Online {time}'), format=_('g:i a d/m/Y')) }}</span>
|
<span class="active-span">{{ relative_time(other_user.last_access, abs=_('Last online on {time}'), rel=_('Online {time}'), format=_('g:i a d/m/Y')) }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if other_user %}
|
{% if other_user %}
|
||||||
<span style="margin-right: 0.3em" id="setting">
|
<span style="margin-right: 0.3em" id="setting">
|
||||||
<button class="control-button small" style="height:100%;" id="setting-button">
|
<button class="control-button small" style="height:100%;" id="setting-button">
|
||||||
<i class="fa fa-ellipsis-h"></i>
|
<i class="fa fa-ellipsis-h"></i>
|
||||||
</button>
|
</button>
|
||||||
<div id="setting-content">
|
<div id="setting-content">
|
||||||
<li>
|
<li>
|
||||||
<a href="{{url('toggle_ignore', other_user.id)}}" class=" {{'green' if is_ignored else 'red'}}">
|
<a href="{{url('toggle_ignore', other_user.id)}}" class=" {{'green' if is_ignored else 'red'}}">
|
||||||
{% if is_ignored %}
|
{% if is_ignored %}
|
||||||
{{_('Unignore')}}
|
{{_('Unignore')}}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{_('Ignore')}}
|
{{_('Ignore')}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="active-span">{{online_count}} {{_('users are online')}}</span>
|
<span class="active-span">{{online_count}} {{_('users are online')}}</span>
|
||||||
{% endif %}
|
{% endif %}
|
|
@ -1,14 +1,14 @@
|
||||||
<div class="comment-submit">
|
<div class="comment-submit">
|
||||||
<form id="comment-edit" action="{{ request.get_full_path() }}" method="post">
|
<form id="comment-edit" action="{{ request.get_full_path() }}" method="post">
|
||||||
<span style="display: none" class="comment-id">{{ comment.id }}</span>
|
<span style="display: none" class="comment-id">{{ comment.id }}</span>
|
||||||
<span style="display: none" class="read-back">{{ url('comment_content', comment.id) }}</span>
|
<span style="display: none" class="read-back">{{ url('comment_content', comment.id) }}</span>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.non_field_errors() }}
|
{{ form.non_field_errors() }}
|
||||||
{{ form.body.errors }}
|
{{ form.body.errors }}
|
||||||
<div class="comment-post-wrapper">
|
<div class="comment-post-wrapper">
|
||||||
<div id="comment-form-body">{{ form.body }}</div>
|
<div id="comment-form-body">{{ form.body }}</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<input style="float: right" type="submit" value="Post!" class="button">
|
<input style="float: right" type="submit" value="Post!" class="button">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block media %}
|
{% block media %}
|
||||||
{% compress css %}
|
{% compress css %}
|
||||||
{{ form.media.css }}
|
{{ form.media.css }}
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js_media %}
|
{% block js_media %}
|
||||||
{{ form.media.js }}
|
{{ form.media.js }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="form-area">{% include "comments/edit-ajax.html" %}</div>
|
<div class="form-area">{% include "comments/edit-ajax.html" %}</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,18 +1,22 @@
|
||||||
<div class="blog-box">
|
{% for comment in comments %}
|
||||||
<h3 class="problem-feed-name">
|
<div class="blog-box">
|
||||||
<a href="{{ comment.link }}#comment-{{ comment.id }}">
|
<h3 class="problem-feed-name">
|
||||||
{{ page_titles[comment.page] }}
|
<a href="{{ comment.get_absolute_url() }}">
|
||||||
</a>
|
{{ comment.page_title }}
|
||||||
</h3>
|
</a>
|
||||||
{% with author=comment.author %}
|
</h3>
|
||||||
{% if author %}
|
{% with author=comment.author %}
|
||||||
<div class="problem-feed-info-entry">
|
{% if author %}
|
||||||
<i class="fa fa-pencil-square-o fa-fw"></i>
|
<div class="problem-feed-info-entry">
|
||||||
<span class="pi-value">{{ link_user(author) }}</span>
|
<i class="fa fa-pencil-square-o fa-fw"></i>
|
||||||
</div>
|
<span class="pi-value">{{ link_user(author) }}</span>
|
||||||
{% endif %}
|
</div>
|
||||||
{% endwith %}
|
{% endif %}
|
||||||
<div class='blog-description content-description'>
|
{% endwith %}
|
||||||
{{ comment.body|markdown(lazy_load=True)|reference|str|safe }}
|
<div class='blog-description content-description'>
|
||||||
</div>
|
{{ comment.body|markdown(lazy_load=True)|reference|str|safe }}
|
||||||
</div>
|
<div class="show-more"> {{_("...More")}} </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% include "feed/has_next.html" %}
|
|
@ -1,165 +1,162 @@
|
||||||
{% set can_comment = request.user.is_authenticated and comment_form and not comment_lock %}
|
{% set can_comment = request.user.is_authenticated and comment_form and not comment_lock %}
|
||||||
<div id="comments" class="comment-area">
|
<div id="comments" class="comment-area">
|
||||||
<h2 id="comment-header">
|
<h2 id="comment-header">
|
||||||
<i style="padding-right: 0.3em" class="fa fa-comments"></i>{{ _('Comments') }}
|
<i style="padding-right: 0.3em" class="fa fa-comments"></i>{{ _('Comments') }}
|
||||||
{% if can_comment %}
|
|
||||||
<a href="" id="write-comment" style="float: right; font-size: 0.6em; margin-right: -26px;"> {{ _('Write comment') }} </a>
|
|
||||||
{% endif %}
|
|
||||||
</h2>
|
|
||||||
{% if can_comment %}
|
{% if can_comment %}
|
||||||
<div id="new-comment" class="form-area comment-submit" style="display: none;" >
|
<a href="" id="write-comment" style="float: right; font-size: 0.6em; margin-right: -26px;"> {{ _('Write comment') }} </a>
|
||||||
{% block comment_submit_title %}
|
{% endif %}
|
||||||
<h3>{{ _('New comment') }}</h3>
|
</h2>
|
||||||
<hr>
|
{% if can_comment %}
|
||||||
{% endblock %}
|
<div id="new-comment" class="form-area comment-submit" style="display: none;" >
|
||||||
{% if is_new_user %}
|
{% block comment_submit_title %}
|
||||||
<div style="margin-bottom: 0" class="alert alert-info">
|
<h3>{{ _('New comment') }}</h3>
|
||||||
{{ _('You need to have solved at least one problem before your voice can be heard.') }}
|
<hr>
|
||||||
|
{% endblock %}
|
||||||
|
{% if is_new_user %}
|
||||||
|
<div style="margin-bottom: 0" class="alert alert-info">
|
||||||
|
{{ _('You need to have solved at least one problem before your voice can be heard.') }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<form class="comment-submit-form" action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if comment_form.errors %}
|
||||||
|
<div id="form-errors">
|
||||||
|
{{ comment_form.non_field_errors() }}
|
||||||
|
{{ comment_form.parent.errors }}
|
||||||
|
{% if comment_form.body.errors %}{{ _('Invalid comment body.') }}{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ comment_form.parent }}
|
||||||
|
<div class="comment-post-wrapper">
|
||||||
|
<div id="comment-form-body">{{ comment_form.body }}</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<input style="float:right" type="submit" value="{{ _('Post!') }}" class="button">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if has_comments %}
|
||||||
|
<ul class="comments top-level-comments new-comments">
|
||||||
|
{% set logged_in = request.user.is_authenticated %}
|
||||||
|
{% set profile = request.profile if logged_in else None %}
|
||||||
|
{% for node in mptt_tree(comment_list) recursive %}
|
||||||
|
<li id="comment-{{ node.id }}" data-revision="{{ node.revisions - 1 }}"
|
||||||
|
data-max-revision="{{ node.revisions - 1 }}"
|
||||||
|
data-revision-ajax="{{ url('comment_revision_ajax', node.id) }}" class="comment">
|
||||||
|
<div class="comment-display{% if node.score <= vote_hide_threshold %} bad-comment{% endif %}">
|
||||||
|
<div class="info">
|
||||||
|
<div class="vote">
|
||||||
|
{% if logged_in %}
|
||||||
|
<a href="javascript:comment_upvote({{ node.id }})"
|
||||||
|
class="upvote-link fa fa-chevron-up fa-fw{% if node.vote_score == 1 %} voted{% endif %}"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="javascript:alert('{{ _('Please login to vote')|escapejs }}')" title="{{ _('Please login to vote') }}"
|
||||||
|
class="upvote-link fa fa-chevron-up fa-fw"></a>
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
<div class="comment-score">{{ node.score }}</div>
|
||||||
|
{% if logged_in %}
|
||||||
|
<a href="javascript:comment_downvote({{ node.id }})"
|
||||||
|
class="downvote-link fa fa-chevron-down fa-fw{% if node.vote_score == -1 %} voted{% endif %}"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="javascript:alert('{{ _('Please login to vote')|escapejs }}')" title="{{ _('Please login to vote') }}"
|
||||||
|
class="downvote-link fa fa-chevron-down fa-fw"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% with author=node.author, user=node.author.user %}
|
||||||
|
<a href="{{ url('user_page', user.username) }}" class="user">
|
||||||
|
<img src="{{ gravatar(author, 135) }}" class="gravatar">
|
||||||
|
</a>
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
<div class="detail">
|
||||||
|
<div class="header">
|
||||||
|
{{ link_user(node.author) }}
|
||||||
|
{{ relative_time(node.time, abs=_('commented on {time}'), rel=_('commented {time}')) }}
|
||||||
|
<span class="comment-spacer"></span>
|
||||||
|
<span class="comment-operation">
|
||||||
|
{% if node.revisions > 1 %}
|
||||||
|
<span class="comment-edits">
|
||||||
|
<a href="javascript:show_revision({{ node.id }}, -1)"
|
||||||
|
class="previous-revision">←</a>
|
||||||
|
<span class="comment-edit-text">
|
||||||
|
{% if node.revisions > 2 %}
|
||||||
|
{% trans edits=node.revisions - 1 %}edit {{ edits }}{% endtrans %}
|
||||||
|
{% else %}
|
||||||
|
{{ _('edited') }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<a href="javascript:show_revision({{ node.id }}, 1)" style="visibility: hidden"
|
||||||
|
class="next-revision">→</a>
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="comment-edits"></span>
|
||||||
|
{% endif %}
|
||||||
|
<a href="#comment-{{ node.id }}" title="{{ _('Link') }}" class="comment-link">
|
||||||
|
<i class="fa fa-link fa-fw"></i>
|
||||||
|
</a>
|
||||||
|
{% if logged_in and not comment_lock %}
|
||||||
|
{% set can_edit = node.author.id == profile.id and not profile.mute %}
|
||||||
|
{% if can_edit %}
|
||||||
|
<a data-featherlight="{{ url('comment_edit_ajax', node.id) }}"
|
||||||
|
href="{{ url('comment_edit', node.id) }}"
|
||||||
|
title="{{ _('Edit') }}" class="edit-link">
|
||||||
|
<i class="fa fa-pencil fa-fw"></i>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="javascript:reply_comment({{ node.id }})"
|
||||||
|
title="{{ _('Reply') }}">
|
||||||
|
<i class="fa fa-reply fa-fw"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.judge.change_comment %}
|
||||||
|
{% if can_edit %}
|
||||||
|
<a href="javascript:reply_comment({{ node.id }})"
|
||||||
|
title="{{ _('Reply') }}"><i class="fa fa-reply fa-fw"></i></a>
|
||||||
|
{% else %}
|
||||||
|
<a data-featherlight="{{ url('comment_edit_ajax', node.id) }}"
|
||||||
|
href="{{ url('comment_edit', node.id) }}" title="{{ _('Edit') }}"
|
||||||
|
class="edit-link"><i class="fa fa-pencil fa-fw"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="javascript:" title="{{ _('Hide') }}" data-id="{{ node.id }}"
|
||||||
|
class="hide-comment"><i class="fa fa-trash fa-fw"></i></a>
|
||||||
|
<a href="{{ url('admin:judge_comment_change', node.id) }}"
|
||||||
|
title="{{ _('Admin') }}"><i class="fa fa-cog fa-fw"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="content content-description">
|
||||||
|
<div class="comment-body"{% if node.score <= vote_hide_threshold %} style="display:none"{% endif %}>
|
||||||
|
{{ node.body|markdown(lazy_load=True)|reference|str|safe }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% if node.score <= vote_hide_threshold %}
|
||||||
<form class="comment-submit-form" action="" method="post">
|
<div class="comment-body bad-comment-body">
|
||||||
{% csrf_token %}
|
<p>
|
||||||
{% if comment_form.errors %}
|
{% trans id=node.id %}This comment is hidden due to too much negative feedback. Click <a href="javascript:comment_show_content({{ id }})">here</a> to view it.{% endtrans %}
|
||||||
<div id="form-errors">
|
</p>
|
||||||
{{ comment_form.non_field_errors() }}
|
</div>
|
||||||
{{ comment_form.parent.errors }}
|
{% endif %}
|
||||||
{% if comment_form.body.errors %}{{ _('Invalid comment body.') }}{% endif %}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
{{ comment_form.parent }}
|
</li>
|
||||||
<div class="comment-post-wrapper">
|
<ul id="comment-{{ node.id }}-reply" class="reply-comment" hidden></ul>
|
||||||
<div id="comment-form-body">{{ comment_form.body }}</div>
|
{% with children=node.get_children() %}
|
||||||
</div>
|
{% if children %}
|
||||||
<hr>
|
<ul id="comment-{{ node.id }}-children" class="comments">{{ loop(children) }}</ul>
|
||||||
<input style="float:right" type="submit" value="{{ _('Post!') }}" class="button">
|
{% endif %}
|
||||||
</form>
|
{% endwith %}
|
||||||
{% endif %}
|
{% endfor %}
|
||||||
</div>
|
</ul>
|
||||||
{% endif %}
|
{% elif not comment_lock %}
|
||||||
{% if has_comments %}
|
<p class="no-comments-message">{{ _('There are no comments at the moment.') }}</p>
|
||||||
<ul class="comments top-level-comments new-comments">
|
{% endif %}
|
||||||
{% set logged_in = request.user.is_authenticated %}
|
|
||||||
{% set profile = request.profile if logged_in else None %}
|
|
||||||
{% for node in mptt_tree(comment_list) recursive %}
|
|
||||||
<li id="comment-{{ node.id }}" data-revision="{{ node.revisions - 1 }}"
|
|
||||||
data-max-revision="{{ node.revisions - 1 }}"
|
|
||||||
data-revision-ajax="{{ url('comment_revision_ajax', node.id) }}" class="comment">
|
|
||||||
<div class="comment-display{% if node.score <= vote_hide_threshold %} bad-comment{% endif %}">
|
|
||||||
<div class="info">
|
|
||||||
<div class="vote">
|
|
||||||
{% if logged_in %}
|
|
||||||
<a href="javascript:comment_upvote({{ node.id }})"
|
|
||||||
class="upvote-link fa fa-chevron-up fa-fw{% if node.vote_score == 1 %} voted{% endif %}"></a>
|
|
||||||
{% else %}
|
|
||||||
<a href="javascript:alert('{{ _('Please login to vote')|escapejs }}')" title="{{ _('Please login to vote') }}"
|
|
||||||
class="upvote-link fa fa-chevron-up fa-fw"></a>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
<div class="comment-score">{{ node.score }}</div>
|
|
||||||
{% if logged_in %}
|
|
||||||
<a href="javascript:comment_downvote({{ node.id }})"
|
|
||||||
class="downvote-link fa fa-chevron-down fa-fw{% if node.vote_score == -1 %} voted{% endif %}"></a>
|
|
||||||
{% else %}
|
|
||||||
<a href="javascript:alert('{{ _('Please login to vote')|escapejs }}')" title="{{ _('Please login to vote') }}"
|
|
||||||
class="downvote-link fa fa-chevron-down fa-fw"></a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% with author=node.author, user=node.author.user %}
|
|
||||||
<a href="{{ url('user_page', user.username) }}" class="user">
|
|
||||||
<img src="{{ gravatar(author, 135) }}" class="gravatar">
|
|
||||||
</a>
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
<div class="detail">
|
|
||||||
<div class="header">
|
|
||||||
{{ link_user(node.author) }}
|
|
||||||
{{ relative_time(node.time, abs=_('commented on {time}'), rel=_('commented {time}')) }}
|
|
||||||
<span class="comment-spacer"></span>
|
|
||||||
<span class="comment-operation">
|
|
||||||
{% if node.revisions > 1 %}
|
|
||||||
<span class="comment-edits">
|
|
||||||
<a href="javascript:show_revision({{ node.id }}, -1)"
|
|
||||||
class="previous-revision">←</a>
|
|
||||||
<span class="comment-edit-text">
|
|
||||||
{% if node.revisions > 2 %}
|
|
||||||
{% trans edits=node.revisions - 1 %}edit {{ edits }}{% endtrans %}
|
|
||||||
{% else %}
|
|
||||||
{{ _('edited') }}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
<a href="javascript:show_revision({{ node.id }}, 1)" style="visibility: hidden"
|
|
||||||
class="next-revision">→</a>
|
|
||||||
</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="comment-edits"></span>
|
|
||||||
{% endif %}
|
|
||||||
<a href="#comment-{{ node.id }}" title="{{ _('Link') }}" class="comment-link">
|
|
||||||
<i class="fa fa-link fa-fw"></i>
|
|
||||||
</a>
|
|
||||||
{% if logged_in and not comment_lock %}
|
|
||||||
{% set can_edit = node.author.id == profile.id and not profile.mute %}
|
|
||||||
{% if can_edit %}
|
|
||||||
<a data-featherlight="{{ url('comment_edit_ajax', node.id) }}"
|
|
||||||
href="{{ url('comment_edit', node.id) }}"
|
|
||||||
title="{{ _('Edit') }}" class="edit-link">
|
|
||||||
<i class="fa fa-pencil fa-fw"></i>
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<a href="javascript:reply_comment({{ node.id }})"
|
|
||||||
title="{{ _('Reply') }}">
|
|
||||||
<i class="fa fa-reply fa-fw"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if perms.judge.change_comment %}
|
|
||||||
{% if can_edit %}
|
|
||||||
<a href="javascript:reply_comment({{ node.id }})"
|
|
||||||
title="{{ _('Reply') }}"><i class="fa fa-reply fa-fw"></i></a>
|
|
||||||
{% else %}
|
|
||||||
<a data-featherlight="{{ url('comment_edit_ajax', node.id) }}"
|
|
||||||
href="{{ url('comment_edit', node.id) }}" title="{{ _('Edit') }}"
|
|
||||||
class="edit-link"><i class="fa fa-pencil fa-fw"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="javascript:" title="{{ _('Hide') }}" data-id="{{ node.id }}"
|
|
||||||
class="hide-comment"><i class="fa fa-trash fa-fw"></i></a>
|
|
||||||
<a href="{{ url('admin:judge_comment_change', node.id) }}"
|
|
||||||
title="{{ _('Admin') }}"><i class="fa fa-cog fa-fw"></i></a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="content content-description">
|
|
||||||
<div class="comment-body"{% if node.score <= vote_hide_threshold %} style="display:none"{% endif %}>
|
|
||||||
{{ node.body|markdown(lazy_load=True)|reference|str|safe }}
|
|
||||||
</div>
|
|
||||||
{% if node.score <= vote_hide_threshold %}
|
|
||||||
<div class="comment-body bad-comment-body">
|
|
||||||
<p>
|
|
||||||
{% trans id=node.id %}
|
|
||||||
This comment is hidden due to too much negative feedback.
|
|
||||||
Click <a href="javascript:comment_show_content({{ id }})">here</a> to view it.
|
|
||||||
{% endtrans %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<ul id="comment-{{ node.id }}-reply" class="reply-comment" hidden></ul>
|
|
||||||
{% with children=node.get_children() %}
|
|
||||||
{% if children %}
|
|
||||||
<ul id="comment-{{ node.id }}-children" class="comments">{{ loop(children) }}</ul>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% elif not comment_lock %}
|
|
||||||
<p class="no-comments-message">{{ _('There are no comments at the moment.') }}</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if comment_lock %}
|
{% if comment_lock %}
|
||||||
<div class="alert alert-warning comment-lock">
|
<div class="alert alert-warning comment-lock">
|
||||||
{{ _('Comments are disabled on this page.') }}
|
{{ _('Comments are disabled on this page.') }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{% compress js, inline %}
|
{% compress js, inline %}
|
||||||
<script src="{{ static('pagedown_math.js') }}"></script>
|
<script src="{{ static('pagedown_math.js') }}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{% compress css %}
|
{% compress css %}
|
||||||
{{ comment_form.media.css }}
|
{{ comment_form.media.css }}
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
|
|
|
@ -1,231 +1,231 @@
|
||||||
<script src="{{ static('libs/featherlight/featherlight.min.js') }}" type="text/javascript"></script>
|
<script src="{{ static('libs/featherlight/featherlight.min.js') }}" type="text/javascript"></script>
|
||||||
{% compress js %}
|
{% compress js %}
|
||||||
{{ comment_form.media.js }}
|
{{ comment_form.media.js }}
|
||||||
{% if not REQUIRE_JAX %}
|
{% if not REQUIRE_JAX %}
|
||||||
<script type="text/javascript">
|
|
||||||
$(function () {
|
|
||||||
$('#id_body').keypress(function () {
|
|
||||||
if (!("MathJax" in window)) {
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
url: '{{ static('mathjax3_config.js') }}',
|
|
||||||
dataType: "script",
|
|
||||||
cache: true,
|
|
||||||
success: function () {
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
url: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js',
|
|
||||||
dataType: "script",
|
|
||||||
cache: true,
|
|
||||||
success: function () {
|
|
||||||
mathjax_pagedown($);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endif %}
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function () {
|
$(function () {
|
||||||
window.reply_comment = function (parent) {
|
$('#id_body').keypress(function () {
|
||||||
var $comment_reply = $('#comment-' + parent + '-reply');
|
if (!("MathJax" in window)) {
|
||||||
var reply_id = 'reply-' + parent;
|
$.ajax({
|
||||||
var new_id = 'id' + parent + '_body';
|
type: "GET",
|
||||||
if ($comment_reply.find('#' + reply_id).length == 0) {
|
url: '{{ static('mathjax3_config.js') }}',
|
||||||
var $reply_form = $('#new-comment').clone(true).prop('id', reply_id).css("display", "");
|
dataType: "script",
|
||||||
$reply_form.find('h3').html('{{ _('Replying to comment') }}');
|
cache: true,
|
||||||
$reply_form.prepend('<a class="close">x</a>');
|
success: function () {
|
||||||
$reply_form.find('form.comment-submit-form input#id_parent').val(parent);
|
$.ajax({
|
||||||
$reply_form.find('div#wmd-button-bar-id_body').empty().prop('id','wmd-button-bar-' + new_id);
|
type: "GET",
|
||||||
$reply_form.find('textarea.wmd-input').val('').prop('id', 'wmd-input-' + new_id);
|
url: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js',
|
||||||
$reply_form.find('div#id_body-preview').attr('data-textarea-id', 'wmd-input-' + new_id).prop('id', new_id + '-preview');
|
dataType: "script",
|
||||||
$reply_form.appendTo($comment_reply);
|
cache: true,
|
||||||
register_dmmd_preview($reply_form.find('div#' + new_id + '-preview'));
|
success: function () {
|
||||||
if ('DjangoPagedown' in window) {
|
mathjax_pagedown($);
|
||||||
window.DjangoPagedown.createEditor($reply_form.find('div.wmd-wrapper').get(0));
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
$comment_reply.fadeIn();
|
|
||||||
|
|
||||||
$('html, body').animate({
|
|
||||||
scrollTop: $comment_reply.offset().top - $('#navigation').height() - 4
|
|
||||||
}, 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
$(document).on('click', '.close', function() {
|
|
||||||
$(this).closest('.reply-comment').fadeOut();
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
function update_math($comment) {
|
|
||||||
if ('MathJax' in window) {
|
|
||||||
var $body = $comment.find('.comment-body');
|
|
||||||
MathJax.typeset($body[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.show_revision = function (comment_id, offset) {
|
|
||||||
var $comment = $("#comment-" + comment_id);
|
|
||||||
|
|
||||||
// If .comment-body is hidden, then this is a bad comment that the user has not clicked
|
|
||||||
// Thus the revision retrieval should do nothing
|
|
||||||
if (!$comment.find('.comment-body').is(':visible'))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var cur_revision = parseInt($comment.attr("data-revision"));
|
|
||||||
var max_revision = parseInt($comment.attr("data-max-revision"));
|
|
||||||
var revision_ajax = $comment.attr("data-revision-ajax");
|
|
||||||
var show_revision = cur_revision + offset;
|
|
||||||
|
|
||||||
$comment.attr("data-revision", show_revision);
|
|
||||||
|
|
||||||
$.get(revision_ajax, {
|
|
||||||
revision: show_revision
|
|
||||||
}).done(function (body) {
|
|
||||||
$comment.find('.previous-revision').css({visibility: show_revision == 0 ? 'hidden' : ''});
|
|
||||||
$comment.find('.next-revision').css({visibility: show_revision == max_revision ? 'hidden' : ''});
|
|
||||||
$comment.find('.content').html(body);
|
|
||||||
|
|
||||||
var edit_text = '{{ _('edit {edits}') }}'.replace("{edits}", show_revision);
|
|
||||||
|
|
||||||
if (show_revision == 0) {
|
|
||||||
edit_text = '{{ _('original') }}';
|
|
||||||
} else if (show_revision == max_revision && max_revision == 1) {
|
|
||||||
edit_text = '{{ _('edited') }}';
|
|
||||||
}
|
|
||||||
|
|
||||||
$comment.find('.comment-edit-text').text(' ' + edit_text + ' ');
|
|
||||||
update_math($comment);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function ajax_vote(url, id, delta, on_success) {
|
|
||||||
return $.ajax({
|
|
||||||
url: url,
|
|
||||||
type: 'POST',
|
|
||||||
data: {
|
|
||||||
id: id
|
|
||||||
},
|
|
||||||
success: function (data, textStatus, jqXHR) {
|
|
||||||
var score = $('#comment-' + id + ' .comment-score').first();
|
|
||||||
score.text(parseInt(score.text()) + delta);
|
|
||||||
if (typeof on_success !== 'undefined')
|
|
||||||
on_success();
|
|
||||||
},
|
|
||||||
error: function (data, textStatus, jqXHR) {
|
|
||||||
alert('Could not vote: ' + data.responseText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var get_$votes = function (id) {
|
|
||||||
var $comment = $('#comment-' + id);
|
|
||||||
return {
|
|
||||||
upvote: $comment.find('.upvote-link').first(),
|
|
||||||
downvote: $comment.find('.downvote-link').first()
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
window.comment_upvote = function (id) {
|
|
||||||
ajax_vote('{{ url('comment_upvote') }}', id, 1, function () {
|
|
||||||
var $votes = get_$votes(id);
|
|
||||||
if ($votes.downvote.hasClass('voted'))
|
|
||||||
$votes.downvote.removeClass('voted');
|
|
||||||
else
|
|
||||||
$votes.upvote.addClass('voted');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.comment_downvote = function (id) {
|
|
||||||
ajax_vote('{{ url('comment_downvote') }}', id, -1, function () {
|
|
||||||
var $votes = get_$votes(id);
|
|
||||||
if ($votes.upvote.hasClass('voted'))
|
|
||||||
$votes.upvote.removeClass('voted');
|
|
||||||
else
|
|
||||||
$votes.downvote.addClass('voted');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var $comments = $('.comments');
|
|
||||||
$comments.find('a.hide-comment').click(function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!(e.ctrlKey || e.metaKey || confirm('Are you sure you want to hide this comment?')))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var id = $(this).attr('data-id');
|
|
||||||
$.post('{{ url('comment_hide') }}', {id: id}).then(function () {
|
|
||||||
$('#comment-' + id).remove();
|
|
||||||
$('#comment-' + id + '-children').remove();
|
|
||||||
}).catch(function () {
|
|
||||||
alert('Failed.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$comments.find('a.edit-link').featherlight({
|
|
||||||
afterOpen: function () {
|
|
||||||
register_dmmd_preview($('#id-edit-comment-body-preview'));
|
|
||||||
if ('DjangoPagedown' in window) {
|
|
||||||
var $wmd = $('.featherlight .wmd-wrapper');
|
|
||||||
if ($wmd.length) {
|
|
||||||
window.DjangoPagedown.createEditor($wmd.get(0));
|
|
||||||
if ('MathJax' in window) {
|
|
||||||
var preview = $('.featherlight div.wmd-preview')[0];
|
|
||||||
MathJax.typeset(preview);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('#comment-edit').submit(function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
var id = $('#comment-edit').find('.comment-id').text();
|
|
||||||
var readback = $('#comment-edit').find('.read-back').text();
|
|
||||||
$.post($(this).attr('action'), $(this).serialize()).done(function (data) {
|
|
||||||
$.featherlight.current().close();
|
|
||||||
$.ajax({
|
|
||||||
url: readback
|
|
||||||
}).done(function (data) {
|
|
||||||
var $comment = $('#comment-' + id);
|
|
||||||
var $area = $comment.find('.comment-body').first();
|
|
||||||
$area.html(data);
|
|
||||||
update_math($comment);
|
|
||||||
var $edits = $comment.find('.comment-edits').first();
|
|
||||||
$edits.text('updated');
|
|
||||||
}).fail(function () {
|
|
||||||
console.log('Failed to update comment:' + id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
variant: 'featherlight-edit'
|
|
||||||
});
|
|
||||||
|
|
||||||
var $root = $('html, body');
|
|
||||||
$comments.find('a.comment-link').click(function () {
|
|
||||||
var href = $.attr(this, 'href');
|
|
||||||
$root.animate({
|
|
||||||
scrollTop: $(href).offset().top
|
|
||||||
}, 500, function () {
|
|
||||||
window.location.hash = href;
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('img.unveil').unveil(200);
|
|
||||||
|
|
||||||
window.comment_show_content = function (comment_id) {
|
|
||||||
var $comment = $('#comment-' + comment_id);
|
|
||||||
$comment.find('.comment-body').show();
|
|
||||||
$comment.find('.bad-comment-body').hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
$("#write-comment").click( function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
$("#new-comment").show("slow");
|
|
||||||
$("#write-comment").hide();
|
|
||||||
$(".no-comments-message").hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function () {
|
||||||
|
window.reply_comment = function (parent) {
|
||||||
|
var $comment_reply = $('#comment-' + parent + '-reply');
|
||||||
|
var reply_id = 'reply-' + parent;
|
||||||
|
var new_id = 'id' + parent + '_body';
|
||||||
|
if ($comment_reply.find('#' + reply_id).length == 0) {
|
||||||
|
var $reply_form = $('#new-comment').clone(true).prop('id', reply_id).css("display", "");
|
||||||
|
$reply_form.find('h3').html('{{ _('Replying to comment') }}');
|
||||||
|
$reply_form.prepend('<a class="close">x</a>');
|
||||||
|
$reply_form.find('form.comment-submit-form input#id_parent').val(parent);
|
||||||
|
$reply_form.find('div#wmd-button-bar-id_body').empty().prop('id','wmd-button-bar-' + new_id);
|
||||||
|
$reply_form.find('textarea.wmd-input').val('').prop('id', 'wmd-input-' + new_id);
|
||||||
|
$reply_form.find('div#id_body-preview').attr('data-textarea-id', 'wmd-input-' + new_id).prop('id', new_id + '-preview');
|
||||||
|
$reply_form.appendTo($comment_reply);
|
||||||
|
register_dmmd_preview($reply_form.find('div#' + new_id + '-preview'));
|
||||||
|
if ('DjangoPagedown' in window) {
|
||||||
|
window.DjangoPagedown.createEditor($reply_form.find('div.wmd-wrapper').get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$comment_reply.fadeIn();
|
||||||
|
|
||||||
|
$('html, body').animate({
|
||||||
|
scrollTop: $comment_reply.offset().top - $('#navigation').height() - 4
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
$(document).on('click', '.close', function() {
|
||||||
|
$(this).closest('.reply-comment').fadeOut();
|
||||||
|
});
|
||||||
|
|
||||||
|
function update_math($comment) {
|
||||||
|
if ('MathJax' in window) {
|
||||||
|
var $body = $comment.find('.comment-body');
|
||||||
|
MathJax.typeset($body[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.show_revision = function (comment_id, offset) {
|
||||||
|
var $comment = $("#comment-" + comment_id);
|
||||||
|
|
||||||
|
// If .comment-body is hidden, then this is a bad comment that the user has not clicked
|
||||||
|
// Thus the revision retrieval should do nothing
|
||||||
|
if (!$comment.find('.comment-body').is(':visible'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var cur_revision = parseInt($comment.attr("data-revision"));
|
||||||
|
var max_revision = parseInt($comment.attr("data-max-revision"));
|
||||||
|
var revision_ajax = $comment.attr("data-revision-ajax");
|
||||||
|
var show_revision = cur_revision + offset;
|
||||||
|
|
||||||
|
$comment.attr("data-revision", show_revision);
|
||||||
|
|
||||||
|
$.get(revision_ajax, {
|
||||||
|
revision: show_revision
|
||||||
|
}).done(function (body) {
|
||||||
|
$comment.find('.previous-revision').css({visibility: show_revision == 0 ? 'hidden' : ''});
|
||||||
|
$comment.find('.next-revision').css({visibility: show_revision == max_revision ? 'hidden' : ''});
|
||||||
|
$comment.find('.content').html(body);
|
||||||
|
|
||||||
|
var edit_text = '{{ _('edit {edits}') }}'.replace("{edits}", show_revision);
|
||||||
|
|
||||||
|
if (show_revision == 0) {
|
||||||
|
edit_text = '{{ _('original') }}';
|
||||||
|
} else if (show_revision == max_revision && max_revision == 1) {
|
||||||
|
edit_text = '{{ _('edited') }}';
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment.find('.comment-edit-text').text(' ' + edit_text + ' ');
|
||||||
|
update_math($comment);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function ajax_vote(url, id, delta, on_success) {
|
||||||
|
return $.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
id: id
|
||||||
|
},
|
||||||
|
success: function (data, textStatus, jqXHR) {
|
||||||
|
var score = $('#comment-' + id + ' .comment-score').first();
|
||||||
|
score.text(parseInt(score.text()) + delta);
|
||||||
|
if (typeof on_success !== 'undefined')
|
||||||
|
on_success();
|
||||||
|
},
|
||||||
|
error: function (data, textStatus, jqXHR) {
|
||||||
|
alert('Could not vote: ' + data.responseText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var get_$votes = function (id) {
|
||||||
|
var $comment = $('#comment-' + id);
|
||||||
|
return {
|
||||||
|
upvote: $comment.find('.upvote-link').first(),
|
||||||
|
downvote: $comment.find('.downvote-link').first()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
window.comment_upvote = function (id) {
|
||||||
|
ajax_vote('{{ url('comment_upvote') }}', id, 1, function () {
|
||||||
|
var $votes = get_$votes(id);
|
||||||
|
if ($votes.downvote.hasClass('voted'))
|
||||||
|
$votes.downvote.removeClass('voted');
|
||||||
|
else
|
||||||
|
$votes.upvote.addClass('voted');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.comment_downvote = function (id) {
|
||||||
|
ajax_vote('{{ url('comment_downvote') }}', id, -1, function () {
|
||||||
|
var $votes = get_$votes(id);
|
||||||
|
if ($votes.upvote.hasClass('voted'))
|
||||||
|
$votes.upvote.removeClass('voted');
|
||||||
|
else
|
||||||
|
$votes.downvote.addClass('voted');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var $comments = $('.comments');
|
||||||
|
$comments.find('a.hide-comment').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!(e.ctrlKey || e.metaKey || confirm('Are you sure you want to hide this comment?')))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var id = $(this).attr('data-id');
|
||||||
|
$.post('{{ url('comment_hide') }}', {id: id}).then(function () {
|
||||||
|
$('#comment-' + id).remove();
|
||||||
|
$('#comment-' + id + '-children').remove();
|
||||||
|
}).catch(function () {
|
||||||
|
alert('Failed.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$comments.find('a.edit-link').featherlight({
|
||||||
|
afterOpen: function () {
|
||||||
|
register_dmmd_preview($('#id-edit-comment-body-preview'));
|
||||||
|
if ('DjangoPagedown' in window) {
|
||||||
|
var $wmd = $('.featherlight .wmd-wrapper');
|
||||||
|
if ($wmd.length) {
|
||||||
|
window.DjangoPagedown.createEditor($wmd.get(0));
|
||||||
|
if ('MathJax' in window) {
|
||||||
|
var preview = $('.featherlight div.wmd-preview')[0];
|
||||||
|
MathJax.typeset(preview);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('#comment-edit').submit(function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
var id = $('#comment-edit').find('.comment-id').text();
|
||||||
|
var readback = $('#comment-edit').find('.read-back').text();
|
||||||
|
$.post($(this).attr('action'), $(this).serialize()).done(function (data) {
|
||||||
|
$.featherlight.current().close();
|
||||||
|
$.ajax({
|
||||||
|
url: readback
|
||||||
|
}).done(function (data) {
|
||||||
|
var $comment = $('#comment-' + id);
|
||||||
|
var $area = $comment.find('.comment-body').first();
|
||||||
|
$area.html(data);
|
||||||
|
update_math($comment);
|
||||||
|
var $edits = $comment.find('.comment-edits').first();
|
||||||
|
$edits.text('updated');
|
||||||
|
}).fail(function () {
|
||||||
|
console.log('Failed to update comment:' + id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
variant: 'featherlight-edit'
|
||||||
|
});
|
||||||
|
|
||||||
|
var $root = $('html, body');
|
||||||
|
$comments.find('a.comment-link').click(function () {
|
||||||
|
var href = $.attr(this, 'href');
|
||||||
|
$root.animate({
|
||||||
|
scrollTop: $(href).offset().top
|
||||||
|
}, 500, function () {
|
||||||
|
window.location.hash = href;
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('img.unveil').unveil(200);
|
||||||
|
|
||||||
|
window.comment_show_content = function (comment_id) {
|
||||||
|
var $comment = $('#comment-' + comment_id);
|
||||||
|
$comment.find('.comment-body').show();
|
||||||
|
$comment.find('.bad-comment-body').hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
$("#write-comment").click( function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
$("#new-comment").show("slow");
|
||||||
|
$("#write-comment").hide();
|
||||||
|
$(".no-comments-message").hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{ preview_data|markdown|reference|str|safe }}
|
{{ preview_data|markdown|reference|str|safe }}
|
||||||
{% if REQUIRE_JAX %}
|
{% if REQUIRE_JAX %}
|
||||||
<div data-config="{{ static('mathjax3_config.js') }}" class="require-mathjax-support"></div>
|
<div data-config="{{ static('mathjax3_config.js') }}" class="require-mathjax-support"></div>
|
||||||
{% endif %}
|
{% endif %}
|
|
@ -1,3 +1,3 @@
|
||||||
{% with node=revision.field_dict %}
|
{% with node=revision.object %}
|
||||||
<div class="comment-body">{{ node.body|markdown|reference|str|safe }}</div>
|
<div class="comment-body">{{ node.body|markdown|reference|str|safe }}</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue