diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..fc3bd4b --- /dev/null +++ b/.pre-commit-config.yaml @@ -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 diff --git a/README.md b/README.md index 97c9512..7087452 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ celery -A dmoj_celery worker 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 Most of the steps are similar to Django tutorials. Here are two usual steps: diff --git a/chat_box/migrations/0011_alter_message_hidden.py b/chat_box/migrations/0011_alter_message_hidden.py new file mode 100644 index 0000000..1393e82 --- /dev/null +++ b/chat_box/migrations/0011_alter_message_hidden.py @@ -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" + ), + ), + ] diff --git a/chat_box/models.py b/chat_box/models.py index 328e4c6..2dc2cb1 100644 --- a/chat_box/models.py +++ b/chat_box/models.py @@ -31,7 +31,7 @@ class Message(models.Model): author = models.ForeignKey(Profile, verbose_name=_("user"), on_delete=CASCADE) time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True) 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, verbose_name="room id", on_delete=CASCADE, default=None, null=True ) diff --git a/chat_box/views.py b/chat_box/views.py index 34b82bb..e9f406f 100644 --- a/chat_box/views.py +++ b/chat_box/views.py @@ -48,16 +48,26 @@ class ChatView(ListView): super().__init__() self.room_id = None self.room = None - self.paginate_by = 50 self.messages = None - self.paginator = None + self.page_size = 20 def get_queryset(self): 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): 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: try: @@ -69,23 +79,20 @@ class ChatView(ListView): else: request_room = None - if request_room != self.room_id or not self.messages: - self.room_id = request_room - self.messages = Message.objects.filter(hidden=False, room=self.room_id) - self.paginator = Paginator(self.messages, self.paginate_by) - - if page == None: + self.room_id = request_room + self.messages = Message.objects.filter( + hidden=False, room=self.room_id, id__lt=last_id + )[: self.page_size] + if not only_messages: update_last_seen(request, **kwargs) return super().get(request, *args, **kwargs) - cur_page = self.paginator.get_page(page) - return render( request, "chat/message_list.html", { - "object_list": cur_page.object_list, - "num_pages": self.paginator.num_pages, + "object_list": self.messages, + "has_next": self.has_next(), }, ) @@ -96,6 +103,7 @@ class ChatView(ListView): context["last_msg"] = event.last() context["status_sections"] = get_status_context(self.request) context["room"] = self.room_id + context["has_next"] = self.has_next() context["unread_count_lobby"] = get_unread_count(None, self.request.profile) if self.room: users_room = [self.room.user_one, self.room.user_two] diff --git a/dmoj/settings.py b/dmoj/settings.py index 076ab39..fbf8cbd 100644 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -247,6 +247,7 @@ INSTALLED_APPS += ( ) MIDDLEWARE = ( + "judge.middleware.SlowRequestMiddleware", "judge.middleware.ShortCircuitMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.locale.LocaleMiddleware", @@ -262,6 +263,7 @@ MIDDLEWARE = ( "judge.middleware.DMOJImpersonationMiddleware", "judge.middleware.ContestMiddleware", "judge.middleware.DarkModeMiddleware", + "judge.middleware.SubdomainMiddleware", "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware", "judge.social_auth.SocialAuthExceptionMiddleware", "django.contrib.redirects.middleware.RedirectFallbackMiddleware", @@ -468,6 +470,9 @@ MESSAGES_TO_LOAD = 15 ML_OUTPUT_PATH = None +# Use subdomain for organizations +USE_SUBDOMAIN = False + try: with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f: exec(f.read(), globals()) diff --git a/dmoj/urls.py b/dmoj/urls.py index 07f0f5c..f367522 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -64,6 +64,7 @@ from judge.views import ( widgets, internal, resolver, + course, ) from judge.views.problem_data import ( ProblemDataView, @@ -230,18 +231,19 @@ urlpatterns = [ url(r"^problems/", paged_list_view(problem.ProblemList, "problem_list")), url(r"^problems/random/$", problem.RandomProblem.as_view(), name="problem_random"), url( - r"^problems/feed/", - paged_list_view(problem.ProblemFeed, "problem_feed", feed_type="for_you"), + r"^problems/feed/$", + problem.ProblemFeed.as_view(feed_type="for_you"), + name="problem_feed", ), url( - r"^problems/feed/new/", - paged_list_view(problem.ProblemFeed, "problem_feed_new", feed_type="new"), + r"^problems/feed/new/$", + problem.ProblemFeed.as_view(feed_type="new"), + name="problem_feed_new", ), url( - r"^problems/feed/volunteer/", - paged_list_view( - problem.ProblemFeed, "problem_feed_volunteer", feed_type="volunteer" - ), + r"^problems/feed/volunteer/$", + problem.ProblemFeed.as_view(feed_type="volunteer"), + name="problem_feed_volunteer", ), url( r"^problem/(?P[^/]+)", @@ -368,6 +370,10 @@ urlpatterns = [ r"^submissions/user/(?P\w+)/", paged_list_view(submission.AllUserSubmissions, "all_user_submissions"), ), + url( + r"^submissions/friends/", + paged_list_view(submission.AllFriendSubmissions, "all_friend_submissions"), + ), url( r"^src/(?P\d+)/raw$", submission.SubmissionSourceRaw.as_view(), @@ -486,6 +492,39 @@ urlpatterns = [ ), ), url(r"^contests/", paged_list_view(contests.ContestList, "contest_list")), + url(r"^courses/", paged_list_view(course.CourseList, "course_list")), + url( + r"^courses/(?P\d+)-(?P[\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\d+)/$", + course.CourseResouceDetail.as_view(), + name="course_resource_detail", + ), + url( + r"^/resource/(?P\d+)/edit", + course.CourseResourceDetailEdit.as_view(), + name="course_resource_detail_edit", + ), + ] + ), + ), url( r"^contests/(?P\d+)/(?P\d+)/$", contests.ContestCalendar.as_view(), @@ -536,13 +575,6 @@ urlpatterns = [ url(r"^/join$", contests.ContestJoin.as_view(), name="contest_join"), url(r"^/leave$", contests.ContestLeave.as_view(), name="contest_leave"), url(r"^/stats$", contests.ContestStats.as_view(), name="contest_stats"), - url( - r"^/rank/(?P\w+)/", - paged_list_view( - ranked_submission.ContestRankedSubmission, - "contest_ranked_submissions", - ), - ), url( r"^/submissions/(?P\w+)/(?P\w+)", 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\d+)-(?P.*)$", blog.PostView.as_view(), name="blog_post"), url(r"^license/(?P[-\w.]+)$", license.LicenseDetail.as_view(), name="license"), url( diff --git a/judge/admin/__init__.py b/judge/admin/__init__.py index c9ad2f3..c386033 100644 --- a/judge/admin/__init__.py +++ b/judge/admin/__init__.py @@ -39,8 +39,11 @@ from judge.models import ( Submission, Ticket, VolunteerProblemVote, + Course, ) +from judge.models.course import CourseResource + admin.site.register(BlogPost, BlogPostAdmin) admin.site.register(Comment, CommentAdmin) @@ -64,3 +67,5 @@ admin.site.register(Profile, ProfileAdmin) admin.site.register(Submission, SubmissionAdmin) admin.site.register(Ticket, TicketAdmin) admin.site.register(VolunteerProblemVote, VolunteerProblemVoteAdmin) +admin.site.register(Course) +admin.site.register(CourseResource) diff --git a/judge/admin/comments.py b/judge/admin/comments.py index b9f63d6..0b2a8e1 100644 --- a/judge/admin/comments.py +++ b/judge/admin/comments.py @@ -22,11 +22,23 @@ class CommentForm(ModelForm): class CommentAdmin(VersionAdmin): fieldsets = ( - (None, {"fields": ("author", "page", "parent", "score", "hidden")}), + ( + None, + { + "fields": ( + "author", + "parent", + "score", + "hidden", + "content_type", + "object_id", + ) + }, + ), ("Content", {"fields": ("body",)}), ) - list_display = ["author", "linked_page", "time"] - search_fields = ["author__user__username", "page", "body"] + list_display = ["author", "linked_object", "time"] + search_fields = ["author__user__username", "body"] readonly_fields = ["score"] actions = ["hide_comment", "unhide_comment"] list_filter = ["hidden"] @@ -66,16 +78,6 @@ class CommentAdmin(VersionAdmin): unhide_comment.short_description = _("Unhide comments") - def linked_page(self, obj): - link = obj.link - if link is not None: - return format_html('{1}', 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): super(CommentAdmin, self).save_model(request, obj, form, change) if obj.hidden: diff --git a/judge/admin/problem.py b/judge/admin/problem.py index 50fdbb8..49a145d 100644 --- a/judge/admin/problem.py +++ b/judge/admin/problem.py @@ -365,22 +365,27 @@ class ProblemAdmin(CompareVersionAdmin): obj.is_organization_private = obj.organizations.count() > 0 obj.save() # 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 = users.union(users, set(obj.curators.all())) + orgs = [] if obj.organizations.count() > 0: for org in obj.organizations.all(): users = users.union(users, set(org.admins.all())) + orgs.append(org.name) else: admins = Profile.objects.filter(user__is_superuser=True).all() users = users.union(users, admins) link = reverse_lazy("admin:judge_problem_change", args=(obj.id,)) html = f'{obj.name}' + category = "Problem public: " + str(obj.is_public) + if orgs: + category += " (" + ", ".join(orgs) + ")" for user in users: notification = Notification( owner=user, html_link=html, - category="Problem public: " + str(obj.is_public), + category=category, author=request.profile, ) notification.save() diff --git a/judge/comments.py b/judge/comments.py index 2509baa..5ce4fa7 100644 --- a/judge/comments.py +++ b/judge/comments.py @@ -22,7 +22,7 @@ from reversion import revisions from reversion.models import Revision, Version 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.jinja2.reference import get_user_from_text @@ -90,7 +90,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): def is_comment_locked(self): if self.request.user.has_perm("judge.override_comment_lock"): return False - return CommentLock.objects.filter(page=self.get_comment_page()).exists() or ( + return ( self.request.in_contest and self.request.participation.contest.use_clarifications ) @@ -98,8 +98,6 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): @method_decorator(login_required) def post(self, request, *args, **kwargs): self.object = self.get_object() - page = self.get_comment_page() - if self.is_comment_locked(): return HttpResponseForbidden() @@ -110,16 +108,14 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): except ValueError: return HttpResponseNotFound() else: - if not Comment.objects.filter( - hidden=False, id=parent, page=page - ).exists(): + if not self.object.comments.filter(hidden=False, id=parent).exists(): return HttpResponseNotFound() form = CommentForm(request, request.POST) if form.is_valid(): comment = form.save(commit=False) comment.author = request.profile - comment.page = page + comment.linked_object = self.object with LockModel( write=(Comment, Revision, Version), read=(ContentType,) @@ -136,7 +132,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): notification_reply.save() # add notification for page authors - page_authors = comment.page_object.authors.all() + page_authors = comment.linked_object.authors.all() for user in page_authors: if user == comment.author: continue @@ -149,7 +145,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): 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) return self.render_to_response(context) @@ -159,15 +155,13 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): return self.render_to_response( self.get_context_data( object=self.object, - comment_form=CommentForm( - request, initial={"page": self.get_comment_page(), "parent": None} - ), + comment_form=CommentForm(request, initial={"parent": None}), ) ) def get_context_data(self, **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["comment_lock"] = self.is_comment_locked() queryset = ( diff --git a/judge/forms.py b/judge/forms.py index f90664f..83b45e5 100644 --- a/judge/forms.py +++ b/judge/forms.py @@ -263,6 +263,7 @@ class EditOrganizationContestForm(ModelForm): "curators", "testers", "time_limit", + "freeze_after", "use_clarifications", "hide_problem_tags", "scoreboard_visibility", diff --git a/judge/jinja2/markdown/__init__.py b/judge/jinja2/markdown/__init__.py index d54f103..b0bc6f6 100644 --- a/judge/jinja2/markdown/__init__.py +++ b/judge/jinja2/markdown/__init__.py @@ -98,7 +98,7 @@ def markdown(value, lazy_load=False): if not html: html = escape(value) - if lazy_load or True: + if lazy_load: soup = BeautifulSoup(html, features="html.parser") for img in soup.findAll("img"): if img.get("src"): diff --git a/judge/jinja2/submission.py b/judge/jinja2/submission.py index 7c39569..cdb3634 100644 --- a/judge/jinja2/submission.py +++ b/judge/jinja2/submission.py @@ -3,34 +3,39 @@ from . import registry @registry.function 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 - can_view = False 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: - can_view = True + return True if user.has_perm("judge.change_submission"): - can_view = True + return True 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"): - can_view = True + return True - if submission.problem_id in completed_problem_ids: - can_view |= ( - submission.problem.is_public or profile_id in submission.problem.tester_ids - ) - - if not can_view and hasattr(submission, "contest"): + if hasattr(submission, "contest"): contest = submission.contest.participation.contest 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 diff --git a/judge/middleware.py b/judge/middleware.py index 940b486..b86731d 100644 --- a/judge/middleware.py +++ b/judge/middleware.py @@ -1,7 +1,19 @@ +import time +import logging + 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.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: @@ -82,3 +94,71 @@ class DarkModeMiddleware(object): reverse("toggle_darkmode") + "?next=" + urlquote(request.path) ) 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 diff --git a/judge/migrations/0145_alter_organization_slug.py b/judge/migrations/0145_alter_organization_slug.py new file mode 100644 index 0000000..1cadbe2 --- /dev/null +++ b/judge/migrations/0145_alter_organization_slug.py @@ -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", + ), + ), + ] diff --git a/judge/migrations/0146_alter_organization_slug.py b/judge/migrations/0146_alter_organization_slug.py new file mode 100644 index 0000000..dbd475d --- /dev/null +++ b/judge/migrations/0146_alter_organization_slug.py @@ -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", + ), + ), + ] diff --git a/judge/migrations/0147_alter_profile_timezone.py b/judge/migrations/0147_alter_profile_timezone.py new file mode 100644 index 0000000..5d6331e --- /dev/null +++ b/judge/migrations/0147_alter_profile_timezone.py @@ -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", + ), + ), + ] diff --git a/judge/migrations/0148_course_courseassignment_courseresource_courserole.py b/judge/migrations/0148_course_courseassignment_courseresource_courserole.py new file mode 100644 index 0000000..64821b7 --- /dev/null +++ b/judge/migrations/0148_course_courseassignment_courseresource_courserole.py @@ -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", + ), + ), + ], + ), + ] diff --git a/judge/migrations/0149_auto_20230202_0902.py b/judge/migrations/0149_auto_20230202_0902.py new file mode 100644 index 0000000..8b0d885 --- /dev/null +++ b/judge/migrations/0149_auto_20230202_0902.py @@ -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", + ), + ), + ] diff --git a/judge/migrations/0150_alter_profile_timezone.py b/judge/migrations/0150_alter_profile_timezone.py new file mode 100644 index 0000000..94287b7 --- /dev/null +++ b/judge/migrations/0150_alter_profile_timezone.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.17 on 2023-02-08 01:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0149_auto_20230202_0902'), + ] + + 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/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/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'), + ), + ] diff --git a/judge/migrations/0151_comment_content_type.py b/judge/migrations/0151_comment_content_type.py new file mode 100644 index 0000000..d1897d9 --- /dev/null +++ b/judge/migrations/0151_comment_content_type.py @@ -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", + ), + ), + ] diff --git a/judge/migrations/0152_migrate_comments.py b/judge/migrations/0152_migrate_comments.py new file mode 100644 index 0000000..bcf6531 --- /dev/null +++ b/judge/migrations/0152_migrate_comments.py @@ -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(), + ), + ] diff --git a/judge/migrations/0153_drop_comment_page.py b/judge/migrations/0153_drop_comment_page.py new file mode 100644 index 0000000..a03ac09 --- /dev/null +++ b/judge/migrations/0153_drop_comment_page.py @@ -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" + ), + ), + ] diff --git a/judge/migrations/0154_auto_20230301_1659.py b/judge/migrations/0154_auto_20230301_1659.py new file mode 100644 index 0000000..38bbf53 --- /dev/null +++ b/judge/migrations/0154_auto_20230301_1659.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.18 on 2023-03-01 09:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0153_drop_comment_page'), + ] + + operations = [ + migrations.AlterField( + model_name='courseassignment', + name='contest', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.contest', verbose_name='contest'), + ), + migrations.AlterField( + model_name='courseassignment', + name='course', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.course', verbose_name='course'), + ), + migrations.AlterField( + model_name='courseresource', + name='course', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.course', verbose_name='course'), + ), + migrations.AlterField( + model_name='courserole', + name='course', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.course', verbose_name='course'), + ), + 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'), + ), + ] diff --git a/judge/ml/collab_filter.py b/judge/ml/collab_filter.py index 7b3e040..f06637f 100644 --- a/judge/ml/collab_filter.py +++ b/judge/ml/collab_filter.py @@ -41,7 +41,7 @@ class CollabFilter: def user_recommendations(self, user, problems, measure=DOT, limit=None, **kwargs): 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])) value = cache.get(cache_key) if value: @@ -65,16 +65,16 @@ class CollabFilter: return res # 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 if pid >= len(self.problem_embeddings): - return None + return [] scores = self.compute_scores( self.problem_embeddings[pid], self.problem_embeddings, measure ) res = [] for p in problemset: - if p.id < len(scores): - res.append((scores[p.id], p)) + if p < len(scores): + res.append((scores[p], p)) res.sort(reverse=True, key=lambda x: x[0]) return res[:limit] diff --git a/judge/models/__init__.py b/judge/models/__init__.py index 39ce95e..ee9d364 100644 --- a/judge/models/__init__.py +++ b/judge/models/__init__.py @@ -56,6 +56,7 @@ from judge.models.ticket import Ticket, TicketMessage from judge.models.volunteer import VolunteerProblemVote from judge.models.pagevote import PageVote, PageVoteVoter from judge.models.bookmark import BookMark, MakeBookMark +from judge.models.course import Course revisions.register(Profile, exclude=["points", "last_access", "ip", "rating"]) revisions.register(Problem, follow=["language_limits"]) @@ -81,4 +82,5 @@ revisions.register(Rating) revisions.register(PageVoteVoter) revisions.register(VolunteerProblemVote) revisions.register(MakeBookMark) +revisions.register(Course) del revisions diff --git a/judge/models/bookmark.py b/judge/models/bookmark.py index 0fb353e..61afd0c 100644 --- a/judge/models/bookmark.py +++ b/judge/models/bookmark.py @@ -3,10 +3,7 @@ from django.db.models import CASCADE from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ObjectDoesNotExist -from judge.models import Profile -from judge.models.contest import Contest -from judge.models.interface import BlogPost -from judge.models.problem import Problem, Solution +from judge.models.profile import Profile __all__ = ["BookMark"] @@ -26,6 +23,10 @@ class BookMark(models.Model): return False def page_object(self): + from judge.models.contest import Contest + from judge.models.interface import BlogPost + from judge.models.problem import Problem, Solution + try: page = self.page if page.startswith("p:"): diff --git a/judge/models/comment.py b/judge/models/comment.py index 9361434..fc9af4c 100644 --- a/judge/models/comment.py +++ b/judge/models/comment.py @@ -9,6 +9,8 @@ from django.db.models import CASCADE from django.urls import reverse from django.utils.functional import cached_property 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.models import MPTTModel from reversion.models import Version @@ -22,10 +24,6 @@ from judge.utils.cachedict import CacheDict __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): def __init__(self): @@ -44,12 +42,9 @@ class VersionRelation(GenericRelation): class Comment(MPTTModel): author = models.ForeignKey(Profile, verbose_name=_("commenter"), on_delete=CASCADE) time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True) - page = models.CharField( - max_length=30, - verbose_name=_("associated page"), - db_index=True, - validators=[comment_validator], - ) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + linked_object = GenericForeignKey("content_type", "object_id") score = models.IntegerField(verbose_name=_("votes"), default=0) body = models.TextField(verbose_name=_("body of comment"), max_length=8192) hidden = models.BooleanField(verbose_name=_("hide the comment"), default=0) @@ -66,12 +61,15 @@ class Comment(MPTTModel): class Meta: verbose_name = _("comment") verbose_name_plural = _("comments") + indexes = [ + models.Index(fields=["content_type", "object_id"]), + ] class MPTTMeta: order_insertion_by = ["-time"] @classmethod - def most_recent(cls, user, n, batch=None): + def most_recent(cls, user, n, batch=None, organization=None): queryset = ( cls.objects.filter(hidden=False) .select_related("author__user") @@ -79,13 +77,12 @@ class Comment(MPTTModel): .order_by("-id") ) - problem_access = CacheDict( - lambda code: Problem.objects.get(code=code).is_accessible_by(user) - ) - contest_access = CacheDict( - lambda key: Contest.objects.get(key=key).is_accessible_by(user) - ) - blog_access = CacheDict(lambda id: BlogPost.objects.get(id=id).can_see(user)) + if organization: + queryset = queryset.filter(author__in=organization.members.all()) + + problem_access = CacheDict(lambda p: p.is_accessible_by(user)) + contest_access = CacheDict(lambda c: c.is_accessible_by(user)) + blog_access = CacheDict(lambda b: b.can_see(user)) if n == -1: n = len(queryset) @@ -99,112 +96,53 @@ class Comment(MPTTModel): if not slice: break for comment in slice: - if comment.page.startswith("p:") or comment.page.startswith("s:"): - try: - if problem_access[comment.page[2:]]: - output.append(comment) - except Problem.DoesNotExist: - pass - elif comment.page.startswith("c:"): - try: - if contest_access[comment.page[2:]]: - output.append(comment) - except Contest.DoesNotExist: - pass - elif comment.page.startswith("b:"): - try: - if blog_access[comment.page[2:]]: - output.append(comment) - except BlogPost.DoesNotExist: - pass - else: - output.append(comment) + if isinstance(comment.linked_object, Problem): + if problem_access[comment.linked_object]: + output.append(comment) + elif isinstance(comment.linked_object, Contest): + if contest_access[comment.linked_object]: + output.append(comment) + elif isinstance(comment.linked_object, BlogPost): + if blog_access[comment.linked_object]: + output.append(comment) + elif isinstance(comment.linked_object, Solution): + if problem_access[comment.linked_object.problem]: + output.append(comment) if len(output) >= n: return output return output @cached_property - def link(self): - try: - link = None - if self.page.startswith("p:"): - link = reverse("problem_detail", args=(self.page[2:],)) - elif self.page.startswith("c:"): - link = reverse("contest_view", args=(self.page[2:],)) - elif self.page.startswith("b:"): - key = "blog_slug:%s" % self.page[2:] - 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 "" - except ObjectDoesNotExist: - return "" + def page_title(self): + if isinstance(self.linked_object, Problem): + return self.linked_object.name + elif isinstance(self.linked_object, Contest): + return self.linked_object.name + elif isinstance(self.linked_object, Solution): + return _("Editorial for ") + self.linked_object.problem.name + elif isinstance(self.linked_object, BlogPost): + return self.linked_object.title @cached_property - def page_title(self): - return self.get_page_title(self.page) + def link(self): + 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): 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): voter = models.ForeignKey(Profile, related_name="voted_comments", on_delete=CASCADE) @@ -222,7 +160,6 @@ class CommentLock(models.Model): max_length=30, verbose_name=_("associated page"), db_index=True, - validators=[comment_validator], ) class Meta: @@ -244,7 +181,7 @@ class Notification(models.Model): Comment, null=True, verbose_name=_("comment"), on_delete=CASCADE ) 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( default="", verbose_name=_("html link to comments, used for non-comments"), diff --git a/judge/models/contest.py b/judge/models/contest.py index 994e3ce..678d7c0 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -6,6 +6,7 @@ from django.urls import reverse from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import gettext, gettext_lazy as _ +from django.contrib.contenttypes.fields import GenericRelation from jsonfield import JSONField from lupa import LuaRuntime from moss import ( @@ -297,6 +298,7 @@ class Contest(models.Model): validators=[MinValueValidator(0), MaxValueValidator(10)], help_text=_("Number of digits to round points to."), ) + comments = GenericRelation("Comment") @cached_property def format_class(self): diff --git a/judge/models/course.py b/judge/models/course.py new file mode 100644 index 0000000..d47da42 --- /dev/null +++ b/judge/models/course.py @@ -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"), + ) diff --git a/judge/models/interface.py b/judge/models/interface.py index 24b05de..880b76b 100644 --- a/judge/models/interface.py +++ b/judge/models/interface.py @@ -5,10 +5,14 @@ from django.db import models from django.urls import reverse from django.utils import timezone 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.models import MPTTModel 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"] @@ -91,6 +95,7 @@ class BlogPost(models.Model): is_organization_private = models.BooleanField( verbose_name=_("private to organizations"), default=False ) + comments = GenericRelation("Comment") def __str__(self): return self.title @@ -125,6 +130,18 @@ class BlogPost(models.Model): 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: permissions = (("edit_all_post", _("Edit all posts")),) verbose_name = _("blog post") diff --git a/judge/models/pagevote.py b/judge/models/pagevote.py index 4923420..16f1c6c 100644 --- a/judge/models/pagevote.py +++ b/judge/models/pagevote.py @@ -2,7 +2,7 @@ from django.db import models from django.db.models import CASCADE from django.utils.translation import gettext_lazy as _ -from judge.models import Profile +from judge.models.profile import Profile __all__ = ["PageVote", "PageVoteVoter"] diff --git a/judge/models/problem.py b/judge/models/problem.py index 1c32738..59017f9 100644 --- a/judge/models/problem.py +++ b/judge/models/problem.py @@ -6,13 +6,15 @@ from django.contrib.contenttypes.fields import GenericRelation from django.core.cache import cache from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator 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.urls import reverse from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ 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.runtime import Language from judge.user_translations import gettext as user_gettext @@ -268,6 +270,7 @@ class Problem(models.Model): objects = TranslatedProblemQuerySet.as_manager() tickets = GenericRelation("Ticket") + comments = GenericRelation("Comment") organizations = models.ManyToManyField( Organization, @@ -369,9 +372,9 @@ class Problem(models.Model): ) @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. - if not user.is_authenticated: + if not user.is_authenticated or not user: return cls.get_public_problems() # Conditions for visible problem: @@ -383,7 +386,7 @@ class Problem(models.Model): # - not is_organization_private or in organization or `judge.see_organization_problem` # - author or curator or tester queryset = cls.objects.defer("description") - + profile = profile or user.profile if not ( user.has_perm("judge.see_private_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. q &= Q(is_organization_private=False) | Q( 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. - q |= Q(authors=user.profile) - q |= Q(curators=user.profile) - q |= Q(testers=user.profile) + filter = Exists( + Problem.authors.through.objects.filter( + 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) return queryset @@ -432,6 +447,18 @@ class Problem(models.Model): def usable_common_names(self): 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 def usable_languages(self): return self.allowed_languages.filter( @@ -532,6 +559,10 @@ class Problem(models.Model): return result 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) if self.code != self.__original_code: try: @@ -632,7 +663,7 @@ class LanguageTemplate(models.Model): class Solution(models.Model): problem = models.OneToOneField( Problem, - on_delete=SET_NULL, + on_delete=CASCADE, verbose_name=_("associated problem"), null=True, blank=True, @@ -642,6 +673,7 @@ class Solution(models.Model): publish_on = models.DateTimeField(verbose_name=_("publish date")) authors = models.ManyToManyField(Profile, verbose_name=_("authors"), blank=True) content = models.TextField(verbose_name=_("editorial content")) + comments = GenericRelation("Comment") def get_absolute_url(self): problem = self.problem diff --git a/judge/models/profile.py b/judge/models/profile.py index c8704cd..63453e4 100644 --- a/judge/models/profile.py +++ b/judge/models/profile.py @@ -33,6 +33,10 @@ class Organization(models.Model): max_length=128, verbose_name=_("organization slug"), help_text=_("Organization name shown in URL"), + unique=True, + validators=[ + RegexValidator("^[-a-zA-Z0-9]+$", _("Only alphanumeric and hyphens")) + ], ) short_name = models.CharField( max_length=20, @@ -329,16 +333,24 @@ class Profile(models.Model): def css_class(self): return self.get_user_css_class(self.display_rank, self.rating) - def get_friends(self): # list of usernames, including you - friend_obj = self.following_users.all() - ret = set() - + def get_friends(self): # list of ids, including you + friend_obj = self.following_users.prefetch_related("users") + ret = [] if friend_obj: - ret = set(friend.username for friend in friend_obj[0].users.all()) - - ret.add(self.username) + ret = [friend.id for friend in friend_obj[0].users.all()] + ret.append(self.id) 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: permissions = ( ("test_site", "Shows in-progress development stuff"), @@ -381,7 +393,9 @@ class OrganizationRequest(models.Model): class Friend(models.Model): users = models.ManyToManyField(Profile) current_user = models.ForeignKey( - Profile, related_name="following_users", on_delete=CASCADE + Profile, + related_name="following_users", + on_delete=CASCADE, ) @classmethod diff --git a/judge/performance_points.py b/judge/performance_points.py index 63a7636..bf2a320 100644 --- a/judge/performance_points.py +++ b/judge/performance_points.py @@ -22,37 +22,48 @@ def get_pp_breakdown(user, start=0, end=settings.DMOJ_PP_ENTRIES): with connection.cursor() as cursor: cursor.execute( """ - SELECT max_points_table.problem_code, - max_points_table.problem_name, - max_points_table.max_points, - judge_submission.id, - judge_submission.date, - judge_submission.case_points, - judge_submission.case_total, - judge_submission.result, + SELECT submission.problem_code, + submission.problem_name, + submission.max_points, + submission.sub_id, + submission.sub_date, + submission.case_points, + submission.case_total, + submission.result, judge_language.short_name, judge_language.key - FROM judge_submission - JOIN (SELECT judge_problem.id problem_id, - judge_problem.name problem_name, - judge_problem.code problem_code, - 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) + FROM + (SELECT max_points_table.problem_code problem_code, + max_points_table.problem_name problem_name, + max_points_table.max_points max_points, + judge_submission.id sub_id, + judge_submission.date sub_date, + judge_submission.case_points case_points, + judge_submission.case_total case_total, + judge_submission.result result, + judge_submission.language_id language_id + FROM judge_submission + JOIN ( + SELECT judge_problem.id problem_id, + judge_problem.name problem_name, + judge_problem.code problem_code, + 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 - ON judge_submission.language_id = judge_language.id - GROUP BY max_points_table.problem_id - ORDER BY max_points DESC, judge_submission.date DESC - LIMIT %s OFFSET %s + ON submission.language_id = judge_language.id + ORDER BY submission.max_points DESC, submission.sub_date DESC; """, (user.id, user.id, end - start + 1, start), ) diff --git a/judge/tasks/contest.py b/judge/tasks/contest.py index ca896a9..beaf2d5 100644 --- a/judge/tasks/contest.py +++ b/judge/tasks/contest.py @@ -20,6 +20,23 @@ def rescore_contest(self, contest_key): self, participations.count(), stage=_("Recalculating contest scores") ) as p: 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() rescored += 1 if rescored % 10 == 0: diff --git a/judge/utils/diggpaginator.py b/judge/utils/diggpaginator.py index de9ee7d..2ba8581 100644 --- a/judge/utils/diggpaginator.py +++ b/judge/utils/diggpaginator.py @@ -191,6 +191,9 @@ class DiggPaginator(ExPaginator): # validate padding value max_padding = int(math.ceil(self.body / 2.0) - 1) 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: raise ValueError("padding too large for body (max %d)" % max_padding) super(DiggPaginator, self).__init__(*args, **kwargs) diff --git a/judge/utils/infinite_paginator.py b/judge/utils/infinite_paginator.py new file mode 100644 index 0000000..5693481 --- /dev/null +++ b/judge/utils/infinite_paginator.py @@ -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 "" % 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), + } + ) diff --git a/judge/utils/problems.py b/judge/utils/problems.py index d20e542..057edf0 100644 --- a/judge/utils/problems.py +++ b/judge/utils/problems.py @@ -1,6 +1,8 @@ from collections import defaultdict from math import e import os, zipfile +from datetime import datetime +import random from django.conf import settings 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 judge.models import Problem, Submission +from judge.ml.collab_filter import CollabFilter + __all__ = [ "contest_completed_ids", "get_result_data", "user_completed_ids", - "user_authored_ids", "user_editable_ids", + "user_tester_ids", ] -def user_authored_ids(profile): - result = set(Problem.objects.filter(authors=profile).values_list("id", flat=True)) - return result +def user_tester_ids(profile): + return set( + Problem.testers.through.objects.filter(profile=profile).values_list( + "problem_id", flat=True + ) + ) def user_editable_ids(profile): @@ -229,3 +236,26 @@ def hot_problems(duration, limit): cache.set(cache_key, qs, 900) 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 diff --git a/judge/views/blog.py b/judge/views/blog.py index 2bcc0a6..3985348 100644 --- a/judge/views/blog.py +++ b/judge/views/blog.py @@ -7,8 +7,8 @@ from django.utils.translation import ugettext as _ from django.views.generic import ListView from judge.comments import CommentedDetailView -from judge.views.pagevote import PageVoteDetailView, PageVoteListView -from judge.views.bookmark import BookMarkDetailView, BookMarkListView +from judge.views.pagevote import PageVoteDetailView +from judge.views.bookmark import BookMarkDetailView from judge.models import ( BlogPost, Comment, @@ -26,28 +26,16 @@ from judge.utils.diggpaginator import DiggPaginator from judge.utils.problems import user_completed_ids from judge.utils.tickets import filter_visible_tickets from judge.utils.views import TitleMixin +from judge.views.feed import FeedView # General view for all content list on home feed -class FeedView(ListView): +class HomeFeedView(FeedView): template_name = "blog/list.html" 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): - context = super(FeedView, self).get_context_data(**kwargs) + context = super(HomeFeedView, self).get_context_data(**kwargs) context["has_clarifications"] = False if self.request.user.is_authenticated: participation = self.request.profile.current_contest @@ -60,22 +48,16 @@ class FeedView(ListView): if participation.contest.is_editable_by(self.request.user): 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() - visible_contests = ( Contest.get_visible_contests(self.request.user, show_own_contests_only=True) .filter(is_visible=True) .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( start_time__lte=now, end_time__gt=now @@ -84,20 +66,26 @@ class FeedView(ListView): context[ "recent_organizations" ] = 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" )[: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" )[:10] return context -class PostList(FeedView, PageVoteListView, BookMarkListView): +class PostList(HomeFeedView): model = BlogPost - paginate_by = 10 + paginate_by = 4 context_object_name = "posts" + feed_content_template_name = "blog/content.html" + url_name = "blog_post_list" def get_queryset(self): queryset = ( @@ -108,6 +96,8 @@ class PostList(FeedView, PageVoteListView, BookMarkListView): filter = Q(is_organization_private=False) if self.request.user.is_authenticated: filter |= Q(organizations__in=self.request.profile.organizations.all()) + if self.request.organization: + filter &= Q(organizations=self.request.organization) queryset = queryset.filter(filter) return queryset @@ -116,30 +106,18 @@ class PostList(FeedView, PageVoteListView, BookMarkListView): context["title"] = ( 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["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 def get_comment_page(self, post): return "b:%s" % post.id -class TicketFeed(FeedView): +class TicketFeed(HomeFeedView): model = Ticket context_object_name = "tickets" - paginate_by = 30 + paginate_by = 8 + feed_content_template_name = "ticket/feed.html" def get_queryset(self, is_own=True): profile = self.request.profile @@ -171,28 +149,25 @@ class TicketFeed(FeedView): def get_context_data(self, **kwargs): context = super(TicketFeed, self).get_context_data(**kwargs) context["page_type"] = "ticket" - context["first_page_href"] = self.request.path - context["page_prefix"] = "?page=" context["title"] = _("Ticket feed") - return context -class CommentFeed(FeedView): +class CommentFeed(HomeFeedView): model = Comment context_object_name = "comments" - paginate_by = 50 + paginate_by = 15 + feed_content_template_name = "comments/feed.html" 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): 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["page_type"] = "comment" return context diff --git a/judge/views/bookmark.py b/judge/views/bookmark.py index 9c3caeb..7efd5c4 100644 --- a/judge/views/bookmark.py +++ b/judge/views/bookmark.py @@ -74,13 +74,3 @@ class BookMarkDetailView(TemplateResponseMixin, SingleObjectMixin, View): queryset = BookMark.objects.get_or_create(page=self.get_comment_page()) context["bookmark"] = queryset[0] 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 diff --git a/judge/views/contests.py b/judge/views/contests.py index 65a8b36..d1f5245 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -149,16 +149,22 @@ class ContestList( def get(self, request, *args, **kwargs): self.contest_query = None 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: try: self.org_query = list(map(int, request.GET.getlist("orgs"))) - self.org_query = [ - i - for i in self.org_query - if i - in self.request.profile.organizations.values_list("id", flat=True) - ] + if not self.request.user.is_superuser: + self.org_query = [ + i + for i in self.org_query + if i + in self.request.profile.organizations.values_list( + "id", flat=True + ) + ] except ValueError: pass @@ -179,6 +185,10 @@ class ContestList( queryset = queryset.filter( 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: queryset = queryset.filter(organizations__in=self.org_query) @@ -225,8 +235,12 @@ class ContestList( context["first_page_href"] = "." context["contest_query"] = self.contest_query context["org_query"] = self.org_query + context["show_orgs"] = int(self.show_orgs) 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.update(self.get_sort_context()) context.update(self.get_sort_paginate_context()) @@ -404,6 +418,15 @@ class ContestDetail( def get_title(self): 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): context = super(ContestDetail, self).get_context_data(**kwargs) context["contest_problems"] = ( @@ -421,6 +444,7 @@ class ContestDetail( ) .add_i18n_name(self.request.LANGUAGE_CODE) ) + context["editable_organizations"] = self.get_editable_organizations() return context @@ -1002,8 +1026,8 @@ def contest_ranking_ajax(request, contest, participation=None): queryset = contest.users.filter(virtual__gte=0) if request.GET.get("friend") == "true" and request.profile: - friends = list(request.profile.get_friends()) - queryset = queryset.filter(user__user__username__in=friends) + friends = request.profile.get_friends() + queryset = queryset.filter(user_id__in=friends) if request.GET.get("virtual") != "true": queryset = queryset.filter(virtual=0) @@ -1085,9 +1109,8 @@ class ContestFinalRanking(LoginRequiredMixin, ContestRanking): def get_ranking_list(self): if not self.object.is_editable_by(self.request.user): raise Http404() - if self.object.format.has_hidden_subtasks: + if not self.object.format.has_hidden_subtasks: raise Http404() - return get_contest_ranking_list(self.request, self.object, show_final=True) diff --git a/judge/views/course.py b/judge/views/course.py new file mode 100644 index 0000000..b8cf5fd --- /dev/null +++ b/judge/views/course.py @@ -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 diff --git a/judge/views/feed.py b/judge/views/feed.py new file mode 100644 index 0000000..89bb882 --- /dev/null +++ b/judge/views/feed.py @@ -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 diff --git a/judge/views/notification.py b/judge/views/notification.py index ea8d55f..b0a875f 100644 --- a/judge/views/notification.py +++ b/judge/views/notification.py @@ -42,7 +42,6 @@ class NotificationList(ListView): context["unseen_count"] = self.unseen_cnt context["title"] = _("Notifications (%d unseen)" % context["unseen_count"]) context["has_notifications"] = self.queryset.exists() - context["page_titles"] = CacheDict(lambda page: Comment.get_page_title(page)) return context def get(self, request, *args, **kwargs): diff --git a/judge/views/organization.py b/judge/views/organization.py index dd3eee7..8f9eace 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -35,6 +35,7 @@ from django.views.generic.detail import ( SingleObjectTemplateResponseMixin, ) from django.core.paginator import Paginator +from django.contrib.sites.shortcuts import get_current_site from reversion import revisions 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.contests import ContestList from judge.views.submission import AllSubmissions, SubmissionsListBase -from judge.views.pagevote import PageVoteListView -from judge.views.bookmark import BookMarkListView +from judge.views.feed import FeedView __all__ = [ "OrganizationList", @@ -96,14 +96,9 @@ class OrganizationBase(object): def can_edit_organization(self, org=None): if org is None: org = self.object - if not self.request.user.is_authenticated: - return False - profile_id = self.request.profile.id - return ( - org.admins.filter(id=profile_id).exists() - or org.registrant_id == profile_id - or self.request.user.is_superuser - ) + if self.request.profile: + return self.request.profile.can_edit_organization(org) + return False def is_member(self, org=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 ) + 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): if self.request.user.is_superuser: return True @@ -124,6 +126,7 @@ class OrganizationMixin(OrganizationBase): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) 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["organization"] = self.organization context["logo_override_image"] = self.organization.logo_override_image @@ -136,7 +139,9 @@ class OrganizationMixin(OrganizationBase): self.organization_id = int(kwargs["pk"]) self.organization = get_object_or_404(Organization, id=self.organization_id) 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: return generic_message( request, @@ -189,7 +194,7 @@ class MemberOrganizationMixin(OrganizationMixin): ) -class OrganizationHomeViewContext: +class OrganizationHomeView(OrganizationMixin): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if not hasattr(self, "organization"): @@ -216,28 +221,6 @@ class OrganizationHomeViewContext: 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): model = Organization context_object_name = "organizations" @@ -267,58 +250,46 @@ class OrganizationList(TitleMixin, ListView, OrganizationBase): return context -class OrganizationHome(OrganizationDetailView, PageVoteListView, BookMarkListView): +class OrganizationHome(OrganizationHomeView, FeedView): 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): - posts = ( + def get_queryset(self): + return ( BlogPost.objects.filter( visible=True, publish_on__lte=timezone.now(), is_organization_private=True, - organizations=self.object, + organizations=self.organization, ) .order_by("-sticky", "-publish_on") .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): return "b:%s" % post.id def get_context_data(self, **kwargs): context = super(OrganizationHome, self).get_context_data(**kwargs) - context["title"] = self.object.name - context["posts"], context["page_obj"] = self.get_posts_and_page_obj() - context = self.add_pagevote_context_data(context, "posts") - context = self.add_bookmark_context_data(context, "posts") - - # Hack: This allows page_obj to have page_range for non-ListView class - setattr( - context["page_obj"], "page_range", context["posts"].paginator.page_range + context["title"] = self.organization.name + http = "http" if settings.DMOJ_SSL == 0 else "https" + context["organization_subdomain"] = ( + http + + "://" + + self.organization.slug + + "." + + 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() visible_contests = ( Contest.get_visible_contests(self.request.user) .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") ) @@ -331,11 +302,27 @@ class OrganizationHome(OrganizationDetailView, PageVoteListView, BookMarkListVie return context -class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView): +class OrganizationUsers(QueryStringSortMixin, OrganizationMixin, FeedView): template_name = "organization/users.html" all_sorts = frozenset(("points", "problem_count", "rating", "performance_points")) default_desc = all_sorts 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): res = super(OrganizationUsers, self).dispatch(request, *args, **kwargs) @@ -350,26 +337,13 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView): def get_context_data(self, **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["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["page_type"] = "users" context.update(self.get_sort_context()) @@ -378,6 +352,7 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView): class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemList): template_name = "organization/problems.html" + filter_organization = True def get_queryset(self): self.org_query = [self.organization_id] @@ -387,17 +362,6 @@ class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemL self.setup_problem_list(request) 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): return user_completed_ids(self.profile) if self.profile is not None else () @@ -418,8 +382,7 @@ class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemL class OrganizationContestMixin( LoginRequiredMixin, TitleMixin, - OrganizationMixin, - OrganizationHomeViewContext, + OrganizationHomeView, ): model = Contest @@ -477,7 +440,7 @@ class OrganizationSubmissions( def contest(self): return None - def _get_queryset(self): + def get_queryset(self): return ( super() ._get_entire_queryset() @@ -610,8 +573,7 @@ class RequestJoinOrganization(LoginRequiredMixin, SingleObjectMixin, FormView): class OrganizationRequestDetail( LoginRequiredMixin, TitleMixin, - OrganizationMixin, - OrganizationHomeViewContext, + OrganizationHomeView, DetailView, ): model = OrganizationRequest @@ -636,7 +598,8 @@ OrganizationRequestFormSet = modelformset_factory( class OrganizationRequestBaseView( - OrganizationDetailView, + DetailView, + OrganizationHomeView, TitleMixin, LoginRequiredMixin, SingleObjectTemplateResponseMixin, @@ -757,7 +720,7 @@ class AddOrganizationMember( LoginRequiredMixin, TitleMixin, AdminOrganizationMixin, - OrganizationDetailView, + OrganizationHomeView, UpdateView, ): template_name = "organization/add-member.html" @@ -819,7 +782,7 @@ class EditOrganization( LoginRequiredMixin, TitleMixin, AdminOrganizationMixin, - OrganizationDetailView, + OrganizationHomeView, UpdateView, ): template_name = "organization/edit.html" @@ -1020,7 +983,7 @@ class EditOrganizationContest( class AddOrganizationBlog( LoginRequiredMixin, TitleMixin, - OrganizationHomeViewContext, + OrganizationHomeView, MemberOrganizationMixin, CreateView, ): @@ -1071,7 +1034,7 @@ class AddOrganizationBlog( class EditOrganizationBlog( LoginRequiredMixin, TitleMixin, - OrganizationHomeViewContext, + OrganizationHomeView, MemberOrganizationMixin, UpdateView, ): @@ -1165,7 +1128,7 @@ class PendingBlogs( LoginRequiredMixin, TitleMixin, MemberOrganizationMixin, - OrganizationHomeViewContext, + OrganizationHomeView, ListView, ): model = BlogPost diff --git a/judge/views/pagevote.py b/judge/views/pagevote.py index e01f664..e4c5006 100644 --- a/judge/views/pagevote.py +++ b/judge/views/pagevote.py @@ -104,13 +104,3 @@ class PageVoteDetailView(TemplateResponseMixin, SingleObjectMixin, View): queryset = PageVote.objects.get_or_create(page=self.get_comment_page()) context["pagevote"] = queryset[0] 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 diff --git a/judge/views/problem.py b/judge/views/problem.py index 2b309a1..dde3acf 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -63,7 +63,6 @@ from judge.models import ( Submission, SubmissionSource, Organization, - VolunteerProblemVote, Profile, LanguageTemplate, ) @@ -76,6 +75,7 @@ from judge.utils.problems import ( hot_problems, user_attempted_ids, user_completed_ids, + get_related_problems, ) from judge.utils.strings import safe_float_or_none, safe_int_or_none from judge.utils.tickets import own_ticket_filter @@ -86,8 +86,9 @@ from judge.utils.views import ( generic_message, ) from judge.ml.collab_filter import CollabFilter -from judge.views.pagevote import PageVoteDetailView, PageVoteListView -from judge.views.bookmark import BookMarkDetailView, BookMarkListView +from judge.views.pagevote import PageVoteDetailView +from judge.views.bookmark import BookMarkDetailView +from judge.views.feed import FeedView 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): model = Problem slug_url_kwarg = "problem" @@ -145,10 +154,13 @@ class SolvedProblemMixin(object): 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: return () 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"]) if limit: result = result[:limit] @@ -185,31 +197,34 @@ class ProblemSolution( template_name = "problem/editorial.html" 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): return format_html( _('Editorial for {0}'), - self.object.name, - reverse("problem_detail", args=[self.object.code]), + self.problem.name, + 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): context = super(ProblemSolution, self).get_context_data(**kwargs) - - solution = get_object_or_404(Solution, problem=self.object) - + solution = self.get_object() if ( not solution.is_public or solution.publish_on > timezone.now() ) and not self.request.user.has_perm("judge.see_private_solution"): raise Http404() 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 def get_comment_page(self): - return "s:" + self.object.code + return "s:" + self.problem.code class ProblemRaw( @@ -341,6 +356,10 @@ class ProblemDetail( else: context["fileio_input"] = None context["fileio_output"] = None + if not self.in_contest: + context["related_problems"] = get_related_problems( + self.profile, self.object + ) return context @@ -454,6 +473,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView default_desc = frozenset(("date", "points", "ac_rate", "user_count")) default_sort = "-date" first_page_href = None + filter_organization = False def get_paginator( self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs @@ -465,13 +485,10 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView padding=2, orphans=orphans, allow_empty_first_page=allow_empty_first_page, + count=queryset.values("pk").count() if not self.in_contest else None, **kwargs ) 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) sort_key = self.order.lstrip("-") if sort_key in self.sql_sort: @@ -573,25 +590,13 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView ] def get_normal_queryset(self): - filter = Q(is_public=True) - if self.profile is not None: - 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) + queryset = Problem.get_visible_problems(self.request.user) + queryset = queryset.select_related("group") if self.profile is not None and self.hide_solved: - queryset = queryset.exclude( - id__in=Submission.objects.filter( - user=self.profile, points=F("problem__points") - ).values_list("problem__id", flat=True) - ) + solved_problems = self.get_completed_problems() + queryset = queryset.exclude(id__in=solved_problems) + if not self.org_query and self.request.organization: + self.org_query = [self.request.organization.id] if self.org_query: self.org_query = self.get_org_query(self.org_query) queryset = queryset.filter( @@ -652,6 +657,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView def get_context_data(self, **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["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) @@ -663,8 +670,12 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView if self.request.profile: context["organizations"] = self.request.profile.organizations.all() - all_authors_ids = set(Problem.objects.values_list("authors", flat=True)) - context["all_authors"] = Profile.objects.filter(id__in=all_authors_ids) + all_authors_ids = Problem.objects.values_list("authors", flat=True) + context["all_authors"] = ( + Profile.objects.filter(id__in=all_authors_ids) + .select_related("user") + .values("id", "user__username") + ) context["category"] = self.category context["categories"] = ProblemGroup.objects.all() if self.show_types: @@ -676,7 +687,9 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView context["search_query"] = self.search_query context["completed_problem_ids"] = self.get_completed_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.update(self.get_sort_paginate_context()) if not self.in_contest: @@ -751,7 +764,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView ) 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.GET.get(key, None) == "1" @@ -820,32 +833,15 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView return HttpResponseRedirect(request.get_full_path()) -class ProblemFeed(ProblemList, PageVoteListView, BookMarkListView): +class ProblemFeed(ProblemList, FeedView): model = Problem context_object_name = "problems" template_name = "problem/feed.html" - paginate_by = 20 + feed_content_template_name = "problem/feed/problems.html" + paginate_by = 4 title = _("Problem feed") 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): return "p:%s" % problem.code @@ -945,6 +941,12 @@ class ProblemFeed(ProblemList, PageVoteListView, BookMarkListView): res[position_in_q[problem.id]] = problem 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): context = super(ProblemFeed, self).get_context_data(**kwargs) context["page_type"] = "feed" @@ -952,8 +954,6 @@ class ProblemFeed(ProblemList, PageVoteListView, BookMarkListView): context["feed_type"] = self.feed_type context["has_show_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 diff --git a/judge/views/ranked_submission.py b/judge/views/ranked_submission.py index a0180b3..e00a460 100644 --- a/judge/views/ranked_submission.py +++ b/judge/views/ranked_submission.py @@ -7,7 +7,7 @@ from judge.utils.problems import get_result_data from judge.utils.raw_sql import join_sql_subquery from judge.views.submission import ForceContestMixin, ProblemSubmissions -__all__ = ["RankedSubmissions", "ContestRankedSubmission"] +__all__ = ["RankedSubmissions"] class RankedSubmissions(ProblemSubmissions): @@ -27,7 +27,7 @@ class RankedSubmissions(ProblemSubmissions): constraint = "" queryset = ( super(RankedSubmissions, self) - ._get_queryset() + .get_queryset() .filter(user__is_unlisted=False) ) join_sql_subquery( @@ -76,43 +76,4 @@ class RankedSubmissions(ProblemSubmissions): ) def _get_result_data(self): - return get_result_data( - 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 {0} in {2}'), - 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 {1}'), - 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, - ) - ) + return get_result_data(super(RankedSubmissions, self).get_queryset().order_by()) diff --git a/judge/views/submission.py b/judge/views/submission.py index 8ae921b..9bc6eea 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -41,19 +41,15 @@ from judge.models import ProblemTranslation from judge.models import Profile from judge.models import Submission from judge.utils.problems import get_result_data -from judge.utils.problems import user_authored_ids -from judge.utils.problems import user_completed_ids -from judge.utils.problems import user_editable_ids +from judge.utils.problems import user_completed_ids, user_editable_ids, user_tester_ids from judge.utils.problem_data import get_problem_case from judge.utils.raw_sql import join_sql_subquery, use_straight_join from judge.utils.views import DiggPaginatorMixin +from judge.utils.infinite_paginator import InfinitePaginationMixin from judge.utils.views import TitleMixin from judge.utils.timedelta import nice_repr -MAX_NUMBER_OF_QUERY_SUBMISSIONS = 50000 - - def submission_related(queryset): return queryset.select_related("user__user", "problem", "language").only( "id", @@ -266,7 +262,7 @@ class SubmissionStatus(SubmissionDetailBase): can_see_testcases = self.access_testcases_in_contest() 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: context["cases_data"] = get_cases_data(submission) @@ -333,7 +329,7 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): return result 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): pass @@ -404,16 +400,21 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): language__in=Language.objects.filter(key__in=self.selected_languages) ) if self.selected_statuses: - queryset = queryset.filter( - Q(result__in=self.selected_statuses) - | Q(status__in=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) return queryset - def _get_queryset(self): + def get_queryset(self): queryset = self._get_entire_queryset() if not self.in_contest: + if self.request.organization: + queryset = queryset.filter( + contest_object__organizations=self.request.organization + ) join_sql_subquery( queryset, subquery=str( @@ -429,12 +430,12 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): ) return queryset - def get_queryset(self): - return self._get_queryset()[:MAX_NUMBER_OF_QUERY_SUBMISSIONS] - def get_my_submissions_page(self): return None + def get_friend_submissions_page(self): + return None + def get_all_submissions_page(self): return reverse("all_submissions") @@ -473,12 +474,12 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): context["completed_problem_ids"] = ( 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"] = ( 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["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["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["page_type"] = self.page_type @@ -513,8 +515,8 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): if check is not None: return check - self.selected_languages = set(request.GET.getlist("language")) - self.selected_statuses = set(request.GET.getlist("status")) + self.selected_languages = request.GET.getlist("language") + self.selected_statuses = request.GET.getlist("status") if self.in_contest and self.contest.is_editable_by(self.request.user): self.include_frozen = True @@ -554,11 +556,25 @@ class ConditionalUserTabMixin(object): return context -class AllUserSubmissions(ConditionalUserTabMixin, UserMixin, SubmissionsListBase): - def _get_queryset(self): +class GeneralSubmissions(SubmissionsListBase): + 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 ( super(AllUserSubmissions, self) - ._get_queryset() + .get_queryset() .filter(user_id=self.profile.id) ) @@ -569,19 +585,13 @@ class AllUserSubmissions(ConditionalUserTabMixin, UserMixin, SubmissionsListBase def get_content_title(self): 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( - 'All submissions by {0}', + _('All submissions by {0}'), 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): context = super(AllUserSubmissions, self).get_context_data(**kwargs) context["dynamic_update"] = context["page_obj"].number == 1 @@ -590,12 +600,29 @@ class AllUserSubmissions(ConditionalUserTabMixin, UserMixin, SubmissionsListBase 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): show_problem = False dynamic_update = True check_contest_in_access_check = False - def _get_queryset(self): + def get_queryset(self): if ( self.in_contest and not self.contest.contest_problems.filter( @@ -687,10 +714,10 @@ class UserProblemSubmissions(ConditionalUserTabMixin, UserMixin, ProblemSubmissi if not self.is_own: self.access_check_contest(request) - def _get_queryset(self): + def get_queryset(self): return ( super(UserProblemSubmissions, self) - ._get_queryset() + .get_queryset() .filter(user_id=self.profile.id) ) @@ -740,15 +767,15 @@ def single_submission(request, submission_id, show_problem=True): "submission/row.html", { "submission": submission, - "authored_problem_ids": user_authored_ids(request.profile) - if authenticated - else [], "completed_problem_ids": user_completed_ids(request.profile) if authenticated else [], "editable_problem_ids": user_editable_ids(request.profile) if authenticated else [], + "tester_problem_ids": user_tester_ids(request.profile) + if authenticated + else [], "show_problem": show_problem, "problem_name": show_problem 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)) -class AllSubmissions(SubmissionsListBase): +class AllSubmissions(InfinitePaginationMixin, GeneralSubmissions): stats_update_interval = 3600 - def get_my_submissions_page(self): - if self.request.user.is_authenticated: - return reverse( - "all_user_submissions", kwargs={"user": self.request.user.username} - ) + @property + def use_infinite_pagination(self): + return not self.in_contest def get_context_data(self, **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["stats_update_interval"] = self.stats_update_interval return context 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() 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) if 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) return result diff --git a/judge/views/user.py b/judge/views/user.py index bd0d34b..9796a90 100644 --- a/judge/views/user.py +++ b/judge/views/user.py @@ -44,12 +44,12 @@ from judge.utils.problems import contest_completed_ids, user_completed_ids from judge.utils.ranker import ranker from judge.utils.unicode import utf8text from judge.utils.views import ( - DiggPaginatorMixin, QueryStringSortMixin, TitleMixin, generic_message, SingleObjectFormView, ) +from judge.utils.infinite_paginator import InfinitePaginationMixin from .contests import ContestRanking __all__ = [ @@ -437,7 +437,7 @@ def edit_profile(request): ) -class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView): +class UserList(QueryStringSortMixin, InfinitePaginationMixin, TitleMixin, ListView): model = Profile title = gettext_lazy("Leaderboard") context_object_name = "users" @@ -449,12 +449,12 @@ class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView): filter_friend = False def filter_friend_queryset(self, queryset): - friends = list(self.request.profile.get_friends()) - ret = queryset.filter(user__username__in=friends) + friends = self.request.profile.get_friends() + ret = queryset.filter(id__in=friends) return ret def get_queryset(self): - ret = ( + queryset = ( Profile.objects.filter(is_unlisted=False) .order_by(self.order, "id") .select_related("user") @@ -467,11 +467,13 @@ class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView): "problem_count", ) ) - + if self.request.organization: + queryset = queryset.filter(organizations=self.request.organization) 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 - return ret + + return queryset def get_context_data(self, **kwargs): context = super(UserList, self).get_context_data(**kwargs) diff --git a/locale/vi/LC_MESSAGES/django.po b/locale/vi/LC_MESSAGES/django.po index 8a2c220..8fb1732 100644 --- a/locale/vi/LC_MESSAGES/django.po +++ b/locale/vi/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: lqdoj2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-12-29 03:29+0700\n" +"POT-Creation-Date: 2023-02-13 09:44+0700\n" "PO-Revision-Date: 2021-07-20 03:44\n" "Last-Translator: Icyene\n" "Language-Team: Vietnamese\n" @@ -19,13 +19,13 @@ msgstr "" "X-Crowdin-File-ID: 5\n" #: chat_box/models.py:31 chat_box/models.py:51 chat_box/models.py:65 -#: judge/admin/interface.py:150 judge/models/contest.py:630 -#: judge/models/contest.py:835 judge/models/profile.py:354 -#: judge/models/profile.py:430 +#: judge/admin/interface.py:150 judge/models/contest.py:633 +#: judge/models/contest.py:842 judge/models/course.py:115 +#: judge/models/profile.py:368 judge/models/profile.py:444 msgid "user" msgstr "người dùng" -#: chat_box/models.py:32 judge/models/comment.py:46 judge/models/comment.py:242 +#: chat_box/models.py:32 judge/models/comment.py:46 judge/models/comment.py:245 msgid "posted time" msgstr "thời gian đăng" @@ -37,23 +37,23 @@ msgstr "nội dung bình luận" msgid "last seen" msgstr "xem lần cuối" -#: chat_box/views.py:45 templates/chat/chat.html:4 templates/chat/chat.html:554 -msgid "Chat Box" -msgstr "Chat Box" +#: chat_box/views.py:45 +msgid "LQDOJ Chat" +msgstr "" -#: dmoj/settings.py:360 +#: dmoj/settings.py:361 msgid "Vietnamese" msgstr "Tiếng Việt" -#: dmoj/settings.py:361 +#: dmoj/settings.py:362 msgid "English" msgstr "" -#: dmoj/urls.py:134 +#: dmoj/urls.py:135 msgid "Login" msgstr "Đăng nhập" -#: dmoj/urls.py:211 templates/base.html:208 +#: dmoj/urls.py:212 templates/base.html:209 #: templates/organization/org-left-sidebar.html:2 msgid "Home" msgstr "Trang chủ" @@ -87,10 +87,10 @@ msgid "Included contests" msgstr "" #: judge/admin/contest.py:80 judge/admin/volunteer.py:54 -#: templates/contest/clarification.html:42 templates/contest/contest.html:94 +#: templates/contest/clarification.html:42 templates/contest/contest.html:100 #: templates/contest/moss.html:41 templates/internal/base.html:29 -#: templates/internal/base.html:37 templates/problem/list.html:15 -#: templates/problem/list.html:30 templates/problem/list.html:146 +#: templates/internal/base.html:37 templates/problem/list.html:17 +#: templates/problem/list.html:34 templates/problem/list.html:153 #: templates/user/user-problems.html:56 templates/user/user-problems.html:98 msgid "Problem" msgstr "Bài tập" @@ -124,47 +124,47 @@ msgstr "Truy cập" msgid "Justice" msgstr "Xử phạt" -#: judge/admin/contest.py:324 +#: judge/admin/contest.py:331 #, python-format msgid "%d contest successfully marked as visible." msgid_plural "%d contests successfully marked as visible." msgstr[0] "%d kỳ thi đã được đánh dấu hiển thị." -#: judge/admin/contest.py:331 +#: judge/admin/contest.py:338 msgid "Mark contests as visible" msgstr "Đánh dấu hiển thị các kỳ thi" -#: judge/admin/contest.py:342 +#: judge/admin/contest.py:349 #, python-format msgid "%d contest successfully marked as hidden." msgid_plural "%d contests successfully marked as hidden." msgstr[0] "%d kỳ thi đã được đánh dấu ẩn." -#: judge/admin/contest.py:349 +#: judge/admin/contest.py:356 msgid "Mark contests as hidden" msgstr "Ẩn các kỳ thi" -#: judge/admin/contest.py:370 judge/admin/submission.py:243 +#: judge/admin/contest.py:377 judge/admin/submission.py:243 #, python-format msgid "%d submission was successfully scheduled for rejudging." msgid_plural "%d submissions were successfully scheduled for rejudging." msgstr[0] "%d bài nộp đã được lên lịch thành công để chấm lại." -#: judge/admin/contest.py:478 +#: judge/admin/contest.py:485 #, python-format msgid "%d participation recalculated." msgid_plural "%d participations recalculated." msgstr[0] "%d thí sinh đã được tính điểm lại." -#: judge/admin/contest.py:485 +#: judge/admin/contest.py:492 msgid "Recalculate results" msgstr "Tính toán lại kết quả" -#: judge/admin/contest.py:490 judge/admin/organization.py:99 +#: judge/admin/contest.py:497 judge/admin/organization.py:99 msgid "username" msgstr "tên đăng nhập" -#: judge/admin/contest.py:496 templates/base.html:316 +#: judge/admin/contest.py:503 templates/base.html:317 msgid "virtual" msgstr "ảo" @@ -213,9 +213,9 @@ msgstr "Mạng Xã Hội" msgid "Taxonomy" msgstr "" -#: judge/admin/problem.py:215 judge/admin/problem.py:433 -#: templates/contest/contest.html:95 templates/problem/data.html:517 -#: templates/problem/list.html:20 templates/problem/list.html:44 +#: judge/admin/problem.py:215 judge/admin/problem.py:438 +#: templates/contest/contest.html:101 templates/problem/data.html:517 +#: templates/problem/list.html:22 templates/problem/list.html:48 #: templates/user/base-users-table.html:10 templates/user/user-about.html:36 #: templates/user/user-about.html:52 templates/user/user-problems.html:58 msgid "Points" @@ -226,8 +226,8 @@ msgid "Limits" msgstr "Giới hạn" #: judge/admin/problem.py:217 judge/admin/submission.py:353 -#: templates/base.html:244 templates/stats/tab.html:4 -#: templates/submission/list.html:342 +#: templates/base.html:245 templates/stats/tab.html:4 +#: templates/submission/list.html:347 msgid "Language" msgstr "Ngôn ngữ" @@ -235,7 +235,7 @@ msgstr "Ngôn ngữ" msgid "History" msgstr "Lịch sử" -#: judge/admin/problem.py:271 templates/problem/list-base.html:100 +#: judge/admin/problem.py:271 templates/problem/list-base.html:99 msgid "Authors" msgstr "Các tác giả" @@ -259,28 +259,28 @@ msgstr[0] "%d bài tập đã được đánh dấu riêng tư." msgid "Mark problems as private" msgstr "Đánh dấu các bài tập là riêng tư" -#: judge/admin/problem.py:427 judge/admin/submission.py:316 -#: templates/problem/list.html:16 templates/problem/list.html:33 +#: judge/admin/problem.py:432 judge/admin/submission.py:316 +#: templates/problem/list.html:18 templates/problem/list.html:37 msgid "Problem code" msgstr "Mã bài" -#: judge/admin/problem.py:439 judge/admin/submission.py:322 +#: judge/admin/problem.py:444 judge/admin/submission.py:322 msgid "Problem name" msgstr "Tên bài" -#: judge/admin/problem.py:445 +#: judge/admin/problem.py:450 #, fuzzy #| msgid "contest rating" msgid "Voter rating" msgstr "rating kỳ thi" -#: judge/admin/problem.py:451 +#: judge/admin/problem.py:456 #, fuzzy #| msgid "Total points" msgid "Voter point" msgstr "Tổng điểm" -#: judge/admin/problem.py:457 +#: judge/admin/problem.py:462 msgid "Vote" msgstr "" @@ -291,7 +291,7 @@ msgstr "múi giờ" #: judge/admin/profile.py:125 judge/admin/submission.py:329 #: templates/notification/list.html:12 #: templates/organization/requests/log.html:9 -#: templates/organization/requests/pending.html:12 +#: templates/organization/requests/pending.html:19 #: templates/ticket/list.html:263 msgid "User" msgstr "Thành viên" @@ -329,7 +329,7 @@ msgstr "Các bài tập không được cho phép" msgid "These problems are NOT allowed to be submitted in this language" msgstr "Các bài này không cho phép sử dụng ngôn ngữ này" -#: judge/admin/runtime.py:117 templates/problem/list.html:148 +#: judge/admin/runtime.py:117 templates/problem/list.html:155 msgid "Description" msgstr "Mô tả" @@ -388,10 +388,10 @@ msgstr "Tính điểm lại cái bài nộp" #: judge/admin/submission.py:334 templates/notification/list.html:15 #: templates/organization/requests/log.html:10 -#: templates/organization/requests/pending.html:13 -#: templates/problem/list.html:147 -#: templates/submission/status-testcases.html:135 -#: templates/submission/status-testcases.html:137 +#: templates/organization/requests/pending.html:20 +#: templates/problem/list.html:154 +#: templates/submission/status-testcases.html:144 +#: templates/submission/status-testcases.html:146 msgid "Time" msgstr "Thời gian" @@ -405,7 +405,7 @@ msgstr "%d KB" msgid "%.2f MB" msgstr "" -#: judge/admin/submission.py:347 templates/submission/status-testcases.html:142 +#: judge/admin/submission.py:347 templates/submission/status-testcases.html:151 msgid "Memory" msgstr "Bộ nhớ" @@ -422,7 +422,7 @@ msgid "These problems are included in this type of problems" msgstr "Các bài tập dạng này" #: judge/admin/volunteer.py:60 templates/internal/base.html:78 -#: templates/problem/list.html:18 templates/problem/list.html:40 +#: templates/problem/list.html:20 templates/problem/list.html:44 msgid "Types" msgstr "Dạng" @@ -467,11 +467,11 @@ msgstr "" msgid "IOI" msgstr "" -#: judge/contest_format/new_ioi.py:13 +#: judge/contest_format/new_ioi.py:11 msgid "New IOI" msgstr "IOI mới" -#: judge/forms.py:102 judge/views/organization.py:536 +#: judge/forms.py:102 judge/views/organization.py:550 #: judge/views/register.py:62 #, python-brace-format msgid "You may not be part of more than {count} public groups." @@ -531,7 +531,7 @@ msgstr "Mã kỳ thi phải có dạng ^[a-z0-9]+$" msgid "Contest with key already exists." msgstr "Mã kỳ thi đã tồn tại." -#: judge/jinja2/datetime.py:26 templates/blog/blog.html:29 +#: judge/jinja2/datetime.py:26 templates/blog/blog.html:28 #: templates/blog/dashboard.html:21 msgid "N j, Y, g:i a" msgstr "g:i a j b, Y" @@ -546,8 +546,20 @@ msgstr "{time}" msgid "on {time}" msgstr "vào {time}" +#: judge/middleware.py:125 +msgid "No permission" +msgstr "Không có quyền truy cập" + +#: judge/middleware.py:126 +msgid "You need to join this group first" +msgstr "Bạn phải là thành viên của nhóm." + +#: judge/middleware.py:136 judge/middleware.py:137 +msgid "No such group" +msgstr "Nhóm không tồn tại" + #: judge/models/bookmark.py:17 judge/models/comment.py:49 -#: judge/models/comment.py:223 judge/models/pagevote.py:13 +#: judge/models/comment.py:226 judge/models/pagevote.py:13 msgid "associated page" msgstr "trang tương ứng" @@ -616,7 +628,7 @@ msgstr "ẩn bình luận" msgid "parent" msgstr "" -#: judge/models/comment.py:67 judge/models/comment.py:244 +#: judge/models/comment.py:67 judge/models/comment.py:247 msgid "comment" msgstr "bình luận" @@ -624,24 +636,24 @@ msgstr "bình luận" msgid "comments" msgstr "" -#: judge/models/comment.py:160 judge/models/problem.py:654 +#: judge/models/comment.py:163 judge/models/problem.py:654 #, python-format msgid "Editorial for %s" msgstr "" -#: judge/models/comment.py:216 +#: judge/models/comment.py:219 msgid "comment vote" msgstr "" -#: judge/models/comment.py:217 +#: judge/models/comment.py:220 msgid "comment votes" msgstr "" -#: judge/models/comment.py:229 +#: judge/models/comment.py:232 msgid "Override comment lock" msgstr "" -#: judge/models/comment.py:238 +#: judge/models/comment.py:241 #: src/dmoj-wpadmin/test_project/apps/books/admin.py:24 #: src/dmoj-wpadmin/test_project/apps/books/models.py:30 #: src/dmoj-wpadmin/test_project/apps/cds/models.py:30 @@ -649,22 +661,22 @@ msgstr "" msgid "owner" msgstr "" -#: judge/models/comment.py:246 judge/models/message.py:28 +#: judge/models/comment.py:249 judge/models/message.py:28 msgid "read" msgstr "" -#: judge/models/comment.py:247 +#: judge/models/comment.py:250 #: src/dmoj-wpadmin/test_project/apps/books/models.py:28 #: src/dmoj-wpadmin/test_project/apps/cds/models.py:28 #: src/dmoj-wpadmin/test_project/apps/dvds/models.py:28 msgid "category" msgstr "" -#: judge/models/comment.py:250 +#: judge/models/comment.py:253 msgid "html link to comments, used for non-comments" msgstr "" -#: judge/models/comment.py:256 +#: judge/models/comment.py:259 msgid "who trigger, used for non-comment" msgstr "" @@ -731,7 +743,8 @@ msgid "These users will be able to view the contest, but not edit it." msgstr "" "Những người dùng này có thể thấy kỳ thi nhưng không có quyền chỉnh sửa." -#: judge/models/contest.py:117 judge/models/runtime.py:211 +#: judge/models/contest.py:117 judge/models/course.py:158 +#: judge/models/runtime.py:211 #: src/dmoj-wpadmin/test_project/apps/books/admin.py:20 #: src/dmoj-wpadmin/test_project/apps/books/models.py:13 #: src/dmoj-wpadmin/test_project/apps/books/models.py:27 @@ -747,7 +760,7 @@ msgstr "mô tả" msgid "problems" msgstr "bài tập" -#: judge/models/contest.py:121 judge/models/contest.py:635 +#: judge/models/contest.py:121 judge/models/contest.py:638 msgid "start time" msgstr "thời gian bắt đầu" @@ -779,7 +792,8 @@ msgstr "" "Định dạng hh:mm:ss (giờ:phút:giây). Ví dụ, nếu muốn đóng băng kỳ thi sau 2h, " "hãy nhập 02:00:00" -#: judge/models/contest.py:140 judge/models/problem.py:222 +#: judge/models/contest.py:140 judge/models/course.py:28 +#: judge/models/course.py:164 judge/models/problem.py:222 msgid "publicly visible" msgstr "công khai" @@ -882,8 +896,9 @@ msgstr "" msgid "private to organizations" msgstr "riêng tư với các tổ chức" -#: judge/models/contest.py:225 judge/models/interface.py:88 -#: judge/models/problem.py:275 judge/models/profile.py:126 +#: judge/models/contest.py:225 judge/models/course.py:34 +#: judge/models/interface.py:88 judge/models/problem.py:275 +#: judge/models/profile.py:130 msgid "organizations" msgstr "tổ chức" @@ -895,7 +910,7 @@ msgstr "Nếu riêng tư, chỉ những tổ chức này thấy được kỳ th msgid "OpenGraph image" msgstr "Hình ảnh OpenGraph" -#: judge/models/contest.py:232 judge/models/profile.py:81 +#: judge/models/contest.py:232 judge/models/profile.py:85 msgid "Logo override image" msgstr "Hình ảnh ghi đè logo" @@ -916,7 +931,7 @@ msgstr "tổng kết kỳ thi" msgid "Plain-text, shown in meta description tag, e.g. for social media." msgstr "" -#: judge/models/contest.py:255 judge/models/profile.py:76 +#: judge/models/contest.py:255 judge/models/profile.py:80 msgid "access code" msgstr "mật khẩu truy cập" @@ -963,260 +978,337 @@ msgstr "Hiển thị điểm" msgid "Number of digits to round points to." msgstr "Số chữ số thập phân trên bảng điểm." -#: judge/models/contest.py:603 +#: judge/models/contest.py:606 msgid "See private contests" msgstr "" -#: judge/models/contest.py:604 +#: judge/models/contest.py:607 msgid "Edit own contests" msgstr "" -#: judge/models/contest.py:605 +#: judge/models/contest.py:608 msgid "Edit all contests" msgstr "" -#: judge/models/contest.py:606 +#: judge/models/contest.py:609 msgid "Clone contest" msgstr "" -#: judge/models/contest.py:607 templates/contest/moss.html:72 +#: judge/models/contest.py:610 templates/contest/moss.html:72 msgid "MOSS contest" msgstr "" -#: judge/models/contest.py:608 +#: judge/models/contest.py:611 msgid "Rate contests" msgstr "" -#: judge/models/contest.py:609 +#: judge/models/contest.py:612 msgid "Contest access codes" msgstr "" -#: judge/models/contest.py:610 +#: judge/models/contest.py:613 msgid "Create private contests" msgstr "" -#: judge/models/contest.py:611 +#: judge/models/contest.py:614 msgid "Change contest visibility" msgstr "" -#: judge/models/contest.py:612 +#: judge/models/contest.py:615 msgid "Edit contest problem label script" msgstr "Cách hiển thị thứ tự bài tập" -#: judge/models/contest.py:614 judge/models/contest.py:757 -#: judge/models/contest.py:838 judge/models/contest.py:868 -#: judge/models/submission.py:116 +#: judge/models/contest.py:617 judge/models/contest.py:764 +#: judge/models/contest.py:845 judge/models/contest.py:875 +#: judge/models/course.py:178 judge/models/submission.py:116 msgid "contest" msgstr "kỳ thi" -#: judge/models/contest.py:615 +#: judge/models/contest.py:618 msgid "contests" msgstr "kỳ thi" -#: judge/models/contest.py:624 +#: judge/models/contest.py:627 msgid "associated contest" msgstr "" -#: judge/models/contest.py:637 +#: judge/models/contest.py:640 msgid "score" msgstr "điểm" -#: judge/models/contest.py:638 +#: judge/models/contest.py:641 msgid "cumulative time" msgstr "tổng thời gian" -#: judge/models/contest.py:640 +#: judge/models/contest.py:643 msgid "is disqualified" msgstr "đã bị loại" -#: judge/models/contest.py:642 +#: judge/models/contest.py:645 msgid "Whether this participation is disqualified." msgstr "Quyết định thí sinh có bị loại không." -#: judge/models/contest.py:644 +#: judge/models/contest.py:647 msgid "tie-breaking field" msgstr "" -#: judge/models/contest.py:646 +#: judge/models/contest.py:649 msgid "virtual participation id" msgstr "id lần tham gia ảo" -#: judge/models/contest.py:648 +#: judge/models/contest.py:651 msgid "0 means non-virtual, otherwise the n-th virtual participation." msgstr "0 nghĩa là tham gia chính thức, ngược lại là lần tham gia ảo thứ n." -#: judge/models/contest.py:651 +#: judge/models/contest.py:654 msgid "contest format specific data" msgstr "" -#: judge/models/contest.py:654 +#: judge/models/contest.py:657 msgid "same as format_data, but including frozen results" msgstr "" -#: judge/models/contest.py:656 +#: judge/models/contest.py:661 #, fuzzy #| msgid "score" msgid "final score" msgstr "điểm" -#: judge/models/contest.py:657 +#: judge/models/contest.py:663 #, fuzzy #| msgid "cumulative time" msgid "final cumulative time" msgstr "tổng thời gian" -#: judge/models/contest.py:732 +#: judge/models/contest.py:739 #, python-format msgid "%s spectating in %s" msgstr "%s đang theo dõi trong %s" -#: judge/models/contest.py:737 +#: judge/models/contest.py:744 #, python-format msgid "%s in %s, v%d" msgstr "%s trong %s, v%d" -#: judge/models/contest.py:742 +#: judge/models/contest.py:749 #, python-format msgid "%s in %s" msgstr "%s trong %s" -#: judge/models/contest.py:745 +#: judge/models/contest.py:752 msgid "contest participation" msgstr "lần tham gia kỳ thi" -#: judge/models/contest.py:746 +#: judge/models/contest.py:753 msgid "contest participations" msgstr "lần tham gia kỳ thi" -#: judge/models/contest.py:753 judge/models/contest.py:809 -#: judge/models/contest.py:871 judge/models/problem.py:558 +#: judge/models/contest.py:760 judge/models/contest.py:816 +#: judge/models/contest.py:878 judge/models/problem.py:558 #: judge/models/problem.py:565 judge/models/problem.py:586 #: judge/models/problem.py:617 judge/models/problem_data.py:50 msgid "problem" msgstr "bài tập" -#: judge/models/contest.py:761 judge/models/contest.py:821 -#: judge/models/problem.py:206 +#: judge/models/contest.py:768 judge/models/contest.py:828 +#: judge/models/course.py:182 judge/models/problem.py:206 msgid "points" msgstr "điểm" -#: judge/models/contest.py:762 +#: judge/models/contest.py:769 msgid "partial" msgstr "thành phần" -#: judge/models/contest.py:763 judge/models/contest.py:823 +#: judge/models/contest.py:770 judge/models/contest.py:830 msgid "is pretested" msgstr "dùng pretest" -#: judge/models/contest.py:764 judge/models/interface.py:43 +#: judge/models/contest.py:771 judge/models/interface.py:43 msgid "order" msgstr "thứ tự" -#: judge/models/contest.py:766 +#: judge/models/contest.py:773 msgid "0 to not show testcases, 1 to show" msgstr "0 để ẩn test, 1 để hiện" -#: judge/models/contest.py:767 +#: judge/models/contest.py:774 msgid "visible testcases" msgstr "hiển thị test" -#: judge/models/contest.py:774 +#: judge/models/contest.py:781 msgid "Maximum number of submissions for this problem, or 0 for no limit." msgstr "Số lần nộp tối đa, đặt là 0 nếu không có giới hạn." -#: judge/models/contest.py:776 +#: judge/models/contest.py:783 msgid "max submissions" msgstr "số lần nộp tối đa" -#: judge/models/contest.py:779 +#: judge/models/contest.py:786 msgid "Why include a problem you can't submit to?" msgstr "" -#: judge/models/contest.py:783 -msgid "Only for format new IOI. Separated by commas, e.g: 2, 3" +#: judge/models/contest.py:790 +#, fuzzy +#| msgid "Only for format new IOI. Separated by commas, e.g: 2, 3" +msgid "Separated by commas, e.g: 2, 3" msgstr "" "Chỉ dùng với format IOI mới. Các sub cách nhau bởi dấu phẩy. Ví dụ: 2, 3" -#: judge/models/contest.py:784 -msgid "frozen subtasks" +#: judge/models/contest.py:791 +#, fuzzy +#| msgid "frozen subtasks" +msgid "hidden subtasks" msgstr "Đóng băng subtasks" -#: judge/models/contest.py:796 +#: judge/models/contest.py:803 msgid "contest problem" msgstr "bài trong kỳ thi" -#: judge/models/contest.py:797 +#: judge/models/contest.py:804 msgid "contest problems" msgstr "bài trong kỳ thi" -#: judge/models/contest.py:803 judge/models/submission.py:233 +#: judge/models/contest.py:810 judge/models/submission.py:233 msgid "submission" msgstr "bài nộp" -#: judge/models/contest.py:816 judge/models/contest.py:842 +#: judge/models/contest.py:823 judge/models/contest.py:849 msgid "participation" msgstr "lần tham gia" -#: judge/models/contest.py:824 +#: judge/models/contest.py:831 msgid "Whether this submission was ran only on pretests." msgstr "Quyết định bài nộp chỉ được chạy trên pretest không." -#: judge/models/contest.py:829 +#: judge/models/contest.py:836 msgid "contest submission" msgstr "bài nộp kỳ thi" -#: judge/models/contest.py:830 +#: judge/models/contest.py:837 msgid "contest submissions" msgstr "bài nộp kỳ thi" -#: judge/models/contest.py:846 +#: judge/models/contest.py:853 msgid "rank" msgstr "rank" -#: judge/models/contest.py:847 +#: judge/models/contest.py:854 msgid "rating" msgstr "rating" -#: judge/models/contest.py:848 +#: judge/models/contest.py:855 msgid "raw rating" msgstr "rating thật" -#: judge/models/contest.py:849 +#: judge/models/contest.py:856 msgid "contest performance" msgstr "" -#: judge/models/contest.py:850 +#: judge/models/contest.py:857 msgid "last rated" msgstr "lần cuối được xếp hạng" -#: judge/models/contest.py:854 +#: judge/models/contest.py:861 msgid "contest rating" msgstr "rating kỳ thi" -#: judge/models/contest.py:855 +#: judge/models/contest.py:862 msgid "contest ratings" msgstr "rating kỳ thi" -#: judge/models/contest.py:879 +#: judge/models/contest.py:886 msgid "contest moss result" msgstr "kết quả MOSS kỳ thi" -#: judge/models/contest.py:880 +#: judge/models/contest.py:887 msgid "contest moss results" msgstr "kết quả MOSS kỳ thi" -#: judge/models/contest.py:885 +#: judge/models/contest.py:892 msgid "clarified problem" msgstr "" -#: judge/models/contest.py:887 +#: judge/models/contest.py:894 msgid "clarification body" msgstr "" -#: judge/models/contest.py:889 +#: judge/models/contest.py:896 msgid "clarification timestamp" msgstr "" +#: judge/models/course.py:21 +#, fuzzy +#| msgid "username" +msgid "course name" +msgstr "tên đăng nhập" + +#: judge/models/course.py:23 judge/models/profile.py:46 +msgid "organization description" +msgstr "mô tả tổ chức" + +#: judge/models/course.py:25 +#, fuzzy +#| msgid "end time" +msgid "ending time" +msgstr "thời gian kết thúc" + +#: judge/models/course.py:35 +#, fuzzy +#| msgid "If private, only these organizations may see the contest" +msgid "If private, only these organizations may see the course" +msgstr "Nếu riêng tư, chỉ những tổ chức này thấy được kỳ thi" + +#: judge/models/course.py:39 +msgid "course slug" +msgstr "" + +#: judge/models/course.py:40 +#, fuzzy +#| msgid "Organization name shown in URL" +msgid "Course name shown in URL" +msgstr "Tên được hiển thị trong đường dẫn" + +#: judge/models/course.py:43 judge/models/profile.py:38 +msgid "Only alphanumeric and hyphens" +msgstr "" + +#: judge/models/course.py:47 +#, fuzzy +#| msgid "Registration" +msgid "public registration" +msgstr "Đăng ký" + +#: judge/models/course.py:51 +msgid "course image" +msgstr "" + +#: judge/models/course.py:109 judge/models/course.py:147 +#: judge/models/course.py:172 +msgid "course" +msgstr "" + +#: judge/models/course.py:117 +msgid "user_of_course" +msgstr "" + +#: judge/models/course.py:121 +msgid "Student" +msgstr "" + +#: judge/models/course.py:122 +msgid "Assistant" +msgstr "" + +#: judge/models/course.py:123 +msgid "Teacher" +msgstr "" + +#: judge/models/course.py:152 +#, fuzzy +#| msgid "user profiles" +msgid "course files" +msgstr "thông tin người dùng" + #: judge/models/interface.py:24 msgid "configuration item" msgstr "" @@ -1394,7 +1486,7 @@ msgstr "đường dẫn" msgid "full name" msgstr "tên đầy đủ" -#: judge/models/problem.py:83 judge/models/profile.py:39 +#: judge/models/problem.py:83 judge/models/profile.py:43 #: judge/models/runtime.py:34 msgid "short name" msgstr "tên ngắn" @@ -1806,198 +1898,194 @@ msgstr "tên ngắn đường dẫn" msgid "Organization name shown in URL" msgstr "Tên được hiển thị trong đường dẫn" -#: judge/models/profile.py:40 +#: judge/models/profile.py:44 msgid "Displayed beside user name during contests" msgstr "Hiển thị bên cạnh tên người dùng trong kỳ thi" -#: judge/models/profile.py:42 -msgid "organization description" -msgstr "mô tả tổ chức" - -#: judge/models/profile.py:45 +#: judge/models/profile.py:49 msgid "registrant" msgstr "người tạo" -#: judge/models/profile.py:48 +#: judge/models/profile.py:52 msgid "User who registered this organization" msgstr "Người tạo tổ chức" -#: judge/models/profile.py:52 +#: judge/models/profile.py:56 msgid "administrators" msgstr "người quản lý" -#: judge/models/profile.py:54 +#: judge/models/profile.py:58 msgid "Those who can edit this organization" msgstr "Những người có thể chỉnh sửa tổ chức" -#: judge/models/profile.py:57 +#: judge/models/profile.py:61 msgid "creation date" msgstr "ngày tạo" -#: judge/models/profile.py:60 +#: judge/models/profile.py:64 msgid "is open organization?" msgstr "tổ chức mở?" -#: judge/models/profile.py:61 +#: judge/models/profile.py:65 msgid "Allow joining organization" msgstr "Cho phép mọi người tham gia tổ chức" -#: judge/models/profile.py:65 +#: judge/models/profile.py:69 msgid "maximum size" msgstr "số lượng thành viên tối đa" -#: judge/models/profile.py:69 +#: judge/models/profile.py:73 msgid "" "Maximum amount of users in this organization, only applicable to private " "organizations" msgstr "Số người tối đa trong tổ chức, chỉ áp dụng với tổ chức riêng tư" -#: judge/models/profile.py:75 +#: judge/models/profile.py:79 msgid "Student access code" msgstr "Mã truy cập cho học sinh" -#: judge/models/profile.py:86 +#: judge/models/profile.py:90 msgid "" "This image will replace the default site logo for users viewing the " "organization." msgstr "Ảnh này sẽ thay thế logo mặc định khi ở trong tổ chức." -#: judge/models/profile.py:125 judge/models/profile.py:154 -#: judge/models/profile.py:360 judge/models/profile.py:437 +#: judge/models/profile.py:129 judge/models/profile.py:158 +#: judge/models/profile.py:374 judge/models/profile.py:451 msgid "organization" msgstr "" -#: judge/models/profile.py:131 +#: judge/models/profile.py:135 msgid "user associated" msgstr "" -#: judge/models/profile.py:133 +#: judge/models/profile.py:137 msgid "self-description" msgstr "" -#: judge/models/profile.py:136 +#: judge/models/profile.py:140 msgid "location" msgstr "" -#: judge/models/profile.py:142 +#: judge/models/profile.py:146 msgid "preferred language" msgstr "" -#: judge/models/profile.py:150 +#: judge/models/profile.py:154 msgid "last access time" msgstr "" -#: judge/models/profile.py:151 +#: judge/models/profile.py:155 msgid "last IP" msgstr "" -#: judge/models/profile.py:162 +#: judge/models/profile.py:166 msgid "display rank" msgstr "" -#: judge/models/profile.py:170 +#: judge/models/profile.py:174 msgid "comment mute" msgstr "" -#: judge/models/profile.py:171 +#: judge/models/profile.py:175 msgid "Some users are at their best when silent." msgstr "" -#: judge/models/profile.py:175 +#: judge/models/profile.py:179 msgid "unlisted user" msgstr "" -#: judge/models/profile.py:176 +#: judge/models/profile.py:180 msgid "User will not be ranked." msgstr "" -#: judge/models/profile.py:180 +#: judge/models/profile.py:184 #, fuzzy #| msgid "Banned from joining" msgid "banned from voting" msgstr "Bị cấm tham gia" -#: judge/models/profile.py:181 +#: judge/models/profile.py:185 msgid "User will not be able to vote on problems' point values." msgstr "" -#: judge/models/profile.py:186 +#: judge/models/profile.py:190 msgid "user script" msgstr "" -#: judge/models/profile.py:190 +#: judge/models/profile.py:194 msgid "User-defined JavaScript for site customization." msgstr "" -#: judge/models/profile.py:194 +#: judge/models/profile.py:198 msgid "current contest" msgstr "kỳ thi hiện tại" -#: judge/models/profile.py:201 +#: judge/models/profile.py:205 msgid "math engine" msgstr "" -#: judge/models/profile.py:205 +#: judge/models/profile.py:209 msgid "the rendering engine used to render math" msgstr "" -#: judge/models/profile.py:208 +#: judge/models/profile.py:212 msgid "2FA enabled" msgstr "" -#: judge/models/profile.py:210 +#: judge/models/profile.py:214 msgid "check to enable TOTP-based two factor authentication" msgstr "đánh dấu để sử dụng TOTP-based two factor authentication" -#: judge/models/profile.py:216 +#: judge/models/profile.py:220 msgid "TOTP key" msgstr "mã TOTP" -#: judge/models/profile.py:217 +#: judge/models/profile.py:221 msgid "32 character base32-encoded key for TOTP" msgstr "" -#: judge/models/profile.py:219 +#: judge/models/profile.py:223 msgid "TOTP key must be empty or base32" msgstr "" -#: judge/models/profile.py:223 +#: judge/models/profile.py:227 msgid "internal notes" msgstr "ghi chú nội bộ" -#: judge/models/profile.py:226 +#: judge/models/profile.py:230 msgid "Notes for administrators regarding this user." msgstr "Ghi chú riêng cho quản trị viên." -#: judge/models/profile.py:347 +#: judge/models/profile.py:361 msgid "user profile" msgstr "thông tin người dùng" -#: judge/models/profile.py:348 +#: judge/models/profile.py:362 msgid "user profiles" msgstr "thông tin người dùng" -#: judge/models/profile.py:364 +#: judge/models/profile.py:378 msgid "request time" msgstr "thời gian đăng ký" -#: judge/models/profile.py:367 +#: judge/models/profile.py:381 msgid "state" msgstr "trạng thái" -#: judge/models/profile.py:374 +#: judge/models/profile.py:388 msgid "reason" msgstr "lý do" -#: judge/models/profile.py:377 +#: judge/models/profile.py:391 msgid "organization join request" msgstr "đơn đăng ký tham gia" -#: judge/models/profile.py:378 +#: judge/models/profile.py:392 msgid "organization join requests" msgstr "đơn đăng ký tham gia" -#: judge/models/profile.py:442 +#: judge/models/profile.py:456 #, fuzzy #| msgid "last seen" msgid "last visit" @@ -2169,7 +2257,7 @@ msgid "judge" msgstr "máy chấm" #: judge/models/submission.py:20 judge/models/submission.py:47 -#: judge/utils/problems.py:116 +#: judge/utils/problems.py:120 msgid "Accepted" msgstr "Accepted" @@ -2198,7 +2286,7 @@ msgid "Runtime Error" msgstr "Runtime Error" #: judge/models/submission.py:27 judge/models/submission.py:41 -#: judge/models/submission.py:55 judge/utils/problems.py:120 +#: judge/models/submission.py:55 judge/utils/problems.py:124 msgid "Compile Error" msgstr "Compile Error" @@ -2415,20 +2503,6 @@ msgstr "vote từ TNV" msgid "volunteer votes" msgstr "vote từ TNV" -#: judge/models/xmas.py:13 -#, fuzzy -#| msgid "Default" -msgid "default" -msgstr "Mặc định" - -#: judge/models/xmas.py:24 -msgid "use snowy background" -msgstr "" - -#: judge/models/xmas.py:31 -msgid "user changed username" -msgstr "" - #: judge/pdf_problems.py:161 judge/pdf_problems.py:221 #: judge/pdf_problems.py:294 msgid "Page [page] of [topage]" @@ -2503,19 +2577,19 @@ msgstr "" msgid "How did you corrupt the interactor path?" msgstr "How did you corrupt the custom checker path?" -#: judge/utils/problems.py:117 +#: judge/utils/problems.py:121 msgid "Wrong" msgstr "Sai" -#: judge/utils/problems.py:123 +#: judge/utils/problems.py:127 msgid "Timeout" msgstr "Quá thời gian" -#: judge/utils/problems.py:126 +#: judge/utils/problems.py:130 msgid "Error" msgstr "Lỗi" -#: judge/utils/problems.py:143 +#: judge/utils/problems.py:147 msgid "Can't pass both queryset and keyword filters" msgstr "" @@ -2555,7 +2629,7 @@ msgctxt "hours and minutes" msgid "%h:%m" msgstr "%h:%m" -#: judge/views/about.py:10 templates/organization/home.html:46 +#: judge/views/about.py:10 templates/organization/home.html:51 #: templates/organization/org-right-sidebar.html:70 #: templates/user/user-about.html:83 templates/user/user-tabs.html:4 #: templates/user/users-table.html:22 @@ -2566,16 +2640,16 @@ msgstr "Giới thiệu" msgid "Custom Checker Sample" msgstr "Hướng dẫn viết trình chấm" -#: judge/views/blog.py:117 +#: judge/views/blog.py:127 #, python-format msgid "Page %d of Posts" msgstr "Trang %d" -#: judge/views/blog.py:176 +#: judge/views/blog.py:186 msgid "Ticket feed" msgstr "Báo cáo" -#: judge/views/blog.py:194 +#: judge/views/blog.py:206 msgid "Comment feed" msgstr "Bình luận" @@ -2591,8 +2665,8 @@ msgstr "Bạn phải giải ít nhất 1 bài trước khi được vote." msgid "You already voted." msgstr "Bạn đã vote." -#: judge/views/comment.py:158 judge/views/organization.py:836 -#: judge/views/organization.py:982 judge/views/organization.py:1147 +#: judge/views/comment.py:158 judge/views/organization.py:850 +#: judge/views/organization.py:996 judge/views/organization.py:1161 msgid "Edited from site" msgstr "Chỉnh sửa từ web" @@ -2600,12 +2674,12 @@ msgstr "Chỉnh sửa từ web" msgid "Editing comment" msgstr "Chỉnh sửa bình luận" -#: judge/views/contests.py:119 judge/views/contests.py:368 -#: judge/views/contests.py:373 judge/views/contests.py:620 +#: judge/views/contests.py:119 judge/views/contests.py:385 +#: judge/views/contests.py:390 judge/views/contests.py:647 msgid "No such contest" msgstr "Không có contest nào như vậy" -#: judge/views/contests.py:120 judge/views/contests.py:369 +#: judge/views/contests.py:120 judge/views/contests.py:386 #, python-format msgid "Could not find a contest with the key \"%s\"." msgstr "Không tìm thấy kỳ thi với mã \"%s\"." @@ -2616,122 +2690,122 @@ msgstr "Không tìm thấy kỳ thi với mã \"%s\"." msgid "Contests" msgstr "Kỳ thi" -#: judge/views/contests.py:373 +#: judge/views/contests.py:390 msgid "Could not find such contest." msgstr "Không tìm thấy kỳ thi nào như vậy." -#: judge/views/contests.py:381 +#: judge/views/contests.py:398 #, python-format msgid "Access to contest \"%s\" denied" msgstr "Truy cập tới kỳ thi \"%s\" bị từ chối" -#: judge/views/contests.py:425 +#: judge/views/contests.py:452 msgid "Clone Contest" msgstr "Nhân bản kỳ thi" -#: judge/views/contests.py:494 +#: judge/views/contests.py:521 msgid "Contest not ongoing" msgstr "Kỳ thi đang không diễn ra" -#: judge/views/contests.py:495 +#: judge/views/contests.py:522 #, python-format msgid "\"%s\" is not currently ongoing." msgstr "\"%s\" kỳ thi đang không diễn ra." -#: judge/views/contests.py:502 +#: judge/views/contests.py:529 msgid "Already in contest" msgstr "Đã ở trong kỳ thi" -#: judge/views/contests.py:503 +#: judge/views/contests.py:530 #, python-format msgid "You are already in a contest: \"%s\"." msgstr "Bạn đã ở trong kỳ thi: \"%s\"." -#: judge/views/contests.py:513 +#: judge/views/contests.py:540 msgid "Banned from joining" msgstr "Bị cấm tham gia" -#: judge/views/contests.py:515 +#: judge/views/contests.py:542 msgid "" "You have been declared persona non grata for this contest. You are " "permanently barred from joining this contest." msgstr "Bạn không được phép tham gia kỳ thi này." -#: judge/views/contests.py:604 +#: judge/views/contests.py:631 #, python-format msgid "Enter access code for \"%s\"" msgstr "Nhập mật khẩu truy cập cho \"%s\"" -#: judge/views/contests.py:621 +#: judge/views/contests.py:648 #, python-format msgid "You are not in contest \"%s\"." msgstr "Bạn không ở trong kỳ thi \"%s\"." -#: judge/views/contests.py:644 +#: judge/views/contests.py:671 msgid "ContestCalendar requires integer year and month" msgstr "Lịch thi yêu cầu giá trị cho năm và tháng là số nguyên" -#: judge/views/contests.py:702 +#: judge/views/contests.py:729 #, python-format msgid "Contests in %(month)s" msgstr "Các kỳ thi trong %(month)s" -#: judge/views/contests.py:703 +#: judge/views/contests.py:730 msgid "F Y" msgstr "F Y" -#: judge/views/contests.py:763 +#: judge/views/contests.py:790 #, python-format msgid "%s Statistics" msgstr "%s Thống kê" -#: judge/views/contests.py:1046 +#: judge/views/contests.py:1082 #, python-format msgid "%s Rankings" msgstr "%s Bảng điểm" -#: judge/views/contests.py:1057 +#: judge/views/contests.py:1093 msgid "???" msgstr "???" -#: judge/views/contests.py:1085 +#: judge/views/contests.py:1120 #, python-format msgid "Your participation in %s" msgstr "Lần tham gia trong %s" -#: judge/views/contests.py:1086 +#: judge/views/contests.py:1121 #, python-format msgid "%s's participation in %s" msgstr "Lần tham gia của %s trong %s" -#: judge/views/contests.py:1100 +#: judge/views/contests.py:1135 msgid "Live" msgstr "Trực tiếp" -#: judge/views/contests.py:1119 templates/contest/contest-tabs.html:19 +#: judge/views/contests.py:1154 templates/contest/contest-tabs.html:19 msgid "Participation" msgstr "Lần tham gia" -#: judge/views/contests.py:1168 +#: judge/views/contests.py:1203 #, python-format msgid "%s MOSS Results" msgstr "%s Kết quả MOSS" -#: judge/views/contests.py:1204 +#: judge/views/contests.py:1239 #, python-format msgid "Running MOSS for %s..." msgstr "Đang chạy MOSS cho %s..." -#: judge/views/contests.py:1227 +#: judge/views/contests.py:1262 #, python-format msgid "Contest tag: %s" msgstr "Nhãn kỳ thi: %s" -#: judge/views/contests.py:1242 judge/views/ticket.py:72 +#: judge/views/contests.py:1277 judge/views/ticket.py:72 msgid "Issue description" msgstr "Mô tả vấn đề" -#: judge/views/contests.py:1289 +#: judge/views/contests.py:1320 #, python-format msgid "New clarification for %s" msgstr "Thông báo mới cho %s" @@ -2771,91 +2845,91 @@ msgstr "Runtimes" msgid "Notifications (%d unseen)" msgstr "Thông báo (%d chưa xem)" -#: judge/views/organization.py:143 judge/views/organization.py:149 +#: judge/views/organization.py:148 judge/views/organization.py:154 msgid "No such organization" msgstr "Không có tổ chức như vậy" -#: judge/views/organization.py:144 +#: judge/views/organization.py:149 #, python-format msgid "Could not find an organization with the key \"%s\"." msgstr "Không tìm thấy tổ chức với mã \"%s\"." -#: judge/views/organization.py:150 +#: judge/views/organization.py:155 msgid "Could not find such organization." msgstr "" -#: judge/views/organization.py:173 +#: judge/views/organization.py:178 msgid "Can't edit organization" msgstr "Không thể chỉnh sửa tổ chức" -#: judge/views/organization.py:174 +#: judge/views/organization.py:179 msgid "You are not allowed to edit this organization." msgstr "Bạn không được phép chỉnh sửa tổ chức này." -#: judge/views/organization.py:186 judge/views/organization.py:346 +#: judge/views/organization.py:191 judge/views/organization.py:359 #, fuzzy #| msgid "Can't edit organization" msgid "Can't access organization" msgstr "Không thể chỉnh sửa tổ chức" -#: judge/views/organization.py:187 judge/views/organization.py:347 +#: judge/views/organization.py:192 judge/views/organization.py:360 msgid "You are not allowed to access this organization." msgstr "Bạn không được phép chỉnh sửa tổ chức này." -#: judge/views/organization.py:245 judge/views/register.py:48 -#: judge/views/stats.py:184 templates/contest/list.html:85 -#: templates/problem/list-base.html:98 templates/stats/site.html:33 +#: judge/views/organization.py:250 judge/views/register.py:48 +#: judge/views/stats.py:184 templates/contest/list.html:89 +#: templates/problem/list-base.html:97 templates/stats/site.html:33 #: templates/user/user-left-sidebar.html:4 templates/user/user-list-tabs.html:6 msgid "Groups" msgstr "Nhóm" -#: judge/views/organization.py:353 +#: judge/views/organization.py:366 #, python-format msgid "%s Members" msgstr "%s Thành viên" -#: judge/views/organization.py:492 +#: judge/views/organization.py:506 #, python-brace-format msgid "All submissions in {0}" msgstr "Bài nộp trong {0}" -#: judge/views/organization.py:522 judge/views/organization.py:528 -#: judge/views/organization.py:535 +#: judge/views/organization.py:536 judge/views/organization.py:542 +#: judge/views/organization.py:549 msgid "Joining group" msgstr "Tham gia nhóm" -#: judge/views/organization.py:523 +#: judge/views/organization.py:537 msgid "You are already in the group." msgstr "Bạn đã ở trong nhóm." -#: judge/views/organization.py:528 +#: judge/views/organization.py:542 msgid "This group is not open." msgstr "Nhóm này là nhóm kín." -#: judge/views/organization.py:551 +#: judge/views/organization.py:565 msgid "Leaving group" msgstr "Rời nhóm" -#: judge/views/organization.py:552 +#: judge/views/organization.py:566 #, python-format msgid "You are not in \"%s\"." msgstr "Bạn không ở trong \"%s\"." -#: judge/views/organization.py:577 +#: judge/views/organization.py:591 #, python-format msgid "Request to join %s" msgstr "Đăng ký tham gia %s" -#: judge/views/organization.py:608 +#: judge/views/organization.py:622 msgid "Join request detail" msgstr "Chi tiết đơn đăng ký" -#: judge/views/organization.py:656 +#: judge/views/organization.py:670 #, python-format msgid "Managing join requests for %s" msgstr "Quản lý đơn đăng ký cho %s" -#: judge/views/organization.py:696 +#: judge/views/organization.py:710 #, python-format msgid "" "Your organization can only receive %d more members. You cannot approve %d " @@ -2864,154 +2938,154 @@ msgstr "" "Tổ chức chỉ có thể chứa %d thành viên. Bạn không thể chấp thuận nhiều hơn %d " "người." -#: judge/views/organization.py:714 +#: judge/views/organization.py:728 #, python-format msgid "Approved %d user." msgid_plural "Approved %d users." msgstr[0] "Đã chấp thuận %d người." -#: judge/views/organization.py:717 +#: judge/views/organization.py:731 #, python-format msgid "Rejected %d user." msgid_plural "Rejected %d users." msgstr[0] "Đã từ chối %d người." -#: judge/views/organization.py:757 +#: judge/views/organization.py:771 #, python-format msgid "Add member for %s" msgstr "Thêm thành viên cho %s" -#: judge/views/organization.py:769 +#: judge/views/organization.py:783 #, fuzzy #| msgid "Edited from site" msgid "Added members from site" msgstr "Chỉnh sửa từ web" -#: judge/views/organization.py:789 judge/views/organization.py:797 +#: judge/views/organization.py:803 judge/views/organization.py:811 msgid "Can't kick user" msgstr "Không thể đuổi" -#: judge/views/organization.py:790 +#: judge/views/organization.py:804 msgid "The user you are trying to kick does not exist!" msgstr "" -#: judge/views/organization.py:798 +#: judge/views/organization.py:812 #, python-format msgid "The user you are trying to kick is not in organization: %s." msgstr "" -#: judge/views/organization.py:819 judge/views/organization.py:971 +#: judge/views/organization.py:833 judge/views/organization.py:985 #, fuzzy, python-format #| msgid "Editing %s" msgid "Edit %s" msgstr "Đang chỉnh sửa %s" -#: judge/views/organization.py:847 templates/organization/list.html:45 +#: judge/views/organization.py:861 templates/organization/list.html:45 msgid "Create group" msgstr "Tạo nhóm" -#: judge/views/organization.py:862 +#: judge/views/organization.py:876 msgid "Exceeded limit" msgstr "" -#: judge/views/organization.py:863 +#: judge/views/organization.py:877 #, python-format msgid "You created too many groups. You can only create at most %d groups" msgstr "" -#: judge/views/organization.py:868 judge/views/organization.py:893 -#: judge/views/organization.py:1037 +#: judge/views/organization.py:882 judge/views/organization.py:907 +#: judge/views/organization.py:1051 msgid "Added from site" msgstr "Thêm từ web" -#: judge/views/organization.py:884 +#: judge/views/organization.py:898 #: templates/organization/org-right-sidebar.html:55 msgid "Add contest" msgstr "Thêm kỳ thi" -#: judge/views/organization.py:927 judge/views/organization.py:1089 +#: judge/views/organization.py:941 judge/views/organization.py:1103 msgid "Permission denied" msgstr "Truy cập bị từ chối" -#: judge/views/organization.py:928 +#: judge/views/organization.py:942 #, fuzzy #| msgid "You are not allowed to edit this organization." msgid "You are not allowed to edit this contest" msgstr "Bạn không được phép chỉnh sửa tổ chức này." -#: judge/views/organization.py:1026 +#: judge/views/organization.py:1040 #, python-format msgid "Add blog for %s" msgstr "Thêm bài đăng cho %s" -#: judge/views/organization.py:1090 +#: judge/views/organization.py:1104 msgid "Not allowed to edit this blog" msgstr "Bạn không được phép chỉnh sửa bài đăng này." -#: judge/views/organization.py:1122 +#: judge/views/organization.py:1136 #, python-format msgid "Edit blog %s" msgstr "Chỉnh sửa %s" -#: judge/views/organization.py:1173 +#: judge/views/organization.py:1187 #, python-format msgid "Pending blogs in %s" msgstr "Bài đang đợi duyệt trong %s" -#: judge/views/problem.py:123 +#: judge/views/problem.py:132 msgid "No such problem" msgstr "Không có bài nào như vậy" -#: judge/views/problem.py:124 +#: judge/views/problem.py:133 #, python-format msgid "Could not find a problem with the code \"%s\"." msgstr "Không tìm thấy bài tập với mã bài \"%s\"." -#: judge/views/problem.py:188 +#: judge/views/problem.py:200 #, python-brace-format msgid "Editorial for {0}" msgstr "Hướng dẫn cho {0}" -#: judge/views/problem.py:192 +#: judge/views/problem.py:204 #, python-brace-format msgid "Editorial for {0}" msgstr "Hướng dẫn cho {0}" -#: judge/views/problem.py:447 templates/contest/contest.html:90 +#: judge/views/problem.py:463 templates/contest/contest.html:96 #: templates/organization/org-left-sidebar.html:4 #: templates/user/user-about.html:28 templates/user/user-bookmarks.html:34 #: templates/user/user-tabs.html:5 templates/user/users-table.html:19 msgid "Problems" msgstr "Bài tập" -#: judge/views/problem.py:827 +#: judge/views/problem.py:837 msgid "Problem feed" msgstr "Bài tập" -#: judge/views/problem.py:1065 +#: judge/views/problem.py:1075 msgid "Banned from submitting" msgstr "Bị cấm nộp bài" -#: judge/views/problem.py:1067 +#: judge/views/problem.py:1077 msgid "" "You have been declared persona non grata for this problem. You are " "permanently barred from submitting this problem." msgstr "Bạn đã bị cấm nộp bài này." -#: judge/views/problem.py:1090 +#: judge/views/problem.py:1100 msgid "Too many submissions" msgstr "Quá nhiều lần nộp" -#: judge/views/problem.py:1092 +#: judge/views/problem.py:1102 msgid "You have exceeded the submission limit for this problem." msgstr "Bạn đã vượt quá số lần nộp cho bài này." -#: judge/views/problem.py:1171 judge/views/problem.py:1176 +#: judge/views/problem.py:1181 judge/views/problem.py:1186 #, python-format msgid "Submit to %(problem)s" msgstr "Nộp bài cho %(problem)s" -#: judge/views/problem.py:1199 +#: judge/views/problem.py:1209 msgid "Clone Problem" msgstr "Nhân bản bài tập" @@ -3152,7 +3226,7 @@ msgstr "Thống kê ngôn ngữ" msgid "Submissions" msgstr "Bài nộp" -#: judge/views/stats.py:160 templates/blog/list.html:45 +#: judge/views/stats.py:160 templates/blog/list.html:44 #: templates/comments/list.html:4 templates/stats/site.html:39 msgid "Comments" msgstr "Bình luận" @@ -3169,7 +3243,7 @@ msgstr "Tin nhắn mới" msgid "Site statistics" msgstr "Thống kê" -#: judge/views/status.py:26 templates/submission/list.html:333 +#: judge/views/status.py:26 templates/submission/list.html:338 msgid "Status" msgstr "Kết quả chấm" @@ -3182,44 +3256,48 @@ msgstr "Ma trận phiên bản" msgid "Submission of %(problem)s by %(user)s" msgstr "Bài nộp của %(user)s cho bài %(problem)s" -#: judge/views/submission.py:298 judge/views/submission.py:299 -#: templates/problem/problem.html:173 +#: judge/views/submission.py:321 judge/views/submission.py:322 +#: templates/problem/problem.html:168 msgid "All submissions" msgstr "Tất cả bài nộp" -#: judge/views/submission.py:518 +#: judge/views/submission.py:571 judge/views/submission.py:576 msgid "All my submissions" msgstr "Tất cả bài nộp của tôi" -#: judge/views/submission.py:519 +#: judge/views/submission.py:572 #, python-format msgid "All submissions by %s" msgstr "Tất cả bài nộp của %s" -#: judge/views/submission.py:564 +#: judge/views/submission.py:578 +msgid "All submissions by {0}" +msgstr "Tất cả bài nộp của {0}" + +#: judge/views/submission.py:617 #, python-format msgid "All submissions for %s" msgstr "Tất cả bài nộp cho %s" -#: judge/views/submission.py:592 +#: judge/views/submission.py:645 msgid "Must pass a problem" msgstr "Phải làm được một bài" -#: judge/views/submission.py:650 +#: judge/views/submission.py:703 #, python-format msgid "My submissions for %(problem)s" msgstr "Bài nộp của tôi cho %(problem)s" -#: judge/views/submission.py:651 +#: judge/views/submission.py:704 #, python-format msgid "%(user)s's submissions for %(problem)s" msgstr "Các bài nộp của %(user)s cho %(problem)s" -#: judge/views/submission.py:781 +#: judge/views/submission.py:841 msgid "Must pass a contest" msgstr "Phải qua một kỳ thi" -#: judge/views/submission.py:811 +#: judge/views/submission.py:871 #, python-brace-format msgid "" "{0}'s submissions for {2} in {0} cho {2} trong {4}" -#: judge/views/submission.py:823 +#: judge/views/submission.py:883 #, python-brace-format msgid "" "{0}'s submissions for problem {2} in {3}" @@ -3237,7 +3315,7 @@ msgstr "" "Các bài nộp của {0} cho bài {2} trong {3}" "" -#: judge/views/submission.py:961 +#: judge/views/submission.py:1020 #, fuzzy #| msgid "You do not have the permission to rejudge submissions." msgid "You don't have permission to access." @@ -3328,7 +3406,7 @@ msgid "Updated on site" msgstr "Được cập nhật trên web" #: judge/views/user.py:431 templates/admin/auth/user/change_form.html:14 -#: templates/admin/auth/user/change_form.html:17 templates/base.html:281 +#: templates/admin/auth/user/change_form.html:17 templates/base.html:282 #: templates/user/user-tabs.html:11 msgid "Edit profile" msgstr "Chỉnh sửa thông tin" @@ -3338,7 +3416,7 @@ msgstr "Chỉnh sửa thông tin" msgid "Leaderboard" msgstr "Xếp hạng" -#: judge/views/user.py:541 +#: judge/views/user.py:543 msgid "Import Users" msgstr "" @@ -3496,19 +3574,19 @@ msgstr "Đổi chủ đề màu sắc" msgid "Like" msgstr "Thích" -#: templates/actionbar/list.html:23 +#: templates/actionbar/list.html:25 msgid "Comment" msgstr "Bình luận" -#: templates/actionbar/list.html:33 templates/user/user-tabs.html:10 +#: templates/actionbar/list.html:35 templates/user/user-tabs.html:10 msgid "Bookmark" msgstr "Lưu" -#: templates/actionbar/list.html:39 +#: templates/actionbar/list.html:42 msgid "Share" msgstr "Chia sẻ" -#: templates/actionbar/list.html:46 +#: templates/actionbar/list.html:49 msgid "Report" msgstr "Báo cáo" @@ -3560,167 +3638,180 @@ msgstr "Chỉnh sửa thông tin" msgid "Rejudge" msgstr "Chấm lại" -#: templates/base.html:226 templates/chat/chat.html:580 +#: templates/base.html:227 templates/chat/chat.html:581 msgid "Chat" msgstr "Chat" -#: templates/base.html:236 +#: templates/base.html:237 msgid "Notification" msgstr "Thông báo" -#: templates/base.html:257 +#: templates/base.html:258 msgid "Dark Mode" msgstr "" -#: templates/base.html:273 +#: templates/base.html:274 msgid "Profile" msgstr "Trang cá nhân" -#: templates/base.html:275 templates/chat/chat.html:20 +#: templates/base.html:276 templates/chat/chat.html:21 #: templates/comments/list.html:125 templates/contest/contest-list-tabs.html:4 #: templates/contest/ranking-table.html:49 templates/internal/base.html:59 #: templates/organization/org-left-sidebar.html:12 -#: templates/problem/left-sidebar.html:5 templates/problem/list-base.html:255 +#: templates/problem/left-sidebar.html:6 #: templates/problem/problem-list-tabs.html:6 -#: templates/submission/info-base.html:12 templates/submission/list.html:386 +#: templates/submission/info-base.html:12 templates/submission/list.html:392 #: templates/submission/submission-list-tabs.html:15 msgid "Admin" msgstr "Admin" -#: templates/base.html:278 +#: templates/base.html:279 msgid "Internal" msgstr "Nội bộ" -#: templates/base.html:279 +#: templates/base.html:280 msgid "Stats" msgstr "Thống kê" -#: templates/base.html:286 +#: templates/base.html:287 msgid "Log out" msgstr "Đăng xuất" -#: templates/base.html:295 +#: templates/base.html:296 #: templates/registration/password_reset_complete.html:4 msgid "Log in" msgstr "Đăng nhập" -#: templates/base.html:296 templates/registration/registration_form.html:72 +#: templates/base.html:297 templates/registration/registration_form.html:72 msgid "or" msgstr "hoặc" -#: templates/base.html:297 +#: templates/base.html:298 msgid "Sign up" msgstr "Đăng ký" -#: templates/base.html:310 +#: templates/base.html:311 msgid "spectating" msgstr "đang theo dõi" -#: templates/base.html:322 +#: templates/base.html:323 msgid "Compete" msgstr "Thi" -#: templates/base.html:324 +#: templates/base.html:325 msgid "General" msgstr "Chung" -#: templates/base.html:331 +#: templates/base.html:332 msgid "This site works best with JavaScript enabled." msgstr "" -#: templates/blog/blog.html:29 +#: templates/blog/blog.html:28 #, python-format msgid " posted on %(time)s" msgstr "đã đăng vào %(time)s" -#: templates/blog/blog.html:32 templates/comments/list.html:104 -#: templates/comments/list.html:119 templates/contest/contest-tabs.html:33 -#: templates/contest/list.html:120 templates/contest/tag-title.html:9 +#: templates/blog/blog.html:31 templates/comments/list.html:104 +#: templates/comments/list.html:119 templates/contest/contest-tabs.html:35 +#: templates/contest/list.html:124 templates/contest/tag-title.html:9 #: templates/flatpages/admin_link.html:3 templates/license.html:10 -#: templates/problem/editorial.html:16 templates/problem/feed.html:78 +#: templates/problem/editorial.html:15 templates/problem/feed.html:76 msgid "Edit" msgstr "Chỉnh sửa" -#: templates/blog/blog.html:35 +#: templates/blog/blog.html:35 templates/contest/contest.html:88 msgid "Edit in" msgstr "Chỉnh sửa trong" +#: templates/blog/content.html:45 templates/comments/feed.html:17 +#: templates/problem/feed.html:135 templates/ticket/feed.html:24 +msgid "...More" +msgstr "...Xem thêm" + #: templates/blog/dashboard.html:21 -#, python-format +#, fuzzy, python-format +#| msgid "" +#| "\n" +#| " on %(time)s\n" +#| " " msgid "" "\n" -" on %(time)s\n" -" " +" on %(time)s\n" +" " msgstr "" "\n" " vào %(time)s\n" " " -#: templates/blog/list.html:44 +#: templates/blog/list.html:43 msgid "News" msgstr "Tin tức" -#: templates/blog/list.html:46 +#: templates/blog/list.html:45 msgid "Tickets" msgstr "Báo cáo" -#: templates/blog/list.html:47 +#: templates/blog/list.html:46 msgid "Events" msgstr "Sự kiện" -#: templates/blog/list.html:63 +#: templates/blog/list.html:62 msgid "You have no ticket" msgstr "Bạn không có báo cáo" -#: templates/blog/list.html:79 templates/problem/list.html:143 -#: templates/problem/problem.html:399 +#: templates/blog/list.html:78 templates/problem/list.html:150 +#: templates/problem/problem.html:383 msgid "Clarifications" msgstr "Thông báo" -#: templates/blog/list.html:85 +#: templates/blog/list.html:84 msgid "Add" msgstr "Thêm mới" -#: templates/blog/list.html:105 templates/problem/list.html:165 -#: templates/problem/problem.html:410 +#: templates/blog/list.html:104 templates/problem/list.html:172 +#: templates/problem/problem.html:394 msgid "No clarifications have been made at this time." msgstr "Không có thông báo nào." -#: templates/chat/chat.html:18 +#: templates/chat/chat.html:5 templates/chat/chat.html:555 +msgid "Chat Box" +msgstr "Chat Box" + +#: templates/chat/chat.html:19 msgid "Recent" msgstr "Gần đây" -#: templates/chat/chat.html:19 +#: templates/chat/chat.html:20 msgid "Following" msgstr "Bạn bè" -#: templates/chat/chat.html:21 +#: templates/chat/chat.html:22 msgid "Other" msgstr "Thành viên khác" -#: templates/chat/chat.html:171 +#: templates/chat/chat.html:172 msgid "New message(s)" msgstr "Tin nhắn mới" -#: templates/chat/chat.html:520 templates/chat/chat.html:602 +#: templates/chat/chat.html:521 templates/chat/chat.html:603 #: templates/user/base-users-js.html:10 #: templates/user/base-users-two-col.html:19 msgid "Search by handle..." msgstr "Tìm kiếm theo tên..." -#: templates/chat/chat.html:582 templates/chat/chat.html:589 +#: templates/chat/chat.html:583 templates/chat/chat.html:590 msgid "Online Users" msgstr "Trực tuyến" -#: templates/chat/chat.html:590 +#: templates/chat/chat.html:591 msgid "Refresh" msgstr "Làm mới" -#: templates/chat/chat.html:623 +#: templates/chat/chat.html:624 msgid "Emoji" msgstr "" -#: templates/chat/chat.html:624 +#: templates/chat/chat.html:625 msgid "Enter your message" msgstr "Nhập tin nhắn" @@ -3804,25 +3895,17 @@ msgstr "Ẩn" #: templates/comments/list.html:137 #, python-format msgid "" -"\n" -" This comment is hidden due " -"to too much negative feedback.\n" -" Click here to view it.\n" -" " +"This comment is hidden due to too much negative feedback. Click here to view it." msgstr "" -"\n" -" Bình luận bị ẩn vì có quá " -"nhiều phản hồi tiêu cực.\n" -" Nhấn vào đây để xem.\n" -" " +"Bình luận bị ẩn vì nhiều phản hồi tiêu cực. Nhấp vào đây để mở." -#: templates/comments/list.html:157 +#: templates/comments/list.html:154 msgid "There are no comments at the moment." msgstr "Không có bình luận nào." -#: templates/comments/list.html:162 +#: templates/comments/list.html:159 msgid "Comments are disabled on this page." msgstr "Bình luận bị tắt trong trang này." @@ -3891,7 +3974,7 @@ msgstr "Thứ sáu" msgid "Saturday" msgstr "Thứ bảy" -#: templates/contest/clarification.html:52 templates/contest/list.html:219 +#: templates/contest/clarification.html:52 templates/contest/list.html:229 #: templates/organization/new.html:10 templates/ticket/new.html:38 msgid "Create" msgstr "Tạo mới" @@ -3970,7 +4053,7 @@ msgid "at" msgstr "lúc" #: templates/contest/contest-list-tabs.html:2 -#: templates/problem/left-sidebar.html:4 templates/problem/list-base.html:254 +#: templates/problem/left-sidebar.html:4 #: templates/problem/problem-list-tabs.html:5 msgid "List" msgstr "Danh sách" @@ -3983,7 +4066,7 @@ msgstr "Lịch" msgid "Info" msgstr "Thông tin" -#: templates/contest/contest-tabs.html:12 templates/submission/list.html:358 +#: templates/contest/contest-tabs.html:12 templates/submission/list.html:363 msgid "Statistics" msgstr "Thống kê" @@ -3995,62 +4078,62 @@ msgstr "Bảng xếp hạng" msgid "Hidden Rankings" msgstr "Bảng xếp hạng ẩn" -#: templates/contest/contest-tabs.html:27 +#: templates/contest/contest-tabs.html:29 msgid "Final rankings" msgstr "BXH chung cuộc" -#: templates/contest/contest-tabs.html:31 +#: templates/contest/contest-tabs.html:33 msgid "MOSS" msgstr "MOSS" -#: templates/contest/contest-tabs.html:36 +#: templates/contest/contest-tabs.html:38 msgid "Clone" msgstr "Nhân bản" -#: templates/contest/contest.html:36 templates/contest/contest.html:56 +#: templates/contest/contest.html:35 templates/contest/contest.html:55 msgid "Leave contest" msgstr "Rời kỳ thi" -#: templates/contest/contest.html:43 templates/contest/list.html:389 +#: templates/contest/contest.html:42 templates/contest/list.html:399 msgid "Virtual join" msgstr "Tham gia ảo" -#: templates/contest/contest.html:54 +#: templates/contest/contest.html:53 msgid "Stop spectating" msgstr "Ngừng theo dõi" -#: templates/contest/contest.html:62 +#: templates/contest/contest.html:61 msgid "Spectate contest" msgstr "Theo dõi kỳ thi" -#: templates/contest/contest.html:68 +#: templates/contest/contest.html:67 msgid "Join contest" msgstr "Tham gia kỳ thi" -#: templates/contest/contest.html:77 +#: templates/contest/contest.html:76 msgid "Login to participate" msgstr "Đăng nhập để tham gia" -#: templates/contest/contest.html:96 +#: templates/contest/contest.html:102 msgid "AC Rate" msgstr "Tỷ lệ AC" -#: templates/contest/contest.html:97 templates/contest/list.html:232 -#: templates/contest/list.html:281 templates/contest/list.html:366 -#: templates/problem/list.html:21 +#: templates/contest/contest.html:103 templates/contest/list.html:242 +#: templates/contest/list.html:291 templates/contest/list.html:376 +#: templates/problem/list.html:24 msgid "Users" msgstr "Người nộp" -#: templates/contest/contest.html:122 templates/problem/list.html:54 -#: templates/problem/list.html:126 +#: templates/contest/contest.html:128 templates/problem/list.html:58 +#: templates/problem/list.html:133 msgid "Editorial" msgstr "Hướng dẫn" -#: templates/contest/list.html:81 templates/contest/media-js.html:152 +#: templates/contest/list.html:85 templates/contest/media-js.html:152 msgid "Are you sure you want to join?" msgstr "Bạn có chắc tham gia?" -#: templates/contest/list.html:82 +#: templates/contest/list.html:86 msgid "" "Joining a contest for the first time starts your timer, after which it " "becomes unstoppable." @@ -4058,76 +4141,80 @@ msgstr "" "Tham gia kỳ thi lần đầu sẽ kích hoạt thời gian đếm ngược, không thể dừng lại " "sau đó." -#: templates/contest/list.html:114 +#: templates/contest/list.html:118 msgid "hidden" msgstr "ẩn" -#: templates/contest/list.html:126 +#: templates/contest/list.html:130 msgid "private" msgstr "riêng tư" -#: templates/contest/list.html:142 +#: templates/contest/list.html:146 msgid "rated" msgstr "rated" -#: templates/contest/list.html:168 +#: templates/contest/list.html:172 #, python-format msgid "%(time_limit)s window" msgstr "Cửa sổ thi dài %(time_limit)s" -#: templates/contest/list.html:170 +#: templates/contest/list.html:174 #, python-format msgid "%(duration)s long" msgstr "Kéo dài %(duration)s" -#: templates/contest/list.html:190 +#: templates/contest/list.html:194 msgid "Spectate" msgstr "Theo dõi" -#: templates/contest/list.html:196 templates/organization/home.html:24 +#: templates/contest/list.html:200 templates/organization/home.html:29 msgid "Join" msgstr "Tham gia" -#: templates/contest/list.html:207 +#: templates/contest/list.html:211 msgid "Search contests..." msgstr "Tìm kiếm kỳ thi..." -#: templates/contest/list.html:217 +#: templates/contest/list.html:221 msgid "Search" msgstr "Tìm kiếm" #: templates/contest/list.html:225 +msgid "Hide organization contests" +msgstr "Ẩn các kỳ thi riêng tư của nhóm" + +#: templates/contest/list.html:235 msgid "Active Contests" msgstr "Kỳ thi bạn đang tham gia" -#: templates/contest/list.html:231 templates/contest/list.html:280 -#: templates/contest/list.html:323 templates/contest/list.html:363 +#: templates/contest/list.html:241 templates/contest/list.html:290 +#: templates/contest/list.html:333 templates/contest/list.html:373 msgid "Contest" msgstr "Kỳ thi" -#: templates/contest/list.html:249 +#: templates/contest/list.html:259 #, python-format msgid "Window ends in %(countdown)s" msgstr "Cửa số thi còn %(countdown)s" -#: templates/contest/list.html:252 templates/contest/list.html:296 +#: templates/contest/list.html:262 templates/contest/list.html:306 #, python-format msgid "Ends in %(countdown)s" msgstr "Kết thúc trong %(countdown)s" -#: templates/contest/list.html:274 +#: templates/contest/list.html:284 msgid "Ongoing Contests" msgstr "Kỳ thi đang diễn ra" -#: templates/contest/list.html:316 +#: templates/contest/list.html:326 msgid "Upcoming Contests" msgstr "Kỳ thi sắp tới" -#: templates/contest/list.html:347 +#: templates/contest/list.html:357 msgid "There are no scheduled contests at this time." msgstr "Không có kỳ thi nào được lên lịch hiện tại." -#: templates/contest/list.html:353 +#: templates/contest/list.html:363 msgid "Past Contests" msgstr "Kỳ thi trong quá khứ" @@ -4315,7 +4402,7 @@ msgstr "" msgid "Thinking" msgstr "Bảng xếp hạng" -#: templates/internal/base.html:82 templates/problem/feed.html:124 +#: templates/internal/base.html:82 templates/problem/feed.html:123 #, fuzzy #| msgid "Feed" msgid "Feedback" @@ -4371,7 +4458,11 @@ msgstr "Bạn phải tham gia lại để được hiển thị trong bảng x msgid "You will have to request membership in order to join again." msgstr "Bạn phải đăng ký thành viên để được tham gia lại." -#: templates/organization/home.html:28 +#: templates/organization/home.html:18 +msgid "Subdomain" +msgstr "Site riêng cho nhóm" + +#: templates/organization/home.html:33 msgid "Request membership" msgstr "Đăng ký thành viên" @@ -4440,26 +4531,26 @@ msgid "Reason:" msgstr "Lý do:" #: templates/organization/requests/log.html:11 -#: templates/organization/requests/pending.html:14 +#: templates/organization/requests/pending.html:21 msgid "State" msgstr "Trạng thái" #: templates/organization/requests/log.html:12 -#: templates/organization/requests/pending.html:15 +#: templates/organization/requests/pending.html:22 msgid "Reason" msgstr "Lý do" #: templates/organization/requests/log.html:28 -#: templates/organization/requests/pending.html:37 +#: templates/organization/requests/pending.html:44 msgid "There are no requests to approve." msgstr "Không có đơn đăng ký." -#: templates/organization/requests/pending.html:17 +#: templates/organization/requests/pending.html:24 #: templates/problem/data.html:520 msgid "Delete?" msgstr "Xóa?" -#: templates/organization/requests/pending.html:34 +#: templates/organization/requests/pending.html:41 #: templates/ticket/edit-notes.html:4 msgid "Update" msgstr "Cập nhật" @@ -4508,7 +4599,7 @@ msgstr "Xem YAML" msgid "Autofill testcases" msgstr "Tự động điền test" -#: templates/problem/data.html:490 templates/problem/problem.html:281 +#: templates/problem/data.html:490 templates/problem/problem.html:276 msgid "Problem type" msgid_plural "Problem types" msgstr[0] "Dạng bài" @@ -4553,7 +4644,7 @@ msgstr "Pretest?" msgid "Add new case" msgstr "Thêm test mới" -#: templates/problem/editorial.html:24 +#: templates/problem/editorial.html:23 msgid "" "Remember to use this editorial only when stuck, and not to copy-" "paste code from it. Please be respectful to the problem author and " @@ -4564,47 +4655,47 @@ msgstr "" "viết hướng dẫn này.

Chép code từ bài hướng dẫn để nộp bài là " "hành vi có thể dẫn đến khóa tài khoản." -#: templates/problem/feed.html:9 +#: templates/problem/feed.html:10 msgid "FOR YOU" msgstr "DÀNH CHO BẠN" -#: templates/problem/feed.html:12 +#: templates/problem/feed.html:13 msgid "NEW" msgstr "MỚI NHẤT" -#: templates/problem/feed.html:16 +#: templates/problem/feed.html:18 msgid "VOLUNTEER" msgstr "TÌNH NGUYỆN" -#: templates/problem/feed.html:22 +#: templates/problem/feed.html:24 msgid "View your votes" msgstr "Xem các đơn đã điền của bạn" -#: templates/problem/feed.html:73 +#: templates/problem/feed.html:69 msgid "View source" msgstr "Xem mã nguồn" -#: templates/problem/feed.html:76 +#: templates/problem/feed.html:73 msgid "Volunteer form" msgstr "Phiếu tình nguyện" -#: templates/problem/feed.html:80 +#: templates/problem/feed.html:79 msgid "Submit" msgstr "Gửi" -#: templates/problem/feed.html:87 +#: templates/problem/feed.html:86 msgid "Value" msgstr "Giá trị" -#: templates/problem/feed.html:94 +#: templates/problem/feed.html:93 msgid "Knowledge point" msgstr "Độ khó kiến thức" -#: templates/problem/feed.html:102 +#: templates/problem/feed.html:101 msgid "Thinking point" msgstr "Độ khó nghĩ" -#: templates/problem/feed.html:110 templates/problem/search-form.html:84 +#: templates/problem/feed.html:109 templates/problem/search-form.html:84 msgid "Problem types" msgstr "Dạng bài" @@ -4612,41 +4703,41 @@ msgstr "Dạng bài" msgid "Any additional note here" msgstr "Lưu ý thêm cho admin" -#: templates/problem/left-sidebar.html:3 templates/problem/list-base.html:253 +#: templates/problem/left-sidebar.html:3 msgid "Feed" msgstr "Gợi ý" -#: templates/problem/list-base.html:96 +#: templates/problem/list-base.html:95 msgid "Filter by type..." msgstr "Lọc theo dạng..." -#: templates/problem/list-base.html:159 templates/problem/list-base.html:185 +#: templates/problem/list-base.html:158 templates/problem/list-base.html:184 msgid "Add types..." msgstr "Thêm dạng" -#: templates/problem/list-base.html:201 +#: templates/problem/list-base.html:200 msgid "Fail to vote!" msgstr "Hệ thống lỗi!" -#: templates/problem/list-base.html:204 +#: templates/problem/list-base.html:203 msgid "Successful vote! Thank you!" msgstr "Đã gửi thành công! Cảm ơn bạn!" -#: templates/problem/list.html:36 templates/problem/search-form.html:68 +#: templates/problem/list.html:40 templates/problem/search-form.html:68 #: templates/user/user-problems.html:57 msgid "Category" msgstr "Nhóm bài" -#: templates/problem/list.html:47 +#: templates/problem/list.html:51 #, python-format msgid "AC %%" msgstr "AC %%" -#: templates/problem/list.html:50 +#: templates/problem/list.html:54 msgid "AC #" msgstr "" -#: templates/problem/list.html:138 +#: templates/problem/list.html:145 msgid "Add clarifications" msgstr "Thêm thông báo" @@ -4681,7 +4772,7 @@ msgid "" msgstr "Bạn chuẩn bị {action} vài bài nộp. Tiếp tục?" #: templates/problem/manage_submission.html:126 -#: templates/submission/list.html:329 +#: templates/submission/list.html:334 msgid "Filter submissions" msgstr "Lọc bài nộp" @@ -4730,136 +4821,128 @@ msgstr "Bạn có chắc muốn tính điểm lại %(count)d bài nộp?" msgid "Rescore all submissions" msgstr "Tính điểm lại các bài nộp" -#: templates/problem/problem.html:138 +#: templates/problem/problem.html:133 msgid "View as PDF" msgstr "Xem PDF" -#: templates/problem/problem.html:147 templates/problem/problem.html:157 -#: templates/problem/problem.html:162 +#: templates/problem/problem.html:142 templates/problem/problem.html:152 +#: templates/problem/problem.html:157 msgid "Submit solution" msgstr "Nộp bài" -#: templates/problem/problem.html:150 +#: templates/problem/problem.html:145 #, python-format msgid "%(counter)s submission left" msgid_plural "%(counter)s submissions left" msgstr[0] "Còn %(counter)s lần nộp" -#: templates/problem/problem.html:158 +#: templates/problem/problem.html:153 msgid "0 submissions left" msgstr "Còn 0 lần nộp" -#: templates/problem/problem.html:170 +#: templates/problem/problem.html:165 msgid "My submissions" msgstr "Bài nộp của tôi" -#: templates/problem/problem.html:174 +#: templates/problem/problem.html:169 msgid "Best submissions" msgstr "Các bài nộp tốt nhất" -#: templates/problem/problem.html:178 +#: templates/problem/problem.html:173 msgid "Read editorial" msgstr "Xem hướng dẫn" -#: templates/problem/problem.html:183 +#: templates/problem/problem.html:178 msgid "Manage tickets" msgstr "Xử lý báo cáo" -#: templates/problem/problem.html:187 +#: templates/problem/problem.html:182 msgid "Edit problem" msgstr "Chỉnh sửa bài" -#: templates/problem/problem.html:189 +#: templates/problem/problem.html:184 msgid "Edit test data" msgstr "Chỉnh sửa test" -#: templates/problem/problem.html:194 +#: templates/problem/problem.html:189 msgid "My tickets" msgstr "Báo cáo của tôi" -#: templates/problem/problem.html:202 +#: templates/problem/problem.html:197 msgid "Manage submissions" msgstr "Quản lý bài nộp" -#: templates/problem/problem.html:208 +#: templates/problem/problem.html:203 msgid "Clone problem" msgstr "Nhân bản bài" -#: templates/problem/problem.html:215 +#: templates/problem/problem.html:210 msgid "Points:" msgstr "Điểm:" -#: templates/problem/problem.html:218 templates/problem/problem.html:220 +#: templates/problem/problem.html:213 templates/problem/problem.html:215 msgid "(partial)" msgstr "(thành phần)" -#: templates/problem/problem.html:225 +#: templates/problem/problem.html:220 msgid "Time limit:" msgstr "Thời gian:" -#: templates/problem/problem.html:237 +#: templates/problem/problem.html:232 msgid "Memory limit:" msgstr "Bộ nhớ:" -#: templates/problem/problem.html:249 templates/problem/raw.html:67 -#: templates/submission/status-testcases.html:151 +#: templates/problem/problem.html:244 templates/problem/raw.html:67 +#: templates/submission/status-testcases.html:160 msgid "Input:" msgstr "Input:" -#: templates/problem/problem.html:251 templates/problem/raw.html:67 +#: templates/problem/problem.html:246 templates/problem/raw.html:67 msgid "stdin" msgstr "bàn phím" -#: templates/problem/problem.html:255 templates/problem/raw.html:70 -#: templates/submission/status-testcases.html:155 +#: templates/problem/problem.html:250 templates/problem/raw.html:70 +#: templates/submission/status-testcases.html:164 msgid "Output:" msgstr "Output:" -#: templates/problem/problem.html:256 templates/problem/raw.html:70 +#: templates/problem/problem.html:251 templates/problem/raw.html:70 msgid "stdout" msgstr "màn hình" -#: templates/problem/problem.html:266 +#: templates/problem/problem.html:261 msgid "Author:" msgid_plural "Authors:" msgstr[0] "Tác giả:" -#: templates/problem/problem.html:294 +#: templates/problem/problem.html:289 msgid "Allowed languages" msgstr "Ngôn ngữ cho phép" -#: templates/problem/problem.html:302 +#: templates/problem/problem.html:297 #, python-format msgid "No %(lang)s judge online" msgstr "Không có máy chấm cho %(lang)s" -#: templates/problem/problem.html:314 +#: templates/problem/problem.html:309 #: templates/status/judge-status-table.html:2 msgid "Judge" msgid_plural "Judges" msgstr[0] "Máy chấm" -#: templates/problem/problem.html:332 +#: templates/problem/problem.html:327 msgid "none available" msgstr "Bài này chưa có máy chấm" -#: templates/problem/problem.html:344 +#: templates/problem/problem.html:339 #, python-format msgid "This problem has %(length)s clarification(s)" msgstr "Bài này có %(length)s thông báo" -#: templates/problem/problem.html:374 +#: templates/problem/problem.html:369 msgid "Request clarification" msgstr "Yêu cầu làm rõ đề" -#: templates/problem/problem.html:387 -msgid "View comments" -msgstr "Xem bình luận" - -#: templates/problem/problem.html:389 -msgid "Be the first to comment" -msgstr "Bình luận đầu tiên" - #: templates/problem/raw.html:73 msgid "Time Limit:" msgstr "Giới hạn thời gian:" @@ -4872,6 +4955,10 @@ msgstr "Giới hạn bộ nhớ:" msgid "Last unsolved" msgstr "Nộp gần đây" +#: templates/problem/related_problems.html:3 +msgid "Recommended problems" +msgstr "Bài tập gợi ý" + #: templates/problem/search-form.html:2 msgid "Problem search" msgstr "Tìm kiếm bài tập" @@ -4905,7 +4992,7 @@ msgid "Group" msgstr "" #: templates/problem/search-form.html:71 templates/problem/search-form.html:73 -#: templates/submission/list.html:375 +#: templates/submission/list.html:381 #: templates/submission/submission-list-tabs.html:4 msgid "All" msgstr "Tất cả" @@ -4914,7 +5001,7 @@ msgstr "Tất cả" msgid "Point range" msgstr "Mốc điểm" -#: templates/problem/search-form.html:101 templates/submission/list.html:351 +#: templates/problem/search-form.html:101 templates/submission/list.html:356 #: templates/ticket/list.html:248 msgid "Go" msgstr "Lọc" @@ -4937,15 +5024,23 @@ msgstr "" "không được sử dụng trong bài này." #: templates/problem/submit.html:183 -#, python-format +#, fuzzy, python-format +#| msgid "" +#| "\n" +#| " You have %(left)s submission left\n" +#| " " +#| msgid_plural "" +#| "\n" +#| " You have %(left)s submissions left\n" +#| " " msgid "" "\n" -" You have %(left)s submission left\n" -" " +" You have %(left)s submission left\n" +" " msgid_plural "" "\n" -" You have %(left)s submissions left\n" -" " +" You have %(left)s submissions left\n" +" " msgstr[0] "" "\n" " Bạn còn %(left)s lần nộp\n" @@ -5212,47 +5307,47 @@ msgstr "Lỗi hệ thống xảy ra trong quá trình chấm." msgid "Error information" msgstr "Thông tin lỗi" -#: templates/submission/list.html:76 +#: templates/submission/list.html:78 msgid "Filter by status..." msgstr "Lọc theo kết quả..." -#: templates/submission/list.html:82 +#: templates/submission/list.html:84 msgid "Filter by language..." msgstr "Lọc theo ngôn ngữ..." -#: templates/submission/list.html:306 +#: templates/submission/list.html:310 msgid "You were disconnected. Refresh to show latest updates." msgstr "Bạn bị ngắt kết nối. Hãy làm mới để xem cập nhật mới nhất." -#: templates/submission/list.html:364 +#: templates/submission/list.html:369 msgid "Total:" msgstr "Tổng:" -#: templates/submission/list.html:377 +#: templates/submission/list.html:383 #: templates/submission/submission-list-tabs.html:6 msgid "Mine" msgstr "Tôi" -#: templates/submission/list.html:380 +#: templates/submission/list.html:386 #: templates/submission/submission-list-tabs.html:9 msgid "Best" msgstr "Tốt nhất" -#: templates/submission/list.html:383 +#: templates/submission/list.html:389 #, fuzzy, python-format #| msgid "user" msgid "%(user)s" msgstr "người dùng" -#: templates/submission/row.html:49 +#: templates/submission/row.html:65 msgid "view" msgstr "xem" -#: templates/submission/row.html:53 +#: templates/submission/row.html:69 msgid "rejudge" msgstr "chấm lại" -#: templates/submission/row.html:58 +#: templates/submission/row.html:74 msgid "admin" msgstr "admin" @@ -5292,59 +5387,60 @@ msgstr "Kết quả chấm Pretest" msgid "Execution Results" msgstr "Kết quả chấm" -#: templates/submission/status-testcases.html:31 -#: templates/submission/status-testcases.html:89 +#: templates/submission/status-testcases.html:32 +#: templates/submission/status-testcases.html:47 +#: templates/submission/status-testcases.html:98 msgid "Batch " msgstr "Nhóm " -#: templates/submission/status-testcases.html:36 -#: templates/submission/status-testcases.html:38 -#: templates/submission/status-testcases.html:115 +#: templates/submission/status-testcases.html:37 +#: templates/submission/status-testcases.html:39 +#: templates/submission/status-testcases.html:124 msgid "Case" msgstr "Test" -#: templates/submission/status-testcases.html:50 +#: templates/submission/status-testcases.html:58 msgid "Overall: " msgstr "Tổng cộng: " -#: templates/submission/status-testcases.html:64 +#: templates/submission/status-testcases.html:72 msgid "Point: " msgstr "Điểm: " -#: templates/submission/status-testcases.html:69 +#: templates/submission/status-testcases.html:77 msgid "Time: " msgstr "Thời gian: " -#: templates/submission/status-testcases.html:78 +#: templates/submission/status-testcases.html:86 msgid "Memory: " msgstr "Bộ nhớ: " -#: templates/submission/status-testcases.html:100 -#: templates/submission/status-testcases.html:129 +#: templates/submission/status-testcases.html:109 +#: templates/submission/status-testcases.html:138 msgid "Point" msgstr "Điểm" -#: templates/submission/status-testcases.html:117 +#: templates/submission/status-testcases.html:126 msgid "Pretest" msgstr "Pretest" -#: templates/submission/status-testcases.html:119 +#: templates/submission/status-testcases.html:128 msgid "Test case" msgstr "Test" -#: templates/submission/status-testcases.html:159 +#: templates/submission/status-testcases.html:168 msgid "Answer:" msgstr "Kết quả:" -#: templates/submission/status-testcases.html:164 +#: templates/submission/status-testcases.html:173 msgid "Judge feedback:" msgstr "Phản hồi từ máy chấm:" -#: templates/submission/status-testcases.html:185 +#: templates/submission/status-testcases.html:195 msgid "Passing pretests does not guarantee a full score on system tests." msgstr "AC pretest không đồng nghĩa AC cả bài nhé :))" -#: templates/submission/status-testcases.html:188 +#: templates/submission/status-testcases.html:198 msgid "Submission aborted!" msgstr "Đã hủy chấm bài nộp!" @@ -5369,15 +5465,15 @@ msgstr "Các bài nộp của" msgid "Subtask" msgstr "Subtask" -#: templates/submission/user-ajax.html:29 +#: templates/submission/user-ajax.html:33 msgid "Total" msgstr "Tổng điểm" -#: templates/submission/user-ajax.html:47 +#: templates/submission/user-ajax.html:51 msgid "g:i a d/m/Y" msgstr "" -#: templates/submission/user-ajax.html:47 +#: templates/submission/user-ajax.html:51 #, fuzzy, python-format #| msgid "" #| "\n" @@ -5385,18 +5481,18 @@ msgstr "" #| " " msgid "" "\n" -" %(time)s\n" -" " +" %(time)s\n" +" " msgstr "" "\n" " vào %(time)s\n" " " -#: templates/submission/user-ajax.html:56 +#: templates/submission/user-ajax.html:60 msgid "pretests" msgstr "pretests" -#: templates/submission/user-ajax.html:58 +#: templates/submission/user-ajax.html:62 msgid "main tests" msgstr "test chính thức" @@ -5554,8 +5650,8 @@ msgstr "Tổ chức" #, python-format msgid "" "\n" -" weighted %(weight)s%%\n" -" " +" weighted %(weight)s%%\n" +" " msgstr "" #: templates/user/pp-row.html:27 @@ -5749,6 +5845,26 @@ msgstr "Thông tin" msgid "Check all" msgstr "Chọn tất cả" +#~ msgid "View comments" +#~ msgstr "Xem bình luận" + +#~ msgid "Be the first to comment" +#~ msgstr "Bình luận đầu tiên" + +#, fuzzy +#~| msgid "Default" +#~ msgid "default" +#~ msgstr "Mặc định" + +#~ msgid "" +#~ "\n" +#~ " on %(time)s\n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ " vào %(time)s\n" +#~ " " + #~ msgid "Subscribe to contest updates" #~ msgstr "Đăng ký để nhận thông báo về các kỳ thi" diff --git a/requirements.txt b/requirements.txt index 5000705..6cd4d1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django>=3.2,<4 +Django>=3.2.17,<4 django_compressor>=3 django-mptt>=0.13 django-pagedown @@ -41,4 +41,5 @@ markdown bleach pymdown-extensions mdx-breakless-lists -beautifulsoup4 \ No newline at end of file +beautifulsoup4 +pre-commit \ No newline at end of file diff --git a/resources/base.scss b/resources/base.scss index f0a8f60..7cfa09a 100644 --- a/resources/base.scss +++ b/resources/base.scss @@ -36,7 +36,7 @@ img { } * { - -webkit-tap-highlight-color: transparent; + -webkit-tap-highlight-color: transparent; } .full { @@ -327,7 +327,7 @@ nav { a { color: black !important; } - + a, button { padding: 8px 20px 8px 8px !important; font-size: 0.8em; @@ -377,7 +377,7 @@ hr { } #content { - margin: 4.5em auto 1em auto; + margin: 3.2em auto 1em auto; // Header width: 90%; @@ -558,7 +558,6 @@ noscript #noscript { margin: 0 auto; border-right: 1px solid $border_gray; border-left: 1px solid $border_gray; - background: white; } // border-bottom: 1px solid rgb(204, 204, 204) @@ -595,7 +594,7 @@ math { } } -@media (max-width: 760px) { +@media (max-width: 799px) { #navigation { height: 36px; } @@ -859,17 +858,22 @@ select { } } -@media (min-width: 800px) { +@media(min-width: 800px) { .anon { padding-right: 1em; } .navbar-icons { margin-top: 6px; } -} - -@media (min-width: 800px) { + #page-container { + background: #f1f2f2; + } #event-tab { display: none; } -} + #content.wrapper { + background: white; + padding: 2em; + border-radius: 1em; + } +} \ No newline at end of file diff --git a/resources/blog.scss b/resources/blog.scss index e327686..9162f74 100644 --- a/resources/blog.scss +++ b/resources/blog.scss @@ -32,7 +32,8 @@ } } -.blog-sidebar, .right-sidebar { +.blog-sidebar, +.right-sidebar { width: 100%; margin-left: auto; } @@ -41,6 +42,7 @@ color: green; font-weight: bold; background-color: lightgreen; + .sidebar-icon { color: green; } @@ -143,6 +145,7 @@ background-color: white; margin-left: auto; margin-right: auto; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); .title { margin-bottom: 0.2em; @@ -150,24 +153,33 @@ } } +.blog-box:hover { + border-color: #8a8a8a; +} + .blog-description { max-height: 30em; overflow: hidden; overflow-wrap: anywhere; padding-bottom: 1em; clear: both; + position: relative; } + .problem-feed-name { display: inline; font-weight: bold; } + .problem-feed-name a { color: #0645ad; } + .problem-feed-info-entry { display: inline; float: right; } + .problem-feed-types { color: gray; } @@ -175,24 +187,30 @@ .left-sidebar-item { display: flex; align-items: center; + .sidebar-icon { font-size: large; display: inline-block; + i { width: 1.4em; } } } + .left-sidebar-item:hover { background-color: #e3e3e3; cursor: pointer; } + .left-sidebar-item.active:hover { background-color: lightgreen; } + .sidebar-icon { color: black; } + .left-sidebar-header { text-align: center; padding-bottom: 1em; @@ -200,15 +218,36 @@ color: black; border-radius: 0; } + .feed-table { margin: 0; } + .pre-expand-blog { - background-image: -webkit-linear-gradient(bottom, gray, lightgray 3%, transparent 8%, transparent 100%); + position: relative; 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 { @@ -219,40 +258,59 @@ } } +.middle-right-content.wrapper { + padding: 1em 0; + background: white; + border-radius: 1em; +} + @media (max-width: 799px) { + .actionbar-box { + margin: 8px 0; + } + .left-sidebar-header { display: none; } + .left-sidebar-item { padding: 0.8em 0.2em 0.8em 0.2em; display: inline-block; flex: 1; border-radius: 8px; - + .sidebar-icon { display: none; } } + .left-sidebar { text-align: center; margin-bottom: 1em; border-radius: 7px; display: flex; } + .blog-box { padding-left: 5%; padding-right: 5%; + margin-bottom: 0; } + .post-title { font-size: 2em; } } + @media (min-width: 800px) { .left-sidebar-item { padding: 0.8em 0.2em 0.8em 1em; } - .middle-content, .blog-sidebar, .right-sidebar { + + .middle-content, + .blog-sidebar, + .right-sidebar { display: block !important; } @@ -274,20 +332,22 @@ width: -webkit-fill-available; } - .blog-sidebar, .right-sidebar { + .blog-sidebar, + .right-sidebar { width: 25%; } .left-sidebar { width: 11%; + max-width: 11%; + min-width: 11%; position: fixed; height: 100%; margin-top: -4em; padding-top: 4em; - } - - .left-sidebar-item { - border-radius: 0 0.5em 0.5em 0; + border-right: 1px solid lightgray; + box-shadow: 0px -10px 2px 0px rgb(0 0 0 / 50%); + background: white; } .feed-table { @@ -297,7 +357,7 @@ .blog-box { border-left: 1.4px solid lightgray; border-right: 1.4px solid lightgray; - border-radius: 8px; + border-radius: 16px; } .post-full { diff --git a/resources/chatbox.scss b/resources/chatbox.scss index 979a239..a3d50d9 100644 --- a/resources/chatbox.scss +++ b/resources/chatbox.scss @@ -1,3 +1,6 @@ +.chat { + background: white; +} #chat-log p { margin: 0; padding-top: 0.1em; @@ -101,7 +104,7 @@ margin-bottom: 1.5px; } .info-circle { - position: absolute; + position: absolute; cx: 86%; cy: 80%; r: 6px; @@ -146,8 +149,8 @@ display: inline-flex; } .status-circle { - position: absolute; - bottom: 0; + position: absolute; + bottom: 0; right: 0; cx: 18px; cy: 18px; diff --git a/resources/comments.scss b/resources/comments.scss index 47620f0..54118e7 100644 --- a/resources/comments.scss +++ b/resources/comments.scss @@ -277,4 +277,10 @@ a { .bookmarked { color: rgb(180, 180, 7); } +} + +@media (max-width: 799px) { + .hide_texts_on_mobile .actionbar-text { + display: none; + } } \ No newline at end of file diff --git a/resources/common.js b/resources/common.js index 18c71cf..488fb33 100644 --- a/resources/common.js +++ b/resources/common.js @@ -359,6 +359,32 @@ function onWindowReady() { $('#form-lang').submit(); }) $('#logout').on('click', () => $('#logout-form').submit()); + + var copyButton; + $('pre code').each(function () { + $(this).parent().before($('
', {'class': 'copy-clipboard'}) + .append(copyButton = $('', { + '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() { diff --git a/resources/darkmode.css b/resources/darkmode.css index d65f63b..8b97bfa 100644 --- a/resources/darkmode.css +++ b/resources/darkmode.css @@ -1686,8 +1686,6 @@ noscript #noscript { #page-container { border-right-color: rgb(62, 68, 70); border-left-color: rgb(62, 68, 70); - background-image: initial; - background-color: rgb(24, 26, 27); } .MathJax:focus { outline-color: initial; @@ -1698,7 +1696,7 @@ noscript #noscript { border-right-color: initial; } } -@media (max-width: 760px) { +@media (max-width: 799px) { #navicon { color: rgb(129, 175, 255); } @@ -1795,6 +1793,16 @@ noscript #noscript { .background-footer { color: rgb(152, 143, 129); } +@media (min-width: 800px) { + #page-container { + background-image: initial; + background-color: rgb(32, 34, 36); + } + #content.wrapper { + background-image: initial; + background-color: rgb(24, 26, 27); + } +} .table { background-image: initial; background-color: rgba(0, 0, 0, 0.01); @@ -1803,6 +1811,10 @@ noscript #noscript { background-image: initial; background-color: rgb(29, 31, 32); } +.table.striped tr:nth-child(2n+1) { + background-image: initial; + background-color: rgb(24, 26, 27); +} .table td:first-child { border-color: rgb(62, 68, 70); } @@ -1918,6 +1930,10 @@ noscript #noscript { border-bottom-color: rgb(60, 65, 68); border-top-color: rgb(60, 65, 68); background-color: rgb(24, 26, 27); + box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 5px; +} +.blog-box:hover { + border-color: rgb(81, 88, 91); } .problem-feed-name a { color: rgb(102, 177, 250); @@ -1938,17 +1954,23 @@ noscript #noscript { border-bottom-color: rgb(140, 130, 115); color: rgb(232, 230, 227); } -.pre-expand-blog { - background-image: -webkit-linear-gradient(bottom, - rgb(96, 104, 108), - rgb(49, 53, 55) 3%, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0) 100%); +.show-more { + color: rgb(232, 230, 227); + background-image: linear-gradient(rgba(0, 0, 0, 0), + rgb(24, 26, 27)); + background-color: initial; } -.pre-expand-blog:hover { - background-color: rgb(31, 33, 35); +.middle-right-content.wrapper { + background-image: initial; + background-color: rgb(24, 26, 27); } @media (min-width: 800px) { + .left-sidebar { + border-right-color: rgb(60, 65, 68); + box-shadow: rgba(0, 0, 0, 0.5) 0px -10px 2px 0px; + background-image: initial; + background-color: rgb(24, 26, 27); + } .blog-box { border-left-color: rgb(60, 65, 68); border-right-color: rgb(60, 65, 68); @@ -2575,10 +2597,15 @@ a.close:hover { border-bottom-color: rgb(62, 68, 70); border-left-color: rgb(62, 68, 70); border-top-color: initial; + background-image: initial; + background-color: rgb(24, 26, 27); } .sidebox-content.sidebox-table { border-color: initial; } +.sidebox { + box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 5px; +} .ws-closed { background-image: initial; background-color: rgb(139, 0, 0); @@ -2737,7 +2764,7 @@ a.voted { } #submissions-table { background-image: initial; - background-color: rgba(0, 0, 0, 0.01); + background-color: rgb(24, 26, 27); } .submission-row { border-left-color: rgb(62, 68, 70); @@ -2976,6 +3003,10 @@ a.voted { background-color: rgba(204, 0, 0, 0.8); color: rgb(232, 230, 227); } +.chat { + background-image: initial; + background-color: rgb(24, 26, 27); +} #emoji-button { color: rgb(204, 200, 193); } @@ -3785,1417 +3816,17 @@ code .il { } mjx-merror { color: rgb(255, 26, 26); - background-color: rgb(153, 153, 0); + background-color: rgb(153, 153, 0); } mjx-assistive-mml { - border-color: initial !important; + border-color: initial !important; } -:root, [data-md-color-scheme="default"] { - --darkreader-bg--md-default-fg-color: rgba(0, 0, 0, 0.87); - --darkreader-text--md-default-fg-color: rgba(232, 230, 227, 0.87); - --darkreader-bg--md-default-fg-color--light: rgba(0, 0, 0, 0.54); - --darkreader-text--md-default-fg-color--light: rgba(232, 230, 227, 0.54); - --darkreader-border--md-default-fg-color--light: rgba(140, 130, 115, 0.54); - --darkreader-bg--md-default-fg-color--lighter: rgba(0, 0, 0, 0.32); - --darkreader-text--md-default-fg-color--lighter: rgba(232, 230, 227, 0.32); - --darkreader-border--md-default-fg-color--lighter: rgba(140, 130, 115, 0.32); - --darkreader-bg--md-default-fg-color--lightest: rgba(0, 0, 0, 0.07); - --darkreader-text--md-default-fg-color--lightest: rgba(232, 230, 227, 0.07); - --darkreader-border--md-default-fg-color--lightest: rgba(140, 130, 115, 0.07); - --darkreader-bg--md-default-bg-color: #181a1b; - --darkreader-text--md-default-bg-color: #e8e6e3; - --darkreader-border--md-default-bg-color: #303436; - --md-default-bg-color--light: hsla(0,0%,100%,0.7); - --md-default-bg-color--lighter: hsla(0,0%,100%,0.3); - --md-default-bg-color--lightest: hsla(0,0%,100%,0.12); - --darkreader-bg--md-primary-fg-color: #334191; - --darkreader-text--md-primary-fg-color: #6d94cb; - --darkreader-border--md-primary-fg-color: #2f3c86; - --md-primary-fg-color--light: #5d6cc0; - --darkreader-bg--md-primary-fg-color--dark: #263281; - --darkreader-text--md-primary-bg-color: #e8e6e3; - --darkreader-text--md-primary-bg-color--light: rgba(232, 230, 227, 0.7); - --darkreader-bg--md-accent-fg-color: #01189b; - --darkreader-text--md-accent-fg-color: #539bfe; - --darkreader-border--md-accent-fg-color: #011899; - --darkreader-bg--md-accent-fg-color--transparent: rgba(1, 24, 155, 0.1); - --darkreader-bg--md-accent-bg-color: #181a1b; - --darkreader-text--md-accent-bg-color: #e8e6e3; - --md-accent-bg-color--light: hsla(0,0%,100%,0.7); - --darkreader-text--md-code-fg-color: #beb9b0; - --darkreader-bg--md-code-bg-color: #1e2021; - --darkreader-bg--md-code-hl-color: rgba(153, 153, 0, 0.5); - --darkreader-text--md-code-hl-number-color: #d93f3f; - --darkreader-text--md-code-hl-special-color: #ed3774; - --darkreader-text--md-code-hl-function-color: #b159c0; - --darkreader-text--md-code-hl-constant-color: #7561db; - --darkreader-text--md-code-hl-keyword-color: #518ecb; - --darkreader-text--md-code-hl-string-color: #7ee2b0; - --darkreader-text--md-code-hl-name-color: var(--darkreader-text--md-code-fg-color); - --darkreader-text--md-code-hl-operator-color: var(--darkreader-text--md-default-fg-color--light); - --darkreader-text--md-code-hl-punctuation-color: var(--darkreader-text--md-default-fg-color--light); - --darkreader-text--md-code-hl-comment-color: var(--darkreader-text--md-default-fg-color--light); - --darkreader-text--md-code-hl-generic-color: var(--darkreader-text--md-default-fg-color--light); - --darkreader-text--md-code-hl-variable-color: var(--darkreader-text--md-default-fg-color--light); - --md-typeset-color: var(--md-default-fg-color); - --darkreader-text--md-typeset-a-color: var(--darkreader-text--md-primary-fg-color); - --darkreader-bg--md-typeset-mark-color: rgba(153, 153, 0, 0.5); - --darkreader-bg--md-typeset-del-color: rgba(165, 25, 9, 0.15); - --darkreader-bg--md-typeset-ins-color: rgba(9, 170, 90, 0.15); - --darkreader-bg--md-typeset-kbd-color: #1b1d1e; - --darkreader-bg--md-typeset-kbd-accent-color: #181a1b; - --darkreader-bg--md-typeset-kbd-border-color: #404548; - --darkreader-border--md-typeset-table-color: rgba(140, 130, 115, 0.12); - --darkreader-text--md-admonition-fg-color: var(--darkreader-text--md-default-fg-color); - --darkreader-bg--md-admonition-bg-color: var(--darkreader-bg--md-default-bg-color); - --darkreader-text--md-footer-fg-color: #e8e6e3; - --darkreader-text--md-footer-fg-color--light: rgba(232, 230, 227, 0.7); - --darkreader-text--md-footer-fg-color--lighter: rgba(232, 230, 227, 0.3); - --darkreader-bg--md-footer-bg-color: rgba(0, 0, 0, 0.87); - --darkreader-bg--md-footer-bg-color--dark: rgba(0, 0, 0, 0.32); - --darkreader-bg--md-shadow-z1: 0 0.2rem 0.5rem rgba(0,0,0,0.05), - 0 0 0.05rem rgba(0,0,0,0.1); - --darkreader-bg--md-shadow-z2: 0 0.2rem 0.5rem rgba(0,0,0,0.1), - 0 0 0.05rem rgba(0,0,0,0.25); - --darkreader-bg--md-shadow-z3: 0 0.2rem 0.5rem rgba(0,0,0,0.2), - 0 0 0.05rem rgba(0,0,0,0.35); +mjx-stretchy-v > mjx-ext { + border-color: transparent; } -.md-icon svg { - fill: currentcolor; -} -body { - --md-text-font-family: var(--md-text-font,_), - -apple-system, - BlinkMacSystemFont, - Helvetica, - Arial, - sans-serif; - --md-code-font-family: var(--md-code-font,_), - SFMono-Regular, - Consolas, - Menlo, - monospace; -} -:root { - --darkreader-bgimg--md-typeset-table-sort-icon: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-typeset-table-sort-icon--asc: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-typeset-table-sort-icon--desc: url("data:image/svg+xml;charset=utf-8,"); -} -.md-typeset h1 { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-typeset h5, -.md-typeset h6 { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-typeset hr { - border-bottom: 0.05rem solid var(--darkreader-border--md-default-fg-color--lightest); -} -.md-typeset code, -.md-typeset kbd, -.md-typeset pre { - color: var(--darkreader-text--md-code-fg-color); -} -.md-typeset kbd { - background-color: var(--darkreader-bg--md-typeset-kbd-color); - box-shadow: 0 0.1rem 0 0.05rem var(--darkreader-bg--md-typeset-kbd-border-color), - 0 0.1rem 0 var(--darkreader-bg--md-typeset-kbd-border-color), - 0 -0.1rem 0.2rem var(--darkreader-bg--md-typeset-kbd-accent-color) inset; - color: var(--darkreader-text--md-default-fg-color); -} -.md-typeset mark { - background-color: var(--darkreader-bg--md-typeset-mark-color); - color: inherit; -} -.md-typeset abbr { - text-decoration-color: initial; - border-bottom: 0.05rem dotted var(--darkreader-border--md-default-fg-color--light); -} -@media (hover: none) { - .md-typeset abbr[title]:-webkit-any(:focus, :hover)::after { - background-color: var(--darkreader-bg--md-default-fg-color); - box-shadow: var(--darkreader-bg--md-shadow-z3); - color: var(--darkreader-text--md-default-bg-color); - } - .md-typeset abbr[title]:is(:focus, :hover)::after { - background-color: var(--darkreader-bg--md-default-fg-color); - box-shadow: var(--darkreader-bg--md-shadow-z3); - color: var(--darkreader-text--md-default-bg-color); - } -} -.md-typeset blockquote { - border-left: 0.2rem solid var(--darkreader-border--md-default-fg-color--lighter) ; -} -[dir="rtl"] .md-typeset blockquote { - border-right: 0.2rem solid var(--darkreader-border--md-default-fg-color--lighter) ; -} -.md-typeset blockquote { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-typeset table:not([class]) { - background-color: var(--darkreader-bg--md-default-bg-color); - border: 0.05rem solid var(--darkreader-border--md-typeset-table-color); -} -.md-typeset table:not([class]) td { - border-top: 0.05rem solid var(--darkreader-border--md-typeset-table-color); -} -.md-typeset table:not([class]) tbody tr:hover { - background-color: rgba(0, 0, 0, 0.04); - box-shadow: 0 0.05rem 0 var(--darkreader-bg--md-default-bg-color) inset; -} -.md-typeset table th[role="columnheader"]:hover::after { - background-color: var(--darkreader-bg--md-default-fg-color--lighter); -} -.md-typeset table th[role="columnheader"][aria-sort="ascending"]::after { - background-color: var(--darkreader-bg--md-default-fg-color--light); -} -.md-typeset table th[role="columnheader"][aria-sort="descending"]::after { - background-color: var(--darkreader-bg--md-default-fg-color--light); -} -.md-banner { - background-color: var(--darkreader-bg--md-footer-bg-color); - color: var(--darkreader-text--md-footer-fg-color); -} -.md-banner--warning { - color: var(--darkreader-text--md-default-fg-color); - background: var(--darkreader-bg--md-typeset-mark-color); -} -.md-banner__button { - color: inherit; -} -.md-option.focus-visible + label { - outline-color: var(--darkreader-border--md-accent-fg-color); -} -.md-skip { - background-color: var(--darkreader-bg--md-default-fg-color); - color: var(--darkreader-text--md-default-bg-color); - outline-color: var(--darkreader-border--md-accent-fg-color); -} -:root { - --darkreader-bgimg--md-clipboard-icon: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjE1MCI+PGRlZnM+PGZpbHRlciBpZD0iZGFya3JlYWRlci1pbWFnZS1maWx0ZXIiPjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwLjI0OSAtMC42MTQgLTAuNjcyIDAuMDAwIDEuMDM1IC0wLjY0NiAwLjI4OCAtMC42NjQgMC4wMDAgMS4wMjAgLTAuNjM2IC0wLjYwOSAwLjI1MCAwLjAwMCAwLjk5NCAwLjAwMCAwLjAwMCAwLjAwMCAxLjAwMCAwLjAwMCIgLz48L2ZpbHRlcj48L2RlZnM+PGltYWdlIHdpZHRoPSIxNTAiIGhlaWdodD0iMTUwIiBmaWx0ZXI9InVybCgjZGFya3JlYWRlci1pbWFnZS1maWx0ZXIpIiB4bGluazpocmVmPSJkYXRhOmltYWdlL3N2Zyt4bWw7Y2hhcnNldD11dGYtOCw8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTkgMjFIOFY3aDExbTAtMkg4YTIgMiAwIDAgMC0yIDJ2MTRhMiAyIDAgMCAwIDIgMmgxMWEyIDIgMCAwIDAgMi0yVjdhMiAyIDAgMCAwLTItMm0tMy00SDRhMiAyIDAgMCAwLTIgMnYxNGgyVjNoMTJWMVoiLz48L3N2Zz4iIC8+PC9zdmc+"); -} -.md-clipboard { - color: var(--darkreader-text--md-default-fg-color--lightest); - outline-color: var(--darkreader-border--md-accent-fg-color); -} -.md-clipboard:not(.focus-visible) { - -webkit-tap-highlight-color: transparent; - outline-color: initial; -} -:hover > .md-clipboard { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-clipboard:-webkit-any(:focus, :hover) { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-clipboard:is(:focus, :hover) { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-clipboard::after { - background-color: currentcolor; -} -.md-clipboard--inline:-webkit-any(:focus, :hover) code { - background-color: var(--darkreader-bg--md-accent-fg-color--transparent); - color: var(--darkreader-text--md-accent-fg-color); -} -.md-clipboard--inline:is(:focus, :hover) code { - background-color: var(--darkreader-bg--md-accent-fg-color--transparent); - color: var(--darkreader-text--md-accent-fg-color); -} -.md-consent__overlay { - background-color: rgba(0, 0, 0, 0.54); -} -.md-consent__inner { - background-color: var(--darkreader-bg--md-default-bg-color); - border-color: initial; - box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 0.2rem, - rgba(0, 0, 0, 0.2) 0px 0.2rem 0.4rem; -} -.md-typeset .md-content__button { - color: var(--darkreader-text--md-default-fg-color--lighter); -} -.md-dialog { - background-color: var(--darkreader-bg--md-default-fg-color); - box-shadow: var(--darkreader-bg--md-shadow-z3); -} -.md-dialog__inner { - color: var(--darkreader-text--md-default-bg-color); -} -.md-feedback fieldset { - border-color: initial; -} -.md-feedback__list:hover .md-icon:not(:disabled) { - color: var(--darkreader-text--md-default-fg-color--lighter); -} -.md-feedback__icon { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-feedback__icon:not(:disabled).md-icon:hover { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-feedback__icon:disabled { - color: var(--darkreader-text--md-default-fg-color--lightest); -} -.md-footer { - background-color: var(--darkreader-bg--md-footer-bg-color); - color: var(--darkreader-text--md-footer-fg-color); -} -.md-footer__link { - outline-color: var(--darkreader-border--md-accent-fg-color); -} -.md-footer-meta { - background-color: var(--darkreader-bg--md-footer-bg-color--dark); -} -html .md-footer-meta.md-typeset a { - color: var(--darkreader-text--md-footer-fg-color--light); -} -html .md-footer-meta.md-typeset a:-webkit-any(:focus, :hover) { - color: var(--darkreader-text--md-footer-fg-color); -} -html .md-footer-meta.md-typeset a:is(:focus, :hover) { - color: var(--darkreader-text--md-footer-fg-color); -} -.md-copyright { - color: var(--darkreader-text--md-footer-fg-color--lighter); -} -.md-copyright__highlight { - color: var(--darkreader-text--md-footer-fg-color--light); -} -.md-social__link svg { - fill: currentcolor; -} -.md-typeset .md-button { - border-color: initial; - color: var(--darkreader-text--md-primary-fg-color); -} -.md-typeset .md-button--primary { - background-color: var(--darkreader-bg--md-primary-fg-color); - color: var(--darkreader-text--md-primary-bg-color); - border-color: var(--darkreader-border--md-primary-fg-color); -} -.md-typeset .md-button:-webkit-any(:focus, :hover) { - background-color: var(--darkreader-bg--md-accent-fg-color); - color: var(--darkreader-text--md-accent-bg-color); - border-color: var(--darkreader-border--md-accent-fg-color); -} -.md-typeset .md-button:is(:focus, :hover) { - background-color: var(--darkreader-bg--md-accent-fg-color); - color: var(--darkreader-text--md-accent-bg-color); - border-color: var(--darkreader-border--md-accent-fg-color); -} -.md-typeset .md-input { - box-shadow: var(--darkreader-bg--md-shadow-z1); - border-bottom: 0.1rem solid var(--darkreader-border--md-default-fg-color--lighter); -} -.md-typeset .md-input:-webkit-any(:focus, :hover) { - border-bottom-color: var(--darkreader-border--md-accent-fg-color); - box-shadow: var(--darkreader-bg--md-shadow-z2); -} -.md-typeset .md-input:is(:focus, :hover) { - border-bottom-color: var(--darkreader-border--md-accent-fg-color); - box-shadow: var(--darkreader-bg--md-shadow-z2); -} -.md-header { - background-color: var(--darkreader-bg--md-primary-fg-color); - box-shadow: rgba(0, 0, 0, 0) 0px 0px 0.2rem, - rgba(0, 0, 0, 0) 0px 0.2rem 0.4rem; - color: var(--darkreader-text--md-primary-bg-color); -} -.md-header--shadow { - box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 0.2rem, - rgba(0, 0, 0, 0.2) 0px 0.2rem 0.4rem; -} -.md-header__button { - color: currentcolor; - outline-color: var(--darkreader-border--md-accent-fg-color); -} -.md-header__button:not(.focus-visible) { - -webkit-tap-highlight-color: transparent; - outline-color: initial; -} -.md-header__button.md-logo :-webkit-any(img, svg) { - fill: currentcolor; -} -.md-header__button.md-logo :is(img, svg) { - fill: currentcolor; -} -:root { - --darkreader-bgimg--md-nav-icon--prev: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-nav-icon--next: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-toc-icon: url("data:image/svg+xml;charset=utf-8,"); -} -.md-nav__title .md-nav__button.md-logo :-webkit-any(img, svg) { - fill: currentcolor; -} -.md-nav__title .md-nav__button.md-logo :is(img, svg) { - fill: currentcolor; -} -.md-nav__list { +.recently-attempted ul { list-style-image: initial; } -.md-nav__link--passed { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-nav__item .md-nav__link--active { - color: var(--darkreader-text--md-typeset-a-color); -} -.md-nav__link:-webkit-any(:focus, :hover) { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-nav__link:is(:focus, :hover) { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-nav__link.focus-visible { - outline-color: var(--darkreader-border--md-accent-fg-color); -} -.md-nav--primary .md-nav__link[for="__toc"] .md-icon::after { - background-color: currentcolor; -} -@media screen and (max-width: 76.1875em) { - .md-nav--primary, - .md-nav--primary .md-nav { - background-color: var(--darkreader-bg--md-default-bg-color); - } - .md-nav--primary .md-nav__title { - background-color: var(--darkreader-bg--md-default-fg-color--lightest); - color: var(--darkreader-text--md-default-fg-color--light); - } - .md-nav--primary .md-nav__title .md-nav__icon::after { - background-color: currentcolor; - } - .md-nav--primary .md-nav__title ~ .md-nav__list { - background-color: var(--darkreader-bg--md-default-bg-color); - box-shadow: 0 0.05rem 0 var(--darkreader-bg--md-default-fg-color--lightest) inset; - } - .md-nav--primary .md-nav__title ~ .md-nav__list > :first-child { - border-top-color: initial; - } - .md-nav--primary .md-nav__title[for="__drawer"] { - background-color: var(--darkreader-bg--md-primary-fg-color); - color: var(--darkreader-text--md-primary-bg-color); - } - .md-nav--primary .md-nav__item { - border-top: 0.05rem solid var(--darkreader-border--md-default-fg-color--lightest); - } - .md-nav--primary .md-nav__item--active > .md-nav__link { - color: var(--darkreader-text--md-typeset-a-color); - } - .md-nav--primary .md-nav__item--active > .md-nav__link:-webkit-any(:focus, :hover) { - color: var(--darkreader-text--md-accent-fg-color); - } - .md-nav--primary .md-nav__item--active > .md-nav__link:is(:focus, :hover) { - color: var(--darkreader-text--md-accent-fg-color); - } - .md-nav--primary .md-nav__link .md-nav__icon::after { - background-color: currentcolor; - } - .md-nav--primary .md-nav--secondary .md-nav { - background-color: initial; - } - .md-nav--secondary { - background-color: initial; - } -} -@media screen and (max-width: 59.9375em) { - .md-nav__source { - background-color: var(--darkreader-bg--md-primary-fg-color--dark); - color: var(--darkreader-text--md-primary-bg-color); - } -} -@media screen and (min-width: 60em) { - .md-nav--secondary .md-nav__title { - box-shadow: 0 0 0.4rem 0.4rem var(--darkreader-bg--md-default-bg-color); - background: var(--darkreader-bg--md-default-bg-color); - } -} -@media screen and (min-width: 76.25em) { - .md-nav--primary .md-nav__title { - box-shadow: 0 0 0.4rem 0.4rem var(--darkreader-bg--md-default-bg-color); - background: var(--darkreader-bg--md-default-bg-color); - } - .md-nav__icon:hover { - background-color: var(--darkreader-bg--md-accent-fg-color--transparent); - } - .md-nav__icon::after { - background-color: currentcolor; - } - .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link { - box-shadow: 0 0 0.4rem 0.4rem var(--darkreader-bg--md-default-bg-color); - background: var(--darkreader-bg--md-default-bg-color); - } - .md-nav--integrated > .md-nav__list > .md-nav__item--active .md-nav--secondary { - border-left: 0.05rem solid var(--darkreader-border--md-primary-fg-color) ; - } - [dir="rtl"] .md-nav--integrated > .md-nav__list > .md-nav__item--active .md-nav--secondary { - border-right: 0.05rem solid var(--darkreader-border--md-primary-fg-color) ; - } -} -:root { - --darkreader-bgimg--md-search-result-icon: url("data:image/svg+xml;charset=utf-8,"); -} -@media screen and (max-width: 59.9375em) { - .md-search__overlay { - background-color: var(--darkreader-bg--md-default-bg-color); - } -} -@media screen and (min-width: 60em) { - .md-search__overlay { - background-color: rgba(0, 0, 0, 0.54); - } -} -.md-search__form { - background-color: var(--darkreader-bg--md-default-bg-color); - box-shadow: rgba(0, 0, 0, 0) 0px 0px 0.6rem; -} -@media screen and (min-width: 60em) { - .md-search__form { - background-color: rgba(0, 0, 0, 0.26); - } - .md-search__form:hover { - background-color: rgba(24, 26, 27, 0.12); - } -} -[data-md-toggle="search"]:checked ~ .md-header .md-search__form { - background-color: var(--darkreader-bg--md-default-bg-color); - box-shadow: rgba(0, 0, 0, 0.07) 0px 0px 0.6rem; - color: var(--darkreader-text--md-default-fg-color); -} -.md-search__input { - background-image: initial; - background-color: transparent; -} -.md-search__input::placeholder, -.md-search__input ~ .md-search__icon { - color: var(--darkreader-text--md-default-fg-color--light); -} -@media screen and (min-width: 60em) { - .md-search__input { - color: inherit; - } - .md-search__input::placeholder { - color: var(--darkreader-text--md-primary-bg-color--light); - } - .md-search__input + .md-search__icon { - color: var(--darkreader-text--md-primary-bg-color); - } - [data-md-toggle="search"]:checked ~ .md-header .md-search__input + .md-search__icon, [data-md-toggle="search"]:checked ~ .md-header .md-search__input::placeholder { - color: var(--darkreader-text--md-default-fg-color--light); - } -} -.md-search__options > * { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-search__options > :not(.focus-visible) { - -webkit-tap-highlight-color: transparent; - outline-color: initial; -} -.md-search__suggest { - color: var(--darkreader-text--md-default-fg-color--lighter); -} -@media screen and (min-width: 60em) { - [data-md-toggle="search"]:checked ~ .md-header .md-search__output { - box-shadow: var(--darkreader-bg--md-shadow-z3); - } -} -.md-search__scrollwrap { - background-color: var(--darkreader-bg--md-default-bg-color); -} -@media screen and (min-width: 60em) { - .md-search__scrollwrap::-webkit-scrollbar-thumb { - background-color: var(--darkreader-bg--md-default-fg-color--lighter); - } - .md-search__scrollwrap::-webkit-scrollbar-thumb:hover { - background-color: var(--darkreader-bg--md-accent-fg-color); - } -} -.md-search-result { - color: var(--darkreader-text--md-default-fg-color); -} -.md-search-result__meta { - background-color: var(--darkreader-bg--md-default-fg-color--lightest); - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-search-result__list { - list-style-image: initial; -} -.md-search-result__item { - box-shadow: 0 -0.05rem var(--darkreader-bg--md-default-fg-color--lightest); -} -.md-search-result__item:first-child { - box-shadow: none; -} -.md-search-result__link { - outline-color: initial; -} -.md-search-result__link:-webkit-any(:focus, :hover) { - background-color: var(--darkreader-bg--md-accent-fg-color--transparent); -} -.md-search-result__link:is(:focus, :hover) { - background-color: var(--darkreader-bg--md-accent-fg-color--transparent); -} -.md-search-result__more summary { - color: var(--darkreader-text--md-typeset-a-color); - outline-color: initial; -} -.md-search-result__more summary:-webkit-any(:focus, :hover) { - background-color: var(--darkreader-bg--md-accent-fg-color--transparent); - color: var(--darkreader-text--md-accent-fg-color); -} -.md-search-result__more summary:is(:focus, :hover) { - background-color: var(--darkreader-bg--md-accent-fg-color--transparent); - color: var(--darkreader-text--md-accent-fg-color); -} -.md-search-result__icon { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-search-result__icon::after { - background-color: currentcolor; -} -.md-search-result__teaser { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-search-result__teaser mark { - background-color: initial; - text-decoration-color: initial; -} -.md-search-result mark { - background-color: initial; - color: var(--darkreader-text--md-accent-fg-color); -} -.md-select__inner { - background-color: var(--darkreader-bg--md-default-bg-color); - box-shadow: var(--darkreader-bg--md-shadow-z2); - color: var(--darkreader-text--md-default-fg-color); -} -.md-select__inner::after { - border-bottom-color: var(--darkreader-border--md-default-bg-color); - border-left-color: transparent; - border-right-color: transparent; - border-top-color: initial; -} -.md-select__link { - outline-color: initial; -} -.md-select__link:-webkit-any(:focus, :hover) { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-select__link:is(:focus, :hover) { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-select__link:focus { - background-color: var(--darkreader-bg--md-default-fg-color--lightest); -} -@media screen and (max-width: 76.1875em) { - .md-sidebar--primary { - background-color: var(--darkreader-bg--md-default-bg-color); - } - [data-md-toggle="drawer"]:checked ~ .md-container .md-sidebar--primary { - box-shadow: var(--darkreader-bg--md-shadow-z3); - } -} -.md-sidebar__scrollwrap::-webkit-scrollbar-thumb { - background-color: var(--darkreader-bg--md-default-fg-color--lighter); -} -.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover { - background-color: var(--darkreader-bg--md-accent-fg-color); -} -@media screen and (max-width: 76.1875em) { - .md-overlay { - background-color: rgba(0, 0, 0, 0.54); - } -} -:root { - --darkreader-bgimg--md-source-forks-icon: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjE1MCI+PGRlZnM+PGZpbHRlciBpZD0iZGFya3JlYWRlci1pbWFnZS1maWx0ZXIiPjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwLjI0OSAtMC42MTQgLTAuNjcyIDAuMDAwIDEuMDM1IC0wLjY0NiAwLjI4OCAtMC42NjQgMC4wMDAgMS4wMjAgLTAuNjM2IC0wLjYwOSAwLjI1MCAwLjAwMCAwLjk5NCAwLjAwMCAwLjAwMCAwLjAwMCAxLjAwMCAwLjAwMCIgLz48L2ZpbHRlcj48L2RlZnM+PGltYWdlIHdpZHRoPSIxNTAiIGhlaWdodD0iMTUwIiBmaWx0ZXI9InVybCgjZGFya3JlYWRlci1pbWFnZS1maWx0ZXIpIiB4bGluazpocmVmPSJkYXRhOmltYWdlL3N2Zyt4bWw7Y2hhcnNldD11dGYtOCw8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik01IDMuMjVhLjc1Ljc1IDAgMSAxLTEuNSAwIC43NS43NSAwIDAgMSAxLjUgMHptMCAyLjEyMmEyLjI1IDIuMjUgMCAxIDAtMS41IDB2Ljg3OEEyLjI1IDIuMjUgMCAwIDAgNS43NSA4LjVoMS41djIuMTI4YTIuMjUxIDIuMjUxIDAgMSAwIDEuNSAwVjguNWgxLjVhMi4yNSAyLjI1IDAgMCAwIDIuMjUtMi4yNXYtLjg3OGEyLjI1IDIuMjUgMCAxIDAtMS41IDB2Ljg3OGEuNzUuNzUgMCAwIDEtLjc1Ljc1aC00LjVBLjc1Ljc1IDAgMCAxIDUgNi4yNXYtLjg3OHptMy43NSA3LjM3OGEuNzUuNzUgMCAxIDEtMS41IDAgLjc1Ljc1IDAgMCAxIDEuNSAwem0zLTguNzVhLjc1Ljc1IDAgMSAwIDAtMS41Ljc1Ljc1IDAgMCAwIDAgMS41eiIvPjwvc3ZnPiIgLz48L3N2Zz4="); - --darkreader-bgimg--md-source-repositories-icon: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjE1MCI+PGRlZnM+PGZpbHRlciBpZD0iZGFya3JlYWRlci1pbWFnZS1maWx0ZXIiPjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwLjI0OSAtMC42MTQgLTAuNjcyIDAuMDAwIDEuMDM1IC0wLjY0NiAwLjI4OCAtMC42NjQgMC4wMDAgMS4wMjAgLTAuNjM2IC0wLjYwOSAwLjI1MCAwLjAwMCAwLjk5NCAwLjAwMCAwLjAwMCAwLjAwMCAxLjAwMCAwLjAwMCIgLz48L2ZpbHRlcj48L2RlZnM+PGltYWdlIHdpZHRoPSIxNTAiIGhlaWdodD0iMTUwIiBmaWx0ZXI9InVybCgjZGFya3JlYWRlci1pbWFnZS1maWx0ZXIpIiB4bGluazpocmVmPSJkYXRhOmltYWdlL3N2Zyt4bWw7Y2hhcnNldD11dGYtOCw8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yIDIuNUEyLjUgMi41IDAgMCAxIDQuNSAwaDguNzVhLjc1Ljc1IDAgMCAxIC43NS43NXYxMi41YS43NS43NSAwIDAgMS0uNzUuNzVoLTIuNWEuNzUuNzUgMCAxIDEgMC0xLjVoMS43NXYtMmgtOGExIDEgMCAwIDAtLjcxNCAxLjcuNzUuNzUgMCAwIDEtMS4wNzIgMS4wNUEyLjQ5NSAyLjQ5NSAwIDAgMSAyIDExLjV2LTl6bTEwLjUtMVY5aC04Yy0uMzU2IDAtLjY5NC4wNzQtMSAuMjA4VjIuNWExIDEgMCAwIDEgMS0xaDh6TTUgMTIuMjV2My4yNWEuMjUuMjUgMCAwIDAgLjQuMmwxLjQ1LTEuMDg3YS4yNS4yNSAwIDAgMSAuMyAwTDguNiAxNS43YS4yNS4yNSAwIDAgMCAuNC0uMnYtMy4yNWEuMjUuMjUgMCAwIDAtLjI1LS4yNWgtMy41YS4yNS4yNSAwIDAgMC0uMjUuMjV6Ii8+PC9zdmc+IiAvPjwvc3ZnPg=="); - --darkreader-bgimg--md-source-stars-icon: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-source-version-icon: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjE1MCI+PGRlZnM+PGZpbHRlciBpZD0iZGFya3JlYWRlci1pbWFnZS1maWx0ZXIiPjxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwLjI0OSAtMC42MTQgLTAuNjcyIDAuMDAwIDEuMDM1IC0wLjY0NiAwLjI4OCAtMC42NjQgMC4wMDAgMS4wMjAgLTAuNjM2IC0wLjYwOSAwLjI1MCAwLjAwMCAwLjk5NCAwLjAwMCAwLjAwMCAwLjAwMCAxLjAwMCAwLjAwMCIgLz48L2ZpbHRlcj48L2RlZnM+PGltYWdlIHdpZHRoPSIxNTAiIGhlaWdodD0iMTUwIiBmaWx0ZXI9InVybCgjZGFya3JlYWRlci1pbWFnZS1maWx0ZXIpIiB4bGluazpocmVmPSJkYXRhOmltYWdlL3N2Zyt4bWw7Y2hhcnNldD11dGYtOCw8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yLjUgNy43NzVWMi43NWEuMjUuMjUgMCAwIDEgLjI1LS4yNWg1LjAyNWEuMjUuMjUgMCAwIDEgLjE3Ny4wNzNsNi4yNSA2LjI1YS4yNS4yNSAwIDAgMSAwIC4zNTRsLTUuMDI1IDUuMDI1YS4yNS4yNSAwIDAgMS0uMzU0IDBsLTYuMjUtNi4yNWEuMjUuMjUgMCAwIDEtLjA3My0uMTc3em0tMS41IDBWMi43NUMxIDEuNzg0IDEuNzg0IDEgMi43NSAxaDUuMDI1Yy40NjQgMCAuOTEuMTg0IDEuMjM4LjUxM2w2LjI1IDYuMjVhMS43NSAxLjc1IDAgMCAxIDAgMi40NzRsLTUuMDI2IDUuMDI2YTEuNzUgMS43NSAwIDAgMS0yLjQ3NCAwbC02LjI1LTYuMjVBMS43NSAxLjc1IDAgMCAxIDEgNy43NzV6TTYgNWExIDEgMCAxIDAgMCAyIDEgMSAwIDAgMCAwLTJ6Ii8+PC9zdmc+IiAvPjwvc3ZnPg=="); -} -.md-source { - outline-color: var(--darkreader-border--md-accent-fg-color); -} -.md-source__fact::before { - background-color: currentcolor; -} -.md-tabs { - background-color: var(--darkreader-bg--md-primary-fg-color); - color: var(--darkreader-text--md-primary-bg-color); -} -.md-tabs__list { - list-style-image: initial; -} -.md-tabs__link { - outline-color: var(--darkreader-border--md-accent-fg-color); -} -.md-tabs__link--active, -.md-tabs__link:-webkit-any(:focus, :hover) { - color: inherit; -} -.md-tabs__link--active, -.md-tabs__link:is(:focus, :hover) { - color: inherit; -} -:root { - --darkreader-bgimg--md-tag-icon: url("data:image/svg+xml;charset=utf-8,"); -} -.md-typeset .md-tag { - background: var(--darkreader-bg--md-default-fg-color--lightest); -} -.md-typeset .md-tag[href] { - -webkit-tap-highlight-color: transparent; - color: inherit; - outline-color: initial; -} -.md-typeset .md-tag[href]:focus, -.md-typeset .md-tag[href]:hover { - background-color: var(--darkreader-bg--md-accent-fg-color); - color: var(--darkreader-text--md-accent-bg-color); -} -.md-typeset .md-tag-icon::before { - background-color: var(--darkreader-bg--md-default-fg-color--lighter); -} -.md-typeset .md-tag-icon:-webkit-any(a:focus, a:hover)::before { - background-color: var(--darkreader-bg--md-accent-bg-color); -} -.md-typeset .md-tag-icon:is(a:focus, a:hover)::before { - background-color: var(--darkreader-bg--md-accent-bg-color); -} -:root { - --md-tooltip-width: 20rem; -} -.md-tooltip { - background-color: var(--darkreader-bg--md-default-bg-color); - box-shadow: var(--darkreader-bg--md-shadow-z2); - color: var(--darkreader-text--md-default-fg-color); -} -:is(.focus-visible > .md-tooltip, .md-tooltip:target) { - outline: var(--darkreader-border--md-accent-fg-color) auto ; -} -.md-annotation { - outline-color: initial; -} -.md-annotation__index { - outline-color: initial; -} -.md-annotation .md-annotation__index { - color: rgb(232, 230, 227); -} -.md-annotation .md-annotation__index:-webkit-any(:focus, :hover) { - color: rgb(232, 230, 227); -} -.md-annotation .md-annotation__index:is(:focus, :hover) { - color: rgb(232, 230, 227); -} -.md-annotation__index::after { - background-color: var(--darkreader-bg--md-default-fg-color--lighter); -} -:is(.md-tooltip--active + .md-annotation__index, :hover > .md-annotation__index) { - color: var(--darkreader-text--md-accent-bg-color); -} -:is(.md-tooltip--active + .md-annotation__index, :hover > .md-annotation__index)::after { - background-color: var(--darkreader-bg--md-accent-fg-color); -} -.md-top { - background-color: var(--darkreader-bg--md-default-bg-color); - box-shadow: var(--darkreader-bg--md-shadow-z2); - color: var(--darkreader-text--md-default-fg-color--light); - outline-color: initial; -} -.md-top:-webkit-any(:focus, :hover) { - background-color: var(--darkreader-bg--md-accent-fg-color); - color: var(--darkreader-text--md-accent-bg-color); -} -.md-top:is(:focus, :hover) { - background-color: var(--darkreader-bg--md-accent-fg-color); - color: var(--darkreader-text--md-accent-bg-color); -} -:root { - --darkreader-bgimg--md-version-icon: url("data:image/svg+xml;charset=utf-8,"); -} -.md-version__current { - color: inherit; - outline-color: initial; -} -.md-version__current::after { - background-color: currentcolor; -} -.md-version__list { - background-color: var(--darkreader-bg--md-default-bg-color); - box-shadow: var(--darkreader-bg--md-shadow-z2); - color: var(--darkreader-text--md-default-fg-color); -} -.md-version__link { - outline-color: initial; -} -.md-version__link:-webkit-any(:focus, :hover) { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-version__link:is(:focus, :hover) { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-version__link:focus { - background-color: var(--darkreader-bg--md-default-fg-color--lightest); -} -:root { - --darkreader-bgimg--md-admonition-icon--note: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--abstract: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--info: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--tip: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--success: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--question: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--warning: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--failure: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--danger: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--bug: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--example: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-admonition-icon--quote: url("data:image/svg+xml;charset=utf-8,"); -} -.md-typeset .admonition, -.md-typeset details { - background-color: var(--darkreader-bg--md-admonition-bg-color); - border-color: rgb(0, 59, 158); - box-shadow: var(--darkreader-bg--md-shadow-z1); - color: var(--darkreader-text--md-admonition-fg-color); -} -.md-typeset .admonition-title, -.md-typeset summary { - background-color: rgba(0, 61, 163, 0.1); - border-color: initial; -} -.md-typeset .admonition-title::before, -.md-typeset summary::before { - background-color: rgb(73, 165, 255); -} -.md-typeset .admonition-title code, -.md-typeset summary code { - box-shadow: 0 0 0 0.05rem var(--darkreader-bg--md-default-fg-color--lightest); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.note) { - border-color: rgb(0, 59, 158); -} -.md-typeset :is(.admonition, details):is(.note) { - border-color: rgb(0, 59, 158); -} -.md-typeset :-webkit-any(.note) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(0, 61, 163, 0.1); -} -.md-typeset :is(.note) > :is(.admonition-title, summary) { - background-color: rgba(0, 61, 163, 0.1); -} -.md-typeset :-webkit-any(.note) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(73, 165, 255); -} -.md-typeset :is(.note) > :is(.admonition-title, summary)::before { - background-color: rgb(73, 165, 255); -} -.md-typeset :-webkit-any(.note) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(73, 165, 255); -} -.md-typeset :is(.note) > :is(.admonition-title, summary)::after { - color: rgb(73, 165, 255); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.abstract, .summary, .tldr) { - border-color: rgb(0, 123, 179); -} -.md-typeset :is(.admonition, details):is(.abstract, .summary, .tldr) { - border-color: rgb(0, 123, 179); -} -.md-typeset :-webkit-any(.abstract, .summary, .tldr) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(0, 141, 204, 0.1); -} -.md-typeset :is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) { - background-color: rgba(0, 141, 204, 0.1); -} -.md-typeset :-webkit-any(.abstract, .summary, .tldr) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(26, 184, 255); -} -.md-typeset :is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before { - background-color: rgb(26, 184, 255); -} -.md-typeset :-webkit-any(.abstract, .summary, .tldr) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(26, 184, 255); -} -.md-typeset :is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::after { - color: rgb(26, 184, 255); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.info, .todo) { - border-color: rgb(0, 166, 191); -} -.md-typeset :is(.admonition, details):is(.info, .todo) { - border-color: rgb(0, 166, 191); -} -.md-typeset :-webkit-any(.info, .todo) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(0, 147, 170, 0.1); -} -.md-typeset :is(.info, .todo) > :is(.admonition-title, summary) { - background-color: rgba(0, 147, 170, 0.1); -} -.md-typeset :-webkit-any(.info, .todo) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(56, 229, 255); -} -.md-typeset :is(.info, .todo) > :is(.admonition-title, summary)::before { - background-color: rgb(56, 229, 255); -} -.md-typeset :-webkit-any(.info, .todo) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(56, 229, 255); -} -.md-typeset :is(.info, .todo) > :is(.admonition-title, summary)::after { - color: rgb(56, 229, 255); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.tip, .hint, .important) { - border-color: rgb(0, 198, 171); -} -.md-typeset :is(.admonition, details):is(.tip, .hint, .important) { - border-color: rgb(0, 198, 171); -} -.md-typeset :-webkit-any(.tip, .hint, .important) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(0, 153, 132, 0.1); -} -.md-typeset :is(.tip, .hint, .important) > :is(.admonition-title, summary) { - background-color: rgba(0, 153, 132, 0.1); -} -.md-typeset :-webkit-any(.tip, .hint, .important) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(70, 255, 230); -} -.md-typeset :is(.tip, .hint, .important) > :is(.admonition-title, summary)::before { - background-color: rgb(70, 255, 230); -} -.md-typeset :-webkit-any(.tip, .hint, .important) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(70, 255, 230); -} -.md-typeset :is(.tip, .hint, .important) > :is(.admonition-title, summary)::after { - color: rgb(70, 255, 230); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.success, .check, .done) { - border-color: rgb(0, 195, 81); -} -.md-typeset :is(.admonition, details):is(.success, .check, .done) { - border-color: rgb(0, 195, 81); -} -.md-typeset :-webkit-any(.success, .check, .done) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(0, 160, 66, 0.1); -} -.md-typeset :is(.success, .check, .done) > :is(.admonition-title, summary) { - background-color: rgba(0, 160, 66, 0.1); -} -.md-typeset :-webkit-any(.success, .check, .done) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(64, 255, 143); -} -.md-typeset :is(.success, .check, .done) > :is(.admonition-title, summary)::before { - background-color: rgb(64, 255, 143); -} -.md-typeset :-webkit-any(.success, .check, .done) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(64, 255, 143); -} -.md-typeset :is(.success, .check, .done) > :is(.admonition-title, summary)::after { - color: rgb(64, 255, 143); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.question, .help, .faq) { - border-color: rgb(75, 165, 17); -} -.md-typeset :is(.admonition, details):is(.question, .help, .faq) { - border-color: rgb(75, 165, 17); -} -.md-typeset :-webkit-any(.question, .help, .faq) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(80, 177, 18, 0.1); -} -.md-typeset :is(.question, .help, .faq) > :is(.admonition-title, summary) { - background-color: rgba(80, 177, 18, 0.1); -} -.md-typeset :-webkit-any(.question, .help, .faq) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(124, 234, 54); -} -.md-typeset :is(.question, .help, .faq) > :is(.admonition-title, summary)::before { - background-color: rgb(124, 234, 54); -} -.md-typeset :-webkit-any(.question, .help, .faq) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(124, 234, 54); -} -.md-typeset :is(.question, .help, .faq) > :is(.admonition-title, summary)::after { - color: rgb(124, 234, 54); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.warning, .caution, .attention) { - border-color: rgb(179, 102, 0); -} -.md-typeset :is(.admonition, details):is(.warning, .caution, .attention) { - border-color: rgb(179, 102, 0); -} -.md-typeset :-webkit-any(.warning, .caution, .attention) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(204, 116, 0, 0.1); -} -.md-typeset :is(.warning, .caution, .attention) > :is(.admonition-title, summary) { - background-color: rgba(204, 116, 0, 0.1); -} -.md-typeset :-webkit-any(.warning, .caution, .attention) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(255, 156, 26); -} -.md-typeset :is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before { - background-color: rgb(255, 156, 26); -} -.md-typeset :-webkit-any(.warning, .caution, .attention) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(255, 156, 26); -} -.md-typeset :is(.warning, .caution, .attention) > :is(.admonition-title, summary)::after { - color: rgb(255, 156, 26); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.failure, .fail, .missing) { - border-color: rgb(154, 0, 0); -} -.md-typeset :is(.admonition, details):is(.failure, .fail, .missing) { - border-color: rgb(154, 0, 0); -} -.md-typeset :-webkit-any(.failure, .fail, .missing) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(155, 0, 0, 0.1); -} -.md-typeset :is(.failure, .fail, .missing) > :is(.admonition-title, summary) { - background-color: rgba(155, 0, 0, 0.1); -} -.md-typeset :-webkit-any(.failure, .fail, .missing) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(255, 83, 83); -} -.md-typeset :is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before { - background-color: rgb(255, 83, 83); -} -.md-typeset :-webkit-any(.failure, .fail, .missing) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(255, 83, 83); -} -.md-typeset :is(.failure, .fail, .missing) > :is(.admonition-title, summary)::after { - color: rgb(255, 83, 83); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.danger, .error) { - border-color: rgb(172, 0, 33); -} -.md-typeset :is(.admonition, details):is(.danger, .error) { - border-color: rgb(172, 0, 33); -} -.md-typeset :-webkit-any(.danger, .error) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(190, 0, 37, 0.1); -} -.md-typeset :is(.danger, .error) > :is(.admonition-title, summary) { - background-color: rgba(190, 0, 37, 0.1); -} -.md-typeset :-webkit-any(.danger, .error) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(255, 42, 83); -} -.md-typeset :is(.danger, .error) > :is(.admonition-title, summary)::before { - background-color: rgb(255, 42, 83); -} -.md-typeset :-webkit-any(.danger, .error) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(255, 42, 83); -} -.md-typeset :is(.danger, .error) > :is(.admonition-title, summary)::after { - color: rgb(255, 42, 83); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.bug) { - border-color: rgb(182, 0, 64); -} -.md-typeset :is(.admonition, details):is(.bug) { - border-color: rgb(182, 0, 64); -} -.md-typeset :-webkit-any(.bug) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(196, 0, 70, 0.1); -} -.md-typeset :is(.bug) > :is(.admonition-title, summary) { - background-color: rgba(196, 0, 70, 0.1); -} -.md-typeset :-webkit-any(.bug) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(255, 33, 112); -} -.md-typeset :is(.bug) > :is(.admonition-title, summary)::before { - background-color: rgb(255, 33, 112); -} -.md-typeset :-webkit-any(.bug) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(255, 33, 112); -} -.md-typeset :is(.bug) > :is(.admonition-title, summary)::after { - color: rgb(255, 33, 112); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.example) { - border-color: rgb(41, 0, 155); -} -.md-typeset :is(.admonition, details):is(.example) { - border-color: rgb(41, 0, 155); -} -.md-typeset :-webkit-any(.example) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(42, 0, 158, 0.1); -} -.md-typeset :is(.example) > :is(.admonition-title, summary) { - background-color: rgba(42, 0, 158, 0.1); -} -.md-typeset :-webkit-any(.example) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(126, 79, 255); -} -.md-typeset :is(.example) > :is(.admonition-title, summary)::before { - background-color: rgb(126, 79, 255); -} -.md-typeset :-webkit-any(.example) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(126, 79, 255); -} -.md-typeset :is(.example) > :is(.admonition-title, summary)::after { - color: rgb(126, 79, 255); -} -.md-typeset :-webkit-any(.admonition, details):-webkit-any(.quote, .cite) { - border-color: rgb(75, 82, 85); -} -.md-typeset :is(.admonition, details):is(.quote, .cite) { - border-color: rgb(75, 82, 85); -} -.md-typeset :-webkit-any(.quote, .cite) > :-webkit-any(.admonition-title, summary) { - background-color: rgba(79, 85, 89, 0.1); -} -.md-typeset :is(.quote, .cite) > :is(.admonition-title, summary) { - background-color: rgba(79, 85, 89, 0.1); -} -.md-typeset :-webkit-any(.quote, .cite) > :-webkit-any(.admonition-title, summary)::before { - background-color: rgb(171, 163, 152); -} -.md-typeset :is(.quote, .cite) > :is(.admonition-title, summary)::before { - background-color: rgb(171, 163, 152); -} -.md-typeset :-webkit-any(.quote, .cite) > :-webkit-any(.admonition-title, summary)::after { - color: rgb(171, 163, 152); -} -.md-typeset :is(.quote, .cite) > :is(.admonition-title, summary)::after { - color: rgb(171, 163, 152); -} -:root { - --darkreader-bgimg--md-footnotes-icon: url("data:image/svg+xml;charset=utf-8,"); -} -.md-typeset .footnote { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-typeset .footnote > ol > li:target { - color: var(--darkreader-text--md-default-fg-color); -} -.md-typeset [id^="fnref:"]:target > .footnote-ref { - outline-color: initial; -} -.md-typeset .footnote-backref { - color: var(--darkreader-text--md-typeset-a-color); -} -.md-typeset .footnote-backref:hover { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-typeset .footnote-backref::before { - background-color: currentcolor; -} -.md-typeset .headerlink { - color: var(--darkreader-text--md-default-fg-color--lighter); -} -.md-typeset .headerlink:-webkit-any(:focus, :hover), -.md-typeset :target > .headerlink { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-typeset .headerlink:is(:focus, :hover), -.md-typeset :target > .headerlink { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-typeset :target { - --md-scroll-margin: 3.6rem; - --md-scroll-offset: 0rem; -} -@media screen and (min-width: 76.25em) { - .md-header--lifted ~ .md-container .md-typeset :target { - --md-scroll-margin: 6rem; - } -} -.md-typeset :-webkit-any(h1, h2, h3):target { - --md-scroll-offset: 0.2rem; -} -.md-typeset :is(h1, h2, h3):target { - --md-scroll-offset: 0.2rem; -} -.md-typeset h4:target { - --md-scroll-offset: 0.15rem; -} -.md-typeset del.critic { - background-color: var(--darkreader-bg--md-typeset-del-color); -} -.md-typeset ins.critic { - background-color: var(--darkreader-bg--md-typeset-ins-color); -} -.md-typeset .critic.comment { - color: var(--darkreader-text--md-code-hl-comment-color); -} -.md-typeset .critic.block { - box-shadow: none; -} -:root { - --darkreader-bgimg--md-details-icon: url("data:image/svg+xml;charset=utf-8,"); -} -.md-typeset details:not([open]) { - box-shadow: none; -} -.md-typeset summary.focus-visible { - outline-color: var(--darkreader-border--md-accent-fg-color); -} -.md-typeset summary:not(.focus-visible) { - -webkit-tap-highlight-color: transparent; - outline-color: initial; -} -.md-typeset summary::after { - background-color: currentcolor; -} -.md-typeset :-webkit-any(.emojione, .twemoji, .gemoji) svg { - fill: currentcolor; -} -.md-typeset :is(.emojione, .twemoji, .gemoji) svg { - fill: currentcolor; -} -.highlight :-webkit-any(.o, .ow) { - color: var(--darkreader-text--md-code-hl-operator-color); -} -.highlight :is(.o, .ow) { - color: var(--darkreader-text--md-code-hl-operator-color); -} -.highlight .p { - color: var(--darkreader-text--md-code-hl-punctuation-color); -} -.highlight :-webkit-any(.cpf, .l, .s, .sb, .sc, .s2, .si, .s1, .ss) { - color: var(--darkreader-text--md-code-hl-string-color); -} -.highlight :is(.cpf, .l, .s, .sb, .sc, .s2, .si, .s1, .ss) { - color: var(--darkreader-text--md-code-hl-string-color); -} -.highlight :-webkit-any(.cp, .se, .sh, .sr, .sx) { - color: var(--darkreader-text--md-code-hl-special-color); -} -.highlight :is(.cp, .se, .sh, .sr, .sx) { - color: var(--darkreader-text--md-code-hl-special-color); -} -.highlight :-webkit-any(.m, .mb, .mf, .mh, .mi, .il, .mo) { - color: var(--darkreader-text--md-code-hl-number-color); -} -.highlight :is(.m, .mb, .mf, .mh, .mi, .il, .mo) { - color: var(--darkreader-text--md-code-hl-number-color); -} -.highlight :-webkit-any(.k, .kd, .kn, .kp, .kr, .kt) { - color: var(--darkreader-text--md-code-hl-keyword-color); -} -.highlight :is(.k, .kd, .kn, .kp, .kr, .kt) { - color: var(--darkreader-text--md-code-hl-keyword-color); -} -.highlight :-webkit-any(.kc, .n) { - color: var(--darkreader-text--md-code-hl-name-color); -} -.highlight :is(.kc, .n) { - color: var(--darkreader-text--md-code-hl-name-color); -} -.highlight :-webkit-any(.no, .nb, .bp) { - color: var(--darkreader-text--md-code-hl-constant-color); -} -.highlight :is(.no, .nb, .bp) { - color: var(--darkreader-text--md-code-hl-constant-color); -} -.highlight :-webkit-any(.nc, .ne, .nf, .nn) { - color: var(--darkreader-text--md-code-hl-function-color); -} -.highlight :is(.nc, .ne, .nf, .nn) { - color: var(--darkreader-text--md-code-hl-function-color); -} -.highlight :-webkit-any(.nd, .ni, .nl, .nt) { - color: var(--darkreader-text--md-code-hl-keyword-color); -} -.highlight :is(.nd, .ni, .nl, .nt) { - color: var(--darkreader-text--md-code-hl-keyword-color); -} -.highlight :-webkit-any(.c, .cm, .c1, .ch, .cs, .sd) { - color: var(--darkreader-text--md-code-hl-comment-color); -} -.highlight :is(.c, .cm, .c1, .ch, .cs, .sd) { - color: var(--darkreader-text--md-code-hl-comment-color); -} -.highlight :-webkit-any(.na, .nv, .vc, .vg, .vi) { - color: var(--darkreader-text--md-code-hl-variable-color); -} -.highlight :is(.na, .nv, .vc, .vg, .vi) { - color: var(--darkreader-text--md-code-hl-variable-color); -} -.highlight :-webkit-any(.ge, .gr, .gh, .go, .gp, .gs, .gu, .gt) { - color: var(--darkreader-text--md-code-hl-generic-color); -} -.highlight :is(.ge, .gr, .gh, .go, .gp, .gs, .gu, .gt) { - color: var(--darkreader-text--md-code-hl-generic-color); -} -.highlight .gd { - background-color: var(--darkreader-bg--md-typeset-del-color); -} -.highlight .gi { - background-color: var(--darkreader-bg--md-typeset-ins-color); -} -.highlight .hll { - background-color: var(--darkreader-bg--md-code-hl-color); -} -.highlight span.filename { - background-color: var(--darkreader-bg--md-code-bg-color); - border-bottom: 0.05rem solid var(--darkreader-border--md-default-fg-color--lightest); -} -.highlight [data-linenos]::before { - background-color: var(--darkreader-bg--md-code-bg-color); - box-shadow: -0.05rem 0 var(--darkreader-bg--md-default-fg-color--lightest) inset; - color: var(--darkreader-text--md-default-fg-color--light); -} -.highlighttable .linenos { - background-color: var(--darkreader-bg--md-code-bg-color); -} -.highlighttable .linenodiv { - box-shadow: -0.05rem 0 var(--darkreader-bg--md-default-fg-color--lightest) inset; -} -.highlighttable .linenodiv pre { - color: var(--darkreader-text--md-default-fg-color--light); -} -.linenodiv a { - color: inherit; -} -.md-typeset .keys span { - color: var(--darkreader-text--md-default-fg-color--light); -} -:root { - --darkreader-bgimg--md-tabbed-icon--prev: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-tabbed-icon--next: url("data:image/svg+xml;charset=utf-8,"); -} -.md-typeset .tabbed-set > input:target { - --md-scroll-offset: 0.625em; -} -.md-typeset .tabbed-labels { - box-shadow: 0 -0.05rem var(--darkreader-bg--md-default-fg-color--lightest) inset; -} -@media screen { - .js .md-typeset .tabbed-labels::before { - background: var(--darkreader-bg--md-accent-fg-color); - } -} -.md-typeset .tabbed-labels > label { - border-bottom-color: transparent; - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-typeset .tabbed-labels > label:hover { - color: var(--darkreader-text--md-accent-fg-color); -} -.md-typeset .tabbed-button { - color: var(--darkreader-text--md-default-fg-color--light); -} -.md-typeset .tabbed-button:hover { - background-color: var(--darkreader-bg--md-accent-fg-color--transparent); - color: var(--darkreader-text--md-accent-fg-color); -} -.md-typeset .tabbed-button::after { - background-color: currentcolor; -} -.md-typeset .tabbed-control { - background: linear-gradient(to right, - var(--darkreader-bg--md-default-bg-color) 60%,transparent); -} -.md-typeset .tabbed-control--next { - background: linear-gradient(to left, - var(--darkreader-bg--md-default-bg-color) 60%,transparent); -} -@media screen { - .md-typeset .tabbed-set > input:first-child:checked ~ .tabbed-labels > :first-child, - .md-typeset .tabbed-set > input:nth-child(10):checked ~ .tabbed-labels > :nth-child(10), - .md-typeset .tabbed-set > input:nth-child(11):checked ~ .tabbed-labels > :nth-child(11), - .md-typeset .tabbed-set > input:nth-child(12):checked ~ .tabbed-labels > :nth-child(12), - .md-typeset .tabbed-set > input:nth-child(13):checked ~ .tabbed-labels > :nth-child(13), - .md-typeset .tabbed-set > input:nth-child(14):checked ~ .tabbed-labels > :nth-child(14), - .md-typeset .tabbed-set > input:nth-child(15):checked ~ .tabbed-labels > :nth-child(15), - .md-typeset .tabbed-set > input:nth-child(16):checked ~ .tabbed-labels > :nth-child(16), - .md-typeset .tabbed-set > input:nth-child(17):checked ~ .tabbed-labels > :nth-child(17), - .md-typeset .tabbed-set > input:nth-child(18):checked ~ .tabbed-labels > :nth-child(18), - .md-typeset .tabbed-set > input:nth-child(19):checked ~ .tabbed-labels > :nth-child(19), - .md-typeset .tabbed-set > input:nth-child(2):checked ~ .tabbed-labels > :nth-child(2), - .md-typeset .tabbed-set > input:nth-child(20):checked ~ .tabbed-labels > :nth-child(20), - .md-typeset .tabbed-set > input:nth-child(3):checked ~ .tabbed-labels > :nth-child(3), - .md-typeset .tabbed-set > input:nth-child(4):checked ~ .tabbed-labels > :nth-child(4), - .md-typeset .tabbed-set > input:nth-child(5):checked ~ .tabbed-labels > :nth-child(5), - .md-typeset .tabbed-set > input:nth-child(6):checked ~ .tabbed-labels > :nth-child(6), - .md-typeset .tabbed-set > input:nth-child(7):checked ~ .tabbed-labels > :nth-child(7), - .md-typeset .tabbed-set > input:nth-child(8):checked ~ .tabbed-labels > :nth-child(8), - .md-typeset .tabbed-set > input:nth-child(9):checked ~ .tabbed-labels > :nth-child(9) { - color: var(--darkreader-text--md-accent-fg-color); - } - .md-typeset .no-js .tabbed-set > input:first-child:checked ~ .tabbed-labels > :first-child, - .md-typeset .no-js .tabbed-set > input:nth-child(10):checked ~ .tabbed-labels > :nth-child(10), - .md-typeset .no-js .tabbed-set > input:nth-child(11):checked ~ .tabbed-labels > :nth-child(11), - .md-typeset .no-js .tabbed-set > input:nth-child(12):checked ~ .tabbed-labels > :nth-child(12), - .md-typeset .no-js .tabbed-set > input:nth-child(13):checked ~ .tabbed-labels > :nth-child(13), - .md-typeset .no-js .tabbed-set > input:nth-child(14):checked ~ .tabbed-labels > :nth-child(14), - .md-typeset .no-js .tabbed-set > input:nth-child(15):checked ~ .tabbed-labels > :nth-child(15), - .md-typeset .no-js .tabbed-set > input:nth-child(16):checked ~ .tabbed-labels > :nth-child(16), - .md-typeset .no-js .tabbed-set > input:nth-child(17):checked ~ .tabbed-labels > :nth-child(17), - .md-typeset .no-js .tabbed-set > input:nth-child(18):checked ~ .tabbed-labels > :nth-child(18), - .md-typeset .no-js .tabbed-set > input:nth-child(19):checked ~ .tabbed-labels > :nth-child(19), - .md-typeset .no-js .tabbed-set > input:nth-child(2):checked ~ .tabbed-labels > :nth-child(2), - .md-typeset .no-js .tabbed-set > input:nth-child(20):checked ~ .tabbed-labels > :nth-child(20), - .md-typeset .no-js .tabbed-set > input:nth-child(3):checked ~ .tabbed-labels > :nth-child(3), - .md-typeset .no-js .tabbed-set > input:nth-child(4):checked ~ .tabbed-labels > :nth-child(4), - .md-typeset .no-js .tabbed-set > input:nth-child(5):checked ~ .tabbed-labels > :nth-child(5), - .md-typeset .no-js .tabbed-set > input:nth-child(6):checked ~ .tabbed-labels > :nth-child(6), - .md-typeset .no-js .tabbed-set > input:nth-child(7):checked ~ .tabbed-labels > :nth-child(7), - .md-typeset .no-js .tabbed-set > input:nth-child(8):checked ~ .tabbed-labels > :nth-child(8), - .md-typeset .no-js .tabbed-set > input:nth-child(9):checked ~ .tabbed-labels > :nth-child(9), - .no-js .md-typeset .tabbed-set > input:first-child:checked ~ .tabbed-labels > :first-child, - .no-js .md-typeset .tabbed-set > input:nth-child(10):checked ~ .tabbed-labels > :nth-child(10), - .no-js .md-typeset .tabbed-set > input:nth-child(11):checked ~ .tabbed-labels > :nth-child(11), - .no-js .md-typeset .tabbed-set > input:nth-child(12):checked ~ .tabbed-labels > :nth-child(12), - .no-js .md-typeset .tabbed-set > input:nth-child(13):checked ~ .tabbed-labels > :nth-child(13), - .no-js .md-typeset .tabbed-set > input:nth-child(14):checked ~ .tabbed-labels > :nth-child(14), - .no-js .md-typeset .tabbed-set > input:nth-child(15):checked ~ .tabbed-labels > :nth-child(15), - .no-js .md-typeset .tabbed-set > input:nth-child(16):checked ~ .tabbed-labels > :nth-child(16), - .no-js .md-typeset .tabbed-set > input:nth-child(17):checked ~ .tabbed-labels > :nth-child(17), - .no-js .md-typeset .tabbed-set > input:nth-child(18):checked ~ .tabbed-labels > :nth-child(18), - .no-js .md-typeset .tabbed-set > input:nth-child(19):checked ~ .tabbed-labels > :nth-child(19), - .no-js .md-typeset .tabbed-set > input:nth-child(2):checked ~ .tabbed-labels > :nth-child(2), - .no-js .md-typeset .tabbed-set > input:nth-child(20):checked ~ .tabbed-labels > :nth-child(20), - .no-js .md-typeset .tabbed-set > input:nth-child(3):checked ~ .tabbed-labels > :nth-child(3), - .no-js .md-typeset .tabbed-set > input:nth-child(4):checked ~ .tabbed-labels > :nth-child(4), - .no-js .md-typeset .tabbed-set > input:nth-child(5):checked ~ .tabbed-labels > :nth-child(5), - .no-js .md-typeset .tabbed-set > input:nth-child(6):checked ~ .tabbed-labels > :nth-child(6), - .no-js .md-typeset .tabbed-set > input:nth-child(7):checked ~ .tabbed-labels > :nth-child(7), - .no-js .md-typeset .tabbed-set > input:nth-child(8):checked ~ .tabbed-labels > :nth-child(8), - .no-js .md-typeset .tabbed-set > input:nth-child(9):checked ~ .tabbed-labels > :nth-child(9) { - border-color: var(--darkreader-border--md-accent-fg-color) ; - } -} -.md-typeset .tabbed-set > input:first-child.focus-visible ~ .tabbed-labels > :first-child, -.md-typeset .tabbed-set > input:nth-child(10).focus-visible ~ .tabbed-labels > :nth-child(10), -.md-typeset .tabbed-set > input:nth-child(11).focus-visible ~ .tabbed-labels > :nth-child(11), -.md-typeset .tabbed-set > input:nth-child(12).focus-visible ~ .tabbed-labels > :nth-child(12), -.md-typeset .tabbed-set > input:nth-child(13).focus-visible ~ .tabbed-labels > :nth-child(13), -.md-typeset .tabbed-set > input:nth-child(14).focus-visible ~ .tabbed-labels > :nth-child(14), -.md-typeset .tabbed-set > input:nth-child(15).focus-visible ~ .tabbed-labels > :nth-child(15), -.md-typeset .tabbed-set > input:nth-child(16).focus-visible ~ .tabbed-labels > :nth-child(16), -.md-typeset .tabbed-set > input:nth-child(17).focus-visible ~ .tabbed-labels > :nth-child(17), -.md-typeset .tabbed-set > input:nth-child(18).focus-visible ~ .tabbed-labels > :nth-child(18), -.md-typeset .tabbed-set > input:nth-child(19).focus-visible ~ .tabbed-labels > :nth-child(19), -.md-typeset .tabbed-set > input:nth-child(2).focus-visible ~ .tabbed-labels > :nth-child(2), -.md-typeset .tabbed-set > input:nth-child(20).focus-visible ~ .tabbed-labels > :nth-child(20), -.md-typeset .tabbed-set > input:nth-child(3).focus-visible ~ .tabbed-labels > :nth-child(3), -.md-typeset .tabbed-set > input:nth-child(4).focus-visible ~ .tabbed-labels > :nth-child(4), -.md-typeset .tabbed-set > input:nth-child(5).focus-visible ~ .tabbed-labels > :nth-child(5), -.md-typeset .tabbed-set > input:nth-child(6).focus-visible ~ .tabbed-labels > :nth-child(6), -.md-typeset .tabbed-set > input:nth-child(7).focus-visible ~ .tabbed-labels > :nth-child(7), -.md-typeset .tabbed-set > input:nth-child(8).focus-visible ~ .tabbed-labels > :nth-child(8), -.md-typeset .tabbed-set > input:nth-child(9).focus-visible ~ .tabbed-labels > :nth-child(9) { - background-color: var(--darkreader-bg--md-accent-fg-color--transparent); -} -:root { - --darkreader-bgimg--md-tasklist-icon: url("data:image/svg+xml;charset=utf-8,"); - --darkreader-bgimg--md-tasklist-icon--checked: url("data:image/svg+xml;charset=utf-8,"); -} -.md-typeset .task-list-indicator::before { - background-color: var(--darkreader-bg--md-default-fg-color--lightest); -} -.md-typeset [type="checkbox"]:checked + .task-list-indicator::before { - background-color: rgb(43, 255, 152); -} -:root > * { - --md-mermaid-font-family: var(--md-text-font-family), - sans-serif; - --md-mermaid-edge-color: var(--md-code-fg-color); - --md-mermaid-node-bg-color: var(--md-accent-fg-color--transparent); - --md-mermaid-node-fg-color: var(--md-accent-fg-color); - --md-mermaid-label-bg-color: var(--md-default-bg-color); - --md-mermaid-label-fg-color: var(--md-code-fg-color); -} /* Override Style */ .vimvixen-hint { diff --git a/resources/dmmd-preview.js b/resources/dmmd-preview.js index 4c16839..2450ea8 100644 --- a/resources/dmmd-preview.js +++ b/resources/dmmd-preview.js @@ -23,6 +23,12 @@ $(function () { csrfmiddlewaretoken: $.cookie('csrftoken') }, function (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'); var $jax = $content.find('.require-mathjax-support'); diff --git a/resources/pagedown_math.js b/resources/pagedown_math.js index 4ebf484..febfd8d 100644 --- a/resources/pagedown_math.js +++ b/resources/pagedown_math.js @@ -12,6 +12,6 @@ function mathjax_pagedown($) { window.mathjax_pagedown = mathjax_pagedown; -$(window).load(function () { +$(function () { (mathjax_pagedown)('$' in window ? $ : django.jQuery); }); \ No newline at end of file diff --git a/resources/problem.scss b/resources/problem.scss index 8528761..6122e8c 100644 --- a/resources/problem.scss +++ b/resources/problem.scss @@ -369,7 +369,6 @@ ul.problem-list { margin-left: 2.5%; padding-bottom: 1em; border-radius: 5px; - margin-bottom: 1em; display: flex; justify-content: space-between; } diff --git a/resources/submission.scss b/resources/submission.scss index cf8f279..4288d5b 100644 --- a/resources/submission.scss +++ b/resources/submission.scss @@ -5,7 +5,7 @@ } #submissions-table { - background: rgba(0, 0, 0, .01); + background: white; } .submission-row { @@ -212,8 +212,8 @@ label[for="language"], label[for="status"] { } // .batch-cases .case-row td b { - // font-weight: 500; - // } + // font-weight: 500; + // } .case-info { margin: 0; @@ -252,7 +252,7 @@ label[for="language"], label[for="status"] { td { padding: 0.6em 0.8em; width: 18.75%; - + } .case-row td:nth-child(2) { @@ -288,7 +288,7 @@ label[for="language"], label[for="status"] { } .overall-result-WA { - background: linear-gradient(45deg, yellow, red); + background: linear-gradient(45deg, yellow, red); } .overall-result-TLE { diff --git a/resources/table.scss b/resources/table.scss index 08c714e..c72149d 100644 --- a/resources/table.scss +++ b/resources/table.scss @@ -12,6 +12,10 @@ background: #f7f7f7; } + &.striped tr:nth-child(odd) { + background: white; + } + td:first-child { border-color: $border_gray; border-width: 1px 1px 0 1px; diff --git a/resources/ticket.scss b/resources/ticket.scss index bba44a7..d7f9143 100644 --- a/resources/ticket.scss +++ b/resources/ticket.scss @@ -1,4 +1,5 @@ .ticket-container { + display: flex; #content > h2:first-child small { color: #999; font-size: 0.9em; diff --git a/resources/widgets.scss b/resources/widgets.scss index dec1bd6..a8b0d2d 100644 --- a/resources/widgets.scss +++ b/resources/widgets.scss @@ -495,6 +495,7 @@ ul.select2-selection__rendered { border-top: none; margin: 0 -5px; padding: 1px 0.5em 3px; + background: white; &.sidebox-table { border: none; @@ -510,7 +511,8 @@ ul.select2-selection__rendered { border-top-left-radius: $widget_border_radius; border-top-right-radius: $widget_border_radius; 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 { @@ -591,7 +593,7 @@ ul.select2-selection__rendered { .control-button { background: lightgray; color: black !important; - border: 0; + border: 0; } .control-button:hover { @@ -701,7 +703,7 @@ ul.select2-selection__rendered { } #login-panel { - + display: inline-block; position: relative; margin: 5em auto auto -10em; diff --git a/templates/about/about.html b/templates/about/about.html index 1fc305e..7924c6e 100644 --- a/templates/about/about.html +++ b/templates/about/about.html @@ -1,13 +1,19 @@ {% extends "base.html" %} {% block body %} + {% if request.organization %} + {% cache 3600 'organization_html' request.organization.id MATH_ENGINE %} + {{ request.organization.about|markdown|reference|str|safe }} + {% endcache %} + {% else %}

- LQDOJ (Le Quy Don Online Judge) 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ở DMOJ. Đượ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 trường THPT chuyên Lê Quý Đôn (TP Đà Nẵng), 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 Github repo chính thức. Mọi ý kiến đóng góp và thắc mắc xin gửi về: -

+ + + {% endif %} {% endblock %} \ No newline at end of file diff --git a/templates/about/custom-checker-sample.html b/templates/about/custom-checker-sample.html index 486e8bd..b6497df 100644 --- a/templates/about/custom-checker-sample.html +++ b/templates/about/custom-checker-sample.html @@ -1,62 +1,62 @@ {% extends "common-content.html" %} {% block description %} - -
-

1. Custom checker (PY)

-
-

- Đâ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: -

+ +
+

1. Custom checker (PY)

+
+

+ Đâ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: +

-{{ -""" -def check(process_output, judge_output, **kwargs): + {{ + """ + def check(process_output, judge_output, **kwargs): # return True/False -"""|highlight('py')}} - -

- Trong đó, **kwargs có thể chứa các biến sau: -

-
    -
  • process_output: output
  • -
  • judge_output: đáp án
  • -
  • submission_source: Code bài nộp
  • -
  • judge_input: input
  • -
  • point_value: điểm của test đang chấm
  • -
  • case_position: thứ tự của test
  • -
  • submission_language: ngôn ngữ của bài nộp
  • -
  • execution_time: thời gian chạy
  • -
-

Return:

-
    -
  • Cách 1: Trả về True/False
  • -
  • Cách 2: Trả về một object CheckerResult có thể được gọi như sau
    CheckerResult(case_passed_bool, points_awarded, feedback='')
  • -
+ """|highlight('py')}} -

Ví dụ:

-

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. -

-{{ -""" -from dmoj.result import CheckerResult +

+ Trong đó, **kwargs có thể chứa các biến sau: +

+
    +
  • process_output: output
  • +
  • judge_output: đáp án
  • +
  • submission_source: Code bài nộp
  • +
  • judge_input: input
  • +
  • point_value: điểm của test đang chấm
  • +
  • case_position: thứ tự của test
  • +
  • submission_language: ngôn ngữ của bài nộp
  • +
  • execution_time: thời gian chạy
  • +
+

Return:

+
    +
  • Cách 1: Trả về True/False
  • +
  • Cách 2: Trả về một object CheckerResult có thể được gọi như sau
    CheckerResult(case_passed_bool, points_awarded, feedback='')
  • +
+ +

Ví dụ:

+

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. +

+ {{ + """ + from dmoj.result import CheckerResult -def wa(feedback): + def wa(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 input_arr = judge_input.split() assert(len(input_arr) == 1) @@ -66,143 +66,143 @@ def check(process_output, judge_output, judge_input, **kwargs): output_arr = process_output.split() if (len(output_arr) != 2): - return wa('Wrong output format') + return wa('Wrong output format') try: - a, b = int(output_arr[0]), int(output_arr[1]) + a, b = int(output_arr[0]), int(output_arr[1]) except: - return wa('Wrong output format') + return wa('Wrong output format') if (n == a + b): - return True + return True return wa('a + b != n') -"""| highlight('py')}} -
-
-

2. Custom validator (CPP)

-
-

- Để 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ự input_file, output_file, ans_file tương ứng với các file input, output, đáp án. -

-

- Để test chương trình trên máy tính, có thể dùng lệnh như sau (Windows): -

+    """| highlight('py')}}
+  
+
+

2. Custom validator (CPP)

+
+

+ Để 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ự input_file, output_file, ans_file tương ứng với các file input, output, đáp án. +

+

+ Để test chương trình trên máy tính, có thể dùng lệnh như sau (Windows): +

 main.exe [input_file] [output_file] [ans_file]
- hoặc thay bằng ./main trên Linux/MacOS. -

-

Return:

-

- Chương trình trả về giá trị: -

    -
  • 0 nếu AC (100% điểm)
  • -
  • 1 nếu WA (0 điểm)
  • -
  • 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.
  • -
- 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) -

+ hoặc thay bằng ./main trên Linux/MacOS. +

+

Return:

+

+ Chương trình trả về giá trị: +

    +
  • 0 nếu AC (100% điểm)
  • +
  • 1 nếu WA (0 điểm)
  • +
  • 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.
  • +
+ 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) +

-

Ví dụ:

-

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.

-

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.

-{{ -""" -#include -using namespace std; +

Ví dụ:

+

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.

+

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.

+ {{ + """ + #include + using namespace std; -int main(int argc, char** argv) { - ifstream inp(argv[1]); - ifstream out(argv[2]); - ifstream ans(argv[3]); + int main(int argc, char** argv) { + ifstream inp(argv[1]); + ifstream out(argv[2]); + ifstream ans(argv[3]); - int n, a, b, c, d; - - inp >> n; - out >> a >> b; - ans >> c >> d; + int n, a, b, c, d; - if (a + b == c + d) { - cout << a << \" + \" << b << \" = \" << c << \" + \" << d << endl; - - if (a >= 0 && b >= 0) { - return 0; // AC - } - else { - cerr << 0.5; - return 2; // PARTIAL - } - } - else { - cout << \"a + b = \" << a + b << \" != \" << n << endl; - return 1; // WA - } -} -""" | highlight('cpp')}} + inp >> n; + out >> a >> b; + ans >> c >> d; + + if (a + b == c + d) { + cout << a << \" + \" << b << \" = \" << c << \" + \" << d << endl; + + if (a >= 0 && b >= 0) { + return 0; // AC + } + else { + cerr << 0.5; + return 2; // PARTIAL + } + } + else { + cout << \"a + b = \" << a + b << \" != \" << n << endl; + return 1; // WA + } + } + """ | highlight('cpp')}}
-

3. Interactive (CPP)

-
-

+

3. Interactive (CPP)

+
+

Để 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 input_file answer_file tương ứng file input và đáp án (nếu cần thiết). -

-

- Để 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): +

 main.exe [input_file] [answer_file]
- hoặc thay bằng ./main trên Linux/MacOS. -

-

Return:

-

- Chương trình trả về giá trị: -

    -
  • 0 nếu AC (100% điểm)
  • -
  • 1 nếu WA (0 điểm)
  • -
  • 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.
  • -
- Thông tin được in ra trong stderr (bằng cerr) sẽ là feedback hiển thị cho người dùng. -

+ hoặc thay bằng ./main trên Linux/MacOS. +

+

Return:

+

+ Chương trình trả về giá trị: +

    +
  • 0 nếu AC (100% điểm)
  • +
  • 1 nếu WA (0 điểm)
  • +
  • 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.
  • +
+ Thông tin được in ra trong stderr (bằng cerr) sẽ là feedback hiển thị cho người dùng. +

-

Ví dụ:

-

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.

-{{ -""" -#include -using namespace std; +

Ví dụ:

+

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.

+ {{ + """ + #include + using namespace std; -void quit(string reason) { - cerr << reason << endl; - exit(1); -} + void quit(string reason) { + cerr << reason << endl; + exit(1); + } -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 (guess < 1 || guess > 2e9) exit(1); -} + 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 (guess < 1 || guess > 2e9) exit(1); + } -int main(int argc, char *argv[]) { - ifstream inp(argv[1]); - int N, guesses = 0; - long long guess; - inp >> N; + int main(int argc, char *argv[]) { + ifstream inp(argv[1]); + int N, guesses = 0; + long long guess; + inp >> N; - while (guess != N && guesses <= 31) { + while (guess != N && guesses <= 31) { read(guess); if (guess == N) { - cout << \"HOLA\" << endl; + cout << \"HOLA\" << endl; } else if (guess > N) { - cout << \"SMALLER\" << endl; + cout << \"SMALLER\" << endl; } else { - cout << \"BIGGER\" << endl; + cout << \"BIGGER\" << endl; } guesses++; - } - cerr << \"Number of used guesses: \" << guesses << endl; - if (guesses <= 31) + } + cerr << \"Number of used guesses: \" << guesses << endl; + if (guesses <= 31) return 0; // AC - else { + else { cerr << \"Used too many guesses\" << endl; return 1; // WA - } -} -""" | highlight('cpp')}} -
+ } + } + """ | highlight('cpp')}} +
{% endblock %} \ No newline at end of file diff --git a/templates/actionbar/list.html b/templates/actionbar/list.html index d5ae2b1..0bb2ef1 100644 --- a/templates/actionbar/list.html +++ b/templates/actionbar/list.html @@ -1,51 +1,62 @@ {% set logged_in = request.user.is_authenticated %} {% 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 include_hr %}
{% endif %} -
+ {% if include_hr %} +
+ {% endif %} +
- - - - + + + + {% if not hide_actionbar_comment %} - + - - {{_("Comment")}} + + + {{_("Comment")}} + + {% if comment_list.count() %} + + ({{comment_list.count()}}) + + {% endif %} - + {% endif %} - - - {{_("Bookmark")}} - + + + {{_("Bookmark")}} + - - - - {{_("Share")}} - + + + + {{_("Share")}} + {% if actionbar_report_url %} - - - - {{_("Report")}} - - + + + + {{_("Report")}} + + {% endif %} -
+
{% endif %} \ No newline at end of file diff --git a/templates/actionbar/media-css.html b/templates/actionbar/media-css.html deleted file mode 100644 index 7e56f59..0000000 --- a/templates/actionbar/media-css.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/templates/actionbar/media-js.html b/templates/actionbar/media-js.html index 1e3b083..99ef8c5 100644 --- a/templates/actionbar/media-js.html +++ b/templates/actionbar/media-js.html @@ -1,124 +1,126 @@ {% compress js %} - + } + + 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(); + }) + }); + {% endcompress %} \ No newline at end of file diff --git a/templates/admin/auth/user/change_form.html b/templates/admin/auth/user/change_form.html index 207ab84..d5e581e 100644 --- a/templates/admin/auth/user/change_form.html +++ b/templates/admin/auth/user/change_form.html @@ -2,19 +2,19 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original %} - - {% endif %} + {% if original %} + + {% endif %} {% endblock %} diff --git a/templates/admin/judge/contest/change_form.html b/templates/admin/judge/contest/change_form.html index 7f540f6..5c94aa4 100644 --- a/templates/admin/judge/contest/change_form.html +++ b/templates/admin/judge/contest/change_form.html @@ -2,22 +2,22 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original and original.is_rated and original.ended and perms.judge.contest_rating %} - - {% endif %} + {% if original and original.is_rated and original.ended and perms.judge.contest_rating %} + + {% endif %} {% endblock %} diff --git a/templates/admin/judge/contest/change_list.html b/templates/admin/judge/contest/change_list.html index ae409fc..ec5c05d 100644 --- a/templates/admin/judge/contest/change_list.html +++ b/templates/admin/judge/contest/change_list.html @@ -2,12 +2,12 @@ {% load i18n %} {% block object-tools-items %} - {{ block.super }} - {% if not is_popup and perms.judge.contest_rating %} -
  • - - {% trans "Rate all ratable contests" %} - -
  • - {% endif %} + {{ block.super }} + {% if not is_popup and perms.judge.contest_rating %} +
  • + + {% trans "Rate all ratable contests" %} + +
  • + {% endif %} {% endblock %} diff --git a/templates/admin/judge/judge/change_form.html b/templates/admin/judge/judge/change_form.html index f477768..754c31d 100644 --- a/templates/admin/judge/judge/change_form.html +++ b/templates/admin/judge/judge/change_form.html @@ -2,25 +2,25 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original %} - - - {% endif %} + {% if original %} + + + {% endif %} {% endblock %} diff --git a/templates/admin/judge/problem/change_form.html b/templates/admin/judge/problem/change_form.html index 8731e7f..c5792c1 100644 --- a/templates/admin/judge/problem/change_form.html +++ b/templates/admin/judge/problem/change_form.html @@ -2,24 +2,24 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original %} - - {% endif %} + {% if original %} + + {% endif %} {% endblock %} diff --git a/templates/admin/judge/profile/change_form.html b/templates/admin/judge/profile/change_form.html index 9372383..0ac5f56 100644 --- a/templates/admin/judge/profile/change_form.html +++ b/templates/admin/judge/profile/change_form.html @@ -2,19 +2,19 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original %} - - {% endif %} + {% if original %} + + {% endif %} {% endblock %} diff --git a/templates/admin/judge/submission/change_form.html b/templates/admin/judge/submission/change_form.html index 6f3c5b7..dcb49c8 100644 --- a/templates/admin/judge/submission/change_form.html +++ b/templates/admin/judge/submission/change_form.html @@ -2,19 +2,19 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original %} - - {% endif %} + {% if original %} + + {% endif %} {% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 34b6cc9..f69a8a8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,13 +1,13 @@ - + {% block title %}{{ title }} - {{ SITE_LONG_NAME }}{% endblock %} {% if misc_config.meta_keywords %} - + {% endif %} {% if meta_description %} - + {% endif %} @@ -30,14 +30,14 @@ {# Chrome 39 for Android colour #} {% if og_image %} - + {% endif %} {% block og_title %}{% endblock %} + content="{{ DMOJ_SCHEME }}://{{ DMOJ_CANONICAL|default(site.domain) }}{{ request.get_full_path() }}"> {% if meta_description %} - + {% endif %} {% block meta %}{% endblock %} {% if not INLINE_FONTAWESOME %} - + {% endif %} {% compress css %} - - {% if PYGMENT_THEME %} - - {% endif %}{% if INLINE_FONTAWESOME %} + + {% if PYGMENT_THEME %} + + {% endif %}{% if INLINE_FONTAWESOME %} {% endif %} - - - - + + + + {% endcompress %} + href="{{ DMOJ_SCHEME }}://{{ DMOJ_CANONICAL|default(site.domain) }}{{ request.get_full_path() }}"> {% if request.user.is_impersonate %} - + {% endif %} {% block media %}{% endblock %} {% if use_darkmode %} - {% compress css %} - - - {% endcompress %} + {% compress css %} + + + {% endcompress %} {% endif %} {% if not INLINE_JQUERY %} - + {% endif %} {% compress js %} - - {% if INLINE_JQUERY %} - - {% endif %} - - - - - - {% include "extra_js.html" %} - - - + + {% if INLINE_JQUERY %} + + {% endif %} + + + + + + + {% include "extra_js.html" %} + + + {% endcompress %} {% block js_media %}{% endblock %} {% if request.in_contest %} - + + 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; + }) + }); + {% endif %} {% if request.user.is_authenticated %} - + {% else %} - + {% endif %} {% if misc_config.analytics %} - {{ misc_config.analytics|safe }} + {{ misc_config.analytics|safe }} {% endif %} {# Don't run userscript since it may be malicious #} {% if request.user.is_authenticated and request.profile.user_script and not request.user.is_impersonate %} - + {% endif %} - - - - + + + + - - - + {% if request.in_contest %} + -{% endif %} -
    -
    + {% endif %} +
    + -
    -
    + +
    +
    {% block title_row %} -

    - {% block content_title %} - {% if content_title %}{{ content_title }}{% else %}{{ title }}{% endif %} - {% endblock %} -

    +

    + {% block content_title %} + {% if content_title %}{{ content_title }}{% else %}{{ title }}{% endif %} + {% endblock %} +

    {% endblock %} {% block header %}{% endblock %} {% block title_ruler %} -
    +
    {% endblock %}
    {% block body %}{% endblock %}
    -
    +
    - {% if i18n_config.announcement %} + {% if i18n_config.announcement %}
    {{ i18n_config.announcement|safe }}
    - {% endif %} + {% endif %} - {% block bodyend %}{% endblock %} - {% block footer %} -
    - + {% block bodyend %}{% endblock %} + {% block footer %} +
    +
    proudly powered by DMOJ | developed by LQDJudge team | {% if i18n_config.footer %} - {{ i18n_config.footer|safe }} | + {{ i18n_config.footer|safe }} | {% endif %}
    - {% csrf_token %} - - + -
    -
    -
    - {% endblock %} -
    + + +
    + + {% endblock %} +
    - + diff --git a/templates/blog/blog.html b/templates/blog/blog.html index 213a291..59d4e3d 100644 --- a/templates/blog/blog.html +++ b/templates/blog/blog.html @@ -1,13 +1,12 @@ {% extends "base.html" %} {% block js_media %} - {% include "comments/media-js.html" %} - {% include "actionbar/media-js.html" %} + {% include "comments/media-js.html" %} + {% include "actionbar/media-js.html" %} {% endblock %} {% block media %} - {% include "comments/media-css.html" %} - {% include "actionbar/media-css.html" %} + {% include "comments/media-css.html" %} {% endblock %} {% block title_row %} @@ -17,40 +16,41 @@ {% endblock %} {% block body %} -
    -
    {{ title }}
    -
    - {% with authors=post.authors.all() %} - {% if authors %} - - {% endif %} - {% endwith %} - - {% trans time=post.publish_on|date(_("N j, Y, g:i a")) %} posted on {{ time }}{% endtrans %} - - {% if post.is_editable_by(request.user) %} - [{{ _('Edit') }}] - {% elif valid_user_to_show_edit %} - {% for org in valid_org_to_show_edit %} - [{{ _('Edit in') }} {{org.slug}}] - {% endfor %} - {% endif %} -
    -
    - {% cache 86400 'post_content' post.id MATH_ENGINE %} - {{ post.content|markdown|reference|str|safe}} - {% endcache %} -
    - {% include "actionbar/list.html" %} +
    +
    {{ title }}
    +
    + {% with authors=post.authors.all() %} + {% if authors %} + + {% endif %} + {% endwith %} + + {% trans time=post.publish_on|date(_("N j, Y, g:i a")) %} posted on {{ time }}{% endtrans %} + + {% if post.is_editable_by(request.user) %} + [{{ _('Edit') }}] + {% endif %} + {% if valid_user_to_show_edit %} + {% for org in valid_org_to_show_edit %} + [{{ _('Edit in') }} {{org.slug}}] + {% endfor %} + {% endif %}
    -
    - {% include "comments/list.html" %} +
    + {% cache 86400 'post_content' post.id MATH_ENGINE %} + {{ post.content|markdown|reference|str|safe}} + {% endcache %} +
    + {% include "actionbar/list.html" %} +
    +
    + {% include "comments/list.html" %} {% endblock %} {% block bodyend %} - {{ super() }} - {% if REQUIRE_JAX %} - {% include "mathjax-load.html" %} - {% endif %} - {% include "comments/math.html" %} + {{ super() }} + {% if REQUIRE_JAX %} + {% include "mathjax-load.html" %} + {% endif %} + {% include "comments/math.html" %} {% endblock %} \ No newline at end of file diff --git a/templates/blog/content.html b/templates/blog/content.html index 506271e..4124729 100644 --- a/templates/blog/content.html +++ b/templates/blog/content.html @@ -1,52 +1,58 @@ -
    +{% for post in posts%} +
    - - {% with authors=post.authors.all() %} - {%- if authors -%} - - - {%- endif -%} - {% endwith %} - • - {{ relative_time(post.publish_on, abs=_('on {time}'), rel=_('{time}')) -}} - {%- if post.sticky %} • - {% endif -%} - {% if post.is_organization_private and show_organization_private_icon %} - • - - {% for org in post.organizations.all() %} - - - {{ org.name }} - - - {% endfor %} - - {% endif %} - - - - - - {{- post_comment_counts[post.id] or 0 -}} - - - + + {% with authors=post.authors.all() %} + {%- if authors -%} + + + {%- endif -%} + {% endwith %} + • + {{ relative_time(post.publish_on, abs=_('on {time}'), rel=_('{time}')) -}} + {%- if post.sticky %} • + {% endif -%} + {% if post.is_organization_private and show_organization_private_icon %} + • + + {% for org in post.organizations.all() %} + + + {{ org.name }} + + + {% endfor %} + + {% endif %} + + + + + + {{- post.comments.filter(hidden=False).count() or 0 -}} + + +

    - {{ post.title }} + {{ post.title }}

    -
    - {% cache 86400 'post_summary' post.id %} - {{ post.summary|default(post.content, true)|markdown(lazy_load=True)|reference|str|safe }} - {% endcache %} -
    - {% set pagevote = post.pagevote %} - {% 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" %} +
    + {% cache 86400 'post_summary' post.id %} + {{ post.summary|default(post.content, true)|markdown(lazy_load=True)|reference|str|safe }} + {% endcache %} +
    +
    {{_("...More")}}
    -
    \ No newline at end of file +
    + {% 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" %} +
    +
    +{% endfor %} +{% include "feed/has_next.html" %} \ No newline at end of file diff --git a/templates/blog/dashboard.html b/templates/blog/dashboard.html index 7ec50e4..b34d406 100644 --- a/templates/blog/dashboard.html +++ b/templates/blog/dashboard.html @@ -1,34 +1,34 @@